@techrox/page-studio-form 1.0.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/dist/index.js ADDED
@@ -0,0 +1,2792 @@
1
+ // src/PageStudioForm.jsx
2
+ import { createContext, useContext, useEffect as useEffect3, useRef, useState as useState3, useTransition as useTransition2 } from "react";
3
+ import {
4
+ Form as Form2,
5
+ Input as Input2,
6
+ Switch,
7
+ Button as Button3,
8
+ Space as Space3,
9
+ Tabs as Tabs2,
10
+ Alert as Alert2,
11
+ App as AntdApp2,
12
+ Popconfirm as Popconfirm2,
13
+ Typography as Typography3,
14
+ Card as Card2,
15
+ Segmented as Segmented2
16
+ } from "antd";
17
+ import {
18
+ ArrowLeftOutlined,
19
+ SaveOutlined,
20
+ DeleteOutlined as DeleteOutlined2,
21
+ PlusOutlined as PlusOutlined2,
22
+ MinusCircleOutlined,
23
+ EyeOutlined,
24
+ HolderOutlined,
25
+ ReloadOutlined as ReloadOutlined2,
26
+ DesktopOutlined,
27
+ MobileOutlined,
28
+ TabletOutlined
29
+ } from "@ant-design/icons";
30
+ import {
31
+ DndContext,
32
+ PointerSensor,
33
+ KeyboardSensor,
34
+ closestCenter,
35
+ useSensor,
36
+ useSensors
37
+ } from "@dnd-kit/core";
38
+ import {
39
+ SortableContext,
40
+ arrayMove,
41
+ sortableKeyboardCoordinates,
42
+ useSortable,
43
+ verticalListSortingStrategy
44
+ } from "@dnd-kit/sortable";
45
+ import { CSS } from "@dnd-kit/utilities";
46
+
47
+ // src/RichText.jsx
48
+ import { useEffect, useMemo, useState } from "react";
49
+ import {
50
+ useEditor,
51
+ EditorContent,
52
+ BubbleMenu,
53
+ FloatingMenu
54
+ } from "@tiptap/react";
55
+ import StarterKit from "@tiptap/starter-kit";
56
+ import Underline from "@tiptap/extension-underline";
57
+ import Link from "@tiptap/extension-link";
58
+ import Placeholder from "@tiptap/extension-placeholder";
59
+ import TextAlign from "@tiptap/extension-text-align";
60
+ import TextStyle from "@tiptap/extension-text-style";
61
+ import { Color } from "@tiptap/extension-color";
62
+ import Highlight from "@tiptap/extension-highlight";
63
+ import Table from "@tiptap/extension-table";
64
+ import TableRow from "@tiptap/extension-table-row";
65
+ import TaskList from "@tiptap/extension-task-list";
66
+ import TaskItem from "@tiptap/extension-task-item";
67
+ import CharacterCount from "@tiptap/extension-character-count";
68
+ import Typography from "@tiptap/extension-typography";
69
+ import {
70
+ Button,
71
+ Divider,
72
+ Dropdown,
73
+ Form,
74
+ Input,
75
+ Modal,
76
+ Popover,
77
+ Space,
78
+ Tabs,
79
+ Tag,
80
+ Tooltip,
81
+ Typography as AntTypography,
82
+ Upload,
83
+ message as antdMessage
84
+ } from "antd";
85
+ import {
86
+ BoldOutlined,
87
+ ItalicOutlined,
88
+ UnderlineOutlined,
89
+ StrikethroughOutlined,
90
+ UnorderedListOutlined,
91
+ OrderedListOutlined,
92
+ CheckSquareOutlined,
93
+ LinkOutlined,
94
+ PictureOutlined,
95
+ TableOutlined,
96
+ ClearOutlined,
97
+ UndoOutlined,
98
+ RedoOutlined,
99
+ AlignLeftOutlined,
100
+ AlignCenterOutlined,
101
+ AlignRightOutlined,
102
+ CodeOutlined,
103
+ BgColorsOutlined,
104
+ HighlightOutlined,
105
+ MinusOutlined,
106
+ BlockOutlined,
107
+ DownOutlined,
108
+ PlusOutlined,
109
+ UploadOutlined,
110
+ LoadingOutlined,
111
+ AppstoreOutlined,
112
+ ColumnWidthOutlined,
113
+ VerticalAlignTopOutlined,
114
+ VerticalAlignMiddleOutlined,
115
+ VerticalAlignBottomOutlined,
116
+ EditOutlined,
117
+ DeleteOutlined,
118
+ ShareAltOutlined,
119
+ MailOutlined,
120
+ ExclamationCircleOutlined
121
+ } from "@ant-design/icons";
122
+
123
+ // src/CustomBlocks.js
124
+ import { Node, mergeAttributes } from "@tiptap/core";
125
+ import Image from "@tiptap/extension-image";
126
+ import TableCell from "@tiptap/extension-table-cell";
127
+ import TableHeader from "@tiptap/extension-table-header";
128
+ var vAlignAttrs = {
129
+ verticalAlign: {
130
+ default: "top",
131
+ parseHTML: (el) => el.style?.verticalAlign || el.getAttribute("data-valign") || "top",
132
+ renderHTML: (attrs) => ({
133
+ "data-valign": attrs.verticalAlign,
134
+ style: `vertical-align: ${attrs.verticalAlign};`
135
+ })
136
+ }
137
+ };
138
+ var CiqTableCell = TableCell.extend({
139
+ addAttributes() {
140
+ return { ...this.parent?.(), ...vAlignAttrs };
141
+ }
142
+ });
143
+ var CiqTableHeader = TableHeader.extend({
144
+ addAttributes() {
145
+ return { ...this.parent?.(), ...vAlignAttrs };
146
+ }
147
+ });
148
+ var CiqImage = Image.extend({
149
+ name: "ciqImage",
150
+ // Inline images don't get alignment — we use block images so we can wrap
151
+ // them in a container div and apply text-align.
152
+ inline: false,
153
+ group: "block",
154
+ draggable: true,
155
+ addAttributes() {
156
+ return {
157
+ ...this.parent?.(),
158
+ align: {
159
+ default: "center",
160
+ parseHTML: (el) => el.getAttribute("data-align") || "center",
161
+ renderHTML: (attrs) => ({ "data-align": attrs.align })
162
+ },
163
+ width: {
164
+ default: "medium",
165
+ // 'small' | 'medium' | 'large' | 'full'
166
+ parseHTML: (el) => el.getAttribute("data-width") || "medium",
167
+ renderHTML: (attrs) => ({ "data-width": attrs.width })
168
+ }
169
+ };
170
+ },
171
+ renderHTML({ HTMLAttributes }) {
172
+ const { align, width, ...rest } = HTMLAttributes;
173
+ return [
174
+ "div",
175
+ {
176
+ class: `tps-block-image tps-img-${width || "medium"}`,
177
+ "data-align": align || "center"
178
+ },
179
+ ["img", mergeAttributes(this.options.HTMLAttributes, rest)]
180
+ ];
181
+ },
182
+ parseHTML() {
183
+ return [
184
+ {
185
+ tag: "div.tps-block-image > img",
186
+ getAttrs: (img) => {
187
+ const wrapper = img.closest(".tps-block-image");
188
+ return {
189
+ src: img.getAttribute("src"),
190
+ alt: img.getAttribute("alt"),
191
+ title: img.getAttribute("title"),
192
+ align: wrapper?.getAttribute("data-align") || "center",
193
+ width: wrapper?.className.match(/tps-img-(small|medium|large|full)/)?.[1] || "medium"
194
+ };
195
+ }
196
+ },
197
+ // Fallback: legacy plain <img> tags
198
+ { tag: 'img[src]:not([src^="data:"])' }
199
+ ];
200
+ }
201
+ });
202
+ var Column = Node.create({
203
+ name: "column",
204
+ group: "column",
205
+ // Allow any block content inside — paragraphs, headings, lists, images.
206
+ content: "block+",
207
+ isolating: true,
208
+ defining: true,
209
+ parseHTML() {
210
+ return [{ tag: "div.tps-block-column" }];
211
+ },
212
+ renderHTML({ HTMLAttributes }) {
213
+ return [
214
+ "div",
215
+ mergeAttributes(HTMLAttributes, { class: "tps-block-column" }),
216
+ 0
217
+ ];
218
+ }
219
+ });
220
+ var Columns = Node.create({
221
+ name: "columns",
222
+ group: "block",
223
+ content: "column{2,4}",
224
+ // 2–4 columns
225
+ defining: true,
226
+ addAttributes() {
227
+ return {
228
+ cols: {
229
+ default: 2,
230
+ parseHTML: (el) => Number(el.getAttribute("data-cols")) || 2,
231
+ renderHTML: (attrs) => ({ "data-cols": attrs.cols })
232
+ }
233
+ };
234
+ },
235
+ parseHTML() {
236
+ return [{ tag: "div.tps-block-columns" }];
237
+ },
238
+ renderHTML({ HTMLAttributes }) {
239
+ const cols = HTMLAttributes["data-cols"] || HTMLAttributes.cols || 2;
240
+ return [
241
+ "div",
242
+ mergeAttributes(HTMLAttributes, {
243
+ class: `tps-block-columns tps-cols-${cols}`
244
+ }),
245
+ 0
246
+ ];
247
+ },
248
+ addCommands() {
249
+ return {
250
+ insertColumns: (cols = 2) => ({ commands }) => {
251
+ const n = Math.max(2, Math.min(4, Number(cols) || 2));
252
+ return commands.insertContent({
253
+ type: this.name,
254
+ attrs: { cols: n },
255
+ content: Array.from({ length: n }, () => ({
256
+ type: "column",
257
+ content: [
258
+ { type: "paragraph" }
259
+ ]
260
+ }))
261
+ });
262
+ }
263
+ };
264
+ }
265
+ });
266
+ var Callout = Node.create({
267
+ name: "callout",
268
+ group: "block",
269
+ content: "block+",
270
+ defining: true,
271
+ addAttributes() {
272
+ return {
273
+ tone: {
274
+ default: "info",
275
+ // 'info' | 'success' | 'warning' | 'danger'
276
+ parseHTML: (el) => el.getAttribute("data-tone") || "info",
277
+ renderHTML: (attrs) => ({ "data-tone": attrs.tone })
278
+ }
279
+ };
280
+ },
281
+ parseHTML() {
282
+ return [{ tag: "div.tps-block-callout" }];
283
+ },
284
+ renderHTML({ HTMLAttributes }) {
285
+ const tone = HTMLAttributes["data-tone"] || "info";
286
+ return [
287
+ "div",
288
+ mergeAttributes(HTMLAttributes, {
289
+ class: `tps-block-callout tps-callout-${tone}`
290
+ }),
291
+ 0
292
+ ];
293
+ },
294
+ addCommands() {
295
+ return {
296
+ insertCallout: (tone = "info") => ({ commands }) => {
297
+ return commands.insertContent({
298
+ type: this.name,
299
+ attrs: { tone },
300
+ content: [
301
+ {
302
+ type: "paragraph",
303
+ content: [{ type: "text", text: "Highlight something important here." }]
304
+ }
305
+ ]
306
+ });
307
+ }
308
+ };
309
+ }
310
+ });
311
+ var ShareBlock = Node.create({
312
+ name: "shareBlock",
313
+ group: "block",
314
+ atom: true,
315
+ selectable: true,
316
+ draggable: true,
317
+ addAttributes() {
318
+ return {
319
+ label: {
320
+ default: "Share this page",
321
+ parseHTML: (el) => el.getAttribute("data-label") || "Share this page",
322
+ renderHTML: (attrs) => ({ "data-label": attrs.label })
323
+ }
324
+ };
325
+ },
326
+ parseHTML() {
327
+ return [{ tag: "div.tps-block-share" }];
328
+ },
329
+ renderHTML({ HTMLAttributes }) {
330
+ const label = HTMLAttributes["data-label"] || "Share this page";
331
+ return [
332
+ "div",
333
+ mergeAttributes(HTMLAttributes, { class: "tps-block-share" }),
334
+ ["div", { class: "tps-block-share-label" }, label],
335
+ [
336
+ "div",
337
+ { class: "tps-block-share-row" },
338
+ [
339
+ "a",
340
+ {
341
+ class: "tps-block-share-btn",
342
+ href: "https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fpagestudio.dev",
343
+ target: "_blank",
344
+ rel: "noopener noreferrer"
345
+ },
346
+ "LinkedIn"
347
+ ],
348
+ [
349
+ "a",
350
+ {
351
+ class: "tps-block-share-btn",
352
+ href: "mailto:?subject=Page%20Studio&body=Thought%20you%27d%20find%20this%20interesting%3A%20https%3A%2F%2Fpagestudio.dev"
353
+ },
354
+ "Email"
355
+ ],
356
+ [
357
+ "a",
358
+ {
359
+ class: "tps-block-share-btn",
360
+ href: "https://twitter.com/intent/tweet?url=https%3A%2F%2Fpagestudio.dev",
361
+ target: "_blank",
362
+ rel: "noopener noreferrer"
363
+ },
364
+ "X / Twitter"
365
+ ]
366
+ ]
367
+ ];
368
+ },
369
+ addCommands() {
370
+ return {
371
+ insertShareBlock: () => ({ commands }) => commands.insertContent({ type: this.name })
372
+ };
373
+ }
374
+ });
375
+ var SubscribeBlock = Node.create({
376
+ name: "subscribeBlock",
377
+ group: "block",
378
+ atom: true,
379
+ selectable: true,
380
+ draggable: true,
381
+ addAttributes() {
382
+ return {
383
+ heading: {
384
+ default: "Stay in the loop",
385
+ parseHTML: (el) => el.getAttribute("data-heading") || "Stay in the loop",
386
+ renderHTML: (attrs) => ({ "data-heading": attrs.heading })
387
+ },
388
+ body: {
389
+ default: "One short email when we publish \u2014 never more.",
390
+ parseHTML: (el) => el.getAttribute("data-body") || "",
391
+ renderHTML: (attrs) => ({ "data-body": attrs.body })
392
+ },
393
+ button: {
394
+ default: "Subscribe",
395
+ parseHTML: (el) => el.getAttribute("data-button") || "Subscribe",
396
+ renderHTML: (attrs) => ({ "data-button": attrs.button })
397
+ }
398
+ };
399
+ },
400
+ parseHTML() {
401
+ return [{ tag: "div.tps-block-subscribe" }];
402
+ },
403
+ renderHTML({ HTMLAttributes }) {
404
+ const heading = HTMLAttributes["data-heading"] || "Stay in the loop";
405
+ const body = HTMLAttributes["data-body"] || "";
406
+ const button = HTMLAttributes["data-button"] || "Subscribe";
407
+ return [
408
+ "div",
409
+ mergeAttributes(HTMLAttributes, { class: "tps-block-subscribe" }),
410
+ ["h3", { class: "tps-block-subscribe-heading" }, heading],
411
+ body ? ["p", { class: "tps-block-subscribe-body" }, body] : "",
412
+ [
413
+ "form",
414
+ { class: "tps-block-subscribe-form", action: "/api/leads", method: "post" },
415
+ ["input", { type: "hidden", name: "source", value: "newsletter" }],
416
+ [
417
+ "input",
418
+ {
419
+ type: "email",
420
+ name: "email",
421
+ required: "true",
422
+ placeholder: "you@company.com",
423
+ class: "tps-block-subscribe-input"
424
+ }
425
+ ],
426
+ ["button", { type: "submit", class: "tps-block-subscribe-button" }, button]
427
+ ]
428
+ ];
429
+ },
430
+ addCommands() {
431
+ return {
432
+ insertSubscribeBlock: () => ({ commands }) => commands.insertContent({ type: this.name })
433
+ };
434
+ }
435
+ });
436
+
437
+ // src/RichText.jsx
438
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
439
+ function defaultUploadMedia() {
440
+ throw new Error(
441
+ "@techrox/page-studio-form: RichText was used without an `uploadMedia` prop. Pass a function `(FormData) => Promise<{ url }>` to enable image uploads."
442
+ );
443
+ }
444
+ var { Text } = AntTypography;
445
+ var HEADING_OPTIONS = [
446
+ { key: "p", label: "Paragraph" },
447
+ { key: "h1", label: "Heading 1" },
448
+ { key: "h2", label: "Heading 2" },
449
+ { key: "h3", label: "Heading 3" },
450
+ { key: "h4", label: "Heading 4" }
451
+ ];
452
+ var TEXT_COLORS = [
453
+ "#0F172A",
454
+ "#475569",
455
+ "#0F766E",
456
+ "#0B5550",
457
+ "#F59E0B",
458
+ "#D97706",
459
+ "#DC2626",
460
+ "#2563EB",
461
+ "#7C3AED",
462
+ "#059669"
463
+ ];
464
+ var HIGHLIGHT_COLORS = [
465
+ "#FEF08A",
466
+ "#FECACA",
467
+ "#FED7AA",
468
+ "#A7F3D0",
469
+ "#BFDBFE",
470
+ "#E9D5FF",
471
+ "#FBCFE8",
472
+ "#FDE68A"
473
+ ];
474
+ var IMAGE_WIDTHS = [
475
+ { value: "small", label: "Small (40%)" },
476
+ { value: "medium", label: "Medium (60%)" },
477
+ { value: "large", label: "Large (80%)" },
478
+ { value: "full", label: "Full width" }
479
+ ];
480
+ function RichText({
481
+ value = "",
482
+ onChange,
483
+ placeholder = "Type '/' for blocks, or just start writing\u2026",
484
+ minHeight = 280,
485
+ uploadMedia = defaultUploadMedia
486
+ }) {
487
+ const [imageOpen, setImageOpen] = useState(false);
488
+ const [linkOpen, setLinkOpen] = useState(false);
489
+ const [editBlock, setEditBlock] = useState(null);
490
+ const editor = useEditor({
491
+ extensions: useMemo(
492
+ () => [
493
+ StarterKit.configure({
494
+ heading: { levels: [1, 2, 3, 4] },
495
+ codeBlock: { HTMLAttributes: { class: "tiptap-code" } }
496
+ }),
497
+ Underline,
498
+ Link.configure({
499
+ openOnClick: false,
500
+ autolink: true,
501
+ HTMLAttributes: { rel: "noopener noreferrer", target: "_blank" }
502
+ }),
503
+ CiqImage.configure({ inline: false, allowBase64: false }),
504
+ Placeholder.configure({
505
+ placeholder: ({ node, editor: editor2 }) => {
506
+ if (node.type.name === "paragraph" && editor2.isEmpty) return placeholder;
507
+ return "";
508
+ },
509
+ showOnlyWhenEditable: true
510
+ }),
511
+ TextAlign.configure({ types: ["heading", "paragraph"] }),
512
+ TextStyle,
513
+ Color,
514
+ Highlight.configure({ multicolor: true }),
515
+ Table.configure({ resizable: true, HTMLAttributes: { class: "tps-rich-table" } }),
516
+ TableRow,
517
+ CiqTableHeader,
518
+ CiqTableCell,
519
+ TaskList,
520
+ TaskItem.configure({ nested: true }),
521
+ CharacterCount,
522
+ Typography,
523
+ Columns,
524
+ Column,
525
+ Callout,
526
+ ShareBlock,
527
+ SubscribeBlock
528
+ ],
529
+ [placeholder]
530
+ ),
531
+ content: value || "",
532
+ onUpdate: ({ editor: editor2 }) => onChange?.(editor2.getHTML()),
533
+ immediatelyRender: false,
534
+ editorProps: {
535
+ attributes: { class: "tiptap", spellcheck: "true" },
536
+ handleDrop: (view, event) => {
537
+ const files = Array.from(event.dataTransfer?.files || []).filter(
538
+ (f) => f.type.startsWith("image/")
539
+ );
540
+ if (!files.length) return false;
541
+ event.preventDefault();
542
+ files.forEach((f) => uploadAndInsert(f, view, event, uploadMedia));
543
+ return true;
544
+ },
545
+ handlePaste: (view, event) => {
546
+ const files = Array.from(event.clipboardData?.files || []).filter(
547
+ (f) => f.type.startsWith("image/")
548
+ );
549
+ if (!files.length) return false;
550
+ event.preventDefault();
551
+ files.forEach((f) => uploadAndInsert(f, view, null, uploadMedia));
552
+ return true;
553
+ }
554
+ }
555
+ });
556
+ useEffect(() => {
557
+ if (!editor) return;
558
+ const current = editor.getHTML();
559
+ const next = value || "";
560
+ if (current === next) return;
561
+ if (current === "<p></p>" && next === "") return;
562
+ editor.commands.setContent(next, false);
563
+ }, [editor, value]);
564
+ if (!editor) {
565
+ return /* @__PURE__ */ jsx(
566
+ "div",
567
+ {
568
+ style: {
569
+ border: "1px solid var(--tps-line)",
570
+ borderRadius: "var(--tps-radius)",
571
+ minHeight,
572
+ background: "#fff"
573
+ }
574
+ }
575
+ );
576
+ }
577
+ return /* @__PURE__ */ jsxs(
578
+ "div",
579
+ {
580
+ style: {
581
+ border: "1px solid var(--tps-line)",
582
+ borderRadius: "var(--tps-radius)",
583
+ background: "#fff",
584
+ overflow: "hidden"
585
+ },
586
+ children: [
587
+ /* @__PURE__ */ jsx(
588
+ Toolbar,
589
+ {
590
+ editor,
591
+ onOpenLink: () => setLinkOpen(true),
592
+ onOpenImage: () => setImageOpen(true)
593
+ }
594
+ ),
595
+ /* @__PURE__ */ jsx(
596
+ BubbleMenu,
597
+ {
598
+ editor,
599
+ shouldShow: ({ editor: editor2, from, to }) => {
600
+ if (from === to) return false;
601
+ if (editor2.isActive("ciqImage") || editor2.isActive("table") || editor2.isActive("codeBlock") || editor2.isActive("shareBlock") || editor2.isActive("subscribeBlock")) return false;
602
+ return true;
603
+ },
604
+ tippyOptions: { duration: 100, placement: "top" },
605
+ pluginKey: "selectionBubble",
606
+ children: /* @__PURE__ */ jsx(SelectionFormatBar, { editor, onLink: () => setLinkOpen(true) })
607
+ }
608
+ ),
609
+ /* @__PURE__ */ jsx(
610
+ BubbleMenu,
611
+ {
612
+ editor,
613
+ shouldShow: ({ editor: editor2 }) => editor2.isActive("ciqImage"),
614
+ tippyOptions: { duration: 100, placement: "top" },
615
+ pluginKey: "imageBubble",
616
+ children: /* @__PURE__ */ jsx(ImageBubble, { editor })
617
+ }
618
+ ),
619
+ /* @__PURE__ */ jsx(
620
+ BubbleMenu,
621
+ {
622
+ editor,
623
+ shouldShow: ({ editor: editor2 }) => editor2.isActive("table"),
624
+ tippyOptions: { duration: 100, placement: "top" },
625
+ pluginKey: "tableBubble",
626
+ children: /* @__PURE__ */ jsx(TableBubble, { editor })
627
+ }
628
+ ),
629
+ /* @__PURE__ */ jsx(
630
+ BubbleMenu,
631
+ {
632
+ editor,
633
+ shouldShow: ({ editor: editor2 }) => editor2.isActive("shareBlock") || editor2.isActive("subscribeBlock"),
634
+ tippyOptions: { duration: 100, placement: "top" },
635
+ pluginKey: "atomBlockBubble",
636
+ children: /* @__PURE__ */ jsx(
637
+ AtomBlockBubble,
638
+ {
639
+ editor,
640
+ onEdit: (kind) => setEditBlock(kind)
641
+ }
642
+ )
643
+ }
644
+ ),
645
+ /* @__PURE__ */ jsx(
646
+ FloatingMenu,
647
+ {
648
+ editor,
649
+ shouldShow: ({ editor: editor2, state }) => {
650
+ const { $from } = state.selection;
651
+ const isEmptyParagraph = $from.parent.type.name === "paragraph" && $from.parent.content.size === 0;
652
+ return editor2.isFocused && isEmptyParagraph && !editor2.isActive("table");
653
+ },
654
+ tippyOptions: { duration: 100, placement: "left-start", offset: [0, 8] },
655
+ children: /* @__PURE__ */ jsx(BlockInsertMenu, { editor, onImage: () => setImageOpen(true) })
656
+ }
657
+ ),
658
+ /* @__PURE__ */ jsx("div", { style: { padding: 16, minHeight }, children: /* @__PURE__ */ jsx(EditorContent, { editor }) }),
659
+ /* @__PURE__ */ jsxs(
660
+ "div",
661
+ {
662
+ style: {
663
+ padding: "6px 14px",
664
+ borderTop: "1px solid var(--tps-line)",
665
+ background: "var(--tps-bg-soft)",
666
+ fontSize: 12,
667
+ color: "var(--tps-muted)",
668
+ display: "flex",
669
+ justifyContent: "space-between"
670
+ },
671
+ children: [
672
+ /* @__PURE__ */ jsxs("span", { children: [
673
+ editor.storage.characterCount.words(),
674
+ " words \xB7",
675
+ " ",
676
+ editor.storage.characterCount.characters(),
677
+ " characters"
678
+ ] }),
679
+ /* @__PURE__ */ jsx("span", { children: "HTML output \xB7 drop or paste images to upload" })
680
+ ]
681
+ }
682
+ ),
683
+ /* @__PURE__ */ jsx(LinkModal, { open: linkOpen, onClose: () => setLinkOpen(false), editor }),
684
+ /* @__PURE__ */ jsx(
685
+ ImageModal,
686
+ {
687
+ open: imageOpen,
688
+ onClose: () => setImageOpen(false),
689
+ editor,
690
+ uploadMedia
691
+ }
692
+ ),
693
+ /* @__PURE__ */ jsx(
694
+ BlockAttributeModal,
695
+ {
696
+ kind: editBlock,
697
+ open: !!editBlock,
698
+ onClose: () => setEditBlock(null),
699
+ editor
700
+ }
701
+ ),
702
+ /* @__PURE__ */ jsx(EditorStyles, {})
703
+ ]
704
+ }
705
+ );
706
+ }
707
+ async function uploadAndInsert(file, view, dropEvent, uploadMedia) {
708
+ try {
709
+ const fd = new FormData();
710
+ fd.append("file", file);
711
+ const { url } = await uploadMedia(fd);
712
+ if (!url) throw new Error("No URL returned");
713
+ const node = view.state.schema.nodes.ciqImage.create({
714
+ src: url,
715
+ alt: file.name,
716
+ align: "center",
717
+ width: "medium"
718
+ });
719
+ let pos;
720
+ if (dropEvent) {
721
+ const coords = view.posAtCoords({ left: dropEvent.clientX, top: dropEvent.clientY });
722
+ pos = coords?.pos ?? view.state.selection.from;
723
+ } else {
724
+ pos = view.state.selection.from;
725
+ }
726
+ const tr = view.state.tr.insert(pos, node);
727
+ view.dispatch(tr);
728
+ } catch (err) {
729
+ antdMessage.error(err.message || "Upload failed");
730
+ }
731
+ }
732
+ function Toolbar({ editor, onOpenLink, onOpenImage }) {
733
+ const headingValue = (() => {
734
+ if (editor.isActive("heading", { level: 1 })) return "h1";
735
+ if (editor.isActive("heading", { level: 2 })) return "h2";
736
+ if (editor.isActive("heading", { level: 3 })) return "h3";
737
+ if (editor.isActive("heading", { level: 4 })) return "h4";
738
+ return "p";
739
+ })();
740
+ const setHeading = (key) => {
741
+ if (key === "p") editor.chain().focus().setParagraph().run();
742
+ else editor.chain().focus().toggleHeading({ level: Number(key.slice(1)) }).run();
743
+ };
744
+ const insertBlockItems = [
745
+ { key: "image", label: "Image", icon: /* @__PURE__ */ jsx(PictureOutlined, {}), run: onOpenImage },
746
+ {
747
+ key: "table",
748
+ label: "Table (3\xD73)",
749
+ icon: /* @__PURE__ */ jsx(TableOutlined, {}),
750
+ run: () => editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run()
751
+ },
752
+ {
753
+ key: "cols2",
754
+ label: "2 columns",
755
+ icon: /* @__PURE__ */ jsx(AppstoreOutlined, {}),
756
+ run: () => editor.chain().focus().insertColumns(2).run()
757
+ },
758
+ {
759
+ key: "cols3",
760
+ label: "3 columns",
761
+ icon: /* @__PURE__ */ jsx(AppstoreOutlined, {}),
762
+ run: () => editor.chain().focus().insertColumns(3).run()
763
+ },
764
+ {
765
+ key: "cols4",
766
+ label: "4 columns",
767
+ icon: /* @__PURE__ */ jsx(AppstoreOutlined, {}),
768
+ run: () => editor.chain().focus().insertColumns(4).run()
769
+ },
770
+ {
771
+ key: "callout-info",
772
+ label: "Callout (info)",
773
+ icon: /* @__PURE__ */ jsx(ExclamationCircleOutlined, {}),
774
+ run: () => editor.chain().focus().insertCallout("info").run()
775
+ },
776
+ {
777
+ key: "callout-success",
778
+ label: "Callout (success)",
779
+ icon: /* @__PURE__ */ jsx(ExclamationCircleOutlined, { style: { color: "#059669" } }),
780
+ run: () => editor.chain().focus().insertCallout("success").run()
781
+ },
782
+ {
783
+ key: "callout-warning",
784
+ label: "Callout (warning)",
785
+ icon: /* @__PURE__ */ jsx(ExclamationCircleOutlined, { style: { color: "#D97706" } }),
786
+ run: () => editor.chain().focus().insertCallout("warning").run()
787
+ },
788
+ {
789
+ key: "share",
790
+ label: "Share buttons",
791
+ icon: /* @__PURE__ */ jsx(ShareAltOutlined, {}),
792
+ run: () => editor.chain().focus().insertShareBlock().run()
793
+ },
794
+ {
795
+ key: "subscribe",
796
+ label: "Subscribe form",
797
+ icon: /* @__PURE__ */ jsx(MailOutlined, {}),
798
+ run: () => editor.chain().focus().insertSubscribeBlock().run()
799
+ }
800
+ ];
801
+ return /* @__PURE__ */ jsxs(
802
+ "div",
803
+ {
804
+ style: {
805
+ display: "flex",
806
+ flexWrap: "wrap",
807
+ gap: 4,
808
+ padding: 8,
809
+ background: "var(--tps-bg-soft)",
810
+ borderBottom: "1px solid var(--tps-line)",
811
+ alignItems: "center",
812
+ position: "sticky",
813
+ top: 0,
814
+ zIndex: 5
815
+ },
816
+ children: [
817
+ /* @__PURE__ */ jsx(
818
+ ToolButton,
819
+ {
820
+ title: "Undo (\u2318Z)",
821
+ icon: /* @__PURE__ */ jsx(UndoOutlined, {}),
822
+ onClick: () => editor.chain().focus().undo().run(),
823
+ disabled: !editor.can().undo()
824
+ }
825
+ ),
826
+ /* @__PURE__ */ jsx(
827
+ ToolButton,
828
+ {
829
+ title: "Redo (\u2318\u21E7Z)",
830
+ icon: /* @__PURE__ */ jsx(RedoOutlined, {}),
831
+ onClick: () => editor.chain().focus().redo().run(),
832
+ disabled: !editor.can().redo()
833
+ }
834
+ ),
835
+ /* @__PURE__ */ jsx(ToolbarDivider, {}),
836
+ /* @__PURE__ */ jsx(
837
+ Dropdown,
838
+ {
839
+ trigger: ["click"],
840
+ menu: {
841
+ items: HEADING_OPTIONS.map((o) => ({ key: o.key, label: o.label })),
842
+ selectedKeys: [headingValue],
843
+ onClick: ({ key }) => setHeading(key)
844
+ },
845
+ children: /* @__PURE__ */ jsxs(Button, { size: "small", type: "text", style: { minWidth: 110 }, children: [
846
+ HEADING_OPTIONS.find((o) => o.key === headingValue)?.label,
847
+ /* @__PURE__ */ jsx(DownOutlined, { style: { fontSize: 10, marginLeft: 4 } })
848
+ ] })
849
+ }
850
+ ),
851
+ /* @__PURE__ */ jsx(ToolbarDivider, {}),
852
+ /* @__PURE__ */ jsx(
853
+ ToolButton,
854
+ {
855
+ title: "Bold (\u2318B)",
856
+ icon: /* @__PURE__ */ jsx(BoldOutlined, {}),
857
+ active: editor.isActive("bold"),
858
+ onClick: () => editor.chain().focus().toggleBold().run()
859
+ }
860
+ ),
861
+ /* @__PURE__ */ jsx(
862
+ ToolButton,
863
+ {
864
+ title: "Italic (\u2318I)",
865
+ icon: /* @__PURE__ */ jsx(ItalicOutlined, {}),
866
+ active: editor.isActive("italic"),
867
+ onClick: () => editor.chain().focus().toggleItalic().run()
868
+ }
869
+ ),
870
+ /* @__PURE__ */ jsx(
871
+ ToolButton,
872
+ {
873
+ title: "Underline (\u2318U)",
874
+ icon: /* @__PURE__ */ jsx(UnderlineOutlined, {}),
875
+ active: editor.isActive("underline"),
876
+ onClick: () => editor.chain().focus().toggleUnderline().run()
877
+ }
878
+ ),
879
+ /* @__PURE__ */ jsx(
880
+ ToolButton,
881
+ {
882
+ title: "Strikethrough",
883
+ icon: /* @__PURE__ */ jsx(StrikethroughOutlined, {}),
884
+ active: editor.isActive("strike"),
885
+ onClick: () => editor.chain().focus().toggleStrike().run()
886
+ }
887
+ ),
888
+ /* @__PURE__ */ jsx(ToolbarDivider, {}),
889
+ /* @__PURE__ */ jsx(
890
+ ColorSwatch,
891
+ {
892
+ title: "Text color",
893
+ icon: /* @__PURE__ */ jsx(BgColorsOutlined, {}),
894
+ colors: TEXT_COLORS,
895
+ onPick: (c) => editor.chain().focus().setColor(c).run(),
896
+ onClear: () => editor.chain().focus().unsetColor().run()
897
+ }
898
+ ),
899
+ /* @__PURE__ */ jsx(
900
+ ColorSwatch,
901
+ {
902
+ title: "Highlight",
903
+ icon: /* @__PURE__ */ jsx(HighlightOutlined, {}),
904
+ colors: HIGHLIGHT_COLORS,
905
+ onPick: (c) => editor.chain().focus().toggleHighlight({ color: c }).run(),
906
+ onClear: () => editor.chain().focus().unsetHighlight().run()
907
+ }
908
+ ),
909
+ /* @__PURE__ */ jsx(ToolbarDivider, {}),
910
+ /* @__PURE__ */ jsx(
911
+ ToolButton,
912
+ {
913
+ title: "Align left",
914
+ icon: /* @__PURE__ */ jsx(AlignLeftOutlined, {}),
915
+ active: editor.isActive({ textAlign: "left" }),
916
+ onClick: () => editor.chain().focus().setTextAlign("left").run()
917
+ }
918
+ ),
919
+ /* @__PURE__ */ jsx(
920
+ ToolButton,
921
+ {
922
+ title: "Align center",
923
+ icon: /* @__PURE__ */ jsx(AlignCenterOutlined, {}),
924
+ active: editor.isActive({ textAlign: "center" }),
925
+ onClick: () => editor.chain().focus().setTextAlign("center").run()
926
+ }
927
+ ),
928
+ /* @__PURE__ */ jsx(
929
+ ToolButton,
930
+ {
931
+ title: "Align right",
932
+ icon: /* @__PURE__ */ jsx(AlignRightOutlined, {}),
933
+ active: editor.isActive({ textAlign: "right" }),
934
+ onClick: () => editor.chain().focus().setTextAlign("right").run()
935
+ }
936
+ ),
937
+ /* @__PURE__ */ jsx(ToolbarDivider, {}),
938
+ /* @__PURE__ */ jsx(
939
+ ToolButton,
940
+ {
941
+ title: "Bulleted list",
942
+ icon: /* @__PURE__ */ jsx(UnorderedListOutlined, {}),
943
+ active: editor.isActive("bulletList"),
944
+ onClick: () => editor.chain().focus().toggleBulletList().run()
945
+ }
946
+ ),
947
+ /* @__PURE__ */ jsx(
948
+ ToolButton,
949
+ {
950
+ title: "Numbered list",
951
+ icon: /* @__PURE__ */ jsx(OrderedListOutlined, {}),
952
+ active: editor.isActive("orderedList"),
953
+ onClick: () => editor.chain().focus().toggleOrderedList().run()
954
+ }
955
+ ),
956
+ /* @__PURE__ */ jsx(
957
+ ToolButton,
958
+ {
959
+ title: "Task list",
960
+ icon: /* @__PURE__ */ jsx(CheckSquareOutlined, {}),
961
+ active: editor.isActive("taskList"),
962
+ onClick: () => editor.chain().focus().toggleTaskList().run()
963
+ }
964
+ ),
965
+ /* @__PURE__ */ jsx(ToolbarDivider, {}),
966
+ /* @__PURE__ */ jsx(
967
+ ToolButton,
968
+ {
969
+ title: "Quote",
970
+ icon: /* @__PURE__ */ jsx(BlockOutlined, {}),
971
+ active: editor.isActive("blockquote"),
972
+ onClick: () => editor.chain().focus().toggleBlockquote().run()
973
+ }
974
+ ),
975
+ /* @__PURE__ */ jsx(
976
+ ToolButton,
977
+ {
978
+ title: "Code block",
979
+ icon: /* @__PURE__ */ jsx(CodeOutlined, {}),
980
+ active: editor.isActive("codeBlock"),
981
+ onClick: () => editor.chain().focus().toggleCodeBlock().run()
982
+ }
983
+ ),
984
+ /* @__PURE__ */ jsx(
985
+ ToolButton,
986
+ {
987
+ title: "Horizontal rule",
988
+ icon: /* @__PURE__ */ jsx(MinusOutlined, {}),
989
+ onClick: () => editor.chain().focus().setHorizontalRule().run()
990
+ }
991
+ ),
992
+ /* @__PURE__ */ jsx(ToolbarDivider, {}),
993
+ /* @__PURE__ */ jsx(
994
+ ToolButton,
995
+ {
996
+ title: "Insert / edit link",
997
+ icon: /* @__PURE__ */ jsx(LinkOutlined, {}),
998
+ active: editor.isActive("link"),
999
+ onClick: onOpenLink
1000
+ }
1001
+ ),
1002
+ /* @__PURE__ */ jsx(
1003
+ Dropdown,
1004
+ {
1005
+ trigger: ["click"],
1006
+ placement: "bottomLeft",
1007
+ menu: {
1008
+ items: insertBlockItems.map((i) => ({ key: i.key, label: i.label, icon: i.icon })),
1009
+ onClick: ({ key }) => insertBlockItems.find((i) => i.key === key)?.run()
1010
+ },
1011
+ children: /* @__PURE__ */ jsxs(Button, { size: "small", type: "text", icon: /* @__PURE__ */ jsx(PlusOutlined, {}), style: { paddingInline: 8 }, children: [
1012
+ "Insert ",
1013
+ /* @__PURE__ */ jsx(DownOutlined, { style: { fontSize: 10 } })
1014
+ ] })
1015
+ }
1016
+ ),
1017
+ /* @__PURE__ */ jsx(ToolbarDivider, {}),
1018
+ /* @__PURE__ */ jsx(Tooltip, { title: "Clear formatting", children: /* @__PURE__ */ jsx(
1019
+ Button,
1020
+ {
1021
+ size: "small",
1022
+ type: "text",
1023
+ icon: /* @__PURE__ */ jsx(ClearOutlined, {}),
1024
+ onClick: () => editor.chain().focus().clearNodes().unsetAllMarks().run()
1025
+ }
1026
+ ) })
1027
+ ]
1028
+ }
1029
+ );
1030
+ }
1031
+ function ToolButton({ title, icon, active, onClick, disabled }) {
1032
+ return /* @__PURE__ */ jsx(Tooltip, { title, children: /* @__PURE__ */ jsx(
1033
+ Button,
1034
+ {
1035
+ size: "small",
1036
+ type: active ? "primary" : "text",
1037
+ icon,
1038
+ disabled,
1039
+ onClick
1040
+ }
1041
+ ) });
1042
+ }
1043
+ function ToolbarDivider() {
1044
+ return /* @__PURE__ */ jsx(Divider, { type: "vertical", style: { height: 22, margin: "0 2px", borderColor: "var(--tps-line)" } });
1045
+ }
1046
+ function SelectionFormatBar({ editor, onLink }) {
1047
+ return /* @__PURE__ */ jsxs(
1048
+ Space,
1049
+ {
1050
+ size: 2,
1051
+ style: {
1052
+ background: "#0F172A",
1053
+ padding: 4,
1054
+ borderRadius: 6,
1055
+ boxShadow: "0 8px 24px rgba(0,0,0,0.25)"
1056
+ },
1057
+ children: [
1058
+ /* @__PURE__ */ jsx(
1059
+ BubbleBtn,
1060
+ {
1061
+ title: "Bold",
1062
+ icon: /* @__PURE__ */ jsx(BoldOutlined, {}),
1063
+ active: editor.isActive("bold"),
1064
+ onClick: () => editor.chain().focus().toggleBold().run()
1065
+ }
1066
+ ),
1067
+ /* @__PURE__ */ jsx(
1068
+ BubbleBtn,
1069
+ {
1070
+ title: "Italic",
1071
+ icon: /* @__PURE__ */ jsx(ItalicOutlined, {}),
1072
+ active: editor.isActive("italic"),
1073
+ onClick: () => editor.chain().focus().toggleItalic().run()
1074
+ }
1075
+ ),
1076
+ /* @__PURE__ */ jsx(
1077
+ BubbleBtn,
1078
+ {
1079
+ title: "Underline",
1080
+ icon: /* @__PURE__ */ jsx(UnderlineOutlined, {}),
1081
+ active: editor.isActive("underline"),
1082
+ onClick: () => editor.chain().focus().toggleUnderline().run()
1083
+ }
1084
+ ),
1085
+ /* @__PURE__ */ jsx(
1086
+ BubbleBtn,
1087
+ {
1088
+ title: "Strike",
1089
+ icon: /* @__PURE__ */ jsx(StrikethroughOutlined, {}),
1090
+ active: editor.isActive("strike"),
1091
+ onClick: () => editor.chain().focus().toggleStrike().run()
1092
+ }
1093
+ ),
1094
+ /* @__PURE__ */ jsx(Divider, { type: "vertical", style: { background: "rgba(255,255,255,0.18)", height: 18, margin: "0 2px" } }),
1095
+ /* @__PURE__ */ jsx(
1096
+ BubbleBtn,
1097
+ {
1098
+ title: "Inline code",
1099
+ icon: /* @__PURE__ */ jsx(CodeOutlined, {}),
1100
+ active: editor.isActive("code"),
1101
+ onClick: () => editor.chain().focus().toggleCode().run()
1102
+ }
1103
+ ),
1104
+ /* @__PURE__ */ jsx(
1105
+ BubbleBtn,
1106
+ {
1107
+ title: "Link",
1108
+ icon: /* @__PURE__ */ jsx(LinkOutlined, {}),
1109
+ active: editor.isActive("link"),
1110
+ onClick: onLink
1111
+ }
1112
+ )
1113
+ ]
1114
+ }
1115
+ );
1116
+ }
1117
+ function BubbleBtn({ title, icon, active, onClick }) {
1118
+ return /* @__PURE__ */ jsx(Tooltip, { title, children: /* @__PURE__ */ jsx(
1119
+ Button,
1120
+ {
1121
+ size: "small",
1122
+ type: "text",
1123
+ icon,
1124
+ onClick,
1125
+ style: { color: active ? "#F59E0B" : "#fff" }
1126
+ }
1127
+ ) });
1128
+ }
1129
+ function ImageBubble({ editor }) {
1130
+ const attrs = editor.getAttributes("ciqImage");
1131
+ const setAlign = (align) => editor.chain().focus().updateAttributes("ciqImage", { align }).run();
1132
+ const setWidth = (width) => editor.chain().focus().updateAttributes("ciqImage", { width }).run();
1133
+ const remove = () => editor.chain().focus().deleteSelection().run();
1134
+ return /* @__PURE__ */ jsxs(
1135
+ Space,
1136
+ {
1137
+ size: 2,
1138
+ style: {
1139
+ background: "#fff",
1140
+ padding: 4,
1141
+ border: "1px solid var(--tps-line)",
1142
+ borderRadius: 6,
1143
+ boxShadow: "0 8px 24px rgba(0,0,0,0.12)"
1144
+ },
1145
+ children: [
1146
+ /* @__PURE__ */ jsx(Tooltip, { title: "Align left", children: /* @__PURE__ */ jsx(
1147
+ Button,
1148
+ {
1149
+ size: "small",
1150
+ type: attrs.align === "left" ? "primary" : "text",
1151
+ icon: /* @__PURE__ */ jsx(AlignLeftOutlined, {}),
1152
+ onClick: () => setAlign("left")
1153
+ }
1154
+ ) }),
1155
+ /* @__PURE__ */ jsx(Tooltip, { title: "Align center", children: /* @__PURE__ */ jsx(
1156
+ Button,
1157
+ {
1158
+ size: "small",
1159
+ type: attrs.align === "center" ? "primary" : "text",
1160
+ icon: /* @__PURE__ */ jsx(AlignCenterOutlined, {}),
1161
+ onClick: () => setAlign("center")
1162
+ }
1163
+ ) }),
1164
+ /* @__PURE__ */ jsx(Tooltip, { title: "Align right", children: /* @__PURE__ */ jsx(
1165
+ Button,
1166
+ {
1167
+ size: "small",
1168
+ type: attrs.align === "right" ? "primary" : "text",
1169
+ icon: /* @__PURE__ */ jsx(AlignRightOutlined, {}),
1170
+ onClick: () => setAlign("right")
1171
+ }
1172
+ ) }),
1173
+ /* @__PURE__ */ jsx(Divider, { type: "vertical" }),
1174
+ /* @__PURE__ */ jsx(
1175
+ Dropdown,
1176
+ {
1177
+ trigger: ["click"],
1178
+ menu: {
1179
+ items: IMAGE_WIDTHS.map((w) => ({ key: w.value, label: w.label })),
1180
+ selectedKeys: [attrs.width || "medium"],
1181
+ onClick: ({ key }) => setWidth(key)
1182
+ },
1183
+ children: /* @__PURE__ */ jsxs(Button, { size: "small", type: "text", icon: /* @__PURE__ */ jsx(ColumnWidthOutlined, {}), children: [
1184
+ IMAGE_WIDTHS.find((w) => w.value === (attrs.width || "medium"))?.label,
1185
+ /* @__PURE__ */ jsx(DownOutlined, { style: { fontSize: 10, marginLeft: 4 } })
1186
+ ] })
1187
+ }
1188
+ ),
1189
+ /* @__PURE__ */ jsx(Divider, { type: "vertical" }),
1190
+ /* @__PURE__ */ jsx(Tooltip, { title: "Edit alt text", children: /* @__PURE__ */ jsx(
1191
+ Button,
1192
+ {
1193
+ size: "small",
1194
+ type: "text",
1195
+ icon: /* @__PURE__ */ jsx(EditOutlined, {}),
1196
+ onClick: () => {
1197
+ const alt = window.prompt("Alt text", attrs.alt || "");
1198
+ if (alt !== null) {
1199
+ editor.chain().focus().updateAttributes("ciqImage", { alt }).run();
1200
+ }
1201
+ }
1202
+ }
1203
+ ) }),
1204
+ /* @__PURE__ */ jsx(Tooltip, { title: "Delete image", children: /* @__PURE__ */ jsx(Button, { size: "small", type: "text", danger: true, icon: /* @__PURE__ */ jsx(DeleteOutlined, {}), onClick: remove }) })
1205
+ ]
1206
+ }
1207
+ );
1208
+ }
1209
+ function TableBubble({ editor }) {
1210
+ const cellAttrs = editor.getAttributes("tableCell");
1211
+ const setVAlign = (verticalAlign) => {
1212
+ editor.chain().focus().updateAttributes("tableCell", { verticalAlign }).run();
1213
+ editor.chain().focus().updateAttributes("tableHeader", { verticalAlign }).run();
1214
+ };
1215
+ return /* @__PURE__ */ jsxs(
1216
+ Space,
1217
+ {
1218
+ size: 4,
1219
+ wrap: true,
1220
+ style: {
1221
+ background: "#fff",
1222
+ padding: 4,
1223
+ border: "1px solid var(--tps-line)",
1224
+ borderRadius: 6,
1225
+ boxShadow: "0 8px 24px rgba(0,0,0,0.12)",
1226
+ maxWidth: 720
1227
+ },
1228
+ children: [
1229
+ /* @__PURE__ */ jsx(Button, { size: "small", onClick: () => editor.chain().focus().addColumnBefore().run(), children: "+ col \u2190" }),
1230
+ /* @__PURE__ */ jsx(Button, { size: "small", onClick: () => editor.chain().focus().addColumnAfter().run(), children: "+ col \u2192" }),
1231
+ /* @__PURE__ */ jsx(Button, { size: "small", onClick: () => editor.chain().focus().deleteColumn().run(), children: "\u2212 col" }),
1232
+ /* @__PURE__ */ jsx(Divider, { type: "vertical" }),
1233
+ /* @__PURE__ */ jsx(Button, { size: "small", onClick: () => editor.chain().focus().addRowBefore().run(), children: "+ row \u2191" }),
1234
+ /* @__PURE__ */ jsx(Button, { size: "small", onClick: () => editor.chain().focus().addRowAfter().run(), children: "+ row \u2193" }),
1235
+ /* @__PURE__ */ jsx(Button, { size: "small", onClick: () => editor.chain().focus().deleteRow().run(), children: "\u2212 row" }),
1236
+ /* @__PURE__ */ jsx(Divider, { type: "vertical" }),
1237
+ /* @__PURE__ */ jsx(Button, { size: "small", onClick: () => editor.chain().focus().mergeCells().run(), children: "Merge" }),
1238
+ /* @__PURE__ */ jsx(Button, { size: "small", onClick: () => editor.chain().focus().splitCell().run(), children: "Split" }),
1239
+ /* @__PURE__ */ jsx(Divider, { type: "vertical" }),
1240
+ /* @__PURE__ */ jsx(Tooltip, { title: "Top", children: /* @__PURE__ */ jsx(
1241
+ Button,
1242
+ {
1243
+ size: "small",
1244
+ type: cellAttrs.verticalAlign === "top" ? "primary" : "default",
1245
+ icon: /* @__PURE__ */ jsx(VerticalAlignTopOutlined, {}),
1246
+ onClick: () => setVAlign("top")
1247
+ }
1248
+ ) }),
1249
+ /* @__PURE__ */ jsx(Tooltip, { title: "Middle", children: /* @__PURE__ */ jsx(
1250
+ Button,
1251
+ {
1252
+ size: "small",
1253
+ type: cellAttrs.verticalAlign === "middle" ? "primary" : "default",
1254
+ icon: /* @__PURE__ */ jsx(VerticalAlignMiddleOutlined, {}),
1255
+ onClick: () => setVAlign("middle")
1256
+ }
1257
+ ) }),
1258
+ /* @__PURE__ */ jsx(Tooltip, { title: "Bottom", children: /* @__PURE__ */ jsx(
1259
+ Button,
1260
+ {
1261
+ size: "small",
1262
+ type: cellAttrs.verticalAlign === "bottom" ? "primary" : "default",
1263
+ icon: /* @__PURE__ */ jsx(VerticalAlignBottomOutlined, {}),
1264
+ onClick: () => setVAlign("bottom")
1265
+ }
1266
+ ) }),
1267
+ /* @__PURE__ */ jsx(Divider, { type: "vertical" }),
1268
+ /* @__PURE__ */ jsx(Button, { size: "small", onClick: () => editor.chain().focus().toggleHeaderRow().run(), children: "Header row" }),
1269
+ /* @__PURE__ */ jsx(Button, { size: "small", onClick: () => editor.chain().focus().toggleHeaderColumn().run(), children: "Header col" }),
1270
+ /* @__PURE__ */ jsx(Divider, { type: "vertical" }),
1271
+ /* @__PURE__ */ jsx(Button, { size: "small", danger: true, onClick: () => editor.chain().focus().deleteTable().run(), children: "Delete" })
1272
+ ]
1273
+ }
1274
+ );
1275
+ }
1276
+ function AtomBlockBubble({ editor, onEdit }) {
1277
+ const kind = editor.isActive("shareBlock") ? "share" : editor.isActive("subscribeBlock") ? "subscribe" : null;
1278
+ if (!kind) return null;
1279
+ return /* @__PURE__ */ jsxs(
1280
+ Space,
1281
+ {
1282
+ size: 4,
1283
+ style: {
1284
+ background: "#fff",
1285
+ padding: 4,
1286
+ border: "1px solid var(--tps-line)",
1287
+ borderRadius: 6,
1288
+ boxShadow: "0 8px 24px rgba(0,0,0,0.12)"
1289
+ },
1290
+ children: [
1291
+ /* @__PURE__ */ jsx(Tag, { color: kind === "share" ? "cyan" : "gold", style: { margin: 0 }, children: kind === "share" ? "Share block" : "Subscribe block" }),
1292
+ /* @__PURE__ */ jsx(Button, { size: "small", icon: /* @__PURE__ */ jsx(EditOutlined, {}), onClick: () => onEdit(kind), children: "Edit" }),
1293
+ /* @__PURE__ */ jsx(
1294
+ Button,
1295
+ {
1296
+ size: "small",
1297
+ type: "text",
1298
+ danger: true,
1299
+ icon: /* @__PURE__ */ jsx(DeleteOutlined, {}),
1300
+ onClick: () => editor.chain().focus().deleteSelection().run()
1301
+ }
1302
+ )
1303
+ ]
1304
+ }
1305
+ );
1306
+ }
1307
+ function BlockInsertMenu({ editor, onImage }) {
1308
+ const items = [
1309
+ { key: "h1", label: "Heading 1", run: () => editor.chain().focus().toggleHeading({ level: 1 }).run() },
1310
+ { key: "h2", label: "Heading 2", run: () => editor.chain().focus().toggleHeading({ level: 2 }).run() },
1311
+ { key: "h3", label: "Heading 3", run: () => editor.chain().focus().toggleHeading({ level: 3 }).run() },
1312
+ { key: "ul", label: "Bulleted list", run: () => editor.chain().focus().toggleBulletList().run() },
1313
+ { key: "ol", label: "Numbered list", run: () => editor.chain().focus().toggleOrderedList().run() },
1314
+ { key: "task", label: "Task list", run: () => editor.chain().focus().toggleTaskList().run() },
1315
+ { key: "quote", label: "Quote", run: () => editor.chain().focus().toggleBlockquote().run() },
1316
+ { key: "code", label: "Code block", run: () => editor.chain().focus().toggleCodeBlock().run() },
1317
+ { key: "hr", label: "Divider", run: () => editor.chain().focus().setHorizontalRule().run() },
1318
+ { type: "divider" },
1319
+ { key: "image", label: "Image", run: () => onImage() },
1320
+ { key: "table", label: "Table", run: () => editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run() },
1321
+ { key: "cols2", label: "2 columns", run: () => editor.chain().focus().insertColumns(2).run() },
1322
+ { key: "cols3", label: "3 columns", run: () => editor.chain().focus().insertColumns(3).run() },
1323
+ { key: "callout", label: "Callout", run: () => editor.chain().focus().insertCallout("info").run() },
1324
+ { key: "share", label: "Share buttons", run: () => editor.chain().focus().insertShareBlock().run() },
1325
+ { key: "subscribe", label: "Subscribe form", run: () => editor.chain().focus().insertSubscribeBlock().run() }
1326
+ ];
1327
+ return /* @__PURE__ */ jsx(
1328
+ Dropdown,
1329
+ {
1330
+ trigger: ["click"],
1331
+ placement: "bottomLeft",
1332
+ menu: {
1333
+ items: items.map(
1334
+ (i, idx) => i.type === "divider" ? { type: "divider", key: `d${idx}` } : { key: i.key, label: i.label }
1335
+ ),
1336
+ onClick: ({ key }) => items.find((i) => i.key === key)?.run()
1337
+ },
1338
+ children: /* @__PURE__ */ jsx(Tooltip, { title: "Insert block", children: /* @__PURE__ */ jsx(
1339
+ Button,
1340
+ {
1341
+ size: "small",
1342
+ shape: "circle",
1343
+ icon: /* @__PURE__ */ jsx(PlusOutlined, {}),
1344
+ style: {
1345
+ background: "#fff",
1346
+ borderColor: "var(--tps-line)",
1347
+ boxShadow: "0 4px 12px rgba(0,0,0,0.08)"
1348
+ }
1349
+ }
1350
+ ) })
1351
+ }
1352
+ );
1353
+ }
1354
+ function ColorSwatch({ title, icon, colors, onPick, onClear }) {
1355
+ const [open, setOpen] = useState(false);
1356
+ return /* @__PURE__ */ jsx(
1357
+ Popover,
1358
+ {
1359
+ open,
1360
+ onOpenChange: setOpen,
1361
+ trigger: "click",
1362
+ placement: "bottomLeft",
1363
+ content: /* @__PURE__ */ jsxs(Space, { direction: "vertical", size: 8, style: { width: 200 }, children: [
1364
+ /* @__PURE__ */ jsx("div", { style: { display: "grid", gridTemplateColumns: "repeat(5, 1fr)", gap: 6 }, children: colors.map((c) => /* @__PURE__ */ jsx(
1365
+ "button",
1366
+ {
1367
+ onClick: () => {
1368
+ onPick(c);
1369
+ setOpen(false);
1370
+ },
1371
+ style: {
1372
+ width: 28,
1373
+ height: 28,
1374
+ background: c,
1375
+ border: "1px solid var(--tps-line)",
1376
+ borderRadius: 4,
1377
+ cursor: "pointer"
1378
+ },
1379
+ title: c
1380
+ },
1381
+ c
1382
+ )) }),
1383
+ /* @__PURE__ */ jsx(Button, { size: "small", block: true, onClick: () => {
1384
+ onClear();
1385
+ setOpen(false);
1386
+ }, children: "Clear" })
1387
+ ] }),
1388
+ children: /* @__PURE__ */ jsx(Tooltip, { title, children: /* @__PURE__ */ jsx(Button, { size: "small", type: "text", icon }) })
1389
+ }
1390
+ );
1391
+ }
1392
+ function LinkModal({ open, onClose, editor }) {
1393
+ const [href, setHref] = useState("");
1394
+ useEffect(() => {
1395
+ if (open) setHref(editor.getAttributes("link").href || "");
1396
+ }, [open, editor]);
1397
+ const apply = () => {
1398
+ if (!href) editor.chain().focus().unsetLink().run();
1399
+ else editor.chain().focus().extendMarkRange("link").setLink({ href }).run();
1400
+ onClose();
1401
+ };
1402
+ return /* @__PURE__ */ jsxs(
1403
+ Modal,
1404
+ {
1405
+ title: "Link",
1406
+ open,
1407
+ onCancel: onClose,
1408
+ onOk: apply,
1409
+ okText: "Apply",
1410
+ okButtonProps: { disabled: !href && !editor.isActive("link") },
1411
+ destroyOnClose: true,
1412
+ children: [
1413
+ /* @__PURE__ */ jsx(
1414
+ Input,
1415
+ {
1416
+ placeholder: "https://example.com",
1417
+ value: href,
1418
+ onChange: (e) => setHref(e.target.value),
1419
+ onPressEnter: apply,
1420
+ autoFocus: true
1421
+ }
1422
+ ),
1423
+ /* @__PURE__ */ jsx(Text, { type: "secondary", style: { fontSize: 12 }, children: "Leave blank and Apply to remove the link." })
1424
+ ]
1425
+ }
1426
+ );
1427
+ }
1428
+ function ImageModal({ open, onClose, editor, uploadMedia }) {
1429
+ const [tab, setTab] = useState("upload");
1430
+ const [src, setSrc] = useState("");
1431
+ const [alt, setAlt] = useState("");
1432
+ const [uploading, setUploading] = useState(false);
1433
+ useEffect(() => {
1434
+ if (open) {
1435
+ setTab("upload");
1436
+ setSrc("");
1437
+ setAlt("");
1438
+ }
1439
+ }, [open]);
1440
+ const apply = () => {
1441
+ if (!src) return;
1442
+ editor.chain().focus().insertContent({
1443
+ type: "ciqImage",
1444
+ attrs: { src, alt, align: "center", width: "medium" }
1445
+ }).run();
1446
+ onClose();
1447
+ };
1448
+ const beforeUpload = async (file) => {
1449
+ if (!file.type.startsWith("image/")) {
1450
+ antdMessage.error("Only image files are supported.");
1451
+ return Upload.LIST_IGNORE;
1452
+ }
1453
+ setUploading(true);
1454
+ try {
1455
+ const fd = new FormData();
1456
+ fd.append("file", file);
1457
+ const { url } = await uploadMedia(fd);
1458
+ setSrc(url);
1459
+ if (!alt) setAlt(file.name.replace(/\.[^.]+$/, ""));
1460
+ } catch (err) {
1461
+ antdMessage.error(err.message || "Upload failed");
1462
+ } finally {
1463
+ setUploading(false);
1464
+ }
1465
+ return Upload.LIST_IGNORE;
1466
+ };
1467
+ return /* @__PURE__ */ jsxs(
1468
+ Modal,
1469
+ {
1470
+ title: "Insert image",
1471
+ open,
1472
+ onCancel: onClose,
1473
+ onOk: apply,
1474
+ okText: "Insert",
1475
+ okButtonProps: { disabled: !src },
1476
+ destroyOnClose: true,
1477
+ width: 560,
1478
+ children: [
1479
+ /* @__PURE__ */ jsx(
1480
+ Tabs,
1481
+ {
1482
+ activeKey: tab,
1483
+ onChange: setTab,
1484
+ items: [
1485
+ {
1486
+ key: "upload",
1487
+ label: "Upload",
1488
+ children: /* @__PURE__ */ jsxs(Space, { direction: "vertical", size: 12, style: { width: "100%" }, children: [
1489
+ /* @__PURE__ */ jsxs(
1490
+ Upload.Dragger,
1491
+ {
1492
+ multiple: false,
1493
+ beforeUpload,
1494
+ showUploadList: false,
1495
+ disabled: uploading,
1496
+ accept: "image/*",
1497
+ children: [
1498
+ /* @__PURE__ */ jsx("p", { style: { fontSize: 28, margin: 0 }, children: uploading ? /* @__PURE__ */ jsx(LoadingOutlined, {}) : /* @__PURE__ */ jsx(UploadOutlined, {}) }),
1499
+ /* @__PURE__ */ jsx("p", { style: { marginTop: 4 }, children: uploading ? "Uploading\u2026" : "Click or drag an image" }),
1500
+ /* @__PURE__ */ jsx("p", { style: { color: "var(--tps-muted)", fontSize: 12 }, children: "JPG / PNG / WEBP / GIF / SVG \xB7 max 10MB" })
1501
+ ]
1502
+ }
1503
+ ),
1504
+ /* @__PURE__ */ jsx(
1505
+ Input,
1506
+ {
1507
+ placeholder: "Alt text (for accessibility & SEO)",
1508
+ value: alt,
1509
+ onChange: (e) => setAlt(e.target.value)
1510
+ }
1511
+ ),
1512
+ src && /* @__PURE__ */ jsx(Tag, { color: "green", style: { width: "fit-content" }, children: "Uploaded" })
1513
+ ] })
1514
+ },
1515
+ {
1516
+ key: "url",
1517
+ label: "From URL",
1518
+ children: /* @__PURE__ */ jsxs(Space, { direction: "vertical", size: 12, style: { width: "100%" }, children: [
1519
+ /* @__PURE__ */ jsx(
1520
+ Input,
1521
+ {
1522
+ placeholder: "https://\u2026",
1523
+ value: src,
1524
+ onChange: (e) => setSrc(e.target.value),
1525
+ autoFocus: true
1526
+ }
1527
+ ),
1528
+ /* @__PURE__ */ jsx(
1529
+ Input,
1530
+ {
1531
+ placeholder: "Alt text (for accessibility & SEO)",
1532
+ value: alt,
1533
+ onChange: (e) => setAlt(e.target.value)
1534
+ }
1535
+ )
1536
+ ] })
1537
+ }
1538
+ ]
1539
+ }
1540
+ ),
1541
+ src && /* @__PURE__ */ jsx("div", { style: { marginTop: 12, border: "1px solid var(--tps-line)", padding: 8, borderRadius: 6 }, children: /* @__PURE__ */ jsx("img", { src, alt, style: { maxWidth: "100%", display: "block" } }) })
1542
+ ]
1543
+ }
1544
+ );
1545
+ }
1546
+ function BlockAttributeModal({ kind, open, onClose, editor }) {
1547
+ const [form] = Form.useForm();
1548
+ const isShare = kind === "share";
1549
+ const nodeName = isShare ? "shareBlock" : "subscribeBlock";
1550
+ useEffect(() => {
1551
+ if (!open) return;
1552
+ const attrs = editor.getAttributes(nodeName);
1553
+ form.setFieldsValue(attrs);
1554
+ }, [open, editor, nodeName, form]);
1555
+ const apply = async () => {
1556
+ const values = await form.validateFields();
1557
+ editor.chain().focus().updateAttributes(nodeName, values).run();
1558
+ onClose();
1559
+ };
1560
+ if (!kind) return null;
1561
+ return /* @__PURE__ */ jsx(
1562
+ Modal,
1563
+ {
1564
+ title: isShare ? "Share block" : "Subscribe block",
1565
+ open,
1566
+ onCancel: onClose,
1567
+ onOk: apply,
1568
+ okText: "Apply",
1569
+ destroyOnClose: true,
1570
+ children: /* @__PURE__ */ jsx(Form, { form, layout: "vertical", preserve: false, children: isShare ? /* @__PURE__ */ jsx(Form.Item, { label: "Label", name: "label", children: /* @__PURE__ */ jsx(Input, { placeholder: "Share this page" }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1571
+ /* @__PURE__ */ jsx(Form.Item, { label: "Heading", name: "heading", children: /* @__PURE__ */ jsx(Input, { placeholder: "Stay in the loop" }) }),
1572
+ /* @__PURE__ */ jsx(Form.Item, { label: "Body", name: "body", children: /* @__PURE__ */ jsx(Input.TextArea, { rows: 2, placeholder: "One short email when we publish \u2014 never more." }) }),
1573
+ /* @__PURE__ */ jsx(Form.Item, { label: "Button label", name: "button", children: /* @__PURE__ */ jsx(Input, { placeholder: "Subscribe" }) })
1574
+ ] }) })
1575
+ }
1576
+ );
1577
+ }
1578
+ function EditorStyles() {
1579
+ return /* @__PURE__ */ jsx("style", { jsx: true, global: true, children: `
1580
+ .tiptap {
1581
+ outline: none;
1582
+ font-size: 15px;
1583
+ line-height: 1.65;
1584
+ color: var(--tps-ink);
1585
+ min-height: 100%;
1586
+ }
1587
+ .tiptap > * + * { margin-top: 0.75em; }
1588
+ .tiptap p.is-editor-empty:first-child::before {
1589
+ content: attr(data-placeholder);
1590
+ float: left;
1591
+ color: var(--tps-muted);
1592
+ pointer-events: none;
1593
+ height: 0;
1594
+ }
1595
+ .tiptap h1 { font-size: 28px; font-weight: 800; line-height: 1.2; margin: 1em 0 0.5em; }
1596
+ .tiptap h2 { font-size: 22px; font-weight: 700; line-height: 1.25; margin: 1em 0 0.5em; }
1597
+ .tiptap h3 { font-size: 18px; font-weight: 700; line-height: 1.3; margin: 1em 0 0.4em; }
1598
+ .tiptap h4 { font-size: 16px; font-weight: 700; line-height: 1.35; margin: 1em 0 0.4em; }
1599
+ .tiptap p { margin: 0; }
1600
+ .tiptap ul, .tiptap ol { padding-left: 22px; }
1601
+ .tiptap ul[data-type='taskList'] { list-style: none; padding-left: 0; }
1602
+ .tiptap ul[data-type='taskList'] li { display: flex; gap: 8px; align-items: flex-start; }
1603
+ .tiptap a { color: var(--tps-primary); text-decoration: underline; }
1604
+ .tiptap blockquote {
1605
+ border-left: 3px solid var(--tps-primary);
1606
+ padding-left: 14px;
1607
+ color: var(--tps-muted);
1608
+ font-style: italic;
1609
+ }
1610
+ .tiptap pre.tiptap-code, .tiptap pre {
1611
+ background: #0f172a; color: #e2e8f0; padding: 12px 14px; border-radius: 6px;
1612
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 13px;
1613
+ overflow-x: auto;
1614
+ }
1615
+ .tiptap code {
1616
+ background: var(--tps-bg-soft); padding: 2px 6px; border-radius: 4px; font-size: 0.9em;
1617
+ }
1618
+ .tiptap hr { border: none; border-top: 1px solid var(--tps-line); margin: 1.5em 0; }
1619
+ .tiptap mark { padding: 0 2px; border-radius: 3px; }
1620
+
1621
+ /* Image block with align + width. */
1622
+ .tiptap .tps-block-image { margin: 1em 0; display: flex; }
1623
+ .tiptap .tps-block-image[data-align='left'] { justify-content: flex-start; }
1624
+ .tiptap .tps-block-image[data-align='center'] { justify-content: center; }
1625
+ .tiptap .tps-block-image[data-align='right'] { justify-content: flex-end; }
1626
+ .tiptap .tps-block-image img { max-width: 100%; height: auto; border-radius: 8px; display: block; }
1627
+ .tiptap .tps-block-image.tps-img-small img { width: 40%; }
1628
+ .tiptap .tps-block-image.tps-img-medium img { width: 60%; }
1629
+ .tiptap .tps-block-image.tps-img-large img { width: 80%; }
1630
+ .tiptap .tps-block-image.tps-img-full img { width: 100%; }
1631
+ .tiptap .tps-block-image.ProseMirror-selectednode img,
1632
+ .tiptap .tps-block-image:has(img.ProseMirror-selectednode) img {
1633
+ outline: 2px solid var(--tps-primary); outline-offset: 2px;
1634
+ }
1635
+
1636
+ /* Tables \u2014 alternating rows + resize handles. */
1637
+ .tiptap table.tps-rich-table, .tiptap table {
1638
+ border-collapse: collapse; width: 100%; margin: 1em 0; table-layout: fixed;
1639
+ }
1640
+ .tiptap table td, .tiptap table th {
1641
+ border: 1px solid var(--tps-line); padding: 8px 10px; vertical-align: top;
1642
+ position: relative; min-width: 40px;
1643
+ }
1644
+ .tiptap table th { background: var(--tps-bg-soft); font-weight: 700; text-align: left; }
1645
+ .tiptap table tbody tr:nth-child(even) td { background: rgba(15, 118, 110, 0.025); }
1646
+ .tiptap table .selectedCell::after {
1647
+ content: ''; position: absolute; inset: 0;
1648
+ background: rgba(15, 118, 110, 0.12); pointer-events: none;
1649
+ }
1650
+ .tiptap table .column-resize-handle {
1651
+ position: absolute; right: -2px; top: 0; bottom: -2px; width: 4px;
1652
+ background-color: var(--tps-primary); pointer-events: none;
1653
+ }
1654
+ .tiptap table p { margin: 0; }
1655
+
1656
+ /* Columns. */
1657
+ .tiptap .tps-block-columns {
1658
+ display: grid; gap: 16px; margin: 1em 0;
1659
+ border: 1px dashed var(--tps-line); padding: 12px; border-radius: 8px;
1660
+ }
1661
+ .tiptap .tps-block-columns.tps-cols-2 { grid-template-columns: 1fr 1fr; }
1662
+ .tiptap .tps-block-columns.tps-cols-3 { grid-template-columns: repeat(3, 1fr); }
1663
+ .tiptap .tps-block-columns.tps-cols-4 { grid-template-columns: repeat(4, 1fr); }
1664
+ .tiptap .tps-block-column { padding: 8px; min-height: 40px; border-radius: 6px; }
1665
+ .tiptap .tps-block-column:hover { background: var(--tps-bg-soft); }
1666
+
1667
+ /* Callout. */
1668
+ .tiptap .tps-block-callout {
1669
+ border-left: 4px solid var(--tps-primary);
1670
+ background: rgba(15, 118, 110, 0.05);
1671
+ padding: 12px 16px; border-radius: 6px; margin: 1em 0;
1672
+ }
1673
+ .tiptap .tps-block-callout.tps-callout-success {
1674
+ border-color: #059669; background: rgba(5, 150, 105, 0.06);
1675
+ }
1676
+ .tiptap .tps-block-callout.tps-callout-warning {
1677
+ border-color: #D97706; background: rgba(217, 119, 6, 0.07);
1678
+ }
1679
+ .tiptap .tps-block-callout.tps-callout-danger {
1680
+ border-color: #DC2626; background: rgba(220, 38, 38, 0.06);
1681
+ }
1682
+
1683
+ /* Atom blocks (share, subscribe) \u2014 show as cards in the canvas. */
1684
+ .tiptap .tps-block-share, .tiptap .tps-block-subscribe {
1685
+ border: 1px solid var(--tps-line); border-radius: 8px;
1686
+ padding: 16px; margin: 1em 0; background: var(--tps-bg-soft);
1687
+ }
1688
+ .tiptap .tps-block-share-label, .tiptap .tps-block-subscribe-heading {
1689
+ font-weight: 700; margin-bottom: 8px; color: var(--tps-ink);
1690
+ }
1691
+ .tiptap .tps-block-share-row { display: flex; gap: 8px; flex-wrap: wrap; }
1692
+ .tiptap .tps-block-share-btn {
1693
+ padding: 6px 12px; background: #fff; border: 1px solid var(--tps-line);
1694
+ border-radius: 6px; text-decoration: none; color: var(--tps-ink);
1695
+ font-size: 13px; font-weight: 600;
1696
+ }
1697
+ .tiptap .tps-block-share-btn:hover { border-color: var(--tps-primary); color: var(--tps-primary); }
1698
+ .tiptap .tps-block-subscribe-form { display: flex; gap: 8px; margin-top: 8px; flex-wrap: wrap; }
1699
+ .tiptap .tps-block-subscribe-input {
1700
+ flex: 1; min-width: 200px; padding: 8px 12px;
1701
+ border: 1px solid var(--tps-line); border-radius: 6px; font-size: 14px;
1702
+ }
1703
+ .tiptap .tps-block-subscribe-button {
1704
+ padding: 8px 16px; background: var(--tps-primary); color: #fff;
1705
+ border: 0; border-radius: 6px; font-weight: 600; cursor: pointer;
1706
+ }
1707
+ .tiptap .tps-block-subscribe-button:hover { background: var(--tps-primary-dark); }
1708
+ .tiptap .tps-block-subscribe-body { color: var(--tps-muted); margin: 4px 0 8px; }
1709
+
1710
+ /* Selected atom block highlight. */
1711
+ .tiptap .ProseMirror-selectednode.tps-block-share,
1712
+ .tiptap .ProseMirror-selectednode.tps-block-subscribe {
1713
+ outline: 2px solid var(--tps-primary); outline-offset: 2px;
1714
+ }
1715
+ ` });
1716
+ }
1717
+
1718
+ // src/HistoryPanel.jsx
1719
+ import { useEffect as useEffect2, useMemo as useMemo2, useState as useState2, useTransition } from "react";
1720
+ import {
1721
+ Alert,
1722
+ Button as Button2,
1723
+ Card,
1724
+ Empty,
1725
+ List,
1726
+ Popconfirm,
1727
+ Segmented,
1728
+ Space as Space2,
1729
+ Spin,
1730
+ Tag as Tag2,
1731
+ Tooltip as Tooltip2,
1732
+ Typography as Typography2,
1733
+ App as AntdApp
1734
+ } from "antd";
1735
+ import {
1736
+ ReloadOutlined,
1737
+ HistoryOutlined,
1738
+ RollbackOutlined,
1739
+ UserOutlined,
1740
+ ClockCircleOutlined,
1741
+ EditOutlined as EditOutlined2
1742
+ } from "@ant-design/icons";
1743
+ import { diffLines } from "diff";
1744
+ import dayjs from "dayjs";
1745
+ import relativeTime from "dayjs/plugin/relativeTime";
1746
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1747
+ dayjs.extend(relativeTime);
1748
+ var { Text: Text2, Paragraph } = Typography2;
1749
+ function notConfigured(name) {
1750
+ return () => {
1751
+ throw new Error(
1752
+ `@techrox/page-studio-form: HistoryPanel was used without a \`${name}\` adapter. Pass loadHistory + restoreRevision callbacks.`
1753
+ );
1754
+ };
1755
+ }
1756
+ function HistoryPanel({
1757
+ pageKey,
1758
+ currentPage,
1759
+ onRestored,
1760
+ loadHistory = notConfigured("loadHistory"),
1761
+ restoreRevision = notConfigured("restoreRevision")
1762
+ }) {
1763
+ const { message, modal } = AntdApp.useApp();
1764
+ const [pending, startTransition] = useTransition();
1765
+ const [loading, setLoading] = useState2(false);
1766
+ const [revisions, setRevisions] = useState2([]);
1767
+ const [error, setError] = useState2(null);
1768
+ const [selectedId, setSelectedId] = useState2(null);
1769
+ const [view, setView] = useState2("summary");
1770
+ const load = async () => {
1771
+ setLoading(true);
1772
+ setError(null);
1773
+ try {
1774
+ const { revisions: revisions2 } = await loadHistory(pageKey);
1775
+ setRevisions(revisions2 || []);
1776
+ if (revisions2?.length && !selectedId) {
1777
+ setSelectedId(revisions2[0]._id);
1778
+ }
1779
+ } catch (e) {
1780
+ setError(e.message);
1781
+ } finally {
1782
+ setLoading(false);
1783
+ }
1784
+ };
1785
+ useEffect2(() => {
1786
+ load();
1787
+ }, [pageKey]);
1788
+ const selected = useMemo2(
1789
+ () => revisions.find((r) => r._id === selectedId) || null,
1790
+ [revisions, selectedId]
1791
+ );
1792
+ const onRestore = (rev) => {
1793
+ modal.confirm({
1794
+ title: `Restore version ${rev.version}?`,
1795
+ content: /* @__PURE__ */ jsxs2("span", { children: [
1796
+ "The current page will be replaced with this snapshot from",
1797
+ " ",
1798
+ /* @__PURE__ */ jsx2(Text2, { strong: true, children: dayjs(rev.created_at).format("YYYY-MM-DD HH:mm") }),
1799
+ ". A new revision is logged so you can undo this restore."
1800
+ ] }),
1801
+ okText: "Restore",
1802
+ okButtonProps: { danger: true },
1803
+ onOk: () => new Promise((resolve, reject) => {
1804
+ startTransition(async () => {
1805
+ try {
1806
+ await restoreRevision(pageKey, rev._id);
1807
+ message.success(`Restored version ${rev.version}.`);
1808
+ onRestored?.();
1809
+ await load();
1810
+ resolve();
1811
+ } catch (e) {
1812
+ message.error(e.message || "Restore failed.");
1813
+ reject(e);
1814
+ }
1815
+ });
1816
+ })
1817
+ });
1818
+ };
1819
+ if (loading) {
1820
+ return /* @__PURE__ */ jsx2(Card, { size: "small", children: /* @__PURE__ */ jsxs2("div", { style: { textAlign: "center", padding: 40 }, children: [
1821
+ /* @__PURE__ */ jsx2(Spin, {}),
1822
+ /* @__PURE__ */ jsx2("div", { style: { marginTop: 12, color: "var(--tps-muted)", fontSize: 13 }, children: "Loading history\u2026" })
1823
+ ] }) });
1824
+ }
1825
+ if (error) {
1826
+ return /* @__PURE__ */ jsx2(Alert, { type: "error", showIcon: true, message: "Could not load history", description: error });
1827
+ }
1828
+ if (!revisions.length) {
1829
+ return /* @__PURE__ */ jsx2(Card, { size: "small", children: /* @__PURE__ */ jsx2(
1830
+ Empty,
1831
+ {
1832
+ image: Empty.PRESENTED_IMAGE_SIMPLE,
1833
+ description: /* @__PURE__ */ jsxs2(Space2, { direction: "vertical", size: 4, children: [
1834
+ /* @__PURE__ */ jsx2(Text2, { strong: true, children: "No history yet" }),
1835
+ /* @__PURE__ */ jsx2(Text2, { type: "secondary", style: { fontSize: 13 }, children: "Every save creates a revision. They\u2019ll appear here once you save changes." })
1836
+ ] })
1837
+ }
1838
+ ) });
1839
+ }
1840
+ return /* @__PURE__ */ jsxs2(
1841
+ "div",
1842
+ {
1843
+ style: {
1844
+ display: "grid",
1845
+ gridTemplateColumns: "minmax(260px, 320px) minmax(0, 1fr)",
1846
+ gap: 16
1847
+ },
1848
+ children: [
1849
+ /* @__PURE__ */ jsx2(
1850
+ Card,
1851
+ {
1852
+ size: "small",
1853
+ title: /* @__PURE__ */ jsxs2(Space2, { children: [
1854
+ /* @__PURE__ */ jsx2(HistoryOutlined, {}),
1855
+ /* @__PURE__ */ jsxs2("span", { children: [
1856
+ revisions.length,
1857
+ " revision",
1858
+ revisions.length === 1 ? "" : "s"
1859
+ ] })
1860
+ ] }),
1861
+ extra: /* @__PURE__ */ jsx2(Tooltip2, { title: "Refresh", children: /* @__PURE__ */ jsx2(Button2, { size: "small", type: "text", icon: /* @__PURE__ */ jsx2(ReloadOutlined, {}), onClick: load }) }),
1862
+ styles: { body: { padding: 0, maxHeight: "70vh", overflowY: "auto" } },
1863
+ children: /* @__PURE__ */ jsx2(
1864
+ List,
1865
+ {
1866
+ dataSource: revisions,
1867
+ renderItem: (r, i) => {
1868
+ const isSelected = r._id === selectedId;
1869
+ return /* @__PURE__ */ jsx2(
1870
+ List.Item,
1871
+ {
1872
+ onClick: () => setSelectedId(r._id),
1873
+ style: {
1874
+ padding: "12px 16px",
1875
+ cursor: "pointer",
1876
+ background: isSelected ? "rgba(15, 118, 110, 0.08)" : "transparent",
1877
+ borderLeft: isSelected ? "3px solid var(--tps-primary)" : "3px solid transparent"
1878
+ },
1879
+ children: /* @__PURE__ */ jsxs2("div", { style: { width: "100%" }, children: [
1880
+ /* @__PURE__ */ jsxs2(Space2, { size: 6, wrap: true, children: [
1881
+ /* @__PURE__ */ jsxs2(Tag2, { color: r.action === "restore" ? "orange" : "geekblue", children: [
1882
+ "v",
1883
+ r.version
1884
+ ] }),
1885
+ i === 0 && /* @__PURE__ */ jsx2(Tag2, { color: "green", children: "latest" }),
1886
+ r.action === "restore" && /* @__PURE__ */ jsx2(Tag2, { icon: /* @__PURE__ */ jsx2(RollbackOutlined, {}), color: "orange", children: "restore" }),
1887
+ r.snapshot?.published === false && /* @__PURE__ */ jsx2(Tag2, { children: "draft" })
1888
+ ] }),
1889
+ /* @__PURE__ */ jsx2("div", { style: { fontSize: 13, marginTop: 6, fontWeight: 500 }, children: r.snapshot?.title || /* @__PURE__ */ jsx2(Text2, { type: "secondary", children: "(no title)" }) }),
1890
+ /* @__PURE__ */ jsxs2(Space2, { size: 10, style: { fontSize: 12, color: "var(--tps-muted)", marginTop: 4 }, wrap: true, children: [
1891
+ /* @__PURE__ */ jsx2(Tooltip2, { title: dayjs(r.created_at).format("YYYY-MM-DD HH:mm:ss"), children: /* @__PURE__ */ jsxs2("span", { children: [
1892
+ /* @__PURE__ */ jsx2(ClockCircleOutlined, {}),
1893
+ " ",
1894
+ dayjs(r.created_at).fromNow()
1895
+ ] }) }),
1896
+ r.created_by_email && /* @__PURE__ */ jsx2(Tooltip2, { title: r.created_by_email, children: /* @__PURE__ */ jsxs2("span", { children: [
1897
+ /* @__PURE__ */ jsx2(UserOutlined, {}),
1898
+ " ",
1899
+ abbreviateEmail(r.created_by_email)
1900
+ ] }) })
1901
+ ] })
1902
+ ] })
1903
+ }
1904
+ );
1905
+ }
1906
+ }
1907
+ )
1908
+ }
1909
+ ),
1910
+ /* @__PURE__ */ jsx2("div", { children: selected ? /* @__PURE__ */ jsx2(
1911
+ RevisionDetail,
1912
+ {
1913
+ revision: selected,
1914
+ currentPage,
1915
+ view,
1916
+ setView,
1917
+ onRestore: () => onRestore(selected),
1918
+ restoring: pending
1919
+ }
1920
+ ) : /* @__PURE__ */ jsx2(Card, { size: "small", children: /* @__PURE__ */ jsx2(Text2, { type: "secondary", children: "Select a revision on the left to see its diff." }) }) })
1921
+ ]
1922
+ }
1923
+ );
1924
+ }
1925
+ function RevisionDetail({ revision, currentPage, view, setView, onRestore, restoring }) {
1926
+ const left = currentPage || {};
1927
+ const right = revision.snapshot || {};
1928
+ const summary = useMemo2(() => fieldChangeSummary(left, right), [left, right]);
1929
+ const jsonDiff = useMemo2(() => makeJsonDiff(left, right), [left, right]);
1930
+ return /* @__PURE__ */ jsxs2(
1931
+ Card,
1932
+ {
1933
+ size: "small",
1934
+ title: /* @__PURE__ */ jsxs2(Space2, { children: [
1935
+ /* @__PURE__ */ jsxs2(Tag2, { color: revision.action === "restore" ? "orange" : "geekblue", children: [
1936
+ "Version ",
1937
+ revision.version
1938
+ ] }),
1939
+ /* @__PURE__ */ jsx2(Text2, { type: "secondary", style: { fontSize: 13 }, children: dayjs(revision.created_at).format("YYYY-MM-DD HH:mm:ss") }),
1940
+ revision.created_by_email && /* @__PURE__ */ jsxs2(Text2, { type: "secondary", style: { fontSize: 13 }, children: [
1941
+ "by ",
1942
+ revision.created_by_email
1943
+ ] })
1944
+ ] }),
1945
+ extra: /* @__PURE__ */ jsxs2(Space2, { children: [
1946
+ /* @__PURE__ */ jsx2(
1947
+ Segmented,
1948
+ {
1949
+ size: "small",
1950
+ value: view,
1951
+ onChange: setView,
1952
+ options: [
1953
+ { label: "Summary", value: "summary" },
1954
+ { label: "JSON diff", value: "json" }
1955
+ ]
1956
+ }
1957
+ ),
1958
+ /* @__PURE__ */ jsx2(
1959
+ Popconfirm,
1960
+ {
1961
+ title: "Restore this version?",
1962
+ description: "A new revision will be logged so you can undo.",
1963
+ onConfirm: onRestore,
1964
+ okText: "Restore",
1965
+ okButtonProps: { danger: true },
1966
+ children: /* @__PURE__ */ jsx2(Button2, { danger: true, icon: /* @__PURE__ */ jsx2(RollbackOutlined, {}), loading: restoring, children: "Restore" })
1967
+ }
1968
+ )
1969
+ ] }),
1970
+ children: [
1971
+ /* @__PURE__ */ jsxs2(Paragraph, { type: "secondary", style: { fontSize: 12, marginBottom: 12 }, children: [
1972
+ "Comparing ",
1973
+ /* @__PURE__ */ jsx2(Text2, { strong: true, children: "current" }),
1974
+ " (left) \u2192 ",
1975
+ /* @__PURE__ */ jsx2(Text2, { strong: true, children: "this revision" }),
1976
+ " (right). Restore replaces the current page with the right side."
1977
+ ] }),
1978
+ view === "summary" ? /* @__PURE__ */ jsx2(SummaryView, { summary }) : /* @__PURE__ */ jsx2(JsonDiffView, { diff: jsonDiff })
1979
+ ]
1980
+ }
1981
+ );
1982
+ }
1983
+ function SummaryView({ summary }) {
1984
+ if (!summary.length) {
1985
+ return /* @__PURE__ */ jsx2(Alert, { type: "info", showIcon: true, message: "No differences \u2014 this revision matches the current page." });
1986
+ }
1987
+ return /* @__PURE__ */ jsx2(Space2, { direction: "vertical", size: 10, style: { width: "100%" }, children: summary.map((row) => /* @__PURE__ */ jsxs2(Card, { size: "small", styles: { body: { padding: 12 } }, children: [
1988
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 6 }, children: [
1989
+ /* @__PURE__ */ jsxs2(Space2, { children: [
1990
+ /* @__PURE__ */ jsx2(EditOutlined2, { style: { color: "var(--tps-primary)" } }),
1991
+ /* @__PURE__ */ jsx2(Text2, { code: true, style: { fontSize: 12 }, children: row.path })
1992
+ ] }),
1993
+ /* @__PURE__ */ jsx2(Tag2, { color: row.kind === "changed" ? "gold" : row.kind === "added" ? "green" : "red", children: row.kind })
1994
+ ] }),
1995
+ /* @__PURE__ */ jsxs2(
1996
+ "div",
1997
+ {
1998
+ style: {
1999
+ display: "grid",
2000
+ gridTemplateColumns: "minmax(0, 1fr) minmax(0, 1fr)",
2001
+ gap: 10
2002
+ },
2003
+ children: [
2004
+ /* @__PURE__ */ jsx2(DiffCell, { label: "Current", value: row.current, kind: "current" }),
2005
+ /* @__PURE__ */ jsx2(DiffCell, { label: "This revision", value: row.next, kind: "next" })
2006
+ ]
2007
+ }
2008
+ )
2009
+ ] }, row.path)) });
2010
+ }
2011
+ function DiffCell({ label, value, kind }) {
2012
+ return /* @__PURE__ */ jsxs2(
2013
+ "div",
2014
+ {
2015
+ style: {
2016
+ background: kind === "current" ? "rgba(220,38,38,0.06)" : "rgba(5,150,105,0.06)",
2017
+ border: "1px solid var(--tps-line)",
2018
+ borderRadius: 6,
2019
+ padding: 8
2020
+ },
2021
+ children: [
2022
+ /* @__PURE__ */ jsx2("div", { style: { fontSize: 11, color: "var(--tps-muted)", marginBottom: 4 }, children: label }),
2023
+ /* @__PURE__ */ jsx2(
2024
+ "pre",
2025
+ {
2026
+ style: {
2027
+ margin: 0,
2028
+ fontSize: 12,
2029
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
2030
+ whiteSpace: "pre-wrap",
2031
+ wordBreak: "break-word",
2032
+ color: "var(--tps-ink)"
2033
+ },
2034
+ children: formatScalar(value)
2035
+ }
2036
+ )
2037
+ ]
2038
+ }
2039
+ );
2040
+ }
2041
+ function JsonDiffView({ diff }) {
2042
+ return /* @__PURE__ */ jsx2(
2043
+ "div",
2044
+ {
2045
+ style: {
2046
+ background: "#0F172A",
2047
+ borderRadius: 6,
2048
+ fontSize: 12,
2049
+ lineHeight: 1.6,
2050
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
2051
+ maxHeight: "60vh",
2052
+ overflow: "auto",
2053
+ padding: 14,
2054
+ // Constrain to parent so long lines wrap instead of forcing the
2055
+ // History tab horizontally (prior bug: the diff blew past the card).
2056
+ maxWidth: "100%"
2057
+ },
2058
+ children: diff.map((part, i) => {
2059
+ const bg = part.added ? "rgba(16,185,129,0.18)" : part.removed ? "rgba(239,68,68,0.20)" : "transparent";
2060
+ const color = part.added ? "#86efac" : part.removed ? "#fca5a5" : "#cbd5e1";
2061
+ const prefix = part.added ? "+ " : part.removed ? "- " : " ";
2062
+ return /* @__PURE__ */ jsx2("div", { style: { background: bg, color }, children: part.value.split("\n").filter((l, idx, arr) => !(l === "" && idx === arr.length - 1)).map((line, j) => /* @__PURE__ */ jsxs2(
2063
+ "div",
2064
+ {
2065
+ style: {
2066
+ padding: "0 6px",
2067
+ whiteSpace: "pre-wrap",
2068
+ wordBreak: "break-word",
2069
+ overflowWrap: "anywhere"
2070
+ },
2071
+ children: [
2072
+ prefix,
2073
+ line
2074
+ ]
2075
+ },
2076
+ j
2077
+ )) }, i);
2078
+ })
2079
+ }
2080
+ );
2081
+ }
2082
+ function makeJsonDiff(left, right) {
2083
+ const a = JSON.stringify(snapshot(left), null, 2);
2084
+ const b = JSON.stringify(snapshot(right), null, 2);
2085
+ return diffLines(a, b);
2086
+ }
2087
+ function snapshot(p) {
2088
+ return {
2089
+ title: p?.title || "",
2090
+ published: p?.published !== false,
2091
+ seo: p?.seo || {},
2092
+ content: p?.content || {}
2093
+ };
2094
+ }
2095
+ function fieldChangeSummary(left, right) {
2096
+ const out = [];
2097
+ const a = snapshot(left);
2098
+ const b = snapshot(right);
2099
+ if (a.title !== b.title) {
2100
+ out.push({ path: "title", kind: classify(a.title, b.title), current: a.title, next: b.title });
2101
+ }
2102
+ if (a.published !== b.published) {
2103
+ out.push({
2104
+ path: "published",
2105
+ kind: "changed",
2106
+ current: a.published,
2107
+ next: b.published
2108
+ });
2109
+ }
2110
+ for (const k of unionKeys(a.seo, b.seo)) {
2111
+ const av = a.seo[k];
2112
+ const bv = b.seo[k];
2113
+ if (!equal(av, bv)) {
2114
+ out.push({ path: `seo.${k}`, kind: classify(av, bv), current: av, next: bv });
2115
+ }
2116
+ }
2117
+ for (const k of unionKeys(a.content, b.content)) {
2118
+ const av = a.content[k];
2119
+ const bv = b.content[k];
2120
+ if (!equal(av, bv)) {
2121
+ out.push({ path: `content.${k}`, kind: classify(av, bv), current: av, next: bv });
2122
+ }
2123
+ }
2124
+ return out;
2125
+ }
2126
+ function unionKeys(a, b) {
2127
+ const s = /* @__PURE__ */ new Set([...Object.keys(a || {}), ...Object.keys(b || {})]);
2128
+ return Array.from(s).sort();
2129
+ }
2130
+ function classify(a, b) {
2131
+ const aEmpty = isEmpty(a);
2132
+ const bEmpty = isEmpty(b);
2133
+ if (aEmpty && !bEmpty) return "added";
2134
+ if (!aEmpty && bEmpty) return "removed";
2135
+ return "changed";
2136
+ }
2137
+ function isEmpty(v) {
2138
+ if (v == null) return true;
2139
+ if (typeof v === "string") return v.trim() === "";
2140
+ if (Array.isArray(v)) return v.length === 0;
2141
+ if (typeof v === "object") return Object.keys(v).length === 0;
2142
+ return false;
2143
+ }
2144
+ function equal(a, b) {
2145
+ if (a === b) return true;
2146
+ if (a == null || b == null) return a === b;
2147
+ if (typeof a !== typeof b) return false;
2148
+ if (Array.isArray(a) || Array.isArray(b) || typeof a === "object") {
2149
+ return JSON.stringify(a) === JSON.stringify(b);
2150
+ }
2151
+ return false;
2152
+ }
2153
+ function formatScalar(v) {
2154
+ if (v === void 0 || v === null) return "\u2014";
2155
+ if (typeof v === "string") return v || "\u2014";
2156
+ if (typeof v === "boolean") return v ? "true" : "false";
2157
+ if (Array.isArray(v) && v.every((x) => typeof x === "string")) {
2158
+ return v.length ? v.join("\n") : "\u2014";
2159
+ }
2160
+ return JSON.stringify(v, null, 2);
2161
+ }
2162
+ function abbreviateEmail(email) {
2163
+ if (!email) return "";
2164
+ const [name, domain] = email.split("@");
2165
+ if (!domain) return email;
2166
+ return `${name}@${domain.split(".")[0]}`;
2167
+ }
2168
+
2169
+ // src/PageStudioForm.jsx
2170
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
2171
+ var { Text: Text3, Paragraph: Paragraph2 } = Typography3;
2172
+ function DefaultLink({ href, target, children, ...rest }) {
2173
+ return /* @__PURE__ */ jsx3(
2174
+ "a",
2175
+ {
2176
+ href,
2177
+ target,
2178
+ rel: target === "_blank" ? "noopener noreferrer" : void 0,
2179
+ ...rest,
2180
+ children
2181
+ }
2182
+ );
2183
+ }
2184
+ function notConfigured2(name) {
2185
+ return () => {
2186
+ throw new Error(
2187
+ `@techrox/page-studio-form: missing required adapter method \`${name}\`.`
2188
+ );
2189
+ };
2190
+ }
2191
+ var FormCtx = createContext({ LinkComponent: DefaultLink, uploadMedia: void 0 });
2192
+ var useFormCtx = () => useContext(FormCtx);
2193
+ function PageStudioForm({
2194
+ pageKey,
2195
+ initialPage,
2196
+ loadError,
2197
+ schema,
2198
+ contentDefaults = {},
2199
+ livePath,
2200
+ homeHref = "/admin/pages",
2201
+ homeLabel = "All pages",
2202
+ LinkComponent = DefaultLink,
2203
+ adapter = {},
2204
+ onSaved,
2205
+ onDeleted,
2206
+ onRestored
2207
+ }) {
2208
+ const {
2209
+ savePage = notConfigured2("savePage"),
2210
+ deletePage = notConfigured2("deletePage"),
2211
+ loadHistory,
2212
+ restoreRevision,
2213
+ uploadMedia
2214
+ } = adapter;
2215
+ const { message } = AntdApp2.useApp();
2216
+ const [form] = Form2.useForm();
2217
+ const [pending, startTransition] = useTransition2();
2218
+ const [savedAt, setSavedAt] = useState3(initialPage?.updated_at || null);
2219
+ const [previewKey, setPreviewKey] = useState3(0);
2220
+ const defaults = contentDefaults;
2221
+ const initialValues = {
2222
+ title: initialPage?.title || "",
2223
+ published: initialPage?.published ?? true,
2224
+ seo_title: initialPage?.seo?.title || "",
2225
+ seo_description: initialPage?.seo?.description || "",
2226
+ seo_og_image: initialPage?.seo?.og_image || "",
2227
+ seo_noindex: initialPage?.seo?.noindex || false,
2228
+ content: { ...defaults, ...initialPage?.content || {} }
2229
+ };
2230
+ useEffect3(() => {
2231
+ form.setFieldsValue(initialValues);
2232
+ setSavedAt(initialPage?.updated_at || null);
2233
+ setPreviewKey((k) => k + 1);
2234
+ }, [initialPage?.updated_at]);
2235
+ const onFinish = (values) => {
2236
+ const fallback = (v, prev) => v === void 0 ? prev : v;
2237
+ const payload = {
2238
+ title: (fallback(values.title, initialPage?.title) || "").trim(),
2239
+ seo: {
2240
+ title: fallback(values.seo_title, initialPage?.seo?.title) || "",
2241
+ description: fallback(values.seo_description, initialPage?.seo?.description) || "",
2242
+ og_image: fallback(values.seo_og_image, initialPage?.seo?.og_image) || "",
2243
+ noindex: !!fallback(values.seo_noindex, initialPage?.seo?.noindex)
2244
+ },
2245
+ content: pruneContent(values.content || {}),
2246
+ published: !!fallback(values.published, initialPage?.published ?? true)
2247
+ };
2248
+ startTransition(async () => {
2249
+ try {
2250
+ const result = await savePage(pageKey, payload);
2251
+ const page = result?.page;
2252
+ message.success("Saved. Public page is being revalidated.");
2253
+ if (page?.updated_at) setSavedAt(page.updated_at);
2254
+ onSaved?.(page);
2255
+ } catch (err) {
2256
+ message.error(err.message || "Failed to save.");
2257
+ }
2258
+ });
2259
+ };
2260
+ const onDelete = () => {
2261
+ startTransition(async () => {
2262
+ try {
2263
+ await deletePage(pageKey);
2264
+ message.success("Override removed. Public page now uses the static defaults.");
2265
+ onDeleted?.();
2266
+ } catch (err) {
2267
+ message.error(err.message || "Failed to delete.");
2268
+ }
2269
+ });
2270
+ };
2271
+ return /* @__PURE__ */ jsx3(FormCtx.Provider, { value: { LinkComponent, uploadMedia }, children: /* @__PURE__ */ jsxs3("div", { style: { padding: 24, maxWidth: 1280 }, children: [
2272
+ /* @__PURE__ */ jsxs3(
2273
+ LinkComponent,
2274
+ {
2275
+ href: homeHref,
2276
+ style: {
2277
+ display: "inline-flex",
2278
+ alignItems: "center",
2279
+ gap: 6,
2280
+ fontSize: 13,
2281
+ color: "var(--tps-muted)",
2282
+ marginBottom: 16
2283
+ },
2284
+ children: [
2285
+ /* @__PURE__ */ jsx3(ArrowLeftOutlined, {}),
2286
+ " ",
2287
+ homeLabel
2288
+ ]
2289
+ }
2290
+ ),
2291
+ /* @__PURE__ */ jsxs3(
2292
+ Form2,
2293
+ {
2294
+ form,
2295
+ layout: "vertical",
2296
+ onFinish,
2297
+ initialValues,
2298
+ children: [
2299
+ /* @__PURE__ */ jsxs3(
2300
+ "div",
2301
+ {
2302
+ style: {
2303
+ display: "flex",
2304
+ justifyContent: "space-between",
2305
+ alignItems: "flex-start",
2306
+ gap: 16,
2307
+ flexWrap: "wrap"
2308
+ },
2309
+ children: [
2310
+ /* @__PURE__ */ jsxs3("div", { children: [
2311
+ /* @__PURE__ */ jsx3("h1", { className: "tps-h3", style: { marginBottom: 4 }, children: initialPage?.title || pageKey }),
2312
+ /* @__PURE__ */ jsxs3(Space3, { size: 8, wrap: true, children: [
2313
+ /* @__PURE__ */ jsx3(Text3, { type: "secondary", children: /* @__PURE__ */ jsx3(Text3, { code: true, children: pageKey }) }),
2314
+ savedAt && /* @__PURE__ */ jsxs3(Text3, { type: "secondary", style: { fontSize: 12 }, children: [
2315
+ "Last saved ",
2316
+ new Date(savedAt).toLocaleString()
2317
+ ] })
2318
+ ] })
2319
+ ] }),
2320
+ /* @__PURE__ */ jsxs3(Space3, { wrap: true, align: "center", children: [
2321
+ /* @__PURE__ */ jsx3(
2322
+ Form2.Item,
2323
+ {
2324
+ name: "published",
2325
+ valuePropName: "checked",
2326
+ style: { marginBottom: 0 },
2327
+ children: /* @__PURE__ */ jsx3(PublishToggle, {})
2328
+ }
2329
+ ),
2330
+ livePath && /* @__PURE__ */ jsx3(LinkComponent, { href: livePath, target: "_blank", children: /* @__PURE__ */ jsx3(Button3, { icon: /* @__PURE__ */ jsx3(EyeOutlined, {}), children: "View live" }) }),
2331
+ /* @__PURE__ */ jsx3(
2332
+ Popconfirm2,
2333
+ {
2334
+ title: "Remove this override?",
2335
+ description: "The public page will fall back to the static defaults.",
2336
+ onConfirm: onDelete,
2337
+ okText: "Remove",
2338
+ cancelText: "Cancel",
2339
+ children: /* @__PURE__ */ jsx3(Button3, { danger: true, icon: /* @__PURE__ */ jsx3(DeleteOutlined2, {}), disabled: pending, children: "Remove override" })
2340
+ }
2341
+ ),
2342
+ /* @__PURE__ */ jsx3(
2343
+ Button3,
2344
+ {
2345
+ type: "primary",
2346
+ icon: /* @__PURE__ */ jsx3(SaveOutlined, {}),
2347
+ loading: pending,
2348
+ onClick: () => form.submit(),
2349
+ children: "Save"
2350
+ }
2351
+ )
2352
+ ] })
2353
+ ]
2354
+ }
2355
+ ),
2356
+ loadError && /* @__PURE__ */ jsx3(
2357
+ Alert2,
2358
+ {
2359
+ type: "error",
2360
+ showIcon: true,
2361
+ message: "Could not load page",
2362
+ description: loadError,
2363
+ style: { margin: "16px 0" }
2364
+ }
2365
+ ),
2366
+ /* @__PURE__ */ jsx3(
2367
+ Tabs2,
2368
+ {
2369
+ defaultActiveKey: "content",
2370
+ style: { marginTop: 24 },
2371
+ items: [
2372
+ {
2373
+ key: "content",
2374
+ label: "Content",
2375
+ forceRender: true,
2376
+ children: schema ? /* @__PURE__ */ jsx3(SchemaEditor, { schema, defaults }) : /* @__PURE__ */ jsx3(
2377
+ Alert2,
2378
+ {
2379
+ type: "info",
2380
+ showIcon: true,
2381
+ message: "No structured content for this page key.",
2382
+ description: "SEO can still be edited on the SEO tab."
2383
+ }
2384
+ )
2385
+ },
2386
+ {
2387
+ key: "preview",
2388
+ label: "Live preview",
2389
+ children: livePath ? /* @__PURE__ */ jsx3(LivePreview, { path: livePath, previewKey }) : /* @__PURE__ */ jsx3(Alert2, { type: "info", message: "No public path mapped for this key." })
2390
+ },
2391
+ {
2392
+ key: "history",
2393
+ label: "History",
2394
+ children: /* @__PURE__ */ jsx3(
2395
+ HistoryPanel,
2396
+ {
2397
+ pageKey,
2398
+ currentPage: initialPage,
2399
+ loadHistory,
2400
+ restoreRevision,
2401
+ onRestored
2402
+ }
2403
+ )
2404
+ },
2405
+ {
2406
+ key: "seo",
2407
+ label: "SEO",
2408
+ forceRender: true,
2409
+ children: /* @__PURE__ */ jsx3(SeoFields, {})
2410
+ }
2411
+ ]
2412
+ }
2413
+ ),
2414
+ /* @__PURE__ */ jsx3(Form2.Item, { name: "title", hidden: true, children: /* @__PURE__ */ jsx3(Input2, {}) })
2415
+ ]
2416
+ }
2417
+ )
2418
+ ] }) });
2419
+ }
2420
+ function PublishToggle({ value, onChange }) {
2421
+ return /* @__PURE__ */ jsxs3(Space3, { size: 6, children: [
2422
+ /* @__PURE__ */ jsx3(Switch, { checked: !!value, onChange }),
2423
+ /* @__PURE__ */ jsx3(Text3, { style: { fontSize: 12, color: "var(--tps-muted)" }, children: value ? "Published" : "Draft" })
2424
+ ] });
2425
+ }
2426
+ function pruneContent(content) {
2427
+ const out = {};
2428
+ for (const [k, v] of Object.entries(content)) {
2429
+ if (v === void 0 || v === null) continue;
2430
+ if (typeof v === "string" && v.trim() === "") continue;
2431
+ if (Array.isArray(v) && v.length === 0) continue;
2432
+ out[k] = v;
2433
+ }
2434
+ return out;
2435
+ }
2436
+ function SeoFields() {
2437
+ return /* @__PURE__ */ jsxs3(Card2, { size: "small", children: [
2438
+ /* @__PURE__ */ jsx3(
2439
+ Form2.Item,
2440
+ {
2441
+ label: "SEO title",
2442
+ name: "seo_title",
2443
+ extra: "Used in <title> and Open Graph. Leave blank to use the static default.",
2444
+ children: /* @__PURE__ */ jsx3(Input2, { maxLength: 70, showCount: true })
2445
+ }
2446
+ ),
2447
+ /* @__PURE__ */ jsx3(
2448
+ Form2.Item,
2449
+ {
2450
+ label: "Meta description",
2451
+ name: "seo_description",
2452
+ extra: "Recommended 140\u2013160 characters.",
2453
+ children: /* @__PURE__ */ jsx3(Input2.TextArea, { rows: 3, maxLength: 170, showCount: true })
2454
+ }
2455
+ ),
2456
+ /* @__PURE__ */ jsx3(Form2.Item, { label: "Open Graph image URL", name: "seo_og_image", children: /* @__PURE__ */ jsx3(Input2, { placeholder: "https://pagestudio.dev/og-image.png" }) }),
2457
+ /* @__PURE__ */ jsx3(Form2.Item, { label: "Hide from search engines", name: "seo_noindex", valuePropName: "checked", children: /* @__PURE__ */ jsx3(Switch, {}) })
2458
+ ] });
2459
+ }
2460
+ function SchemaEditor({ schema, defaults }) {
2461
+ return /* @__PURE__ */ jsx3(Space3, { direction: "vertical", size: 20, style: { width: "100%" }, children: schema.map((section) => /* @__PURE__ */ jsx3(SectionBlock, { section, defaults }, section.title)) });
2462
+ }
2463
+ function SectionBlock({ section, defaults }) {
2464
+ return /* @__PURE__ */ jsxs3(
2465
+ Card2,
2466
+ {
2467
+ size: "small",
2468
+ title: /* @__PURE__ */ jsx3(
2469
+ "span",
2470
+ {
2471
+ style: {
2472
+ fontSize: 11,
2473
+ fontWeight: 700,
2474
+ letterSpacing: 1.5,
2475
+ color: "var(--tps-accent-dark)"
2476
+ },
2477
+ children: section.title.toUpperCase()
2478
+ }
2479
+ ),
2480
+ children: [
2481
+ section.help && /* @__PURE__ */ jsx3(Paragraph2, { type: "secondary", style: { fontSize: 13, marginTop: 0 }, children: section.help }),
2482
+ section.fields.map((f) => /* @__PURE__ */ jsx3(FieldRenderer, { field: f, parent: ["content"], defaults }, f.name))
2483
+ ]
2484
+ }
2485
+ );
2486
+ }
2487
+ function FieldRenderer({ field, parent, defaults }) {
2488
+ const namePath = [...parent, field.name];
2489
+ const placeholder = placeholderFor(field, defaults?.[field.name]);
2490
+ if (field.type === "text") {
2491
+ return /* @__PURE__ */ jsx3(Form2.Item, { label: field.label, name: namePath, extra: field.help, children: /* @__PURE__ */ jsx3(Input2, { placeholder }) });
2492
+ }
2493
+ if (field.type === "textarea" || field.type === "html-text") {
2494
+ return /* @__PURE__ */ jsx3(Form2.Item, { label: field.label, name: namePath, extra: field.help, children: /* @__PURE__ */ jsx3(Input2.TextArea, { rows: field.rows || 3, placeholder }) });
2495
+ }
2496
+ if (field.type === "richtext") {
2497
+ return /* @__PURE__ */ jsx3(
2498
+ Form2.Item,
2499
+ {
2500
+ label: field.label || void 0,
2501
+ name: namePath,
2502
+ extra: field.help,
2503
+ valuePropName: "value",
2504
+ trigger: "onChange",
2505
+ children: /* @__PURE__ */ jsx3(RichTextField, { placeholder })
2506
+ }
2507
+ );
2508
+ }
2509
+ if (field.type === "list") {
2510
+ return /* @__PURE__ */ jsx3(
2511
+ Form2.Item,
2512
+ {
2513
+ label: field.label,
2514
+ name: namePath,
2515
+ extra: field.help,
2516
+ getValueFromEvent: (e) => e.target.value.split("\n").map((s) => s.trim()).filter(Boolean),
2517
+ getValueProps: (v) => ({ value: Array.isArray(v) ? v.join("\n") : v || "" }),
2518
+ children: /* @__PURE__ */ jsx3(Input2.TextArea, { rows: field.rows || 4, placeholder })
2519
+ }
2520
+ );
2521
+ }
2522
+ if (field.type === "list-csv") {
2523
+ return /* @__PURE__ */ jsx3(
2524
+ Form2.Item,
2525
+ {
2526
+ label: field.label,
2527
+ name: namePath,
2528
+ extra: field.help,
2529
+ getValueFromEvent: (e) => e.target.value.split(",").map((s) => s.trim()).filter(Boolean),
2530
+ getValueProps: (v) => ({ value: Array.isArray(v) ? v.join(", ") : v || "" }),
2531
+ children: /* @__PURE__ */ jsx3(Input2, { placeholder })
2532
+ }
2533
+ );
2534
+ }
2535
+ if (field.type === "repeater") {
2536
+ return /* @__PURE__ */ jsx3(Repeater, { field, namePath, defaults: defaults?.[field.name] });
2537
+ }
2538
+ return /* @__PURE__ */ jsx3(
2539
+ Alert2,
2540
+ {
2541
+ type: "warning",
2542
+ showIcon: true,
2543
+ message: `Unknown field type "${field.type}" for ${field.name}`,
2544
+ style: { marginBottom: 12 }
2545
+ }
2546
+ );
2547
+ }
2548
+ function Repeater({ field, namePath, defaults }) {
2549
+ const sensors = useSensors(
2550
+ useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
2551
+ useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
2552
+ );
2553
+ return /* @__PURE__ */ jsx3(Form2.List, { name: namePath, children: (fields, { add, remove, move }) => {
2554
+ const ids = fields.map((f) => f.key);
2555
+ const onDragEnd = (e) => {
2556
+ const { active, over } = e;
2557
+ if (!over || active.id === over.id) return;
2558
+ const from = ids.indexOf(active.id);
2559
+ const to = ids.indexOf(over.id);
2560
+ if (from < 0 || to < 0) return;
2561
+ move(from, to);
2562
+ };
2563
+ return /* @__PURE__ */ jsxs3("div", { children: [
2564
+ field.label && /* @__PURE__ */ jsx3(Text3, { strong: true, style: { display: "block", marginBottom: 8 }, children: field.label }),
2565
+ field.help && /* @__PURE__ */ jsx3(Paragraph2, { type: "secondary", style: { fontSize: 13, marginTop: 0 }, children: field.help }),
2566
+ /* @__PURE__ */ jsx3(
2567
+ DndContext,
2568
+ {
2569
+ sensors,
2570
+ collisionDetection: closestCenter,
2571
+ onDragEnd,
2572
+ children: /* @__PURE__ */ jsx3(SortableContext, { items: ids, strategy: verticalListSortingStrategy, children: /* @__PURE__ */ jsx3(Space3, { direction: "vertical", size: 12, style: { width: "100%" }, children: fields.map((row, idx) => /* @__PURE__ */ jsx3(
2573
+ SortableRow,
2574
+ {
2575
+ id: row.key,
2576
+ field,
2577
+ row,
2578
+ idx,
2579
+ namePath,
2580
+ defaults: defaults?.[idx] || null,
2581
+ onRemove: () => remove(idx)
2582
+ },
2583
+ row.key
2584
+ )) }) })
2585
+ }
2586
+ ),
2587
+ /* @__PURE__ */ jsxs3(
2588
+ Button3,
2589
+ {
2590
+ type: "dashed",
2591
+ icon: /* @__PURE__ */ jsx3(PlusOutlined2, {}),
2592
+ onClick: () => add(buildEmptyItem(field)),
2593
+ block: true,
2594
+ style: { marginTop: 12 },
2595
+ children: [
2596
+ "Add ",
2597
+ field.itemLabel ? singular(field.itemLabel(fields.length)) : "item"
2598
+ ]
2599
+ }
2600
+ )
2601
+ ] });
2602
+ } });
2603
+ }
2604
+ function SortableRow({ id, field, row, idx, namePath, defaults, onRemove }) {
2605
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id });
2606
+ const style = {
2607
+ transform: CSS.Transform.toString(transform),
2608
+ transition,
2609
+ opacity: isDragging ? 0.6 : 1,
2610
+ zIndex: isDragging ? 5 : "auto"
2611
+ };
2612
+ return /* @__PURE__ */ jsx3("div", { ref: setNodeRef, style, children: /* @__PURE__ */ jsx3(
2613
+ Card2,
2614
+ {
2615
+ size: "small",
2616
+ style: {
2617
+ background: "var(--tps-bg-soft)",
2618
+ boxShadow: isDragging ? "0 8px 24px rgba(0,0,0,0.12)" : void 0
2619
+ },
2620
+ title: /* @__PURE__ */ jsxs3(Space3, { children: [
2621
+ /* @__PURE__ */ jsx3(
2622
+ Button3,
2623
+ {
2624
+ size: "small",
2625
+ type: "text",
2626
+ icon: /* @__PURE__ */ jsx3(HolderOutlined, {}),
2627
+ style: { cursor: "grab", touchAction: "none" },
2628
+ ...attributes,
2629
+ ...listeners
2630
+ }
2631
+ ),
2632
+ /* @__PURE__ */ jsx3(Text3, { strong: true, style: { fontSize: 13 }, children: /* @__PURE__ */ jsx3(RepeaterItemLabel, { field, index: idx, listPath: namePath }) })
2633
+ ] }),
2634
+ extra: /* @__PURE__ */ jsx3(
2635
+ Button3,
2636
+ {
2637
+ size: "small",
2638
+ type: "text",
2639
+ danger: true,
2640
+ icon: /* @__PURE__ */ jsx3(MinusCircleOutlined, {}),
2641
+ onClick: onRemove,
2642
+ children: "Remove"
2643
+ }
2644
+ ),
2645
+ children: field.itemFields.map((sub) => /* @__PURE__ */ jsx3(
2646
+ FieldRenderer,
2647
+ {
2648
+ field: sub,
2649
+ parent: [...namePath, row.name],
2650
+ defaults
2651
+ },
2652
+ sub.name
2653
+ ))
2654
+ }
2655
+ ) });
2656
+ }
2657
+ function RepeaterItemLabel({ field, index, listPath }) {
2658
+ const item = Form2.useWatch([...listPath, index]);
2659
+ const text = field.itemLabel ? field.itemLabel(index, item) : `Item ${index + 1}`;
2660
+ return /* @__PURE__ */ jsx3("span", { children: text });
2661
+ }
2662
+ function buildEmptyItem(field) {
2663
+ const empty = {};
2664
+ for (const sub of field.itemFields) {
2665
+ if (sub.type === "list" || sub.type === "list-csv") empty[sub.name] = [];
2666
+ else empty[sub.name] = "";
2667
+ }
2668
+ return empty;
2669
+ }
2670
+ function singular(label) {
2671
+ return String(label).replace(/\s+\d+$/, "").toLowerCase() || "item";
2672
+ }
2673
+ function placeholderFor(field, defaultValue) {
2674
+ if (defaultValue == null) return void 0;
2675
+ if (Array.isArray(defaultValue)) {
2676
+ if (field.type === "list-csv") return defaultValue.join(", ");
2677
+ return defaultValue.join("\n");
2678
+ }
2679
+ if (typeof defaultValue === "string") {
2680
+ return defaultValue.length > 100 ? defaultValue.slice(0, 100) + "\u2026" : defaultValue;
2681
+ }
2682
+ return void 0;
2683
+ }
2684
+ var VIEWPORTS = {
2685
+ desktop: { width: "100%", label: "Desktop" },
2686
+ tablet: { width: 820, label: "Tablet" },
2687
+ mobile: { width: 390, label: "Mobile" }
2688
+ };
2689
+ function LivePreview({ path, previewKey }) {
2690
+ const [device, setDevice] = useState3("desktop");
2691
+ const [version, setVersion] = useState3(0);
2692
+ const iframeRef = useRef(null);
2693
+ useEffect3(() => {
2694
+ setVersion((v) => v + 1);
2695
+ }, [previewKey]);
2696
+ const reload = () => setVersion((v) => v + 1);
2697
+ return /* @__PURE__ */ jsxs3("div", { children: [
2698
+ /* @__PURE__ */ jsxs3("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 12, gap: 12 }, children: [
2699
+ /* @__PURE__ */ jsx3(
2700
+ Segmented2,
2701
+ {
2702
+ value: device,
2703
+ onChange: setDevice,
2704
+ options: [
2705
+ { value: "desktop", icon: /* @__PURE__ */ jsx3(DesktopOutlined, {}), label: "Desktop" },
2706
+ { value: "tablet", icon: /* @__PURE__ */ jsx3(TabletOutlined, {}), label: "Tablet" },
2707
+ { value: "mobile", icon: /* @__PURE__ */ jsx3(MobileOutlined, {}), label: "Mobile" }
2708
+ ]
2709
+ }
2710
+ ),
2711
+ /* @__PURE__ */ jsxs3(Space3, { children: [
2712
+ /* @__PURE__ */ jsxs3(Text3, { type: "secondary", style: { fontSize: 12 }, children: [
2713
+ "Showing ",
2714
+ /* @__PURE__ */ jsx3(Text3, { code: true, children: path })
2715
+ ] }),
2716
+ /* @__PURE__ */ jsx3(Button3, { size: "small", icon: /* @__PURE__ */ jsx3(ReloadOutlined2, {}), onClick: reload, children: "Reload" }),
2717
+ /* @__PURE__ */ jsx3(PreviewLink, { href: path })
2718
+ ] })
2719
+ ] }),
2720
+ /* @__PURE__ */ jsx3(
2721
+ "div",
2722
+ {
2723
+ style: {
2724
+ background: "#0F172A",
2725
+ borderRadius: "var(--tps-radius)",
2726
+ padding: 12,
2727
+ display: "flex",
2728
+ justifyContent: "center"
2729
+ },
2730
+ children: /* @__PURE__ */ jsx3(
2731
+ "div",
2732
+ {
2733
+ style: {
2734
+ width: VIEWPORTS[device].width,
2735
+ maxWidth: "100%",
2736
+ transition: "width 200ms ease",
2737
+ background: "#fff",
2738
+ borderRadius: 8,
2739
+ overflow: "hidden",
2740
+ boxShadow: "0 12px 36px -8px rgba(0,0,0,0.45)"
2741
+ },
2742
+ children: /* @__PURE__ */ jsx3(
2743
+ "iframe",
2744
+ {
2745
+ ref: iframeRef,
2746
+ src: `${path}?_preview=${version}`,
2747
+ title: "Live preview",
2748
+ style: {
2749
+ width: "100%",
2750
+ height: "70vh",
2751
+ border: 0,
2752
+ display: "block"
2753
+ }
2754
+ },
2755
+ version
2756
+ )
2757
+ }
2758
+ )
2759
+ }
2760
+ )
2761
+ ] });
2762
+ }
2763
+ function RichTextField({ value, onChange, placeholder }) {
2764
+ const { uploadMedia } = useFormCtx();
2765
+ return /* @__PURE__ */ jsx3(
2766
+ RichText,
2767
+ {
2768
+ value,
2769
+ onChange,
2770
+ placeholder,
2771
+ uploadMedia
2772
+ }
2773
+ );
2774
+ }
2775
+ function PreviewLink({ href }) {
2776
+ const { LinkComponent } = useFormCtx();
2777
+ return /* @__PURE__ */ jsx3(LinkComponent, { href, target: "_blank", children: /* @__PURE__ */ jsx3(Button3, { size: "small", icon: /* @__PURE__ */ jsx3(EyeOutlined, {}), children: "Open in new tab" }) });
2778
+ }
2779
+ export {
2780
+ Callout,
2781
+ CiqImage,
2782
+ CiqTableCell,
2783
+ CiqTableHeader,
2784
+ Column,
2785
+ Columns,
2786
+ HistoryPanel,
2787
+ PageStudioForm,
2788
+ RichText,
2789
+ ShareBlock,
2790
+ SubscribeBlock
2791
+ };
2792
+ //# sourceMappingURL=index.js.map