5htp-core 0.4.8-3 → 0.4.9-1

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 (186) hide show
  1. package/package.json +5 -1
  2. package/src/client/components/Form.ts +2 -6
  3. package/src/client/components/button.tsx +2 -1
  4. package/src/client/components/containers/Popover/index.tsx +2 -2
  5. package/src/client/components/dropdown/index.tsx +16 -6
  6. package/src/client/components/input/Slider/index.tsx +0 -2
  7. package/src/client/components/inputv3/Rte/Editor.tsx +271 -0
  8. package/src/client/components/inputv3/Rte/ToolbarPlugin/BlockFormat.tsx +220 -0
  9. package/src/client/components/inputv3/Rte/ToolbarPlugin/ElementFormat.tsx +107 -0
  10. package/src/client/components/inputv3/Rte/ToolbarPlugin/index.tsx +768 -0
  11. package/src/client/components/inputv3/Rte/appSettings.ts +36 -0
  12. package/src/client/components/inputv3/Rte/context/FlashMessageContext.tsx +68 -0
  13. package/src/client/components/inputv3/Rte/context/SettingsContext.tsx +71 -0
  14. package/src/client/components/inputv3/Rte/context/SharedAutocompleteContext.tsx +71 -0
  15. package/src/client/components/inputv3/Rte/context/SharedHistoryContext.tsx +35 -0
  16. package/src/client/components/inputv3/Rte/currentEditor.ts +3 -1
  17. package/src/client/components/inputv3/Rte/hooks/useFlashMessage.tsx +16 -0
  18. package/src/client/components/inputv3/Rte/hooks/useReport.ts +67 -0
  19. package/src/client/components/inputv3/Rte/images/emoji/1F600.png +0 -0
  20. package/src/client/components/inputv3/Rte/images/emoji/1F641.png +0 -0
  21. package/src/client/components/inputv3/Rte/images/emoji/1F642.png +0 -0
  22. package/src/client/components/inputv3/Rte/images/emoji/2764.png +0 -0
  23. package/src/client/components/inputv3/Rte/images/emoji/LICENSE.md +5 -0
  24. package/src/client/components/inputv3/Rte/images/icons/draggable-block-menu.svg +1 -0
  25. package/src/client/components/inputv3/Rte/images/icons/prettier-error.svg +1 -0
  26. package/src/client/components/inputv3/Rte/images/icons/prettier.svg +1 -0
  27. package/src/client/components/inputv3/Rte/images/image/LICENSE.md +5 -0
  28. package/src/client/components/inputv3/Rte/images/image-broken.svg +4 -0
  29. package/src/client/components/inputv3/Rte/images/logo.svg +1 -0
  30. package/src/client/components/inputv3/Rte/index.tsx +25 -94
  31. package/src/client/components/inputv3/Rte/nodes/AutocompleteNode.tsx +119 -0
  32. package/src/client/components/inputv3/Rte/nodes/EmojiNode.tsx +102 -0
  33. package/src/client/components/inputv3/Rte/nodes/EquationComponent.tsx +141 -0
  34. package/src/client/components/inputv3/Rte/nodes/EquationNode.tsx +174 -0
  35. package/src/client/components/inputv3/Rte/nodes/FigmaNode.tsx +135 -0
  36. package/src/client/components/inputv3/Rte/nodes/ImageComponent.tsx +468 -0
  37. package/src/client/components/inputv3/Rte/nodes/ImageNode.css +43 -0
  38. package/src/client/components/inputv3/Rte/nodes/ImageNode.tsx +266 -0
  39. package/src/client/components/inputv3/Rte/nodes/InlineImageNode/InlineImageComponent.tsx +402 -0
  40. package/src/client/components/inputv3/Rte/nodes/InlineImageNode/InlineImageNode.css +94 -0
  41. package/src/client/components/inputv3/Rte/nodes/InlineImageNode/InlineImageNode.tsx +294 -0
  42. package/src/client/components/inputv3/Rte/nodes/KeywordNode.ts +67 -0
  43. package/src/client/components/inputv3/Rte/nodes/LayoutContainerNode.ts +137 -0
  44. package/src/client/components/inputv3/Rte/nodes/LayoutItemNode.ts +71 -0
  45. package/src/client/components/inputv3/Rte/nodes/MentionNode.ts +130 -0
  46. package/src/client/components/inputv3/Rte/nodes/PageBreakNode/index.css +62 -0
  47. package/src/client/components/inputv3/Rte/nodes/PageBreakNode/index.tsx +170 -0
  48. package/src/client/components/inputv3/Rte/nodes/PlaygroundNodes.ts +76 -0
  49. package/src/client/components/inputv3/Rte/nodes/PollComponent.tsx +249 -0
  50. package/src/client/components/inputv3/Rte/nodes/PollNode.css +187 -0
  51. package/src/client/components/inputv3/Rte/nodes/PollNode.tsx +209 -0
  52. package/src/client/components/inputv3/Rte/nodes/StickyComponent.tsx +261 -0
  53. package/src/client/components/inputv3/Rte/nodes/StickyNode.css +37 -0
  54. package/src/client/components/inputv3/Rte/nodes/StickyNode.tsx +150 -0
  55. package/src/client/components/inputv3/Rte/nodes/TweetNode.tsx +223 -0
  56. package/src/client/components/inputv3/Rte/nodes/YouTubeNode.tsx +184 -0
  57. package/src/client/components/inputv3/Rte/plugins/ActionsPlugin/index.tsx +334 -0
  58. package/src/client/components/inputv3/Rte/plugins/AutoEmbedPlugin/index.tsx +352 -0
  59. package/src/client/components/inputv3/Rte/plugins/AutoLinkPlugin/index.tsx +32 -0
  60. package/src/client/components/inputv3/Rte/plugins/AutocompletePlugin/index.tsx +2529 -0
  61. package/src/client/components/inputv3/Rte/plugins/CodeActionMenuPlugin/components/CopyButton/index.tsx +70 -0
  62. package/src/client/components/inputv3/Rte/plugins/CodeActionMenuPlugin/components/PrettierButton/index.css +14 -0
  63. package/src/client/components/inputv3/Rte/plugins/CodeActionMenuPlugin/components/PrettierButton/index.tsx +156 -0
  64. package/src/client/components/inputv3/Rte/plugins/CodeActionMenuPlugin/index.css +54 -0
  65. package/src/client/components/inputv3/Rte/plugins/CodeActionMenuPlugin/index.tsx +190 -0
  66. package/src/client/components/inputv3/Rte/plugins/CodeActionMenuPlugin/utils.ts +33 -0
  67. package/src/client/components/inputv3/Rte/plugins/CodeHighlightPlugin/index.ts +21 -0
  68. package/src/client/components/inputv3/Rte/plugins/CollapsiblePlugin/Collapsible.css +57 -0
  69. package/src/client/components/inputv3/Rte/plugins/CollapsiblePlugin/CollapsibleContainerNode.ts +168 -0
  70. package/src/client/components/inputv3/Rte/plugins/CollapsiblePlugin/CollapsibleContentNode.ts +127 -0
  71. package/src/client/components/inputv3/Rte/plugins/CollapsiblePlugin/CollapsibleTitleNode.ts +152 -0
  72. package/src/client/components/inputv3/Rte/plugins/CollapsiblePlugin/CollapsibleUtils.ts +17 -0
  73. package/src/client/components/inputv3/Rte/plugins/CollapsiblePlugin/index.ts +284 -0
  74. package/src/client/components/inputv3/Rte/plugins/ComponentPickerPlugin/index.tsx +370 -0
  75. package/src/client/components/inputv3/Rte/plugins/ContextMenuPlugin/index.tsx +270 -0
  76. package/src/client/components/inputv3/Rte/plugins/DocsPlugin/index.tsx +20 -0
  77. package/src/client/components/inputv3/Rte/plugins/DragDropPastePlugin/index.ts +51 -0
  78. package/src/client/components/inputv3/Rte/plugins/DraggableBlockPlugin/index.css +36 -0
  79. package/src/client/components/inputv3/Rte/plugins/DraggableBlockPlugin/index.tsx +43 -0
  80. package/src/client/components/inputv3/Rte/plugins/EmojiPickerPlugin/index.tsx +198 -0
  81. package/src/client/components/inputv3/Rte/plugins/EmojisPlugin/index.ts +75 -0
  82. package/src/client/components/inputv3/Rte/plugins/EquationsPlugin/index.tsx +82 -0
  83. package/src/client/components/inputv3/Rte/plugins/FigmaPlugin/index.tsx +40 -0
  84. package/src/client/components/inputv3/Rte/plugins/FloatingLinkEditorPlugin/index.css +41 -0
  85. package/src/client/components/inputv3/Rte/plugins/FloatingLinkEditorPlugin/index.tsx +393 -0
  86. package/src/client/components/inputv3/Rte/plugins/FloatingTextFormatToolbarPlugin/index.css +141 -0
  87. package/src/client/components/inputv3/Rte/plugins/FloatingTextFormatToolbarPlugin/index.tsx +388 -0
  88. package/src/client/components/inputv3/Rte/plugins/ImagesPlugin/index.tsx +350 -0
  89. package/src/client/components/inputv3/Rte/plugins/InlineImagePlugin/index.tsx +336 -0
  90. package/src/client/components/inputv3/Rte/plugins/KeywordsPlugin/index.ts +56 -0
  91. package/src/client/components/inputv3/Rte/plugins/LayoutPlugin/InsertLayoutDialog.tsx +58 -0
  92. package/src/client/components/inputv3/Rte/plugins/LayoutPlugin/LayoutPlugin.tsx +219 -0
  93. package/src/client/components/inputv3/Rte/plugins/LinkPlugin/index.tsx +34 -0
  94. package/src/client/components/inputv3/Rte/plugins/ListMaxIndentLevelPlugin/index.ts +85 -0
  95. package/src/client/components/inputv3/Rte/plugins/MarkdownShortcutPlugin/index.tsx +16 -0
  96. package/src/client/components/inputv3/Rte/plugins/MarkdownTransformers/index.ts +324 -0
  97. package/src/client/components/inputv3/Rte/plugins/MaxLengthPlugin/index.tsx +53 -0
  98. package/src/client/components/inputv3/Rte/plugins/MentionsPlugin/index.tsx +696 -0
  99. package/src/client/components/inputv3/Rte/plugins/PageBreakPlugin/index.tsx +57 -0
  100. package/src/client/components/inputv3/Rte/plugins/PasteLogPlugin/index.tsx +54 -0
  101. package/src/client/components/inputv3/Rte/plugins/PollPlugin/index.tsx +86 -0
  102. package/src/client/components/inputv3/Rte/plugins/SpeechToTextPlugin/index.ts +125 -0
  103. package/src/client/components/inputv3/Rte/plugins/StickyPlugin/index.ts +22 -0
  104. package/src/client/components/inputv3/Rte/plugins/TabFocusPlugin/index.tsx +65 -0
  105. package/src/client/components/inputv3/Rte/plugins/TableActionMenuPlugin/index.tsx +773 -0
  106. package/src/client/components/inputv3/Rte/plugins/TableCellResizer/index.css +12 -0
  107. package/src/client/components/inputv3/Rte/plugins/TableCellResizer/index.tsx +436 -0
  108. package/src/client/components/inputv3/Rte/plugins/TableHoverActionsPlugin/index.tsx +287 -0
  109. package/src/client/components/inputv3/Rte/plugins/TableOfContentsPlugin/index.css +95 -0
  110. package/src/client/components/inputv3/Rte/plugins/TableOfContentsPlugin/index.tsx +197 -0
  111. package/src/client/components/inputv3/Rte/plugins/TablePlugin.tsx +178 -0
  112. package/src/client/components/inputv3/Rte/plugins/TestRecorderPlugin/index.tsx +468 -0
  113. package/src/client/components/inputv3/Rte/plugins/TreeViewPlugin/index.tsx +26 -0
  114. package/src/client/components/inputv3/Rte/plugins/TwitterPlugin/index.ts +41 -0
  115. package/src/client/components/inputv3/Rte/plugins/TypingPerfPlugin/index.ts +117 -0
  116. package/src/client/components/inputv3/Rte/plugins/YouTubePlugin/index.ts +41 -0
  117. package/src/client/components/inputv3/Rte/shared/canUseDOM.ts +4 -0
  118. package/src/client/components/inputv3/Rte/shared/caretFromPoint.ts +40 -0
  119. package/src/client/components/inputv3/Rte/shared/environment.ts +56 -0
  120. package/src/client/components/inputv3/Rte/shared/invariant.ts +26 -0
  121. package/src/client/components/inputv3/Rte/shared/normalizeClassNames.ts +21 -0
  122. package/src/client/components/inputv3/Rte/shared/react-test-utils.ts +18 -0
  123. package/src/client/components/inputv3/Rte/shared/reactPatches.ts +22 -0
  124. package/src/client/components/inputv3/Rte/shared/simpleDiffWithCursor.ts +49 -0
  125. package/src/client/components/inputv3/Rte/shared/useLayoutEffect.ts +19 -0
  126. package/src/client/components/inputv3/Rte/shared/warnOnlyOnce.ts +20 -0
  127. package/src/client/components/inputv3/Rte/style.less +30 -60
  128. package/src/client/components/inputv3/Rte/themes/CommentEditorTheme.css +13 -0
  129. package/src/client/components/inputv3/Rte/themes/CommentEditorTheme.ts +20 -0
  130. package/src/client/components/inputv3/Rte/themes/PlaygroundEditorTheme.css +447 -0
  131. package/src/client/components/inputv3/Rte/themes/PlaygroundEditorTheme.ts +120 -0
  132. package/src/client/components/inputv3/Rte/themes/StickyEditorTheme.css +13 -0
  133. package/src/client/components/inputv3/Rte/themes/StickyEditorTheme.ts +20 -0
  134. package/src/client/components/inputv3/Rte/ui/ColorPicker.css +88 -0
  135. package/src/client/components/inputv3/Rte/ui/ColorPicker.tsx +365 -0
  136. package/src/client/components/inputv3/Rte/ui/ContentEditable.css +44 -0
  137. package/src/client/components/inputv3/Rte/ui/ContentEditable.tsx +36 -0
  138. package/src/client/components/inputv3/Rte/ui/DropDown.tsx +259 -0
  139. package/src/client/components/inputv3/Rte/ui/DropdownColorPicker.tsx +41 -0
  140. package/src/client/components/inputv3/Rte/ui/EquationEditor.css +38 -0
  141. package/src/client/components/inputv3/Rte/ui/EquationEditor.tsx +56 -0
  142. package/src/client/components/inputv3/Rte/ui/FileInput.tsx +38 -0
  143. package/src/client/components/inputv3/Rte/ui/FlashMessage.css +28 -0
  144. package/src/client/components/inputv3/Rte/ui/FlashMessage.tsx +29 -0
  145. package/src/client/components/inputv3/Rte/ui/ImageResizer.tsx +316 -0
  146. package/src/client/components/inputv3/Rte/ui/Input.css +32 -0
  147. package/src/client/components/inputv3/Rte/ui/KatexRenderer.tsx +54 -0
  148. package/src/client/components/inputv3/Rte/ui/Switch.tsx +36 -0
  149. package/src/client/components/inputv3/Rte/utils/docSerialization.ts +77 -0
  150. package/src/client/components/inputv3/Rte/utils/emoji-list.ts +16615 -0
  151. package/src/client/components/inputv3/Rte/utils/getDOMRangeRect.ts +27 -0
  152. package/src/client/components/inputv3/Rte/utils/getSelectedNode.ts +27 -0
  153. package/src/client/components/inputv3/Rte/utils/guard.ts +10 -0
  154. package/src/client/components/inputv3/Rte/utils/isMobileWidth.ts +7 -0
  155. package/src/client/components/inputv3/Rte/utils/joinClasses.ts +13 -0
  156. package/src/client/components/inputv3/Rte/utils/setFloatingElemPosition.ts +51 -0
  157. package/src/client/components/inputv3/Rte/utils/setFloatingElemPositionForLinkEditor.ts +46 -0
  158. package/src/client/components/inputv3/Rte/utils/swipe.ts +127 -0
  159. package/src/client/components/inputv3/Rte/utils/url.ts +38 -0
  160. package/src/client/components/inputv3/base.tsx +8 -5
  161. package/src/client/components/inputv3/index.tsx +1 -1
  162. package/src/common/data/rte/nodes.ts +60 -9
  163. package/src/common/validation/index.ts +21 -2
  164. package/src/common/validation/schema.ts +27 -10
  165. package/src/common/validation/validator.ts +12 -4
  166. package/src/common/validation/validators.ts +108 -66
  167. package/src/server/services/router/http/multipart.ts +0 -1
  168. package/src/server/services/schema/index.ts +26 -4
  169. package/src/server/services/schema/request.ts +3 -2
  170. package/src/server/services/schema/rte.ts +110 -0
  171. package/src/server/utils/rte.ts +7 -4
  172. package/src/client/components/inputv3/Rte/ExampleTheme.tsx +0 -42
  173. package/src/client/components/inputv3/Rte/ToolbarPlugin.tsx +0 -167
  174. package/src/client/components/inputv3/Rte/icons/LICENSE.md +0 -5
  175. package/src/client/components/inputv3/Rte/icons/arrow-clockwise.svg +0 -4
  176. package/src/client/components/inputv3/Rte/icons/arrow-counterclockwise.svg +0 -4
  177. package/src/client/components/inputv3/Rte/icons/journal-text.svg +0 -5
  178. package/src/client/components/inputv3/Rte/icons/justify.svg +0 -3
  179. package/src/client/components/inputv3/Rte/icons/text-center.svg +0 -3
  180. package/src/client/components/inputv3/Rte/icons/text-left.svg +0 -3
  181. package/src/client/components/inputv3/Rte/icons/text-paragraph.svg +0 -3
  182. package/src/client/components/inputv3/Rte/icons/text-right.svg +0 -3
  183. package/src/client/components/inputv3/Rte/icons/type-bold.svg +0 -3
  184. package/src/client/components/inputv3/Rte/icons/type-italic.svg +0 -3
  185. package/src/client/components/inputv3/Rte/icons/type-strikethrough.svg +0 -3
  186. package/src/client/components/inputv3/Rte/icons/type-underline.svg +0 -3
