@alpaca-editor/core 1.0.4083 → 1.0.4085
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ui/card.js +1 -1
- package/dist/components/ui/card.js.map +1 -1
- package/dist/editor/FieldListField.d.ts +3 -2
- package/dist/editor/FieldListField.js +8 -8
- package/dist/editor/FieldListField.js.map +1 -1
- package/dist/editor/FieldListFieldWithFallbacks.js +2 -2
- package/dist/editor/FieldListFieldWithFallbacks.js.map +1 -1
- package/dist/editor/LinkEditorDialog.d.ts +1 -0
- package/dist/editor/LinkEditorDialog.js +4 -2
- package/dist/editor/LinkEditorDialog.js.map +1 -1
- package/dist/editor/client/EditorShell.js +11 -3
- package/dist/editor/client/EditorShell.js.map +1 -1
- package/dist/editor/client/editContext.d.ts +1 -0
- package/dist/editor/client/editContext.js.map +1 -1
- package/dist/editor/client/hooks/useWorkbox.js +6 -7
- package/dist/editor/client/hooks/useWorkbox.js.map +1 -1
- package/dist/editor/client/ui/EditorChrome.js +2 -1
- package/dist/editor/client/ui/EditorChrome.js.map +1 -1
- package/dist/editor/component-designer/ComponentDesigner.d.ts +1 -1
- package/dist/editor/component-designer/ComponentDesigner.js +48 -53
- package/dist/editor/component-designer/ComponentDesigner.js.map +1 -1
- package/dist/editor/control-center/About.js +1 -1
- package/dist/editor/control-center/About.js.map +1 -1
- package/dist/editor/field-types/RichTextEditor.d.ts +2 -1
- package/dist/editor/field-types/RichTextEditor.js +4 -4
- package/dist/editor/field-types/RichTextEditor.js.map +1 -1
- package/dist/editor/field-types/RichTextEditorComponent.d.ts +2 -1
- package/dist/editor/field-types/RichTextEditorComponent.js +2 -2
- package/dist/editor/field-types/RichTextEditorComponent.js.map +1 -1
- package/dist/editor/field-types/richtext/components/ReactSlate.js +36 -25
- package/dist/editor/field-types/richtext/components/ReactSlate.js.map +1 -1
- package/dist/editor/field-types/richtext/components/ToolbarButton.d.ts +2 -2
- package/dist/editor/field-types/richtext/components/ToolbarButton.js +3 -10
- package/dist/editor/field-types/richtext/components/ToolbarButton.js.map +1 -1
- package/dist/editor/field-types/richtext/types.d.ts +1 -0
- package/dist/editor/field-types/richtext/types.js.map +1 -1
- package/dist/editor/field-types/richtext/utils/conversion.js +180 -21
- package/dist/editor/field-types/richtext/utils/conversion.js.map +1 -1
- package/dist/editor/field-types/richtext/utils/plugins.d.ts +1 -0
- package/dist/editor/field-types/richtext/utils/plugins.js +19 -4
- package/dist/editor/field-types/richtext/utils/plugins.js.map +1 -1
- package/dist/editor/page-editor-chrome/FieldActionIndicator.js +11 -2
- package/dist/editor/page-editor-chrome/FieldActionIndicator.js.map +1 -1
- package/dist/editor/page-editor-chrome/FieldActionIndicators.js +1 -1
- package/dist/editor/page-editor-chrome/FieldActionIndicators.js.map +1 -1
- package/dist/editor/page-viewer/EditorForm.js +1 -1
- package/dist/editor/page-viewer/PageViewerFrame.js +2 -1
- package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
- package/dist/editor/utils/urlUtils.d.ts +9 -0
- package/dist/editor/utils/urlUtils.js +25 -0
- package/dist/editor/utils/urlUtils.js.map +1 -0
- package/dist/page-wizard/steps/SelectStep.js +28 -12
- package/dist/page-wizard/steps/SelectStep.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/dist/styles.css +24 -0
- package/package.json +1 -2
- package/src/components/ui/card.tsx +1 -1
- package/src/editor/FieldListField.tsx +11 -4
- package/src/editor/FieldListFieldWithFallbacks.tsx +3 -1
- package/src/editor/LinkEditorDialog.tsx +6 -2
- package/src/editor/client/EditorShell.tsx +20 -4
- package/src/editor/client/editContext.ts +1 -0
- package/src/editor/client/hooks/useWorkbox.ts +6 -7
- package/src/editor/client/ui/EditorChrome.tsx +4 -1
- package/src/editor/component-designer/ComponentDesigner.tsx +49 -53
- package/src/editor/control-center/About.tsx +0 -15
- package/src/editor/field-types/RichTextEditor.tsx +13 -10
- package/src/editor/field-types/RichTextEditorComponent.tsx +3 -0
- package/src/editor/field-types/richtext/components/ReactSlate.tsx +503 -437
- package/src/editor/field-types/richtext/components/ToolbarButton.tsx +12 -14
- package/src/editor/field-types/richtext/types.ts +18 -10
- package/src/editor/field-types/richtext/utils/conversion.ts +204 -23
- package/src/editor/field-types/richtext/utils/plugins.ts +24 -4
- package/src/editor/page-editor-chrome/FieldActionIndicator.tsx +63 -7
- package/src/editor/page-editor-chrome/FieldActionIndicators.tsx +1 -1
- package/src/editor/page-viewer/EditorForm.tsx +2 -2
- package/src/editor/page-viewer/PageViewerFrame.tsx +2 -1
- package/src/editor/utils/urlUtils.ts +24 -0
- package/src/page-wizard/steps/SelectStep.tsx +47 -18
- package/src/revision.ts +2 -2
- package/dist/editor/ui/StackedPanels.d.ts +0 -5
- package/dist/editor/ui/StackedPanels.js +0 -67
- package/dist/editor/ui/StackedPanels.js.map +0 -1
- package/src/editor/ui/StackedPanels.tsx +0 -134
|
@@ -8,7 +8,13 @@ import React, {
|
|
|
8
8
|
memo,
|
|
9
9
|
} from "react";
|
|
10
10
|
import { createEditor, Descendant, Editor, Element, Transforms } from "slate";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
Slate,
|
|
13
|
+
Editable,
|
|
14
|
+
withReact,
|
|
15
|
+
ReactEditor,
|
|
16
|
+
useSlateSelector,
|
|
17
|
+
} from "slate-react";
|
|
12
18
|
import { withHistory } from "slate-history";
|
|
13
19
|
import "./ReactSlate.css";
|
|
14
20
|
|
|
@@ -34,7 +40,9 @@ import { LinkEditorDialog } from "../../../LinkEditorDialog";
|
|
|
34
40
|
|
|
35
41
|
import { htmlToSlate, slateToHtml } from "../utils/conversion";
|
|
36
42
|
import { createPluginsFromConfig } from "../config/pluginFactory";
|
|
37
|
-
import { createKeyboardHandler } from "../utils/plugins";
|
|
43
|
+
import { createKeyboardHandler, generateInternalLinkUrl } from "../utils/plugins";
|
|
44
|
+
import { normalizeUrl } from "../../../utils/urlUtils";
|
|
45
|
+
|
|
38
46
|
import { useCachedSimplifiedProfile } from "../hooks/useProfileCache";
|
|
39
47
|
import { classNames } from "primereact/utils";
|
|
40
48
|
|
|
@@ -56,7 +64,8 @@ const ToolbarButtonWrapper: React.FC<{
|
|
|
56
64
|
case "link":
|
|
57
65
|
return editor.isLinkActive();
|
|
58
66
|
case "list":
|
|
59
|
-
const listType =
|
|
67
|
+
const listType =
|
|
68
|
+
option.id === "unordered-list" ? "unordered" : "ordered";
|
|
60
69
|
return editor.isListActive(listType);
|
|
61
70
|
case "insertion":
|
|
62
71
|
return false; // Insertion buttons are never active
|
|
@@ -66,11 +75,7 @@ const ToolbarButtonWrapper: React.FC<{
|
|
|
66
75
|
});
|
|
67
76
|
|
|
68
77
|
return (
|
|
69
|
-
<ToolbarButton
|
|
70
|
-
icon={icon}
|
|
71
|
-
active={isActive}
|
|
72
|
-
onMouseDown={onMouseDown}
|
|
73
|
-
/>
|
|
78
|
+
<ToolbarButton icon={icon} active={isActive} onMouseDown={onMouseDown} />
|
|
74
79
|
);
|
|
75
80
|
});
|
|
76
81
|
|
|
@@ -104,6 +109,7 @@ export const ReactSlate = forwardRef<any, ReactSlateProps>((props, ref) => {
|
|
|
104
109
|
readOnly = false,
|
|
105
110
|
placeholder = "Enter some text...",
|
|
106
111
|
profile,
|
|
112
|
+
showControls,
|
|
107
113
|
} = props;
|
|
108
114
|
|
|
109
115
|
const editorProfile = profile || {
|
|
@@ -118,12 +124,12 @@ export const ReactSlate = forwardRef<any, ReactSlateProps>((props, ref) => {
|
|
|
118
124
|
// Create the Slate editor with plugins - only once
|
|
119
125
|
const editor = useMemo(() => {
|
|
120
126
|
const baseEditor = createEditor();
|
|
121
|
-
|
|
127
|
+
|
|
122
128
|
// Apply plugins in correct order
|
|
123
129
|
const enhancedEditor = createPluginsFromConfig(
|
|
124
|
-
withHistory(withReact(baseEditor))
|
|
130
|
+
withHistory(withReact(baseEditor)),
|
|
125
131
|
);
|
|
126
|
-
|
|
132
|
+
|
|
127
133
|
return enhancedEditor;
|
|
128
134
|
}, []);
|
|
129
135
|
|
|
@@ -164,21 +170,24 @@ export const ReactSlate = forwardRef<any, ReactSlateProps>((props, ref) => {
|
|
|
164
170
|
// Only update if value actually changed from outside
|
|
165
171
|
if (value !== previousValueRef.current) {
|
|
166
172
|
previousValueRef.current = value;
|
|
167
|
-
|
|
173
|
+
|
|
168
174
|
// Update editor children with new value
|
|
169
175
|
const newChildren = slateValue;
|
|
170
|
-
|
|
176
|
+
|
|
171
177
|
// Prevent infinite loops by temporarily removing selection
|
|
172
178
|
const currentSelection = editor.selection;
|
|
173
|
-
|
|
179
|
+
|
|
174
180
|
// Update the editor's children
|
|
175
181
|
editor.children = newChildren;
|
|
176
|
-
|
|
182
|
+
|
|
177
183
|
// Normalize the editor to ensure consistency
|
|
178
184
|
Editor.normalize(editor, { force: true });
|
|
179
|
-
|
|
185
|
+
|
|
180
186
|
// Restore selection if it was valid, otherwise set to end
|
|
181
|
-
if (
|
|
187
|
+
if (
|
|
188
|
+
currentSelection &&
|
|
189
|
+
Editor.hasPath(editor, currentSelection.anchor.path)
|
|
190
|
+
) {
|
|
182
191
|
try {
|
|
183
192
|
Transforms.select(editor, currentSelection);
|
|
184
193
|
} catch {
|
|
@@ -198,7 +207,7 @@ export const ReactSlate = forwardRef<any, ReactSlateProps>((props, ref) => {
|
|
|
198
207
|
if (onChange) {
|
|
199
208
|
try {
|
|
200
209
|
const html = slateToHtml(newValue, simplifiedProfile);
|
|
201
|
-
|
|
210
|
+
|
|
202
211
|
// Only trigger onChange if content actually changed
|
|
203
212
|
if (html !== value) {
|
|
204
213
|
// Mark this as an internal change
|
|
@@ -235,19 +244,26 @@ export const ReactSlate = forwardRef<any, ReactSlateProps>((props, ref) => {
|
|
|
235
244
|
});
|
|
236
245
|
}, [editor]);
|
|
237
246
|
|
|
238
|
-
const handleStripFormattingClick = useCallback(
|
|
239
|
-
event.
|
|
240
|
-
|
|
241
|
-
|
|
247
|
+
const handleStripFormattingClick = useCallback(
|
|
248
|
+
(event: React.MouseEvent<HTMLButtonElement>) => {
|
|
249
|
+
event.preventDefault();
|
|
250
|
+
editor.stripFormatting();
|
|
251
|
+
},
|
|
252
|
+
[editor],
|
|
253
|
+
);
|
|
242
254
|
|
|
243
255
|
// Memoize option handlers to prevent creating new functions on every render
|
|
244
256
|
const optionHandlers = useMemo(() => {
|
|
245
|
-
const handlers: Record<
|
|
246
|
-
|
|
247
|
-
|
|
257
|
+
const handlers: Record<
|
|
258
|
+
string,
|
|
259
|
+
(editor: Editor, event: React.MouseEvent) => void
|
|
260
|
+
> = {};
|
|
261
|
+
|
|
262
|
+
const handleOptionSelect =
|
|
263
|
+
(option: ToolbarOptionConfig) =>
|
|
248
264
|
(editor: Editor, event: React.MouseEvent) => {
|
|
249
265
|
event.preventDefault();
|
|
250
|
-
|
|
266
|
+
|
|
251
267
|
switch (option.type) {
|
|
252
268
|
case "mark":
|
|
253
269
|
editor.toggleMark(option.id);
|
|
@@ -263,7 +279,8 @@ export const ReactSlate = forwardRef<any, ReactSlateProps>((props, ref) => {
|
|
|
263
279
|
handleLinkButtonClick();
|
|
264
280
|
break;
|
|
265
281
|
case "list":
|
|
266
|
-
const listType =
|
|
282
|
+
const listType =
|
|
283
|
+
option.id === "unordered-list" ? "unordered" : "ordered";
|
|
267
284
|
editor.toggleList(listType);
|
|
268
285
|
break;
|
|
269
286
|
case "insertion":
|
|
@@ -277,8 +294,8 @@ export const ReactSlate = forwardRef<any, ReactSlateProps>((props, ref) => {
|
|
|
277
294
|
};
|
|
278
295
|
|
|
279
296
|
// Pre-create handlers for all possible options
|
|
280
|
-
editorProfile.toolbar.groups.forEach(group => {
|
|
281
|
-
group.options.forEach(option => {
|
|
297
|
+
editorProfile.toolbar.groups.forEach((group) => {
|
|
298
|
+
group.options.forEach((option) => {
|
|
282
299
|
const key = `${option.type}-${option.id}`;
|
|
283
300
|
handlers[key] = handleOptionSelect(option);
|
|
284
301
|
});
|
|
@@ -289,8 +306,11 @@ export const ReactSlate = forwardRef<any, ReactSlateProps>((props, ref) => {
|
|
|
289
306
|
|
|
290
307
|
// Create button handlers that adapt the signature for toolbar buttons
|
|
291
308
|
const buttonHandlers = useMemo(() => {
|
|
292
|
-
const handlers: Record<
|
|
293
|
-
|
|
309
|
+
const handlers: Record<
|
|
310
|
+
string,
|
|
311
|
+
(event: React.MouseEvent<HTMLButtonElement>) => void
|
|
312
|
+
> = {};
|
|
313
|
+
|
|
294
314
|
Object.entries(optionHandlers).forEach(([key, handler]) => {
|
|
295
315
|
handlers[key] = (event: React.MouseEvent<HTMLButtonElement>) => {
|
|
296
316
|
handler(editor, event);
|
|
@@ -300,266 +320,288 @@ export const ReactSlate = forwardRef<any, ReactSlateProps>((props, ref) => {
|
|
|
300
320
|
return handlers;
|
|
301
321
|
}, [optionHandlers, editor]);
|
|
302
322
|
|
|
303
|
-
const getOption = useCallback(
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
id: option.id,
|
|
311
|
-
label: markConfig.label,
|
|
312
|
-
icon: markConfig.icon,
|
|
313
|
-
isActive: (editor: Editor) => editor.isMarkActive(option.id),
|
|
314
|
-
toggle: optionHandlers[handlerKey],
|
|
315
|
-
};
|
|
316
|
-
case "block":
|
|
317
|
-
const blockConfig = SLATE_BLOCKS[option.id];
|
|
318
|
-
return {
|
|
319
|
-
id: option.id,
|
|
320
|
-
label: blockConfig.label,
|
|
321
|
-
icon: blockConfig.icon,
|
|
322
|
-
isActive: (editor: Editor) => editor.isBlockActive(option.id),
|
|
323
|
-
toggle: optionHandlers[handlerKey],
|
|
324
|
-
};
|
|
325
|
-
case "alignment":
|
|
326
|
-
const alignConfig = SLATE_ALIGNMENTS[option.id];
|
|
327
|
-
return {
|
|
328
|
-
id: option.id,
|
|
329
|
-
label: alignConfig.label,
|
|
330
|
-
icon: alignConfig.icon,
|
|
331
|
-
isActive: (editor: Editor) => editor.isAlignActive(alignConfig.value),
|
|
332
|
-
toggle: optionHandlers[handlerKey],
|
|
333
|
-
};
|
|
334
|
-
case "link":
|
|
335
|
-
return {
|
|
336
|
-
id: "link",
|
|
337
|
-
label: "Link",
|
|
338
|
-
icon: "🔗",
|
|
339
|
-
isActive: (editor: Editor) => editor.isLinkActive(),
|
|
340
|
-
toggle: optionHandlers[handlerKey],
|
|
341
|
-
};
|
|
342
|
-
case "list":
|
|
343
|
-
return {
|
|
344
|
-
id: option.id,
|
|
345
|
-
label: option.id === "unordered-list" ? "Bulleted List" : "Numbered List",
|
|
346
|
-
icon: option.id === "unordered-list" ? "•" : "1.",
|
|
347
|
-
isActive: (editor: Editor) => editor.isListActive(
|
|
348
|
-
option.id === "unordered-list" ? "unordered" : "ordered"
|
|
349
|
-
),
|
|
350
|
-
toggle: optionHandlers[handlerKey],
|
|
351
|
-
};
|
|
352
|
-
case "divider":
|
|
353
|
-
return { id: "divider", label: "Divider" };
|
|
354
|
-
case "insertion":
|
|
355
|
-
if (option.id === "horizontal-rule") {
|
|
323
|
+
const getOption = useCallback(
|
|
324
|
+
(option: ToolbarOptionConfig): CustomOption | undefined => {
|
|
325
|
+
const handlerKey = `${option.type}-${option.id}`;
|
|
326
|
+
|
|
327
|
+
switch (option.type) {
|
|
328
|
+
case "mark":
|
|
329
|
+
const markConfig = SLATE_MARKS[option.id];
|
|
356
330
|
return {
|
|
357
331
|
id: option.id,
|
|
358
|
-
label:
|
|
359
|
-
icon:
|
|
360
|
-
isActive: () =>
|
|
332
|
+
label: markConfig.label,
|
|
333
|
+
icon: markConfig.icon,
|
|
334
|
+
isActive: (editor: Editor) => editor.isMarkActive(option.id),
|
|
361
335
|
toggle: optionHandlers[handlerKey],
|
|
362
336
|
};
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
337
|
+
case "block":
|
|
338
|
+
const blockConfig = SLATE_BLOCKS[option.id];
|
|
339
|
+
return {
|
|
340
|
+
id: option.id,
|
|
341
|
+
label: blockConfig.label,
|
|
342
|
+
icon: blockConfig.icon,
|
|
343
|
+
isActive: (editor: Editor) => editor.isBlockActive(option.id),
|
|
344
|
+
toggle: optionHandlers[handlerKey],
|
|
345
|
+
};
|
|
346
|
+
case "alignment":
|
|
347
|
+
const alignConfig = SLATE_ALIGNMENTS[option.id];
|
|
348
|
+
return {
|
|
349
|
+
id: option.id,
|
|
350
|
+
label: alignConfig.label,
|
|
351
|
+
icon: alignConfig.icon,
|
|
352
|
+
isActive: (editor: Editor) =>
|
|
353
|
+
editor.isAlignActive(alignConfig.value),
|
|
354
|
+
toggle: optionHandlers[handlerKey],
|
|
355
|
+
};
|
|
356
|
+
case "link":
|
|
357
|
+
return {
|
|
358
|
+
id: "link",
|
|
359
|
+
label: "Link",
|
|
360
|
+
icon: "🔗",
|
|
361
|
+
isActive: (editor: Editor) => editor.isLinkActive(),
|
|
362
|
+
toggle: optionHandlers[handlerKey],
|
|
363
|
+
};
|
|
364
|
+
case "list":
|
|
365
|
+
return {
|
|
366
|
+
id: option.id,
|
|
367
|
+
label:
|
|
368
|
+
option.id === "unordered-list"
|
|
369
|
+
? "Bulleted List"
|
|
370
|
+
: "Numbered List",
|
|
371
|
+
icon: option.id === "unordered-list" ? "•" : "1.",
|
|
372
|
+
isActive: (editor: Editor) =>
|
|
373
|
+
editor.isListActive(
|
|
374
|
+
option.id === "unordered-list" ? "unordered" : "ordered",
|
|
375
|
+
),
|
|
376
|
+
toggle: optionHandlers[handlerKey],
|
|
377
|
+
};
|
|
378
|
+
case "divider":
|
|
379
|
+
return { id: "divider", label: "Divider" };
|
|
380
|
+
case "insertion":
|
|
381
|
+
if (option.id === "horizontal-rule") {
|
|
382
|
+
return {
|
|
383
|
+
id: option.id,
|
|
384
|
+
label: "Horizontal Rule",
|
|
385
|
+
icon: "─",
|
|
386
|
+
isActive: () => false, // Insertion buttons are never "active"
|
|
387
|
+
toggle: optionHandlers[handlerKey],
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
return undefined;
|
|
391
|
+
default:
|
|
392
|
+
return undefined;
|
|
393
|
+
}
|
|
394
|
+
},
|
|
395
|
+
[optionHandlers],
|
|
396
|
+
);
|
|
369
397
|
|
|
370
398
|
// Memoize dropdown options creation with strict type safety
|
|
371
|
-
const createDropdownOptions = useCallback(
|
|
372
|
-
options: readonly ToolbarOptionConfig[]
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
399
|
+
const createDropdownOptions = useCallback(
|
|
400
|
+
(options: readonly ToolbarOptionConfig[]): DropdownOption<any>[] => {
|
|
401
|
+
return options
|
|
402
|
+
.map((option) => {
|
|
403
|
+
const optionObj = getOption(option);
|
|
404
|
+
if (!optionObj) return null;
|
|
405
|
+
|
|
406
|
+
return {
|
|
407
|
+
value: optionObj,
|
|
408
|
+
label: optionObj.label || option.id,
|
|
409
|
+
icon: optionObj.icon,
|
|
410
|
+
style: (optionObj as any).style,
|
|
411
|
+
isActive: (editor: Editor) => {
|
|
412
|
+
switch (option.type) {
|
|
413
|
+
case "mark":
|
|
414
|
+
return editor.isMarkActive(option.id);
|
|
415
|
+
case "block":
|
|
416
|
+
return editor.isBlockActive(option.id);
|
|
417
|
+
case "alignment":
|
|
418
|
+
return editor.isAlignActive(
|
|
419
|
+
SLATE_ALIGNMENTS[option.id].value,
|
|
420
|
+
);
|
|
421
|
+
case "link":
|
|
422
|
+
return editor.isLinkActive();
|
|
423
|
+
case "list":
|
|
424
|
+
const listType =
|
|
425
|
+
option.id === "unordered-list" ? "unordered" : "ordered";
|
|
426
|
+
return editor.isListActive(listType);
|
|
427
|
+
default:
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
430
|
+
},
|
|
431
|
+
onSelect: optionHandlers[`${option.type}-${option.id}`],
|
|
432
|
+
};
|
|
433
|
+
})
|
|
434
|
+
.filter(Boolean) as DropdownOption<any>[];
|
|
435
|
+
},
|
|
436
|
+
[getOption, optionHandlers],
|
|
437
|
+
);
|
|
406
438
|
|
|
407
439
|
// Helper function to split options by dividers into sub-groups
|
|
408
|
-
const splitOptionsByDividers = useCallback(
|
|
409
|
-
options: ToolbarOptionConfig[]
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
440
|
+
const splitOptionsByDividers = useCallback(
|
|
441
|
+
(options: ToolbarOptionConfig[]): ToolbarOptionConfig[][] => {
|
|
442
|
+
const subGroups: ToolbarOptionConfig[][] = [];
|
|
443
|
+
let currentGroup: ToolbarOptionConfig[] = [];
|
|
444
|
+
|
|
445
|
+
options.forEach((option) => {
|
|
446
|
+
if (option.type === "divider") {
|
|
447
|
+
if (currentGroup.length > 0) {
|
|
448
|
+
subGroups.push(currentGroup);
|
|
449
|
+
currentGroup = [];
|
|
450
|
+
}
|
|
451
|
+
} else {
|
|
452
|
+
currentGroup.push(option);
|
|
419
453
|
}
|
|
420
|
-
}
|
|
421
|
-
currentGroup.push(option);
|
|
422
|
-
}
|
|
423
|
-
});
|
|
454
|
+
});
|
|
424
455
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
456
|
+
// Add the last group if it has options
|
|
457
|
+
if (currentGroup.length > 0) {
|
|
458
|
+
subGroups.push(currentGroup);
|
|
459
|
+
}
|
|
429
460
|
|
|
430
|
-
|
|
431
|
-
|
|
461
|
+
return subGroups;
|
|
462
|
+
},
|
|
463
|
+
[],
|
|
464
|
+
);
|
|
432
465
|
|
|
433
466
|
// Memoize toolbar group rendering
|
|
434
|
-
const renderToolbarGroup = useCallback(
|
|
435
|
-
|
|
436
|
-
(
|
|
437
|
-
getOption(option) || option.type === "divider",
|
|
438
|
-
|
|
467
|
+
const renderToolbarGroup = useCallback(
|
|
468
|
+
(group: ToolbarGroupConfig, index: number) => {
|
|
469
|
+
const validOptions = group.options.filter(
|
|
470
|
+
(option) => getOption(option) || option.type === "divider",
|
|
471
|
+
);
|
|
439
472
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
{group.
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
// If there's only one block option, render it as a disabled-style button
|
|
486
|
-
if (blockOptions.length === 1) {
|
|
487
|
-
const singleOption = dropdownOptions.find((opt) =>
|
|
488
|
-
blockOptions.some(
|
|
489
|
-
(blockOpt) =>
|
|
490
|
-
blockOpt.type === "block" &&
|
|
491
|
-
getOption(blockOpt) === opt.value,
|
|
492
|
-
),
|
|
473
|
+
if (validOptions.length === 0) return null;
|
|
474
|
+
|
|
475
|
+
// Skip rendering dropdown groups with only one option
|
|
476
|
+
if (group.display === "dropdown" && validOptions.length === 1)
|
|
477
|
+
return null;
|
|
478
|
+
|
|
479
|
+
const groupStyle: React.CSSProperties = {
|
|
480
|
+
display: "flex",
|
|
481
|
+
alignItems: "center",
|
|
482
|
+
gap: "8px",
|
|
483
|
+
flexWrap: "wrap",
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
return (
|
|
487
|
+
<div key={`group-${group.id || index}`} style={groupStyle}>
|
|
488
|
+
{group.display === "buttons"
|
|
489
|
+
? (() => {
|
|
490
|
+
const subGroups = splitOptionsByDividers(validOptions);
|
|
491
|
+
return subGroups.map((subGroup, subGroupIndex) => (
|
|
492
|
+
<div
|
|
493
|
+
key={`subgroup-${subGroupIndex}`}
|
|
494
|
+
className="toolbar-button-group"
|
|
495
|
+
>
|
|
496
|
+
{subGroup.map((option) => {
|
|
497
|
+
const optionObj = getOption(option);
|
|
498
|
+
const handler =
|
|
499
|
+
buttonHandlers[`${option.type}-${option.id}`];
|
|
500
|
+
if (!optionObj || !handler) return null;
|
|
501
|
+
|
|
502
|
+
return (
|
|
503
|
+
<ToolbarButtonWrapper
|
|
504
|
+
key={`${option.type}-${option.id}`}
|
|
505
|
+
option={option}
|
|
506
|
+
icon={optionObj.icon}
|
|
507
|
+
onMouseDown={handler}
|
|
508
|
+
/>
|
|
509
|
+
);
|
|
510
|
+
})}
|
|
511
|
+
</div>
|
|
512
|
+
));
|
|
513
|
+
})()
|
|
514
|
+
: (() => {
|
|
515
|
+
const dropdownOptions = createDropdownOptions(validOptions);
|
|
516
|
+
const blockOptions = validOptions.filter(
|
|
517
|
+
(option) => option.type === "block",
|
|
493
518
|
);
|
|
494
519
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
<span className="toolbar-dropdown-arrow">▼</span>
|
|
511
|
-
</>
|
|
512
|
-
) : (
|
|
513
|
-
<>
|
|
514
|
-
<span className="toolbar-dropdown-icon">
|
|
515
|
-
{singleOption?.icon && (
|
|
516
|
-
<span className="toolbar-dropdown-icon">
|
|
517
|
-
{singleOption.icon}
|
|
518
|
-
</span>
|
|
519
|
-
)}
|
|
520
|
+
// If there's only one block option, render it as a disabled-style button
|
|
521
|
+
if (blockOptions.length === 1) {
|
|
522
|
+
const singleOption = dropdownOptions.find((opt) =>
|
|
523
|
+
blockOptions.some(
|
|
524
|
+
(blockOpt) =>
|
|
525
|
+
blockOpt.type === "block" &&
|
|
526
|
+
getOption(blockOpt) === opt.value,
|
|
527
|
+
),
|
|
528
|
+
);
|
|
529
|
+
|
|
530
|
+
return (
|
|
531
|
+
<div className="toolbar-dropdown-container">
|
|
532
|
+
<button className="toolbar-dropdown-button" disabled>
|
|
533
|
+
{group.label ? (
|
|
534
|
+
<>
|
|
520
535
|
<span className="toolbar-dropdown-content">
|
|
521
|
-
{singleOption?.label}
|
|
536
|
+
{group.label}: {singleOption?.label}
|
|
522
537
|
</span>
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
538
|
+
<span className="toolbar-dropdown-arrow">▼</span>
|
|
539
|
+
</>
|
|
540
|
+
) : group.showIconsOnly ? (
|
|
541
|
+
<>
|
|
542
|
+
<span className="toolbar-dropdown-icon">
|
|
543
|
+
{singleOption?.icon}
|
|
544
|
+
</span>
|
|
545
|
+
<span className="toolbar-dropdown-arrow">▼</span>
|
|
546
|
+
</>
|
|
547
|
+
) : (
|
|
548
|
+
<>
|
|
549
|
+
<span className="toolbar-dropdown-icon">
|
|
550
|
+
{singleOption?.icon && (
|
|
551
|
+
<span className="toolbar-dropdown-icon">
|
|
552
|
+
{singleOption.icon}
|
|
553
|
+
</span>
|
|
554
|
+
)}
|
|
555
|
+
<span className="toolbar-dropdown-content">
|
|
556
|
+
{singleOption?.label}
|
|
557
|
+
</span>
|
|
558
|
+
</span>
|
|
559
|
+
<span className="toolbar-dropdown-arrow">▼</span>
|
|
560
|
+
</>
|
|
561
|
+
)}
|
|
562
|
+
</button>
|
|
563
|
+
</div>
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// Multiple options - render normally wrapped in grey container
|
|
568
|
+
const isActive = dropdownOptions.some((option) =>
|
|
569
|
+
option.isActive(editor),
|
|
529
570
|
);
|
|
530
|
-
}
|
|
531
571
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
572
|
+
const buttonStyle = {
|
|
573
|
+
padding: "5px 10px",
|
|
574
|
+
margin: "0",
|
|
575
|
+
background: isActive ? "#ffffff" : "transparent",
|
|
576
|
+
border: "none",
|
|
577
|
+
borderRadius: "3px",
|
|
578
|
+
cursor: "pointer",
|
|
579
|
+
boxShadow: isActive ? "0 1px 2px rgba(0,0,0,0.1)" : "none",
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
return (
|
|
583
|
+
<div className="toolbar-dropdown-container">
|
|
584
|
+
<MemoizedEditorDropdown
|
|
585
|
+
options={dropdownOptions}
|
|
586
|
+
editor={editor}
|
|
587
|
+
label={group.label}
|
|
588
|
+
buttonStyle={buttonStyle}
|
|
589
|
+
/>
|
|
590
|
+
</div>
|
|
591
|
+
);
|
|
592
|
+
})()}
|
|
593
|
+
</div>
|
|
594
|
+
);
|
|
595
|
+
},
|
|
596
|
+
[
|
|
597
|
+
getOption,
|
|
598
|
+
splitOptionsByDividers,
|
|
599
|
+
createDropdownOptions,
|
|
600
|
+
optionHandlers,
|
|
601
|
+
buttonHandlers,
|
|
602
|
+
editor,
|
|
603
|
+
],
|
|
604
|
+
);
|
|
563
605
|
|
|
564
606
|
// Memoize toolbar structure (expensive grouping operation)
|
|
565
607
|
const toolbarStructure = useMemo(() => {
|
|
@@ -575,8 +617,9 @@ export const ReactSlate = forwardRef<any, ReactSlateProps>((props, ref) => {
|
|
|
575
617
|
);
|
|
576
618
|
|
|
577
619
|
// Return sorted entries for consistent rendering
|
|
578
|
-
return Object.entries(groupsByRow)
|
|
579
|
-
|
|
620
|
+
return Object.entries(groupsByRow).sort(
|
|
621
|
+
([a], [b]) => parseInt(a) - parseInt(b),
|
|
622
|
+
);
|
|
580
623
|
}, [editorProfile.toolbar.groups]);
|
|
581
624
|
|
|
582
625
|
const editLink = useCallback((element: any) => {
|
|
@@ -634,11 +677,12 @@ export const ReactSlate = forwardRef<any, ReactSlateProps>((props, ref) => {
|
|
|
634
677
|
},
|
|
635
678
|
};
|
|
636
679
|
} else {
|
|
680
|
+
const normalizedUrl = normalizeUrl(link.url || "");
|
|
637
681
|
newProperties = {
|
|
638
|
-
url:
|
|
682
|
+
url: normalizedUrl,
|
|
639
683
|
link: {
|
|
640
684
|
type: "external",
|
|
641
|
-
url:
|
|
685
|
+
url: normalizedUrl,
|
|
642
686
|
target: link.target,
|
|
643
687
|
queryString: link.queryString,
|
|
644
688
|
},
|
|
@@ -664,51 +708,59 @@ export const ReactSlate = forwardRef<any, ReactSlateProps>((props, ref) => {
|
|
|
664
708
|
);
|
|
665
709
|
|
|
666
710
|
// Use the keyboard handler from plugins
|
|
667
|
-
const handleKeyDown = useMemo(
|
|
711
|
+
const handleKeyDown = useMemo(
|
|
712
|
+
() => createKeyboardHandler(editor, simplifiedProfile),
|
|
713
|
+
[editor, simplifiedProfile],
|
|
714
|
+
);
|
|
668
715
|
|
|
669
716
|
// Calculate proper list numbering for ordered lists
|
|
670
|
-
const calculateListNumbers = useCallback(
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
(element
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
717
|
+
const calculateListNumbers = useCallback(
|
|
718
|
+
(elements: Descendant[]): Map<string, string> => {
|
|
719
|
+
const numberMap = new Map<string, string>();
|
|
720
|
+
const counters: number[] = [0, 0, 0, 0, 0, 0]; // Support up to 6 levels
|
|
721
|
+
let wasInList = false; // Track if previous element was a list item
|
|
722
|
+
|
|
723
|
+
elements.forEach((element, index) => {
|
|
724
|
+
if (
|
|
725
|
+
Element.isElement(element) &&
|
|
726
|
+
element.type === "list-item" &&
|
|
727
|
+
(element as any).listType === "ordered"
|
|
728
|
+
) {
|
|
729
|
+
const indent = (element as any).indent || 0;
|
|
730
|
+
|
|
731
|
+
// Ensure indent is within bounds
|
|
732
|
+
if (indent < 0 || indent >= counters.length) return;
|
|
733
|
+
|
|
734
|
+
// If we weren't in a list before, this is a new list - reset all counters
|
|
735
|
+
if (!wasInList) {
|
|
736
|
+
for (let i = 0; i < counters.length; i++) {
|
|
737
|
+
counters[i] = 0;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// Increment counter for current level
|
|
742
|
+
counters[indent] = (counters[indent] || 0) + 1;
|
|
743
|
+
|
|
744
|
+
// Reset all deeper level counters
|
|
745
|
+
for (let i = indent + 1; i < counters.length; i++) {
|
|
687
746
|
counters[i] = 0;
|
|
688
747
|
}
|
|
748
|
+
|
|
749
|
+
// Build number string for current level only (e.g., "3.")
|
|
750
|
+
const numberString = counters[indent] + ".";
|
|
751
|
+
numberMap.set(`${index}`, numberString);
|
|
752
|
+
|
|
753
|
+
wasInList = true;
|
|
754
|
+
} else {
|
|
755
|
+
// Not a list item, so we're no longer in a list
|
|
756
|
+
wasInList = false;
|
|
689
757
|
}
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
counters[i] = 0;
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
// Build number string for current level only (e.g., "3.")
|
|
700
|
-
const numberString = counters[indent] + '.';
|
|
701
|
-
numberMap.set(`${index}`, numberString);
|
|
702
|
-
|
|
703
|
-
wasInList = true;
|
|
704
|
-
} else {
|
|
705
|
-
// Not a list item, so we're no longer in a list
|
|
706
|
-
wasInList = false;
|
|
707
|
-
}
|
|
708
|
-
});
|
|
709
|
-
|
|
710
|
-
return numberMap;
|
|
711
|
-
}, []);
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
return numberMap;
|
|
761
|
+
},
|
|
762
|
+
[],
|
|
763
|
+
);
|
|
712
764
|
|
|
713
765
|
// Calculate list numbers for the entire editor content (memoized for performance)
|
|
714
766
|
const listNumbers = useMemo(() => {
|
|
@@ -716,146 +768,160 @@ export const ReactSlate = forwardRef<any, ReactSlateProps>((props, ref) => {
|
|
|
716
768
|
}, [editor.children, calculateListNumbers]);
|
|
717
769
|
|
|
718
770
|
// Memoize renderElement to prevent unnecessary re-renders as per Slate performance docs
|
|
719
|
-
const renderElement = useCallback(
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
771
|
+
const renderElement = useCallback(
|
|
772
|
+
({ attributes, children, element }: any) => {
|
|
773
|
+
const style: React.CSSProperties = {
|
|
774
|
+
textAlign: element.align || "left",
|
|
775
|
+
};
|
|
723
776
|
|
|
724
777
|
if (element.type === "link") {
|
|
725
778
|
const isInternal = element.link?.type === "internal";
|
|
726
779
|
const url = isInternal
|
|
727
|
-
?
|
|
728
|
-
: element.url || element.link?.url || "#";
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
780
|
+
? generateInternalLinkUrl(element.link?.itemId, element.link?.targetItemLongId, element.link?.queryString)
|
|
781
|
+
: normalizeUrl(element.url || element.link?.url || "#");
|
|
782
|
+
|
|
783
|
+
return (
|
|
784
|
+
<a
|
|
785
|
+
{...attributes}
|
|
786
|
+
href={url}
|
|
787
|
+
style={style}
|
|
788
|
+
className={`slate-link ${isInternal ? "internal-link" : "external-link"}`}
|
|
789
|
+
onClick={(e) => {
|
|
790
|
+
if (!readOnly) {
|
|
791
|
+
e.preventDefault();
|
|
792
|
+
editLink(element);
|
|
793
|
+
}
|
|
794
|
+
}}
|
|
795
|
+
>
|
|
796
|
+
{children}
|
|
797
|
+
</a>
|
|
798
|
+
);
|
|
799
|
+
}
|
|
747
800
|
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
801
|
+
if (element.type === "list-item") {
|
|
802
|
+
const indent = element.indent || 0;
|
|
803
|
+
const listType = element.listType || "unordered";
|
|
751
804
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
805
|
+
const listStyle: React.CSSProperties = {
|
|
806
|
+
...style,
|
|
807
|
+
position: "relative",
|
|
808
|
+
listStyleType: "none",
|
|
809
|
+
};
|
|
757
810
|
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
811
|
+
// Find the element's index in the editor children to get the correct number
|
|
812
|
+
const elementIndex = editor.children.findIndex(
|
|
813
|
+
(child: any) => child === element,
|
|
814
|
+
);
|
|
815
|
+
const listNumber = listNumbers.get(`${elementIndex}`) || "";
|
|
816
|
+
|
|
817
|
+
return (
|
|
818
|
+
<div
|
|
819
|
+
{...attributes}
|
|
820
|
+
style={listStyle}
|
|
821
|
+
className={`slate-list-item slate-list-${listType}`}
|
|
822
|
+
data-indent={indent}
|
|
823
|
+
data-list-number={listNumber}
|
|
824
|
+
>
|
|
825
|
+
<span className="slate-list-bullet"></span>
|
|
826
|
+
<div className="slate-list-content">{children}</div>
|
|
827
|
+
</div>
|
|
828
|
+
);
|
|
829
|
+
}
|
|
761
830
|
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
831
|
+
if (element.type === "horizontal-rule") {
|
|
832
|
+
return (
|
|
833
|
+
<div
|
|
834
|
+
{...attributes}
|
|
835
|
+
contentEditable={false}
|
|
836
|
+
style={{ ...style, userSelect: "none" }}
|
|
837
|
+
>
|
|
838
|
+
{children}
|
|
839
|
+
<hr
|
|
840
|
+
style={{
|
|
841
|
+
border: "none",
|
|
842
|
+
borderTop: "1px solid #ccc",
|
|
843
|
+
margin: "1em 0",
|
|
844
|
+
width: "100%",
|
|
845
|
+
}}
|
|
846
|
+
/>
|
|
847
|
+
</div>
|
|
848
|
+
);
|
|
849
|
+
}
|
|
775
850
|
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
width: "100%"
|
|
785
|
-
}} />
|
|
786
|
-
</div>
|
|
787
|
-
);
|
|
788
|
-
}
|
|
851
|
+
if (element.type === "line-break") {
|
|
852
|
+
return (
|
|
853
|
+
<span {...attributes} contentEditable={false}>
|
|
854
|
+
{children}
|
|
855
|
+
<br />
|
|
856
|
+
</span>
|
|
857
|
+
);
|
|
858
|
+
}
|
|
789
859
|
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
860
|
+
// Handle different block types using built-in SLATE_BLOCKS configuration
|
|
861
|
+
const isValidBlockId = element.type in SLATE_BLOCKS;
|
|
862
|
+
const blockConfig = isValidBlockId
|
|
863
|
+
? SLATE_BLOCKS[element.type as BlockId]
|
|
864
|
+
: undefined;
|
|
865
|
+
if (blockConfig && element.type === "no-tag") {
|
|
866
|
+
// Special handling for no-tag blocks (plain text without wrapper)
|
|
867
|
+
return (
|
|
868
|
+
<div {...attributes} style={style}>
|
|
869
|
+
{children}
|
|
870
|
+
</div>
|
|
871
|
+
);
|
|
872
|
+
}
|
|
798
873
|
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
874
|
+
// For standard blocks, use the appropriate HTML tag
|
|
875
|
+
if (blockConfig) {
|
|
876
|
+
const tagName = blockConfig.htmlTag;
|
|
877
|
+
return React.createElement(tagName, { ...attributes, style }, children);
|
|
878
|
+
}
|
|
879
|
+
// Default fallback to paragraph
|
|
804
880
|
return (
|
|
805
|
-
<
|
|
881
|
+
<p {...attributes} style={style}>
|
|
806
882
|
{children}
|
|
807
|
-
</
|
|
883
|
+
</p>
|
|
808
884
|
);
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
if (blockConfig) {
|
|
813
|
-
const tagName = blockConfig.htmlTag;
|
|
814
|
-
return React.createElement(
|
|
815
|
-
tagName,
|
|
816
|
-
{ ...attributes, style },
|
|
817
|
-
children,
|
|
818
|
-
);
|
|
819
|
-
}
|
|
820
|
-
// Default fallback to paragraph
|
|
821
|
-
return (
|
|
822
|
-
<p {...attributes} style={style}>
|
|
823
|
-
{children}
|
|
824
|
-
</p>
|
|
825
|
-
);
|
|
826
|
-
}, [readOnly, editLink, editor.children, listNumbers]);
|
|
885
|
+
},
|
|
886
|
+
[readOnly, editLink, editor.children, listNumbers],
|
|
887
|
+
);
|
|
827
888
|
|
|
828
889
|
// Memoize renderLeaf to prevent unnecessary re-renders as per Slate performance docs
|
|
829
|
-
const renderLeaf = useCallback(
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
890
|
+
const renderLeaf = useCallback(
|
|
891
|
+
({ attributes, children, leaf }: any) => {
|
|
892
|
+
let el = <span {...attributes}>{children}</span>;
|
|
893
|
+
|
|
894
|
+
// Apply marks using the built-in SLATE_MARKS configuration
|
|
895
|
+
simplifiedProfile.marks.forEach((markId) => {
|
|
896
|
+
if ((leaf as any)[markId]) {
|
|
897
|
+
const markConfig = SLATE_MARKS[markId];
|
|
898
|
+
if (markConfig) {
|
|
899
|
+
if (markId === "extrabold") {
|
|
900
|
+
// Special handling for extrabold with CSS class
|
|
901
|
+
el = <span className="extrabold">{el}</span>;
|
|
902
|
+
} else {
|
|
903
|
+
// Standard HTML tag rendering
|
|
904
|
+
const tagName = markConfig.htmlTag;
|
|
905
|
+
el = React.createElement(tagName, {}, el);
|
|
906
|
+
}
|
|
845
907
|
}
|
|
846
908
|
}
|
|
847
|
-
}
|
|
848
|
-
});
|
|
909
|
+
});
|
|
849
910
|
|
|
850
|
-
|
|
851
|
-
|
|
911
|
+
return el;
|
|
912
|
+
},
|
|
913
|
+
[simplifiedProfile.marks],
|
|
914
|
+
);
|
|
852
915
|
|
|
853
916
|
// Memoize renderPlaceholder for consistency
|
|
854
|
-
const renderPlaceholder = useCallback(
|
|
855
|
-
|
|
856
|
-
{
|
|
857
|
-
|
|
858
|
-
|
|
917
|
+
const renderPlaceholder = useCallback(
|
|
918
|
+
({ attributes, children }: any) => (
|
|
919
|
+
<span {...attributes} className="p-2 text-gray-500">
|
|
920
|
+
{children}
|
|
921
|
+
</span>
|
|
922
|
+
),
|
|
923
|
+
[],
|
|
924
|
+
);
|
|
859
925
|
|
|
860
926
|
return (
|
|
861
927
|
<div className={`slate-editor ${props.className}`}>
|
|
@@ -864,8 +930,8 @@ export const ReactSlate = forwardRef<any, ReactSlateProps>((props, ref) => {
|
|
|
864
930
|
initialValue={initialSlateValue}
|
|
865
931
|
onChange={handleChange}
|
|
866
932
|
>
|
|
867
|
-
{!readOnly && (
|
|
868
|
-
<div className="
|
|
933
|
+
{!readOnly && showControls && (
|
|
934
|
+
<div className="mb-4 flex flex-col gap-2">
|
|
869
935
|
{toolbarStructure.map(([rowIndex, rowGroups], mapIndex) => (
|
|
870
936
|
<div key={`row-${rowIndex}`} className="toolbar-row">
|
|
871
937
|
{rowGroups.map((group) =>
|
|
@@ -901,7 +967,7 @@ export const ReactSlate = forwardRef<any, ReactSlateProps>((props, ref) => {
|
|
|
901
967
|
className={classNames(
|
|
902
968
|
readOnly ? "bg-gray-4" : "bg-gray-5",
|
|
903
969
|
"focus-shadow p-2",
|
|
904
|
-
"slate-editable"
|
|
970
|
+
"slate-editable",
|
|
905
971
|
)}
|
|
906
972
|
readOnly={readOnly}
|
|
907
973
|
placeholder={placeholder}
|