@lobehub/editor 1.4.1 → 1.4.3

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/README.md CHANGED
@@ -99,20 +99,21 @@ The simplest way to get started with a fully-featured editor:
99
99
 
100
100
  ```tsx
101
101
  import {
102
+ INSERT_HEADING_COMMAND,
102
103
  ReactCodeblockPlugin,
103
104
  ReactImagePlugin,
104
105
  ReactLinkPlugin,
105
106
  ReactListPlugin,
106
107
  } from '@lobehub/editor';
107
- import { Editor } from '@lobehub/editor/react';
108
+ import { Editor, useEditor } from '@lobehub/editor/react';
108
109
 
109
110
  export default function MyEditor() {
110
- const editorRef = Editor.useEditor();
111
+ const editor = useEditor();
111
112
 
112
113
  return (
113
114
  <Editor
114
115
  placeholder="Start typing..."
115
- editorRef={editorRef}
116
+ editor={editor}
116
117
  plugins={[ReactListPlugin, ReactLinkPlugin, ReactImagePlugin, ReactCodeblockPlugin]}
117
118
  slashOption={{
118
119
  items: [
@@ -143,19 +144,20 @@ Add more functionality with built-in plugins:
143
144
  ```tsx
144
145
  import {
145
146
  INSERT_FILE_COMMAND,
147
+ INSERT_MENTION_COMMAND,
146
148
  INSERT_TABLE_COMMAND,
147
149
  ReactFilePlugin,
148
150
  ReactHRPlugin,
149
151
  ReactTablePlugin,
150
152
  } from '@lobehub/editor';
151
- import { Editor } from '@lobehub/editor/react';
153
+ import { Editor, useEditor } from '@lobehub/editor/react';
152
154
 
