5htp-core 0.4.8 → 0.4.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.
Files changed (187) hide show
  1. package/package.json +5 -1
  2. package/src/client/assets/css/components/table.less +2 -0
  3. package/src/client/components/Form.ts +1 -1
  4. package/src/client/components/button.tsx +2 -1
  5. package/src/client/components/containers/Popover/index.tsx +2 -2
  6. package/src/client/components/dropdown/index.tsx +16 -6
  7. package/src/client/components/input/Slider/index.tsx +0 -2
  8. package/src/client/components/inputv3/Rte/Editor.tsx +271 -0
  9. package/src/client/components/inputv3/Rte/ToolbarPlugin/BlockFormat.tsx +220 -0
  10. package/src/client/components/inputv3/Rte/ToolbarPlugin/ElementFormat.tsx +107 -0
  11. package/src/client/components/inputv3/Rte/ToolbarPlugin/index.tsx +768 -0
  12. package/src/client/components/inputv3/Rte/appSettings.ts +36 -0
  13. package/src/client/components/inputv3/Rte/context/FlashMessageContext.tsx +68 -0
  14. package/src/client/components/inputv3/Rte/context/SettingsContext.tsx +71 -0
  15. package/src/client/components/inputv3/Rte/context/SharedAutocompleteContext.tsx +71 -0
  16. package/src/client/components/inputv3/Rte/context/SharedHistoryContext.tsx +35 -0
  17. package/src/client/components/inputv3/Rte/currentEditor.ts +42 -0
  18. package/src/client/components/inputv3/Rte/hooks/useFlashMessage.tsx +16 -0
  19. package/src/client/components/inputv3/Rte/hooks/useReport.ts +67 -0
  20. package/src/client/components/inputv3/Rte/images/emoji/1F600.png +0 -0
  21. package/src/client/components/inputv3/Rte/images/emoji/1F641.png +0 -0
  22. package/src/client/components/inputv3/Rte/images/emoji/1F642.png +0 -0
  23. package/src/client/components/inputv3/Rte/images/emoji/2764.png +0 -0
  24. package/src/client/components/inputv3/Rte/images/emoji/LICENSE.md +5 -0
  25. package/src/client/components/inputv3/Rte/images/icons/draggable-block-menu.svg +1 -0
  26. package/src/client/components/inputv3/Rte/images/icons/prettier-error.svg +1 -0
  27. package/src/client/components/inputv3/Rte/images/icons/prettier.svg +1 -0
  28. package/src/client/components/inputv3/Rte/images/image/LICENSE.md +5 -0
  29. package/src/client/components/inputv3/Rte/images/image-broken.svg +4 -0
  30. package/src/client/components/inputv3/Rte/images/logo.svg +1 -0
  31. package/src/client/components/inputv3/Rte/index.tsx +63 -79
  32. package/src/client/components/inputv3/Rte/nodes/AutocompleteNode.tsx +119 -0
  33. package/src/client/components/inputv3/Rte/nodes/EmojiNode.tsx +102 -0
  34. package/src/client/components/inputv3/Rte/nodes/EquationComponent.tsx +141 -0
  35. package/src/client/components/inputv3/Rte/nodes/EquationNode.tsx +174 -0
  36. package/src/client/components/inputv3/Rte/nodes/FigmaNode.tsx +135 -0
  37. package/src/client/components/inputv3/Rte/nodes/ImageComponent.tsx +468 -0
  38. package/src/client/components/inputv3/Rte/nodes/ImageNode.css +43 -0
  39. package/src/client/components/inputv3/Rte/nodes/ImageNode.tsx +266 -0
  40. package/src/client/components/inputv3/Rte/nodes/InlineImageNode/InlineImageComponent.tsx +402 -0
  41. package/src/client/components/inputv3/Rte/nodes/InlineImageNode/InlineImageNode.css +94 -0
  42. package/src/client/components/inputv3/Rte/nodes/InlineImageNode/InlineImageNode.tsx +294 -0
  43. package/src/client/components/inputv3/Rte/nodes/KeywordNode.ts +67 -0
  44. package/src/client/components/inputv3/Rte/nodes/LayoutContainerNode.ts +137 -0
  45. package/src/client/components/inputv3/Rte/nodes/LayoutItemNode.ts +71 -0
  46. package/src/client/components/inputv3/Rte/nodes/MentionNode.ts +130 -0
  47. package/src/client/components/inputv3/Rte/nodes/PageBreakNode/index.css +62 -0
  48. package/src/client/components/inputv3/Rte/nodes/PageBreakNode/index.tsx +170 -0
  49. package/src/client/components/inputv3/Rte/nodes/PlaygroundNodes.ts +76 -0
  50. package/src/client/components/inputv3/Rte/nodes/PollComponent.tsx +249 -0
  51. package/src/client/components/inputv3/Rte/nodes/PollNode.css +187 -0
  52. package/src/client/components/inputv3/Rte/nodes/PollNode.tsx +209 -0
  53. package/src/client/components/inputv3/Rte/nodes/StickyComponent.tsx +261 -0
  54. package/src/client/components/inputv3/Rte/nodes/StickyNode.css +37 -0
  55. package/src/client/components/inputv3/Rte/nodes/StickyNode.tsx +150 -0
  56. package/src/client/components/inputv3/Rte/nodes/TweetNode.tsx +223 -0
  57. package/src/client/components/inputv3/Rte/nodes/YouTubeNode.tsx +184 -0
  58. package/src/client/components/inputv3/Rte/plugins/ActionsPlugin/index.tsx +334 -0
  59. package/src/client/components/inputv3/Rte/plugins/AutoEmbedPlugin/index.tsx +352 -0
  60. package/src/client/components/inputv3/Rte/plugins/AutoLinkPlugin/index.tsx +32 -0
  61. package/src/client/components/inputv3/Rte/plugins/AutocompletePlugin/index.tsx +2529 -0
  62. package/src/client/components/inputv3/Rte/plugins/CodeActionMenuPlugin/components/CopyButton/index.tsx +70 -0
  63. package/src/client/components/inputv3/Rte/plugins/CodeActionMenuPlugin/components/PrettierButton/index.css +14 -0
  64. package/src/client/components/inputv3/Rte/plugins/CodeActionMenuPlugin/components/PrettierButton/index.tsx +156 -0
  65. package/src/client/components/inputv3/Rte/plugins/CodeActionMenuPlugin/index.css +54 -0
  66. package/src/client/components/inputv3/Rte/plugins/CodeActionMenuPlugin/index.tsx +190 -0
  67. package/src/client/components/inputv3/Rte/plugins/CodeActionMenuPlugin/utils.ts +33 -0
  68. package/src/client/components/inputv3/Rte/plugins/CodeHighlightPlugin/index.ts +21 -0
  69. package/src/client/components/inputv3/Rte/plugins/CollapsiblePlugin/Collapsible.css +57 -0
  70. package/src/client/components/inputv3/Rte/plugins/CollapsiblePlugin/CollapsibleContainerNode.ts +168 -0
  71. package/src/client/components/inputv3/Rte/plugins/CollapsiblePlugin/CollapsibleContentNode.ts +127 -0
  72. package/src/client/components/inputv3/Rte/plugins/CollapsiblePlugin/CollapsibleTitleNode.ts +152 -0
  73. package/src/client/components/inputv3/Rte/plugins/CollapsiblePlugin/CollapsibleUtils.ts +17 -0
  74. package/src/client/components/inputv3/Rte/plugins/CollapsiblePlugin/index.ts +284 -0
  75. package/src/client/components/inputv3/Rte/plugins/ComponentPickerPlugin/index.tsx +370 -0
  76. package/src/client/components/inputv3/Rte/plugins/ContextMenuPlugin/index.tsx +270 -0
  77. package/src/client/components/inputv3/Rte/plugins/DocsPlugin/index.tsx +20 -0
  78. package/src/client/components/inputv3/Rte/plugins/DragDropPastePlugin/index.ts +51 -0
  79. package/src/client/components/inputv3/Rte/plugins/DraggableBlockPlugin/index.css +36 -0
  80. package/src/client/components/inputv3/Rte/plugins/DraggableBlockPlugin/index.tsx +43 -0
  81. package/src/client/components/inputv3/Rte/plugins/EmojiPickerPlugin/index.tsx +198 -0
  82. package/src/client/components/inputv3/Rte/plugins/EmojisPlugin/index.ts +75 -0
  83. package/src/client/components/inputv3/Rte/plugins/EquationsPlugin/index.tsx +82 -0
  84. package/src/client/components/inputv3/Rte/plugins/FigmaPlugin/index.tsx +40 -0
  85. package/src/client/components/inputv3/Rte/plugins/FloatingLinkEditorPlugin/index.css +41 -0
  86. package/src/client/components/inputv3/Rte/plugins/FloatingLinkEditorPlugin/index.tsx +393 -0
  87. package/src/client/components/inputv3/Rte/plugins/FloatingTextFormatToolbarPlugin/index.css +141 -0
  88. package/src/client/components/inputv3/Rte/plugins/FloatingTextFormatToolbarPlugin/index.tsx +388 -0
  89. package/src/client/components/inputv3/Rte/plugins/ImagesPlugin/index.tsx +350 -0
  90. package/src/client/components/inputv3/Rte/plugins/InlineImagePlugin/index.tsx +336 -0
  91. package/src/client/components/inputv3/Rte/plugins/KeywordsPlugin/index.ts +56 -0
  92. package/src/client/components/inputv3/Rte/plugins/LayoutPlugin/InsertLayoutDialog.tsx +58 -0
  93. package/src/client/components/inputv3/Rte/plugins/LayoutPlugin/LayoutPlugin.tsx +219 -0
  94. package/src/client/components/inputv3/Rte/plugins/LinkPlugin/index.tsx +34 -0
  95. package/src/client/components/inputv3/Rte/plugins/ListMaxIndentLevelPlugin/index.ts +85 -0
  96. package/src/client/components/inputv3/Rte/plugins/MarkdownShortcutPlugin/index.tsx +16 -0
  97. package/src/client/components/inputv3/Rte/plugins/MarkdownTransformers/index.ts +324 -0
  98. package/src/client/components/inputv3/Rte/plugins/MaxLengthPlugin/index.tsx +53 -0
  99. package/src/client/components/inputv3/Rte/plugins/MentionsPlugin/index.tsx +696 -0
  100. package/src/client/components/inputv3/Rte/plugins/PageBreakPlugin/index.tsx +57 -0
  101. package/src/client/components/inputv3/Rte/plugins/PasteLogPlugin/index.tsx +54 -0
  102. package/src/client/components/inputv3/Rte/plugins/PollPlugin/index.tsx +86 -0
  103. package/src/client/components/inputv3/Rte/plugins/SpeechToTextPlugin/index.ts +125 -0
  104. package/src/client/components/inputv3/Rte/plugins/StickyPlugin/index.ts +22 -0
  105. package/src/client/components/inputv3/Rte/plugins/TabFocusPlugin/index.tsx +65 -0
  106. package/src/client/components/inputv3/Rte/plugins/TableActionMenuPlugin/index.tsx +773 -0
  107. package/src/client/components/inputv3/Rte/plugins/TableCellResizer/index.css +12 -0
  108. package/src/client/components/inputv3/Rte/plugins/TableCellResizer/index.tsx +436 -0
  109. package/src/client/components/inputv3/Rte/plugins/TableHoverActionsPlugin/index.tsx +287 -0
  110. package/src/client/components/inputv3/Rte/plugins/TableOfContentsPlugin/index.css +95 -0
  111. package/src/client/components/inputv3/Rte/plugins/TableOfContentsPlugin/index.tsx +197 -0
  112. package/src/client/components/inputv3/Rte/plugins/TablePlugin.tsx +178 -0
  113. package/src/client/components/inputv3/Rte/plugins/TestRecorderPlugin/index.tsx +468 -0
  114. package/src/client/components/inputv3/Rte/plugins/TreeViewPlugin/index.tsx +26 -0
  115. package/src/client/components/inputv3/Rte/plugins/TwitterPlugin/index.ts +41 -0
  116. package/src/client/components/inputv3/Rte/plugins/TypingPerfPlugin/index.ts +117 -0
  117. package/src/client/components/inputv3/Rte/plugins/YouTubePlugin/index.ts +41 -0
  118. package/src/client/components/inputv3/Rte/shared/canUseDOM.ts +4 -0
  119. package/src/client/components/inputv3/Rte/shared/caretFromPoint.ts +40 -0
  120. package/src/client/components/inputv3/Rte/shared/environment.ts +56 -0
  121. package/src/client/components/inputv3/Rte/shared/invariant.ts +26 -0
  122. package/src/client/components/inputv3/Rte/shared/normalizeClassNames.ts +21 -0
  123. package/src/client/components/inputv3/Rte/shared/react-test-utils.ts +18 -0
  124. package/src/client/components/inputv3/Rte/shared/reactPatches.ts +22 -0
  125. package/src/client/components/inputv3/Rte/shared/simpleDiffWithCursor.ts +49 -0
  126. package/src/client/components/inputv3/Rte/shared/useLayoutEffect.ts +19 -0
  127. package/src/client/components/inputv3/Rte/shared/warnOnlyOnce.ts +20 -0
  128. package/src/client/components/inputv3/Rte/style.less +30 -60
  129. package/src/client/components/inputv3/Rte/themes/CommentEditorTheme.css +13 -0
  130. package/src/client/components/inputv3/Rte/themes/CommentEditorTheme.ts +20 -0
  131. package/src/client/components/inputv3/Rte/themes/PlaygroundEditorTheme.css +447 -0
  132. package/src/client/components/inputv3/Rte/themes/PlaygroundEditorTheme.ts +120 -0
  133. package/src/client/components/inputv3/Rte/themes/StickyEditorTheme.css +13 -0
  134. package/src/client/components/inputv3/Rte/themes/StickyEditorTheme.ts +20 -0
  135. package/src/client/components/inputv3/Rte/ui/ColorPicker.css +88 -0
  136. package/src/client/components/inputv3/Rte/ui/ColorPicker.tsx +365 -0
  137. package/src/client/components/inputv3/Rte/ui/ContentEditable.css +44 -0
  138. package/src/client/components/inputv3/Rte/ui/ContentEditable.tsx +36 -0
  139. package/src/client/components/inputv3/Rte/ui/DropDown.tsx +259 -0
  140. package/src/client/components/inputv3/Rte/ui/DropdownColorPicker.tsx +41 -0
  141. package/src/client/components/inputv3/Rte/ui/EquationEditor.css +38 -0
  142. package/src/client/components/inputv3/Rte/ui/EquationEditor.tsx +56 -0
  143. package/src/client/components/inputv3/Rte/ui/FileInput.tsx +38 -0
  144. package/src/client/components/inputv3/Rte/ui/FlashMessage.css +28 -0
  145. package/src/client/components/inputv3/Rte/ui/FlashMessage.tsx +29 -0
  146. package/src/client/components/inputv3/Rte/ui/ImageResizer.tsx +316 -0
  147. package/src/client/components/inputv3/Rte/ui/Input.css +32 -0
  148. package/src/client/components/inputv3/Rte/ui/KatexRenderer.tsx +54 -0
  149. package/src/client/components/inputv3/Rte/ui/Switch.tsx +36 -0
  150. package/src/client/components/inputv3/Rte/utils/docSerialization.ts +77 -0
  151. package/src/client/components/inputv3/Rte/utils/emoji-list.ts +16615 -0
  152. package/src/client/components/inputv3/Rte/utils/getDOMRangeRect.ts +27 -0
  153. package/src/client/components/inputv3/Rte/utils/getSelectedNode.ts +27 -0
  154. package/src/client/components/inputv3/Rte/utils/guard.ts +10 -0
  155. package/src/client/components/inputv3/Rte/utils/isMobileWidth.ts +7 -0
  156. package/src/client/components/inputv3/Rte/utils/joinClasses.ts +13 -0
  157. package/src/client/components/inputv3/Rte/utils/setFloatingElemPosition.ts +51 -0
  158. package/src/client/components/inputv3/Rte/utils/setFloatingElemPositionForLinkEditor.ts +46 -0
  159. package/src/client/components/inputv3/Rte/utils/swipe.ts +127 -0
  160. package/src/client/components/inputv3/Rte/utils/url.ts +38 -0
  161. package/src/client/components/inputv3/base.tsx +8 -5
  162. package/src/client/components/inputv3/file/index.tsx +11 -5
  163. package/src/common/data/rte/nodes.ts +60 -9
  164. package/src/common/validation/index.ts +21 -2
  165. package/src/common/validation/schema.ts +42 -10
  166. package/src/common/validation/validator.ts +12 -4
  167. package/src/common/validation/validators.ts +82 -53
  168. package/src/server/services/router/http/multipart.ts +0 -1
  169. package/src/server/services/schema/index.ts +24 -2
  170. package/src/server/services/schema/request.ts +3 -2
  171. package/src/server/services/schema/rte.ts +110 -0
  172. package/src/{common/data/rte/index.ts → server/utils/rte.ts} +27 -16
  173. package/src/client/components/inputv3/Rte/ExampleTheme.tsx +0 -42
  174. package/src/client/components/inputv3/Rte/ToolbarPlugin.tsx +0 -167
  175. package/src/client/components/inputv3/Rte/icons/LICENSE.md +0 -5
  176. package/src/client/components/inputv3/Rte/icons/arrow-clockwise.svg +0 -4
  177. package/src/client/components/inputv3/Rte/icons/arrow-counterclockwise.svg +0 -4
  178. package/src/client/components/inputv3/Rte/icons/journal-text.svg +0 -5
  179. package/src/client/components/inputv3/Rte/icons/justify.svg +0 -3
  180. package/src/client/components/inputv3/Rte/icons/text-center.svg +0 -3
  181. package/src/client/components/inputv3/Rte/icons/text-left.svg +0 -3
  182. package/src/client/components/inputv3/Rte/icons/text-paragraph.svg +0 -3
  183. package/src/client/components/inputv3/Rte/icons/text-right.svg +0 -3
  184. package/src/client/components/inputv3/Rte/icons/type-bold.svg +0 -3
  185. package/src/client/components/inputv3/Rte/icons/type-italic.svg +0 -3
  186. package/src/client/components/inputv3/Rte/icons/type-strikethrough.svg +0 -3
  187. package/src/client/components/inputv3/Rte/icons/type-underline.svg +0 -3
