@lobehub/editor 1.17.0 → 1.17.2

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.
@@ -24,7 +24,7 @@ function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e
24
24
  import { registerDragonSupport } from '@lexical/dragon';
25
25
  import { createEmptyHistoryState, registerHistory } from '@lexical/history';
26
26
  import { $createHeadingNode, $createQuoteNode, $isHeadingNode, $isQuoteNode, HeadingNode, QuoteNode, registerRichText } from '@lexical/rich-text';
27
- import { $createLineBreakNode, $createParagraphNode, $isTextNode } from 'lexical';
27
+ import { $createLineBreakNode, $createParagraphNode, $isTextNode, COMMAND_PRIORITY_HIGH, INSERT_LINE_BREAK_COMMAND, INSERT_PARAGRAPH_COMMAND } from 'lexical';
28
28
  import { KernelPlugin } from "../../../editor-kernel/plugin";
29
29
  import { IMarkdownShortCutService, isPunctuationChar } from "../../markdown";
30
30
  import { registerCommands } from "../command";
@@ -82,7 +82,7 @@ export var CommonPlugin = (_class = /*#__PURE__*/function (_KernelPlugin) {
82
82
  // Parse markdown options
83
83
  var markdownOption = (_this$config$markdown = (_this$config = this.config) === null || _this$config === void 0 ? void 0 : _this$config.markdownOption) !== null && _this$config$markdown !== void 0 ? _this$config$markdown : true;
84
84
  var isMarkdownEnabled = markdownOption !== false;
85
- var softBreak = isMarkdownEnabled ? '\n\n' : '\n';
85
+ var breakMark = isMarkdownEnabled ? '\n\n' : '\n';
86
86
 
87
87
  // Determine which formats are enabled
88
88
  var formats = {
@@ -189,11 +189,11 @@ export var CommonPlugin = (_class = /*#__PURE__*/function (_KernelPlugin) {
189
189
  markdownService.registerMarkdownShortCuts(textFormatShortcuts);
190
190
  }
191
191
  markdownService.registerMarkdownWriter('paragraph', function (ctx) {
192
- ctx.wrap('', softBreak);
192
+ ctx.wrap('', '\n');
193
193
  });
194
194
  markdownService.registerMarkdownWriter('quote', function (ctx, node) {
195
195
  if ($isQuoteNode(node)) {
196
- ctx.wrap('> ', softBreak);
196
+ ctx.wrap('> ', breakMark);
197
197
  }
198
198
  });
199
199
  markdownService.registerMarkdownWriter('heading', function (ctx, node) {
@@ -201,37 +201,37 @@ export var CommonPlugin = (_class = /*#__PURE__*/function (_KernelPlugin) {
201
201
  switch (node.getTag()) {
202
202
  case 'h1':
203
203
  {
204
- ctx.wrap('# ', '\n');
204
+ ctx.wrap('# ', breakMark);
205
205
  break;
206
206
  }
207
207
  case 'h2':
208
208
  {
209
- ctx.wrap('## ', '\n');
209
+ ctx.wrap('## ', breakMark);
210
210
  break;
211
211
  }
212
212
  case 'h3':
213
213
  {
214
- ctx.wrap('### ', '\n');
214
+ ctx.wrap('### ', breakMark);
215
215
  break;
216
216
  }
217
217
  case 'h4':
218
218
  {
219
- ctx.wrap('#### ', '\n');
219
+ ctx.wrap('#### ', breakMark);
220
220
  break;
221
221
  }
222
222
  case 'h5':
223
223
  {
224
- ctx.wrap('##### ', '\n');
224
+ ctx.wrap('##### ', breakMark);
225
225
  break;
226
226
  }
227
227
  case 'h6':
228
228
  {
229
- ctx.wrap('###### ', '\n');
229
+ ctx.wrap('###### ', breakMark);
230
230
  break;
231
231
  }
232
232
  default:
233
233
  {
234
- ctx.wrap('', softBreak);
234
+ ctx.wrap('', '\n');
235
235
  break;
236
236
  }
237
237
  }
@@ -315,7 +315,14 @@ export var CommonPlugin = (_class = /*#__PURE__*/function (_KernelPlugin) {
315
315
  var _this$config2;
316
316
  this.registerClears(registerRichText(editor), registerDragonSupport(editor), registerHistory(editor, createEmptyHistoryState(), 300), registerHeaderBackspace(editor), registerRichKeydown(editor, this.kernel, {
317
317
  enableHotkey: (_this$config2 = this.config) === null || _this$config2 === void 0 ? void 0 : _this$config2.enableHotkey
318
- }), registerCommands(editor), registerBreakLineClick(editor), registerCursorNode(editor), registerLastElement(editor));
318
+ }), registerCommands(editor), registerBreakLineClick(editor), registerCursorNode(editor), registerLastElement(editor),
319
+ // Convert soft line breaks (Shift+Enter) to hard line breaks (paragraph breaks)
320
+ // This allows breaking out of code blocks with Shift+Enter
321
+ editor.registerCommand(INSERT_LINE_BREAK_COMMAND, function () {
322
+ // Dispatch paragraph command instead of line break
323
+ editor.dispatchCommand(INSERT_PARAGRAPH_COMMAND, undefined);
324
+ return true; // Prevent default line break behavior
325
+ }, COMMAND_PRIORITY_HIGH));
319
326
  this.registerMarkdown(this.kernel);
320
327
  }
321
328
  }, {
@@ -14,12 +14,13 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
14
14
  function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
15
15
  function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
16
16
  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); }
17
+ import { preprocessMarkdownContent } from '@lobehub/ui';
17
18
  import { remark } from 'remark';
19
+ import remarkCjkFriendly from 'remark-cjk-friendly';
18
20
  import remarkGfm from 'remark-gfm';
19
21
  import remarkMath from 'remark-math';
20
22
  import { INodeHelper } from "../../../../editor-kernel/inode/helper";
21
23
  import { logger } from "../../utils/logger";
22
- import remarkSupersub from "./supersub";
23
24
 
24
25
  // 使用条件类型确保类型匹配
25
26
 
@@ -225,9 +226,9 @@ function registerDefaultReaders(markdownReaders) {
225
226
  }
226
227
  export function parseMarkdownToLexical(markdown) {
227
228
  var markdownReaders = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
228
- var ast = remark().use(remarkMath).use(remarkSupersub).use([[remarkGfm, {
229
+ var ast = remark().use(remarkCjkFriendly).use(remarkMath).use([[remarkGfm, {
229
230
  singleTilde: false
230
- }]]).parse(markdown);
231
+ }]]).parse(preprocessMarkdownContent(markdown));
231
232
  logger.debug('Parsed MDAST:', ast);
232
233
  var ctx = new MarkdownContext(ast);
233
234
  registerDefaultReaders(markdownReaders);
@@ -130,66 +130,114 @@ export var MarkdownPlugin = (_class = /*#__PURE__*/function (_KernelPlugin) {
130
130
 
131
131
  // If there's no text content, let Lexical handle it
132
132
  if (!text) return false;
133
+ _this2.logger.debug('paste content analysis:', {
134
+ clipboardTypes: Array.from(clipboardData.types || []),
135
+ hasHTML: !!(html && html.trim()),
136
+ htmlLength: (html === null || html === void 0 ? void 0 : html.length) || 0,
137
+ textLength: text.length
138
+ });
139
+
140
+ // Check if this is likely a rich-text paste from the editor or other rich editor
141
+ // Rich text pastes typically have HTML that's more complex than just wrapping text
142
+ if (html && html.trim()) {
143
+ var htmlDoc = new DOMParser().parseFromString(html, 'text/html');
144
+ var hasRichContent =
145
+ // Has block elements (p, div, h1-h6, etc.)
146
+ htmlDoc.querySelectorAll('p, div, h1, h2, h3, h4, h5, h6, ul, ol, li, table').length > 0 ||
147
+ // Has inline formatting (strong, em, code, etc.)
148
+ htmlDoc.querySelectorAll('strong, em, b, i, u, code, span[style]').length > 0 ||
149
+ // Has data attributes (often used by editors to store metadata)
150
+ htmlDoc.querySelectorAll('[data-lexical-text], [data-lexical-decorator]').length > 0;
151
+ if (hasRichContent) {
152
+ // This looks like rich content from an editor - let Lexical handle it
153
+ _this2.logger.debug('rich content detected, letting Lexical handle paste');
154
+ return false;
155
+ }
156
+ }
133
157
 
134
158
  // Check if markdown paste formatting is enabled (default: true)
135
159
  var enablePasteMarkdown = (_this2$config$enableP = (_this2$config = _this2.config) === null || _this2$config === void 0 ? void 0 : _this2$config.enablePasteMarkdown) !== null && _this2$config$enableP !== void 0 ? _this2$config$enableP : true;
136
- if (!enablePasteMarkdown) {
137
- // Force plain text paste - ignore all formatting (like Cmd+Shift+V)
138
- _this2.logger.debug('paste markdown formatting is disabled, inserting as plain text');
139
- event.preventDefault();
140
- event.stopPropagation();
141
- editor.update(function () {
142
- var selection = $getSelection();
143
- if (!$isRangeSelection(selection)) return;
144
160
 
145
- // Simply insert the plain text
146
- selection.insertText(text);
147
- });
148
- return true;
149
- }
161
+ // Force plain text paste for external content
162
+ event.preventDefault();
163
+ event.stopPropagation();
164
+ editor.update(function () {
165
+ var selection = $getSelection();
166
+ if (!$isRangeSelection(selection)) return;
150
167
 
151
- // If there's HTML content, it's a rich text paste
152
- // Let Lexical's rich text handler process it
153
- if (html && html.trim()) {
154
- _this2.logger.debug('paste content analysis: HTML detected, letting Lexical handle it');
155
- return false;
168
+ // Insert plain text
169
+ selection.insertText(text);
170
+ });
171
+
172
+ // If markdown formatting is disabled, we're done
173
+ if (!enablePasteMarkdown) {
174
+ _this2.logger.debug('markdown formatting disabled, plain text inserted');
175
+ return true;
156
176
  }
157
177
 
158
- // Only handle plain text paste - check for markdown patterns
178
+ // Check if the pasted plain text contains markdown patterns
159
179
  var hasMarkdownContent = _this2.detectMarkdownContent(text);
160
- _this2.logger.debug('paste content analysis:', {
161
- hasHTML: false,
162
- hasMarkdown: hasMarkdownContent,
163
- markdownPatterns: _this2.getMarkdownPatterns(text),
164
- text: text.slice(0, 100) + (text.length > 100 ? '...' : '')
165
- });
166
180
  if (hasMarkdownContent) {
167
- // Handle markdown paste
168
- // return this.handleMarkdownPaste(editor, text);
169
- var cacheState = editor.getEditorState();
170
- _this2.kernel.emit('markdownParse', {
171
- cacheState: cacheState,
172
- markdown: text
173
- });
174
- // setTimeout(() => {
175
- // editor.setEditorState(cacheState);
176
- // editor.update(() => {
177
- // this.handleMarkdownPaste(editor, text);
178
- // });
179
- // }, 5000);
180
- return false;
181
+ // Markdown detected - show confirmation dialog
182
+ _this2.logger.debug('markdown patterns detected:', _this2.getMarkdownPatterns(text));
183
+ setTimeout(function () {
184
+ _this2.kernel.emit('markdownParse', {
185
+ cacheState: editor.getEditorState(),
186
+ markdown: text
187
+ });
188
+ }, 10);
189
+ } else {
190
+ // No markdown detected - plain text is already inserted
191
+ _this2.logger.debug('no markdown patterns detected, keeping as plain text');
181
192
  }
182
- return false;
193
+ return true; // Command handled
183
194
  }, COMMAND_PRIORITY_CRITICAL));
184
195
  this.register(registerMarkdownCommand(editor, this.service));
185
196
  }
186
197
 
187
198
  /**
188
199
  * Detect if text contains markdown patterns
200
+ * Returns false if content is likely code (JSON, HTML, SQL, etc.)
189
201
  */
190
202
  }, {
191
203
  key: "detectMarkdownContent",
192
204
  value: function detectMarkdownContent(text) {
205
+ var trimmed = text.trim();
206
+
207
+ // Check if content is JSON
208
+ if (trimmed.startsWith('{') && trimmed.endsWith('}') || trimmed.startsWith('[') && trimmed.endsWith(']')) {
209
+ try {
210
+ JSON.parse(trimmed);
211
+ this.logger.debug('content is valid JSON, not treating as markdown');
212
+ return false;
213
+ } catch (_unused) {
214
+ // Not valid JSON, continue checking
215
+ }
216
+ }
217
+
218
+ // Check if content has significant HTML structure
219
+ var htmlTagPattern = /<[a-z][\S\s]*?>/gi;
220
+ var htmlMatches = text.match(htmlTagPattern);
221
+ if (htmlMatches && htmlMatches.length > 5) {
222
+ // More than 5 HTML tags suggests this is HTML content, not markdown
223
+ this.logger.debug('content has significant HTML structure, not treating as markdown');
224
+ return false;
225
+ }
226
+
227
+ // Check if content looks like code (SQL, XML, etc.)
228
+ // Common patterns: SQL keywords, XML declarations, file paths
229
+ var codePatterns = [/^\s*(select|insert|update|delete|create|alter|drop)\s+/im,
230
+ // SQL
231
+ /^\s*<\?xml/i,
232
+ // XML declaration
233
+ /^[a-z]:\\|^\/[a-z]/im // File paths (Windows/Unix)
234
+ ];
235
+ if (codePatterns.some(function (pattern) {
236
+ return pattern.test(text);
237
+ })) {
238
+ this.logger.debug('content looks like code, not treating as markdown');
239
+ return false;
240
+ }
193
241
  var markdownPatterns = [
194
242
  // Headers
195
243
  /^#{1,6}\s+/m,
@@ -6,7 +6,8 @@ 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 { Button, Space, notification } from 'antd';
9
+ import { Button } from '@lobehub/ui';
10
+ import { Space, notification } from 'antd';
10
11
  import { UNDO_COMMAND } from 'lexical';
11
12
  import { useLayoutEffect } from 'react';
12
13
  import { useLexicalComposerContext } from "../../../editor-kernel/react";
@@ -35,7 +36,6 @@ var ReactMarkdownPlugin = function ReactMarkdownPlugin() {
35
36
  return api.destroy();
36
37
  },
37
38
  size: "small",
38
- type: "link",
39
39
  children: t('markdown.cancel')
40
40
  }), /*#__PURE__*/_jsx(Button, {
41
41
  onClick: function onClick() {
@@ -56,7 +56,7 @@ var ReactMarkdownPlugin = function ReactMarkdownPlugin() {
56
56
  duration: 5,
57
57
  key: key,
58
58
  message: t('markdown.parseTitle'),
59
- onClose: close
59
+ showProgress: true
60
60
  });
61
61
  };
62
62
  editor.on('markdownParse', handleEvent);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/editor",
3
- "version": "1.17.0",
3
+ "version": "1.17.2",
4
4
  "description": "A powerful and extensible rich text editor built on Meta's Lexical framework, providing a modern editing experience with React integration.",
5
5
  "keywords": [
6
6
  "lobehub",
@@ -56,6 +56,7 @@
56
56
  "react-error-boundary": "^6.0.0",
57
57
  "react-layout-kit": "^2.0.0",
58
58
  "react-merge-refs": "^3.0.2",
59
+ "remark-cjk-friendly": "^1.2.1",
59
60
  "remark-supersub": "^1.0.0",
60
61
  "shiki": "^3.9.2",
61
62
  "ts-key-enum": "^3.0.13",