@blocklet/editor-lite 2.6.0 → 2.6.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.
@@ -4,7 +4,6 @@ import { useRef, useEffect } from 'react';
4
4
  import { $createNodeKeyToSidMap, $createSidToNodeKeyMap, $nodeToHtml, $findTranslatableElementNodes, $htmlToNodes, } from '../translation/translation-utils';
5
5
  import { $createEmojiNode } from '../../main/nodes/EmojiNode';
6
6
  import { $createTranslationNode } from './TranslationNode';
7
- import { $isExcalidrawNode } from '../../main/nodes/ExcalidrawNode';
8
7
  import { $isImageNode } from '../../main/nodes/ImageNode';
9
8
  import { $isVideoNode } from '../VideoPlugin/VideoNode';
10
9
  import { $isFileNode } from '../FilePlugin/FileNode';
@@ -150,7 +149,7 @@ export class EditorTranslator {
150
149
  }
151
150
  async preprocessNodes() {
152
151
  const isSpecialNode = (node) => {
153
- return $isExcalidrawNode(node) || $isImageNode(node) || $isVideoNode(node) || $isFileNode(node);
152
+ return $isImageNode(node) || $isVideoNode(node) || $isFileNode(node);
154
153
  };
155
154
  this.editor.update(() => {
156
155
  $findTranslatableElementNodes().forEach((node) => {
@@ -3,7 +3,6 @@ import { $isHeadingNode, $isQuoteNode } from '@lexical/rich-text';
3
3
  import { $isListNode, $isListItemNode } from '@lexical/list';
4
4
  import { $dfs } from '@lexical/utils';
5
5
  import { $generateHtmlFromNodes, $generateNodesFromDOM } from '@lexical/html';
6
- import { $isExcalidrawNode } from '../../main/nodes/ExcalidrawNode';
7
6
  import { $isMentionNode } from '../../main/nodes/MentionNode';
8
7
  export const $nodeToHtml = (editor, node) => {
9
8
  const selection = $createNodeSelection();
@@ -11,9 +10,7 @@ export const $nodeToHtml = (editor, node) => {
11
10
  // 剔除嵌套 list 节点
12
11
  children
13
12
  .filter((x) => !$isListNode(x))
14
- .forEach((child) => $dfs(child)
15
- .filter((x) => !$isExcalidrawNode(x.node))
16
- .forEach((x) => selection.add(x.node.getKey())));
13
+ .forEach((child) => $dfs(child).forEach((x) => selection.add(x.node.getKey())));
17
14
  const html = $generateHtmlFromNodes(editor, selection);
18
15
  return html;
19
16
  };
@@ -29,7 +29,6 @@ import CollapsiblePlugin from './plugins/CollapsiblePlugin';
29
29
  import ComponentPickerPlugin from './plugins/ComponentPickerPlugin';
30
30
  import DragDropPaste from './plugins/DragDropPastePlugin';
31
31
  import DraggableBlockPlugin from './plugins/DraggableBlockPlugin';
32
- import ExcalidrawPlugin from './plugins/ExcalidrawPlugin';
33
32
  import FigmaPlugin from './plugins/FigmaPlugin';
34
33
  import FloatingLinkEditorPlugin from './plugins/FloatingLinkEditorPlugin';
35
34
  import FloatingTextFormatToolbarPlugin from './plugins/FloatingTextFormatToolbarPlugin';
@@ -93,7 +92,7 @@ export default function Editor({ children, prepend, placeholder, onChange, autoF
93
92
  }
94
93
  }, [hasNodes('image'), hasUploader]);
95
94
  if (minimalMode) {
96
- return (_jsxs(_Fragment, { children: [prepend, _jsx(VideoPathFixerPlugin, {}), _jsx(ImagePathFixerPlugin, {}), 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('autolink') && !!editable && _jsx(AutoLinkPlugin, {}), hasNodes('tweet') && _jsx(TwitterPlugin, {}), hasNodes('youtube') && _jsx(YouTubePlugin, {}), hasNodes('figma') && _jsx(FigmaPlugin, {}), _jsx(PostLinkEmbedPlugin, {}), _jsx(BookmarkPlugin, {}), hasNodes('file') && _jsx(FilePlugin, {}), hasNodes('horizontalrule') && _jsx(HorizontalRulePlugin, {}), hasNodes('excalidraw') && _jsx(ExcalidrawPlugin, {}), hasNodes('alert') && _jsx(AlertPlugin, {}), _jsx(TabFocusPlugin, {}), onChange && _jsx(OnChangePlugin, { onChange: onChange }), floatingAnchorElem && editable && hasNodes('link') && (_jsx(FloatingLinkEditorPlugin, { anchorElem: floatingAnchorElem })), !!characterLimitConfig?.maxLength && ENABLE_EDITOR_PLUGIN_CHAR_LIMIT && (_jsx(CharacterLimitPlugin, { maxLength: characterLimitConfig.maxLength, charLimitIndicatorStyle: characterLimitConfig.indicatorStyle, alignLeft: characterLimitConfig.alignLeft })), editorRef && _jsx(EditorRefPlugin, { editorRef: editorRef }), children] }));
95
+ return (_jsxs(_Fragment, { children: [prepend, _jsx(VideoPathFixerPlugin, {}), _jsx(ImagePathFixerPlugin, {}), 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('autolink') && !!editable && _jsx(AutoLinkPlugin, {}), hasNodes('tweet') && _jsx(TwitterPlugin, {}), hasNodes('youtube') && _jsx(YouTubePlugin, {}), hasNodes('figma') && _jsx(FigmaPlugin, {}), _jsx(PostLinkEmbedPlugin, {}), _jsx(BookmarkPlugin, {}), hasNodes('file') && _jsx(FilePlugin, {}), hasNodes('horizontalrule') && _jsx(HorizontalRulePlugin, {}), hasNodes('alert') && _jsx(AlertPlugin, {}), _jsx(TabFocusPlugin, {}), onChange && _jsx(OnChangePlugin, { onChange: onChange }), floatingAnchorElem && editable && hasNodes('link') && (_jsx(FloatingLinkEditorPlugin, { anchorElem: floatingAnchorElem })), !!characterLimitConfig?.maxLength && ENABLE_EDITOR_PLUGIN_CHAR_LIMIT && (_jsx(CharacterLimitPlugin, { maxLength: characterLimitConfig.maxLength, charLimitIndicatorStyle: characterLimitConfig.indicatorStyle, alignLeft: characterLimitConfig.alignLeft })), editorRef && _jsx(EditorRefPlugin, { editorRef: editorRef }), children] }));
97
96
  }
98
- return (_jsxs(_Fragment, { children: [prepend, _jsx(VideoPathFixerPlugin, {}), _jsx(ImagePathFixerPlugin, {}), editable && showToolbar && _jsx(ToolbarPlugin, {}), !!characterLimitConfig?.maxLength && ENABLE_EDITOR_PLUGIN_CHAR_LIMIT && (_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, {}), hasNodes('link') && _jsx(AutoEmbedPlugin, {}), !!editable && _jsx(MentionsPlugin, {}), hasNodes('autolink') && !!editable && _jsx(AutoLinkPlugin, {}), _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('table', 'tablerow', 'tablecell') && (_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, {}), hasNodes('file') && _jsx(FilePlugin, {}), hasNodes('link') && _jsx(ClickableLinkPlugin, {}), hasNodes('horizontalrule') && _jsx(HorizontalRulePlugin, {}), hasNodes('excalidraw') && _jsx(ExcalidrawPlugin, {}), hasNodes('alert') && _jsx(AlertPlugin, {}), _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, {})] }), _jsx(EditorHolderPlugin, {}), editorRef && _jsx(EditorRefPlugin, { editorRef: editorRef }), onReady && _jsx(EditorReadyPlugin, { onReady: onReady }), config.enableSafeAreaPlugin && _jsx(SafeAreaPlugin, {}), children] }));
97
+ return (_jsxs(_Fragment, { children: [prepend, _jsx(VideoPathFixerPlugin, {}), _jsx(ImagePathFixerPlugin, {}), editable && showToolbar && _jsx(ToolbarPlugin, {}), !!characterLimitConfig?.maxLength && ENABLE_EDITOR_PLUGIN_CHAR_LIMIT && (_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, {}), hasNodes('link') && _jsx(AutoEmbedPlugin, {}), !!editable && _jsx(MentionsPlugin, {}), hasNodes('autolink') && !!editable && _jsx(AutoLinkPlugin, {}), _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('table', 'tablerow', 'tablecell') && (_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, {}), hasNodes('file') && _jsx(FilePlugin, {}), hasNodes('link') && _jsx(ClickableLinkPlugin, {}), hasNodes('horizontalrule') && _jsx(HorizontalRulePlugin, {}), hasNodes('alert') && _jsx(AlertPlugin, {}), _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, {})] }), _jsx(EditorHolderPlugin, {}), editorRef && _jsx(EditorRefPlugin, { editorRef: editorRef }), onReady && _jsx(EditorReadyPlugin, { onReady: onReady }), config.enableSafeAreaPlugin && _jsx(SafeAreaPlugin, {}), children] }));
99
98
  }
