@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.
Files changed (51) hide show
  1. package/dist/components/cms/field-control.js +2 -1
  2. package/dist/components/cms/field-control.js.map +1 -1
  3. package/dist/components/cms/rich-text/asset-image.d.ts +12 -0
  4. package/dist/components/cms/rich-text/asset-image.js +24 -0
  5. package/dist/components/cms/rich-text/asset-image.js.map +1 -0
  6. package/dist/components/cms/rich-text/component-embed.d.ts +15 -0
  7. package/dist/components/cms/rich-text/component-embed.js +54 -0
  8. package/dist/components/cms/rich-text/component-embed.js.map +1 -0
  9. package/dist/components/cms/rich-text/editor.d.ts +27 -0
  10. package/dist/components/cms/rich-text/editor.js +111 -0
  11. package/dist/components/cms/rich-text/editor.js.map +1 -0
  12. package/dist/components/cms/rich-text/embed-control.d.ts +17 -0
  13. package/dist/components/cms/rich-text/embed-control.js +62 -0
  14. package/dist/components/cms/rich-text/embed-control.js.map +1 -0
  15. package/dist/components/cms/rich-text/embed-picker.d.ts +17 -0
  16. package/dist/components/cms/rich-text/embed-picker.js +34 -0
  17. package/dist/components/cms/rich-text/embed-picker.js.map +1 -0
  18. package/dist/components/cms/rich-text/extensions.d.ts +29 -0
  19. package/dist/components/cms/rich-text/extensions.js +45 -0
  20. package/dist/components/cms/rich-text/extensions.js.map +1 -0
  21. package/dist/components/cms/rich-text/image-control.d.ts +16 -0
  22. package/dist/components/cms/rich-text/image-control.js +36 -0
  23. package/dist/components/cms/rich-text/image-control.js.map +1 -0
  24. package/dist/components/cms/rich-text/index.d.ts +7 -0
  25. package/dist/components/cms/rich-text/index.js +8 -0
  26. package/dist/components/cms/rich-text/index.js.map +1 -0
  27. package/dist/components/cms/rich-text/insert.d.ts +21 -0
  28. package/dist/components/cms/rich-text/insert.js +55 -0
  29. package/dist/components/cms/rich-text/insert.js.map +1 -0
  30. package/dist/components/cms/rich-text/link-control.d.ts +22 -0
  31. package/dist/components/cms/rich-text/link-control.js +65 -0
  32. package/dist/components/cms/rich-text/link-control.js.map +1 -0
  33. package/dist/components/cms/rich-text/ref-link.d.ts +14 -0
  34. package/dist/components/cms/rich-text/ref-link.js +26 -0
  35. package/dist/components/cms/rich-text/ref-link.js.map +1 -0
  36. package/dist/components/cms/rich-text/slash-command.d.ts +27 -0
  37. package/dist/components/cms/rich-text/slash-command.js +158 -0
  38. package/dist/components/cms/rich-text/slash-command.js.map +1 -0
  39. package/dist/components/cms/rich-text/toolbar-button.d.ts +15 -0
  40. package/dist/components/cms/rich-text/toolbar-button.js +11 -0
  41. package/dist/components/cms/rich-text/toolbar-button.js.map +1 -0
  42. package/dist/components/cms/rich-text/toolbar.d.ts +19 -0
  43. package/dist/components/cms/rich-text/toolbar.js +38 -0
  44. package/dist/components/cms/rich-text/toolbar.js.map +1 -0
  45. package/dist/components/cms/rich-text/value.d.ts +33 -0
  46. package/dist/components/cms/rich-text/value.js +114 -0
  47. package/dist/components/cms/rich-text/value.js.map +1 -0
  48. package/dist/lib/i18n/messages.en.js +35 -0
  49. package/dist/lib/i18n/messages.en.js.map +1 -1
  50. package/package.json +9 -2
  51. 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