153
155
  export default function AdvancedEditor() {
154
- const editorRef = Editor.useEditor();
156
+ const editor = useEditor();
155
157
 
156
158
  return (
157
159
  <Editor
158
- editorRef={editorRef}
160
+ editor={editor}
159
161
  plugins={[
160
162
  ReactTablePlugin,
161
163
  ReactHRPlugin,
@@ -298,8 +300,8 @@ All plugins follow a **dual-architecture design**:
298
300
  #### Utility Hooks
299
301
 
300
302
  ```tsx
301
- // Get editor reference
302
- const editorRef = Editor.useEditor();
303
+ // Get editor instance
304
+ const editor = useEditor();
303
305
 
304
306
  // Helper for plugin configuration
305
307
  const PluginWithConfig = Editor.withProps(ReactFilePlugin, {
@@ -72,4 +72,7 @@ export declare class Kernel extends EventEmitter implements IEditorKernel {
72
72
  updateTheme(key: string, value: string | Record<string, string>): void;
73
73
  registerLocale(locale: Partial<Record<keyof ILocaleKeys, string>>): void;
74
74
  t<K extends keyof ILocaleKeys>(key: K, params?: Record<string, any>): string;
75
+ get isEmpty(): boolean;
76
+ get isSelected(): boolean;
77
+ cleanDocument(): void;
75
78
  }
@@ -20,9 +20,10 @@ function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key i
20
20
  function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
21
21
  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); }
22
22
  import EventEmitter from 'eventemitter3';
23
- import { createEditor } from 'lexical';
23
+ import { $getSelection, $isRangeSelection, createEditor } from 'lexical';
24
24
  import { get, merge, template, templateSettings } from 'lodash-es';
25
25
  import defaultLocale from "../locale";
26
+ import { $isRootTextContentEmpty } from "../plugins/common/utils";
26
27
  import { registerEvent } from "./event";
27
28
  import { createEmptyEditorState } from "./utils";
28
29
  templateSettings.interpolate = /{{([\S\s]+?)}}/g;
@@ -421,6 +422,36 @@ export var Kernel = /*#__PURE__*/function (_EventEmitter) {
421
422
  }
422
423
  return translation;
423
424
  }
425
+ }, {
426
+ key: "isEmpty",
427
+ get: function get() {
428
+ var _this3 = this;
429
+ if (!this.editor) {
430
+ return true;
431
+ }
432
+ return this.editor.getEditorState().read(function () {
433
+ return $isRootTextContentEmpty(_this3.editor.isComposing(), true);
434
+ });
435
+ }
436
+ }, {
437
+ key: "isSelected",
438
+ get: function get() {
439
+ if (!this.editor) {
440
+ return false;
441
+ }
442
+ return this.editor.getEditorState().read(function () {
443
+ var selection = $getSelection();
444
+ if ($isRangeSelection(selection)) {
445
+ return !!selection._cachedNodes;
446
+ }
447
+ return false;
448
+ });
449
+ }
450
+ }, {
451
+ key: "cleanDocument",
452
+ value: function cleanDocument() {
453
+ this.setDocument('text', '');
454
+ }
424
455
  }], [{
425
456
  key: "setGlobalHotReloadMode",
426
457
  value: function setGlobalHotReloadMode(enabled) {
@@ -2,4 +2,3 @@ export { LexicalErrorBoundary } from './LexicalErrorBoundary';
2
2
  export { useLexicalComposerContext } from './react-context';
3
3
  export { ReactEditor } from './react-editor';
4
4
  export { useLexicalEditor } from './useLexicalEditor';
5
- export { useToolbarState } from './useToolbarState';
@@ -1,5 +1,4 @@
1
1
  export { LexicalErrorBoundary } from "./LexicalErrorBoundary";
2
2
  export { useLexicalComposerContext } from "./react-context";
3
3
  export { ReactEditor } from "./react-editor";
4
- export { useLexicalEditor } from "./useLexicalEditor";
5
- export { useToolbarState } from "./useToolbarState";
4
+ export { useLexicalEditor } from "./useLexicalEditor";
@@ -1,12 +1,15 @@
1
1
  /**
2
2
  * Support configuration through react children
3
3
  */
4
- import { type FC, type ReactNode, type Ref } from 'react';
4
+ import { type FC, type ReactNode } from 'react';
5
5
  import type { IEditor } from "../../types";
6
6
  export interface IReactEditorProps {
7
7
  children?: ReactNode | undefined;
8
8
  /** Editor configuration */
9
9
  config?: Record<string, any>;
10
- editorRef?: Ref<IEditor>;
10
+ /** Editor instance to use */
11
+ editor?: IEditor;
12
+ /** Callback called when editor is initialized */
13
+ onInit?: (editor: IEditor) => void;
11
14
  }
12
15
  export declare const ReactEditor: FC<IReactEditorProps>;
@@ -12,14 +12,6 @@ import Editor from "../";
12
12
  import { LexicalComposerContext, createLexicalComposerContext, useLexicalComposerContext } from "./react-context";
13
13
  import { jsx as _jsx } from "react/jsx-runtime";
14
14
  import { jsxs as _jsxs } from "react/jsx-runtime";
15
- function updateRef(ref, value) {
16
- if (typeof ref === 'function') {
17
- ref(value);
18
- } else if (ref) {
19
- ref.current = value;
20
- }
21
- }
22
-
23
15
  // Configuration injection component
24
16
  var ConfigInjector = function ConfigInjector(_ref) {
25
17
  var config = _ref.config;
@@ -34,20 +26,23 @@ var ConfigInjector = function ConfigInjector(_ref) {
34
26
  return null;
35
27
  };
36
28
  export var ReactEditor = function ReactEditor(_ref2) {
37
- var editorRef = _ref2.editorRef,
29
+ var editorProp = _ref2.editor,
38
30
  children = _ref2.children,
39
- config = _ref2.config;
31
+ config = _ref2.config,
32
+ onInit = _ref2.onInit;
40
33
  var composerContext = useMemo(function () {
41
- var editor = Editor.createEditor();
34
+ var editor = editorProp || Editor.createEditor();
42
35
  var theme = createLexicalComposerContext(null, null);
43
36
  return [editor, theme];
44
- }, []);
37
+ }, [editorProp]);
45
38
  useEffect(function () {
46
- updateRef(editorRef, composerContext[0]);
47
- return function () {
48
- updateRef(editorRef, undefined);
49
- };
50
- }, [editorRef, composerContext]);
39
+ var editor = composerContext[0];
40
+
41
+ // Call onInit callback
42
+ if (onInit) {
43
+ onInit(editor);
44
+ }
45
+ }, [composerContext, onInit]);
51
46
  return /*#__PURE__*/_jsxs(LexicalComposerContext, {
52
47
  value: composerContext,
53
48
  children: [/*#__PURE__*/_jsx(ConfigInjector, {
@@ -102,7 +102,7 @@ var ChatInputActions = /*#__PURE__*/memo(function (_ref) {
102
102
  height: 20
103
103
  },
104
104
  type: 'vertical'
105
- }, i);
105
+ }, "divider-".concat(i));
106
106
  }
107
107
  var wrapper = item.wrapper,
108
108
  icon = item.icon,
@@ -157,7 +157,7 @@ var ChatInputActions = /*#__PURE__*/memo(function (_ref) {
157
157
  children: item.children.map(function (child, childIndex) {
158
158
  return mapActions(child, childIndex);
159
159
  })
160
- }, index);
160
+ }, "collapse-".concat(index));
161
161
  }
162
162
  if (item.type === 'dropdown') {
163
163
  return /*#__PURE__*/_jsx(Dropdown, {
@@ -21,7 +21,8 @@ var Editor = /*#__PURE__*/memo(function (_ref) {
21
21
  var content = _ref.content,
22
22
  style = _ref.style,
23
23
  className = _ref.className,
24
- editorRef = _ref.editorRef,
24
+ editor = _ref.editor,
25
+ onInit = _ref.onInit,
25
26
  onChange = _ref.onChange,
26
27
  placeholder = _ref.placeholder,
27
28
  _ref$plugins = _ref.plugins,
@@ -79,7 +80,8 @@ var Editor = /*#__PURE__*/memo(function (_ref) {
79
80
  }, [enableSlash, enableMention, slashOption, restMentionOption]);
80
81
  return /*#__PURE__*/_jsxs(ReactEditor, {
81
82
  config: config,
82
- editorRef: editorRef,
83
+ editor: editor,
84
+ onInit: onInit,
83
85
  children: [memoPlugins, memoSlash, memoMention, /*#__PURE__*/_jsx(ReactPlainText, {
84
86
  autoFocus: autoFocus,
85
87
  className: className,
@@ -1,14 +1,15 @@
1
1
  import type { ReactNode } from 'react';
2
+ import { useEditor } from '../hooks/useEditor';
3
+ import { useEditorState } from '../hooks/useEditorState';
2
4
  import type { EditorProps } from './type';
3
- import { useEditor } from './useEditor';
4
5
  import { withProps } from './utils';
5
6
  interface IEditor {
6
7
  (props: EditorProps): ReactNode;
7
8
  useEditor: typeof useEditor;
9
+ useEditorState: typeof useEditorState;
8
10
  withProps: typeof withProps;
9
11
  }
10
12
  declare const Editor: IEditor;
11
13
  export default Editor;
12
14
  export * from './type';
13
- export { useEditor } from './useEditor';
14
15
  export * from './utils';
@@ -1,10 +1,11 @@
1
+ import { useEditor } from "../hooks/useEditor";
2
+ import { useEditorState } from "../hooks/useEditorState";
1
3
  import EditorParent from "./Editor";
2
- import { useEditor } from "./useEditor";
3
4
  import { withProps } from "./utils";
4
5
  var Editor = EditorParent;
5
6
  Editor.useEditor = useEditor;
7
+ Editor.useEditorState = useEditorState;
6
8
  Editor.withProps = withProps;
7
9
  export default Editor;
8
10
  export * from "./type";
9
- export { useEditor } from "./useEditor";
10
11
  export * from "./utils";
@@ -1,4 +1,4 @@
1
- import type { CSSProperties, FC, ReactNode, RefObject } from 'react';
1
+ import type { CSSProperties, FC, ReactNode } from 'react';
2
2
  import type { ReactEditorContentProps, ReactPlainTextProps } from "../../plugins/common/react";
3
3
  import type { ReactMentionPluginProps } from "../../plugins/mention/react";
4
4
  import type { ReactSlashOptionProps } from "../../plugins/slash/react";
@@ -11,8 +11,9 @@ export interface EditorProps extends Partial<ReactEditorContentProps>, Omit<Reac
11
11
  autoFocus?: boolean;
12
12
  children?: ReactNode;
13
13
  className?: string;
14
- editorRef?: RefObject<IEditor | null>;
14
+ editor?: IEditor;
15
15
  mentionOption?: MentionOption;
16
+ onInit?: (editor: IEditor) => void;
16
17
  plugins?: EditorPlugin[];
17
18
  slashOption?: Partial<ReactSlashOptionProps>;
18
19
  style?: CSSProperties;
@@ -0,0 +1,2 @@
1
+ import type { IEditor } from "../../types";
2
+ export declare const useEditor: () => IEditor;
@@ -0,0 +1,7 @@
1
+ import { useMemo } from 'react';
2
+ import Editor from "../../editor-kernel";
3
+ export var useEditor = function useEditor() {
4
+ return useMemo(function () {
5
+ return Editor.createEditor();
6
+ }, []);
7
+ };
@@ -0,0 +1,64 @@
1
+ import { IEditor } from "../../../types";
2
+ /**
3
+ * Editor state and toolbar methods return type
4
+ */
5
+ export interface EditorState {
6
+ /** Current block type (e.g., 'paragraph', 'h1', 'h2', 'bullet', 'number', 'code') */
7
+ blockType: string | null;
8
+ /** Format selection as blockquote */
9
+ blockquote: () => void;
10
+ /** Toggle bold formatting */
11
+ bold: () => void;
12
+ /** Toggle bullet list */
13
+ bulletList: () => void;
14
+ /** Whether redo operation is available */
15
+ canRedo: boolean;
16
+ /** Whether undo operation is available */
17
+ canUndo: boolean;
18
+ /** Toggle code formatting */
19
+ code: () => void;
20
+ /** Format selection as code block */
21
+ codeblock: () => void;
22
+ /** Current code block language */
23
+ codeblockLang: string | null | undefined;
24
+ /** Insert or toggle link */
25
+ insertLink: () => void;
26
+ /** Whether cursor is inside a blockquote */
27
+ isBlockquote: boolean;
28
+ /** Whether selection has bold formatting */
29
+ isBold: boolean;
30
+ /** Whether selection has code formatting */
31
+ isCode: boolean;
32
+ /** Whether cursor is inside a code block */
33
+ isCodeblock: boolean;
34
+ /** Whether editor content is empty */
35
+ isEmpty: boolean;
36
+ /** Whether selection has italic formatting */
37
+ isItalic: boolean;
38
+ /** Whether editor has selection */
39
+ isSelected: boolean;
40
+ /** Whether selection has strikethrough formatting */
41
+ isStrikethrough: boolean;
42
+ /** Whether selection has underline formatting */
43
+ isUnderline: boolean;
44
+ /** Toggle italic formatting */
45
+ italic: () => void;
46
+ /** Toggle numbered list */
47
+ numberList: () => void;
48
+ /** Redo last operation */
49
+ redo: () => void;
50
+ /** Toggle strikethrough formatting */
51
+ strikethrough: () => void;
52
+ /** Toggle underline formatting */
53
+ underline: () => void;
54
+ /** Undo last operation */
55
+ undo: () => void;
56
+ /** Update code block language */
57
+ updateCodeblockLang: (lang: string) => void;
58
+ }
59
+ /**
60
+ * Provide toolbar state and toolbar methods
61
+ * @param editor - Editor instance
62
+ * @returns Editor state and methods for toolbar functionality
63
+ */
64
+ export declare function useEditorState(editor: IEditor): EditorState;
@@ -6,54 +6,27 @@ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" !=
6
6
  function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
7
7
  import { $createCodeNode, $isCodeNode } from '@lexical/code';
8
8
  import { $isListNode, INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND, ListNode } from '@lexical/list';
9
- import { $isHeadingNode } from '@lexical/rich-text';
10
- import { $isAtNodeEnd, $setBlocksType } from '@lexical/selection';
11
- import { $findMatchingParent, $getNearestNodeOfType, mergeRegister } from '@lexical/utils';
12
- import { $createParagraphNode, $getSelection, $isRangeSelection, $isRootOrShadowRoot, CAN_REDO_COMMAND, CAN_UNDO_COMMAND, COMMAND_PRIORITY_LOW, FORMAT_TEXT_COMMAND, REDO_COMMAND, SELECTION_CHANGE_COMMAND, UNDO_COMMAND } from 'lexical';
13
- import { useCallback, useEffect, useState } from 'react';
14
- import { UPDATE_CODEBLOCK_LANG } from "../../plugins/codeblock";
15
- import { $isRootTextContentEmpty } from "../../plugins/common/utils";
16
- import { $isLinkNode, TOGGLE_LINK_COMMAND } from "../../plugins/link/node/LinkNode";
17
- import { sanitizeUrl } from "../../plugins/link/utils";
18
- function $findTopLevelElement(node) {
19
- var topLevelElement = node.getKey() === 'root' ? node : $findMatchingParent(node, function (e) {
20
- var parent = e.getParent();
21
- return parent !== null && $isRootOrShadowRoot(parent);
22
- });
23
- if (topLevelElement === null) {
24
- topLevelElement = node.getTopLevelElementOrThrow();
25
- }
26
- return topLevelElement;
27
- }
28
- var formatParagraph = function formatParagraph(editor) {
29
- editor === null || editor === void 0 || editor.update(function () {
30
- var selection = $getSelection();
31
- $setBlocksType(selection, function () {
32
- return $createParagraphNode();
33
- });
34
- });
35
- };
36
- function getSelectedNode(selection) {
37
- var anchor = selection.anchor;
38
- var focus = selection.focus;
39
- var anchorNode = selection.anchor.getNode();
40
- var focusNode = selection.focus.getNode();
41
- if (anchorNode === focusNode) {
42
- return anchorNode;
43
- }
44
- var isBackward = selection.isBackward();
45
- if (isBackward) {
46
- return $isAtNodeEnd(focus) ? anchorNode : focusNode;
47
- } else {
48
- return $isAtNodeEnd(anchor) ? anchorNode : focusNode;
49
- }
50
- }
9
+ import { $createQuoteNode, $isHeadingNode, $isQuoteNode } from '@lexical/rich-text';
10
+ import { $setBlocksType } from '@lexical/selection';
11
+ import { $getNearestNodeOfType, mergeRegister } from '@lexical/utils';
12
+ import { $getSelection, $isRangeSelection, CAN_REDO_COMMAND, CAN_UNDO_COMMAND, COMMAND_PRIORITY_LOW, FORMAT_TEXT_COMMAND, REDO_COMMAND, SELECTION_CHANGE_COMMAND, UNDO_COMMAND } from 'lexical';
13
+ import { useCallback, useEffect, useMemo, useState } from 'react';
14
+ import { UPDATE_CODEBLOCK_LANG } from "../../../plugins/codeblock";
15
+ import { $isRootTextContentEmpty } from "../../../plugins/common/utils";
16
+ import { $isLinkNode, TOGGLE_LINK_COMMAND } from "../../../plugins/link/node/LinkNode";
17
+ import { sanitizeUrl } from "../../../plugins/link/utils";
18
+ import { $findTopLevelElement, formatParagraph, getSelectedNode } from "./utils";
19
+
20
+ /**
21
+ * Editor state and toolbar methods return type
22
+ */
51
23
 
52
24
  /**
53
25
  * Provide toolbar state and toolbar methods
54
- * @returns
26
+ * @param editor - Editor instance
27
+ * @returns Editor state and methods for toolbar functionality
55
28
  */
56
- export function useToolbarState(editorRef) {
29
+ export function useEditorState(editor) {
57
30
  var _useState = useState(false),
58
31
  _useState2 = _slicedToArray(_useState, 2),
59
32
  canUndo = _useState2[0],
@@ -88,33 +61,42 @@ export function useToolbarState(editorRef) {
88
61
  setIsLink = _useState16[1];
89
62
  var _useState17 = useState(false),
90
63
  _useState18 = _slicedToArray(_useState17, 2),
91
- isInCodeblock = _useState18[0],
64
+ isCodeblock = _useState18[0],
92
65
  setIsInCodeblok = _useState18[1];
93
- var _useState19 = useState(null),
66
+ var _useState19 = useState(false),
94
67
  _useState20 = _slicedToArray(_useState19, 2),
95
- codeblockLang = _useState20[0],
96
- setCodeblockLang = _useState20[1];
97
- var _useState21 = useState(true),
68
+ isBlockquote = _useState20[0],
69
+ setIsInBlockquote = _useState20[1];
70
+ var _useState21 = useState(null),
98
71
  _useState22 = _slicedToArray(_useState21, 2),
99
- isEmpty = _useState22[0],
100
- setIsEmpty = _useState22[1];
101
- var _useState23 = useState(null),
72
+ codeblockLang = _useState22[0],
73
+ setCodeblockLang = _useState22[1];
74
+ var _useState23 = useState(true),
102
75
  _useState24 = _slicedToArray(_useState23, 2),
103
- blockType = _useState24[0],
104
- setBlockType = _useState24[1];
76
+ isEmpty = _useState24[0],
77
+ setIsEmpty = _useState24[1];
78
+ var _useState25 = useState(false),
79
+ _useState26 = _slicedToArray(_useState25, 2),
80
+ isSelected = _useState26[0],
81
+ setIsSelected = _useState26[1];
82
+ var _useState27 = useState(null),
83
+ _useState28 = _slicedToArray(_useState27, 2),
84
+ blockType = _useState28[0],
85
+ setBlockType = _useState28[1];
105
86
  var $handleHeadingNode = useCallback(function (selectedElement) {
106
87
  var type = $isHeadingNode(selectedElement) ? selectedElement.getTag() : selectedElement.getType();
107
88
  setBlockType(type);
108
89
  }, [setBlockType]);
109
90
  var $updateToolbar = useCallback(function () {
110
- var _editorRef$current;
111
91
  var selection = $getSelection();
112
- var editor = (_editorRef$current = editorRef.current) === null || _editorRef$current === void 0 ? void 0 : _editorRef$current.getLexicalEditor();
113
- if (editor) {
114
- setIsEmpty($isRootTextContentEmpty(editor.isComposing(), false));
92
+ var lexicalEditor = editor === null || editor === void 0 ? void 0 : editor.getLexicalEditor();
93
+ setIsSelected(false);
94
+ if (lexicalEditor) {
95
+ setIsEmpty($isRootTextContentEmpty(lexicalEditor.isComposing(), false));
115
96
  }
116
97
  if ($isRangeSelection(selection)) {
117
- var _editorRef$current2;
98
+ var _editor$getLexicalEdi;
99
+ setIsSelected(!!selection._cachedNodes);
118
100
  setIsBold(selection.hasFormat('bold'));
119
101
  setIsItalic(selection.hasFormat('italic'));
120
102
  setIsUnderline(selection.hasFormat('underline'));
@@ -125,13 +107,15 @@ export function useToolbarState(editorRef) {
125
107
  var element = $findTopLevelElement(anchorNode);
126
108
  var focusElement = $findTopLevelElement(focusNode);
127
109
  var elementKey = element.getKey();
128
- var elementDOM = (_editorRef$current2 = editorRef.current) === null || _editorRef$current2 === void 0 || (_editorRef$current2 = _editorRef$current2.getLexicalEditor()) === null || _editorRef$current2 === void 0 ? void 0 : _editorRef$current2.getElementByKey(elementKey);
110
+ var elementDOM = editor === null || editor === void 0 || (_editor$getLexicalEdi = editor.getLexicalEditor()) === null || _editor$getLexicalEdi === void 0 ? void 0 : _editor$getLexicalEdi.getElementByKey(elementKey);
129
111
  var node = getSelectedNode(selection);
130
112
  var parent = node.getParent();
131
113
  setIsLink($isLinkNode(parent) || $isLinkNode(node));
132
114
  var isCodeBlock = $isCodeNode(element) && $isCodeNode(focusElement) && elementKey === focusElement.getKey();
133
115
  setIsInCodeblok(isCodeBlock);
134
116
  setCodeblockLang(isCodeBlock ? element.getLanguage() : '');
117
+ var _isBlockquote = $isQuoteNode(element) && $isQuoteNode(focusElement) && elementKey === focusElement.getKey();
118
+ setIsInBlockquote(_isBlockquote);
135
119
  if (elementDOM !== null) {
136
120
  if ($isListNode(element)) {
137
121
  var parentList = $getNearestNodeOfType(anchorNode, ListNode);
@@ -142,6 +126,7 @@ export function useToolbarState(editorRef) {
142
126
  }
143
127
  }
144
128
  } else if (!selection) {
129
+ setIsSelected(false);
145
130
  setIsBold(false);
146
131
  setIsItalic(false);
147
132
  setIsUnderline(false);
@@ -149,22 +134,20 @@ export function useToolbarState(editorRef) {
149
134
  setIsCode(false);
150
135
  setIsLink(false);
151
136
  setIsInCodeblok(false);
137
+ setIsInBlockquote(false);
152
138
  setCodeblockLang(null);
153
139
  setBlockType(null);
154
140
  }
155
- }, [editorRef.current]);
141
+ }, [editor]);
156
142
  var undo = useCallback(function () {
157
- var _editorRef$current3;
158
- (_editorRef$current3 = editorRef.current) === null || _editorRef$current3 === void 0 || _editorRef$current3.dispatchCommand(UNDO_COMMAND, undefined);
159
- }, [editorRef.current]);
143
+ editor === null || editor === void 0 || editor.dispatchCommand(UNDO_COMMAND, undefined);
144
+ }, [editor]);
160
145
  var redo = useCallback(function () {
161
- var _editorRef$current4;
162
- (_editorRef$current4 = editorRef.current) === null || _editorRef$current4 === void 0 || _editorRef$current4.dispatchCommand(REDO_COMMAND, undefined);
163
- }, [editorRef.current]);
146
+ editor === null || editor === void 0 || editor.dispatchCommand(REDO_COMMAND, undefined);
147
+ }, [editor]);
164
148
  var formatText = useCallback(function (type) {
165
- var _editorRef$current5;
166
- (_editorRef$current5 = editorRef.current) === null || _editorRef$current5 === void 0 || _editorRef$current5.dispatchCommand(FORMAT_TEXT_COMMAND, type);
167
- }, [editorRef.current]);
149
+ editor === null || editor === void 0 || editor.dispatchCommand(FORMAT_TEXT_COMMAND, type);
150
+ }, [editor]);
168
151
  var bold = useCallback(function () {
169
152
  formatText('bold');
170
153
  }, [formatText]);
@@ -182,26 +165,22 @@ export function useToolbarState(editorRef) {
182
165
  }, [formatText]);
183
166
  var bulletList = useCallback(function () {
184
167
  if (blockType !== 'bullet') {
185
- var _editorRef$current6;
186
- (_editorRef$current6 = editorRef.current) === null || _editorRef$current6 === void 0 || _editorRef$current6.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined);
168
+ editor === null || editor === void 0 || editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined);
187
169
  } else {
188
- var _editorRef$current7;
189
- formatParagraph((_editorRef$current7 = editorRef.current) === null || _editorRef$current7 === void 0 ? void 0 : _editorRef$current7.getLexicalEditor());
170
+ formatParagraph(editor === null || editor === void 0 ? void 0 : editor.getLexicalEditor());
190
171
  }
191
- }, [blockType]);
172
+ }, [blockType, editor]);
192
173
  var numberList = useCallback(function () {
193
174
  if (blockType !== 'number') {
194
- var _editorRef$current8;
195
- (_editorRef$current8 = editorRef.current) === null || _editorRef$current8 === void 0 || _editorRef$current8.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined);
175
+ editor === null || editor === void 0 || editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined);
196
176
  } else {
197
- var _editorRef$current9;
198
- formatParagraph((_editorRef$current9 = editorRef.current) === null || _editorRef$current9 === void 0 ? void 0 : _editorRef$current9.getLexicalEditor());
177
+ formatParagraph(editor === null || editor === void 0 ? void 0 : editor.getLexicalEditor());
199
178
  }
200
- }, [blockType]);
201
- var formatCodeblock = useCallback(function () {
179
+ }, [blockType, editor]);
180
+ var codeblock = useCallback(function () {
202
181
  if (blockType !== 'code') {
203
- var _editorRef$current10;
204
- (_editorRef$current10 = editorRef.current) === null || _editorRef$current10 === void 0 || (_editorRef$current10 = _editorRef$current10.getLexicalEditor()) === null || _editorRef$current10 === void 0 || _editorRef$current10.update(function () {
182
+ var _editor$getLexicalEdi2;
183
+ editor === null || editor === void 0 || (_editor$getLexicalEdi2 = editor.getLexicalEditor()) === null || _editor$getLexicalEdi2 === void 0 || _editor$getLexicalEdi2.update(function () {
205
184
  var selection = $getSelection();
206
185
  if (!selection) {
207
186
  return;
@@ -220,83 +199,103 @@ export function useToolbarState(editorRef) {
220
199
  }
221
200
  }
222
201
  });
202
+ } else {
203
+ formatParagraph(editor === null || editor === void 0 ? void 0 : editor.getLexicalEditor());
204
+ }
205
+ }, [blockType, editor]);
206
+ var blockquote = useCallback(function () {
207
+ if (blockType !== 'quote') {
208
+ var _editor$getLexicalEdi3;
209
+ editor === null || editor === void 0 || (_editor$getLexicalEdi3 = editor.getLexicalEditor()) === null || _editor$getLexicalEdi3 === void 0 || _editor$getLexicalEdi3.update(function () {
210
+ var selection = $getSelection();
211
+ if ($isRangeSelection(selection)) {
212
+ $setBlocksType(selection, function () {
213
+ return $createQuoteNode();
214
+ });
215
+ }
216
+ });
217
+ } else {
218
+ formatParagraph(editor === null || editor === void 0 ? void 0 : editor.getLexicalEditor());
223
219
  }
224
- }, [blockType]);
220
+ }, [blockType, editor]);
225
221
  var updateCodeblockLang = useCallback(function (lang) {
226
- var _editorRef$current11;
227
- if (!isInCodeblock) {
222
+ if (!isCodeblock) {
228
223
  return;
229
224
  }
230
- (_editorRef$current11 = editorRef.current) === null || _editorRef$current11 === void 0 || _editorRef$current11.dispatchCommand(UPDATE_CODEBLOCK_LANG, {
225
+ editor === null || editor === void 0 || editor.dispatchCommand(UPDATE_CODEBLOCK_LANG, {
231
226
  lang: lang
232
227
  });
233
- }, [editorRef.current, isInCodeblock]);
228
+ }, [editor, isCodeblock]);
234
229
  var insertLink = useCallback(function () {
235
230
  if (!isLink) {
236
- var _editorRef$current12;
231
+ var _editor$getLexicalEdi4;
237
232
  setIsLink(true);
238
- (_editorRef$current12 = editorRef.current) === null || _editorRef$current12 === void 0 || (_editorRef$current12 = _editorRef$current12.getLexicalEditor()) === null || _editorRef$current12 === void 0 || _editorRef$current12.dispatchCommand(TOGGLE_LINK_COMMAND, sanitizeUrl('https://'));
233
+ editor === null || editor === void 0 || (_editor$getLexicalEdi4 = editor.getLexicalEditor()) === null || _editor$getLexicalEdi4 === void 0 || _editor$getLexicalEdi4.dispatchCommand(TOGGLE_LINK_COMMAND, sanitizeUrl('https://'));
239
234
  } else {
240
- var _editorRef$current13;
235
+ var _editor$getLexicalEdi5;
241
236
  setIsLink(false);
242
- (_editorRef$current13 = editorRef.current) === null || _editorRef$current13 === void 0 || (_editorRef$current13 = _editorRef$current13.getLexicalEditor()) === null || _editorRef$current13 === void 0 || _editorRef$current13.dispatchCommand(TOGGLE_LINK_COMMAND, null);
237
+ editor === null || editor === void 0 || (_editor$getLexicalEdi5 = editor.getLexicalEditor()) === null || _editor$getLexicalEdi5 === void 0 || _editor$getLexicalEdi5.dispatchCommand(TOGGLE_LINK_COMMAND, null);
243
238
  }
244
- }, [editorRef.current, isLink]);
239
+ }, [editor, isLink]);
245
240
  useEffect(function () {
246
- if (!editorRef.current) return;
247
- var editor = editorRef.current;
241
+ if (!editor) return;
248
242
  var lexicalEditor = editor.getLexicalEditor();
249
243
  var cleanup = function cleanup() {};
250
- var handleLeixcalEditor = function handleLeixcalEditor(editor) {
251
- cleanup = mergeRegister(editor.registerUpdateListener(function (_ref) {
244
+ var handleLexicalEditor = function handleLexicalEditor(lexicalEditor) {
245
+ cleanup = mergeRegister(lexicalEditor.registerUpdateListener(function (_ref) {
252
246
  var editorState = _ref.editorState;
253
247
  editorState.read(function () {
254
248
  $updateToolbar();
255
249
  });
256
- }), editor.registerCommand(SELECTION_CHANGE_COMMAND, function () {
250
+ }), lexicalEditor.registerCommand(SELECTION_CHANGE_COMMAND, function () {
257
251
  $updateToolbar();
258
252
  return false;
259
- }, COMMAND_PRIORITY_LOW), editor.registerCommand(CAN_UNDO_COMMAND, function (payload) {
253
+ }, COMMAND_PRIORITY_LOW), lexicalEditor.registerCommand(CAN_UNDO_COMMAND, function (payload) {
260
254
  setCanUndo(payload);
261
255
  return false;
262
- }, COMMAND_PRIORITY_LOW), editor.registerCommand(CAN_REDO_COMMAND, function (payload) {
256
+ }, COMMAND_PRIORITY_LOW), lexicalEditor.registerCommand(CAN_REDO_COMMAND, function (payload) {
263
257
  setCanRedo(payload);
264
258
  return false;
265
259
  }, COMMAND_PRIORITY_LOW));
266
260
  return cleanup;
267
261
  };
268
262
  if (!lexicalEditor) {
269
- editor.on('initialized', handleLeixcalEditor);
263
+ editor.on('initialized', handleLexicalEditor);
270
264
  return function () {
271
265
  cleanup();
272
- editor.off('initialized', handleLeixcalEditor);
266
+ editor.off('initialized', handleLexicalEditor);
273
267
  };
274
268
  }
275
- return handleLeixcalEditor(lexicalEditor);
276
- }, [editorRef.current]);
277
- return {
278
- blockType: blockType,
279
- bold: bold,
280
- bulletList: bulletList,
281
- canRedo: canRedo,
282
- canUndo: canUndo,
283
- code: code,
284
- codeblockLang: codeblockLang,
285
- formatCodeblock: formatCodeblock,
286
- insertLink: insertLink,
287
- isBold: isBold,
288
- isCode: isCode,
289
- isEmpty: isEmpty,
290
- isInCodeblock: isInCodeblock,
291
- isItalic: isItalic,
292
- isStrikethrough: isStrikethrough,
293
- isUnderline: isUnderline,
294
- italic: italic,
295
- numberList: numberList,
296
- redo: redo,
297
- strikethrough: strikethrough,
298
- underline: underline,
299
- undo: undo,
300
- updateCodeblockLang: updateCodeblockLang
301
- };
269
+ return handleLexicalEditor(lexicalEditor);
270
+ }, [editor, $updateToolbar]);
271
+ return useMemo(function () {
272
+ return {
273
+ blockType: blockType,
274
+ blockquote: blockquote,
275
+ bold: bold,
276
+ bulletList: bulletList,
277
+ canRedo: canRedo,
278
+ canUndo: canUndo,
279
+ code: code,
280
+ codeblock: codeblock,
281
+ codeblockLang: codeblockLang,
282
+ insertLink: insertLink,
283
+ isBlockquote: isBlockquote,
284
+ isBold: isBold,
285
+ isCode: isCode,
286
+ isCodeblock: isCodeblock,
287
+ isEmpty: isEmpty,
288
+ isItalic: isItalic,
289
+ isSelected: isSelected,
290
+ isStrikethrough: isStrikethrough,
291
+ isUnderline: isUnderline,
292
+ italic: italic,
293
+ numberList: numberList,
294
+ redo: redo,
295
+ strikethrough: strikethrough,
296
+ underline: underline,
297
+ undo: undo,
298
+ updateCodeblockLang: updateCodeblockLang
299
+ };
300
+ }, [blockType, bold, bulletList, canRedo, canUndo, code, codeblockLang, blockquote, codeblock, insertLink, isBold, isCode, isEmpty, isBlockquote, isCodeblock, isItalic, isSelected, isStrikethrough, isUnderline, italic, numberList, redo, strikethrough, underline, undo, updateCodeblockLang]);
302
301
  }
@@ -0,0 +1,4 @@
1
+ import { ElementNode, LexicalEditor, LexicalNode, RangeSelection, TextNode } from 'lexical';
2
+ export declare const $findTopLevelElement: (node: LexicalNode) => LexicalNode;
3
+ export declare const formatParagraph: (editor?: LexicalEditor | null) => void;
4
+ export declare const getSelectedNode: (selection: RangeSelection) => TextNode | ElementNode;
@@ -0,0 +1,36 @@
1
+ import { $isAtNodeEnd, $setBlocksType } from '@lexical/selection';
2
+ import { $findMatchingParent } from '@lexical/utils';
3
+ import { $createParagraphNode, $getSelection, $isRootOrShadowRoot } from 'lexical';
4
+ export var $findTopLevelElement = function $findTopLevelElement(node) {
5
+ var topLevelElement = node.getKey() === 'root' ? node : $findMatchingParent(node, function (e) {
6
+ var parent = e.getParent();
7
+ return parent !== null && $isRootOrShadowRoot(parent);
8
+ });
9
+ if (topLevelElement === null) {
10
+ topLevelElement = node.getTopLevelElementOrThrow();
11
+ }
12
+ return topLevelElement;
13
+ };
14
+ export var formatParagraph = function formatParagraph(editor) {
15
+ editor === null || editor === void 0 || editor.update(function () {
16
+ var selection = $getSelection();
17
+ $setBlocksType(selection, function () {
18
+ return $createParagraphNode();
19
+ });
20
+ });
21
+ };
22
+ export var getSelectedNode = function getSelectedNode(selection) {
23
+ var anchor = selection.anchor;
24
+ var focus = selection.focus;
25
+ var anchorNode = selection.anchor.getNode();
26
+ var focusNode = selection.focus.getNode();
27
+ if (anchorNode === focusNode) {
28
+ return anchorNode;
29
+ }
30
+ var isBackward = selection.isBackward();
31
+ if (isBackward) {
32
+ return $isAtNodeEnd(focus) ? anchorNode : focusNode;
33
+ } else {
34
+ return $isAtNodeEnd(anchor) ? anchorNode : focusNode;
35
+ }
36
+ };
@@ -2,7 +2,9 @@ export { default as ChatInput, type ChatInputProps } from './ChatInput';
2
2
  export { default as ChatInputActionBar, type ChatInputActionBarProps } from './ChatInputActionBar';
3
3
  export { type ChatInputActionEvent, default as ChatInputActions, type ChatInputActionsProps, } from './ChatInputActions';
4
4
  export { default as CodeLanguageSelect, type CodeLanguageSelectProps } from './CodeLanguageSelect';
5
- export { default as Editor, type EditorProps, useEditor, withProps } from './Editor';
5
+ export { default as Editor, type EditorProps, withProps } from './Editor';
6
6
  export { EditorProvider, type EditorProviderConfig, type EditorProviderProps, useEditorContent, } from './EditorProvider';
7
+ export { useEditor } from './hooks/useEditor';
8
+ export { type EditorState, useEditorState } from './hooks/useEditorState';
7
9
  export { default as SendButton, type SendButtonProps } from './SendButton';
8
10
  export { default as SlashMenu, type SlashMenuProps } from './SlashMenu';
package/es/react/index.js CHANGED
@@ -2,7 +2,9 @@ export { default as ChatInput } from "./ChatInput";
2
2
  export { default as ChatInputActionBar } from "./ChatInputActionBar";
3
3
  export { default as ChatInputActions } from "./ChatInputActions";
4
4
  export { default as CodeLanguageSelect } from "./CodeLanguageSelect";
5
- export { default as Editor, useEditor, withProps } from "./Editor";
5
+ export { default as Editor, withProps } from "./Editor";
6
6
  export { EditorProvider, useEditorContent } from "./EditorProvider";
7
+ export { useEditor } from "./hooks/useEditor";
8
+ export { useEditorState } from "./hooks/useEditorState";
7
9
  export { default as SendButton } from "./SendButton";
8
10
  export { default as SlashMenu } from "./SlashMenu";
@@ -28,6 +28,10 @@ export interface IEditor {
28
28
  * Lose focus
29
29
  */
30
30
  blur(): void;
31
+ /**
32
+ * Clean editor content (clear all content)
33
+ */
34
+ cleanDocument(): void;
31
35
  /**
32
36
  * Destroy editor instance
33
37
  */
@@ -63,6 +67,16 @@ export interface IEditor {
63
67
  * Get editor theme
64
68
  */
65
69
  getTheme(): Record<string, string | Record<string, string>>;
70
+ /**
71
+ * Check if editor content is empty
72
+ * @returns true if editor content is empty, false otherwise
73
+ */
74
+ get isEmpty(): boolean;
75
+ /**
76
+ * Check if editor has active selection
77
+ * @returns true if editor has selection, false otherwise
78
+ */
79
+ get isSelected(): boolean;
66
80
  /**
67
81
  * Remove editor event listener
68
82
  * @param event
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/editor",
3
- "version": "1.4.1",
3
+ "version": "1.4.3",
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",
@@ -1,31 +0,0 @@
1
- import { type RefObject } from 'react';
2
- import { IEditor } from "../../types";
3
- /**
4
- * Provide toolbar state and toolbar methods
5
- * @returns
6
- */
7
- export declare function useToolbarState(editorRef: RefObject<IEditor | null>): {
8
- blockType: string | null;
9
- bold: () => void;
10
- bulletList: () => void;
11
- canRedo: boolean;
12
- canUndo: boolean;
13
- code: () => void;
14
- codeblockLang: string | null | undefined;
15
- formatCodeblock: () => void;
16
- insertLink: () => void;
17
- isBold: boolean;
18
- isCode: boolean;
19
- isEmpty: boolean;
20
- isInCodeblock: boolean;
21
- isItalic: boolean;
22
- isStrikethrough: boolean;
23
- isUnderline: boolean;
24
- italic: () => void;
25
- numberList: () => void;
26
- redo: () => void;
27
- strikethrough: () => void;
28
- underline: () => void;
29
- undo: () => void;
30
- updateCodeblockLang: (lang: string) => void;
31
- };
@@ -1,3 +0,0 @@
1
- import { RefObject } from 'react';
2
- import type { IEditor } from "../../types";
3
- export declare const useEditor: (editorRef?: RefObject<IEditor | null>) => RefObject<IEditor | null>;
@@ -1,5 +0,0 @@
1
- import { useRef } from 'react';
2
- export var useEditor = function useEditor(editorRef) {
3
- var ref = useRef(null);
4
- return editorRef || ref;
5
- };