@elytracms/studio 0.0.36 → 0.0.38
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/dist/components/cms/field-control.js +2 -1
- package/dist/components/cms/field-control.js.map +1 -1
- package/dist/components/cms/rich-text/asset-image.d.ts +12 -0
- package/dist/components/cms/rich-text/asset-image.js +24 -0
- package/dist/components/cms/rich-text/asset-image.js.map +1 -0
- package/dist/components/cms/rich-text/component-embed.d.ts +15 -0
- package/dist/components/cms/rich-text/component-embed.js +54 -0
- package/dist/components/cms/rich-text/component-embed.js.map +1 -0
- package/dist/components/cms/rich-text/editor.d.ts +27 -0
- package/dist/components/cms/rich-text/editor.js +111 -0
- package/dist/components/cms/rich-text/editor.js.map +1 -0
- package/dist/components/cms/rich-text/embed-control.d.ts +17 -0
- package/dist/components/cms/rich-text/embed-control.js +62 -0
- package/dist/components/cms/rich-text/embed-control.js.map +1 -0
- package/dist/components/cms/rich-text/embed-picker.d.ts +17 -0
- package/dist/components/cms/rich-text/embed-picker.js +34 -0
- package/dist/components/cms/rich-text/embed-picker.js.map +1 -0
- package/dist/components/cms/rich-text/extensions.d.ts +29 -0
- package/dist/components/cms/rich-text/extensions.js +45 -0
- package/dist/components/cms/rich-text/extensions.js.map +1 -0
- package/dist/components/cms/rich-text/image-control.d.ts +16 -0
- package/dist/components/cms/rich-text/image-control.js +36 -0
- package/dist/components/cms/rich-text/image-control.js.map +1 -0
- package/dist/components/cms/rich-text/index.d.ts +7 -0
- package/dist/components/cms/rich-text/index.js +8 -0
- package/dist/components/cms/rich-text/index.js.map +1 -0
- package/dist/components/cms/rich-text/insert.d.ts +21 -0
- package/dist/components/cms/rich-text/insert.js +55 -0
- package/dist/components/cms/rich-text/insert.js.map +1 -0
- package/dist/components/cms/rich-text/link-control.d.ts +22 -0
- package/dist/components/cms/rich-text/link-control.js +65 -0
- package/dist/components/cms/rich-text/link-control.js.map +1 -0
- package/dist/components/cms/rich-text/ref-link.d.ts +14 -0
- package/dist/components/cms/rich-text/ref-link.js +26 -0
- package/dist/components/cms/rich-text/ref-link.js.map +1 -0
- package/dist/components/cms/rich-text/slash-command.d.ts +27 -0
- package/dist/components/cms/rich-text/slash-command.js +158 -0
- package/dist/components/cms/rich-text/slash-command.js.map +1 -0
- package/dist/components/cms/rich-text/toolbar-button.d.ts +15 -0
- package/dist/components/cms/rich-text/toolbar-button.js +11 -0
- package/dist/components/cms/rich-text/toolbar-button.js.map +1 -0
- package/dist/components/cms/rich-text/toolbar.d.ts +19 -0
- package/dist/components/cms/rich-text/toolbar.js +38 -0
- package/dist/components/cms/rich-text/toolbar.js.map +1 -0
- package/dist/components/cms/rich-text/value.d.ts +33 -0
- package/dist/components/cms/rich-text/value.js +114 -0
- package/dist/components/cms/rich-text/value.js.map +1 -0
- package/dist/lib/i18n/messages.en.js +35 -0
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/package.json +9 -2
- package/src/styles/studio.css +122 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolbar.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/toolbar.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEditorState } from '@tiptap/react'\nimport type { Editor } from '@tiptap/react'\nimport {\n Blocks,\n Bold,\n ChevronDown,\n Code,\n ImagePlus,\n Italic,\n List,\n ListOrdered,\n Minus,\n Pilcrow,\n Quote,\n Redo2,\n SquareCode,\n Strikethrough,\n Underline,\n Undo2,\n} from 'lucide-react'\nimport {\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n Toolbar,\n ToolbarGroup,\n ToolbarSeparator,\n} from '@elytracms/studio/ui'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { ToolbarButton } from './toolbar-button'\nimport { LinkControl } from './link-control'\nimport type { LinkTarget } from './link-control'\n\ninterface RichTextToolbarProps {\n editor: Editor\n /** Internal-link destinations across the workspace (empty ⇒ external-only). */\n targets: LinkTarget[]\n /** Open the shared image picker (omitted ⇒ no image button). */\n onOpenImage?: () => void\n /** Open the shared component-embed picker (omitted ⇒ no embed button). */\n onOpenEmbed?: () => void\n}\n\n/** Snapshot of the active formatting state, recomputed reactively per selection. */\ninterface ToolbarState {\n bold: boolean\n italic: boolean\n underline: boolean\n strike: boolean\n code: boolean\n bulletList: boolean\n orderedList: boolean\n blockquote: boolean\n codeBlock: boolean\n paragraph: boolean\n headingLevel: number | null\n canUndo: boolean\n canRedo: boolean\n}\n\n/**\n * The CMS rich-text toolbar (EC-314) — a compact, Notion-grade control strip that\n * reads its active/enabled state reactively from the editor (`useEditorState`) so a\n * pressed control always mirrors the caret. Every button prevents the default\n * mousedown so clicking it never steals the editor selection.\n */\nexport function RichTextToolbar({ editor, targets, onOpenImage, onOpenEmbed }: RichTextToolbarProps) {\n const state = useEditorState({\n editor,\n selector: ({ editor }): ToolbarState => ({\n bold: editor.isActive('bold'),\n italic: editor.isActive('italic'),\n underline: editor.isActive('underline'),\n strike: editor.isActive('strike'),\n code: editor.isActive('code'),\n bulletList: editor.isActive('bulletList'),\n orderedList: editor.isActive('orderedList'),\n blockquote: editor.isActive('blockquote'),\n codeBlock: editor.isActive('codeBlock'),\n paragraph: editor.isActive('paragraph'),\n headingLevel: [1, 2, 3, 4, 5, 6].find((l) => editor.isActive('heading', { level: l })) ?? null,\n canUndo: editor.can().undo(),\n canRedo: editor.can().redo(),\n }),\n })\n\n const styleLabel =\n state.headingLevel != null\n ? translate('richText.heading', { level: state.headingLevel })\n : translate('richText.paragraph')\n\n return (\n <Toolbar className=\"h-10 flex-wrap gap-0.5 rounded-none border-x-0 border-t-0 bg-muted/30 px-1.5\">\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-7 gap-1 px-2 font-medium\"\n onMouseDown={(e) => e.preventDefault()}\n >\n {styleLabel}\n <ChevronDown className=\"size-3 opacity-60\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"min-w-40\">\n <DropdownMenuItem onSelect={() => editor.chain().focus().setParagraph().run()}>\n <Pilcrow className=\"size-3.5\" /> {translate('richText.paragraph')}\n </DropdownMenuItem>\n {([1, 2, 3] as const).map((level) => (\n <DropdownMenuItem\n key={level}\n onSelect={() => editor.chain().focus().toggleHeading({ level }).run()}\n >\n <span className=\"w-3.5 text-center text-xs font-semibold\">H{level}</span>{' '}\n {translate('richText.heading', { level })}\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n\n <ToolbarSeparator />\n\n <ToolbarGroup>\n <ToolbarButton\n active={state.bold}\n label={translate('richText.bold')}\n onClick={() => editor.chain().focus().toggleBold().run()}\n >\n <Bold className=\"size-3.5\" />\n </ToolbarButton>\n <ToolbarButton\n active={state.italic}\n label={translate('richText.italic')}\n onClick={() => editor.chain().focus().toggleItalic().run()}\n >\n <Italic className=\"size-3.5\" />\n </ToolbarButton>\n <ToolbarButton\n active={state.underline}\n label={translate('richText.underline')}\n onClick={() => editor.chain().focus().toggleUnderline().run()}\n >\n <Underline className=\"size-3.5\" />\n </ToolbarButton>\n <ToolbarButton\n active={state.strike}\n label={translate('richText.strike')}\n onClick={() => editor.chain().focus().toggleStrike().run()}\n >\n <Strikethrough className=\"size-3.5\" />\n </ToolbarButton>\n <ToolbarButton\n active={state.code}\n label={translate('richText.code')}\n onClick={() => editor.chain().focus().toggleCode().run()}\n >\n <Code className=\"size-3.5\" />\n </ToolbarButton>\n </ToolbarGroup>\n\n <ToolbarSeparator />\n\n <ToolbarGroup>\n <ToolbarButton\n active={state.bulletList}\n label={translate('richText.bulletList')}\n onClick={() => editor.chain().focus().toggleBulletList().run()}\n >\n <List className=\"size-3.5\" />\n </ToolbarButton>\n <ToolbarButton\n active={state.orderedList}\n label={translate('richText.orderedList')}\n onClick={() => editor.chain().focus().toggleOrderedList().run()}\n >\n <ListOrdered className=\"size-3.5\" />\n </ToolbarButton>\n <ToolbarButton\n active={state.blockquote}\n label={translate('richText.blockquote')}\n onClick={() => editor.chain().focus().toggleBlockquote().run()}\n >\n <Quote className=\"size-3.5\" />\n </ToolbarButton>\n <ToolbarButton\n active={state.codeBlock}\n label={translate('richText.codeBlock')}\n onClick={() => editor.chain().focus().toggleCodeBlock().run()}\n >\n <SquareCode className=\"size-3.5\" />\n </ToolbarButton>\n <ToolbarButton\n active={false}\n label={translate('richText.horizontalRule')}\n onClick={() => editor.chain().focus().setHorizontalRule().run()}\n >\n <Minus className=\"size-3.5\" />\n </ToolbarButton>\n </ToolbarGroup>\n\n <ToolbarSeparator />\n\n <ToolbarGroup>\n <LinkControl editor={editor} targets={targets} />\n {onOpenImage ? (\n <ToolbarButton label={translate('richText.image.add')} onClick={onOpenImage}>\n <ImagePlus className=\"size-3.5\" />\n </ToolbarButton>\n ) : null}\n {onOpenEmbed ? (\n <ToolbarButton label={translate('richText.embed.add')} onClick={onOpenEmbed}>\n <Blocks className=\"size-3.5\" />\n </ToolbarButton>\n ) : null}\n </ToolbarGroup>\n\n <div className=\"flex-1\" />\n\n <ToolbarGroup>\n <ToolbarButton\n active={false}\n disabled={!state.canUndo}\n label={translate('richText.undo')}\n onClick={() => editor.chain().focus().undo().run()}\n >\n <Undo2 className=\"size-3.5\" />\n </ToolbarButton>\n <ToolbarButton\n active={false}\n disabled={!state.canRedo}\n label={translate('richText.redo')}\n onClick={() => editor.chain().focus().redo().run()}\n >\n <Redo2 className=\"size-3.5\" />\n </ToolbarButton>\n </ToolbarGroup>\n </Toolbar>\n )\n}\n"]}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { RichTextDoc, RichTextValue } from '@elytracms/core/rich-text';
|
|
2
|
+
/**
|
|
3
|
+
* Interpret whatever a `richText` field currently holds as an editor document.
|
|
4
|
+
*
|
|
5
|
+
* Accepts the canonical `{ version, doc }` object, its JSON-string form, a bare
|
|
6
|
+
* `doc`, and — for graceful migration off the retired textarea stub — a plain
|
|
7
|
+
* string, which becomes a single paragraph. Anything unparseable becomes an empty
|
|
8
|
+
* document (never throws; validation is first-class elsewhere).
|
|
9
|
+
*/
|
|
10
|
+
export declare function toEditorDoc(value: unknown): RichTextDoc;
|
|
11
|
+
/** Wrap an editor document back into a stamped, versioned storage value. */
|
|
12
|
+
export declare function richTextValueFromDoc(doc: RichTextDoc): RichTextValue;
|
|
13
|
+
/**
|
|
14
|
+
* Prune the `null` attribute values ProseMirror stamps from declared-but-unset
|
|
15
|
+
* attributes, and drop the resulting empty `attrs` bags, so the editor's output
|
|
16
|
+
* matches the lean storage convention. Recurses `content`; leaves `marks` attrs
|
|
17
|
+
* pruned the same way. Pure; returns a fresh tree.
|
|
18
|
+
*/
|
|
19
|
+
export declare function normalizeEditorDoc(doc: RichTextDoc): RichTextDoc;
|
|
20
|
+
/**
|
|
21
|
+
* Whether a document carries no author-visible content — only empty paragraphs or
|
|
22
|
+
* headings (TipTap keeps a mandatory trailing block even when the field is blank).
|
|
23
|
+
* Any text, atom (image/embed/hardBreak/hr), list, or blockquote makes it non-empty.
|
|
24
|
+
*/
|
|
25
|
+
export declare function isEffectivelyEmpty(doc: RichTextDoc): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Whether the (already normalized) editor document is semantically the stored one —
|
|
28
|
+
* so the editor can re-emit the exact stored value instead of a re-serialization,
|
|
29
|
+
* keeping the field un-dirtied. Two empty documents are always equivalent (the
|
|
30
|
+
* trailing-paragraph difference between `content: []` and `[{ paragraph }]` is not a
|
|
31
|
+
* change); otherwise defers to the shared structural comparison.
|
|
32
|
+
*/
|
|
33
|
+
export declare function isEquivalentToStored(current: RichTextDoc, storedValue: unknown): boolean;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { createRichTextValue, parseRichTextValue, richTextDocsEquivalent, } from '@elytracms/core/rich-text';
|
|
2
|
+
/**
|
|
3
|
+
* Storage ⟷ editor bridging for the CMS TipTap editor (EC-314).
|
|
4
|
+
*
|
|
5
|
+
* The storage format (`@elytracms/core/rich-text` `RichTextValue`) is a versioned,
|
|
6
|
+
* ProseMirror-shaped `{ version, doc }` envelope — the *same* JSON TipTap's
|
|
7
|
+
* `editor.getJSON()` produces for its document. So the bridge is thin: unwrap the
|
|
8
|
+
* `doc` to feed TipTap, wrap it back on the way out. Two concerns make it more than
|
|
9
|
+
* an identity function, both handled here so the editor component stays declarative:
|
|
10
|
+
*
|
|
11
|
+
* 1. **Lean canonicalisation.** ProseMirror stamps every declared attribute onto a
|
|
12
|
+
* node/mark, so TipTap emits `null` defaults the lean storage convention omits
|
|
13
|
+
* (`link` → `ref: null`, `codeBlock` → `language: null`, `image` → `title: null`).
|
|
14
|
+
* {@link normalizeEditorDoc} prunes those so an untouched document round-trips to
|
|
15
|
+
* the same shape the extractor/renderer already read — a bare external `link`
|
|
16
|
+
* stays `{ href }`, never `{ href, ref: null }`.
|
|
17
|
+
*
|
|
18
|
+
* 2. **No false dirty.** Opening a field must not mark it changed, and typing then
|
|
19
|
+
* undoing back to the original must clear dirty. {@link isEquivalentToStored}
|
|
20
|
+
* compares semantically (via the shared `richTextDocsEquivalent`, plus an
|
|
21
|
+
* empty-document tolerance for TipTap's mandatory trailing paragraph) so the
|
|
22
|
+
* editor can re-emit the *exact stored value* when nothing really changed.
|
|
23
|
+
*/
|
|
24
|
+
/** ProseMirror node types that carry no reference/atom payload and render as prose. */
|
|
25
|
+
const EMPTY_TOLERANT_BLOCKS = new Set(['paragraph', 'heading']);
|
|
26
|
+
/**
|
|
27
|
+
* Interpret whatever a `richText` field currently holds as an editor document.
|
|
28
|
+
*
|
|
29
|
+
* Accepts the canonical `{ version, doc }` object, its JSON-string form, a bare
|
|
30
|
+
* `doc`, and — for graceful migration off the retired textarea stub — a plain
|
|
31
|
+
* string, which becomes a single paragraph. Anything unparseable becomes an empty
|
|
32
|
+
* document (never throws; validation is first-class elsewhere).
|
|
33
|
+
*/
|
|
34
|
+
export function toEditorDoc(value) {
|
|
35
|
+
const parsed = parseRichTextValue(value);
|
|
36
|
+
if (parsed.ok)
|
|
37
|
+
return parsed.value.doc;
|
|
38
|
+
// A bare doc (no version envelope) — accept it directly.
|
|
39
|
+
if (isDocShape(value))
|
|
40
|
+
return value;
|
|
41
|
+
// Legacy plaintext written by the old textarea stub → one paragraph.
|
|
42
|
+
if (typeof value === 'string' && value.trim().length > 0) {
|
|
43
|
+
return { type: 'doc', content: [{ type: 'paragraph', content: [{ type: 'text', text: value }] }] };
|
|
44
|
+
}
|
|
45
|
+
return { type: 'doc', content: [] };
|
|
46
|
+
}
|
|
47
|
+
function isDocShape(value) {
|
|
48
|
+
return (typeof value === 'object' &&
|
|
49
|
+
value !== null &&
|
|
50
|
+
value.type === 'doc');
|
|
51
|
+
}
|
|
52
|
+
/** Wrap an editor document back into a stamped, versioned storage value. */
|
|
53
|
+
export function richTextValueFromDoc(doc) {
|
|
54
|
+
return createRichTextValue(doc);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Prune the `null` attribute values ProseMirror stamps from declared-but-unset
|
|
58
|
+
* attributes, and drop the resulting empty `attrs` bags, so the editor's output
|
|
59
|
+
* matches the lean storage convention. Recurses `content`; leaves `marks` attrs
|
|
60
|
+
* pruned the same way. Pure; returns a fresh tree.
|
|
61
|
+
*/
|
|
62
|
+
export function normalizeEditorDoc(doc) {
|
|
63
|
+
return { type: 'doc', content: (doc.content ?? []).map(pruneNode) };
|
|
64
|
+
}
|
|
65
|
+
function pruneNode(node) {
|
|
66
|
+
const out = { type: node.type };
|
|
67
|
+
const attrs = pruneAttrs(node.attrs);
|
|
68
|
+
if (attrs)
|
|
69
|
+
out.attrs = attrs;
|
|
70
|
+
if (typeof node.text === 'string')
|
|
71
|
+
out.text = node.text;
|
|
72
|
+
if (node.marks && node.marks.length > 0) {
|
|
73
|
+
out.marks = node.marks.map((mark) => {
|
|
74
|
+
const markAttrs = pruneAttrs(mark.attrs);
|
|
75
|
+
return markAttrs ? { type: mark.type, attrs: markAttrs } : { type: mark.type };
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
if (node.content)
|
|
79
|
+
out.content = node.content.map(pruneNode);
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
function pruneAttrs(attrs) {
|
|
83
|
+
if (!attrs)
|
|
84
|
+
return undefined;
|
|
85
|
+
const entries = Object.entries(attrs).filter(([, v]) => v !== null && v !== undefined);
|
|
86
|
+
return entries.length > 0 ? Object.fromEntries(entries) : undefined;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Whether a document carries no author-visible content — only empty paragraphs or
|
|
90
|
+
* headings (TipTap keeps a mandatory trailing block even when the field is blank).
|
|
91
|
+
* Any text, atom (image/embed/hardBreak/hr), list, or blockquote makes it non-empty.
|
|
92
|
+
*/
|
|
93
|
+
export function isEffectivelyEmpty(doc) {
|
|
94
|
+
return (doc.content ?? []).every(isEmptyBlock);
|
|
95
|
+
}
|
|
96
|
+
function isEmptyBlock(node) {
|
|
97
|
+
if (!EMPTY_TOLERANT_BLOCKS.has(node.type))
|
|
98
|
+
return false;
|
|
99
|
+
return (node.content ?? []).every((child) => child.type === 'text' && (child.text ?? '').length === 0);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Whether the (already normalized) editor document is semantically the stored one —
|
|
103
|
+
* so the editor can re-emit the exact stored value instead of a re-serialization,
|
|
104
|
+
* keeping the field un-dirtied. Two empty documents are always equivalent (the
|
|
105
|
+
* trailing-paragraph difference between `content: []` and `[{ paragraph }]` is not a
|
|
106
|
+
* change); otherwise defers to the shared structural comparison.
|
|
107
|
+
*/
|
|
108
|
+
export function isEquivalentToStored(current, storedValue) {
|
|
109
|
+
const stored = toEditorDoc(storedValue);
|
|
110
|
+
if (isEffectivelyEmpty(current) && isEffectivelyEmpty(stored))
|
|
111
|
+
return true;
|
|
112
|
+
return richTextDocsEquivalent(current, stored);
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=value.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"value.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/value.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n createRichTextValue,\n parseRichTextValue,\n richTextDocsEquivalent,\n} from '@elytracms/core/rich-text'\nimport type { RichTextDoc, RichTextNode, RichTextValue } from '@elytracms/core/rich-text'\n\n/**\n * Storage ⟷ editor bridging for the CMS TipTap editor (EC-314).\n *\n * The storage format (`@elytracms/core/rich-text` `RichTextValue`) is a versioned,\n * ProseMirror-shaped `{ version, doc }` envelope — the *same* JSON TipTap's\n * `editor.getJSON()` produces for its document. So the bridge is thin: unwrap the\n * `doc` to feed TipTap, wrap it back on the way out. Two concerns make it more than\n * an identity function, both handled here so the editor component stays declarative:\n *\n * 1. **Lean canonicalisation.** ProseMirror stamps every declared attribute onto a\n * node/mark, so TipTap emits `null` defaults the lean storage convention omits\n * (`link` → `ref: null`, `codeBlock` → `language: null`, `image` → `title: null`).\n * {@link normalizeEditorDoc} prunes those so an untouched document round-trips to\n * the same shape the extractor/renderer already read — a bare external `link`\n * stays `{ href }`, never `{ href, ref: null }`.\n *\n * 2. **No false dirty.** Opening a field must not mark it changed, and typing then\n * undoing back to the original must clear dirty. {@link isEquivalentToStored}\n * compares semantically (via the shared `richTextDocsEquivalent`, plus an\n * empty-document tolerance for TipTap's mandatory trailing paragraph) so the\n * editor can re-emit the *exact stored value* when nothing really changed.\n */\n\n/** ProseMirror node types that carry no reference/atom payload and render as prose. */\nconst EMPTY_TOLERANT_BLOCKS = new Set(['paragraph', 'heading'])\n\n/**\n * Interpret whatever a `richText` field currently holds as an editor document.\n *\n * Accepts the canonical `{ version, doc }` object, its JSON-string form, a bare\n * `doc`, and — for graceful migration off the retired textarea stub — a plain\n * string, which becomes a single paragraph. Anything unparseable becomes an empty\n * document (never throws; validation is first-class elsewhere).\n */\nexport function toEditorDoc(value: unknown): RichTextDoc {\n const parsed = parseRichTextValue(value)\n if (parsed.ok) return parsed.value.doc\n // A bare doc (no version envelope) — accept it directly.\n if (isDocShape(value)) return value\n // Legacy plaintext written by the old textarea stub → one paragraph.\n if (typeof value === 'string' && value.trim().length > 0) {\n return { type: 'doc', content: [{ type: 'paragraph', content: [{ type: 'text', text: value }] }] }\n }\n return { type: 'doc', content: [] }\n}\n\nfunction isDocShape(value: unknown): value is RichTextDoc {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { type?: unknown }).type === 'doc'\n )\n}\n\n/** Wrap an editor document back into a stamped, versioned storage value. */\nexport function richTextValueFromDoc(doc: RichTextDoc): RichTextValue {\n return createRichTextValue(doc)\n}\n\n/**\n * Prune the `null` attribute values ProseMirror stamps from declared-but-unset\n * attributes, and drop the resulting empty `attrs` bags, so the editor's output\n * matches the lean storage convention. Recurses `content`; leaves `marks` attrs\n * pruned the same way. Pure; returns a fresh tree.\n */\nexport function normalizeEditorDoc(doc: RichTextDoc): RichTextDoc {\n return { type: 'doc', content: (doc.content ?? []).map(pruneNode) }\n}\n\nfunction pruneNode(node: RichTextNode): RichTextNode {\n const out: RichTextNode = { type: node.type }\n const attrs = pruneAttrs(node.attrs)\n if (attrs) out.attrs = attrs\n if (typeof node.text === 'string') out.text = node.text\n if (node.marks && node.marks.length > 0) {\n out.marks = node.marks.map((mark) => {\n const markAttrs = pruneAttrs(mark.attrs)\n return markAttrs ? { type: mark.type, attrs: markAttrs } : { type: mark.type }\n })\n }\n if (node.content) out.content = node.content.map(pruneNode)\n return out\n}\n\nfunction pruneAttrs(\n attrs: Record<string, unknown> | undefined,\n): Record<string, never> | undefined {\n if (!attrs) return undefined\n const entries = Object.entries(attrs).filter(([, v]) => v !== null && v !== undefined)\n return entries.length > 0 ? (Object.fromEntries(entries) as Record<string, never>) : undefined\n}\n\n/**\n * Whether a document carries no author-visible content — only empty paragraphs or\n * headings (TipTap keeps a mandatory trailing block even when the field is blank).\n * Any text, atom (image/embed/hardBreak/hr), list, or blockquote makes it non-empty.\n */\nexport function isEffectivelyEmpty(doc: RichTextDoc): boolean {\n return (doc.content ?? []).every(isEmptyBlock)\n}\n\nfunction isEmptyBlock(node: RichTextNode): boolean {\n if (!EMPTY_TOLERANT_BLOCKS.has(node.type)) return false\n return (node.content ?? []).every(\n (child) => child.type === 'text' && (child.text ?? '').length === 0,\n )\n}\n\n/**\n * Whether the (already normalized) editor document is semantically the stored one —\n * so the editor can re-emit the exact stored value instead of a re-serialization,\n * keeping the field un-dirtied. Two empty documents are always equivalent (the\n * trailing-paragraph difference between `content: []` and `[{ paragraph }]` is not a\n * change); otherwise defers to the shared structural comparison.\n */\nexport function isEquivalentToStored(current: RichTextDoc, storedValue: unknown): boolean {\n const stored = toEditorDoc(storedValue)\n if (isEffectivelyEmpty(current) && isEffectivelyEmpty(stored)) return true\n return richTextDocsEquivalent(current, stored)\n}\n"]}
|
|
@@ -703,5 +703,40 @@ export const EN_MESSAGES = {
|
|
|
703
703
|
'builder.richText.link': msg('Link'),
|
|
704
704
|
'builder.richText.removeLink': msg('Remove link'),
|
|
705
705
|
'builder.richText.unlink': msg('Unlink'),
|
|
706
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
707
|
+
// CMS rich-text editor (EC-314) — the TipTap field editor
|
|
708
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
709
|
+
'richText.placeholder': msg('Write something…'),
|
|
710
|
+
'richText.bold': msg('Bold'),
|
|
711
|
+
'richText.italic': msg('Italic'),
|
|
712
|
+
'richText.underline': msg('Underline'),
|
|
713
|
+
'richText.strike': msg('Strikethrough'),
|
|
714
|
+
'richText.code': msg('Inline code'),
|
|
715
|
+
'richText.paragraph': msg('Paragraph'),
|
|
716
|
+
'richText.heading': msg('Heading {level}'),
|
|
717
|
+
'richText.style': msg('Style'),
|
|
718
|
+
'richText.bulletList': msg('Bulleted list'),
|
|
719
|
+
'richText.orderedList': msg('Numbered list'),
|
|
720
|
+
'richText.blockquote': msg('Quote'),
|
|
721
|
+
'richText.codeBlock': msg('Code block'),
|
|
722
|
+
'richText.horizontalRule': msg('Divider'),
|
|
723
|
+
'richText.undo': msg('Undo'),
|
|
724
|
+
'richText.redo': msg('Redo'),
|
|
725
|
+
'richText.link.add': msg('Link'),
|
|
726
|
+
'richText.link.remove': msg('Remove link'),
|
|
727
|
+
'richText.link.external': msg('External URL'),
|
|
728
|
+
'richText.link.externalPlaceholder': msg('https://…'),
|
|
729
|
+
'richText.link.document': msg('Link to a document'),
|
|
730
|
+
'richText.link.documentSearch': msg('Search documents…'),
|
|
731
|
+
'richText.link.documentEmpty': msg('No documents match.'),
|
|
732
|
+
'richText.link.apply': msg('Apply'),
|
|
733
|
+
'richText.image.add': msg('Image'),
|
|
734
|
+
'richText.image.alt': msg('Alt text'),
|
|
735
|
+
'richText.embed.add': msg('Embed component'),
|
|
736
|
+
'richText.embed.badge': msg('Embed'),
|
|
737
|
+
'richText.embed.unknown': msg('Unknown component'),
|
|
738
|
+
'richText.embed.empty': msg('No components available to embed.'),
|
|
739
|
+
'richText.slash.text': msg('Text'),
|
|
740
|
+
'richText.slash.empty': msg('No matching blocks'),
|
|
706
741
|
};
|
|
707
742
|
//# sourceMappingURL=messages.en.js.map
|