@@ -18,7 +18,6 @@ import { CollapsibleContentNode } from '../plugins/CollapsiblePlugin/Collapsible
18
18
  import { CollapsibleTitleNode } from '../plugins/CollapsiblePlugin/CollapsibleTitleNode';
19
19
  import { EmojiNode } from './EmojiNode';
20
20
  import { EquationNode } from './EquationNode';
21
- import { ExcalidrawNode } from './ExcalidrawNode';
22
21
  import { FigmaNode } from './FigmaNode';
23
22
  import { ImageNode } from './ImageNode';
24
23
  import { MentionNode } from './MentionNode';
@@ -49,7 +48,6 @@ const PlaygroundNodes = [
49
48
  VideoNode,
50
49
  MentionNode,
51
50
  EmojiNode,
52
- ExcalidrawNode,
53
51
  EquationNode,
54
52
  HorizontalRuleNode,
55
53
  TweetNode,
@@ -23,7 +23,6 @@ import useHasNodes from '../../hooks/useHasNodes';
23
23
  import useModal from '../../hooks/useModal';
24
24
  import { EmbedConfigs } from '../AutoEmbedPlugin';
25
25
  import { INSERT_COLLAPSIBLE_COMMAND } from '../CollapsiblePlugin';
26
- import { INSERT_EXCALIDRAW_COMMAND } from '../ExcalidrawPlugin';
27
26
  import { INSERT_IMAGE_COMMAND, isImage } from '../ImagesPlugin';
28
27
  import { InsertTableDialog } from '../TablePlugin';
29
28
  import { useEditorConfig } from '../../../config';
@@ -258,13 +257,6 @@ export default function ComponentPickerMenuPlugin() {
258
257
  keywords: ['horizontal rule', 'divider', 'hr'],
259
258
  onSelect: () => editor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, undefined),
260
259
  }),
