@blocklet/editor 2.4.122 → 2.4.124
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/config.d.ts +1 -0
- package/lib/ext/BlockletEmbedPlugin/index.js +3 -2
- package/lib/ext/ImagePathFixerPlugin.d.ts +1 -0
- package/lib/ext/ImagePathFixerPlugin.js +31 -0
- package/lib/ext/VideoPathFixerPlugin.d.ts +1 -0
- package/lib/ext/VideoPathFixerPlugin.js +31 -0
- package/lib/ext/utils.d.ts +1 -1
- package/lib/ext/utils.js +2 -6
- package/lib/lexical-utils/index.d.ts +2 -0
- package/lib/lexical-utils/index.js +17 -0
- package/lib/main/editor.js +5 -2
- package/lib/main/index.js +2 -3
- package/lib/main/markdown-editor/editor.js +1 -1
- package/lib/main/markdown-editor/index.js +2 -2
- package/lib/main/markdown-editor/plugins/MediaUrlFixerPlugin.js +2 -2
- package/lib/main/nodes/ImageComponent.js +4 -2
- package/lib/main/viewer/index.js +2 -3
- package/lib/main/viewer/viewer.js +3 -1
- package/package.json +2 -2
- package/lib/main/markdown-editor/transformers.d.ts +0 -12
- package/lib/main/markdown-editor/transformers.js +0 -185
package/lib/config.d.ts
CHANGED
|
@@ -27,6 +27,7 @@ export interface EditorConfig {
|
|
|
27
27
|
openLinkInNewTab?: boolean;
|
|
28
28
|
characterLimitConfig?: CharacterLimitConfig;
|
|
29
29
|
simpleImageComponent?: boolean;
|
|
30
|
+
enableSafeAreaPlugin?: boolean;
|
|
30
31
|
}
|
|
31
32
|
export declare function EditorConfigProvider({ children, value }: {
|
|
32
33
|
children: React.ReactNode;
|
|
@@ -4,8 +4,9 @@ import { COMMAND_PRIORITY_EDITOR, createCommand } from 'lexical';
|
|
|
4
4
|
import { useEffect } from 'react';
|
|
5
5
|
import { AutoEmbedOption } from '@lexical/react/LexicalAutoEmbedPlugin';
|
|
6
6
|
import pick from 'lodash/pick';
|
|
7
|
+
import { joinURL } from 'ufo';
|
|
7
8
|
import { $createBlockletEmbedNode, BlockletEmbedNode } from './BlockletEmbedNode';
|
|
8
|
-
import { $getLinkNode
|
|
9
|
+
import { $getLinkNode } from '../utils';
|
|
9
10
|
export const INSERT_BLOCKLET_EMBED_COMMAND = createCommand('INSERT_BLOCKLET_EMBED_COMMAND');
|
|
10
11
|
const parseBlockletEmbedUrl = async (blockletEmbedEndpoint, url) => {
|
|
11
12
|
const urlObj = new URL(blockletEmbedEndpoint);
|
|
@@ -14,7 +15,7 @@ const parseBlockletEmbedUrl = async (blockletEmbedEndpoint, url) => {
|
|
|
14
15
|
const data = await response.json();
|
|
15
16
|
const embed = data?.embed?.[0];
|
|
16
17
|
if (embed) {
|
|
17
|
-
const embedUrl =
|
|
18
|
+
const embedUrl = joinURL(data.origin, embed.url);
|
|
18
19
|
return {
|
|
19
20
|
id: url,
|
|
20
21
|
url: embedUrl,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function ImagePathFixerPlugin(): null;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useLayoutEffect } from 'react';
|
|
2
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
3
|
+
import { joinURL } from 'ufo';
|
|
4
|
+
import { discussKitUploadsUrl } from './utils';
|
|
5
|
+
import { ImageNode } from '../main/nodes/ImageNode';
|
|
6
|
+
const originalExportJSON = ImageNode.prototype.exportJSON;
|
|
7
|
+
const ensureLeadingSlash = (str) => (str.startsWith('/') ? str : `/${str}`);
|
|
8
|
+
ImageNode.prototype.exportJSON = function exportJSON() {
|
|
9
|
+
const json = originalExportJSON.call(this);
|
|
10
|
+
// 保存 editor content 时恢复 image src (剥除 discussKitUploadsUrl)
|
|
11
|
+
if (json.src && json.src.startsWith(discussKitUploadsUrl)) {
|
|
12
|
+
json.src = json.src.replace(discussKitUploadsUrl, '');
|
|
13
|
+
json.src = ensureLeadingSlash(json.src);
|
|
14
|
+
}
|
|
15
|
+
return json;
|
|
16
|
+
};
|
|
17
|
+
function imageNodeTransform(node) {
|
|
18
|
+
const targetNode = node;
|
|
19
|
+
const src = targetNode.getSrc();
|
|
20
|
+
// 仅处理以 "/" 为前缀的 image src, 并且避免重复处理
|
|
21
|
+
if (src?.startsWith('/') && !src.startsWith(discussKitUploadsUrl)) {
|
|
22
|
+
targetNode.setSrc(joinURL(discussKitUploadsUrl, src));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export default function ImagePathFixerPlugin() {
|
|
26
|
+
const [editor] = useLexicalComposerContext();
|
|
27
|
+
useLayoutEffect(() => {
|
|
28
|
+
return editor.registerNodeTransform(ImageNode, imageNodeTransform);
|
|
29
|
+
}, [editor]);
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function VideoPathFixerPlugin(): null;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useLayoutEffect } from 'react';
|
|
2
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
3
|
+
import { joinURL } from 'ufo';
|
|
4
|
+
import { discussKitUploadsUrl } from './utils';
|
|
5
|
+
import { VideoNode } from './VideoPlugin/VideoNode';
|
|
6
|
+
const originalExportJSON = VideoNode.prototype.exportJSON;
|
|
7
|
+
const ensureLeadingSlash = (str) => (str.startsWith('/') ? str : `/${str}`);
|
|
8
|
+
VideoNode.prototype.exportJSON = function exportJSON() {
|
|
9
|
+
const json = originalExportJSON.call(this);
|
|
10
|
+
// 保存 editor content 时恢复 src (剥除 discussKitUploadsUrl)
|
|
11
|
+
if (json.src && json.src.startsWith(discussKitUploadsUrl)) {
|
|
12
|
+
json.src = json.src.replace(discussKitUploadsUrl, '');
|
|
13
|
+
json.src = ensureLeadingSlash(json.src);
|
|
14
|
+
}
|
|
15
|
+
return json;
|
|
16
|
+
};
|
|
17
|
+
function VideoNodeTransform(node) {
|
|
18
|
+
const targetNode = node;
|
|
19
|
+
const src = targetNode.getSrc();
|
|
20
|
+
// 仅处理以 "/" 为前缀的 image src, 并且避免重复处理
|
|
21
|
+
if (src?.startsWith('/') && !src.startsWith(discussKitUploadsUrl)) {
|
|
22
|
+
targetNode.setSrc(joinURL(discussKitUploadsUrl, src));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function VideoPathFixerPlugin() {
|
|
26
|
+
const [editor] = useLexicalComposerContext();
|
|
27
|
+
useLayoutEffect(() => {
|
|
28
|
+
return editor.registerNodeTransform(VideoNode, VideoNodeTransform);
|
|
29
|
+
}, [editor]);
|
|
30
|
+
return null;
|
|
31
|
+
}
|
package/lib/ext/utils.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { LinkNode } from '@lexical/link';
|
|
2
2
|
import { type LexicalEditor } from 'lexical';
|
|
3
|
-
export declare function joinUrl(...paths: string[]): string;
|
|
4
3
|
export declare function isValidUrl(url: string): boolean;
|
|
5
4
|
export declare const safeParseJSON: (json: string, defaultValue?: any) => any;
|
|
6
5
|
export declare const getBlockletMountPointInfo: (name: string) => any;
|
|
7
6
|
export declare const discussKitMountPoint: any;
|
|
7
|
+
export declare const discussKitUploadsUrl: string;
|
|
8
8
|
export declare const blockletExists: (name: string) => boolean;
|
|
9
9
|
export declare const fetchOpenGraphInfo: (openGraphEndpoint: string, url: string) => Promise<{
|
|
10
10
|
url: any;
|
package/lib/ext/utils.js
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import { $isLinkNode, LinkNode } from '@lexical/link';
|
|
2
2
|
import { $getNearestNodeOfType } from '@lexical/utils';
|
|
3
3
|
import { $getSelection, $isRangeSelection } from 'lexical';
|
|
4
|
-
|
|
5
|
-
return path.replace(/([^:]\/)\/+/g, '$1');
|
|
6
|
-
}
|
|
7
|
-
export function joinUrl(...paths) {
|
|
8
|
-
return dedupeSlashes(paths.join('/'));
|
|
9
|
-
}
|
|
4
|
+
import { joinURL } from 'ufo';
|
|
10
5
|
export function isValidUrl(url) {
|
|
11
6
|
try {
|
|
12
7
|
// eslint-disable-next-line no-new
|
|
@@ -29,6 +24,7 @@ export const getBlockletMountPointInfo = (name) => {
|
|
|
29
24
|
return window.blocklet?.componentMountPoints?.find((x) => x.name === name);
|
|
30
25
|
};
|
|
31
26
|
export const discussKitMountPoint = window.blocklet?.componentMountPoints?.find((x) => x.did === 'z8ia1WEiBZ7hxURf6LwH21Wpg99vophFwSJdu')?.mountPoint || '/';
|
|
27
|
+
export const discussKitUploadsUrl = joinURL(discussKitMountPoint || '', '/uploads');
|
|
32
28
|
export const blockletExists = (name) => {
|
|
33
29
|
return !!getBlockletMountPointInfo(name);
|
|
34
30
|
};
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
*/
|
|
9
9
|
import { EditorState, ElementNode, Klass, LexicalEditor, LexicalNode } from 'lexical';
|
|
10
|
+
import { InitialEditorStateType } from '@lexical/react/LexicalComposer';
|
|
10
11
|
export type DFSNode = Readonly<{
|
|
11
12
|
depth: number;
|
|
12
13
|
node: LexicalNode;
|
|
@@ -41,4 +42,5 @@ export declare function mergeRegister(...func: Array<Func>): () => void;
|
|
|
41
42
|
export declare function registerNestedElementResolver<N extends ElementNode>(editor: LexicalEditor, targetNode: Klass<N>, cloneNode: (from: N) => N, handleOverlap: (from: N, to: N) => void): () => void;
|
|
42
43
|
export declare function $restoreEditorState(editor: LexicalEditor, editorState: EditorState): void;
|
|
43
44
|
export declare function $wrapNodeInElement(node: LexicalNode, createElementNode: () => ElementNode): ElementNode;
|
|
45
|
+
export declare function getInitialEditorState(content: string, markdown?: boolean): InitialEditorStateType;
|
|
44
46
|
export {};
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
*
|
|
10
10
|
*/
|
|
11
11
|
import { $getRoot, $isElementNode, $setSelection, } from 'lexical';
|
|
12
|
+
import { $convertFromMarkdownString } from '@lexical/markdown';
|
|
12
13
|
import invariant from '../shared/invariant';
|
|
14
|
+
import { PLAYGROUND_TRANSFORMERS } from '../main/plugins/MarkdownTransformers';
|
|
13
15
|
export function addClassNamesToElement(element, ...classNames) {
|
|
14
16
|
classNames.forEach((className) => {
|
|
15
17
|
if (typeof className === 'string') {
|
|
@@ -224,3 +226,18 @@ export function $wrapNodeInElement(node, createElementNode) {
|
|
|
224
226
|
elementNode.append(node);
|
|
225
227
|
return elementNode;
|
|
226
228
|
}
|
|
229
|
+
export function getInitialEditorState(content, markdown) {
|
|
230
|
+
if (markdown) {
|
|
231
|
+
return () => $convertFromMarkdownString(content, PLAYGROUND_TRANSFORMERS);
|
|
232
|
+
}
|
|
233
|
+
try {
|
|
234
|
+
const parsed = JSON.parse(content);
|
|
235
|
+
if (parsed && typeof parsed === 'object') {
|
|
236
|
+
return content;
|
|
237
|
+
}
|
|
238
|
+
return () => $convertFromMarkdownString(content, PLAYGROUND_TRANSFORMERS);
|
|
239
|
+
}
|
|
240
|
+
catch (e) {
|
|
241
|
+
return () => $convertFromMarkdownString(content, PLAYGROUND_TRANSFORMERS);
|
|
242
|
+
}
|
|
243
|
+
}
|
package/lib/main/editor.js
CHANGED
|
@@ -75,6 +75,9 @@ import { PagesKitComponentPlugin } from '../ext/PagesKitComponent/PagesKitCompon
|
|
|
75
75
|
import { EditorHolderPlugin } from '../ext/EditorHolderPlugin';
|
|
76
76
|
import { StyledEditorContent } from './styled-editor-content';
|
|
77
77
|
import { CodeCopyPlugin } from '../ext/CodeCopyPlugin';
|
|
78
|
+
import { VideoPathFixerPlugin } from '../ext/VideoPathFixerPlugin';
|
|
79
|
+
import ImagePathFixerPlugin from '../ext/ImagePathFixerPlugin';
|
|
80
|
+
import { SafeAreaPlugin } from '../ext/SafeAreaPlugin';
|
|
78
81
|
export default function Editor({ children, prepend, placeholder, onChange, autoFocus = true, showToolbar = true, editorRef, onReady, enableHeadingsIdPlugin, }) {
|
|
79
82
|
const [editor] = useLexicalComposerContext();
|
|
80
83
|
const [editable, setEditable] = useState(false);
|
|
@@ -104,7 +107,7 @@ export default function Editor({ children, prepend, placeholder, onChange, autoF
|
|
|
104
107
|
}
|
|
105
108
|
}, [hasNodes('image'), hasUploader]);
|
|
106
109
|
if (minimalMode) {
|
|
107
|
-
return (_jsxs(_Fragment, { children: [prepend, isRichText && editable && showToolbar && _jsx(ToolbarPlugin, {}), hasNodes('image') && hasUploader && _jsx(DragDropPaste, {}), autoFocus && _jsx(AutoFocusPlugin, { defaultSelection: "rootEnd" }), _jsx(ClearEditorPlugin, {}), !!editable && _jsx(ComponentPickerPlugin, {}), !!editable && _jsx(MentionsPlugin, {}), hasNodes('link') && _jsx(AutoEmbedPlugin, {}), _jsx(PasteSlackImagePlugin, {}), _jsx(StyledEditorContent, { ref: onRef, className: cx('be-content', editable && 'editable'), children: _jsx(RichTextPlugin, { contentEditable: _jsx(ContentEditable, { className: cx('be-editable', 'notranslate') }), placeholder: _jsx(Placeholder, { className: "be-placeholder", children: placeholder }), ErrorBoundary: LexicalErrorBoundary }) }), hasNodes('code', 'code-highlight') && _jsx(CodeHighlightPlugin, {}), hasNodes('code') && _jsx(CodeCopyPlugin, {}), hasNodes('image') && hasUploader && _jsx(ImagesPlugin, {}), hasNodes('video') && _jsx(VideoPlugin, {}), hasNodes('link') && _jsx(LinkPlugin, {}), hasNodes('autolink') && !!editable && _jsx(AutoLinkPlugin, {}), hasNodes('tweet') && _jsx(TwitterPlugin, {}), hasNodes('youtube') && _jsx(YouTubePlugin, {}), hasNodes('figma') && _jsx(FigmaPlugin, {}), _jsx(PostLinkEmbedPlugin, {}), _jsx(BookmarkPlugin, {}), editable && _jsx(CustomOnChangePlugin, { placeholder: placeholder }), editable && _jsx(TemplatePlugin, {}), !editable && _jsx(BlurTextPlugin, {}), hasNodes('pdf') && _jsx(PdfPlugin, {}), hasNodes('file') && _jsx(FilePlugin, {}), hasNodes('horizontalrule') && _jsx(HorizontalRulePlugin, {}), hasNodes('excalidraw') && _jsx(ExcalidrawPlugin, {}), hasNodes('alert') && _jsx(AlertPlugin, {}), hasNodes('pages-kit-component') && _jsx(PagesKitComponentPlugin, {}), _jsx(TabFocusPlugin, {}), onChange && _jsx(OnChangePlugin, { onChange: onChange }), floatingAnchorElem && editable && hasNodes('link') && (_jsx(FloatingLinkEditorPlugin, { anchorElem: floatingAnchorElem })), !!characterLimitConfig?.maxLength && (_jsx(CharacterLimitPlugin, { maxLength: characterLimitConfig.maxLength, charLimitIndicatorStyle: characterLimitConfig.indicatorStyle, alignLeft: characterLimitConfig.alignLeft })), editorRef && _jsx(EditorRefPlugin, { editorRef: editorRef }), children] }));
|
|
110
|
+
return (_jsxs(_Fragment, { children: [prepend, _jsx(VideoPathFixerPlugin, {}), _jsx(ImagePathFixerPlugin, {}), isRichText && editable && showToolbar && _jsx(ToolbarPlugin, {}), hasNodes('image') && hasUploader && _jsx(DragDropPaste, {}), autoFocus && _jsx(AutoFocusPlugin, { defaultSelection: "rootEnd" }), _jsx(ClearEditorPlugin, {}), !!editable && _jsx(ComponentPickerPlugin, {}), !!editable && _jsx(MentionsPlugin, {}), hasNodes('link') && _jsx(AutoEmbedPlugin, {}), _jsx(PasteSlackImagePlugin, {}), _jsx(StyledEditorContent, { ref: onRef, className: cx('be-content', editable && 'editable'), children: _jsx(RichTextPlugin, { contentEditable: _jsx(ContentEditable, { className: cx('be-editable', 'notranslate') }), placeholder: _jsx(Placeholder, { className: "be-placeholder", children: placeholder }), ErrorBoundary: LexicalErrorBoundary }) }), hasNodes('code', 'code-highlight') && _jsx(CodeHighlightPlugin, {}), hasNodes('code') && _jsx(CodeCopyPlugin, {}), hasNodes('image') && hasUploader && _jsx(ImagesPlugin, {}), hasNodes('video') && _jsx(VideoPlugin, {}), hasNodes('link') && _jsx(LinkPlugin, {}), hasNodes('autolink') && !!editable && _jsx(AutoLinkPlugin, {}), hasNodes('tweet') && _jsx(TwitterPlugin, {}), hasNodes('youtube') && _jsx(YouTubePlugin, {}), hasNodes('figma') && _jsx(FigmaPlugin, {}), _jsx(PostLinkEmbedPlugin, {}), _jsx(BookmarkPlugin, {}), editable && _jsx(CustomOnChangePlugin, { placeholder: placeholder }), editable && _jsx(TemplatePlugin, {}), !editable && _jsx(BlurTextPlugin, {}), hasNodes('pdf') && _jsx(PdfPlugin, {}), hasNodes('file') && _jsx(FilePlugin, {}), hasNodes('horizontalrule') && _jsx(HorizontalRulePlugin, {}), hasNodes('excalidraw') && _jsx(ExcalidrawPlugin, {}), hasNodes('alert') && _jsx(AlertPlugin, {}), hasNodes('pages-kit-component') && _jsx(PagesKitComponentPlugin, {}), _jsx(TabFocusPlugin, {}), onChange && _jsx(OnChangePlugin, { onChange: onChange }), floatingAnchorElem && editable && hasNodes('link') && (_jsx(FloatingLinkEditorPlugin, { anchorElem: floatingAnchorElem })), !!characterLimitConfig?.maxLength && (_jsx(CharacterLimitPlugin, { maxLength: characterLimitConfig.maxLength, charLimitIndicatorStyle: characterLimitConfig.indicatorStyle, alignLeft: characterLimitConfig.alignLeft })), editorRef && _jsx(EditorRefPlugin, { editorRef: editorRef }), children] }));
|
|
108
111
|
}
|
|
109
|
-
return (_jsxs(_Fragment, { children: [prepend, isRichText && editable && showToolbar && _jsx(ToolbarPlugin, {}), !!characterLimitConfig?.maxLength && (_jsx(CharacterLimitPlugin, { maxLength: characterLimitConfig.maxLength, charLimitIndicatorStyle: characterLimitConfig.indicatorStyle, alignLeft: characterLimitConfig.alignLeft })), hasNodes('image') && hasUploader && _jsx(DragDropPaste, {}), autoFocus && _jsx(AutoFocusPlugin, { defaultSelection: "rootEnd" }), _jsx(ClearEditorPlugin, {}), !!editable && _jsx(ComponentPickerPlugin, {}), hasNodes('link') && _jsx(AutoEmbedPlugin, {}), !!editable && _jsx(MentionsPlugin, {}), hasNodes('autolink') && !!editable && _jsx(AutoLinkPlugin, {}), isRichText ? (_jsxs(_Fragment, { children: [_jsx(PasteSlackImagePlugin, {}), _jsx(HistoryPlugin, { externalHistoryState: historyState }), _jsx(StyledEditorContent, { ref: onRef, className: cx('be-content', editable && 'editable'), children: _jsx(RichTextPlugin, { contentEditable: _jsx(ContentEditable, { className: cx('be-editable', 'notranslate') }), placeholder: _jsx(Placeholder, { className: "be-placeholder", children: placeholder }), ErrorBoundary: LexicalErrorBoundary }) }), _jsx(MarkdownShortcutPlugin, {}), _jsx(TabIndentationPlugin, {}), hasNodes('code', 'code-highlight') && _jsx(CodeHighlightPlugin, {}), hasNodes('code') && _jsx(CodeCopyPlugin, {}), hasNodes('list', 'listitem') && _jsx(ListPlugin, {}), hasNodes('list', 'listitem') && _jsx(CheckListPlugin, {}), hasNodes('list', 'listitem') && _jsx(ListMaxIndentLevelPlugin, { maxDepth: 7 }), hasNodes('table', 'tablerow', 'tablecell') && editable && (_jsx(TablePlugin, { hasCellMerge: true, hasCellBackgroundColor: true, hasHorizontalScroll: true })), hasNodes('table', 'tablerow', 'tablecell') && _jsx(TableCellResizer, {}), hasNodes('image') && hasUploader && _jsx(ImagesPlugin, {}), hasNodes('video') && _jsx(VideoPlugin, {}), hasNodes('link') && _jsx(LinkPlugin, {}), hasNodes('tweet') && _jsx(TwitterPlugin, {}), hasNodes('youtube') && _jsx(YouTubePlugin, {}), hasNodes('figma') && _jsx(FigmaPlugin, {}), _jsx(BilibiliPlugin, {}), _jsx(BlockletEmbedPlugin, {}), _jsx(PostLinkEmbedPlugin, {}), _jsx(BookmarkPlugin, {}), _jsx(AidePlugin, {}), editable && _jsx(CustomOnChangePlugin, { placeholder: placeholder }), editable && _jsx(TemplatePlugin, {}), !editable && _jsx(BlurTextPlugin, {}), hasNodes('pdf') && _jsx(PdfPlugin, {}), hasNodes('file') && _jsx(FilePlugin, {}), hasNodes('link') && _jsx(ClickableLinkPlugin, {}), hasNodes('horizontalrule') && _jsx(HorizontalRulePlugin, {}), hasNodes('excalidraw') && _jsx(ExcalidrawPlugin, {}), hasNodes('alert') && _jsx(AlertPlugin, {}), hasNodes('pages-kit-component') && _jsx(PagesKitComponentPlugin, {}), _jsx(TabFocusPlugin, {}), hasNodes('collapsible-container', 'collapsible-content', 'collapsible-title') && _jsx(CollapsiblePlugin, {}), onChange && _jsx(OnChangePlugin, { onChange: onChange }), floatingAnchorElem && editable && (_jsxs(_Fragment, { children: [_jsx(DraggableBlockPlugin, { anchorElem: floatingAnchorElem }), hasNodes('link') && _jsx(FloatingLinkEditorPlugin, { anchorElem: floatingAnchorElem }), hasNodes('table') && _jsx(TableCellActionMenuPlugin, { anchorElem: floatingAnchorElem, cellMerge: true }), _jsx(FloatingTextFormatToolbarPlugin, { anchorElem: floatingAnchorElem })] })), enableHeadingsIdPlugin && _jsx(HeadingsIdPlugin, {})] })) : (_jsxs(_Fragment, { children: [_jsx(PlainTextPlugin, { contentEditable: _jsx(ContentEditable, {}), placeholder: _jsx(Placeholder, { children: "placeholder" }), ErrorBoundary: LexicalErrorBoundary }), _jsx(HistoryPlugin, { externalHistoryState: historyState })] })), _jsx("div", { children: showTableOfContents && _jsx(TableOfContentsPlugin, {}) }), _jsx(MarkdownHeadTextPlugin, {}), _jsx(EditorHolderPlugin, {}), editorRef && _jsx(EditorRefPlugin, { editorRef: editorRef }), onReady && _jsx(EditorReadyPlugin, { onReady: onReady }), children] }));
|
|
112
|
+
return (_jsxs(_Fragment, { children: [prepend, _jsx(VideoPathFixerPlugin, {}), _jsx(ImagePathFixerPlugin, {}), isRichText && editable && showToolbar && _jsx(ToolbarPlugin, {}), !!characterLimitConfig?.maxLength && (_jsx(CharacterLimitPlugin, { maxLength: characterLimitConfig.maxLength, charLimitIndicatorStyle: characterLimitConfig.indicatorStyle, alignLeft: characterLimitConfig.alignLeft })), hasNodes('image') && hasUploader && _jsx(DragDropPaste, {}), autoFocus && _jsx(AutoFocusPlugin, { defaultSelection: "rootEnd" }), _jsx(ClearEditorPlugin, {}), !!editable && _jsx(ComponentPickerPlugin, {}), hasNodes('link') && _jsx(AutoEmbedPlugin, {}), !!editable && _jsx(MentionsPlugin, {}), hasNodes('autolink') && !!editable && _jsx(AutoLinkPlugin, {}), isRichText ? (_jsxs(_Fragment, { children: [_jsx(PasteSlackImagePlugin, {}), _jsx(HistoryPlugin, { externalHistoryState: historyState }), _jsx(StyledEditorContent, { ref: onRef, className: cx('be-content', editable && 'editable'), children: _jsx(RichTextPlugin, { contentEditable: _jsx(ContentEditable, { className: cx('be-editable', 'notranslate') }), placeholder: _jsx(Placeholder, { className: "be-placeholder", children: placeholder }), ErrorBoundary: LexicalErrorBoundary }) }), _jsx(MarkdownShortcutPlugin, {}), _jsx(TabIndentationPlugin, {}), hasNodes('code', 'code-highlight') && _jsx(CodeHighlightPlugin, {}), hasNodes('code') && _jsx(CodeCopyPlugin, {}), hasNodes('list', 'listitem') && _jsx(ListPlugin, {}), hasNodes('list', 'listitem') && _jsx(CheckListPlugin, {}), hasNodes('list', 'listitem') && _jsx(ListMaxIndentLevelPlugin, { maxDepth: 7 }), hasNodes('table', 'tablerow', 'tablecell') && editable && (_jsx(TablePlugin, { hasCellMerge: true, hasCellBackgroundColor: true, hasHorizontalScroll: true })), hasNodes('table', 'tablerow', 'tablecell') && _jsx(TableCellResizer, {}), hasNodes('image') && hasUploader && _jsx(ImagesPlugin, {}), hasNodes('video') && _jsx(VideoPlugin, {}), hasNodes('link') && _jsx(LinkPlugin, {}), hasNodes('tweet') && _jsx(TwitterPlugin, {}), hasNodes('youtube') && _jsx(YouTubePlugin, {}), hasNodes('figma') && _jsx(FigmaPlugin, {}), _jsx(BilibiliPlugin, {}), _jsx(BlockletEmbedPlugin, {}), _jsx(PostLinkEmbedPlugin, {}), _jsx(BookmarkPlugin, {}), _jsx(AidePlugin, {}), editable && _jsx(CustomOnChangePlugin, { placeholder: placeholder }), editable && _jsx(TemplatePlugin, {}), !editable && _jsx(BlurTextPlugin, {}), hasNodes('pdf') && _jsx(PdfPlugin, {}), hasNodes('file') && _jsx(FilePlugin, {}), hasNodes('link') && _jsx(ClickableLinkPlugin, {}), hasNodes('horizontalrule') && _jsx(HorizontalRulePlugin, {}), hasNodes('excalidraw') && _jsx(ExcalidrawPlugin, {}), hasNodes('alert') && _jsx(AlertPlugin, {}), hasNodes('pages-kit-component') && _jsx(PagesKitComponentPlugin, {}), _jsx(TabFocusPlugin, {}), hasNodes('collapsible-container', 'collapsible-content', 'collapsible-title') && _jsx(CollapsiblePlugin, {}), onChange && _jsx(OnChangePlugin, { onChange: onChange }), floatingAnchorElem && editable && (_jsxs(_Fragment, { children: [_jsx(DraggableBlockPlugin, { anchorElem: floatingAnchorElem }), hasNodes('link') && _jsx(FloatingLinkEditorPlugin, { anchorElem: floatingAnchorElem }), hasNodes('table') && _jsx(TableCellActionMenuPlugin, { anchorElem: floatingAnchorElem, cellMerge: true }), _jsx(FloatingTextFormatToolbarPlugin, { anchorElem: floatingAnchorElem })] })), enableHeadingsIdPlugin && _jsx(HeadingsIdPlugin, {})] })) : (_jsxs(_Fragment, { children: [_jsx(PlainTextPlugin, { contentEditable: _jsx(ContentEditable, {}), placeholder: _jsx(Placeholder, { children: "placeholder" }), ErrorBoundary: LexicalErrorBoundary }), _jsx(HistoryPlugin, { externalHistoryState: historyState })] })), _jsx("div", { children: showTableOfContents && _jsx(TableOfContentsPlugin, {}) }), _jsx(MarkdownHeadTextPlugin, {}), _jsx(EditorHolderPlugin, {}), editorRef && _jsx(EditorRefPlugin, { editorRef: editorRef }), onReady && _jsx(EditorReadyPlugin, { onReady: onReady }), config.enableSafeAreaPlugin && _jsx(SafeAreaPlugin, {}), children] }));
|
|
110
113
|
}
|
package/lib/main/index.js
CHANGED
|
@@ -13,7 +13,6 @@ import { cx } from '@emotion/css';
|
|
|
13
13
|
import { LexicalComposer } from '@lexical/react/LexicalComposer';
|
|
14
14
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
15
15
|
import { useCallback, useEffect, useRef } from 'react';
|
|
16
|
-
import { $convertFromMarkdownString } from '@lexical/markdown';
|
|
17
16
|
import { useTheme } from '@mui/material';
|
|
18
17
|
import { SettingsContext } from './context/SettingsContext';
|
|
19
18
|
import { SharedAutocompleteContext } from './context/SharedAutocompleteContext';
|
|
@@ -23,12 +22,12 @@ import { EditorRoot } from './editor-root';
|
|
|
23
22
|
import PlaygroundNodes from './nodes/PlaygroundNodes';
|
|
24
23
|
import { TableContext } from './plugins/TablePlugin';
|
|
25
24
|
import { useCustomTheme } from './themes/customTheme';
|
|
26
|
-
import {
|
|
25
|
+
import { getInitialEditorState } from '../lexical-utils';
|
|
27
26
|
export default function BlockletEditor({ editorState, nodes = PlaygroundNodes, editable = true, markdown, ...props }) {
|
|
28
27
|
const theme = useCustomTheme();
|
|
29
28
|
const initialConfig = {
|
|
30
29
|
namespace: 'Playground',
|
|
31
|
-
editorState:
|
|
30
|
+
editorState: getInitialEditorState(editorState ?? '', markdown),
|
|
32
31
|
nodes,
|
|
33
32
|
editable,
|
|
34
33
|
onError: (error) => {
|
|
@@ -38,7 +38,7 @@ import { HeadingsIdPlugin } from '../../ext/HeadingsIdPlugin';
|
|
|
38
38
|
import { EditorReadyPlugin } from '../../ext/EditorReadyPlugin';
|
|
39
39
|
import { BlurTextPlugin } from '../../ext/BlurTextPlugin';
|
|
40
40
|
import ToolbarPlugin from './plugins/ToolbarPlugin';
|
|
41
|
-
import { TRANSFORMERS, IMAGE } from '
|
|
41
|
+
import { PLAYGROUND_TRANSFORMERS as TRANSFORMERS, IMAGE } from '../plugins/MarkdownTransformers';
|
|
42
42
|
import { MediaUrlFixerPlugin } from './plugins/MediaUrlFixerPlugin';
|
|
43
43
|
import { StyledEditorContent } from '../styled-editor-content';
|
|
44
44
|
export default function Editor({ children, placeholder, onChange, autoFocus = true, showToolbar = true, editorRef, onReady, enableHeadingsIdPlugin, mediaUrlPrefix, }) {
|
|
@@ -9,7 +9,7 @@ import { EditorRoot } from '../editor-root';
|
|
|
9
9
|
import nodes from './nodes';
|
|
10
10
|
import { useCustomTheme } from '../themes/customTheme';
|
|
11
11
|
import { MarkdownEditorThemeProvider } from './theme';
|
|
12
|
-
import {
|
|
12
|
+
import { PLAYGROUND_TRANSFORMERS } from '../plugins/MarkdownTransformers';
|
|
13
13
|
export function MarkdownEditor(props) {
|
|
14
14
|
return (_jsx(MarkdownEditorThemeProvider, { children: _jsx(InnerMarkdownEditor, { ...props }) }));
|
|
15
15
|
}
|
|
@@ -17,7 +17,7 @@ function InnerMarkdownEditor({ editorState, editable = true, ...props }) {
|
|
|
17
17
|
const theme = useCustomTheme();
|
|
18
18
|
const initialConfig = {
|
|
19
19
|
namespace: 'MarkdownEditor',
|
|
20
|
-
editorState: editorState ? () => $convertFromMarkdownString(editorState,
|
|
20
|
+
editorState: editorState ? () => $convertFromMarkdownString(editorState, PLAYGROUND_TRANSFORMERS) : undefined,
|
|
21
21
|
nodes,
|
|
22
22
|
editable,
|
|
23
23
|
onError: (error) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useEffect } from 'react';
|
|
2
2
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
3
|
-
import
|
|
3
|
+
import { joinURL } from 'ufo';
|
|
4
4
|
import { $isImageNode, ImageNode } from '../../nodes/ImageNode';
|
|
5
5
|
export function MediaUrlFixerPlugin({ mediaUrlPrefix, transformers, }) {
|
|
6
6
|
const [editor] = useLexicalComposerContext();
|
|
@@ -26,7 +26,7 @@ export function MediaUrlFixerPlugin({ mediaUrlPrefix, transformers, }) {
|
|
|
26
26
|
const src = targetNode.getSrc();
|
|
27
27
|
// 仅处理以 "/" 为前缀的 image src, 并且避免重复处理
|
|
28
28
|
if (src?.startsWith('/') && !src.startsWith(mediaUrlPrefix)) {
|
|
29
|
-
targetNode.setSrc(
|
|
29
|
+
targetNode.setSrc(joinURL(mediaUrlPrefix, src));
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
return editor.registerNodeTransform(ImageNode, imageNodeTransform);
|
|
@@ -18,7 +18,7 @@ import { useLexicalNodeSelection } from '@lexical/react/useLexicalNodeSelection'
|
|
|
18
18
|
import { mergeRegister } from '@lexical/utils';
|
|
19
19
|
import { Alert, Box, Button, Dialog, LinearProgress, Skeleton } from '@mui/material';
|
|
20
20
|
import { $getNodeByKey, $getSelection, $isNodeSelection, $setSelection, CLICK_COMMAND, COMMAND_PRIORITY_LOW, KEY_BACKSPACE_COMMAND, KEY_DELETE_COMMAND, KEY_ENTER_COMMAND, KEY_ESCAPE_COMMAND, SELECTION_CHANGE_COMMAND, } from 'lexical';
|
|
21
|
-
import { Suspense, useCallback, useEffect, useRef, useState } from 'react';
|
|
21
|
+
import { Suspense, useCallback, useEffect, useRef, useState, lazy } from 'react';
|
|
22
22
|
import { createPortal } from 'react-dom';
|
|
23
23
|
import { useEditorConfig } from '../../config';
|
|
24
24
|
import { useSettings } from '../context/SettingsContext';
|
|
@@ -28,12 +28,14 @@ import ImageResizer from '../ui/ImageResizer';
|
|
|
28
28
|
import Placeholder from '../ui/Placeholder';
|
|
29
29
|
import { $isImageNode } from './ImageNode';
|
|
30
30
|
import './ImageNode.css';
|
|
31
|
-
import { ImageAnnotation, ImageAnnotationView, ImageEnhancer } from '../ui/ImageEnhancer';
|
|
32
31
|
import { useImageDisplayWidth } from '../hooks/hooks';
|
|
33
32
|
import { useMediumZoom } from '../hooks/medium-zoom';
|
|
34
33
|
import { FrameMockup, isDeviceFrame } from '../ui/FrameMockup';
|
|
35
34
|
import { useSafeArea } from '../../ext/SafeAreaPlugin';
|
|
36
35
|
import { UPDATE_BUSY_COMMAND } from '../../ext/BusyPlugin';
|
|
36
|
+
const ImageAnnotation = lazy(() => import('../ui/ImageEnhancer').then((module) => ({ default: module.ImageAnnotation })));
|
|
37
|
+
const ImageAnnotationView = lazy(() => import('../ui/ImageEnhancer').then((module) => ({ default: module.ImageAnnotationView })));
|
|
38
|
+
const ImageEnhancer = lazy(() => import('../ui/ImageEnhancer').then((module) => ({ default: module.ImageEnhancer })));
|
|
37
39
|
const imageCache = new Set();
|
|
38
40
|
// Min size that require user confirm to upload image
|
|
39
41
|
const REQUIRE_CONFIRM_UPLOAD_SIZE = 10 << 20;
|
package/lib/main/viewer/index.js
CHANGED
|
@@ -2,19 +2,18 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import '../index.css';
|
|
3
3
|
import { cx } from '@emotion/css';
|
|
4
4
|
import { LexicalComposer } from '@lexical/react/LexicalComposer';
|
|
5
|
-
import { $convertFromMarkdownString } from '@lexical/markdown';
|
|
6
5
|
import { useTheme } from '@mui/material';
|
|
7
6
|
import PlaygroundNodes from '../nodes/PlaygroundNodes';
|
|
8
7
|
import { useCustomTheme } from '../themes/customTheme';
|
|
9
|
-
import { PLAYGROUND_TRANSFORMERS } from '../plugins/MarkdownTransformers';
|
|
10
8
|
import { EditorViewer } from './viewer';
|
|
11
9
|
import { EditorRoot } from '../editor-root';
|
|
10
|
+
import { getInitialEditorState } from '../../lexical-utils';
|
|
12
11
|
export function BlockletEditorViewer({ editorState, markdown, ...props }) {
|
|
13
12
|
const muiTheme = useTheme();
|
|
14
13
|
const theme = useCustomTheme();
|
|
15
14
|
const initialConfig = {
|
|
16
15
|
namespace: 'Playground',
|
|
17
|
-
editorState:
|
|
16
|
+
editorState: getInitialEditorState(editorState ?? '', markdown),
|
|
18
17
|
nodes: PlaygroundNodes,
|
|
19
18
|
editable: false,
|
|
20
19
|
onError: (error) => {
|
|
@@ -23,8 +23,10 @@ import { EditorHolderPlugin } from '../../ext/EditorHolderPlugin';
|
|
|
23
23
|
import { StyledEditorContent } from '../styled-editor-content';
|
|
24
24
|
import { CodeCopyPlugin } from '../../ext/CodeCopyPlugin';
|
|
25
25
|
import CheckboxPlugin from '../../ext/CheckboxPlugin';
|
|
26
|
+
import { VideoPathFixerPlugin } from '../../ext/VideoPathFixerPlugin';
|
|
27
|
+
import ImagePathFixerPlugin from '../../ext/ImagePathFixerPlugin';
|
|
26
28
|
export function EditorViewer({ children, prepend, onChange, editorRef, onReady, enableHeadingsIdPlugin, onCheckboxChange, }) {
|
|
27
29
|
useResponsiveTable();
|
|
28
30
|
const hasNodes = useHasNodes();
|
|
29
|
-
return (_jsxs(_Fragment, { children: [prepend, _jsx(StyledEditorContent, { className: "be-content", children: _jsx(RichTextPlugin, { contentEditable: _jsx(ContentEditable, { className: cx('be-editable', 'notranslate') }), ErrorBoundary: LexicalErrorBoundary }) }), _jsx(MarkdownShortcutPlugin, {}), hasNodes('code', 'code-highlight') && _jsx(CodeHighlightPlugin, {}), hasNodes('code') && _jsx(CodeCopyPlugin, {}), hasNodes('list', 'listitem') && _jsx(ListPlugin, {}), hasNodes('list', 'listitem') && _jsx(CheckListPlugin, {}), hasNodes('list', 'listitem') && _jsx(ListMaxIndentLevelPlugin, { maxDepth: 7 }), hasNodes('link') && _jsx(LinkPlugin, {}), _jsx(BlurTextPlugin, {}), hasNodes('link') && _jsx(ClickableLinkPlugin, {}), _jsx(TabFocusPlugin, {}), hasNodes('collapsible-container', 'collapsible-content', 'collapsible-title') && _jsx(CollapsiblePlugin, {}), onChange && _jsx(OnChangePlugin, { onChange: onChange }), enableHeadingsIdPlugin && _jsx(HeadingsIdPlugin, {}), _jsx(EditorHolderPlugin, {}), editorRef && _jsx(EditorRefPlugin, { editorRef: editorRef }), onReady && _jsx(EditorReadyPlugin, { onReady: onReady }), _jsx(CheckboxPlugin, { send: onCheckboxChange }), children] }));
|
|
31
|
+
return (_jsxs(_Fragment, { children: [prepend, _jsx(VideoPathFixerPlugin, {}), _jsx(ImagePathFixerPlugin, {}), _jsx(StyledEditorContent, { className: "be-content", children: _jsx(RichTextPlugin, { contentEditable: _jsx(ContentEditable, { className: cx('be-editable', 'notranslate') }), ErrorBoundary: LexicalErrorBoundary }) }), _jsx(MarkdownShortcutPlugin, {}), hasNodes('code', 'code-highlight') && _jsx(CodeHighlightPlugin, {}), hasNodes('code') && _jsx(CodeCopyPlugin, {}), hasNodes('list', 'listitem') && _jsx(ListPlugin, {}), hasNodes('list', 'listitem') && _jsx(CheckListPlugin, {}), hasNodes('list', 'listitem') && _jsx(ListMaxIndentLevelPlugin, { maxDepth: 7 }), hasNodes('link') && _jsx(LinkPlugin, {}), _jsx(BlurTextPlugin, {}), hasNodes('link') && _jsx(ClickableLinkPlugin, {}), _jsx(TabFocusPlugin, {}), hasNodes('collapsible-container', 'collapsible-content', 'collapsible-title') && _jsx(CollapsiblePlugin, {}), onChange && _jsx(OnChangePlugin, { onChange: onChange }), enableHeadingsIdPlugin && _jsx(HeadingsIdPlugin, {}), _jsx(EditorHolderPlugin, {}), editorRef && _jsx(EditorRefPlugin, { editorRef: editorRef }), onReady && _jsx(EditorReadyPlugin, { onReady: onReady }), _jsx(CheckboxPlugin, { send: onCheckboxChange }), children] }));
|
|
30
32
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blocklet/editor",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.124",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"publishConfig": {
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"ufo": "^1.5.4",
|
|
74
74
|
"url-join": "^4.0.1",
|
|
75
75
|
"zustand": "^4.5.5",
|
|
76
|
-
"@blocklet/pdf": "2.4.
|
|
76
|
+
"@blocklet/pdf": "2.4.124"
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
79
|
"@babel/core": "^7.25.2",
|
|
@@ -1,12 +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 { ElementTransformer, TextMatchTransformer, Transformer } from '@lexical/markdown';
|
|
9
|
-
export declare const HR: ElementTransformer;
|
|
10
|
-
export declare const IMAGE: TextMatchTransformer;
|
|
11
|
-
export declare const TABLE: ElementTransformer;
|
|
12
|
-
export declare const TRANSFORMERS: Array<Transformer>;
|
|
@@ -1,185 +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 { $convertFromMarkdownString, $convertToMarkdownString, CHECK_LIST, ELEMENT_TRANSFORMERS, MULTILINE_ELEMENT_TRANSFORMERS, TEXT_FORMAT_TRANSFORMERS, TEXT_MATCH_TRANSFORMERS, } from '@lexical/markdown';
|
|
9
|
-
import { $createHorizontalRuleNode, $isHorizontalRuleNode, HorizontalRuleNode, } from '@lexical/react/LexicalHorizontalRuleNode';
|
|
10
|
-
import { $createTableCellNode, $createTableNode, $createTableRowNode, $isTableCellNode, $isTableNode, $isTableRowNode, TableCellHeaderStates, TableCellNode, TableNode, TableRowNode, } from '@lexical/table';
|
|
11
|
-
import { $isParagraphNode, $isTextNode } from 'lexical';
|
|
12
|
-
import { $createImageNode, $isImageNode, ImageNode } from '../nodes/ImageNode';
|
|
13
|
-
export const HR = {
|
|
14
|
-
dependencies: [HorizontalRuleNode],
|
|
15
|
-
export: (node) => {
|
|
16
|
-
return $isHorizontalRuleNode(node) ? '***' : null;
|
|
17
|
-
},
|
|
18
|
-
regExp: /^(---|\*\*\*|___)\s?$/,
|
|
19
|
-
replace: (parentNode, _1, _2, isImport) => {
|
|
20
|
-
const line = $createHorizontalRuleNode();
|
|
21
|
-
// TODO: Get rid of isImport flag
|
|
22
|
-
if (isImport || parentNode.getNextSibling() != null) {
|
|
23
|
-
parentNode.replace(line);
|
|
24
|
-
}
|
|
25
|
-
else {
|
|
26
|
-
parentNode.insertBefore(line);
|
|
27
|
-
}
|
|
28
|
-
line.selectNext();
|
|
29
|
-
},
|
|
30
|
-
type: 'element',
|
|
31
|
-
};
|
|
32
|
-
export const IMAGE = {
|
|
33
|
-
dependencies: [ImageNode],
|
|
34
|
-
export: (node) => {
|
|
35
|
-
if (!$isImageNode(node)) {
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
return `})`;
|
|
39
|
-
},
|
|
40
|
-
importRegExp: /!(?:\[([^[]*)\])(?:\(([^(]+)\))/,
|
|
41
|
-
regExp: /!(?:\[([^[]*)\])(?:\(([^(]+)\))$/,
|
|
42
|
-
replace: (textNode, match) => {
|
|
43
|
-
const [, altText, src] = match;
|
|
44
|
-
const imageNode = $createImageNode({
|
|
45
|
-
altText,
|
|
46
|
-
maxWidth: 800,
|
|
47
|
-
src,
|
|
48
|
-
});
|
|
49
|
-
textNode.replace(imageNode);
|
|
50
|
-
},
|
|
51
|
-
trigger: ')',
|
|
52
|
-
type: 'text-match',
|
|
53
|
-
};
|
|
54
|
-
// Very primitive table setup
|
|
55
|
-
const TABLE_ROW_REG_EXP = /^(?:\|)(.+)(?:\|)\s?$/;
|
|
56
|
-
const TABLE_ROW_DIVIDER_REG_EXP = /^(\| ?:?-*:? ?)+\|\s?$/;
|
|
57
|
-
export const TABLE = {
|
|
58
|
-
dependencies: [TableNode, TableRowNode, TableCellNode],
|
|
59
|
-
export: (node) => {
|
|
60
|
-
if (!$isTableNode(node)) {
|
|
61
|
-
return null;
|
|
62
|
-
}
|
|
63
|
-
const output = [];
|
|
64
|
-
for (const row of node.getChildren()) {
|
|
65
|
-
const rowOutput = [];
|
|
66
|
-
if (!$isTableRowNode(row)) {
|
|
67
|
-
// eslint-disable-next-line no-continue
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
let isHeaderRow = false;
|
|
71
|
-
for (const cell of row.getChildren()) {
|
|
72
|
-
// It's TableCellNode so it's just to make flow happy
|
|
73
|
-
if ($isTableCellNode(cell)) {
|
|
74
|
-
rowOutput.push($convertToMarkdownString(TRANSFORMERS, cell).replace(/\n/g, '\\n').trim());
|
|
75
|
-
if (cell.__headerState === TableCellHeaderStates.ROW) {
|
|
76
|
-
isHeaderRow = true;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
output.push(`| ${rowOutput.join(' | ')} |`);
|
|
81
|
-
if (isHeaderRow) {
|
|
82
|
-
output.push(`| ${rowOutput.map((_) => '---').join(' | ')} |`);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
return output.join('\n');
|
|
86
|
-
},
|
|
87
|
-
regExp: TABLE_ROW_REG_EXP,
|
|
88
|
-
replace: (parentNode, _1, match) => {
|
|
89
|
-
// Header row
|
|
90
|
-
if (TABLE_ROW_DIVIDER_REG_EXP.test(match[0])) {
|
|
91
|
-
const table = parentNode.getPreviousSibling();
|
|
92
|
-
if (!table || !$isTableNode(table)) {
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
const rows = table.getChildren();
|
|
96
|
-
const lastRow = rows[rows.length - 1];
|
|
97
|
-
if (!lastRow || !$isTableRowNode(lastRow)) {
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
// Add header state to row cells
|
|
101
|
-
lastRow.getChildren().forEach((cell) => {
|
|
102
|
-
if (!$isTableCellNode(cell)) {
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
cell.setHeaderStyles(TableCellHeaderStates.ROW, TableCellHeaderStates.ROW);
|
|
106
|
-
});
|
|
107
|
-
// Remove line
|
|
108
|
-
parentNode.remove();
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
const matchCells = mapToTableCells(match[0]);
|
|
112
|
-
if (matchCells == null) {
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
const rows = [matchCells];
|
|
116
|
-
let sibling = parentNode.getPreviousSibling();
|
|
117
|
-
let maxCells = matchCells.length;
|
|
118
|
-
while (sibling) {
|
|
119
|
-
if (!$isParagraphNode(sibling)) {
|
|
120
|
-
break;
|
|
121
|
-
}
|
|
122
|
-
if (sibling.getChildrenSize() !== 1) {
|
|
123
|
-
break;
|
|
124
|
-
}
|
|
125
|
-
const firstChild = sibling.getFirstChild();
|
|
126
|
-
if (!$isTextNode(firstChild)) {
|
|
127
|
-
break;
|
|
128
|
-
}
|
|
129
|
-
const cells = mapToTableCells(firstChild.getTextContent());
|
|
130
|
-
if (cells == null) {
|
|
131
|
-
break;
|
|
132
|
-
}
|
|
133
|
-
maxCells = Math.max(maxCells, cells.length);
|
|
134
|
-
rows.unshift(cells);
|
|
135
|
-
const previousSibling = sibling.getPreviousSibling();
|
|
136
|
-
sibling.remove();
|
|
137
|
-
sibling = previousSibling;
|
|
138
|
-
}
|
|
139
|
-
const table = $createTableNode();
|
|
140
|
-
for (const cells of rows) {
|
|
141
|
-
const tableRow = $createTableRowNode();
|
|
142
|
-
table.append(tableRow);
|
|
143
|
-
for (let i = 0; i < maxCells; i++) {
|
|
144
|
-
tableRow.append(i < cells.length ? cells[i] : $createTableCell(''));
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
const previousSibling = parentNode.getPreviousSibling();
|
|
148
|
-
if ($isTableNode(previousSibling) && getTableColumnsSize(previousSibling) === maxCells) {
|
|
149
|
-
previousSibling.append(...table.getChildren());
|
|
150
|
-
parentNode.remove();
|
|
151
|
-
}
|
|
152
|
-
else {
|
|
153
|
-
parentNode.replace(table);
|
|
154
|
-
}
|
|
155
|
-
table.selectEnd();
|
|
156
|
-
},
|
|
157
|
-
type: 'element',
|
|
158
|
-
};
|
|
159
|
-
function getTableColumnsSize(table) {
|
|
160
|
-
const row = table.getFirstChild();
|
|
161
|
-
return $isTableRowNode(row) ? row.getChildrenSize() : 0;
|
|
162
|
-
}
|
|
163
|
-
const $createTableCell = (textContent) => {
|
|
164
|
-
textContent = textContent.replace(/\\n/g, '\n');
|
|
165
|
-
const cell = $createTableCellNode(TableCellHeaderStates.NO_STATUS);
|
|
166
|
-
$convertFromMarkdownString(textContent, TRANSFORMERS, cell);
|
|
167
|
-
return cell;
|
|
168
|
-
};
|
|
169
|
-
const mapToTableCells = (textContent) => {
|
|
170
|
-
const match = textContent.match(TABLE_ROW_REG_EXP);
|
|
171
|
-
if (!match || !match[1]) {
|
|
172
|
-
return null;
|
|
173
|
-
}
|
|
174
|
-
return match[1].split('|').map((text) => $createTableCell(text));
|
|
175
|
-
};
|
|
176
|
-
export const TRANSFORMERS = [
|
|
177
|
-
TABLE,
|
|
178
|
-
HR,
|
|
179
|
-
IMAGE,
|
|
180
|
-
CHECK_LIST,
|
|
181
|
-
...ELEMENT_TRANSFORMERS,
|
|
182
|
-
...MULTILINE_ELEMENT_TRANSFORMERS,
|
|
183
|
-
...TEXT_FORMAT_TRANSFORMERS,
|
|
184
|
-
...TEXT_MATCH_TRANSFORMERS,
|
|
185
|
-
];
|