@lobehub/editor 3.12.1 → 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.
- package/es/plugins/common/plugin/index.d.ts +2 -1
- package/es/plugins/common/plugin/index.js +19 -28
- package/es/plugins/common/plugin/paste-handler.d.ts +37 -0
- package/es/plugins/common/plugin/paste-handler.js +140 -0
- package/es/plugins/common/react/ReactPlainText.js +4 -1
- package/es/plugins/common/react/type.d.ts +7 -0
- package/es/react/Editor/Editor.js +3 -0
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { IEditorPluginConstructor } from "../../../types";
|
|
2
|
-
|
|
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,
|
|
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
|
-
//
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
var
|
|
363
|
-
|
|
364
|
-
|
|
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
|
-
|
|
380
|
-
if (!text) return false;
|
|
372
|
+
}
|
|
381
373
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
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