261
- !minimalMode &&
262
- hasNodes('excalidraw') &&
263
- new ComponentPickerOption('Excalidraw', {
264
- icon: (_jsx(Box, { component: "span", className: "bg-excalidraw", sx: { display: 'inline-block', width: 20, height: 20 } })),
265
- keywords: ['excalidraw', 'diagram', 'drawing'],
266
- onSelect: () => editor.dispatchCommand(INSERT_EXCALIDRAW_COMMAND, undefined),
267
- }),
268
260
  ...(minimalMode
269
261
  ? []
270
262
  : EmbedConfigs.map((embedConfig) => hasNodes(embedConfig.type) &&
@@ -22,7 +22,6 @@ import { getSelectedNode } from '../../utils/getSelectedNode';
22
22
  import { sanitizeUrl } from '../../utils/sanitizeUrl';
23
23
  // import { EmbedConfigs } from "../AutoEmbedPlugin";
24
24
  // import { INSERT_COLLAPSIBLE_COMMAND } from "../CollapsiblePlugin";
25
- // import { INSERT_EXCALIDRAW_COMMAND } from "../ExcalidrawPlugin";
26
25
  import { INSERT_IMAGE_COMMAND } from '../ImagesPlugin';
27
26
  import { uploadFile, INSERT_COMPONENT_COMMAND } from '../ComponentPickerPlugin';
28
27
  // import { InsertNewTableDialog, InsertTableDialog } from "../TablePlugin";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocklet/editor-lite",
3
- "version": "2.6.0",
3
+ "version": "2.6.1",
4
4
  "main": "lib/index.js",
5
5
  "sideEffects": false,
6
6
  "publishConfig": {
@@ -27,7 +27,6 @@
27
27
  "dependencies": {
28
28
  "@blocklet/embed": "^0.3.5",
29
29
  "@blocklet/js-sdk": "^1.17.7-beta-20251225-073259-cb6ecf68",
30
- "@excalidraw/excalidraw": "^0.18.0",
31
30
  "@iconify/iconify": "^3.1.1",
32
31
  "@iconify/icons-tabler": "^1.2.95",
33
32
  "@iconify/react": "^4.1.1",
@@ -1,13 +0,0 @@
1
- /**
2
- * Copyright (c) Meta Platforms, Inc. and affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- *
7
- */
8
- import type { NodeKey } from 'lexical';
9
- import { type JSX } from 'react';
10
- export default function ExcalidrawComponent({ nodeKey, data }: {
11
- data: string;
12
- nodeKey: NodeKey;
13
- }): JSX.Element;
@@ -1,155 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
3
- import { useLexicalNodeSelection } from '@lexical/react/useLexicalNodeSelection';
4
- import { mergeRegister } from '@lexical/utils';
5
- import { $getNodeByKey, $getSelection, $isNodeSelection, CLICK_COMMAND, COMMAND_PRIORITY_LOW, KEY_BACKSPACE_COMMAND, KEY_DELETE_COMMAND, } from 'lexical';
6
- import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
7
- import ImageResizer from '../../ui/ImageResizer';
8
- import { $isExcalidrawNode } from '.';
9
- import ExcalidrawImage from './ExcalidrawImage';
10
- import ExcalidrawModal from './ExcalidrawModal';
11
- import { covertImageWithMediaKitPath } from './utils';
12
- export default function ExcalidrawComponent({ nodeKey, data }) {
13
- const [editor] = useLexicalComposerContext();
14
- const [isModalOpen, setModalOpen] = useState(data === '[]' && editor.isEditable());
15
- const imageContainerRef = useRef(null);
16
- const buttonRef = useRef(null);
17
- const captionButtonRef = useRef(null);
18
- const [isSelected, setSelected, clearSelection] = useLexicalNodeSelection(nodeKey);
19
- const [isResizing, setIsResizing] = useState(false);
20
- const initialEditorEditable = useRef(editor.isEditable());
21
- // 兼容 excalidraw 旧版本数据 - 缺少 "elements" key, 并且是一个 array, 新版本数据是 object
22
- const parsed = useMemo(() => {
23
- try {
24
- const tempData = JSON.parse(data);
25
- covertImageWithMediaKitPath(tempData.elements, tempData.files);
26
- return tempData;
27
- }
28
- catch (e) {
29
- console.error(e);
30
- return {};
31
- }
32
- }, [data]);
33
- const { files = {}, appState = {}, width: containerWidth, height: containerHeight } = parsed;
34
- const elements = Array.isArray(parsed) ? parsed : parsed.elements || [];
35
- const onDelete = useCallback((event) => {
36
- if (isSelected && $isNodeSelection($getSelection())) {
37
- event.preventDefault();
38
- editor.update(() => {
39
- const node = $getNodeByKey(nodeKey);
40
- if ($isExcalidrawNode(node)) {
41
- node.remove();
42
- }
43
- setSelected(false);
44
- });
45
- }
46
- return false;
47
- }, [editor, isSelected, nodeKey, setSelected]);
48
- // Set editor to readOnly if excalidraw is open to prevent unwanted changes
49
- useEffect(() => {
50
- if (isModalOpen) {
51
- editor.setEditable(false);
52
- }
53
- else {
54
- editor.setEditable(initialEditorEditable.current);
55
- }
56
- }, [isModalOpen, editor]);
57
- useEffect(() => {
58
- return mergeRegister(editor.registerCommand(CLICK_COMMAND, (event) => {
59
- const buttonElem = buttonRef.current;
60
- const eventTarget = event.target;
61
- if (isResizing) {
62
- return true;
63
- }
64
- if (buttonElem !== null && buttonElem.contains(eventTarget)) {
65
- if (!event.shiftKey) {
66
- clearSelection();
67
- }
68
- setSelected(!isSelected);
69
- if (event.detail > 1) {
70
- setModalOpen(true);
71
- }
72
- return true;
73
- }
74
- return false;
75
- }, COMMAND_PRIORITY_LOW), editor.registerCommand(KEY_DELETE_COMMAND, onDelete, COMMAND_PRIORITY_LOW), editor.registerCommand(KEY_BACKSPACE_COMMAND, onDelete, COMMAND_PRIORITY_LOW));
76
- }, [clearSelection, editor, isSelected, isResizing, onDelete, setSelected]);
77
- const deleteNode = useCallback(() => {
78
- setModalOpen(false);
79
- return editor.update(() => {
80
- const node = $getNodeByKey(nodeKey);
81
- if ($isExcalidrawNode(node)) {
82
- node.remove();
83
- }
84
- });
85
- }, [editor, nodeKey]);
86
- const setData = (els, aps, fls) => {
87
- if (!editor.isEditable()) {
88
- return;
89
- }
90
- return editor.update(() => {
91
- const node = $getNodeByKey(nodeKey);
92
- if ($isExcalidrawNode(node)) {
93
- if (els.length > 0 || Object.keys(fls).length > 0) {
94
- // covert image with mediaKit path
95
- covertImageWithMediaKitPath(els, fls);
96
- node.setData(JSON.stringify({
97
- appState: aps,
98
- elements: els,
99
- files: fls,
100
- width: containerWidth,
101
- height: containerHeight,
102
- }));
103
- }
104
- else {
105
- node.remove();
106
- }
107
- }
108
- });
109
- };
110
- const onResizeStart = () => {
111
- setIsResizing(true);
112
- };
113
- const onResizeEnd = (width, height) => {
114
- updateSize(Array.isArray(parsed) ? { elements: parsed } : parsed, width, height);
115
- // Delay hiding the resize bars for click case
116
- setTimeout(() => {
117
- setIsResizing(false);
118
- }, 200);
119
- };
120
- const updateSize = (parsedData, width, height) => {
121
- if (!editor.isEditable()) {
122
- return;
123
- }
124
- return editor.update(() => {
125
- const node = $getNodeByKey(nodeKey);
126
- if ($isExcalidrawNode(node)) {
127
- node.setData(JSON.stringify({ ...parsedData, width, height }));
128
- }
129
- });
130
- };
131
- const handleClick = () => {
132
- // view mode 仅在 readonly 的 editor 中有效
133
- if (!initialEditorEditable.current) {
134
- setModalOpen(true);
135
- }
136
- };
137
- const handleClose = () => {
138
- setModalOpen(false);
139
- };
140
- const containerStyles = {
141
- ...(containerWidth && { width: containerWidth }),
142
- ...(containerHeight && { height: containerHeight }),
143
- maxWidth: '100%',
144
- };
145
- // 若从未 resize 过, 则使用 editor width 的 90% 作为默认的显示宽度 (#1178)
146
- if (!containerWidth && editor.getRootElement()) {
147
- containerStyles.width = '90%';
148
- }
149
- const _appState = !initialEditorEditable.current ? { ...appState, viewModeEnabled: true } : appState;
150
- return (_jsxs(_Fragment, { children: [_jsx(ExcalidrawModal, { initialElements: elements, initialFiles: files, initialAppState: _appState, isShown: isModalOpen, readonly: !initialEditorEditable.current, onDelete: deleteNode, onClose: handleClose, onSave: (els, aps, fls) => {
151
- editor.setEditable(true);
152
- setData(els, aps, fls);
153
- setModalOpen(false);
154
- }, closeOnClickOutside: false }), elements.length > 0 && (_jsxs("button", { ref: buttonRef, className: `excalidraw-button ${isSelected ? 'selected' : ''}`, children: [_jsx(ExcalidrawImage, { imageContainerRef: imageContainerRef, className: "image", elements: elements, files: files, appState: appState, onClick: handleClick, style: containerStyles }), (isSelected || isResizing) && (_jsx(ImageResizer, { buttonRef: captionButtonRef, showCaption: true, setShowCaption: () => null, imageRef: imageContainerRef, editor: editor, onResizeStart: onResizeStart, onResizeEnd: onResizeEnd, captionsEnabled: true }))] }))] }));
155
- }
@@ -1,58 +0,0 @@
1
- /**
2
- * Copyright (c) Meta Platforms, Inc. and affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- *
7
- */
8
- import type { ExcalidrawElement, NonDeleted } from '@excalidraw/excalidraw/dist/types/excalidraw/element/types';
9
- import type { AppState, BinaryFiles } from '@excalidraw/excalidraw/dist/types/excalidraw/types';
10
- import * as React from 'react';
11
- import { type JSX } from 'react';
12
- type ImageType = 'svg' | 'canvas';
13
- type Props = {
14
- /**
15
- * Configures the export setting for SVG/Canvas
16
- */
17
- appState: AppState;
18
- /**
19
- * The css class applied to image to be rendered
20
- */
21
- className?: string;
22
- /**
23
- * The Excalidraw elements to be rendered as an image
24
- */
25
- elements: NonDeleted<ExcalidrawElement>[];
26
- /**
27
- * The Excalidraw elements to be rendered as an image
28
- */
29
- files: BinaryFiles;
30
- /**
31
- * The height of the image to be rendered
32
- */
33
- height?: number | null;
34
- /**
35
- * The ref object to be used to render the image
36
- */
37
- imageContainerRef: {
38
- current: null | HTMLDivElement;
39
- };
40
- /**
41
- * The type of image to be rendered
42
- */
43
- imageType?: ImageType;
44
- /**
45
- * The css class applied to the root element of this component
46
- */
47
- rootClassName?: string | null;
48
- /**
49
- * The width of the image to be rendered
50
- */
51
- width?: number | null;
52
- };
53
- /**
54
- * @explorer-desc
55
- * A component for rendering Excalidraw elements as a static image
56
- */
57
- export default function ExcalidrawImage({ elements, files, imageContainerRef, appState, rootClassName, ...rest }: Props & React.HTMLProps<HTMLDivElement>): JSX.Element;
58
- export {};
@@ -1,60 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- /**
3
- * Copyright (c) Meta Platforms, Inc. and affiliates.
4
- *
5
- * This source code is licensed under the MIT license found in the
6
- * LICENSE file in the root directory of this source tree.
7
- *
8
- */
9
- import { exportToSvg } from '@excalidraw/excalidraw';
10
- import { useTheme } from '@mui/material';
11
- import { useEffect, useState } from 'react';
12
- // exportToSvg has fonts from excalidraw.com
13
- // We don't want them to be used in open source
14
- const removeStyleFromSvg_HACK = (svg) => {
15
- const styleTag = svg?.firstElementChild?.firstElementChild;
16
- // Generated SVG is getting double-sized by height and width attributes
17
- // We want to match the real size of the SVG element
18
- const viewBox = svg.getAttribute('viewBox');
19
- if (viewBox != null) {
20
- const viewBoxDimensions = viewBox.split(' ');
21
- svg.setAttribute('width', viewBoxDimensions[2]);
22
- svg.setAttribute('height', viewBoxDimensions[3]);
23
- }
24
- if (styleTag && styleTag.tagName === 'style') {
25
- styleTag.remove();
26
- }
27
- };
28
- /**
29
- * @explorer-desc
30
- * A component for rendering Excalidraw elements as a static image
31
- */
32
- export default function ExcalidrawImage({ elements, files, imageContainerRef, appState, rootClassName = null, ...rest }) {
33
- const [Svg, setSvg] = useState(null);
34
- const theme = useTheme();
35
- useEffect(() => {
36
- const setContent = async () => {
37
- const svg = await exportToSvg({
38
- appState: {
39
- ...appState,
40
- ...(theme.palette.mode === 'dark' && {
41
- theme: 'dark',
42
- viewBackgroundColor: theme.palette.background.default,
43
- // exportWithDarkMode: true,
44
- }),
45
- },
46
- elements,
47
- files,
48
- });
49
- removeStyleFromSvg_HACK(svg);
50
- svg.setAttribute('width', '100%');
51
- svg.setAttribute('height', '100%');
52
- svg.setAttribute('display', 'block');
53
- setSvg(svg);
54
- };
55
- setContent();
56
- }, [elements, files, appState]);
57
- return (_jsx("div", { ref: imageContainerRef, className: rootClassName ?? '',
58
- // eslint-disable-next-line react/no-danger
59
- dangerouslySetInnerHTML: { __html: Svg?.outerHTML ?? '' }, ...rest }));
60
- }
@@ -1,75 +0,0 @@
1
- /**
2
- * Copyright (c) Meta Platforms, Inc. and affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- *
7
- *
8
- */
9
-
10
- .ExcalidrawModal__overlay {
11
- display: flex;
12
- align-items: center;
13
- position: fixed;
14
- flex-direction: column;
15
- top: 0px;
16
- bottom: 0px;
17
- left: 0px;
18
- right: 0px;
19
- flex-grow: 0px;
20
- flex-shrink: 1px;
21
- z-index: 10001;
22
- background-color: rgba(40, 40, 40, 0.6);
23
- }
24
- .ExcalidrawModal__actions {
25
- display: flex;
26
- text-align: end;
27
- position: absolute;
28
- right: 5px;
29
- top: 5px;
30
- z-index: 1;
31
- }
32
- .ExcalidrawModal__actions button {
33
- background-color: #fff;
34
- border-radius: 5px;
35
- }
36
- .ExcalidrawModal__row {
37
- position: relative;
38
- padding: 42px 5px 5px;
39
- width: 90vw;
40
- height: 90vh;
41
- border-radius: 8px;
42
- box-shadow: 0 12px 28px 0 rgba(0, 0, 0, 0.2), 0 2px 4px 0 rgba(0, 0, 0, 0.1), inset 0 0 0 1px rgba(255, 255, 255, 0.5);
43
- }
44
- .ExcalidrawModal__row > div {
45
- border-radius: 5px;
46
- }
47
- .ExcalidrawModal__modal {
48
- position: relative;
49
- z-index: 99999;
50
- top: 50px;
51
- width: auto;
52
- left: 0;
53
- display: flex;
54
- justify-content: center;
55
- align-items: center;
56
- border-radius: 8px;
57
- background-color: #eee;
58
- }
59
-
60
- .ExcalidrawModal__discardModal {
61
- margin-top: 60px;
62
- text-align: center;
63
- }
64
-
65
- .ExcalidrawModal__fullscreen .ExcalidrawModal__modal {
66
- top: 0;
67
- }
68
- .ExcalidrawModal__fullscreen .ExcalidrawModal__row {
69
- width: 100vw;
70
- height: 100vh;
71
- }
72
-
73
- body .excalidraw-modal-container {
74
- z-index: 99999 !important;
75
- }
@@ -1,49 +0,0 @@
1
- import { ReactPortal } from 'react';
2
- import type { AppState, BinaryFiles, LibraryItems } from '@excalidraw/excalidraw/dist/types/excalidraw/types';
3
- import '@excalidraw/excalidraw/index.css';
4
- export type ExcalidrawElementFragment = {
5
- isDeleted?: boolean;
6
- };
7
- type Props = {
8
- closeOnClickOutside?: boolean;
9
- /**
10
- * The initial set of elements to draw into the scene
11
- */
12
- initialElements: ReadonlyArray<ExcalidrawElementFragment>;
13
- /**
14
- * The initial set of elements to draw into the scene
15
- */
16
- initialAppState: AppState;
17
- /**
18
- * The initial set of files to draw into the scene
19
- */
20
- initialFiles: BinaryFiles;
21
- /**
22
- * The initial set of library items to draw into the scene
23
- */
24
- initialLibraryItems?: LibraryItems;
25
- /**
26
- * Controls the visibility of the modal
27
- */
28
- isShown?: boolean;
29
- /**
30
- * Callback when closing and discarding the new changes
31
- */
32
- onClose: () => void;
33
- /**
34
- * Completely remove Excalidraw component
35
- */
36
- onDelete: () => void;
37
- /**
38
- * Callback when the save button is clicked
39
- */
40
- onSave: (elements: ReadonlyArray<ExcalidrawElementFragment>, appState: Partial<AppState>, files: BinaryFiles) => void;
41
- readonly?: boolean;
42
- };
43
- /**
44
- * @explorer-desc
45
- * A component which renders a modal with Excalidraw (a painting app)
46
- * which can be used to export an editable image
47
- */
48
- export default function ExcalidrawModal({ closeOnClickOutside, onSave, initialElements, initialAppState, initialFiles, initialLibraryItems, isShown, onDelete, onClose, readonly, }: Props): ReactPortal | null;
49
- export {};
@@ -1,197 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- /**
3
- * Copyright (c) Meta Platforms, Inc. and affiliates.
4
- *
5
- * This source code is licensed under the MIT license found in the
6
- * LICENSE file in the root directory of this source tree.
7
- *
8
- */
9
- import { Excalidraw, useHandleLibrary, serializeLibraryAsJSON } from '@excalidraw/excalidraw';
10
- import { useEffect, useLayoutEffect, useRef, useState } from 'react';
11
- import { createPortal } from 'react-dom';
12
- import LoadingButton from '@mui/lab/LoadingButton';
13
- import '@excalidraw/excalidraw/index.css';
14
- import { useTheme } from '@mui/material';
15
- import Button from '../../ui/Button';
16
- import Modal from '../../ui/Modal';
17
- import { uploadImageToMediaKit } from './utils';
18
- const safeParseJSON = (json) => {
19
- try {
20
- return JSON.parse(json);
21
- }
22
- catch (error) {
23
- return null;
24
- }
25
- };
26
- const LIBRARY_KEY = 'excalidraw-library';
27
- /**
28
- * @explorer-desc
29
- * A component which renders a modal with Excalidraw (a painting app)
30
- * which can be used to export an editable image
31
- */
32
- export default function ExcalidrawModal({ closeOnClickOutside = false, onSave, initialElements, initialAppState, initialFiles, initialLibraryItems = safeParseJSON(localStorage.getItem(LIBRARY_KEY))?.libraryItems, isShown = false, onDelete, onClose, readonly, }) {
33
- const excaliDrawModelRef = useRef(null);
34
- const excaliDrawSceneRef = useRef(null);
35
- const [discardModalOpen, setDiscardModalOpen] = useState(false);
36
- const [elements, setElements] = useState(initialElements);
37
- const [files, setFiles] = useState(initialFiles);
38
- const [loading, setLoading] = useState(false);
39
- const [saveText, setSaveText] = useState('Save');
40
- const [fullScreen, setFullScreen] = useState(false);
41
- const theme = useTheme();
42
- const markBlockletDirtyState = () => {
43
- if (!readonly) {
44
- window.dispatchEvent(new CustomEvent('blocklet:markDirty', { detail: { key: 'excalidraw' } }));
45
- }
46
- };
47
- const resetBlockletDirtyState = () => {
48
- window.dispatchEvent(new CustomEvent('blocklet:resetDirty', { detail: { key: 'excalidraw' } }));
49
- };
50
- useEffect(() => {
51
- // @ts-ignore set window.name to add library target
52
- window.name = window.location.href;
53
- if (excaliDrawModelRef.current !== null) {
54
- excaliDrawModelRef.current.focus();
55
- }
56
- }, []);
57
- useHandleLibrary({
58
- excalidrawAPI: excaliDrawSceneRef.current,
59
- getInitialLibraryItems: () => {
60
- return initialLibraryItems;
61
- },
62
- });
63
- useEffect(() => {
64
- let modalOverlayElement = null;
65
- const clickOutsideHandler = (event) => {
66
- const { target } = event;
67
- if (excaliDrawModelRef.current !== null &&
68
- !excaliDrawModelRef.current.contains(target) &&
69
- closeOnClickOutside) {
70
- onDelete();
71
- }
72
- };
73
- if (excaliDrawModelRef.current !== null) {
74
- modalOverlayElement = excaliDrawModelRef.current?.parentElement;
75
- if (modalOverlayElement !== null) {
76
- modalOverlayElement?.addEventListener('click', clickOutsideHandler);
77
- }
78
- }
79
- return () => {
80
- if (modalOverlayElement !== null) {
81
- modalOverlayElement?.removeEventListener('click', clickOutsideHandler);
82
- }
83
- };
84
- }, [closeOnClickOutside, onDelete]);
85
- useLayoutEffect(() => {
86
- const currentModalRef = excaliDrawModelRef.current;
87
- const onKeyDown = (event) => {
88
- if (event.key === 'Escape') {
89
- setDiscardModalOpen(true);
90
- }
91
- };
92
- if (currentModalRef !== null) {
93
- currentModalRef.addEventListener('keydown', onKeyDown);
94
- }
95
- return () => {
96
- if (currentModalRef !== null) {
97
- currentModalRef.removeEventListener('keydown', onKeyDown);
98
- }
99
- };
100
- }, [elements, files, onDelete]);
101
- const save = async () => {
102
- setLoading(true);
103
- setSaveText('Saving...');
104
- try {
105
- if (elements.filter((el) => !el.isDeleted).length > 0) {
106
- const appState = excaliDrawSceneRef?.current?.getAppState();
107
- // We only need a subset of the state
108
- const partialState = appState
109
- ? {
110
- exportBackground: appState.exportBackground,
111
- exportScale: appState.exportScale,
112
- exportWithDarkMode: appState.theme === 'dark',
113
- isBindingEnabled: appState.isBindingEnabled,
114
- isLoading: appState.isLoading,
115
- name: appState.name,
116
- theme: appState.theme,
117
- viewBackgroundColor: appState.viewBackgroundColor,
118
- zenModeEnabled: appState.zenModeEnabled,
119
- zoom: appState.zoom,
120
- }
121
- : {};
122
- setSaveText('Uploading...');
123
- await uploadImageToMediaKit(elements, files);
124
- setSaveText('Saving...');
125
- await onSave(elements, partialState, files);
126
- }
127
- else {
128
- // delete node if the scene is clear
129
- await onDelete();
130
- }
131
- }
132
- catch (error) {
133
- console.error('Error saving Excalidraw', error);
134
- }
135
- finally {
136
- setLoading(false);
137
- setSaveText('Save');
138
- resetBlockletDirtyState();
139
- }
140
- };
141
- const discard = () => {
142
- if (elements.filter((el) => !el.isDeleted).length === 0) {
143
- // delete node if the scene is clear
144
- onDelete();
145
- resetBlockletDirtyState();
146
- }
147
- else {
148
- // Otherwise, show confirmation dialog before closing
149
- setDiscardModalOpen(true);
150
- }
151
- };
152
- // eslint-disable-next-line react/no-unstable-nested-components
153
- function ShowDiscardDialog() {
154
- return (_jsxs(Modal, { title: "Discard", onClose: () => {
155
- setDiscardModalOpen(false);
156
- }, closeOnClickOutside: false, children: ["Are you sure you want to discard the changes?", _jsxs("div", { className: "ExcalidrawModal__discardModal", children: [_jsx(Button, { onClick: () => {
157
- resetBlockletDirtyState();
158
- setDiscardModalOpen(false);
159
- if (!initialElements?.length) {
160
- onDelete();
161
- }
162
- else {
163
- onClose();
164
- }
165
- }, children: "Discard" }), ' ', _jsx(Button, { onClick: () => {
166
- setDiscardModalOpen(false);
167
- }, children: "Cancel" })] })] }));
168
- }
169
- if (isShown === false) {
170
- return null;
171
- }
172
- const onChange = (els, _, fls) => {
173
- markBlockletDirtyState();
174
- setElements(els);
175
- setFiles(fls);
176
- };
177
- const onLibraryChange = (items) => {
178
- const serializeJSON = serializeLibraryAsJSON(items);
179
- localStorage.setItem(LIBRARY_KEY, serializeJSON);
180
- };
181
- // This is a hacky work-around for Excalidraw + Vite.
182
- // In DEV, Vite pulls this in fine, in prod it doesn't. It seems
183
- // like a module resolution issue with ESM vs CJS?
184
- // @ts-ignore
185
- const _Excalidraw = Excalidraw.$$typeof != null ? Excalidraw : Excalidraw.default;
186
- const appState = {
187
- ...initialAppState,
188
- ...(theme.palette.mode === 'dark' && {
189
- theme: 'dark',
190
- }),
191
- };
192
- return createPortal(_jsx("div", { className: `ExcalidrawModal__overlay ${fullScreen ? 'ExcalidrawModal__fullscreen' : ''}`, role: "dialog", children: _jsx("div", { className: "ExcalidrawModal__modal", ref: excaliDrawModelRef, tabIndex: -1, style: { backgroundColor: theme.palette.grey[100] }, children: _jsxs("div", { className: "ExcalidrawModal__row", children: [discardModalOpen && _jsx(ShowDiscardDialog, {}), _jsx(_Excalidraw, { onChange: onChange, onLibraryChange: onLibraryChange, ref: excaliDrawSceneRef, initialData: {
193
- appState: appState || { isLoading: false },
194
- elements: initialElements,
195
- files: initialFiles,
196
- } }), _jsxs("div", { className: "ExcalidrawModal__actions", children: [!fullScreen && (_jsx(LoadingButton, { className: "action-button", style: { width: 32, padding: 0, display: 'flex', alignItems: 'center' }, onClick: () => setFullScreen(true), children: _jsx("i", { className: "iconify", "data-icon": "material-symbols:fullscreen", "data-height": "18" }) })), fullScreen && (_jsx(LoadingButton, { className: "action-button", style: { width: 32, padding: 0, display: 'flex', alignItems: 'center' }, onClick: () => setFullScreen(false), children: _jsx("i", { className: "iconify", "data-icon": "material-symbols:fullscreen-exit", "data-height": "18" }) })), !readonly && (_jsxs(_Fragment, { children: [_jsx(LoadingButton, { loadingPosition: "start", disabled: loading, className: "action-button", onClick: discard, children: "Discard" }), _jsx(LoadingButton, { loadingPosition: "start", loading: loading, className: "action-button", onClick: save, children: saveText })] })), readonly && (_jsx(LoadingButton, { className: "action-button", onClick: () => onClose(), children: "Close" }))] })] }) }) }), document.body);
197
- }
@@ -1,30 +0,0 @@
1
- /**
2
- * Copyright (c) Meta Platforms, Inc. and affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- *
7
- */
8
- import './ExcalidrawModal.css';
9
- import type { DOMConversionMap, DOMExportOutput, EditorConfig, LexicalEditor, LexicalNode, NodeKey, SerializedLexicalNode, Spread } from 'lexical';
10
- import { DecoratorNode } from 'lexical';
11
- import { type JSX } from 'react';
12
- export type SerializedExcalidrawNode = Spread<{
13
- data: string;
14
- }, SerializedLexicalNode>;
15
- export declare class ExcalidrawNode extends DecoratorNode<JSX.Element> {
16
- __data: string;
17
- static getType(): string;
18
- static clone(node: ExcalidrawNode): ExcalidrawNode;
19
- static importJSON(serializedNode: SerializedExcalidrawNode): ExcalidrawNode;
20
- exportJSON(): SerializedExcalidrawNode;
21
- constructor(data?: string, key?: NodeKey);
22
- createDOM(config: EditorConfig): HTMLElement;
23
- updateDOM(): false;
24
- static importDOM(): DOMConversionMap<HTMLSpanElement> | null;
25
- exportDOM(editor: LexicalEditor): DOMExportOutput;
26
- setData(data: string): void;
27
- decorate(editor: LexicalEditor, config: EditorConfig): JSX.Element;
28
- }
29
- export declare function $createExcalidrawNode(): ExcalidrawNode;
30
- export declare function $isExcalidrawNode(node: LexicalNode | null): node is ExcalidrawNode;
@@ -1,95 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- /**
3
- * Copyright (c) Meta Platforms, Inc. and affiliates.
4
- *
5
- * This source code is licensed under the MIT license found in the
6
- * LICENSE file in the root directory of this source tree.
7
- *
8
- */
9
- import './ExcalidrawModal.css';
10
- import { DecoratorNode } from 'lexical';
11
- import { lazyRetry as lazy } from '@arcblock/ux/lib/Util';
12
- import { Suspense } from 'react';
13
- const ExcalidrawComponent = lazy(() => import('./ExcalidrawComponent'));
14
- function convertExcalidrawElement(domNode) {
15
- const excalidrawData = domNode.getAttribute('data-lexical-excalidraw-json');
16
- if (excalidrawData) {
17
- const node = $createExcalidrawNode();
18
- node.__data = excalidrawData;
19
- return {
20
- node,
21
- };
22
- }
23
- return null;
24
- }
25
- export class ExcalidrawNode extends DecoratorNode {
26
- __data;
27
- static getType() {
28
- return 'excalidraw';
29
- }
30
- static clone(node) {
31
- return new ExcalidrawNode(node.__data, node.__key);
32
- }
33
- static importJSON(serializedNode) {
34
- return new ExcalidrawNode(serializedNode.data);
35
- }
36
- exportJSON() {
37
- return {
38
- data: this.__data,
39
- type: 'excalidraw',
40
- version: 1,
41
- };
42
- }
43
- constructor(data = '[]', key) {
44
- super(key);
45
- this.__data = data;
46
- }
47
- // View
48
- createDOM(config) {
49
- const span = document.createElement('span');
50
- const { theme } = config;
51
- span.className = `${theme.image ?? ''} ${theme.responsiveContainer ?? ''}`;
52
- return span;
53
- }
54
- updateDOM() {
55
- return false;
56
- }
57
- static importDOM() {
58
- return {
59
- span: (domNode) => {
60
- if (!domNode.hasAttribute('data-lexical-excalidraw-json')) {
61
- return null;
62
- }
63
- return {
64
- conversion: convertExcalidrawElement,
65
- priority: 1,
66
- };
67
- },
68
- };
69
- }
70
- exportDOM(editor) {
71
- const element = document.createElement('span');
72
- const content = editor.getElementByKey(this.getKey());
73
- if (content !== null) {
74
- const svg = content.querySelector('svg');
75
- if (svg !== null) {
76
- element.innerHTML = svg.outerHTML;
77
- }
78
- }
79
- element.setAttribute('data-lexical-excalidraw-json', this.__data);
80
- return { element };
81
- }
82
- setData(data) {
83
- const self = this.getWritable();
84
- self.__data = data;
85
- }
86
- decorate(editor, config) {
87
- return (_jsx(Suspense, { fallback: null, children: _jsx(ExcalidrawComponent, { nodeKey: this.getKey(), data: this.__data }) }));
88
- }
89
- }
90
- export function $createExcalidrawNode() {
91
- return new ExcalidrawNode();
92
- }
93
- export function $isExcalidrawNode(node) {
94
- return node instanceof ExcalidrawNode;
95
- }
@@ -1,5 +0,0 @@
1
- export declare const getBlockletMountPointInfo: (name: string) => any;
2
- export declare function isLocalImage(src: string): boolean;
3
- export declare function joinImageUrl(src: string): string;
4
- export declare function uploadImageToMediaKit(elements: any, files: any): Promise<void>;
5
- export declare function covertImageWithMediaKitPath(elements: any, files: any): void;
@@ -1,71 +0,0 @@
1
- import { joinURL } from 'ufo';
2
- // chain promise
3
- const runPromiseInSequence = (list, fn) => {
4
- return list.reduce((p, item) => {
5
- return p.then(() => fn(item));
6
- }, Promise.resolve()); // initial
7
- };
8
- export const getBlockletMountPointInfo = (name) => {
9
- return window.blocklet?.componentMountPoints?.find((x) => x.name === name || x.did === name);
10
- };
11
- export function isLocalImage(src) {
12
- return src.startsWith('data:image') || src.startsWith('blob:');
13
- }
14
- export function joinImageUrl(src) {
15
- if (src?.startsWith('http')) {
16
- return src;
17
- }
18
- // media kit
19
- // const mountPoint = getBlockletMountPointInfo('z8ia1mAXo8ZE7ytGF36L5uBf9kD2kenhqFGp9');
20
- // discuss kit
21
- const mountPoint = getBlockletMountPointInfo('did-comments')?.mountPoint;
22
- if (mountPoint) {
23
- const prefix = joinURL(window.location.origin, mountPoint, 'uploads');
24
- // not add prefix
25
- if (!src.startsWith(prefix)) {
26
- return joinURL(prefix, src);
27
- }
28
- }
29
- return src;
30
- }
31
- export async function uploadImageToMediaKit(elements, files) {
32
- const filteredImages = elements?.filter((el) => el.type === 'image');
33
- if (filteredImages.length > 0) {
34
- // upload image to Media Kit
35
- await runPromiseInSequence(filteredImages, async (el) => {
36
- const fileItem = files[el.fileId];
37
- // @ts-ignore
38
- const uploader = window?.uploaderRef?.current?.getUploader?.();
39
- const prefix = joinImageUrl('/');
40
- // if not uploaded yet and is has uploader
41
- if (isLocalImage(fileItem.dataURL) && !!uploader) {
42
- // try to upload the image
43
- const blobFile = await fetch(fileItem.dataURL).then((res) => res.blob());
44
- const { data } = await uploader.uploadFile(blobFile).then((result) => {
45
- // https://uppy.io/docs/uppy/#options
46
- return result?.response;
47
- });
48
- // only save filename
49
- files[el.fileId].dataURL = data.filename;
50
- }
51
- else if (fileItem.dataURL?.startsWith(prefix)) {
52
- // remove the prefix
53
- files[el.fileId].dataURL = fileItem.dataURL.replace(prefix, '');
54
- }
55
- });
56
- // promise all to chain the upload
57
- }
58
- }
59
- export function covertImageWithMediaKitPath(elements, files) {
60
- const filteredImages = elements?.filter((el) => el.type === 'image') || [];
61
- if (filteredImages?.length > 0) {
62
- // upload image to Media Kit
63
- filteredImages.forEach((el) => {
64
- const fileItem = files[el.fileId];
65
- if (!isLocalImage(fileItem.dataURL)) {
66
- // replace dataURL with Media Kit URL
67
- files[el.fileId].dataURL = joinImageUrl(fileItem.dataURL);
68
- }
69
- });
70
- }
71
- }
@@ -1,3 +0,0 @@
1
- import { LexicalCommand } from 'lexical';
2
- export declare const INSERT_EXCALIDRAW_COMMAND: LexicalCommand<void>;
3
- export default function ExcalidrawPlugin(): null;
@@ -1,30 +0,0 @@
1
- /**
2
- * Copyright (c) Meta Platforms, Inc. and affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- *
7
- */
8
- import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
9
- import { $wrapNodeInElement } from '@lexical/utils';
10
- import { $createParagraphNode, $insertNodes, $isRootOrShadowRoot, COMMAND_PRIORITY_EDITOR, createCommand, } from 'lexical';
11
- import { useEffect } from 'react';
12
- import { $createExcalidrawNode, ExcalidrawNode } from '../../nodes/ExcalidrawNode';
13
- export const INSERT_EXCALIDRAW_COMMAND = createCommand('INSERT_EXCALIDRAW_COMMAND');
14
- export default function ExcalidrawPlugin() {
15
- const [editor] = useLexicalComposerContext();
16
- useEffect(() => {
17
- if (!editor.hasNodes([ExcalidrawNode])) {
18
- throw new Error('ExcalidrawPlugin: ExcalidrawNode not registered on editor');
19
- }
20
- return editor.registerCommand(INSERT_EXCALIDRAW_COMMAND, () => {
21
- const excalidrawNode = $createExcalidrawNode();
22
- $insertNodes([excalidrawNode]);
23
- if ($isRootOrShadowRoot(excalidrawNode.getParentOrThrow())) {
24
- $wrapNodeInElement(excalidrawNode, $createParagraphNode).selectEnd();
25
- }
26
- return true;
27
- }, COMMAND_PRIORITY_EDITOR);
28
- }, [editor]);
29
- return null;
30
- }