onchain-lexical-instance 0.0.8 → 0.0.9

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.
@@ -8,26 +8,19 @@
8
8
 
9
9
  'use strict';
10
10
 
11
- var lexical = require('lexical');
12
- var utils$1 = require('@lexical/utils');
13
- var traversal = require('onchain-utility/traversal');
14
11
  var code = require('@lexical/code');
15
- var list = require('@lexical/list');
16
- var richText = require('@lexical/rich-text');
17
- var onchainLexicalMarkdown = require('onchain-lexical-markdown');
18
- var onchainUtility = require('onchain-utility');
12
+ var utils$1 = require('@lexical/utils');
13
+ var lexical = require('lexical');
14
+ var LexicalHorizontalRuleNode = require('@lexical/react/LexicalHorizontalRuleNode');
19
15
  var LexicalComposerContext = require('@lexical/react/LexicalComposerContext');
20
- var instanceConfig = require('onchain-lexical-context/instanceConfig');
21
- var Skeleton = require('onchain-lexical-ui/Skeleton');
22
16
  var React = require('react');
23
17
  var jsxRuntime = require('react/jsx-runtime');
24
- var settings = require('onchain-lexical-context/settings');
25
- var DropDown = require('onchain-lexical-ui/DropDown');
26
- var Icon = require('onchain-lexical-ui/Icon');
27
- var LexicalHorizontalRuleNode = require('@lexical/react/LexicalHorizontalRuleNode');
18
+ var onchainLexicalMarkdown = require('onchain-lexical-markdown');
19
+ var list = require('@lexical/list');
28
20
  var selection = require('@lexical/selection');
29
21
  var table = require('@lexical/table');
30
22
  var useLexicalNodeSelection = require('@lexical/react/useLexicalNodeSelection');
23
+ var richText = require('@lexical/rich-text');
31
24
  var useLexicalEditable = require('@lexical/react/useLexicalEditable');
32
25
  var EquationEditor = require('onchain-lexical-ui/EquationEditor');
33
26
  var KatexRenderer = require('onchain-lexical-ui/KatexRenderer');
@@ -41,9 +34,16 @@ var LexicalNestedComposer = require('@lexical/react/LexicalNestedComposer');
41
34
  var LexicalRichTextPlugin = require('@lexical/react/LexicalRichTextPlugin');
42
35
  var onchainLexicalContext = require('onchain-lexical-context');
43
36
  var collaboration = require('onchain-lexical-context/collaboration');
37
+ var instanceConfig = require('onchain-lexical-context/instanceConfig');
44
38
  var sharedHistory = require('onchain-lexical-context/sharedHistory');
45
39
  var ContentEditable = require('onchain-lexical-ui/ContentEditable');
46
40
  var ImageResizer = require('onchain-lexical-ui/ImageResizer');
41
+ var onchainUtility = require('onchain-utility');
42
+ var traversal = require('onchain-utility/traversal');
43
+ var Skeleton = require('onchain-lexical-ui/Skeleton');
44
+ var settings = require('onchain-lexical-context/settings');
45
+ var DropDown = require('onchain-lexical-ui/DropDown');
46
+ var Icon = require('onchain-lexical-ui/Icon');
47
47
  var hashtag = require('@lexical/hashtag');
48
48
  var Button = require('onchain-lexical-ui/Button');
49
49
  var Dialog = require('onchain-lexical-ui/Dialog');
@@ -142,12 +142,28 @@ const fixedAddress = new Map();
142
142
 
143
143
  /** 用来存储内联链接,更新文本函数,用来实现链接目标标题变化时,引用处可以实时更新 */
144
144
  const internalLinkNameUpdateMap = new Map();
145
+
146
+ /** 打开创建实例节点 */
145
147
  const OPEN_CREATE_WINDOW = lexical.createCommand('OPEN_CREATE_WINDOW');
148
+
149
+ /** 添加新的实例节点 */
146
150
  const ADD_NEW_INSTANCE_NODE = lexical.createCommand('ADD_NEW_INSTANCE_NODE');
151
+
152
+ /** 删除实例节点 */
147
153
  const DELETE_INSTANCE_NODE = 'DELETE_INSTANCE_NODE';
154
+
155
+ /** 插入参数 */
148
156
  const INSERT_PARAMETERS = lexical.createCommand('INSERT_PARAMETERS');
157
+
158
+ /** 富文本 DecoratorNode 节点组件更新事件*/
149
159
  const COMPONENT_UPDATE = lexical.createCommand('COMPONENT_UPDATE');
150
160
 
161
+ /** 标题更新事件 */
162
+ const INSTANCE_TITLE_UPDATE = lexical.createCommand('INSTANCE_TITLE_UPDATE');
163
+
164
+ /** 获取禁止编辑dom节点选择器 */
165
+ const DisableSelector = `[contenteditable='false']:not([ignorecontenteditable])`;
166
+
151
167
  /**
152
168
  * Copyright (c) Meta Platforms, Inc. and affiliates.
153
169
  *
@@ -417,6 +433,7 @@ class PlaceholderDecoratorNode extends lexical.DecoratorNode {
417
433
  pointer-events: none;
418
434
  user-select: none;
419
435
  `;
436
+ span.setAttribute('ignorecontenteditable', '');
420
437
  return span;
421
438
  }
422
439
  updateDOM() {
@@ -565,7 +582,16 @@ class InstanceTitleNode extends InstanceHeadingNode {
565
582
  if (instanceNode && instanceNode.__instance.value) {
566
583
  const text = this.getTextContent().trim();
567
584
  setInstanceAttrValue(instanceNode.__instance.value, 'insDesc', text);
568
- internalLinkNameUpdateMap.get(instanceNode.__instance.value.number)?.values().forEach(update => update());
585
+ const editor = lexical.$getEditor();
586
+ Promise.resolve().then(() => {
587
+ editor.read(() => {
588
+ editor.dispatchCommand(INSTANCE_TITLE_UPDATE, {
589
+ isInput: true,
590
+ number: instanceNode.__instance.value.number,
591
+ title: text
592
+ });
593
+ });
594
+ });
569
595
  }
570
596
  return super.updateDOM(prevNode, dom, config);
571
597
  }
@@ -837,7 +863,14 @@ function updateRelatedInternalLink(nodes) {
837
863
  const insNodes = nodes.filter(node => $isInstanceNode(node));
838
864
  if (insNodes.length) {
839
865
  insNodes.forEach(node => {
840
- internalLinkNameUpdateMap.get(node.__instance.value.number)?.values().forEach(update => update());
866
+ const editor = lexical.$getEditor();
867
+ Promise.resolve().then(() => {
868
+ editor.read(() => {
869
+ editor.dispatchCommand(INSTANCE_TITLE_UPDATE, {
870
+ number: node.__instance.value.number
871
+ });
872
+ });
873
+ });
841
874
  });
842
875
  }
843
876
  }
@@ -853,7 +886,7 @@ function nodeMoveUp(nodes) {
853
886
  previous?.insertBefore(current);
854
887
  }
855
888
  }
856
- updateRelatedInternalLink(nodes);
889
+ updateRelatedInternalLink($isInstanceNode(previous) ? [...nodes, previous] : nodes);
857
890
  }
858
891
  }
859
892
  function nodeMoveDown(nodes) {
@@ -868,7 +901,7 @@ function nodeMoveDown(nodes) {
868
901
  next?.insertAfter(current);
869
902
  }
870
903
  }
871
- updateRelatedInternalLink(nodes);
904
+ updateRelatedInternalLink($isInstanceNode(next) ? [...nodes, next] : nodes);
872
905
  }
873
906
  }
874
907
  async function $nodeUpgrade(nodes) {
@@ -1011,6 +1044,27 @@ function setHSEntryMap(hs, instanceNode) {
1011
1044
  return state;
1012
1045
  });
1013
1046
  }
1047
+ function useContentEditable() {
1048
+ const [editor] = LexicalComposerContext.useLexicalComposerContext();
1049
+ const [contentEditable, setContentEditable] = React.useState(true);
1050
+ const $updateContentEditable = React.useCallback(() => {
1051
+ const selection = lexical.$getSelection();
1052
+ if (selection) {
1053
+ setContentEditable(!selection.getNodes().some(node => {
1054
+ return editor.getElementByKey(node.getKey())?.closest(DisableSelector);
1055
+ }));
1056
+ }
1057
+ }, [editor]);
1058
+ React.useEffect(() => {
1059
+ return editor.registerCommand(lexical.SELECTION_CHANGE_COMMAND, _payload => {
1060
+ $updateContentEditable();
1061
+ return false;
1062
+ }, lexical.COMMAND_PRIORITY_CRITICAL);
1063
+ }, [editor, $updateContentEditable]);
1064
+ return {
1065
+ contentEditable
1066
+ };
1067
+ }
1014
1068
 
1015
1069
  /**
1016
1070
  * Copyright (c) Meta Platforms, Inc. and affiliates.
@@ -22342,6 +22396,7 @@ class ImageNode extends lexical.DecoratorNode {
22342
22396
  if (className !== undefined) {
22343
22397
  span.className = className;
22344
22398
  }
22399
+ span.setAttribute('ignorecontenteditable', '');
22345
22400
  return span;
22346
22401
  }
22347
22402
  updateDOM() {
@@ -23674,7 +23729,7 @@ const InstancePlugin = props => {
23674
23729
  const selection = lexical.$getSelection();
23675
23730
  if (selection) {
23676
23731
  return selection.getNodes().some(node => {
23677
- return editor.getElementByKey(node.getKey())?.closest(`[contenteditable='false']`);
23732
+ return editor.getElementByKey(node.getKey())?.closest(DisableSelector);
23678
23733
  });
23679
23734
  }
23680
23735
  return false;
@@ -23765,43 +23820,38 @@ styleInject(css_248z$3);
23765
23820
  /* eslint-disable @lexical/rules-of-lexical */