@@ -0,0 +1,388 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ import './index.css';
10
+
11
+ import {$isCodeHighlightNode} from '@lexical/code';
12
+ import {$isLinkNode, TOGGLE_LINK_COMMAND} from '@lexical/link';
13
+ import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
14
+ import {mergeRegister} from '@lexical/utils';
15
+ import {
16
+ $getSelection,
17
+ $isParagraphNode,
18
+ $isRangeSelection,
19
+ $isTextNode,
20
+ COMMAND_PRIORITY_LOW,
21
+ FORMAT_TEXT_COMMAND,
22
+ LexicalEditor,
23
+ SELECTION_CHANGE_COMMAND,
24
+ } from 'lexical';
25
+ import {Dispatch, useCallback, useEffect, useRef, useState} from 'react';
26
+ import * as React from 'react';
27
+ import {createPortal} from 'react-dom';
28
+
29
+ import {getDOMRangeRect} from '../../utils/getDOMRangeRect';
30
+ import {getSelectedNode} from '../../utils/getSelectedNode';
31
+ import {setFloatingElemPosition} from '../../utils/setFloatingElemPosition';
32
+
33
+ function TextFormatFloatingToolbar({
34
+ editor,
35
+ anchorElem,
36
+ isLink,
37
+ isBold,
38
+ isItalic,
39
+ isUnderline,
40
+ isCode,
41
+ isStrikethrough,
42
+ isSubscript,
43
+ isSuperscript,
44
+ setIsLinkEditMode,
45
+ }: {
46
+ editor: LexicalEditor;
47
+ anchorElem: HTMLElement;
48
+ isBold: boolean;
49
+ isCode: boolean;
50
+ isItalic: boolean;
51
+ isLink: boolean;
52
+ isStrikethrough: boolean;
53
+ isSubscript: boolean;
54
+ isSuperscript: boolean;
55
+ isUnderline: boolean;
56
+ setIsLinkEditMode: Dispatch<boolean>;
57
+ }): JSX.Element {
58
+ const popupCharStylesEditorRef = useRef<HTMLDivElement | null>(null);
59
+
60
+ const insertLink = useCallback(() => {
61
+ if (!isLink) {
62
+ setIsLinkEditMode(true);
63
+ editor.dispatchCommand(TOGGLE_LINK_COMMAND, 'https://');
64
+ } else {
65
+ setIsLinkEditMode(false);
66
+ editor.dispatchCommand(TOGGLE_LINK_COMMAND, null);
67
+ }
68
+ }, [editor, isLink, setIsLinkEditMode]);
69
+
70
+ function mouseMoveListener(e: MouseEvent) {
71
+ if (
72
+ popupCharStylesEditorRef?.current &&
73
+ (e.buttons === 1 || e.buttons === 3)
74
+ ) {
75
+ if (popupCharStylesEditorRef.current.style.pointerEvents !== 'none') {
76
+ const x = e.clientX;
77
+ const y = e.clientY;
78
+ const elementUnderMouse = document.elementFromPoint(x, y);
79
+
80
+ if (!popupCharStylesEditorRef.current.contains(elementUnderMouse)) {
81
+ // Mouse is not over the target element => not a normal click, but probably a drag
82
+ popupCharStylesEditorRef.current.style.pointerEvents = 'none';
83
+ }
84
+ }
85
+ }
86
+ }
87
+ function mouseUpListener(e: MouseEvent) {
88
+ if (popupCharStylesEditorRef?.current) {
89
+ if (popupCharStylesEditorRef.current.style.pointerEvents !== 'auto') {
90
+ popupCharStylesEditorRef.current.style.pointerEvents = 'auto';
91
+ }
92
+ }
93
+ }
94
+
95
+ useEffect(() => {
96
+ if (popupCharStylesEditorRef?.current) {
97
+ document.addEventListener('mousemove', mouseMoveListener);
98
+ document.addEventListener('mouseup', mouseUpListener);
99
+
100
+ return () => {
101
+ document.removeEventListener('mousemove', mouseMoveListener);
102
+ document.removeEventListener('mouseup', mouseUpListener);
103
+ };
104
+ }
105
+ }, [popupCharStylesEditorRef]);
106
+
107
+ const $updateTextFormatFloatingToolbar = useCallback(() => {
108
+ const selection = $getSelection();
109
+
110
+ const popupCharStylesEditorElem = popupCharStylesEditorRef.current;
111
+ const nativeSelection = window.getSelection();
112
+
113
+ if (popupCharStylesEditorElem === null) {
114
+ return;
115
+ }
116
+
117
+ const rootElement = editor.getRootElement();
118
+ if (
119
+ selection !== null &&
120
+ nativeSelection !== null &&
121
+ !nativeSelection.isCollapsed &&
122
+ rootElement !== null &&
123
+ rootElement.contains(nativeSelection.anchorNode)
124
+ ) {
125
+ const rangeRect = getDOMRangeRect(nativeSelection, rootElement);
126
+
127
+ setFloatingElemPosition(
128
+ rangeRect,
129
+ popupCharStylesEditorElem,
130
+ anchorElem,
131
+ isLink,
132
+ );
133
+ }
134
+ }, [editor, anchorElem, isLink]);
135
+
136
+ useEffect(() => {
137
+ const scrollerElem = anchorElem.parentElement;
138
+
139
+ const update = () => {
140
+ editor.getEditorState().read(() => {
141
+ $updateTextFormatFloatingToolbar();
142
+ });
143
+ };
144
+
145
+ window.addEventListener('resize', update);
146
+ if (scrollerElem) {
147
+ scrollerElem.addEventListener('scroll', update);
148
+ }
149
+
150
+ return () => {
151
+ window.removeEventListener('resize', update);
152
+ if (scrollerElem) {
153
+ scrollerElem.removeEventListener('scroll', update);
154
+ }
155
+ };
156
+ }, [editor, $updateTextFormatFloatingToolbar, anchorElem]);
157
+
158
+ useEffect(() => {
159
+ editor.getEditorState().read(() => {
160
+ $updateTextFormatFloatingToolbar();
161
+ });
162
+ return mergeRegister(
163
+ editor.registerUpdateListener(({editorState}) => {
164
+ editorState.read(() => {
165
+ $updateTextFormatFloatingToolbar();
166
+ });
167
+ }),
168
+
169
+ editor.registerCommand(
170
+ SELECTION_CHANGE_COMMAND,
171
+ () => {
172
+ $updateTextFormatFloatingToolbar();
173
+ return false;
174
+ },
175
+ COMMAND_PRIORITY_LOW,
176
+ ),
177
+ );
178
+ }, [editor, $updateTextFormatFloatingToolbar]);
179
+
180
+ return (
181
+ <div ref={popupCharStylesEditorRef} className="floating-text-format-popup">
182
+ {editor.isEditable() && (
183
+ <>
184
+ <button
185
+ type="button"
186
+ onClick={() => {
187
+ editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'bold');
188
+ }}
189
+ className={'popup-item spaced ' + (isBold ? 'active' : '')}
190
+ aria-label="Format text as bold">
191
+ <i className="format bold" />
192
+ </button>
193
+ <button
194
+ type="button"
195
+ onClick={() => {
196
+ editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'italic');
197
+ }}
198
+ className={'popup-item spaced ' + (isItalic ? 'active' : '')}
199
+ aria-label="Format text as italics">
200
+ <i className="format italic" />
201
+ </button>
202
+ <button
203
+ type="button"
204
+ onClick={() => {
205
+ editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'underline');
206
+ }}
207
+ className={'popup-item spaced ' + (isUnderline ? 'active' : '')}
208
+ aria-label="Format text to underlined">
209
+ <i className="format underline" />
210
+ </button>
211
+ <button
212
+ type="button"
213
+ onClick={() => {
214
+ editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'strikethrough');
215
+ }}
216
+ className={'popup-item spaced ' + (isStrikethrough ? 'active' : '')}
217
+ aria-label="Format text with a strikethrough">
218
+ <i className="format strikethrough" />
219
+ </button>
220
+ <button
221
+ type="button"
222
+ onClick={() => {
223
+ editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'subscript');
224
+ }}
225
+ className={'popup-item spaced ' + (isSubscript ? 'active' : '')}
226
+ title="Subscript"
227
+ aria-label="Format Subscript">
228
+ <i className="format subscript" />
229
+ </button>
230
+ <button
231
+ type="button"
232
+ onClick={() => {
233
+ editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'superscript');
234
+ }}
235
+ className={'popup-item spaced ' + (isSuperscript ? 'active' : '')}
236
+ title="Superscript"
237
+ aria-label="Format Superscript">
238
+ <i className="format superscript" />
239
+ </button>
240
+ <button
241
+ type="button"
242
+ onClick={() => {
243
+ editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'code');
244
+ }}
245
+ className={'popup-item spaced ' + (isCode ? 'active' : '')}
246
+ aria-label="Insert code block">
247
+ <i className="format code" />
248
+ </button>
249
+ <button
250
+ type="button"
251
+ onClick={insertLink}
252
+ className={'popup-item spaced ' + (isLink ? 'active' : '')}
253
+ aria-label="Insert link">
254
+ <i className="format link" />
255
+ </button>
256
+ </>
257
+ )}
258
+ </div>
259
+ );
260
+ }
261
+
262
+ function useFloatingTextFormatToolbar(
263
+ editor: LexicalEditor,
264
+ anchorElem: HTMLElement,
265
+ setIsLinkEditMode: Dispatch<boolean>,
266
+ ): JSX.Element | null {
267
+ const [isText, setIsText] = useState(false);
268
+ const [isLink, setIsLink] = useState(false);
269
+ const [isBold, setIsBold] = useState(false);
270
+ const [isItalic, setIsItalic] = useState(false);
271
+ const [isUnderline, setIsUnderline] = useState(false);
272
+ const [isStrikethrough, setIsStrikethrough] = useState(false);
273
+ const [isSubscript, setIsSubscript] = useState(false);
274
+ const [isSuperscript, setIsSuperscript] = useState(false);
275
+ const [isCode, setIsCode] = useState(false);
276
+
277
+ const updatePopup = useCallback(() => {
278
+ editor.getEditorState().read(() => {
279
+ // Should not to pop up the floating toolbar when using IME input
280
+ if (editor.isComposing()) {
281
+ return;
282
+ }
283
+ const selection = $getSelection();
284
+ const nativeSelection = window.getSelection();
285
+ const rootElement = editor.getRootElement();
286
+
287
+ if (
288
+ nativeSelection !== null &&
289
+ (!$isRangeSelection(selection) ||
290
+ rootElement === null ||
291
+ !rootElement.contains(nativeSelection.anchorNode))
292
+ ) {
293
+ setIsText(false);
294
+ return;
295
+ }
296
+
297
+ if (!$isRangeSelection(selection)) {
298
+ return;
299
+ }
300
+
301
+ const node = getSelectedNode(selection);
302
+
303
+ // Update text format
304
+ setIsBold(selection.hasFormat('bold'));
305
+ setIsItalic(selection.hasFormat('italic'));
306
+ setIsUnderline(selection.hasFormat('underline'));
307
+ setIsStrikethrough(selection.hasFormat('strikethrough'));
308
+ setIsSubscript(selection.hasFormat('subscript'));
309
+ setIsSuperscript(selection.hasFormat('superscript'));
310
+ setIsCode(selection.hasFormat('code'));
311
+
312
+ // Update links
313
+ const parent = node.getParent();
314
+ if ($isLinkNode(parent) || $isLinkNode(node)) {
315
+ setIsLink(true);
316
+ } else {
317
+ setIsLink(false);
318
+ }
319
+
320
+ if (
321
+ !$isCodeHighlightNode(selection.anchor.getNode()) &&
322
+ selection.getTextContent() !== ''
323
+ ) {
324
+ setIsText($isTextNode(node) || $isParagraphNode(node));
325
+ } else {
326
+ setIsText(false);
327
+ }
328
+
329
+ const rawTextContent = selection.getTextContent().replace(/\n/g, '');
330
+ if (!selection.isCollapsed() && rawTextContent === '') {
331
+ setIsText(false);
332
+ return;
333
+ }
334
+ });
335
+ }, [editor]);
336
+
337
+ useEffect(() => {
338
+ document.addEventListener('selectionchange', updatePopup);
339
+ return () => {
340
+ document.removeEventListener('selectionchange', updatePopup);
341
+ };
342
+ }, [updatePopup]);
343
+
344
+ useEffect(() => {
345
+ return mergeRegister(
346
+ editor.registerUpdateListener(() => {
347
+ updatePopup();
348
+ }),
349
+ editor.registerRootListener(() => {
350
+ if (editor.getRootElement() === null) {
351
+ setIsText(false);
352
+ }
353
+ }),
354
+ );
355
+ }, [editor, updatePopup]);
356
+
357
+ if (!isText) {
358
+ return null;
359
+ }
360
+
361
+ return createPortal(
362
+ <TextFormatFloatingToolbar
363
+ editor={editor}
364
+ anchorElem={anchorElem}
365
+ isLink={isLink}
366
+ isBold={isBold}
367
+ isItalic={isItalic}
368
+ isStrikethrough={isStrikethrough}
369
+ isSubscript={isSubscript}
370
+ isSuperscript={isSuperscript}
371
+ isUnderline={isUnderline}
372
+ isCode={isCode}
373
+ setIsLinkEditMode={setIsLinkEditMode}
374
+ />,
375
+ anchorElem,
376
+ );
377
+ }
378
+
379
+ export default function FloatingTextFormatToolbarPlugin({
380
+ anchorElem = document.body,
381
+ setIsLinkEditMode,
382
+ }: {
383
+ anchorElem?: HTMLElement;
384
+ setIsLinkEditMode: Dispatch<boolean>;
385
+ }): JSX.Element | null {
386
+ const [editor] = useLexicalComposerContext();
387
+ return useFloatingTextFormatToolbar(editor, anchorElem, setIsLinkEditMode);
388
+ }