rshow 0.0.7 → 0.0.9
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 +648 -82
- package/package.json +1 -1
- package/skills/rshow/SKILL.md +7 -5
- package/skills/rshow/reference/cli.md +13 -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 +5 -5
- package/skills/rshow/reference/sketch.md +5 -5
- package/skills/rshow/reference/terminal.md +5 -5
- package/skills/rshow-slides/SKILL.md +8 -10
- package/worker/index.js +127 -76
- package/worker/migrations/0005_version_counters.sql +3 -0
- package/worker/migrations/0006_retired_slugs.sql +5 -0
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();
|
|
@@ -3271,21 +3277,18 @@ __name(etagMatches, "etagMatches");
|
|
|
3271
3277
|
|
|
3272
3278
|
// src/cache.ts
|
|
3273
3279
|
var CACHE_ORIGIN = "https://rshow.internal";
|
|
3274
|
-
var ACCESS_TTL_MS = 15e3;
|
|
3275
|
-
var ACCESS_MAX_ENTRIES = 500;
|
|
3276
3280
|
var MANIFEST_MAX_ENTRIES = 200;
|
|
3277
3281
|
var VERSION_MAX_ENTRIES = 500;
|
|
3278
|
-
var ACCESS_CACHE_CONTROL = "max-age=
|
|
3282
|
+
var ACCESS_CACHE_CONTROL = "max-age=2";
|
|
3279
3283
|
var MANIFEST_CACHE_CONTROL = "public, max-age=31536000, immutable";
|
|
3280
3284
|
var VERSION_CACHE_CONTROL = "public, max-age=31536000, immutable";
|
|
3281
3285
|
var JSON_CONTENT_TYPE = "application/json";
|
|
3282
3286
|
var BLOB_CACHE_CONTROL = "public, max-age=31536000, immutable";
|
|
3283
|
-
var ARTIFACT_COLUMNS2 = "slug, title, owner, mode, pinned, latest, project, created_at, updated_at";
|
|
3287
|
+
var ARTIFACT_COLUMNS2 = "slug, title, owner, mode, pinned, latest, last_n, version_gen, project, created_at, updated_at";
|
|
3284
3288
|
var VERSION_COLUMNS = "slug, n, label, manifest_key, files, bytes, created_by, created_at";
|
|
3285
|
-
var accessMemory = /* @__PURE__ */ new Map();
|
|
3286
3289
|
var manifestMemory = /* @__PURE__ */ new Map();
|
|
3287
3290
|
var versionMemory = /* @__PURE__ */ new Map();
|
|
3288
|
-
var
|
|
3291
|
+
var versionKeys = /* @__PURE__ */ new Map();
|
|
3289
3292
|
var trackedKeys = /* @__PURE__ */ new Set();
|
|
3290
3293
|
function accessCacheKey(slug) {
|
|
3291
3294
|
return `${CACHE_ORIGIN}/access/${slug}`;
|
|
@@ -3299,24 +3302,14 @@ function blobCacheKey(hash) {
|
|
|
3299
3302
|
return `${CACHE_ORIGIN}/blob/${hash}`;
|
|
3300
3303
|
}
|
|
3301
3304
|
__name(blobCacheKey, "blobCacheKey");
|
|
3302
|
-
function versionCacheKey(slug, n) {
|
|
3303
|
-
return `${CACHE_ORIGIN}/version/${slug}/${n}`;
|
|
3305
|
+
function versionCacheKey(slug, n, gen) {
|
|
3306
|
+
return `${CACHE_ORIGIN}/version/${slug}/${n}/${gen}`;
|
|
3304
3307
|
}
|
|
3305
3308
|
__name(versionCacheKey, "versionCacheKey");
|
|
3306
|
-
function versionMemoryKey(slug, n) {
|
|
3307
|
-
return `${slug}/${n}`;
|
|
3309
|
+
function versionMemoryKey(slug, n, gen) {
|
|
3310
|
+
return `${slug}/${n}/${gen}`;
|
|
3308
3311
|
}
|
|
3309
3312
|
__name(versionMemoryKey, "versionMemoryKey");
|
|
3310
|
-
function rememberAccess(slug, value, expires) {
|
|
3311
|
-
accessMemory.delete(slug);
|
|
3312
|
-
accessMemory.set(slug, { value, expires });
|
|
3313
|
-
while (accessMemory.size > ACCESS_MAX_ENTRIES) {
|
|
3314
|
-
const oldest = accessMemory.keys().next().value;
|
|
3315
|
-
if (oldest === void 0) break;
|
|
3316
|
-
accessMemory.delete(oldest);
|
|
3317
|
-
}
|
|
3318
|
-
}
|
|
3319
|
-
__name(rememberAccess, "rememberAccess");
|
|
3320
3313
|
function rememberManifest(key, value) {
|
|
3321
3314
|
manifestMemory.delete(key);
|
|
3322
3315
|
manifestMemory.set(key, value);
|
|
@@ -3327,8 +3320,8 @@ function rememberManifest(key, value) {
|
|
|
3327
3320
|
}
|
|
3328
3321
|
}
|
|
3329
3322
|
__name(rememberManifest, "rememberManifest");
|
|
3330
|
-
function rememberVersion(slug, n, row) {
|
|
3331
|
-
const key = versionMemoryKey(slug, n);
|
|
3323
|
+
function rememberVersion(slug, n, gen, row) {
|
|
3324
|
+
const key = versionMemoryKey(slug, n, gen);
|
|
3332
3325
|
versionMemory.delete(key);
|
|
3333
3326
|
versionMemory.set(key, row);
|
|
3334
3327
|
while (versionMemory.size > VERSION_MAX_ENTRIES) {
|
|
@@ -3336,9 +3329,9 @@ function rememberVersion(slug, n, row) {
|
|
|
3336
3329
|
if (oldest === void 0) break;
|
|
3337
3330
|
versionMemory.delete(oldest);
|
|
3338
3331
|
}
|
|
3339
|
-
const
|
|
3340
|
-
|
|
3341
|
-
|
|
3332
|
+
const keys = versionKeys.get(slug) ?? /* @__PURE__ */ new Set();
|
|
3333
|
+
keys.add(versionCacheKey(slug, n, gen));
|
|
3334
|
+
versionKeys.set(slug, keys);
|
|
3342
3335
|
}
|
|
3343
3336
|
__name(rememberVersion, "rememberVersion");
|
|
3344
3337
|
function decodeVersion(text) {
|
|
@@ -3447,30 +3440,18 @@ async function loadAccess(env, slug) {
|
|
|
3447
3440
|
}
|
|
3448
3441
|
__name(loadAccess, "loadAccess");
|
|
3449
3442
|
async function accessFor(env, slug) {
|
|
3450
|
-
const now = Date.now();
|
|
3451
|
-
const memory = accessMemory.get(slug);
|
|
3452
|
-
if (memory && memory.expires > now) {
|
|
3453
|
-
rememberAccess(slug, memory.value, memory.expires);
|
|
3454
|
-
return memory.value;
|
|
3455
|
-
}
|
|
3456
|
-
accessMemory.delete(slug);
|
|
3457
3443
|
const key = accessCacheKey(slug);
|
|
3458
3444
|
const cached = await cacheGet(key);
|
|
3459
3445
|
if (cached) {
|
|
3460
3446
|
const decoded = decodeAccess(await cached.text());
|
|
3461
|
-
if (decoded.ok)
|
|
3462
|
-
rememberAccess(slug, decoded.value, now + ACCESS_TTL_MS);
|
|
3463
|
-
return decoded.value;
|
|
3464
|
-
}
|
|
3447
|
+
if (decoded.ok) return decoded.value;
|
|
3465
3448
|
}
|
|
3466
3449
|
const value = await loadAccess(env, slug);
|
|
3467
|
-
rememberAccess(slug, value, now + ACCESS_TTL_MS);
|
|
3468
3450
|
await cachePut(key, JSON.stringify(value), ACCESS_CACHE_CONTROL);
|
|
3469
3451
|
return value;
|
|
3470
3452
|
}
|
|
3471
3453
|
__name(accessFor, "accessFor");
|
|
3472
3454
|
async function invalidateAccess(slug) {
|
|
3473
|
-
accessMemory.delete(slug);
|
|
3474
3455
|
const key = accessCacheKey(slug);
|
|
3475
3456
|
trackedKeys.delete(key);
|
|
3476
3457
|
try {
|
|
@@ -3487,34 +3468,38 @@ async function invalidateProjectAccess(env, project) {
|
|
|
3487
3468
|
}
|
|
3488
3469
|
}
|
|
3489
3470
|
__name(invalidateProjectAccess, "invalidateProjectAccess");
|
|
3490
|
-
async function versionFor(env,
|
|
3491
|
-
const
|
|
3471
|
+
async function versionFor(env, artifact, n) {
|
|
3472
|
+
const gen = artifact.version_gen;
|
|
3473
|
+
const memory = versionMemory.get(versionMemoryKey(artifact.slug, n, gen));
|
|
3492
3474
|
if (memory) {
|
|
3493
|
-
rememberVersion(slug, n, memory);
|
|
3475
|
+
rememberVersion(artifact.slug, n, gen, memory);
|
|
3494
3476
|
return memory;
|
|
3495
3477
|
}
|
|
3496
|
-
const
|
|
3478
|
+
const key = versionCacheKey(artifact.slug, n, gen);
|
|
3479
|
+
const cached = await cacheGet(key);
|
|
3497
3480
|
if (cached) {
|
|
3498
3481
|
const row2 = decodeVersion(await cached.text());
|
|
3499
3482
|
if (row2) {
|
|
3500
|
-
rememberVersion(slug, n, row2);
|
|
3483
|
+
rememberVersion(artifact.slug, n, gen, row2);
|
|
3501
3484
|
return row2;
|
|
3502
3485
|
}
|
|
3503
3486
|
}
|
|
3504
|
-
const row = await getVersion(env, slug, n);
|
|
3487
|
+
const row = await getVersion(env, artifact.slug, n);
|
|
3505
3488
|
if (!row) return null;
|
|
3506
|
-
rememberVersion(slug, n, row);
|
|
3507
|
-
await cachePut(
|
|
3489
|
+
rememberVersion(artifact.slug, n, gen, row);
|
|
3490
|
+
await cachePut(key, JSON.stringify(row), VERSION_CACHE_CONTROL);
|
|
3508
3491
|
return row;
|
|
3509
3492
|
}
|
|
3510
3493
|
__name(versionFor, "versionFor");
|
|
3511
3494
|
async function invalidateVersions(slug) {
|
|
3512
|
-
const
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3495
|
+
const prefix = `${slug}/`;
|
|
3496
|
+
for (const key of [...versionMemory.keys()]) {
|
|
3497
|
+
if (key.startsWith(prefix)) versionMemory.delete(key);
|
|
3498
|
+
}
|
|
3499
|
+
const keys = versionKeys.get(slug);
|
|
3500
|
+
versionKeys.delete(slug);
|
|
3501
|
+
if (!keys) return;
|
|
3502
|
+
for (const key of keys) {
|
|
3518
3503
|
trackedKeys.delete(key);
|
|
3519
3504
|
try {
|
|
3520
3505
|
await caches.default.delete(key);
|
|
@@ -3796,7 +3781,7 @@ async function storeVersion(env, artifact, input2) {
|
|
|
3796
3781
|
env.DB.prepare(
|
|
3797
3782
|
"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
3783
|
).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(
|
|
3784
|
+
env.DB.prepare("UPDATE artifacts SET latest = ?2, last_n = ?2, updated_at = ?3 WHERE slug = ?1 AND latest IS ?4").bind(
|
|
3800
3785
|
artifact.slug,
|
|
3801
3786
|
input2.n,
|
|
3802
3787
|
input2.now,
|
|
@@ -3841,6 +3826,52 @@ function registerVersions(app) {
|
|
|
3841
3826
|
if (!manifest) return jsonError("no such version", 404);
|
|
3842
3827
|
return json(toVersionDetail(c.env, access.artifact.slug, version, manifest));
|
|
3843
3828
|
});
|
|
3829
|
+
app.delete("/api/artifacts/:slug/versions/:n", async (c) => {
|
|
3830
|
+
const email = c.get("identity").email;
|
|
3831
|
+
const access = await loadAccessible(c.env, c.req.param("slug"), email);
|
|
3832
|
+
if (!access) return jsonError("not found", 404);
|
|
3833
|
+
if (!canManage(access.artifact, access.shares, actorOf(c), access.project)) {
|
|
3834
|
+
return jsonError("owner access is required", 403);
|
|
3835
|
+
}
|
|
3836
|
+
const raw2 = c.req.param("n");
|
|
3837
|
+
if (!/^[1-9][0-9]{0,8}$/.test(raw2)) return jsonError("no such version", 404);
|
|
3838
|
+
const n = Number(raw2);
|
|
3839
|
+
const slug = access.artifact.slug;
|
|
3840
|
+
if (slug.startsWith(THEME_PREFIX) && c.req.query("force") !== "1") {
|
|
3841
|
+
return jsonError("pages link to theme versions; pass force to delete it", 409);
|
|
3842
|
+
}
|
|
3843
|
+
const version = await getVersion(c.env, slug, n);
|
|
3844
|
+
if (!version) return jsonError("no such version", 404);
|
|
3845
|
+
if (access.artifact.pinned === n) {
|
|
3846
|
+
return jsonError(`version ${n} is pinned; pin another version first`, 409);
|
|
3847
|
+
}
|
|
3848
|
+
if (await countVersions(c.env, slug) <= 1) {
|
|
3849
|
+
return jsonError("this is the only version; delete the artifact instead", 409);
|
|
3850
|
+
}
|
|
3851
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3852
|
+
let deletedChanges = 0;
|
|
3853
|
+
try {
|
|
3854
|
+
const [deleted] = await c.env.DB.batch([
|
|
3855
|
+
c.env.DB.prepare(
|
|
3856
|
+
"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"
|
|
3857
|
+
).bind(slug, n),
|
|
3858
|
+
c.env.DB.prepare(
|
|
3859
|
+
"UPDATE artifacts SET latest = (SELECT MAX(n) FROM versions WHERE slug = ?1), version_gen = version_gen + 1, updated_at = ?2 WHERE slug = ?1"
|
|
3860
|
+
).bind(slug, now)
|
|
3861
|
+
]);
|
|
3862
|
+
deletedChanges = deleted?.meta?.changes ?? 0;
|
|
3863
|
+
} catch {
|
|
3864
|
+
return jsonError("could not delete the version", 500);
|
|
3865
|
+
}
|
|
3866
|
+
if (deletedChanges !== 1) return jsonError("another change came first, retry", 409);
|
|
3867
|
+
await discardManifest(c.env, version.manifest_key);
|
|
3868
|
+
await invalidateVersions(slug);
|
|
3869
|
+
await invalidateAccess(slug);
|
|
3870
|
+
const updated = await getArtifact(c.env, slug);
|
|
3871
|
+
if (!updated) return jsonError("not found", 404);
|
|
3872
|
+
const tags = await getArtifactTags(c.env, slug);
|
|
3873
|
+
return json(toArtifact(c.env, updated, access.role, tags));
|
|
3874
|
+
});
|
|
3844
3875
|
app.post("/api/artifacts/:slug/versions", async (c) => {
|
|
3845
3876
|
const body = await readBody(c);
|
|
3846
3877
|
if (!body.ok) return body.response;
|
|
@@ -3882,7 +3913,7 @@ function registerVersions(app) {
|
|
|
3882
3913
|
}
|
|
3883
3914
|
const label = typeof body.body.label === "string" ? body.body.label.trim().slice(0, 200) : "";
|
|
3884
3915
|
const base = access.artifact.latest;
|
|
3885
|
-
const n = (base ?? 0) + 1;
|
|
3916
|
+
const n = Math.max(access.artifact.last_n, base ?? 0) + 1;
|
|
3886
3917
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3887
3918
|
const stored = await storeVersion(c.env, access.artifact, {
|
|
3888
3919
|
label,
|
|
@@ -3980,10 +4011,11 @@ function registerArtifactCreate(app) {
|
|
|
3980
4011
|
const existing = await getArtifact(c.env, slug);
|
|
3981
4012
|
if (existing) return jsonError("slug is already taken", 409);
|
|
3982
4013
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4014
|
+
let counters;
|
|
3983
4015
|
try {
|
|
3984
|
-
await c.env.DB.prepare(
|
|
3985
|
-
"INSERT INTO artifacts (slug, title, owner, mode, pinned, latest, project, created_at, updated_at)
|
|
3986
|
-
).bind(slug, title2, email, MODE.private, project, now).
|
|
4016
|
+
counters = await c.env.DB.prepare(
|
|
4017
|
+
"INSERT INTO artifacts (slug, title, owner, mode, pinned, latest, last_n, version_gen, project, created_at, updated_at) SELECT ?1, ?2, ?3, ?4, NULL, NULL, COALESCE((SELECT last_n FROM retired_slugs WHERE slug = ?1), 0), COALESCE((SELECT version_gen FROM retired_slugs WHERE slug = ?1), 0), ?5, ?6, ?6 RETURNING last_n, version_gen"
|
|
4018
|
+
).bind(slug, title2, email, MODE.private, project, now).first();
|
|
3987
4019
|
} catch {
|
|
3988
4020
|
return jsonError("slug is already taken", 409);
|
|
3989
4021
|
}
|
|
@@ -3999,6 +4031,8 @@ function registerArtifactCreate(app) {
|
|
|
3999
4031
|
mode: MODE.private,
|
|
4000
4032
|
pinned: null,
|
|
4001
4033
|
latest: null,
|
|
4034
|
+
last_n: counters?.last_n ?? 0,
|
|
4035
|
+
version_gen: counters?.version_gen ?? 0,
|
|
4002
4036
|
project,
|
|
4003
4037
|
created_at: now,
|
|
4004
4038
|
updated_at: now
|
|
@@ -4138,6 +4172,9 @@ function registerArtifactItem(app) {
|
|
|
4138
4172
|
if (!canManage(access.artifact, access.shares, actorOf(c), access.project)) {
|
|
4139
4173
|
return jsonError("owner access is required", 403);
|
|
4140
4174
|
}
|
|
4175
|
+
if (access.artifact.slug.startsWith(THEME_PREFIX) && c.req.query("force") !== "1") {
|
|
4176
|
+
return jsonError("pages link to this theme; pass force to delete it", 409);
|
|
4177
|
+
}
|
|
4141
4178
|
const prefix = `manifests/${access.artifact.slug}/`;
|
|
4142
4179
|
const keys = [];
|
|
4143
4180
|
let cursor;
|
|
@@ -4149,6 +4186,9 @@ function registerArtifactItem(app) {
|
|
|
4149
4186
|
if (keys.length > 0) await c.env.BUCKET.delete(keys);
|
|
4150
4187
|
await deleteArtifactData(c.env, access.artifact.slug);
|
|
4151
4188
|
await c.env.DB.batch([
|
|
4189
|
+
c.env.DB.prepare(
|
|
4190
|
+
"INSERT INTO retired_slugs (slug, last_n, version_gen) SELECT slug, MAX(last_n, COALESCE(latest, 0)), version_gen + 1 FROM artifacts WHERE slug = ?1 ON CONFLICT(slug) DO UPDATE SET last_n = excluded.last_n, version_gen = excluded.version_gen"
|
|
4191
|
+
).bind(access.artifact.slug),
|
|
4152
4192
|
c.env.DB.prepare("DELETE FROM page_data WHERE slug = ?1").bind(access.artifact.slug),
|
|
4153
4193
|
c.env.DB.prepare("DELETE FROM artifact_tags WHERE slug = ?1").bind(access.artifact.slug),
|
|
4154
4194
|
c.env.DB.prepare("DELETE FROM versions WHERE slug = ?1").bind(access.artifact.slug),
|
|
@@ -4498,7 +4538,7 @@ __name(registerApi, "registerApi");
|
|
|
4498
4538
|
|
|
4499
4539
|
// src/static.generated.ts
|
|
4500
4540
|
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';
|
|
4541
|
+
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
4542
|
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
4543
|
|
|
4504
4544
|
// src/static.ts
|
|
@@ -5403,6 +5443,7 @@ function VersionRowView(props) {
|
|
|
5403
5443
|
const version = props.version;
|
|
5404
5444
|
const served = props.artifact.served === version.n;
|
|
5405
5445
|
const pinned = props.artifact.pinned === version.n;
|
|
5446
|
+
const deletable = props.owner && !props.artifact.slug.startsWith(THEME_PREFIX) && !pinned && props.count > 1;
|
|
5406
5447
|
return /* @__PURE__ */ jsxDEV("li", { class: served ? "version-row is-served" : "version-row", children: [
|
|
5407
5448
|
/* @__PURE__ */ jsxDEV("span", { class: "version-n mono", children: [
|
|
5408
5449
|
"v",
|
|
@@ -5423,7 +5464,8 @@ function VersionRowView(props) {
|
|
|
5423
5464
|
] }),
|
|
5424
5465
|
served ? /* @__PURE__ */ jsxDEV("span", { class: "pill pill-live", children: pinned ? "Pinned, live" : "Live" }) : null,
|
|
5425
5466
|
/* @__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
|
|
5467
|
+
props.owner && !served ? /* @__PURE__ */ jsxDEV("button", { type: "button", class: "btn-link", "data-action": "pin", "data-n": version.n, children: "Pin here" }) : null,
|
|
5468
|
+
deletable ? /* @__PURE__ */ jsxDEV("button", { type: "button", class: "btn-link", "data-action": "delete-version", "data-n": version.n, children: "Delete" }) : null
|
|
5427
5469
|
] });
|
|
5428
5470
|
}
|
|
5429
5471
|
__name(VersionRowView, "VersionRowView");
|
|
@@ -5557,7 +5599,7 @@ function artifactPage(env, email, artifact, versions, shares, project, editableP
|
|
|
5557
5599
|
/* @__PURE__ */ jsxDEV("h2", { children: "Versions" }),
|
|
5558
5600
|
/* @__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
5601
|
] }),
|
|
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 })) }),
|
|
5602
|
+
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
5603
|
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
5604
|
]
|
|
5563
5605
|
}
|
|
@@ -5627,7 +5669,7 @@ function artifactPage(env, email, artifact, versions, shares, project, editableP
|
|
|
5627
5669
|
] }) : null,
|
|
5628
5670
|
owner ? /* @__PURE__ */ jsxDEV("div", { class: "danger-zone", children: [
|
|
5629
5671
|
/* @__PURE__ */ jsxDEV("h2", { children: "Danger zone" }),
|
|
5630
|
-
/* @__PURE__ */ jsxDEV(
|
|
5672
|
+
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
5673
|
"button",
|
|
5632
5674
|
{
|
|
5633
5675
|
type: "button",
|
|
@@ -6503,7 +6545,6 @@ __name(loginRedirect, "loginRedirect");
|
|
|
6503
6545
|
|
|
6504
6546
|
// src/project.ts
|
|
6505
6547
|
var THEME_SLUG = `${THEME_PREFIX}default`;
|
|
6506
|
-
var THEME_VERSION = 3;
|
|
6507
6548
|
var MODE_WORDS = {
|
|
6508
6549
|
private: "Private",
|
|
6509
6550
|
people: "People",
|
|
@@ -6513,15 +6554,15 @@ var LIST_STYLE = ".rshow-project{max-width:44rem}.rshow-project h1{margin-bottom
|
|
|
6513
6554
|
async function defaultThemeReady(env) {
|
|
6514
6555
|
const artifact = await env.DB.prepare("SELECT mode FROM artifacts WHERE slug = ?1").bind(THEME_SLUG).first();
|
|
6515
6556
|
if (!artifact || artifact.mode !== MODE.public) return false;
|
|
6516
|
-
const version = await env.DB.prepare("SELECT n FROM versions WHERE slug = ?1
|
|
6557
|
+
const version = await env.DB.prepare("SELECT n FROM versions WHERE slug = ?1 LIMIT 1").bind(THEME_SLUG).first();
|
|
6517
6558
|
return version !== null;
|
|
6518
6559
|
}
|
|
6519
6560
|
__name(defaultThemeReady, "defaultThemeReady");
|
|
6520
6561
|
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@
|
|
6562
|
+
const links = theme ? `<link rel="stylesheet" href="/_themes/default@latest/fonts.css">
|
|
6563
|
+
<link rel="stylesheet" href="/_themes/default@latest/tokens.css">
|
|
6564
|
+
<link rel="stylesheet" href="/_themes/default@latest/page.css">
|
|
6565
|
+
<script src="/_themes/default@latest/mode.js"><\/script>` : `<style>${MINIMAL_STYLE}</style>`;
|
|
6525
6566
|
const descriptionHtml = description === "" ? "" : `
|
|
6526
6567
|
<p class="rshow-desc">${escapeHtml(description)}</p>`;
|
|
6527
6568
|
const listHtml = artifacts.length === 0 ? `
|
|
@@ -6593,7 +6634,8 @@ var CACHE_CONTROL2 = {
|
|
|
6593
6634
|
publicLive: "public, max-age=0, s-maxage=15, must-revalidate",
|
|
6594
6635
|
publicVersion: "public, max-age=31536000, immutable, s-maxage=300",
|
|
6595
6636
|
theme: "public, max-age=31536000, immutable",
|
|
6596
|
-
themeJson: "public, max-age=60"
|
|
6637
|
+
themeJson: "public, max-age=60",
|
|
6638
|
+
themeLatest: "public, max-age=60"
|
|
6597
6639
|
};
|
|
6598
6640
|
function viewerCsp(env) {
|
|
6599
6641
|
return [
|
|
@@ -6719,7 +6761,7 @@ async function handleViewer(c) {
|
|
|
6719
6761
|
if (identity.email === null) return loginRedirect(pathname, url.search);
|
|
6720
6762
|
return forbiddenPage(identity.email);
|
|
6721
6763
|
}
|
|
6722
|
-
const version = versionN === null ? access.version : access.version && access.version.n === versionN ? access.version : await versionFor(env,
|
|
6764
|
+
const version = versionN === null ? access.version : access.version && access.version.n === versionN ? access.version : await versionFor(env, artifact, versionN);
|
|
6723
6765
|
if (!version) return notFoundPage("This artifact has no such version yet.");
|
|
6724
6766
|
const relative = relativePath(parts);
|
|
6725
6767
|
if (relative === null) return notFoundPage();
|
|
@@ -6743,11 +6785,11 @@ async function handleTheme(c, rest, url) {
|
|
|
6743
6785
|
if (info) {
|
|
6744
6786
|
if (rest.length !== 1) return withoutBodyOnHead(c, jsonError("not found", 404));
|
|
6745
6787
|
const name2 = info[1];
|
|
6746
|
-
const
|
|
6747
|
-
if (!
|
|
6788
|
+
const latest2 = await accessFor(env, `${THEME_PREFIX}${name2}`);
|
|
6789
|
+
if (!latest2 || latest2.artifact.mode !== MODE.public || !latest2.version) {
|
|
6748
6790
|
return withoutBodyOnHead(c, jsonError("not found", 404));
|
|
6749
6791
|
}
|
|
6750
|
-
const version2 =
|
|
6792
|
+
const version2 = latest2.version.n;
|
|
6751
6793
|
const reply = { name: name2, version: version2, url: `/${THEMES_SEGMENT}/${name2}@${version2}/` };
|
|
6752
6794
|
return withoutBodyOnHead(
|
|
6753
6795
|
c,
|
|
@@ -6757,6 +6799,15 @@ async function handleTheme(c, rest, url) {
|
|
|
6757
6799
|
})
|
|
6758
6800
|
);
|
|
6759
6801
|
}
|
|
6802
|
+
const latest = THEME_LATEST_PATTERN.exec(segment);
|
|
6803
|
+
if (latest) {
|
|
6804
|
+
const name2 = latest[1];
|
|
6805
|
+
const access2 = await accessFor(env, `${THEME_PREFIX}${name2}`);
|
|
6806
|
+
if (!access2 || access2.artifact.mode !== MODE.public || !access2.version) return notFoundPage();
|
|
6807
|
+
const path = rest.slice(1).map(encodeURIComponent).join("/");
|
|
6808
|
+
const location = `/${THEMES_SEGMENT}/${name2}@${access2.version.n}/${path}${url.search}`;
|
|
6809
|
+
return withoutBodyOnHead(c, redirect(location, 302, CACHE_CONTROL2.themeLatest));
|
|
6810
|
+
}
|
|
6760
6811
|
const match2 = THEME_VERSION_PATTERN.exec(segment);
|
|
6761
6812
|
if (!match2) return notFoundPage();
|
|
6762
6813
|
const name = match2[1];
|
|
@@ -6767,7 +6818,7 @@ async function handleTheme(c, rest, url) {
|
|
|
6767
6818
|
if (!access || access.artifact.mode !== MODE.public) return notFoundPage();
|
|
6768
6819
|
const parts = rest.slice(1);
|
|
6769
6820
|
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,
|
|
6821
|
+
const version = access.version && access.version.n === n ? access.version : await versionFor(env, access.artifact, n);
|
|
6771
6822
|
if (!version) return notFoundPage("This theme has no such version yet.");
|
|
6772
6823
|
const relative = relativePath(parts);
|
|
6773
6824
|
if (relative === null) return notFoundPage();
|