@antscorp/antsomi-ui 2.0.84 → 2.0.86-text-editor-beta.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/es/assets/css/main.scss +4 -2
- package/es/components/index.scss +2 -0
- package/es/components/molecules/EmojiPopover/EmojiPopover.js +5 -1
- package/es/components/molecules/FontSizeInput/FontSizeInput.d.ts +3 -0
- package/es/components/molecules/FontSizeInput/FontSizeInput.js +128 -0
- package/es/components/molecules/FontSizeInput/components/FontSizeControl.d.ts +8 -0
- package/es/components/molecules/FontSizeInput/components/FontSizeControl.js +14 -0
- package/es/components/molecules/FontSizeInput/components/FontSizeDropdown.d.ts +20 -0
- package/es/components/molecules/FontSizeInput/components/FontSizeDropdown.js +17 -0
- package/es/components/molecules/FontSizeInput/constants.d.ts +2 -0
- package/es/components/molecules/FontSizeInput/constants.js +5 -0
- package/es/components/molecules/FontSizeInput/index.d.ts +2 -0
- package/es/components/molecules/FontSizeInput/index.js +1 -0
- package/es/components/molecules/FontSizeInput/styled.d.ts +3 -0
- package/es/components/molecules/FontSizeInput/styled.js +22 -0
- package/es/components/molecules/FontSizeInput/styles.scss +15 -0
- package/es/components/molecules/FontSizeInput/types.d.ts +24 -0
- package/es/components/molecules/FontSizeInput/types.js +1 -0
- package/es/components/molecules/FontSizeInput/utils.d.ts +7 -0
- package/es/components/molecules/FontSizeInput/utils.js +9 -0
- package/es/components/molecules/TagifyInput/TagifyInput.js +20 -14
- package/es/components/molecules/TagifyInput/utils.d.ts +10 -1
- package/es/components/molecules/TagifyInput/utils.js +64 -6
- package/es/components/molecules/VirtualizedMenu/VirtualizedMenu.d.ts +6 -3
- package/es/components/molecules/VirtualizedMenu/__mocks__/index.js +2550 -938
- package/es/components/molecules/VirtualizedMenu/components/Item/Item.d.ts +13 -3
- package/es/components/molecules/VirtualizedMenu/components/Item/Item.js +53 -25
- package/es/components/molecules/VirtualizedMenu/components/Item/index.d.ts +1 -1
- package/es/components/molecules/VirtualizedMenu/components/MenuInline/MenuInline.d.ts +8 -5
- package/es/components/molecules/VirtualizedMenu/components/MenuInline/MenuInline.js +289 -46
- package/es/components/molecules/VirtualizedMenu/components/MenuInline/index.js +1 -0
- package/es/components/molecules/VirtualizedMenu/styled.d.ts +1 -1
- package/es/components/molecules/VirtualizedMenu/styled.js +23 -4
- package/es/components/molecules/VirtualizedMenu/types.d.ts +6 -9
- package/es/components/molecules/VirtualizedMenu/utils.d.ts +8 -5
- package/es/components/molecules/VirtualizedMenu/utils.js +13 -18
- package/es/components/molecules/index.d.ts +1 -0
- package/es/components/molecules/index.js +1 -0
- package/es/components/molecules/index.scss +1 -0
- package/es/components/organism/ActivityTimeline/__mocks__/event_tracking.json +1290 -0
- package/es/components/organism/ActivityTimeline/__mocks__/timeline.json +3059 -0
- package/es/components/organism/TextEditor/TextEditor.d.ts +3 -0
- package/es/components/organism/TextEditor/TextEditor.js +251 -0
- package/es/components/organism/TextEditor/__mocks__/text-block.settings.json +320 -0
- package/es/components/organism/TextEditor/__mocks__/text-contennt.d.ts +1 -0
- package/es/components/organism/TextEditor/__mocks__/text-contennt.js +38 -0
- package/es/components/organism/TextEditor/constants.d.ts +135 -0
- package/es/components/organism/TextEditor/constants.js +280 -0
- package/es/components/organism/TextEditor/extensions/BackgroundColor.d.ts +25 -0
- package/es/components/organism/TextEditor/extensions/BackgroundColor.js +46 -0
- package/es/components/organism/TextEditor/extensions/BubbleMenu/bubble-menu-plugin.d.ts +130 -0
- package/es/components/organism/TextEditor/extensions/BubbleMenu/bubble-menu-plugin.js +247 -0
- package/es/components/organism/TextEditor/extensions/BubbleMenu/bubble-menu.d.ts +15 -0
- package/es/components/organism/TextEditor/extensions/BubbleMenu/bubble-menu.js +31 -0
- package/es/components/organism/TextEditor/extensions/BubbleMenu/index.d.ts +2 -0
- package/es/components/organism/TextEditor/extensions/BubbleMenu/index.js +2 -0
- package/es/components/organism/TextEditor/extensions/Color.d.ts +6 -0
- package/es/components/organism/TextEditor/extensions/Color.js +41 -0
- package/es/components/organism/TextEditor/extensions/Emoji.d.ts +57 -0
- package/es/components/organism/TextEditor/extensions/Emoji.js +184 -0
- package/es/components/organism/TextEditor/extensions/FontFamily.d.ts +6 -0
- package/es/components/organism/TextEditor/extensions/FontFamily.js +43 -0
- package/es/components/organism/TextEditor/extensions/FontSize.d.ts +32 -0
- package/es/components/organism/TextEditor/extensions/FontSize.js +47 -0
- package/es/components/organism/TextEditor/extensions/FontWeight.d.ts +23 -0
- package/es/components/organism/TextEditor/extensions/FontWeight.js +41 -0
- package/es/components/organism/TextEditor/extensions/Highlight.d.ts +1 -0
- package/es/components/organism/TextEditor/extensions/Highlight.js +14 -0
- package/es/components/organism/TextEditor/extensions/Indent.d.ts +28 -0
- package/es/components/organism/TextEditor/extensions/Indent.js +68 -0
- package/es/components/organism/TextEditor/extensions/LineHeight.d.ts +20 -0
- package/es/components/organism/TextEditor/extensions/LineHeight.js +36 -0
- package/es/components/organism/TextEditor/extensions/Link.d.ts +15 -0
- package/es/components/organism/TextEditor/extensions/Link.js +50 -0
- package/es/components/organism/TextEditor/extensions/ListItemMarker.d.ts +13 -0
- package/es/components/organism/TextEditor/extensions/ListItemMarker.js +174 -0
- package/es/components/organism/TextEditor/extensions/Selection.d.ts +6 -0
- package/es/components/organism/TextEditor/extensions/Selection.js +40 -0
- package/es/components/organism/TextEditor/extensions/SmartTag.d.ts +39 -0
- package/es/components/organism/TextEditor/extensions/SmartTag.js +167 -0
- package/es/components/organism/TextEditor/extensions/StyleMemory.d.ts +36 -0
- package/es/components/organism/TextEditor/extensions/StyleMemory.js +163 -0
- package/es/components/organism/TextEditor/extensions/TextTransform.d.ts +31 -0
- package/es/components/organism/TextEditor/extensions/TextTransform.js +37 -0
- package/es/components/organism/TextEditor/hooks/index.d.ts +6 -0
- package/es/components/organism/TextEditor/hooks/index.js +6 -0
- package/es/components/organism/TextEditor/hooks/useDocumentState.d.ts +18 -0
- package/es/components/organism/TextEditor/hooks/useDocumentState.js +42 -0
- package/es/components/organism/TextEditor/hooks/useLinkHandler.d.ts +10 -0
- package/es/components/organism/TextEditor/hooks/useLinkHandler.js +223 -0
- package/es/components/organism/TextEditor/hooks/useMarkTracking.d.ts +26 -0
- package/es/components/organism/TextEditor/hooks/useMarkTracking.js +68 -0
- package/es/components/organism/TextEditor/hooks/usePersistence.d.ts +31 -0
- package/es/components/organism/TextEditor/hooks/usePersistence.js +169 -0
- package/es/components/organism/TextEditor/hooks/useStyleMemory.d.ts +6 -0
- package/es/components/organism/TextEditor/hooks/useStyleMemory.js +42 -0
- package/es/components/organism/TextEditor/hooks/useStylePresets.d.ts +34 -0
- package/es/components/organism/TextEditor/hooks/useStylePresets.js +83 -0
- package/es/components/organism/TextEditor/index.d.ts +14 -0
- package/es/components/organism/TextEditor/index.js +6 -0
- package/es/components/organism/TextEditor/index.scss +61 -0
- package/es/components/organism/TextEditor/provider.d.ts +10 -0
- package/es/components/organism/TextEditor/provider.js +20 -0
- package/es/components/organism/TextEditor/store.d.ts +11 -0
- package/es/components/organism/TextEditor/store.js +12 -0
- package/es/components/organism/TextEditor/styled.d.ts +8 -0
- package/es/components/organism/TextEditor/styled.js +90 -0
- package/es/components/organism/TextEditor/types.d.ts +92 -0
- package/es/components/organism/TextEditor/types.js +1 -0
- package/es/components/organism/TextEditor/ui/BubbleMenu/BubbleMenu.d.ts +6 -0
- package/es/components/organism/TextEditor/ui/BubbleMenu/BubbleMenu.js +78 -0
- package/es/components/organism/TextEditor/ui/BubbleMenu/index.d.ts +1 -0
- package/es/components/organism/TextEditor/ui/BubbleMenu/index.js +1 -0
- package/es/components/organism/TextEditor/ui/ColorPicker/ColorPicker.d.ts +43 -0
- package/es/components/organism/TextEditor/ui/ColorPicker/ColorPicker.js +120 -0
- package/es/components/organism/TextEditor/ui/ColorPicker/index.d.ts +1 -0
- package/es/components/organism/TextEditor/ui/ColorPicker/index.js +1 -0
- package/es/components/organism/TextEditor/ui/Emoji/EmojiList.d.ts +11 -0
- package/es/components/organism/TextEditor/ui/Emoji/EmojiList.js +66 -0
- package/es/components/organism/TextEditor/ui/Emoji/index.d.ts +2 -0
- package/es/components/organism/TextEditor/ui/Emoji/index.js +2 -0
- package/es/components/organism/TextEditor/ui/Emoji/suggestion.d.ts +4 -0
- package/es/components/organism/TextEditor/ui/Emoji/suggestion.js +71 -0
- package/es/components/organism/TextEditor/ui/FontPopover/FontPopover.d.ts +12 -0
- package/es/components/organism/TextEditor/ui/FontPopover/FontPopover.js +69 -0
- package/es/components/organism/TextEditor/ui/FontPopover/styled.d.ts +1 -0
- package/es/components/organism/TextEditor/ui/FontPopover/styled.js +20 -0
- package/es/components/organism/TextEditor/ui/Popover/Popover.d.ts +6 -0
- package/es/components/organism/TextEditor/ui/Popover/Popover.js +7 -0
- package/es/components/organism/TextEditor/ui/Popover/index.d.ts +1 -0
- package/es/components/organism/TextEditor/ui/Popover/index.js +1 -0
- package/es/components/organism/TextEditor/ui/Select/Select.d.ts +4 -0
- package/es/components/organism/TextEditor/ui/Select/Select.js +7 -0
- package/es/components/organism/TextEditor/ui/Select/index.d.ts +1 -0
- package/es/components/organism/TextEditor/ui/Select/index.js +1 -0
- package/es/components/organism/TextEditor/ui/TextAlignSelect/TextAlignSelect.d.ts +30 -0
- package/es/components/organism/TextEditor/ui/TextAlignSelect/TextAlignSelect.js +49 -0
- package/es/components/organism/TextEditor/ui/TextAlignSelect/index.d.ts +1 -0
- package/es/components/organism/TextEditor/ui/TextAlignSelect/index.js +1 -0
- package/es/components/organism/TextEditor/ui/Toolbar/Toolbar.d.ts +14 -0
- package/es/components/organism/TextEditor/ui/Toolbar/Toolbar.js +42 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/BoldAction.d.ts +5 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/BoldAction.js +7 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/BulletListAction.d.ts +5 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/BulletListAction.js +7 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/ClearFormattingAction.d.ts +5 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/ClearFormattingAction.js +18 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/EmojiAction.d.ts +4 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/EmojiAction.js +13 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/FontFamilyAction.d.ts +7 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/FontFamilyAction.js +18 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/FontSizeAction.d.ts +7 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/FontSizeAction.js +37 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/HighlightAction.d.ts +5 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/HighlightAction.js +7 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/IndentAction.d.ts +5 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/IndentAction.js +7 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/ItalicAction.d.ts +5 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/ItalicAction.js +7 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/LinkAction.d.ts +6 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/LinkAction.js +4 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/OrderedListAction.d.ts +5 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/OrderedListAction.js +7 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/OutdentAction.d.ts +5 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/OutdentAction.js +7 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/SmartTagAction.d.ts +7 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/SmartTagAction.js +9 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/SpacingAction.d.ts +9 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/SpacingAction.js +22 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/StrikeAction.d.ts +5 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/StrikeAction.js +7 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/SubscriptAction.d.ts +5 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/SubscriptAction.js +13 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/SuperscriptAction.d.ts +5 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/SuperscriptAction.js +13 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/TextAlignAction.d.ts +5 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/TextAlignAction.js +3 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/TextBackgroundColorAction.d.ts +7 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/TextBackgroundColorAction.js +19 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/TextColorAction.d.ts +15 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/TextColorAction.js +14 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/TextTransformAction.d.ts +5 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/TextTransformAction.js +30 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/UnderlineAction.d.ts +5 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/UnderlineAction.js +5 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/UnsetLink.d.ts +6 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/UnsetLink.js +10 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/index.d.ts +20 -0
- package/es/components/organism/TextEditor/ui/Toolbar/actions/index.js +20 -0
- package/es/components/organism/TextEditor/ui/Toolbar/index.d.ts +1 -0
- package/es/components/organism/TextEditor/ui/Toolbar/index.js +1 -0
- package/es/components/organism/TextEditor/utils/documentState.d.ts +57 -0
- package/es/components/organism/TextEditor/utils/documentState.js +100 -0
- package/es/components/organism/TextEditor/utils/font.d.ts +84 -0
- package/es/components/organism/TextEditor/utils/font.js +175 -0
- package/es/components/organism/TextEditor/utils/htmlProcessing.d.ts +63 -0
- package/es/components/organism/TextEditor/utils/htmlProcessing.js +321 -0
- package/es/components/organism/TextEditor/utils/index.d.ts +8 -0
- package/es/components/organism/TextEditor/utils/index.js +16 -0
- package/es/components/organism/TextEditor/utils/link.d.ts +100 -0
- package/es/components/organism/TextEditor/utils/link.js +149 -0
- package/es/components/organism/TextEditor/utils/menu.d.ts +134 -0
- package/es/components/organism/TextEditor/utils/menu.js +317 -0
- package/es/components/organism/TextEditor/utils/selection.d.ts +25 -0
- package/es/components/organism/TextEditor/utils/selection.js +57 -0
- package/es/components/organism/TextEditor/utils/smartTag.d.ts +49 -0
- package/es/components/organism/TextEditor/utils/smartTag.js +89 -0
- package/es/components/organism/TextEditor/utils/style.d.ts +78 -0
- package/es/components/organism/TextEditor/utils/style.js +193 -0
- package/es/components/organism/index.d.ts +1 -0
- package/es/components/organism/index.js +1 -0
- package/es/components/organism/index.scss +1 -0
- package/es/config/index.d.ts +1 -0
- package/es/config/index.js +1 -0
- package/es/constants/api.d.ts +10 -0
- package/es/constants/api.js +10 -0
- package/es/hooks/index.d.ts +1 -0
- package/es/hooks/index.js +1 -0
- package/es/hooks/useBroadcastedLocalStorage.d.ts +5 -0
- package/es/hooks/useBroadcastedLocalStorage.js +71 -0
- package/es/hooks/useElementSize.d.ts +7 -0
- package/es/hooks/useElementSize.js +56 -0
- package/es/utils/common.d.ts +6 -9
- package/es/utils/common.js +44 -23
- package/es/utils/index.d.ts +1 -0
- package/es/utils/index.js +1 -0
- package/es/utils/tree.d.ts +225 -0
- package/es/utils/tree.js +469 -0
- package/es/utils/web.d.ts +4 -0
- package/es/utils/web.js +25 -0
- package/package.json +29 -3
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { TextSelection } from '@tiptap/pm/state';
|
|
3
|
+
import { getLinkMark, getLinkMarkRanges, getLinkRange, isLinkColor } from '../utils';
|
|
4
|
+
import { TEXT_EDITOR_CONSTANTS } from '../constants';
|
|
5
|
+
const { LINK_TEXT_COLOR } = TEXT_EDITOR_CONSTANTS;
|
|
6
|
+
export function useLinkHandler(params) {
|
|
7
|
+
const { editor } = params;
|
|
8
|
+
const setLink = useCallback(attrs => {
|
|
9
|
+
if (!editor)
|
|
10
|
+
return;
|
|
11
|
+
const { selection } = editor?.state;
|
|
12
|
+
const { from, to } = selection;
|
|
13
|
+
let linkContent = editor.state.doc.textBetween(from, to);
|
|
14
|
+
if (attrs.content && linkContent !== attrs.content) {
|
|
15
|
+
linkContent = attrs.content;
|
|
16
|
+
}
|
|
17
|
+
editor
|
|
18
|
+
.chain()
|
|
19
|
+
.focus()
|
|
20
|
+
.deleteSelection()
|
|
21
|
+
.insertContent(linkContent)
|
|
22
|
+
.command(({ tr }) => {
|
|
23
|
+
const endPos = tr.selection.from;
|
|
24
|
+
const startPos = endPos - linkContent.length;
|
|
25
|
+
tr.setSelection(TextSelection.create(tr.doc, startPos, endPos));
|
|
26
|
+
return true;
|
|
27
|
+
})
|
|
28
|
+
.setUnderline()
|
|
29
|
+
.setColor(LINK_TEXT_COLOR)
|
|
30
|
+
.setLink(attrs)
|
|
31
|
+
.command(({ tr }) => {
|
|
32
|
+
const endPos = tr.selection.to;
|
|
33
|
+
tr.setSelection(TextSelection.create(tr.doc, endPos, endPos));
|
|
34
|
+
return true;
|
|
35
|
+
})
|
|
36
|
+
.run();
|
|
37
|
+
}, [editor]);
|
|
38
|
+
const deleteLink = useCallback((id) => {
|
|
39
|
+
if (!editor)
|
|
40
|
+
return;
|
|
41
|
+
const { state } = editor;
|
|
42
|
+
const { doc } = state;
|
|
43
|
+
let linkMark;
|
|
44
|
+
doc.descendants((node, pos) => {
|
|
45
|
+
if (linkMark)
|
|
46
|
+
return;
|
|
47
|
+
linkMark = getLinkMarkRanges({
|
|
48
|
+
state,
|
|
49
|
+
from: pos,
|
|
50
|
+
to: pos + node.nodeSize,
|
|
51
|
+
}).find(range => range.mark.attrs.id === id);
|
|
52
|
+
});
|
|
53
|
+
if (linkMark) {
|
|
54
|
+
editor
|
|
55
|
+
.chain()
|
|
56
|
+
.deleteRange({
|
|
57
|
+
from: linkMark.from,
|
|
58
|
+
to: linkMark.to,
|
|
59
|
+
})
|
|
60
|
+
.run();
|
|
61
|
+
}
|
|
62
|
+
}, [editor]);
|
|
63
|
+
const updateLinkAttrsGlobally = useCallback((updateFn) => {
|
|
64
|
+
if (!editor)
|
|
65
|
+
return;
|
|
66
|
+
const { state, view } = editor;
|
|
67
|
+
const { doc, schema, tr } = state;
|
|
68
|
+
const linkType = schema.marks.link;
|
|
69
|
+
let somethingChanged = false;
|
|
70
|
+
doc.descendants((node, pos) => {
|
|
71
|
+
// Get all link mark ranges within this node
|
|
72
|
+
const linkMarkRanges = getLinkMarkRanges({
|
|
73
|
+
state,
|
|
74
|
+
from: pos,
|
|
75
|
+
to: pos + node.nodeSize,
|
|
76
|
+
});
|
|
77
|
+
linkMarkRanges.forEach(markRange => {
|
|
78
|
+
const updatedAttrs = updateFn(markRange.mark.attrs);
|
|
79
|
+
if (updatedAttrs) {
|
|
80
|
+
// Remove the old mark
|
|
81
|
+
tr.removeMark(markRange.from, markRange.to, markRange.mark);
|
|
82
|
+
// Create a new mark with updated attributes and add it
|
|
83
|
+
const newMark = linkType.create({
|
|
84
|
+
...markRange.mark.attrs,
|
|
85
|
+
...updatedAttrs,
|
|
86
|
+
});
|
|
87
|
+
tr.addMark(markRange.from, markRange.to, newMark);
|
|
88
|
+
somethingChanged = true;
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
if (somethingChanged) {
|
|
93
|
+
view.dispatch(tr);
|
|
94
|
+
}
|
|
95
|
+
}, [editor]);
|
|
96
|
+
const updateLinkTextGlobally = useCallback((updateFn) => {
|
|
97
|
+
if (!editor)
|
|
98
|
+
return;
|
|
99
|
+
const { state, view } = editor;
|
|
100
|
+
const { doc, schema, tr } = state;
|
|
101
|
+
const linkType = schema.marks.link;
|
|
102
|
+
const underlineType = schema.marks.underline; // Assuming you have an underline mark
|
|
103
|
+
const textStyleType = schema.marks.textStyle; // Assuming textStyle mark handles color
|
|
104
|
+
const boldType = schema.marks.bold; // Assuming bold mark
|
|
105
|
+
const italicType = schema.marks.italic; // Assuming italic mark
|
|
106
|
+
const rangesToProcess = [];
|
|
107
|
+
// Collect all unique extended link ranges
|
|
108
|
+
doc.descendants((node, pos) => {
|
|
109
|
+
if (!node.isText)
|
|
110
|
+
return;
|
|
111
|
+
const linkMark = linkType.isInSet(node.marks);
|
|
112
|
+
if (linkMark) {
|
|
113
|
+
const linkRange = getLinkRange({ state, pos });
|
|
114
|
+
if (linkRange) {
|
|
115
|
+
// Check if this extended range has already been added
|
|
116
|
+
const isProcessed = rangesToProcess.some(pr => pr.from === linkRange.from && pr.to === linkRange.to);
|
|
117
|
+
if (!isProcessed) {
|
|
118
|
+
// Find the first link mark in the range to get original attributes
|
|
119
|
+
const originalLinkMark = getLinkMark({
|
|
120
|
+
state,
|
|
121
|
+
from: linkRange.from,
|
|
122
|
+
to: linkRange.to,
|
|
123
|
+
});
|
|
124
|
+
if (originalLinkMark) {
|
|
125
|
+
rangesToProcess.push({
|
|
126
|
+
from: linkRange.from,
|
|
127
|
+
to: linkRange.to,
|
|
128
|
+
originalAttrs: originalLinkMark.attrs,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
// Sort ranges in descending order to avoid issues with position shifts during deletion
|
|
136
|
+
rangesToProcess.sort((a, b) => b.from - a.from);
|
|
137
|
+
let hasChanged = false;
|
|
138
|
+
const applyLinkMarks = (from, to, originalAttrs, originalMarks) => {
|
|
139
|
+
// Apply the link mark with original attributes
|
|
140
|
+
const newLinkMark = linkType.create({
|
|
141
|
+
...originalAttrs,
|
|
142
|
+
href: originalAttrs.href, // Ensure href is included
|
|
143
|
+
});
|
|
144
|
+
tr.addMark(from, to, newLinkMark);
|
|
145
|
+
// Apply underline mark
|
|
146
|
+
if (underlineType) {
|
|
147
|
+
const newUnderlineMark = underlineType.create();
|
|
148
|
+
tr.addMark(from, to, newUnderlineMark);
|
|
149
|
+
}
|
|
150
|
+
// Apply blue color mark (#0066CC) by finding the existing textStyle mark or creating a new one
|
|
151
|
+
if (textStyleType) {
|
|
152
|
+
const originalTextStyle = originalMarks.find(m => m.type === textStyleType);
|
|
153
|
+
const newColorMark = textStyleType.create({
|
|
154
|
+
...originalTextStyle?.attrs,
|
|
155
|
+
color: LINK_TEXT_COLOR,
|
|
156
|
+
});
|
|
157
|
+
tr.addMark(from, to, newColorMark);
|
|
158
|
+
}
|
|
159
|
+
// Apply bold mark if it existed
|
|
160
|
+
if (boldType) {
|
|
161
|
+
const originalBoldMark = originalMarks.find(m => m.type === boldType);
|
|
162
|
+
if (originalBoldMark) {
|
|
163
|
+
tr.addMark(from, to, originalBoldMark);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// Apply italic mark if it existed
|
|
167
|
+
if (italicType) {
|
|
168
|
+
const originalItalicMark = originalMarks.find(m => m.type === italicType);
|
|
169
|
+
if (originalItalicMark) {
|
|
170
|
+
tr.addMark(from, to, originalItalicMark);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
rangesToProcess.forEach(({ from, to, originalAttrs }) => {
|
|
175
|
+
const currentTextContent = state.doc.textBetween(from, to);
|
|
176
|
+
const updatedText = updateFn({
|
|
177
|
+
currentText: currentTextContent,
|
|
178
|
+
attrs: originalAttrs,
|
|
179
|
+
});
|
|
180
|
+
if (updatedText !== undefined) {
|
|
181
|
+
hasChanged = true;
|
|
182
|
+
// Get the marks from the node at the start of the link range
|
|
183
|
+
const originalMarks = state.doc.resolve(from).marks();
|
|
184
|
+
// Delete the old content
|
|
185
|
+
tr.delete(from, to);
|
|
186
|
+
// Insert the new text node
|
|
187
|
+
const newTextNode = schema.text(updatedText);
|
|
188
|
+
tr.insert(from, newTextNode);
|
|
189
|
+
// Apply the desired marks to the new text range
|
|
190
|
+
applyLinkMarks(from, from + updatedText.length, originalAttrs, originalMarks);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
if (hasChanged) {
|
|
194
|
+
view.dispatch(tr);
|
|
195
|
+
}
|
|
196
|
+
}, [editor]);
|
|
197
|
+
const unsetLink = useCallback(() => {
|
|
198
|
+
if (!editor)
|
|
199
|
+
return;
|
|
200
|
+
const { state } = editor;
|
|
201
|
+
const linkRange = getLinkRange({ state, pos: state.selection.from });
|
|
202
|
+
if (!linkRange)
|
|
203
|
+
return;
|
|
204
|
+
const command = editor
|
|
205
|
+
.chain()
|
|
206
|
+
.setTextSelection({
|
|
207
|
+
from: linkRange.from,
|
|
208
|
+
to: linkRange.to,
|
|
209
|
+
})
|
|
210
|
+
.unsetUnderline()
|
|
211
|
+
.unsetLink();
|
|
212
|
+
if (isLinkColor(editor.getAttributes('textStyle').color)) {
|
|
213
|
+
command.unsetColor();
|
|
214
|
+
}
|
|
215
|
+
command.run();
|
|
216
|
+
}, [editor]);
|
|
217
|
+
return {
|
|
218
|
+
setLink,
|
|
219
|
+
deleteLink,
|
|
220
|
+
unsetLink,
|
|
221
|
+
updateLinkAttrsGlobally,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Editor } from '@tiptap/react';
|
|
2
|
+
import { Mark } from '@tiptap/pm/model';
|
|
3
|
+
export interface MarkSnapshot {
|
|
4
|
+
marks: Mark[];
|
|
5
|
+
position: number;
|
|
6
|
+
timestamp: number;
|
|
7
|
+
textContent: string;
|
|
8
|
+
}
|
|
9
|
+
export interface UseMarkTrackingOptions {
|
|
10
|
+
/** Maximum number of snapshots to keep */
|
|
11
|
+
maxSnapshots?: number;
|
|
12
|
+
/** Marks to exclude from tracking */
|
|
13
|
+
excludeMarks?: string[];
|
|
14
|
+
/** Debounce delay for tracking (ms) */
|
|
15
|
+
debounceDelay?: number;
|
|
16
|
+
/** Auto-capture snapshots */
|
|
17
|
+
autoCapture?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare function useMarkTracking(editor: Editor | null, options?: UseMarkTrackingOptions): {
|
|
20
|
+
snapshots: MarkSnapshot[];
|
|
21
|
+
currentMarks: Mark[];
|
|
22
|
+
captureMarkSnapshot: () => void;
|
|
23
|
+
applySnapshot: (index: number) => void;
|
|
24
|
+
getSimilarSnapshots: (targetMarks?: Mark[]) => MarkSnapshot[];
|
|
25
|
+
clearSnapshots: () => void;
|
|
26
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
export function useMarkTracking(editor, options = {}) {
|
|
3
|
+
const { maxSnapshots = 10, excludeMarks = ['link', 'smartTag'], debounceDelay = 500, autoCapture = true, } = options;
|
|
4
|
+
const [snapshots, setSnapshots] = useState([]);
|
|
5
|
+
const [currentMarks, setCurrentMarks] = useState([]);
|
|
6
|
+
// Capture current mark state
|
|
7
|
+
const captureMarkSnapshot = useCallback(() => {
|
|
8
|
+
if (!editor)
|
|
9
|
+
return;
|
|
10
|
+
const { selection, storedMarks } = editor.state;
|
|
11
|
+
const { $from } = selection;
|
|
12
|
+
const marks = (storedMarks || $from.marks()).filter(mark => !excludeMarks.includes(mark.type.name));
|
|
13
|
+
const snapshot = {
|
|
14
|
+
marks,
|
|
15
|
+
position: $from.pos,
|
|
16
|
+
timestamp: Date.now(),
|
|
17
|
+
textContent: editor.state.doc.textBetween(Math.max(0, $from.pos - 50), Math.min(editor.state.doc.content.size, $from.pos + 50)),
|
|
18
|
+
};
|
|
19
|
+
setSnapshots(prev => {
|
|
20
|
+
const updated = [snapshot, ...prev];
|
|
21
|
+
return updated.slice(0, maxSnapshots);
|
|
22
|
+
});
|
|
23
|
+
setCurrentMarks(marks);
|
|
24
|
+
}, [editor, excludeMarks, maxSnapshots]);
|
|
25
|
+
// Apply marks from snapshot
|
|
26
|
+
const applySnapshot = useCallback((index) => {
|
|
27
|
+
if (!editor || !snapshots[index])
|
|
28
|
+
return;
|
|
29
|
+
const snapshot = snapshots[index];
|
|
30
|
+
// Clear current marks
|
|
31
|
+
editor.chain().unsetAllMarks().run();
|
|
32
|
+
// Apply snapshot marks
|
|
33
|
+
snapshot.marks.forEach(mark => {
|
|
34
|
+
editor.chain().setMark(mark.type.name, mark.attrs).run();
|
|
35
|
+
});
|
|
36
|
+
}, [editor, snapshots]);
|
|
37
|
+
// Get similar snapshots based on marks
|
|
38
|
+
const getSimilarSnapshots = useCallback((targetMarks = currentMarks) => snapshots.filter(snapshot => {
|
|
39
|
+
const markNames = snapshot.marks.map(m => m.type.name).sort();
|
|
40
|
+
const targetNames = targetMarks.map(m => m.type.name).sort();
|
|
41
|
+
return JSON.stringify(markNames) === JSON.stringify(targetNames);
|
|
42
|
+
}), [snapshots, currentMarks]);
|
|
43
|
+
// Auto-capture setup
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (!editor || !autoCapture)
|
|
46
|
+
return;
|
|
47
|
+
let timeoutId;
|
|
48
|
+
const handleUpdate = () => {
|
|
49
|
+
clearTimeout(timeoutId);
|
|
50
|
+
timeoutId = setTimeout(captureMarkSnapshot, debounceDelay);
|
|
51
|
+
};
|
|
52
|
+
editor.on('selectionUpdate', handleUpdate);
|
|
53
|
+
editor.on('update', handleUpdate);
|
|
54
|
+
return () => {
|
|
55
|
+
clearTimeout(timeoutId);
|
|
56
|
+
editor.off('selectionUpdate', handleUpdate);
|
|
57
|
+
editor.off('update', handleUpdate);
|
|
58
|
+
};
|
|
59
|
+
}, [editor, autoCapture, debounceDelay, captureMarkSnapshot]);
|
|
60
|
+
return {
|
|
61
|
+
snapshots,
|
|
62
|
+
currentMarks,
|
|
63
|
+
captureMarkSnapshot,
|
|
64
|
+
applySnapshot,
|
|
65
|
+
getSimilarSnapshots,
|
|
66
|
+
clearSnapshots: () => setSnapshots([]),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Editor } from '@tiptap/react';
|
|
2
|
+
export interface PersistenceConfig {
|
|
3
|
+
/** Storage key */
|
|
4
|
+
key: string;
|
|
5
|
+
/** Storage type */
|
|
6
|
+
storage?: 'localStorage' | 'sessionStorage' | 'indexedDB';
|
|
7
|
+
/** Auto-save delay */
|
|
8
|
+
autoSaveDelay?: number;
|
|
9
|
+
/** Version for data migration */
|
|
10
|
+
version?: number;
|
|
11
|
+
/** Compression */
|
|
12
|
+
compress?: boolean;
|
|
13
|
+
/** Encryption key */
|
|
14
|
+
encryptionKey?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface PersistentData {
|
|
17
|
+
content: string;
|
|
18
|
+
marks: any[];
|
|
19
|
+
metadata: {
|
|
20
|
+
timestamp: number;
|
|
21
|
+
version: number;
|
|
22
|
+
wordCount: number;
|
|
23
|
+
characterCount: number;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export declare function usePersistence(editor: Editor | null, config: PersistenceConfig): {
|
|
27
|
+
save: () => Promise<void>;
|
|
28
|
+
load: () => Promise<boolean>;
|
|
29
|
+
saveToStorage: (data: PersistentData) => Promise<void>;
|
|
30
|
+
loadFromStorage: () => Promise<PersistentData | null>;
|
|
31
|
+
};
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef } from 'react';
|
|
2
|
+
import { useDebouncedCallback } from 'use-debounce';
|
|
3
|
+
// Helper functions (simplified implementations)
|
|
4
|
+
async function compressString(str) {
|
|
5
|
+
// Implement compression logic
|
|
6
|
+
return str;
|
|
7
|
+
}
|
|
8
|
+
async function decompressString(str) {
|
|
9
|
+
// Implement decompression logic
|
|
10
|
+
return str;
|
|
11
|
+
}
|
|
12
|
+
async function encryptString(str, _key) {
|
|
13
|
+
// Implement encryption logic
|
|
14
|
+
return str;
|
|
15
|
+
}
|
|
16
|
+
async function decryptString(str, _key) {
|
|
17
|
+
// Implement decryption logic
|
|
18
|
+
return str;
|
|
19
|
+
}
|
|
20
|
+
async function saveToIndexedDB(_key, _data) {
|
|
21
|
+
// Implement IndexedDB save
|
|
22
|
+
}
|
|
23
|
+
async function loadFromIndexedDB(_key) {
|
|
24
|
+
// Implement IndexedDB load
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
function migrateData(data, _targetVersion) {
|
|
28
|
+
// Implement data migration logic
|
|
29
|
+
return data;
|
|
30
|
+
}
|
|
31
|
+
export function usePersistence(editor, config) {
|
|
32
|
+
const { key, storage = 'localStorage', autoSaveDelay = 2000, version = 1, compress = false, encryptionKey, } = config;
|
|
33
|
+
const lastSavedContent = useRef('');
|
|
34
|
+
// Storage operations
|
|
35
|
+
const saveToStorage = useCallback(async (data) => {
|
|
36
|
+
try {
|
|
37
|
+
let serializedData = JSON.stringify(data);
|
|
38
|
+
// Apply compression if enabled
|
|
39
|
+
if (compress && 'CompressionStream' in window) {
|
|
40
|
+
// Use browser compression API if available
|
|
41
|
+
serializedData = await compressString(serializedData);
|
|
42
|
+
}
|
|
43
|
+
// Apply encryption if key provided
|
|
44
|
+
if (encryptionKey) {
|
|
45
|
+
serializedData = await encryptString(serializedData, encryptionKey);
|
|
46
|
+
}
|
|
47
|
+
switch (storage) {
|
|
48
|
+
case 'localStorage':
|
|
49
|
+
localStorage.setItem(key, serializedData);
|
|
50
|
+
break;
|
|
51
|
+
case 'sessionStorage':
|
|
52
|
+
sessionStorage.setItem(key, serializedData);
|
|
53
|
+
break;
|
|
54
|
+
case 'indexedDB':
|
|
55
|
+
await saveToIndexedDB(key, serializedData);
|
|
56
|
+
break;
|
|
57
|
+
default:
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
// eslint-disable-next-line no-console
|
|
63
|
+
console.error('Failed to save to storage:', error);
|
|
64
|
+
}
|
|
65
|
+
}, [key, storage, compress, encryptionKey]);
|
|
66
|
+
const loadFromStorage = useCallback(async () => {
|
|
67
|
+
try {
|
|
68
|
+
let serializedData = null;
|
|
69
|
+
switch (storage) {
|
|
70
|
+
case 'localStorage':
|
|
71
|
+
serializedData = localStorage.getItem(key);
|
|
72
|
+
break;
|
|
73
|
+
case 'sessionStorage':
|
|
74
|
+
serializedData = sessionStorage.getItem(key);
|
|
75
|
+
break;
|
|
76
|
+
case 'indexedDB':
|
|
77
|
+
serializedData = await loadFromIndexedDB(key);
|
|
78
|
+
break;
|
|
79
|
+
default:
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
if (!serializedData)
|
|
83
|
+
return null;
|
|
84
|
+
// Apply decryption if key provided
|
|
85
|
+
if (encryptionKey) {
|
|
86
|
+
serializedData = await decryptString(serializedData, encryptionKey);
|
|
87
|
+
}
|
|
88
|
+
// Apply decompression if enabled
|
|
89
|
+
if (compress && 'DecompressionStream' in window) {
|
|
90
|
+
serializedData = await decompressString(serializedData);
|
|
91
|
+
}
|
|
92
|
+
const data = JSON.parse(serializedData);
|
|
93
|
+
// Handle version migration
|
|
94
|
+
if (data.metadata.version !== version) {
|
|
95
|
+
return migrateData(data, version);
|
|
96
|
+
}
|
|
97
|
+
return data;
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
// eslint-disable-next-line no-console
|
|
101
|
+
console.error('Failed to load from storage:', error);
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}, [key, storage, compress, encryptionKey, version]);
|
|
105
|
+
// Auto-save debounced function
|
|
106
|
+
const debouncedSave = useDebouncedCallback(async () => {
|
|
107
|
+
if (!editor)
|
|
108
|
+
return;
|
|
109
|
+
const currentContent = editor.getHTML();
|
|
110
|
+
// Only save if content changed
|
|
111
|
+
if (currentContent === lastSavedContent.current)
|
|
112
|
+
return;
|
|
113
|
+
const { selection, storedMarks } = editor.state;
|
|
114
|
+
const marks = storedMarks || selection.$from.marks();
|
|
115
|
+
const data = {
|
|
116
|
+
content: currentContent,
|
|
117
|
+
marks: marks.map(mark => ({
|
|
118
|
+
type: mark.type.name,
|
|
119
|
+
attrs: mark.attrs,
|
|
120
|
+
})),
|
|
121
|
+
metadata: {
|
|
122
|
+
timestamp: Date.now(),
|
|
123
|
+
version,
|
|
124
|
+
wordCount: editor.state.doc.textContent.split(/\s+/).length,
|
|
125
|
+
characterCount: editor.state.doc.textContent.length,
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
await saveToStorage(data);
|
|
129
|
+
lastSavedContent.current = currentContent;
|
|
130
|
+
}, autoSaveDelay);
|
|
131
|
+
// Manual save
|
|
132
|
+
const save = useCallback(async () => {
|
|
133
|
+
debouncedSave.flush();
|
|
134
|
+
}, [debouncedSave]);
|
|
135
|
+
// Load data
|
|
136
|
+
const load = useCallback(async () => {
|
|
137
|
+
if (!editor)
|
|
138
|
+
return false;
|
|
139
|
+
const data = await loadFromStorage();
|
|
140
|
+
if (!data)
|
|
141
|
+
return false;
|
|
142
|
+
// Set content
|
|
143
|
+
editor.commands.setContent(data.content);
|
|
144
|
+
// Restore marks if available
|
|
145
|
+
if (data.marks.length > 0) {
|
|
146
|
+
data.marks.forEach(({ type, attrs }) => {
|
|
147
|
+
editor.commands.setMark(type, attrs);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
lastSavedContent.current = data.content;
|
|
151
|
+
return true;
|
|
152
|
+
}, [editor, loadFromStorage]);
|
|
153
|
+
// Auto-save setup
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
if (!editor)
|
|
156
|
+
return;
|
|
157
|
+
editor.on('update', debouncedSave);
|
|
158
|
+
return () => {
|
|
159
|
+
editor.off('update', debouncedSave);
|
|
160
|
+
debouncedSave.flush(); // Save any pending changes
|
|
161
|
+
};
|
|
162
|
+
}, [editor, debouncedSave]);
|
|
163
|
+
return {
|
|
164
|
+
save,
|
|
165
|
+
load,
|
|
166
|
+
saveToStorage,
|
|
167
|
+
loadFromStorage,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useCallback, useEffect } from 'react';
|
|
2
|
+
export function useStyleMemory(editor) {
|
|
3
|
+
// Store current marks manually
|
|
4
|
+
const storeCurrentMarks = useCallback(() => {
|
|
5
|
+
editor?.commands.storeCurrentMarks();
|
|
6
|
+
}, [editor]);
|
|
7
|
+
// Clear stored marks
|
|
8
|
+
const clearStoredMarks = useCallback(() => {
|
|
9
|
+
editor?.commands.clearStoredMarks();
|
|
10
|
+
}, [editor]);
|
|
11
|
+
// Apply stored marks
|
|
12
|
+
const applyStoredMarks = useCallback(() => {
|
|
13
|
+
editor?.commands.applyStoredMarks();
|
|
14
|
+
}, [editor]);
|
|
15
|
+
// Auto-store marks when selection changes
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (!editor)
|
|
18
|
+
return;
|
|
19
|
+
const handleSelectionUpdate = () => {
|
|
20
|
+
const { selection, doc } = editor.state;
|
|
21
|
+
// Only store if editor has content and selection has marks
|
|
22
|
+
if (doc.content.size > 4 && !selection.empty) {
|
|
23
|
+
const { $from } = selection;
|
|
24
|
+
const marks = editor.state.storedMarks || $from.marks();
|
|
25
|
+
if (marks.length > 0) {
|
|
26
|
+
storeCurrentMarks();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
editor.on('selectionUpdate', handleSelectionUpdate);
|
|
31
|
+
editor.on('update', handleSelectionUpdate);
|
|
32
|
+
return () => {
|
|
33
|
+
editor.off('selectionUpdate', handleSelectionUpdate);
|
|
34
|
+
editor.off('update', handleSelectionUpdate);
|
|
35
|
+
};
|
|
36
|
+
}, [editor, storeCurrentMarks]);
|
|
37
|
+
return {
|
|
38
|
+
storeCurrentMarks,
|
|
39
|
+
clearStoredMarks,
|
|
40
|
+
applyStoredMarks,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Editor } from '@tiptap/react';
|
|
2
|
+
export interface StylePreset {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
marks: Array<{
|
|
7
|
+
type: string;
|
|
8
|
+
attrs: Record<string, any>;
|
|
9
|
+
}>;
|
|
10
|
+
textStyle: {
|
|
11
|
+
fontSize?: string;
|
|
12
|
+
fontFamily?: string;
|
|
13
|
+
color?: string;
|
|
14
|
+
backgroundColor?: string;
|
|
15
|
+
fontWeight?: string | number;
|
|
16
|
+
};
|
|
17
|
+
createdAt: number;
|
|
18
|
+
usageCount: number;
|
|
19
|
+
}
|
|
20
|
+
export interface UseStylePresetsOptions {
|
|
21
|
+
/** Storage key for presets */
|
|
22
|
+
storageKey?: string;
|
|
23
|
+
/** Maximum number of presets */
|
|
24
|
+
maxPresets?: number;
|
|
25
|
+
/** Default presets */
|
|
26
|
+
defaultPresets?: Omit<StylePreset, 'id' | 'createdAt' | 'usageCount'>[];
|
|
27
|
+
}
|
|
28
|
+
export declare function useStylePresets(editor: Editor | null, options?: UseStylePresetsOptions): {
|
|
29
|
+
presets: StylePreset[];
|
|
30
|
+
createPreset: (name: string, description?: string) => StylePreset | undefined;
|
|
31
|
+
applyPreset: (presetId: string) => void;
|
|
32
|
+
deletePreset: (presetId: string) => void;
|
|
33
|
+
getPopularPresets: (limit?: number) => StylePreset[];
|
|
34
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Use case**: Template systems, corporate branding, user productivity
|
|
2
|
+
import { useCallback } from 'react';
|
|
3
|
+
import { useBroadcastedLocalStorage } from '@antscorp/antsomi-ui/es/hooks/useBroadcastedLocalStorage';
|
|
4
|
+
export function useStylePresets(editor, options = {}) {
|
|
5
|
+
const { storageKey = 'text-editor-style-presets', maxPresets = 20, defaultPresets = [], } = options;
|
|
6
|
+
const { value: presets, setValue: setPresets } = useBroadcastedLocalStorage(storageKey, defaultPresets.map((preset, index) => ({
|
|
7
|
+
...preset,
|
|
8
|
+
id: `default-${index}`,
|
|
9
|
+
createdAt: Date.now(),
|
|
10
|
+
usageCount: 0,
|
|
11
|
+
})));
|
|
12
|
+
// Create preset from current style
|
|
13
|
+
const createPreset = useCallback((name, description) => {
|
|
14
|
+
if (!editor)
|
|
15
|
+
return;
|
|
16
|
+
const { selection, storedMarks } = editor.state;
|
|
17
|
+
const { $from } = selection;
|
|
18
|
+
const marks = (storedMarks || $from.marks()).map(mark => ({
|
|
19
|
+
type: mark.type.name,
|
|
20
|
+
attrs: mark.attrs,
|
|
21
|
+
}));
|
|
22
|
+
const textStyle = editor.getAttributes('textStyle');
|
|
23
|
+
const preset = {
|
|
24
|
+
id: `preset-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
25
|
+
name,
|
|
26
|
+
description,
|
|
27
|
+
marks,
|
|
28
|
+
textStyle,
|
|
29
|
+
createdAt: Date.now(),
|
|
30
|
+
usageCount: 0,
|
|
31
|
+
};
|
|
32
|
+
setPresets(prev => {
|
|
33
|
+
const updated = [preset, ...prev];
|
|
34
|
+
return updated.slice(0, maxPresets);
|
|
35
|
+
});
|
|
36
|
+
return preset;
|
|
37
|
+
}, [editor, maxPresets, setPresets]);
|
|
38
|
+
// Apply preset
|
|
39
|
+
const applyPreset = useCallback((presetId) => {
|
|
40
|
+
if (!editor)
|
|
41
|
+
return;
|
|
42
|
+
const preset = presets.find(p => p.id === presetId);
|
|
43
|
+
if (!preset)
|
|
44
|
+
return;
|
|
45
|
+
// Clear current formatting
|
|
46
|
+
editor.chain().focus().unsetAllMarks().run();
|
|
47
|
+
// Apply text style attributes
|
|
48
|
+
if (preset.textStyle.fontSize) {
|
|
49
|
+
editor.chain().setFontSize(preset.textStyle.fontSize).run();
|
|
50
|
+
}
|
|
51
|
+
if (preset.textStyle.fontFamily) {
|
|
52
|
+
editor.chain().setFontFamily(preset.textStyle.fontFamily).run();
|
|
53
|
+
}
|
|
54
|
+
if (preset.textStyle.color) {
|
|
55
|
+
editor.chain().setColor(preset.textStyle.color).run();
|
|
56
|
+
}
|
|
57
|
+
if (preset.textStyle.backgroundColor) {
|
|
58
|
+
editor.chain().setBackgroundColor(preset.textStyle.backgroundColor).run();
|
|
59
|
+
}
|
|
60
|
+
if (preset.textStyle.fontWeight) {
|
|
61
|
+
editor.chain().setFontWeight(preset.textStyle.fontWeight).run();
|
|
62
|
+
}
|
|
63
|
+
// Apply marks
|
|
64
|
+
preset.marks.forEach(({ type, attrs }) => {
|
|
65
|
+
editor.chain().setMark(type, attrs).run();
|
|
66
|
+
});
|
|
67
|
+
// Update usage count
|
|
68
|
+
setPresets(prev => prev.map(p => (p.id === presetId ? { ...p, usageCount: p.usageCount + 1 } : p)));
|
|
69
|
+
}, [editor, presets, setPresets]);
|
|
70
|
+
// Delete preset
|
|
71
|
+
const deletePreset = useCallback((presetId) => {
|
|
72
|
+
setPresets(prev => prev.filter(p => p.id !== presetId));
|
|
73
|
+
}, [setPresets]);
|
|
74
|
+
// Get popular presets
|
|
75
|
+
const getPopularPresets = useCallback((limit = 5) => [...presets].sort((a, b) => b.usageCount - a.usageCount).slice(0, limit), [presets]);
|
|
76
|
+
return {
|
|
77
|
+
presets,
|
|
78
|
+
createPreset,
|
|
79
|
+
applyPreset,
|
|
80
|
+
deletePreset,
|
|
81
|
+
getPopularPresets,
|
|
82
|
+
};
|
|
83
|
+
}
|