@blocklet/editor 2.4.15 → 2.4.17

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.
@@ -0,0 +1,2 @@
1
+ import './style.css';
2
+ export declare function CodeCopyPlugin(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,90 @@
1
+ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { CodeNode } from '@lexical/code';
3
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
4
+ import { Box, IconButton } from '@mui/material';
5
+ import { $getNodeByKey } from 'lexical';
6
+ import { useEffect, useMemo, useState } from 'react';
7
+ import { Icon } from '@iconify/react';
8
+ import { usePopper } from 'react-popper';
9
+ import './style.css';
10
+ function isPointInsideElement(point, element) {
11
+ if (!element) {
12
+ return false;
13
+ }
14
+ const rect = element.getBoundingClientRect();
15
+ return point.x > rect.left && point.x < rect.right && point.y > rect.top && point.y < rect.bottom;
16
+ }
17
+ export function CodeCopyPlugin() {
18
+ const [editor] = useLexicalComposerContext();
19
+ const [codeNodes, setCodeNodes] = useState([]);
20
+ useEffect(() => {
21
+ return editor.registerMutationListener(CodeNode, (mutatedNodes) => {
22
+ const nodes = Array.from(mutatedNodes.keys())
23
+ .map((key) => $getNodeByKey(key, editor.getEditorState()))
24
+ .filter(Boolean);
25
+ setCodeNodes(nodes);
26
+ }, { skipInitialization: false });
27
+ }, [editor]);
28
+ return (_jsx(_Fragment, { children: codeNodes.map((codeNode) => (_jsx(CodeCopyButton, { editor: editor, codeNode: codeNode }, codeNode.getKey()))) }));
29
+ }
30
+ function CodeCopyButton({ editor, codeNode }) {
31
+ const [isCopied, setIsCopied] = useState(false);
32
+ const [buttonElement, setButtonElement] = useState(null);
33
+ const codeElement = useMemo(() => editor.getElementByKey(codeNode.getKey()), [editor, codeNode]);
34
+ const [isActivated, setIsActivated] = useState(false);
35
+ const popper = usePopper(codeElement, buttonElement, {
36
+ placement: 'right-start',
37
+ modifiers: [
38
+ { name: 'flip', enabled: false },
39
+ {
40
+ name: 'offset',
41
+ options: {
42
+ offset: [8, -36],
43
+ },
44
+ },
45
+ ],
46
+ });
47
+ useEffect(() => {
48
+ const handleMouseEnter = () => setIsActivated(true);
49
+ const handleMouseLeave = (e) => {
50
+ if (!isPointInsideElement(e, codeElement)) {
51
+ setIsActivated(false);
52
+ }
53
+ };
54
+ if (codeElement) {
55
+ codeElement.addEventListener('mouseenter', handleMouseEnter);
56
+ codeElement.addEventListener('mouseleave', handleMouseLeave);
57
+ return () => {
58
+ codeElement.removeEventListener('mouseenter', handleMouseEnter);
59
+ codeElement.removeEventListener('mouseleave', handleMouseLeave);
60
+ };
61
+ }
62
+ }, [codeElement]);
63
+ if (!isActivated && !isCopied) {
64
+ return null;
65
+ }
66
+ return (_jsx(IconButton, { ref: setButtonElement, style: popper.styles.popper, ...popper.attributes.popper, sx: {
67
+ color: 'text.secondary',
68
+ fontSize: 18,
69
+ bgcolor: 'background.paper',
70
+ '&:hover': {
71
+ color: 'text.primary',
72
+ bgcolor: 'background.paper',
73
+ },
74
+ }, onClick: async () => {
75
+ let content = '';
76
+ editor.update(() => {
77
+ content = codeNode.getTextContent();
78
+ });
79
+ try {
80
+ await navigator.clipboard.writeText(content);
81
+ setIsCopied(true);
82
+ setTimeout(() => {
83
+ setIsCopied(false);
84
+ }, 1500);
85
+ }
86
+ catch (err) {
87
+ console.error('Failed to copy: ', err);
88
+ }
89
+ }, children: isCopied ? (_jsx(Box, { component: Icon, icon: "tabler:check", sx: { color: 'success.main' } })) : (_jsx(Icon, { icon: "tabler:copy" })) }));
90
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Tabler Icons by Paweł Kuna - https://github.com/tabler/tabler-icons/blob/master/LICENSE --><g fill="none" stroke="#888888" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M7 9.667A2.667 2.667 0 0 1 9.667 7h8.666A2.667 2.667 0 0 1 21 9.667v8.666A2.667 2.667 0 0 1 18.333 21H9.667A2.667 2.667 0 0 1 7 18.333z"/><path d="M4.012 16.737A2 2 0 0 1 3 15V5c0-1.1.9-2 2-2h10c.75 0 1.158.385 1.5 1"/></g></svg>
@@ -0,0 +1 @@
1
+ export * from './CodeCopyPlugin';
@@ -0,0 +1 @@
1
+ export * from './CodeCopyPlugin';
@@ -0,0 +1,12 @@
1
+ .be-editable > code:after {
2
+ position: absolute;
3
+ right: 12px;
4
+ top: 12px;
5
+
6
+ display: block;
7
+ content: ' ';
8
+ background-image: url('./copy.svg');
9
+ background-size: 18px 18px;
10
+ height: 18px;
11
+ width: 18px;
12
+ }
@@ -28,7 +28,6 @@ import useHasNodes from './hooks/useHasNodes';
28
28
  import AutoEmbedPlugin from './plugins/AutoEmbedPlugin';
29
29
  import AutoLinkPlugin from './plugins/AutoLinkPlugin';
30
30
  import ClickableLinkPlugin from './plugins/ClickableLinkPlugin';
31
- import CodeActionMenuPlugin from './plugins/CodeActionMenuPlugin';
32
31
  import CodeHighlightPlugin from './plugins/CodeHighlightPlugin';
33
32
  import CollapsiblePlugin from './plugins/CollapsiblePlugin';
34
33
  import ComponentPickerPlugin from './plugins/ComponentPickerPlugin';
@@ -77,6 +76,7 @@ import { useTranslationListener } from './hooks/useTranslationListener';
77
76
  import { PagesKitComponentPlugin } from '../ext/PagesKitComponent/PagesKitComponentPlugin';
78
77
  import { EditorHolderPlugin } from '../ext/EditorHolderPlugin';
79
78
  import { StyledEditorContent } from './styled-editor-content';
79
+ import { CodeCopyPlugin } from '../ext/CodeCopyPlugin';
80
80
  export default function Editor({ children, prepend, placeholder, onChange, autoFocus = true, showToolbar = true, editorRef, onReady, enableHeadingsIdPlugin, }) {
81
81
  const [editor] = useLexicalComposerContext();
82
82
  const [editable, setEditable] = useState(false);
@@ -106,7 +106,7 @@ export default function Editor({ children, prepend, placeholder, onChange, autoF
106
106
  }
107
107
  }, [hasNodes('image'), hasUploader]);
108
108
  if (minimalMode) {
109
- 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] }));
109
+ 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('code') && _jsx(CodeCopyPlugin, {}), 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 && 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] }));
110
110
  }
