artifacty 0.13.1 → 0.15.0
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/README.ko.md +748 -0
- package/README.md +40 -2
- package/SECURITY.ko.md +55 -0
- package/SECURITY.md +2 -0
- package/docs/README.md +39 -0
- package/docs/artifact-schema-v1.md +2 -0
- package/docs/claude-artifact-formats.md +652 -0
- package/docs/integrations.md +2 -0
- package/docs/ko/README.md +39 -0
- package/docs/ko/artifact-schema-v1.md +238 -0
- package/docs/ko/integrations.md +400 -0
- package/docs/ko/mcp-public-api.md +121 -0
- package/docs/ko/network-sharing.md +60 -0
- package/docs/ko/release-checklist.md +72 -0
- package/docs/ko/threat-model.md +275 -0
- package/docs/ko/upgrade-backups.md +152 -0
- package/docs/ko/writing-style.md +60 -0
- package/docs/mcp-public-api.md +2 -0
- package/docs/network-sharing.md +2 -0
- package/docs/release-checklist.md +2 -0
- package/docs/threat-model.md +2 -0
- package/docs/upgrade-backups.md +61 -0
- package/docs/writing-style.md +2 -0
- package/package.json +7 -2
- package/src/cli.js +22 -1
- package/src/client/editor.js +75 -20
- package/src/client/update.js +33 -0
- package/src/lib/background.js +3 -1
- package/src/lib/i18n.js +106 -0
- package/src/lib/render.js +378 -272
- package/src/lib/sample-content.js +6 -6
- package/src/lib/self-update.js +351 -0
- package/src/lib/web-update.js +177 -0
- package/src/server.js +39 -4
- package/src/update-runner.js +7 -0
package/src/lib/render.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
1
2
|
import { EDITOR_CLIENT_PATH, VIEWER_CLIENT_PATH, editorImportMapJson } from "./editor-assets.js";
|
|
2
3
|
import { createI18n, DEFAULT_LOCALE, editorMessages, localizedHref, switchLocaleHref } from "./i18n.js";
|
|
3
4
|
import { ARTIFACT_FORMATS, ARTIFACT_TYPES, REVIEW_STATUSES, SAVED_VIEW_FILTER_KEYS, VISIBILITY_VALUES } from "./storage.js";
|
|
@@ -12,62 +13,62 @@ import {
|
|
|
12
13
|
import { isNotebookObject } from "./converters.js";
|
|
13
14
|
import { GROUP_BY_VALUES, groupKeyFor } from "./listing.js";
|
|
14
15
|
|
|
16
|
+
const APP_VERSION = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8")).version;
|
|
17
|
+
|
|
15
18
|
export function renderDashboard({ artifacts, baseUrl, filters = {}, pagination, locale = DEFAULT_LOCALE, currentPath = "/", user = null, savedViews = [], embeddingsAvailable = false }) {
|
|
16
19
|
const view = viewContext(locale, currentPath);
|
|
17
20
|
const total = pagination?.total ?? artifacts.length;
|
|
18
21
|
const start = artifacts.length ? (pagination?.offset ?? 0) + 1 : 0;
|
|
19
22
|
const end = artifacts.length ? (pagination?.offset ?? 0) + artifacts.length : 0;
|
|
20
|
-
const searchBackend = pagination?.search?.backend;
|
|
21
23
|
const pager = renderDashboardPager({ pagination, filters, view });
|
|
22
24
|
const rowsHtml = renderArtifactRows(artifacts, filters.groupBy, view);
|
|
23
|
-
const
|
|
25
|
+
const activeFilters = [
|
|
26
|
+
["tag", "filter.tag"], ["sourceAgent", "filter.source"], ["artifactType", "filter.artifactType"],
|
|
27
|
+
["publisher", "filter.publisher"], ["createdAfter", "filter.createdAfter"], ["createdBefore", "filter.createdBefore"],
|
|
28
|
+
["reviewStatus", "filter.reviewStatus"], ["mode", "filter.mode"], ["groupBy", "filter.groupBy"],
|
|
29
|
+
["relatedTo", "filter.relatedTo"], ["relation", "filter.relation"], ["includeArchived", "filter.archived"]
|
|
30
|
+
].filter(([key]) => filters[key]);
|
|
31
|
+
const field = (key, label, type = "text") => `<label class="dashboard-field"><span>${view.text(label)}</span><input type="${type}" name="${key}" value="${escapeAttribute(filters[key] || "")}"></label>`;
|
|
32
|
+
const selectField = (label, select) => `<label class="dashboard-field"><span>${view.text(label)}</span>${select}</label>`;
|
|
24
33
|
|
|
25
34
|
return pageShell({
|
|
26
35
|
title: "Artifacty",
|
|
27
36
|
body: `
|
|
28
|
-
|
|
29
|
-
<a class="brand" href="${view.href("/")}">
|
|
30
|
-
${brandMark()}
|
|
31
|
-
<span class="brand-text">
|
|
32
|
-
<span class="brand-name">artifacty</span>
|
|
33
|
-
<span class="brand-tag">local artifact exchange · ${escapeHtml(baseUrl)}</span>
|
|
34
|
-
</span>
|
|
35
|
-
</a>
|
|
36
|
-
<nav>
|
|
37
|
-
<a href="${view.href("/new")}">${view.text("nav.new")}</a>
|
|
38
|
-
<a href="${view.href("/import")}">${view.text("nav.import")}</a>
|
|
39
|
-
<a href="/api/artifacts">${view.text("nav.api")}</a>
|
|
40
|
-
${authNav(user, view)}
|
|
41
|
-
${languageSwitcher(view)}
|
|
42
|
-
</nav>
|
|
43
|
-
</header>
|
|
37
|
+
${workspaceHeader(user, view)}
|
|
44
38
|
<div class="dashboard-layout">
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
<section class="toolbar">
|
|
48
|
-
<span>${view.text("dashboard.range", { start, end, total })}</span>
|
|
49
|
-
${searchBackend ? `<span>${view.text("dashboard.searchBackend", { backend: searchBackend })}</span>` : ""}
|
|
50
|
-
</section>
|
|
39
|
+
<main class="dashboard" id="main-content">
|
|
40
|
+
<div class="dashboard-heading"><h1>${view.text("nav.artifacts")}</h1><nav><a class="button-link" href="${view.href("/new")}">${view.text("nav.new")}</a><a href="${view.href("/import")}">${view.text("nav.import")}</a></nav></div>
|
|
51
41
|
<form class="filter-form" method="get" action="/">
|
|
52
42
|
${localeInput(view.locale)}
|
|
53
43
|
${filters.limit ? `<input type="hidden" name="limit" value="${escapeAttribute(filters.limit)}">` : ""}
|
|
54
|
-
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
<
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
44
|
+
${!embeddingsAvailable && filters.mode ? `<input type="hidden" name="mode" value="${escapeAttribute(filters.mode)}">` : ""}
|
|
45
|
+
<div class="dashboard-search-row">
|
|
46
|
+
<label class="dashboard-search"><span class="dashboard-sr-only">${view.text("filter.search")}</span><input type="search" name="q" value="${escapeAttribute(filters.query || "")}" placeholder="${view.attr("dashboard.searchPlaceholder")}"></label>
|
|
47
|
+
<button type="submit">${view.text("filter.search")}</button>
|
|
48
|
+
<a class="dashboard-clear" href="${view.href("/")}">${view.text("filter.clear")}</a>
|
|
49
|
+
</div>
|
|
50
|
+
<details class="dashboard-advanced">
|
|
51
|
+
<summary>${view.text("dashboard.advanced")}${activeFilters.length ? `<span class="dashboard-filter-count">${view.text("dashboard.activeFilters", { count: activeFilters.length })}</span>` : ""}</summary>
|
|
52
|
+
<div class="dashboard-filter-grid">
|
|
53
|
+
${field("tag", "filter.tag")}${field("sourceAgent", "filter.source")}
|
|
54
|
+
${selectField("filter.artifactType", filterArtifactTypeSelect(filters.artifactType, view))}
|
|
55
|
+
${field("publisher", "filter.publisher")}
|
|
56
|
+
${field("createdAfter", "filter.createdAfter", "date")}${field("createdBefore", "filter.createdBefore", "date")}
|
|
57
|
+
${selectField("filter.reviewStatus", filterReviewStatusSelect(filters.reviewStatus, view))}
|
|
58
|
+
${embeddingsAvailable ? selectField("filter.mode", filterModeSelect(filters.mode, view)) : ""}
|
|
59
|
+
${selectField("filter.groupBy", filterGroupBySelect(filters.groupBy, view))}
|
|
60
|
+
${field("relatedTo", "filter.relatedTo")}${field("relation", "filter.relation")}
|
|
61
|
+
<label class="checkbox-field"><input type="checkbox" name="includeArchived" value="true"${filters.includeArchived ? " checked" : ""}> ${view.text("filter.archived")}</label>
|
|
62
|
+
<div class="dashboard-filter-actions"><button type="submit">${view.text("filter.submit")}</button></div>
|
|
63
|
+
</div>
|
|
64
|
+
</details>
|
|
65
|
+
${activeFilters.length ? `<p class="dashboard-active-filters">${activeFilters.map(([key, label]) => `<span>${view.text(label)}${key === "includeArchived" ? "" : `: ${escapeHtml(filters[key])}`}</span>`).join("")}</p>` : ""}
|
|
67
66
|
</form>
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
${renderSavedViewsSidebar({ savedViews, filters, view })}
|
|
68
|
+
<section class="toolbar dashboard-results">
|
|
69
|
+
<span>${view.text("dashboard.range", { start, end, total })}</span>
|
|
70
70
|
</section>
|
|
71
|
+
<section class="artifact-list">${rowsHtml || `<div class="empty">${view.text("dashboard.empty")}</div>`}</section>
|
|
71
72
|
${pager}
|
|
72
73
|
</main>
|
|
73
74
|
</div>
|
|
@@ -88,14 +89,11 @@ function renderArtifactRow(artifact, view) {
|
|
|
88
89
|
<a class="artifact-row" href="${view.href(`/artifacts/${encodeURIComponent(artifact.id)}`)}">
|
|
89
90
|
<span class="row-main">
|
|
90
91
|
<strong>${escapeHtml(artifact.title)}</strong>
|
|
91
|
-
<span>${escapeHtml(artifact.id)}</span>
|
|
92
|
+
<span class="dashboard-sr-only">${escapeHtml(artifact.id)}</span>
|
|
92
93
|
${snippet}
|
|
93
94
|
</span>
|
|
94
|
-
<span>${escapeHtml(artifact.sourceAgent)}</span>
|
|
95
|
-
<span class="
|
|
96
|
-
${typeBadge(artifact.artifactType || "document")}
|
|
97
|
-
${formatBadge(artifact.format || "text")}
|
|
98
|
-
<span>v${artifact.latestVersion}</span>
|
|
95
|
+
<span class="row-agent"><span>${escapeHtml(artifact.sourceAgent)}</span>${artifact.publisherId || artifact.publisherName ? `<span class="publisher-cell" title="${escapeAttribute(publisherTitle(artifact, view))}">${escapeHtml(publisher)}</span>` : ""}</span>
|
|
96
|
+
<span class="row-formats">${typeBadge(artifact.artifactType || "document")}${formatBadge(artifact.format || "text")}<span>v${artifact.latestVersion}</span></span>
|
|
99
97
|
<span class="tags">${status}${visibility}${tags}</span>
|
|
100
98
|
</a>
|
|
101
99
|
`;
|
|
@@ -188,17 +186,19 @@ function renderSavedViewsSidebar({ savedViews, filters, view }) {
|
|
|
188
186
|
: `<li class="empty">${view.text("views.empty")}</li>`;
|
|
189
187
|
|
|
190
188
|
return `
|
|
191
|
-
<
|
|
192
|
-
<
|
|
189
|
+
<details class="dashboard-saved-views">
|
|
190
|
+
<summary>${view.text("views.title")} <span class="dashboard-filter-count">${savedViews.length}</span></summary>
|
|
191
|
+
<div class="dashboard-saved-content">
|
|
193
192
|
<ul class="saved-view-list">${items}</ul>
|
|
194
193
|
<form method="post" action="${view.href("/views")}" class="saved-view-form">
|
|
195
194
|
${localeInput(view.locale)}
|
|
196
195
|
${savedViewFilterHiddenInputs(filters)}
|
|
197
|
-
<input name="name" placeholder="${view.attr("views.namePlaceholder")}" required>
|
|
196
|
+
<label class="dashboard-field"><span>${view.text("views.namePlaceholder")}</span><input name="name" placeholder="${view.attr("views.namePlaceholder")}" required></label>
|
|
198
197
|
<label class="checkbox-field"><input type="checkbox" name="shared" value="true"> ${view.text("views.shared")}</label>
|
|
199
198
|
<button type="submit">${view.text("views.save")}</button>
|
|
200
199
|
</form>
|
|
201
|
-
|
|
200
|
+
</div>
|
|
201
|
+
</details>
|
|
202
202
|
`;
|
|
203
203
|
}
|
|
204
204
|
|
|
@@ -260,7 +260,7 @@ function dashboardPageHref(filters, offset) {
|
|
|
260
260
|
return query ? `/?${query}` : "/";
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
-
export function renderArtifactFormPage({ mode, baseUrl, artifact, version, content, authToken = "", locale = DEFAULT_LOCALE, currentPath = "/", expectedVersion, conflict = null }) {
|
|
263
|
+
export function renderArtifactFormPage({ user, mode, baseUrl, artifact, version, content, authToken = "", locale = DEFAULT_LOCALE, currentPath = "/", expectedVersion, conflict = null }) {
|
|
264
264
|
const view = viewContext(locale, currentPath);
|
|
265
265
|
const isEdit = mode === "edit";
|
|
266
266
|
const title = isEdit ? view.raw("form.editTitle", { title: artifact.title }) : view.raw("form.newTitle");
|
|
@@ -281,6 +281,7 @@ export function renderArtifactFormPage({ mode, baseUrl, artifact, version, conte
|
|
|
281
281
|
return pageShell({
|
|
282
282
|
title,
|
|
283
283
|
body: `
|
|
284
|
+
${workspaceHeader(user, view)}
|
|
284
285
|
<header class="topbar">
|
|
285
286
|
<div>
|
|
286
287
|
<h1>${escapeHtml(title)}</h1>
|
|
@@ -288,10 +289,10 @@ export function renderArtifactFormPage({ mode, baseUrl, artifact, version, conte
|
|
|
288
289
|
</div>
|
|
289
290
|
<nav>
|
|
290
291
|
<a href="${view.href(isEdit ? `/artifacts/${encodeURIComponent(artifact.id)}` : "/")}">${view.text("nav.cancel")}</a>
|
|
291
|
-
|
|
292
|
+
|
|
292
293
|
</nav>
|
|
293
294
|
</header>
|
|
294
|
-
<main class="artifact-editor">
|
|
295
|
+
<main id="main-content" class="artifact-editor">
|
|
295
296
|
${conflictBanner}
|
|
296
297
|
<form class="editor-form" method="post" action="${action}">
|
|
297
298
|
${hiddenToken(authToken)}
|
|
@@ -340,8 +341,8 @@ export function renderArtifactFormPage({ mode, baseUrl, artifact, version, conte
|
|
|
340
341
|
});
|
|
341
342
|
}
|
|
342
343
|
|
|
343
|
-
export function renderNewArtifactPage({ baseUrl, authToken = "", locale = DEFAULT_LOCALE, currentPath = "/new" }) {
|
|
344
|
-
return renderArtifactFormPage({ mode: "new", baseUrl, authToken, locale, currentPath });
|
|
344
|
+
export function renderNewArtifactPage({ user, baseUrl, authToken = "", locale = DEFAULT_LOCALE, currentPath = "/new" }) {
|
|
345
|
+
return renderArtifactFormPage({ mode: "new", baseUrl, authToken, locale, currentPath, user });
|
|
345
346
|
}
|
|
346
347
|
|
|
347
348
|
export function renderAdminArtifactVersionsPage({ artifact, selectedVersion, content, baseUrl, user, error = "", locale = DEFAULT_LOCALE, currentPath = "/" }) {
|
|
@@ -371,19 +372,20 @@ export function renderAdminArtifactVersionsPage({ artifact, selectedVersion, con
|
|
|
371
372
|
return pageShell({
|
|
372
373
|
title: `Manage versions · ${artifact.title}`,
|
|
373
374
|
body: `
|
|
375
|
+
${workspaceHeader(user, view)}
|
|
374
376
|
<header class="topbar">
|
|
375
377
|
<div>
|
|
376
378
|
<h1>Manage versions</h1>
|
|
377
379
|
<p>${escapeHtml(artifact.title)} · ${escapeHtml(artifact.id)} · ${escapeHtml(baseUrl)}</p>
|
|
378
380
|
</div>
|
|
379
381
|
<nav>
|
|
380
|
-
<a href="${view.href(artifactPath)}"
|
|
382
|
+
<a href="${view.href(artifactPath)}">${view.text("nav.artifact")}</a>
|
|
381
383
|
<a href="${view.href(`${artifactPath}/diff`)}">${view.text("nav.diff")}</a>
|
|
382
|
-
|
|
383
|
-
|
|
384
|
+
|
|
385
|
+
|
|
384
386
|
</nav>
|
|
385
387
|
</header>
|
|
386
|
-
<main class="artifact-editor">
|
|
388
|
+
<main id="main-content" class="artifact-editor">
|
|
387
389
|
${error ? `<p class="auth-error">${escapeHtml(error)}</p>` : ""}
|
|
388
390
|
<section class="meta-card">
|
|
389
391
|
<h2>Versions</h2>
|
|
@@ -421,23 +423,21 @@ export function renderAdminArtifactVersionsPage({ artifact, selectedVersion, con
|
|
|
421
423
|
});
|
|
422
424
|
}
|
|
423
425
|
|
|
424
|
-
export function renderImportArtifactPage({ baseUrl, authToken = "", locale = DEFAULT_LOCALE, currentPath = "/import" }) {
|
|
426
|
+
export function renderImportArtifactPage({ user, baseUrl, authToken = "", locale = DEFAULT_LOCALE, currentPath = "/import" }) {
|
|
425
427
|
const view = viewContext(locale, currentPath);
|
|
426
428
|
const title = view.raw("form.importTitle");
|
|
427
429
|
return pageShell({
|
|
428
430
|
title,
|
|
429
431
|
body: `
|
|
432
|
+
${workspaceHeader(user, view)}
|
|
430
433
|
<header class="topbar">
|
|
431
434
|
<div>
|
|
432
435
|
<h1>${escapeHtml(title)}</h1>
|
|
433
436
|
<p>${escapeHtml(baseUrl)}</p>
|
|
434
437
|
</div>
|
|
435
|
-
|
|
436
|
-
<a href="${view.href("/")}">${view.text("nav.index")}</a>
|
|
437
|
-
${languageSwitcher(view)}
|
|
438
|
-
</nav>
|
|
438
|
+
|
|
439
439
|
</header>
|
|
440
|
-
<main class="artifact-editor">
|
|
440
|
+
<main id="main-content" class="artifact-editor">
|
|
441
441
|
<form class="editor-form" method="post" action="/import">
|
|
442
442
|
${hiddenToken(authToken)}
|
|
443
443
|
${localeInput(view.locale)}
|
|
@@ -490,17 +490,15 @@ export function renderLoginPage({ baseUrl, setup = false, error = "", locale = D
|
|
|
490
490
|
return pageShell({
|
|
491
491
|
title,
|
|
492
492
|
body: `
|
|
493
|
+
${workspaceHeader(null, view)}
|
|
493
494
|
<header class="topbar">
|
|
494
495
|
<div>
|
|
495
496
|
<h1>${escapeHtml(title)}</h1>
|
|
496
497
|
<p>${escapeHtml(baseUrl)}</p>
|
|
497
498
|
</div>
|
|
498
|
-
|
|
499
|
-
<a href="${view.href("/")}">${view.text("nav.index")}</a>
|
|
500
|
-
${languageSwitcher(view)}
|
|
501
|
-
</nav>
|
|
499
|
+
|
|
502
500
|
</header>
|
|
503
|
-
<main class="artifact-editor auth-panel">
|
|
501
|
+
<main id="main-content" class="artifact-editor auth-panel">
|
|
504
502
|
${error ? `<p class="auth-error">${escapeHtml(error)}</p>` : ""}
|
|
505
503
|
${setup ? `<p class="muted">No users exist yet. The first account becomes an administrator.</p>` : ""}
|
|
506
504
|
<form class="editor-form auth-form" method="post" action="/login">
|
|
@@ -556,20 +554,15 @@ export function renderAccountPage({ baseUrl, user, tokens = [], createdToken = "
|
|
|
556
554
|
return pageShell({
|
|
557
555
|
title: "Account",
|
|
558
556
|
body: `
|
|
557
|
+
${workspaceHeader(user, view)}
|
|
559
558
|
<header class="topbar">
|
|
560
559
|
<div>
|
|
561
560
|
<h1>Account</h1>
|
|
562
561
|
<p>${escapeHtml(user.email)} · ${escapeHtml(user.role)} · ${escapeHtml(baseUrl)}</p>
|
|
563
562
|
</div>
|
|
564
|
-
|
|
565
|
-
<a href="${view.href("/")}">${view.text("nav.index")}</a>
|
|
566
|
-
${user.role === "admin" ? `<a href="/admin/users">Users</a><a href="/admin/backup">Backup</a><a href="${view.href("/admin/access")}">${view.text("access.title")}</a>` : ""}
|
|
567
|
-
<a href="/account/password">Password</a>
|
|
568
|
-
<form class="nav-form" method="post" action="/logout"><button type="submit">Sign out</button></form>
|
|
569
|
-
${languageSwitcher(view)}
|
|
570
|
-
</nav>
|
|
563
|
+
|
|
571
564
|
</header>
|
|
572
|
-
<main class="artifact-view">
|
|
565
|
+
<main id="main-content" class="settings-page artifact-view">
|
|
573
566
|
${createdToken ? `<section class="token-once">
|
|
574
567
|
<h2>New API token</h2>
|
|
575
568
|
<p>Copy this token now. Artifacty stores only its hash and cannot show it again.</p>
|
|
@@ -619,19 +612,15 @@ export function renderPasswordPage({ baseUrl, user, required = false, error = ""
|
|
|
619
612
|
return pageShell({
|
|
620
613
|
title: "Change Password",
|
|
621
614
|
body: `
|
|
615
|
+
${workspaceHeader(user, view)}
|
|
622
616
|
<header class="topbar">
|
|
623
617
|
<div>
|
|
624
618
|
<h1>Change Password</h1>
|
|
625
619
|
<p>${escapeHtml(user.email)} · ${escapeHtml(baseUrl)}</p>
|
|
626
620
|
</div>
|
|
627
|
-
|
|
628
|
-
<a href="${view.href("/")}">${view.text("nav.index")}</a>
|
|
629
|
-
${required ? "" : `<a href="/account">Account</a>`}
|
|
630
|
-
<form class="nav-form" method="post" action="/logout"><button type="submit">Sign out</button></form>
|
|
631
|
-
${languageSwitcher(view)}
|
|
632
|
-
</nav>
|
|
621
|
+
|
|
633
622
|
</header>
|
|
634
|
-
<main class="artifact-editor auth-panel">
|
|
623
|
+
<main id="main-content" class="settings-page artifact-editor auth-panel">
|
|
635
624
|
${required ? `<p class="auth-warning">Password change is required before continuing.</p>` : ""}
|
|
636
625
|
${error ? `<p class="auth-error">${escapeHtml(error)}</p>` : ""}
|
|
637
626
|
${success ? `<p class="auth-success">${escapeHtml(success)}</p>` : ""}
|
|
@@ -670,25 +659,20 @@ function renderFullImportSummary(tableCounts) {
|
|
|
670
659
|
return parts ? ` (${parts})` : "";
|
|
671
660
|
}
|
|
672
661
|
|
|
673
|
-
export function renderAdminBackupPage({ baseUrl, integrity, importResult = null, importError = "", locale = DEFAULT_LOCALE, currentPath = "/admin/backup" }) {
|
|
662
|
+
export function renderAdminBackupPage({ user, baseUrl, integrity, importResult = null, importError = "", locale = DEFAULT_LOCALE, currentPath = "/admin/backup" }) {
|
|
674
663
|
const view = viewContext(locale, currentPath);
|
|
675
664
|
return pageShell({
|
|
676
665
|
title: "Backup",
|
|
677
666
|
body: `
|
|
667
|
+
${workspaceHeader(user, view)}
|
|
678
668
|
<header class="topbar">
|
|
679
669
|
<div>
|
|
680
670
|
<h1>Backup</h1>
|
|
681
671
|
<p>${escapeHtml(baseUrl)}</p>
|
|
682
672
|
</div>
|
|
683
|
-
|
|
684
|
-
<a href="${view.href("/")}">${view.text("nav.index")}</a>
|
|
685
|
-
<a href="/admin/users">Users</a><a href="${view.href("/admin/access")}">${view.text("access.title")}</a>
|
|
686
|
-
<a href="/admin/retention">${view.text("retention.navLabel")}</a>
|
|
687
|
-
<a href="/account">Account</a>
|
|
688
|
-
${languageSwitcher(view)}
|
|
689
|
-
</nav>
|
|
673
|
+
|
|
690
674
|
</header>
|
|
691
|
-
<main class="artifact-view">
|
|
675
|
+
<main id="main-content" class="settings-page artifact-view">
|
|
692
676
|
${importResult ? `<p class="auth-success">Restore complete. ${escapeHtml(importResult.artifactCount)} artifacts imported${importResult.scope === "full" ? renderFullImportSummary(importResult.tableCounts) : ""}.</p>` : ""}
|
|
693
677
|
${importError ? `<p class="auth-error">${escapeHtml(importError)}</p>` : ""}
|
|
694
678
|
<section class="meta-card">
|
|
@@ -737,14 +721,57 @@ export function renderAdminBackupPage({ baseUrl, integrity, importResult = null,
|
|
|
737
721
|
});
|
|
738
722
|
}
|
|
739
723
|
|
|
724
|
+
export function renderAdminUpdatePage({ baseUrl, user, status = {}, locale = DEFAULT_LOCALE, currentPath = "/admin/update" }) {
|
|
725
|
+
const view = viewContext(locale, currentPath);
|
|
726
|
+
const check = status.check;
|
|
727
|
+
const run = status.run;
|
|
728
|
+
const active = ["queued", "running"].includes(run?.state);
|
|
729
|
+
const canStart = check?.supported && !check.errorCode && check.targetVersion && check.updateAvailable && check.confirmationId && Date.parse(check.expiresAt) > Date.now() && !active && run?.state !== "interrupted" && status.currentVersion !== check.targetVersion;
|
|
730
|
+
const states = Object.fromEntries(["queued", "running", "succeeded", "failed", "interrupted"].map((state) => [state, view.raw(`update.${state}`)]));
|
|
731
|
+
const messages = { ...states, reconnect: view.raw("update.reconnect"), signIn: view.raw("update.signIn") };
|
|
732
|
+
return pageShell({
|
|
733
|
+
title: view.text("update.title"), locale: view.locale,
|
|
734
|
+
body: `${workspaceHeader(user, view)}
|
|
735
|
+
<header class="topbar"><div><h1>${view.text("update.title")}</h1><p>${escapeHtml(baseUrl)}</p></div></header>
|
|
736
|
+
<main id="main-content" class="settings-page artifact-view">
|
|
737
|
+
<section class="meta-card"><h2>${view.text("update.installed")}</h2><p>Artifacty <strong data-update-version>${escapeHtml(status.currentVersion || APP_VERSION)}</strong></p>
|
|
738
|
+
<p>${view.text("update.help")}</p>
|
|
739
|
+
<form method="post" action="/admin/update/check">${localeInput(view.locale)}<button type="submit"${active ? " disabled" : ""}>${view.text("update.check")}</button></form>
|
|
740
|
+
</section>
|
|
741
|
+
${check ? `<section class="meta-card"><h2>${view.text("update.latest")}</h2>
|
|
742
|
+
<p>${escapeHtml(status.currentVersion || APP_VERSION)} → <strong>${escapeHtml(check.targetVersion || "—")}</strong></p>
|
|
743
|
+
${check.targetVersion ? `<p><a href="https://github.com/raeseoklee/artifacty/releases/tag/v${escapeAttribute(encodeURIComponent(check.targetVersion))}" target="_blank" rel="noopener noreferrer">${view.text("update.releaseNotes")}</a></p>` : ""}
|
|
744
|
+
${check.errorCode ? `<p role="alert">${view.text("update.checkFailed")}</p>` : !check.supported ? `<p>${view.text("update.unsupported")}</p><p>${view.text("update.manual")}</p>` : !check.updateAvailable && !check.errorCode && check.targetVersion ? `<p>${view.text("update.current")}</p>` : ""}
|
|
745
|
+
${check.supported && check.updateAvailable && !canStart && !active && !check.errorCode && status.currentVersion !== check.targetVersion ? `<p>${view.text("update.checkAgain")}</p>` : ""}
|
|
746
|
+
${canStart ? `<p>${view.text("update.available", { version: check.targetVersion })}</p><p>${view.text("update.backup")}</p>
|
|
747
|
+
<form class="editor-form" method="post" action="/admin/update/start">${localeInput(view.locale)}
|
|
748
|
+
<input type="hidden" name="confirmationId" value="${escapeAttribute(check.confirmationId)}">
|
|
749
|
+
<input type="hidden" name="targetVersion" value="${escapeAttribute(check.targetVersion)}">
|
|
750
|
+
<label><input type="checkbox" name="confirmUpdate" value="true" required> ${view.text("update.confirm")}</label>
|
|
751
|
+
${check.expiresAt ? `<p class="muted">${view.text("update.expires")}: ${`<time datetime="${escapeAttribute(check.expiresAt)}" title="${escapeAttribute(check.expiresAt)}">${escapeHtml(new Intl.DateTimeFormat(view.locale, { dateStyle: "medium", timeStyle: "short", timeZone: "UTC" }).format(new Date(check.expiresAt)))} UTC</time>`}</p>` : ""}
|
|
752
|
+
<button type="submit">${view.text("update.start")}</button>
|
|
753
|
+
</form>` : ""}
|
|
754
|
+
</section>` : ""}
|
|
755
|
+
${run ? `<section class="meta-card" data-update-status data-active="${active}" data-messages="${escapeAttribute(JSON.stringify(messages))}"><h2>${view.text("update.progress")}</h2>
|
|
756
|
+
<p>${view.text("update.latest")}: ${escapeHtml(run.targetVersion || "—")}</p>
|
|
757
|
+
<p role="status" aria-live="polite" data-update-state>${escapeHtml(states[run.state] || states.running)}</p>
|
|
758
|
+
<p data-update-error>${escapeHtml(run.error || "")}</p>
|
|
759
|
+
<a href="${view.href("/admin/update")}">${view.text("update.refresh")}</a> <a data-update-login hidden href="${view.href("/login")}">${view.text("nav.signIn")}</a>
|
|
760
|
+
</section>` : ""}
|
|
761
|
+
</main>`,
|
|
762
|
+
afterBody: active ? '<script type="module" src="/assets/update.js"></script>' : ""
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
|
|
740
766
|
export function renderAdminAccessPage({ baseUrl, user, mode, teams = [], users = [], networkPolicy = { allowedCidrs: [] }, currentIp = "", preview = null, pendingNetworkPolicy = null, shareLinkPolicy = {}, locale = DEFAULT_LOCALE, currentPath = "/admin/access" }) {
|
|
741
767
|
const view = viewContext(locale, currentPath);
|
|
742
768
|
return pageShell({
|
|
743
769
|
title: view.text("access.title"),
|
|
744
770
|
locale: view.locale,
|
|
745
|
-
body:
|
|
746
|
-
<
|
|
747
|
-
|
|
771
|
+
body: `${workspaceHeader(user, view)}
|
|
772
|
+
<header class="topbar"><div><h1>${view.text("access.title")}</h1><p>${escapeHtml(baseUrl)}</p></div>
|
|
773
|
+
</header>
|
|
774
|
+
<main id="main-content" class="settings-page artifact-view">
|
|
748
775
|
<section class="meta-card"><h2>${view.text("access.mode")}</h2>
|
|
749
776
|
<form class="editor-form" method="post" action="/admin/access/preview">${localeInput(view.locale)}
|
|
750
777
|
<label><input type="radio" name="mode" value="public"${mode === "public" ? " checked" : ""}> ${view.text("access.public")}</label>
|
|
@@ -768,8 +795,6 @@ export function renderAdminAccessPage({ baseUrl, user, mode, teams = [], users =
|
|
|
768
795
|
<button type="submit">${view.text("access.saveNetwork")}</button>
|
|
769
796
|
</form>
|
|
770
797
|
</section>
|
|
771
|
-
<section class="meta-card"><h2>${view.text("access.samples")}</h2><p>${view.text("access.samplesHelp")}</p>
|
|
772
|
-
${["install", "remove"].map((action) => `<form method="post" action="/admin/samples/${action}">${localeInput(view.locale)}<button type="submit">${view.text(`access.samples.${action}`)}</button></form>`).join("")}</section>
|
|
773
798
|
<section class="meta-card"><h2>${view.text("access.teams")}</h2>
|
|
774
799
|
<form class="editor-form auth-form" method="post" action="/admin/teams">${localeInput(view.locale)}
|
|
775
800
|
<label class="field"><span>${view.text("access.teamName")}</span><input name="name" required></label>
|
|
@@ -781,6 +806,8 @@ export function renderAdminAccessPage({ baseUrl, user, mode, teams = [], users =
|
|
|
781
806
|
<button type="submit">${view.text("access.saveMembers")}</button>
|
|
782
807
|
</form></section>`).join("") : `<p class="muted">${view.text("access.noTeams")}</p>`}
|
|
783
808
|
</section>
|
|
809
|
+
<section class="meta-card"><h2>${view.text("access.samples")}</h2><p>${view.text("access.samplesHelp")}</p>
|
|
810
|
+
${["install", "remove"].map((action) => `<form method="post" action="/admin/samples/${action}">${localeInput(view.locale)}<button type="submit">${view.text(`access.samples.${action}`)}</button></form>`).join("")}</section>
|
|
784
811
|
</main>`
|
|
785
812
|
});
|
|
786
813
|
}
|
|
@@ -790,9 +817,10 @@ export function renderArtifactSharingPage({ baseUrl, user, artifact, sharing = {
|
|
|
790
817
|
return pageShell({
|
|
791
818
|
title: view.text("sharing.title"),
|
|
792
819
|
locale: view.locale,
|
|
793
|
-
body:
|
|
794
|
-
<
|
|
795
|
-
<
|
|
820
|
+
body: `${workspaceHeader(user, view)}
|
|
821
|
+
<header class="topbar"><div><h1>${view.text("sharing.title")}: ${escapeHtml(artifact.title)}</h1><p>${escapeHtml(baseUrl)}</p></div>
|
|
822
|
+
<nav><a href="${view.href(`/artifacts/${encodeURIComponent(artifact.id)}`)}">${view.text("nav.artifact")}</a></nav></header>
|
|
823
|
+
<main id="main-content" class="settings-page artifact-view"><section class="meta-card">
|
|
796
824
|
<p>${view.text("sharing.help")}</p>
|
|
797
825
|
<form class="editor-form" method="post" action="/artifacts/${encodeURIComponent(artifact.id)}/sharing">${localeInput(view.locale)}
|
|
798
826
|
<fieldset><legend>${view.text("sharing.users")}</legend>${accessCheckboxes(users, "userIds", sharing.userIds || [], view, sharing.userPermissions || {})}</fieldset>
|
|
@@ -821,8 +849,9 @@ function accessCheckboxes(items, name, selectedIds, view, permissions = null) {
|
|
|
821
849
|
export function renderInboxPage({ baseUrl, user, notifications = [], unreadCount = 0, locale = DEFAULT_LOCALE, currentPath = "/inbox" }) {
|
|
822
850
|
const view = viewContext(locale, currentPath);
|
|
823
851
|
return pageShell({ title: view.text("inbox.title"), locale: view.locale,
|
|
824
|
-
body:
|
|
825
|
-
<
|
|
852
|
+
body: `${workspaceHeader(user, view)}
|
|
853
|
+
<header class="topbar"><div><h1>${view.text("inbox.title")}</h1><p>${escapeHtml(baseUrl)}</p></div></header>
|
|
854
|
+
<main id="main-content" class="artifact-view"><section class="meta-card"><p>${view.text("inbox.unread", { count: unreadCount })}</p>
|
|
826
855
|
<form method="post" action="/inbox/read-all">${localeInput(view.locale)}<button type="submit">${view.text("inbox.readAll")}</button></form>
|
|
827
856
|
${notifications.length ? notifications.map((item) => `<section><h2><a href="${view.href(`/artifacts/${encodeURIComponent(item.artifactId)}`)}">${escapeHtml(item.title)}</a></h2><p>${escapeHtml(item.type)} · ${escapeHtml(item.createdAt)}</p>
|
|
828
857
|
${item.readAt ? `<p>${view.text("inbox.read")}</p>` : `<form method="post" action="/inbox/${encodeURIComponent(item.id)}/read">${localeInput(view.locale)}<button type="submit">${view.text("inbox.markRead")}</button></form>`}</section>`).join("") : `<p>${view.text("inbox.empty")}</p>`}
|
|
@@ -842,7 +871,7 @@ export function renderSharedArtifactPage({ artifact, version, content, locale =
|
|
|
842
871
|
});
|
|
843
872
|
const needsViewer = ["code", "markdown", "notebook", "sarif", "csv"].includes(version.format);
|
|
844
873
|
return pageShell({ title: artifact.title, locale: view.locale,
|
|
845
|
-
body: `<header class="topbar"><div><h1>${escapeHtml(artifact.title)}</h1><p>${view.text("sharing.readOnly")} · v${Number(version.version)}</p></div></header><main class="artifact-view">${rendered}</main>`,
|
|
874
|
+
body: `<header class="topbar"><div><h1>${escapeHtml(artifact.title)}</h1><p>${view.text("sharing.readOnly")} · v${Number(version.version)}</p></div></header><main id="main-content" class="artifact-view">${rendered}</main>`,
|
|
846
875
|
head: needsViewer ? editorHead() : "",
|
|
847
876
|
afterBody: `${frameResizeScript()}${needsViewer ? viewerScript() : ""}` });
|
|
848
877
|
}
|
|
@@ -899,20 +928,15 @@ export function renderAdminUsersPage({ baseUrl, user, users = [], importResult =
|
|
|
899
928
|
return pageShell({
|
|
900
929
|
title: "Users",
|
|
901
930
|
body: `
|
|
931
|
+
${workspaceHeader(user, view)}
|
|
902
932
|
<header class="topbar">
|
|
903
933
|
<div>
|
|
904
934
|
<h1>Users</h1>
|
|
905
935
|
<p>${escapeHtml(baseUrl)}</p>
|
|
906
936
|
</div>
|
|
907
|
-
|
|
908
|
-
<a href="${view.href("/")}">${view.text("nav.index")}</a>
|
|
909
|
-
<a href="/admin/backup">Backup</a><a href="${view.href("/admin/access")}">${view.text("access.title")}</a>
|
|
910
|
-
<a href="/admin/retention">${view.text("retention.navLabel")}</a>
|
|
911
|
-
<a href="/account">Account</a>
|
|
912
|
-
${languageSwitcher(view)}
|
|
913
|
-
</nav>
|
|
937
|
+
|
|
914
938
|
</header>
|
|
915
|
-
<main class="artifact-view">
|
|
939
|
+
<main id="main-content" class="settings-page artifact-view">
|
|
916
940
|
<section class="meta-card">
|
|
917
941
|
<h2>Create user</h2>
|
|
918
942
|
<form class="editor-form auth-form" method="post" action="/admin/users">
|
|
@@ -952,7 +976,7 @@ export function renderAdminUsersPage({ baseUrl, user, users = [], importResult =
|
|
|
952
976
|
});
|
|
953
977
|
}
|
|
954
978
|
|
|
955
|
-
export function renderAdminWebhooksPage({ baseUrl, webhooks = [], createdSecret = null, createdWebhookId = null, locale = DEFAULT_LOCALE, currentPath = "/admin/webhooks" }) {
|
|
979
|
+
export function renderAdminWebhooksPage({ user, baseUrl, webhooks = [], createdSecret = null, createdWebhookId = null, locale = DEFAULT_LOCALE, currentPath = "/admin/webhooks" }) {
|
|
956
980
|
const view = viewContext(locale, currentPath);
|
|
957
981
|
const rows = webhooks.map((webhook) => `
|
|
958
982
|
<tr>
|
|
@@ -981,21 +1005,15 @@ export function renderAdminWebhooksPage({ baseUrl, webhooks = [], createdSecret
|
|
|
981
1005
|
return pageShell({
|
|
982
1006
|
title: view.text("webhooks.title"),
|
|
983
1007
|
body: `
|
|
1008
|
+
${workspaceHeader(user, view)}
|
|
984
1009
|
<header class="topbar">
|
|
985
1010
|
<div>
|
|
986
1011
|
<h1>${view.text("webhooks.title")}</h1>
|
|
987
1012
|
<p>${escapeHtml(baseUrl)}</p>
|
|
988
1013
|
</div>
|
|
989
|
-
|
|
990
|
-
<a href="${view.href("/")}">${view.text("nav.index")}</a>
|
|
991
|
-
<a href="/admin/backup">Backup</a><a href="${view.href("/admin/access")}">${view.text("access.title")}</a>
|
|
992
|
-
<a href="/admin/users">Users</a>
|
|
993
|
-
<a href="/admin/retention">${view.text("retention.navLabel")}</a>
|
|
994
|
-
<a href="/account">Account</a>
|
|
995
|
-
${languageSwitcher(view)}
|
|
996
|
-
</nav>
|
|
1014
|
+
|
|
997
1015
|
</header>
|
|
998
|
-
<main class="artifact-view">
|
|
1016
|
+
<main id="main-content" class="settings-page artifact-view">
|
|
999
1017
|
${secretBanner}
|
|
1000
1018
|
<section class="meta-card">
|
|
1001
1019
|
<h2>${view.text("webhooks.createTitle")}</h2>
|
|
@@ -1020,7 +1038,7 @@ export function renderAdminWebhooksPage({ baseUrl, webhooks = [], createdSecret
|
|
|
1020
1038
|
});
|
|
1021
1039
|
}
|
|
1022
1040
|
|
|
1023
|
-
export function renderAdminRetentionPage({ baseUrl, policy, lastSweep = null, report = null, locale = DEFAULT_LOCALE, currentPath = "/admin/retention" }) {
|
|
1041
|
+
export function renderAdminRetentionPage({ user, baseUrl, policy, lastSweep = null, report = null, locale = DEFAULT_LOCALE, currentPath = "/admin/retention" }) {
|
|
1024
1042
|
const view = viewContext(locale, currentPath);
|
|
1025
1043
|
const byTypeText = Object.entries(policy.archiveAfterDays.byType || {})
|
|
1026
1044
|
.map(([type, days]) => `${type}=${days}`)
|
|
@@ -1070,20 +1088,15 @@ export function renderAdminRetentionPage({ baseUrl, policy, lastSweep = null, re
|
|
|
1070
1088
|
return pageShell({
|
|
1071
1089
|
title: view.text("retention.title"),
|
|
1072
1090
|
body: `
|
|
1091
|
+
${workspaceHeader(user, view)}
|
|
1073
1092
|
<header class="topbar">
|
|
1074
1093
|
<div>
|
|
1075
1094
|
<h1>${view.text("retention.title")}</h1>
|
|
1076
1095
|
<p>${escapeHtml(baseUrl)}</p>
|
|
1077
1096
|
</div>
|
|
1078
|
-
|
|
1079
|
-
<a href="${view.href("/")}">${view.text("nav.index")}</a>
|
|
1080
|
-
<a href="/admin/backup">Backup</a><a href="${view.href("/admin/access")}">${view.text("access.title")}</a>
|
|
1081
|
-
<a href="/admin/users">Users</a><a href="${view.href("/admin/access")}">${view.text("access.title")}</a>
|
|
1082
|
-
<a href="/account">Account</a>
|
|
1083
|
-
${languageSwitcher(view)}
|
|
1084
|
-
</nav>
|
|
1097
|
+
|
|
1085
1098
|
</header>
|
|
1086
|
-
<main class="artifact-view">
|
|
1099
|
+
<main id="main-content" class="settings-page artifact-view">
|
|
1087
1100
|
<section class="meta-card">
|
|
1088
1101
|
<h2>${view.text("retention.policyTitle")}</h2>
|
|
1089
1102
|
<form class="editor-form" method="post" action="/admin/retention">
|
|
@@ -1168,33 +1181,36 @@ export function renderArtifactPage({ artifact, version, content, comments = [],
|
|
|
1168
1181
|
return pageShell({
|
|
1169
1182
|
title: artifact.title,
|
|
1170
1183
|
body: `
|
|
1184
|
+
${workspaceHeader(user, view)}
|
|
1171
1185
|
<header class="topbar">
|
|
1172
1186
|
<div>
|
|
1173
1187
|
<h1>${escapeHtml(artifact.title)}</h1>
|
|
1174
1188
|
<p>${escapeHtml(artifact.id)} · ${escapeHtml(artifact.sourceAgent)} · ${escapeHtml(baseUrl)}</p>
|
|
1175
1189
|
</div>
|
|
1176
1190
|
<nav>
|
|
1177
|
-
|
|
1191
|
+
|
|
1178
1192
|
${canEdit ? `<a href="${view.href(`/artifacts/${encodeURIComponent(artifact.id)}/edit`)}">${view.text("nav.edit")}</a>` : ""}
|
|
1179
1193
|
<a href="${view.href(`/artifacts/${encodeURIComponent(artifact.id)}/diff`)}">${view.text("nav.diff")}</a>
|
|
1180
|
-
${user?.role === "admin" ? `<a href="${view.href(`/admin/artifacts/${encodeURIComponent(artifact.id)}/versions`)}"
|
|
1194
|
+
${user?.role === "admin" ? `<a href="${view.href(`/admin/artifacts/${encodeURIComponent(artifact.id)}/versions`)}">${view.text("nav.versions")}</a>` : ""}
|
|
1181
1195
|
<a href="${view.href(rawUrl)}">${view.text("nav.raw")}</a>
|
|
1182
1196
|
${user && canManageVisibility ? `<a href="${view.href(`/artifacts/${encodeURIComponent(artifact.id)}/sharing`)}">${view.text("sharing.title")}</a>` : ""}
|
|
1183
|
-
|
|
1184
|
-
|
|
1197
|
+
|
|
1198
|
+
|
|
1185
1199
|
</nav>
|
|
1186
1200
|
</header>
|
|
1187
|
-
<main class="${viewClass}">
|
|
1188
|
-
<section class="meta-strip">
|
|
1189
|
-
|
|
1190
|
-
${typeBadge(artifact.artifactType || "document")}
|
|
1191
|
-
<span>${view.text("artifact.schema", { version: artifact.schemaVersion || 1 })}</span>
|
|
1201
|
+
<main id="main-content" class="${viewClass}">
|
|
1202
|
+
<section class="meta-strip artifact-overview">
|
|
1203
|
+
<span>${escapeHtml(version.format)} · v${version.version}</span>
|
|
1192
1204
|
${artifact.archivedAt ? statusBadge(view.text("artifact.archived", { date: artifact.archivedAt })) : ""}
|
|
1193
1205
|
${visibilityBadge(artifact, view)}
|
|
1194
|
-
<
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1206
|
+
<details class="artifact-details"><summary>${view.text("artifact.details")}</summary><div class="meta-strip">
|
|
1207
|
+
${typeBadge(artifact.artifactType || "document")}
|
|
1208
|
+
<span>${view.text("artifact.schema", { version: artifact.schemaVersion || 1 })}</span>
|
|
1209
|
+
<span title="${escapeAttribute(publisherTitle(artifact, view))}">${view.text("artifact.publisher", { publisher })}</span>
|
|
1210
|
+
<span>${view.text("artifact.bytes", { size: version.sizeBytes })}</span>
|
|
1211
|
+
<span>${escapeHtml(version.createdAt)}</span>
|
|
1212
|
+
<span>${escapeHtml(version.sha256.slice(0, 12))}</span>
|
|
1213
|
+
</div></details>
|
|
1198
1214
|
</section>
|
|
1199
1215
|
${canArchive ? `<form class="inline-action" method="post" action="/artifacts/${encodeURIComponent(artifact.id)}/${archiveAction}">
|
|
1200
1216
|
${hiddenToken(authToken)}
|
|
@@ -1456,7 +1472,7 @@ function artifactViewClass({ artifact, version }) {
|
|
|
1456
1472
|
return classes.join(" ");
|
|
1457
1473
|
}
|
|
1458
1474
|
|
|
1459
|
-
export function renderDiffPage({ artifact, fromVersion, toVersion, fromContent, toContent, diffRows, view: diffView = "lines", structuredDiff, baseUrl, locale = DEFAULT_LOCALE, currentPath = "/" }) {
|
|
1475
|
+
export function renderDiffPage({ user, artifact, fromVersion, toVersion, fromContent, toContent, diffRows, view: diffView = "lines", structuredDiff, baseUrl, locale = DEFAULT_LOCALE, currentPath = "/" }) {
|
|
1460
1476
|
const view = viewContext(locale, currentPath);
|
|
1461
1477
|
const title = view.raw("diff.title", { title: artifact.title });
|
|
1462
1478
|
const versionOptions = artifact.versions
|
|
@@ -1504,6 +1520,7 @@ export function renderDiffPage({ artifact, fromVersion, toVersion, fromContent,
|
|
|
1504
1520
|
return pageShell({
|
|
1505
1521
|
title,
|
|
1506
1522
|
body: `
|
|
1523
|
+
${workspaceHeader(user, view)}
|
|
1507
1524
|
<header class="topbar">
|
|
1508
1525
|
<div>
|
|
1509
1526
|
<h1>${escapeHtml(title)}</h1>
|
|
@@ -1511,10 +1528,10 @@ export function renderDiffPage({ artifact, fromVersion, toVersion, fromContent,
|
|
|
1511
1528
|
</div>
|
|
1512
1529
|
<nav>
|
|
1513
1530
|
<a href="${view.href(`/artifacts/${encodeURIComponent(artifact.id)}`)}">${view.text("nav.artifact")}</a>
|
|
1514
|
-
|
|
1531
|
+
|
|
1515
1532
|
</nav>
|
|
1516
1533
|
</header>
|
|
1517
|
-
<main class="artifact-view">
|
|
1534
|
+
<main id="main-content" class="artifact-view">
|
|
1518
1535
|
<form class="diff-form" method="get" action="/artifacts/${encodeURIComponent(artifact.id)}/diff">
|
|
1519
1536
|
${localeInput(view.locale)}
|
|
1520
1537
|
<input type="hidden" name="view" value="${escapeAttribute(resolvedView)}">
|
|
@@ -1915,20 +1932,20 @@ export function pageShell({ title, body, head = "", afterBody = "", locale = DEF
|
|
|
1915
1932
|
<style>
|
|
1916
1933
|
:root {
|
|
1917
1934
|
color-scheme: light dark;
|
|
1918
|
-
--bg: #
|
|
1935
|
+
--bg: #f5f6f8;
|
|
1919
1936
|
--grid: rgba(31, 58, 99, 0.05);
|
|
1920
1937
|
--panel: #ffffff;
|
|
1921
|
-
--panel-2: #
|
|
1922
|
-
--text: #
|
|
1923
|
-
--muted: #
|
|
1924
|
-
--faint: #
|
|
1925
|
-
--line: #
|
|
1926
|
-
--line-2: #
|
|
1927
|
-
--accent: #
|
|
1928
|
-
--accent-2: #
|
|
1938
|
+
--panel-2: #eef0f3;
|
|
1939
|
+
--text: #20242b;
|
|
1940
|
+
--muted: #58616e;
|
|
1941
|
+
--faint: #626c79;
|
|
1942
|
+
--line: #dfe3e8;
|
|
1943
|
+
--line-2: #b9c1cc;
|
|
1944
|
+
--accent: #315ca8;
|
|
1945
|
+
--accent-2: #264a89;
|
|
1929
1946
|
--accent-ink: #ffffff;
|
|
1930
|
-
--accent-soft:
|
|
1931
|
-
--ring:
|
|
1947
|
+
--accent-soft: #eaf0fb;
|
|
1948
|
+
--ring: #315ca8;
|
|
1932
1949
|
--code: #0e1320;
|
|
1933
1950
|
--code-text: #e6edf3;
|
|
1934
1951
|
--sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Apple SD Gothic Neo", "Malgun Gothic", "Noto Sans KR", sans-serif;
|
|
@@ -1936,20 +1953,20 @@ export function pageShell({ title, body, head = "", afterBody = "", locale = DEF
|
|
|
1936
1953
|
}
|
|
1937
1954
|
@media (prefers-color-scheme: dark) {
|
|
1938
1955
|
:root {
|
|
1939
|
-
--bg: #
|
|
1956
|
+
--bg: #15171b;
|
|
1940
1957
|
--grid: rgba(120, 160, 200, 0.06);
|
|
1941
|
-
--panel: #
|
|
1942
|
-
--panel-2: #
|
|
1958
|
+
--panel: #1c1f24;
|
|
1959
|
+
--panel-2: #25292f;
|
|
1943
1960
|
--text: #e6edf3;
|
|
1944
|
-
--muted: #
|
|
1945
|
-
--faint: #
|
|
1946
|
-
--line: #
|
|
1947
|
-
--line-2: #
|
|
1948
|
-
--accent: #
|
|
1949
|
-
--accent-2: #
|
|
1950
|
-
--accent-ink: #
|
|
1951
|
-
--accent-soft:
|
|
1952
|
-
--ring:
|
|
1961
|
+
--muted: #adb5c0;
|
|
1962
|
+
--faint: #9aa5b2;
|
|
1963
|
+
--line: #343b44;
|
|
1964
|
+
--line-2: #596575;
|
|
1965
|
+
--accent: #9cbcf5;
|
|
1966
|
+
--accent-2: #bbd1f8;
|
|
1967
|
+
--accent-ink: #18253d;
|
|
1968
|
+
--accent-soft: #29374e;
|
|
1969
|
+
--ring: #9cbcf5;
|
|
1953
1970
|
--code: #080c12;
|
|
1954
1971
|
--code-text: #e6edf3;
|
|
1955
1972
|
}
|
|
@@ -1959,13 +1976,10 @@ export function pageShell({ title, body, head = "", afterBody = "", locale = DEF
|
|
|
1959
1976
|
margin: 0;
|
|
1960
1977
|
min-height: 100vh;
|
|
1961
1978
|
background-color: var(--bg);
|
|
1962
|
-
|
|
1963
|
-
linear-gradient(var(--grid) 1px, transparent 1px),
|
|
1964
|
-
linear-gradient(90deg, var(--grid) 1px, transparent 1px);
|
|
1965
|
-
background-size: 28px 28px;
|
|
1966
|
-
background-position: center top;
|
|
1979
|
+
|
|
1967
1980
|
color: var(--text);
|
|
1968
1981
|
font-family: var(--sans);
|
|
1982
|
+
font-size: 14px;
|
|
1969
1983
|
line-height: 1.55;
|
|
1970
1984
|
letter-spacing: 0;
|
|
1971
1985
|
-webkit-font-smoothing: antialiased;
|
|
@@ -1973,28 +1987,28 @@ export function pageShell({ title, body, head = "", afterBody = "", locale = DEF
|
|
|
1973
1987
|
a { color: var(--accent); text-decoration: none; }
|
|
1974
1988
|
a:hover { color: var(--accent-2); text-decoration: underline; }
|
|
1975
1989
|
.topbar {
|
|
1976
|
-
position:
|
|
1977
|
-
|
|
1978
|
-
|
|
1990
|
+
position: relative;
|
|
1991
|
+
width: min(1440px, calc(100% - 64px));
|
|
1992
|
+
margin: 28px auto 20px;
|
|
1979
1993
|
display: flex;
|
|
1980
1994
|
align-items: center;
|
|
1981
1995
|
justify-content: space-between;
|
|
1982
1996
|
gap: 24px;
|
|
1983
|
-
padding:
|
|
1997
|
+
padding: 0 0 20px;
|
|
1984
1998
|
border-bottom: 1px solid var(--line);
|
|
1985
|
-
background:
|
|
1986
|
-
backdrop-filter: saturate(150%) blur(10px);
|
|
1999
|
+
background: transparent;
|
|
1987
2000
|
}
|
|
1988
2001
|
.topbar h1 {
|
|
1989
2002
|
margin: 0;
|
|
1990
|
-
font-size:
|
|
1991
|
-
|
|
2003
|
+
font-size: 25px;
|
|
2004
|
+
font-weight: 650;
|
|
2005
|
+
line-height: 1.3;
|
|
1992
2006
|
letter-spacing: -0.01em;
|
|
1993
2007
|
}
|
|
1994
2008
|
.topbar p {
|
|
1995
2009
|
margin: 5px 0 0;
|
|
1996
2010
|
color: var(--muted);
|
|
1997
|
-
font-family: var(--
|
|
2011
|
+
font-family: var(--sans);
|
|
1998
2012
|
font-size: 12px;
|
|
1999
2013
|
overflow-wrap: anywhere;
|
|
2000
2014
|
}
|
|
@@ -2008,14 +2022,14 @@ export function pageShell({ title, body, head = "", afterBody = "", locale = DEF
|
|
|
2008
2022
|
.brand-mark { display: inline-flex; color: var(--accent); }
|
|
2009
2023
|
.brand-text { display: grid; gap: 2px; }
|
|
2010
2024
|
.brand-name {
|
|
2011
|
-
font-family: var(--
|
|
2025
|
+
font-family: var(--sans);
|
|
2012
2026
|
font-size: 19px;
|
|
2013
2027
|
font-weight: 600;
|
|
2014
2028
|
letter-spacing: -0.01em;
|
|
2015
2029
|
}
|
|
2016
2030
|
.brand-tag {
|
|
2017
2031
|
color: var(--muted);
|
|
2018
|
-
font-family: var(--
|
|
2032
|
+
font-family: var(--sans);
|
|
2019
2033
|
font-size: 12px;
|
|
2020
2034
|
overflow-wrap: anywhere;
|
|
2021
2035
|
}
|
|
@@ -2024,7 +2038,7 @@ export function pageShell({ title, body, head = "", afterBody = "", locale = DEF
|
|
|
2024
2038
|
align-items: center;
|
|
2025
2039
|
gap: 3px;
|
|
2026
2040
|
flex-wrap: wrap;
|
|
2027
|
-
font-family: var(--
|
|
2041
|
+
font-family: var(--sans);
|
|
2028
2042
|
font-size: 13px;
|
|
2029
2043
|
}
|
|
2030
2044
|
.topbar nav > a {
|
|
@@ -2058,17 +2072,17 @@ export function pageShell({ title, body, head = "", afterBody = "", locale = DEF
|
|
|
2058
2072
|
.dashboard,
|
|
2059
2073
|
.artifact-view,
|
|
2060
2074
|
.artifact-editor {
|
|
2061
|
-
width: min(
|
|
2075
|
+
width: min(1440px, calc(100% - 64px));
|
|
2062
2076
|
margin: 28px auto 72px;
|
|
2063
2077
|
}
|
|
2064
2078
|
.artifact-view-wide {
|
|
2065
|
-
width: min(1760px, calc(
|
|
2079
|
+
width: min(1760px, calc(100% - 64px));
|
|
2066
2080
|
}
|
|
2067
2081
|
.toolbar {
|
|
2068
|
-
font-family: var(--
|
|
2082
|
+
font-family: var(--sans);
|
|
2069
2083
|
font-size: 12px;
|
|
2070
|
-
letter-spacing: 0
|
|
2071
|
-
text-transform:
|
|
2084
|
+
letter-spacing: 0;
|
|
2085
|
+
text-transform: none;
|
|
2072
2086
|
color: var(--faint);
|
|
2073
2087
|
}
|
|
2074
2088
|
.toolbar,
|
|
@@ -2096,34 +2110,8 @@ export function pageShell({ title, body, head = "", afterBody = "", locale = DEF
|
|
|
2096
2110
|
.filter-form select {
|
|
2097
2111
|
min-width: 120px;
|
|
2098
2112
|
}
|
|
2099
|
-
.dashboard-layout {
|
|
2100
|
-
|
|
2101
|
-
align-items: flex-start;
|
|
2102
|
-
gap: 24px;
|
|
2103
|
-
width: min(1180px, calc(100vw - 32px));
|
|
2104
|
-
margin: 28px auto 72px;
|
|
2105
|
-
}
|
|
2106
|
-
.dashboard-layout .dashboard {
|
|
2107
|
-
flex: 1;
|
|
2108
|
-
min-width: 0;
|
|
2109
|
-
width: auto;
|
|
2110
|
-
margin: 0;
|
|
2111
|
-
}
|
|
2112
|
-
.dashboard-sidebar {
|
|
2113
|
-
width: 240px;
|
|
2114
|
-
flex: none;
|
|
2115
|
-
border: 1px solid var(--line);
|
|
2116
|
-
border-radius: 12px;
|
|
2117
|
-
background: var(--panel);
|
|
2118
|
-
padding: 14px;
|
|
2119
|
-
}
|
|
2120
|
-
.dashboard-sidebar h2 {
|
|
2121
|
-
margin: 0 0 10px;
|
|
2122
|
-
font-size: 13px;
|
|
2123
|
-
text-transform: uppercase;
|
|
2124
|
-
letter-spacing: 0.06em;
|
|
2125
|
-
color: var(--faint);
|
|
2126
|
-
}
|
|
2113
|
+
.dashboard-layout { width: min(1440px, calc(100% - 64px)); margin: 28px auto 48px; }
|
|
2114
|
+
.dashboard-layout .dashboard { min-width: 0; width: 100%; margin: 0; }
|
|
2127
2115
|
.saved-view-list {
|
|
2128
2116
|
list-style: none;
|
|
2129
2117
|
margin: 0 0 14px;
|
|
@@ -2160,8 +2148,8 @@ export function pageShell({ title, body, head = "", afterBody = "", locale = DEF
|
|
|
2160
2148
|
margin: 0;
|
|
2161
2149
|
padding: 10px 14px;
|
|
2162
2150
|
font-size: 12px;
|
|
2163
|
-
text-transform:
|
|
2164
|
-
letter-spacing: 0
|
|
2151
|
+
text-transform: none;
|
|
2152
|
+
letter-spacing: 0;
|
|
2165
2153
|
color: var(--faint);
|
|
2166
2154
|
background: var(--panel-2);
|
|
2167
2155
|
border-bottom: 1px solid var(--line);
|
|
@@ -2226,10 +2214,10 @@ export function pageShell({ title, body, head = "", afterBody = "", locale = DEF
|
|
|
2226
2214
|
}
|
|
2227
2215
|
.artifact-list {
|
|
2228
2216
|
border: 1px solid var(--line);
|
|
2229
|
-
border-radius:
|
|
2217
|
+
border-radius: 8px;
|
|
2230
2218
|
overflow: hidden;
|
|
2231
2219
|
background: var(--panel);
|
|
2232
|
-
box-shadow:
|
|
2220
|
+
box-shadow: none;
|
|
2233
2221
|
}
|
|
2234
2222
|
.pager {
|
|
2235
2223
|
display: flex;
|
|
@@ -2342,8 +2330,8 @@ export function pageShell({ title, body, head = "", afterBody = "", locale = DEF
|
|
|
2342
2330
|
color: var(--faint);
|
|
2343
2331
|
font-family: var(--mono);
|
|
2344
2332
|
font-size: 11.5px;
|
|
2345
|
-
letter-spacing: 0
|
|
2346
|
-
text-transform:
|
|
2333
|
+
letter-spacing: 0;
|
|
2334
|
+
text-transform: none;
|
|
2347
2335
|
}
|
|
2348
2336
|
.data-table form {
|
|
2349
2337
|
margin: 0;
|
|
@@ -2363,8 +2351,8 @@ export function pageShell({ title, body, head = "", afterBody = "", locale = DEF
|
|
|
2363
2351
|
.field > span {
|
|
2364
2352
|
font-family: var(--mono);
|
|
2365
2353
|
font-size: 11.5px;
|
|
2366
|
-
letter-spacing: 0
|
|
2367
|
-
text-transform:
|
|
2354
|
+
letter-spacing: 0;
|
|
2355
|
+
text-transform: none;
|
|
2368
2356
|
color: var(--faint);
|
|
2369
2357
|
}
|
|
2370
2358
|
.check-field {
|
|
@@ -2380,7 +2368,7 @@ export function pageShell({ title, body, head = "", afterBody = "", locale = DEF
|
|
|
2380
2368
|
min-height: 0;
|
|
2381
2369
|
padding: 0;
|
|
2382
2370
|
}
|
|
2383
|
-
input,
|
|
2371
|
+
input:not([type="checkbox"]):not([type="radio"]),
|
|
2384
2372
|
select,
|
|
2385
2373
|
textarea {
|
|
2386
2374
|
width: 100%;
|
|
@@ -2393,13 +2381,13 @@ export function pageShell({ title, body, head = "", afterBody = "", locale = DEF
|
|
|
2393
2381
|
}
|
|
2394
2382
|
input::placeholder,
|
|
2395
2383
|
textarea::placeholder { color: var(--faint); }
|
|
2396
|
-
input,
|
|
2384
|
+
input:not([type="checkbox"]):not([type="radio"]),
|
|
2397
2385
|
select {
|
|
2398
2386
|
min-height: 38px;
|
|
2399
2387
|
padding: 7px 10px;
|
|
2400
2388
|
}
|
|
2401
2389
|
textarea {
|
|
2402
|
-
min-height:
|
|
2390
|
+
min-height: 120px;
|
|
2403
2391
|
padding: 14px;
|
|
2404
2392
|
resize: vertical;
|
|
2405
2393
|
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
|
@@ -2408,6 +2396,8 @@ export function pageShell({ title, body, head = "", afterBody = "", locale = DEF
|
|
|
2408
2396
|
white-space: pre;
|
|
2409
2397
|
overflow: auto;
|
|
2410
2398
|
}
|
|
2399
|
+
textarea[data-artifacty-editor] { min-height: 52vh; }
|
|
2400
|
+
.comments textarea { min-height: 96px; white-space: pre-wrap; }
|
|
2411
2401
|
.compact-textarea {
|
|
2412
2402
|
min-height: 160px;
|
|
2413
2403
|
}
|
|
@@ -2570,7 +2560,7 @@ export function pageShell({ title, body, head = "", afterBody = "", locale = DEF
|
|
|
2570
2560
|
overflow-wrap: anywhere;
|
|
2571
2561
|
}
|
|
2572
2562
|
.artifact-row > span:not(.row-main):not(.tags):not(.badge) {
|
|
2573
|
-
font-family: var(--
|
|
2563
|
+
font-family: var(--sans);
|
|
2574
2564
|
font-size: 12.5px;
|
|
2575
2565
|
color: var(--muted);
|
|
2576
2566
|
}
|
|
@@ -2596,7 +2586,7 @@ export function pageShell({ title, body, head = "", afterBody = "", locale = DEF
|
|
|
2596
2586
|
border-radius: 999px;
|
|
2597
2587
|
background: var(--panel-2);
|
|
2598
2588
|
color: var(--muted);
|
|
2599
|
-
font-family: var(--
|
|
2589
|
+
font-family: var(--sans);
|
|
2600
2590
|
font-size: 11.5px;
|
|
2601
2591
|
}
|
|
2602
2592
|
.badge {
|
|
@@ -2604,10 +2594,10 @@ export function pageShell({ title, body, head = "", afterBody = "", locale = DEF
|
|
|
2604
2594
|
align-items: center;
|
|
2605
2595
|
padding: 3px 9px;
|
|
2606
2596
|
border-radius: 999px;
|
|
2607
|
-
border: 1px solid
|
|
2608
|
-
background:
|
|
2609
|
-
color:
|
|
2610
|
-
font-family: var(--
|
|
2597
|
+
border: 1px solid var(--line);
|
|
2598
|
+
background: var(--panel-2);
|
|
2599
|
+
color: var(--muted);
|
|
2600
|
+
font-family: var(--sans);
|
|
2611
2601
|
font-size: 11.5px;
|
|
2612
2602
|
line-height: 1.45;
|
|
2613
2603
|
white-space: nowrap;
|
|
@@ -2694,9 +2684,9 @@ export function pageShell({ title, body, head = "", afterBody = "", locale = DEF
|
|
|
2694
2684
|
width: 100%;
|
|
2695
2685
|
min-height: 320px;
|
|
2696
2686
|
border: 1px solid var(--line);
|
|
2697
|
-
border-radius:
|
|
2687
|
+
border-radius: 8px;
|
|
2698
2688
|
background: var(--panel);
|
|
2699
|
-
box-shadow:
|
|
2689
|
+
box-shadow: none;
|
|
2700
2690
|
}
|
|
2701
2691
|
.split-preview {
|
|
2702
2692
|
display: grid;
|
|
@@ -2790,12 +2780,12 @@ export function pageShell({ title, body, head = "", afterBody = "", locale = DEF
|
|
|
2790
2780
|
.artifact-code {
|
|
2791
2781
|
margin: 0;
|
|
2792
2782
|
border: 1px solid var(--line);
|
|
2793
|
-
border-radius:
|
|
2783
|
+
border-radius: 8px;
|
|
2794
2784
|
background: var(--panel);
|
|
2795
2785
|
}
|
|
2796
2786
|
.artifact-doc {
|
|
2797
2787
|
padding: 32px;
|
|
2798
|
-
box-shadow:
|
|
2788
|
+
box-shadow: none;
|
|
2799
2789
|
}
|
|
2800
2790
|
.artifact-doc h1,
|
|
2801
2791
|
.artifact-doc h2,
|
|
@@ -2831,10 +2821,16 @@ export function pageShell({ title, body, head = "", afterBody = "", locale = DEF
|
|
|
2831
2821
|
border-radius: 10px;
|
|
2832
2822
|
overflow: hidden;
|
|
2833
2823
|
}
|
|
2834
|
-
.artifact-doc-codemirror .cm-editor
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
}
|
|
2824
|
+
.artifact-doc-codemirror .cm-editor,
|
|
2825
|
+
.artifact-code-viewer .cm-editor { min-height: 0; background: #f8f9fb; color: #20242b; }
|
|
2826
|
+
.artifact-doc-codemirror .cm-editor .cm-content,
|
|
2827
|
+
.artifact-code-viewer .cm-editor .cm-content { min-height: 0; }
|
|
2828
|
+
.artifact-doc-codemirror .cm-editor .cm-gutters,
|
|
2829
|
+
.artifact-code-viewer .cm-editor .cm-gutters { background: #eef0f3; color: #626c79; border-color: #dfe3e8; }
|
|
2830
|
+
.artifact-doc-codemirror .cm-editor .cm-activeLine,
|
|
2831
|
+
.artifact-doc-codemirror .cm-editor .cm-activeLineGutter,
|
|
2832
|
+
.artifact-code-viewer .cm-editor .cm-activeLine,
|
|
2833
|
+
.artifact-code-viewer .cm-editor .cm-activeLineGutter { background: transparent; }
|
|
2838
2834
|
.artifact-doc-codemirror .cm-scroller {
|
|
2839
2835
|
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
|
2840
2836
|
font-size: 13px;
|
|
@@ -2887,7 +2883,7 @@ export function pageShell({ title, body, head = "", afterBody = "", locale = DEF
|
|
|
2887
2883
|
.artifact-notebook-cell-code {
|
|
2888
2884
|
padding: 14px;
|
|
2889
2885
|
border: 1px solid var(--line);
|
|
2890
|
-
border-radius:
|
|
2886
|
+
border-radius: 8px;
|
|
2891
2887
|
background: var(--panel);
|
|
2892
2888
|
}
|
|
2893
2889
|
.artifact-notebook-execcount {
|
|
@@ -2976,8 +2972,8 @@ export function pageShell({ title, body, head = "", afterBody = "", locale = DEF
|
|
|
2976
2972
|
color: var(--muted);
|
|
2977
2973
|
font-family: var(--mono);
|
|
2978
2974
|
font-size: 11.5px;
|
|
2979
|
-
text-transform:
|
|
2980
|
-
letter-spacing: 0
|
|
2975
|
+
text-transform: none;
|
|
2976
|
+
letter-spacing: 0;
|
|
2981
2977
|
}
|
|
2982
2978
|
.summary-card strong {
|
|
2983
2979
|
font-size: 20px;
|
|
@@ -3070,7 +3066,7 @@ export function pageShell({ title, body, head = "", afterBody = "", locale = DEF
|
|
|
3070
3066
|
}
|
|
3071
3067
|
.artifact-raw-details {
|
|
3072
3068
|
border: 1px solid var(--line);
|
|
3073
|
-
border-radius:
|
|
3069
|
+
border-radius: 8px;
|
|
3074
3070
|
background: var(--panel);
|
|
3075
3071
|
overflow: hidden;
|
|
3076
3072
|
}
|
|
@@ -3092,9 +3088,9 @@ export function pageShell({ title, body, head = "", afterBody = "", locale = DEF
|
|
|
3092
3088
|
margin: 0;
|
|
3093
3089
|
padding: 14px;
|
|
3094
3090
|
border: 1px solid var(--line);
|
|
3095
|
-
border-radius:
|
|
3091
|
+
border-radius: 8px;
|
|
3096
3092
|
background: var(--panel);
|
|
3097
|
-
box-shadow:
|
|
3093
|
+
box-shadow: none;
|
|
3098
3094
|
}
|
|
3099
3095
|
.artifact-media img,
|
|
3100
3096
|
.artifact-media video {
|
|
@@ -3123,25 +3119,17 @@ export function pageShell({ title, body, head = "", afterBody = "", locale = DEF
|
|
|
3123
3119
|
}
|
|
3124
3120
|
.artifact-code-viewer {
|
|
3125
3121
|
border: 1px solid var(--line);
|
|
3126
|
-
border-radius:
|
|
3122
|
+
border-radius: 8px;
|
|
3127
3123
|
overflow: hidden;
|
|
3128
3124
|
background: var(--code);
|
|
3129
3125
|
}
|
|
3130
|
-
|
|
3131
|
-
min-height: 50vh;
|
|
3132
|
-
background: var(--code);
|
|
3133
|
-
color: var(--code-text);
|
|
3134
|
-
}
|
|
3126
|
+
|
|
3135
3127
|
.artifact-code-viewer .cm-scroller {
|
|
3136
3128
|
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
|
3137
3129
|
font-size: 13px;
|
|
3138
3130
|
line-height: 1.55;
|
|
3139
3131
|
}
|
|
3140
|
-
|
|
3141
|
-
background: color-mix(in srgb, var(--code) 84%, white);
|
|
3142
|
-
color: var(--muted);
|
|
3143
|
-
border-color: rgba(255, 255, 255, 0.08);
|
|
3144
|
-
}
|
|
3132
|
+
|
|
3145
3133
|
.artifact-react-disabled {
|
|
3146
3134
|
display: grid;
|
|
3147
3135
|
gap: 12px;
|
|
@@ -3160,11 +3148,9 @@ export function pageShell({ title, body, head = "", afterBody = "", locale = DEF
|
|
|
3160
3148
|
font-size: 13px;
|
|
3161
3149
|
}
|
|
3162
3150
|
@media (max-width: 760px) {
|
|
3163
|
-
.topbar {
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
padding: 20px;
|
|
3167
|
-
}
|
|
3151
|
+
.topbar { align-items: flex-start; flex-direction: column; gap: 12px; }
|
|
3152
|
+
.topbar, .dashboard-layout, .artifact-view, .artifact-editor, .artifact-view-wide { width: calc(100% - 32px); margin-top: 20px; }
|
|
3153
|
+
input:not([type="checkbox"]):not([type="radio"]), select, button { min-height: 44px; }
|
|
3168
3154
|
.artifact-row {
|
|
3169
3155
|
grid-template-columns: 1fr;
|
|
3170
3156
|
gap: 6px;
|
|
@@ -3186,6 +3172,114 @@ export function pageShell({ title, body, head = "", afterBody = "", locale = DEF
|
|
|
3186
3172
|
padding: 18px;
|
|
3187
3173
|
}
|
|
3188
3174
|
}
|
|
3175
|
+
.artifact-editor { max-width: 1000px; }
|
|
3176
|
+
.auth-panel { max-width: 520px; }
|
|
3177
|
+
.meta-card { padding: 24px; }
|
|
3178
|
+
.meta-card h2 { font-size: 16px; font-weight: 650; }
|
|
3179
|
+
.meta-card table { display: block; max-width: 100%; overflow-x: auto; }
|
|
3180
|
+
.artifact-doc { font-size: 15px; line-height: 1.7; }
|
|
3181
|
+
.workspace-header { background: var(--panel); border-bottom: 1px solid var(--line); padding: 12px 24px; display: flex; align-items: center; justify-content: space-between; gap: 16px; }
|
|
3182
|
+
.workspace-brand { display: flex; align-items: center; gap: 10px; color: var(--text); font-size: 19px; font-weight: 700; letter-spacing: -.04em; }
|
|
3183
|
+
.workspace-brand svg { width: 27px; height: 27px; color: var(--accent); }
|
|
3184
|
+
.workspace-desktop { display: none; }
|
|
3185
|
+
.workspace-mobile > summary { cursor: pointer; padding: 8px 12px; border: 1px solid var(--line-2); border-radius: 6px; }
|
|
3186
|
+
.workspace-mobile[open] { flex-basis: 100%; }
|
|
3187
|
+
.workspace-header:has(.workspace-mobile[open]) { flex-wrap: wrap; }
|
|
3188
|
+
.workspace-mobile nav { padding-top: 16px; }
|
|
3189
|
+
.workspace-primary, .workspace-actions { display: grid; gap: 4px; }
|
|
3190
|
+
.workspace-primary { padding-bottom: 16px; border-bottom: 1px solid var(--line); margin-bottom: 16px; }
|
|
3191
|
+
.workspace-header nav a, .workspace-dropdown summary, .workspace-dropdown .nav-form button { display: block; min-height: 38px; padding: 8px 10px; color: var(--muted); border-radius: 6px; font-size: 14px; text-align: left; }
|
|
3192
|
+
.workspace-header nav a:hover, .workspace-dropdown summary:hover { background: var(--panel-2); color: var(--text); text-decoration: none; }
|
|
3193
|
+
.workspace-header [aria-current="page"], .workspace-header .is-current { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
|
|
3194
|
+
.workspace-dropdown summary { cursor: pointer; list-style: none; }
|
|
3195
|
+
.workspace-dropdown summary::-webkit-details-marker { display: none; }
|
|
3196
|
+
.workspace-dropdown summary::after { content: "⌄"; float: right; margin-left: 8px; }
|
|
3197
|
+
.workspace-dropdown[open] > summary::after { content: "⌃"; }
|
|
3198
|
+
.workspace-version { color: var(--faint); font-size: 12px; padding: 10px; }
|
|
3199
|
+
.settings-page { max-width: 1100px; }
|
|
3200
|
+
.settings-page .meta-card { gap: 16px; }
|
|
3201
|
+
.settings-page .meta-card p { margin: 0; }
|
|
3202
|
+
.settings-page form > button { justify-self: start; width: fit-content; }
|
|
3203
|
+
.settings-page form:not(.editor-form) { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
|
|
3204
|
+
.settings-page .field { min-width: 0; }
|
|
3205
|
+
.settings-page textarea[name="allowedCidrs"] { min-height: 120px; height: 140px; }
|
|
3206
|
+
.settings-page .editor-form { gap: 12px; }
|
|
3207
|
+
.workspace-dropdown-content { display: grid; padding-left: 10px; border-left: 1px solid var(--line); margin: 4px 0 8px 10px; }
|
|
3208
|
+
.workspace-dropdown-content .nav-form { display: block; }
|
|
3209
|
+
.workspace-identity { overflow-wrap: anywhere; font-size: 12px; color: var(--faint); padding: 8px 10px; }
|
|
3210
|
+
.workspace-header .language-switcher { border: 0; margin: 12px 0 0; padding: 0; display: flex; flex-wrap: wrap; font-size: 12px; }
|
|
3211
|
+
.workspace-header .language-switcher a, .workspace-header .language-switcher .active { min-height: 38px; padding: 8px 10px; }
|
|
3212
|
+
.workspace-skip { position: absolute; left: 16px; top: -100px; z-index: 100; background: var(--panel); padding: 8px 16px; }
|
|
3213
|
+
.workspace-skip:focus { top: 8px; }
|
|
3214
|
+
:focus-visible { outline: 2px solid var(--ring); outline-offset: 3px; }
|
|
3215
|
+
.dashboard-heading { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 20px; }
|
|
3216
|
+
.dashboard-heading h1 { font-size: 25px; font-weight: 650; line-height: 1.3; margin: 0; letter-spacing: -.025em; }
|
|
3217
|
+
.dashboard-heading nav { display: flex; align-items: center; gap: 16px; }
|
|
3218
|
+
.dashboard-heading .button-link { padding: 8px 14px; background: var(--accent); color: var(--accent-ink); border-radius: 6px; }
|
|
3219
|
+
.dashboard .filter-form { display: block; width: 100%; margin-bottom: 0; }
|
|
3220
|
+
.dashboard-search-row { display: flex; align-items: center; gap: 12px; }
|
|
3221
|
+
.dashboard-search { flex: 1; min-width: 0; }
|
|
3222
|
+
.dashboard-search input { width: 100%; min-width: 0; background: var(--panel); }
|
|
3223
|
+
.dashboard-clear { white-space: nowrap; padding: 8px 0; font-size: 13px; }
|
|
3224
|
+
.dashboard-sr-only { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
|
|
3225
|
+
.dashboard-advanced, .dashboard-saved-views { margin: 0; border-bottom: 1px solid var(--line); }
|
|
3226
|
+
.dashboard-advanced > summary, .dashboard-saved-views > summary { padding: 10px 0; cursor: pointer; color: var(--muted); font-size: 13px; }
|
|
3227
|
+
.dashboard-filter-count { color: var(--faint); margin-left: 8px; font-size: 12px; }
|
|
3228
|
+
.dashboard-filter-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; padding: 12px 0 20px; }
|
|
3229
|
+
.dashboard-field { display: grid; gap: 6px; min-width: 0; color: var(--muted); font-size: 12px; }
|
|
3230
|
+
.dashboard-field input, .dashboard-field select { min-width: 0; width: 100%; font-size: 14px; }
|
|
3231
|
+
.dashboard-filter-actions { grid-column: 1 / -1; }
|
|
3232
|
+
.dashboard-active-filters { display: flex; gap: 4px 16px; flex-wrap: wrap; font-size: 12px; color: var(--muted); margin: 10px 0; overflow-wrap: anywhere; }
|
|
3233
|
+
.dashboard-saved-content { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; padding: 8px 0 20px; }
|
|
3234
|
+
.dashboard-saved-content .empty { padding: 0; }
|
|
3235
|
+
.dashboard .saved-view-form { align-content: start; }
|
|
3236
|
+
.dashboard .saved-view-list { margin: 0; }
|
|
3237
|
+
.dashboard .saved-view-item { min-width: 0; }
|
|
3238
|
+
.artifact-overview { align-items: flex-start; }
|
|
3239
|
+
.artifact-details { min-width: 0; }
|
|
3240
|
+
.artifact-details > summary { cursor: pointer; color: var(--muted); font-size: 13px; }
|
|
3241
|
+
.artifact-details > .meta-strip { padding: 12px 0 0; max-width: 100%; overflow-wrap: anywhere; }
|
|
3242
|
+
.dashboard-results { margin: 18px 0 10px; display: flex; justify-content: space-between; }
|
|
3243
|
+
.dashboard-results > span + span { color: var(--faint); font-size: 12px; }
|
|
3244
|
+
.dashboard .row-main strong { white-space: normal; overflow-wrap: anywhere; font-size: 15px; line-height: 1.45; font-weight: 600; }
|
|
3245
|
+
.dashboard .row-agent, .dashboard .row-formats { display: grid; gap: 2px; min-width: 0; font-size: 12px; color: var(--muted); }
|
|
3246
|
+
.dashboard .row-formats { display: flex; flex-wrap: wrap; gap: 4px 8px; }
|
|
3247
|
+
.dashboard .row-formats .badge { border: 0; padding: 0; background: transparent; color: var(--muted); font: inherit; border-radius: 0; }
|
|
3248
|
+
.dashboard .artifact-row .tag { border: 0; background: transparent; padding: 0; border-radius: 0; font-family: var(--sans); font-size: 12px; }
|
|
3249
|
+
.dashboard .artifact-row .tag + .tag::before { content: "·"; margin-right: 6px; color: var(--faint); }
|
|
3250
|
+
.dashboard .tags { min-width: 0; overflow-wrap: anywhere; }
|
|
3251
|
+
.dashboard .row-agent .publisher-cell { font-size: 12px; color: var(--faint); }
|
|
3252
|
+
.dashboard .artifact-row { grid-template-columns: minmax(180px, 1fr) minmax(100px, 150px) minmax(100px, 145px) minmax(80px, 150px); min-height: 76px; padding: 14px 16px; gap: 16px; }
|
|
3253
|
+
@media (min-width: 1100px) {
|
|
3254
|
+
body:has(> .workspace-header) { padding-left: 196px; }
|
|
3255
|
+
.workspace-header { position: fixed; inset: 0 auto 0 0; width: 196px; padding: 24px 12px; border-bottom: 0; border-right: 1px solid var(--line); display: block; overflow-y: auto; z-index: 20; }
|
|
3256
|
+
.workspace-brand { padding: 0 10px 26px; }
|
|
3257
|
+
.workspace-desktop { display: block; }
|
|
3258
|
+
.workspace-mobile { display: none; }
|
|
3259
|
+
}
|
|
3260
|
+
@media (max-width: 1099px) {
|
|
3261
|
+
.dashboard-filter-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
3262
|
+
}
|
|
3263
|
+
@media (max-width: 899px) {
|
|
3264
|
+
.dashboard .artifact-row { grid-template-columns: auto minmax(0, 1fr); gap: 8px 12px; }
|
|
3265
|
+
.dashboard .row-main, .dashboard .tags { grid-column: 1 / -1; }
|
|
3266
|
+
.dashboard .row-agent { max-width: 42vw; }
|
|
3267
|
+
.dashboard .row-agent, .dashboard .row-formats { display: flex; gap: 8px; flex-wrap: wrap; }
|
|
3268
|
+
.dashboard .row-agent .publisher-cell { max-width: 100%; }
|
|
3269
|
+
.dashboard .tags { gap: 6px; }
|
|
3270
|
+
}
|
|
3271
|
+
@media (max-width: 640px) {
|
|
3272
|
+
.workspace-header { padding: 12px 16px; }
|
|
3273
|
+
.workspace-header nav a, .workspace-dropdown summary, .workspace-dropdown .nav-form button { min-height: 44px; }
|
|
3274
|
+
.dashboard-filter-grid, .dashboard-saved-content { grid-template-columns: 1fr; }
|
|
3275
|
+
.dashboard-heading h1 { font-size: 22px; }
|
|
3276
|
+
.dashboard-heading nav { gap: 12px; font-size: 13px; }
|
|
3277
|
+
.dashboard-search-row { gap: 8px; }
|
|
3278
|
+
.dashboard-search-row button { padding: 8px 12px; }
|
|
3279
|
+
.dashboard-advanced > summary, .dashboard-saved-views > summary { min-height: 44px; }
|
|
3280
|
+
.dashboard .artifact-row { padding: 14px; }
|
|
3281
|
+
.dashboard .row-main strong { font-size: 14px; }
|
|
3282
|
+
}
|
|
3189
3283
|
</style>
|
|
3190
3284
|
</head>
|
|
3191
3285
|
<body>
|
|
@@ -3418,13 +3512,25 @@ function languageSwitcher(view) {
|
|
|
3418
3512
|
return `<span class="language-switcher">${english}${korean}</span>`;
|
|
3419
3513
|
}
|
|
3420
3514
|
|
|
3421
|
-
function
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3515
|
+
function workspaceHeader(user, view) {
|
|
3516
|
+
const pathname = new URL(view.currentPath, "http://artifacty.local").pathname;
|
|
3517
|
+
const link = (href, key, active = pathname === href, className = "") => `<a href="${view.href(href)}"${active ? ' aria-current="page"' : ""}${className ? ` class="${className}"` : ""}>${view.text(key)}</a>`;
|
|
3518
|
+
const artifactActive = pathname === "/" || pathname.startsWith("/artifacts/");
|
|
3519
|
+
const adminLinks = user?.role === "admin" ? `<details class="workspace-dropdown"${pathname.startsWith("/admin/") ? " open" : ""}><summary${pathname.startsWith("/admin/") ? ' class="is-current"' : ""}>${view.text("nav.administration")}</summary><div class="workspace-dropdown-content">
|
|
3520
|
+
${link("/admin/access", "access.title")}${link("/admin/users", "nav.users")}${link("/admin/backup", "nav.backup")}${link("/admin/retention", "retention.navLabel")}${link("/admin/webhooks", "nav.webhooks")}${link("/admin/update", "update.title")}<span class="workspace-version">Artifacty v${escapeHtml(APP_VERSION)}</span>
|
|
3521
|
+
</div></details>` : "";
|
|
3522
|
+
const account = user ? `<details class="workspace-dropdown"${pathname.startsWith("/account") ? " open" : ""}><summary${pathname.startsWith("/account") ? ' class="is-current"' : ""}>${view.text("nav.account")}</summary><div class="workspace-dropdown-content">
|
|
3523
|
+
<span class="workspace-identity">${escapeHtml(user.name || user.email || "")}</span>${link("/account", "nav.accountTokens")}${link("/account/password", "nav.password")}
|
|
3524
|
+
<form method="post" action="/logout" class="nav-form">${localeInput(view.locale)}<button type="submit">${view.text("nav.signOut")}</button></form>
|
|
3525
|
+
</div></details>` : user === null ? link("/login", "nav.signIn") : "";
|
|
3526
|
+
const links = `<div class="workspace-primary">${link("/", "nav.artifacts", artifactActive)}${user ? link("/inbox", "inbox.title") : ""}</div><div class="workspace-actions">${link("/new", "nav.new", pathname === "/new", "workspace-create")}${link("/import", "nav.import")}${adminLinks}${account}${link("/docs/api", "nav.apiDocs")}${languageSwitcher(view)}</div>`;
|
|
3527
|
+
return `<a class="workspace-skip" href="#main-content">${view.text("nav.skip")}</a><header class="workspace-header"><a class="workspace-brand" href="${view.href("/")}">${brandMark()}<span>artifacty</span></a>
|
|
3528
|
+
<nav class="workspace-desktop" aria-label="${view.attr("dashboard.navigation")}">${links}</nav>
|
|
3529
|
+
<details class="workspace-mobile"><summary>${view.text("dashboard.more")}</summary><nav aria-label="${view.attr("dashboard.navigation")}">${links}</nav></details>
|
|
3530
|
+
</header>`;
|
|
3426
3531
|
}
|
|
3427
3532
|
|
|
3533
|
+
|
|
3428
3534
|
export function escapeHtml(value) {
|
|
3429
3535
|
return String(value)
|
|
3430
3536
|
.replaceAll("&", "&")
|