@rigel-cms/rigel 1.0.0 → 1.2.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 +8 -0
- package/docs/content-locales.md +53 -0
- package/docs/extensions.md +194 -0
- package/package.json +16 -3
- package/src/article-body.ts +49 -37
- package/src/article-render.ts +25 -2
- package/src/article.ts +44 -0
- package/src/config.ts +26 -2
- package/src/editor/App.svelte +149 -107
- 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 +182 -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 +182 -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 +182 -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 +182 -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 +182 -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 +182 -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 +182 -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 +182 -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 +182 -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 +5 -4
- 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 +19 -18
- 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/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 +15 -3
- package/src/env.d.ts +11 -1
- package/src/extensions.ts +160 -0
- package/src/index.ts +1 -1
- package/src/manifest/collections.ts +6 -5
- package/src/manifest/content-locales.ts +45 -0
- package/src/manifest/menu.ts +12 -10
- package/src/manifest/navigation.ts +13 -8
- package/src/manifest/references.ts +8 -7
- package/src/manifest/seo.ts +4 -3
- package/src/manifest/types.ts +4 -0
- package/src/preview.ts +36 -2
- 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 +2 -0
- package/src/schema/validate.ts +73 -69
- package/src/slugs.ts +11 -10
- package/src/vite/manifest-plugin.ts +38 -2
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);
|
|
@@ -12,6 +13,9 @@
|
|
|
12
13
|
import ImagePicker from "./lib/ImagePicker.svelte";
|
|
13
14
|
import Settings from "./lib/Settings.svelte";
|
|
14
15
|
import TopBar from "./lib/TopBar.svelte";
|
|
16
|
+
import Select from "./lib/Select.svelte";
|
|
17
|
+
import { contentForLocale, selectionInContent } from "../manifest/content-locales";
|
|
18
|
+
import { validateDocumentSlugs } from "../slugs";
|
|
15
19
|
import Shortcuts from "./lib/Shortcuts.svelte";
|
|
16
20
|
import CollectionPanel from "./lib/CollectionPanel.svelte";
|
|
17
21
|
import { emptyBody, type ArticleBody } from "../article-body";
|
|
@@ -37,11 +41,15 @@
|
|
|
37
41
|
import type { ContentDocument, Schema } from "../schema/types";
|
|
38
42
|
import { buildSidebar, navigationKey, type Selection } from "../manifest/navigation";
|
|
39
43
|
import { startGitHubLogin, checkGitHubAccess, type GitHubAccess } from "./lib/github-auth";
|
|
40
|
-
import { loadManifestRemote, saveRemoteSelection, deleteRemotePost, reviewRemotePublish, confirmRemotePublish, type GitContext, type PublishReview } from "./lib/github-backend";
|
|
44
|
+
import { loadManifestRemote, saveRemoteSelection, deleteRemotePost, reviewRemotePublish, confirmRemotePublish, PublishReviewChangedError, type GitContext, type PublishReview } from "./lib/github-backend";
|
|
41
45
|
|
|
42
46
|
type Sel = Selection;
|
|
43
47
|
|
|
44
48
|
let manifest = $state<Manifest>({ pages: [] });
|
|
49
|
+
let selectedContentLocale = $state<string | undefined>(undefined);
|
|
50
|
+
let localeControlRevision = $state(0);
|
|
51
|
+
const contentLocale = $derived(selectedContentLocale ?? manifest.contentLocales?.defaultLocale);
|
|
52
|
+
const visibleContent = $derived(contentForLocale(manifest, contentLocale));
|
|
45
53
|
let ready = $state(false);
|
|
46
54
|
let blueprint: Manifest | null = null;
|
|
47
55
|
let gitContext: GitContext | null = null;
|
|
@@ -79,9 +87,9 @@
|
|
|
79
87
|
}
|
|
80
88
|
|
|
81
89
|
function publishGuard(context: GitContext, revision: number, auth: number) {
|
|
82
|
-
if (!publishOpen || revision !== publishRevision || auth !== authRevision || gitContext !== context) throw new Error("
|
|
90
|
+
if (!publishOpen || revision !== publishRevision || auth !== authRevision || gitContext !== context) throw new Error($t("shell.publishReviewInactive"));
|
|
83
91
|
requireGitContext();
|
|
84
|
-
if (dirty || saveTask || saveState === "saving") throw new Error("
|
|
92
|
+
if (dirty || saveTask || saveState === "saving") throw new Error($t("shell.saveBeforeReview"));
|
|
85
93
|
}
|
|
86
94
|
|
|
87
95
|
async function reviewPublish() {
|
|
@@ -101,7 +109,7 @@
|
|
|
101
109
|
publishReview = review;
|
|
102
110
|
} catch (error) {
|
|
103
111
|
handleAuthError(error);
|
|
104
|
-
if (revision === publishRevision) publishError = error instanceof Error ? error.message : "
|
|
112
|
+
if (revision === publishRevision) publishError = error instanceof Error ? error.message : $t("shell.reviewError");
|
|
105
113
|
} finally { publishLoading = false; }
|
|
106
114
|
}
|
|
107
115
|
|
|
@@ -122,17 +130,18 @@
|
|
|
122
130
|
guard();
|
|
123
131
|
manifest = remote;
|
|
124
132
|
openSelection(sel);
|
|
125
|
-
publishMessage = "
|
|
126
|
-
} else publishMessage =
|
|
133
|
+
publishMessage = $t("shell.draftsSynced");
|
|
134
|
+
} else publishMessage = $t("shell.publishedToBranch", { name: review.publishBranch });
|
|
127
135
|
} catch (error) {
|
|
128
136
|
handleAuthError(error);
|
|
129
137
|
if (revision === publishRevision) {
|
|
130
|
-
publishError = error instanceof Error ? error.message : "
|
|
131
|
-
if (
|
|
138
|
+
publishError = error instanceof Error ? error.message : $t("shell.publishVerificationError");
|
|
139
|
+
if (error instanceof PublishReviewChangedError) publishReview = null;
|
|
132
140
|
}
|
|
133
141
|
} finally { publishBusy = false; }
|
|
134
142
|
}
|
|
135
143
|
let pendingNavigation = $state<(() => void) | null>(null);
|
|
144
|
+
let pendingNavigationCancel: (() => void) | null = null;
|
|
136
145
|
let actionDialog = $state<{ title: string; description: string; confirmLabel: string; fieldLabel?: string; danger?: boolean; onconfirm: (value: string) => void | Promise<void> } | null>(null);
|
|
137
146
|
let newEntry = $state<{ collection: string; post: CollectionPost } | null>(null);
|
|
138
147
|
let newDocument = $state<{ type: string; id: string; slug: string } | null>(null);
|
|
@@ -145,9 +154,9 @@
|
|
|
145
154
|
ready = false;
|
|
146
155
|
try {
|
|
147
156
|
const response = await fetch("/rigel/manifest.json", { cache: "no-store" });
|
|
148
|
-
if (!response.ok) throw new Error("
|
|
157
|
+
if (!response.ok) throw new Error($t("shell.configurationLoadError"));
|
|
149
158
|
const data: Manifest = await response.json();
|
|
150
|
-
if (!Array.isArray(data.pages) || (data.backend === "github" && !data.github)) throw new Error("
|
|
159
|
+
if (!Array.isArray(data.pages) || (data.backend === "github" && !data.github)) throw new Error($t("shell.configurationIncomplete"));
|
|
151
160
|
blueprint = data;
|
|
152
161
|
manifest = data;
|
|
153
162
|
ready = true;
|
|
@@ -160,16 +169,16 @@
|
|
|
160
169
|
}
|
|
161
170
|
}
|
|
162
171
|
} catch (error) {
|
|
163
|
-
loadError = error instanceof Error ? error.message : "
|
|
172
|
+
loadError = error instanceof Error ? error.message : $t("shell.configurationLoadFailed");
|
|
164
173
|
}
|
|
165
174
|
}
|
|
166
175
|
|
|
167
176
|
function requireGitContext() {
|
|
168
|
-
if (connectionBusy) throw new Error("
|
|
169
|
-
if (!gitContext || !connected || githubAccess?.status !== "ready") throw new Error("
|
|
177
|
+
if (connectionBusy) throw new Error($t("shell.waitForGithubVerification"));
|
|
178
|
+
if (!gitContext || !connected || githubAccess?.status !== "ready") throw new Error($t("shell.connectBeforeSaving"));
|
|
170
179
|
if (Date.now() >= sessionExpiresAt - 30_000) {
|
|
171
180
|
invalidateSession();
|
|
172
|
-
throw new Error("
|
|
181
|
+
throw new Error($t("shell.sessionExpiredRetry"));
|
|
173
182
|
}
|
|
174
183
|
return gitContext;
|
|
175
184
|
}
|
|
@@ -181,7 +190,7 @@
|
|
|
181
190
|
try {
|
|
182
191
|
if (Date.now() >= sessionExpiresAt - 30_000) {
|
|
183
192
|
invalidateSession();
|
|
184
|
-
throw new Error("
|
|
193
|
+
throw new Error($t("shell.sessionExpiredCheckAccess"));
|
|
185
194
|
}
|
|
186
195
|
const access = await checkGitHubAccess(context.token, config.repo, config.appSlug);
|
|
187
196
|
if (gitContext !== context) return;
|
|
@@ -228,7 +237,7 @@
|
|
|
228
237
|
writeSession(manifest.github, session);
|
|
229
238
|
await verifyGitHub();
|
|
230
239
|
} catch (error) {
|
|
231
|
-
connectionError = error instanceof Error ? error.message : "
|
|
240
|
+
connectionError = error instanceof Error ? error.message : $t("shell.githubConnectionError");
|
|
232
241
|
} finally {
|
|
233
242
|
connectionBusy = false;
|
|
234
243
|
}
|
|
@@ -239,7 +248,7 @@
|
|
|
239
248
|
connectionBusy = true;
|
|
240
249
|
connectionError = "";
|
|
241
250
|
try { await verifyGitHub(); }
|
|
242
|
-
catch (error) { connectionError = error instanceof Error ? error.message : "
|
|
251
|
+
catch (error) { connectionError = error instanceof Error ? error.message : $t("shell.githubAccessError"); }
|
|
243
252
|
finally { connectionBusy = false; }
|
|
244
253
|
}
|
|
245
254
|
|
|
@@ -292,19 +301,19 @@
|
|
|
292
301
|
const creatingDoc = docSel !== null && newDocument?.id === docSel.id;
|
|
293
302
|
try {
|
|
294
303
|
if ((selection?.kind === "post" || (docSel && !activeDocType?.shared)) && Object.hasOwn(values, "slug")) {
|
|
295
|
-
values.slug = normalizeUrlSlug(values.slug);
|
|
304
|
+
values.slug = normalizeUrlSlug(values.slug, $t);
|
|
296
305
|
draft.slug = values.slug;
|
|
297
306
|
}
|
|
298
307
|
if (githubMode) {
|
|
299
|
-
if (!selection) throw new Error("
|
|
308
|
+
if (!selection) throw new Error($t("shell.selectBeforeSaving"));
|
|
300
309
|
await saveRemoteSelection(requireGitContext(), manifest, selection, values, body, !!creating || creatingDoc);
|
|
301
310
|
} else {
|
|
302
311
|
const url = docSel
|
|
303
312
|
? `/rigel/api/local/documents/${encodeURIComponent(docSel.id)}`
|
|
304
313
|
: m ? `/rigel/api/local/menus/${m.slug}` : po && c ? `/rigel/api/local/${c.name}/${encodeURIComponent(po.slug)}` : "/rigel/api/local/save";
|
|
305
314
|
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()) ||
|
|
315
|
+
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) });
|
|
316
|
+
if (!res.ok) throw new Error((await res.text()) || $t("shell.saveFailedStatus", { status: res.status }));
|
|
308
317
|
}
|
|
309
318
|
if (docSel) {
|
|
310
319
|
const list = manifest.documents ?? (manifest.documents = []);
|
|
@@ -324,7 +333,7 @@
|
|
|
324
333
|
} catch (error) {
|
|
325
334
|
handleAuthError(error);
|
|
326
335
|
saveState = "error";
|
|
327
|
-
saveError = error instanceof Error ? error.message : "
|
|
336
|
+
saveError = error instanceof Error ? error.message : $t("shell.saveChangesError");
|
|
328
337
|
return false;
|
|
329
338
|
}
|
|
330
339
|
}
|
|
@@ -338,10 +347,10 @@
|
|
|
338
347
|
|
|
339
348
|
let settings = $state(getSettings());
|
|
340
349
|
let settingsError = $state("");
|
|
341
|
-
const userName = $derived(githubMode ? githubAccess?.user.name ?? githubAccess?.user.login ?? "
|
|
350
|
+
const userName = $derived(githubMode ? githubAccess?.user.name ?? githubAccess?.user.login ?? $t("shell.githubUser") : $t("shell.localUser"));
|
|
342
351
|
const userAvatar = $derived(githubMode && /^https:\/\/avatars\.githubusercontent\.com\//.test(githubAccess?.user.avatar_url ?? "") ? githubAccess!.user.avatar_url : "");
|
|
343
352
|
let failedAvatar = $state("");
|
|
344
|
-
const userLogin = $derived(githubMode && githubAccess ? `@${githubAccess.user.login}` : "
|
|
353
|
+
const userLogin = $derived(githubMode && githubAccess ? `@${githubAccess.user.login}` : $t("shell.localMode"));
|
|
345
354
|
let content: HTMLDivElement;
|
|
346
355
|
let contentWidth = $state(0);
|
|
347
356
|
let resizing = $state(false);
|
|
@@ -390,10 +399,10 @@
|
|
|
390
399
|
|
|
391
400
|
function eraseBrowserData() {
|
|
392
401
|
actionDialog = {
|
|
393
|
-
title: "
|
|
394
|
-
description: "
|
|
402
|
+
title: $t("shell.eraseBrowserDataTitle"), confirmLabel: $t("shell.eraseData"), danger: true,
|
|
403
|
+
description: $t("shell.eraseBrowserDataDescription"),
|
|
395
404
|
onconfirm: () => {
|
|
396
|
-
if (saveState === "saving") throw new Error("
|
|
405
|
+
if (saveState === "saving") throw new Error($t("shell.waitForSave"));
|
|
397
406
|
eraseData();
|
|
398
407
|
dirty = false;
|
|
399
408
|
location.reload();
|
|
@@ -413,7 +422,7 @@
|
|
|
413
422
|
});
|
|
414
423
|
|
|
415
424
|
function findColl(name: string): ManifestCollection | undefined {
|
|
416
|
-
return
|
|
425
|
+
return visibleContent.collections?.find((x) => x.name === name);
|
|
417
426
|
}
|
|
418
427
|
|
|
419
428
|
function collName(s: Sel | null): string | null {
|
|
@@ -439,12 +448,12 @@
|
|
|
439
448
|
});
|
|
440
449
|
|
|
441
450
|
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: "
|
|
451
|
+
if (sel?.kind === "menus") return [{ key: "title", label: $t("shell.name") }, { key: "slug", label: $t("shell.slug") }];
|
|
452
|
+
if (sel?.kind === "pages") return [{ key: "label", label: $t("shell.title") }, { key: "slug", label: $t("shell.slug") }, { key: "category", label: $t("shell.category") }];
|
|
444
453
|
const source = sel?.kind === "documents" ? activeDocType : coll;
|
|
445
454
|
if (!source) return [];
|
|
446
455
|
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 ?? "
|
|
456
|
+
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
457
|
const seen = new Set<string>();
|
|
449
458
|
const out: { key: string; label: string; image?: boolean }[] = [];
|
|
450
459
|
|
|
@@ -452,7 +461,7 @@
|
|
|
452
461
|
if (seen.has(k)) continue;
|
|
453
462
|
seen.add(k);
|
|
454
463
|
const f = source.fields.find((x) => x.key === k);
|
|
455
|
-
out.push({ key: k, label: k === "slug" ? "
|
|
464
|
+
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
465
|
}
|
|
457
466
|
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
467
|
});
|
|
@@ -468,13 +477,13 @@
|
|
|
468
477
|
});
|
|
469
478
|
});
|
|
470
479
|
|
|
471
|
-
const sidebar = $derived(buildSidebar(
|
|
480
|
+
const sidebar = $derived(buildSidebar(visibleContent, $t));
|
|
472
481
|
const pageCategory = $derived(sel?.kind === "pages" ? sel.category : undefined);
|
|
473
482
|
const schema = $derived<Schema | undefined>(manifest.schema);
|
|
474
|
-
const docTypes = $derived(schema?.documents ?? []);
|
|
483
|
+
const docTypes = $derived(visibleContent.schema?.documents ?? []);
|
|
475
484
|
|
|
476
485
|
const pageDocType = $derived(docTypes.find((type) => !type.shared && !type.singleton));
|
|
477
|
-
const pageDocuments = $derived((
|
|
486
|
+
const pageDocuments = $derived((visibleContent.documents ?? []).filter((doc) => docTypes.some(type => type.name === doc.type && !type.shared)));
|
|
478
487
|
function docTypeName(s: Sel | null): string | null {
|
|
479
488
|
return s && (s.kind === "document" || s.kind === "documents") ? s.type : null;
|
|
480
489
|
}
|
|
@@ -492,14 +501,15 @@
|
|
|
492
501
|
});
|
|
493
502
|
function documentPreview(mode: "draft" | "website") {
|
|
494
503
|
if (sel?.kind !== "document" || (mode === "draft" && newDocument?.id === sel.id)) return null;
|
|
495
|
-
|
|
504
|
+
const storedSlug = activeDocument ? effectiveSlug(activeDocument.data, activeDocument.slug) : sel.id.slice(sel.type.length + 1);
|
|
505
|
+
return previewUrl(manifest.preview ?? {}, { kind: "document", type: sel.type, slug: storedSlug, urlSlug: Object.hasOwn(draft, "slug") ? String(draft.slug) : undefined }, mode);
|
|
496
506
|
}
|
|
497
507
|
const standaloneDocument = $derived(sel?.kind === "document" && activeDocType?.shared === true);
|
|
498
508
|
const visualSelection = $derived(sel?.kind === "post" || (sel?.kind === "document" && !standaloneDocument));
|
|
499
509
|
|
|
500
510
|
const sortedPages = $derived.by(() => {
|
|
501
511
|
const rows = [
|
|
502
|
-
...
|
|
512
|
+
...visibleContent.pages.filter((p) => !p.shared && (pageCategory === undefined || p.category === pageCategory))
|
|
503
513
|
.map((p) => ({ slug: p.slug, title: p.label, category: p.category, draft: false, target: { kind: "page", slug: p.slug } as Sel })),
|
|
504
514
|
...(pageCategory === undefined ? pageDocuments
|
|
505
515
|
.map((doc) => ({ slug: effectiveSlug(doc.data, doc.slug), title: String(doc.data.title ?? doc.slug), category: undefined, draft: doc.data.draft === true, target: { kind: "document", type: doc.type, id: doc.id } as Sel })) : []),
|
|
@@ -510,12 +520,12 @@
|
|
|
510
520
|
function documentCell(doc: ContentDocument, key: string): string {
|
|
511
521
|
return String(key === "title" ? doc.data.title ?? doc.data.name ?? doc.slug : key === "slug" ? activeDocType?.shared ? doc.slug : effectiveSlug(doc.data, doc.slug) : doc.data[key] ?? "");
|
|
512
522
|
}
|
|
513
|
-
const typeDocuments = $derived((
|
|
523
|
+
const typeDocuments = $derived((visibleContent.documents ?? []).filter((doc) => doc.type === docTypeName(sel)).sort((a, b) => documentCell(a, sortBy).localeCompare(documentCell(b, sortBy)) * sortDir));
|
|
514
524
|
|
|
515
525
|
|
|
516
526
|
const menu = $derived.by(() => { const slug = sel?.kind === "menu" ? sel.slug : null; return manifest.menus?.entries.find((m) => m.slug === slug); });
|
|
517
527
|
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" ? "
|
|
528
|
+
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
529
|
const listRows = $derived(sel?.kind === "menus"
|
|
520
530
|
? 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
531
|
: sel?.kind === "documents"
|
|
@@ -538,7 +548,7 @@
|
|
|
538
548
|
setSettings(settings);
|
|
539
549
|
settingsError = "";
|
|
540
550
|
} catch {
|
|
541
|
-
settingsError = "
|
|
551
|
+
settingsError = $t("shell.settingsStorageError");
|
|
542
552
|
}
|
|
543
553
|
});
|
|
544
554
|
|
|
@@ -586,22 +596,31 @@
|
|
|
586
596
|
return false;
|
|
587
597
|
}
|
|
588
598
|
|
|
589
|
-
function requestNavigation(action: () => void) {
|
|
599
|
+
function requestNavigation(action: () => void, oncancel: (() => void) | null = null) {
|
|
590
600
|
if (publishOpen || publishBusy) return;
|
|
601
|
+
pendingNavigationCancel = oncancel;
|
|
591
602
|
if (dirty || saveState === "saving") pendingNavigation = action;
|
|
592
603
|
else action();
|
|
593
604
|
}
|
|
594
605
|
|
|
606
|
+
function cancelNavigation() {
|
|
607
|
+
pendingNavigation = null;
|
|
608
|
+
pendingNavigationCancel?.();
|
|
609
|
+
pendingNavigationCancel = null;
|
|
610
|
+
}
|
|
611
|
+
|
|
595
612
|
async function saveAndLeave() {
|
|
596
613
|
if (!(await save())) return;
|
|
597
614
|
const action = pendingNavigation;
|
|
598
615
|
pendingNavigation = null;
|
|
616
|
+
pendingNavigationCancel = null;
|
|
599
617
|
action?.();
|
|
600
618
|
}
|
|
601
619
|
|
|
602
620
|
function discardAndLeave() {
|
|
603
621
|
const action = pendingNavigation;
|
|
604
622
|
pendingNavigation = null;
|
|
623
|
+
pendingNavigationCancel = null;
|
|
605
624
|
const fallback: Sel | null = newEntry ? { kind: "collection", name: newEntry.collection } : newDocument ? { kind: "pages" } : sel;
|
|
606
625
|
newEntry = null;
|
|
607
626
|
newDocument = null;
|
|
@@ -610,6 +629,7 @@
|
|
|
610
629
|
}
|
|
611
630
|
|
|
612
631
|
function select(next: Sel | null) {
|
|
632
|
+
if (manifest.contentLocales && !selectionInContent(visibleContent, next)) return;
|
|
613
633
|
if (sel && next && sameSel(sel, next)) return;
|
|
614
634
|
requestNavigation(() => openSelection(next));
|
|
615
635
|
}
|
|
@@ -654,6 +674,21 @@
|
|
|
654
674
|
|
|
655
675
|
}
|
|
656
676
|
|
|
677
|
+
function focusContentLocale() {
|
|
678
|
+
void tick().then(() => document.querySelector<HTMLButtonElement>(".content-language button[role=combobox]")?.focus({ preventScroll: true }));
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
function switchContentLocale(value: string | string[]) {
|
|
682
|
+
localeControlRevision += 1;
|
|
683
|
+
if (typeof value !== "string" || value === contentLocale || !manifest.contentLocales?.locales.some(locale => locale.code === value)) return;
|
|
684
|
+
requestNavigation(() => {
|
|
685
|
+
const destination = contentForLocale(manifest, value);
|
|
686
|
+
selectedContentLocale = value;
|
|
687
|
+
if (!selectionInContent(destination, sel)) openSelection(null);
|
|
688
|
+
focusContentLocale();
|
|
689
|
+
}, focusContentLocale);
|
|
690
|
+
}
|
|
691
|
+
|
|
657
692
|
function sort(key: string) {
|
|
658
693
|
if (sortBy === key) sortDir = sortDir === 1 ? -1 : 1;
|
|
659
694
|
else {
|
|
@@ -682,7 +717,7 @@
|
|
|
682
717
|
function cellValue(p: { slug: string; frontmatter: Record<string, unknown> }, key: string): string {
|
|
683
718
|
const v = key === "slug" ? effectiveSlug(p.frontmatter, p.slug) : p.frontmatter[key];
|
|
684
719
|
const field = coll?.fields.find(field => field.key === key);
|
|
685
|
-
if (field?.type === "reference") return referenceLabel(v, referenceOptions(field, manifest.documents)) || "—";
|
|
720
|
+
if (field?.type === "reference") return referenceLabel(v, referenceOptions(field, manifest.documents), $t) || "—";
|
|
686
721
|
if (v === true) return "✓";
|
|
687
722
|
if (v === false || v == null) return "—";
|
|
688
723
|
if (Array.isArray(v)) return v.join(", ");
|
|
@@ -692,14 +727,14 @@
|
|
|
692
727
|
function newPage(type = pageDocType) {
|
|
693
728
|
if (!type || !schema || type.creatable === false || type.singleton) return;
|
|
694
729
|
actionDialog = {
|
|
695
|
-
title:
|
|
730
|
+
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
731
|
onconfirm: (title) => {
|
|
697
732
|
const slug = slugify(title);
|
|
698
|
-
if (!slug) throw new Error("
|
|
699
|
-
|
|
700
|
-
if (taken.includes(slug)) throw new Error(`The slug "${slug}" is already used by another document or page.`);
|
|
733
|
+
if (!slug) throw new Error($t("shell.titleRequired"));
|
|
734
|
+
if (manifest.documents?.some(doc => doc.type === type.name && doc.slug === slug)) throw new Error($t("shell.slugAlreadyUsed", { slug }));
|
|
701
735
|
const id = `${type.name}:${slug}`;
|
|
702
736
|
const data = newDocumentFromTemplate(schema, type.name, title, slug);
|
|
737
|
+
validateDocumentSlugs(schema, [...(manifest.documents ?? []), { id, type: type.name, slug, data }], manifest.preview, $t);
|
|
703
738
|
newDocument = { type: type.name, id, slug };
|
|
704
739
|
draft = JSON.parse(JSON.stringify(data));
|
|
705
740
|
docBase = null;
|
|
@@ -710,10 +745,10 @@
|
|
|
710
745
|
|
|
711
746
|
function newPost(c: ManifestCollection) {
|
|
712
747
|
actionDialog = {
|
|
713
|
-
title:
|
|
748
|
+
title: $t("shell.addToCollection", { name: c.label }), description: $t("shell.newPostDescription"), fieldLabel: $t("shell.title"), confirmLabel: $t("shell.createDraft"),
|
|
714
749
|
onconfirm: (title) => {
|
|
715
750
|
const slug = slugify(title, c.posts.flatMap((post) => [post.slug, effectiveSlug(post.frontmatter, post.slug)]));
|
|
716
|
-
if (!slug) throw new Error("
|
|
751
|
+
if (!slug) throw new Error($t("shell.titleRequired"));
|
|
717
752
|
newEntry = { collection: c.name, post: { slug, frontmatter: { ...initialCollectionValues(c.fields, title), slug }, body: emptyBody() } };
|
|
718
753
|
openSelection({ kind: "post", collection: c.name, slug });
|
|
719
754
|
},
|
|
@@ -725,8 +760,8 @@
|
|
|
725
760
|
const current = post, collection = coll, unsaved = newEntry !== null;
|
|
726
761
|
if (!current || !collection) return;
|
|
727
762
|
actionDialog = {
|
|
728
|
-
title:
|
|
729
|
-
description: unsaved ? "
|
|
763
|
+
title: $t("shell.deletePostTitle", { name: String(current.frontmatter.title ?? current.slug) }), confirmLabel: $t("shell.delete"), danger: true,
|
|
764
|
+
description: unsaved ? $t("shell.discardNewPostDescription") : githubMode ? $t("shell.deleteRemotePostDescription", { slug: current.slug, branch: String(manifest.github?.branch) }) : $t("shell.deleteLocalPostDescription", { slug: current.slug }),
|
|
730
765
|
onconfirm: async () => {
|
|
731
766
|
if (!unsaved) {
|
|
732
767
|
if (githubMode) {
|
|
@@ -734,8 +769,8 @@
|
|
|
734
769
|
catch (error) { handleAuthError(error); throw error; }
|
|
735
770
|
}
|
|
736
771
|
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()) ||
|
|
772
|
+
const res = await fetch(`/rigel/api/local/${collection.name}/${encodeURIComponent(current.slug)}`, { method: "DELETE", headers: { "Accept-Language": $locale } });
|
|
773
|
+
if (!res.ok) throw new Error((await res.text()) || $t("shell.deleteFailedStatus", { status: res.status }));
|
|
739
774
|
}
|
|
740
775
|
collection.posts = collection.posts.filter((p) => p.slug !== current.slug);
|
|
741
776
|
}
|
|
@@ -762,7 +797,7 @@
|
|
|
762
797
|
onstorage={(event) => {
|
|
763
798
|
if ((event.key === "rigelcms.session" || event.key === null) && blueprint?.github && !readSession(blueprint.github)) {
|
|
764
799
|
invalidateSession();
|
|
765
|
-
connectionError = "
|
|
800
|
+
connectionError = $t("shell.signedOutAnotherTab");
|
|
766
801
|
}
|
|
767
802
|
}}
|
|
768
803
|
onresize={endPreviewResize}
|
|
@@ -784,31 +819,31 @@
|
|
|
784
819
|
<div class="rigel connection-shell">
|
|
785
820
|
<main id="github-connection" class="connection-card" aria-busy={connectionBusy}>
|
|
786
821
|
<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 ? "
|
|
822
|
+
<h1>{!ready ? $t("shell.loadingEditor") : $t("shell.connectGithub")}</h1>
|
|
788
823
|
{#if !ready}
|
|
789
|
-
{#if loadError}<p class="connection-error" role="alert">{loadError}</p><button class="primary" onclick={loadBlueprint}>
|
|
790
|
-
{:else}<p role="status">
|
|
824
|
+
{#if loadError}<p class="connection-error" role="alert">{loadError}</p><button class="primary" onclick={loadBlueprint}>{$t("shell.retryLoading")}</button>
|
|
825
|
+
{:else}<p role="status">{$t("shell.loadingConfiguration")}</p>{/if}
|
|
791
826
|
{:else if manifest.github}
|
|
792
827
|
{#if githubAccess}
|
|
793
|
-
<p>
|
|
828
|
+
<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
829
|
{#if githubAccess.status === "read-only" || githubAccess.status === "installation-required"}
|
|
795
|
-
<p>
|
|
830
|
+
<p>{$t("shell.noEditingAccess")}</p>
|
|
796
831
|
{:else}
|
|
797
|
-
<p role="status">{connectionBusy ? "
|
|
832
|
+
<p role="status">{connectionBusy ? $t("shell.openingEditor") : $t("shell.accessConfirmed")}</p>
|
|
798
833
|
{/if}
|
|
799
834
|
{#if githubAccess.status !== "ready"}
|
|
800
|
-
<a id="github-install" class="primary" href={githubAccess.installUrl} target="_blank" rel="noopener noreferrer">
|
|
835
|
+
<a id="github-install" class="primary" href={githubAccess.installUrl} target="_blank" rel="noopener noreferrer">{$t("shell.manageGithubAccess")}</a>
|
|
801
836
|
{/if}
|
|
802
837
|
{:else}
|
|
803
|
-
<p>
|
|
838
|
+
<p>{$t("shell.signInHint")}</p>
|
|
804
839
|
{/if}
|
|
805
840
|
<div class="connection-actions">
|
|
806
841
|
{#if sessionExpiresAt && !tokenExpired}
|
|
807
|
-
<button id="github-check-access" class="primary" disabled={connectionBusy} onclick={recheckGitHub}>{connectionBusy ? "
|
|
842
|
+
<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
843
|
{/if}
|
|
809
|
-
<button id="github-sign-in" class:primary={!sessionExpiresAt || tokenExpired} class:outlined={sessionExpiresAt > 0 && !tokenExpired} disabled={connectionBusy} onclick={connectGitHub}>{connectionBusy && !sessionExpiresAt ? "
|
|
844
|
+
<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
845
|
</div>
|
|
811
|
-
<p class="connection-note">
|
|
846
|
+
<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
847
|
{/if}
|
|
813
848
|
{#if connectionError}<p class="connection-error" role="alert">{connectionError}</p>{/if}
|
|
814
849
|
</main>
|
|
@@ -816,13 +851,20 @@
|
|
|
816
851
|
{:else}
|
|
817
852
|
<div class="rigel" class:sidebar-collapsed={settings.userPref.sidebarCollapsed}>
|
|
818
853
|
<aside id="rigel-sidebar" data-tooltip-position="right">
|
|
819
|
-
<a class="brand" href="/rigel/" role="button" aria-label="
|
|
854
|
+
<a class="brand" href="/rigel/" role="button" aria-label={$t("shell.welcome")} onclick={(event) => { event.preventDefault(); goWelcome(); }}>
|
|
820
855
|
<img src={logo} alt="" width="32" height="32" />
|
|
821
856
|
<span>Rigel <span class="brand-suffix">CMS</span></span>
|
|
822
857
|
</a>
|
|
823
|
-
<TopBar {
|
|
858
|
+
<TopBar manifest={visibleContent} {sel} onselect={select} />
|
|
859
|
+
{#if manifest.contentLocales && manifest.contentLocales.locales.length > 1}
|
|
860
|
+
<div class="content-language" data-locale={contentLocale}>
|
|
861
|
+
{#key localeControlRevision}
|
|
862
|
+
<Select label={$t("shell.contentLanguage")} value={contentLocale} options={manifest.contentLocales.locales.map(locale => ({ value: locale.code, label: locale.label }))} disabled={publishOpen || publishBusy} onchange={switchContentLocale} />
|
|
863
|
+
{/key}
|
|
864
|
+
</div>
|
|
865
|
+
{/if}
|
|
824
866
|
|
|
825
|
-
<nav id="rigel-sidebar-navigation" class="rail-scroll" aria-label="
|
|
867
|
+
<nav id="rigel-sidebar-navigation" class="rail-scroll" aria-label={$t("shell.content")}>
|
|
826
868
|
{#each sidebar as node}
|
|
827
869
|
{#if node.kind === "heading"}
|
|
828
870
|
<h3 class="sidebar-heading">{node.label}</h3>
|
|
@@ -850,16 +892,16 @@
|
|
|
850
892
|
</nav>
|
|
851
893
|
|
|
852
894
|
{#if githubMode && manifest.github?.publishBranch}
|
|
853
|
-
<button id="publish-button" class="sidebar-publish" aria-label="
|
|
854
|
-
<Icon name="rocketLaunch" size={18} /><span class="sidebar-label">
|
|
895
|
+
<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}>
|
|
896
|
+
<Icon name="rocketLaunch" size={18} /><span class="sidebar-label">{$t("shell.publish")}</span>
|
|
855
897
|
</button>
|
|
856
898
|
{/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">
|
|
899
|
+
<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; }}>
|
|
900
|
+
<SidebarIcon name="sidebar-simple" /><span class="sidebar-label">{$t("shell.collapseSidebar")}</span>
|
|
859
901
|
</button>
|
|
860
902
|
<div class="aside-foot">
|
|
861
903
|
<div class="user-menu-wrap">
|
|
862
|
-
<button class="user-btn" aria-label={githubMode ? "
|
|
904
|
+
<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
905
|
{@render avatar()}
|
|
864
906
|
<span class="user-details"><span class="uname">{userName}</span><small>{userLogin}</small></span>
|
|
865
907
|
<span class="user-menu-icon"><Icon name="dotsThreeOutlineVertical" size={18} /></span>
|
|
@@ -873,26 +915,26 @@
|
|
|
873
915
|
<div class="divider"></div>
|
|
874
916
|
<a role="menuitem" href="/" target="_blank" rel="noopener">
|
|
875
917
|
<Icon name="external" size={13} />
|
|
876
|
-
|
|
918
|
+
{$t("shell.liveSite")}
|
|
877
919
|
</a>
|
|
878
920
|
<div class="divider"></div>
|
|
879
921
|
<button role="menuitem" onclick={() => { userOpen = false; settingsOpen = true; }}>
|
|
880
922
|
<Icon name="gear" size={13} />
|
|
881
|
-
|
|
923
|
+
{$t("shell.settings")}
|
|
882
924
|
</button>
|
|
883
925
|
<button role="menuitem" onclick={() => { userOpen = false; shortcutsOpen = true; }}>
|
|
884
926
|
<Icon name="keyboard" size={13} />
|
|
885
|
-
|
|
927
|
+
{$t("shell.keyboardShortcutsMenu")}
|
|
886
928
|
</button>
|
|
887
929
|
<div class="divider"></div>
|
|
888
930
|
{#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(); }}>
|
|
931
|
+
<a role="menuitem" href={githubAccess?.installUrl} target="_blank" rel="noopener noreferrer">{$t("shell.configureGithubAccess")}</a>
|
|
932
|
+
<button role="menuitem" class="github-reconnect" disabled={connectionBusy || saveState === "saving"} onclick={() => { userOpen = false; void connectGitHub(); }}>{$t("shell.reconnectGithub")}</button>
|
|
891
933
|
{/if}
|
|
892
|
-
<button id={githubMode ? "github-sign-out" : undefined} role="menuitem" class:muted={!githubMode} disabled={!githubMode || saveState === "saving" || connectionBusy} data-tooltip={githubMode ? "
|
|
934
|
+
<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
935
|
<Icon name="signOut" size={13} />
|
|
894
|
-
|
|
895
|
-
{#if !githubMode}<span class="soon">
|
|
936
|
+
{$t("shell.signOut")}
|
|
937
|
+
{#if !githubMode}<span class="soon">{$t("shell.githubBackendHint")}</span>{/if}
|
|
896
938
|
</button>
|
|
897
939
|
</div>
|
|
898
940
|
{/if}
|
|
@@ -904,8 +946,8 @@
|
|
|
904
946
|
<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
947
|
{#if githubMode && (tokenExpired || githubAccess?.status !== "ready" || connectionError)}
|
|
906
948
|
<div class="github-session" role="status">
|
|
907
|
-
<p>{connectionError || (tokenExpired ? "
|
|
908
|
-
<button class="primary" disabled={connectionBusy || saveState === "saving"} onclick={connectGitHub}>{connectionBusy ? "
|
|
949
|
+
<p>{connectionError || (tokenExpired ? $t("shell.sessionExpired") : $t("shell.repositoryAccessChanged"))}</p>
|
|
950
|
+
<button class="primary" disabled={connectionBusy || saveState === "saving"} onclick={connectGitHub}>{connectionBusy ? $t("shell.connecting") : $t("shell.reconnectGithub")}</button>
|
|
909
951
|
</div>
|
|
910
952
|
{/if}
|
|
911
953
|
{#if settingsError}<p role="alert">{settingsError}</p>{/if}
|
|
@@ -913,30 +955,30 @@
|
|
|
913
955
|
{#if sel.kind === "page" || sel.kind === "post" || sel.kind === "menu" || sel.kind === "document"}
|
|
914
956
|
<div class="editor-actions">
|
|
915
957
|
{#if sel.kind === "page" && page}
|
|
916
|
-
<p class="back"><button onclick={() => select(page.shared ? null : { kind: "pages", category: page.category })}
|
|
958
|
+
<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
959
|
{:else if sel.kind === "document"}
|
|
918
960
|
{@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)}
|
|
961
|
+
<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
962
|
{:else if sel.kind === "post" && coll}
|
|
921
|
-
<p class="back"><button onclick={() => select({ kind: "collection", name: coll.name })}
|
|
963
|
+
<p class="back"><button onclick={() => select({ kind: "collection", name: coll.name })}>{$t("shell.backTo", { name: coll.label })}</button></p>
|
|
922
964
|
{:else if sel.kind === "menu"}
|
|
923
|
-
<p class="back"><button onclick={() => select({ kind: "menus" })}
|
|
965
|
+
<p class="back"><button onclick={() => select({ kind: "menus" })}>{$t("shell.backTo", { name: $t("shell.menus") })}</button></p>
|
|
924
966
|
{/if}
|
|
925
967
|
<div class="save-actions">
|
|
926
968
|
<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" ? "
|
|
969
|
+
{saveState === "saving" ? $t("shell.saving") : saveState === "error" ? $t("shell.saveFailed") : dirty ? $t("shell.unsaved") : saveState === "saved" ? $t("shell.saved") : ""}
|
|
928
970
|
</span>
|
|
929
|
-
<button class="primary save-button" disabled={!dirty || saveState === "saving"} onclick={() => save()}>{saveState === "saving" ? "
|
|
971
|
+
<button class="primary save-button" disabled={!dirty || saveState === "saving"} onclick={() => save()}>{saveState === "saving" ? $t("shell.saving") : $t("shell.save")}</button>
|
|
930
972
|
</div>
|
|
931
973
|
</div>
|
|
932
974
|
{/if}
|
|
933
975
|
<div class="titlebar">
|
|
934
976
|
<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
977
|
{#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))}>
|
|
978
|
+
<button class="primary" onclick={() => (sel?.kind === "collection" ? newPost(coll!) : newPage(sel?.kind === "documents" ? activeDocType : pageDocType))}>{$t("shell.addNew")}</button>
|
|
937
979
|
{/if}
|
|
938
980
|
{#if sel?.kind === "post" && post}
|
|
939
|
-
<button class="icon-btn" aria-label="
|
|
981
|
+
<button class="icon-btn" aria-label={$t("shell.deletePost")} data-tooltip={$t("shell.deletePost")} disabled={saveState === "saving"} onclick={deletePost}>
|
|
940
982
|
<Icon name="trash" size={15} />
|
|
941
983
|
</button>
|
|
942
984
|
{/if}
|
|
@@ -945,16 +987,16 @@
|
|
|
945
987
|
{#if saveError}
|
|
946
988
|
<div class="save-toast" role="alert">
|
|
947
989
|
<div class="save-toast-content">
|
|
948
|
-
<strong>
|
|
990
|
+
<strong>{$t("shell.saveChangesError")}</strong>
|
|
949
991
|
<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}>
|
|
992
|
+
<button class="outlined" disabled={saveState === "saving"} onclick={() => save()}>{$t("shell.retrySave")}</button>
|
|
993
|
+
{#if githubMode}<button class="outlined github-reconnect" disabled={connectionBusy || saveState === "saving"} onclick={connectGitHub}>{$t("shell.reconnectGithub")}</button>{/if}
|
|
952
994
|
</div>
|
|
953
|
-
<button type="button" class="icon-btn" aria-label="
|
|
995
|
+
<button type="button" class="icon-btn" aria-label={$t("shell.dismissSaveError")} onclick={() => { saveError = ""; }}><Icon name="x" size={18} /></button>
|
|
954
996
|
</div>
|
|
955
997
|
{/if}
|
|
956
998
|
{#if !ready}
|
|
957
|
-
<p>
|
|
999
|
+
<p>{$t("shell.loading")}</p>
|
|
958
1000
|
|
|
959
1001
|
{:else if sel?.kind === "media"}
|
|
960
1002
|
<ImagePicker inline />
|
|
@@ -981,7 +1023,7 @@
|
|
|
981
1023
|
<td>
|
|
982
1024
|
{#if col.key === titleColumn}
|
|
983
1025
|
<button class="table-link" onclick={() => select(row.target)}>{row.cells[col.key]}</button>
|
|
984
|
-
{#if row.draft}<span class="badge">
|
|
1026
|
+
{#if row.draft}<span class="badge">{$t("shell.draftBadge")}</span>{/if}
|
|
985
1027
|
{:else if col.image}
|
|
986
1028
|
{#if row.cells[col.key]}
|
|
987
1029
|
<img class="table-thumbnail" src={row.cells[col.key]} alt="" width="32" height="32" loading="lazy" decoding="async" />
|
|
@@ -995,7 +1037,7 @@
|
|
|
995
1037
|
{/each}
|
|
996
1038
|
</tr>
|
|
997
1039
|
{:else}
|
|
998
|
-
<tr><td colspan={columns.length}>
|
|
1040
|
+
<tr><td colspan={columns.length}>{$t("shell.noEntries")}</td></tr>
|
|
999
1041
|
{/each}
|
|
1000
1042
|
</tbody>
|
|
1001
1043
|
</table>
|
|
@@ -1028,14 +1070,14 @@
|
|
|
1028
1070
|
|
|
1029
1071
|
{:else if sel?.kind === "menu" && menu && manifest.menus}
|
|
1030
1072
|
<section class="menu-details">
|
|
1031
|
-
<label><span>
|
|
1032
|
-
<label><span>
|
|
1073
|
+
<label><span>{$t("shell.slug")}</span><input type="text" value={menu.slug} readonly /></label>
|
|
1074
|
+
<label><span>{$t("shell.name")}</span><input type="text" value={String(draft.title ?? "")} oninput={(e) => { draft.title = e.currentTarget.value; touch(); }} /></label>
|
|
1033
1075
|
</section>
|
|
1034
1076
|
<MenuEditor items={draft.items as MenuItem[]} maxDepth={manifest.menus.maxDepth} oninput={(items) => { draft.items = items; touch(); }} />
|
|
1035
1077
|
|
|
1036
1078
|
{:else if sel?.kind === "page" && page}
|
|
1037
1079
|
{#if !page.shared}
|
|
1038
|
-
<section><label><span>
|
|
1080
|
+
<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
1081
|
{/if}
|
|
1040
1082
|
{#each page.groups as group (group.id)}
|
|
1041
1083
|
<section>
|
|
@@ -1060,7 +1102,7 @@
|
|
|
1060
1102
|
<VisualEditor
|
|
1061
1103
|
docId={`${coll.name}:${post.slug}`}
|
|
1062
1104
|
values={draft}
|
|
1063
|
-
previewValues={collectionPreviewValues(coll.fields, draft, manifest.documents ?? [])}
|
|
1105
|
+
previewValues={collectionPreviewValues(coll.fields, draft, manifest.documents ?? [], $t)}
|
|
1064
1106
|
body={bodyDraft}
|
|
1065
1107
|
bodyInline={settings.userPref.editorExperience === "inline"}
|
|
1066
1108
|
onbody={(value) => { bodyDraft = value; touch(); }}
|
|
@@ -1079,20 +1121,20 @@
|
|
|
1079
1121
|
{:else}
|
|
1080
1122
|
<div class="welcome">
|
|
1081
1123
|
<img src={logo} alt="" width="64" height="64" />
|
|
1082
|
-
<h2>
|
|
1083
|
-
<p>
|
|
1084
|
-
<p class="welcome-hint">
|
|
1124
|
+
<h2>{$t("shell.welcomeTitle")}</h2>
|
|
1125
|
+
<p>{$t("shell.welcomeTagline")}</p>
|
|
1126
|
+
<p class="welcome-hint">{$t("shell.welcomeHint")}</p>
|
|
1085
1127
|
</div>
|
|
1086
1128
|
{/if}
|
|
1087
1129
|
{#if settings.devMode && (sel?.kind === "page" || sel?.kind === "post" || sel?.kind === "menu" || sel?.kind === "document")}
|
|
1088
1130
|
<section class="md-preview" class:article-state={sel.kind === "post"}>
|
|
1089
|
-
<h3>
|
|
1131
|
+
<h3>{$t("shell.currentDraft")}</h3>
|
|
1090
1132
|
<pre>{JSON.stringify(sel.kind === "post" ? { frontmatter: draft, body: bodyDraft } : draft, null, 2)}</pre>
|
|
1091
1133
|
</section>
|
|
1092
1134
|
{/if}
|
|
1093
1135
|
</main>
|
|
1094
1136
|
{#if manifest.preview && previewTarget}
|
|
1095
|
-
<div class="preview-divider" role="separator" tabindex="0" aria-label="
|
|
1137
|
+
<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
1138
|
<SitePreview target={previewTarget} config={manifest.preview} isNew={!!newEntry} values={draft} body={bodyDraft} devMode={settings.devMode} />
|
|
1097
1139
|
{/if}
|
|
1098
1140
|
</div>
|
|
@@ -1106,7 +1148,7 @@
|
|
|
1106
1148
|
<Shortcuts onclose={() => (shortcutsOpen = false)} />
|
|
1107
1149
|
{/if}
|
|
1108
1150
|
{#if pendingNavigation}
|
|
1109
|
-
<UnsavedChanges busy={saveState === "saving"} error={saveError} onsave={saveAndLeave} ondiscard={discardAndLeave} oncancel={
|
|
1151
|
+
<UnsavedChanges busy={saveState === "saving"} error={saveError} onsave={saveAndLeave} ondiscard={discardAndLeave} oncancel={cancelNavigation} />
|
|
1110
1152
|
{/if}
|
|
1111
1153
|
{#if actionDialog}
|
|
1112
1154
|
<ActionDialog {...actionDialog} onclose={() => { actionDialog = null; }} />
|