@alpaca-editor/core 1.0.4053 → 1.0.4054
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/editor/ai/AgentTerminal.js +1 -0
- package/dist/editor/ai/AgentTerminal.js.map +1 -1
- package/dist/editor/ai/Agents.js +0 -2
- package/dist/editor/ai/Agents.js.map +1 -1
- package/dist/editor/ai/AiResponseMessage.js +1 -1
- package/dist/editor/ai/AiResponseMessage.js.map +1 -1
- package/dist/editor/ai/AiTerminal.d.ts +1 -0
- package/dist/editor/ai/AiTerminal.js.map +1 -1
- package/dist/editor/field-types/richtext/components/ReactSlate.js +141 -101
- package/dist/editor/field-types/richtext/components/ReactSlate.js.map +1 -1
- package/dist/editor/field-types/richtext/config/pluginFactory.d.ts +1 -0
- package/dist/editor/field-types/richtext/config/pluginFactory.js +3 -1
- package/dist/editor/field-types/richtext/config/pluginFactory.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 +27 -9
- package/dist/editor/field-types/richtext/utils/conversion.js.map +1 -1
- package/dist/editor/field-types/richtext/utils/plugins.d.ts +46 -1
- package/dist/editor/field-types/richtext/utils/plugins.js +67 -8
- package/dist/editor/field-types/richtext/utils/plugins.js.map +1 -1
- package/dist/editor/services/agentService.js +2 -0
- package/dist/editor/services/agentService.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/package.json +2 -2
- package/src/editor/ai/AgentTerminal.tsx +1 -0
- package/src/editor/ai/Agents.tsx +0 -3
- package/src/editor/ai/AiResponseMessage.tsx +2 -2
- package/src/editor/ai/AiTerminal.tsx +1 -0
- package/src/editor/field-types/richtext/components/ReactSlate.css +41 -55
- package/src/editor/field-types/richtext/components/ReactSlate.tsx +237 -155
- package/src/editor/field-types/richtext/config/pluginFactory.tsx +3 -1
- package/src/editor/field-types/richtext/types.ts +3 -0
- package/src/editor/field-types/richtext/utils/conversion.ts +25 -9
- package/src/editor/field-types/richtext/utils/plugins.ts +86 -9
- package/src/editor/services/agentService.ts +2 -0
- package/src/revision.ts +2 -2
|
@@ -8,7 +8,7 @@ import React, {
|
|
|
8
8
|
memo,
|
|
9
9
|
} from "react";
|
|
10
10
|
import { createEditor, Descendant, Editor, Element, Transforms } from "slate";
|
|
11
|
-
import { Slate, Editable, withReact, ReactEditor } from "slate-react";
|
|
11
|
+
import { Slate, Editable, withReact, ReactEditor, useSlateSelector } from "slate-react";
|
|
12
12
|
import { withHistory } from "slate-history";
|
|
13
13
|
import "./ReactSlate.css";
|
|
14
14
|
|
|
@@ -38,38 +38,32 @@ import { createKeyboardHandler } from "../utils/plugins";
|
|
|
38
38
|
import { useCachedSimplifiedProfile } from "../hooks/useProfileCache";
|
|
39
39
|
import { classNames } from "primereact/utils";
|
|
40
40
|
|
|
41
|
-
//
|
|
41
|
+
// Memoized toolbar button component using useSlateSelector for performance
|
|
42
42
|
const ToolbarButtonWrapper: React.FC<{
|
|
43
43
|
option: ToolbarOptionConfig;
|
|
44
44
|
icon: React.ReactNode;
|
|
45
|
-
editor: Editor;
|
|
46
45
|
onMouseDown: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
|
47
|
-
}> = ({ option, icon,
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
isActive = false; // Insertion buttons are never active
|
|
69
|
-
break;
|
|
70
|
-
default:
|
|
71
|
-
isActive = false;
|
|
72
|
-
}
|
|
46
|
+
}> = memo(({ option, icon, onMouseDown }) => {
|
|
47
|
+
// Use useSlateSelector to only re-render when the active state changes
|
|
48
|
+
const isActive = useSlateSelector((editor) => {
|
|
49
|
+
switch (option.type) {
|
|
50
|
+
case "mark":
|
|
51
|
+
return editor.isMarkActive(option.id);
|
|
52
|
+
case "block":
|
|
53
|
+
return editor.isBlockActive(option.id);
|
|
54
|
+
case "alignment":
|
|
55
|
+
return editor.isAlignActive(SLATE_ALIGNMENTS[option.id].value);
|
|
56
|
+
case "link":
|
|
57
|
+
return editor.isLinkActive();
|
|
58
|
+
case "list":
|
|
59
|
+
const listType = option.id === "unordered-list" ? "unordered" : "ordered";
|
|
60
|
+
return editor.isListActive(listType);
|
|
61
|
+
case "insertion":
|
|
62
|
+
return false; // Insertion buttons are never active
|
|
63
|
+
default:
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
});
|
|
73
67
|
|
|
74
68
|
return (
|
|
75
69
|
<ToolbarButton
|
|
@@ -78,7 +72,9 @@ const ToolbarButtonWrapper: React.FC<{
|
|
|
78
72
|
onMouseDown={onMouseDown}
|
|
79
73
|
/>
|
|
80
74
|
);
|
|
81
|
-
};
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
ToolbarButtonWrapper.displayName = "ToolbarButtonWrapper";
|
|
82
78
|
|
|
83
79
|
// Memoized dropdown component to prevent unnecessary re-renders
|
|
84
80
|
const MemoizedEditorDropdown = memo<{
|
|
@@ -239,6 +235,11 @@ export const ReactSlate = forwardRef<any, ReactSlateProps>((props, ref) => {
|
|
|
239
235
|
});
|
|
240
236
|
}, [editor]);
|
|
241
237
|
|
|
238
|
+
const handleStripFormattingClick = useCallback((event: React.MouseEvent<HTMLButtonElement>) => {
|
|
239
|
+
event.preventDefault();
|
|
240
|
+
editor.stripFormatting();
|
|
241
|
+
}, [editor]);
|
|
242
|
+
|
|
242
243
|
// Memoize option handlers to prevent creating new functions on every render
|
|
243
244
|
const optionHandlers = useMemo(() => {
|
|
244
245
|
const handlers: Record<string, (editor: Editor, event: React.MouseEvent) => void> = {};
|
|
@@ -468,7 +469,6 @@ export const ReactSlate = forwardRef<any, ReactSlateProps>((props, ref) => {
|
|
|
468
469
|
key={`${option.type}-${option.id}`}
|
|
469
470
|
option={option}
|
|
470
471
|
icon={optionObj.icon}
|
|
471
|
-
editor={editor}
|
|
472
472
|
onMouseDown={handler}
|
|
473
473
|
/>
|
|
474
474
|
);
|
|
@@ -664,7 +664,185 @@ export const ReactSlate = forwardRef<any, ReactSlateProps>((props, ref) => {
|
|
|
664
664
|
);
|
|
665
665
|
|
|
666
666
|
// Use the keyboard handler from plugins
|
|
667
|
-
const handleKeyDown = useMemo(() => createKeyboardHandler(editor), [editor]);
|
|
667
|
+
const handleKeyDown = useMemo(() => createKeyboardHandler(editor, simplifiedProfile), [editor, simplifiedProfile]);
|
|
668
|
+
|
|
669
|
+
// Calculate proper list numbering for ordered lists
|
|
670
|
+
const calculateListNumbers = useCallback((elements: Descendant[]): Map<string, string> => {
|
|
671
|
+
const numberMap = new Map<string, string>();
|
|
672
|
+
const counters: number[] = [0, 0, 0, 0, 0, 0]; // Support up to 6 levels
|
|
673
|
+
|
|
674
|
+
elements.forEach((element, index) => {
|
|
675
|
+
if (Element.isElement(element) &&
|
|
676
|
+
element.type === 'list-item' &&
|
|
677
|
+
(element as any).listType === 'ordered') {
|
|
678
|
+
const indent = ((element as any).indent || 0);
|
|
679
|
+
|
|
680
|
+
// Ensure indent is within bounds
|
|
681
|
+
if (indent < 0 || indent >= counters.length) return;
|
|
682
|
+
|
|
683
|
+
// Increment counter for current level
|
|
684
|
+
counters[indent] = (counters[indent] || 0) + 1;
|
|
685
|
+
|
|
686
|
+
// Reset all deeper level counters
|
|
687
|
+
for (let i = indent + 1; i < counters.length; i++) {
|
|
688
|
+
counters[i] = 0;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// Build number string for current level only (e.g., "3.")
|
|
692
|
+
const numberString = counters[indent] + '.';
|
|
693
|
+
numberMap.set(`${index}`, numberString);
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
return numberMap;
|
|
698
|
+
}, []);
|
|
699
|
+
|
|
700
|
+
// Calculate list numbers for the entire editor content (memoized for performance)
|
|
701
|
+
const listNumbers = useMemo(() => {
|
|
702
|
+
return calculateListNumbers(editor.children);
|
|
703
|
+
}, [editor.children, calculateListNumbers]);
|
|
704
|
+
|
|
705
|
+
// Memoize renderElement to prevent unnecessary re-renders as per Slate performance docs
|
|
706
|
+
const renderElement = useCallback(({ attributes, children, element }: any) => {
|
|
707
|
+
const style: React.CSSProperties = {
|
|
708
|
+
textAlign: element.align || "left",
|
|
709
|
+
};
|
|
710
|
+
|
|
711
|
+
if (element.type === "link") {
|
|
712
|
+
const isInternal = element.link?.type === "internal";
|
|
713
|
+
const url = isInternal
|
|
714
|
+
? "#"
|
|
715
|
+
: element.url || element.link?.url || "#";
|
|
716
|
+
|
|
717
|
+
return (
|
|
718
|
+
<a
|
|
719
|
+
{...attributes}
|
|
720
|
+
href={url}
|
|
721
|
+
style={style}
|
|
722
|
+
className={`slate-link ${isInternal ? "internal-link" : "external-link"}`}
|
|
723
|
+
onClick={(e) => {
|
|
724
|
+
if (!readOnly) {
|
|
725
|
+
e.preventDefault();
|
|
726
|
+
editLink(element);
|
|
727
|
+
}
|
|
728
|
+
}}
|
|
729
|
+
>
|
|
730
|
+
{children}
|
|
731
|
+
</a>
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
if (element.type === "list-item") {
|
|
736
|
+
const indent = element.indent || 0;
|
|
737
|
+
const listType = element.listType || "unordered";
|
|
738
|
+
|
|
739
|
+
const listStyle: React.CSSProperties = {
|
|
740
|
+
...style,
|
|
741
|
+
position: "relative",
|
|
742
|
+
listStyleType: "none",
|
|
743
|
+
};
|
|
744
|
+
|
|
745
|
+
// Find the element's index in the editor children to get the correct number
|
|
746
|
+
const elementIndex = editor.children.findIndex((child: any) => child === element);
|
|
747
|
+
const listNumber = listNumbers.get(`${elementIndex}`) || '';
|
|
748
|
+
|
|
749
|
+
return (
|
|
750
|
+
<div
|
|
751
|
+
{...attributes}
|
|
752
|
+
style={listStyle}
|
|
753
|
+
className={`slate-list-item slate-list-${listType}`}
|
|
754
|
+
data-indent={indent}
|
|
755
|
+
data-list-number={listNumber}
|
|
756
|
+
>
|
|
757
|
+
<span className="slate-list-bullet"></span>
|
|
758
|
+
<div className="slate-list-content">{children}</div>
|
|
759
|
+
</div>
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
if (element.type === "horizontal-rule") {
|
|
764
|
+
return (
|
|
765
|
+
<div {...attributes} contentEditable={false} style={{ ...style, userSelect: "none" }}>
|
|
766
|
+
{children}
|
|
767
|
+
<hr style={{
|
|
768
|
+
border: "none",
|
|
769
|
+
borderTop: "1px solid #ccc",
|
|
770
|
+
margin: "1em 0",
|
|
771
|
+
width: "100%"
|
|
772
|
+
}} />
|
|
773
|
+
</div>
|
|
774
|
+
);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
if (element.type === "line-break") {
|
|
778
|
+
return (
|
|
779
|
+
<span {...attributes} contentEditable={false}>
|
|
780
|
+
{children}
|
|
781
|
+
<br />
|
|
782
|
+
</span>
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// Handle different block types using built-in SLATE_BLOCKS configuration
|
|
787
|
+
const isValidBlockId = element.type in SLATE_BLOCKS;
|
|
788
|
+
const blockConfig = isValidBlockId ? SLATE_BLOCKS[element.type as BlockId] : undefined;
|
|
789
|
+
if (blockConfig && element.type === "no-tag") {
|
|
790
|
+
// Special handling for no-tag blocks (plain text without wrapper)
|
|
791
|
+
return (
|
|
792
|
+
<div {...attributes} style={style}>
|
|
793
|
+
{children}
|
|
794
|
+
</div>
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// For standard blocks, use the appropriate HTML tag
|
|
799
|
+
if (blockConfig) {
|
|
800
|
+
const tagName = blockConfig.htmlTag;
|
|
801
|
+
return React.createElement(
|
|
802
|
+
tagName,
|
|
803
|
+
{ ...attributes, style },
|
|
804
|
+
children,
|
|
805
|
+
);
|
|
806
|
+
}
|
|
807
|
+
// Default fallback to paragraph
|
|
808
|
+
return (
|
|
809
|
+
<p {...attributes} style={style}>
|
|
810
|
+
{children}
|
|
811
|
+
</p>
|
|
812
|
+
);
|
|
813
|
+
}, [readOnly, editLink, editor.children, listNumbers]);
|
|
814
|
+
|
|
815
|
+
// Memoize renderLeaf to prevent unnecessary re-renders as per Slate performance docs
|
|
816
|
+
const renderLeaf = useCallback(({ attributes, children, leaf }: any) => {
|
|
817
|
+
|
|
818
|
+
let el = <span {...attributes}>{children}</span>;
|
|
819
|
+
|
|
820
|
+
// Apply marks using the built-in SLATE_MARKS configuration
|
|
821
|
+
simplifiedProfile.marks.forEach((markId) => {
|
|
822
|
+
if ((leaf as any)[markId]) {
|
|
823
|
+
const markConfig = SLATE_MARKS[markId];
|
|
824
|
+
if (markConfig) {
|
|
825
|
+
if (markId === "extrabold") {
|
|
826
|
+
// Special handling for extrabold with CSS class
|
|
827
|
+
el = <span className="extrabold">{el}</span>;
|
|
828
|
+
} else {
|
|
829
|
+
// Standard HTML tag rendering
|
|
830
|
+
const tagName = markConfig.htmlTag;
|
|
831
|
+
el = React.createElement(tagName, {}, el);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
return el;
|
|
838
|
+
}, [simplifiedProfile.marks]);
|
|
839
|
+
|
|
840
|
+
// Memoize renderPlaceholder for consistency
|
|
841
|
+
const renderPlaceholder = useCallback(({ attributes, children }: any) => (
|
|
842
|
+
<span {...attributes} className="p-2 text-gray-500">
|
|
843
|
+
{children}
|
|
844
|
+
</span>
|
|
845
|
+
), []);
|
|
668
846
|
|
|
669
847
|
return (
|
|
670
848
|
<div className={`slate-editor ${props.className}`}>
|
|
@@ -675,147 +853,51 @@ export const ReactSlate = forwardRef<any, ReactSlateProps>((props, ref) => {
|
|
|
675
853
|
>
|
|
676
854
|
{!readOnly && (
|
|
677
855
|
<div className="toolbar">
|
|
678
|
-
{toolbarStructure.map(([rowIndex, rowGroups]) => (
|
|
856
|
+
{toolbarStructure.map(([rowIndex, rowGroups], mapIndex) => (
|
|
679
857
|
<div key={`row-${rowIndex}`} className="toolbar-row">
|
|
680
858
|
{rowGroups.map((group) =>
|
|
681
859
|
renderToolbarGroup(group, parseInt(rowIndex)),
|
|
682
860
|
)}
|
|
861
|
+
{/* Add strip formatting button to the last row */}
|
|
862
|
+
{mapIndex === toolbarStructure.length - 1 && (
|
|
863
|
+
<div className="toolbar-button-group">
|
|
864
|
+
<ToolbarButton
|
|
865
|
+
icon="✗"
|
|
866
|
+
active={false}
|
|
867
|
+
onMouseDown={handleStripFormattingClick}
|
|
868
|
+
/>
|
|
869
|
+
</div>
|
|
870
|
+
)}
|
|
683
871
|
</div>
|
|
684
872
|
))}
|
|
873
|
+
{/* Fallback: if no toolbar structure exists, create a row for strip formatting */}
|
|
874
|
+
{toolbarStructure.length === 0 && (
|
|
875
|
+
<div className="toolbar-row">
|
|
876
|
+
<div className="toolbar-button-group">
|
|
877
|
+
<ToolbarButton
|
|
878
|
+
icon="✗"
|
|
879
|
+
active={false}
|
|
880
|
+
onMouseDown={handleStripFormattingClick}
|
|
881
|
+
/>
|
|
882
|
+
</div>
|
|
883
|
+
</div>
|
|
884
|
+
)}
|
|
685
885
|
</div>
|
|
686
886
|
)}
|
|
687
887
|
<Editable
|
|
688
888
|
className={classNames(
|
|
689
889
|
readOnly ? "bg-gray-4" : "bg-gray-5",
|
|
690
890
|
"focus-shadow p-2",
|
|
891
|
+
"slate-editable"
|
|
691
892
|
)}
|
|
692
893
|
readOnly={readOnly}
|
|
693
894
|
placeholder={placeholder}
|
|
694
|
-
renderPlaceholder={
|
|
695
|
-
<span {...attributes} className="p-2 text-gray-500">
|
|
696
|
-
{children}
|
|
697
|
-
</span>
|
|
698
|
-
)}
|
|
895
|
+
renderPlaceholder={renderPlaceholder}
|
|
699
896
|
onFocus={onFocus}
|
|
700
897
|
onBlur={onBlur}
|
|
701
898
|
onKeyDown={handleKeyDown}
|
|
702
|
-
renderElement={
|
|
703
|
-
|
|
704
|
-
textAlign: element.align || "left",
|
|
705
|
-
};
|
|
706
|
-
|
|
707
|
-
if (element.type === "link") {
|
|
708
|
-
const isInternal = element.link?.type === "internal";
|
|
709
|
-
const url = isInternal
|
|
710
|
-
? "#"
|
|
711
|
-
: element.url || element.link?.url || "#";
|
|
712
|
-
|
|
713
|
-
return (
|
|
714
|
-
<a
|
|
715
|
-
{...attributes}
|
|
716
|
-
href={url}
|
|
717
|
-
style={style}
|
|
718
|
-
className={`slate-link ${isInternal ? "internal-link" : "external-link"}`}
|
|
719
|
-
onClick={(e) => {
|
|
720
|
-
if (!readOnly) {
|
|
721
|
-
e.preventDefault();
|
|
722
|
-
editLink(element);
|
|
723
|
-
}
|
|
724
|
-
}}
|
|
725
|
-
>
|
|
726
|
-
{children}
|
|
727
|
-
</a>
|
|
728
|
-
);
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
if (element.type === "list-item") {
|
|
732
|
-
const indent = element.indent || 0;
|
|
733
|
-
const listType = element.listType || "unordered";
|
|
734
|
-
const isOrdered = listType === "ordered";
|
|
735
|
-
|
|
736
|
-
const listStyle: React.CSSProperties = {
|
|
737
|
-
...style,
|
|
738
|
-
position: "relative",
|
|
739
|
-
listStyleType: "none",
|
|
740
|
-
};
|
|
741
|
-
|
|
742
|
-
return (
|
|
743
|
-
<div
|
|
744
|
-
{...attributes}
|
|
745
|
-
style={listStyle}
|
|
746
|
-
className={`slate-list-item slate-list-${listType}`}
|
|
747
|
-
data-indent={indent}
|
|
748
|
-
>
|
|
749
|
-
<span className="slate-list-bullet"></span>
|
|
750
|
-
<div className="slate-list-content">{children}</div>
|
|
751
|
-
</div>
|
|
752
|
-
);
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
if (element.type === "horizontal-rule") {
|
|
756
|
-
return (
|
|
757
|
-
<div {...attributes} contentEditable={false} style={{ ...style, userSelect: "none" }}>
|
|
758
|
-
<hr style={{
|
|
759
|
-
border: "none",
|
|
760
|
-
borderTop: "1px solid #ccc",
|
|
761
|
-
margin: "1em 0",
|
|
762
|
-
width: "100%"
|
|
763
|
-
}} />
|
|
764
|
-
{children}
|
|
765
|
-
</div>
|
|
766
|
-
);
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
// Handle different block types using built-in SLATE_BLOCKS configuration
|
|
770
|
-
const isValidBlockId = element.type in SLATE_BLOCKS;
|
|
771
|
-
const blockConfig = isValidBlockId ? SLATE_BLOCKS[element.type as BlockId] : undefined;
|
|
772
|
-
if (blockConfig && element.type === "no-tag") {
|
|
773
|
-
// Special handling for no-tag blocks (plain text without wrapper)
|
|
774
|
-
return (
|
|
775
|
-
<div {...attributes} style={style}>
|
|
776
|
-
{children}
|
|
777
|
-
</div>
|
|
778
|
-
);
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
// For standard blocks, use the appropriate HTML tag
|
|
782
|
-
if (blockConfig) {
|
|
783
|
-
const tagName = blockConfig.htmlTag;
|
|
784
|
-
return React.createElement(
|
|
785
|
-
tagName,
|
|
786
|
-
{ ...attributes, style },
|
|
787
|
-
children,
|
|
788
|
-
);
|
|
789
|
-
}
|
|
790
|
-
// Default fallback to paragraph
|
|
791
|
-
return (
|
|
792
|
-
<p {...attributes} style={style}>
|
|
793
|
-
{children}
|
|
794
|
-
</p>
|
|
795
|
-
);
|
|
796
|
-
}}
|
|
797
|
-
renderLeaf={({ attributes, children, leaf }) => {
|
|
798
|
-
let el = <span {...attributes}>{children}</span>;
|
|
799
|
-
|
|
800
|
-
// Apply marks using the built-in SLATE_MARKS configuration
|
|
801
|
-
simplifiedProfile.marks.forEach((markId) => {
|
|
802
|
-
if ((leaf as any)[markId]) {
|
|
803
|
-
const markConfig = SLATE_MARKS[markId];
|
|
804
|
-
if (markConfig) {
|
|
805
|
-
if (markId === "extrabold") {
|
|
806
|
-
// Special handling for extrabold with CSS class
|
|
807
|
-
el = <span className="extrabold">{el}</span>;
|
|
808
|
-
} else {
|
|
809
|
-
// Standard HTML tag rendering
|
|
810
|
-
const tagName = markConfig.htmlTag;
|
|
811
|
-
el = React.createElement(tagName, {}, el);
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
});
|
|
816
|
-
|
|
817
|
-
return el;
|
|
818
|
-
}}
|
|
899
|
+
renderElement={renderElement}
|
|
900
|
+
renderLeaf={renderLeaf}
|
|
819
901
|
/>
|
|
820
902
|
</Slate>
|
|
821
903
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Editor } from 'slate';
|
|
2
|
-
import { withMark, withBlock, withAlignment, withLink, withList, withNormalization, withInsertion } from '../utils/plugins';
|
|
2
|
+
import { withMark, withBlock, withAlignment, withLink, withList, withNormalization, withInsertion, withFormatting, withVoidElements } from '../utils/plugins';
|
|
3
3
|
|
|
4
4
|
// Create plugins from configuration
|
|
5
5
|
export const createPluginsFromConfig = (editor: Editor) => {
|
|
@@ -11,6 +11,8 @@ export const createPluginsFromConfig = (editor: Editor) => {
|
|
|
11
11
|
enhancedEditor = withList(enhancedEditor);
|
|
12
12
|
enhancedEditor = withLink(enhancedEditor);
|
|
13
13
|
enhancedEditor = withInsertion(enhancedEditor);
|
|
14
|
+
enhancedEditor = withFormatting(enhancedEditor);
|
|
15
|
+
enhancedEditor = withVoidElements(enhancedEditor);
|
|
14
16
|
|
|
15
17
|
// Add normalization plugin last to ensure it can normalize all other plugin behaviors
|
|
16
18
|
enhancedEditor = withNormalization(enhancedEditor);
|
|
@@ -82,6 +82,8 @@ declare module "slate" {
|
|
|
82
82
|
outdentList(): void;
|
|
83
83
|
|
|
84
84
|
insertHorizontalRule(): void;
|
|
85
|
+
|
|
86
|
+
stripFormatting(): void;
|
|
85
87
|
};
|
|
86
88
|
Element: CustomElement;
|
|
87
89
|
Text: CustomText;
|
|
@@ -100,6 +102,7 @@ export type CustomText = {
|
|
|
100
102
|
readonly superscript?: boolean;
|
|
101
103
|
readonly extrabold?: boolean;
|
|
102
104
|
readonly isRawHtml?: boolean; // Flag to indicate this text should be preserved as raw HTML
|
|
105
|
+
|
|
103
106
|
};
|
|
104
107
|
|
|
105
108
|
export type Alignment = "left" | "center" | "right" | "justify";
|
|
@@ -206,7 +206,11 @@ const htmlToSlateInternal = (html: string, profile: SimplifiedProfile): Descenda
|
|
|
206
206
|
}
|
|
207
207
|
|
|
208
208
|
if (tagName === 'br') {
|
|
209
|
-
|
|
209
|
+
// Handle inline br elements as line-break elements
|
|
210
|
+
results.push({
|
|
211
|
+
type: 'line-break',
|
|
212
|
+
children: [{ text: '' }]
|
|
213
|
+
} as any);
|
|
210
214
|
} else if (tagName === 'a') {
|
|
211
215
|
const href = childElement.getAttribute('href') || '';
|
|
212
216
|
const target = childElement.getAttribute('target') || '_self';
|
|
@@ -273,7 +277,11 @@ const htmlToSlateInternal = (html: string, profile: SimplifiedProfile): Descenda
|
|
|
273
277
|
const tagName = childElement.tagName.toLowerCase();
|
|
274
278
|
|
|
275
279
|
if (tagName === 'br') {
|
|
276
|
-
|
|
280
|
+
// Handle inline br elements as line-break elements
|
|
281
|
+
results.push({
|
|
282
|
+
type: 'line-break',
|
|
283
|
+
children: [{ text: '' }]
|
|
284
|
+
} as any);
|
|
277
285
|
} else if (tagName === 'a') {
|
|
278
286
|
const href = childElement.getAttribute('href') || '';
|
|
279
287
|
const target = childElement.getAttribute('target') || '_self';
|
|
@@ -342,7 +350,11 @@ const htmlToSlateInternal = (html: string, profile: SimplifiedProfile): Descenda
|
|
|
342
350
|
const tagName = element.tagName.toLowerCase();
|
|
343
351
|
|
|
344
352
|
if (tagName === 'br') {
|
|
345
|
-
|
|
353
|
+
// Handle inline br elements as line-break elements
|
|
354
|
+
combinedChildren.push({
|
|
355
|
+
type: 'line-break',
|
|
356
|
+
children: [{ text: '' }]
|
|
357
|
+
} as any);
|
|
346
358
|
} else {
|
|
347
359
|
// Process other inline elements
|
|
348
360
|
const inlineChildren = processNodeWithInlines(element, profile.marks);
|
|
@@ -370,10 +382,10 @@ const htmlToSlateInternal = (html: string, profile: SimplifiedProfile): Descenda
|
|
|
370
382
|
const align = alignMatch ? alignMatch[1] as Alignment : undefined;
|
|
371
383
|
|
|
372
384
|
if (tagName === 'br') {
|
|
373
|
-
// Handle standalone br elements
|
|
385
|
+
// Handle standalone br elements as line-break elements
|
|
374
386
|
nodes.push({
|
|
375
|
-
type:
|
|
376
|
-
children: [{ text: '
|
|
387
|
+
type: 'line-break',
|
|
388
|
+
children: [{ text: '' }]
|
|
377
389
|
});
|
|
378
390
|
} else if (tagName === 'hr') {
|
|
379
391
|
// Handle horizontal rule elements
|
|
@@ -448,8 +460,7 @@ export const formatTextWithMarks = (child: CustomText, configuredMarks: readonly
|
|
|
448
460
|
|
|
449
461
|
let text = child.text;
|
|
450
462
|
|
|
451
|
-
//
|
|
452
|
-
text = text.replace(/<br>/g, '<br>');
|
|
463
|
+
// Line breaks are now handled as elements, not text nodes
|
|
453
464
|
|
|
454
465
|
// Apply marks using Slate's built-in mark system with strict typing
|
|
455
466
|
configuredMarks.forEach(markId => {
|
|
@@ -563,7 +574,10 @@ const slateToHtmlInternal = (value: Descendant[], profile: SimplifiedProfile): s
|
|
|
563
574
|
|
|
564
575
|
children.forEach(child => {
|
|
565
576
|
if (SlateElement.isElement(child)) {
|
|
566
|
-
if (child.type === '
|
|
577
|
+
if (child.type === 'line-break') {
|
|
578
|
+
// Handle inline line break elements
|
|
579
|
+
result += '<br>';
|
|
580
|
+
} else if (child.type === 'link') {
|
|
567
581
|
const linkElement = child as LinkElement;
|
|
568
582
|
const isInternal = linkElement.link?.type === 'internal';
|
|
569
583
|
|
|
@@ -619,6 +633,8 @@ const slateToHtmlInternal = (value: Descendant[], profile: SimplifiedProfile): s
|
|
|
619
633
|
// Handle horizontal rule elements
|
|
620
634
|
if (element.type === 'horizontal-rule') {
|
|
621
635
|
html += '<hr>';
|
|
636
|
+
} else if (element.type === 'line-break') {
|
|
637
|
+
html += '<br>';
|
|
622
638
|
} else if (element.type === 'preserved-element') {
|
|
623
639
|
// Handle preserved elements by restoring their original HTML
|
|
624
640
|
const preservedElement = element as any;
|