rshow 0.0.6 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.js +15002 -668
- package/package.json +2 -1
- package/skills/rshow/SKILL.md +13 -4
- package/skills/rshow/reference/cli.md +8 -6
- package/skills/rshow/reference/dashboard.md +5 -5
- package/skills/rshow/reference/page-contract.md +2 -1
- package/skills/rshow/reference/pages.md +7 -7
- package/skills/rshow/reference/poster.md +5 -5
- package/skills/rshow/reference/project-page.md +5 -5
- package/skills/rshow/reference/reports.md +10 -6
- package/skills/rshow/reference/sketch.md +5 -5
- package/skills/rshow/reference/terminal.md +5 -5
- package/skills/rshow-slides/SKILL.md +11 -6
- package/skills/rshow-slides/reference/layouts.md +15 -0
- package/themes/default/deck.js +86 -4
- package/themes/default/example/index.html +19 -2
- package/themes/default/example/report.html +17 -10
- package/themes/default/fonts/katex/KaTeX_AMS-Regular.woff2 +0 -0
- package/themes/default/fonts/katex/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/themes/default/fonts/katex/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/themes/default/fonts/katex/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/themes/default/fonts/katex/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/themes/default/fonts/katex/KaTeX_Main-Bold.woff2 +0 -0
- package/themes/default/fonts/katex/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/themes/default/fonts/katex/KaTeX_Main-Italic.woff2 +0 -0
- package/themes/default/fonts/katex/KaTeX_Main-Regular.woff2 +0 -0
- package/themes/default/fonts/katex/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/themes/default/fonts/katex/KaTeX_Math-Italic.woff2 +0 -0
- package/themes/default/fonts/katex/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/themes/default/fonts/katex/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/themes/default/fonts/katex/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/themes/default/fonts/katex/KaTeX_Script-Regular.woff2 +0 -0
- package/themes/default/fonts/katex/KaTeX_Size1-Regular.woff2 +0 -0
- package/themes/default/fonts/katex/KaTeX_Size2-Regular.woff2 +0 -0
- package/themes/default/fonts/katex/KaTeX_Size3-Regular.woff2 +0 -0
- package/themes/default/fonts/katex/KaTeX_Size4-Regular.woff2 +0 -0
- package/themes/default/fonts/katex/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/themes/default/fonts/katex/LICENSE +21 -0
- package/themes/default/math.css +46 -0
- package/themes/default/math.js +58 -0
- package/themes/default/report.css +15 -6
- package/themes/default/theme.json +7 -0
- package/worker/index.js +118 -46
- package/worker/migrations/0005_version_counters.sql +3 -0
|
@@ -388,24 +388,30 @@ tbody tr:last-child td {
|
|
|
388
388
|
font-variant-numeric: tabular-nums;
|
|
389
389
|
}
|
|
390
390
|
|
|
391
|
-
/* An equation is
|
|
392
|
-
column, so the number lines up with the right edge of the
|
|
393
|
-
|
|
394
|
-
|
|
391
|
+
/* An equation is a .math box in KaTeX's own face with its number at the right
|
|
392
|
+
end of the text column, so the number lines up with the right edge of the
|
|
393
|
+
prose. A tall equation centres its number instead of sticking to the first
|
|
394
|
+
line. The box itself stays in the body face, which is what makes --measure
|
|
395
|
+
resolve to the same width here as in a paragraph. */
|
|
395
396
|
.equation {
|
|
396
397
|
display: grid;
|
|
397
398
|
grid-template-columns: minmax(0, 1fr) auto;
|
|
398
|
-
align-items:
|
|
399
|
+
align-items: center;
|
|
399
400
|
gap: var(--space-05);
|
|
400
401
|
margin: var(--space-06) 0;
|
|
401
402
|
line-height: 1.5;
|
|
402
403
|
}
|
|
403
404
|
|
|
404
|
-
.equation > span:not(.eq-num) {
|
|
405
|
+
.equation > span:not(.eq-num):not(.math) {
|
|
405
406
|
font-family: var(--font-mono);
|
|
406
407
|
font-size: 0.95em;
|
|
407
408
|
}
|
|
408
409
|
|
|
410
|
+
.equation > .math {
|
|
411
|
+
margin: 0;
|
|
412
|
+
min-width: 0;
|
|
413
|
+
}
|
|
414
|
+
|
|
409
415
|
.eq-num {
|
|
410
416
|
color: var(--muted);
|
|
411
417
|
font-variant-numeric: tabular-nums;
|
|
@@ -539,6 +545,9 @@ tbody tr:last-child td {
|
|
|
539
545
|
|
|
540
546
|
.refs li {
|
|
541
547
|
margin: 0 0 var(--space-04);
|
|
548
|
+
/* A cited address is longer than a phone is wide and has no space to break
|
|
549
|
+
at, so it wraps mid path rather than widening the page. */
|
|
550
|
+
overflow-wrap: anywhere;
|
|
542
551
|
}
|
|
543
552
|
|
|
544
553
|
.refs li::marker {
|
|
@@ -55,6 +55,13 @@
|
|
|
55
55
|
"example": "example/tui.html"
|
|
56
56
|
}
|
|
57
57
|
},
|
|
58
|
+
"addons": {
|
|
59
|
+
"math": {
|
|
60
|
+
"css": ["math.css"],
|
|
61
|
+
"js": ["math.js"],
|
|
62
|
+
"example": "example/report.html"
|
|
63
|
+
}
|
|
64
|
+
},
|
|
58
65
|
"tokens": {
|
|
59
66
|
"--font-sans": "\"IBM Plex Sans\", system-ui, -apple-system, \"Segoe UI\", sans-serif",
|
|
60
67
|
"--font-mono": "\"IBM Plex Mono\", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
|
package/worker/index.js
CHANGED
|
@@ -2703,6 +2703,7 @@ var SLUG_PATTERN = /^[a-z0-9][a-z0-9-]{0,62}$/;
|
|
|
2703
2703
|
var TAG_PATTERN = /^[a-z0-9][a-z0-9-]{0,31}$/;
|
|
2704
2704
|
var THEME_NAME_PATTERN = /^[a-z0-9-]{1,40}$/;
|
|
2705
2705
|
var THEME_VERSION_PATTERN = /^([a-z0-9-]{1,40})@([0-9]{1,9})$/;
|
|
2706
|
+
var THEME_LATEST_PATTERN = /^([a-z0-9-]{1,40})@latest$/;
|
|
2706
2707
|
var THEME_JSON_PATTERN = /^([a-z0-9-]{1,40})\.json$/;
|
|
2707
2708
|
var DATA_KEY_PATTERN = /^[A-Za-z0-9_.-]{1,128}$/;
|
|
2708
2709
|
var EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
@@ -3002,13 +3003,13 @@ function jsonError(message2, status) {
|
|
|
3002
3003
|
return json({ error: message2 }, status);
|
|
3003
3004
|
}
|
|
3004
3005
|
__name(jsonError, "jsonError");
|
|
3005
|
-
function redirect(location, status = 301) {
|
|
3006
|
-
return new Response(null, { status, headers: { location, "cache-control":
|
|
3006
|
+
function redirect(location, status = 301, cacheControl = "no-store") {
|
|
3007
|
+
return new Response(null, { status, headers: { location, "cache-control": cacheControl } });
|
|
3007
3008
|
}
|
|
3008
3009
|
__name(redirect, "redirect");
|
|
3009
3010
|
|
|
3010
3011
|
// src/db.ts
|
|
3011
|
-
var ARTIFACT_COLUMNS = "slug, title, owner, mode, pinned, latest, project, created_at, updated_at";
|
|
3012
|
+
var ARTIFACT_COLUMNS = "slug, title, owner, mode, pinned, latest, last_n, version_gen, project, created_at, updated_at";
|
|
3012
3013
|
var PROJECT_COLUMNS = "slug, title, description, owner, created_at, updated_at";
|
|
3013
3014
|
async function getArtifact(env, slug) {
|
|
3014
3015
|
return await env.DB.prepare(`SELECT ${ARTIFACT_COLUMNS} FROM artifacts WHERE slug = ?1`).bind(slug).first();
|
|
@@ -3048,6 +3049,11 @@ async function listVersions(env, slug) {
|
|
|
3048
3049
|
return result.results ?? [];
|
|
3049
3050
|
}
|
|
3050
3051
|
__name(listVersions, "listVersions");
|
|
3052
|
+
async function countVersions(env, slug) {
|
|
3053
|
+
const row = await env.DB.prepare("SELECT COUNT(*) AS total FROM versions WHERE slug = ?1").bind(slug).first();
|
|
3054
|
+
return row?.total ?? 0;
|
|
3055
|
+
}
|
|
3056
|
+
__name(countVersions, "countVersions");
|
|
3051
3057
|
async function sharesForEmail(env, email) {
|
|
3052
3058
|
const result = await env.DB.prepare("SELECT slug, email, role FROM shares WHERE email = ?1").bind(email).all();
|
|
3053
3059
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -3280,12 +3286,12 @@ var MANIFEST_CACHE_CONTROL = "public, max-age=31536000, immutable";
|
|
|
3280
3286
|
var VERSION_CACHE_CONTROL = "public, max-age=31536000, immutable";
|
|
3281
3287
|
var JSON_CONTENT_TYPE = "application/json";
|
|
3282
3288
|
var BLOB_CACHE_CONTROL = "public, max-age=31536000, immutable";
|
|
3283
|
-
var ARTIFACT_COLUMNS2 = "slug, title, owner, mode, pinned, latest, project, created_at, updated_at";
|
|
3289
|
+
var ARTIFACT_COLUMNS2 = "slug, title, owner, mode, pinned, latest, last_n, version_gen, project, created_at, updated_at";
|
|
3284
3290
|
var VERSION_COLUMNS = "slug, n, label, manifest_key, files, bytes, created_by, created_at";
|
|
3285
3291
|
var accessMemory = /* @__PURE__ */ new Map();
|
|
3286
3292
|
var manifestMemory = /* @__PURE__ */ new Map();
|
|
3287
3293
|
var versionMemory = /* @__PURE__ */ new Map();
|
|
3288
|
-
var
|
|
3294
|
+
var versionKeys = /* @__PURE__ */ new Map();
|
|
3289
3295
|
var trackedKeys = /* @__PURE__ */ new Set();
|
|
3290
3296
|
function accessCacheKey(slug) {
|
|
3291
3297
|
return `${CACHE_ORIGIN}/access/${slug}`;
|
|
@@ -3299,12 +3305,12 @@ function blobCacheKey(hash) {
|
|
|
3299
3305
|
return `${CACHE_ORIGIN}/blob/${hash}`;
|
|
3300
3306
|
}
|
|
3301
3307
|
__name(blobCacheKey, "blobCacheKey");
|
|
3302
|
-
function versionCacheKey(slug, n) {
|
|
3303
|
-
return `${CACHE_ORIGIN}/version/${slug}/${n}`;
|
|
3308
|
+
function versionCacheKey(slug, n, gen) {
|
|
3309
|
+
return `${CACHE_ORIGIN}/version/${slug}/${n}/${gen}`;
|
|
3304
3310
|
}
|
|
3305
3311
|
__name(versionCacheKey, "versionCacheKey");
|
|
3306
|
-
function versionMemoryKey(slug, n) {
|
|
3307
|
-
return `${slug}/${n}`;
|
|
3312
|
+
function versionMemoryKey(slug, n, gen) {
|
|
3313
|
+
return `${slug}/${n}/${gen}`;
|
|
3308
3314
|
}
|
|
3309
3315
|
__name(versionMemoryKey, "versionMemoryKey");
|
|
3310
3316
|
function rememberAccess(slug, value, expires) {
|
|
@@ -3327,8 +3333,8 @@ function rememberManifest(key, value) {
|
|
|
3327
3333
|
}
|
|
3328
3334
|
}
|
|
3329
3335
|
__name(rememberManifest, "rememberManifest");
|
|
3330
|
-
function rememberVersion(slug, n, row) {
|
|
3331
|
-
const key = versionMemoryKey(slug, n);
|
|
3336
|
+
function rememberVersion(slug, n, gen, row) {
|
|
3337
|
+
const key = versionMemoryKey(slug, n, gen);
|
|
3332
3338
|
versionMemory.delete(key);
|
|
3333
3339
|
versionMemory.set(key, row);
|
|
3334
3340
|
while (versionMemory.size > VERSION_MAX_ENTRIES) {
|
|
@@ -3336,9 +3342,9 @@ function rememberVersion(slug, n, row) {
|
|
|
3336
3342
|
if (oldest === void 0) break;
|
|
3337
3343
|
versionMemory.delete(oldest);
|
|
3338
3344
|
}
|
|
3339
|
-
const
|
|
3340
|
-
|
|
3341
|
-
|
|
3345
|
+
const keys = versionKeys.get(slug) ?? /* @__PURE__ */ new Set();
|
|
3346
|
+
keys.add(versionCacheKey(slug, n, gen));
|
|
3347
|
+
versionKeys.set(slug, keys);
|
|
3342
3348
|
}
|
|
3343
3349
|
__name(rememberVersion, "rememberVersion");
|
|
3344
3350
|
function decodeVersion(text) {
|
|
@@ -3487,34 +3493,38 @@ async function invalidateProjectAccess(env, project) {
|
|
|
3487
3493
|
}
|
|
3488
3494
|
}
|
|
3489
3495
|
__name(invalidateProjectAccess, "invalidateProjectAccess");
|
|
3490
|
-
async function versionFor(env,
|
|
3491
|
-
const
|
|
3496
|
+
async function versionFor(env, artifact, n) {
|
|
3497
|
+
const gen = artifact.version_gen;
|
|
3498
|
+
const memory = versionMemory.get(versionMemoryKey(artifact.slug, n, gen));
|
|
3492
3499
|
if (memory) {
|
|
3493
|
-
rememberVersion(slug, n, memory);
|
|
3500
|
+
rememberVersion(artifact.slug, n, gen, memory);
|
|
3494
3501
|
return memory;
|
|
3495
3502
|
}
|
|
3496
|
-
const
|
|
3503
|
+
const key = versionCacheKey(artifact.slug, n, gen);
|
|
3504
|
+
const cached = await cacheGet(key);
|
|
3497
3505
|
if (cached) {
|
|
3498
3506
|
const row2 = decodeVersion(await cached.text());
|
|
3499
3507
|
if (row2) {
|
|
3500
|
-
rememberVersion(slug, n, row2);
|
|
3508
|
+
rememberVersion(artifact.slug, n, gen, row2);
|
|
3501
3509
|
return row2;
|
|
3502
3510
|
}
|
|
3503
3511
|
}
|
|
3504
|
-
const row = await getVersion(env, slug, n);
|
|
3512
|
+
const row = await getVersion(env, artifact.slug, n);
|
|
3505
3513
|
if (!row) return null;
|
|
3506
|
-
rememberVersion(slug, n, row);
|
|
3507
|
-
await cachePut(
|
|
3514
|
+
rememberVersion(artifact.slug, n, gen, row);
|
|
3515
|
+
await cachePut(key, JSON.stringify(row), VERSION_CACHE_CONTROL);
|
|
3508
3516
|
return row;
|
|
3509
3517
|
}
|
|
3510
3518
|
__name(versionFor, "versionFor");
|
|
3511
3519
|
async function invalidateVersions(slug) {
|
|
3512
|
-
const
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3520
|
+
const prefix = `${slug}/`;
|
|
3521
|
+
for (const key of [...versionMemory.keys()]) {
|
|
3522
|
+
if (key.startsWith(prefix)) versionMemory.delete(key);
|
|
3523
|
+
}
|
|
3524
|
+
const keys = versionKeys.get(slug);
|
|
3525
|
+
versionKeys.delete(slug);
|
|
3526
|
+
if (!keys) return;
|
|
3527
|
+
for (const key of keys) {
|
|
3518
3528
|
trackedKeys.delete(key);
|
|
3519
3529
|
try {
|
|
3520
3530
|
await caches.default.delete(key);
|
|
@@ -3796,7 +3806,7 @@ async function storeVersion(env, artifact, input2) {
|
|
|
3796
3806
|
env.DB.prepare(
|
|
3797
3807
|
"INSERT INTO versions (slug, n, label, manifest_key, files, bytes, created_by, created_at) SELECT ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8 WHERE (SELECT latest FROM artifacts WHERE slug = ?1) IS ?9"
|
|
3798
3808
|
).bind(artifact.slug, input2.n, input2.label, key, input2.files, input2.bytes, input2.createdBy, input2.now, input2.base),
|
|
3799
|
-
env.DB.prepare("UPDATE artifacts SET latest = ?2, updated_at = ?3 WHERE slug = ?1 AND latest IS ?4").bind(
|
|
3809
|
+
env.DB.prepare("UPDATE artifacts SET latest = ?2, last_n = ?2, updated_at = ?3 WHERE slug = ?1 AND latest IS ?4").bind(
|
|
3800
3810
|
artifact.slug,
|
|
3801
3811
|
input2.n,
|
|
3802
3812
|
input2.now,
|
|
@@ -3841,6 +3851,52 @@ function registerVersions(app) {
|
|
|
3841
3851
|
if (!manifest) return jsonError("no such version", 404);
|
|
3842
3852
|
return json(toVersionDetail(c.env, access.artifact.slug, version, manifest));
|
|
3843
3853
|
});
|
|
3854
|
+
app.delete("/api/artifacts/:slug/versions/:n", async (c) => {
|
|
3855
|
+
const email = c.get("identity").email;
|
|
3856
|
+
const access = await loadAccessible(c.env, c.req.param("slug"), email);
|
|
3857
|
+
if (!access) return jsonError("not found", 404);
|
|
3858
|
+
if (!canManage(access.artifact, access.shares, actorOf(c), access.project)) {
|
|
3859
|
+
return jsonError("owner access is required", 403);
|
|
3860
|
+
}
|
|
3861
|
+
const raw2 = c.req.param("n");
|
|
3862
|
+
if (!/^[1-9][0-9]{0,8}$/.test(raw2)) return jsonError("no such version", 404);
|
|
3863
|
+
const n = Number(raw2);
|
|
3864
|
+
const slug = access.artifact.slug;
|
|
3865
|
+
if (slug.startsWith(THEME_PREFIX) && c.req.query("force") !== "1") {
|
|
3866
|
+
return jsonError("pages link to theme versions; pass force to delete it", 409);
|
|
3867
|
+
}
|
|
3868
|
+
const version = await getVersion(c.env, slug, n);
|
|
3869
|
+
if (!version) return jsonError("no such version", 404);
|
|
3870
|
+
if (access.artifact.pinned === n) {
|
|
3871
|
+
return jsonError(`version ${n} is pinned; pin another version first`, 409);
|
|
3872
|
+
}
|
|
3873
|
+
if (await countVersions(c.env, slug) <= 1) {
|
|
3874
|
+
return jsonError("this is the only version; delete the artifact instead", 409);
|
|
3875
|
+
}
|
|
3876
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3877
|
+
let deletedChanges = 0;
|
|
3878
|
+
try {
|
|
3879
|
+
const [deleted] = await c.env.DB.batch([
|
|
3880
|
+
c.env.DB.prepare(
|
|
3881
|
+
"DELETE FROM versions WHERE slug = ?1 AND n = ?2 AND (SELECT pinned FROM artifacts WHERE slug = ?1) IS NOT ?2 AND (SELECT COUNT(*) FROM versions WHERE slug = ?1) > 1"
|
|
3882
|
+
).bind(slug, n),
|
|
3883
|
+
c.env.DB.prepare(
|
|
3884
|
+
"UPDATE artifacts SET latest = (SELECT MAX(n) FROM versions WHERE slug = ?1), version_gen = version_gen + 1, updated_at = ?2 WHERE slug = ?1"
|
|
3885
|
+
).bind(slug, now)
|
|
3886
|
+
]);
|
|
3887
|
+
deletedChanges = deleted?.meta?.changes ?? 0;
|
|
3888
|
+
} catch {
|
|
3889
|
+
return jsonError("could not delete the version", 500);
|
|
3890
|
+
}
|
|
3891
|
+
if (deletedChanges !== 1) return jsonError("another change came first, retry", 409);
|
|
3892
|
+
await discardManifest(c.env, version.manifest_key);
|
|
3893
|
+
await invalidateVersions(slug);
|
|
3894
|
+
await invalidateAccess(slug);
|
|
3895
|
+
const updated = await getArtifact(c.env, slug);
|
|
3896
|
+
if (!updated) return jsonError("not found", 404);
|
|
3897
|
+
const tags = await getArtifactTags(c.env, slug);
|
|
3898
|
+
return json(toArtifact(c.env, updated, access.role, tags));
|
|
3899
|
+
});
|
|
3844
3900
|
app.post("/api/artifacts/:slug/versions", async (c) => {
|
|
3845
3901
|
const body = await readBody(c);
|
|
3846
3902
|
if (!body.ok) return body.response;
|
|
@@ -3882,7 +3938,7 @@ function registerVersions(app) {
|
|
|
3882
3938
|
}
|
|
3883
3939
|
const label = typeof body.body.label === "string" ? body.body.label.trim().slice(0, 200) : "";
|
|
3884
3940
|
const base = access.artifact.latest;
|
|
3885
|
-
const n = (base ?? 0) + 1;
|
|
3941
|
+
const n = Math.max(access.artifact.last_n, base ?? 0) + 1;
|
|
3886
3942
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3887
3943
|
const stored = await storeVersion(c.env, access.artifact, {
|
|
3888
3944
|
label,
|
|
@@ -3999,6 +4055,8 @@ function registerArtifactCreate(app) {
|
|
|
3999
4055
|
mode: MODE.private,
|
|
4000
4056
|
pinned: null,
|
|
4001
4057
|
latest: null,
|
|
4058
|
+
last_n: 0,
|
|
4059
|
+
version_gen: 0,
|
|
4002
4060
|
project,
|
|
4003
4061
|
created_at: now,
|
|
4004
4062
|
updated_at: now
|
|
@@ -4138,6 +4196,9 @@ function registerArtifactItem(app) {
|
|
|
4138
4196
|
if (!canManage(access.artifact, access.shares, actorOf(c), access.project)) {
|
|
4139
4197
|
return jsonError("owner access is required", 403);
|
|
4140
4198
|
}
|
|
4199
|
+
if (access.artifact.slug.startsWith(THEME_PREFIX) && c.req.query("force") !== "1") {
|
|
4200
|
+
return jsonError("pages link to this theme; pass force to delete it", 409);
|
|
4201
|
+
}
|
|
4141
4202
|
const prefix = `manifests/${access.artifact.slug}/`;
|
|
4142
4203
|
const keys = [];
|
|
4143
4204
|
let cursor;
|
|
@@ -4498,7 +4559,7 @@ __name(registerApi, "registerApi");
|
|
|
4498
4559
|
|
|
4499
4560
|
// src/static.generated.ts
|
|
4500
4561
|
var APP_CSS = '/* IBM Carbon Design System v11, g10 theme (light) and g100 theme (dark) */\n:root{--bg:#f4f4f4;--layer-01:#ffffff;--layer-02:#f4f4f4;--layer-hover:#e8e8e8;--layer-active:#d1d1d1;--border-subtle:#e0e0e0;--border-strong:#8d8d8d;--text-primary:#161616;--text-secondary:#525252;--link:#0f62fe;--link-hover:#0043ce;--focus:#0f62fe;--support-error:#da1e28;--support-success:#24a148;--error-bg:#fff1f1;--tag-gray:#e0e0e0;--tag-blue:#d0e2ff;--tag-blue-ink:#0043ce;--tag-teal:#9ef0f0;--tag-teal-ink:#004144;--btn-primary:#0f62fe;--btn-primary-hover:#0043ce;--btn-primary-ink:#ffffff;--btn-secondary:#393939;--btn-secondary-hover:#4c4c4c;--tag-live-bg:#a7f0ba;--tag-live-ink:#044317;--code-bg:#161616;--code-ink:#f4f4f4;--shell:#161616;--shell-fg:#f4f4f4;--shell-muted:#c6c6c6;--shell-field:#393939;--nav-selected:#e8e8e8;--segment-on-bg:#161616;--segment-on-fg:#ffffff;--font-sans:"IBM Plex Sans",system-ui,-apple-system,sans-serif;--font-mono:"IBM Plex Mono",ui-monospace,monospace}\n@media (prefers-color-scheme: dark){:root:not([data-mode="light"]){--bg:#161616;--layer-01:#262626;--layer-02:#393939;--layer-hover:#333333;--layer-active:#525252;--border-subtle:#393939;--border-strong:#6f6f6f;--text-primary:#f4f4f4;--text-secondary:#c6c6c6;--link:#78a9ff;--link-hover:#a6c8ff;--focus:#ffffff;--support-error:#fa4d56;--support-success:#42be65;--error-bg:#2d0709;--tag-gray:#393939;--tag-blue:#002d9c;--tag-blue-ink:#d0e2ff;--tag-teal:#004144;--tag-teal-ink:#9ef0f0;--btn-primary:#0f62fe;--btn-primary-hover:#0353e9;--btn-primary-ink:#ffffff;--btn-secondary:#6f6f6f;--btn-secondary-hover:#525252;--tag-live-bg:#044317;--tag-live-ink:#a7f0ba;--code-bg:#262626;--code-ink:#f4f4f4;--shell:#161616;--shell-fg:#f4f4f4;--shell-muted:#c6c6c6;--shell-field:#262626;--nav-selected:#393939;--segment-on-bg:#f4f4f4;--segment-on-fg:#161616}}\n:root[data-mode="dark"]{--bg:#161616;--layer-01:#262626;--layer-02:#393939;--layer-hover:#333333;--layer-active:#525252;--border-subtle:#393939;--border-strong:#6f6f6f;--text-primary:#f4f4f4;--text-secondary:#c6c6c6;--link:#78a9ff;--link-hover:#a6c8ff;--focus:#ffffff;--support-error:#fa4d56;--support-success:#42be65;--error-bg:#2d0709;--tag-gray:#393939;--tag-blue:#002d9c;--tag-blue-ink:#d0e2ff;--tag-teal:#004144;--tag-teal-ink:#9ef0f0;--btn-primary:#0f62fe;--btn-primary-hover:#0353e9;--btn-primary-ink:#ffffff;--btn-secondary:#6f6f6f;--btn-secondary-hover:#525252;--tag-live-bg:#044317;--tag-live-ink:#a7f0ba;--code-bg:#262626;--code-ink:#f4f4f4;--shell:#161616;--shell-fg:#f4f4f4;--shell-muted:#c6c6c6;--shell-field:#262626;--nav-selected:#393939;--segment-on-bg:#f4f4f4;--segment-on-fg:#161616}\n*{box-sizing:border-box}\nhtml,body{margin:0;padding:0}\nbody{background:var(--bg);color:var(--text-primary);font:400 14px/1.43 var(--font-sans);-webkit-font-smoothing:antialiased}\na{color:var(--link);text-decoration:none}\na:hover{color:var(--link-hover);text-decoration:underline}\na:focus-visible,button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible,summary:focus-visible,.segment:focus-within{outline:2px solid var(--focus);outline-offset:-2px}\nh1,h2,h3,p{margin:0}\nh1{font-size:28px;font-weight:400;line-height:1.29;letter-spacing:0}\nh2{font-size:16px;font-weight:600;line-height:1.375;letter-spacing:0}\n.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n.muted{color:var(--text-secondary)}\n.mono{font-family:var(--font-mono);font-size:12px;line-height:1.33;overflow-wrap:anywhere}\n.empty{padding:24px 16px;margin:0;color:var(--text-secondary)}\n.app{min-height:100vh;display:flex;flex-direction:column;background:var(--bg);color:var(--text-primary)}\n.topbar{height:48px;flex:0 0 auto;display:flex;align-items:center;gap:16px;padding:0 16px;background:var(--shell);color:var(--shell-fg)}\n.brand{display:inline-flex;align-items:baseline;gap:8px;color:var(--shell-fg);font-size:14px;font-weight:600;white-space:nowrap}\n.brand:hover{color:var(--shell-fg);text-decoration:none}\n.brand-sub{font-family:var(--font-mono);font-size:12px;font-weight:400;color:var(--shell-muted)}\n.search{flex:1 1 240px;min-width:0;max-width:520px;height:32px;display:flex;align-items:center;gap:8px;padding:0 12px;background:var(--shell-field);color:var(--shell-fg);margin-left:8px}\n.search-icon{flex:0 0 auto;color:var(--shell-muted);font-size:16px;line-height:1}\n.search input{flex:1;min-width:0;height:32px;padding:0;border:0;background:transparent;color:var(--shell-fg);font:400 14px/1.29 var(--font-sans)}\n.search input::placeholder{color:var(--shell-muted)}\n.search input:focus{outline:2px solid var(--focus);outline-offset:-2px}\n.top-spacer{flex:1}\n.topbar-user{max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--font-mono);font-size:12px;color:var(--shell-muted)}\nbutton.mode-toggle{height:48px;padding:0 12px;display:inline-flex;align-items:center;gap:8px;color:var(--shell-fg);background:transparent;border:0;border-radius:0;white-space:nowrap}\nbutton.mode-toggle:hover{background:var(--shell-field);color:var(--shell-fg)}\n.shell{flex:1;display:flex;align-items:stretch;min-height:0}\n.sidenav{flex:0 0 240px;width:240px;padding:16px 0;background:var(--layer-01);border-right:1px solid var(--border-subtle)}\n.nav-item{display:flex;align-items:center;gap:8px;min-height:40px;padding:8px 16px;border-left:3px solid transparent;color:var(--text-primary);font-size:14px;line-height:1.29}\n.nav-item:hover{background:var(--layer-hover);color:var(--text-primary);text-decoration:none}\n.nav-item.is-active{background:var(--nav-selected);border-left-color:var(--link);font-weight:600}\n.nav-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.nav-count{margin-left:auto;color:var(--text-secondary);font-weight:400;font-family:var(--font-mono);font-size:12px}\n.nav-heading{margin:24px 16px 8px;font-size:12px;line-height:1.33;color:var(--text-secondary);letter-spacing:.32px}\n.main{flex:999 1 640px;min-width:0;padding:32px}\n.page{width:100%;max-width:1376px;margin:0 auto;padding:24px 32px 48px}\n.page-head{display:flex;align-items:flex-end;justify-content:space-between;gap:16px;flex-wrap:wrap;margin-bottom:24px}\n.detail-head{align-items:flex-start}\n.detail-head h1,.signin-card h1{font-size:32px;font-weight:300;line-height:1.25}\n.title-block{min-width:0;flex:1 1 480px;overflow-wrap:anywhere}\n.page-sub{margin:4px 0 0;color:var(--text-secondary);max-width:80ch}\n.project-description{margin-top:12px;color:var(--text-primary);font-size:16px;line-height:1.5}\n.meta-line{display:flex;align-items:center;gap:16px;flex-wrap:wrap;margin:8px 0 0;color:var(--text-secondary);font-size:14px;overflow-wrap:anywhere}\n.meta-line a{color:var(--text-primary)}\n.meta-line a.mono:hover{color:var(--link);text-decoration:none}\n.meta-line .pill{vertical-align:middle}\n.breadcrumbs{margin:0 0 12px;font-size:14px;line-height:1.29;color:var(--text-secondary);overflow-wrap:anywhere}\n.breadcrumbs a{color:var(--link)}\n.crumb-sep{margin:0 4px;color:var(--text-secondary)}\n.button-row{display:flex;align-items:center;gap:1px;flex-wrap:wrap}\n.btn{display:inline-flex;align-items:center;justify-content:center;height:48px;padding:0 16px;border:1px solid transparent;border-radius:0;background:var(--btn-secondary);color:#fff;font:400 14px/1.29 var(--font-sans);white-space:nowrap;cursor:pointer}\n.btn:hover{background:var(--btn-secondary-hover);color:#fff;text-decoration:none}\n.btn-primary{background:var(--btn-primary);color:var(--btn-primary-ink)}\n.btn-primary:hover{background:var(--btn-primary-hover);color:var(--btn-primary-ink)}\n.btn-tertiary{background:transparent;border-color:var(--border-strong);color:var(--text-primary)}\n.btn-tertiary:hover{background:var(--layer-hover);color:var(--text-primary)}\n.btn-danger{background:transparent;border-color:var(--support-error);color:var(--support-error)}\n.btn-danger:hover{background:var(--error-bg);color:var(--support-error)}\n.btn-sm{height:40px;padding:0 12px}\n.btn-link{display:inline-flex;align-items:center;height:32px;padding:0 8px;border:0;background:transparent;color:var(--link);font:400 14px/1.29 var(--font-sans);cursor:pointer}\n.btn-link:hover{background:var(--layer-hover);color:var(--link-hover);text-decoration:none}\nbutton{height:40px;padding:0 16px;border:1px solid transparent;border-radius:0;background:var(--btn-secondary);color:#fff;font:400 14px/1.29 var(--font-sans);cursor:pointer}\nbutton:hover{background:var(--btn-secondary-hover)}\nbutton.primary{background:var(--btn-primary);color:var(--btn-primary-ink)}\nbutton.primary:hover{background:var(--btn-primary-hover)}\nbutton.small{height:40px;padding:0 12px;border-color:transparent;background:transparent;color:var(--link)}\nbutton.small:hover{background:var(--layer-hover);color:var(--link-hover)}\nbutton.small.danger{color:var(--support-error)}\nbutton.danger{background:transparent;border-color:transparent;color:var(--support-error)}\nbutton.danger:hover{background:var(--error-bg);color:var(--support-error)}\n.icon-btn{width:40px;height:40px;padding:0;display:inline-flex;align-items:center;justify-content:center;background:transparent;border:0;color:var(--text-secondary);font-size:20px;line-height:1}\n.icon-btn:hover{background:var(--layer-hover);color:var(--text-primary)}\n.tagbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:16px;min-height:32px}\n.tagbar-label{margin-right:4px;color:var(--text-secondary);font-size:14px}\n.tagbar-spacer{flex:1}\n.tag-chip{display:inline-flex;align-items:center;gap:6px;height:32px;padding:0 12px;border-radius:16px;border:0;background:var(--tag-gray);color:var(--text-primary);font:400 12px/1.33 var(--font-sans);text-decoration:none;white-space:nowrap;cursor:pointer}\n.tag-chip:hover{background:var(--layer-active);color:var(--text-primary);text-decoration:none}\n.tag-chip.is-on{background:var(--btn-primary);color:var(--btn-primary-ink)}\n.tag-count{font-family:var(--font-mono);opacity:.72}\n.tag-chip-static{cursor:default;padding-right:6px}\n.tag-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap;min-height:32px}\n.tag-remove{width:20px;height:20px;padding:0;display:inline-flex;align-items:center;justify-content:center;border:0;background:transparent;color:inherit;font-size:16px;line-height:1}\n.tag-remove:hover{background:var(--layer-active);color:var(--text-primary)}\n.sharing-filter{display:flex;align-items:center;gap:8px;flex-wrap:wrap}\n.inline-label{display:inline-flex;align-items:center;gap:8px;color:var(--text-secondary);font-size:14px;white-space:nowrap}\ninput,select,textarea{height:40px;padding:0 12px;border:0;border-bottom:1px solid var(--border-strong);border-radius:0;background:var(--layer-02);color:var(--text-primary);font:400 14px/1.29 var(--font-sans)}\nselect{padding-right:32px;cursor:pointer}\ntextarea{height:auto;min-height:80px;padding:8px 12px;resize:vertical}\ninput::placeholder,textarea::placeholder{color:var(--text-secondary)}\ninput:focus,select:focus,textarea:focus{outline:2px solid var(--focus);outline-offset:-2px}\ninput:disabled,select:disabled,textarea:disabled{color:var(--text-secondary);cursor:not-allowed}\n.field-row{display:flex;align-items:stretch;gap:1px;flex-wrap:wrap}\n.field-row input,.field-row select{flex:1 1 160px;min-width:0}\n.field-row .btn{height:40px}\n.field-label{display:block;margin-bottom:8px;color:var(--text-secondary);font-size:12px;line-height:1.33;letter-spacing:.32px}\n.field-hint{color:var(--text-secondary);font-size:12px;line-height:1.33}\n.form-stack{display:flex;flex-direction:column;gap:24px}\n.form-field{display:flex;flex-direction:column;gap:8px;max-width:640px}\n.form-field input,.form-field textarea{width:100%}\n.form-card{max-width:720px}\n.form-actions{display:flex;gap:1px;flex-wrap:wrap}\n.detail-block{display:flex;flex-direction:column;gap:8px;margin-bottom:24px}\n.detail-block:last-child{margin-bottom:0}\n.tag-form{margin-top:12px;max-width:420px}\n.table-box{width:100%;overflow-x:auto;background:var(--layer-01);border-radius:0}\n.data-table{width:100%;border-collapse:collapse;font-size:14px;line-height:1.29}\n.gallery-table{min-width:860px}\n.project-table{min-width:620px}\n.token-table{min-width:640px}\n.data-table th,.data-table td{height:48px;padding:0 16px;text-align:left;vertical-align:middle;white-space:nowrap;border-bottom:1px solid var(--border-subtle)}\n.data-table th{background:var(--border-subtle);color:var(--text-primary);font-weight:600}\n.data-table tbody tr:hover td{background:var(--layer-hover)}\n.data-table tr:last-child td{border-bottom:0}\n.cell-name{min-width:220px}\n.cell-name .row-title{display:block;color:var(--text-primary);font-weight:400;max-width:360px;overflow:hidden;text-overflow:ellipsis}\n.cell-name .row-title:hover{color:var(--link);text-decoration:none}\n.cell-name .slug-link{display:block;margin-top:2px;color:var(--text-secondary)}\n.cell-name .slug-link:hover{color:var(--link);text-decoration:none}\n.empty-row td{height:auto;padding:32px 16px;white-space:normal;color:var(--text-secondary);text-align:center}\n.pill{display:inline-flex;align-items:center;height:24px;padding:0 8px;border-radius:12px;background:var(--tag-gray);color:var(--text-primary);font-size:12px;font-weight:400;line-height:1;white-space:nowrap}\n.pill-private{background:var(--tag-gray);color:var(--text-primary)}\n.pill-people{background:var(--tag-blue);color:var(--tag-blue-ink)}\n.pill-public{background:var(--tag-teal);color:var(--tag-teal-ink)}\n.pill-live{background:var(--tag-live-bg);color:var(--tag-live-ink)}\na.pill:hover{text-decoration:none;color:var(--text-primary)}\n.panel{margin-bottom:24px;background:var(--layer-01);border-radius:0}\n.panel-head{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;min-height:48px;padding:8px 16px;border-bottom:1px solid var(--border-subtle)}\n.panel-head h2{font-size:16px;font-weight:600}\n.panel-head .muted{font-size:14px}\n.panel-body{padding:16px}\n.detail-layout{display:flex;align-items:flex-start;gap:24px;flex-wrap:wrap}\n.detail-main{flex:999 1 640px;min-width:0}\n.detail-side{flex:1 1 380px;min-width:0;display:flex;flex-direction:column;gap:20px;padding:16px;background:var(--layer-01)}\n.detail-side h2{font-size:16px;font-weight:600}\n.side-text{margin:4px 0 0;color:var(--text-secondary);line-height:1.43}\n.side-hint{margin:4px 0 0;color:var(--text-secondary);font-size:12px;line-height:1.33}\n.side-section{display:flex;flex-direction:column;gap:12px}\n.preview-picker{display:inline-flex;align-items:center;gap:8px}\n.preview-body{padding:16px}\n.preview-frame{display:block;width:100%;aspect-ratio:16/9;border:1px solid var(--border-subtle);background:#ffffff}\n.preview-empty{display:flex;align-items:center;justify-content:center;min-height:200px;padding:24px;border:1px solid var(--border-subtle);background:var(--layer-02);color:var(--text-secondary);text-align:center}\n.version-list{list-style:none;margin:0;padding:0}\n.version-row{display:flex;align-items:center;gap:16px;flex-wrap:wrap;min-height:56px;padding:8px 16px;border-bottom:1px solid var(--border-subtle)}\n.version-row:last-child{border-bottom:0}\n.version-row.is-served{background:var(--layer-02);border-left:3px solid var(--support-success);padding-left:13px}\n.version-n{flex:0 0 36px}\n.version-main{flex:1 1 220px;min-width:0}\n.version-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.version-meta{color:var(--text-secondary);font-size:12px;line-height:1.33}\n.version-actions{padding:12px 16px;border-top:1px solid var(--border-subtle)}\n.segmented{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:1px;margin:0;background:var(--border-strong)}\n.segment{position:relative;display:flex;align-items:center;justify-content:center;height:40px;background:var(--layer-01);color:var(--text-primary);font-size:14px;cursor:pointer}\n.segment:hover{background:var(--layer-hover)}\n.segment.is-on{background:var(--segment-on-bg);color:var(--segment-on-fg)}\n.segment-input{position:absolute;width:1px;height:1px;padding:0;margin:0;overflow:hidden;clip:rect(0,0,0,0);border:0;opacity:0}\n.people-list{list-style:none;margin:0;padding:0}\n.person-row{display:flex;align-items:center;gap:8px;min-height:48px;padding:4px 0;border-bottom:1px solid var(--border-subtle)}\n.person-row:last-child{border-bottom:0}\n.person-email{flex:1;min-width:0;overflow-wrap:anywhere}\n.person-role{color:var(--text-secondary);font-size:12px;white-space:nowrap}\n.info-box{padding:12px 16px;background:var(--layer-02)}\n.info-title{font-weight:600}\n.info-text{margin-top:4px;color:var(--text-secondary);font-size:14px}\n.info-box a{display:inline-block;margin-top:8px}\n.danger-zone{padding-top:16px;border-top:1px solid var(--border-subtle)}\n.danger-zone h2{margin-bottom:8px;font-size:14px;font-weight:600;color:var(--support-error)}\n.theme-list{list-style:none;margin:0;padding:0}\n.theme-row{display:flex;align-items:center;justify-content:space-between;gap:16px;min-height:48px;padding:0 16px;border-bottom:1px solid var(--border-subtle)}\n.theme-row:last-child{border-bottom:0}\n.token-box{margin-top:16px;padding:16px;background:var(--code-bg);color:var(--code-ink)}\n.token-box code{display:block;margin-top:8px;font-family:var(--font-mono);font-size:12px;word-break:break-all}\n.token-box .muted{color:var(--code-ink);opacity:.78}\n.token-error{margin-top:12px;color:var(--support-error)}\n.signin{display:flex;align-items:flex-start;justify-content:center;padding-top:80px}\n.signin-card{width:100%;max-width:560px}\n.signin-card .btn{margin-top:8px}\n@media(max-width:900px){\n.shell{display:block}\n.sidenav{width:auto;display:flex;flex-wrap:nowrap;align-items:stretch;overflow-x:auto;overflow-y:hidden;padding:0;border-right:0;border-bottom:1px solid var(--border-subtle);scrollbar-width:thin}\n.nav-item{width:auto;flex:0 0 auto;min-height:48px;padding:0 16px;border-left:0;border-bottom:3px solid transparent}\n.nav-item.is-active{border-left-color:transparent;border-bottom-color:var(--link)}\n.nav-heading{display:none}\n.data-table{min-width:0}\n.data-table th,.data-table td{white-space:normal;overflow-wrap:anywhere}\n.data-table th{overflow-wrap:normal}\n.main{padding:24px 16px 48px}\n.page{padding:16px 16px 40px}\n.detail-side{flex-basis:100%}\n}\n@media(max-width:620px){\n.topbar{height:auto;min-height:48px;flex-wrap:wrap;gap:8px;padding:8px 16px}\n.search{order:3;flex:1 1 100%;max-width:none;height:40px;margin-left:0}\n.search input{height:40px}\n.brand-sub{display:none}\n.topbar-user{max-width:100%}\nbutton.mode-toggle{height:40px}\nh1,.detail-head h1,.signin-card h1{font-size:24px}\n.page-head,.detail-head{align-items:flex-start}\n.button-row{width:100%}\n.button-row .btn{flex:1 1 auto}\n.field-row input,.field-row select{flex:1 1 140px}\n.panel-head{align-items:flex-start}\n.version-row{gap:8px}\n.preview-frame{aspect-ratio:4/3}\n.data-table,.data-table tbody,.data-table tr,.data-table td{display:block;width:100%;min-width:0}\n.data-table thead{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n.data-table tr{padding:12px 16px;border-bottom:1px solid var(--border-subtle);background:var(--layer-01)}\n.data-table tr:hover{background:var(--layer-hover)}\n.data-table tbody tr:hover td{background:transparent}\n.data-table tr:last-child{border-bottom:0}\n.data-table td,.data-table th{height:auto;padding:0;border:0;text-align:left;white-space:normal;overflow-wrap:anywhere;background:transparent}\n.data-table tr>td:first-child{padding-bottom:8px}\n.data-table td[data-label]{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap;min-height:32px;padding:2px 0}\n.data-table td[data-label]::before{content:attr(data-label) ": ";flex:0 0 auto;min-width:96px;color:var(--text-secondary);font-family:var(--font-sans);font-weight:400;font-size:12px;line-height:1.33}\n.data-table .btn-link{height:40px;padding:0}\n.data-table .icon-btn{height:40px}\n.data-table .empty-row td{padding:24px 0;text-align:center;color:var(--text-secondary)}\n.cell-name .row-title{max-width:none;overflow:visible;white-space:normal}\n}\n';
|
|
4501
|
-
var APP_JS = '"use strict";\n(function () {\n function api(method, path, body) {\n var init = { method: method, credentials: "same-origin", headers: {} };\n if (method !== "GET") init.headers["Content-Type"] = "application/json";\n if (body !== undefined) init.body = JSON.stringify(body);\n return fetch(path, init).then(function (res) {\n if (res.status === 204) return null;\n return res.text().then(function (text) {\n var data = null;\n if (text) { try { data = JSON.parse(text); } catch (err) { data = null; } }\n if (!res.ok) {\n var msg = data && data.error ? data.error : "Request failed with status " + res.status;\n throw new Error(msg);\n }\n return data;\n });\n });\n }\n\n function fail(err) {\n window.alert(err && err.message ? err.message : String(err));\n }\n\n function reload() {\n window.location.reload();\n }\n\n function copyText(value) {\n if (navigator.clipboard && navigator.clipboard.writeText) {\n navigator.clipboard.writeText(value).catch(fail);\n return;\n }\n var input = document.createElement("textarea");\n input.value = value;\n document.body.appendChild(input);\n input.select();\n try { document.execCommand("copy"); } catch (err) { fail(err); }\n document.body.removeChild(input);\n }\n\n function propsOf(el) {\n var raw = el.getAttribute("data-props");\n if (!raw) return {};\n try { return JSON.parse(raw) || {}; } catch (err) { return {}; }\n }\n\n function closest(event, selector) {\n var target = event.target;\n if (!target || !target.closest) return null;\n return target.closest(selector);\n }\n\n function flash(el, text) {\n var label = el.textContent;\n el.textContent = text;\n window.setTimeout(function () { el.textContent = label; }, 1200);\n }\n\n function field(form, name) {\n var el = form.querySelector("[name=" + name + "]");\n return el && typeof el.value === "string" ? el.value : "";\n }\n\n var islands = {\n copy: function (root, props) {\n root.addEventListener("click", function (event) {\n event.preventDefault();\n copyText(String(props.text || ""));\n flash(root, "Copied");\n });\n },\n delete: function (root, props) {\n root.addEventListener("click", function (event) {\n event.preventDefault();\n if (!window.confirm("Delete this artifact, all versions and all shares?")) return;\n api("DELETE", "/api/artifacts/" + encodeURIComponent(props.slug)).then(function () {\n window.location.assign("/");\n }, fail);\n });\n },\n versions: function (root, props) {\n root.addEventListener("click", function (event) {\n var el = closest(event, "[data-action]");\n if (!el || !root.contains(el)) return;\n var action = el.getAttribute("data-action");\n if (action !== "pin" && action !== "set-live" && action !== "newest") return;\n event.preventDefault();\n if (action === "newest") {\n api("PATCH", "/api/artifacts/" + encodeURIComponent(props.slug), { pinned: null }).then(reload, fail);\n } else {\n api("PATCH", "/api/artifacts/" + encodeURIComponent(props.slug), {\n pinned: Number(el.getAttribute("data-n"))\n }).then(reload, fail);\n }\n });\n },\n share: function (root, props) {\n root.addEventListener("click", function (event) {\n var removed = closest(event, "[data-action=remove-share]");\n if (removed && root.contains(removed)) {\n event.preventDefault();\n api(\n "DELETE",\n "/api/artifacts/" + encodeURIComponent(props.slug) + "/shares/" + encodeURIComponent(removed.getAttribute("data-email"))\n ).then(reload, fail);\n return;\n }\n var tag = closest(event, "[data-action=remove-tag]");\n if (tag && root.contains(tag)) {\n event.preventDefault();\n var tags = Array.isArray(props.tags) ? props.tags.filter(function (value) {\n return value !== tag.getAttribute("data-tag");\n }) : [];\n api("PATCH", "/api/artifacts/" + encodeURIComponent(props.slug), { tags: tags }).then(reload, fail);\n }\n });\n root.addEventListener("submit", function (event) {\n var form = event.target;\n var kind = form.getAttribute("data-form");\n if (!kind) return;\n event.preventDefault();\n if (kind === "mode") {\n var checked = form.querySelector("[name=mode]:checked") || form.querySelector("[name=mode]");\n api("PATCH", "/api/artifacts/" + encodeURIComponent(props.slug), {\n mode: checked ? checked.value : "private"\n }).then(reload, fail);\n } else if (kind === "title") {\n api("PATCH", "/api/artifacts/" + encodeURIComponent(props.slug), {\n title: field(form, "title").trim()\n }).then(reload, fail);\n } else if (kind === "share") {\n var email = field(form, "email").trim();\n if (email === "") return;\n api(\n "PUT",\n "/api/artifacts/" + encodeURIComponent(props.slug) + "/shares/" + encodeURIComponent(email),\n { role: field(form, "role") }\n ).then(reload, fail);\n } else if (kind === "tags") {\n var tagInput = field(form, "tag").trim().toLowerCase();\n if (tagInput === "") return;\n var next = Array.isArray(props.tags) ? props.tags.slice() : [];\n if (next.indexOf(tagInput) === -1) next.push(tagInput);\n api("PATCH", "/api/artifacts/" + encodeURIComponent(props.slug), { tags: next }).then(reload, fail);\n } else if (kind === "project") {\n var project = field(form, "project");\n api("PATCH", "/api/artifacts/" + encodeURIComponent(props.slug), {\n project: project === "" ? null : project\n }).then(reload, fail);\n }\n });\n },\n project: function (root, props) {\n root.addEventListener("click", function (event) {\n var el = closest(event, "[data-action=remove-member]");\n if (!el || !root.contains(el)) return;\n event.preventDefault();\n api(\n "DELETE",\n "/api/projects/" + encodeURIComponent(props.slug) + "/members/" + encodeURIComponent(el.getAttribute("data-email"))\n ).then(reload, fail);\n });\n root.addEventListener("submit", function (event) {\n var form = event.target;\n var kind = form.getAttribute("data-form");\n if (!kind) return;\n event.preventDefault();\n if (kind === "project-new") {\n var slug = field(form, "slug").trim().toLowerCase();\n if (slug === "") return;\n var body = { slug: slug };\n var title = field(form, "title").trim();\n var description = field(form, "description").trim();\n if (title !== "") body.title = title;\n if (description !== "") body.description = description;\n api("POST", "/api/projects", body).then(function (data) {\n window.location.assign("/p/" + encodeURIComponent(data.slug));\n }, fail);\n } else if (kind === "description") {\n api("PATCH", "/api/projects/" + encodeURIComponent(props.slug), {\n description: field(form, "description").trim()\n }).then(reload, fail);\n } else if (kind === "add-artifact") {\n var artifact = field(form, "artifact");\n if (artifact === "") return;\n api("PATCH", "/api/artifacts/" + encodeURIComponent(artifact), { project: props.slug }).then(reload, fail);\n } else if (kind === "member") {\n var email = field(form, "email").trim();\n if (email === "") return;\n api(\n "PUT",\n "/api/projects/" + encodeURIComponent(props.slug) + "/members/" + encodeURIComponent(email),\n { role: field(form, "role") }\n ).then(reload, fail);\n }\n });\n },\n tokens: function (root) {\n root.addEventListener("click", function (event) {\n var copy = closest(event, "[data-action=copy]");\n if (copy && root.contains(copy)) {\n event.preventDefault();\n copyText(copy.getAttribute("data-copy") || "");\n flash(copy, "Copied");\n return;\n }\n var revoke = closest(event, "[data-action=revoke], [data-action=revoke-token]");\n if (!revoke || !root.contains(revoke)) return;\n event.preventDefault();\n if (!window.confirm("Revoke this token? The CLI using it will stop working.")) return;\n api("DELETE", "/api/tokens/" + encodeURIComponent(revoke.getAttribute("data-id"))).then(function () {\n var row = revoke.closest("tr");\n if (row) row.remove();\n }, fail);\n });\n root.addEventListener("submit", function (event) {\n var form = event.target;\n if (form.getAttribute("data-form") !== "token") return;\n event.preventDefault();\n var name = field(form, "name").trim();\n var scope = field(form, "scope");\n api("POST", "/api/tokens", { name: name, scope: scope }).then(function (data) {\n var box = document.getElementById("token-box");\n var code = document.getElementById("token-value");\n var error = document.getElementById("token-error");\n if (box && code) {\n code.textContent = data.token;\n box.hidden = false;\n }\n if (error) error.hidden = true;\n var body = document.getElementById("token-rows");\n if (body) {\n var tr = document.createElement("tr");\n var cells = [data.name, data.scope.charAt(0).toUpperCase() + data.scope.slice(1), String(data.created_at).slice(0, 10), "Never"];\n var labels = [null, "Scope", "Created", "Last used"];\n for (var i = 0; i < cells.length; i += 1) {\n var td = document.createElement("td");\n td.textContent = cells[i];\n if (labels[i]) td.setAttribute("data-label", labels[i]);\n tr.appendChild(td);\n }\n var last = document.createElement("td");\n last.setAttribute("data-label", "Actions");\n var button = document.createElement("button");\n button.type = "button";\n button.className = "btn-link";\n button.setAttribute("data-action", "revoke");\n button.setAttribute("data-id", data.id);\n button.textContent = "Revoke";\n last.appendChild(button);\n tr.appendChild(last);\n body.appendChild(tr);\n }\n form.reset();\n }, function (err) {\n var box = document.getElementById("token-error");\n if (box) {\n box.textContent = err && err.message ? err.message : String(err);\n box.hidden = false;\n return;\n }\n fail(err);\n });\n });\n }\n };\n\n var MODE_KEY = "rshow-mode";\n var MODES = ["system", "light", "dark"];\n var MODE_LABELS = { system: "System", light: "Light", dark: "Dark" };\n\n function normalizeMode(value) {\n return value === "light" || value === "dark" ? value : "system";\n }\n\n function readMode() {\n try { return normalizeMode(window.localStorage.getItem(MODE_KEY)); } catch (err) { return "system"; }\n }\n\n function applyMode(mode) {\n var root = document.documentElement;\n if (mode === "light" || mode === "dark") root.dataset.mode = mode;\n else delete root.dataset.mode;\n }\n\n function saveMode(mode) {\n try { window.localStorage.setItem(MODE_KEY, mode); } catch (err) { /* storage may be blocked */ }\n applyMode(mode);\n }\n\n function paintMode(toggle) {\n var mode = readMode();\n toggle.textContent = MODE_LABELS[mode];\n toggle.setAttribute("aria-label", "Color mode: " + MODE_LABELS[mode] + ". Activate to cycle System, Light, Dark.");\n }\n\n var modeToggle = document.getElementById("mode-toggle");\n if (modeToggle) {\n paintMode(modeToggle);\n modeToggle.addEventListener("click", function () {\n var next = MODES[(MODES.indexOf(readMode()) + 1) % MODES.length];\n saveMode(next);\n paintMode(modeToggle);\n });\n }\n\n document.addEventListener("change", function (event) {\n var el = event.target;\n if (!el || !el.matches || !el.matches("[data-submit-on-change]")) return;\n if (el.form) el.form.requestSubmit();\n });\n\n var navBar = document.querySelector(".sidenav");\n var activeNav = navBar ? navBar.querySelector(".nav-item.is-active") : null;\n if (navBar && activeNav && navBar.scrollWidth > navBar.clientWidth) {\n var delta = activeNav.getBoundingClientRect().left - navBar.getBoundingClientRect().left;\n if (delta < 0 || delta + activeNav.offsetWidth > navBar.clientWidth) {\n navBar.scrollLeft = Math.max(0, navBar.scrollLeft + delta - 16);\n }\n }\n\n var nodes = document.querySelectorAll("[data-island]");\n for (var i = 0; i < nodes.length; i += 1) {\n var kind = nodes[i].getAttribute("data-island");\n var mount = islands[kind];\n if (mount) mount(nodes[i], propsOf(nodes[i]));\n }\n})();\n';
|
|
4562
|
+
var APP_JS = '"use strict";\n(function () {\n function api(method, path, body) {\n var init = { method: method, credentials: "same-origin", headers: {} };\n if (method !== "GET") init.headers["Content-Type"] = "application/json";\n if (body !== undefined) init.body = JSON.stringify(body);\n return fetch(path, init).then(function (res) {\n if (res.status === 204) return null;\n return res.text().then(function (text) {\n var data = null;\n if (text) { try { data = JSON.parse(text); } catch (err) { data = null; } }\n if (!res.ok) {\n var msg = data && data.error ? data.error : "Request failed with status " + res.status;\n throw new Error(msg);\n }\n return data;\n });\n });\n }\n\n function fail(err) {\n window.alert(err && err.message ? err.message : String(err));\n }\n\n function reload() {\n window.location.reload();\n }\n\n function copyText(value) {\n if (navigator.clipboard && navigator.clipboard.writeText) {\n navigator.clipboard.writeText(value).catch(fail);\n return;\n }\n var input = document.createElement("textarea");\n input.value = value;\n document.body.appendChild(input);\n input.select();\n try { document.execCommand("copy"); } catch (err) { fail(err); }\n document.body.removeChild(input);\n }\n\n function propsOf(el) {\n var raw = el.getAttribute("data-props");\n if (!raw) return {};\n try { return JSON.parse(raw) || {}; } catch (err) { return {}; }\n }\n\n function closest(event, selector) {\n var target = event.target;\n if (!target || !target.closest) return null;\n return target.closest(selector);\n }\n\n function flash(el, text) {\n var label = el.textContent;\n el.textContent = text;\n window.setTimeout(function () { el.textContent = label; }, 1200);\n }\n\n function field(form, name) {\n var el = form.querySelector("[name=" + name + "]");\n return el && typeof el.value === "string" ? el.value : "";\n }\n\n var islands = {\n copy: function (root, props) {\n root.addEventListener("click", function (event) {\n event.preventDefault();\n copyText(String(props.text || ""));\n flash(root, "Copied");\n });\n },\n delete: function (root, props) {\n root.addEventListener("click", function (event) {\n event.preventDefault();\n if (!window.confirm("Delete this artifact, all versions and all shares?")) return;\n api("DELETE", "/api/artifacts/" + encodeURIComponent(props.slug)).then(function () {\n window.location.assign("/");\n }, fail);\n });\n },\n versions: function (root, props) {\n root.addEventListener("click", function (event) {\n var el = closest(event, "[data-action]");\n if (!el || !root.contains(el)) return;\n var action = el.getAttribute("data-action");\n if (action !== "pin" && action !== "set-live" && action !== "newest" && action !== "delete-version") return;\n event.preventDefault();\n if (action === "newest") {\n api("PATCH", "/api/artifacts/" + encodeURIComponent(props.slug), { pinned: null }).then(reload, fail);\n } else if (action === "delete-version") {\n var version = Number(el.getAttribute("data-n"));\n if (!window.confirm("Delete v" + version + "? This cannot be undone.")) return;\n api("DELETE", "/api/artifacts/" + encodeURIComponent(props.slug) + "/versions/" + version).then(reload, fail);\n } else {\n api("PATCH", "/api/artifacts/" + encodeURIComponent(props.slug), {\n pinned: Number(el.getAttribute("data-n"))\n }).then(reload, fail);\n }\n });\n },\n share: function (root, props) {\n root.addEventListener("click", function (event) {\n var removed = closest(event, "[data-action=remove-share]");\n if (removed && root.contains(removed)) {\n event.preventDefault();\n api(\n "DELETE",\n "/api/artifacts/" + encodeURIComponent(props.slug) + "/shares/" + encodeURIComponent(removed.getAttribute("data-email"))\n ).then(reload, fail);\n return;\n }\n var tag = closest(event, "[data-action=remove-tag]");\n if (tag && root.contains(tag)) {\n event.preventDefault();\n var tags = Array.isArray(props.tags) ? props.tags.filter(function (value) {\n return value !== tag.getAttribute("data-tag");\n }) : [];\n api("PATCH", "/api/artifacts/" + encodeURIComponent(props.slug), { tags: tags }).then(reload, fail);\n }\n });\n root.addEventListener("submit", function (event) {\n var form = event.target;\n var kind = form.getAttribute("data-form");\n if (!kind) return;\n event.preventDefault();\n if (kind === "mode") {\n var checked = form.querySelector("[name=mode]:checked") || form.querySelector("[name=mode]");\n api("PATCH", "/api/artifacts/" + encodeURIComponent(props.slug), {\n mode: checked ? checked.value : "private"\n }).then(reload, fail);\n } else if (kind === "title") {\n api("PATCH", "/api/artifacts/" + encodeURIComponent(props.slug), {\n title: field(form, "title").trim()\n }).then(reload, fail);\n } else if (kind === "share") {\n var email = field(form, "email").trim();\n if (email === "") return;\n api(\n "PUT",\n "/api/artifacts/" + encodeURIComponent(props.slug) + "/shares/" + encodeURIComponent(email),\n { role: field(form, "role") }\n ).then(reload, fail);\n } else if (kind === "tags") {\n var tagInput = field(form, "tag").trim().toLowerCase();\n if (tagInput === "") return;\n var next = Array.isArray(props.tags) ? props.tags.slice() : [];\n if (next.indexOf(tagInput) === -1) next.push(tagInput);\n api("PATCH", "/api/artifacts/" + encodeURIComponent(props.slug), { tags: next }).then(reload, fail);\n } else if (kind === "project") {\n var project = field(form, "project");\n api("PATCH", "/api/artifacts/" + encodeURIComponent(props.slug), {\n project: project === "" ? null : project\n }).then(reload, fail);\n }\n });\n },\n project: function (root, props) {\n root.addEventListener("click", function (event) {\n var el = closest(event, "[data-action=remove-member]");\n if (!el || !root.contains(el)) return;\n event.preventDefault();\n api(\n "DELETE",\n "/api/projects/" + encodeURIComponent(props.slug) + "/members/" + encodeURIComponent(el.getAttribute("data-email"))\n ).then(reload, fail);\n });\n root.addEventListener("submit", function (event) {\n var form = event.target;\n var kind = form.getAttribute("data-form");\n if (!kind) return;\n event.preventDefault();\n if (kind === "project-new") {\n var slug = field(form, "slug").trim().toLowerCase();\n if (slug === "") return;\n var body = { slug: slug };\n var title = field(form, "title").trim();\n var description = field(form, "description").trim();\n if (title !== "") body.title = title;\n if (description !== "") body.description = description;\n api("POST", "/api/projects", body).then(function (data) {\n window.location.assign("/p/" + encodeURIComponent(data.slug));\n }, fail);\n } else if (kind === "description") {\n api("PATCH", "/api/projects/" + encodeURIComponent(props.slug), {\n description: field(form, "description").trim()\n }).then(reload, fail);\n } else if (kind === "add-artifact") {\n var artifact = field(form, "artifact");\n if (artifact === "") return;\n api("PATCH", "/api/artifacts/" + encodeURIComponent(artifact), { project: props.slug }).then(reload, fail);\n } else if (kind === "member") {\n var email = field(form, "email").trim();\n if (email === "") return;\n api(\n "PUT",\n "/api/projects/" + encodeURIComponent(props.slug) + "/members/" + encodeURIComponent(email),\n { role: field(form, "role") }\n ).then(reload, fail);\n }\n });\n },\n tokens: function (root) {\n root.addEventListener("click", function (event) {\n var copy = closest(event, "[data-action=copy]");\n if (copy && root.contains(copy)) {\n event.preventDefault();\n copyText(copy.getAttribute("data-copy") || "");\n flash(copy, "Copied");\n return;\n }\n var revoke = closest(event, "[data-action=revoke], [data-action=revoke-token]");\n if (!revoke || !root.contains(revoke)) return;\n event.preventDefault();\n if (!window.confirm("Revoke this token? The CLI using it will stop working.")) return;\n api("DELETE", "/api/tokens/" + encodeURIComponent(revoke.getAttribute("data-id"))).then(function () {\n var row = revoke.closest("tr");\n if (row) row.remove();\n }, fail);\n });\n root.addEventListener("submit", function (event) {\n var form = event.target;\n if (form.getAttribute("data-form") !== "token") return;\n event.preventDefault();\n var name = field(form, "name").trim();\n var scope = field(form, "scope");\n api("POST", "/api/tokens", { name: name, scope: scope }).then(function (data) {\n var box = document.getElementById("token-box");\n var code = document.getElementById("token-value");\n var error = document.getElementById("token-error");\n if (box && code) {\n code.textContent = data.token;\n box.hidden = false;\n }\n if (error) error.hidden = true;\n var body = document.getElementById("token-rows");\n if (body) {\n var tr = document.createElement("tr");\n var cells = [data.name, data.scope.charAt(0).toUpperCase() + data.scope.slice(1), String(data.created_at).slice(0, 10), "Never"];\n var labels = [null, "Scope", "Created", "Last used"];\n for (var i = 0; i < cells.length; i += 1) {\n var td = document.createElement("td");\n td.textContent = cells[i];\n if (labels[i]) td.setAttribute("data-label", labels[i]);\n tr.appendChild(td);\n }\n var last = document.createElement("td");\n last.setAttribute("data-label", "Actions");\n var button = document.createElement("button");\n button.type = "button";\n button.className = "btn-link";\n button.setAttribute("data-action", "revoke");\n button.setAttribute("data-id", data.id);\n button.textContent = "Revoke";\n last.appendChild(button);\n tr.appendChild(last);\n body.appendChild(tr);\n }\n form.reset();\n }, function (err) {\n var box = document.getElementById("token-error");\n if (box) {\n box.textContent = err && err.message ? err.message : String(err);\n box.hidden = false;\n return;\n }\n fail(err);\n });\n });\n }\n };\n\n var MODE_KEY = "rshow-mode";\n var MODES = ["system", "light", "dark"];\n var MODE_LABELS = { system: "System", light: "Light", dark: "Dark" };\n\n function normalizeMode(value) {\n return value === "light" || value === "dark" ? value : "system";\n }\n\n function readMode() {\n try { return normalizeMode(window.localStorage.getItem(MODE_KEY)); } catch (err) { return "system"; }\n }\n\n function applyMode(mode) {\n var root = document.documentElement;\n if (mode === "light" || mode === "dark") root.dataset.mode = mode;\n else delete root.dataset.mode;\n }\n\n function saveMode(mode) {\n try { window.localStorage.setItem(MODE_KEY, mode); } catch (err) { /* storage may be blocked */ }\n applyMode(mode);\n }\n\n function paintMode(toggle) {\n var mode = readMode();\n toggle.textContent = MODE_LABELS[mode];\n toggle.setAttribute("aria-label", "Color mode: " + MODE_LABELS[mode] + ". Activate to cycle System, Light, Dark.");\n }\n\n var modeToggle = document.getElementById("mode-toggle");\n if (modeToggle) {\n paintMode(modeToggle);\n modeToggle.addEventListener("click", function () {\n var next = MODES[(MODES.indexOf(readMode()) + 1) % MODES.length];\n saveMode(next);\n paintMode(modeToggle);\n });\n }\n\n document.addEventListener("change", function (event) {\n var el = event.target;\n if (!el || !el.matches || !el.matches("[data-submit-on-change]")) return;\n if (el.form) el.form.requestSubmit();\n });\n\n var navBar = document.querySelector(".sidenav");\n var activeNav = navBar ? navBar.querySelector(".nav-item.is-active") : null;\n if (navBar && activeNav && navBar.scrollWidth > navBar.clientWidth) {\n var delta = activeNav.getBoundingClientRect().left - navBar.getBoundingClientRect().left;\n if (delta < 0 || delta + activeNav.offsetWidth > navBar.clientWidth) {\n navBar.scrollLeft = Math.max(0, navBar.scrollLeft + delta - 16);\n }\n }\n\n var nodes = document.querySelectorAll("[data-island]");\n for (var i = 0; i < nodes.length; i += 1) {\n var kind = nodes[i].getAttribute("data-island");\n var mount = islands[kind];\n if (mount) mount(nodes[i], propsOf(nodes[i]));\n }\n})();\n';
|
|
4502
4563
|
var MODE_JS = '"use strict";\n(function () {\n var KEY = "rshow-mode";\n var root = document.documentElement;\n var mode = null;\n try { mode = window.localStorage.getItem(KEY); } catch (err) { mode = null; }\n if (mode === "light" || mode === "dark") root.dataset.mode = mode;\n else delete root.dataset.mode;\n})();\n';
|
|
4503
4564
|
|
|
4504
4565
|
// src/static.ts
|
|
@@ -5403,6 +5464,7 @@ function VersionRowView(props) {
|
|
|
5403
5464
|
const version = props.version;
|
|
5404
5465
|
const served = props.artifact.served === version.n;
|
|
5405
5466
|
const pinned = props.artifact.pinned === version.n;
|
|
5467
|
+
const deletable = props.owner && !props.artifact.slug.startsWith(THEME_PREFIX) && !pinned && props.count > 1;
|
|
5406
5468
|
return /* @__PURE__ */ jsxDEV("li", { class: served ? "version-row is-served" : "version-row", children: [
|
|
5407
5469
|
/* @__PURE__ */ jsxDEV("span", { class: "version-n mono", children: [
|
|
5408
5470
|
"v",
|
|
@@ -5423,7 +5485,8 @@ function VersionRowView(props) {
|
|
|
5423
5485
|
] }),
|
|
5424
5486
|
served ? /* @__PURE__ */ jsxDEV("span", { class: "pill pill-live", children: pinned ? "Pinned, live" : "Live" }) : null,
|
|
5425
5487
|
/* @__PURE__ */ jsxDEV("a", { class: "btn-link", href: `/a/${props.artifact.slug}?preview=${version.n}`, children: "Preview" }),
|
|
5426
|
-
props.owner && !served ? /* @__PURE__ */ jsxDEV("button", { type: "button", class: "btn-link", "data-action": "pin", "data-n": version.n, children: "Pin here" }) : null
|
|
5488
|
+
props.owner && !served ? /* @__PURE__ */ jsxDEV("button", { type: "button", class: "btn-link", "data-action": "pin", "data-n": version.n, children: "Pin here" }) : null,
|
|
5489
|
+
deletable ? /* @__PURE__ */ jsxDEV("button", { type: "button", class: "btn-link", "data-action": "delete-version", "data-n": version.n, children: "Delete" }) : null
|
|
5427
5490
|
] });
|
|
5428
5491
|
}
|
|
5429
5492
|
__name(VersionRowView, "VersionRowView");
|
|
@@ -5557,7 +5620,7 @@ function artifactPage(env, email, artifact, versions, shares, project, editableP
|
|
|
5557
5620
|
/* @__PURE__ */ jsxDEV("h2", { children: "Versions" }),
|
|
5558
5621
|
/* @__PURE__ */ jsxDEV("span", { class: "muted", children: artifact.pinned === null ? "The link follows the newest version." : `Pinned to v${artifact.pinned}. New publishes wait behind it.` })
|
|
5559
5622
|
] }),
|
|
5560
|
-
versions.length === 0 ? /* @__PURE__ */ jsxDEV("p", { class: "empty", children: "No versions yet." }) : /* @__PURE__ */ jsxDEV("ul", { class: "version-list", children: versions.map((version) => /* @__PURE__ */ jsxDEV(VersionRowView, { artifact, version, owner })) }),
|
|
5623
|
+
versions.length === 0 ? /* @__PURE__ */ jsxDEV("p", { class: "empty", children: "No versions yet." }) : /* @__PURE__ */ jsxDEV("ul", { class: "version-list", children: versions.map((version) => /* @__PURE__ */ jsxDEV(VersionRowView, { artifact, version, owner, count: versions.length })) }),
|
|
5561
5624
|
artifact.pinned !== null ? /* @__PURE__ */ jsxDEV("div", { class: "version-actions", children: /* @__PURE__ */ jsxDEV("button", { type: "button", class: "btn btn-tertiary", "data-action": "newest", children: "Follow the newest version again" }) }) : null
|
|
5562
5625
|
]
|
|
5563
5626
|
}
|
|
@@ -5627,7 +5690,7 @@ function artifactPage(env, email, artifact, versions, shares, project, editableP
|
|
|
5627
5690
|
] }) : null,
|
|
5628
5691
|
owner ? /* @__PURE__ */ jsxDEV("div", { class: "danger-zone", children: [
|
|
5629
5692
|
/* @__PURE__ */ jsxDEV("h2", { children: "Danger zone" }),
|
|
5630
|
-
/* @__PURE__ */ jsxDEV(
|
|
5693
|
+
artifact.slug.startsWith(THEME_PREFIX) ? /* @__PURE__ */ jsxDEV("p", { class: "side-text", children: "Pages link to this theme. Delete it with rshow delete --force." }) : /* @__PURE__ */ jsxDEV(
|
|
5631
5694
|
"button",
|
|
5632
5695
|
{
|
|
5633
5696
|
type: "button",
|
|
@@ -6503,7 +6566,6 @@ __name(loginRedirect, "loginRedirect");
|
|
|
6503
6566
|
|
|
6504
6567
|
// src/project.ts
|
|
6505
6568
|
var THEME_SLUG = `${THEME_PREFIX}default`;
|
|
6506
|
-
var THEME_VERSION = 3;
|
|
6507
6569
|
var MODE_WORDS = {
|
|
6508
6570
|
private: "Private",
|
|
6509
6571
|
people: "People",
|
|
@@ -6513,15 +6575,15 @@ var LIST_STYLE = ".rshow-project{max-width:44rem}.rshow-project h1{margin-bottom
|
|
|
6513
6575
|
async function defaultThemeReady(env) {
|
|
6514
6576
|
const artifact = await env.DB.prepare("SELECT mode FROM artifacts WHERE slug = ?1").bind(THEME_SLUG).first();
|
|
6515
6577
|
if (!artifact || artifact.mode !== MODE.public) return false;
|
|
6516
|
-
const version = await env.DB.prepare("SELECT n FROM versions WHERE slug = ?1
|
|
6578
|
+
const version = await env.DB.prepare("SELECT n FROM versions WHERE slug = ?1 LIMIT 1").bind(THEME_SLUG).first();
|
|
6517
6579
|
return version !== null;
|
|
6518
6580
|
}
|
|
6519
6581
|
__name(defaultThemeReady, "defaultThemeReady");
|
|
6520
6582
|
function groupPage(title2, description, artifacts, theme) {
|
|
6521
|
-
const links = theme ? `<link rel="stylesheet" href="/_themes/default@
|
|
6522
|
-
<link rel="stylesheet" href="/_themes/default@
|
|
6523
|
-
<link rel="stylesheet" href="/_themes/default@
|
|
6524
|
-
<script src="/_themes/default@
|
|
6583
|
+
const links = theme ? `<link rel="stylesheet" href="/_themes/default@latest/fonts.css">
|
|
6584
|
+
<link rel="stylesheet" href="/_themes/default@latest/tokens.css">
|
|
6585
|
+
<link rel="stylesheet" href="/_themes/default@latest/page.css">
|
|
6586
|
+
<script src="/_themes/default@latest/mode.js"><\/script>` : `<style>${MINIMAL_STYLE}</style>`;
|
|
6525
6587
|
const descriptionHtml = description === "" ? "" : `
|
|
6526
6588
|
<p class="rshow-desc">${escapeHtml(description)}</p>`;
|
|
6527
6589
|
const listHtml = artifacts.length === 0 ? `
|
|
@@ -6593,7 +6655,8 @@ var CACHE_CONTROL2 = {
|
|
|
6593
6655
|
publicLive: "public, max-age=0, s-maxage=15, must-revalidate",
|
|
6594
6656
|
publicVersion: "public, max-age=31536000, immutable, s-maxage=300",
|
|
6595
6657
|
theme: "public, max-age=31536000, immutable",
|
|
6596
|
-
themeJson: "public, max-age=60"
|
|
6658
|
+
themeJson: "public, max-age=60",
|
|
6659
|
+
themeLatest: "public, max-age=60"
|
|
6597
6660
|
};
|
|
6598
6661
|
function viewerCsp(env) {
|
|
6599
6662
|
return [
|
|
@@ -6719,7 +6782,7 @@ async function handleViewer(c) {
|
|
|
6719
6782
|
if (identity.email === null) return loginRedirect(pathname, url.search);
|
|
6720
6783
|
return forbiddenPage(identity.email);
|
|
6721
6784
|
}
|
|
6722
|
-
const version = versionN === null ? access.version : access.version && access.version.n === versionN ? access.version : await versionFor(env,
|
|
6785
|
+
const version = versionN === null ? access.version : access.version && access.version.n === versionN ? access.version : await versionFor(env, artifact, versionN);
|
|
6723
6786
|
if (!version) return notFoundPage("This artifact has no such version yet.");
|
|
6724
6787
|
const relative = relativePath(parts);
|
|
6725
6788
|
if (relative === null) return notFoundPage();
|
|
@@ -6743,11 +6806,11 @@ async function handleTheme(c, rest, url) {
|
|
|
6743
6806
|
if (info) {
|
|
6744
6807
|
if (rest.length !== 1) return withoutBodyOnHead(c, jsonError("not found", 404));
|
|
6745
6808
|
const name2 = info[1];
|
|
6746
|
-
const
|
|
6747
|
-
if (!
|
|
6809
|
+
const latest2 = await accessFor(env, `${THEME_PREFIX}${name2}`);
|
|
6810
|
+
if (!latest2 || latest2.artifact.mode !== MODE.public || !latest2.version) {
|
|
6748
6811
|
return withoutBodyOnHead(c, jsonError("not found", 404));
|
|
6749
6812
|
}
|
|
6750
|
-
const version2 =
|
|
6813
|
+
const version2 = latest2.version.n;
|
|
6751
6814
|
const reply = { name: name2, version: version2, url: `/${THEMES_SEGMENT}/${name2}@${version2}/` };
|
|
6752
6815
|
return withoutBodyOnHead(
|
|
6753
6816
|
c,
|
|
@@ -6757,6 +6820,15 @@ async function handleTheme(c, rest, url) {
|
|
|
6757
6820
|
})
|
|
6758
6821
|
);
|
|
6759
6822
|
}
|
|
6823
|
+
const latest = THEME_LATEST_PATTERN.exec(segment);
|
|
6824
|
+
if (latest) {
|
|
6825
|
+
const name2 = latest[1];
|
|
6826
|
+
const access2 = await accessFor(env, `${THEME_PREFIX}${name2}`);
|
|
6827
|
+
if (!access2 || access2.artifact.mode !== MODE.public || !access2.version) return notFoundPage();
|
|
6828
|
+
const path = rest.slice(1).map(encodeURIComponent).join("/");
|
|
6829
|
+
const location = `/${THEMES_SEGMENT}/${name2}@${access2.version.n}/${path}${url.search}`;
|
|
6830
|
+
return withoutBodyOnHead(c, redirect(location, 302, CACHE_CONTROL2.themeLatest));
|
|
6831
|
+
}
|
|
6760
6832
|
const match2 = THEME_VERSION_PATTERN.exec(segment);
|
|
6761
6833
|
if (!match2) return notFoundPage();
|
|
6762
6834
|
const name = match2[1];
|
|
@@ -6767,7 +6839,7 @@ async function handleTheme(c, rest, url) {
|
|
|
6767
6839
|
if (!access || access.artifact.mode !== MODE.public) return notFoundPage();
|
|
6768
6840
|
const parts = rest.slice(1);
|
|
6769
6841
|
if (parts.length === 0) return redirect(`/${THEMES_SEGMENT}/${segment}/${url.search}`, 301);
|
|
6770
|
-
const version = access.version && access.version.n === n ? access.version : await versionFor(env,
|
|
6842
|
+
const version = access.version && access.version.n === n ? access.version : await versionFor(env, access.artifact, n);
|
|
6771
6843
|
if (!version) return notFoundPage("This theme has no such version yet.");
|
|
6772
6844
|
const relative = relativePath(parts);
|
|
6773
6845
|
if (relative === null) return notFoundPage();
|