Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ endpoints, renders only MoonBit source/project files, and never accepts,
stores, or sends a personal access token. Anonymous GitHub API rate limits
therefore apply.

Submitting a GitHub URL updates the browser to a static-host-friendly share
route:

```text
https://{playground-host}/{base}/#/owner/repo/commit/sha
```

Opening that URL restores and loads the same commit automatically. The result
page also exposes the full URL in a read-only field with a one-click copy
button. Hash routing keeps shared links working on GitHub Pages without a
server-side rewrite rule.

Run the live development server from the Warren app directory:

```sh
Expand Down
51 changes: 48 additions & 3 deletions playground/main/logic_wbtest.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,45 @@ test "parse supported GitHub commit URL forms" {
assert_eq(pr.sha, "0123456789abcdef")
}

///|
test "shared playground routes round-trip to canonical GitHub commit URLs" {
let reference = CommitRef::{
owner: "moonbit-community",
repo: "ldiff",
sha: "aBcDeF1",
}
assert_eq(
playground_route(reference),
"#/moonbit-community/ldiff/commit/aBcDeF1",
)
assert_eq(
canonical_github_url(reference),
"https://github.com/moonbit-community/ldiff/commit/aBcDeF1",
)
guard reference_from_playground_url(
"https://example.github.io/ldiff/#/moonbit-community/ldiff/commit/aBcDeF1",
)
is Ok(Some(parsed)) else {
fail("expected the shared route to parse")
}
assert_true(parsed == reference)
assert_true(
reference_from_playground_url("https://example.com/ldiff/") is Ok(None),
)
assert_true(
reference_from_playground_url(
"https://example.com/ldiff/#/moonbit-community/ldiff/pull/1",
)
is Err(_),
)
assert_true(
reference_from_playground_url(
"https://example.com/ldiff/#/bad owner/ldiff/commit/abcdef1",
)
is Err(_),
)
}

///|
test "reject unsupported or malformed URLs" {
for
Expand Down Expand Up @@ -132,7 +171,7 @@ test "pagination appends file metadata and stops at GitHub's 30-page cap" {
assert_false(should_fetch_next_page(1, 99))
assert_false(should_fetch_next_page(30, 100))
assert_true(
commit_api_url(CommitRef::{ owner: "a", repo: "b", sha: "abcdef1" }, 30).has_suffix(
commit_api_url({ owner: "a", repo: "b", sha: "abcdef1" }, 30).has_suffix(
"?per_page=100&page=30",
),
)
Expand Down Expand Up @@ -197,11 +236,14 @@ test "stale commit responses are discarded by generation" {
generation: 2,
view_mode: Split,
phase: Idle,
route: None,
share_url: None,
copy_status: CopyReady,
}
let fixture = decode_commit(
read_fixture("playground/main/fixtures/root_commit.json"),
)
let emit : @rabbita.Emit[Msg] = @cmd.Emit(_ => @rabbita.none)
let emit : @rabbita.Emit[Msg] = Emit(_ => @rabbita.none)
let (after, _) = update(model, CommitPageLoaded(1, 1, Ok(fixture)), emit)
assert_true(after == model)
}
Expand All @@ -215,7 +257,7 @@ test "only the first twenty matching files auto-load and stale source replies ar
for index in 0..<21 {
files.push(fixture_file("src/file_\{index}.mbt", "added"))
}
let emit : @rabbita.Emit[Msg] = @cmd.Emit(_ => @rabbita.none)
let emit : @rabbita.Emit[Msg] = Emit(_ => @rabbita.none)
let reference = CommitRef::{
owner: "example",
repo: "root",
Expand All @@ -235,6 +277,9 @@ test "only the first twenty matching files auto-load and stale source replies ar
generation: 7,
view_mode: Split,
phase: Showing(view),
route: Some(reference),
share_url: None,
copy_status: CopyReady,
}
let (after, _) = update(model, SourceLoaded(6, 0, New, Ok("stale")), emit)
assert_true(after == model)
Expand Down
9 changes: 8 additions & 1 deletion playground/main/main.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,12 @@

///|
fn main {
@rabbita.elmish(model=initial_model(), view=app_view, update~).mount("app")
@rabbita.new(() => {
let (model, emit) = @rabbita.create_state_with_init(
init=app_init,
update~,
subscriptions=app_subscriptions,
)
model.map(model => app_view(model, emit))
}).mount("app")
}
3 changes: 3 additions & 0 deletions playground/main/moon.pkg
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import {
"moonbit-community/ldiff",
"moonbit-community/rabbita",
"moonbit-community/rabbita/clipboard",
"moonbit-community/rabbita/html",
"moonbit-community/rabbita/http",
"moonbit-community/rabbita/nav",
"moonbit-community/rabbita/sub",
"moonbit-community/rabbita/url",
"moonbitlang/core/encoding/utf8",
"moonbitlang/core/json",
Expand Down
58 changes: 58 additions & 0 deletions playground/main/share_url.mbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0

///|
fn canonical_github_url(reference : CommitRef) -> String {
"https://github.com/\{reference.owner}/\{reference.repo}/commit/\{reference.sha}"
}

///|
fn playground_route(reference : CommitRef) -> String {
"#/\{reference.owner}/\{reference.repo}/commit/\{reference.sha}"
}

///|
fn reference_from_route(fragment : String?) -> Result[CommitRef?, String] {
guard fragment is Some(value) && !value.is_empty() else { return Ok(None) }
let parts : Array[String] = value
.split("/")
.map(StringView::to_owned)
.collect()
let reference = match parts {
["", owner, repo, "commit", sha] | [owner, repo, "commit", sha] =>
CommitRef::{ owner, repo, sha }
_ => return Err("This shared playground link is not valid.")
}
if !valid_owner(reference.owner) ||
!valid_repo(reference.repo) ||
!valid_sha(reference.sha) {
return Err("This shared playground link is not valid.")
}
Ok(Some(reference))
}

///|
fn reference_from_playground_url(input : String) -> Result[CommitRef?, String] {
let parsed = try @url.parse(input) catch {
_ => return Err("This shared playground link is not valid.")
} noraise {
parsed => parsed
}
reference_from_route(parsed.fragment)
}

///|
extern "js" fn browser_current_url() -> String = "() => window.location.href"

///|
extern "js" fn browser_base_url() -> String = "() => window.location.origin + window.location.pathname"

///|
fn playground_share_url(reference : CommitRef) -> String {
"\{browser_base_url()}\{playground_route(reference)}"
}
Loading
Loading