@rigel-cms/rigel 1.0.0 → 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 +4 -0
- package/docs/extensions.md +194 -0
- package/package.json +15 -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 +12 -2
- package/src/editor/App.svelte +98 -97
- 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/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.ts +22 -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 +1 -0
- package/src/schema/validate.ts +73 -69
- package/src/slugs.ts +11 -10
- package/src/vite/manifest-plugin.ts +33 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { t } from "../i18n";
|
|
2
3
|
import { onDestroy, tick } from "svelte";
|
|
3
4
|
import { EditorState, TextSelection } from "prosemirror-state";
|
|
4
5
|
import { EditorView } from "prosemirror-view";
|
|
@@ -39,14 +40,14 @@
|
|
|
39
40
|
view.focus();
|
|
40
41
|
}
|
|
41
42
|
const marks = [
|
|
42
|
-
{ name: "
|
|
43
|
-
{ name: "
|
|
44
|
-
{ name: "
|
|
43
|
+
{ name: "richtext.bold", icon: "textB", type: "strong" },
|
|
44
|
+
{ name: "richtext.italic", icon: "textItalic", type: "em" },
|
|
45
|
+
{ name: "richtext.code", icon: "code", type: "code" },
|
|
45
46
|
] as const;
|
|
46
47
|
const blocks = [
|
|
47
|
-
{ name: "
|
|
48
|
-
{ name: "
|
|
49
|
-
{ name: "
|
|
48
|
+
{ name: "richtext.bulletList", icon: "listBullets", type: "bullet_list" },
|
|
49
|
+
{ name: "richtext.numberedList", icon: "listNumbers", type: "ordered_list" },
|
|
50
|
+
{ name: "richtext.quote", icon: "quotes", type: "blockquote" },
|
|
50
51
|
] as const;
|
|
51
52
|
const format = $derived(state?.selection.$from.parent.type.name === "heading" ? `h${state.selection.$from.parent.attrs.level}` : state?.selection.$from.parent.type.name ?? "paragraph");
|
|
52
53
|
|
|
@@ -57,7 +58,7 @@
|
|
|
57
58
|
|
|
58
59
|
function load(source: string) {
|
|
59
60
|
const doc = parseVisualMarkdown(source);
|
|
60
|
-
warning = doc ? "" : "
|
|
61
|
+
warning = doc ? "" : $t("richtext.unsupportedMarkdown");
|
|
61
62
|
if (!doc) { mode = "source"; return false; }
|
|
62
63
|
state = EditorState.create({ doc, plugins: exampleSetup({ schema, menuBar: false }) });
|
|
63
64
|
view?.updateState(state);
|
|
@@ -148,7 +149,7 @@
|
|
|
148
149
|
|
|
149
150
|
function link(remove = false) {
|
|
150
151
|
if (!view) return;
|
|
151
|
-
if (!remove && (!safeMarkdownUrl(linkUrl) || !linkUrl)) { linkError = "
|
|
152
|
+
if (!remove && (!safeMarkdownUrl(linkUrl) || !linkUrl)) { linkError = $t("richtext.safeUrlError"); return; }
|
|
152
153
|
const { from, to, empty } = view.state.selection;
|
|
153
154
|
let tr = view.state.tr;
|
|
154
155
|
if (empty) tr = remove ? tr.removeStoredMark(schema.marks.link) : tr.addStoredMark(schema.marks.link.create({ href: linkUrl }));
|
|
@@ -164,36 +165,36 @@
|
|
|
164
165
|
<div class="markdown-editor body-editor">
|
|
165
166
|
<div class="markdown-heading">
|
|
166
167
|
<div class="field-label" id={`${id}-label`}>{label}</div>
|
|
167
|
-
<div class="markdown-modes" role="group" aria-label={
|
|
168
|
-
<button type="button" aria-pressed={mode === "visual"} onclick={visual}>
|
|
169
|
-
<button type="button" aria-pressed={mode === "source"} onclick={() => { mode = "source"; linkOpen = false; }}>
|
|
168
|
+
<div class="markdown-modes" role="group" aria-label={$t("richtext.editingMode", { label })}>
|
|
169
|
+
<button type="button" aria-pressed={mode === "visual"} onclick={visual}>{$t("richtext.visual")}</button>
|
|
170
|
+
<button type="button" aria-pressed={mode === "source"} onclick={() => { mode = "source"; linkOpen = false; }}>{$t("richtext.markdown")}</button>
|
|
170
171
|
</div>
|
|
171
172
|
</div>
|
|
172
173
|
{#if warning}<p class="markdown-warning" role="status">{warning}</p>{/if}
|
|
173
174
|
<div class="markdown-visual" hidden={mode !== "visual"}>
|
|
174
|
-
<div class="body-toolbar formatting-toolbar" role="group" aria-label={
|
|
175
|
-
<Select ariaLabel="
|
|
176
|
-
<div class="format-group" role="group" aria-label="
|
|
175
|
+
<div class="body-toolbar formatting-toolbar" role="group" aria-label={$t("richtext.formatting", { label })}>
|
|
176
|
+
<Select ariaLabel={$t("richtext.textStyle")} data-tooltip={$t("richtext.textStyle")} value={format} options={[{ value: "paragraph", label: $t("richtext.paragraph") }, ...[1, 2, 3, 4, 5, 6].map(level => ({ value: `h${level}`, label: $t("richtext.heading", { level }) })), { value: "code_block", label: $t("richtext.codeBlock") }].map(option => ({ ...option, disabled: !!state && format !== option.value && !formatCommand(option.value)(state) }))} onchange={next => command(formatCommand(String(next)))} />
|
|
177
|
+
<div class="format-group" role="group" aria-label={$t("richtext.textFormatting")}>
|
|
177
178
|
{#each marks as item}
|
|
178
|
-
<button type="button" aria-label={item.name} data-tooltip={item.name} aria-pressed={activeMark(item.type)} disabled={!state || !toggleMark(schema.marks[item.type])(state)} onmousedown={event => event.preventDefault()} onclick={() => command(toggleMark(schema.marks[item.type]))}><Icon name={item.icon} size={18} /></button>
|
|
179
|
+
<button type="button" aria-label={$t(item.name)} data-tooltip={$t(item.name)} aria-pressed={activeMark(item.type)} disabled={!state || !toggleMark(schema.marks[item.type])(state)} onmousedown={event => event.preventDefault()} onclick={() => command(toggleMark(schema.marks[item.type]))}><Icon name={item.icon} size={18} /></button>
|
|
179
180
|
{/each}
|
|
180
181
|
</div>
|
|
181
|
-
<div class="format-group" role="group" aria-label="
|
|
182
|
+
<div class="format-group" role="group" aria-label={$t("richtext.listsAndQuote")}>
|
|
182
183
|
{#each blocks as item}
|
|
183
|
-
<button type="button" aria-label={item.name} data-tooltip={item.name} aria-pressed={activeBlock(item.type)} disabled={!state || !blockCommand(item.type)(state)} onmousedown={event => event.preventDefault()} onclick={() => command(blockCommand(item.type))}><Icon name={item.icon} size={18} /></button>
|
|
184
|
+
<button type="button" aria-label={$t(item.name)} data-tooltip={$t(item.name)} aria-pressed={activeBlock(item.type)} disabled={!state || !blockCommand(item.type)(state)} onmousedown={event => event.preventDefault()} onclick={() => command(blockCommand(item.type))}><Icon name={item.icon} size={18} /></button>
|
|
184
185
|
{/each}
|
|
185
186
|
</div>
|
|
186
|
-
<button bind:this={linkButton} type="button" aria-label="
|
|
187
|
+
<button bind:this={linkButton} type="button" aria-label={$t("richtext.link")} data-tooltip={$t("richtext.link")} aria-pressed={activeMark("link")} aria-expanded={linkOpen} aria-controls={`${id}-link`} disabled={!state || !toggleMark(schema.marks.link)(state)} onmousedown={event => event.preventDefault()} onclick={openLink}><Icon name="link" size={18} /></button>
|
|
187
188
|
<ImagePicker toolbar onopen={() => { imageSelection = view?.state.selection.getBookmark(); linkOpen = false; }} onpick={insertImage} onclose={() => { imageSelection = undefined; }} />
|
|
188
189
|
</div>
|
|
189
190
|
{#if linkOpen}
|
|
190
|
-
<div class="markdown-link" id={`${id}-link`} role="group" aria-label="
|
|
191
|
-
<label for={`${id}-url`}>
|
|
191
|
+
<div class="markdown-link" id={`${id}-link`} role="group" aria-label={$t("richtext.editLink")} onkeydown={event => { if (event.key === "Escape") { event.stopPropagation(); linkOpen = false; linkButton.focus(); } if (event.key === "Enter") { event.preventDefault(); link(); } }}>
|
|
192
|
+
<label for={`${id}-url`}>{$t("richtext.linkUrl")}</label>
|
|
192
193
|
<div class="markdown-link-controls">
|
|
193
194
|
<input id={`${id}-url`} type="text" placeholder="https://…" bind:this={linkInput} bind:value={linkUrl} aria-invalid={!!linkError} aria-describedby={linkError ? `${id}-error` : undefined} />
|
|
194
|
-
<button type="button" aria-label="
|
|
195
|
-
<button type="button" aria-label="
|
|
196
|
-
<button type="button" aria-label="
|
|
195
|
+
<button type="button" aria-label={$t("richtext.applyLink")} data-tooltip={$t("richtext.applyLink")} onclick={() => link()}><Icon name="check" size={16} /></button>
|
|
196
|
+
<button type="button" aria-label={$t("richtext.removeLink")} data-tooltip={$t("richtext.removeLink")} disabled={!activeMark("link")} onclick={() => link(true)}><Icon name="linkBreak" size={16} /></button>
|
|
197
|
+
<button type="button" aria-label={$t("richtext.closeLink")} data-tooltip={$t("richtext.closeLink")} onclick={() => { linkOpen = false; linkButton.focus(); }}><Icon name="x" size={16} /></button>
|
|
197
198
|
</div>
|
|
198
199
|
{#if linkError}<p id={`${id}-error`} role="alert">{linkError}</p>{/if}
|
|
199
200
|
</div>
|
|
@@ -201,6 +202,6 @@
|
|
|
201
202
|
<div class="md-preview rich-text" bind:this={host}></div>
|
|
202
203
|
</div>
|
|
203
204
|
{#if mode === "source"}
|
|
204
|
-
<textarea class="mono" rows="14" aria-label={
|
|
205
|
+
<textarea class="mono" rows="14" aria-label={$t("richtext.markdownSource", { label })} value={value} oninput={(event) => { warning = ""; emit(event.currentTarget.value); }}></textarea>
|
|
205
206
|
{/if}
|
|
206
207
|
</div>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { t } from "../i18n";
|
|
2
3
|
import Icon from "./Icon.svelte";
|
|
3
4
|
import { tick } from "svelte";
|
|
4
5
|
import ImagePicker from "./ImagePicker.svelte";
|
|
@@ -6,7 +7,7 @@
|
|
|
6
7
|
import type { EmbedData } from "../blocks/embed";
|
|
7
8
|
import type { VideoData } from "../blocks/video";
|
|
8
9
|
|
|
9
|
-
let { triggerLabel
|
|
10
|
+
let { triggerLabel, hiddenTrigger = false, onimage, onvideo, onembed, onopen, onclose = () => {} }: {
|
|
10
11
|
triggerLabel?: string;
|
|
11
12
|
hiddenTrigger?: boolean;
|
|
12
13
|
onimage?: () => void;
|
|
@@ -62,10 +63,10 @@
|
|
|
62
63
|
|
|
63
64
|
</script>
|
|
64
65
|
|
|
65
|
-
{#if !hiddenTrigger}<button bind:this={trigger} data-media-trigger type="button" aria-label={triggerLabel} data-tooltip={triggerLabel} aria-haspopup="menu" popovertarget={`${id}-media`} onmousedown={event => event.preventDefault()} onclick={event => { event.preventDefault(); toggleMenu(); }}><Icon name="image" size={18} /></button>{/if}
|
|
66
|
-
<div bind:this={menu} id={`${id}-media`} class="block-menu media-menu" popover role="menu" aria-label="
|
|
67
|
-
<button type="button" role="menuitem" onclick={() => pick("image")}><Icon name="image" size={16} />
|
|
68
|
-
<button type="button" role="menuitem" onclick={() => pick("video")}><Icon name="filmStrip" size={16} />
|
|
66
|
+
{#if !hiddenTrigger}<button bind:this={trigger} data-media-trigger type="button" aria-label={triggerLabel ?? $t("media.insertMedia")} data-tooltip={triggerLabel ?? $t("media.insertMedia")} aria-haspopup="menu" popovertarget={`${id}-media`} onmousedown={event => event.preventDefault()} onclick={event => { event.preventDefault(); toggleMenu(); }}><Icon name="image" size={18} /></button>{/if}
|
|
67
|
+
<div bind:this={menu} id={`${id}-media`} class="block-menu media-menu" popover role="menu" aria-label={$t("media.insertMedia")}>
|
|
68
|
+
<button type="button" role="menuitem" onclick={() => pick("image")}><Icon name="image" size={16} />{$t("media.image")}</button>
|
|
69
|
+
<button type="button" role="menuitem" onclick={() => pick("video")}><Icon name="filmStrip" size={16} />{$t("media.video")}</button>
|
|
69
70
|
<button type="button" role="menuitem" onclick={() => pick("youtube")}><Icon name="youtubeLogo" size={16} />YouTube</button>
|
|
70
71
|
<button type="button" role="menuitem" onclick={() => pick("vimeo")}><Icon name="playCircle" size={16} />Vimeo</button>
|
|
71
72
|
</div>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { t } from "../i18n";
|
|
2
3
|
import { tick } from "svelte";
|
|
3
4
|
import type { MenuItem } from "../../manifest/menu";
|
|
4
5
|
import { editMenuItem, menuRows, moveMenuItem } from "./menu-tree";
|
|
@@ -68,8 +69,8 @@
|
|
|
68
69
|
function move(id: string, targetId: string | null, position: "before" | "after" | "inside") {
|
|
69
70
|
try {
|
|
70
71
|
oninput(moveMenuItem(items, id, targetId, position, maxDepth));
|
|
71
|
-
message = "
|
|
72
|
-
} catch (error) { message = error instanceof Error ? error.message : "
|
|
72
|
+
message = $t("fields.menuItemMoved");
|
|
73
|
+
} catch (error) { message = error instanceof Error ? error.message : $t("fields.menuCannotMoveItem"); }
|
|
73
74
|
dragging = null;
|
|
74
75
|
target = "";
|
|
75
76
|
}
|
|
@@ -82,10 +83,10 @@
|
|
|
82
83
|
}
|
|
83
84
|
function add(parentId: string | null = null) {
|
|
84
85
|
const parent = rows.find((row) => row.item.id === parentId);
|
|
85
|
-
if (parent && parent.depth >= maxDepth) { message =
|
|
86
|
-
const item: MenuItem = { id: crypto.randomUUID(), label: "
|
|
86
|
+
if (parent && parent.depth >= maxDepth) { message = $t("fields.menuMaximumLevels", { maxDepth }); return; }
|
|
87
|
+
const item: MenuItem = { id: crypto.randomUUID(), label: $t("fields.menuNewItem"), url: "/", children: [] };
|
|
87
88
|
oninput(parentId ? editMenuItem(items, parentId, (p) => ({ ...p, children: [...p.children, item] })) : [...items, item]);
|
|
88
|
-
message = "
|
|
89
|
+
message = $t("fields.menuItemAdded");
|
|
89
90
|
void open(item.id);
|
|
90
91
|
}
|
|
91
92
|
function remove(item: MenuItem) {
|
|
@@ -96,70 +97,70 @@
|
|
|
96
97
|
}
|
|
97
98
|
</script>
|
|
98
99
|
|
|
99
|
-
<svelte:window onpointermove={dragPointer} onpointerup={finishDrag} onpointercancel={cancelDrag} onkeydown={(event) => { if (event.key === 'Escape' && pointer) { cancelDrag(); message = '
|
|
100
|
-
<section class="menu-editor" aria-label="
|
|
101
|
-
<nav class="menu-path" aria-label="
|
|
102
|
-
<button type="button" data-menu-path="" onclick={() => open(null)}>
|
|
100
|
+
<svelte:window onpointermove={dragPointer} onpointerup={finishDrag} onpointercancel={cancelDrag} onkeydown={(event) => { if (event.key === 'Escape' && pointer) { cancelDrag(); message = $t('fields.menuMoveCancelled'); } }} />
|
|
101
|
+
<section class="menu-editor" aria-label={$t("fields.menuItems")} bind:this={host}>
|
|
102
|
+
<nav class="menu-path" aria-label={$t("fields.menuPath")}>
|
|
103
|
+
<button type="button" data-menu-path="" onclick={() => open(null)}>{$t("fields.menuItems")}</button>
|
|
103
104
|
{#each chain as row}
|
|
104
105
|
<Icon name="caretRight" size={12} />
|
|
105
|
-
<button type="button" data-menu-path={row.item.id} aria-current={row.item.id === selectedId ? 'step' : undefined} onclick={() => open(row.item.id)}>{row.item.label || "
|
|
106
|
+
<button type="button" data-menu-path={row.item.id} aria-current={row.item.id === selectedId ? 'step' : undefined} onclick={() => open(row.item.id)}>{row.item.label || $t("fields.menuUntitledItem")}</button>
|
|
106
107
|
{/each}
|
|
107
108
|
</nav>
|
|
108
109
|
<p class="menu-message" role="status" aria-live="polite">{message}</p>
|
|
109
110
|
<div class="menu-panes">
|
|
110
111
|
{#each panes as parent, index (parent?.item.id ?? '')}
|
|
111
|
-
<section class="menu-pane" class:active={index === panes.length - 1} data-menu-parent={parent?.item.id ?? ''} aria-label={parent ?
|
|
112
|
+
<section class="menu-pane" class:active={index === panes.length - 1} data-menu-parent={parent?.item.id ?? ''} aria-label={parent ? $t("fields.menuItemDetails", { label: parent.item.label || $t("fields.menuUntitledItem") }) : $t("fields.menuItems")}>
|
|
112
113
|
<div class="menu-pane-head">
|
|
113
|
-
<h3>{parent?.item.label || (parent ? "
|
|
114
|
+
<h3>{parent?.item.label || $t(parent ? "fields.menuUntitledItem" : "fields.menuItems")}</h3>
|
|
114
115
|
{#if parent}
|
|
115
116
|
<details class="menu-actions">
|
|
116
|
-
<summary aria-label={
|
|
117
|
+
<summary aria-label={$t("fields.menuItemActions", { label: parent.item.label })}><Icon name="dotsThreeOutlineVertical" /></summary>
|
|
117
118
|
<div>
|
|
118
|
-
<button type="button" disabled={!parent.previous} onclick={() => parent.previous && move(parent.item.id, parent.previous, 'before')}>
|
|
119
|
-
<button type="button" disabled={!parent.next} onclick={() => parent.next && move(parent.item.id, parent.next, 'after')}>
|
|
120
|
-
<button type="button" disabled={!parent.previous || parent.depth + Math.max(...menuRows([parent.item]).map(row => row.depth)) > maxDepth} onclick={() => parent.previous && move(parent.item.id, parent.previous, 'inside')}>
|
|
121
|
-
<button type="button" disabled={!parent.parentId} onclick={() => parent.parentId && move(parent.item.id, parent.parentId, 'after')}>
|
|
122
|
-
<button type="button" class="menu-remove" onclick={() => remove(parent.item)}>
|
|
119
|
+
<button type="button" disabled={!parent.previous} onclick={() => parent.previous && move(parent.item.id, parent.previous, 'before')}>{$t("fields.menuUp")}</button>
|
|
120
|
+
<button type="button" disabled={!parent.next} onclick={() => parent.next && move(parent.item.id, parent.next, 'after')}>{$t("fields.menuDown")}</button>
|
|
121
|
+
<button type="button" disabled={!parent.previous || parent.depth + Math.max(...menuRows([parent.item]).map(row => row.depth)) > maxDepth} onclick={() => parent.previous && move(parent.item.id, parent.previous, 'inside')}>{$t("fields.menuIndent")}</button>
|
|
122
|
+
<button type="button" disabled={!parent.parentId} onclick={() => parent.parentId && move(parent.item.id, parent.parentId, 'after')}>{$t("fields.menuOutdent")}</button>
|
|
123
|
+
<button type="button" class="menu-remove" onclick={() => remove(parent.item)}>{$t("fields.remove")}</button>
|
|
123
124
|
</div>
|
|
124
125
|
</details>
|
|
125
126
|
{/if}
|
|
126
127
|
</div>
|
|
127
128
|
{#if parent}
|
|
128
129
|
<div class="menu-fields">
|
|
129
|
-
<label><span>
|
|
130
|
-
<label><span>
|
|
131
|
-
<label class="inline"><input type="checkbox" role="switch" class="switch" checked={parent.item.newTab ?? false} onchange={event => oninput(editMenuItem(items, parent.item.id, item => ({ ...item, newTab: event.currentTarget.checked })))} /><span>
|
|
130
|
+
<label><span>{$t("fields.menuLabel")}</span><input type="text" aria-label={$t("fields.menuLabelFor", { id: parent.item.id })} value={parent.item.label} oninput={event => change(parent.item.id, 'label', event.currentTarget.value)} /></label>
|
|
131
|
+
<label><span>{$t("fields.menuUrl")}</span><input type="text" aria-label={$t("fields.menuUrlFor", { id: parent.item.id })} value={parent.item.url} placeholder={$t("fields.menuUrlPlaceholder")} oninput={event => change(parent.item.id, 'url', event.currentTarget.value)} /></label>
|
|
132
|
+
<label class="inline"><input type="checkbox" role="switch" class="switch" checked={parent.item.newTab ?? false} onchange={event => oninput(editMenuItem(items, parent.item.id, item => ({ ...item, newTab: event.currentTarget.checked })))} /><span>{$t("fields.menuOpenInNewTab")}</span></label>
|
|
132
133
|
</div>
|
|
133
|
-
<h4>
|
|
134
|
+
<h4>{$t("fields.menuGroupItems")}</h4>
|
|
134
135
|
{/if}
|
|
135
136
|
<div class="menu-list">
|
|
136
137
|
{#each (parent ? parent.item.children : items) as item (item.id)}
|
|
137
138
|
<div class="menu-item" class:is-dragging={dragging === item.id} class:drop-before={target === item.id + 'before'} class:drop-after={target === item.id + 'after'} class:selected={chain[index]?.item.id === item.id} data-menu-id={item.id} data-parent-id={parent?.item.id ?? ''} data-depth={index + 1}>
|
|
138
|
-
<button type="button" class="menu-drag" aria-label={
|
|
139
|
-
<button type="button" class="menu-open" aria-label={
|
|
140
|
-
<span><span class="menu-label">{item.label || "
|
|
139
|
+
<button type="button" class="menu-drag" aria-label={$t("fields.menuDragItem", { label: item.label })} onpointerdown={event => startDrag(event, item.id)} onlostpointercapture={cancelDrag}><Icon name="listBullets" /></button>
|
|
140
|
+
<button type="button" class="menu-open" aria-label={$t("fields.menuOpenItem", { label: item.label || $t("fields.menuUntitledItem") })} aria-expanded={chain[index]?.item.id === item.id} onclick={() => open(item.id)}>
|
|
141
|
+
<span><span class="menu-label">{item.label || $t("fields.menuUntitledItem")}</span><span class="menu-url">{item.url}</span></span>
|
|
141
142
|
{#if item.children.length}<span class="menu-count">{item.children.length}</span>{/if}
|
|
142
143
|
<Icon name="caretRight" size={12} />
|
|
143
144
|
</button>
|
|
144
145
|
</div>
|
|
145
146
|
{:else}
|
|
146
|
-
<p class="menu-empty">{parent ? "
|
|
147
|
+
<p class="menu-empty">{$t(parent ? "fields.menuEmptyGroup" : "fields.menuEmpty")}</p>
|
|
147
148
|
{/each}
|
|
148
149
|
</div>
|
|
149
|
-
<button type="button" class="menu-add" disabled={!!parent && parent.depth >= maxDepth} onclick={() => add(parent?.item.id ?? null)}><Icon name="plus" />{parent ? "
|
|
150
|
-
{#if parent && parent.depth >= maxDepth}<p class="hint">
|
|
150
|
+
<button type="button" class="menu-add" disabled={!!parent && parent.depth >= maxDepth} onclick={() => add(parent?.item.id ?? null)}><Icon name="plus" />{$t(parent ? "fields.menuAddChild" : "fields.addItem")}</button>
|
|
151
|
+
{#if parent && parent.depth >= maxDepth}<p class="hint">{$t("fields.menuMaximumLevelsHint", { maxDepth })}</p>{/if}
|
|
151
152
|
</section>
|
|
152
153
|
{/each}
|
|
153
154
|
</div>
|
|
154
155
|
</section>
|
|
155
156
|
{#if removing}
|
|
156
|
-
<ActionDialog title={
|
|
157
|
+
<ActionDialog title={$t("fields.menuRemoveTitle", { label: removing.label })} description={$t("fields.menuRemoveDescription")} confirmLabel={$t("fields.remove")} danger onconfirm={() => {
|
|
157
158
|
if (removing) {
|
|
158
159
|
const parentId = rows.find(row => row.item.id === removing?.id)?.parentId ?? null;
|
|
159
160
|
oninput(editMenuItem(items, removing.id, () => null));
|
|
160
161
|
void open(parentId);
|
|
161
162
|
}
|
|
162
|
-
message = "
|
|
163
|
+
message = $t("fields.menuItemRemoved");
|
|
163
164
|
}} onclose={() => { removing = null; }} />
|
|
164
165
|
{/if}
|
|
165
166
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { t } from "../i18n";
|
|
2
3
|
import { tick, untrack } from "svelte";
|
|
3
4
|
import { fetchPreview } from "./preview-frame";
|
|
5
|
+
import { subscribeMediaLabels } from "./embed-preview";
|
|
4
6
|
|
|
5
7
|
let { url, title, frame = $bindable(), reloadToken = 0, onload, onloading, onerror }: {
|
|
6
8
|
url: string;
|
|
@@ -22,18 +24,19 @@
|
|
|
22
24
|
const reducedMotion = matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
23
25
|
const controller = new AbortController();
|
|
24
26
|
const { signal } = controller;
|
|
27
|
+
let stopMediaLabels: (() => void) | undefined;
|
|
25
28
|
source = null;
|
|
26
29
|
loading = true;
|
|
27
30
|
revealed = false;
|
|
28
31
|
untrack(onloading);
|
|
29
|
-
function fail(message = "
|
|
32
|
+
function fail(message = $t("richtext.preparedPageLoadError")) {
|
|
30
33
|
if (signal.aborted) return;
|
|
31
34
|
controller.abort();
|
|
32
35
|
clearTimeout(timeout);
|
|
33
36
|
loading = false;
|
|
34
37
|
onerror(message);
|
|
35
38
|
}
|
|
36
|
-
const timeout = setTimeout(() => fail("
|
|
39
|
+
const timeout = setTimeout(() => fail($t("richtext.previewTimeout")), 20000);
|
|
37
40
|
function wait(ms: number) {
|
|
38
41
|
return new Promise<void>(resolve => {
|
|
39
42
|
const finish = () => { clearTimeout(timer); signal.removeEventListener("abort", finish); resolve(); };
|
|
@@ -57,11 +60,13 @@
|
|
|
57
60
|
onload();
|
|
58
61
|
await tick();
|
|
59
62
|
if (signal.aborted || iframe !== frame) return;
|
|
63
|
+
stopMediaLabels?.();
|
|
64
|
+
stopMediaLabels = subscribeMediaLabels(doc.body);
|
|
60
65
|
clearTimeout(timeout);
|
|
61
66
|
loading = false;
|
|
62
67
|
revealed = true;
|
|
63
68
|
} catch (cause) {
|
|
64
|
-
fail(cause instanceof Error ? cause.message : "
|
|
69
|
+
fail(cause instanceof Error ? cause.message : $t("richtext.previewUnavailable"));
|
|
65
70
|
}
|
|
66
71
|
}
|
|
67
72
|
const handlers = { ready, fail: () => fail() };
|
|
@@ -69,9 +74,9 @@
|
|
|
69
74
|
else fetchPreview(target, signal).then(srcdoc => {
|
|
70
75
|
if (!signal.aborted) source = { srcdoc, ...handlers };
|
|
71
76
|
}).catch(cause => {
|
|
72
|
-
fail(cause instanceof Error ? cause.message : "
|
|
77
|
+
fail(cause instanceof Error ? cause.message : $t("richtext.previewUnavailable"));
|
|
73
78
|
});
|
|
74
|
-
return () => { controller.abort(); clearTimeout(timeout); };
|
|
79
|
+
return () => { stopMediaLabels?.(); controller.abort(); clearTimeout(timeout); };
|
|
75
80
|
});
|
|
76
81
|
</script>
|
|
77
82
|
|
|
@@ -82,6 +87,6 @@
|
|
|
82
87
|
{/key}
|
|
83
88
|
{/if}
|
|
84
89
|
{#if loading}
|
|
85
|
-
<div class="preview-loading" role="status"><span class="preview-spinner" aria-hidden="true"></span>
|
|
90
|
+
<div class="preview-loading" role="status"><span class="preview-spinner" aria-hidden="true"></span>{$t("richtext.loadingPreview")}</div>
|
|
86
91
|
{/if}
|
|
87
92
|
</div>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { t } from "../i18n";
|
|
2
3
|
let { tab = $bindable("content") }: { tab?: "content" | "seo" } = $props();
|
|
3
4
|
function onkey(event: KeyboardEvent) {
|
|
4
5
|
if (!["ArrowLeft", "ArrowRight", "Home", "End"].includes(event.key)) return;
|
|
@@ -8,8 +9,8 @@
|
|
|
8
9
|
}
|
|
9
10
|
</script>
|
|
10
11
|
|
|
11
|
-
<div class="panel-tabs" role="tablist" aria-label="
|
|
12
|
+
<div class="panel-tabs" role="tablist" aria-label={$t("fields.properties")}>
|
|
12
13
|
{#each ["content", "seo"] as value}
|
|
13
|
-
<button type="button" role="tab" id={`properties-tab-${value}`} aria-controls={`properties-${value}`} aria-selected={tab === value} tabindex={tab === value ? 0 : -1} data-tab={value} onclick={() => (tab = value as "content" | "seo")} onkeydown={onkey}>{value === "content" ? "
|
|
14
|
+
<button type="button" role="tab" id={`properties-tab-${value}`} aria-controls={`properties-${value}`} aria-selected={tab === value} tabindex={tab === value ? 0 : -1} data-tab={value} onclick={() => (tab = value as "content" | "seo")} onkeydown={onkey}>{$t(value === "content" ? "fields.content" : "fields.seo")}</button>
|
|
14
15
|
{/each}
|
|
15
16
|
</div>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { t } from "../i18n";
|
|
2
3
|
import Icon from "./Icon.svelte";
|
|
3
4
|
import type { PublishReview } from "./github-backend";
|
|
4
5
|
let { review, loading, busy, error, message, onrefresh, onconfirm, onclose }: {
|
|
@@ -16,31 +17,31 @@
|
|
|
16
17
|
|
|
17
18
|
<dialog bind:this={dialog} class="rigel rigel-modal-backdrop publish-dialog" aria-labelledby="publish-title" aria-describedby="publish-description" oncancel={(event) => { event.preventDefault(); if (!busy) onclose(); }}>
|
|
18
19
|
<form class="rigel-modal" onsubmit={(event) => { event.preventDefault(); onconfirm(); }} aria-busy={loading || busy}>
|
|
19
|
-
<div class="publish-head"><h2 id="publish-title">
|
|
20
|
-
<p id="publish-description">
|
|
21
|
-
{#if loading}<p role="status">
|
|
20
|
+
<div class="publish-head"><h2 id="publish-title">{$t("connection.publishTitle")}</h2><button type="button" class="icon-btn" aria-label={$t("connection.closePublishReview")} disabled={busy} onclick={onclose}><Icon name="x" size={16} /></button></div>
|
|
21
|
+
<p id="publish-description">{#each $t("connection.publishDescription", { scope: "{scope}" }).split("{scope}") as part, index}{#if index}<strong>{$t("connection.publishScope")}</strong>{/if}{part}{/each}</p>
|
|
22
|
+
{#if loading}<p role="status">{$t("connection.loadingReview")}</p>{/if}
|
|
22
23
|
{#if review}
|
|
23
|
-
<p><strong>{review.repo}</strong><br
|
|
24
|
+
<p><strong>{review.repo}</strong><br />{#each $t("connection.publishBranches", { branch: "{branch}", publishBranch: "{publishBranch}" }).split(/(\{branch\}|\{publishBranch\})/) as part}{#if part === "{branch}"}<span class="publish-branch">{review.branch}</span>{:else if part === "{publishBranch}"}<span class="publish-branch">{review.publishBranch}</span>{:else}{part}{/if}{/each}</p>
|
|
24
25
|
{#if review.needsSync}
|
|
25
|
-
<p>
|
|
26
|
-
<p>
|
|
26
|
+
<p>{$t("connection.syncDescription")}</p>
|
|
27
|
+
<p>{$t("connection.treeDifferences")}</p>
|
|
27
28
|
{/if}
|
|
28
|
-
<p id="publish-file-count"><strong>{
|
|
29
|
+
<p id="publish-file-count"><strong>{$t("connection.fileCount", { count: review.files.length })}</strong></p>
|
|
29
30
|
{#if review.files.length}
|
|
30
|
-
<ul class="publish-files" tabindex="0" aria-label="
|
|
31
|
+
<ul class="publish-files" tabindex="0" aria-label={$t("connection.allChangedFiles")} aria-describedby="publish-file-count">
|
|
31
32
|
{#each review.files as file (file.filename)}
|
|
32
|
-
<li><span class="publish-file-status">{file.status}</span><span>{file.filename}{#if file.previous_filename}<small>
|
|
33
|
+
<li><span class="publish-file-status">{$t(`connection.fileStatus.${file.status}`)}</span><span>{file.filename}{#if file.previous_filename}<small>{$t("connection.previousFilename", { filename: file.previous_filename })}</small>{/if}</span></li>
|
|
33
34
|
{/each}
|
|
34
35
|
</ul>
|
|
35
|
-
{:else}<p>
|
|
36
|
-
<p class="hint">
|
|
36
|
+
{:else}<p>{$t("connection.noSavedChanges")}</p>{/if}
|
|
37
|
+
<p class="hint">{$t("connection.publishSafety")}</p>
|
|
37
38
|
{/if}
|
|
38
39
|
{#if error}<p class="error" role="alert">{error}</p>{/if}
|
|
39
40
|
{#if message}<p role="status">{message}</p>{/if}
|
|
40
41
|
<div class="publish-actions">
|
|
41
|
-
<button type="button" bind:this={cancel} class="outlined" disabled={busy} onclick={onclose}>{message ? "
|
|
42
|
-
{#if !message}<button type="button" class="outlined" disabled={loading || busy} onclick={onrefresh}>
|
|
43
|
-
{#if review && !message}<button id="confirm-publish" type="submit" class="primary" disabled={loading || busy || !review.files.length}>{busy ? "
|
|
42
|
+
<button type="button" bind:this={cancel} class="outlined" disabled={busy} onclick={onclose}>{message ? $t("connection.close") : $t("connection.cancel")}</button>
|
|
43
|
+
{#if !message}<button type="button" class="outlined" disabled={loading || busy} onclick={onrefresh}>{$t("connection.reviewAgain")}</button>{/if}
|
|
44
|
+
{#if review && !message}<button id="confirm-publish" type="submit" class="primary" disabled={loading || busy || !review.files.length}>{busy ? $t("connection.working") : review.needsSync ? $t("connection.syncDraftsFirst") : $t("connection.confirmPublish")}</button>{/if}
|
|
44
45
|
</div>
|
|
45
46
|
</form>
|
|
46
47
|
</dialog>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { t } from "../i18n";
|
|
2
3
|
import type { ReferenceOption } from "../../manifest/references";
|
|
3
4
|
import Select from "./Select.svelte";
|
|
4
5
|
|
|
@@ -13,5 +14,5 @@
|
|
|
13
14
|
const missing = $derived(selected.filter(id => !options.some(option => option.value === id)));
|
|
14
15
|
</script>
|
|
15
16
|
|
|
16
|
-
<Select {label} {multiple} value={multiple ? selected : selected[0] ?? ""} options={[...(!multiple ? [{ value: "", label:
|
|
17
|
-
{#if !options.length}<p class="hint">
|
|
17
|
+
<Select {label} {multiple} value={multiple ? selected : selected[0] ?? ""} options={[...(!multiple ? [{ value: "", label: $t("fields.selectLabel", { label: label.toLowerCase() }) }] : []), ...options, ...missing.map(id => ({ value: id, label: $t("fields.unavailableReference", { id }) }))]} {onchange} />
|
|
18
|
+
{#if !options.length}<p class="hint">{$t("fields.noEntries")}</p>{/if}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { t } from "../i18n";
|
|
2
3
|
import ImagePicker from "./ImagePicker.svelte";
|
|
3
4
|
import MarkdownEditor from "./MarkdownEditor.svelte";
|
|
4
5
|
import SchemaField from "./SchemaField.svelte";
|
|
@@ -35,7 +36,8 @@
|
|
|
35
36
|
const itemSchema = $derived(field.type === "array" ? itemFields(schema, field) : []);
|
|
36
37
|
const objectSchema = $derived(field.type === "object" ? objectFields(schema, field) : []);
|
|
37
38
|
const options = $derived(field.type === "reference" ? referenceOptions(documents, field.to ?? []) : []);
|
|
38
|
-
const
|
|
39
|
+
const fieldLabel = $derived(field.labelKey ? $t(field.labelKey) : field.label);
|
|
40
|
+
const label = $derived(`${fieldLabel}${field.required ? " *" : ""}${devMode ? ` (${field.key})` : ""}`);
|
|
39
41
|
|
|
40
42
|
function normalizeColor(input: string): string {
|
|
41
43
|
const hex = input.trim().replace(/^#/, "");
|
|
@@ -60,8 +62,8 @@
|
|
|
60
62
|
<div class="schema-field">
|
|
61
63
|
<label for={id}><span>{label}</span></label>
|
|
62
64
|
<div class="schema-color">
|
|
63
|
-
<input type="color" aria-label={
|
|
64
|
-
<input id={id} type="text" aria-label={
|
|
65
|
+
<input type="color" aria-label={$t("fields.colorPicker", { label: fieldLabel })} value={/^#[0-9a-f]{6}$/.test(color) ? color : "#000000"} oninput={(event) => onvalue(event.currentTarget.value)} />
|
|
66
|
+
<input id={id} type="text" aria-label={$t("fields.colorHexValue", { label: fieldLabel })} value={typeof value === "string" ? value : ""} placeholder="#000000" spellcheck={false} oninput={(event) => onvalue(event.currentTarget.value)} onblur={(event) => onvalue(normalizeColor(event.currentTarget.value))} />
|
|
65
67
|
</div>
|
|
66
68
|
</div>
|
|
67
69
|
{:else if field.type === "code"}
|
|
@@ -74,7 +76,7 @@
|
|
|
74
76
|
<label for={id}><span>{label}</span></label>
|
|
75
77
|
<div class="secret-input">
|
|
76
78
|
<input id={id} type={revealed ? "text" : "password"} value={typeof value === "string" ? value : ""} placeholder={field.placeholder ?? ""} autocomplete="off" spellcheck={false} oninput={(event) => onvalue(event.currentTarget.value)} />
|
|
77
|
-
<button type="button" aria-label={
|
|
79
|
+
<button type="button" aria-label={$t(revealed ? "fields.hideValue" : "fields.showValue", { label: fieldLabel })} aria-controls={id} aria-pressed={revealed} onclick={() => revealed = !revealed}><Icon name={revealed ? "eyeSlash" : "eye"} size={18} /></button>
|
|
78
80
|
</div>
|
|
79
81
|
</div>
|
|
80
82
|
{:else if field.type === "text"}
|
|
@@ -83,7 +85,7 @@
|
|
|
83
85
|
<textarea rows="3" value={typeof value === "string" ? value : ""} oninput={(event) => onvalue(event.currentTarget.value)}></textarea>
|
|
84
86
|
</label>
|
|
85
87
|
{:else if field.type === "markdown"}
|
|
86
|
-
<MarkdownEditor label={
|
|
88
|
+
<MarkdownEditor label={fieldLabel} value={typeof value === "string" ? value : ""} oninput={(next) => onvalue(next)} />
|
|
87
89
|
{:else if field.type === "image"}
|
|
88
90
|
<div class="schema-field">
|
|
89
91
|
<span class="schema-label">{label}</span>
|
|
@@ -102,7 +104,7 @@
|
|
|
102
104
|
{:else if field.type === "date"}
|
|
103
105
|
<DatePicker {label} value={typeof value === "string" ? value : ""} required={field.required} onchange={next => onvalue(next || undefined)} />
|
|
104
106
|
{:else if field.type === "reference"}
|
|
105
|
-
<Select {label} value={value && typeof value === "object" ? String((value as Record<string, unknown>)._ref ?? "") : ""} options={[{ value: "", label: "
|
|
107
|
+
<Select {label} value={value && typeof value === "object" ? String((value as Record<string, unknown>)._ref ?? "") : ""} options={[{ value: "", label: $t("fields.choose") }, ...options.map(option => ({ value: option.id, label: String(option.data.title ?? option.data.name ?? option.slug) }))]} onchange={next => onvalue(next ? { _ref: String(next) } : undefined)} />
|
|
106
108
|
{:else if field.type === "object"}
|
|
107
109
|
<fieldset class="schema-object">
|
|
108
110
|
<legend>{label}</legend>
|
|
@@ -124,13 +126,13 @@
|
|
|
124
126
|
{#each itemList as item, index (`${base}.${item && typeof item === "object" ? (item as Record<string, unknown>)._key ?? index : index}`)}
|
|
125
127
|
<div class="schema-item" data-schema-path={`${base}.${item && typeof item === "object" ? (item as Record<string, unknown>)._key ?? index : index}`}>
|
|
126
128
|
<div class="schema-item-head">
|
|
127
|
-
<span>
|
|
129
|
+
<span>{$t("fields.itemNumber", { number: index + 1 })}</span>
|
|
128
130
|
<span class="schema-key">{(item && typeof item === "object" ? (item as Record<string, unknown>)._key : "") ?? ""}</span>
|
|
129
131
|
<div class="schema-item-actions">
|
|
130
|
-
<button type="button" aria-label={
|
|
131
|
-
<button type="button" aria-label={
|
|
132
|
-
<button type="button" aria-label={
|
|
133
|
-
<button type="button" aria-label={
|
|
132
|
+
<button type="button" aria-label={$t("fields.moveItemUp", { label: fieldLabel, number: index + 1 })} data-tooltip={$t("fields.moveUp")} disabled={index === 0} onclick={() => onvalue(moveItem(itemList, index, -1))}>↑</button>
|
|
133
|
+
<button type="button" aria-label={$t("fields.moveItemDown", { label: fieldLabel, number: index + 1 })} data-tooltip={$t("fields.moveDown")} disabled={index === itemList.length - 1} onclick={() => onvalue(moveItem(itemList, index, 1))}>↓</button>
|
|
134
|
+
<button type="button" aria-label={$t("fields.duplicateItem", { label: fieldLabel, number: index + 1 })} data-tooltip={$t("fields.duplicate")} onclick={() => onvalue(duplicateItem(itemList, index))}>⧉</button>
|
|
135
|
+
<button type="button" aria-label={$t("fields.removeItem", { label: fieldLabel, number: index + 1 })} data-tooltip={$t("fields.remove")} onclick={() => onvalue(itemList.filter((_, position) => position !== index))}>×</button>
|
|
134
136
|
</div>
|
|
135
137
|
</div>
|
|
136
138
|
{#if itemSchema.length}
|
|
@@ -146,11 +148,11 @@
|
|
|
146
148
|
/>
|
|
147
149
|
{/each}
|
|
148
150
|
{:else}
|
|
149
|
-
<input type="text" aria-label={
|
|
151
|
+
<input type="text" aria-label={$t("fields.itemLabel", { label: fieldLabel, number: index + 1 })} value={typeof item === "string" ? item : ""} oninput={(event) => setItem(index, event.currentTarget.value)} />
|
|
150
152
|
{/if}
|
|
151
153
|
</div>
|
|
152
154
|
{/each}
|
|
153
|
-
<button type="button" class="outlined" onclick={() => onvalue([...itemList, itemSchema.length ? blankItem(itemSchema) : field.items?.some((item) => item.type === "reference") ? { _key: newKey(), _ref: "" } : ""])}>
|
|
155
|
+
<button type="button" class="outlined" onclick={() => onvalue([...itemList, itemSchema.length ? blankItem(itemSchema) : field.items?.some((item) => item.type === "reference") ? { _key: newKey(), _ref: "" } : ""])}>{$t("fields.addItem")}</button>
|
|
154
156
|
</div>
|
|
155
157
|
{/if}
|
|
156
158
|
</div>
|