@antscorp/antsomi-ui 1.3.7-beta.2 → 1.3.7-beta.21
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/components/atoms/index.d.ts +0 -2
- package/es/components/atoms/index.js +0 -2
- package/es/components/molecules/TagifyInput/TagifyInput.js +48 -18
- package/es/components/molecules/TagifyInput/types.d.ts +20 -1
- package/es/components/molecules/TagifyInput/utils.d.ts +3 -1
- package/es/components/molecules/TagifyInput/utils.js +9 -0
- package/es/components/organism/ActivityTimeline/ActivityTimeline.js +3 -3
- package/es/components/organism/ActivityTimeline/components/ItemEvent/ItemEvent.js +7 -1
- package/es/components/organism/ActivityTimeline/components/ItemGroupEvent/ItemGroupEvent.js +14 -2
- package/es/components/organism/ActivityTimeline/constants.d.ts +9 -9
- package/es/components/organism/ActivityTimeline/constants.js +3 -3
- package/es/components/organism/ActivityTimeline/index.d.ts +530 -1
- package/es/components/organism/ActivityTimeline/index.js +9 -1
- package/es/components/organism/ActivityTimeline/utils.d.ts +7 -1
- package/es/components/organism/ActivityTimeline/utils.js +10 -7
- package/es/components/organism/TextEditor/TextEditor.d.ts +8 -2
- package/es/components/organism/TextEditor/TextEditor.js +76 -3
- package/es/components/organism/TextEditor/extensions/BubbleMenu/bubble-menu-plugin.js +3 -2
- package/es/components/organism/TextEditor/extensions/Link.js +3 -3
- package/es/components/organism/TextEditor/extensions/SmartTag.d.ts +0 -6
- package/es/components/organism/TextEditor/extensions/SmartTag.js +3 -2
- package/es/components/organism/TextEditor/hooks/useMarkTracking.js +2 -1
- package/es/components/organism/TextEditor/index.d.ts +6 -5
- package/es/components/organism/TextEditor/stories/WithOldDynAndLink/settings.json +95 -0
- package/es/components/organism/TextEditor/types.d.ts +75 -2
- package/es/components/organism/TextEditor/types.js +1 -0
- package/es/components/organism/TextEditor/utils/documentState.d.ts +14 -0
- package/es/components/organism/TextEditor/utils/documentState.js +25 -0
- package/es/components/organism/TextEditor/utils/htmlProcessing.js +60 -0
- package/es/components/organism/TextEditor/utils/link.d.ts +10 -1
- package/es/components/organism/TextEditor/utils/link.js +161 -7
- package/es/components/organism/TextEditor/utils/menu.js +2 -1
- package/es/components/organism/TextEditor/utils/selection.js +3 -2
- package/es/components/organism/TextEditor/utils/smartTag.js +2 -1
- package/es/components/organism/index.d.ts +1 -1
- package/es/hooks/index.d.ts +1 -1
- package/es/hooks/index.js +1 -1
- package/es/services/MediaTemplateDesign/UploadFile/index.js +4 -4
- package/es/types/index.d.ts +1 -1
- package/es/types/index.js +1 -1
- package/es/utils/common.d.ts +1 -1
- package/es/utils/common.js +3 -3
- package/package.json +11 -23
|
@@ -28,13 +28,14 @@ import { SmartTag } from './extensions/SmartTag';
|
|
|
28
28
|
import { TextTransform } from './extensions/TextTransform';
|
|
29
29
|
import { CustomUnorderedList } from './extensions/UnorderedList';
|
|
30
30
|
import { ClearFormatting } from './extensions/ClearFormatting';
|
|
31
|
-
import { useTextEditorStore } from './provider';
|
|
31
|
+
import { TextEditorProvider, useTextEditorStore } from './provider';
|
|
32
32
|
import { StyledEditorContent } from './styled';
|
|
33
|
+
import { isSmartTagNode, } from './types';
|
|
33
34
|
import { emojiSuggestion } from './ui/Emoji';
|
|
34
35
|
import { Toolbar } from './ui/Toolbar';
|
|
35
|
-
import { analyzeFont, defaultShouldShowBubbleMenu, handleLinkAction, handleSmartTagAction, isShowLinkToolbar, safeParseHTMLContent, htmlSerializerForOutput, } from './utils';
|
|
36
|
+
import { analyzeFont, defaultShouldShowBubbleMenu, getAllFullLinkGroups, handleLinkAction, handleSmartTagAction, isShowLinkToolbar, safeParseHTMLContent, htmlSerializerForOutput, } from './utils';
|
|
36
37
|
import { BubbleMenu } from './ui/BubbleMenu';
|
|
37
|
-
|
|
38
|
+
const TextEditorInternal = memo(forwardRef((props, ref) => {
|
|
38
39
|
const { id, className, config, editable = true, initialContent = '', dataAttributes, onUpdateDebounced = 400, defaultTextStyle: defaultTextStyleProp, linkHandler: outerLinkHandler, smartTagHandler: outerSmartTagHandler, bubbleMenuProps, style, render, onUpdate, onFocus, onChangeFont, } = props;
|
|
39
40
|
const isShowBubbleMenu = useTextEditorStore(state => state.isShowBubbleMenu);
|
|
40
41
|
const setBubbleMenuContainer = useTextEditorStore(state => state.setBubbleMenuContainer);
|
|
@@ -203,6 +204,23 @@ export const TextEditor = memo(forwardRef((props, ref) => {
|
|
|
203
204
|
editor?.chain().blur().setTextSelection({ from: 0, to: 0 }).run();
|
|
204
205
|
}
|
|
205
206
|
}, [editor]);
|
|
207
|
+
const handleEachLinkGroup = useCallback(callback => {
|
|
208
|
+
if (!editor)
|
|
209
|
+
return;
|
|
210
|
+
const linkGroups = getAllFullLinkGroups(editor.state);
|
|
211
|
+
linkGroups.forEach(callback);
|
|
212
|
+
}, [editor]);
|
|
213
|
+
const handleEachSmartag = useCallback(callback => {
|
|
214
|
+
if (!editor)
|
|
215
|
+
return;
|
|
216
|
+
const { state } = editor;
|
|
217
|
+
const { doc } = state;
|
|
218
|
+
doc.descendants(node => {
|
|
219
|
+
if (isSmartTagNode(node)) {
|
|
220
|
+
callback(node.attrs);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
}, [editor]);
|
|
206
224
|
const handleBubbleMenuRef = useCallback((htmlElement) => {
|
|
207
225
|
setBubbleMenuContainer(htmlElement);
|
|
208
226
|
}, [setBubbleMenuContainer]);
|
|
@@ -226,6 +244,8 @@ export const TextEditor = memo(forwardRef((props, ref) => {
|
|
|
226
244
|
})
|
|
227
245
|
.run();
|
|
228
246
|
},
|
|
247
|
+
eachLinkGroup: handleEachLinkGroup,
|
|
248
|
+
eachSmartag: handleEachSmartag,
|
|
229
249
|
setSmartTag: handleSetSmartTag,
|
|
230
250
|
deleteSmartTag: handleDeleteSmartTag,
|
|
231
251
|
updateSmartTagAttrs: handleUpdateSmartTagAttrs,
|
|
@@ -256,3 +276,56 @@ export const TextEditor = memo(forwardRef((props, ref) => {
|
|
|
256
276
|
});
|
|
257
277
|
} })) })] }));
|
|
258
278
|
}));
|
|
279
|
+
TextEditorInternal.displayName = 'TextEditorInternal';
|
|
280
|
+
const TextEditorWithProvider = forwardRef((props, ref) => {
|
|
281
|
+
const { onChangeColors, ...editorProps } = props;
|
|
282
|
+
const providerRef = useRef(null);
|
|
283
|
+
const editorRef = useRef(null);
|
|
284
|
+
useImperativeHandle(ref, () => ({
|
|
285
|
+
// Forward TextEditorRef methods - accessed at call time
|
|
286
|
+
get style() {
|
|
287
|
+
return editorRef.current?.style;
|
|
288
|
+
},
|
|
289
|
+
get editor() {
|
|
290
|
+
return editorRef.current?.editor ?? null;
|
|
291
|
+
},
|
|
292
|
+
get setLink() {
|
|
293
|
+
return editorRef.current.setLink;
|
|
294
|
+
},
|
|
295
|
+
get deleteLink() {
|
|
296
|
+
return editorRef.current.deleteLink;
|
|
297
|
+
},
|
|
298
|
+
get updateLinkAttrs() {
|
|
299
|
+
return editorRef.current.updateLinkAttrs;
|
|
300
|
+
},
|
|
301
|
+
get updateLinkText() {
|
|
302
|
+
return editorRef.current.updateLinkText;
|
|
303
|
+
},
|
|
304
|
+
get eachLinkGroup() {
|
|
305
|
+
return editorRef.current.eachLinkGroup;
|
|
306
|
+
},
|
|
307
|
+
get eachSmartag() {
|
|
308
|
+
return editorRef.current.eachSmartag;
|
|
309
|
+
},
|
|
310
|
+
get setSmartTag() {
|
|
311
|
+
return editorRef.current.setSmartTag;
|
|
312
|
+
},
|
|
313
|
+
get deleteSmartTag() {
|
|
314
|
+
return editorRef.current.deleteSmartTag;
|
|
315
|
+
},
|
|
316
|
+
get updateSmartTagAttrs() {
|
|
317
|
+
return editorRef.current.updateSmartTagAttrs;
|
|
318
|
+
},
|
|
319
|
+
get blur() {
|
|
320
|
+
return editorRef.current.blur;
|
|
321
|
+
},
|
|
322
|
+
// Forward TextEditorProviderRefHandler methods
|
|
323
|
+
updateColors: colors => providerRef.current?.updateColors?.(colors),
|
|
324
|
+
}), []);
|
|
325
|
+
return (_jsx(TextEditorProvider, { ref: providerRef, onChangeColors: onChangeColors, children: _jsx(TextEditorInternal, { ...editorProps, ref: editorRef }) }));
|
|
326
|
+
});
|
|
327
|
+
TextEditorWithProvider.displayName = 'TextEditorWithProvider';
|
|
328
|
+
export const TextEditor = Object.assign(TextEditorWithProvider, {
|
|
329
|
+
Provider: TextEditorProvider,
|
|
330
|
+
Internal: TextEditorInternal,
|
|
331
|
+
});
|
|
@@ -2,6 +2,7 @@ import { arrow, autoPlacement, computePosition, flip, hide, inline, offset, shif
|
|
|
2
2
|
import { isTextSelection, posToDOMRect } from '@tiptap/core';
|
|
3
3
|
import { Plugin, PluginKey } from '@tiptap/pm/state';
|
|
4
4
|
import { CellSelection } from '@tiptap/pm/tables';
|
|
5
|
+
import { textBetween } from '../../utils';
|
|
5
6
|
function combineDOMRects(rect1, rect2) {
|
|
6
7
|
const top = Math.min(rect1.top, rect2.top);
|
|
7
8
|
const bottom = Math.max(rect1.bottom, rect2.bottom);
|
|
@@ -76,12 +77,12 @@ export class BubbleMenuView {
|
|
|
76
77
|
onDestroy: undefined,
|
|
77
78
|
};
|
|
78
79
|
this.shouldShow = ({ view, state, from, to, }) => {
|
|
79
|
-
const {
|
|
80
|
+
const { selection } = state;
|
|
80
81
|
const { empty } = selection;
|
|
81
82
|
// Sometime check for `empty` is not enough.
|
|
82
83
|
// Doubleclick an empty paragraph returns a node size of 2.
|
|
83
84
|
// So we check also for an empty text size.
|
|
84
|
-
const isEmptyTextBlock = !
|
|
85
|
+
const isEmptyTextBlock = !textBetween(state, from, to).length && isTextSelection(state.selection);
|
|
85
86
|
// When clicking on a element inside the bubble menu the editor "blur" event
|
|
86
87
|
// is called and the bubble menu item is focussed. In this case we should
|
|
87
88
|
// consider the menu as part of the editor and keep showing the menu
|
|
@@ -3,7 +3,7 @@ import { CUSTOM_LINK_EXTENSION_NAME, LINK_TEXT_COLOR } from '../constants';
|
|
|
3
3
|
import { TextSelection } from '@tiptap/pm/state';
|
|
4
4
|
import { dataAttrArrayToObject } from '@antscorp/antsomi-ui/es/utils';
|
|
5
5
|
import { isEmpty, isEqual, map, toString } from 'lodash';
|
|
6
|
-
import { getLinkMarkRanges, getLinkRange, isLinkColor } from '../utils';
|
|
6
|
+
import { getLinkMarkRanges, getLinkRange, isLinkColor, textBetween } from '../utils';
|
|
7
7
|
import { mergeAdjacentRanges } from '../utils/shared';
|
|
8
8
|
export const CustomLink = TiptapLinkExtension.extend({
|
|
9
9
|
name: CUSTOM_LINK_EXTENSION_NAME,
|
|
@@ -40,7 +40,7 @@ export const CustomLink = TiptapLinkExtension.extend({
|
|
|
40
40
|
setCustomLink: ({ attrs, content }) => ({ state, chain }) => {
|
|
41
41
|
const { selection } = state;
|
|
42
42
|
const { from, to } = selection;
|
|
43
|
-
let linkContent =
|
|
43
|
+
let linkContent = textBetween(state, from, to);
|
|
44
44
|
if (content && linkContent !== content) {
|
|
45
45
|
linkContent = content;
|
|
46
46
|
}
|
|
@@ -146,7 +146,7 @@ export const CustomLink = TiptapLinkExtension.extend({
|
|
|
146
146
|
to: state.doc.content.size,
|
|
147
147
|
}).filter(range => predicate(range.mark.attrs));
|
|
148
148
|
mergeAdjacentRanges(linkMarkRanges, (existingRanges, candidateRange) => existingRanges.some(range => mergeAdjacentEqualFn(range.mark.attrs, candidateRange.mark.attrs))).forEach(range => {
|
|
149
|
-
const currentText =
|
|
149
|
+
const currentText = textBetween(state, range.from, range.to);
|
|
150
150
|
const updatedText = updated(currentText);
|
|
151
151
|
if (currentText === updatedText)
|
|
152
152
|
return;
|
|
@@ -58,8 +58,9 @@ export const SmartTag = Node.create({
|
|
|
58
58
|
return {
|
|
59
59
|
setSmartTag: attrs => ({ chain, state }) => {
|
|
60
60
|
const { selection, storedMarks } = state;
|
|
61
|
-
const { $from } = selection;
|
|
62
|
-
|
|
61
|
+
const { $from, from, to } = selection;
|
|
62
|
+
// Get marks from inside the selection to capture boundary marks
|
|
63
|
+
const marks = storedMarks || (from !== to ? state.doc.resolve(from + 1).marks() : $from.marks());
|
|
63
64
|
const smartTagNode = this.type.create(attrs);
|
|
64
65
|
const nodeWithMarks = marks.length > 0 ? smartTagNode.mark(marks) : smartTagNode;
|
|
65
66
|
return chain()
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import { textBetween } from '../utils';
|
|
2
3
|
export function useMarkTracking(editor, options = {}) {
|
|
3
4
|
const { maxSnapshots = 10, excludeMarks = ['link', 'smartTag'], debounceDelay = 500, autoCapture = true, } = options;
|
|
4
5
|
const [snapshots, setSnapshots] = useState([]);
|
|
@@ -14,7 +15,7 @@ export function useMarkTracking(editor, options = {}) {
|
|
|
14
15
|
marks,
|
|
15
16
|
position: $from.pos,
|
|
16
17
|
timestamp: Date.now(),
|
|
17
|
-
textContent: editor.state
|
|
18
|
+
textContent: textBetween(editor.state, Math.max(0, $from.pos - 50), Math.min(editor.state.doc.content.size, $from.pos + 50)),
|
|
18
19
|
};
|
|
19
20
|
setSnapshots(prev => {
|
|
20
21
|
const updated = [snapshot, ...prev];
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
2
|
import { JSONContent } from '@tiptap/core';
|
|
3
|
-
export type { TextEditorProps, TextEditorRef, TextEditorComponentsRender, LinkAttrs, FontConfig, } from './types';
|
|
3
|
+
export type { TextEditorProps, TextEditorAllProps, TextEditorRef, TextEditorWithProviderRef, TextEditorComponentsRender, LinkAttrs, FontConfig, } from './types';
|
|
4
4
|
export { isLinkMark, isLinkMarkRange } from './types';
|
|
5
5
|
export { CUSTOM_LINK_EXTENSION_NAME } from './constants';
|
|
6
6
|
export type { JSONContent as TextEditorJSONContent };
|
|
7
7
|
export { TextEditorProvider, type TextEditorProviderProps, type TextEditorProviderRefHandler, } from './provider';
|
|
8
|
-
export declare const TextEditor: import("react").
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
|
|
8
|
+
export declare const TextEditor: import("react").ForwardRefExoticComponent<import("./types").TextEditorProps & {
|
|
9
|
+
onChangeColors?: ((colors: string[]) => void) | undefined;
|
|
10
|
+
} & import("react").RefAttributes<import("./types").TextEditorWithProviderRef>> & {
|
|
11
|
+
Provider: import("react").ForwardRefExoticComponent<import("./provider").TextEditorProviderProps & import("react").RefAttributes<import("./provider").TextEditorProviderRefHandler>>;
|
|
12
|
+
Internal: import("react").MemoExoticComponent<import("react").ForwardRefExoticComponent<import("./types").TextEditorProps & import("react").RefAttributes<import("./types").TextEditorRef>>>;
|
|
12
13
|
} & {
|
|
13
14
|
Utils: {
|
|
14
15
|
htmlMinifyForEmail: (htmlEditorContent: string) => string;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
{
|
|
2
|
+
"rawHTML": "<div class=\"fr-element fr-view\" dir=\"auto\" contenteditable=\"true\" aria-disabled=\"false\" spellcheck=\"true\"><p><span style=\"font-family: Montserrat; font-size: 22px; letter-spacing: 0px; color: #000000;\"><a data-link-id=\"j24jwuk2\" target=\"_blank\" href=\"https://ant.design/components/modal\"><span data-dynamic=\"true\" data-dynamic-id=\"i6fapgk6\" style=\"direction: unset; unicode-bidi: bidi-override; background-color: rgba(0, 199, 97, 0.2);\">Last modified by</span></a> Special <a data-link-id=\"rrd48wh5\" target=\"_blank\" href=\"https://ant.design\">Bonus <span data-dynamic=\"true\" data-dynamic-id=\"4ip5g3w4\" style=\"direction: unset; unicode-bidi: bidi-override; background-color: rgba(0, 199, 97, 0.2);\">Created date</span> Has</a> Been <span data-dynamic=\"true\" data-dynamic-id=\"vd3tmfc3\" style=\"direction: unset; unicode-bidi: bidi-override; background-color: rgba(0, 199, 97, 0.2);\"><a data-link-id=\"ir1h7e67\" target=\"_blank\" href=\"https://fireup.pro/news/goodbye-useeffect-exploring-use-in-react-19?ref=dailydev\">Name</a></span> Special <a data-link-id=\"rrd48wh5\" href=\"https://ant.design\" target=\"_blank\" id=\"isPasted\">Bonus</a> fasdfas sdfadf</span></p></div>",
|
|
3
|
+
"dynamic": {
|
|
4
|
+
"data": {
|
|
5
|
+
"4ip5g3w4": {
|
|
6
|
+
"type": "visitor-attribute",
|
|
7
|
+
"attribute": {
|
|
8
|
+
"label": "Created date",
|
|
9
|
+
"value": "date_created",
|
|
10
|
+
"status": 1,
|
|
11
|
+
"dataType": "datetime",
|
|
12
|
+
"disabled": false,
|
|
13
|
+
"datetimeFormatSettings": {
|
|
14
|
+
"type": "datetime",
|
|
15
|
+
"language": "en",
|
|
16
|
+
"hasDateFormat": true,
|
|
17
|
+
"hasTimeFormat": true,
|
|
18
|
+
"dateParseFormat": "MM/DD/YYYY",
|
|
19
|
+
"dateParseOption": "medium",
|
|
20
|
+
"timeParseFormat": "12hour",
|
|
21
|
+
"timeParseOption": "medium",
|
|
22
|
+
"dateFormatString": "MMM DD, YYYY h:mm:ss A"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"mappingKey": "lg5pk39o2qmxyc7oano5-4ip5g3w4",
|
|
26
|
+
"mappingFields": "visitor.date_created"
|
|
27
|
+
},
|
|
28
|
+
"i6fapgk6": {
|
|
29
|
+
"type": "event-attribute",
|
|
30
|
+
"event": "226539:17",
|
|
31
|
+
"source": [556657814],
|
|
32
|
+
"attribute": {
|
|
33
|
+
"type": 1,
|
|
34
|
+
"label": "Last modified by",
|
|
35
|
+
"value": "ad_zone.u_user_id",
|
|
36
|
+
"status": 1,
|
|
37
|
+
"children": [],
|
|
38
|
+
"dataType": "number",
|
|
39
|
+
"itemTypeId": -1013,
|
|
40
|
+
"itemTypeName": "ad_zone",
|
|
41
|
+
"propertyName": "u_user_id",
|
|
42
|
+
"eventPropertySyntax": "dims.ad_zone_id",
|
|
43
|
+
"numberFormatSettings": {
|
|
44
|
+
"type": "number",
|
|
45
|
+
"decimal": ".",
|
|
46
|
+
"grouping": ",",
|
|
47
|
+
"isCompact": false,
|
|
48
|
+
"prefixType": "code",
|
|
49
|
+
"currencyCode": "USD",
|
|
50
|
+
"decimalPlaces": 2
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"mappingKey": "lg5pk39o2qmxyc7oano5-i6fapgk6",
|
|
54
|
+
"mappingFields": "event.ad_zone.u_user_id"
|
|
55
|
+
},
|
|
56
|
+
"vd3tmfc3": {
|
|
57
|
+
"type": "customer-attribute",
|
|
58
|
+
"attribute": {
|
|
59
|
+
"label": "Name",
|
|
60
|
+
"value": "name",
|
|
61
|
+
"status": 1,
|
|
62
|
+
"dataType": "string",
|
|
63
|
+
"disabled": false
|
|
64
|
+
},
|
|
65
|
+
"mappingKey": "lg5pk39o2qmxyc7oano5-vd3tmfc3",
|
|
66
|
+
"mappingFields": "customer.name"
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
"highlight": true,
|
|
70
|
+
"selectedId": ""
|
|
71
|
+
},
|
|
72
|
+
"link": {
|
|
73
|
+
"data": {
|
|
74
|
+
"ir1h7e67": {
|
|
75
|
+
"url": "https://fireup.pro/news/goodbye-useeffect-exploring-use-in-react-19?ref=dailydev",
|
|
76
|
+
"text": "Name",
|
|
77
|
+
"linkType": "static",
|
|
78
|
+
"openNewTab": true
|
|
79
|
+
},
|
|
80
|
+
"j24jwuk2": {
|
|
81
|
+
"url": "https://ant.design/components/modal",
|
|
82
|
+
"text": "Your",
|
|
83
|
+
"linkType": "static",
|
|
84
|
+
"openNewTab": true
|
|
85
|
+
},
|
|
86
|
+
"rrd48wh5": {
|
|
87
|
+
"url": "https://ant.design",
|
|
88
|
+
"text": "Bonus Offer Has",
|
|
89
|
+
"linkType": "static",
|
|
90
|
+
"openNewTab": true
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
"selectedId": ""
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
2
|
import type { JSONContent, Editor, MarkRange } from '@tiptap/core';
|
|
3
|
-
import type { Mark } from '@tiptap/pm/model';
|
|
4
|
-
import type { SmartTagAttrs } from './extensions/SmartTag';
|
|
3
|
+
import type { Mark, Node } from '@tiptap/pm/model';
|
|
5
4
|
import { ORDERED_LIST_STYLE_TYPE, UNORDERED_LIST_STYLE_TYPE } from './constants';
|
|
6
5
|
import type { OverrideProperties } from 'type-fest';
|
|
7
6
|
import { BubbleMenuPluginProps } from './extensions/BubbleMenu';
|
|
@@ -22,6 +21,11 @@ export type HandleLinkRef = {
|
|
|
22
21
|
updated: (currentText: string) => string;
|
|
23
22
|
mergeAdjacentEqualFn?: (currentAttrs: LinkAttrs, candidateAttrs: LinkAttrs) => boolean;
|
|
24
23
|
}) => void;
|
|
24
|
+
eachLinkGroup: (callback: (params: {
|
|
25
|
+
attrs: LinkAttrs;
|
|
26
|
+
content: string;
|
|
27
|
+
}) => void) => void;
|
|
28
|
+
eachSmartag: (callback: (attrs: SmartTagAttrs) => void) => void;
|
|
25
29
|
};
|
|
26
30
|
export type TextEditorRef = HandleSmartTagRef & HandleLinkRef & {
|
|
27
31
|
blur: (perserveSelection?: boolean) => void;
|
|
@@ -142,6 +146,12 @@ export type TextEditorProps = {
|
|
|
142
146
|
};
|
|
143
147
|
}) => void;
|
|
144
148
|
};
|
|
149
|
+
export type TextEditorAllProps = TextEditorProps & {
|
|
150
|
+
onChangeColors?: (colors: string[]) => void;
|
|
151
|
+
};
|
|
152
|
+
export type TextEditorWithProviderRef = TextEditorRef & {
|
|
153
|
+
updateColors?: (colors: string[]) => void;
|
|
154
|
+
};
|
|
145
155
|
export type OrderedListStyleType = (typeof ORDERED_LIST_STYLE_TYPE)[keyof typeof ORDERED_LIST_STYLE_TYPE];
|
|
146
156
|
export type UnorderedListStyleType = (typeof UNORDERED_LIST_STYLE_TYPE)[keyof typeof UNORDERED_LIST_STYLE_TYPE];
|
|
147
157
|
export declare const isOrderedListStyleType: (value: unknown) => value is OrderedListStyleType;
|
|
@@ -155,9 +165,18 @@ export type LinkAttrs = {
|
|
|
155
165
|
title: string | null;
|
|
156
166
|
data: Record<string, string>;
|
|
157
167
|
}>;
|
|
168
|
+
export type SmartTagAttrs = {
|
|
169
|
+
id: string;
|
|
170
|
+
} & Partial<{
|
|
171
|
+
content: string;
|
|
172
|
+
tag: string;
|
|
173
|
+
}>;
|
|
158
174
|
export type LinkMark = OverrideProperties<Mark, {
|
|
159
175
|
attrs: LinkAttrs;
|
|
160
176
|
}>;
|
|
177
|
+
export type SmartTagNode = OverrideProperties<Node, {
|
|
178
|
+
attrs: SmartTagAttrs;
|
|
179
|
+
}>;
|
|
161
180
|
export type LinkMarkRange = OverrideProperties<MarkRange, {
|
|
162
181
|
mark: LinkMark;
|
|
163
182
|
}>;
|
|
@@ -170,6 +189,60 @@ export declare const isLinkMark: (mark: Mark) => mark is {
|
|
|
170
189
|
toJSON: () => any;
|
|
171
190
|
attrs: LinkAttrs;
|
|
172
191
|
};
|
|
192
|
+
export declare const isSmartTagNode: (node: Node) => node is {
|
|
193
|
+
toString: () => string;
|
|
194
|
+
readonly type: import("prosemirror-model").NodeType;
|
|
195
|
+
readonly marks: readonly Mark[];
|
|
196
|
+
readonly content: import("prosemirror-model").Fragment;
|
|
197
|
+
readonly children: readonly Node[];
|
|
198
|
+
readonly text: string | undefined;
|
|
199
|
+
readonly nodeSize: number;
|
|
200
|
+
readonly childCount: number;
|
|
201
|
+
child: (index: number) => Node;
|
|
202
|
+
maybeChild: (index: number) => Node | null;
|
|
203
|
+
forEach: (f: (node: Node, offset: number, index: number) => void) => void;
|
|
204
|
+
nodesBetween: (from: number, to: number, f: (node: Node, pos: number, parent: Node | null, index: number) => boolean | void, startPos?: number | undefined) => void;
|
|
205
|
+
descendants: (f: (node: Node, pos: number, parent: Node | null, index: number) => boolean | void) => void;
|
|
206
|
+
readonly textContent: string;
|
|
207
|
+
textBetween: (from: number, to: number, blockSeparator?: string | null | undefined, leafText?: string | ((leafNode: Node) => string) | null | undefined) => string;
|
|
208
|
+
readonly firstChild: Node | null;
|
|
209
|
+
readonly lastChild: Node | null;
|
|
210
|
+
eq: (other: Node) => boolean;
|
|
211
|
+
sameMarkup: (other: Node) => boolean;
|
|
212
|
+
hasMarkup: (type: import("prosemirror-model").NodeType, attrs?: import("prosemirror-model").Attrs | null | undefined, marks?: readonly Mark[] | undefined) => boolean;
|
|
213
|
+
copy: (content?: import("prosemirror-model").Fragment | null | undefined) => Node;
|
|
214
|
+
mark: (marks: readonly Mark[]) => Node;
|
|
215
|
+
cut: (from: number, to?: number | undefined) => Node;
|
|
216
|
+
slice: (from: number, to?: number | undefined, includeParents?: boolean | undefined) => import("prosemirror-model").Slice;
|
|
217
|
+
replace: (from: number, to: number, slice: import("prosemirror-model").Slice) => Node;
|
|
218
|
+
nodeAt: (pos: number) => Node | null;
|
|
219
|
+
childAfter: (pos: number) => {
|
|
220
|
+
node: Node | null;
|
|
221
|
+
index: number;
|
|
222
|
+
offset: number;
|
|
223
|
+
};
|
|
224
|
+
childBefore: (pos: number) => {
|
|
225
|
+
node: Node | null;
|
|
226
|
+
index: number;
|
|
227
|
+
offset: number;
|
|
228
|
+
};
|
|
229
|
+
resolve: (pos: number) => import("prosemirror-model").ResolvedPos;
|
|
230
|
+
rangeHasMark: (from: number, to: number, type: import("prosemirror-model").MarkType | Mark) => boolean;
|
|
231
|
+
readonly isBlock: boolean;
|
|
232
|
+
readonly isTextblock: boolean;
|
|
233
|
+
readonly inlineContent: boolean;
|
|
234
|
+
readonly isInline: boolean;
|
|
235
|
+
readonly isText: boolean;
|
|
236
|
+
readonly isLeaf: boolean;
|
|
237
|
+
readonly isAtom: boolean;
|
|
238
|
+
contentMatchAt: (index: number) => import("prosemirror-model").ContentMatch;
|
|
239
|
+
canReplace: (from: number, to: number, replacement?: import("prosemirror-model").Fragment | undefined, start?: number | undefined, end?: number | undefined) => boolean;
|
|
240
|
+
canReplaceWith: (from: number, to: number, type: import("prosemirror-model").NodeType, marks?: readonly Mark[] | undefined) => boolean;
|
|
241
|
+
canAppend: (other: Node) => boolean;
|
|
242
|
+
check: () => void;
|
|
243
|
+
toJSON: () => any;
|
|
244
|
+
attrs: SmartTagAttrs;
|
|
245
|
+
};
|
|
173
246
|
export declare const isLinkMarkRange: (markRange: MarkRange) => markRange is {
|
|
174
247
|
from: number;
|
|
175
248
|
to: number;
|
|
@@ -2,4 +2,5 @@ import { CUSTOM_LINK_EXTENSION_NAME, ORDERED_LIST_STYLE_TYPE, UNORDERED_LIST_STY
|
|
|
2
2
|
export const isOrderedListStyleType = (value) => typeof value === 'string' && Object.values(ORDERED_LIST_STYLE_TYPE).some(v => v === value);
|
|
3
3
|
export const isUnorderedListStyleType = (value) => typeof value === 'string' && Object.values(UNORDERED_LIST_STYLE_TYPE).some(v => v === value);
|
|
4
4
|
export const isLinkMark = (mark) => mark.type.name === CUSTOM_LINK_EXTENSION_NAME;
|
|
5
|
+
export const isSmartTagNode = (node) => node.type.name === 'smartTag';
|
|
5
6
|
export const isLinkMarkRange = (markRange) => isLinkMark(markRange.mark);
|
|
@@ -57,3 +57,17 @@ export declare class DocumentStateTracker {
|
|
|
57
57
|
reset(): void;
|
|
58
58
|
}
|
|
59
59
|
export declare function isEntireParagraphSelected(editor: Editor): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Helper function to extract text from atom nodes (emoji, smartTag)
|
|
62
|
+
* Used as leafText callback for doc.textBetween()
|
|
63
|
+
*/
|
|
64
|
+
export declare const getTextFromAtomNode: (node: ProseMirrorNode) => string;
|
|
65
|
+
/**
|
|
66
|
+
* Wrapper for doc.textBetween that automatically handles atom nodes (emoji, smartTag)
|
|
67
|
+
* @param state EditorState
|
|
68
|
+
* @param from Start position
|
|
69
|
+
* @param to End position
|
|
70
|
+
* @param blockSeparator Optional separator between blocks (default: '')
|
|
71
|
+
* @returns Text content including atom nodes
|
|
72
|
+
*/
|
|
73
|
+
export declare const textBetween: (state: EditorState, from: number, to: number, blockSeparator?: string) => string;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { LIST_EMOJI } from '../extensions/Emoji';
|
|
1
2
|
/**
|
|
2
3
|
* Default configuration for document state detection
|
|
3
4
|
*/
|
|
@@ -122,3 +123,27 @@ export function isEntireParagraphSelected(editor) {
|
|
|
122
123
|
// Kiểm tra xem selection có bao phủ toàn bộ nội dung của paragraph không
|
|
123
124
|
return from === paragraphStart && to === paragraphEnd;
|
|
124
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Helper function to extract text from atom nodes (emoji, smartTag)
|
|
128
|
+
* Used as leafText callback for doc.textBetween()
|
|
129
|
+
*/
|
|
130
|
+
export const getTextFromAtomNode = (node) => {
|
|
131
|
+
if (node.type.name === 'emoji') {
|
|
132
|
+
// Find emoji character from name
|
|
133
|
+
const emojiChar = LIST_EMOJI.find(i => i.name === node.attrs.name)?.emoji;
|
|
134
|
+
return emojiChar || node.attrs.name || '*';
|
|
135
|
+
}
|
|
136
|
+
if (node.type.name === 'smartTag') {
|
|
137
|
+
return node.attrs.content || '*';
|
|
138
|
+
}
|
|
139
|
+
return '*';
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* Wrapper for doc.textBetween that automatically handles atom nodes (emoji, smartTag)
|
|
143
|
+
* @param state EditorState
|
|
144
|
+
* @param from Start position
|
|
145
|
+
* @param to End position
|
|
146
|
+
* @param blockSeparator Optional separator between blocks (default: '')
|
|
147
|
+
* @returns Text content including atom nodes
|
|
148
|
+
*/
|
|
149
|
+
export const textBetween = (state, from, to, blockSeparator = '') => state.doc.textBetween(from, to, blockSeparator, getTextFromAtomNode);
|
|
@@ -237,6 +237,64 @@ export function cleanLineBreaks(html) {
|
|
|
237
237
|
return html;
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
|
+
/**
|
|
241
|
+
* Swaps the structure when a dynamic tag contains only a single link.
|
|
242
|
+
* Changes from: <span data-dynamic><a>text</a></span>
|
|
243
|
+
* To: <a><span data-dynamic>text</span></a>
|
|
244
|
+
*
|
|
245
|
+
* @param html - HTML string to process
|
|
246
|
+
* @returns Processed HTML string with swapped structure
|
|
247
|
+
*/
|
|
248
|
+
function swapDynamicTagWithLink(html) {
|
|
249
|
+
try {
|
|
250
|
+
const parser = new DOMParser();
|
|
251
|
+
const doc = parser.parseFromString(html, 'text/html');
|
|
252
|
+
// Find all dynamic tag spans
|
|
253
|
+
const dynamicSpans = doc.querySelectorAll('span[data-dynamic="true"]');
|
|
254
|
+
dynamicSpans.forEach(dynamicSpan => {
|
|
255
|
+
// Get all child nodes (elements and text nodes)
|
|
256
|
+
const childNodes = Array.from(dynamicSpan.childNodes);
|
|
257
|
+
// Filter out empty text nodes (whitespace only)
|
|
258
|
+
const nonEmptyNodes = childNodes.filter(node => {
|
|
259
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
260
|
+
return node.textContent?.trim() !== '';
|
|
261
|
+
}
|
|
262
|
+
return true;
|
|
263
|
+
});
|
|
264
|
+
// Check if there's exactly one child and it's an <a> element
|
|
265
|
+
if (nonEmptyNodes.length === 1 && nonEmptyNodes[0].nodeType === Node.ELEMENT_NODE) {
|
|
266
|
+
const childElement = nonEmptyNodes[0];
|
|
267
|
+
if (childElement instanceof HTMLElement && childElement.tagName.toLowerCase() === 'a') {
|
|
268
|
+
const linkElement = childElement;
|
|
269
|
+
// Create new span with dynamic attributes
|
|
270
|
+
const newSpan = doc.createElement('span');
|
|
271
|
+
// Copy all attributes from original dynamic span
|
|
272
|
+
Array.from(dynamicSpan.attributes).forEach(attr => {
|
|
273
|
+
newSpan.setAttribute(attr.name, attr.value);
|
|
274
|
+
});
|
|
275
|
+
// Set the text content from the link
|
|
276
|
+
newSpan.textContent = linkElement.textContent;
|
|
277
|
+
// Create new link element
|
|
278
|
+
const newLink = doc.createElement('a');
|
|
279
|
+
// Copy all attributes from original link
|
|
280
|
+
Array.from(linkElement.attributes).forEach(attr => {
|
|
281
|
+
newLink.setAttribute(attr.name, attr.value);
|
|
282
|
+
});
|
|
283
|
+
// Put span inside link
|
|
284
|
+
newLink.appendChild(newSpan);
|
|
285
|
+
// Replace original dynamic span with the new link
|
|
286
|
+
dynamicSpan.replaceWith(newLink);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
return doc.body.innerHTML;
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
// eslint-disable-next-line no-console
|
|
294
|
+
console.error('Error swapping dynamic tag with link:', error);
|
|
295
|
+
return html; // Return original HTML in case of error
|
|
296
|
+
}
|
|
297
|
+
}
|
|
240
298
|
/**
|
|
241
299
|
* Safely parses and processes HTML content for editor use
|
|
242
300
|
* @param html - HTML string to process
|
|
@@ -260,6 +318,8 @@ export function safeParseHTMLContent(params) {
|
|
|
260
318
|
defaultStyle,
|
|
261
319
|
});
|
|
262
320
|
}
|
|
321
|
+
// Swap dynamic tag structure when it contains only a link
|
|
322
|
+
resultHTML = swapDynamicTagWithLink(resultHTML);
|
|
263
323
|
// console.log('after safeParseHTMLContent', resultHTML);
|
|
264
324
|
return resultHTML;
|
|
265
325
|
}
|
|
@@ -78,7 +78,7 @@ export declare const getActiveLinkAttrsFromRange: (params: {
|
|
|
78
78
|
readonly inactiveLinks: import("../types").LinkAttrs[];
|
|
79
79
|
} | undefined;
|
|
80
80
|
/**
|
|
81
|
-
* Extends selection to include full link marks
|
|
81
|
+
* Extends selection to include full link marks and adjacent links with same href
|
|
82
82
|
* @param state EditorState
|
|
83
83
|
* @param from Start position
|
|
84
84
|
* @param to End position
|
|
@@ -93,6 +93,15 @@ export declare const extendSelectionToFullLinks: (state: EditorState, from: numb
|
|
|
93
93
|
to: number;
|
|
94
94
|
linkAttrs: import("../types").LinkAttrs;
|
|
95
95
|
};
|
|
96
|
+
/**
|
|
97
|
+
* Gets all full link groups in the document (adjacent links with same href are grouped)
|
|
98
|
+
* @param state EditorState
|
|
99
|
+
* @returns Array of link groups with attrs and content
|
|
100
|
+
*/
|
|
101
|
+
export declare const getAllFullLinkGroups: (state: EditorState) => {
|
|
102
|
+
attrs: LinkMarkRange['mark']['attrs'];
|
|
103
|
+
content: string;
|
|
104
|
+
}[];
|
|
96
105
|
/**
|
|
97
106
|
* Handles link actions (create or edit)
|
|
98
107
|
* @param view EditorView instance
|