@lobehub/editor 3.12.0 → 3.13.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.
@@ -6,6 +6,7 @@ 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 { LOBE_THEME_APP_ID } from '@lobehub/ui';
9
10
  import { useMemo } from 'react';
10
11
  import { useLexicalComposerContext } from "./react-context";
11
12
  export var useAnchor = function useAnchor() {
@@ -20,7 +21,7 @@ export var useAnchor = function useAnchor() {
20
21
  var anchor = root ? root.parentElement : null;
21
22
  if (anchor) return anchor;
22
23
  // Fallback to .ant-app if exists, otherwise document.body
23
- var antApp = document.querySelector('.ant-app');
24
- return antApp || document.body;
24
+ var app = document.querySelector("#".concat(LOBE_THEME_APP_ID));
25
+ return app || document.body;
25
26
  }, [editor]);
26
27
  };
@@ -1,5 +1,6 @@
1
1
  import type { IEditorPluginConstructor } from "../../../types";
2
- export interface CommonPluginOptions {
2
+ import { type PasteHandlerConfig } from './paste-handler';
3
+ export interface CommonPluginOptions extends PasteHandlerConfig {
3
4
  enableHotkey?: boolean;
4
5
  /**
5
6
  * Enable/disable markdown shortcuts
@@ -1,5 +1,4 @@
1
1
  var _class;
2
- function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
3
2
  function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
4
3
  function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
5
4
  function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
@@ -26,7 +25,7 @@ import { registerDragonSupport } from '@lexical/dragon';
26
25
  import { registerHistory } from '@lexical/history';
27
26
  import { $createHeadingNode, $createQuoteNode, $isHeadingNode, $isQuoteNode, HeadingNode, QuoteNode, registerRichText } from '@lexical/rich-text';
28
27
  import { CAN_USE_DOM } from '@lexical/utils';
29
- import { $createLineBreakNode, $createParagraphNode, $getSelection, $isRangeSelection, $isTextNode, COMMAND_PRIORITY_CRITICAL, COMMAND_PRIORITY_HIGH, CONTROLLED_TEXT_INSERTION_COMMAND, INSERT_LINE_BREAK_COMMAND, INSERT_PARAGRAPH_COMMAND, PASTE_COMMAND, ParagraphNode, TEXT_TYPE_TO_FORMAT, TextNode } from 'lexical';
28
+ import { $createLineBreakNode, $createParagraphNode, $getSelection, $isRangeSelection, $isTextNode, COMMAND_PRIORITY_CRITICAL, COMMAND_PRIORITY_HIGH, INSERT_LINE_BREAK_COMMAND, INSERT_PARAGRAPH_COMMAND, PASTE_COMMAND, ParagraphNode, TEXT_TYPE_TO_FORMAT, TextNode } from 'lexical';
30
29
  import { noop } from "../../../editor-kernel";
31
30
  import { INodeHelper } from "../../../editor-kernel/inode/helper";
32
31
  import { KernelPlugin } from "../../../editor-kernel/plugin";
@@ -40,6 +39,7 @@ import { patchBreakLine, registerBreakLineClick } from "../node/ElementDOMSlot";
40
39
  import { CursorNode, registerCursorNode } from "../node/cursor";
41
40
  import { $isCursorInQuote, $isCursorInTable, createBlockNode, sampleReader } from "../utils";
42
41
  import { registerMDReader } from "./mdReader";
42
+ import { handleFilePaste, handlePlainTextPaste, handleVSCodePaste, runPasteHandlers } from "./paste-handler";
43
43
  import { registerHeaderBackspace, registerLastElement, registerRichKeydown } from "./register";
44
44
  patchBreakLine();
45
45
  export var CommonPlugin = (_class = /*#__PURE__*/function (_KernelPlugin) {
@@ -350,39 +350,30 @@ export var CommonPlugin = (_class = /*#__PURE__*/function (_KernelPlugin) {
350
350
  var _this2 = this,
351
351
  _this$config2;
352
352
  this.register(this.kernel.registerHighCommand(PASTE_COMMAND, function (event) {
353
- var _this2$config;
353
+ var _this2$config, _this2$config2;
354
354
  if (!(event instanceof ClipboardEvent)) return false;
355
355
  var clipboardData = event.clipboardData;
356
356
  if (!clipboardData) return false;
357
357
  _this2.kernel.emit('onPaste', event);
358
+ var ctx = {
359
+ clipboardData: clipboardData,
360
+ config: _this2.config,
361
+ editor: editor,
362
+ event: event
363
+ };
358
364
 
359
- // If pasteAsPlainText is enabled, intercept and paste as plain text
360
- if ((_this2$config = _this2.config) !== null && _this2$config !== void 0 && _this2$config.pasteAsPlainText) {
361
- // Check if it's a file paste - don't interfere with file uploads
362
- var items = clipboardData.items;
363
- var _iterator = _createForOfIteratorHelper(items),
364
- _step;
365
- try {
366
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
367
- var item = _step.value;
368
- if (item.kind === 'file') {
369
- return false; // Let file paste be handled normally
370
- }
371
- }
372
-
373
- // Get plain text from clipboard
374
- } catch (err) {
375
- _iterator.e(err);
376
- } finally {
377
- _iterator.f();
365
+ // VS Code paste handling is independent of pasteAsPlainText
366
+ // This ensures code blocks get proper language even in rich text mode
367
+ if ((_this2$config = _this2.config) !== null && _this2$config !== void 0 && _this2$config.pasteVSCodeAsCodeBlock) {
368
+ var result = handleVSCodePaste(ctx);
369
+ if (result === 'handled') {
370
+ return true;
378
371
  }
379
- var text = clipboardData.getData('text/plain');
380
- if (!text) return false;
372
+ }
381
373
 
382
- // Prevent default paste behavior and insert plain text
383
- event.preventDefault();
384
- editor.dispatchCommand(CONTROLLED_TEXT_INSERTION_COMMAND, text);
385
- return true;
374
+ // If pasteAsPlainText is enabled, run the plain text handlers chain
375
+ if ((_this2$config2 = _this2.config) !== null && _this2$config2 !== void 0 && _this2$config2.pasteAsPlainText) {
376
+ return runPasteHandlers(ctx, [handleFilePaste, handlePlainTextPaste]);
386
377
  }
387
378
  return false;
388
379
  }, COMMAND_PRIORITY_CRITICAL));
@@ -0,0 +1,37 @@
1
+ import type { LexicalEditor } from 'lexical';
2
+ /**
3
+ * Configuration options relevant to paste handling
4
+ */
5
+ export interface PasteHandlerConfig {
6
+ pasteVSCodeAsCodeBlock?: boolean;
7
+ }
8
+ export interface PasteContext {
9
+ clipboardData: DataTransfer;
10
+ config: PasteHandlerConfig;
11
+ editor: LexicalEditor;
12
+ event: ClipboardEvent;
13
+ }
14
+ /**
15
+ * Paste handler function type.
16
+ * @returns 'handled' - stop the chain, event is handled
17
+ * @returns 'skip' - skip pasteAsPlainText logic, let default behavior handle
18
+ * @returns 'next' - continue to next handler
19
+ */
20
+ export type PasteHandler = (ctx: PasteContext) => 'handled' | 'skip' | 'next';
21
+ /**
22
+ * Check if it's a file paste - skip plain text handling to let file uploads work normally
23
+ */
24
+ export declare const handleFilePaste: PasteHandler;
25
+ /**
26
+ * Handle VS Code paste - create code block with language from vscode-editor-data
27
+ */
28
+ export declare const handleVSCodePaste: PasteHandler;
29
+ /**
30
+ * Handle plain text paste - fallback handler that pastes as plain text
31
+ */
32
+ export declare const handlePlainTextPaste: PasteHandler;
33
+ /**
34
+ * Run paste handlers in sequence (middleware chain pattern)
35
+ * @returns true if event was handled, false to let default behavior continue
36
+ */
37
+ export declare function runPasteHandlers(ctx: PasteContext, handlers: PasteHandler[]): boolean;
@@ -0,0 +1,140 @@
1
+ function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
2
+ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
3
+ 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; }
4
+ import { $createNodeSelection, $getSelection, $insertNodes, $isRangeSelection, $setSelection, CONTROLLED_TEXT_INSERTION_COMMAND } from 'lexical';
5
+ import { $createCodeMirrorNode } from "../../codemirror-block/node/CodeMirrorNode";
6
+
7
+ // ============================================================================
8
+ // Paste Middleware Types
9
+ // ============================================================================
10
+
11
+ /**
12
+ * Configuration options relevant to paste handling
13
+ */
14
+
15
+ /**
16
+ * Paste handler function type.
17
+ * @returns 'handled' - stop the chain, event is handled
18
+ * @returns 'skip' - skip pasteAsPlainText logic, let default behavior handle
19
+ * @returns 'next' - continue to next handler
20
+ */
21
+
22
+ // ============================================================================
23
+ // Paste Handlers
24
+ // ============================================================================
25
+
26
+ /**
27
+ * Check if it's a file paste - skip plain text handling to let file uploads work normally
28
+ */
29
+ export var handleFilePaste = function handleFilePaste(_ref) {
30
+ var clipboardData = _ref.clipboardData;
31
+ var items = clipboardData.items;
32
+ var _iterator = _createForOfIteratorHelper(items),
33
+ _step;
34
+ try {
35
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
36
+ var item = _step.value;
37
+ if (item.kind === 'file') {
38
+ return 'skip';
39
+ }
40
+ }
41
+ } catch (err) {
42
+ _iterator.e(err);
43
+ } finally {
44
+ _iterator.f();
45
+ }
46
+ return 'next';
47
+ };
48
+
49
+ /**
50
+ * Handle VS Code paste - create code block with language from vscode-editor-data
51
+ */
52
+ export var handleVSCodePaste = function handleVSCodePaste(_ref2) {
53
+ var clipboardData = _ref2.clipboardData,
54
+ config = _ref2.config,
55
+ editor = _ref2.editor,
56
+ event = _ref2.event;
57
+ if (!config.pasteVSCodeAsCodeBlock) {
58
+ return 'next';
59
+ }
60
+ var vscodeDataStr = clipboardData.getData('vscode-editor-data');
61
+ if (!vscodeDataStr) {
62
+ return 'next';
63
+ }
64
+ try {
65
+ var vscodeData = JSON.parse(vscodeDataStr);
66
+ // VS Code uses 'mode' for language, fallback to 'language' or 'plaintext'
67
+ var language = vscodeData.mode || vscodeData.language || 'plaintext';
68
+ var text = clipboardData.getData('text/plain');
69
+ if (!text) {
70
+ return 'next';
71
+ }
72
+ event.preventDefault();
73
+ editor.update(function () {
74
+ var selection = $getSelection();
75
+ if (!$isRangeSelection(selection)) {
76
+ return;
77
+ }
78
+
79
+ // Create CodeMirror node with language and text content
80
+ var codeNode = $createCodeMirrorNode(language, text);
81
+ $insertNodes([codeNode]);
82
+
83
+ // Select the inserted CodeMirror node
84
+ var nodeSelection = $createNodeSelection();
85
+ nodeSelection.add(codeNode.getKey());
86
+ $setSelection(nodeSelection);
87
+ });
88
+ return 'handled';
89
+ } catch (_unused) {
90
+ // If parsing fails, continue to next handler
91
+ return 'next';
92
+ }
93
+ };
94
+
95
+ /**
96
+ * Handle plain text paste - fallback handler that pastes as plain text
97
+ */
98
+ export var handlePlainTextPaste = function handlePlainTextPaste(_ref3) {
99
+ var clipboardData = _ref3.clipboardData,
100
+ editor = _ref3.editor,
101
+ event = _ref3.event;
102
+ var text = clipboardData.getData('text/plain');
103
+ if (!text) {
104
+ return 'skip';
105
+ }
106
+ event.preventDefault();
107
+ editor.dispatchCommand(CONTROLLED_TEXT_INSERTION_COMMAND, text);
108
+ return 'handled';
109
+ };
110
+
111
+ // ============================================================================
112
+ // Middleware Runner
113
+ // ============================================================================
114
+
115
+ /**
116
+ * Run paste handlers in sequence (middleware chain pattern)
117
+ * @returns true if event was handled, false to let default behavior continue
118
+ */
119
+ export function runPasteHandlers(ctx, handlers) {
120
+ var _iterator2 = _createForOfIteratorHelper(handlers),
121
+ _step2;
122
+ try {
123
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
124
+ var handler = _step2.value;
125
+ var result = handler(ctx);
126
+ if (result === 'handled') {
127
+ return true;
128
+ }
129
+ if (result === 'skip') {
130
+ return false;
131
+ }
132
+ // 'next' continues to the next handler
133
+ }
134
+ } catch (err) {
135
+ _iterator2.e(err);
136
+ } finally {
137
+ _iterator2.f();
138
+ }
139
+ return false;
140
+ }
@@ -46,6 +46,8 @@ var ReactPlainText = /*#__PURE__*/memo(function (_ref) {
46
46
  markdownOption = _ref$markdownOption === void 0 ? true : _ref$markdownOption,
47
47
  _ref$pasteAsPlainText = _ref.pasteAsPlainText,
48
48
  pasteAsPlainText = _ref$pasteAsPlainText === void 0 ? false : _ref$pasteAsPlainText,
49
+ _ref$pasteVSCodeAsCod = _ref.pasteVSCodeAsCodeBlock,
50
+ pasteVSCodeAsCodeBlock = _ref$pasteVSCodeAsCod === void 0 ? true : _ref$pasteVSCodeAsCod,
49
51
  onKeyDown = _ref.onKeyDown,
50
52
  onFocus = _ref.onFocus,
51
53
  onBlur = _ref.onBlur,
@@ -112,9 +114,10 @@ var ReactPlainText = /*#__PURE__*/memo(function (_ref) {
112
114
  enableHotkey: enableHotkey,
113
115
  markdownOption: markdownOption,
114
116
  pasteAsPlainText: pasteAsPlainText,
117
+ pasteVSCodeAsCodeBlock: pasteVSCodeAsCodeBlock,
115
118
  theme: restTheme ? _objectSpread(_objectSpread({}, computedThemeStyles), restTheme) : computedThemeStyles
116
119
  });
117
- }, [editor, enableHotkey, enablePasteMarkdown, markdownOption, pasteAsPlainText, restTheme, computedThemeStyles]);
120
+ }, [editor, enableHotkey, enablePasteMarkdown, markdownOption, pasteAsPlainText, pasteVSCodeAsCodeBlock, restTheme, computedThemeStyles]);
118
121
  useEffect(function () {
119
122
  var _editor$getLexicalEdi;
120
123
  var container = editorContainerRef.current;
@@ -62,6 +62,13 @@ export interface ReactPlainTextProps {
62
62
  * @default false
63
63
  */
64
64
  pasteAsPlainText?: boolean;
65
+ /**
66
+ * When pasting VS Code content (detected via vscode-editor-data clipboard type),
67
+ * create a code block with the language from VS Code instead of pasting as plain text.
68
+ * This option only takes effect when pasteAsPlainText is enabled.
69
+ * @default true
70
+ */
71
+ pasteVSCodeAsCodeBlock?: boolean;
65
72
  style?: CSSProperties;
66
73
  theme?: CommonPluginOptions['theme'] & {
67
74
  fontSize?: number;
@@ -59,6 +59,8 @@ var Editor = /*#__PURE__*/memo(function (_ref) {
59
59
  markdownOption = _ref$markdownOption === void 0 ? true : _ref$markdownOption,
60
60
  _ref$pasteAsPlainText = _ref.pasteAsPlainText,
61
61
  pasteAsPlainText = _ref$pasteAsPlainText === void 0 ? false : _ref$pasteAsPlainText,
62
+ _ref$pasteVSCodeAsCod = _ref.pasteVSCodeAsCodeBlock,
63
+ pasteVSCodeAsCodeBlock = _ref$pasteVSCodeAsCod === void 0 ? true : _ref$pasteVSCodeAsCod,
62
64
  onCompositionStart = _ref.onCompositionStart,
63
65
  onCompositionEnd = _ref.onCompositionEnd,
64
66
  onContextMenu = _ref.onContextMenu,
@@ -127,6 +129,7 @@ var Editor = /*#__PURE__*/memo(function (_ref) {
127
129
  onPressEnter: onPressEnter,
128
130
  onTextChange: debouncedOnTextChange,
129
131
  pasteAsPlainText: pasteAsPlainText,
132
+ pasteVSCodeAsCodeBlock: pasteVSCodeAsCodeBlock,
130
133
  style: style,
131
134
  variant: variant,
132
135
  children: /*#__PURE__*/_jsx(ReactEditorContent, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/editor",
3
- "version": "3.12.0",
3
+ "version": "3.13.0",
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",