23766
23821
  /* eslint-disable jsx-a11y/no-static-element-interactions */
23767
23822
 
23768
- function InternalLinkComponent({
23769
- self,
23770
- editor,
23771
- config,
23772
- number,
23773
- nodeKey
23774
- }) {
23823
+ function InternalLinkComponent(props) {
23824
+ const {
23825
+ editor,
23826
+ number
23827
+ } = props;
23775
23828
  const [name, setName] = React.useState();
23776
23829
  const [serial, setSerial] = React.useState();
23777
- const update = React.useCallback(function () {
23778
- editor.read(() => {
23779
- const node = $getInstanceNodeByNumber(number);
23780
- setName(getLatestValue(node.__instance.value, 'insDesc'));
23781
- setSerial(node.getSerialNumber());
23782
- });
23783
- }, []);
23784
23830
  React.useEffect(() => {
23785
- update();
23831
+ Promise.resolve().then(() => {
23832
+ // 需要等待节点载入富文本后才能获取到实例节点
23833
+ editor.read(() => {
23834
+ const node = $getInstanceNodeByNumber(number);
23835
+ if (node) {
23836
+ setName(getLatestValue(node.__instance.value, 'insDesc'));
23837
+ setSerial(node.getSerialNumber());
23838
+ }
23839
+ });
23840
+ });
23786
23841
  }, []);
23787
23842
  React.useEffect(() => {
23788
- let timeout = 0;
23789
- let map;
23790
- const debounceUpdate = () => {
23791
- clearTimeout(timeout);
23792
- timeout = window.setTimeout(() => {
23793
- update();
23794
- }, 100);
23795
- };
23796
- if (internalLinkNameUpdateMap.has(number)) {
23797
- map = internalLinkNameUpdateMap.get(number).set(nodeKey, debounceUpdate);
23798
- } else {
23799
- map = new Map([[nodeKey, debounceUpdate]]);
23800
- internalLinkNameUpdateMap.set(number, map);
23801
- }
23802
- return () => {
23803
- map.delete(number);
23804
- };
23843
+ return utils$1.mergeRegister(editor.registerCommand(INSTANCE_TITLE_UPDATE, priority => {
23844
+ if (priority.number === number) {
23845
+ editor.read(() => {
23846
+ const node = $getInstanceNodeByNumber(priority.number);
23847
+ if (priority.title !== undefined && priority.title !== null) {
23848
+ setName(priority.title);
23849
+ }
23850
+ setSerial(node.getSerialNumber());
23851
+ });
23852
+ }
23853
+ return false;
23854
+ }, lexical.COMMAND_PRIORITY_EDITOR));
23805
23855
  }, []);
23806
23856
  if (name) {
23807
23857
  return /*#__PURE__*/jsxRuntime.jsxs("span", {
@@ -23836,12 +23886,23 @@ class InternalLinkNode extends lexical.TextDecoratorNode {
23836
23886
  return new InternalLinkNode(node.__number, node.__key);
23837
23887
  }
23838
23888
  static importJSON(serializedNode) {
23839
- return $createInternalLinkNode(serializedNode.__number).updateFromJSON(serializedNode);
23889
+ return $createInternalLinkNode(serializedNode.number).updateFromJSON(serializedNode);
23840
23890
  }
23841
23891
  constructor(number, key) {
23842
23892
  super(key);
23843
23893
  this.__number = number;
23844
23894
  }
23895
+ exportJSON() {
23896
+ return {
23897
+ ...super.exportJSON(),
23898
+ number: this.__number
23899
+ };
23900
+ }
23901
+ createDOM(config, editor) {
23902
+ const span = super.createDOM(config, editor);
23903
+ span.setAttribute('ignorecontenteditable', '');
23904
+ return span;
23905
+ }
23845
23906
  updateDOM(prevNode, dom, config) {
23846
23907
  return super.updateDOM(prevNode, dom, config);
23847
23908
  }
@@ -24620,6 +24681,9 @@ function ImageComponent({
24620
24681
  const onResizeStart = () => {
24621
24682
  setIsResizing(true);
24622
24683
  };
24684
+ const {
24685
+ contentEditable
24686
+ } = useContentEditable();
24623
24687
  const {
24624
24688
  historyState
24625
24689
  } = sharedHistory.useSharedHistoryContext();
@@ -24660,7 +24724,7 @@ function ImageComponent({
24660
24724
  ErrorBoundary: LexicalErrorBoundary.LexicalErrorBoundary
24661
24725
  })]
24662
24726
  })
24663
- }), resizable && lexical.$isNodeSelection(selection) && isFocused && /*#__PURE__*/jsxRuntime.jsx(ImageResizer, {
24727
+ }), resizable && lexical.$isNodeSelection(selection) && isFocused && contentEditable && /*#__PURE__*/jsxRuntime.jsx(ImageResizer, {
24664
24728
  showCaption: showCaption,
24665
24729
  setShowCaption: setShowCaption,
24666
24730
  editor: editor,
@@ -25073,10 +25137,12 @@ exports.CollapsibleContainerNode = CollapsibleContainerNode;
25073
25137
  exports.CollapsibleContentNode = CollapsibleContentNode;
25074
25138
  exports.CollapsibleTitleNode = CollapsibleTitleNode;
25075
25139
  exports.DELETE_INSTANCE_NODE = DELETE_INSTANCE_NODE;
25140
+ exports.DisableSelector = DisableSelector;
25076
25141
  exports.Fragment = Fragment;
25077
25142
  exports.INSERT_COLLAPSIBLE_COMMAND = INSERT_COLLAPSIBLE_COMMAND;
25078
25143
  exports.INSERT_INS_HORIZONTAL_RULE_COMMAND = INSERT_INS_HORIZONTAL_RULE_COMMAND;
25079
25144
  exports.INSERT_PARAMETERS = INSERT_PARAMETERS;
25145
+ exports.INSTANCE_TITLE_UPDATE = INSTANCE_TITLE_UPDATE;
25080
25146
  exports.ImageNode = ImageNode;
25081
25147
  exports.InlineImageNode = InlineImageNode;
25082
25148
  exports.InstanceCodeHighlightNode = InstanceCodeHighlightNode;
@@ -25127,3 +25193,4 @@ exports.setInstanceAttrValue = setInstanceAttrValue;
25127
25193
  exports.setTemporaryContentText = setTemporaryContentText;
25128
25194
  exports.updateChildrenListItemValue = updateChildrenListItemValue;
25129
25195
  exports.updateRelatedInternalLink = updateRelatedInternalLink;
25196
+ exports.useContentEditable = useContentEditable;
@@ -6,27 +6,20 @@
6
6
  *
7
7
  */
8
8
 
9
- import { createCommand, ElementNode, $applyNodeReplacement, isHTMLElement, setNodeIndentFromDOM, DecoratorNode, $isTextNode, $createTextNode, TextNode as TextNode$1, $getSelection, $isRootOrShadowRoot, $isRootNode, $getEditor, $getNodeByKey, scrollIntoViewIfNeeded, $isElementNode, ParagraphNode, COMMAND_PRIORITY_EDITOR, $getSiblingCaret, $isTabNode, $createTabNode, $createLineBreakNode, KEY_TAB_COMMAND, COMMAND_PRIORITY_LOW, INSERT_TAB_COMMAND, $insertNodes, INDENT_CONTENT_COMMAND, OUTDENT_CONTENT_COMMAND, KEY_ARROW_UP_COMMAND, $isRangeSelection, KEY_ARROW_DOWN_COMMAND, MOVE_TO_START, MOVE_TO_END, $isLineBreakNode, $rewindSiblingCaret, $createParagraphNode, createEditor, RootNode, LineBreakNode, $isLeafNode, $setPointFromCaret, $normalizeCaret, $getChildCaret, INSERT_PARAGRAPH_COMMAND, COMMAND_PRIORITY_NORMAL, $getPreviousSelection, DELETE_CHARACTER_COMMAND, COMMAND_PRIORITY_CRITICAL, TextDecoratorNode, COMMAND_PRIORITY_HIGH, CLICK_COMMAND, SELECTION_CHANGE_COMMAND, KEY_ESCAPE_COMMAND, $isNodeSelection, $createNodeSelection, $setSelection, DRAGSTART_COMMAND, KEY_ENTER_COMMAND } from 'lexical';
10
- import { $findMatchingParent, addClassNamesToElement, mergeRegister, removeClassNamesFromElement, $getAdjacentCaret, isHTMLElement as isHTMLElement$1, IS_CHROME, $insertNodeToNearestRoot, $getNearestNodeOfType, $descendantsMatching } from '@lexical/utils';
11
- import { dfs as dfs$1 } from 'onchain-utility/traversal';
12
9
  import { $isCodeNode, CodeNode } from '@lexical/code';
13
- import { $isListNode, $isListItemNode, $createListItemNode, ListItemNode, ListNode } from '@lexical/list';
14
- import { HeadingNode, $isHeadingNode, $isQuoteNode, QuoteNode } from '@lexical/rich-text';
15
- import { $textToRichNodes } from 'onchain-lexical-markdown';
16
- import { hasOwnProperty, dfs, translateI18n } from 'onchain-utility';
10
+ import { $findMatchingParent, addClassNamesToElement, mergeRegister, removeClassNamesFromElement, $getAdjacentCaret, isHTMLElement as isHTMLElement$1, IS_CHROME, $insertNodeToNearestRoot, $getNearestNodeOfType, $descendantsMatching } from '@lexical/utils';
11
+ import { createCommand, ElementNode, $applyNodeReplacement, isHTMLElement, setNodeIndentFromDOM, DecoratorNode, $getEditor, $isTextNode, $createTextNode, TextNode as TextNode$1, $getSelection, $isRootOrShadowRoot, $isRootNode, $getNodeByKey, scrollIntoViewIfNeeded, $isElementNode, SELECTION_CHANGE_COMMAND, COMMAND_PRIORITY_CRITICAL, ParagraphNode, COMMAND_PRIORITY_EDITOR, $getSiblingCaret, $isTabNode, $createTabNode, $createLineBreakNode, KEY_TAB_COMMAND, COMMAND_PRIORITY_LOW, INSERT_TAB_COMMAND, $insertNodes, INDENT_CONTENT_COMMAND, OUTDENT_CONTENT_COMMAND, KEY_ARROW_UP_COMMAND, $isRangeSelection, KEY_ARROW_DOWN_COMMAND, MOVE_TO_START, MOVE_TO_END, $isLineBreakNode, $rewindSiblingCaret, $createParagraphNode, createEditor, RootNode, LineBreakNode, $isLeafNode, $setPointFromCaret, $normalizeCaret, $getChildCaret, INSERT_PARAGRAPH_COMMAND, COMMAND_PRIORITY_NORMAL, $getPreviousSelection, DELETE_CHARACTER_COMMAND, TextDecoratorNode, COMMAND_PRIORITY_HIGH, CLICK_COMMAND, KEY_ESCAPE_COMMAND, $isNodeSelection, $createNodeSelection, $setSelection, DRAGSTART_COMMAND, KEY_ENTER_COMMAND } from 'lexical';
12
+ import { HorizontalRuleNode } from '@lexical/react/LexicalHorizontalRuleNode';
17
13
  import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
18
- import { useInstanceConfig } from 'onchain-lexical-context/instanceConfig';
19
- import Skeleton from 'onchain-lexical-ui/Skeleton';
20
14
  import * as React from 'react';
21
- import React__default, { useState, useEffect, useMemo, useCallback, useRef, Suspense } from 'react';
15
+ import React__default, { useState, useCallback, useEffect, useMemo, useRef, Suspense } from 'react';
22
16
  import { jsxs, jsx, Fragment as Fragment$1 } from 'react/jsx-runtime';
23
- import { useSettings } from 'onchain-lexical-context/settings';
24
- import DropDown, { DropDownItem } from 'onchain-lexical-ui/DropDown';
25
- import { Icon, StaticIcon } from 'onchain-lexical-ui/Icon';
26
- import { HorizontalRuleNode } from '@lexical/react/LexicalHorizontalRuleNode';
17
+ import { $textToRichNodes } from 'onchain-lexical-markdown';
18
+ import { $isListNode, $isListItemNode, $createListItemNode, ListItemNode, ListNode } from '@lexical/list';
27
19
  import { $setBlocksType } from '@lexical/selection';
28
20
  import { TableNode, INSERT_TABLE_COMMAND, $findTableNode, PIXEL_VALUE_REG_EXP, $isTableRowNode, $createTableRowNode, $createTableCellNode, TableCellHeaderStates } from '@lexical/table';
29
21
  import { useLexicalNodeSelection } from '@lexical/react/useLexicalNodeSelection';
22
+ import { HeadingNode, $isHeadingNode, $isQuoteNode, QuoteNode } from '@lexical/rich-text';
30
23
  import { useLexicalEditable } from '@lexical/react/useLexicalEditable';
31
24
  import EquationEditor from 'onchain-lexical-ui/EquationEditor';
32
25
  import KatexRenderer from 'onchain-lexical-ui/KatexRenderer';
@@ -40,9 +33,16 @@ import { LexicalNestedComposer } from '@lexical/react/LexicalNestedComposer';
40
33
  import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
41
34
  import { upload, deleteUpload } from 'onchain-lexical-context';
42
35
  import { createWebsocketProvider } from 'onchain-lexical-context/collaboration';
36
+ import { useInstanceConfig } from 'onchain-lexical-context/instanceConfig';
43
37
  import { useSharedHistoryContext } from 'onchain-lexical-context/sharedHistory';
44
38
  import ContentEditable from 'onchain-lexical-ui/ContentEditable';
45
39
  import ImageResizer from 'onchain-lexical-ui/ImageResizer';
40
+ import { hasOwnProperty, dfs, translateI18n } from 'onchain-utility';
41
+ import { dfs as dfs$1 } from 'onchain-utility/traversal';
42
+ import Skeleton from 'onchain-lexical-ui/Skeleton';
43
+ import { useSettings } from 'onchain-lexical-context/settings';
44
+ import DropDown, { DropDownItem } from 'onchain-lexical-ui/DropDown';
45
+ import { Icon, StaticIcon } from 'onchain-lexical-ui/Icon';
46
46
  import { HashtagNode } from '@lexical/hashtag';
47
47
  import Button from 'onchain-lexical-ui/Button';
48
48
  import { DialogActions } from 'onchain-lexical-ui/Dialog';
@@ -128,12 +128,28 @@ const fixedAddress = new Map();
128
128
 
129
129
  /** 用来存储内联链接,更新文本函数,用来实现链接目标标题变化时,引用处可以实时更新 */
130
130
  const internalLinkNameUpdateMap = new Map();
131
+
132
+ /** 打开创建实例节点 */
131
133
  const OPEN_CREATE_WINDOW = createCommand('OPEN_CREATE_WINDOW');
134
+
135
+ /** 添加新的实例节点 */
132
136
  const ADD_NEW_INSTANCE_NODE = createCommand('ADD_NEW_INSTANCE_NODE');
137
+
138
+ /** 删除实例节点 */
133
139
  const DELETE_INSTANCE_NODE = 'DELETE_INSTANCE_NODE';
140
+
141
+ /** 插入参数 */
134
142
  const INSERT_PARAMETERS = createCommand('INSERT_PARAMETERS');
143
+
144
+ /** 富文本 DecoratorNode 节点组件更新事件*/
135
145
  const COMPONENT_UPDATE = createCommand('COMPONENT_UPDATE');
136
146
 
147
+ /** 标题更新事件 */
148
+ const INSTANCE_TITLE_UPDATE = createCommand('INSTANCE_TITLE_UPDATE');
149
+
150
+ /** 获取禁止编辑dom节点选择器 */
151
+ const DisableSelector = `[contenteditable='false']:not([ignorecontenteditable])`;
152
+
137
153
  /**
138
154
  * Copyright (c) Meta Platforms, Inc. and affiliates.
139
155
  *
@@ -403,6 +419,7 @@ class PlaceholderDecoratorNode extends DecoratorNode {
403
419
  pointer-events: none;
404
420
  user-select: none;
405
421
  `;
422
+ span.setAttribute('ignorecontenteditable', '');
406
423
  return span;
407
424
  }
408
425
  updateDOM() {
@@ -551,7 +568,16 @@ class InstanceTitleNode extends InstanceHeadingNode {
551
568
  if (instanceNode && instanceNode.__instance.value) {
552
569
  const text = this.getTextContent().trim();
553
570
  setInstanceAttrValue(instanceNode.__instance.value, 'insDesc', text);
554
- internalLinkNameUpdateMap.get(instanceNode.__instance.value.number)?.values().forEach(update => update());
571
+ const editor = $getEditor();
572
+ Promise.resolve().then(() => {
573
+ editor.read(() => {
574
+ editor.dispatchCommand(INSTANCE_TITLE_UPDATE, {
575
+ isInput: true,
576
+ number: instanceNode.__instance.value.number,
577
+ title: text
578
+ });
579
+ });
580
+ });
555
581
  }
556
582
  return super.updateDOM(prevNode, dom, config);
557
583
  }
@@ -823,7 +849,14 @@ function updateRelatedInternalLink(nodes) {
823
849
  const insNodes = nodes.filter(node => $isInstanceNode(node));
824
850
  if (insNodes.length) {
825
851
  insNodes.forEach(node => {
826
- internalLinkNameUpdateMap.get(node.__instance.value.number)?.values().forEach(update => update());
852
+ const editor = $getEditor();
853
+ Promise.resolve().then(() => {
854
+ editor.read(() => {
855
+ editor.dispatchCommand(INSTANCE_TITLE_UPDATE, {
856
+ number: node.__instance.value.number
857
+ });
858
+ });
859
+ });
827
860
  });
828
861
  }
829
862
  }
@@ -839,7 +872,7 @@ function nodeMoveUp(nodes) {
839
872
  previous?.insertBefore(current);
840
873
  }
841
874
  }
842
- updateRelatedInternalLink(nodes);
875
+ updateRelatedInternalLink($isInstanceNode(previous) ? [...nodes, previous] : nodes);
843
876
  }
844
877
  }
845
878
  function nodeMoveDown(nodes) {
@@ -854,7 +887,7 @@ function nodeMoveDown(nodes) {
854
887
  next?.insertAfter(current);
855
888
  }
856
889
  }
857
- updateRelatedInternalLink(nodes);
890
+ updateRelatedInternalLink($isInstanceNode(next) ? [...nodes, next] : nodes);
858
891
  }
859
892
  }
860
893
  async function $nodeUpgrade(nodes) {
@@ -997,6 +1030,27 @@ function setHSEntryMap(hs, instanceNode) {
997
1030
  return state;
998
1031
  });
999
1032
  }
1033
+ function useContentEditable() {
1034
+ const [editor] = useLexicalComposerContext();
1035
+ const [contentEditable, setContentEditable] = useState(true);
1036
+ const $updateContentEditable = useCallback(() => {
1037
+ const selection = $getSelection();
1038
+ if (selection) {
1039
+ setContentEditable(!selection.getNodes().some(node => {
1040
+ return editor.getElementByKey(node.getKey())?.closest(DisableSelector);
1041
+ }));
1042
+ }
1043
+ }, [editor]);
1044
+ useEffect(() => {
1045
+ return editor.registerCommand(SELECTION_CHANGE_COMMAND, _payload => {
1046
+ $updateContentEditable();
1047
+ return false;
1048
+ }, COMMAND_PRIORITY_CRITICAL);
1049
+ }, [editor, $updateContentEditable]);
1050
+ return {
1051
+ contentEditable
1052
+ };
1053
+ }
1000
1054
 
1001
1055
  /**
1002
1056
  * Copyright (c) Meta Platforms, Inc. and affiliates.
@@ -22328,6 +22382,7 @@ class ImageNode extends DecoratorNode {
22328
22382
  if (className !== undefined) {
22329
22383
  span.className = className;
22330
22384
  }
22385
+ span.setAttribute('ignorecontenteditable', '');
22331
22386
  return span;
22332
22387
  }
22333
22388
  updateDOM() {
@@ -23660,7 +23715,7 @@ const InstancePlugin = props => {
23660
23715
  const selection = $getSelection();
23661
23716
  if (selection) {
23662
23717
  return selection.getNodes().some(node => {
23663
- return editor.getElementByKey(node.getKey())?.closest(`[contenteditable='false']`);
23718
+ return editor.getElementByKey(node.getKey())?.closest(DisableSelector);
23664
23719
  });
23665
23720
  }
23666
23721
  return false;
@@ -23751,43 +23806,38 @@ styleInject(css_248z$3);
23751
23806
  /* eslint-disable @lexical/rules-of-lexical */
23752
23807
  /* eslint-disable jsx-a11y/no-static-element-interactions */
23753
23808
 
23754
- function InternalLinkComponent({
23755
- self,
23756
- editor,
23757
- config,
23758
- number,
23759
- nodeKey
23760
- }) {
23809
+ function InternalLinkComponent(props) {
23810
+ const {
23811
+ editor,
23812
+ number
23813
+ } = props;
23761
23814
  const [name, setName] = useState();
23762
23815
  const [serial, setSerial] = useState();
23763
- const update = useCallback(function () {
23764
- editor.read(() => {
23765
- const node = $getInstanceNodeByNumber(number);
23766
- setName(getLatestValue(node.__instance.value, 'insDesc'));
23767
- setSerial(node.getSerialNumber());
23768
- });
23769
- }, []);
23770
23816
  useEffect(() => {
23771
- update();
23817
+ Promise.resolve().then(() => {
23818
+ // 需要等待节点载入富文本后才能获取到实例节点
23819
+ editor.read(() => {
23820
+ const node = $getInstanceNodeByNumber(number);
23821
+ if (node) {
23822
+ setName(getLatestValue(node.__instance.value, 'insDesc'));
23823
+ setSerial(node.getSerialNumber());
23824
+ }
23825
+ });
23826
+ });
23772
23827
  }, []);
23773
23828
  useEffect(() => {
23774
- let timeout = 0;
23775
- let map;
23776
- const debounceUpdate = () => {
23777
- clearTimeout(timeout);
23778
- timeout = window.setTimeout(() => {
23779
- update();
23780
- }, 100);
23781
- };
23782
- if (internalLinkNameUpdateMap.has(number)) {
23783
- map = internalLinkNameUpdateMap.get(number).set(nodeKey, debounceUpdate);
23784
- } else {
23785
- map = new Map([[nodeKey, debounceUpdate]]);
23786
- internalLinkNameUpdateMap.set(number, map);
23787
- }
23788
- return () => {
23789
- map.delete(number);
23790
- };
23829
+ return mergeRegister(editor.registerCommand(INSTANCE_TITLE_UPDATE, priority => {
23830
+ if (priority.number === number) {
23831
+ editor.read(() => {
23832
+ const node = $getInstanceNodeByNumber(priority.number);
23833
+ if (priority.title !== undefined && priority.title !== null) {
23834
+ setName(priority.title);
23835
+ }
23836
+ setSerial(node.getSerialNumber());
23837
+ });
23838
+ }
23839
+ return false;
23840
+ }, COMMAND_PRIORITY_EDITOR));
23791
23841
  }, []);
23792
23842
  if (name) {
23793
23843
  return /*#__PURE__*/jsxs("span", {
@@ -23822,12 +23872,23 @@ class InternalLinkNode extends TextDecoratorNode {
23822
23872
  return new InternalLinkNode(node.__number, node.__key);
23823
23873
  }
23824
23874
  static importJSON(serializedNode) {
23825
- return $createInternalLinkNode(serializedNode.__number).updateFromJSON(serializedNode);
23875
+ return $createInternalLinkNode(serializedNode.number).updateFromJSON(serializedNode);
23826
23876
  }
23827
23877
  constructor(number, key) {
23828
23878
  super(key);
23829
23879
  this.__number = number;
23830
23880
  }
23881
+ exportJSON() {
23882
+ return {
23883
+ ...super.exportJSON(),
23884
+ number: this.__number
23885
+ };
23886
+ }
23887
+ createDOM(config, editor) {
23888
+ const span = super.createDOM(config, editor);
23889
+ span.setAttribute('ignorecontenteditable', '');
23890
+ return span;
23891
+ }
23831
23892
  updateDOM(prevNode, dom, config) {
23832
23893
  return super.updateDOM(prevNode, dom, config);
23833
23894
  }
@@ -24606,6 +24667,9 @@ function ImageComponent({
24606
24667
  const onResizeStart = () => {
24607
24668
  setIsResizing(true);
24608
24669
  };
24670
+ const {
24671
+ contentEditable
24672
+ } = useContentEditable();
24609
24673
  const {
24610
24674
  historyState
24611
24675
  } = useSharedHistoryContext();
@@ -24646,7 +24710,7 @@ function ImageComponent({
24646
24710
  ErrorBoundary: LexicalErrorBoundary
24647
24711
  })]
24648
24712
  })
24649
- }), resizable && $isNodeSelection(selection) && isFocused && /*#__PURE__*/jsx(ImageResizer, {
24713
+ }), resizable && $isNodeSelection(selection) && isFocused && contentEditable && /*#__PURE__*/jsx(ImageResizer, {
24650
24714
  showCaption: showCaption,
24651
24715
  setShowCaption: setShowCaption,
24652
24716
  editor: editor,
@@ -24974,4 +25038,4 @@ var InlineImageComponent$1 = {
24974
25038
  default: InlineImageComponent
24975
25039
  };
24976
25040
 
24977
- export { $createBarDecoratorNode, $createCollapsibleContainerNode, $createCollapsibleContentNode, $createCollapsibleTitleNode, $createFragmentNode, $createImageNode, $createInlineImageNode, $createInstanceCodeHighlightNode, $createInstanceCodeNode, $createInstanceEquationNode, $createInstanceHeadingNode, $createInstanceHorizontalRuleNode, $createInstanceListItemNode, $createInstanceListNode, $createInstanceNode, $createInstanceParagraphNode, $createInstanceQuoteNode, $createInstanceTableNode, $createInstanceTitleNode, $createInternalLinkNode, $createKeywordNode, $createNumberDecoratorNode, $createPageBreakNode, $createPlaceholderDecoratorNode, $createTitleOnlyInstanceNode, $findNearestListItemNode, $getAllListItems, $getAnchorRootNode, $getInstanceNodeByChild, $getInstanceNodeByNumber, $getInstanceNodeKeyByNumber, $getListDepth, $getTitleNodeByChild, $getTopListNode, $handleIndent, $handleListInsertParagraph, $handleOutdent, $insertInstanceList, $isBarDecoratorNode, $isCollapsibleContainerNode, $isCollapsibleContentNode, $isCollapsibleTitleNode, $isEmptyInstanceParagraphNode, $isEmptyParagraphNode, $isImageNode, $isInInstanceTitleNode, $isInlineImageNode, $isInstanceCodeHighlightNode, $isInstanceCodeNode, $isInstanceEquationNode, $isInstanceHeadingNode, $isInstanceHorizontalRuleNode, $isInstanceListItemNode, $isInstanceListNode, $isInstanceNode, $isInstanceParagraphFormalNode, $isInstanceParagraphNode, $isInstanceQuoteNode, $isInstanceTableNode, $isInstanceTitleNode, $isInternalLinkNode, $isKeywordNode, $isLastItemInList, $isNumberDecoratorNode, $isPageBreakNode, $isPlaceholderDecoratorNode, $isRemoved, $isSelectedTitleNode, $isTextTypeNode, $nodeDowngrade, $nodeUpgrade, $removeHighestEmptyListParent, $removeList, $scrollTo, $updateRichHistoryStateMap, $updateRichInstanceContent, $updateRichInstanceTitle, $wrapInListItem, ADD_NEW_INSTANCE_NODE, BarDecoratorNode, COMPONENT_UPDATE, CollapsibleContainerNode, CollapsibleContentNode, CollapsibleTitleNode, DELETE_INSTANCE_NODE, Fragment, INSERT_COLLAPSIBLE_COMMAND, INSERT_INS_HORIZONTAL_RULE_COMMAND, INSERT_PARAMETERS, ImageNode, InlineImageNode, InstanceCodeHighlightNode, InstanceCodeNode, InstanceEquationNode, InstanceHeadingNode, InstanceHorizontalRuleNode, InstanceListItemNode, InstanceListNode, InstanceNode, InstanceParagraphNode, InstanceParagraphType, InstancePlugin, InstanceQuoteNode, InstanceTableNode, InstanceTitleNode, InternalLinkNode, KeywordNode, NumberDecoratorNode, OPEN_CREATE_WINDOW, PageBreakNode, PlaceholderDecoratorNode, clearCache, fixedAddress, getCachedClassNameArray, getCodeLanguages, getDefaultCodeLanguage, getInstanceAttrValue, getInstanceBaseInfo, getLanguageFriendlyName, getLatestValue, getTemporaryContentText, ignoreHistory, instanceNodeMap, internalLinkNameUpdateMap, isCompleteInstance, isNestedListNode, mergeLists, mergeNextSiblingListIfSameType, nodeMoveDown, nodeMoveUp, normalizeCodeLang, numberNodeKey, paragraphSymbol, registerCodeHighlighting, setDisable, setInstanceAttrValue, setTemporaryContentText, updateChildrenListItemValue, updateRelatedInternalLink };
25041
+ export { $createBarDecoratorNode, $createCollapsibleContainerNode, $createCollapsibleContentNode, $createCollapsibleTitleNode, $createFragmentNode, $createImageNode, $createInlineImageNode, $createInstanceCodeHighlightNode, $createInstanceCodeNode, $createInstanceEquationNode, $createInstanceHeadingNode, $createInstanceHorizontalRuleNode, $createInstanceListItemNode, $createInstanceListNode, $createInstanceNode, $createInstanceParagraphNode, $createInstanceQuoteNode, $createInstanceTableNode, $createInstanceTitleNode, $createInternalLinkNode, $createKeywordNode, $createNumberDecoratorNode, $createPageBreakNode, $createPlaceholderDecoratorNode, $createTitleOnlyInstanceNode, $findNearestListItemNode, $getAllListItems, $getAnchorRootNode, $getInstanceNodeByChild, $getInstanceNodeByNumber, $getInstanceNodeKeyByNumber, $getListDepth, $getTitleNodeByChild, $getTopListNode, $handleIndent, $handleListInsertParagraph, $handleOutdent, $insertInstanceList, $isBarDecoratorNode, $isCollapsibleContainerNode, $isCollapsibleContentNode, $isCollapsibleTitleNode, $isEmptyInstanceParagraphNode, $isEmptyParagraphNode, $isImageNode, $isInInstanceTitleNode, $isInlineImageNode, $isInstanceCodeHighlightNode, $isInstanceCodeNode, $isInstanceEquationNode, $isInstanceHeadingNode, $isInstanceHorizontalRuleNode, $isInstanceListItemNode, $isInstanceListNode, $isInstanceNode, $isInstanceParagraphFormalNode, $isInstanceParagraphNode, $isInstanceQuoteNode, $isInstanceTableNode, $isInstanceTitleNode, $isInternalLinkNode, $isKeywordNode, $isLastItemInList, $isNumberDecoratorNode, $isPageBreakNode, $isPlaceholderDecoratorNode, $isRemoved, $isSelectedTitleNode, $isTextTypeNode, $nodeDowngrade, $nodeUpgrade, $removeHighestEmptyListParent, $removeList, $scrollTo, $updateRichHistoryStateMap, $updateRichInstanceContent, $updateRichInstanceTitle, $wrapInListItem, ADD_NEW_INSTANCE_NODE, BarDecoratorNode, COMPONENT_UPDATE, CollapsibleContainerNode, CollapsibleContentNode, CollapsibleTitleNode, DELETE_INSTANCE_NODE, DisableSelector, Fragment, INSERT_COLLAPSIBLE_COMMAND, INSERT_INS_HORIZONTAL_RULE_COMMAND, INSERT_PARAMETERS, INSTANCE_TITLE_UPDATE, ImageNode, InlineImageNode, InstanceCodeHighlightNode, InstanceCodeNode, InstanceEquationNode, InstanceHeadingNode, InstanceHorizontalRuleNode, InstanceListItemNode, InstanceListNode, InstanceNode, InstanceParagraphNode, InstanceParagraphType, InstancePlugin, InstanceQuoteNode, InstanceTableNode, InstanceTitleNode, InternalLinkNode, KeywordNode, NumberDecoratorNode, OPEN_CREATE_WINDOW, PageBreakNode, PlaceholderDecoratorNode, clearCache, fixedAddress, getCachedClassNameArray, getCodeLanguages, getDefaultCodeLanguage, getInstanceAttrValue, getInstanceBaseInfo, getLanguageFriendlyName, getLatestValue, getTemporaryContentText, ignoreHistory, instanceNodeMap, internalLinkNameUpdateMap, isCompleteInstance, isNestedListNode, mergeLists, mergeNextSiblingListIfSameType, nodeMoveDown, nodeMoveUp, normalizeCodeLang, numberNodeKey, paragraphSymbol, registerCodeHighlighting, setDisable, setInstanceAttrValue, setTemporaryContentText, updateChildrenListItemValue, updateRelatedInternalLink, useContentEditable };
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "instance"
8
8
  ],
9
9
  "license": "MIT",
10
- "version": "0.0.8",
10
+ "version": "0.0.9",
11
11
  "types": "index.d.ts",
12
12
  "files": [
13
13
  "dist",
@@ -23,9 +23,9 @@
23
23
  "@lexical/table": "^0.30.0",
24
24
  "@lexical/utils": "^0.30.0",
25
25
  "lexical": "0.30.0",
26
- "onchain-lexical-context": "^0.0.7",
27
- "onchain-lexical-markdown": "^0.0.7",
28
- "onchain-lexical-ui": "^0.0.7",
26
+ "onchain-lexical-context": "^0.0.8",
27
+ "onchain-lexical-markdown": "^0.0.8",
28
+ "onchain-lexical-ui": "^0.0.8",
29
29
  "onchain-utility": "^0.0.8"
30
30
  },
31
31
  "sideEffects": false,
package/src/const.ts CHANGED
@@ -70,24 +70,39 @@ export const internalLinkNameUpdateMap = new Map<
70
70
  Map<string, () => void>
71
71
  >();
72
72
 
73
+ /** 打开创建实例节点 */
73
74
  export const OPEN_CREATE_WINDOW: LexicalCommand<{
74
75
  isAddChildLevel: boolean;
75
76
  number: string;
76
77
  insNodeKey?: string;
77
78
  }> = createCommand('OPEN_CREATE_WINDOW');
78
79
 
80
+ /** 添加新的实例节点 */
79
81
  export const ADD_NEW_INSTANCE_NODE: LexicalCommand<{
80
82
  instances: Instance[];
81
83
  isAddChildLevel: boolean;
82
84
  insNodeKey?: string;
83
85
  }> = createCommand('ADD_NEW_INSTANCE_NODE');
84
86
 
87
+ /** 删除实例节点 */
85
88
  export const DELETE_INSTANCE_NODE = 'DELETE_INSTANCE_NODE';
86
89
 
90
+ /** 插入参数 */
87
91
  export const INSERT_PARAMETERS: LexicalCommand<undefined> =
88
92
  createCommand('INSERT_PARAMETERS');
89
93
 
94
+ /** 富文本 DecoratorNode 节点组件更新事件*/
90
95
  export const COMPONENT_UPDATE: LexicalCommand<{
91
96
  insNodeKey?: string;
92
97
  name: 'bar';
93
98
  }> = createCommand('COMPONENT_UPDATE');
99
+
100
+ /** 标题更新事件 */
101
+ export const INSTANCE_TITLE_UPDATE: LexicalCommand<{
102
+ number: string;
103
+ isInput?: boolean;
104
+ title?: string;
105
+ }> = createCommand('INSTANCE_TITLE_UPDATE');
106
+
107
+ /** 获取禁止编辑dom节点选择器 */
108
+ export const DisableSelector = `[contenteditable='false']:not([ignorecontenteditable])`;
@@ -49,6 +49,7 @@ import ImageResizer from 'onchain-lexical-ui/ImageResizer';
49
49
  import * as React from 'react';
50
50
  import {Suspense, useCallback, useEffect, useRef, useState} from 'react';
51
51
 
52
+ import {useContentEditable} from '../utils';
52
53
  import {$isImageNode} from '.';
53
54
  import Styles from './ImageNode.module.less';
54
55
 
@@ -461,7 +462,7 @@ export default function ImageComponent({
461
462
  const onResizeStart = () => {
462
463
  setIsResizing(true);
463
464
  };
464
-
465
+ const {contentEditable} = useContentEditable();
465
466
  const {historyState} = useSharedHistoryContext();
466
467
 
467
468
  const draggable = isSelected && $isNodeSelection(selection) && !isResizing;
@@ -518,19 +519,22 @@ export default function ImageComponent({
518
519
  </LexicalNestedComposer>
519
520
  </div>
520
521
  )}
521
- {resizable && $isNodeSelection(selection) && isFocused && (
522
- <ImageResizer
523
- showCaption={showCaption}
524
- setShowCaption={setShowCaption}
525
- editor={editor}
526
- buttonRef={buttonRef}
527
- imageRef={imageRef}
528
- maxWidth={maxWidth}
529
- onResizeStart={onResizeStart}
530
- onResizeEnd={onResizeEnd}
531
- captionsEnabled={!isLoadError && captionsEnabled}
532
- />
533
- )}
522
+ {resizable &&
523
+ $isNodeSelection(selection) &&
524
+ isFocused &&
525
+ contentEditable && (
526
+ <ImageResizer
527
+ showCaption={showCaption}
528
+ setShowCaption={setShowCaption}
529
+ editor={editor}
530
+ buttonRef={buttonRef}
531
+ imageRef={imageRef}
532
+ maxWidth={maxWidth}
533
+ onResizeStart={onResizeStart}
534
+ onResizeEnd={onResizeEnd}
535
+ captionsEnabled={!isLoadError && captionsEnabled}
536
+ />
537
+ )}
534
538
  </>
535
539
  </Suspense>
536
540
  );
@@ -222,6 +222,7 @@ export class ImageNode extends DecoratorNode<JSX.Element> {
222
222
  if (className !== undefined) {
223
223
  span.className = className;
224
224
  }
225
+ span.setAttribute('ignorecontenteditable', '');
225
226
  return span;
226
227
  }
227
228
 
@@ -23,7 +23,7 @@ import {
23
23
  $createTitleOnlyInstanceNode,
24
24
  $isInstanceNode,
25
25
  } from './base';
26
- import {ADD_NEW_INSTANCE_NODE, PluginProps} from './const';
26
+ import {ADD_NEW_INSTANCE_NODE, DisableSelector, PluginProps} from './const';
27
27
  import {$createFragmentNode} from './fragment';
28
28
  import {HorizontalRulePlugin} from './horizontal/horizontalPlugin';
29
29
  import {$registerInstanceListItemInsertParagraph} from './list/item';
@@ -57,7 +57,7 @@ export const InstancePlugin: React.FC<PluginProps> = (props) => {
57
57
  return selection.getNodes().some((node) => {
58
58
  return editor
59
59
  .getElementByKey(node.getKey())
60
- ?.closest(`[contenteditable='false']`);
60
+ ?.closest(DisableSelector);
61
61
  });
62
62
  }
63
63
  return false;
@@ -20,7 +20,7 @@ import InternalLinkComponent from './internalLinkComponent';
20
20
 
21
21
  export type SerializedInternalNode = Spread<
22
22
  {
23
- __number: string;
23
+ number: string;
24
24
  },
25
25
  SerializedTextDecoratorNode
26
26
  >;
@@ -35,7 +35,7 @@ export class InternalLinkNode extends TextDecoratorNode<React.ReactNode> {
35
35
  }
36
36
 
37
37
  static importJSON(serializedNode: SerializedInternalNode): InternalLinkNode {
38
- return $createInternalLinkNode(serializedNode.__number).updateFromJSON(
38
+ return $createInternalLinkNode(serializedNode.number).updateFromJSON(
39
39
  serializedNode,
40
40
  );
41
41
  }
@@ -45,6 +45,19 @@ export class InternalLinkNode extends TextDecoratorNode<React.ReactNode> {
45
45
  this.__number = number;
46
46
  }
47
47
 
48
+ exportJSON(): SerializedInternalNode {
49
+ return {
50
+ ...super.exportJSON(),
51
+ number: this.__number,
52
+ };
53
+ }
54
+
55
+ createDOM(config: EditorConfig, editor?: LexicalEditor): HTMLElement {
56
+ const span = super.createDOM(config, editor);
57
+ span.setAttribute('ignorecontenteditable', '');
58
+ return span;
59
+ }
60
+
48
61
  updateDOM(prevNode: this, dom: HTMLElement, config: EditorConfig): boolean {
49
62
  return super.updateDOM(prevNode, dom, config);
50
63
  }
@@ -10,59 +10,59 @@
10
10
 
11
11
  import type {InternalLinkNode} from '.';
12
12
 
13
- import {EditorConfig, LexicalEditor} from 'lexical';
14
- import {useCallback, useEffect, useState} from 'react';
13
+ import {mergeRegister} from '@lexical/utils';
14
+ import {COMMAND_PRIORITY_EDITOR, EditorConfig, LexicalEditor} from 'lexical';
15
+ import {useEffect, useState} from 'react';
15
16
 
16
- import {internalLinkNameUpdateMap} from '../const';
17
+ import {INSTANCE_TITLE_UPDATE} from '../const';
17
18
  import {$getInstanceNodeByNumber, $scrollTo, getLatestValue} from '../utils';
18
19
  import Styles from './styles.module.less';
19
20
 
20
- export default function InternalLinkComponent({
21
- self,
22
- editor,
23
- config,
24
- number,
25
- nodeKey,
26
- }: {
21
+ export default function InternalLinkComponent(props: {
27
22
  self: InternalLinkNode;
23
+ /** 链接目标的编号 */
28
24
  number: string;
25
+ /** 引用链接组件所在文档内自身节点 */
29
26
  nodeKey: string;
30
27
  editor: LexicalEditor;
31
28
  config: EditorConfig;
32
29
  }) {
30
+ const {editor, number} = props;
33
31
  const [name, setName] = useState<string>();
34
32
  const [serial, setSerial] = useState<string>();
35
33
 
36
- const update = useCallback(function () {
37
- editor.read(() => {
38
- const node = $getInstanceNodeByNumber(number)!;
39
- setName(getLatestValue(node.__instance.value, 'insDesc'));
40
- setSerial(node.getSerialNumber());
41
- });
42
- }, []);
43
-
44
34
  useEffect(() => {
45
- update();
35
+ Promise.resolve().then(() => {
36
+ // 需要等待节点载入富文本后才能获取到实例节点
37
+ editor.read(() => {
38
+ const node = $getInstanceNodeByNumber(number)!;
39
+ if (node) {
40
+ setName(getLatestValue(node.__instance.value, 'insDesc'));
41
+ setSerial(node.getSerialNumber());
42
+ }
43
+ });
44
+ });
46
45
  }, []);
47
46
 
48
47
  useEffect(() => {
49
- let timeout = 0;
50
- let map: Map<string, () => void>;
51
- const debounceUpdate = () => {
52
- clearTimeout(timeout);
53
- timeout = window.setTimeout(() => {
54
- update();
55
- }, 100);
56
- };
57
- if (internalLinkNameUpdateMap.has(number)) {
58
- map = internalLinkNameUpdateMap.get(number)!.set(nodeKey, debounceUpdate);
59
- } else {
60
- map = new Map([[nodeKey, debounceUpdate]]);
61
- internalLinkNameUpdateMap.set(number, map);
62
- }
63
- return () => {
64
- map.delete(number);
65
- };
48
+ return mergeRegister(
49
+ editor.registerCommand(
50
+ INSTANCE_TITLE_UPDATE,
51
+ (priority) => {
52
+ if (priority.number === number) {
53
+ editor.read(() => {
54
+ const node = $getInstanceNodeByNumber(priority.number)!;
55
+ if (priority.title !== undefined && priority.title !== null) {
56
+ setName(priority.title);
57
+ }
58
+ setSerial(node.getSerialNumber());
59
+ });
60
+ }
61
+ return false;
62
+ },
63
+ COMMAND_PRIORITY_EDITOR,
64
+ ),
65
+ );
66
66
  }, []);
67
67
 
68
68
  if (name) {
@@ -13,6 +13,7 @@ import {
13
13
  import {
14
14
  $applyNodeReplacement,
15
15
  $createTextNode,
16
+ $getEditor,
16
17
  $getSelection,
17
18
  $isTextNode,
18
19
  BaseSelection,
@@ -29,7 +30,7 @@ import {
29
30
  } from 'lexical';
30
31
 
31
32
  import {$isInstanceNode} from '../base';
32
- import {internalLinkNameUpdateMap, Placeholder} from '../const';
33
+ import {INSTANCE_TITLE_UPDATE, Placeholder} from '../const';
33
34
  import {InstanceHeadingNode, isGoogleDocsTitle} from '../heading';
34
35
  import {
35
36
  $createPlaceholderDecoratorNode,
@@ -143,10 +144,16 @@ export class InstanceTitleNode extends InstanceHeadingNode {
143
144
  if (instanceNode && instanceNode.__instance.value) {
144
145
  const text = this.getTextContent().trim();
145
146
  setInstanceAttrValue(instanceNode.__instance.value, 'insDesc', text);
146
- internalLinkNameUpdateMap
147
- .get(instanceNode.__instance.value.number!)
148
- ?.values()
149
- .forEach((update) => update());
147
+ const editor = $getEditor();
148
+ Promise.resolve().then(() => {
149
+ editor.read(() => {
150
+ editor.dispatchCommand(INSTANCE_TITLE_UPDATE, {
151
+ isInput: true,
152
+ number: instanceNode.__instance.value.number!,
153
+ title: text,
154
+ });
155
+ });
156
+ });
150
157
  }
151
158
  return super.updateDOM(prevNode, dom, config);
152
159
  }
@@ -86,6 +86,7 @@ export class PlaceholderDecoratorNode extends DecoratorNode<string> {
86
86
  pointer-events: none;
87
87
  user-select: none;
88
88
  `;
89
+ span.setAttribute('ignorecontenteditable', '');
89
90
  return span;
90
91
  }
91
92
 
package/src/utils.ts CHANGED
@@ -11,6 +11,7 @@ import type {HistoryState, HistoryStateEntry} from '@lexical/history';
11
11
 
12
12
  import {$isCodeNode} from '@lexical/code';
13
13
  import {$isListNode} from '@lexical/list';
14
+ import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
14
15
  import {$isQuoteNode} from '@lexical/rich-text';
15
16
  import {$findMatchingParent} from '@lexical/utils';
16
17
  import {
@@ -19,21 +20,26 @@ import {
19
20
  $getSelection,
20
21
  $isElementNode,
21
22
  $isRootNode,
23
+ COMMAND_PRIORITY_CRITICAL,
22
24
  type EditorThemeClasses,
23
25
  ElementNode,
24
26
  LexicalEditor,
25
27
  type LexicalNode,
26
28
  scrollIntoViewIfNeeded,
29
+ SELECTION_CHANGE_COMMAND,
27
30
  TextNode,
28
31
  } from 'lexical';
29
32
  import {$isRootOrShadowRoot} from 'lexical';
30
33
  import {$textToRichNodes} from 'onchain-lexical-markdown';
31
34
  import {dfs, hasOwnProperty} from 'onchain-utility';
35
+ import {useCallback, useEffect, useState} from 'react';
32
36
  import normalizeClassNames from 'shared/normalizeClassNames';
33
37
 
34
38
  import {$createBarDecoratorNode, $isBarDecoratorNode} from './bar';
35
39
  import {$isInstanceNode, InstanceNode} from './base';
36
40
  import {
41
+ DisableSelector,
42
+ INSTANCE_TITLE_UPDATE,
37
43
  internalLinkNameUpdateMap,
38
44
  numberNodeKey,
39
45
  paragraphSymbol,
@@ -253,10 +259,14 @@ export function updateRelatedInternalLink<T extends ElementNode>(nodes: T[]) {
253
259
  const insNodes = nodes.filter((node) => $isInstanceNode(node));
254
260
  if (insNodes.length) {
255
261
  insNodes.forEach((node) => {
256
- internalLinkNameUpdateMap
257
- .get(node.__instance.value.number!)
258
- ?.values()
259
- .forEach((update) => update());
262
+ const editor = $getEditor();
263
+ Promise.resolve().then(() => {
264
+ editor.read(() => {
265
+ editor.dispatchCommand(INSTANCE_TITLE_UPDATE, {
266
+ number: node.__instance.value.number!,
267
+ });
268
+ });
269
+ });
260
270
  });
261
271
  }
262
272
  }
@@ -273,7 +283,9 @@ export function nodeMoveUp<T extends ElementNode>(nodes?: T[] | null) {
273
283
  previous?.insertBefore(current);
274
284
  }
275
285
  }
276
- updateRelatedInternalLink(nodes);
286
+ updateRelatedInternalLink(
287
+ $isInstanceNode(previous) ? [...nodes, previous] : nodes,
288
+ );
277
289
  }
278
290
  }
279
291
 
@@ -289,7 +301,7 @@ export function nodeMoveDown<T extends ElementNode>(nodes?: T[] | null) {
289
301
  next?.insertAfter(current);
290
302
  }
291
303
  }
292
- updateRelatedInternalLink(nodes);
304
+ updateRelatedInternalLink($isInstanceNode(next) ? [...nodes, next] : nodes);
293
305
  }
294
306
  }
295
307
 
@@ -463,3 +475,36 @@ function setHSEntryMap(hs: HistoryStateEntry[], instanceNode: InstanceNode) {
463
475
  return state;
464
476
  });
465
477
  }
478
+
479
+ export function useContentEditable() {
480
+ const [editor] = useLexicalComposerContext();
481
+ const [contentEditable, setContentEditable] = useState(true);
482
+
483
+ const $updateContentEditable = useCallback(() => {
484
+ const selection = $getSelection();
485
+ if (selection) {
486
+ setContentEditable(
487
+ !selection.getNodes().some((node) => {
488
+ return editor
489
+ .getElementByKey(node.getKey())
490
+ ?.closest(DisableSelector);
491
+ }),
492
+ );
493
+ }
494
+ }, [editor]);
495
+
496
+ useEffect(() => {
497
+ return editor.registerCommand(
498
+ SELECTION_CHANGE_COMMAND,
499
+ (_payload) => {
500
+ $updateContentEditable();
501
+ return false;
502
+ },
503
+ COMMAND_PRIORITY_CRITICAL,
504
+ );
505
+ }, [editor, $updateContentEditable]);
506
+
507
+ return {
508
+ contentEditable,
509
+ };
510
+ }