@kyro-cms/admin 0.8.0 → 0.9.1
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.cjs +11960 -11006
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +67 -65
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +563 -0
- package/dist/index.d.ts +7 -7
- package/dist/index.js +12183 -11238
- package/dist/index.js.map +1 -1
- package/package.json +15 -11
- package/src/components/ActionBar.tsx +27 -14
- package/src/components/Admin.tsx +1 -1
- package/src/components/ApiKeysManager.tsx +5 -5
- package/src/components/AutoForm.tsx +585 -369
- package/src/components/BrandingHub.tsx +7 -4
- package/src/components/CreateView.tsx +2 -0
- package/src/components/DetailView.tsx +71 -56
- package/src/components/DeveloperCenter.tsx +8 -6
- package/src/components/FieldRenderer.tsx +94 -19
- package/src/components/ListView.tsx +33 -20
- package/src/components/MediaGallery.tsx +219 -194
- package/src/components/PluginsManager.tsx +197 -70
- package/src/components/RestPlayground.tsx +7 -7
- package/src/components/SessionsManager.tsx +1 -1
- package/src/components/SettingsPage.tsx +22 -0
- package/src/components/Sidebar.astro +13 -41
- package/src/components/UserManagement.tsx +153 -15
- package/src/components/UserMenu.tsx +30 -4
- package/src/components/VersionHistoryPanel.tsx +112 -119
- package/src/components/WebhookManager.tsx +6 -4
- package/src/components/blocks/ArrayBlock.tsx +6 -23
- package/src/components/blocks/BlockEditModal.tsx +82 -309
- package/src/components/blocks/CardBlock.tsx +35 -0
- package/src/components/blocks/ChildBlocksTree.tsx +57 -31
- package/src/components/blocks/GenericBlock.tsx +44 -0
- package/src/components/blocks/HeadingSubheadingBlock.tsx +32 -0
- package/src/components/blocks/HeroBlock.tsx +5 -14
- package/src/components/blocks/RichTextBlock.tsx +5 -5
- package/src/components/blocks/index.ts +5 -3
- package/src/components/fields/AccordionField.tsx +2 -2
- package/src/components/fields/ArrayField.tsx +1 -1
- package/src/components/fields/ArrayLayout.tsx +120 -29
- package/src/components/fields/BlocksField.tsx +430 -50
- package/src/components/fields/CardField.tsx +73 -0
- package/src/components/fields/CheckboxField.tsx +7 -3
- package/src/components/fields/DateField.tsx +4 -1
- package/src/components/fields/GroupLayout.tsx +2 -2
- package/src/components/fields/HeadingSubheadingField.tsx +43 -0
- package/src/components/fields/ListField.tsx +2 -2
- package/src/components/fields/NumberField.tsx +4 -1
- package/src/components/fields/RelationshipField.tsx +153 -87
- package/src/components/fields/RichTextField.tsx +781 -0
- package/src/components/fields/SecretField.tsx +102 -0
- package/src/components/fields/SelectField.tsx +19 -6
- package/src/components/fields/TabsLayout.tsx +19 -9
- package/src/components/fields/TextField.tsx +4 -1
- package/src/components/fields/UploadField.tsx +122 -56
- package/src/components/fields/extensions/blockComponents.tsx +103 -174
- package/src/components/fields/extensions/blocksStore.ts +8 -1
- package/src/components/fields/index.ts +4 -2
- package/src/components/ui/PageHeader.tsx +5 -5
- package/src/components/ui/SlidePanel.tsx +8 -3
- package/src/components/ui/icons.tsx +109 -109
- package/src/components/users/UserDetail.tsx +79 -16
- package/src/hooks/useAutoFormState.ts +125 -62
- package/src/integration.ts +148 -46
- package/src/kyro-cms.d.ts +7 -2
- package/src/layouts/AuthLayout.astro +14 -2
- package/src/lib/autoform-store.ts +85 -52
- package/src/lib/change-source.ts +9 -0
- package/src/lib/config.ts +104 -8
- package/src/lib/globals.ts +44 -9
- package/src/lib/normalize-upload-fields.ts +41 -0
- package/src/lib/paths.ts +2 -2
- package/src/lib/resolve-field-value.ts +110 -0
- package/src/lib/shim/use-sync-external-store-with-selector.js +30 -0
- package/src/lib/shim/use-sync-external-store.js +1 -0
- package/src/lib/stores/index.ts +1 -0
- package/src/lib/useResourceManager.ts +4 -4
- package/src/lib/vite-shim-plugin.ts +100 -0
- package/src/pages/[collection]/[id].astro +1 -1
- package/src/pages/preview/[collection]/[id].astro +4 -4
- package/src/pages/settings/[slug].astro +2 -2
- package/src/styles/main.css +60 -54
- package/README.md +0 -46
- package/dist/EditorClient-Q23UXR37.cjs +0 -468
- package/dist/EditorClient-Q23UXR37.cjs.map +0 -1
- package/dist/EditorClient-T5PASFNR.js +0 -466
- package/dist/EditorClient-T5PASFNR.js.map +0 -1
- package/dist/chunk-3BGDYKTD.cjs +0 -348
- package/dist/chunk-3BGDYKTD.cjs.map +0 -1
- package/dist/chunk-EEFXLQVT.js +0 -3
- package/dist/chunk-EEFXLQVT.js.map +0 -1
- package/src/components/blocks/ButtonBlock.tsx +0 -64
- package/src/components/blocks/ColumnsBlock.tsx +0 -55
- package/src/components/blocks/DividerBlock.tsx +0 -43
- package/src/components/blocks/LinkBlock.tsx +0 -65
- package/src/components/blocks/VStackBlock.tsx +0 -29
- package/src/components/fields/EditorClient.tsx +0 -535
- package/src/components/fields/PortableTextField.tsx +0 -155
- package/src/components/fields/PortableTextRenderer.tsx +0 -68
|
@@ -0,0 +1,781 @@
|
|
|
1
|
+
import React, { useEffect, useState, useRef } from "react";
|
|
2
|
+
import { createPortal } from "react-dom";
|
|
3
|
+
import { useEditor, EditorContent } from "@tiptap/react";
|
|
4
|
+
import StarterKit from "@tiptap/starter-kit";
|
|
5
|
+
import Link from "@tiptap/extension-link";
|
|
6
|
+
import Image from "@tiptap/extension-image";
|
|
7
|
+
import TextAlign from "@tiptap/extension-text-align";
|
|
8
|
+
import Underline from "@tiptap/extension-underline";
|
|
9
|
+
import Highlight from "@tiptap/extension-highlight";
|
|
10
|
+
import TaskList from "@tiptap/extension-task-list";
|
|
11
|
+
import TaskItem from "@tiptap/extension-task-item";
|
|
12
|
+
import { TextStyle } from "@tiptap/extension-text-style";
|
|
13
|
+
import Color from "@tiptap/extension-color";
|
|
14
|
+
import type { Field } from "@kyro-cms/core/client";
|
|
15
|
+
import FieldLayout from "./FieldLayout";
|
|
16
|
+
import { SlidePanel } from "../ui/SlidePanel";
|
|
17
|
+
import { MediaGallery } from "../MediaGallery";
|
|
18
|
+
import {
|
|
19
|
+
Bold,
|
|
20
|
+
Italic,
|
|
21
|
+
Strikethrough,
|
|
22
|
+
Code,
|
|
23
|
+
Heading1,
|
|
24
|
+
Heading2,
|
|
25
|
+
Heading3,
|
|
26
|
+
List,
|
|
27
|
+
ListOrdered,
|
|
28
|
+
Quote,
|
|
29
|
+
AlignLeft,
|
|
30
|
+
AlignCenter,
|
|
31
|
+
AlignRight,
|
|
32
|
+
Link as LinkIcon,
|
|
33
|
+
Image as ImageIcon,
|
|
34
|
+
Undo,
|
|
35
|
+
Redo,
|
|
36
|
+
Terminal,
|
|
37
|
+
Minus,
|
|
38
|
+
Underline as UnderlineIcon,
|
|
39
|
+
Highlighter,
|
|
40
|
+
Palette,
|
|
41
|
+
CheckSquare,
|
|
42
|
+
Eye,
|
|
43
|
+
Edit2,
|
|
44
|
+
Maximize2,
|
|
45
|
+
Minimize2,
|
|
46
|
+
ChevronDown,
|
|
47
|
+
} from "lucide-react";
|
|
48
|
+
|
|
49
|
+
interface RichTextFieldProps {
|
|
50
|
+
field: Field;
|
|
51
|
+
value: Record<string, any> | null;
|
|
52
|
+
onChange: (value: Record<string, any>) => void;
|
|
53
|
+
error?: string;
|
|
54
|
+
disabled?: boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const PRESET_COLORS = [
|
|
58
|
+
{ name: "Default", value: "inherit" },
|
|
59
|
+
{ name: "Red", value: "#ef4444" },
|
|
60
|
+
{ name: "Orange", value: "#f97316" },
|
|
61
|
+
{ name: "Amber", value: "#f59e0b" },
|
|
62
|
+
{ name: "Emerald", value: "#10b981" },
|
|
63
|
+
{ name: "Blue", value: "#3b82f6" },
|
|
64
|
+
{ name: "Indigo", value: "#6366f1" },
|
|
65
|
+
{ name: "Violet", value: "#8b5cf6" },
|
|
66
|
+
{ name: "Rose", value: "#f43f5e" },
|
|
67
|
+
{ name: "Slate", value: "#64748b" },
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
const MenuBar = ({
|
|
71
|
+
editor,
|
|
72
|
+
isExpanded,
|
|
73
|
+
setIsExpanded,
|
|
74
|
+
onOpenMediaPicker,
|
|
75
|
+
}: {
|
|
76
|
+
editor: any;
|
|
77
|
+
isExpanded: boolean;
|
|
78
|
+
setIsExpanded: (expanded: boolean) => void;
|
|
79
|
+
onOpenMediaPicker: () => void;
|
|
80
|
+
}) => {
|
|
81
|
+
const [activeDropdown, setActiveDropdown] = useState<string | null>(null);
|
|
82
|
+
const menuBarRef = useRef<HTMLDivElement>(null);
|
|
83
|
+
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
function handleClickOutside(event: MouseEvent) {
|
|
86
|
+
if (menuBarRef.current && !menuBarRef.current.contains(event.target as Node)) {
|
|
87
|
+
setActiveDropdown(null);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
91
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
92
|
+
}, []);
|
|
93
|
+
|
|
94
|
+
if (!editor) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const addImage = () => {
|
|
99
|
+
onOpenMediaPicker();
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const setLink = () => {
|
|
103
|
+
const previousUrl = editor.getAttributes("link").href;
|
|
104
|
+
const url = window.prompt("URL", previousUrl);
|
|
105
|
+
|
|
106
|
+
if (url === null) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (url === "") {
|
|
111
|
+
editor.chain().focus().extendMarkRange("link").unsetLink().run();
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
editor.chain().focus().extendMarkRange("link").setLink({ href: url }).run();
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const selectColor = (colorVal: string) => {
|
|
119
|
+
if (colorVal === "inherit") {
|
|
120
|
+
editor.chain().focus().unsetColor().run();
|
|
121
|
+
} else {
|
|
122
|
+
editor.chain().focus().setColor(colorVal).run();
|
|
123
|
+
}
|
|
124
|
+
setActiveDropdown(null);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const toggleDropdown = (name: string) => {
|
|
128
|
+
setActiveDropdown(activeDropdown === name ? null : name);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const getHeadingLabel = () => {
|
|
132
|
+
if (editor.isActive("heading", { level: 1 })) return "Heading 1";
|
|
133
|
+
if (editor.isActive("heading", { level: 2 })) return "Heading 2";
|
|
134
|
+
if (editor.isActive("heading", { level: 3 })) return "Heading 3";
|
|
135
|
+
if (editor.isActive("heading", { level: 4 })) return "Heading 4";
|
|
136
|
+
return "Normal Text";
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const getAlignIcon = () => {
|
|
140
|
+
if (editor.isActive({ textAlign: "center" })) return <AlignCenter size={12} />;
|
|
141
|
+
if (editor.isActive({ textAlign: "right" })) return <AlignRight size={12} />;
|
|
142
|
+
return <AlignLeft size={12} />;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const getListIcon = () => {
|
|
146
|
+
if (editor.isActive("orderedList")) return <ListOrdered size={12} />;
|
|
147
|
+
if (editor.isActive("taskList")) return <CheckSquare size={12} />;
|
|
148
|
+
return <List size={12} />;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const getBlockIcon = () => {
|
|
152
|
+
if (editor.isActive("codeBlock")) return <Terminal size={12} />;
|
|
153
|
+
return <Quote size={12} />;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const ToolbarButton = ({
|
|
157
|
+
onClick,
|
|
158
|
+
isActive = false,
|
|
159
|
+
disabled = false,
|
|
160
|
+
children,
|
|
161
|
+
title,
|
|
162
|
+
}: any) => (
|
|
163
|
+
<button
|
|
164
|
+
type="button"
|
|
165
|
+
onClick={onClick}
|
|
166
|
+
disabled={disabled}
|
|
167
|
+
title={title}
|
|
168
|
+
className={`p-1 rounded flex items-center justify-center transition-all duration-150 relative
|
|
169
|
+
${isActive ? "bg-[var(--kyro-primary)] text-white shadow-xs scale-95" : "text-[var(--kyro-text)] hover:bg-[var(--kyro-bg-hover)]"}
|
|
170
|
+
${disabled ? "opacity-35 cursor-not-allowed" : "cursor-pointer active:scale-90"}`}
|
|
171
|
+
>
|
|
172
|
+
{children}
|
|
173
|
+
</button>
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
const DropdownTrigger = ({ onClick, isActive, children, title }: any) => (
|
|
177
|
+
<button
|
|
178
|
+
type="button"
|
|
179
|
+
onClick={onClick}
|
|
180
|
+
title={title}
|
|
181
|
+
className={`px-2 py-1 rounded flex items-center gap-1 transition-all duration-150 text-xs border border-transparent hover:bg-[var(--kyro-bg-hover)] cursor-pointer text-[var(--kyro-text)] active:scale-98
|
|
182
|
+
${isActive ? "bg-[var(--kyro-bg-hover)] border-[var(--kyro-border)]" : ""}`}
|
|
183
|
+
>
|
|
184
|
+
{children}
|
|
185
|
+
<ChevronDown size={10} className="opacity-60" />
|
|
186
|
+
</button>
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
return (
|
|
190
|
+
<div
|
|
191
|
+
ref={menuBarRef}
|
|
192
|
+
className="flex flex-wrap items-center gap-1.5 p-1.5 border-b border-[var(--kyro-border)] bg-[var(--kyro-bg-secondary)] rounded-t-lg select-none"
|
|
193
|
+
>
|
|
194
|
+
{/* Group 1: History Actions */}
|
|
195
|
+
<div className="flex items-center gap-0.5 p-0.5 bg-[var(--kyro-bg)] border border-[var(--kyro-border)] rounded-md shadow-xs">
|
|
196
|
+
<ToolbarButton
|
|
197
|
+
onClick={() => editor.chain().focus().undo().run()}
|
|
198
|
+
disabled={!editor.can().chain().focus().undo().run()}
|
|
199
|
+
title="Undo (Ctrl+Z)"
|
|
200
|
+
>
|
|
201
|
+
<Undo size={12} />
|
|
202
|
+
</ToolbarButton>
|
|
203
|
+
<ToolbarButton
|
|
204
|
+
onClick={() => editor.chain().focus().redo().run()}
|
|
205
|
+
disabled={!editor.can().chain().focus().redo().run()}
|
|
206
|
+
title="Redo (Ctrl+Y)"
|
|
207
|
+
>
|
|
208
|
+
<Redo size={12} />
|
|
209
|
+
</ToolbarButton>
|
|
210
|
+
</div>
|
|
211
|
+
|
|
212
|
+
{/* Group 2: Inline Styles */}
|
|
213
|
+
<div className="flex items-center gap-0.5 p-0.5 bg-[var(--kyro-bg)] border border-[var(--kyro-border)] rounded-md shadow-xs">
|
|
214
|
+
<ToolbarButton
|
|
215
|
+
onClick={() => editor.chain().focus().toggleBold().run()}
|
|
216
|
+
disabled={!editor.can().chain().focus().toggleBold().run()}
|
|
217
|
+
isActive={editor.isActive("bold")}
|
|
218
|
+
title="Bold (Ctrl+B)"
|
|
219
|
+
>
|
|
220
|
+
<Bold size={12} />
|
|
221
|
+
</ToolbarButton>
|
|
222
|
+
<ToolbarButton
|
|
223
|
+
onClick={() => editor.chain().focus().toggleItalic().run()}
|
|
224
|
+
disabled={!editor.can().chain().focus().toggleItalic().run()}
|
|
225
|
+
isActive={editor.isActive("italic")}
|
|
226
|
+
title="Italic (Ctrl+I)"
|
|
227
|
+
>
|
|
228
|
+
<Italic size={12} />
|
|
229
|
+
</ToolbarButton>
|
|
230
|
+
<ToolbarButton
|
|
231
|
+
onClick={() => editor.chain().focus().toggleUnderline().run()}
|
|
232
|
+
disabled={!editor.can().chain().focus().toggleUnderline().run()}
|
|
233
|
+
isActive={editor.isActive("underline")}
|
|
234
|
+
title="Underline (Ctrl+U)"
|
|
235
|
+
>
|
|
236
|
+
<UnderlineIcon size={12} />
|
|
237
|
+
</ToolbarButton>
|
|
238
|
+
<ToolbarButton
|
|
239
|
+
onClick={() => editor.chain().focus().toggleStrike().run()}
|
|
240
|
+
disabled={!editor.can().chain().focus().toggleStrike().run()}
|
|
241
|
+
isActive={editor.isActive("strike")}
|
|
242
|
+
title="Strikethrough"
|
|
243
|
+
>
|
|
244
|
+
<Strikethrough size={12} />
|
|
245
|
+
</ToolbarButton>
|
|
246
|
+
<ToolbarButton
|
|
247
|
+
onClick={() => editor.chain().focus().toggleCode().run()}
|
|
248
|
+
disabled={!editor.can().chain().focus().toggleCode().run()}
|
|
249
|
+
isActive={editor.isActive("code")}
|
|
250
|
+
title="Inline Code"
|
|
251
|
+
>
|
|
252
|
+
<Code size={12} />
|
|
253
|
+
</ToolbarButton>
|
|
254
|
+
<ToolbarButton
|
|
255
|
+
onClick={() => editor.chain().focus().toggleHighlight().run()}
|
|
256
|
+
isActive={editor.isActive("highlight")}
|
|
257
|
+
title="Highlight Text"
|
|
258
|
+
>
|
|
259
|
+
<Highlighter size={12} />
|
|
260
|
+
</ToolbarButton>
|
|
261
|
+
|
|
262
|
+
{/* Text Color Picker Dropdown */}
|
|
263
|
+
<div className="relative flex items-center justify-center">
|
|
264
|
+
<ToolbarButton
|
|
265
|
+
onClick={() => toggleDropdown("color")}
|
|
266
|
+
title="Text Color"
|
|
267
|
+
isActive={activeDropdown === "color" || editor.isActive("textStyle")}
|
|
268
|
+
>
|
|
269
|
+
<Palette size={12} />
|
|
270
|
+
</ToolbarButton>
|
|
271
|
+
{activeDropdown === "color" && (
|
|
272
|
+
<div className="absolute top-full left-0 mt-1.5 p-2 bg-[var(--kyro-bg)] border border-[var(--kyro-border)] rounded-lg shadow-xl z-50 flex flex-wrap gap-1 w-44 animate-in fade-in slide-in-from-top-1 duration-150">
|
|
273
|
+
{PRESET_COLORS.map((col) => (
|
|
274
|
+
<button
|
|
275
|
+
key={col.name}
|
|
276
|
+
type="button"
|
|
277
|
+
onClick={() => selectColor(col.value)}
|
|
278
|
+
title={col.name}
|
|
279
|
+
className="w-6 h-6 rounded-full border border-[var(--kyro-border)] transition-transform hover:scale-115 active:scale-95 cursor-pointer relative"
|
|
280
|
+
style={{
|
|
281
|
+
backgroundColor: col.value === "inherit" ? "transparent" : col.value,
|
|
282
|
+
}}
|
|
283
|
+
>
|
|
284
|
+
{col.value === "inherit" && (
|
|
285
|
+
<span className="absolute inset-0 flex items-center justify-center text-[10px] text-[var(--kyro-text)] font-semibold">
|
|
286
|
+
∅
|
|
287
|
+
</span>
|
|
288
|
+
)}
|
|
289
|
+
</button>
|
|
290
|
+
))}
|
|
291
|
+
</div>
|
|
292
|
+
)}
|
|
293
|
+
</div>
|
|
294
|
+
</div>
|
|
295
|
+
|
|
296
|
+
{/* Group 3: Headings hierarchy Dropdown */}
|
|
297
|
+
<div className="relative flex items-center p-0.5 bg-[var(--kyro-bg)] border border-[var(--kyro-border)] rounded-md shadow-xs">
|
|
298
|
+
<DropdownTrigger
|
|
299
|
+
onClick={() => toggleDropdown("heading")}
|
|
300
|
+
isActive={activeDropdown === "heading"}
|
|
301
|
+
title="Heading hierarchy"
|
|
302
|
+
>
|
|
303
|
+
<span className="font-medium text-[11px] leading-none min-w-[70px] text-left">
|
|
304
|
+
{getHeadingLabel()}
|
|
305
|
+
</span>
|
|
306
|
+
</DropdownTrigger>
|
|
307
|
+
{activeDropdown === "heading" && (
|
|
308
|
+
<div className="absolute top-full left-0 mt-1.5 p-1 bg-[var(--kyro-bg)] border border-[var(--kyro-border)] rounded-lg shadow-xl z-50 min-w-36 flex flex-col gap-0.5 animate-in fade-in slide-in-from-top-1 duration-150">
|
|
309
|
+
<button
|
|
310
|
+
type="button"
|
|
311
|
+
onClick={() => {
|
|
312
|
+
editor.chain().focus().setParagraph().run();
|
|
313
|
+
setActiveDropdown(null);
|
|
314
|
+
}}
|
|
315
|
+
className={`px-2.5 py-1.5 text-xs text-left rounded-md hover:bg-[var(--kyro-bg-hover)] cursor-pointer text-[var(--kyro-text)] transition-colors
|
|
316
|
+
${!editor.isActive("heading") ? "font-semibold text-[var(--kyro-primary)] bg-[var(--kyro-bg-hover)]" : ""}`}
|
|
317
|
+
>
|
|
318
|
+
Normal Text
|
|
319
|
+
</button>
|
|
320
|
+
{[1, 2, 3, 4].map((level) => (
|
|
321
|
+
<button
|
|
322
|
+
key={level}
|
|
323
|
+
type="button"
|
|
324
|
+
onClick={() => {
|
|
325
|
+
editor.chain().focus().toggleHeading({ level }).run();
|
|
326
|
+
setActiveDropdown(null);
|
|
327
|
+
}}
|
|
328
|
+
className={`px-2.5 py-1.5 text-xs text-left rounded-md hover:bg-[var(--kyro-bg-hover)] cursor-pointer text-[var(--kyro-text)] transition-colors
|
|
329
|
+
${editor.isActive("heading", { level }) ? "font-semibold text-[var(--kyro-primary)] bg-[var(--kyro-bg-hover)]" : ""}`}
|
|
330
|
+
>
|
|
331
|
+
Heading {level}
|
|
332
|
+
</button>
|
|
333
|
+
))}
|
|
334
|
+
</div>
|
|
335
|
+
)}
|
|
336
|
+
</div>
|
|
337
|
+
|
|
338
|
+
{/* Group 4: List Types Dropdown */}
|
|
339
|
+
<div className="relative flex items-center p-0.5 bg-[var(--kyro-bg)] border border-[var(--kyro-border)] rounded-md shadow-xs">
|
|
340
|
+
<DropdownTrigger
|
|
341
|
+
onClick={() => toggleDropdown("lists")}
|
|
342
|
+
isActive={activeDropdown === "lists"}
|
|
343
|
+
title="List Types"
|
|
344
|
+
>
|
|
345
|
+
{getListIcon()}
|
|
346
|
+
</DropdownTrigger>
|
|
347
|
+
{activeDropdown === "lists" && (
|
|
348
|
+
<div className="absolute top-full left-0 mt-1.5 p-1 bg-[var(--kyro-bg)] border border-[var(--kyro-border)] rounded-lg shadow-xl z-50 min-w-36 flex flex-col gap-0.5 animate-in fade-in slide-in-from-top-1 duration-150">
|
|
349
|
+
<button
|
|
350
|
+
type="button"
|
|
351
|
+
onClick={() => {
|
|
352
|
+
editor.chain().focus().toggleBulletList().run();
|
|
353
|
+
setActiveDropdown(null);
|
|
354
|
+
}}
|
|
355
|
+
className={`px-2.5 py-1.5 text-xs text-left rounded-md hover:bg-[var(--kyro-bg-hover)] cursor-pointer text-[var(--kyro-text)] flex items-center gap-2 transition-colors
|
|
356
|
+
${editor.isActive("bulletList") ? "font-semibold text-[var(--kyro-primary)] bg-[var(--kyro-bg-hover)]" : ""}`}
|
|
357
|
+
>
|
|
358
|
+
<List size={12} />
|
|
359
|
+
Bullet List
|
|
360
|
+
</button>
|
|
361
|
+
<button
|
|
362
|
+
type="button"
|
|
363
|
+
onClick={() => {
|
|
364
|
+
editor.chain().focus().toggleOrderedList().run();
|
|
365
|
+
setActiveDropdown(null);
|
|
366
|
+
}}
|
|
367
|
+
className={`px-2.5 py-1.5 text-xs text-left rounded-md hover:bg-[var(--kyro-bg-hover)] cursor-pointer text-[var(--kyro-text)] flex items-center gap-2 transition-colors
|
|
368
|
+
${editor.isActive("orderedList") ? "font-semibold text-[var(--kyro-primary)] bg-[var(--kyro-bg-hover)]" : ""}`}
|
|
369
|
+
>
|
|
370
|
+
<ListOrdered size={12} />
|
|
371
|
+
Ordered List
|
|
372
|
+
</button>
|
|
373
|
+
<button
|
|
374
|
+
type="button"
|
|
375
|
+
onClick={() => {
|
|
376
|
+
editor.chain().focus().toggleTaskList().run();
|
|
377
|
+
setActiveDropdown(null);
|
|
378
|
+
}}
|
|
379
|
+
className={`px-2.5 py-1.5 text-xs text-left rounded-md hover:bg-[var(--kyro-bg-hover)] cursor-pointer text-[var(--kyro-text)] flex items-center gap-2 transition-colors
|
|
380
|
+
${editor.isActive("taskList") ? "font-semibold text-[var(--kyro-primary)] bg-[var(--kyro-bg-hover)]" : ""}`}
|
|
381
|
+
>
|
|
382
|
+
<CheckSquare size={12} />
|
|
383
|
+
Task Checklist
|
|
384
|
+
</button>
|
|
385
|
+
</div>
|
|
386
|
+
)}
|
|
387
|
+
</div>
|
|
388
|
+
|
|
389
|
+
{/* Group 5: Blocks Dropdown */}
|
|
390
|
+
<div className="relative flex items-center p-0.5 bg-[var(--kyro-bg)] border border-[var(--kyro-border)] rounded-md shadow-xs">
|
|
391
|
+
<DropdownTrigger
|
|
392
|
+
onClick={() => toggleDropdown("blocks")}
|
|
393
|
+
isActive={activeDropdown === "blocks"}
|
|
394
|
+
title="Structural Blocks"
|
|
395
|
+
>
|
|
396
|
+
{getBlockIcon()}
|
|
397
|
+
</DropdownTrigger>
|
|
398
|
+
{activeDropdown === "blocks" && (
|
|
399
|
+
<div className="absolute top-full left-0 mt-1.5 p-1 bg-[var(--kyro-bg)] border border-[var(--kyro-border)] rounded-lg shadow-xl z-50 min-w-36 flex flex-col gap-0.5 animate-in fade-in slide-in-from-top-1 duration-150">
|
|
400
|
+
<button
|
|
401
|
+
type="button"
|
|
402
|
+
onClick={() => {
|
|
403
|
+
editor.chain().focus().toggleBlockquote().run();
|
|
404
|
+
setActiveDropdown(null);
|
|
405
|
+
}}
|
|
406
|
+
className={`px-2.5 py-1.5 text-xs text-left rounded-md hover:bg-[var(--kyro-bg-hover)] cursor-pointer text-[var(--kyro-text)] flex items-center gap-2 transition-colors
|
|
407
|
+
${editor.isActive("blockquote") ? "font-semibold text-[var(--kyro-primary)] bg-[var(--kyro-bg-hover)]" : ""}`}
|
|
408
|
+
>
|
|
409
|
+
<Quote size={12} />
|
|
410
|
+
Blockquote
|
|
411
|
+
</button>
|
|
412
|
+
<button
|
|
413
|
+
type="button"
|
|
414
|
+
onClick={() => {
|
|
415
|
+
editor.chain().focus().toggleCodeBlock().run();
|
|
416
|
+
setActiveDropdown(null);
|
|
417
|
+
}}
|
|
418
|
+
className={`px-2.5 py-1.5 text-xs text-left rounded-md hover:bg-[var(--kyro-bg-hover)] cursor-pointer text-[var(--kyro-text)] flex items-center gap-2 transition-colors
|
|
419
|
+
${editor.isActive("codeBlock") ? "font-semibold text-[var(--kyro-primary)] bg-[var(--kyro-bg-hover)]" : ""}`}
|
|
420
|
+
>
|
|
421
|
+
<Terminal size={12} />
|
|
422
|
+
Code Block
|
|
423
|
+
</button>
|
|
424
|
+
<button
|
|
425
|
+
type="button"
|
|
426
|
+
onClick={() => {
|
|
427
|
+
editor.chain().focus().setHorizontalRule().run();
|
|
428
|
+
setActiveDropdown(null);
|
|
429
|
+
}}
|
|
430
|
+
className="px-2.5 py-1.5 text-xs text-left rounded-md hover:bg-[var(--kyro-bg-hover)] cursor-pointer text-[var(--kyro-text)] flex items-center gap-2 transition-colors"
|
|
431
|
+
>
|
|
432
|
+
<Minus size={12} />
|
|
433
|
+
Horizontal Rule
|
|
434
|
+
</button>
|
|
435
|
+
</div>
|
|
436
|
+
)}
|
|
437
|
+
</div>
|
|
438
|
+
|
|
439
|
+
{/* Group 6: Text Alignments Dropdown */}
|
|
440
|
+
<div className="relative flex items-center p-0.5 bg-[var(--kyro-bg)] border border-[var(--kyro-border)] rounded-md shadow-xs">
|
|
441
|
+
<DropdownTrigger
|
|
442
|
+
onClick={() => toggleDropdown("align")}
|
|
443
|
+
isActive={activeDropdown === "align"}
|
|
444
|
+
title="Alignment"
|
|
445
|
+
>
|
|
446
|
+
{getAlignIcon()}
|
|
447
|
+
</DropdownTrigger>
|
|
448
|
+
{activeDropdown === "align" && (
|
|
449
|
+
<div className="absolute top-full left-0 mt-1.5 p-1 bg-[var(--kyro-bg)] border border-[var(--kyro-border)] rounded-lg shadow-xl z-50 min-w-32 flex flex-col gap-0.5 animate-in fade-in slide-in-from-top-1 duration-150">
|
|
450
|
+
<button
|
|
451
|
+
type="button"
|
|
452
|
+
onClick={() => {
|
|
453
|
+
editor.chain().focus().setTextAlign("left").run();
|
|
454
|
+
setActiveDropdown(null);
|
|
455
|
+
}}
|
|
456
|
+
className={`px-2.5 py-1.5 text-xs text-left rounded-md hover:bg-[var(--kyro-bg-hover)] cursor-pointer text-[var(--kyro-text)] flex items-center gap-2 transition-colors
|
|
457
|
+
${editor.isActive({ textAlign: "left" }) ? "font-semibold text-[var(--kyro-primary)] bg-[var(--kyro-bg-hover)]" : ""}`}
|
|
458
|
+
>
|
|
459
|
+
<AlignLeft size={12} />
|
|
460
|
+
Align Left
|
|
461
|
+
</button>
|
|
462
|
+
<button
|
|
463
|
+
type="button"
|
|
464
|
+
onClick={() => {
|
|
465
|
+
editor.chain().focus().setTextAlign("center").run();
|
|
466
|
+
setActiveDropdown(null);
|
|
467
|
+
}}
|
|
468
|
+
className={`px-2.5 py-1.5 text-xs text-left rounded-md hover:bg-[var(--kyro-bg-hover)] cursor-pointer text-[var(--kyro-text)] flex items-center gap-2 transition-colors
|
|
469
|
+
${editor.isActive({ textAlign: "center" }) ? "font-semibold text-[var(--kyro-primary)] bg-[var(--kyro-bg-hover)]" : ""}`}
|
|
470
|
+
>
|
|
471
|
+
<AlignCenter size={12} />
|
|
472
|
+
Align Center
|
|
473
|
+
</button>
|
|
474
|
+
<button
|
|
475
|
+
type="button"
|
|
476
|
+
onClick={() => {
|
|
477
|
+
editor.chain().focus().setTextAlign("right").run();
|
|
478
|
+
setActiveDropdown(null);
|
|
479
|
+
}}
|
|
480
|
+
className={`px-2.5 py-1.5 text-xs text-left rounded-md hover:bg-[var(--kyro-bg-hover)] cursor-pointer text-[var(--kyro-text)] flex items-center gap-2 transition-colors
|
|
481
|
+
${editor.isActive({ textAlign: "right" }) ? "font-semibold text-[var(--kyro-primary)] bg-[var(--kyro-bg-hover)]" : ""}`}
|
|
482
|
+
>
|
|
483
|
+
<AlignRight size={12} />
|
|
484
|
+
Align Right
|
|
485
|
+
</button>
|
|
486
|
+
</div>
|
|
487
|
+
)}
|
|
488
|
+
</div>
|
|
489
|
+
|
|
490
|
+
{/* Group 7: Rich Media Embeds */}
|
|
491
|
+
<div className="flex items-center gap-0.5 p-0.5 bg-[var(--kyro-bg)] border border-[var(--kyro-border)] rounded-md shadow-xs">
|
|
492
|
+
<ToolbarButton
|
|
493
|
+
onClick={setLink}
|
|
494
|
+
isActive={editor.isActive("link")}
|
|
495
|
+
title="Link"
|
|
496
|
+
>
|
|
497
|
+
<LinkIcon size={12} />
|
|
498
|
+
</ToolbarButton>
|
|
499
|
+
<ToolbarButton onClick={addImage} title="Add Image">
|
|
500
|
+
<ImageIcon size={12} />
|
|
501
|
+
</ToolbarButton>
|
|
502
|
+
</div>
|
|
503
|
+
|
|
504
|
+
{/* Group 8: Workspace Controls */}
|
|
505
|
+
<div className="flex items-center gap-0.5 p-0.5 bg-[var(--kyro-bg)] border border-[var(--kyro-border)] rounded-md shadow-xs ml-auto">
|
|
506
|
+
<ToolbarButton
|
|
507
|
+
onClick={() => setIsExpanded(!isExpanded)}
|
|
508
|
+
title={isExpanded ? "Collapse Workspace" : "Enlarge Workspace"}
|
|
509
|
+
>
|
|
510
|
+
{isExpanded ? <Minimize2 size={12} /> : <Maximize2 size={12} />}
|
|
511
|
+
</ToolbarButton>
|
|
512
|
+
</div>
|
|
513
|
+
</div>
|
|
514
|
+
);
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
export default function RichTextField({
|
|
518
|
+
field,
|
|
519
|
+
value,
|
|
520
|
+
onChange,
|
|
521
|
+
error,
|
|
522
|
+
disabled,
|
|
523
|
+
}: RichTextFieldProps) {
|
|
524
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
525
|
+
const [panelWidth, setPanelWidth] = useState(0);
|
|
526
|
+
const [isMediaPickerOpen, setIsMediaPickerOpen] = useState(false);
|
|
527
|
+
const [isMounted, setIsMounted] = useState(false);
|
|
528
|
+
|
|
529
|
+
useEffect(() => {
|
|
530
|
+
setIsMounted(true);
|
|
531
|
+
}, []);
|
|
532
|
+
|
|
533
|
+
useEffect(() => {
|
|
534
|
+
if (!isExpanded) {
|
|
535
|
+
setPanelWidth(0);
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const updateWidth = () => {
|
|
540
|
+
const panel = document.querySelector('[data-kyro-slide-panel="true"]');
|
|
541
|
+
if (panel) {
|
|
542
|
+
setPanelWidth(panel.getBoundingClientRect().width);
|
|
543
|
+
} else {
|
|
544
|
+
setPanelWidth(0);
|
|
545
|
+
}
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
updateWidth();
|
|
549
|
+
|
|
550
|
+
let observer: ResizeObserver | null = null;
|
|
551
|
+
const panel = document.querySelector('[data-kyro-slide-panel="true"]');
|
|
552
|
+
if (panel && typeof ResizeObserver !== "undefined") {
|
|
553
|
+
observer = new ResizeObserver(() => {
|
|
554
|
+
updateWidth();
|
|
555
|
+
});
|
|
556
|
+
observer.observe(panel);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
window.addEventListener("resize", updateWidth);
|
|
560
|
+
const interval = setInterval(updateWidth, 100);
|
|
561
|
+
|
|
562
|
+
return () => {
|
|
563
|
+
if (observer) {
|
|
564
|
+
observer.disconnect();
|
|
565
|
+
}
|
|
566
|
+
window.removeEventListener("resize", updateWidth);
|
|
567
|
+
clearInterval(interval);
|
|
568
|
+
};
|
|
569
|
+
}, [isExpanded]);
|
|
570
|
+
|
|
571
|
+
const editor = useEditor({
|
|
572
|
+
extensions: [
|
|
573
|
+
StarterKit.configure({
|
|
574
|
+
codeBlock: true,
|
|
575
|
+
}),
|
|
576
|
+
Link.configure({
|
|
577
|
+
openOnClick: false,
|
|
578
|
+
}),
|
|
579
|
+
Image,
|
|
580
|
+
TextAlign.configure({
|
|
581
|
+
types: ["heading", "paragraph"],
|
|
582
|
+
}),
|
|
583
|
+
Underline,
|
|
584
|
+
Highlight.configure({
|
|
585
|
+
multicolor: true,
|
|
586
|
+
}),
|
|
587
|
+
TaskList,
|
|
588
|
+
TaskItem.configure({
|
|
589
|
+
nested: true,
|
|
590
|
+
}),
|
|
591
|
+
TextStyle,
|
|
592
|
+
Color,
|
|
593
|
+
],
|
|
594
|
+
content: value || {},
|
|
595
|
+
editable: !disabled,
|
|
596
|
+
onUpdate: ({ editor }: { editor: any }) => {
|
|
597
|
+
onChange(editor.getJSON());
|
|
598
|
+
},
|
|
599
|
+
editorProps: {
|
|
600
|
+
attributes: {
|
|
601
|
+
class:
|
|
602
|
+
"prose prose-sm dark:prose-invert focus:outline-none min-h-[160px] p-4 max-w-none kyro-richtext text-[0.875rem] leading-relaxed",
|
|
603
|
+
},
|
|
604
|
+
},
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
useEffect(() => {
|
|
608
|
+
if (editor && value && JSON.stringify(value) !== JSON.stringify(editor.getJSON())) {
|
|
609
|
+
editor.commands.setContent(value);
|
|
610
|
+
}
|
|
611
|
+
}, [value, editor]);
|
|
612
|
+
|
|
613
|
+
if (!isMounted) {
|
|
614
|
+
return (
|
|
615
|
+
<FieldLayout field={field} error={error}>
|
|
616
|
+
<div
|
|
617
|
+
className={`border rounded-lg bg-[var(--kyro-bg)] overflow-hidden border-[var(--kyro-border)] flex flex-col shadow-sm transition-all duration-200
|
|
618
|
+
${error ? "border-[var(--kyro-error)] shadow-[0_0_0_1px_var(--kyro-error)]" : "border-[var(--kyro-border)]"}
|
|
619
|
+
${disabled ? "opacity-60 cursor-not-allowed" : ""}`}
|
|
620
|
+
>
|
|
621
|
+
<div className="flex flex-wrap items-center gap-1.5 p-1.5 border-b border-[var(--kyro-border)] bg-[var(--kyro-bg-secondary)] rounded-t-lg h-[40px]"></div>
|
|
622
|
+
<div className="overflow-y-auto min-h-[160px] max-h-[400px] p-4"></div>
|
|
623
|
+
</div>
|
|
624
|
+
</FieldLayout>
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
return (
|
|
629
|
+
<FieldLayout field={field} error={error}>
|
|
630
|
+
{isExpanded ? (
|
|
631
|
+
// Maximize mode placeholder inside form
|
|
632
|
+
<div className="border rounded-lg bg-[var(--kyro-bg-secondary)] border-[var(--kyro-border)] p-4 text-center text-xs text-[var(--kyro-text-muted)] flex items-center justify-center gap-3 h-20 transition-all duration-200 shadow-inner">
|
|
633
|
+
<span className="font-medium">
|
|
634
|
+
Rich text editor workspace is currently maximized.
|
|
635
|
+
</span>
|
|
636
|
+
<button
|
|
637
|
+
type="button"
|
|
638
|
+
onClick={() => setIsExpanded(false)}
|
|
639
|
+
className="px-2.5 py-1 bg-[var(--kyro-primary)] text-white text-xs rounded-md shadow-sm font-medium hover:scale-102 active:scale-98 transition-transform cursor-pointer"
|
|
640
|
+
>
|
|
641
|
+
Minimize Workspace
|
|
642
|
+
</button>
|
|
643
|
+
</div>
|
|
644
|
+
) : (
|
|
645
|
+
// Standard inline editor
|
|
646
|
+
<div
|
|
647
|
+
className={`border rounded-lg bg-[var(--kyro-bg)] overflow-hidden border-[var(--kyro-border)] flex flex-col shadow-sm transition-all duration-200
|
|
648
|
+
${error ? "border-[var(--kyro-error)] shadow-[0_0_0_1px_var(--kyro-error)]" : "border-[var(--kyro-border)] focus-within:border-[var(--kyro-primary)] focus-within:ring-1 focus-within:ring-[var(--kyro-primary)]"}
|
|
649
|
+
${disabled ? "opacity-60 cursor-not-allowed" : ""}`}
|
|
650
|
+
>
|
|
651
|
+
<MenuBar
|
|
652
|
+
editor={editor}
|
|
653
|
+
isExpanded={isExpanded}
|
|
654
|
+
setIsExpanded={setIsExpanded}
|
|
655
|
+
onOpenMediaPicker={() => setIsMediaPickerOpen(true)}
|
|
656
|
+
/>
|
|
657
|
+
<div className="overflow-y-auto min-h-[160px] max-h-[400px]">
|
|
658
|
+
<EditorContent editor={editor} />
|
|
659
|
+
</div>
|
|
660
|
+
</div>
|
|
661
|
+
)}
|
|
662
|
+
|
|
663
|
+
{/* Portal absolute fullscreen overlay directly inside document.body */}
|
|
664
|
+
{isExpanded &&
|
|
665
|
+
typeof document !== "undefined" &&
|
|
666
|
+
createPortal(
|
|
667
|
+
<div
|
|
668
|
+
style={{
|
|
669
|
+
position: "fixed",
|
|
670
|
+
top: "16px",
|
|
671
|
+
bottom: "16px",
|
|
672
|
+
left: "16px",
|
|
673
|
+
right: panelWidth > 0 ? `${panelWidth + 16}px` : "16px",
|
|
674
|
+
zIndex: 9999,
|
|
675
|
+
}}
|
|
676
|
+
className="flex flex-col bg-[var(--kyro-bg)] border border-[var(--kyro-border)] rounded-md shadow-2xl overflow-hidden animate-in fade-in zoom-in-95 duration-200 transition-all duration-300"
|
|
677
|
+
>
|
|
678
|
+
<MenuBar
|
|
679
|
+
editor={editor}
|
|
680
|
+
isExpanded={isExpanded}
|
|
681
|
+
setIsExpanded={setIsExpanded}
|
|
682
|
+
onOpenMediaPicker={() => setIsMediaPickerOpen(true)}
|
|
683
|
+
/>
|
|
684
|
+
<div className="overflow-y-auto flex-1 h-[calc(100vh-140px)] bg-[var(--kyro-bg)]">
|
|
685
|
+
<EditorContent editor={editor} />
|
|
686
|
+
</div>
|
|
687
|
+
</div>,
|
|
688
|
+
document.body
|
|
689
|
+
)}
|
|
690
|
+
|
|
691
|
+
{/* Injected tasklist, and highlight custom styles for preview & editor */}
|
|
692
|
+
<style>{`
|
|
693
|
+
.kyro-richtext ul[data-type="taskList"] {
|
|
694
|
+
list-style: none !important;
|
|
695
|
+
padding: 0 !important;
|
|
696
|
+
margin: 0.5rem 0 !important;
|
|
697
|
+
}
|
|
698
|
+
.kyro-richtext li[data-type="taskItem"] {
|
|
699
|
+
display: flex !important;
|
|
700
|
+
align-items: flex-start !important;
|
|
701
|
+
gap: 0.5rem !important;
|
|
702
|
+
margin-bottom: 0.25rem !important;
|
|
703
|
+
}
|
|
704
|
+
.kyro-richtext li[data-type="taskItem"] > label {
|
|
705
|
+
margin-top: 0.25rem !important;
|
|
706
|
+
user-select: none !important;
|
|
707
|
+
cursor: pointer !important;
|
|
708
|
+
}
|
|
709
|
+
.kyro-richtext li[data-type="taskItem"] > div {
|
|
710
|
+
flex: 1 !important;
|
|
711
|
+
}
|
|
712
|
+
.kyro-richtext mark {
|
|
713
|
+
background-color: #fef08a !important;
|
|
714
|
+
border-radius: 0.25rem !important;
|
|
715
|
+
padding: 0.125rem 0.25rem !important;
|
|
716
|
+
color: #1e293b !important;
|
|
717
|
+
}
|
|
718
|
+
.kyro-richtext pre {
|
|
719
|
+
background-color: var(--kyro-bg-secondary, #1e1e3f) !important;
|
|
720
|
+
color: var(--kyro-text, #f8f8f2) !important;
|
|
721
|
+
padding: 1rem !important;
|
|
722
|
+
border-radius: 0.375rem !important;
|
|
723
|
+
border: 1px solid var(--kyro-border) !important;
|
|
724
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !important;
|
|
725
|
+
overflow-x: auto !important;
|
|
726
|
+
margin: 1rem 0 !important;
|
|
727
|
+
}
|
|
728
|
+
.kyro-richtext pre code {
|
|
729
|
+
background: none !important;
|
|
730
|
+
padding: 0 !important;
|
|
731
|
+
border-radius: 0 !important;
|
|
732
|
+
font-size: 0.85rem !important;
|
|
733
|
+
}
|
|
734
|
+
.kyro-richtext h1 { font-size: 2em !important; font-weight: 700 !important; margin: 0 0 0.75rem !important; line-height: 1.2 !important; }
|
|
735
|
+
.kyro-richtext h2 { font-size: 1.5em !important; font-weight: 600 !important; margin: 0 0 0.75rem !important; line-height: 1.2 !important; }
|
|
736
|
+
.kyro-richtext h3 { font-size: 1.17em !important; font-weight: 600 !important; margin: 0 0 0.75rem !important; line-height: 1.2 !important; }
|
|
737
|
+
.kyro-richtext h4 { font-size: 1em !important; font-weight: 600 !important; margin: 0 0 0.75rem !important; line-height: 1.2 !important; }
|
|
738
|
+
.kyro-richtext ul, .kyro-richtext ol {
|
|
739
|
+
padding-left: 1.5rem !important;
|
|
740
|
+
}
|
|
741
|
+
.kyro-richtext blockquote {
|
|
742
|
+
border-left: 4px solid rgba(148, 163, 184, 0.5) !important;
|
|
743
|
+
margin-left: 0 !important;
|
|
744
|
+
padding-left: 1rem !important;
|
|
745
|
+
font-style: italic !important;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
`}</style>
|
|
749
|
+
|
|
750
|
+
{/* Media Picker Modal */}
|
|
751
|
+
{isMediaPickerOpen && (
|
|
752
|
+
<SlidePanel
|
|
753
|
+
open={isMediaPickerOpen}
|
|
754
|
+
onClose={() => setIsMediaPickerOpen(false)}
|
|
755
|
+
title="Select Image"
|
|
756
|
+
width="xl"
|
|
757
|
+
>
|
|
758
|
+
<MediaGallery
|
|
759
|
+
pickerMode
|
|
760
|
+
multiple={false}
|
|
761
|
+
onSelect={(selectedItems) => {
|
|
762
|
+
if (selectedItems && selectedItems.length > 0) {
|
|
763
|
+
const selectedImage = selectedItems[0];
|
|
764
|
+
editor
|
|
765
|
+
.chain()
|
|
766
|
+
.focus()
|
|
767
|
+
.setImage({
|
|
768
|
+
src: selectedImage.url,
|
|
769
|
+
alt: selectedImage.alt || selectedImage.title || "",
|
|
770
|
+
title: selectedImage.title || "",
|
|
771
|
+
})
|
|
772
|
+
.run();
|
|
773
|
+
}
|
|
774
|
+
setIsMediaPickerOpen(false);
|
|
775
|
+
}}
|
|
776
|
+
/>
|
|
777
|
+
</SlidePanel>
|
|
778
|
+
)}
|
|
779
|
+
</FieldLayout>
|
|
780
|
+
);
|
|
781
|
+
}
|