@lobehub/editor 1.5.10 → 1.6.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.
Files changed (55) hide show
  1. package/es/common/sys.d.ts +2 -0
  2. package/es/common/sys.js +5 -0
  3. package/es/const/hotkey.d.ts +3 -0
  4. package/es/const/hotkey.js +58 -0
  5. package/es/editor-kernel/kernel.d.ts +3 -0
  6. package/es/editor-kernel/kernel.js +20 -1
  7. package/es/index.d.ts +2 -0
  8. package/es/index.js +4 -0
  9. package/es/plugins/code/plugin/index.d.ts +1 -0
  10. package/es/plugins/code/plugin/index.js +7 -6
  11. package/es/plugins/code/plugin/registry.d.ts +4 -1
  12. package/es/plugins/code/plugin/registry.js +15 -13
  13. package/es/plugins/code/react/CodeReactPlugin.js +5 -2
  14. package/es/plugins/code/react/type.d.ts +1 -0
  15. package/es/plugins/common/plugin/index.d.ts +1 -0
  16. package/es/plugins/common/plugin/index.js +4 -1
  17. package/es/plugins/common/plugin/register.d.ts +4 -1
  18. package/es/plugins/common/plugin/register.js +43 -14
  19. package/es/plugins/common/react/ReactPlainText.js +4 -1
  20. package/es/plugins/common/react/type.d.ts +1 -0
  21. package/es/plugins/link/node/LinkNode.js +1 -3
  22. package/es/plugins/link/plugin/index.d.ts +4 -0
  23. package/es/plugins/link/plugin/index.js +11 -1
  24. package/es/plugins/link/plugin/registry.d.ts +9 -0
  25. package/es/plugins/link/plugin/registry.js +108 -0
  26. package/es/plugins/link/react/ReactLinkPlugin.js +23 -50
  27. package/es/plugins/link/react/components/LinkEdit.js +41 -21
  28. package/es/plugins/link/react/components/LinkToolbar.js +58 -24
  29. package/es/plugins/link/react/type.d.ts +1 -0
  30. package/es/plugins/link/utils/index.d.ts +5 -0
  31. package/es/plugins/link/utils/index.js +14 -0
  32. package/es/plugins/list/plugin/index.d.ts +1 -0
  33. package/es/plugins/list/plugin/index.js +9 -76
  34. package/es/plugins/list/plugin/registry.d.ts +6 -0
  35. package/es/plugins/list/plugin/registry.js +98 -0
  36. package/es/plugins/list/react/ReactListPlugin.js +5 -2
  37. package/es/plugins/list/react/type.d.ts +1 -0
  38. package/es/plugins/math/react/component/MathEditor.js +41 -5
  39. package/es/plugins/math/react/component/MathEditorContainer.js +1 -0
  40. package/es/plugins/math/react/component/MathEditorContent.d.ts +2 -0
  41. package/es/plugins/math/react/component/MathEditorContent.js +7 -3
  42. package/es/react/ChatInputActions/components/ActionItem.js +8 -2
  43. package/es/react/ChatInputActions/components/ActionRender.js +10 -3
  44. package/es/react/ChatInputActions/type.d.ts +2 -1
  45. package/es/react/hooks/useEditorState/index.js +44 -8
  46. package/es/types/hotkey.d.ts +71 -0
  47. package/es/types/hotkey.js +71 -0
  48. package/es/types/kernel.d.ts +9 -0
  49. package/es/utils/hotkey/isHotkeyMatch.d.ts +1 -0
  50. package/es/utils/hotkey/isHotkeyMatch.js +9 -0
  51. package/es/utils/hotkey/parseHotkeys.d.ts +6 -0
  52. package/es/utils/hotkey/parseHotkeys.js +42 -0
  53. package/es/utils/hotkey/registerHotkey.d.ts +15 -0
  54. package/es/utils/hotkey/registerHotkey.js +32 -0
  55. package/package.json +2 -1
