@antscorp/antsomi-ui 1.3.7-beta.25 → 1.3.7-beta.26
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/organism/TextEditor/TextEditor.js +5 -7
- package/es/components/organism/TextEditor/extensions/ListItem.d.ts +10 -0
- package/es/components/organism/TextEditor/extensions/ListItem.js +93 -0
- package/es/components/organism/TextEditor/styled.js +1 -0
- package/es/components/organism/TextEditor/ui/TextAlignSelect/TextAlignSelect.js +1 -3
- package/es/components/organism/TextEditor/ui/Toolbar/actions/FontSizeAction.js +6 -1
- package/package.json +1 -1
|
@@ -9,7 +9,7 @@ import { Selection, Focus } from '@tiptap/extensions';
|
|
|
9
9
|
import { useEditor } from '@tiptap/react';
|
|
10
10
|
import StarterKit from '@tiptap/starter-kit';
|
|
11
11
|
import clsx from 'clsx';
|
|
12
|
-
import { isBoolean
|
|
12
|
+
import { isBoolean } from 'lodash';
|
|
13
13
|
import { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, } from 'react';
|
|
14
14
|
import { useDebouncedCallback } from 'use-debounce';
|
|
15
15
|
import { DEFAULT_TEXT_STYLE } from './constants';
|
|
@@ -23,6 +23,7 @@ import { Indent } from './extensions/Indent';
|
|
|
23
23
|
import { LetterSpacing } from './extensions/LetterSpacing';
|
|
24
24
|
import { LineHeight } from './extensions/LineHeight';
|
|
25
25
|
import { CustomLink } from './extensions/Link';
|
|
26
|
+
import { CustomListItem } from './extensions/ListItem';
|
|
26
27
|
import { CustomOrderedList } from './extensions/OrderedList';
|
|
27
28
|
import { SmartTag } from './extensions/SmartTag';
|
|
28
29
|
import { TextTransform } from './extensions/TextTransform';
|
|
@@ -104,15 +105,12 @@ const TextEditorInternal = memo(forwardRef((props, ref) => {
|
|
|
104
105
|
link: false,
|
|
105
106
|
bulletList: false,
|
|
106
107
|
orderedList: false,
|
|
107
|
-
listItem:
|
|
108
|
-
HTMLAttributes: {
|
|
109
|
-
style: 'line-height:normal;',
|
|
110
|
-
},
|
|
111
|
-
},
|
|
108
|
+
listItem: false,
|
|
112
109
|
undoRedo: {
|
|
113
110
|
depth: 100,
|
|
114
111
|
},
|
|
115
112
|
}),
|
|
113
|
+
CustomListItem,
|
|
116
114
|
CustomUnorderedList,
|
|
117
115
|
CustomOrderedList,
|
|
118
116
|
FontWeight.configure({
|
|
@@ -307,7 +305,7 @@ const TextEditorInternal = memo(forwardRef((props, ref) => {
|
|
|
307
305
|
}
|
|
308
306
|
return (_jsxs(_Fragment, { children: [_jsx(StyledEditorContent, { className: clsx(`${ANTSOMI_COMPONENT_PREFIX_CLS}-text-editor`, className), "$textStyle": defaultTextStyle, style: {
|
|
309
307
|
// Inline styles apply to inner html elements (support email client)
|
|
310
|
-
...
|
|
308
|
+
...defaultTextStyle,
|
|
311
309
|
...style,
|
|
312
310
|
}, id: id, ref: contentRef, editor: editor, ...dataAttributes }), _jsx(LinkPopover, { editor: editor }), !linkFormVisible && (_jsxs(_Fragment, { children: [_jsx(BubbleMenu, { pluginKey: "linkPreviewBubbleMenu", ...bubbleMenuProps, appendTo: bubbleMenuContainer.current, editor: editor, shouldShow: shouldShowLinkPreview, children: _jsx(LinkPreviewToolbar, { editor: editor, linkHanlder: {
|
|
313
311
|
onUpsert: () => {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom ListItem extension that extends the default TipTap ListItem
|
|
3
|
+
* with custom Backspace behavior to prevent merging paragraphs into lists
|
|
4
|
+
*
|
|
5
|
+
* This extension only overrides keyboard shortcuts while maintaining
|
|
6
|
+
* all other default ListItem functionality (HTML parsing, rendering, etc.)
|
|
7
|
+
*
|
|
8
|
+
* @see https://www.tiptap.dev/api/nodes/list-item
|
|
9
|
+
*/
|
|
10
|
+
export declare const CustomListItem: import("@tiptap/core").Node<import("@tiptap/extension-list").ListItemOptions, any>;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { ListItem } from '@tiptap/extension-list-item';
|
|
2
|
+
/**
|
|
3
|
+
* Custom ListItem extension that extends the default TipTap ListItem
|
|
4
|
+
* with custom Backspace behavior to prevent merging paragraphs into lists
|
|
5
|
+
*
|
|
6
|
+
* This extension only overrides keyboard shortcuts while maintaining
|
|
7
|
+
* all other default ListItem functionality (HTML parsing, rendering, etc.)
|
|
8
|
+
*
|
|
9
|
+
* @see https://www.tiptap.dev/api/nodes/list-item
|
|
10
|
+
*/
|
|
11
|
+
export const CustomListItem = ListItem.extend({
|
|
12
|
+
addOptions() {
|
|
13
|
+
return {
|
|
14
|
+
HTMLAttributes: {
|
|
15
|
+
style: 'line-height:normal;',
|
|
16
|
+
},
|
|
17
|
+
bulletListTypeName: 'customUnorderedList',
|
|
18
|
+
orderedListTypeName: 'customOrderedList',
|
|
19
|
+
};
|
|
20
|
+
},
|
|
21
|
+
addKeyboardShortcuts() {
|
|
22
|
+
return {
|
|
23
|
+
// Keep default keyboard shortcuts from parent ListItem
|
|
24
|
+
Enter: () => this.editor.commands.splitListItem(this.name),
|
|
25
|
+
Tab: () => this.editor.commands.sinkListItem(this.name),
|
|
26
|
+
'Shift-Tab': () => this.editor.commands.liftListItem(this.name),
|
|
27
|
+
/**
|
|
28
|
+
* Custom Backspace handler to prevent merging empty paragraphs into lists
|
|
29
|
+
*
|
|
30
|
+
* Behavior:
|
|
31
|
+
* 1. Check if cursor is at the start of a paragraph
|
|
32
|
+
* 2. Check if the node before is a list (ordered or unordered)
|
|
33
|
+
* 3. If both conditions are true and paragraph is empty, delete the paragraph
|
|
34
|
+
* 4. Otherwise, use default backspace behavior
|
|
35
|
+
*/
|
|
36
|
+
Backspace: () => {
|
|
37
|
+
const { state } = this.editor;
|
|
38
|
+
const { selection } = state;
|
|
39
|
+
const { $from, empty } = selection;
|
|
40
|
+
// Only handle if selection is empty (cursor position, not text selection)
|
|
41
|
+
if (!empty) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
// Check if cursor is at the start of current node
|
|
45
|
+
const isAtStart = $from.parentOffset === 0;
|
|
46
|
+
if (!isAtStart) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
// Get current node (should be a paragraph)
|
|
50
|
+
const currentNode = $from.parent;
|
|
51
|
+
// Only handle paragraphs
|
|
52
|
+
if (currentNode.type.name !== 'paragraph') {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
// Get the index of current paragraph in its parent (usually doc)
|
|
56
|
+
const paragraphDepth = $from.depth;
|
|
57
|
+
const indexInParent = $from.index(paragraphDepth - 1);
|
|
58
|
+
// Check if there's a node before (index > 0)
|
|
59
|
+
if (indexInParent === 0) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
// Get parent node (usually doc) and the node before current paragraph
|
|
63
|
+
const parent = $from.node(paragraphDepth - 1);
|
|
64
|
+
const nodeBefore = parent.child(indexInParent - 1);
|
|
65
|
+
if (!nodeBefore) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
// Check if the node before is a list (ordered or unordered)
|
|
69
|
+
const isListBefore = nodeBefore.type.name === this.options.orderedListTypeName ||
|
|
70
|
+
nodeBefore.type.name === this.options.bulletListTypeName;
|
|
71
|
+
if (!isListBefore) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
// Check if current paragraph is empty
|
|
75
|
+
const isParagraphEmpty = currentNode.content.size === 0;
|
|
76
|
+
if (!isParagraphEmpty) {
|
|
77
|
+
// If paragraph has content, allow default backspace to handle
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
// Calculate the exact positions of the paragraph node
|
|
81
|
+
// We need to delete from the position before the paragraph open tag
|
|
82
|
+
// to the position after the paragraph close tag
|
|
83
|
+
const paragraphPos = $from.before();
|
|
84
|
+
const paragraphEndPos = $from.after();
|
|
85
|
+
// Delete the entire paragraph node using deleteRange
|
|
86
|
+
return this.editor.commands.deleteRange({
|
|
87
|
+
from: paragraphPos,
|
|
88
|
+
to: paragraphEndPos,
|
|
89
|
+
});
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
},
|
|
93
|
+
});
|
|
@@ -12,6 +12,7 @@ export const StyledEditorContent = styled(EditorContent) `
|
|
|
12
12
|
|
|
13
13
|
// Some apps have global p styles, so we need to override it
|
|
14
14
|
|
|
15
|
+
font-size: ${p => p.$textStyle.fontSize};
|
|
15
16
|
line-height: ${p => p.$textStyle.lineHeight};
|
|
16
17
|
font-family: ${p => p.$textStyle.fontFamily};
|
|
17
18
|
color: ${p => p.$textStyle.color};
|
|
@@ -52,9 +52,7 @@ export const TextAlignSelect = (props) => {
|
|
|
52
52
|
editor: editor,
|
|
53
53
|
selector: ({ editor: editorInstance }) => {
|
|
54
54
|
const alignment = ['left', 'center', 'right', 'justify'].find(alignment => editorInstance?.isActive({ textAlign: alignment }));
|
|
55
|
-
return {
|
|
56
|
-
currentAlignment: alignment || 'left',
|
|
57
|
-
};
|
|
55
|
+
return { currentAlignment: alignment };
|
|
58
56
|
},
|
|
59
57
|
});
|
|
60
58
|
const selectedOption = useMemo(() => textAlignOptions.find(option => option.value === currentAlignment), [currentAlignment]);
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { useTextEditorStore } from '../../../provider';
|
|
3
3
|
import { FontSizeInput, } from '@antscorp/antsomi-ui/es/components/molecules';
|
|
4
4
|
import styled from 'styled-components';
|
|
5
|
-
import { memo, useCallback, useMemo, useRef, useState } from 'react';
|
|
5
|
+
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
6
6
|
import { useThrottledCallback } from 'use-debounce';
|
|
7
7
|
import { useEditorState } from '@tiptap/react';
|
|
8
8
|
import { toString } from 'lodash';
|
|
@@ -30,6 +30,11 @@ export const FontSizeAction = memo(({ editor, throttled = 100, defaultFontSize =
|
|
|
30
30
|
},
|
|
31
31
|
});
|
|
32
32
|
const [currentFontSize, setCurrentFontSize] = useState(numberFontSize);
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (numberFontSize) {
|
|
35
|
+
setCurrentFontSize(numberFontSize);
|
|
36
|
+
}
|
|
37
|
+
}, [numberFontSize]);
|
|
33
38
|
const updateEditorFontSize = useThrottledCallback((v) => {
|
|
34
39
|
if (v === numberFontSize)
|
|
35
40
|
return;
|