@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.
- package/lib/ext/InlineTranslationPlugin/EditorTranslator.js +1 -2
- package/lib/ext/translation/translation-utils.js +1 -4
- package/lib/main/editor.js +2 -3
- package/lib/main/nodes/PlaygroundNodes.js +0 -2
- package/lib/main/plugins/ComponentPickerPlugin/index.js +0 -8
- package/lib/main/plugins/ToolbarPlugin/index.js +0 -1
- package/package.json +1 -2
- package/lib/main/nodes/ExcalidrawNode/ExcalidrawComponent.d.ts +0 -13
- package/lib/main/nodes/ExcalidrawNode/ExcalidrawComponent.js +0 -155
- package/lib/main/nodes/ExcalidrawNode/ExcalidrawImage.d.ts +0 -58
- package/lib/main/nodes/ExcalidrawNode/ExcalidrawImage.js +0 -60
- package/lib/main/nodes/ExcalidrawNode/ExcalidrawModal.css +0 -75
- package/lib/main/nodes/ExcalidrawNode/ExcalidrawModal.d.ts +0 -49
- package/lib/main/nodes/ExcalidrawNode/ExcalidrawModal.js +0 -197
- package/lib/main/nodes/ExcalidrawNode/index.d.ts +0 -30
- package/lib/main/nodes/ExcalidrawNode/index.js +0 -95
- package/lib/main/nodes/ExcalidrawNode/utils.d.ts +0 -5
- package/lib/main/nodes/ExcalidrawNode/utils.js +0 -71
- package/lib/main/plugins/ExcalidrawPlugin/index.d.ts +0 -3
- package/lib/main/plugins/ExcalidrawPlugin/index.js +0 -30
|
@@ -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 $
|
|
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
|
};
|
package/lib/main/editor.js
CHANGED
|
@@ -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('
|
|
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('
|
|
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.
|
|
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,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
|
-
}
|