@blocklet/editor 2.3.99 → 2.3.101
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/lib/config.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
|
+
import { SxProps } from '@mui/material';
|
|
2
3
|
import { AI, UserService } from './types';
|
|
3
4
|
export type ToolbarConfigItem = 'component' | 'block' | 'align' | 'font-family' | 'font-size' | 'bold' | 'italic' | 'underline' | 'code' | 'link' | 'image' | 'color' | 'bgcolor' | 'strikethrough' | 'media' | 'alert';
|
|
4
5
|
export interface ToolbarConfig {
|
|
@@ -9,6 +10,11 @@ export interface Uploader {
|
|
|
9
10
|
url: string;
|
|
10
11
|
}>;
|
|
11
12
|
}
|
|
13
|
+
export interface CharacterLimitConfig {
|
|
14
|
+
maxLength?: number;
|
|
15
|
+
indicatorStyle?: SxProps;
|
|
16
|
+
alignLeft?: boolean;
|
|
17
|
+
}
|
|
12
18
|
export interface EditorConfig {
|
|
13
19
|
toolbar?: ToolbarConfig;
|
|
14
20
|
uploader?: Uploader;
|
|
@@ -19,6 +25,7 @@ export interface EditorConfig {
|
|
|
19
25
|
templatePlugin?: any;
|
|
20
26
|
minimalMode?: boolean;
|
|
21
27
|
openLinkInNewTab?: boolean;
|
|
28
|
+
characterLimitConfig?: CharacterLimitConfig;
|
|
22
29
|
}
|
|
23
30
|
export declare function EditorConfigProvider({ children, value }: {
|
|
24
31
|
children: React.ReactNode;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { SxProps } from '@mui/material';
|
|
3
|
+
interface CharacterLimitPluginProps {
|
|
4
|
+
maxLength?: number;
|
|
5
|
+
charLimitIndicatorStyle?: SxProps;
|
|
6
|
+
alignLeft?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function CharacterLimitPlugin({ maxLength, charLimitIndicatorStyle, alignLeft, }: CharacterLimitPluginProps): JSX.Element | null;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
3
|
+
import { CharacterLimitPlugin as LexicalCharacterLimitPlugin } from '@lexical/react/LexicalCharacterLimitPlugin';
|
|
4
|
+
import { Box } from '@mui/material';
|
|
5
|
+
import { MaxLengthPlugin } from '../../main/plugins/MaxLengthPlugin';
|
|
6
|
+
export function CharacterLimitPlugin({ maxLength, charLimitIndicatorStyle, alignLeft = false, }) {
|
|
7
|
+
const [editor] = useLexicalComposerContext();
|
|
8
|
+
if (!editor.isEditable()) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
if (!editor.isEditable() || !maxLength) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
const mergedSx = Array.isArray(charLimitIndicatorStyle) ? charLimitIndicatorStyle : [charLimitIndicatorStyle];
|
|
15
|
+
if (alignLeft) {
|
|
16
|
+
const left = editor.getRootElement()?.getBoundingClientRect()?.left ?? 0;
|
|
17
|
+
mergedSx.push({ left: left + 24 });
|
|
18
|
+
}
|
|
19
|
+
return (_jsxs(_Fragment, { children: [_jsx(MaxLengthPlugin, { maxLength: maxLength }), _jsx(LexicalCharacterLimitPlugin, { charset: "UTF-16", maxLength: maxLength, renderer: ({ remainingCharacters }) => {
|
|
20
|
+
return (_jsx(CharLimitIndicator, { currentCount: maxLength - remainingCharacters, maxLength: maxLength, sx: mergedSx }));
|
|
21
|
+
} })] }));
|
|
22
|
+
}
|
|
23
|
+
function CharLimitIndicator({ currentCount, maxLength, sx }) {
|
|
24
|
+
const percentage = (currentCount / maxLength) * 100;
|
|
25
|
+
const isNearLimit = percentage >= 80;
|
|
26
|
+
const isOverLimit = currentCount > maxLength;
|
|
27
|
+
// Calculate the circumference for the circular progress
|
|
28
|
+
const radius = 8;
|
|
29
|
+
const circumference = 2 * Math.PI * radius;
|
|
30
|
+
const strokeDashoffset = circumference - (percentage / 100) * circumference;
|
|
31
|
+
if (percentage === 0) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const mergedSx = [
|
|
35
|
+
{ display: 'flex', alignItems: 'center', position: 'absolute', bottom: 6, left: 32 },
|
|
36
|
+
...(Array.isArray(sx) ? sx : [sx]),
|
|
37
|
+
];
|
|
38
|
+
return (_jsxs(Box, { sx: mergedSx, children: [_jsx(Box, { sx: { position: 'relative', width: 20, height: 20 }, children: _jsxs(Box, { component: "svg", sx: { width: 20, height: 20, transform: 'rotate(-90deg)' }, viewBox: "0 0 20 20", children: [_jsx(Box, { component: "circle", cx: "10", cy: "10", r: radius, stroke: "currentColor", strokeWidth: "2", fill: "none", sx: { opacity: 0.2 } }), _jsx(Box, { component: "circle", cx: "10", cy: "10", r: radius, stroke: "currentColor", strokeWidth: "2", fill: "none", strokeDasharray: circumference, strokeDashoffset: strokeDashoffset, strokeLinecap: "round", sx: {
|
|
39
|
+
transition: 'all 0.3s ease-out',
|
|
40
|
+
color: isOverLimit ? 'error.main' : isNearLimit ? 'warning.main' : 'primary.main',
|
|
41
|
+
} })] }) }), _jsxs(Box, { component: "span", sx: {
|
|
42
|
+
display: 'inline-block',
|
|
43
|
+
ml: 0.5,
|
|
44
|
+
fontSize: 13,
|
|
45
|
+
fontWeight: 500,
|
|
46
|
+
transition: 'all 0.3s ease-out',
|
|
47
|
+
color: isOverLimit ? 'error.main' : isNearLimit ? 'warning.main' : 'primary.main',
|
|
48
|
+
}, children: [currentCount.toLocaleString(), "/", maxLength.toLocaleString()] })] }));
|
|
49
|
+
}
|
package/lib/main/editor.js
CHANGED
|
@@ -8,7 +8,6 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
8
8
|
*/
|
|
9
9
|
import { cx } from '@emotion/css';
|
|
10
10
|
import { AutoFocusPlugin } from '@lexical/react/LexicalAutoFocusPlugin';
|
|
11
|
-
import { CharacterLimitPlugin } from '@lexical/react/LexicalCharacterLimitPlugin';
|
|
12
11
|
import { CheckListPlugin } from '@lexical/react/LexicalCheckListPlugin';
|
|
13
12
|
import { ClearEditorPlugin } from '@lexical/react/LexicalClearEditorPlugin';
|
|
14
13
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
@@ -45,7 +44,7 @@ import HorizontalRulePlugin from './plugins/HorizontalRulePlugin';
|
|
|
45
44
|
import ImagesPlugin from './plugins/ImagesPlugin';
|
|
46
45
|
import ListMaxIndentLevelPlugin from './plugins/ListMaxIndentLevelPlugin';
|
|
47
46
|
import MarkdownShortcutPlugin from './plugins/MarkdownShortcutPlugin';
|
|
48
|
-
import {
|
|
47
|
+
import { CharacterLimitPlugin } from '../ext/CharacterLimitPlugin';
|
|
49
48
|
import MentionsPlugin from './plugins/MentionsPlugin';
|
|
50
49
|
import TabFocusPlugin from './plugins/TabFocusPlugin';
|
|
51
50
|
import TableCellActionMenuPlugin from './plugins/TableActionMenuPlugin';
|
|
@@ -83,7 +82,7 @@ export default function Editor({ children, prepend, placeholder, onChange, autoF
|
|
|
83
82
|
const [editor] = useLexicalComposerContext();
|
|
84
83
|
const [editable, setEditable] = useState(false);
|
|
85
84
|
const config = useEditorConfig();
|
|
86
|
-
const { minimalMode } = config;
|
|
85
|
+
const { minimalMode, characterLimitConfig } = config;
|
|
87
86
|
const hasUploader = !!config.uploader;
|
|
88
87
|
useEffect(() => {
|
|
89
88
|
setEditable(editor.isEditable());
|
|
@@ -95,7 +94,7 @@ export default function Editor({ children, prepend, placeholder, onChange, autoF
|
|
|
95
94
|
useTranslationListener();
|
|
96
95
|
const hasNodes = useHasNodes();
|
|
97
96
|
const { historyState } = useSharedHistoryContext();
|
|
98
|
-
const { settings: {
|
|
97
|
+
const { settings: { isRichText, showTableOfContents }, } = useSettings();
|
|
99
98
|
const [floatingAnchorElem, setFloatingAnchorElem] = useState(null);
|
|
100
99
|
const onRef = (_floatingAnchorElem) => {
|
|
101
100
|
if (_floatingAnchorElem !== null) {
|
|
@@ -108,7 +107,7 @@ export default function Editor({ children, prepend, placeholder, onChange, autoF
|
|
|
108
107
|
}
|
|
109
108
|
}, [hasNodes('image'), hasUploader]);
|
|
110
109
|
if (minimalMode) {
|
|
111
|
-
return (_jsxs(_Fragment, { children: [prepend, isRichText && editable && showToolbar && _jsx(ToolbarPlugin, {}), hasNodes('image') && hasUploader && _jsx(DragDropPaste, {}), autoFocus && _jsx(AutoFocusPlugin, { defaultSelection: "rootEnd" }), _jsx(ClearEditorPlugin, {}), !!editable && _jsx(ComponentPickerPlugin, {}), !!editable && _jsx(MentionsPlugin, {}), hasNodes('link') && _jsx(AutoEmbedPlugin, {}), _jsx(PasteSlackImagePlugin, {}), _jsx(StyledEditorContent, { ref: onRef, className: cx('be-content', editable && 'editable'), children: _jsx(RichTextPlugin, { contentEditable: _jsx(ContentEditable, { className: cx('be-editable', 'notranslate') }), placeholder: _jsx(Placeholder, { className: "be-placeholder", children: placeholder }), ErrorBoundary: LexicalErrorBoundary }) }), hasNodes('code', 'code-highlight') && _jsx(CodeHighlightPlugin, {}), hasNodes('image') && hasUploader && _jsx(ImagesPlugin, {}), hasNodes('video') && _jsx(VideoPlugin, {}), hasNodes('link') && _jsx(LinkPlugin, {}), hasNodes('tweet') && _jsx(TwitterPlugin, {}), hasNodes('youtube') && _jsx(YouTubePlugin, {}), hasNodes('figma') && _jsx(FigmaPlugin, {}), _jsx(PostLinkEmbedPlugin, {}), _jsx(BookmarkPlugin, {}), editable && _jsx(CustomOnChangePlugin, { placeholder: placeholder }), editable && _jsx(TemplatePlugin, {}), !editable && _jsx(BlurTextPlugin, {}), hasNodes('pdf') && _jsx(PdfPlugin, {}), hasNodes('file') && _jsx(FilePlugin, {}), hasNodes('horizontalrule') && _jsx(HorizontalRulePlugin, {}), hasNodes('excalidraw') && _jsx(ExcalidrawPlugin, {}), hasNodes('alert') && _jsx(AlertPlugin, {}), hasNodes('pages-kit-component') && _jsx(PagesKitComponentPlugin, {}), _jsx(TabFocusPlugin, {}), onChange && _jsx(OnChangePlugin, { onChange: onChange }), floatingAnchorElem && editable && (_jsxs(_Fragment, { children: [hasNodes('code') && _jsx(CodeActionMenuPlugin, { anchorElem: floatingAnchorElem }), hasNodes('link') && _jsx(FloatingLinkEditorPlugin, { anchorElem: floatingAnchorElem })] })), editorRef && _jsx(EditorRefPlugin, { editorRef: editorRef }), children] }));
|
|
110
|
+
return (_jsxs(_Fragment, { children: [prepend, isRichText && editable && showToolbar && _jsx(ToolbarPlugin, {}), hasNodes('image') && hasUploader && _jsx(DragDropPaste, {}), autoFocus && _jsx(AutoFocusPlugin, { defaultSelection: "rootEnd" }), _jsx(ClearEditorPlugin, {}), !!editable && _jsx(ComponentPickerPlugin, {}), !!editable && _jsx(MentionsPlugin, {}), hasNodes('link') && _jsx(AutoEmbedPlugin, {}), _jsx(PasteSlackImagePlugin, {}), _jsx(StyledEditorContent, { ref: onRef, className: cx('be-content', editable && 'editable'), children: _jsx(RichTextPlugin, { contentEditable: _jsx(ContentEditable, { className: cx('be-editable', 'notranslate') }), placeholder: _jsx(Placeholder, { className: "be-placeholder", children: placeholder }), ErrorBoundary: LexicalErrorBoundary }) }), hasNodes('code', 'code-highlight') && _jsx(CodeHighlightPlugin, {}), hasNodes('image') && hasUploader && _jsx(ImagesPlugin, {}), hasNodes('video') && _jsx(VideoPlugin, {}), hasNodes('link') && _jsx(LinkPlugin, {}), hasNodes('tweet') && _jsx(TwitterPlugin, {}), hasNodes('youtube') && _jsx(YouTubePlugin, {}), hasNodes('figma') && _jsx(FigmaPlugin, {}), _jsx(PostLinkEmbedPlugin, {}), _jsx(BookmarkPlugin, {}), editable && _jsx(CustomOnChangePlugin, { placeholder: placeholder }), editable && _jsx(TemplatePlugin, {}), !editable && _jsx(BlurTextPlugin, {}), hasNodes('pdf') && _jsx(PdfPlugin, {}), hasNodes('file') && _jsx(FilePlugin, {}), hasNodes('horizontalrule') && _jsx(HorizontalRulePlugin, {}), hasNodes('excalidraw') && _jsx(ExcalidrawPlugin, {}), hasNodes('alert') && _jsx(AlertPlugin, {}), hasNodes('pages-kit-component') && _jsx(PagesKitComponentPlugin, {}), _jsx(TabFocusPlugin, {}), onChange && _jsx(OnChangePlugin, { onChange: onChange }), floatingAnchorElem && editable && (_jsxs(_Fragment, { children: [hasNodes('code') && _jsx(CodeActionMenuPlugin, { anchorElem: floatingAnchorElem }), hasNodes('link') && _jsx(FloatingLinkEditorPlugin, { anchorElem: floatingAnchorElem })] })), !!characterLimitConfig?.maxLength && (_jsx(CharacterLimitPlugin, { maxLength: characterLimitConfig.maxLength, charLimitIndicatorStyle: characterLimitConfig.indicatorStyle, alignLeft: characterLimitConfig.alignLeft })), editorRef && _jsx(EditorRefPlugin, { editorRef: editorRef }), children] }));
|
|
112
111
|
}
|
|
113
|
-
return (_jsxs(_Fragment, { children: [prepend, isRichText && editable && showToolbar && _jsx(ToolbarPlugin, {}),
|
|
112
|
+
return (_jsxs(_Fragment, { children: [prepend, isRichText && editable && showToolbar && _jsx(ToolbarPlugin, {}), !!characterLimitConfig?.maxLength && (_jsx(CharacterLimitPlugin, { maxLength: characterLimitConfig.maxLength, charLimitIndicatorStyle: characterLimitConfig.indicatorStyle, alignLeft: characterLimitConfig.alignLeft })), hasNodes('image') && hasUploader && _jsx(DragDropPaste, {}), autoFocus && _jsx(AutoFocusPlugin, { defaultSelection: "rootEnd" }), _jsx(ClearEditorPlugin, {}), !!editable && _jsx(ComponentPickerPlugin, {}), !!editable && _jsx(EmojiPickerPlugin, {}), hasNodes('link') && _jsx(AutoEmbedPlugin, {}), !!editable && _jsx(MentionsPlugin, {}), hasNodes('emoji') && _jsx(EmojisPlugin, {}), hasNodes('hashtag') && _jsx(HashtagPlugin, {}), hasNodes('autolink') && !!editable && _jsx(AutoLinkPlugin, {}), isRichText ? (_jsxs(_Fragment, { children: [_jsx(PasteSlackImagePlugin, {}), _jsx(HistoryPlugin, { externalHistoryState: historyState }), _jsx(StyledEditorContent, { ref: onRef, className: cx('be-content', editable && 'editable'), children: _jsx(RichTextPlugin, { contentEditable: _jsx(ContentEditable, { className: cx('be-editable', 'notranslate') }), placeholder: _jsx(Placeholder, { className: "be-placeholder", children: placeholder }), ErrorBoundary: LexicalErrorBoundary }) }), _jsx(MarkdownShortcutPlugin, {}), _jsx(TabIndentationPlugin, {}), hasNodes('code', 'code-highlight') && _jsx(CodeHighlightPlugin, {}), hasNodes('list', 'listitem') && _jsx(ListPlugin, {}), hasNodes('list', 'listitem') && _jsx(CheckListPlugin, {}), hasNodes('list', 'listitem') && _jsx(ListMaxIndentLevelPlugin, { maxDepth: 7 }), hasNodes('table', 'tablerow', 'tablecell') && editable && (_jsx(TablePlugin, { hasCellMerge: true, hasCellBackgroundColor: true, hasHorizontalScroll: true })), hasNodes('table', 'tablerow', 'tablecell') && _jsx(TableCellResizer, {}), hasNodes('image') && hasUploader && _jsx(ImagesPlugin, {}), hasNodes('video') && _jsx(VideoPlugin, {}), hasNodes('link') && _jsx(LinkPlugin, {}), hasNodes('tweet') && _jsx(TwitterPlugin, {}), hasNodes('youtube') && _jsx(YouTubePlugin, {}), hasNodes('figma') && _jsx(FigmaPlugin, {}), _jsx(BilibiliPlugin, {}), _jsx(BlockletEmbedPlugin, {}), _jsx(PostLinkEmbedPlugin, {}), _jsx(BookmarkPlugin, {}), _jsx(AidePlugin, {}), editable && _jsx(CustomOnChangePlugin, { placeholder: placeholder }), editable && _jsx(TemplatePlugin, {}), !editable && _jsx(BlurTextPlugin, {}), hasNodes('pdf') && _jsx(PdfPlugin, {}), hasNodes('file') && _jsx(FilePlugin, {}), hasNodes('link') && _jsx(ClickableLinkPlugin, {}), hasNodes('horizontalrule') && _jsx(HorizontalRulePlugin, {}), hasNodes('excalidraw') && _jsx(ExcalidrawPlugin, {}), hasNodes('alert') && _jsx(AlertPlugin, {}), hasNodes('pages-kit-component') && _jsx(PagesKitComponentPlugin, {}), _jsx(TabFocusPlugin, {}), hasNodes('collapsible-container', 'collapsible-content', 'collapsible-title') && _jsx(CollapsiblePlugin, {}), onChange && _jsx(OnChangePlugin, { onChange: onChange }), floatingAnchorElem && editable && (_jsxs(_Fragment, { children: [_jsx(DraggableBlockPlugin, { anchorElem: floatingAnchorElem }), hasNodes('code') && _jsx(CodeActionMenuPlugin, { anchorElem: floatingAnchorElem }), hasNodes('link') && _jsx(FloatingLinkEditorPlugin, { anchorElem: floatingAnchorElem }), hasNodes('table') && _jsx(TableCellActionMenuPlugin, { anchorElem: floatingAnchorElem, cellMerge: true }), _jsx(FloatingTextFormatToolbarPlugin, { anchorElem: floatingAnchorElem })] })), enableHeadingsIdPlugin && _jsx(HeadingsIdPlugin, {})] })) : (_jsxs(_Fragment, { children: [_jsx(PlainTextPlugin, { contentEditable: _jsx(ContentEditable, {}), placeholder: _jsx(Placeholder, { children: "placeholder" }), ErrorBoundary: LexicalErrorBoundary }), _jsx(HistoryPlugin, { externalHistoryState: historyState })] })), _jsx("div", { children: showTableOfContents && _jsx(TableOfContentsPlugin, {}) }), _jsx(MarkdownHeadTextPlugin, {}), _jsx(EditorHolderPlugin, {}), editorRef && _jsx(EditorRefPlugin, { editorRef: editorRef }), onReady && _jsx(EditorReadyPlugin, { onReady: onReady }), children] }));
|
|
114
113
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
*/
|
|
8
8
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
9
|
-
import { trimTextContentFromAnchor } from '@lexical/selection';
|
|
9
|
+
import { $trimTextContentFromAnchor } from '@lexical/selection';
|
|
10
10
|
import { $restoreEditorState } from '@lexical/utils';
|
|
11
11
|
import { $getSelection, $isRangeSelection, RootNode } from 'lexical';
|
|
12
12
|
import { useEffect } from 'react';
|
|
@@ -20,21 +20,20 @@ export function MaxLengthPlugin({ maxLength }) {
|
|
|
20
20
|
return;
|
|
21
21
|
}
|
|
22
22
|
const prevEditorState = editor.getEditorState();
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
if (
|
|
26
|
-
const
|
|
27
|
-
const delCount = textLength - maxLength;
|
|
23
|
+
const prevTextContentSize = prevEditorState.read(() => rootNode.getTextContentSize());
|
|
24
|
+
const textContentSize = rootNode.getTextContentSize();
|
|
25
|
+
if (prevTextContentSize !== textContentSize) {
|
|
26
|
+
const delCount = textContentSize - maxLength;
|
|
28
27
|
const { anchor } = selection;
|
|
29
28
|
if (delCount > 0) {
|
|
30
29
|
// Restore the old editor state instead if the last
|
|
31
30
|
// text content was already at the limit.
|
|
32
|
-
if (
|
|
31
|
+
if (prevTextContentSize === maxLength && lastRestoredEditorState !== prevEditorState) {
|
|
33
32
|
lastRestoredEditorState = prevEditorState;
|
|
34
33
|
$restoreEditorState(editor, prevEditorState);
|
|
35
34
|
}
|
|
36
35
|
else {
|
|
37
|
-
trimTextContentFromAnchor(editor, anchor, delCount);
|
|
36
|
+
$trimTextContentFromAnchor(editor, anchor, delCount);
|
|
38
37
|
}
|
|
39
38
|
}
|
|
40
39
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blocklet/editor",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.101",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"ufo": "^1.5.4",
|
|
69
69
|
"url-join": "^4.0.1",
|
|
70
70
|
"zustand": "^4.5.5",
|
|
71
|
-
"@blocklet/pdf": "^2.3.
|
|
71
|
+
"@blocklet/pdf": "^2.3.101"
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|
|
74
74
|
"@babel/core": "^7.25.2",
|