@@ -0,0 +1,287 @@
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 Button from '@client/components/button';
10
+
11
+ import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
12
+ import {useLexicalEditable} from '@lexical/react/useLexicalEditable';
13
+ import {
14
+ $getTableColumnIndexFromTableCellNode,
15
+ $getTableRowIndexFromTableCellNode,
16
+ $insertTableColumn__EXPERIMENTAL,
17
+ $insertTableRow__EXPERIMENTAL,
18
+ $isTableCellNode,
19
+ $isTableNode,
20
+ TableCellNode,
21
+ TableNode,
22
+ TableRowNode,
23
+ } from '@lexical/table';
24
+ import {$findMatchingParent, mergeRegister} from '@lexical/utils';
25
+ import {$getNearestNodeFromDOMNode, NodeKey} from 'lexical';
26
+ import {useEffect, useMemo, useRef, useState} from 'react';
27
+ import * as React from 'react';
28
+ import {createPortal} from 'react-dom';
29
+
30
+ import {useDebounce} from '../CodeActionMenuPlugin/utils';
31
+
32
+ const BUTTON_WIDTH_PX = 20;
33
+
34
+ function TableHoverActionsContainer({
35
+ anchorElem,
36
+ }: {
37
+ anchorElem: HTMLElement;
38
+ }): JSX.Element | null {
39
+ const [editor] = useLexicalComposerContext();
40
+ const isEditable = useLexicalEditable();
41
+ const [isShownRow, setShownRow] = useState<boolean>(false);
42
+ const [isShownColumn, setShownColumn] = useState<boolean>(false);
43
+ const [shouldListenMouseMove, setShouldListenMouseMove] =
44
+ useState<boolean>(false);
45
+ const [position, setPosition] = useState({});
46
+ const tableSetRef = useRef<Set<NodeKey>>(new Set());
47
+ const tableCellDOMNodeRef = useRef<HTMLElement | null>(null);
48
+
49
+ const debouncedOnMouseMove = useDebounce(
50
+ (event: MouseEvent) => {
51
+ const {isOutside, tableDOMNode} = getMouseInfo(event);
52
+
53
+ if (isOutside) {
54
+ setShownRow(false);
55
+ setShownColumn(false);
56
+ return;
57
+ }
58
+
59
+ if (!tableDOMNode) {
60
+ return;
61
+ }
62
+
63
+ tableCellDOMNodeRef.current = tableDOMNode;
64
+
65
+ let hoveredRowNode: TableCellNode | null = null;
66
+ let hoveredColumnNode: TableCellNode | null = null;
67
+ let tableDOMElement: HTMLElement | null = null;
68
+
69
+ editor.update(() => {
70
+ const maybeTableCell = $getNearestNodeFromDOMNode(tableDOMNode);
71
+
72
+ if ($isTableCellNode(maybeTableCell)) {
73
+ const table = $findMatchingParent(maybeTableCell, (node) =>
74
+ $isTableNode(node),
75
+ );
76
+ if (!$isTableNode(table)) {
77
+ return;
78
+ }
79
+
80
+ tableDOMElement = editor.getElementByKey(table?.getKey());
81
+
82
+ if (tableDOMElement) {
83
+ const rowCount = table.getChildrenSize();
84
+ const colCount = (
85
+ (table as TableNode).getChildAtIndex(0) as TableRowNode
86
+ )?.getChildrenSize();
87
+
88
+ const rowIndex = $getTableRowIndexFromTableCellNode(maybeTableCell);
89
+ const colIndex =
90
+ $getTableColumnIndexFromTableCellNode(maybeTableCell);
91
+
92
+ if (rowIndex === rowCount - 1) {
93
+ hoveredRowNode = maybeTableCell;
94
+ } else if (colIndex === colCount - 1) {
95
+ hoveredColumnNode = maybeTableCell;
96
+ }
97
+ }
98
+ }
99
+ });
100
+
101
+ if (tableDOMElement) {
102
+ const {
103
+ width: tableElemWidth,
104
+ y: tableElemY,
105
+ right: tableElemRight,
106
+ left: tableElemLeft,
107
+ bottom: tableElemBottom,
108
+ height: tableElemHeight,
109
+ } = (tableDOMElement as HTMLTableElement).getBoundingClientRect();
110
+
111
+ const {y: editorElemY, left: editorElemLeft} =
112
+ anchorElem.getBoundingClientRect();
113
+
114
+ if (hoveredRowNode) {
115
+ setShownColumn(false);
116
+ setShownRow(true);
117
+ setPosition({
118
+ height: BUTTON_WIDTH_PX,
119
+ left: tableElemLeft - editorElemLeft,
120
+ top: tableElemBottom - editorElemY + 5,
121
+ width: tableElemWidth,
122
+ });
123
+ } else if (hoveredColumnNode) {
124
+ setShownColumn(true);
125
+ setShownRow(false);
126
+ setPosition({
127
+ height: tableElemHeight,
128
+ left: tableElemRight - editorElemLeft + 5,
129
+ top: tableElemY - editorElemY,
130
+ width: BUTTON_WIDTH_PX,
131
+ });
132
+ }
133
+ }
134
+ },
135
+ 50,
136
+ 250,
137
+ );
138
+
139
+ // Hide the buttons on any table dimensions change to prevent last row cells
140
+ // overlap behind the 'Add Row' button when text entry changes cell height
141
+ const tableResizeObserver = useMemo(() => {
142
+ return new ResizeObserver(() => {
143
+ setShownRow(false);
144
+ setShownColumn(false);
145
+ });
146
+ }, []);
147
+
148
+ useEffect(() => {
149
+ if (!shouldListenMouseMove) {
150
+ return;
151
+ }
152
+
153
+ document.addEventListener('mousemove', debouncedOnMouseMove);
154
+
155
+ return () => {
156
+ setShownRow(false);
157
+ setShownColumn(false);
158
+ debouncedOnMouseMove.cancel();
159
+ document.removeEventListener('mousemove', debouncedOnMouseMove);
160
+ };
161
+ }, [shouldListenMouseMove, debouncedOnMouseMove]);
162
+
163
+ useEffect(() => {
164
+ return mergeRegister(
165
+ editor.registerMutationListener(
166
+ TableNode,
167
+ (mutations) => {
168
+ editor.getEditorState().read(() => {
169
+ for (const [key, type] of mutations) {
170
+ const tableDOMElement = editor.getElementByKey(key);
171
+ switch (type) {
172
+ case 'created':
173
+ tableSetRef.current.add(key);
174
+ setShouldListenMouseMove(tableSetRef.current.size > 0);
175
+ if (tableDOMElement) {
176
+ tableResizeObserver.observe(tableDOMElement);
177
+ }
178
+ break;
179
+
180
+ case 'destroyed':
181
+ tableSetRef.current.delete(key);
182
+ setShouldListenMouseMove(tableSetRef.current.size > 0);
183
+ // Reset resize observers
184
+ tableResizeObserver.disconnect();
185
+ tableSetRef.current.forEach((tableKey: NodeKey) => {
186
+ const tableElement = editor.getElementByKey(tableKey);
187
+ if (tableElement) {
188
+ tableResizeObserver.observe(tableElement);
189
+ }
190
+ });
191
+ break;
192
+
193
+ default:
194
+ break;
195
+ }
196
+ }
197
+ });
198
+ },
199
+ {skipInitialization: false},
200
+ ),
201
+ );
202
+ }, [editor, tableResizeObserver]);
203
+
204
+ const insertAction = (insertRow: boolean) => {
205
+ editor.update(() => {
206
+ if (tableCellDOMNodeRef.current) {
207
+ const maybeTableNode = $getNearestNodeFromDOMNode(
208
+ tableCellDOMNodeRef.current,
209
+ );
210
+ maybeTableNode?.selectEnd();
211
+ if (insertRow) {
212
+ $insertTableRow__EXPERIMENTAL();
213
+ setShownRow(false);
214
+ } else {
215
+ $insertTableColumn__EXPERIMENTAL();
216
+ setShownColumn(false);
217
+ }
218
+ }
219
+ });
220
+ };
221
+
222
+ if (!isEditable) {
223
+ return null;
224
+ }
225
+
226
+ return (
227
+ <>
228
+ {isShownRow && (
229
+ <Button size="s"
230
+ icon="plus"
231
+ style={{...position}}
232
+ onClick={() => insertAction(true)}
233
+ />
234
+ )}
235
+ {isShownColumn && (
236
+ <Button size="s"
237
+ icon="plus"
238
+ style={{...position}}
239
+ onClick={() => insertAction(false)}
240
+ />
241
+ )}
242
+ </>
243
+ );
244
+ }
245
+
246
+ function getMouseInfo(event: MouseEvent): {
247
+ tableDOMNode: HTMLElement | null;
248
+ isOutside: boolean;
249
+ } {
250
+ const target = event.target;
251
+
252
+ if (target && target instanceof HTMLElement) {
253
+ const tableDOMNode = target.closest<HTMLElement>(
254
+ 'td.PlaygroundEditorTheme__tableCell, th.PlaygroundEditorTheme__tableCell',
255
+ );
256
+
257
+ const isOutside = !(
258
+ tableDOMNode ||
259
+ target.closest<HTMLElement>(
260
+ 'button.PlaygroundEditorTheme__tableAddRows',
261
+ ) ||
262
+ target.closest<HTMLElement>(
263
+ 'button.PlaygroundEditorTheme__tableAddColumns',
264
+ ) ||
265
+ target.closest<HTMLElement>('div.TableCellResizer__resizer')
266
+ );
267
+
268
+ return {isOutside, tableDOMNode};
269
+ } else {
270
+ return {isOutside: true, tableDOMNode: null};
271
+ }
272
+ }
273
+
274
+ export default function TableHoverActionsPlugin({
275
+ anchorElem = document.body,
276
+ }: {
277
+ anchorElem?: HTMLElement;
278
+ }): React.ReactPortal | null {
279
+ const isEditable = useLexicalEditable();
280
+
281
+ return isEditable
282
+ ? createPortal(
283
+ <TableHoverActionsContainer anchorElem={anchorElem} />,
284
+ anchorElem,
285
+ )
286
+ : null;
287
+ }
@@ -0,0 +1,95 @@
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
+ .table-of-contents .heading2 {
10
+ margin-left: 10px;
11
+ }
12
+
13
+ .table-of-contents .heading3 {
14
+ margin-left: 20px;
15
+ }
16
+
17
+ .selected-heading {
18
+ color: #3578e5;
19
+ position: relative;
20
+ }
21
+
22
+ .selected-heading-wrapper::before {
23
+ content: ' ';
24
+ position: absolute;
25
+ display: inline-block;
26
+ left: -30px;
27
+ top: 4px;
28
+ z-index: 10;
29
+ height: 4px;
30
+ width: 4px;
31
+ background-color: #3578e5;
32
+ border: solid 4px white;
33
+ border-radius: 50%;
34
+ }
35
+
36
+ .normal-heading {
37
+ cursor: pointer;
38
+ line-height: 20px;
39
+ font-size: 16px;
40
+ }
41
+
42
+ .table-of-contents {
43
+ color: #65676b;
44
+ position: fixed;
45
+ top: 200px;
46
+ right: -35px;
47
+ padding: 10px;
48
+ width: 250px;
49
+ display: flex;
50
+ flex-direction: row;
51
+ justify-content: flex-start;
52
+ z-index: 1;
53
+ height: 300px;
54
+ }
55
+
56
+ .first-heading {
57
+ color: black;
58
+ font-weight: bold;
59
+ cursor: pointer;
60
+ }
61
+
62
+ .headings {
63
+ list-style: none;
64
+ margin-top: 0;
65
+ margin-left: 10px;
66
+ padding: 0;
67
+ overflow: scroll;
68
+ width: 200px;
69
+ height: 220px;
70
+ overflow-x: hidden;
71
+ overflow-y: auto;
72
+ -ms-overflow-style: none; /* IE and Edge */
73
+ scrollbar-width: none; /* Firefox */
74
+ }
75
+
76
+ /* Hide scrollbar for Chrome, Safari and Opera */
77
+ .headings::-webkit-scrollbar {
78
+ display: none;
79
+ }
80
+
81
+ .headings::before {
82
+ content: ' ';
83
+ position: absolute;
84
+ height: 220px;
85
+ width: 4px;
86
+ right: 240px;
87
+ margin-top: 5px;
88
+ background-color: #ccd0d5;
89
+ border-radius: 2px;
90
+ }
91
+
92
+ .normal-heading-wrapper {
93
+ margin-left: 32px;
94
+ position: relative;
95
+ }
@@ -0,0 +1,197 @@
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
+ import type {TableOfContentsEntry} from '@lexical/react/LexicalTableOfContentsPlugin';
9
+ import type {HeadingTagType} from '@lexical/rich-text';
10
+ import type {NodeKey} from 'lexical';
11
+
12
+ import './index.css';
13
+
14
+ import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
15
+ import {TableOfContentsPlugin as LexicalTableOfContentsPlugin} from '@lexical/react/LexicalTableOfContentsPlugin';
16
+ import {useEffect, useRef, useState} from 'react';
17
+ import * as React from 'react';
18
+
19
+ const MARGIN_ABOVE_EDITOR = 624;
20
+ const HEADING_WIDTH = 9;
21
+
22
+ function indent(tagName: HeadingTagType) {
23
+ if (tagName === 'h2') {
24
+ return 'heading2';
25
+ } else if (tagName === 'h3') {
26
+ return 'heading3';
27
+ }
28
+ }
29
+
30
+ function isHeadingAtTheTopOfThePage(element: HTMLElement): boolean {
31
+ const elementYPosition = element?.getClientRects()[0].y;
32
+ return (
33
+ elementYPosition >= MARGIN_ABOVE_EDITOR &&
34
+ elementYPosition <= MARGIN_ABOVE_EDITOR + HEADING_WIDTH
35
+ );
36
+ }
37
+ function isHeadingAboveViewport(element: HTMLElement): boolean {
38
+ const elementYPosition = element?.getClientRects()[0].y;
39
+ return elementYPosition < MARGIN_ABOVE_EDITOR;
40
+ }
41
+ function isHeadingBelowTheTopOfThePage(element: HTMLElement): boolean {
42
+ const elementYPosition = element?.getClientRects()[0].y;
43
+ return elementYPosition >= MARGIN_ABOVE_EDITOR + HEADING_WIDTH;
44
+ }
45
+
46
+ function TableOfContentsList({
47
+ tableOfContents,
48
+ }: {
49
+ tableOfContents: Array<TableOfContentsEntry>;
50
+ }): JSX.Element {
51
+ const [selectedKey, setSelectedKey] = useState('');
52
+ const selectedIndex = useRef(0);
53
+ const [editor] = useLexicalComposerContext();
54
+
55
+ function scrollToNode(key: NodeKey, currIndex: number) {
56
+ editor.getEditorState().read(() => {
57
+ const domElement = editor.getElementByKey(key);
58
+ if (domElement !== null) {
59
+ domElement.scrollIntoView();
60
+ setSelectedKey(key);
61
+ selectedIndex.current = currIndex;
62
+ }
63
+ });
64
+ }
65
+
66
+ useEffect(() => {
67
+ function scrollCallback() {
68
+ if (
69
+ tableOfContents.length !== 0 &&
70
+ selectedIndex.current < tableOfContents.length - 1
71
+ ) {
72
+ let currentHeading = editor.getElementByKey(
73
+ tableOfContents[selectedIndex.current][0],
74
+ );
75
+ if (currentHeading !== null) {
76
+ if (isHeadingBelowTheTopOfThePage(currentHeading)) {
77
+ //On natural scroll, user is scrolling up
78
+ while (
79
+ currentHeading !== null &&
80
+ isHeadingBelowTheTopOfThePage(currentHeading) &&
81
+ selectedIndex.current > 0
82
+ ) {
83
+ const prevHeading = editor.getElementByKey(
84
+ tableOfContents[selectedIndex.current - 1][0],
85
+ );
86
+ if (
87
+ prevHeading !== null &&
88
+ (isHeadingAboveViewport(prevHeading) ||
89
+ isHeadingBelowTheTopOfThePage(prevHeading))
90
+ ) {
91
+ selectedIndex.current--;
92
+ }
93
+ currentHeading = prevHeading;
94
+ }
95
+ const prevHeadingKey = tableOfContents[selectedIndex.current][0];
96
+ setSelectedKey(prevHeadingKey);
97
+ } else if (isHeadingAboveViewport(currentHeading)) {
98
+ //On natural scroll, user is scrolling down
99
+ while (
100
+ currentHeading !== null &&
101
+ isHeadingAboveViewport(currentHeading) &&
102
+ selectedIndex.current < tableOfContents.length - 1
103
+ ) {
104
+ const nextHeading = editor.getElementByKey(
105
+ tableOfContents[selectedIndex.current + 1][0],
106
+ );
107
+ if (
108
+ nextHeading !== null &&
109
+ (isHeadingAtTheTopOfThePage(nextHeading) ||
110
+ isHeadingAboveViewport(nextHeading))
111
+ ) {
112
+ selectedIndex.current++;
113
+ }
114
+ currentHeading = nextHeading;
115
+ }
116
+ const nextHeadingKey = tableOfContents[selectedIndex.current][0];
117
+ setSelectedKey(nextHeadingKey);
118
+ }
119
+ }
120
+ } else {
121
+ selectedIndex.current = 0;
122
+ }
123
+ }
124
+ let timerId: ReturnType<typeof setTimeout>;
125
+
126
+ function debounceFunction(func: () => void, delay: number) {
127
+ clearTimeout(timerId);
128
+ timerId = setTimeout(func, delay);
129
+ }
130
+
131
+ function onScroll(): void {
132
+ debounceFunction(scrollCallback, 10);
133
+ }
134
+
135
+ document.addEventListener('scroll', onScroll);
136
+ return () => document.removeEventListener('scroll', onScroll);
137
+ }, [tableOfContents, editor]);
138
+
139
+ return (
140
+ <div className="table-of-contents">
141
+ <ul className="headings">
142
+ {tableOfContents.map(([key, text, tag], index) => {
143
+ if (index === 0) {
144
+ return (
145
+ <div className="normal-heading-wrapper" key={key}>
146
+ <div
147
+ className="first-heading"
148
+ onClick={() => scrollToNode(key, index)}
149
+ role="button"
150
+ tabIndex={0}>
151
+ {('' + text).length > 20
152
+ ? text.substring(0, 20) + '...'
153
+ : text}
154
+ </div>
155
+ <br />
156
+ </div>
157
+ );
158
+ } else {
159
+ return (
160
+ <div
161
+ className={`normal-heading-wrapper ${
162
+ selectedKey === key ? 'selected-heading-wrapper' : ''
163
+ }`}
164
+ key={key}>
165
+ <div
166
+ onClick={() => scrollToNode(key, index)}
167
+ role="button"
168
+ className={indent(tag)}
169
+ tabIndex={0}>
170
+ <li
171
+ className={`normal-heading ${
172
+ selectedKey === key ? 'selected-heading' : ''
173
+ }
174
+ `}>
175
+ {('' + text).length > 27
176
+ ? text.substring(0, 27) + '...'
177
+ : text}
178
+ </li>
179
+ </div>
180
+ </div>
181
+ );
182
+ }
183
+ })}
184
+ </ul>
185
+ </div>
186
+ );
187
+ }
188
+
189
+ export default function TableOfContentsPlugin() {
190
+ return (
191
+ <LexicalTableOfContentsPlugin>
192
+ {(tableOfContents) => {
193
+ return <TableOfContentsList tableOfContents={tableOfContents} />;
194
+ }}
195
+ </LexicalTableOfContentsPlugin>
196
+ );
197
+ }