@rigel-cms/rigel 1.0.0-rc.1 → 1.1.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.md +19 -7
- package/docs/extensions.md +194 -0
- package/package.json +16 -9
- package/src/article-body.ts +49 -37
- package/src/article-render.ts +25 -2
- package/src/article.ts +44 -0
- package/src/config.ts +18 -3
- package/src/editor/App.svelte +99 -98
- package/src/editor/blocks/extension.ts +29 -0
- package/src/editor/blocks/index.ts +17 -15
- package/src/editor/blocks/video.ts +2 -1
- package/src/editor/i18n/README.md +23 -0
- package/src/editor/i18n/de/connection.json +136 -0
- package/src/editor/i18n/de/defaults.json +6 -0
- package/src/editor/i18n/de/fields.json +130 -0
- package/src/editor/i18n/de/local.json +49 -0
- package/src/editor/i18n/de/media.json +166 -0
- package/src/editor/i18n/de/richtext.json +116 -0
- package/src/editor/i18n/de/shell.json +181 -0
- package/src/editor/i18n/de/system.json +14 -0
- package/src/editor/i18n/de/validation.json +82 -0
- package/src/editor/i18n/en/connection.json +136 -0
- package/src/editor/i18n/en/defaults.json +6 -0
- package/src/editor/i18n/en/fields.json +130 -0
- package/src/editor/i18n/en/local.json +49 -0
- package/src/editor/i18n/en/media.json +166 -0
- package/src/editor/i18n/en/richtext.json +116 -0
- package/src/editor/i18n/en/shell.json +181 -0
- package/src/editor/i18n/en/system.json +14 -0
- package/src/editor/i18n/en/validation.json +82 -0
- package/src/editor/i18n/es/connection.json +136 -0
- package/src/editor/i18n/es/defaults.json +6 -0
- package/src/editor/i18n/es/fields.json +130 -0
- package/src/editor/i18n/es/local.json +49 -0
- package/src/editor/i18n/es/media.json +166 -0
- package/src/editor/i18n/es/richtext.json +116 -0
- package/src/editor/i18n/es/shell.json +181 -0
- package/src/editor/i18n/es/system.json +14 -0
- package/src/editor/i18n/es/validation.json +82 -0
- package/src/editor/i18n/format.ts +26 -0
- package/src/editor/i18n/fr/connection.json +136 -0
- package/src/editor/i18n/fr/defaults.json +6 -0
- package/src/editor/i18n/fr/fields.json +130 -0
- package/src/editor/i18n/fr/local.json +49 -0
- package/src/editor/i18n/fr/media.json +166 -0
- package/src/editor/i18n/fr/richtext.json +116 -0
- package/src/editor/i18n/fr/shell.json +181 -0
- package/src/editor/i18n/fr/system.json +14 -0
- package/src/editor/i18n/fr/validation.json +82 -0
- package/src/editor/i18n/index.ts +18 -0
- package/src/editor/i18n/it/connection.json +136 -0
- package/src/editor/i18n/it/defaults.json +6 -0
- package/src/editor/i18n/it/fields.json +130 -0
- package/src/editor/i18n/it/local.json +49 -0
- package/src/editor/i18n/it/media.json +166 -0
- package/src/editor/i18n/it/richtext.json +116 -0
- package/src/editor/i18n/it/shell.json +181 -0
- package/src/editor/i18n/it/system.json +14 -0
- package/src/editor/i18n/it/validation.json +82 -0
- package/src/editor/i18n/ja/connection.json +136 -0
- package/src/editor/i18n/ja/defaults.json +6 -0
- package/src/editor/i18n/ja/fields.json +130 -0
- package/src/editor/i18n/ja/local.json +49 -0
- package/src/editor/i18n/ja/media.json +166 -0
- package/src/editor/i18n/ja/richtext.json +116 -0
- package/src/editor/i18n/ja/shell.json +181 -0
- package/src/editor/i18n/ja/system.json +14 -0
- package/src/editor/i18n/ja/validation.json +82 -0
- package/src/editor/i18n/locales.ts +13 -0
- package/src/editor/i18n/nl/connection.json +136 -0
- package/src/editor/i18n/nl/defaults.json +6 -0
- package/src/editor/i18n/nl/fields.json +130 -0
- package/src/editor/i18n/nl/local.json +49 -0
- package/src/editor/i18n/nl/media.json +166 -0
- package/src/editor/i18n/nl/richtext.json +116 -0
- package/src/editor/i18n/nl/shell.json +181 -0
- package/src/editor/i18n/nl/system.json +14 -0
- package/src/editor/i18n/nl/validation.json +82 -0
- package/src/editor/i18n/ru/connection.json +138 -0
- package/src/editor/i18n/ru/defaults.json +6 -0
- package/src/editor/i18n/ru/fields.json +132 -0
- package/src/editor/i18n/ru/local.json +49 -0
- package/src/editor/i18n/ru/media.json +166 -0
- package/src/editor/i18n/ru/richtext.json +116 -0
- package/src/editor/i18n/ru/shell.json +181 -0
- package/src/editor/i18n/ru/system.json +14 -0
- package/src/editor/i18n/ru/validation.json +82 -0
- package/src/editor/i18n/server.ts +13 -0
- package/src/editor/i18n/zh/connection.json +136 -0
- package/src/editor/i18n/zh/defaults.json +6 -0
- package/src/editor/i18n/zh/fields.json +130 -0
- package/src/editor/i18n/zh/local.json +49 -0
- package/src/editor/i18n/zh/media.json +166 -0
- package/src/editor/i18n/zh/richtext.json +116 -0
- package/src/editor/i18n/zh/shell.json +181 -0
- package/src/editor/i18n/zh/system.json +14 -0
- package/src/editor/i18n/zh/validation.json +82 -0
- package/src/editor/lib/ActionDialog.svelte +5 -4
- package/src/editor/lib/BlockEditor.svelte +305 -88
- package/src/editor/lib/CollectionPanel.svelte +17 -15
- package/src/editor/lib/DatePicker.svelte +15 -12
- package/src/editor/lib/ExtensionMount.svelte +12 -0
- package/src/editor/lib/Field.svelte +3 -2
- package/src/editor/lib/ImagePicker.svelte +82 -81
- package/src/editor/lib/MarkdownEditor.svelte +25 -24
- package/src/editor/lib/MediaPicker.svelte +6 -5
- package/src/editor/lib/MenuEditor.svelte +31 -30
- package/src/editor/lib/PreviewFrame.svelte +11 -6
- package/src/editor/lib/PropertiesTabs.svelte +3 -2
- package/src/editor/lib/PublishDialog.svelte +15 -14
- package/src/editor/lib/ReferenceSelect.svelte +3 -2
- package/src/editor/lib/SchemaField.svelte +15 -13
- package/src/editor/lib/SchemaPanel.svelte +20 -19
- package/src/editor/lib/Select.svelte +3 -2
- package/src/editor/lib/SeoGuidance.svelte +6 -5
- package/src/editor/lib/Settings.svelte +54 -54
- package/src/editor/lib/Shortcuts.svelte +7 -6
- package/src/editor/lib/SitePreview.svelte +10 -9
- package/src/editor/lib/TopBar.svelte +18 -17
- package/src/editor/lib/UnsavedChanges.svelte +7 -6
- package/src/editor/lib/VisualEditor.svelte +14 -13
- package/src/editor/lib/calendar.ts +10 -4
- package/src/editor/lib/embed-preview.ts +23 -5
- package/src/editor/lib/extension-preview.ts +121 -0
- package/src/editor/lib/github-auth.ts +23 -22
- package/src/editor/lib/github-backend.ts +96 -91
- package/src/editor/lib/icons.ts +4 -0
- package/src/editor/lib/images.ts +33 -31
- package/src/editor/lib/menu-tree.ts +5 -4
- package/src/editor/lib/preview-frame.ts +4 -3
- package/src/editor/lib/schema-edit.ts +3 -2
- package/src/editor/lib/settings.ts +5 -3
- package/src/editor/lib/slash-command.ts +1 -0
- package/src/editor/lib/tooltip.ts +1 -1
- package/src/editor/main.ts +9 -0
- package/src/editor/styles.css +6 -3
- package/src/env.d.ts +11 -1
- package/src/extensions.ts +160 -0
- package/src/manifest/collections.ts +6 -5
- package/src/manifest/menu.ts +12 -10
- package/src/manifest/navigation.ts +9 -8
- package/src/manifest/references.ts +8 -7
- package/src/manifest/seo.ts +4 -3
- package/src/manifest/types.ts +1 -0
- package/src/preview-urls.ts +6 -0
- package/src/preview.ts +38 -7
- package/src/routes/local-api.ts +5 -3
- package/src/routes/local-documents.ts +20 -16
- package/src/routes/local-media.ts +21 -16
- package/src/routes/local-menu.ts +9 -7
- package/src/routes/local-post.ts +18 -12
- package/src/routes/media-files.ts +10 -8
- package/src/routes/rigel-page.astro +2 -1
- package/src/schema/store.ts +22 -20
- package/src/schema/types.ts +1 -0
- package/src/schema/validate.ts +73 -69
- package/src/slugs.ts +12 -11
- package/src/vite/manifest-plugin.ts +33 -1
package/src/editor/App.svelte
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { t, locale } from "./i18n";
|
|
2
3
|
import { onMount, tick } from "svelte";
|
|
3
4
|
import { tooltipRoot } from "./lib/tooltip";
|
|
4
5
|
onMount(() => tooltipRoot(document.body).destroy);
|
|
@@ -33,11 +34,11 @@
|
|
|
33
34
|
import PublishDialog from "./lib/PublishDialog.svelte";
|
|
34
35
|
import VisualEditor from "./lib/VisualEditor.svelte";
|
|
35
36
|
import { newDocumentFromTemplate } from "../schema/paths";
|
|
36
|
-
import { previewUrl } from "../preview";
|
|
37
|
+
import { previewUrl, previewPath } from "../preview";
|
|
37
38
|
import type { ContentDocument, Schema } from "../schema/types";
|
|
38
39
|
import { buildSidebar, navigationKey, type Selection } from "../manifest/navigation";
|
|
39
40
|
import { startGitHubLogin, checkGitHubAccess, type GitHubAccess } from "./lib/github-auth";
|
|
40
|
-
import { loadManifestRemote, saveRemoteSelection, deleteRemotePost, reviewRemotePublish, confirmRemotePublish, type GitContext, type PublishReview } from "./lib/github-backend";
|
|
41
|
+
import { loadManifestRemote, saveRemoteSelection, deleteRemotePost, reviewRemotePublish, confirmRemotePublish, PublishReviewChangedError, type GitContext, type PublishReview } from "./lib/github-backend";
|
|
41
42
|
|
|
42
43
|
type Sel = Selection;
|
|
43
44
|
|
|
@@ -79,9 +80,9 @@
|
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
function publishGuard(context: GitContext, revision: number, auth: number) {
|
|
82
|
-
if (!publishOpen || revision !== publishRevision || auth !== authRevision || gitContext !== context) throw new Error("
|
|
83
|
+
if (!publishOpen || revision !== publishRevision || auth !== authRevision || gitContext !== context) throw new Error($t("shell.publishReviewInactive"));
|
|
83
84
|
requireGitContext();
|
|
84
|
-
if (dirty || saveTask || saveState === "saving") throw new Error("
|
|
85
|
+
if (dirty || saveTask || saveState === "saving") throw new Error($t("shell.saveBeforeReview"));
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
async function reviewPublish() {
|
|
@@ -101,7 +102,7 @@
|
|
|
101
102
|
publishReview = review;
|
|
102
103
|
} catch (error) {
|
|
103
104
|
handleAuthError(error);
|
|
104
|
-
if (revision === publishRevision) publishError = error instanceof Error ? error.message : "
|
|
105
|
+
if (revision === publishRevision) publishError = error instanceof Error ? error.message : $t("shell.reviewError");
|
|
105
106
|
} finally { publishLoading = false; }
|
|
106
107
|
}
|
|
107
108
|
|
|
@@ -122,13 +123,13 @@
|
|
|
122
123
|
guard();
|
|
123
124
|
manifest = remote;
|
|
124
125
|
openSelection(sel);
|
|
125
|
-
publishMessage = "
|
|
126
|
-
} else publishMessage =
|
|
126
|
+
publishMessage = $t("shell.draftsSynced");
|
|
127
|
+
} else publishMessage = $t("shell.publishedToBranch", { name: review.publishBranch });
|
|
127
128
|
} catch (error) {
|
|
128
129
|
handleAuthError(error);
|
|
129
130
|
if (revision === publishRevision) {
|
|
130
|
-
publishError = error instanceof Error ? error.message : "
|
|
131
|
-
if (
|
|
131
|
+
publishError = error instanceof Error ? error.message : $t("shell.publishVerificationError");
|
|
132
|
+
if (error instanceof PublishReviewChangedError) publishReview = null;
|
|
132
133
|
}
|
|
133
134
|
} finally { publishBusy = false; }
|
|
134
135
|
}
|
|
@@ -145,9 +146,9 @@
|
|
|
145
146
|
ready = false;
|
|
146
147
|
try {
|
|
147
148
|
const response = await fetch("/rigel/manifest.json", { cache: "no-store" });
|
|
148
|
-
if (!response.ok) throw new Error("
|
|
149
|
+
if (!response.ok) throw new Error($t("shell.configurationLoadError"));
|
|
149
150
|
const data: Manifest = await response.json();
|
|
150
|
-
if (!Array.isArray(data.pages) || (data.backend === "github" && !data.github)) throw new Error("
|
|
151
|
+
if (!Array.isArray(data.pages) || (data.backend === "github" && !data.github)) throw new Error($t("shell.configurationIncomplete"));
|
|
151
152
|
blueprint = data;
|
|
152
153
|
manifest = data;
|
|
153
154
|
ready = true;
|
|
@@ -160,16 +161,16 @@
|
|
|
160
161
|
}
|
|
161
162
|
}
|
|
162
163
|
} catch (error) {
|
|
163
|
-
loadError = error instanceof Error ? error.message : "
|
|
164
|
+
loadError = error instanceof Error ? error.message : $t("shell.configurationLoadFailed");
|
|
164
165
|
}
|
|
165
166
|
}
|
|
166
167
|
|
|
167
168
|
function requireGitContext() {
|
|
168
|
-
if (connectionBusy) throw new Error("
|
|
169
|
-
if (!gitContext || !connected || githubAccess?.status !== "ready") throw new Error("
|
|
169
|
+
if (connectionBusy) throw new Error($t("shell.waitForGithubVerification"));
|
|
170
|
+
if (!gitContext || !connected || githubAccess?.status !== "ready") throw new Error($t("shell.connectBeforeSaving"));
|
|
170
171
|
if (Date.now() >= sessionExpiresAt - 30_000) {
|
|
171
172
|
invalidateSession();
|
|
172
|
-
throw new Error("
|
|
173
|
+
throw new Error($t("shell.sessionExpiredRetry"));
|
|
173
174
|
}
|
|
174
175
|
return gitContext;
|
|
175
176
|
}
|
|
@@ -181,7 +182,7 @@
|
|
|
181
182
|
try {
|
|
182
183
|
if (Date.now() >= sessionExpiresAt - 30_000) {
|
|
183
184
|
invalidateSession();
|
|
184
|
-
throw new Error("
|
|
185
|
+
throw new Error($t("shell.sessionExpiredCheckAccess"));
|
|
185
186
|
}
|
|
186
187
|
const access = await checkGitHubAccess(context.token, config.repo, config.appSlug);
|
|
187
188
|
if (gitContext !== context) return;
|
|
@@ -228,7 +229,7 @@
|
|
|
228
229
|
writeSession(manifest.github, session);
|
|
229
230
|
await verifyGitHub();
|
|
230
231
|
} catch (error) {
|
|
231
|
-
connectionError = error instanceof Error ? error.message : "
|
|
232
|
+
connectionError = error instanceof Error ? error.message : $t("shell.githubConnectionError");
|
|
232
233
|
} finally {
|
|
233
234
|
connectionBusy = false;
|
|
234
235
|
}
|
|
@@ -239,7 +240,7 @@
|
|
|
239
240
|
connectionBusy = true;
|
|
240
241
|
connectionError = "";
|
|
241
242
|
try { await verifyGitHub(); }
|
|
242
|
-
catch (error) { connectionError = error instanceof Error ? error.message : "
|
|
243
|
+
catch (error) { connectionError = error instanceof Error ? error.message : $t("shell.githubAccessError"); }
|
|
243
244
|
finally { connectionBusy = false; }
|
|
244
245
|
}
|
|
245
246
|
|
|
@@ -292,19 +293,19 @@
|
|
|
292
293
|
const creatingDoc = docSel !== null && newDocument?.id === docSel.id;
|
|
293
294
|
try {
|
|
294
295
|
if ((selection?.kind === "post" || (docSel && !activeDocType?.shared)) && Object.hasOwn(values, "slug")) {
|
|
295
|
-
values.slug = normalizeUrlSlug(values.slug);
|
|
296
|
+
values.slug = normalizeUrlSlug(values.slug, $t);
|
|
296
297
|
draft.slug = values.slug;
|
|
297
298
|
}
|
|
298
299
|
if (githubMode) {
|
|
299
|
-
if (!selection) throw new Error("
|
|
300
|
+
if (!selection) throw new Error($t("shell.selectBeforeSaving"));
|
|
300
301
|
await saveRemoteSelection(requireGitContext(), manifest, selection, values, body, !!creating || creatingDoc);
|
|
301
302
|
} else {
|
|
302
303
|
const url = docSel
|
|
303
304
|
? `/rigel/api/local/documents/${encodeURIComponent(docSel.id)}`
|
|
304
305
|
: m ? `/rigel/api/local/menus/${m.slug}` : po && c ? `/rigel/api/local/${c.name}/${encodeURIComponent(po.slug)}` : "/rigel/api/local/save";
|
|
305
306
|
const payload = docSel ? { data: values } : m ? { slug: m.slug, title: values.title, items: values.items } : po ? { frontmatter: values, body } : { slug: p?.slug, values };
|
|
306
|
-
const res = await fetch(url, { method: docSel ? (creatingDoc ? "POST" : "PUT") : m ? "PUT" : "POST", headers: { "Content-Type": "application/json", ...(po && creating ? { "If-None-Match": "*" } : {}) }, body: JSON.stringify(payload) });
|
|
307
|
-
if (!res.ok) throw new Error((await res.text()) ||
|
|
307
|
+
const res = await fetch(url, { method: docSel ? (creatingDoc ? "POST" : "PUT") : m ? "PUT" : "POST", headers: { "Content-Type": "application/json", "Accept-Language": $locale, ...(po && creating ? { "If-None-Match": "*" } : {}) }, body: JSON.stringify(payload) });
|
|
308
|
+
if (!res.ok) throw new Error((await res.text()) || $t("shell.saveFailedStatus", { status: res.status }));
|
|
308
309
|
}
|
|
309
310
|
if (docSel) {
|
|
310
311
|
const list = manifest.documents ?? (manifest.documents = []);
|
|
@@ -324,7 +325,7 @@
|
|
|
324
325
|
} catch (error) {
|
|
325
326
|
handleAuthError(error);
|
|
326
327
|
saveState = "error";
|
|
327
|
-
saveError = error instanceof Error ? error.message : "
|
|
328
|
+
saveError = error instanceof Error ? error.message : $t("shell.saveChangesError");
|
|
328
329
|
return false;
|
|
329
330
|
}
|
|
330
331
|
}
|
|
@@ -338,10 +339,10 @@
|
|
|
338
339
|
|
|
339
340
|
let settings = $state(getSettings());
|
|
340
341
|
let settingsError = $state("");
|
|
341
|
-
const userName = $derived(githubMode ? githubAccess?.user.name ?? githubAccess?.user.login ?? "
|
|
342
|
+
const userName = $derived(githubMode ? githubAccess?.user.name ?? githubAccess?.user.login ?? $t("shell.githubUser") : $t("shell.localUser"));
|
|
342
343
|
const userAvatar = $derived(githubMode && /^https:\/\/avatars\.githubusercontent\.com\//.test(githubAccess?.user.avatar_url ?? "") ? githubAccess!.user.avatar_url : "");
|
|
343
344
|
let failedAvatar = $state("");
|
|
344
|
-
const userLogin = $derived(githubMode && githubAccess ? `@${githubAccess.user.login}` : "
|
|
345
|
+
const userLogin = $derived(githubMode && githubAccess ? `@${githubAccess.user.login}` : $t("shell.localMode"));
|
|
345
346
|
let content: HTMLDivElement;
|
|
346
347
|
let contentWidth = $state(0);
|
|
347
348
|
let resizing = $state(false);
|
|
@@ -390,10 +391,10 @@
|
|
|
390
391
|
|
|
391
392
|
function eraseBrowserData() {
|
|
392
393
|
actionDialog = {
|
|
393
|
-
title: "
|
|
394
|
-
description: "
|
|
394
|
+
title: $t("shell.eraseBrowserDataTitle"), confirmLabel: $t("shell.eraseData"), danger: true,
|
|
395
|
+
description: $t("shell.eraseBrowserDataDescription"),
|
|
395
396
|
onconfirm: () => {
|
|
396
|
-
if (saveState === "saving") throw new Error("
|
|
397
|
+
if (saveState === "saving") throw new Error($t("shell.waitForSave"));
|
|
397
398
|
eraseData();
|
|
398
399
|
dirty = false;
|
|
399
400
|
location.reload();
|
|
@@ -439,12 +440,12 @@
|
|
|
439
440
|
});
|
|
440
441
|
|
|
441
442
|
const columns = $derived.by((): { key: string; label: string; image?: boolean }[] => {
|
|
442
|
-
if (sel?.kind === "menus") return [{ key: "title", label: "
|
|
443
|
-
if (sel?.kind === "pages") return [{ key: "label", label: "
|
|
443
|
+
if (sel?.kind === "menus") return [{ key: "title", label: $t("shell.name") }, { key: "slug", label: $t("shell.slug") }];
|
|
444
|
+
if (sel?.kind === "pages") return [{ key: "label", label: $t("shell.title") }, { key: "slug", label: $t("shell.slug") }, { key: "category", label: $t("shell.category") }];
|
|
444
445
|
const source = sel?.kind === "documents" ? activeDocType : coll;
|
|
445
446
|
if (!source) return [];
|
|
446
447
|
const keys = ["title", "slug", ...(source.columns ?? (sel?.kind === "documents" ? [] : source.fields.filter(field => !isSeoField(field.key)).map(field => field.key)))];
|
|
447
|
-
const titleLabel = sel?.kind === "documents" && !source.fields.some(field => field.key === "title") ? source.fields.find(field => field.key === "name")?.label ?? "
|
|
448
|
+
const titleLabel = sel?.kind === "documents" && !source.fields.some(field => field.key === "title") ? source.fields.find(field => field.key === "name")?.label ?? $t("shell.title") : $t("shell.title");
|
|
448
449
|
const seen = new Set<string>();
|
|
449
450
|
const out: { key: string; label: string; image?: boolean }[] = [];
|
|
450
451
|
|
|
@@ -452,7 +453,7 @@
|
|
|
452
453
|
if (seen.has(k)) continue;
|
|
453
454
|
seen.add(k);
|
|
454
455
|
const f = source.fields.find((x) => x.key === k);
|
|
455
|
-
out.push({ key: k, label: k === "slug" ? "
|
|
456
|
+
out.push({ key: k, label: k === "slug" ? $t("shell.slug") : k === "title" ? titleLabel : f?.labelKey ? $t(f.labelKey) : f?.label ?? k, image: f?.type === "image" });
|
|
456
457
|
}
|
|
457
458
|
return sel?.kind === "documents" ? [...out.filter(column => column.image), ...out.filter(column => !column.image && column.key !== "slug"), ...out.filter(column => column.key === "slug")] : out;
|
|
458
459
|
});
|
|
@@ -468,7 +469,7 @@
|
|
|
468
469
|
});
|
|
469
470
|
});
|
|
470
471
|
|
|
471
|
-
const sidebar = $derived(buildSidebar(manifest));
|
|
472
|
+
const sidebar = $derived(buildSidebar(manifest, $t));
|
|
472
473
|
const pageCategory = $derived(sel?.kind === "pages" ? sel.category : undefined);
|
|
473
474
|
const schema = $derived<Schema | undefined>(manifest.schema);
|
|
474
475
|
const docTypes = $derived(schema?.documents ?? []);
|
|
@@ -515,7 +516,7 @@
|
|
|
515
516
|
|
|
516
517
|
const menu = $derived.by(() => { const slug = sel?.kind === "menu" ? sel.slug : null; return manifest.menus?.entries.find((m) => m.slug === slug); });
|
|
517
518
|
const sortedMenus = $derived([...(manifest.menus?.entries ?? [])].sort((a, b) => (sortBy === "slug" ? a.slug.localeCompare(b.slug) : a.title.localeCompare(b.title)) * sortDir));
|
|
518
|
-
const listTitle = $derived(sel?.kind === "menus" ? "
|
|
519
|
+
const listTitle = $derived(sel?.kind === "menus" ? $t("shell.allMenus") : sel?.kind === "documents" ? activeDocType?.title ?? $t("shell.documents") : sel?.kind === "pages" ? pageCategory !== undefined ? $t("shell.allNamedEntries", { name: pageCategory }) : $t("shell.allPages") : coll?.listTitle?.trim() || (coll?.label !== undefined ? $t("shell.allNamedEntries", { name: coll.label }) : $t("shell.allEntries")));
|
|
519
520
|
const listRows = $derived(sel?.kind === "menus"
|
|
520
521
|
? sortedMenus.map((m) => ({ slug: m.slug, cells: { slug: m.slug, title: m.title } as Record<string, string>, draft: false, target: { kind: "menu", slug: m.slug } as Sel }))
|
|
521
522
|
: sel?.kind === "documents"
|
|
@@ -538,7 +539,7 @@
|
|
|
538
539
|
setSettings(settings);
|
|
539
540
|
settingsError = "";
|
|
540
541
|
} catch {
|
|
541
|
-
settingsError = "
|
|
542
|
+
settingsError = $t("shell.settingsStorageError");
|
|
542
543
|
}
|
|
543
544
|
});
|
|
544
545
|
|
|
@@ -682,7 +683,7 @@
|
|
|
682
683
|
function cellValue(p: { slug: string; frontmatter: Record<string, unknown> }, key: string): string {
|
|
683
684
|
const v = key === "slug" ? effectiveSlug(p.frontmatter, p.slug) : p.frontmatter[key];
|
|
684
685
|
const field = coll?.fields.find(field => field.key === key);
|
|
685
|
-
if (field?.type === "reference") return referenceLabel(v, referenceOptions(field, manifest.documents)) || "—";
|
|
686
|
+
if (field?.type === "reference") return referenceLabel(v, referenceOptions(field, manifest.documents), $t) || "—";
|
|
686
687
|
if (v === true) return "✓";
|
|
687
688
|
if (v === false || v == null) return "—";
|
|
688
689
|
if (Array.isArray(v)) return v.join(", ");
|
|
@@ -692,12 +693,12 @@
|
|
|
692
693
|
function newPage(type = pageDocType) {
|
|
693
694
|
if (!type || !schema || type.creatable === false || type.singleton) return;
|
|
694
695
|
actionDialog = {
|
|
695
|
-
title:
|
|
696
|
+
title: $t("shell.addDocument", { name: type.title }), description: $t("shell.newDocumentDescription"), fieldLabel: type.shared ? $t("shell.name") : $t("shell.pageName"), confirmLabel: $t("shell.createDraft"),
|
|
696
697
|
onconfirm: (title) => {
|
|
697
698
|
const slug = slugify(title);
|
|
698
|
-
if (!slug) throw new Error("
|
|
699
|
+
if (!slug) throw new Error($t("shell.titleRequired"));
|
|
699
700
|
const taken = [...(manifest.documents ?? []).filter(doc => !type.shared || doc.type === type.name).flatMap((doc) => [doc.slug, ...(type.shared || docTypes.find(type => type.name === doc.type)?.shared ? [] : [effectiveSlug(doc.data, doc.slug)])]), ...(!type.shared ? manifest.pages.map((page) => page.slug) : [])];
|
|
700
|
-
if (taken.includes(slug)) throw new Error(
|
|
701
|
+
if (taken.includes(slug)) throw new Error($t("shell.slugAlreadyUsed", { slug }));
|
|
701
702
|
const id = `${type.name}:${slug}`;
|
|
702
703
|
const data = newDocumentFromTemplate(schema, type.name, title, slug);
|
|
703
704
|
newDocument = { type: type.name, id, slug };
|
|
@@ -710,10 +711,10 @@
|
|
|
710
711
|
|
|
711
712
|
function newPost(c: ManifestCollection) {
|
|
712
713
|
actionDialog = {
|
|
713
|
-
title:
|
|
714
|
+
title: $t("shell.addToCollection", { name: c.label }), description: $t("shell.newPostDescription"), fieldLabel: $t("shell.title"), confirmLabel: $t("shell.createDraft"),
|
|
714
715
|
onconfirm: (title) => {
|
|
715
716
|
const slug = slugify(title, c.posts.flatMap((post) => [post.slug, effectiveSlug(post.frontmatter, post.slug)]));
|
|
716
|
-
if (!slug) throw new Error("
|
|
717
|
+
if (!slug) throw new Error($t("shell.titleRequired"));
|
|
717
718
|
newEntry = { collection: c.name, post: { slug, frontmatter: { ...initialCollectionValues(c.fields, title), slug }, body: emptyBody() } };
|
|
718
719
|
openSelection({ kind: "post", collection: c.name, slug });
|
|
719
720
|
},
|
|
@@ -725,8 +726,8 @@
|
|
|
725
726
|
const current = post, collection = coll, unsaved = newEntry !== null;
|
|
726
727
|
if (!current || !collection) return;
|
|
727
728
|
actionDialog = {
|
|
728
|
-
title:
|
|
729
|
-
description: unsaved ? "
|
|
729
|
+
title: $t("shell.deletePostTitle", { name: String(current.frontmatter.title ?? current.slug) }), confirmLabel: $t("shell.delete"), danger: true,
|
|
730
|
+
description: unsaved ? $t("shell.discardNewPostDescription") : githubMode ? $t("shell.deleteRemotePostDescription", { slug: current.slug, branch: String(manifest.github?.branch) }) : $t("shell.deleteLocalPostDescription", { slug: current.slug }),
|
|
730
731
|
onconfirm: async () => {
|
|
731
732
|
if (!unsaved) {
|
|
732
733
|
if (githubMode) {
|
|
@@ -734,8 +735,8 @@
|
|
|
734
735
|
catch (error) { handleAuthError(error); throw error; }
|
|
735
736
|
}
|
|
736
737
|
else {
|
|
737
|
-
const res = await fetch(`/rigel/api/local/${collection.name}/${encodeURIComponent(current.slug)}`, { method: "DELETE" });
|
|
738
|
-
if (!res.ok) throw new Error((await res.text()) ||
|
|
738
|
+
const res = await fetch(`/rigel/api/local/${collection.name}/${encodeURIComponent(current.slug)}`, { method: "DELETE", headers: { "Accept-Language": $locale } });
|
|
739
|
+
if (!res.ok) throw new Error((await res.text()) || $t("shell.deleteFailedStatus", { status: res.status }));
|
|
739
740
|
}
|
|
740
741
|
collection.posts = collection.posts.filter((p) => p.slug !== current.slug);
|
|
741
742
|
}
|
|
@@ -762,7 +763,7 @@
|
|
|
762
763
|
onstorage={(event) => {
|
|
763
764
|
if ((event.key === "rigelcms.session" || event.key === null) && blueprint?.github && !readSession(blueprint.github)) {
|
|
764
765
|
invalidateSession();
|
|
765
|
-
connectionError = "
|
|
766
|
+
connectionError = $t("shell.signedOutAnotherTab");
|
|
766
767
|
}
|
|
767
768
|
}}
|
|
768
769
|
onresize={endPreviewResize}
|
|
@@ -784,31 +785,31 @@
|
|
|
784
785
|
<div class="rigel connection-shell">
|
|
785
786
|
<main id="github-connection" class="connection-card" aria-busy={connectionBusy}>
|
|
786
787
|
<div class="connection-brand"><img src={logo} alt="" width="64" height="64" /><span>Rigel <span class="brand-suffix">CMS</span></span></div>
|
|
787
|
-
<h1>{!ready ? "
|
|
788
|
+
<h1>{!ready ? $t("shell.loadingEditor") : $t("shell.connectGithub")}</h1>
|
|
788
789
|
{#if !ready}
|
|
789
|
-
{#if loadError}<p class="connection-error" role="alert">{loadError}</p><button class="primary" onclick={loadBlueprint}>
|
|
790
|
-
{:else}<p role="status">
|
|
790
|
+
{#if loadError}<p class="connection-error" role="alert">{loadError}</p><button class="primary" onclick={loadBlueprint}>{$t("shell.retryLoading")}</button>
|
|
791
|
+
{:else}<p role="status">{$t("shell.loadingConfiguration")}</p>{/if}
|
|
791
792
|
{:else if manifest.github}
|
|
792
793
|
{#if githubAccess}
|
|
793
|
-
<p>
|
|
794
|
+
<p>{#each $t("shell.signedInAs", { name: "{name}" }).split(/(\{name\})/) as part}{#if part === "{name}"}<strong>{githubAccess.user.login}</strong>{:else}{part}{/if}{/each}</p>
|
|
794
795
|
{#if githubAccess.status === "read-only" || githubAccess.status === "installation-required"}
|
|
795
|
-
<p>
|
|
796
|
+
<p>{$t("shell.noEditingAccess")}</p>
|
|
796
797
|
{:else}
|
|
797
|
-
<p role="status">{connectionBusy ? "
|
|
798
|
+
<p role="status">{connectionBusy ? $t("shell.openingEditor") : $t("shell.accessConfirmed")}</p>
|
|
798
799
|
{/if}
|
|
799
800
|
{#if githubAccess.status !== "ready"}
|
|
800
|
-
<a id="github-install" class="primary" href={githubAccess.installUrl} target="_blank" rel="noopener noreferrer">
|
|
801
|
+
<a id="github-install" class="primary" href={githubAccess.installUrl} target="_blank" rel="noopener noreferrer">{$t("shell.manageGithubAccess")}</a>
|
|
801
802
|
{/if}
|
|
802
803
|
{:else}
|
|
803
|
-
<p>
|
|
804
|
+
<p>{$t("shell.signInHint")}</p>
|
|
804
805
|
{/if}
|
|
805
806
|
<div class="connection-actions">
|
|
806
807
|
{#if sessionExpiresAt && !tokenExpired}
|
|
807
|
-
<button id="github-check-access" class="primary" disabled={connectionBusy} onclick={recheckGitHub}>{connectionBusy ? "
|
|
808
|
+
<button id="github-check-access" class="primary" disabled={connectionBusy} onclick={recheckGitHub}>{connectionBusy ? $t("shell.checkingAccess") : githubAccess?.status === "ready" ? $t("shell.openEditor") : $t("shell.checkAccess")}</button>
|
|
808
809
|
{/if}
|
|
809
|
-
<button id="github-sign-in" class:primary={!sessionExpiresAt || tokenExpired} class:outlined={sessionExpiresAt > 0 && !tokenExpired} disabled={connectionBusy} onclick={connectGitHub}>{connectionBusy && !sessionExpiresAt ? "
|
|
810
|
+
<button id="github-sign-in" class:primary={!sessionExpiresAt || tokenExpired} class:outlined={sessionExpiresAt > 0 && !tokenExpired} disabled={connectionBusy} onclick={connectGitHub}>{connectionBusy && !sessionExpiresAt ? $t("shell.signingIn") : sessionExpiresAt ? $t("shell.signInAgain") : $t("shell.signInWithGithub")}</button>
|
|
810
811
|
</div>
|
|
811
|
-
<p class="connection-note">
|
|
812
|
+
<p class="connection-note">{#each $t("shell.legalNotice", { terms: "{terms}", privacy: "{privacy}" }).split(/(\{terms\}|\{privacy\})/) as part}{#if part === "{terms}"}<a href="https://rigelcms.dev/terms" target="_blank" rel="noopener noreferrer">{$t("shell.termsOfUse")}</a>{:else if part === "{privacy}"}<a href="https://rigelcms.dev/privacy" target="_blank" rel="noopener noreferrer">{$t("shell.privacyPolicy")}</a>{:else}{part}{/if}{/each}</p>
|
|
812
813
|
{/if}
|
|
813
814
|
{#if connectionError}<p class="connection-error" role="alert">{connectionError}</p>{/if}
|
|
814
815
|
</main>
|
|
@@ -816,13 +817,13 @@
|
|
|
816
817
|
{:else}
|
|
817
818
|
<div class="rigel" class:sidebar-collapsed={settings.userPref.sidebarCollapsed}>
|
|
818
819
|
<aside id="rigel-sidebar" data-tooltip-position="right">
|
|
819
|
-
<a class="brand" href="/rigel/" role="button" aria-label="
|
|
820
|
+
<a class="brand" href="/rigel/" role="button" aria-label={$t("shell.welcome")} onclick={(event) => { event.preventDefault(); goWelcome(); }}>
|
|
820
821
|
<img src={logo} alt="" width="32" height="32" />
|
|
821
822
|
<span>Rigel <span class="brand-suffix">CMS</span></span>
|
|
822
823
|
</a>
|
|
823
824
|
<TopBar {manifest} {sel} onselect={select} />
|
|
824
825
|
|
|
825
|
-
<nav id="rigel-sidebar-navigation" class="rail-scroll" aria-label="
|
|
826
|
+
<nav id="rigel-sidebar-navigation" class="rail-scroll" aria-label={$t("shell.content")}>
|
|
826
827
|
{#each sidebar as node}
|
|
827
828
|
{#if node.kind === "heading"}
|
|
828
829
|
<h3 class="sidebar-heading">{node.label}</h3>
|
|
@@ -850,16 +851,16 @@
|
|
|
850
851
|
</nav>
|
|
851
852
|
|
|
852
853
|
{#if githubMode && manifest.github?.publishBranch}
|
|
853
|
-
<button id="publish-button" class="sidebar-publish" aria-label="
|
|
854
|
-
<Icon name="
|
|
854
|
+
<button id="publish-button" class="sidebar-publish" aria-label={$t("shell.publish")} data-tooltip={dirty ? $t("shell.saveBeforePublishing") : $t("shell.publishSavedChanges")} disabled={publishBusy || publishLoading || saveState === "saving" || connectionBusy || tokenExpired || githubAccess?.status !== "ready"} onclick={reviewPublish}>
|
|
855
|
+
<Icon name="rocketLaunch" size={18} /><span class="sidebar-label">{$t("shell.publish")}</span>
|
|
855
856
|
</button>
|
|
856
857
|
{/if}
|
|
857
|
-
<button class="sidebar-toggle" aria-expanded={!settings.userPref.sidebarCollapsed} aria-controls="rigel-sidebar-navigation" aria-label={settings.userPref.sidebarCollapsed ? "
|
|
858
|
-
<SidebarIcon name="sidebar-simple" /><span class="sidebar-label">
|
|
858
|
+
<button class="sidebar-toggle" aria-expanded={!settings.userPref.sidebarCollapsed} aria-controls="rigel-sidebar-navigation" aria-label={settings.userPref.sidebarCollapsed ? $t("shell.expandSidebar") : $t("shell.collapseSidebar")} data-tooltip={settings.userPref.sidebarCollapsed ? $t("shell.expandSidebar") : $t("shell.collapseSidebar")} onclick={() => { settings.userPref.sidebarCollapsed = !settings.userPref.sidebarCollapsed; }}>
|
|
859
|
+
<SidebarIcon name="sidebar-simple" /><span class="sidebar-label">{$t("shell.collapseSidebar")}</span>
|
|
859
860
|
</button>
|
|
860
861
|
<div class="aside-foot">
|
|
861
862
|
<div class="user-menu-wrap">
|
|
862
|
-
<button class="user-btn" aria-label={githubMode ? "
|
|
863
|
+
<button class="user-btn" aria-label={githubMode ? $t("shell.githubUserMenu") : $t("shell.localUserMenu")} data-tooltip={githubMode ? $t("shell.githubUserMenu") : $t("shell.localUserMenu")} aria-haspopup="menu" aria-expanded={userOpen} onclick={() => (userOpen = !userOpen)}>
|
|
863
864
|
{@render avatar()}
|
|
864
865
|
<span class="user-details"><span class="uname">{userName}</span><small>{userLogin}</small></span>
|
|
865
866
|
<span class="user-menu-icon"><Icon name="dotsThreeOutlineVertical" size={18} /></span>
|
|
@@ -873,26 +874,26 @@
|
|
|
873
874
|
<div class="divider"></div>
|
|
874
875
|
<a role="menuitem" href="/" target="_blank" rel="noopener">
|
|
875
876
|
<Icon name="external" size={13} />
|
|
876
|
-
|
|
877
|
+
{$t("shell.liveSite")}
|
|
877
878
|
</a>
|
|
878
879
|
<div class="divider"></div>
|
|
879
880
|
<button role="menuitem" onclick={() => { userOpen = false; settingsOpen = true; }}>
|
|
880
881
|
<Icon name="gear" size={13} />
|
|
881
|
-
|
|
882
|
+
{$t("shell.settings")}
|
|
882
883
|
</button>
|
|
883
884
|
<button role="menuitem" onclick={() => { userOpen = false; shortcutsOpen = true; }}>
|
|
884
885
|
<Icon name="keyboard" size={13} />
|
|
885
|
-
|
|
886
|
+
{$t("shell.keyboardShortcutsMenu")}
|
|
886
887
|
</button>
|
|
887
888
|
<div class="divider"></div>
|
|
888
889
|
{#if githubMode}
|
|
889
|
-
<a role="menuitem" href={githubAccess?.installUrl} target="_blank" rel="noopener noreferrer">
|
|
890
|
-
<button role="menuitem" class="github-reconnect" disabled={connectionBusy || saveState === "saving"} onclick={() => { userOpen = false; void connectGitHub(); }}>
|
|
890
|
+
<a role="menuitem" href={githubAccess?.installUrl} target="_blank" rel="noopener noreferrer">{$t("shell.configureGithubAccess")}</a>
|
|
891
|
+
<button role="menuitem" class="github-reconnect" disabled={connectionBusy || saveState === "saving"} onclick={() => { userOpen = false; void connectGitHub(); }}>{$t("shell.reconnectGithub")}</button>
|
|
891
892
|
{/if}
|
|
892
|
-
<button id={githubMode ? "github-sign-out" : undefined} role="menuitem" class:muted={!githubMode} disabled={!githubMode || saveState === "saving" || connectionBusy} data-tooltip={githubMode ? "
|
|
893
|
+
<button id={githubMode ? "github-sign-out" : undefined} role="menuitem" class:muted={!githubMode} disabled={!githubMode || saveState === "saving" || connectionBusy} data-tooltip={githubMode ? $t("shell.signOutTooltip") : $t("shell.localMode")} onclick={signOut}>
|
|
893
894
|
<Icon name="signOut" size={13} />
|
|
894
|
-
|
|
895
|
-
{#if !githubMode}<span class="soon">
|
|
895
|
+
{$t("shell.signOut")}
|
|
896
|
+
{#if !githubMode}<span class="soon">{$t("shell.githubBackendHint")}</span>{/if}
|
|
896
897
|
</button>
|
|
897
898
|
</div>
|
|
898
899
|
{/if}
|
|
@@ -904,8 +905,8 @@
|
|
|
904
905
|
<main id="rigel-editor-pane" class:wide={sel?.kind === "collection" || sel?.kind === "pages" || sel?.kind === "documents" || sel?.kind === "document" || sel?.kind === "post" || sel?.kind === "menus" || sel?.kind === "menu" || sel?.kind === "media"} class:visual-pane={visualSelection}>
|
|
905
906
|
{#if githubMode && (tokenExpired || githubAccess?.status !== "ready" || connectionError)}
|
|
906
907
|
<div class="github-session" role="status">
|
|
907
|
-
<p>{connectionError || (tokenExpired ? "
|
|
908
|
-
<button class="primary" disabled={connectionBusy || saveState === "saving"} onclick={connectGitHub}>{connectionBusy ? "
|
|
908
|
+
<p>{connectionError || (tokenExpired ? $t("shell.sessionExpired") : $t("shell.repositoryAccessChanged"))}</p>
|
|
909
|
+
<button class="primary" disabled={connectionBusy || saveState === "saving"} onclick={connectGitHub}>{connectionBusy ? $t("shell.connecting") : $t("shell.reconnectGithub")}</button>
|
|
909
910
|
</div>
|
|
910
911
|
{/if}
|
|
911
912
|
{#if settingsError}<p role="alert">{settingsError}</p>{/if}
|
|
@@ -913,30 +914,30 @@
|
|
|
913
914
|
{#if sel.kind === "page" || sel.kind === "post" || sel.kind === "menu" || sel.kind === "document"}
|
|
914
915
|
<div class="editor-actions">
|
|
915
916
|
{#if sel.kind === "page" && page}
|
|
916
|
-
<p class="back"><button onclick={() => select(page.shared ? null : { kind: "pages", category: page.category })}
|
|
917
|
+
<p class="back"><button onclick={() => select(page.shared ? null : { kind: "pages", category: page.category })}>{$t("shell.backTo", { name: page.shared ? $t("shell.welcome") : page.category ?? $t("shell.pages") })}</button></p>
|
|
917
918
|
{:else if sel.kind === "document"}
|
|
918
919
|
{@const backSel: Sel | null = activeDocType?.singleton ? null : sel.type === pageDocType?.name ? { kind: "pages" } : { kind: "documents", type: sel.type }}
|
|
919
|
-
<p class="back"><button onclick={() => select(backSel)}
|
|
920
|
+
<p class="back"><button onclick={() => select(backSel)}>{$t("shell.backTo", { name: activeDocType?.singleton ? $t("shell.welcome") : sel.type === pageDocType?.name ? $t("shell.pages") : activeDocType?.title ?? $t("shell.documents") })}</button></p>
|
|
920
921
|
{:else if sel.kind === "post" && coll}
|
|
921
|
-
<p class="back"><button onclick={() => select({ kind: "collection", name: coll.name })}
|
|
922
|
+
<p class="back"><button onclick={() => select({ kind: "collection", name: coll.name })}>{$t("shell.backTo", { name: coll.label })}</button></p>
|
|
922
923
|
{:else if sel.kind === "menu"}
|
|
923
|
-
<p class="back"><button onclick={() => select({ kind: "menus" })}
|
|
924
|
+
<p class="back"><button onclick={() => select({ kind: "menus" })}>{$t("shell.backTo", { name: $t("shell.menus") })}</button></p>
|
|
924
925
|
{/if}
|
|
925
926
|
<div class="save-actions">
|
|
926
927
|
<span class="save-status" role="status" class:dirty={dirty && saveState !== "saving"} class:saving={saveState === "saving"} class:saved={!dirty && saveState === "saved"} class:error={saveState === "error"}>
|
|
927
|
-
{saveState === "saving" ? "
|
|
928
|
+
{saveState === "saving" ? $t("shell.saving") : saveState === "error" ? $t("shell.saveFailed") : dirty ? $t("shell.unsaved") : saveState === "saved" ? $t("shell.saved") : ""}
|
|
928
929
|
</span>
|
|
929
|
-
<button class="primary save-button" disabled={!dirty || saveState === "saving"} onclick={() => save()}>{saveState === "saving" ? "
|
|
930
|
+
<button class="primary save-button" disabled={!dirty || saveState === "saving"} onclick={() => save()}>{saveState === "saving" ? $t("shell.saving") : $t("shell.save")}</button>
|
|
930
931
|
</div>
|
|
931
932
|
</div>
|
|
932
933
|
{/if}
|
|
933
934
|
<div class="titlebar">
|
|
934
935
|
<h2>{sel.kind === "page" ? page?.label : sel.kind === "menu" ? String(draft.title || menu?.slug || "") : sel.kind === "post" ? String(draft.title || post?.slug || "") : sel.kind === "document" ? activeDocType?.singleton ? activeDocType.title : String(draft.title || draft.name || activeDocument?.slug || sel.id) : listTitle}</h2>
|
|
935
936
|
{#if (sel?.kind === "collection" && coll) || (sel?.kind === "pages" && pageDocType?.creatable !== false && pageDocType) || (sel?.kind === "documents" && activeDocType && activeDocType.creatable !== false && !activeDocType.singleton)}
|
|
936
|
-
<button class="primary" onclick={() => (sel?.kind === "collection" ? newPost(coll!) : newPage(sel?.kind === "documents" ? activeDocType : pageDocType))}>
|
|
937
|
+
<button class="primary" onclick={() => (sel?.kind === "collection" ? newPost(coll!) : newPage(sel?.kind === "documents" ? activeDocType : pageDocType))}>{$t("shell.addNew")}</button>
|
|
937
938
|
{/if}
|
|
938
939
|
{#if sel?.kind === "post" && post}
|
|
939
|
-
<button class="icon-btn" aria-label="
|
|
940
|
+
<button class="icon-btn" aria-label={$t("shell.deletePost")} data-tooltip={$t("shell.deletePost")} disabled={saveState === "saving"} onclick={deletePost}>
|
|
940
941
|
<Icon name="trash" size={15} />
|
|
941
942
|
</button>
|
|
942
943
|
{/if}
|
|
@@ -945,16 +946,16 @@
|
|
|
945
946
|
{#if saveError}
|
|
946
947
|
<div class="save-toast" role="alert">
|
|
947
948
|
<div class="save-toast-content">
|
|
948
|
-
<strong>
|
|
949
|
+
<strong>{$t("shell.saveChangesError")}</strong>
|
|
949
950
|
<p>{saveError}</p>
|
|
950
|
-
<button class="outlined" disabled={saveState === "saving"} onclick={() => save()}>
|
|
951
|
-
{#if githubMode}<button class="outlined github-reconnect" disabled={connectionBusy || saveState === "saving"} onclick={connectGitHub}>
|
|
951
|
+
<button class="outlined" disabled={saveState === "saving"} onclick={() => save()}>{$t("shell.retrySave")}</button>
|
|
952
|
+
{#if githubMode}<button class="outlined github-reconnect" disabled={connectionBusy || saveState === "saving"} onclick={connectGitHub}>{$t("shell.reconnectGithub")}</button>{/if}
|
|
952
953
|
</div>
|
|
953
|
-
<button type="button" class="icon-btn" aria-label="
|
|
954
|
+
<button type="button" class="icon-btn" aria-label={$t("shell.dismissSaveError")} onclick={() => { saveError = ""; }}><Icon name="x" size={18} /></button>
|
|
954
955
|
</div>
|
|
955
956
|
{/if}
|
|
956
957
|
{#if !ready}
|
|
957
|
-
<p>
|
|
958
|
+
<p>{$t("shell.loading")}</p>
|
|
958
959
|
|
|
959
960
|
{:else if sel?.kind === "media"}
|
|
960
961
|
<ImagePicker inline />
|
|
@@ -981,7 +982,7 @@
|
|
|
981
982
|
<td>
|
|
982
983
|
{#if col.key === titleColumn}
|
|
983
984
|
<button class="table-link" onclick={() => select(row.target)}>{row.cells[col.key]}</button>
|
|
984
|
-
{#if row.draft}<span class="badge">
|
|
985
|
+
{#if row.draft}<span class="badge">{$t("shell.draftBadge")}</span>{/if}
|
|
985
986
|
{:else if col.image}
|
|
986
987
|
{#if row.cells[col.key]}
|
|
987
988
|
<img class="table-thumbnail" src={row.cells[col.key]} alt="" width="32" height="32" loading="lazy" decoding="async" />
|
|
@@ -995,7 +996,7 @@
|
|
|
995
996
|
{/each}
|
|
996
997
|
</tr>
|
|
997
998
|
{:else}
|
|
998
|
-
<tr><td colspan={columns.length}>
|
|
999
|
+
<tr><td colspan={columns.length}>{$t("shell.noEntries")}</td></tr>
|
|
999
1000
|
{/each}
|
|
1000
1001
|
</tbody>
|
|
1001
1002
|
</table>
|
|
@@ -1028,14 +1029,14 @@
|
|
|
1028
1029
|
|
|
1029
1030
|
{:else if sel?.kind === "menu" && menu && manifest.menus}
|
|
1030
1031
|
<section class="menu-details">
|
|
1031
|
-
<label><span>
|
|
1032
|
-
<label><span>
|
|
1032
|
+
<label><span>{$t("shell.slug")}</span><input type="text" value={menu.slug} readonly /></label>
|
|
1033
|
+
<label><span>{$t("shell.name")}</span><input type="text" value={String(draft.title ?? "")} oninput={(e) => { draft.title = e.currentTarget.value; touch(); }} /></label>
|
|
1033
1034
|
</section>
|
|
1034
1035
|
<MenuEditor items={draft.items as MenuItem[]} maxDepth={manifest.menus.maxDepth} oninput={(items) => { draft.items = items; touch(); }} />
|
|
1035
1036
|
|
|
1036
1037
|
{:else if sel?.kind === "page" && page}
|
|
1037
1038
|
{#if !page.shared}
|
|
1038
|
-
<section><label><span>
|
|
1039
|
+
<section><label><span>{$t("shell.websiteRoute")}</span><input type="text" value={previewPath(manifest.preview ?? {}, { kind: "page", slug: page.slug }) ?? $t("shell.notConfigured")} readonly /></label><p class="hint">{$t("shell.fixedRouteHint")}</p></section>
|
|
1039
1040
|
{/if}
|
|
1040
1041
|
{#each page.groups as group (group.id)}
|
|
1041
1042
|
<section>
|
|
@@ -1060,7 +1061,7 @@
|
|
|
1060
1061
|
<VisualEditor
|
|
1061
1062
|
docId={`${coll.name}:${post.slug}`}
|
|
1062
1063
|
values={draft}
|
|
1063
|
-
previewValues={collectionPreviewValues(coll.fields, draft, manifest.documents ?? [])}
|
|
1064
|
+
previewValues={collectionPreviewValues(coll.fields, draft, manifest.documents ?? [], $t)}
|
|
1064
1065
|
body={bodyDraft}
|
|
1065
1066
|
bodyInline={settings.userPref.editorExperience === "inline"}
|
|
1066
1067
|
onbody={(value) => { bodyDraft = value; touch(); }}
|
|
@@ -1079,20 +1080,20 @@
|
|
|
1079
1080
|
{:else}
|
|
1080
1081
|
<div class="welcome">
|
|
1081
1082
|
<img src={logo} alt="" width="64" height="64" />
|
|
1082
|
-
<h2>
|
|
1083
|
-
<p>
|
|
1084
|
-
<p class="welcome-hint">
|
|
1083
|
+
<h2>{$t("shell.welcomeTitle")}</h2>
|
|
1084
|
+
<p>{$t("shell.welcomeTagline")}</p>
|
|
1085
|
+
<p class="welcome-hint">{$t("shell.welcomeHint")}</p>
|
|
1085
1086
|
</div>
|
|
1086
1087
|
{/if}
|
|
1087
1088
|
{#if settings.devMode && (sel?.kind === "page" || sel?.kind === "post" || sel?.kind === "menu" || sel?.kind === "document")}
|
|
1088
1089
|
<section class="md-preview" class:article-state={sel.kind === "post"}>
|
|
1089
|
-
<h3>
|
|
1090
|
+
<h3>{$t("shell.currentDraft")}</h3>
|
|
1090
1091
|
<pre>{JSON.stringify(sel.kind === "post" ? { frontmatter: draft, body: bodyDraft } : draft, null, 2)}</pre>
|
|
1091
1092
|
</section>
|
|
1092
1093
|
{/if}
|
|
1093
1094
|
</main>
|
|
1094
1095
|
{#if manifest.preview && previewTarget}
|
|
1095
|
-
<div class="preview-divider" role="separator" tabindex="0" aria-label="
|
|
1096
|
+
<div class="preview-divider" role="separator" tabindex="0" aria-label={$t("shell.editorPreviewWidth")} aria-orientation="vertical" aria-controls="rigel-editor-pane" aria-valuemin={Math.round(splitMin * 100)} aria-valuemax={Math.round(splitMax * 100)} aria-valuenow={Math.round(previewSplit * 100)} aria-valuetext={$t("shell.editorWidthPercent", { percent: Math.round(previewSplit * 100) })} onpointerdown={startPreviewResize} onpointermove={movePreviewResize} onpointerup={endPreviewResize} onpointercancel={endPreviewResize} onlostpointercapture={endPreviewResize} onkeydown={keyPreviewResize}></div>
|
|
1096
1097
|
<SitePreview target={previewTarget} config={manifest.preview} isNew={!!newEntry} values={draft} body={bodyDraft} devMode={settings.devMode} />
|
|
1097
1098
|
{/if}
|
|
1098
1099
|
</div>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { DecoratorNode, type LexicalEditor, type NodeKey } from "lexical";
|
|
2
|
+
import type { BlockData } from "../../extensions";
|
|
3
|
+
import { validateBlockData } from "../../extensions";
|
|
4
|
+
|
|
5
|
+
export function cloneBlockData(data: unknown): BlockData {
|
|
6
|
+
validateBlockData(data);
|
|
7
|
+
return JSON.parse(JSON.stringify(data));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class ExtensionNode extends DecoratorNode<null> {
|
|
11
|
+
__name: string;
|
|
12
|
+
__data: BlockData;
|
|
13
|
+
static getType() { return "extension"; }
|
|
14
|
+
static clone(node: ExtensionNode) { return new ExtensionNode(node.__name, node.__data, node.__key); }
|
|
15
|
+
constructor(name: string, data: BlockData, key?: NodeKey) { super(key); this.__name = name; this.__data = cloneBlockData(data); }
|
|
16
|
+
static importJSON(raw: unknown) { const node = raw as { name: string; data: BlockData }; return new ExtensionNode(node.name, node.data); }
|
|
17
|
+
exportJSON() { return { type: "extension", version: 1, name: this.__name, data: cloneBlockData(this.__data) }; }
|
|
18
|
+
setData(data: BlockData) { this.getWritable().__data = cloneBlockData(data); }
|
|
19
|
+
createDOM(_config: unknown, editor: LexicalEditor) {
|
|
20
|
+
const element = (editor.getRootElement()?.ownerDocument ?? document).createElement("div");
|
|
21
|
+
element.contentEditable = "false";
|
|
22
|
+
element.dataset.rigelExtensionEditor = this.__name;
|
|
23
|
+
return element;
|
|
24
|
+
}
|
|
25
|
+
updateDOM() { return false; }
|
|
26
|
+
decorate() { return null; }
|
|
27
|
+
isInline() { return false; }
|
|
28
|
+
getTextContent() { return this.__name; }
|
|
29
|
+
}
|