@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.
Files changed (48) hide show
  1. package/es/editor-kernel/inode/helper.d.ts +9 -6
  2. package/es/editor-kernel/inode/helper.js +27 -0
  3. package/es/editor-kernel/inode/text-node.d.ts +2 -9
  4. package/es/plugins/code/plugin/index.d.ts +1 -1
  5. package/es/plugins/code/plugin/index.js +12 -1
  6. package/es/plugins/codeblock/command/index.d.ts +6 -0
  7. package/es/plugins/codeblock/command/index.js +1 -0
  8. package/es/plugins/codeblock/plugin/CodeHighlighterShiki.d.ts +7 -0
  9. package/es/plugins/codeblock/plugin/CodeHighlighterShiki.js +43 -2
  10. package/es/plugins/codeblock/plugin/FacadeShiki.d.ts +8 -1
  11. package/es/plugins/codeblock/plugin/FacadeShiki.js +95 -6
  12. package/es/plugins/codeblock/plugin/index.js +74 -29
  13. package/es/plugins/common/data-source/json-data-source.d.ts +2 -2
  14. package/es/plugins/common/data-source/json-data-source.js +2 -10
  15. package/es/plugins/common/index.d.ts +1 -1
  16. package/es/plugins/common/index.js +1 -1
  17. package/es/plugins/common/node/cursor.d.ts +3 -1
  18. package/es/plugins/common/node/cursor.js +9 -0
  19. package/es/plugins/common/plugin/index.d.ts +1 -1
  20. package/es/plugins/common/plugin/index.js +28 -1
  21. package/es/plugins/common/plugin/mdReader.d.ts +2 -0
  22. package/es/plugins/common/plugin/mdReader.js +59 -0
  23. package/es/plugins/common/react/ReactPlainText.d.ts +1 -1
  24. package/es/plugins/common/utils/index.d.ts +2 -2
  25. package/es/plugins/hr/plugin/index.js +26 -22
  26. package/es/plugins/link/plugin/index.js +42 -26
  27. package/es/plugins/list/plugin/index.js +121 -63
  28. package/es/plugins/list/utils/index.d.ts +3 -3
  29. package/es/plugins/markdown/data-source/markdown/parse.d.ts +10 -0
  30. package/es/plugins/markdown/data-source/markdown/parse.js +82 -0
  31. package/es/plugins/markdown/data-source/markdown/supersub.d.ts +1 -0
  32. package/es/plugins/markdown/data-source/markdown/supersub.js +14 -0
  33. package/es/plugins/markdown/data-source/markdown-data-source.d.ts +4 -4
  34. package/es/plugins/markdown/data-source/markdown-data-source.js +8 -2
  35. package/es/plugins/markdown/plugin/index.js +135 -2
  36. package/es/plugins/markdown/service/shortcut.d.ts +15 -85
  37. package/es/plugins/markdown/service/shortcut.js +34 -293
  38. package/es/plugins/markdown/service/transformers.d.ts +60 -0
  39. package/es/plugins/markdown/service/transformers.js +286 -0
  40. package/es/plugins/markdown/utils/index.d.ts +45 -1
  41. package/es/plugins/markdown/utils/index.js +147 -1
  42. package/es/plugins/markdown/utils/logger.d.ts +7 -0
  43. package/es/plugins/markdown/utils/logger.js +2 -0
  44. package/es/plugins/math/plugin/index.js +64 -45
  45. package/es/plugins/table/plugin/index.js +71 -26
  46. package/es/react/hooks/useEditorState/index.js +43 -21
  47. package/es/types/global.d.ts +64 -0
  48. 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
- throw new Error('MarkdownDataSource not implemented yet!');
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, RangeSelection, TextFormatType, TextNode } from 'lexical';
2
- import { IServiceID } from "../../../types/kernel";
3
- export type TextFormatTransformer = Readonly<{
4
- format?: ReadonlyArray<TextFormatType>;
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
- constructor(kernel?: import("../../../types/kernel").IEditorKernel | undefined);
97
- get markdownWriters(): Record<string, (ctx: IMarkdownWriterContext, node: LexicalNode) => boolean | void>;
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
  }