@lobehub/editor 4.4.0 → 4.5.0
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/es/locale/index.d.ts +2 -0
- package/es/locale/index.js +2 -0
- package/es/plugins/common/react/ReactPlainText.js +7 -2
- package/es/plugins/common/react/type.d.ts +11 -1
- package/es/plugins/markdown/command/index.d.ts +2 -2
- package/es/plugins/markdown/command/index.js +13 -20
- package/es/plugins/markdown/plugin/index.d.ts +11 -1
- package/es/plugins/markdown/plugin/index.js +175 -98
- package/es/plugins/markdown/react/index.js +8 -42
- package/es/react/Editor/Editor.js +7 -2
- package/es/react/Editor/type.d.ts +11 -1
- package/es/types/kernel.d.ts +6 -4
- package/package.json +1 -1
package/es/locale/index.d.ts
CHANGED
package/es/locale/index.js
CHANGED
|
@@ -28,6 +28,8 @@ export default {
|
|
|
28
28
|
unlink: 'Unlink Link'
|
|
29
29
|
},
|
|
30
30
|
markdown: {
|
|
31
|
+
autoFormatMessage: 'Markdown was converted automatically. Use Command/Ctrl + Z to undo this conversion.',
|
|
32
|
+
autoFormatTitle: 'Markdown Converted',
|
|
31
33
|
cancel: 'Cancel',
|
|
32
34
|
confirm: 'Confirm',
|
|
33
35
|
parseMessage: 'Convert to markdown format, existing content will be overwritten, confirm? (Auto close in 5 seconds)',
|
|
@@ -42,10 +42,13 @@ var ReactPlainText = /*#__PURE__*/memo(function (_ref) {
|
|
|
42
42
|
enableHotkey = _ref$enableHotkey === void 0 ? true : _ref$enableHotkey,
|
|
43
43
|
_ref$enablePasteMarkd = _ref.enablePasteMarkdown,
|
|
44
44
|
enablePasteMarkdown = _ref$enablePasteMarkd === void 0 ? true : _ref$enablePasteMarkd,
|
|
45
|
+
_ref$autoFormatMarkdo = _ref.autoFormatMarkdown,
|
|
46
|
+
autoFormatMarkdown = _ref$autoFormatMarkdo === void 0 ? true : _ref$autoFormatMarkdo,
|
|
45
47
|
_ref$markdownOption = _ref.markdownOption,
|
|
46
48
|
markdownOption = _ref$markdownOption === void 0 ? true : _ref$markdownOption,
|
|
47
49
|
_ref$pasteAsPlainText = _ref.pasteAsPlainText,
|
|
48
50
|
pasteAsPlainText = _ref$pasteAsPlainText === void 0 ? false : _ref$pasteAsPlainText,
|
|
51
|
+
pasteMarkdownAutoConvertThreshold = _ref.pasteMarkdownAutoConvertThreshold,
|
|
49
52
|
_ref$pasteVSCodeAsCod = _ref.pasteVSCodeAsCodeBlock,
|
|
50
53
|
pasteVSCodeAsCodeBlock = _ref$pasteVSCodeAsCod === void 0 ? true : _ref$pasteVSCodeAsCod,
|
|
51
54
|
onKeyDown = _ref.onKeyDown,
|
|
@@ -108,7 +111,9 @@ var ReactPlainText = /*#__PURE__*/memo(function (_ref) {
|
|
|
108
111
|
lineEmptyPlaceholder = _Children$only$props.lineEmptyPlaceholder;
|
|
109
112
|
useLayoutEffect(function () {
|
|
110
113
|
editor.registerPlugin(MarkdownPlugin, {
|
|
111
|
-
|
|
114
|
+
autoFormatMarkdown: autoFormatMarkdown,
|
|
115
|
+
enablePasteMarkdown: enablePasteMarkdown,
|
|
116
|
+
pasteMarkdownAutoConvertThreshold: pasteMarkdownAutoConvertThreshold
|
|
112
117
|
});
|
|
113
118
|
editor.registerPlugin(CommonPlugin, {
|
|
114
119
|
enableHotkey: enableHotkey,
|
|
@@ -117,7 +122,7 @@ var ReactPlainText = /*#__PURE__*/memo(function (_ref) {
|
|
|
117
122
|
pasteVSCodeAsCodeBlock: pasteVSCodeAsCodeBlock,
|
|
118
123
|
theme: restTheme ? _objectSpread(_objectSpread({}, computedThemeStyles), restTheme) : computedThemeStyles
|
|
119
124
|
});
|
|
120
|
-
}, [editor, enableHotkey, enablePasteMarkdown, markdownOption, pasteAsPlainText, pasteVSCodeAsCodeBlock, restTheme, computedThemeStyles]);
|
|
125
|
+
}, [editor, enableHotkey, enablePasteMarkdown, autoFormatMarkdown, markdownOption, pasteAsPlainText, pasteMarkdownAutoConvertThreshold, pasteVSCodeAsCodeBlock, restTheme, computedThemeStyles]);
|
|
121
126
|
useEffect(function () {
|
|
122
127
|
var _editor$getLexicalEdi;
|
|
123
128
|
var container = editorContainerRef.current;
|
|
@@ -9,12 +9,17 @@ export interface ReactEditorContentProps {
|
|
|
9
9
|
}
|
|
10
10
|
export interface ReactPlainTextProps {
|
|
11
11
|
autoFocus?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Automatically convert pasted markdown once the detection threshold is reached
|
|
14
|
+
* @default true
|
|
15
|
+
*/
|
|
16
|
+
autoFormatMarkdown?: boolean;
|
|
12
17
|
children: ReactElement<ReactEditorContentProps>;
|
|
13
18
|
className?: string;
|
|
14
19
|
editable?: boolean;
|
|
15
20
|
enableHotkey?: boolean;
|
|
16
21
|
/**
|
|
17
|
-
* Enable automatic markdown
|
|
22
|
+
* Enable automatic markdown conversion for pasted content
|
|
18
23
|
* @default true
|
|
19
24
|
*/
|
|
20
25
|
enablePasteMarkdown?: boolean;
|
|
@@ -62,6 +67,11 @@ export interface ReactPlainTextProps {
|
|
|
62
67
|
* @default false
|
|
63
68
|
*/
|
|
64
69
|
pasteAsPlainText?: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Minimum markdown score required before auto conversion runs
|
|
72
|
+
* @default 5
|
|
73
|
+
*/
|
|
74
|
+
pasteMarkdownAutoConvertThreshold?: number;
|
|
65
75
|
/**
|
|
66
76
|
* When pasting VS Code content (detected via vscode-editor-data clipboard type),
|
|
67
77
|
* create a code block with the language from VS Code instead of pasting as plain text.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { HistoryStateEntry } from '@lexical/history';
|
|
2
2
|
import { LexicalEditor } from 'lexical';
|
|
3
3
|
import { IEditorKernel } from "../../../types";
|
|
4
4
|
import { MarkdownShortCutService } from '../service/shortcut';
|
|
@@ -9,4 +9,4 @@ export declare const INSERT_MARKDOWN_COMMAND: import("lexical").LexicalCommand<{
|
|
|
9
9
|
export declare const GET_MARKDOWN_SELECTION_COMMAND: import("lexical").LexicalCommand<{
|
|
10
10
|
onResult: (startLine: number, endLine: number) => void;
|
|
11
11
|
}>;
|
|
12
|
-
export declare function registerMarkdownCommand(editor: LexicalEditor, kernel: IEditorKernel, service: MarkdownShortCutService
|
|
12
|
+
export declare function registerMarkdownCommand(editor: LexicalEditor, kernel: IEditorKernel, service: MarkdownShortCutService): () => void;
|
|
@@ -1,39 +1,30 @@
|
|
|
1
1
|
import { mergeRegister } from '@lexical/utils';
|
|
2
|
-
import { $getSelection, $isRangeSelection,
|
|
2
|
+
import { $getSelection, $isRangeSelection, COMMAND_PRIORITY_HIGH, HISTORIC_TAG, HISTORY_PUSH_TAG, createCommand } from 'lexical';
|
|
3
3
|
import { createDebugLogger } from "../../../utils/debug";
|
|
4
4
|
import { parseMarkdownToLexical } from "../data-source/markdown/parse";
|
|
5
5
|
import { $generateNodesFromSerializedNodes, $insertGeneratedNodes } from "../utils";
|
|
6
6
|
var logger = createDebugLogger('plugin', 'markdown');
|
|
7
7
|
export var INSERT_MARKDOWN_COMMAND = createCommand('INSERT_MARKDOWN_COMMAND');
|
|
8
8
|
export var GET_MARKDOWN_SELECTION_COMMAND = createCommand('GET_MARKDOWN_SELECTION_COMMAND');
|
|
9
|
-
function
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
editor.dispatchCommand(CAN_UNDO_COMMAND, false);
|
|
15
|
-
}
|
|
16
|
-
historyState.current = entry || null;
|
|
17
|
-
if (entry) {
|
|
18
|
-
editor.setEditorState(entry.editorState, {
|
|
19
|
-
tag: HISTORIC_TAG
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
return editor.getEditorState();
|
|
9
|
+
function restoreToEntry(editor, entry) {
|
|
10
|
+
if (!entry) return;
|
|
11
|
+
editor.setEditorState(entry.editorState, {
|
|
12
|
+
tag: HISTORIC_TAG
|
|
13
|
+
});
|
|
23
14
|
}
|
|
24
15
|
var SPICAL_TEXT = "\uFFF0";
|
|
25
16
|
var getLineNumber = function getLineNumber(content, charIndex) {
|
|
26
17
|
return content.slice(0, Math.max(0, charIndex)).split('\n').length;
|
|
27
18
|
};
|
|
28
|
-
export function registerMarkdownCommand(editor, kernel, service
|
|
19
|
+
export function registerMarkdownCommand(editor, kernel, service) {
|
|
29
20
|
return mergeRegister(editor.registerCommand(INSERT_MARKDOWN_COMMAND, function (payload) {
|
|
30
21
|
var markdown = payload.markdown;
|
|
31
22
|
logger.debug('INSERT_MARKDOWN_COMMAND payload:', payload);
|
|
32
|
-
|
|
33
|
-
|
|
23
|
+
restoreToEntry(editor, payload.historyState);
|
|
24
|
+
setTimeout(function () {
|
|
34
25
|
editor.update(function () {
|
|
35
26
|
try {
|
|
36
|
-
//
|
|
27
|
+
// Force a new history entry so undo returns to the raw pasted text.
|
|
37
28
|
var root = parseMarkdownToLexical(markdown, service.markdownReaders);
|
|
38
29
|
var selection = $getSelection();
|
|
39
30
|
var nodes = $generateNodesFromSerializedNodes(root.children);
|
|
@@ -43,8 +34,10 @@ export function registerMarkdownCommand(editor, kernel, service, history) {
|
|
|
43
34
|
} catch (error) {
|
|
44
35
|
logger.error('Failed to handle markdown paste:', error);
|
|
45
36
|
}
|
|
37
|
+
}, {
|
|
38
|
+
tag: HISTORY_PUSH_TAG
|
|
46
39
|
});
|
|
47
|
-
});
|
|
40
|
+
}, 0);
|
|
48
41
|
return false;
|
|
49
42
|
}, COMMAND_PRIORITY_HIGH // Priority
|
|
50
43
|
), editor.registerCommand(GET_MARKDOWN_SELECTION_COMMAND, function (payload) {
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import { IEditorPluginConstructor } from "../../../types";
|
|
2
2
|
export interface MarkdownPluginOptions {
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Automatically convert pasted markdown once the detection threshold is reached
|
|
5
|
+
* @default true
|
|
6
|
+
*/
|
|
7
|
+
autoFormatMarkdown?: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Enable automatic markdown conversion for pasted content
|
|
5
10
|
* @default true
|
|
6
11
|
*/
|
|
7
12
|
enablePasteMarkdown?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Minimum markdown score required before auto conversion runs
|
|
15
|
+
* @default 5
|
|
16
|
+
*/
|
|
17
|
+
pasteMarkdownAutoConvertThreshold?: number;
|
|
8
18
|
}
|
|
9
19
|
export declare const MarkdownPlugin: IEditorPluginConstructor<MarkdownPluginOptions>;
|
|
@@ -17,11 +17,116 @@ import { $isCodeNode } from '@lexical/code';
|
|
|
17
17
|
import { $getNodeByKey, $getSelection, $isRangeSelection, $isTextNode, COLLABORATION_TAG, COMMAND_PRIORITY_CRITICAL, HISTORIC_TAG, KEY_ENTER_COMMAND, PASTE_COMMAND } from 'lexical';
|
|
18
18
|
import { KernelPlugin } from "../../../editor-kernel/plugin";
|
|
19
19
|
import { createDebugLogger } from "../../../utils/debug";
|
|
20
|
-
import { registerMarkdownCommand } from "../command";
|
|
20
|
+
import { INSERT_MARKDOWN_COMMAND, registerMarkdownCommand } from "../command";
|
|
21
21
|
import MarkdownDataSource from "../data-source/markdown-data-source";
|
|
22
22
|
import { IMarkdownShortCutService, MarkdownShortCutService } from "../service/shortcut";
|
|
23
23
|
import { canContainTransformableMarkdown } from "../utils";
|
|
24
24
|
import { detectCodeLanguage, detectLanguage } from "../utils/detectLanguage";
|
|
25
|
+
var DEFAULT_PASTE_MARKDOWN_AUTO_CONVERT_THRESHOLD = 5;
|
|
26
|
+
var RICH_HTML_SELECTOR = 'strong,em,b,i,h1,h2,h3,h4,h5,h6,ul,ol,table,img,blockquote,pre>code,a[href]';
|
|
27
|
+
var MARKDOWN_DETECTION_RULES = [{
|
|
28
|
+
name: 'headers',
|
|
29
|
+
score: 5,
|
|
30
|
+
test: function test(text) {
|
|
31
|
+
return /^#{1,6}\s+\S/m.test(text);
|
|
32
|
+
}
|
|
33
|
+
}, {
|
|
34
|
+
name: 'code-fence-start',
|
|
35
|
+
score: 5,
|
|
36
|
+
test: function test(text) {
|
|
37
|
+
return /^```[\w-]*$/m.test(text);
|
|
38
|
+
}
|
|
39
|
+
}, {
|
|
40
|
+
name: 'links',
|
|
41
|
+
score: 4,
|
|
42
|
+
test: function test(text) {
|
|
43
|
+
return /\[[^\]]+]\([^)]+\)/.test(text);
|
|
44
|
+
}
|
|
45
|
+
}, {
|
|
46
|
+
name: 'images',
|
|
47
|
+
score: 5,
|
|
48
|
+
test: function test(text) {
|
|
49
|
+
return /!\[[^\]]*]\([^)]+\)/.test(text);
|
|
50
|
+
}
|
|
51
|
+
}, {
|
|
52
|
+
name: 'tables',
|
|
53
|
+
score: 5,
|
|
54
|
+
test: function test(text) {
|
|
55
|
+
return /^\|.+\|$/m.test(text) && /^\|[\s:|-]+\|$/m.test(text);
|
|
56
|
+
}
|
|
57
|
+
}, {
|
|
58
|
+
name: 'admonitions',
|
|
59
|
+
score: 5,
|
|
60
|
+
test: function test(text) {
|
|
61
|
+
return /^>\s*\[!(?:note|tip|warning|caution|important)]/im.test(text);
|
|
62
|
+
}
|
|
63
|
+
}, {
|
|
64
|
+
name: 'task-lists',
|
|
65
|
+
score: 4,
|
|
66
|
+
test: function test(text) {
|
|
67
|
+
return /^[*-]\s+\[[ x]]/m.test(text);
|
|
68
|
+
}
|
|
69
|
+
}, {
|
|
70
|
+
name: 'bold',
|
|
71
|
+
score: 2,
|
|
72
|
+
test: function test(text) {
|
|
73
|
+
return /\*\*.+?\*\*/.test(text);
|
|
74
|
+
}
|
|
75
|
+
}, {
|
|
76
|
+
name: 'italic',
|
|
77
|
+
score: 1,
|
|
78
|
+
test: function test(text) {
|
|
79
|
+
return /(?<!\*)\*(?!\*)(?!\s).+?(?<!\s)(?<!\*)\*(?!\*)/.test(text);
|
|
80
|
+
}
|
|
81
|
+
}, {
|
|
82
|
+
name: 'unordered-lists',
|
|
83
|
+
score: 1,
|
|
84
|
+
test: function test(text) {
|
|
85
|
+
return /^[*+-]\s+\S/m.test(text);
|
|
86
|
+
}
|
|
87
|
+
}, {
|
|
88
|
+
name: 'ordered-lists',
|
|
89
|
+
score: 1,
|
|
90
|
+
test: function test(text) {
|
|
91
|
+
return /^\d+\.\s+\S/m.test(text);
|
|
92
|
+
}
|
|
93
|
+
}, {
|
|
94
|
+
name: 'blockquotes',
|
|
95
|
+
score: 1,
|
|
96
|
+
test: function test(text) {
|
|
97
|
+
return /^>\s+\S/m.test(text);
|
|
98
|
+
}
|
|
99
|
+
}, {
|
|
100
|
+
name: 'inline-code',
|
|
101
|
+
score: 1,
|
|
102
|
+
test: function test(text) {
|
|
103
|
+
return /`.+?`/.test(text);
|
|
104
|
+
}
|
|
105
|
+
}, {
|
|
106
|
+
name: 'horizontal-rules',
|
|
107
|
+
score: 2,
|
|
108
|
+
test: function test(text) {
|
|
109
|
+
return /^[*_-]{3,}$/m.test(text);
|
|
110
|
+
}
|
|
111
|
+
}, {
|
|
112
|
+
name: 'multi-paragraph',
|
|
113
|
+
score: 5,
|
|
114
|
+
test: function test(text) {
|
|
115
|
+
return text.split(/\n{2,}/).filter(Boolean).length >= 2;
|
|
116
|
+
}
|
|
117
|
+
}, {
|
|
118
|
+
name: 'short-text-penalty',
|
|
119
|
+
score: -3,
|
|
120
|
+
test: function test(text) {
|
|
121
|
+
return text.length < 20;
|
|
122
|
+
}
|
|
123
|
+
}, {
|
|
124
|
+
name: 'single-line-penalty',
|
|
125
|
+
score: -2,
|
|
126
|
+
test: function test(text) {
|
|
127
|
+
return !text.includes('\n');
|
|
128
|
+
}
|
|
129
|
+
}];
|
|
25
130
|
export var MarkdownPlugin = (_class = /*#__PURE__*/function (_KernelPlugin) {
|
|
26
131
|
_inherits(MarkdownPlugin, _KernelPlugin);
|
|
27
132
|
var _super = _createSuper(MarkdownPlugin);
|
|
@@ -122,6 +227,7 @@ export var MarkdownPlugin = (_class = /*#__PURE__*/function (_KernelPlugin) {
|
|
|
122
227
|
}, COMMAND_PRIORITY_CRITICAL));
|
|
123
228
|
this.register(editor.registerCommand(PASTE_COMMAND, function (event) {
|
|
124
229
|
if (!(event instanceof ClipboardEvent)) return false;
|
|
230
|
+
if (!_this2.shouldHandlePasteMarkdown()) return false;
|
|
125
231
|
var clipboardData = event.clipboardData;
|
|
126
232
|
if (!clipboardData) return false;
|
|
127
233
|
|
|
@@ -139,33 +245,39 @@ export var MarkdownPlugin = (_class = /*#__PURE__*/function (_KernelPlugin) {
|
|
|
139
245
|
htmlLength: (html === null || html === void 0 ? void 0 : html.length) || 0,
|
|
140
246
|
textLength: text.length
|
|
141
247
|
});
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
248
|
+
if (_this2.hasRichHTML(clipboardData)) {
|
|
249
|
+
_this2.logger.debug('rich HTML detected, skipping markdown auto-convert');
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
var detectionResult = _this2.getMarkdownDetectionResult(text);
|
|
253
|
+
if (detectionResult.shouldAutoConvert) {
|
|
254
|
+
_this2.logger.debug('markdown auto-convert detected:', detectionResult);
|
|
148
255
|
var historyState = _this2.kernel.getHistoryState().current;
|
|
149
256
|
setTimeout(function () {
|
|
257
|
+
editor.dispatchCommand(INSERT_MARKDOWN_COMMAND, {
|
|
258
|
+
historyState: historyState,
|
|
259
|
+
markdown: text
|
|
260
|
+
});
|
|
150
261
|
_this2.kernel.emit('markdownParse', {
|
|
151
262
|
cacheState: editor.getEditorState(),
|
|
152
263
|
historyState: historyState,
|
|
153
|
-
markdown: text
|
|
264
|
+
markdown: text,
|
|
265
|
+
matchedPatterns: detectionResult.matchedPatterns,
|
|
266
|
+
score: detectionResult.score
|
|
154
267
|
});
|
|
155
268
|
}, 10);
|
|
156
269
|
} else {
|
|
157
|
-
|
|
158
|
-
_this2.logger.debug('no markdown patterns detected, keeping as plain text');
|
|
270
|
+
_this2.logger.debug('markdown score below auto-convert threshold, keeping as plain text:', detectionResult);
|
|
159
271
|
}
|
|
160
272
|
return false;
|
|
161
273
|
}, COMMAND_PRIORITY_CRITICAL));
|
|
162
|
-
this.register(registerMarkdownCommand(editor, this.kernel, this.service
|
|
274
|
+
this.register(registerMarkdownCommand(editor, this.kernel, this.service));
|
|
163
275
|
}
|
|
164
276
|
|
|
165
277
|
/**
|
|
166
278
|
* Detect if content is code and should be inserted as code block
|
|
167
279
|
* Uses advanced language detection with pattern matching
|
|
168
|
-
* Excludes markdown
|
|
280
|
+
* Excludes markdown because markdown content is handled by the paste auto-convert flow
|
|
169
281
|
*/
|
|
170
282
|
}, {
|
|
171
283
|
key: "detectCodeContent",
|
|
@@ -173,7 +285,7 @@ export var MarkdownPlugin = (_class = /*#__PURE__*/function (_KernelPlugin) {
|
|
|
173
285
|
// Use the advanced language detector
|
|
174
286
|
var detected = detectLanguage(text);
|
|
175
287
|
if (detected && detected.confidence > 50) {
|
|
176
|
-
// Don't insert markdown as code block - it should
|
|
288
|
+
// Don't insert markdown as code block - it should use the markdown auto-convert flow
|
|
177
289
|
if (detected.language === 'markdown') {
|
|
178
290
|
return null;
|
|
179
291
|
}
|
|
@@ -195,99 +307,64 @@ export var MarkdownPlugin = (_class = /*#__PURE__*/function (_KernelPlugin) {
|
|
|
195
307
|
}
|
|
196
308
|
return null;
|
|
197
309
|
}
|
|
310
|
+
}, {
|
|
311
|
+
key: "hasRichHTML",
|
|
312
|
+
value: function hasRichHTML(clipboardData) {
|
|
313
|
+
var html = clipboardData.getData('text/html');
|
|
314
|
+
if (!html) return false;
|
|
315
|
+
if (/data-vscode|vscode-/i.test(html)) return false;
|
|
316
|
+
if (typeof DOMParser === 'undefined') return false;
|
|
317
|
+
var doc = new DOMParser().parseFromString(html, 'text/html');
|
|
318
|
+
var richTags = doc.body.querySelectorAll(RICH_HTML_SELECTOR);
|
|
319
|
+
return richTags.length > 0;
|
|
320
|
+
}
|
|
198
321
|
|
|
199
322
|
/**
|
|
200
|
-
*
|
|
201
|
-
* Returns false if content is likely code (will be handled by detectCodeContent)
|
|
323
|
+
* Analyze pasted text and determine whether it should auto convert to markdown
|
|
202
324
|
*/
|
|
203
325
|
}, {
|
|
204
|
-
key: "
|
|
205
|
-
value: function
|
|
206
|
-
// If code is detected, don't treat as markdown
|
|
326
|
+
key: "getMarkdownDetectionResult",
|
|
327
|
+
value: function getMarkdownDetectionResult(text) {
|
|
207
328
|
if (this.detectCodeContent(text)) {
|
|
208
|
-
return
|
|
329
|
+
return {
|
|
330
|
+
matchedPatterns: [],
|
|
331
|
+
score: 0,
|
|
332
|
+
shouldAutoConvert: false
|
|
333
|
+
};
|
|
209
334
|
}
|
|
210
|
-
var
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
/^[*+-]\s+/m, /^\d+\.\s+/m,
|
|
225
|
-
// Blockquotes
|
|
226
|
-
/^>\s+/m,
|
|
227
|
-
// Tables
|
|
228
|
-
/\|.*\|.*\|/,
|
|
229
|
-
// Horizontal rules
|
|
230
|
-
/^---+$/m, /^\*\*\*+$/m,
|
|
231
|
-
// Strikethrough
|
|
232
|
-
/~~[^~]+~~/];
|
|
233
|
-
return markdownPatterns.some(function (pattern) {
|
|
234
|
-
return pattern.test(text);
|
|
235
|
-
});
|
|
335
|
+
var matchedPatterns = [];
|
|
336
|
+
var score = 0;
|
|
337
|
+
var threshold = this.getPasteMarkdownAutoConvertThreshold();
|
|
338
|
+
for (var _i = 0, _MARKDOWN_DETECTION_R = MARKDOWN_DETECTION_RULES; _i < _MARKDOWN_DETECTION_R.length; _i++) {
|
|
339
|
+
var rule = _MARKDOWN_DETECTION_R[_i];
|
|
340
|
+
if (!rule.test(text)) continue;
|
|
341
|
+
matchedPatterns.push(rule.name);
|
|
342
|
+
score += rule.score;
|
|
343
|
+
}
|
|
344
|
+
return {
|
|
345
|
+
matchedPatterns: matchedPatterns,
|
|
346
|
+
score: score,
|
|
347
|
+
shouldAutoConvert: score >= threshold
|
|
348
|
+
};
|
|
236
349
|
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Get specific markdown patterns found in text
|
|
240
|
-
*/
|
|
241
350
|
}, {
|
|
242
|
-
key: "
|
|
243
|
-
value: function
|
|
244
|
-
var
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}, {
|
|
260
|
-
name: 'links',
|
|
261
|
-
regex: /\[[^\]]*]\([^)]+\)/
|
|
262
|
-
}, {
|
|
263
|
-
name: 'images',
|
|
264
|
-
regex: /!\[[^\]]*]\([^)]+\)/
|
|
265
|
-
}, {
|
|
266
|
-
name: 'lists',
|
|
267
|
-
regex: /^[*+-]\s+/m
|
|
268
|
-
}, {
|
|
269
|
-
name: 'ordered-lists',
|
|
270
|
-
regex: /^\d+\.\s+/m
|
|
271
|
-
}, {
|
|
272
|
-
name: 'blockquotes',
|
|
273
|
-
regex: /^>\s+/m
|
|
274
|
-
}, {
|
|
275
|
-
name: 'tables',
|
|
276
|
-
regex: /\|.*\|.*\|/
|
|
277
|
-
}, {
|
|
278
|
-
name: 'horizontal-rules',
|
|
279
|
-
regex: /^---+$/m
|
|
280
|
-
}, {
|
|
281
|
-
name: 'strikethrough',
|
|
282
|
-
regex: /~~[^~]+~~/
|
|
283
|
-
}];
|
|
284
|
-
return patterns.filter(function (_ref2) {
|
|
285
|
-
var regex = _ref2.regex;
|
|
286
|
-
return regex.test(text);
|
|
287
|
-
}).map(function (_ref3) {
|
|
288
|
-
var name = _ref3.name;
|
|
289
|
-
return name;
|
|
290
|
-
});
|
|
351
|
+
key: "getPasteMarkdownAutoConvertThreshold",
|
|
352
|
+
value: function getPasteMarkdownAutoConvertThreshold() {
|
|
353
|
+
var _this$config;
|
|
354
|
+
var threshold = (_this$config = this.config) === null || _this$config === void 0 ? void 0 : _this$config.pasteMarkdownAutoConvertThreshold;
|
|
355
|
+
if (typeof threshold !== 'number' || Number.isNaN(threshold)) {
|
|
356
|
+
return DEFAULT_PASTE_MARKDOWN_AUTO_CONVERT_THRESHOLD;
|
|
357
|
+
}
|
|
358
|
+
return Math.max(1, threshold);
|
|
359
|
+
}
|
|
360
|
+
}, {
|
|
361
|
+
key: "shouldHandlePasteMarkdown",
|
|
362
|
+
value: function shouldHandlePasteMarkdown() {
|
|
363
|
+
var _this$config2, _this$config3;
|
|
364
|
+
if (((_this$config2 = this.config) === null || _this$config2 === void 0 ? void 0 : _this$config2.enablePasteMarkdown) === false) {
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
return ((_this$config3 = this.config) === null || _this$config3 === void 0 ? void 0 : _this$config3.autoFormatMarkdown) !== false;
|
|
291
368
|
}
|
|
292
369
|
|
|
293
370
|
// /**
|
|
@@ -6,67 +6,33 @@ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o =
|
|
|
6
6
|
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
|
|
7
7
|
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
|
|
8
8
|
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
|
|
9
|
-
import {
|
|
10
|
-
import { Space, notification } from 'antd';
|
|
9
|
+
import { toast } from '@lobehub/ui';
|
|
11
10
|
import { useLayoutEffect } from 'react';
|
|
12
11
|
import { useLexicalComposerContext } from "../../../editor-kernel/react";
|
|
13
12
|
import { useTranslation } from "../../../editor-kernel/react/useTranslation";
|
|
14
13
|
import { INodePlugin } from "../../inode";
|
|
15
|
-
import { INSERT_MARKDOWN_COMMAND } from "../command";
|
|
16
14
|
import { MarkdownPlugin } from "../plugin";
|
|
17
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
18
|
-
import { jsxs as _jsxs } from "react/jsx-runtime";
|
|
19
15
|
var ReactMarkdownPlugin = function ReactMarkdownPlugin() {
|
|
20
16
|
var _useLexicalComposerCo = useLexicalComposerContext(),
|
|
21
17
|
_useLexicalComposerCo2 = _slicedToArray(_useLexicalComposerCo, 1),
|
|
22
18
|
editor = _useLexicalComposerCo2[0];
|
|
23
|
-
var _notification$useNoti = notification.useNotification(),
|
|
24
|
-
_notification$useNoti2 = _slicedToArray(_notification$useNoti, 2),
|
|
25
|
-
api = _notification$useNoti2[0],
|
|
26
|
-
contextHolder = _notification$useNoti2[1];
|
|
27
19
|
var t = useTranslation();
|
|
28
20
|
useLayoutEffect(function () {
|
|
29
21
|
editor.registerPlugin(INodePlugin);
|
|
30
22
|
editor.registerPlugin(MarkdownPlugin);
|
|
31
|
-
var handleEvent = function handleEvent(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
children: [/*#__PURE__*/_jsx(Button, {
|
|
37
|
-
onClick: function onClick() {
|
|
38
|
-
return api.destroy();
|
|
39
|
-
},
|
|
40
|
-
size: "small",
|
|
41
|
-
children: t('markdown.cancel')
|
|
42
|
-
}), /*#__PURE__*/_jsx(Button, {
|
|
43
|
-
onClick: function onClick() {
|
|
44
|
-
editor.dispatchCommand(INSERT_MARKDOWN_COMMAND, {
|
|
45
|
-
historyState: historyState,
|
|
46
|
-
markdown: markdown
|
|
47
|
-
});
|
|
48
|
-
api.destroy();
|
|
49
|
-
},
|
|
50
|
-
size: "small",
|
|
51
|
-
type: "primary",
|
|
52
|
-
children: t('markdown.confirm')
|
|
53
|
-
})]
|
|
54
|
-
});
|
|
55
|
-
api.open({
|
|
56
|
-
actions: actions,
|
|
57
|
-
description: t('markdown.parseMessage'),
|
|
58
|
-
duration: 5,
|
|
59
|
-
key: key,
|
|
60
|
-
message: t('markdown.parseTitle'),
|
|
61
|
-
showProgress: true
|
|
23
|
+
var handleEvent = function handleEvent() {
|
|
24
|
+
toast.info({
|
|
25
|
+
description: t('markdown.autoFormatMessage'),
|
|
26
|
+
duration: 5000,
|
|
27
|
+
title: t('markdown.autoFormatTitle')
|
|
62
28
|
});
|
|
63
29
|
};
|
|
64
30
|
editor.on('markdownParse', handleEvent);
|
|
65
31
|
return function () {
|
|
66
32
|
editor.off('markdownParse', handleEvent);
|
|
67
33
|
};
|
|
68
|
-
}, [editor]);
|
|
69
|
-
return
|
|
34
|
+
}, [editor, t]);
|
|
35
|
+
return null;
|
|
70
36
|
};
|
|
71
37
|
ReactMarkdownPlugin.displayName = 'ReactMarkdownPlugin';
|
|
72
38
|
export default ReactMarkdownPlugin;
|
|
@@ -57,8 +57,11 @@ var Editor = /*#__PURE__*/memo(function (_ref) {
|
|
|
57
57
|
autoFocus = _ref.autoFocus,
|
|
58
58
|
_ref$enablePasteMarkd = _ref.enablePasteMarkdown,
|
|
59
59
|
enablePasteMarkdown = _ref$enablePasteMarkd === void 0 ? true : _ref$enablePasteMarkd,
|
|
60
|
+
_ref$autoFormatMarkdo = _ref.autoFormatMarkdown,
|
|
61
|
+
autoFormatMarkdown = _ref$autoFormatMarkdo === void 0 ? true : _ref$autoFormatMarkdo,
|
|
60
62
|
_ref$markdownOption = _ref.markdownOption,
|
|
61
63
|
markdownOption = _ref$markdownOption === void 0 ? true : _ref$markdownOption,
|
|
64
|
+
pasteMarkdownAutoConvertThreshold = _ref.pasteMarkdownAutoConvertThreshold,
|
|
62
65
|
_ref$pasteAsPlainText = _ref.pasteAsPlainText,
|
|
63
66
|
pasteAsPlainText = _ref$pasteAsPlainText === void 0 ? false : _ref$pasteAsPlainText,
|
|
64
67
|
_ref$pasteVSCodeAsCod = _ref.pasteVSCodeAsCodeBlock,
|
|
@@ -82,7 +85,7 @@ var Editor = /*#__PURE__*/memo(function (_ref) {
|
|
|
82
85
|
return onTextChange ? debounce(onTextChange, debounceWait) : undefined;
|
|
83
86
|
}, [onTextChange, debounceWait]);
|
|
84
87
|
var memoPlugins = useMemo(function () {
|
|
85
|
-
return [enablePasteMarkdown && ReactMarkdownPlugin].concat(_toConsumableArray(plugins)).filter(Boolean).map(function (plugin, index) {
|
|
88
|
+
return [enablePasteMarkdown && autoFormatMarkdown && ReactMarkdownPlugin].concat(_toConsumableArray(plugins)).filter(Boolean).map(function (plugin, index) {
|
|
86
89
|
var withNoProps = typeof plugin === 'function';
|
|
87
90
|
if (withNoProps) return /*#__PURE__*/createElement(plugin, {
|
|
88
91
|
key: index
|
|
@@ -91,7 +94,7 @@ var Editor = /*#__PURE__*/memo(function (_ref) {
|
|
|
91
94
|
key: index
|
|
92
95
|
}, plugin[1]));
|
|
93
96
|
});
|
|
94
|
-
}, [plugins, enablePasteMarkdown, ReactMarkdownPlugin]);
|
|
97
|
+
}, [plugins, enablePasteMarkdown, autoFormatMarkdown, ReactMarkdownPlugin]);
|
|
95
98
|
var memoMention = useMemo(function () {
|
|
96
99
|
if (!enableMention) return;
|
|
97
100
|
return /*#__PURE__*/_jsx(ReactMentionPlugin, {
|
|
@@ -119,6 +122,7 @@ var Editor = /*#__PURE__*/memo(function (_ref) {
|
|
|
119
122
|
onInit: onInit,
|
|
120
123
|
children: [memoPlugins, memoSlash, memoMention, /*#__PURE__*/_jsx(ReactPlainText, {
|
|
121
124
|
autoFocus: autoFocus,
|
|
125
|
+
autoFormatMarkdown: autoFormatMarkdown,
|
|
122
126
|
className: className,
|
|
123
127
|
editable: editable,
|
|
124
128
|
enablePasteMarkdown: enablePasteMarkdown,
|
|
@@ -133,6 +137,7 @@ var Editor = /*#__PURE__*/memo(function (_ref) {
|
|
|
133
137
|
onPressEnter: onPressEnter,
|
|
134
138
|
onTextChange: debouncedOnTextChange,
|
|
135
139
|
pasteAsPlainText: pasteAsPlainText,
|
|
140
|
+
pasteMarkdownAutoConvertThreshold: pasteMarkdownAutoConvertThreshold,
|
|
136
141
|
pasteVSCodeAsCodeBlock: pasteVSCodeAsCodeBlock,
|
|
137
142
|
style: style,
|
|
138
143
|
variant: variant,
|
|
@@ -9,6 +9,11 @@ interface MentionOption extends Partial<ReactSlashOptionProps> {
|
|
|
9
9
|
}
|
|
10
10
|
export interface EditorProps extends Partial<ReactEditorContentProps>, Omit<ReactPlainTextProps, 'children'> {
|
|
11
11
|
autoFocus?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Automatically convert pasted markdown once the detection threshold is reached
|
|
14
|
+
* @default true
|
|
15
|
+
*/
|
|
16
|
+
autoFormatMarkdown?: boolean;
|
|
12
17
|
children?: ReactNode;
|
|
13
18
|
className?: string;
|
|
14
19
|
/**
|
|
@@ -19,7 +24,7 @@ export interface EditorProps extends Partial<ReactEditorContentProps>, Omit<Reac
|
|
|
19
24
|
editable?: boolean;
|
|
20
25
|
editor?: IEditor;
|
|
21
26
|
/**
|
|
22
|
-
* Enable automatic markdown
|
|
27
|
+
* Enable automatic markdown conversion for pasted content
|
|
23
28
|
* @default true
|
|
24
29
|
*/
|
|
25
30
|
enablePasteMarkdown?: boolean;
|
|
@@ -42,6 +47,11 @@ export interface EditorProps extends Partial<ReactEditorContentProps>, Omit<Reac
|
|
|
42
47
|
* Unlike onChange, this won't trigger on cursor movement or selection changes
|
|
43
48
|
*/
|
|
44
49
|
onTextChange?: (editor: IEditor) => void;
|
|
50
|
+
/**
|
|
51
|
+
* Minimum markdown score required before auto conversion runs
|
|
52
|
+
* @default 5
|
|
53
|
+
*/
|
|
54
|
+
pasteMarkdownAutoConvertThreshold?: number;
|
|
45
55
|
plugins?: EditorPlugin[];
|
|
46
56
|
slashOption?: Partial<ReactSlashOptionProps>;
|
|
47
57
|
/** Force slash menu placement direction, skipping auto-flip detection */
|
package/es/types/kernel.d.ts
CHANGED
|
@@ -57,6 +57,8 @@ export interface IKernelEventMap {
|
|
|
57
57
|
cacheState: EditorState;
|
|
58
58
|
historyState: HistoryStateEntry | null;
|
|
59
59
|
markdown: string;
|
|
60
|
+
matchedPatterns: string[];
|
|
61
|
+
score: number;
|
|
60
62
|
}) => void;
|
|
61
63
|
/**
|
|
62
64
|
* handle paste event
|
|
@@ -93,6 +95,10 @@ export interface IEditor {
|
|
|
93
95
|
* Get editor content of specified type
|
|
94
96
|
*/
|
|
95
97
|
getDocument(type: string): DataSource | undefined;
|
|
98
|
+
/**
|
|
99
|
+
* Lexical history state (undo stack metadata)
|
|
100
|
+
*/
|
|
101
|
+
getHistoryState(): HistoryState;
|
|
96
102
|
/**
|
|
97
103
|
* Get Lexical editor instance
|
|
98
104
|
*/
|
|
@@ -249,10 +255,6 @@ export interface IEditorKernel extends IEditor {
|
|
|
249
255
|
* @param name
|
|
250
256
|
*/
|
|
251
257
|
getDecorator(name: string): IDecorator | undefined;
|
|
252
|
-
/**
|
|
253
|
-
* Get editor history state
|
|
254
|
-
*/
|
|
255
|
-
getHistoryState(): HistoryState;
|
|
256
258
|
/**
|
|
257
259
|
* Get all registered decorator names
|
|
258
260
|
*/
|
package/package.json
CHANGED