@lobehub/editor 1.11.0 → 1.12.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/editor-kernel/inode/helper.d.ts +9 -6
- package/es/editor-kernel/inode/helper.js +27 -0
- package/es/editor-kernel/inode/text-node.d.ts +2 -9
- package/es/plugins/code/plugin/index.d.ts +1 -1
- package/es/plugins/code/plugin/index.js +12 -1
- package/es/plugins/codeblock/command/index.d.ts +6 -0
- package/es/plugins/codeblock/command/index.js +1 -0
- package/es/plugins/codeblock/plugin/CodeHighlighterShiki.d.ts +7 -0
- package/es/plugins/codeblock/plugin/CodeHighlighterShiki.js +43 -2
- package/es/plugins/codeblock/plugin/FacadeShiki.d.ts +8 -1
- package/es/plugins/codeblock/plugin/FacadeShiki.js +95 -6
- package/es/plugins/codeblock/plugin/index.js +74 -29
- package/es/plugins/common/data-source/json-data-source.d.ts +2 -2
- package/es/plugins/common/data-source/json-data-source.js +2 -10
- package/es/plugins/common/index.d.ts +1 -1
- package/es/plugins/common/index.js +1 -1
- package/es/plugins/common/node/cursor.d.ts +3 -1
- package/es/plugins/common/node/cursor.js +9 -0
- package/es/plugins/common/plugin/index.d.ts +1 -1
- package/es/plugins/common/plugin/index.js +28 -1
- package/es/plugins/common/plugin/mdReader.d.ts +2 -0
- package/es/plugins/common/plugin/mdReader.js +59 -0
- package/es/plugins/common/react/ReactPlainText.d.ts +1 -1
- package/es/plugins/common/utils/index.d.ts +2 -2
- package/es/plugins/hr/plugin/index.js +26 -22
- package/es/plugins/link/plugin/index.js +42 -26
- package/es/plugins/list/plugin/index.js +121 -63
- package/es/plugins/list/utils/index.d.ts +3 -3
- package/es/plugins/markdown/data-source/markdown/parse.d.ts +10 -0
- package/es/plugins/markdown/data-source/markdown/parse.js +82 -0
- package/es/plugins/markdown/data-source/markdown/supersub.d.ts +1 -0
- package/es/plugins/markdown/data-source/markdown/supersub.js +14 -0
- package/es/plugins/markdown/data-source/markdown-data-source.d.ts +4 -4
- package/es/plugins/markdown/data-source/markdown-data-source.js +8 -2
- package/es/plugins/markdown/plugin/index.js +135 -2
- package/es/plugins/markdown/service/shortcut.d.ts +15 -85
- package/es/plugins/markdown/service/shortcut.js +34 -293
- package/es/plugins/markdown/service/transformers.d.ts +60 -0
- package/es/plugins/markdown/service/transformers.js +286 -0
- package/es/plugins/markdown/utils/index.d.ts +45 -1
- package/es/plugins/markdown/utils/index.js +147 -1
- package/es/plugins/markdown/utils/logger.d.ts +7 -0
- package/es/plugins/markdown/utils/logger.js +2 -0
- package/es/plugins/math/plugin/index.js +64 -45
- package/es/plugins/table/plugin/index.js +71 -26
- package/es/react/hooks/useEditorState/index.js +43 -21
- package/es/types/global.d.ts +64 -0
- package/package.json +2 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { LexicalEditor } from 'lexical';
|
|
1
|
+
import type { LexicalEditor } from 'lexical';
|
|
2
2
|
import { DataSource } from "../../../editor-kernel";
|
|
3
|
-
import { IWriteOptions } from "../../../editor-kernel/data-source";
|
|
4
|
-
import { MarkdownShortCutService } from '../service/shortcut';
|
|
3
|
+
import type { IWriteOptions } from "../../../editor-kernel/data-source";
|
|
4
|
+
import type { MarkdownShortCutService } from '../service/shortcut';
|
|
5
5
|
export default class MarkdownDataSource extends DataSource {
|
|
6
6
|
protected dataType: string;
|
|
7
7
|
protected markdownService: MarkdownShortCutService;
|
|
8
8
|
constructor(dataType: string, markdownService: MarkdownShortCutService);
|
|
9
|
-
read(): void;
|
|
9
|
+
read(editor: LexicalEditor, data: string): void;
|
|
10
10
|
write(editor: LexicalEditor, options?: IWriteOptions): any;
|
|
11
11
|
}
|
|
@@ -29,7 +29,9 @@ import { $isTableSelection } from '@lexical/table';
|
|
|
29
29
|
import { $getCharacterOffsets, $getNodeByKey, $getRoot, $getSelection, $isElementNode, $isRangeSelection, $isTextNode } from 'lexical';
|
|
30
30
|
import { DataSource } from "../../../editor-kernel";
|
|
31
31
|
import { INodeHelper } from "../../../editor-kernel/inode/helper";
|
|
32
|
+
import { logger } from "../utils/logger";
|
|
32
33
|
import { MarkdownWriterContext } from "./markdown-writer-context";
|
|
34
|
+
import { parseMarkdownToLexical } from "./markdown/parse";
|
|
33
35
|
var MarkdownDataSource = /*#__PURE__*/function (_DataSource) {
|
|
34
36
|
_inherits(MarkdownDataSource, _DataSource);
|
|
35
37
|
var _super = _createSuper(MarkdownDataSource);
|
|
@@ -43,8 +45,12 @@ var MarkdownDataSource = /*#__PURE__*/function (_DataSource) {
|
|
|
43
45
|
}
|
|
44
46
|
_createClass(MarkdownDataSource, [{
|
|
45
47
|
key: "read",
|
|
46
|
-
value: function read() {
|
|
47
|
-
|
|
48
|
+
value: function read(editor, data) {
|
|
49
|
+
var inode = {
|
|
50
|
+
root: parseMarkdownToLexical(data, this.markdownService.markdownReaders)
|
|
51
|
+
};
|
|
52
|
+
logger.debug('Parsed Lexical State:', inode);
|
|
53
|
+
editor.setEditorState(editor.parseEditorState(inode));
|
|
48
54
|
}
|
|
49
55
|
}, {
|
|
50
56
|
key: "write",
|
|
@@ -14,11 +14,12 @@ function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key i
|
|
|
14
14
|
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
|
|
15
15
|
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
16
16
|
import { $isCodeNode } from '@lexical/code';
|
|
17
|
-
import { $getNodeByKey, $getSelection, $isRangeSelection, $isTextNode, COLLABORATION_TAG, COMMAND_PRIORITY_CRITICAL, HISTORIC_TAG, KEY_ENTER_COMMAND } from 'lexical';
|
|
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 MarkdownDataSource from "../data-source/markdown-data-source";
|
|
20
|
+
import { parseMarkdownToLexical } from "../data-source/markdown/parse";
|
|
20
21
|
import { IMarkdownShortCutService, MarkdownShortCutService } from "../service/shortcut";
|
|
21
|
-
import { canContainTransformableMarkdown } from "../utils";
|
|
22
|
+
import { $generateNodesFromSerializedNodes, $insertGeneratedNodes, canContainTransformableMarkdown } from "../utils";
|
|
22
23
|
|
|
23
24
|
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
24
25
|
|
|
@@ -117,6 +118,138 @@ export var MarkdownPlugin = (_class = /*#__PURE__*/function (_KernelPlugin) {
|
|
|
117
118
|
}
|
|
118
119
|
return false;
|
|
119
120
|
}, COMMAND_PRIORITY_CRITICAL));
|
|
121
|
+
this.register(editor.registerCommand(PASTE_COMMAND, function (event) {
|
|
122
|
+
if (!(event instanceof ClipboardEvent)) return false;
|
|
123
|
+
var clipboardData = event.clipboardData;
|
|
124
|
+
if (!clipboardData) return false;
|
|
125
|
+
|
|
126
|
+
// Get plain text content
|
|
127
|
+
var text = clipboardData.getData('text/plain');
|
|
128
|
+
var html = clipboardData.getData('text/html');
|
|
129
|
+
if (!text) return false;
|
|
130
|
+
|
|
131
|
+
// Check if content contains markdown patterns
|
|
132
|
+
var hasMarkdownContent = _this2.detectMarkdownContent(text);
|
|
133
|
+
console.log('paste content analysis:', {
|
|
134
|
+
hasHTML: !!html,
|
|
135
|
+
hasMarkdown: hasMarkdownContent,
|
|
136
|
+
markdownPatterns: _this2.getMarkdownPatterns(text),
|
|
137
|
+
text: text.slice(0, 100) + (text.length > 100 ? '...' : '')
|
|
138
|
+
});
|
|
139
|
+
if (hasMarkdownContent) {
|
|
140
|
+
// Handle markdown paste
|
|
141
|
+
return _this2.handleMarkdownPaste(editor, text);
|
|
142
|
+
}
|
|
143
|
+
return false;
|
|
144
|
+
}, COMMAND_PRIORITY_CRITICAL));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Detect if text contains markdown patterns
|
|
149
|
+
*/
|
|
150
|
+
}, {
|
|
151
|
+
key: "detectMarkdownContent",
|
|
152
|
+
value: function detectMarkdownContent(text) {
|
|
153
|
+
var markdownPatterns = [
|
|
154
|
+
// Headers
|
|
155
|
+
/^#{1,6}\s+/m,
|
|
156
|
+
// Bold/italic
|
|
157
|
+
/\*{1,2}[^*]+\*{1,2}/, /__?[^_]+__?/,
|
|
158
|
+
// Code blocks
|
|
159
|
+
/```[\S\s]*```/,
|
|
160
|
+
// Inline code
|
|
161
|
+
/`[^`]+`/,
|
|
162
|
+
// Links
|
|
163
|
+
/\[[^\]]*]\([^)]+\)/,
|
|
164
|
+
// Images
|
|
165
|
+
/!\[[^\]]*]\([^)]+\)/,
|
|
166
|
+
// Lists
|
|
167
|
+
/^[*+-]\s+/m, /^\d+\.\s+/m,
|
|
168
|
+
// Blockquotes
|
|
169
|
+
/^>\s+/m,
|
|
170
|
+
// Tables
|
|
171
|
+
/\|.*\|.*\|/,
|
|
172
|
+
// Horizontal rules
|
|
173
|
+
/^---+$/m, /^\*\*\*+$/m,
|
|
174
|
+
// Strikethrough
|
|
175
|
+
/~~[^~]+~~/];
|
|
176
|
+
return markdownPatterns.some(function (pattern) {
|
|
177
|
+
return pattern.test(text);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Get specific markdown patterns found in text
|
|
183
|
+
*/
|
|
184
|
+
}, {
|
|
185
|
+
key: "getMarkdownPatterns",
|
|
186
|
+
value: function getMarkdownPatterns(text) {
|
|
187
|
+
var patterns = [{
|
|
188
|
+
name: 'headers',
|
|
189
|
+
regex: /^#{1,6}\s+/m
|
|
190
|
+
}, {
|
|
191
|
+
name: 'bold',
|
|
192
|
+
regex: /\*{2}[^*]+\*{2}/
|
|
193
|
+
}, {
|
|
194
|
+
name: 'italic',
|
|
195
|
+
regex: /\*[^*]+\*/
|
|
196
|
+
}, {
|
|
197
|
+
name: 'code-blocks',
|
|
198
|
+
regex: /```[\S\s]*```/
|
|
199
|
+
}, {
|
|
200
|
+
name: 'inline-code',
|
|
201
|
+
regex: /`[^`]+`/
|
|
202
|
+
}, {
|
|
203
|
+
name: 'links',
|
|
204
|
+
regex: /\[[^\]]*]\([^)]+\)/
|
|
205
|
+
}, {
|
|
206
|
+
name: 'images',
|
|
207
|
+
regex: /!\[[^\]]*]\([^)]+\)/
|
|
208
|
+
}, {
|
|
209
|
+
name: 'lists',
|
|
210
|
+
regex: /^[*+-]\s+/m
|
|
211
|
+
}, {
|
|
212
|
+
name: 'ordered-lists',
|
|
213
|
+
regex: /^\d+\.\s+/m
|
|
214
|
+
}, {
|
|
215
|
+
name: 'blockquotes',
|
|
216
|
+
regex: /^>\s+/m
|
|
217
|
+
}, {
|
|
218
|
+
name: 'tables',
|
|
219
|
+
regex: /\|.*\|.*\|/
|
|
220
|
+
}, {
|
|
221
|
+
name: 'horizontal-rules',
|
|
222
|
+
regex: /^---+$/m
|
|
223
|
+
}, {
|
|
224
|
+
name: 'strikethrough',
|
|
225
|
+
regex: /~~[^~]+~~/
|
|
226
|
+
}];
|
|
227
|
+
return patterns.filter(function (_ref2) {
|
|
228
|
+
var regex = _ref2.regex;
|
|
229
|
+
return regex.test(text);
|
|
230
|
+
}).map(function (_ref3) {
|
|
231
|
+
var name = _ref3.name;
|
|
232
|
+
return name;
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Handle markdown paste by parsing and inserting as structured content
|
|
238
|
+
*/
|
|
239
|
+
}, {
|
|
240
|
+
key: "handleMarkdownPaste",
|
|
241
|
+
value: function handleMarkdownPaste(editor, text) {
|
|
242
|
+
try {
|
|
243
|
+
// Use the markdown data source to parse the content
|
|
244
|
+
var root = parseMarkdownToLexical(text, this.service.markdownReaders);
|
|
245
|
+
var selection = $getSelection();
|
|
246
|
+
var nodes = $generateNodesFromSerializedNodes(root.children);
|
|
247
|
+
$insertGeneratedNodes(editor, nodes, selection);
|
|
248
|
+
return true;
|
|
249
|
+
} catch (error) {
|
|
250
|
+
console.error('Failed to handle markdown paste:', error);
|
|
251
|
+
}
|
|
252
|
+
return false;
|
|
120
253
|
}
|
|
121
254
|
}]);
|
|
122
255
|
return MarkdownPlugin;
|
|
@@ -1,60 +1,7 @@
|
|
|
1
|
-
import { ElementNode, LexicalNode,
|
|
2
|
-
import { IServiceID } from "../../../types/kernel";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
intraword?: boolean;
|
|
6
|
-
process?: (selection: RangeSelection) => void;
|
|
7
|
-
tag: string;
|
|
8
|
-
type: 'text-format';
|
|
9
|
-
}>;
|
|
10
|
-
export type TextMatchTransformer = Readonly<{
|
|
11
|
-
/**
|
|
12
|
-
* For import operations, this function can be used to determine the end index of the match, after `importRegExp` has matched.
|
|
13
|
-
* Without this function, the end index will be determined by the length of the match from `importRegExp`. Manually determining the end index can be useful if
|
|
14
|
-
* the match from `importRegExp` is not the entire text content of the node. That way, `importRegExp` can be used to match only the start of the node, and `getEndIndex`
|
|
15
|
-
* can be used to match the end of the node.
|
|
16
|
-
*
|
|
17
|
-
* @returns The end index of the match, or false if the match was unsuccessful and a different transformer should be tried.
|
|
18
|
-
*/
|
|
19
|
-
getEndIndex?: (node: TextNode, match: RegExpMatchArray) => number | false;
|
|
20
|
-
/**
|
|
21
|
-
* This regex determines what text is matched during markdown imports
|
|
22
|
-
*/
|
|
23
|
-
importRegExp?: RegExp;
|
|
24
|
-
/**
|
|
25
|
-
* This regex determines what text is matched for markdown shortcuts while typing in the editor
|
|
26
|
-
*/
|
|
27
|
-
regExp: RegExp;
|
|
28
|
-
/**
|
|
29
|
-
* Determines how the matched markdown text should be transformed into a node during the markdown import process
|
|
30
|
-
*
|
|
31
|
-
* @returns nothing, or a TextNode that may be a child of the new node that is created.
|
|
32
|
-
* If a TextNode is returned, text format matching will be applied to it (e.g. bold, italic, etc.)
|
|
33
|
-
*/
|
|
34
|
-
replace?: (node: TextNode, match: RegExpMatchArray) => void | TextNode;
|
|
35
|
-
/**
|
|
36
|
-
* Single character that allows the transformer to trigger when typed in the editor. This does not affect markdown imports outside of the markdown shortcut plugin.
|
|
37
|
-
* If the trigger is matched, the `regExp` will be used to match the text in the second step.
|
|
38
|
-
*/
|
|
39
|
-
trigger?: string;
|
|
40
|
-
type: 'text-match';
|
|
41
|
-
}>;
|
|
42
|
-
export type ElementTransformer = {
|
|
43
|
-
regExp: RegExp;
|
|
44
|
-
/**
|
|
45
|
-
* `replace` is called when markdown is imported or typed in the editor
|
|
46
|
-
*
|
|
47
|
-
* @return return false to cancel the transform, even though the regex matched. Lexical will then search for the next transformer.
|
|
48
|
-
*/
|
|
49
|
-
replace: (parentNode: ElementNode, children: Array<LexicalNode>, match: Array<string>,
|
|
50
|
-
/**
|
|
51
|
-
* Whether the match is from an import operation (e.g. through `$convertFromMarkdownString`) or not (e.g. through typing in the editor).
|
|
52
|
-
*/
|
|
53
|
-
isImport: boolean) => boolean | void;
|
|
54
|
-
trigger?: 'enter';
|
|
55
|
-
type: 'element';
|
|
56
|
-
};
|
|
57
|
-
export type Transformer = ElementTransformer | TextFormatTransformer | TextMatchTransformer;
|
|
1
|
+
import { ElementNode, LexicalNode, TextNode } from 'lexical';
|
|
2
|
+
import type { IEditorKernel, IServiceID } from "../../../types/kernel";
|
|
3
|
+
import type { TransformerRecord } from '../data-source/markdown/parse';
|
|
4
|
+
import type { Transformer } from './transformers';
|
|
58
5
|
export interface IMarkdownWriterContext {
|
|
59
6
|
/**
|
|
60
7
|
* Add processor
|
|
@@ -76,6 +23,10 @@ export interface IMarkdownWriterContext {
|
|
|
76
23
|
wrap: (before: string, after: string) => void;
|
|
77
24
|
}
|
|
78
25
|
export interface IMarkdownShortCutService {
|
|
26
|
+
/**
|
|
27
|
+
* Register Markdown reader
|
|
28
|
+
*/
|
|
29
|
+
registerMarkdownReader<K extends keyof TransformerRecord>(type: K, reader: TransformerRecord[K]): void;
|
|
79
30
|
registerMarkdownShortCut(transformer: Transformer): void;
|
|
80
31
|
registerMarkdownShortCuts(transformers: Transformer[]): void;
|
|
81
32
|
/**
|
|
@@ -93,46 +44,24 @@ export declare class MarkdownShortCutService implements IMarkdownShortCutService
|
|
|
93
44
|
private textMatchTransformers;
|
|
94
45
|
private logger;
|
|
95
46
|
private _markdownWriters;
|
|
96
|
-
|
|
97
|
-
|
|
47
|
+
private _markdownReaders;
|
|
48
|
+
constructor(kernel?: IEditorKernel | undefined);
|
|
49
|
+
get markdownWriters(): Record<string, (_ctx: IMarkdownWriterContext, _node: LexicalNode) => boolean | void>;
|
|
50
|
+
get markdownReaders(): TransformerRecord;
|
|
98
51
|
private _textFormatTransformersByTrigger;
|
|
99
52
|
private _textMatchTransformersByTrigger;
|
|
100
53
|
get textMatchTransformersByTrigger(): Readonly<Record<string, Readonly<{
|
|
101
|
-
/**
|
|
102
|
-
* For import operations, this function can be used to determine the end index of the match, after `importRegExp` has matched.
|
|
103
|
-
* Without this function, the end index will be determined by the length of the match from `importRegExp`. Manually determining the end index can be useful if
|
|
104
|
-
* the match from `importRegExp` is not the entire text content of the node. That way, `importRegExp` can be used to match only the start of the node, and `getEndIndex`
|
|
105
|
-
* can be used to match the end of the node.
|
|
106
|
-
*
|
|
107
|
-
* @returns The end index of the match, or false if the match was unsuccessful and a different transformer should be tried.
|
|
108
|
-
*/
|
|
109
54
|
getEndIndex?: ((node: TextNode, match: RegExpMatchArray) => number | false) | undefined;
|
|
110
|
-
/**
|
|
111
|
-
* This regex determines what text is matched during markdown imports
|
|
112
|
-
*/
|
|
113
55
|
importRegExp?: RegExp | undefined;
|
|
114
|
-
/**
|
|
115
|
-
* This regex determines what text is matched for markdown shortcuts while typing in the editor
|
|
116
|
-
*/
|
|
117
56
|
regExp: RegExp;
|
|
118
|
-
/**
|
|
119
|
-
* Determines how the matched markdown text should be transformed into a node during the markdown import process
|
|
120
|
-
*
|
|
121
|
-
* @returns nothing, or a TextNode that may be a child of the new node that is created.
|
|
122
|
-
* If a TextNode is returned, text format matching will be applied to it (e.g. bold, italic, etc.)
|
|
123
|
-
*/
|
|
124
57
|
replace?: ((node: TextNode, match: RegExpMatchArray) => void | TextNode) | undefined;
|
|
125
|
-
/**
|
|
126
|
-
* Single character that allows the transformer to trigger when typed in the editor. This does not affect markdown imports outside of the markdown shortcut plugin.
|
|
127
|
-
* If the trigger is matched, the `regExp` will be used to match the text in the second step.
|
|
128
|
-
*/
|
|
129
58
|
trigger?: string | undefined;
|
|
130
59
|
type: "text-match";
|
|
131
60
|
}>[]>>;
|
|
132
61
|
get textFormatTransformersByTrigger(): Readonly<Record<string, readonly Readonly<{
|
|
133
|
-
format?: readonly TextFormatType[] | undefined;
|
|
62
|
+
format?: readonly import("lexical").TextFormatType[] | undefined;
|
|
134
63
|
intraword?: boolean | undefined;
|
|
135
|
-
process?: ((selection: RangeSelection) => void) | undefined;
|
|
64
|
+
process?: ((selection: import("lexical").RangeSelection) => void) | undefined;
|
|
136
65
|
tag: string;
|
|
137
66
|
type: "text-format";
|
|
138
67
|
}>[]>>;
|
|
@@ -141,4 +70,5 @@ export declare class MarkdownShortCutService implements IMarkdownShortCutService
|
|
|
141
70
|
testTransformers(parentNode: ElementNode, anchorNode: TextNode, anchorOffset: number, trigger?: 'enter'): boolean;
|
|
142
71
|
runTransformers(parentNode: ElementNode, anchorNode: TextNode, anchorOffset: number, trigger?: 'enter'): boolean;
|
|
143
72
|
registerMarkdownWriter(type: string, writer: (ctx: IMarkdownWriterContext, node: LexicalNode) => boolean | void): void;
|
|
73
|
+
registerMarkdownReader<K extends keyof TransformerRecord>(type: K, reader: TransformerRecord[K]): void;
|
|
144
74
|
}
|