@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
|
|
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('',
|
|
192
|
+
ctx.wrap('', '\n');
|
|
193
193
|
});
|
|
194
194
|
markdownService.registerMarkdownWriter('quote', function (ctx, node) {
|
|
195
195
|
if ($isQuoteNode(node)) {
|
|
196
|
-
ctx.wrap('> ',
|
|
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('# ',
|
|
204
|
+
ctx.wrap('# ', breakMark);
|
|
205
205
|
break;
|
|
206
206
|
}
|
|
207
207
|
case 'h2':
|
|
208
208
|
{
|
|
209
|
-
ctx.wrap('## ',
|
|
209
|
+
ctx.wrap('## ', breakMark);
|
|
210
210
|
break;
|
|
211
211
|
}
|
|
212
212
|
case 'h3':
|
|
213
213
|
{
|
|
214
|
-
ctx.wrap('### ',
|
|
214
|
+
ctx.wrap('### ', breakMark);
|
|
215
215
|
break;
|
|
216
216
|
}
|
|
217
217
|
case 'h4':
|
|
218
218
|
{
|
|
219
|
-
ctx.wrap('#### ',
|
|
219
|
+
ctx.wrap('#### ', breakMark);
|
|
220
220
|
break;
|
|
221
221
|
}
|
|
222
222
|
case 'h5':
|
|
223
223
|
{
|
|
224
|
-
ctx.wrap('##### ',
|
|
224
|
+
ctx.wrap('##### ', breakMark);
|
|
225
225
|
break;
|
|
226
226
|
}
|
|
227
227
|
case 'h6':
|
|
228
228
|
{
|
|
229
|
-
ctx.wrap('###### ',
|
|
229
|
+
ctx.wrap('###### ', breakMark);
|
|
230
230
|
break;
|
|
231
231
|
}
|
|
232
232
|
default:
|
|
233
233
|
{
|
|
234
|
-
ctx.wrap('',
|
|
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(
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
//
|
|
177
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
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",
|