@lobehub/editor 1.17.0 → 1.17.1

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);
@@ -133,63 +133,92 @@ export var MarkdownPlugin = (_class = /*#__PURE__*/function (_KernelPlugin) {
133
133
 
134
134
  // Check if markdown paste formatting is enabled (default: true)
135
135
  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;
136
+ _this2.logger.debug('paste content analysis:', {
137
+ enablePasteMarkdown: enablePasteMarkdown,
138
+ hasHTML: !!(html && html.trim()),
139
+ text: text.slice(0, 100) + (text.length > 100 ? '...' : '')
140
+ });
144
141
 
145
- // Simply insert the plain text
146
- selection.insertText(text);
147
- });
148
- return true;
149
- }
142
+ // Always force plain text paste first (prevents HTML formatting issues)
143
+ event.preventDefault();
144
+ event.stopPropagation();
145
+ editor.update(function () {
146
+ var selection = $getSelection();
147
+ if (!$isRangeSelection(selection)) return;
150
148
 
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;
149
+ // Insert plain text
150
+ selection.insertText(text);
151
+ });
152
+
153
+ // If markdown formatting is disabled, we're done
154
+ if (!enablePasteMarkdown) {
155
+ _this2.logger.debug('markdown formatting disabled, plain text inserted');
156
+ return true;
156
157
  }
157
158
 
158
- // Only handle plain text paste - check for markdown patterns
159
+ // Check if the pasted plain text contains markdown patterns
159
160
  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
161
  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;
162
+ // Markdown detected - show confirmation dialog
163
+ _this2.logger.debug('markdown patterns detected:', _this2.getMarkdownPatterns(text));
164
+ setTimeout(function () {
165
+ _this2.kernel.emit('markdownParse', {
166
+ cacheState: editor.getEditorState(),
167
+ markdown: text
168
+ });
169
+ }, 10);
170
+ } else {
171
+ // No markdown detected - plain text is already inserted
172
+ _this2.logger.debug('no markdown patterns detected, keeping as plain text');
181
173
  }
182
- return false;
174
+ return true; // Command handled
183
175
  }, COMMAND_PRIORITY_CRITICAL));
184
176
  this.register(registerMarkdownCommand(editor, this.service));
185
177
  }
186
178
 
187
179
  /**
188
180
  * Detect if text contains markdown patterns
181
+ * Returns false if content is likely code (JSON, HTML, SQL, etc.)
189
182
  */
190
183
  }, {
191
184
  key: "detectMarkdownContent",
192
185
  value: function detectMarkdownContent(text) {
186
+ var trimmed = text.trim();
187
+
188
+ // Check if content is JSON
189
+ if (trimmed.startsWith('{') && trimmed.endsWith('}') || trimmed.startsWith('[') && trimmed.endsWith(']')) {
190
+ try {
191
+ JSON.parse(trimmed);
192
+ this.logger.debug('content is valid JSON, not treating as markdown');
193
+ return false;
194
+ } catch (_unused) {
195
+ // Not valid JSON, continue checking
196
+ }
197
+ }
198
+
199
+ // Check if content has significant HTML structure
200
+ var htmlTagPattern = /<[a-z][\S\s]*?>/gi;
201
+ var htmlMatches = text.match(htmlTagPattern);
202
+ if (htmlMatches && htmlMatches.length > 5) {
203
+ // More than 5 HTML tags suggests this is HTML content, not markdown
204
+ this.logger.debug('content has significant HTML structure, not treating as markdown');
205
+ return false;
206
+ }
207
+
208
+ // Check if content looks like code (SQL, XML, etc.)
209
+ // Common patterns: SQL keywords, XML declarations, file paths
210
+ var codePatterns = [/^\s*(select|insert|update|delete|create|alter|drop)\s+/im,
211
+ // SQL
212
+ /^\s*<\?xml/i,
213
+ // XML declaration
214
+ /^[a-z]:\\|^\/[a-z]/im // File paths (Windows/Unix)
215
+ ];
216
+ if (codePatterns.some(function (pattern) {
217
+ return pattern.test(text);
218
+ })) {
219
+ this.logger.debug('content looks like code, not treating as markdown');
220
+ return false;
221
+ }
193
222
  var markdownPatterns = [
194
223
  // Headers
195
224
  /^#{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.1",
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",