@@ -26,6 +26,9 @@ var MathEdit = /*#__PURE__*/memo(function (_ref) {
26
26
  _useState4 = _slicedToArray(_useState3, 2),
27
27
  value = _useState4[0],
28
28
  setValue = _useState4[1];
29
+ // 最近一次成功渲染(校验通过)的 LaTeX
30
+ var lastValidRef = useRef('');
31
+ var isInputValidRef = useRef(true);
29
32
  var _useState5 = useState(null),
30
33
  _useState6 = _slicedToArray(_useState5, 2),
31
34
  mathDOM = _useState6[0],
@@ -46,7 +49,7 @@ var MathEdit = /*#__PURE__*/memo(function (_ref) {
46
49
  _useLexicalComposerCo2 = _slicedToArray(_useLexicalComposerCo, 1),
47
50
  editor = _useLexicalComposerCo2[0];
48
51
 
49
- // 实时更新节点内容
52
+ // 实时更新节点内容(仅当输入可渲染时才同步到 document)
50
53
  useEffect(function () {
51
54
  if (!mathNode) return;
52
55
 
@@ -55,6 +58,10 @@ var MathEdit = /*#__PURE__*/memo(function (_ref) {
55
58
  // 直接更新节点内容
56
59
  var lexicalEditor = editor.getLexicalEditor();
57
60
  if (lexicalEditor && !isUpdatingRef.current) {
61
+ // 仅在校验通过时更新文档;失败时不更新,保持最后一次成功渲染
62
+ if (!isInputValidRef.current) {
63
+ return;
64
+ }
58
65
  // 检查当前值是否与节点中的值不同,避免不必要的更新
59
66
  var currentCode = mathNode.code;
60
67
  if (currentCode === value) {
@@ -73,9 +80,9 @@ var MathEdit = /*#__PURE__*/memo(function (_ref) {
73
80
  // 延迟重置更新标志,确保更新监听器不会立即触发
74
81
  setTimeout(function () {
75
82
  isUpdatingRef.current = false;
76
- }, 100);
83
+ }, 50);
77
84
  }
78
- }, 100); // 增加防抖延迟
85
+ }, 50); // 增加防抖延迟
79
86
 
80
87
  return function () {
81
88
  return clearTimeout(timeoutId);
@@ -87,9 +94,10 @@ var MathEdit = /*#__PURE__*/memo(function (_ref) {
87
94
  if (!mathNode) return;
88
95
  var lexicalEditor = editor.getLexicalEditor();
89
96
  if (lexicalEditor) {
90
- // 提交时总是使用原始的 value,不包含错误标记
97
+ // 提交时若当前不可渲染,则使用最近一次成功渲染的内容
98
+ var codeToCommit = isInputValidRef.current ? value : lastValidRef.current;
91
99
  lexicalEditor.dispatchCommand(UPDATE_MATH_COMMAND, {
92
- code: value,
100
+ code: codeToCommit,
93
101
  key: mathNode.getKey()
94
102
  });
95
103
  }
@@ -180,6 +188,8 @@ var MathEdit = /*#__PURE__*/memo(function (_ref) {
180
188
  // 只有在不是同一个节点或值真正改变时才更新 value
181
189
  if (!isSameNode && node.code !== value) {
182
190
  setValue(node.code);
191
+ lastValidRef.current = node.code; // 切换节点时以节点里的内容作为最近一次有效值
192
+ isInputValidRef.current = true; // 重置有效状态
183
193
  }
184
194
  setMathDOM(editor.getElementByKey(node.getKey()));
185
195
  setIsBlockMode(node instanceof MathBlockNode);
@@ -211,6 +221,8 @@ var MathEdit = /*#__PURE__*/memo(function (_ref) {
211
221
  setMathNode(null);
212
222
  setMathDOM(null);
213
223
  setValue('');
224
+ lastValidRef.current = '';
225
+ isInputValidRef.current = true;
214
226
  setIsBlockMode(false);
215
227
  setIsOpen(false);
216
228
  }
@@ -226,11 +238,35 @@ var MathEdit = /*#__PURE__*/memo(function (_ref) {
226
238
  onCancel: handleCancel,
227
239
  onDelete: handleDelete,
228
240
  onSubmit: handleSubmit,
241
+ onValidationChange: function onValidationChange(isValid) {
242
+ isInputValidRef.current = isValid;
243
+ if (isValid) {
244
+ lastValidRef.current = value;
245
+ }
246
+ },
229
247
  onValueChange: setValue,
230
248
  prev: prev,
231
249
  value: value
232
250
  });
233
251
 
252
+ // 点击编辑器外部时关闭面板
253
+ useEffect(function () {
254
+ if (!isOpen) return;
255
+ var handlePointerDown = function handlePointerDown(event) {
256
+ var target = event.target;
257
+ if (!target) return;
258
+ var container = document.querySelector('[data-math-editor-container]');
259
+ if (container && container.contains(target)) return;
260
+ handleCancel();
261
+ };
262
+ document.addEventListener('mousedown', handlePointerDown);
263
+ document.addEventListener('touchstart', handlePointerDown);
264
+ return function () {
265
+ document.removeEventListener('mousedown', handlePointerDown);
266
+ document.removeEventListener('touchstart', handlePointerDown);
267
+ };
268
+ }, [isOpen, handleCancel]);
269
+
234
270
  // 如果有自定义渲染组件,使用它来包装 MathEditorContent
235
271
  if (renderComp) {
236
272
  return /*#__PURE__*/_jsx(_Fragment, {
@@ -51,6 +51,7 @@ export var MathEditorContainer = /*#__PURE__*/memo(function (_ref) {
51
51
  }, [mathDOM]);
52
52
  return /*#__PURE__*/_jsx(Block, {
53
53
  className: styles.mathEditor,
54
+ "data-math-editor-container": true,
54
55
  ref: divRef,
55
56
  shadow: true,
56
57
  variant: 'outlined',
@@ -16,6 +16,8 @@ export interface MathEditorContentProps {
16
16
  onDelete: () => void;
17
17
  /** 提交回调 */
18
18
  onSubmit: () => void;
19
+ /** 验证状态变化回调 */
20
+ onValidationChange?: (isValid: boolean) => void;
19
21
  /** 值变化回调 */
20
22
  onValueChange: (value: string) => void;
21
23
  /** 是否从前一个位置进入 */
@@ -23,6 +23,7 @@ export var MathEditorContent = /*#__PURE__*/memo(function (_ref) {
23
23
  onCancel = _ref.onCancel,
24
24
  onDelete = _ref.onDelete,
25
25
  onSubmit = _ref.onSubmit,
26
+ onValidationChange = _ref.onValidationChange,
26
27
  onValueChange = _ref.onValueChange,
27
28
  prev = _ref.prev,
28
29
  value = _ref.value;
@@ -57,6 +58,7 @@ export var MathEditorContent = /*#__PURE__*/memo(function (_ref) {
57
58
  var isEmpty = !value.trim();
58
59
  if (isEmpty) {
59
60
  setLatexError('');
61
+ onValidationChange === null || onValidationChange === void 0 || onValidationChange(true); // 空值视为有效
60
62
  return;
61
63
  }
62
64
 
@@ -67,18 +69,20 @@ export var MathEditorContent = /*#__PURE__*/memo(function (_ref) {
67
69
  displayMode: true,
68
70
  throwOnError: true
69
71
  });
70
- // 验证成功:清除错误
72
+ // 验证成功:清除错误,通知父组件验证通过
71
73
  setLatexError('');
74
+ onValidationChange === null || onValidationChange === void 0 || onValidationChange(true);
72
75
  } catch (error) {
73
- // 验证失败:设置错误信息
76
+ // 验证失败:设置错误信息,通知父组件验证失败
74
77
  var errorMessage = error instanceof Error ? error.message : 'LaTeX Parse Error';
75
78
  setLatexError(errorMessage);
79
+ onValidationChange === null || onValidationChange === void 0 || onValidationChange(false);
76
80
  }
77
81
  }, 50);
78
82
  return function () {
79
83
  return clearTimeout(timeoutId);
80
84
  };
81
- }, [value, mathNode]);
85
+ }, [value, mathNode, onValidationChange]);
82
86
  var handleKeyDown = useCallback(function (e) {
83
87
  if (!mathNode) return;
84
88
  if (isModifierMatch(e, CONTROL_OR_META) && e.key === 'Enter') {
@@ -1,5 +1,11 @@
1
1
  'use client';
2
2
 
3
+ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
4
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
5
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
6
+ 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; }
7
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
8
+ 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); }
3
9
  import { ActionIcon, Dropdown } from '@lobehub/ui';
4
10
  import { memo } from 'react';
5
11
  import ActionRender from "./ActionRender";
@@ -44,9 +50,9 @@ var ActionItem = /*#__PURE__*/memo(function (_ref) {
44
50
  size: 20
45
51
  },
46
52
  title: item.label,
47
- tooltipProps: {
53
+ tooltipProps: _objectSpread({
48
54
  placement: 'top'
49
- }
55
+ }, item.tooltipProps)
50
56
  })
51
57
  }, item.key);
52
58
  }
@@ -1,4 +1,10 @@
1
- var _excluded = ["wrapper", "icon", "key", "label", "onClick", "danger", "loading", "active"];
1
+ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
2
+ var _excluded = ["wrapper", "icon", "key", "label", "onClick", "danger", "loading", "active", "tooltipProps"];
3
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
4
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
5
+ 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; }
6
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
7
+ 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); }
2
8
  function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
3
9
  function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
4
10
  import { ActionIcon } from '@lobehub/ui';
@@ -30,6 +36,7 @@ var ActionRender = /*#__PURE__*/memo(function (_ref) {
30
36
  danger = _ref2.danger,
31
37
  loading = _ref2.loading,
32
38
  active = _ref2.active,
39
+ tooltipProps = _ref2.tooltipProps,
33
40
  itemRest = _objectWithoutProperties(_ref2, _excluded);
34
41
  if (item.children && /*#__PURE__*/isValidElement(item.children)) {
35
42
  if (!wrapper) return item.children;
@@ -54,9 +61,9 @@ var ActionRender = /*#__PURE__*/memo(function (_ref) {
54
61
  size: 20
55
62
  },
56
63
  title: label,
57
- tooltipProps: {
64
+ tooltipProps: _objectSpread({
58
65
  placement: 'top'
59
- }
66
+ }, tooltipProps)
60
67
  });
61
68
  if (!wrapper) return action;
62
69
  return wrapper(action);
@@ -1,4 +1,4 @@
1
- import type { MenuInfo, MenuItemType } from '@lobehub/ui';
1
+ import type { MenuInfo, MenuItemType, TooltipProps } from '@lobehub/ui';
2
2
  import type { ReactNode } from 'react';
3
3
  import type { FlexboxProps } from 'react-layout-kit';
4
4
  export type ChatInputActionEvent = Pick<MenuInfo, 'key' | 'keyPath' | 'domEvent'>;
@@ -6,6 +6,7 @@ export interface ActionItem extends MenuItemType {
6
6
  active?: boolean;
7
7
  alwaysDisplay?: boolean;
8
8
  children?: ReactNode;
9
+ tooltipProps?: TooltipProps;
9
10
  wrapper?: (dom: ReactNode) => ReactNode;
10
11
  }
11
12
  export type DividerItem = {
@@ -15,8 +15,8 @@ import { INSERT_CODEINLINE_COMMAND } from "../../../plugins/code";
15
15
  import { $isSelectionInCodeInline } from "../../../plugins/code/node/code";
16
16
  import { UPDATE_CODEBLOCK_LANG } from "../../../plugins/codeblock";
17
17
  import { $isRootTextContentEmpty } from "../../../plugins/common/utils";
18
- import { $isLinkNode, TOGGLE_LINK_COMMAND } from "../../../plugins/link/node/LinkNode";
19
- import { sanitizeUrl } from "../../../plugins/link/utils";
18
+ import { $isLinkNode, TOGGLE_LINK_COMMAND, formatUrl } from "../../../plugins/link/node/LinkNode";
19
+ import { extractUrlFromText, sanitizeUrl, validateUrl } from "../../../plugins/link/utils";
20
20
  import { INSERT_CHECK_LIST_COMMAND } from "../../../plugins/list";
21
21
  import { $createMathBlockNode, $createMathInlineNode } from "../../../plugins/math/node";
22
22
  import { $findTopLevelElement, formatParagraph, getSelectedNode } from "./utils";
@@ -238,19 +238,55 @@ export function useEditorState(editor) {
238
238
  });
239
239
  }, [editor, isCodeblock]);
240
240
  var insertLink = useCallback(function () {
241
+ var lexical = editor === null || editor === void 0 ? void 0 : editor.getLexicalEditor();
242
+ if (!lexical) return;
241
243
  if (!isLink) {
242
- var _editor$getLexicalEdi4;
244
+ var nextUrl = sanitizeUrl('https://');
245
+ var expandTo = null;
246
+ lexical.getEditorState().read(function () {
247
+ var selection = $getSelection();
248
+ if ($isRangeSelection(selection)) {
249
+ var text = selection.getTextContent();
250
+ if (!selection.isCollapsed()) {
251
+ var maybeUrl = formatUrl(text.trim());
252
+ if (validateUrl(maybeUrl)) {
253
+ nextUrl = maybeUrl;
254
+ }
255
+ } else {
256
+ var lineText = selection.anchor.getNode().getTextContent();
257
+ var found = extractUrlFromText(lineText);
258
+ if (found && validateUrl(formatUrl(found.url))) {
259
+ expandTo = {
260
+ index: found.index,
261
+ length: found.length
262
+ };
263
+ }
264
+ }
265
+ }
266
+ });
243
267
  setIsLink(true);
244
- editor === null || editor === void 0 || (_editor$getLexicalEdi4 = editor.getLexicalEditor()) === null || _editor$getLexicalEdi4 === void 0 || _editor$getLexicalEdi4.dispatchCommand(TOGGLE_LINK_COMMAND, sanitizeUrl('https://'));
268
+ console.log(nextUrl);
269
+ lexical.update(function () {
270
+ if (expandTo) {
271
+ var selection = $getSelection();
272
+ if ($isRangeSelection(selection)) {
273
+ var anchorNode = selection.anchor.getNode();
274
+ // Move selection to the URL range within the current text node
275
+ selection.anchor.set(anchorNode.getKey(), expandTo.index, 'text');
276
+ selection.focus.set(anchorNode.getKey(), expandTo.index + expandTo.length, 'text');
277
+ }
278
+ }
279
+ console.log(nextUrl);
280
+ lexical.dispatchCommand(TOGGLE_LINK_COMMAND, validateUrl(nextUrl) ? nextUrl : sanitizeUrl('https://'));
281
+ });
245
282
  } else {
246
- var _editor$getLexicalEdi5;
247
283
  setIsLink(false);
248
- editor === null || editor === void 0 || (_editor$getLexicalEdi5 = editor.getLexicalEditor()) === null || _editor$getLexicalEdi5 === void 0 || _editor$getLexicalEdi5.dispatchCommand(TOGGLE_LINK_COMMAND, null);
284
+ lexical.dispatchCommand(TOGGLE_LINK_COMMAND, null);
249
285
  }
250
286
  }, [editor, isLink]);
251
287
  var insertMath = useCallback(function () {
252
- var _editor$getLexicalEdi6;
253
- editor === null || editor === void 0 || (_editor$getLexicalEdi6 = editor.getLexicalEditor()) === null || _editor$getLexicalEdi6 === void 0 || _editor$getLexicalEdi6.update(function () {
288
+ var _editor$getLexicalEdi4;
289
+ editor === null || editor === void 0 || (_editor$getLexicalEdi4 = editor.getLexicalEditor()) === null || _editor$getLexicalEdi4 === void 0 || _editor$getLexicalEdi4.update(function () {
254
290
  var selection = $getSelection();
255
291
  if ($isRangeSelection(selection)) {
256
292
  // 检查当前上下文来决定插入行内还是块级数学公式
@@ -0,0 +1,71 @@
1
+ import { CommandListenerPriority } from 'lexical';
2
+ export declare const KeyEnum: {
3
+ readonly Alt: "alt";
4
+ readonly Backquote: "backquote";
5
+ readonly Backslash: "backslash";
6
+ readonly Backspace: "backspace";
7
+ readonly BracketLeft: "bracketleft";
8
+ readonly BracketRight: "bracketright";
9
+ readonly Comma: "comma";
10
+ readonly Ctrl: "ctrl";
11
+ readonly Down: "down";
12
+ readonly Enter: "enter";
13
+ readonly Equal: "equal";
14
+ readonly Esc: "esc";
15
+ readonly Left: "left";
16
+ readonly LeftClick: "left-click";
17
+ readonly LeftDoubleClick: "left-double-click";
18
+ readonly Meta: "meta";
19
+ readonly MiddleClick: "middle-click";
20
+ readonly Minus: "minus";
21
+ readonly Mod: "mod";
22
+ readonly Number: "1-9";
23
+ readonly Period: "period";
24
+ readonly Plus: "equal";
25
+ readonly QuestionMark: "slash";
26
+ readonly Quote: "quote";
27
+ readonly Right: "right";
28
+ readonly RightClick: "right-click";
29
+ readonly RightDoubleClick: "right-double-click";
30
+ readonly Semicolon: "semicolon";
31
+ readonly Shift: "shift";
32
+ readonly Slash: "slash";
33
+ readonly Space: "space";
34
+ readonly Tab: "tab";
35
+ readonly Up: "up";
36
+ readonly Zero: "0";
37
+ };
38
+ export declare const HotkeyEnum: {
39
+ readonly Bold: "bold";
40
+ readonly BulletList: "bulletList";
41
+ readonly CodeInline: "codeInline";
42
+ readonly Italic: "italic";
43
+ readonly Link: "link";
44
+ readonly Mention: "mention";
45
+ readonly NumberList: "numberList";
46
+ readonly Redo: "redo";
47
+ readonly Slash: "slash";
48
+ readonly Strikethrough: "strikethrough";
49
+ readonly Underline: "underline";
50
+ readonly Undo: "undo";
51
+ };
52
+ export declare const HotkeyScopeEnum: {
53
+ readonly Format: "format";
54
+ readonly Insert: "insert";
55
+ readonly Plugin: "plugin";
56
+ readonly Slash: "slash";
57
+ };
58
+ export type HotkeyId = (typeof HotkeyEnum)[keyof typeof HotkeyEnum];
59
+ export type HotkeyScopeId = (typeof HotkeyScopeEnum)[keyof typeof HotkeyScopeEnum];
60
+ export interface HotkeyItem {
61
+ id: HotkeyId;
62
+ keys: string;
63
+ priority: CommandListenerPriority;
64
+ scopes?: HotkeyScopeId[];
65
+ }
66
+ export type ModifierCombination = {
67
+ altKey?: boolean;
68
+ ctrlKey?: boolean;
69
+ metaKey?: boolean;
70
+ shiftKey?: boolean;
71
+ } | Record<string, never>;
@@ -0,0 +1,71 @@
1
+ export var KeyEnum = {
2
+ Alt: 'alt',
3
+ Backquote: 'backquote',
4
+ // `
5
+ Backslash: 'backslash',
6
+ // \
7
+ Backspace: 'backspace',
8
+ BracketLeft: 'bracketleft',
9
+ // [
10
+ BracketRight: 'bracketright',
11
+ // ]
12
+ Comma: 'comma',
13
+ // ,
14
+ Ctrl: 'ctrl',
15
+ Down: 'down',
16
+ Enter: 'enter',
17
+ Equal: 'equal',
18
+ // =
19
+ Esc: 'esc',
20
+ Left: 'left',
21
+ LeftClick: 'left-click',
22
+ LeftDoubleClick: 'left-double-click',
23
+ Meta: 'meta',
24
+ // Command on Mac, Win on Win
25
+ MiddleClick: 'middle-click',
26
+ Minus: 'minus',
27
+ // -
28
+ Mod: 'mod',
29
+ Number: '1-9',
30
+ // Command on Mac, Ctrl on Win
31
+ Period: 'period',
32
+ // .
33
+ Plus: 'equal',
34
+ // +
35
+ QuestionMark: 'slash',
36
+ // ?
37
+ Quote: 'quote',
38
+ // '
39
+ Right: 'right',
40
+ RightClick: 'right-click',
41
+ RightDoubleClick: 'right-double-click',
42
+ Semicolon: 'semicolon',
43
+ // ;
44
+ Shift: 'shift',
45
+ Slash: 'slash',
46
+ // /
47
+ Space: 'space',
48
+ Tab: 'tab',
49
+ Up: 'up',
50
+ Zero: '0'
51
+ };
52
+ export var HotkeyEnum = {
53
+ Bold: 'bold',
54
+ BulletList: 'bulletList',
55
+ CodeInline: 'codeInline',
56
+ Italic: 'italic',
57
+ Link: 'link',
58
+ Mention: 'mention',
59
+ NumberList: 'numberList',
60
+ Redo: 'redo',
61
+ Slash: 'slash',
62
+ Strikethrough: 'strikethrough',
63
+ Underline: 'underline',
64
+ Undo: 'undo'
65
+ };
66
+ export var HotkeyScopeEnum = {
67
+ Format: 'format',
68
+ Insert: 'insert',
69
+ Plugin: 'plugin',
70
+ Slash: 'slash'
71
+ };
@@ -1,5 +1,7 @@
1
1
  import type { CommandListener, CommandListenerPriority, CommandPayloadType, DecoratorNode, LexicalCommand, LexicalEditor, LexicalNodeConfig } from 'lexical';
2
2
  import type DataSource from "../editor-kernel/data-source";
3
+ import { HotkeyId } from "./hotkey";
4
+ import { HotkeyOptions, HotkeysEvent } from "../utils/hotkey/registerHotkey";
3
5
  import { ILocaleKeys } from './locale';
4
6
  export type Commands = Map<LexicalCommand<unknown>, Array<Set<CommandListener<unknown>>>>;
5
7
  export type CommandsClean = Map<LexicalCommand<unknown>, () => void>;
@@ -120,6 +122,13 @@ export interface IEditor {
120
122
  * @returns A teardown function to clean up the listener.
121
123
  */
122
124
  registerHighCommand<P>(command: LexicalCommand<P>, listener: CommandListener<P>, priority: CommandListenerPriority): () => void;
125
+ /**
126
+ * Register keyboard shortcut
127
+ * @param hotkey
128
+ * @param callback
129
+ * @param options
130
+ */
131
+ registerHotkey(hotkey: HotkeyId, callback: (event: KeyboardEvent, handler: HotkeysEvent) => void, options?: HotkeyOptions): () => void;
123
132
  /**
124
133
  * Register internationalization text
125
134
  * @param locale Internationalization text object
@@ -0,0 +1 @@
1
+ export declare const isHotkeyMatch: (event: KeyboardEvent, hotkey: string) => boolean;
@@ -0,0 +1,9 @@
1
+ import { isModifierMatch } from 'lexical';
2
+ import { mapCode, parseHotkey } from "./parseHotkeys";
3
+ export var isHotkeyMatch = function isHotkeyMatch(event, hotkey) {
4
+ var keys = parseHotkey(hotkey);
5
+ var modifierMatch = isModifierMatch(event, keys.modifiers);
6
+ if (!modifierMatch) return false;
7
+ if (mapCode(event.code) !== keys.key) return false;
8
+ return true;
9
+ };
@@ -0,0 +1,6 @@
1
+ import type { ModifierCombination } from "../../types/hotkey";
2
+ export declare function mapCode(key: string): string;
3
+ export declare function parseHotkey(hotkey: string): {
4
+ key?: string;
5
+ modifiers: ModifierCombination;
6
+ };
@@ -0,0 +1,42 @@
1
+ import { isApple } from "../../common/sys";
2
+ var reservedModifierKeywords = new Set(['shift', 'alt', 'meta', 'mod', 'ctrl', 'control']);
3
+ var mappedKeys = {
4
+ AltLeft: 'alt',
5
+ AltRight: 'alt',
6
+ ControlLeft: 'ctrl',
7
+ ControlRight: 'ctrl',
8
+ MetaLeft: 'meta',
9
+ MetaRight: 'meta',
10
+ OSLeft: 'meta',
11
+ OSRight: 'meta',
12
+ ShiftLeft: 'shift',
13
+ ShiftRight: 'shift',
14
+ down: 'arrowdown',
15
+ esc: 'escape',
16
+ left: 'arrowleft',
17
+ return: 'enter',
18
+ right: 'arrowright',
19
+ up: 'arrowup'
20
+ };
21
+ export function mapCode(key) {
22
+ return (mappedKeys[key.trim()] || key.trim()).toLowerCase().replace(/key|digit|numpad/, '');
23
+ }
24
+ export function parseHotkey(hotkey) {
25
+ var keys = [];
26
+ keys = hotkey.toLocaleLowerCase().split('+').map(function (k) {
27
+ return mapCode(k);
28
+ });
29
+ var modifiers = {
30
+ altKey: keys.includes('alt'),
31
+ ctrlKey: keys.includes('ctrl') || keys.includes('control') || (!isApple ? keys.includes('mod') : false),
32
+ metaKey: keys.includes('meta') || (isApple ? keys.includes('mod') : false),
33
+ shiftKey: keys.includes('shift')
34
+ };
35
+ var singleCharKeys = keys.find(function (k) {
36
+ return !reservedModifierKeywords.has(k);
37
+ });
38
+ return {
39
+ key: singleCharKeys,
40
+ modifiers: modifiers
41
+ };
42
+ }
@@ -0,0 +1,15 @@
1
+ import { HotkeyId, HotkeyItem, ModifierCombination } from "../../types/hotkey";
2
+ export type HotkeysEvent = {
3
+ key?: string;
4
+ keys: string;
5
+ modifiers: ModifierCombination;
6
+ scopes?: string[];
7
+ };
8
+ export type HotkeyOptions = {
9
+ enabled?: boolean;
10
+ preventDefault?: boolean;
11
+ stopImmediatePropagation?: boolean;
12
+ stopPropagation?: boolean;
13
+ };
14
+ export declare const registerHotkey: (hotkey: HotkeyItem, callback: (event: KeyboardEvent, handler: HotkeysEvent) => void, options?: HotkeyOptions) => (e: KeyboardEvent) => boolean;
15
+ export declare const getHotkeyById: (id: HotkeyId) => HotkeyItem;
@@ -0,0 +1,32 @@
1
+ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
2
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
3
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
4
+ 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; }
5
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
6
+ 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); }
7
+ import { HOTKEYS_REGISTRATION } from "../../const/hotkey";
8
+ import { createDebugLogger } from "../debug";
9
+ import { parseHotkey } from "./parseHotkeys";
10
+ import { isHotkeyMatch } from "./isHotkeyMatch";
11
+ var logger = createDebugLogger('hotkey');
12
+ export var registerHotkey = function registerHotkey(hotkey, callback) {
13
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
14
+ return function (e) {
15
+ if (!isHotkeyMatch(e, hotkey.keys)) return false;
16
+ var keys = parseHotkey(hotkey.keys);
17
+ if (options.preventDefault) e.preventDefault();
18
+ if (options.stopPropagation) e.stopPropagation();
19
+ callback(e, _objectSpread(_objectSpread({
20
+ keys: hotkey.keys
21
+ }, keys), {}, {
22
+ scopes: hotkey.scopes
23
+ }));
24
+ logger.debug("\u2328\uFE0F Hotkey matched: ".concat(hotkey.id, " [").concat(hotkey.keys, "]"), hotkey);
25
+ return true;
26
+ };
27
+ };
28
+ export var getHotkeyById = function getHotkeyById(id) {
29
+ return HOTKEYS_REGISTRATION.find(function (hotkey) {
30
+ return hotkey.id === id;
31
+ });
32
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/editor",
3
- "version": "1.5.10",
3
+ "version": "1.6.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",
@@ -56,6 +56,7 @@
56
56
  "react-layout-kit": "^2.0.0",
57
57
  "react-merge-refs": "^3.0.2",
58
58
  "shiki": "^3.9.2",
59
+ "ts-key-enum": "^3.0.13",
59
60
  "use-merge-value": "^1.2.0"
60
61
  },
61
62
  "peerDependencies": {