@rigel-cms/rigel 1.4.0-rc.2 → 1.4.0-rc.3
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 -2
- package/docs/README.md +28 -0
- package/docs/authors.md +133 -0
- package/docs/content-locales.md +1 -1
- package/docs/content-model.md +131 -0
- package/docs/getting-started.md +139 -0
- package/docs/github.md +112 -0
- package/docs/media.md +75 -0
- package/docs/menus.md +95 -0
- package/docs/preview-hosts.md +78 -0
- package/docs/preview.md +100 -0
- package/docs/seo.md +87 -0
- package/docs/sidebar.md +97 -0
- package/package.json +2 -3
- package/src/article-render.ts +12 -5
- package/src/editor/App.svelte +36 -15
- package/src/editor/i18n/de/fields.json +2 -0
- package/src/editor/i18n/de/shell.json +2 -1
- package/src/editor/i18n/en/fields.json +2 -0
- package/src/editor/i18n/en/shell.json +2 -1
- package/src/editor/i18n/es/fields.json +2 -0
- package/src/editor/i18n/es/shell.json +2 -1
- package/src/editor/i18n/fr/fields.json +2 -0
- package/src/editor/i18n/fr/shell.json +2 -1
- package/src/editor/i18n/it/fields.json +2 -0
- package/src/editor/i18n/it/shell.json +2 -1
- package/src/editor/i18n/ja/fields.json +2 -0
- package/src/editor/i18n/ja/shell.json +2 -1
- package/src/editor/i18n/nl/fields.json +2 -0
- package/src/editor/i18n/nl/shell.json +2 -1
- package/src/editor/i18n/ru/fields.json +2 -0
- package/src/editor/i18n/ru/shell.json +2 -1
- package/src/editor/i18n/zh/fields.json +2 -0
- package/src/editor/i18n/zh/shell.json +2 -1
- package/src/editor/lib/BlockEditor.svelte +4 -3
- package/src/editor/lib/CollectionPanel.svelte +10 -2
- package/src/editor/lib/MarkdownEditor.svelte +38 -5
- package/src/editor/lib/PropertiesTabs.svelte +8 -1
- package/src/editor/lib/SchemaField.svelte +16 -2
- package/src/editor/lib/SchemaPanel.svelte +9 -3
- package/src/editor/lib/VisualEditor.svelte +64 -13
- package/src/editor/lib/markdown-blocks.ts +69 -0
- package/src/editor/lib/media-library.ts +2 -1
- package/src/editor/styles.css +7 -1
- package/src/schema/types.ts +1 -0
- package/src/schema/validate.ts +1 -0
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
import { slugify } from "../../slugs";
|
|
24
24
|
|
|
25
25
|
let {
|
|
26
|
-
schema, docType, values, docId, selected, creating, pending, documents, focusToken, devMode, onchange, onselect, oneditdocument,
|
|
26
|
+
schema, docType, values, docId, selected, creating, pending, documents, focusToken, devMode, inlineFields = [], oneditfield = () => {}, onchange, onselect, oneditdocument,
|
|
27
27
|
}: {
|
|
28
28
|
schema: Schema;
|
|
29
29
|
docType: DocumentDef;
|
|
@@ -35,6 +35,8 @@
|
|
|
35
35
|
documents: ContentDocument[];
|
|
36
36
|
focusToken: number;
|
|
37
37
|
devMode: boolean;
|
|
38
|
+
inlineFields?: string[];
|
|
39
|
+
oneditfield?: (path: string) => void;
|
|
38
40
|
onchange: (next: Record<string, unknown>) => void;
|
|
39
41
|
onselect: (next: SelectedField | null) => void;
|
|
40
42
|
oneditdocument: (id: string) => void;
|
|
@@ -176,7 +178,7 @@
|
|
|
176
178
|
{/if}
|
|
177
179
|
{#each docFields.filter(field => docType.shared || !isSeoField(field.key)) as field (field.key)}
|
|
178
180
|
<div data-schema-path={field.key}>
|
|
179
|
-
<SchemaField {schema} {documents} {devMode} {field} base={field.key} value={values[field.key]} onvalue={(next) => onchange(replaceAt(values, field.key, next))} />
|
|
181
|
+
<SchemaField {schema} {documents} {devMode} {inlineFields} {oneditfield} {field} base={field.key} value={values[field.key]} onvalue={(next) => onchange(replaceAt(values, field.key, next))} />
|
|
180
182
|
</div>
|
|
181
183
|
{/each}
|
|
182
184
|
</section>
|
|
@@ -223,6 +225,8 @@
|
|
|
223
225
|
{schema}
|
|
224
226
|
{documents}
|
|
225
227
|
{devMode}
|
|
228
|
+
{inlineFields}
|
|
229
|
+
{oneditfield}
|
|
226
230
|
{field}
|
|
227
231
|
base={`sections.${selected.sectionKey}.${field.key}`}
|
|
228
232
|
value={sections.find((section) => section._key === selected?.sectionKey)?.[field.key]}
|
|
@@ -239,6 +243,8 @@
|
|
|
239
243
|
{schema}
|
|
240
244
|
{documents}
|
|
241
245
|
{devMode}
|
|
246
|
+
{inlineFields}
|
|
247
|
+
{oneditfield}
|
|
242
248
|
field={selectedField}
|
|
243
249
|
base={selected.path}
|
|
244
250
|
value={selectedValue}
|
|
@@ -255,7 +261,7 @@
|
|
|
255
261
|
<h3>{$t("fields.seo")}</h3>
|
|
256
262
|
{#each seoFields as field (field.key)}
|
|
257
263
|
<div data-schema-path={field.key}>
|
|
258
|
-
<SchemaField {schema} {documents} {devMode} {field} base={field.key} value={values[field.key]} onvalue={(next) => onchange(replaceAt(values, field.key, next))} />
|
|
264
|
+
<SchemaField {schema} {documents} {devMode} {inlineFields} {oneditfield} {field} base={field.key} value={values[field.key]} onvalue={(next) => onchange(replaceAt(values, field.key, next))} />
|
|
259
265
|
<SeoGuidance field={field.key} value={values[field.key]} />
|
|
260
266
|
</div>
|
|
261
267
|
{/each}
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
import { mount, unmount, onDestroy, tick, type Snippet } from "svelte";
|
|
4
4
|
import BlockEditor from "./BlockEditor.svelte";
|
|
5
5
|
import MarkdownEditor from "./MarkdownEditor.svelte";
|
|
6
|
+
import { markdownToBody } from "./markdown-blocks";
|
|
7
|
+
import { articleMarkdown } from "../../article-render";
|
|
6
8
|
import { tooltipRoot } from "./tooltip";
|
|
7
9
|
import editorStyles from "../styles.css?inline";
|
|
8
10
|
import type { ArticleBody } from "../../article-body";
|
|
@@ -27,7 +29,7 @@
|
|
|
27
29
|
previewPath: string | null;
|
|
28
30
|
websitePath?: string | null;
|
|
29
31
|
body?: "" | import("../../article-body").ArticleBody;
|
|
30
|
-
properties?: Snippet<[SelectedField | null, number, boolean, () => void, boolean]>;
|
|
32
|
+
properties?: Snippet<[SelectedField | null, number, boolean, () => void, boolean, string[], (path: string) => void]>;
|
|
31
33
|
bodyInline?: boolean;
|
|
32
34
|
onbody?: (body: ArticleBody) => void;
|
|
33
35
|
creating: boolean;
|
|
@@ -65,6 +67,7 @@
|
|
|
65
67
|
let highlighted: HTMLElement | null = null;
|
|
66
68
|
let hovered: HTMLElement | null = null;
|
|
67
69
|
let structureSignature = "";
|
|
70
|
+
let richtextPaths = $state<string[]>([]);
|
|
68
71
|
let inline = $state<{ path: string; value: string; original: string; left: number; top: number; width: number; height: number; fontSize: string } | null>(null);
|
|
69
72
|
let inlineInput: HTMLInputElement | undefined = $state();
|
|
70
73
|
const documentList = $derived(Object.entries(documents).map(([id, data]) => ({
|
|
@@ -164,6 +167,21 @@
|
|
|
164
167
|
applyStructure();
|
|
165
168
|
}
|
|
166
169
|
applySelection();
|
|
170
|
+
syncInlineRichText();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function inlineMarkdownPath(path: string) {
|
|
174
|
+
const def = schema && docType ? fieldForPath(schema, docType, values, path) : null;
|
|
175
|
+
return def?.type === "markdown" && !def.sidebarOnly;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function syncInlineRichText() {
|
|
179
|
+
const doc = docElement();
|
|
180
|
+
const paths = !bodyInline || !doc || !schema || !docType || mode !== "edit" ? [] : [...new Set([...doc.querySelectorAll<HTMLElement>("[data-rigel-richtext]")]
|
|
181
|
+
.filter(element => (element.getAttribute("data-rigel-doc") ?? docId) === docId)
|
|
182
|
+
.map(element => element.getAttribute("data-rigel-richtext")!)
|
|
183
|
+
.filter(path => path !== "$body" && inlineMarkdownPath(path)))];
|
|
184
|
+
if (paths.length !== richtextPaths.length || paths.some((path, index) => path !== richtextPaths[index])) richtextPaths = paths;
|
|
167
185
|
}
|
|
168
186
|
|
|
169
187
|
function fieldPathFrom(element: HTMLElement): { path: string; doc: string } | null {
|
|
@@ -201,6 +219,7 @@
|
|
|
201
219
|
editingBody = null; bodyContents = [];
|
|
202
220
|
patch();
|
|
203
221
|
}
|
|
222
|
+
const regionAttributes = ["class", "style", "role", "aria-label", "aria-multiline", "aria-keyshortcuts", "contenteditable", "data-lexical-editor", "spellcheck", "tabindex", "translate"];
|
|
204
223
|
async function startBodyEdit(element: HTMLElement, event?: MouseEvent) {
|
|
205
224
|
if (!body) return;
|
|
206
225
|
if (bodyEditor) { element.focus(); return; }
|
|
@@ -217,7 +236,7 @@
|
|
|
217
236
|
if (editorStyle.textContent !== editorStyles) editorStyle.textContent = editorStyles;
|
|
218
237
|
clearHover(); clearHighlight();
|
|
219
238
|
editingBody = element; bodyContents = [...element.childNodes];
|
|
220
|
-
bodyAttributes =
|
|
239
|
+
bodyAttributes = regionAttributes.map(name => [name, element.getAttribute(name)]);
|
|
221
240
|
const pre = doc.createElement("pre"), code = doc.createElement("code");
|
|
222
241
|
pre.append(code); pre.style.cssText = "position:absolute;visibility:hidden"; element.append(pre);
|
|
223
242
|
for (const [sample, properties] of [[pre, ["background-color", "padding", "margin", "border", "border-radius"]], [code, ["color", "font-family", "font-size", "font-weight", "line-height", "letter-spacing"]]] as const) {
|
|
@@ -251,9 +270,9 @@
|
|
|
251
270
|
}
|
|
252
271
|
function startMarkdownEdit(element: HTMLElement) {
|
|
253
272
|
const field = fieldPathFrom(element);
|
|
254
|
-
if (!field || field.doc !== docId || !schema || !docType) return false;
|
|
273
|
+
if (!bodyInline || !field || field.doc !== docId || !schema || !docType) return false;
|
|
255
274
|
const def = fieldForPath(schema, docType, values, field.path);
|
|
256
|
-
if (def?.type !== "markdown") return false;
|
|
275
|
+
if (def?.type !== "markdown" || def.sidebarOnly) return false;
|
|
257
276
|
stopBodyEdit();
|
|
258
277
|
const doc = element.ownerDocument;
|
|
259
278
|
let editorStyle = doc.querySelector<HTMLStyleElement>('style[data-rigel-editor-styles]');
|
|
@@ -262,29 +281,59 @@
|
|
|
262
281
|
clearHover(); clearHighlight();
|
|
263
282
|
editingBody = element; bodyContents = [...element.childNodes];
|
|
264
283
|
const scope = doc.createElement("div");
|
|
265
|
-
bodyAttributes =
|
|
284
|
+
bodyAttributes = regionAttributes.map(name => [name, element.getAttribute(name)]);
|
|
266
285
|
const syncTheme = () => { scope.dataset.theme = document.documentElement.dataset.theme; scope.dataset.contrast = document.documentElement.dataset.contrast; };
|
|
267
286
|
syncTheme();
|
|
268
287
|
const host = doc.createElement("div"); host.className = "rigel rigel-inline";
|
|
269
288
|
scope.append(host); doc.body.append(scope); bodyScope = scope;
|
|
270
289
|
removeTooltips = tooltipRoot(host).destroy;
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
290
|
+
const label = def.labelKey ? $t(def.labelKey) : def.label;
|
|
291
|
+
const source = String(anchorValue(values, field.path).value ?? "");
|
|
292
|
+
const blocks = markdownToBody(source);
|
|
293
|
+
if (blocks) {
|
|
294
|
+
const editor = { body: blocks, markdown: source };
|
|
295
|
+
bodyEditor = mount(BlockEditor, { target: host, props: {
|
|
296
|
+
editableRoot: element,
|
|
297
|
+
label,
|
|
298
|
+
get value() { return editor.body as ArticleBody; },
|
|
299
|
+
oninput: (next: ArticleBody) => {
|
|
300
|
+
editor.body = next;
|
|
301
|
+
const markdown = articleMarkdown(next);
|
|
302
|
+
if (markdown === editor.markdown) return;
|
|
303
|
+
editor.markdown = markdown;
|
|
304
|
+
onchange(replaceAt(values, field.path, markdown));
|
|
305
|
+
},
|
|
306
|
+
} });
|
|
307
|
+
} else {
|
|
308
|
+
bodyEditor = mount(MarkdownEditor, { target: host, props: {
|
|
309
|
+
editableRoot: element,
|
|
310
|
+
label,
|
|
311
|
+
get value() { return String(anchorValue(values, field.path).value ?? ""); },
|
|
312
|
+
oninput: (next: string) => onchange(replaceAt(values, field.path, next)),
|
|
313
|
+
} });
|
|
314
|
+
}
|
|
277
315
|
bodyThemeObserver = new MutationObserver(syncTheme);
|
|
278
316
|
bodyThemeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme", "data-contrast"] });
|
|
279
317
|
void tick().then(() => (element.isContentEditable ? element : host.querySelector<HTMLTextAreaElement>("textarea"))?.focus());
|
|
280
318
|
return true;
|
|
281
319
|
}
|
|
282
320
|
|
|
321
|
+
function editField(path: string) {
|
|
322
|
+
const doc = docElement();
|
|
323
|
+
if (!doc) return;
|
|
324
|
+
const element = [...doc.querySelectorAll<HTMLElement>(anchorSelector(path))]
|
|
325
|
+
.find(node => (node.getAttribute("data-rigel-doc") ?? docId) === docId && node.hasAttribute("data-rigel-richtext"));
|
|
326
|
+
if (!element) return;
|
|
327
|
+
element.scrollIntoView({ block: "center" });
|
|
328
|
+
if (editingBody === element) { element.focus(); return; }
|
|
329
|
+
startMarkdownEdit(element);
|
|
330
|
+
}
|
|
331
|
+
|
|
283
332
|
function focusBody() {
|
|
284
333
|
const target = docElement()?.querySelector<HTMLElement>('[data-rigel-richtext="$body"]');
|
|
285
334
|
if (inlineBodyEnabled && target) { target.scrollIntoView({ block: "center" }); void startBodyEdit(target); }
|
|
286
335
|
}
|
|
287
|
-
$effect(() => { if ((!schema && !inlineBodyEnabled) || mode !== "edit") stopBodyEdit(); });
|
|
336
|
+
$effect(() => { if (!bodyInline || (!schema && !inlineBodyEnabled) || mode !== "edit") stopBodyEdit(); });
|
|
288
337
|
onDestroy(stopBodyEdit);
|
|
289
338
|
|
|
290
339
|
function onClick(event: MouseEvent) {
|
|
@@ -516,7 +565,7 @@
|
|
|
516
565
|
<div class="properties-divider" role="separator" tabindex="0" aria-label={$t("richtext.propertiesPanelWidth")} aria-orientation="vertical" aria-valuemin="280" aria-valuemax={panelMax} aria-valuenow={panelWidth} aria-valuetext={$t("richtext.panelPixels", { width: Math.round(panelWidth) })} onpointerdown={startResize} onpointermove={moveResize} onpointerup={endResize} onpointercancel={endResize} onlostpointercapture={endResize} onkeydown={keyResize}></div>
|
|
517
566
|
{/if}
|
|
518
567
|
{#if panelOpen && properties}
|
|
519
|
-
{@render properties(selected, focusToken, inlineBodyEnabled || inlineBodyLoading, focusBody, inlineBodyLoading)}
|
|
568
|
+
{@render properties(selected, focusToken, inlineBodyEnabled || inlineBodyLoading, focusBody, inlineBodyLoading, richtextPaths, editField)}
|
|
520
569
|
{:else if panelOpen && schema && docType}
|
|
521
570
|
<SchemaPanel
|
|
522
571
|
{schema}
|
|
@@ -529,6 +578,8 @@
|
|
|
529
578
|
{focusToken}
|
|
530
579
|
{devMode}
|
|
531
580
|
documents={documentList}
|
|
581
|
+
inlineFields={richtextPaths}
|
|
582
|
+
oneditfield={editField}
|
|
532
583
|
onchange={onchange}
|
|
533
584
|
{oneditdocument}
|
|
534
585
|
onselect={async (next) => { selected = next; focusToken++; await tick(); if (selected?.docId === next?.docId && selected?.sectionKey === next?.sectionKey) applySelection(true); }}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import MarkdownIt from "markdown-it";
|
|
2
|
+
import type { Mark, Node as MarkdownNode } from "prosemirror-model";
|
|
3
|
+
import { parseVisualMarkdown } from "./rich-markdown";
|
|
4
|
+
import { articleMarkdown } from "../../article-render";
|
|
5
|
+
import { validateBody, type ArticleBody, type ArticleNode } from "../../article-body";
|
|
6
|
+
|
|
7
|
+
const renderer = new MarkdownIt({ html: true });
|
|
8
|
+
|
|
9
|
+
const FORMATS: Record<string, number> = { strong: 1, em: 2, code: 16 };
|
|
10
|
+
|
|
11
|
+
function textFormat(marks: readonly Mark[]): number {
|
|
12
|
+
return marks.reduce((bits, mark) => bits | (FORMATS[mark.type.name] ?? 0), 0);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function linkHref(marks: readonly Mark[]): string | null {
|
|
16
|
+
const link = marks.find(mark => mark.type.name === "link");
|
|
17
|
+
return link && typeof link.attrs.href === "string" ? link.attrs.href : null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function textNode(text: string, format: number): ArticleNode {
|
|
21
|
+
return { type: "text", version: 1, text, format, detail: 0, mode: "normal", style: "" };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function inline(nodes: readonly MarkdownNode[]): ArticleNode[] {
|
|
25
|
+
const result: ArticleNode[] = [];
|
|
26
|
+
const parts = nodes.map(node => ({ node, href: node.type.name === "text" ? linkHref(node.marks) : null }));
|
|
27
|
+
for (let index = 0; index < parts.length; index++) {
|
|
28
|
+
const { node, href } = parts[index];
|
|
29
|
+
if (node.type.name === "hard_break") { result.push({ type: "linebreak", version: 1 }); continue; }
|
|
30
|
+
if (node.type.name !== "text") return [];
|
|
31
|
+
const children = [textNode(node.text!, textFormat(node.marks))];
|
|
32
|
+
while (index + 1 < parts.length && parts[index + 1].href === href && parts[index + 1].node.type.name === "text") {
|
|
33
|
+
index++;
|
|
34
|
+
children.push(textNode(parts[index].node.text!, textFormat(parts[index].node.marks)));
|
|
35
|
+
}
|
|
36
|
+
if (href) result.push({ type: "link", version: 1, url: href, children });
|
|
37
|
+
else result.push(...children);
|
|
38
|
+
}
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function block(node: MarkdownNode): ArticleNode | null {
|
|
43
|
+
const children = () => [...node.content.content].map(block).filter((child): child is ArticleNode => !!child);
|
|
44
|
+
const inlineChildren = () => inline([...node.content.content]);
|
|
45
|
+
if (node.type.name === "paragraph") return { type: "paragraph", version: 1, children: inlineChildren(), direction: null, format: "", indent: 0, textFormat: 0, textStyle: "" };
|
|
46
|
+
if (node.type.name === "heading") return { type: "heading", version: 1, tag: `h${node.attrs.level}`, children: inlineChildren(), direction: null, format: "", indent: 0 };
|
|
47
|
+
if (node.type.name === "blockquote") return { type: "quote", version: 1, children: children() };
|
|
48
|
+
if (node.type.name === "bullet_list") return { type: "list", version: 1, listType: "bullet", tag: "ul", start: 1, children: children() };
|
|
49
|
+
if (node.type.name === "ordered_list") return { type: "list", version: 1, listType: "number", tag: "ol", start: Number(node.attrs.order) || 1, children: children() };
|
|
50
|
+
if (node.type.name === "list_item") return { type: "listitem", version: 1, value: 1, children: children() };
|
|
51
|
+
if (node.type.name === "code_block") return { type: "code", version: 1, language: null, children: node.textContent ? [textNode(node.textContent, 0)] : [] };
|
|
52
|
+
if (node.type.name === "horizontal_rule") return { type: "divider", version: 1 };
|
|
53
|
+
if (node.type.name === "image") return { type: "image", version: 1, src: String(node.attrs.src ?? ""), alt: String(node.attrs.alt ?? "") };
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function markdownToBody(source: string): ArticleBody | null {
|
|
58
|
+
const doc = parseVisualMarkdown(source);
|
|
59
|
+
if (!doc) return null;
|
|
60
|
+
const blocks = [...doc.content.content].map(block);
|
|
61
|
+
if (blocks.some(item => !item)) return null;
|
|
62
|
+
const body = { root: { type: "root", version: 1, children: blocks as ArticleNode[], direction: null, format: "", indent: 0 } } as ArticleBody;
|
|
63
|
+
try {
|
|
64
|
+
validateBody(body);
|
|
65
|
+
} catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
return renderer.render(source).trimEnd() === renderer.render(articleMarkdown(body)).trimEnd() ? body : null;
|
|
69
|
+
}
|
|
@@ -33,7 +33,8 @@ export function modifiedMonth(value?: string): string {
|
|
|
33
33
|
|
|
34
34
|
const CURATED = new Set(['Images', 'Audio', 'Video', 'Documents', 'Spreadsheets', 'Archives']);
|
|
35
35
|
export function filterMedia(assets: MediaAsset[], search: string, type: string, month: string): MediaAsset[] {
|
|
36
|
-
|
|
36
|
+
const query = search.trim().toLowerCase();
|
|
37
|
+
return assets.filter(asset => [asset.name, assetFile(asset.url), asset.title, asset.description, asset.alt].some(value => value?.toLowerCase().includes(query))
|
|
37
38
|
&& (!type || asset.type === type || assetCategory(asset) === type) && (!month || modifiedMonth(asset.modified) === month));
|
|
38
39
|
}
|
|
39
40
|
function assetCategory(asset: MediaAsset): string {
|
package/src/editor/styles.css
CHANGED
|
@@ -1171,6 +1171,7 @@
|
|
|
1171
1171
|
.rigel .visual-error { margin: 1.5rem; font-size: 0.875rem; color: var(--text-2); max-width: 44ch; }
|
|
1172
1172
|
.rigel .schema-panel { border-left: 1px solid var(--border); overflow-y: auto; min-height: 0; padding: 1rem; background: var(--panel); }
|
|
1173
1173
|
.rigel .schema-panel section { border: 0; padding: 0 0 1rem; }
|
|
1174
|
+
.rigel .schema-panel :is([data-schema-path], [data-collection-field]) + :is([data-schema-path], [data-collection-field]) { margin-top: 1rem; }
|
|
1174
1175
|
.rigel .custom-select { min-width: 0; max-width: 100%; }
|
|
1175
1176
|
.rigel .content-language { position: relative; margin: -0.75rem 0.75rem 1.5rem; flex-shrink: 0; }
|
|
1176
1177
|
.rigel .content-language .select-label { margin-top: 0; }
|
|
@@ -1193,7 +1194,12 @@
|
|
|
1193
1194
|
.rigel.rigel-inline .markdown-link, .rigel .block-editor .markdown-link { position: fixed; inset: auto; width: 340px; max-width: calc(100vw - 16px); z-index: 121; }
|
|
1194
1195
|
.rigel.rigel-inline .block-markdown-view { position: fixed; inset: 12px; max-height: calc(100vh - 24px); overflow: auto; padding: 0.5rem; border: 1px solid var(--border-control); background: var(--panel); }
|
|
1195
1196
|
.rigel-article-content:focus { outline: 0; }
|
|
1197
|
+
.rigel-markdown-content:focus { outline: 0; }
|
|
1196
1198
|
.rigel.rigel-inline .markdown-inline { position: fixed; top: 8px; left: 8px; width: max-content; max-width: calc(100vw - 16px); max-height: calc(100vh - 16px); overflow: auto; background: var(--panel); border: 1px solid var(--border-control); box-shadow: var(--shadow-lg); pointer-events: auto; }
|
|
1199
|
+
.rigel.rigel-inline .markdown-inline.markdown-floating { position: fixed; inset: auto; width: auto; max-width: none; max-height: none; margin: 0; padding: 0; overflow: visible; background: transparent; border: 0; box-shadow: none; pointer-events: none; }
|
|
1200
|
+
.rigel.rigel-inline .markdown-inline.markdown-floating .body-toolbar { position: fixed; inset: auto; width: max-content; max-width: calc(100vw - 16px); margin: 0; padding: 0.25rem; gap: 0.25rem; border: 1px solid var(--border-control); box-shadow: var(--shadow-lg); z-index: 120; pointer-events: auto; }
|
|
1201
|
+
.rigel.rigel-inline .markdown-inline.markdown-floating .body-toolbar > .markdown-toggle { width: auto; padding: 0 0.5rem; font-size: 0.75rem; }
|
|
1202
|
+
.rigel.rigel-inline .markdown-inline.markdown-floating .markdown-link { position: fixed; inset: auto; z-index: 121; pointer-events: auto; }
|
|
1197
1203
|
.rigel .markdown-editor [hidden] { display: none !important; }
|
|
1198
1204
|
.rigel-markdown-content { white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
1199
1205
|
:is(.rigel .block-content, .rigel-article-content)::highlight(rigel-link-selection), :is(.rigel .block-content, .rigel-article-content) ::highlight(rigel-link-selection) { background-color: Highlight; color: HighlightText; }
|
|
@@ -1303,7 +1309,7 @@
|
|
|
1303
1309
|
.rigel .add-section .custom-select { flex: 1; }
|
|
1304
1310
|
@media (max-width: 767px) { .rigel .select-option { min-height: 2.75rem; } }
|
|
1305
1311
|
.rigel .schema-panel:has(> .panel-tabs) { scroll-padding-top: 4rem; }
|
|
1306
|
-
.rigel .schema-panel .block-editor > .body-toolbar:not(.image-toolbar) { position:
|
|
1312
|
+
.rigel .schema-panel .block-editor > .body-toolbar:not(.image-toolbar), .rigel .schema-panel .markdown-editor:not(.markdown-inline) .formatting-toolbar { position: sticky; top: var(--properties-tabs-height, 0px); z-index: 3; }
|
|
1307
1313
|
.rigel .schema-panel > .panel-tabs { position: sticky; top: 0; z-index: 10; padding-top: 0.5rem; margin-top: -0.5rem; background: var(--panel); }
|
|
1308
1314
|
.rigel .schema-panel > .panel-tabs::before { content: ""; position: absolute; inset: -1rem -1rem 0; z-index: -1; background: var(--panel); }
|
|
1309
1315
|
.rigel .panel-tabs { display: flex; gap: 0.25rem; border-bottom: 1px solid var(--border-control); }
|
package/src/schema/types.ts
CHANGED
package/src/schema/validate.ts
CHANGED
|
@@ -91,6 +91,7 @@ function validateFieldsShape(schema: Schema, fields: FieldDef[], scope: string)
|
|
|
91
91
|
keys.add(field.key);
|
|
92
92
|
if (typeof field.label !== "string" || !field.label.trim()) throw new SchemaError(`${scope}.${field.key}: label required`);
|
|
93
93
|
if (field.accept !== undefined && (field.type !== "file" || typeof field.accept !== "string" || !field.accept.split(",").every(token => /^(?:\.[a-z0-9]+|[a-z0-9!#$&^_.+-]+\/(?:[a-z0-9!#$&^_.+-]+|\*))$/i.test(token.trim())))) throw new SchemaError(`${scope}.${field.key}: accept must contain file extensions or MIME types`);
|
|
94
|
+
if (field.sidebarOnly !== undefined && (field.type !== "markdown" || typeof field.sidebarOnly !== "boolean")) throw new SchemaError(`${scope}.${field.key}: sidebarOnly applies to markdown fields`);
|
|
94
95
|
if (![...SCALARS, "object", "array", "reference"].includes(field.type)) throw new SchemaError(`${scope}.${field.key}: unknown field type ${field.type}`);
|
|
95
96
|
if (field.type === "object") {
|
|
96
97
|
if (field.of) objectDef(schema, field.of);
|