111
- 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('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] }));
111
+ 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('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('code') && _jsx(CodeCopyPlugin, {}), 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('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] }));
112
112
  }
@@ -402,7 +402,7 @@ export const uploadFile = (editor) => {
402
402
  editor.blur();
403
403
  let selection = null;
404
404
  // listen to all upload success
405
- uploader.onUploadSuccess(({ file, response }) => {
405
+ const handleUploadSuccess = ({ file, response }) => {
406
406
  // missing the source: function-upload-file
407
407
  if (file.source !== 'function-upload-file') {
408
408
  const { data } = response;
@@ -436,7 +436,8 @@ export const uploadFile = (editor) => {
436
436
  }, 10);
437
437
  }
438
438
  }
439
- });
439
+ };
440
+ uploader.onUploadSuccess(preventDuplicateUploadSuccess(handleUploadSuccess));
440
441
  // 点击 Uploader 中的 Done 后恢复 selection
441
442
  uploader.onClose(() => {
442
443
  if (selection) {
@@ -450,3 +451,19 @@ export const uploadFile = (editor) => {
450
451
  throw new Error('Missing required `window.uploaderRef`');
451
452
  }
452
453
  };
454
+ // Avoid handling upload success multiple times for the same file within a short interval
455
+ // ref: https://github.com/blocklet/media-kit/issues/428
456
+ function preventDuplicateUploadSuccess(handler) {
457
+ let lastHandledTimestamp = 0;
458
+ let lastHandledFileHash = '';
459
+ return (...args) => {
460
+ const now = Date.now();
461
+ const fileHash = args[0]?.file?.hashFileName;
462
+ if (now - lastHandledTimestamp < 1000 && fileHash === lastHandledFileHash) {
463
+ return;
464
+ }
465
+ lastHandledTimestamp = now;
466
+ lastHandledFileHash = fileHash;
467
+ handler(...args);
468
+ };
469
+ }
@@ -0,0 +1,62 @@
1
+ export declare const codeHighlightDark: {
2
+ atrule: string;
3
+ attr: string;
4
+ boolean: string;
5
+ builtin: string;
6
+ cdata: string;
7
+ char: string;
8
+ class: string;
9
+ comment: string;
10
+ constant: string;
11
+ deleted: string;
12
+ doctype: string;
13
+ entity: string;
14
+ function: string;
15
+ important: string;
16
+ inserted: string;
17
+ keyword: string;
18
+ namespace: string;
19
+ number: string;
20
+ operator: string;
21
+ prolog: string;
22
+ property: string;
23
+ punctuation: string;
24
+ regex: string;
25
+ selector: string;
26
+ string: string;
27
+ symbol: string;
28
+ tag: string;
29
+ url: string;
30
+ variable: string;
31
+ };
32
+ export declare const codeHighlightLight: {
33
+ atrule: string;
34
+ attr: string;
35
+ boolean: string;
36
+ builtin: string;
37
+ cdata: string;
38
+ char: string;
39
+ class: string;
40
+ comment: string;
41
+ constant: string;
42
+ deleted: string;
43
+ doctype: string;
44
+ entity: string;
45
+ function: string;
46
+ important: string;
47
+ inserted: string;
48
+ keyword: string;
49
+ namespace: string;
50
+ number: string;
51
+ operator: string;
52
+ prolog: string;
53
+ property: string;
54
+ punctuation: string;
55
+ regex: string;
56
+ selector: string;
57
+ string: string;
58
+ symbol: string;
59
+ tag: string;
60
+ url: string;
61
+ variable: string;
62
+ };
@@ -0,0 +1,113 @@
1
+ import { css } from '@emotion/css';
2
+ // Dark theme tokens
3
+ const tokenComment = css `
4
+ color: #6a9955;
5
+ `;
6
+ const tokenPunctuation = css `
7
+ color: #d4d4d4;
8
+ `;
9
+ const tokenProperty = css `
10
+ color: #9cdcfe;
11
+ `;
12
+ const tokenSelector = css `
13
+ color: #d7ba7d;
14
+ `;
15
+ const tokenOperator = css `
16
+ color: #d4d4d4;
17
+ `;
18
+ const tokenAttr = css `
19
+ color: #9cdcfe;
20
+ `;
21
+ const tokenVariable = css `
22
+ color: #9cdcfe;
23
+ `;
24
+ const tokenFunction = css `
25
+ color: #dcdcaa;
26
+ `;
27
+ // Light theme tokens - 更有活力和年轻化的配色,优化对比度
28
+ const tokenCommentLight = css `
29
+ color: #1e8449;
30
+ `;
31
+ const tokenPunctuationLight = css `
32
+ color: #2c3e50;
33
+ `;
34
+ const tokenPropertyLight = css `
35
+ color: #e67e22;
36
+ `;
37
+ const tokenSelectorLight = css `
38
+ color: #c0392b;
39
+ `;
40
+ const tokenOperatorLight = css `
41
+ color: #8e44ad;
42
+ `;
43
+ const tokenAttrLight = css `
44
+ color: #2980b9;
45
+ `;
46
+ const tokenVariableLight = css `
47
+ color: #27ae60;
48
+ `;
49
+ const tokenFunctionLight = css `
50
+ color: #f39c12;
51
+ `;
52
+ export const codeHighlightDark = {
53
+ atrule: tokenAttr,
54
+ attr: tokenAttr,
55
+ boolean: tokenProperty,
56
+ builtin: tokenSelector,
57
+ cdata: tokenComment,
58
+ char: tokenSelector,
59
+ class: tokenFunction,
60
+ comment: tokenComment,
61
+ constant: tokenProperty,
62
+ deleted: tokenProperty,
63
+ doctype: tokenComment,
64
+ entity: tokenOperator,
65
+ function: tokenFunction,
66
+ important: tokenVariable,
67
+ inserted: tokenSelector,
68
+ keyword: tokenAttr,
69
+ namespace: tokenVariable,
70
+ number: tokenProperty,
71
+ operator: tokenOperator,
72
+ prolog: tokenComment,
73
+ property: tokenProperty,
74
+ punctuation: tokenPunctuation,
75
+ regex: tokenVariable,
76
+ selector: tokenSelector,
77
+ string: tokenSelector,
78
+ symbol: tokenProperty,
79
+ tag: tokenProperty,
80
+ url: tokenOperator,
81
+ variable: tokenVariable,
82
+ };
83
+ export const codeHighlightLight = {
84
+ atrule: tokenAttrLight,
85
+ attr: tokenAttrLight,
86
+ boolean: tokenPropertyLight,
87
+ builtin: tokenSelectorLight,
88
+ cdata: tokenCommentLight,
89
+ char: tokenSelectorLight,
90
+ class: tokenFunctionLight,
91
+ comment: tokenCommentLight,
92
+ constant: tokenPropertyLight,
93
+ deleted: tokenPropertyLight,
94
+ doctype: tokenCommentLight,
95
+ entity: tokenOperatorLight,
96
+ function: tokenFunctionLight,
97
+ important: tokenVariableLight,
98
+ inserted: tokenSelectorLight,
99
+ keyword: tokenAttrLight,
100
+ namespace: tokenVariableLight,
101
+ number: tokenPropertyLight,
102
+ operator: tokenOperatorLight,
103
+ prolog: tokenCommentLight,
104
+ property: tokenPropertyLight,
105
+ punctuation: tokenPunctuationLight,
106
+ regex: tokenVariableLight,
107
+ selector: tokenSelectorLight,
108
+ string: tokenSelectorLight,
109
+ symbol: tokenPropertyLight,
110
+ tag: tokenPropertyLight,
111
+ url: tokenOperatorLight,
112
+ variable: tokenVariableLight,
113
+ };
@@ -1,6 +1,7 @@
1
1
  import { css } from '@emotion/css';
2
2
  import { useTheme } from '@mui/material';
3
3
  import defaultTheme from './defaultTheme';
4
+ import { codeHighlightLight, codeHighlightDark } from './code-highlight';
4
5
  const createCustomTheme = (theme) => {
5
6
  const { palette } = theme;
6
7
  const noMarginTopStyle = css(`
@@ -235,8 +236,8 @@ const createCustomTheme = (theme) => {
235
236
  }
236
237
  `;
237
238
  const code = css `
238
- background-color: #18181b;
239
- color: #fff;
239
+ background-color: ${palette.background.paper};
240
+ color: ${palette.text.primary};
240
241
  font-family: Menlo, Consolas, Monaco, monospace;
241
242
  display: block;
242
243
  // padding: 16px;
@@ -251,12 +252,11 @@ const createCustomTheme = (theme) => {
251
252
  overflow-x: auto;
252
253
  position: relative;
253
254
  border-radius: 8px;
254
- border: 1px solid #27272;
255
+ border: 1px solid ${palette.divider};
255
256
 
256
257
  &:before {
257
258
  content: attr(data-gutter);
258
259
  position: absolute;
259
- background-color: #18181b;
260
260
  left: 0;
261
261
  top: 0;
262
262
  // border-right: 1px solid #ccc;
@@ -296,6 +296,7 @@ const createCustomTheme = (theme) => {
296
296
  olDepth: [ol1, ol2, ol3, ol4, ol5],
297
297
  ul,
298
298
  },
299
+ codeHighlight: palette.mode === 'dark' ? codeHighlightDark : codeHighlightLight,
299
300
  embedBlock: {
300
301
  base: embedBlock,
301
302
  focus: embedBlockFocus,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocklet/editor",
3
- "version": "2.4.15",
3
+ "version": "2.4.17",
4
4
  "main": "lib/index.js",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -32,6 +32,7 @@
32
32
  "@excalidraw/excalidraw": "^0.18.0",
33
33
  "@iconify/iconify": "^3.1.1",
34
34
  "@iconify/icons-tabler": "^1.2.95",
35
+ "@iconify/react": "^4.1.1",
35
36
  "@lexical/clipboard": "^0.30.0",
36
37
  "@lexical/code": "^0.30.0",
37
38
  "@lexical/file": "^0.30.0",
@@ -68,7 +69,7 @@
68
69
  "ufo": "^1.5.4",
69
70
  "url-join": "^4.0.1",
70
71
  "zustand": "^4.5.5",
71
- "@blocklet/pdf": "^2.4.15"
72
+ "@blocklet/pdf": "^2.4.17"
72
73
  },
73
74
  "devDependencies": {
74
75
  "@babel/core": "^7.25.2",