@collabchron/notiq 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/README.md +71 -0
  2. package/components.json +21 -0
  3. package/eslint.config.mjs +16 -0
  4. package/next.config.ts +12 -0
  5. package/package.json +108 -0
  6. package/postcss.config.mjs +5 -0
  7. package/public/file.svg +1 -0
  8. package/public/globe.svg +1 -0
  9. package/public/images/icons/plus.svg +10 -0
  10. package/public/next.svg +1 -0
  11. package/public/vercel.svg +1 -0
  12. package/public/window.svg +1 -0
  13. package/src/app/actions.ts +2 -0
  14. package/src/app/api/ai/route.ts +175 -0
  15. package/src/app/api/edgestore/[...edgestore]/route.ts +28 -0
  16. package/src/app/favicon.ico +0 -0
  17. package/src/app/globals.css +205 -0
  18. package/src/app/layout.tsx +38 -0
  19. package/src/app/page.tsx +12 -0
  20. package/src/components/editor/Core.tsx +220 -0
  21. package/src/components/editor/hooks/instructions-messages.ts +300 -0
  22. package/src/components/editor/hooks/use-mobile.ts +19 -0
  23. package/src/components/editor/hooks/useReport.ts +67 -0
  24. package/src/components/editor/hooks/useResizeObservert.ts +22 -0
  25. package/src/components/editor/index.tsx +39 -0
  26. package/src/components/editor/lexical-on-change.tsx +28 -0
  27. package/src/components/editor/nodes/CollapsibleNode/CollapsibleContainerNode.ts +92 -0
  28. package/src/components/editor/nodes/CollapsibleNode/CollapsibleContentNode.ts +65 -0
  29. package/src/components/editor/nodes/CollapsibleNode/CollapsibleTitleNode.ts +105 -0
  30. package/src/components/editor/nodes/EquationNode/EquationComponent.tsx +143 -0
  31. package/src/components/editor/nodes/EquationNode/EquationNode.tsx +170 -0
  32. package/src/components/editor/nodes/ExcalidrawNode/ExcalidrawComponent.tsx +228 -0
  33. package/src/components/editor/nodes/ExcalidrawNode/ExcalidrawImage.tsx +137 -0
  34. package/src/components/editor/nodes/ExcalidrawNode/ImageResizer.tsx +317 -0
  35. package/src/components/editor/nodes/ExcalidrawNode/index.tsx +204 -0
  36. package/src/components/editor/nodes/FigmaNode/FigmaNode.tsx +134 -0
  37. package/src/components/editor/nodes/Hint/HintComponet.tsx +221 -0
  38. package/src/components/editor/nodes/Hint/index.tsx +190 -0
  39. package/src/components/editor/nodes/ImageNode/index.tsx +328 -0
  40. package/src/components/editor/nodes/InlineImageNode/InlineImageComponent.tsx +383 -0
  41. package/src/components/editor/nodes/InlineImageNode/InlineImageNode.css +94 -0
  42. package/src/components/editor/nodes/InlineImageNode/InlineImageNode.tsx +309 -0
  43. package/src/components/editor/nodes/LayoutNode/LayoutContainerNode.ts +146 -0
  44. package/src/components/editor/nodes/LayoutNode/LayoutItemNode.ts +79 -0
  45. package/src/components/editor/nodes/PollNode/index.tsx +204 -0
  46. package/src/components/editor/nodes/Stepper/index.tsx +260 -0
  47. package/src/components/editor/nodes/TweetNode/index.tsx +214 -0
  48. package/src/components/editor/nodes/index.ts +81 -0
  49. package/src/components/editor/plugins/AutoEmbedPlugin/index.tsx +350 -0
  50. package/src/components/editor/plugins/AutoLinkPlugin/index.tsx +56 -0
  51. package/src/components/editor/plugins/CodeActionMenuPlugin/components/CopyButton.tsx +70 -0
  52. package/src/components/editor/plugins/CodeActionMenuPlugin/components/PrettierButton.tsx +192 -0
  53. package/src/components/editor/plugins/CodeActionMenuPlugin/index.tsx +217 -0
  54. package/src/components/editor/plugins/CodeActionMenuPlugin/utils.ts +26 -0
  55. package/src/components/editor/plugins/CodeHighlightPlugin/index.ts +21 -0
  56. package/src/components/editor/plugins/CollapsiblePlugin/Collapsible.css +76 -0
  57. package/src/components/editor/plugins/CollapsiblePlugin/index.ts +228 -0
  58. package/src/components/editor/plugins/DragDropPastePlugin/index.tsx +44 -0
  59. package/src/components/editor/plugins/DraggableBlockPlugin/index.tsx +52 -0
  60. package/src/components/editor/plugins/EquationsPlugin/index.tsx +85 -0
  61. package/src/components/editor/plugins/ExcalidrawPlugin/index.tsx +98 -0
  62. package/src/components/editor/plugins/FigmaPlugin/index.tsx +42 -0
  63. package/src/components/editor/plugins/FloatingLinkEditorPlugin/index.tsx +445 -0
  64. package/src/components/editor/plugins/FloatingTextFormatToolbarPlugin/index.tsx +275 -0
  65. package/src/components/editor/plugins/ImagesPlugin/index.tsx +222 -0
  66. package/src/components/editor/plugins/InlineImagePlugin/index.tsx +351 -0
  67. package/src/components/editor/plugins/LayoutPlugin/index.tsx +238 -0
  68. package/src/components/editor/plugins/LinkPlugin/index.tsx +36 -0
  69. package/src/components/editor/plugins/LinkWithMetaData/index.tsx +271 -0
  70. package/src/components/editor/plugins/MarkdownShortcutPlugin/index.tsx +11 -0
  71. package/src/components/editor/plugins/MarkdownTransformers/index.tsx +304 -0
  72. package/src/components/editor/plugins/PollPlugin/index.tsx +49 -0
  73. package/src/components/editor/plugins/ShortcutsPlugin/index.tsx +180 -0
  74. package/src/components/editor/plugins/ShortcutsPlugin/shortcuts.ts +253 -0
  75. package/src/components/editor/plugins/SlashCommand/index.tsx +621 -0
  76. package/src/components/editor/plugins/SpeechToTextPlugin/index.ts +127 -0
  77. package/src/components/editor/plugins/TabFocusPlugin/index.ts +58 -0
  78. package/src/components/editor/plugins/TableCellActionMenuPlugin/index.tsx +759 -0
  79. package/src/components/editor/plugins/TableCellResizer/index.tsx +438 -0
  80. package/src/components/editor/plugins/TableHoverActionsPlugin/index.tsx +314 -0
  81. package/src/components/editor/plugins/TablePlugin/index.tsx +99 -0
  82. package/src/components/editor/plugins/ToolbarPlugin/index.tsx +522 -0
  83. package/src/components/editor/plugins/TwitterPlugin/index.ts +35 -0
  84. package/src/components/editor/plugins/YouTubeNode/index.tsx +179 -0
  85. package/src/components/editor/plugins/YouTubePlugin/index.ts +41 -0
  86. package/src/components/editor/themes/editor-theme.ts +113 -0
  87. package/src/components/editor/themes/theme.css +377 -0
  88. package/src/components/editor/utils/ai.ts +291 -0
  89. package/src/components/editor/utils/canUseDOM.ts +12 -0
  90. package/src/components/editor/utils/editorFormatting.ts +282 -0
  91. package/src/components/editor/utils/environment.ts +50 -0
  92. package/src/components/editor/utils/extract-data.ts +166 -0
  93. package/src/components/editor/utils/getAllLexicalChildren.ts +13 -0
  94. package/src/components/editor/utils/getDOMRangeRect.ts +27 -0
  95. package/src/components/editor/utils/getSelectedNode.ts +27 -0
  96. package/src/components/editor/utils/gif.ts +29 -0
  97. package/src/components/editor/utils/invariant.ts +15 -0
  98. package/src/components/editor/utils/setFloatingElemPosition.ts +51 -0
  99. package/src/components/editor/utils/setFloatingElemPositionForLinkEditor.ts +40 -0
  100. package/src/components/editor/utils/setNodePlaceholderFromSelection/getNodePlaceholder.ts +51 -0
  101. package/src/components/editor/utils/setNodePlaceholderFromSelection/setNodePlaceholderFromSelection.ts +15 -0
  102. package/src/components/editor/utils/setNodePlaceholderFromSelection/setPlaceholderOnSelection.ts +114 -0
  103. package/src/components/editor/utils/setNodePlaceholderFromSelection/styles.css +6 -0
  104. package/src/components/editor/utils/url.ts +109 -0
  105. package/src/components/editor/utils/useLayoutEffect.ts +13 -0
  106. package/src/components/providers/QueryProvider.tsx +15 -0
  107. package/src/components/providers/SharedHistoryContext.tsx +28 -0
  108. package/src/components/providers/ToolbarContext.tsx +123 -0
  109. package/src/components/providers/theme-provider.tsx +11 -0
  110. package/src/components/theme/ModeToggle.tsx +40 -0
  111. package/src/components/ui/FileInput.tsx +40 -0
  112. package/src/components/ui/Input.css +32 -0
  113. package/src/components/ui/Select.css +42 -0
  114. package/src/components/ui/Select.tsx +36 -0
  115. package/src/components/ui/TextInput.tsx +48 -0
  116. package/src/components/ui/ai/ai-button.tsx +574 -0
  117. package/src/components/ui/ai/border.tsx +99 -0
  118. package/src/components/ui/ai/placeholder-input-vanish.tsx +282 -0
  119. package/src/components/ui/button.tsx +89 -0
  120. package/src/components/ui/card.tsx +76 -0
  121. package/src/components/ui/checkbox.tsx +30 -0
  122. package/src/components/ui/command.tsx +153 -0
  123. package/src/components/ui/dialog/Dialog.css +25 -0
  124. package/src/components/ui/dialog/Dialog.tsx +34 -0
  125. package/src/components/ui/dialog.tsx +122 -0
  126. package/src/components/ui/drop-downs/background-color.tsx +183 -0
  127. package/src/components/ui/drop-downs/block-format.tsx +159 -0
  128. package/src/components/ui/drop-downs/code.tsx +42 -0
  129. package/src/components/ui/drop-downs/color.tsx +177 -0
  130. package/src/components/ui/drop-downs/font-size.tsx +138 -0
  131. package/src/components/ui/drop-downs/font.tsx +155 -0
  132. package/src/components/ui/drop-downs/index.tsx +122 -0
  133. package/src/components/ui/drop-downs/insert-node.tsx +213 -0
  134. package/src/components/ui/drop-downs/text-align.tsx +123 -0
  135. package/src/components/ui/drop-downs/text-format.tsx +104 -0
  136. package/src/components/ui/dropdown-menu.tsx +201 -0
  137. package/src/components/ui/equation/EquationEditor.css +38 -0
  138. package/src/components/ui/equation/EquationEditor.tsx +56 -0
  139. package/src/components/ui/equation/KatexEquationAlterer.css +41 -0
  140. package/src/components/ui/equation/KatexEquationAlterer.tsx +83 -0
  141. package/src/components/ui/equation/KatexRenderer.tsx +66 -0
  142. package/src/components/ui/excalidraw/ExcalidrawModal.css +64 -0
  143. package/src/components/ui/excalidraw/ExcalidrawModal.tsx +234 -0
  144. package/src/components/ui/excalidraw/Modal.css +62 -0
  145. package/src/components/ui/excalidraw/Modal.tsx +110 -0
  146. package/src/components/ui/hover-card.tsx +29 -0
  147. package/src/components/ui/image/error-image.tsx +17 -0
  148. package/src/components/ui/image/file-upload.tsx +240 -0
  149. package/src/components/ui/image/image-resizer.tsx +297 -0
  150. package/src/components/ui/image/image-toolbar.tsx +264 -0
  151. package/src/components/ui/image/index.tsx +408 -0
  152. package/src/components/ui/image/lazy-image.tsx +68 -0
  153. package/src/components/ui/image/lazy-video.tsx +71 -0
  154. package/src/components/ui/input.tsx +22 -0
  155. package/src/components/ui/models/custom-dialog.tsx +320 -0
  156. package/src/components/ui/models/insert-gif.tsx +90 -0
  157. package/src/components/ui/models/insert-image.tsx +52 -0
  158. package/src/components/ui/models/insert-poll.tsx +29 -0
  159. package/src/components/ui/models/insert-table.tsx +62 -0
  160. package/src/components/ui/models/use-model.tsx +91 -0
  161. package/src/components/ui/poll/poll-component.tsx +304 -0
  162. package/src/components/ui/popover.tsx +33 -0
  163. package/src/components/ui/progress.tsx +28 -0
  164. package/src/components/ui/scroll-area.tsx +48 -0
  165. package/src/components/ui/separator.tsx +31 -0
  166. package/src/components/ui/skeleton.tsx +15 -0
  167. package/src/components/ui/sonner.tsx +31 -0
  168. package/src/components/ui/stepper/step.tsx +179 -0
  169. package/src/components/ui/stepper/stepper.tsx +89 -0
  170. package/src/components/ui/textarea.tsx +22 -0
  171. package/src/components/ui/toggle.tsx +71 -0
  172. package/src/components/ui/tooltip.tsx +32 -0
  173. package/src/components/ui/write/text-format-floting-toolbar.tsx +346 -0
  174. package/src/lib/edgestore.ts +9 -0
  175. package/src/lib/pinecone-client.ts +0 -0
  176. package/src/lib/utils.ts +6 -0
  177. package/src/utils/docSerialization.ts +77 -0
  178. package/src/utils/emoji-list.ts +16615 -0
  179. package/src/utils/getDOMRangeRect.ts +27 -0
  180. package/src/utils/getSelectedNode.ts +27 -0
  181. package/src/utils/getThemeSelector.ts +25 -0
  182. package/src/utils/isMobileWidth.ts +7 -0
  183. package/src/utils/joinClasses.ts +13 -0
  184. package/src/utils/setFloatingElemPosition.ts +74 -0
  185. package/src/utils/setFloatingElemPositionForLinkEditor.ts +46 -0
  186. package/src/utils/swipe.ts +127 -0
  187. package/src/utils/url.ts +38 -0
  188. package/tsconfig.json +27 -0
@@ -0,0 +1,759 @@
1
+ import type { ElementNode, LexicalEditor } from "lexical";
2
+ import { Compact } from '@uiw/react-color';
3
+
4
+ import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
5
+ import { useLexicalEditable } from "@lexical/react/useLexicalEditable";
6
+ import {
7
+ $deleteTableColumn__EXPERIMENTAL,
8
+ $deleteTableRow__EXPERIMENTAL,
9
+ $getNodeTriplet,
10
+ $getTableCellNodeFromLexicalNode,
11
+ $getTableColumnIndexFromTableCellNode,
12
+ $getTableNodeFromLexicalNodeOrThrow,
13
+ $getTableRowIndexFromTableCellNode,
14
+ $insertTableColumn__EXPERIMENTAL,
15
+ $insertTableRow__EXPERIMENTAL,
16
+ $isTableCellNode,
17
+ $isTableRowNode,
18
+ $isTableSelection,
19
+ $unmergeCell,
20
+ getTableElement,
21
+ getTableObserverFromTableElement,
22
+ TableCellHeaderStates,
23
+ TableCellNode,
24
+ TableObserver,
25
+ TableRowNode,
26
+ TableSelection,
27
+ } from "@lexical/table";
28
+ import { mergeRegister } from "@lexical/utils";
29
+ import {
30
+ $createParagraphNode,
31
+ $getRoot,
32
+ $getSelection,
33
+ $isElementNode,
34
+ $isParagraphNode,
35
+ $isRangeSelection,
36
+ $isTextNode,
37
+ COMMAND_PRIORITY_CRITICAL,
38
+ getDOMSelection,
39
+ SELECTION_CHANGE_COMMAND,
40
+ } from "lexical";
41
+ import * as React from "react";
42
+ import { ReactPortal, useCallback, useEffect, useRef, useState } from "react";
43
+ import { createPortal } from "react-dom";
44
+ import invariant from "../../utils/invariant";
45
+ import {
46
+ Menu,
47
+ Merge,
48
+ PaintBucket,
49
+ Split,
50
+ Table,
51
+ TableColumnsSplit,
52
+ TableRowsSplit,
53
+ Trash,
54
+ } from "lucide-react";
55
+ import { DropDown } from "@/components/ui/drop-downs";
56
+ import useModal from "@/components/ui/models/use-model";
57
+
58
+ function computeSelectionCount(selection: TableSelection): {
59
+ columns: number;
60
+ rows: number;
61
+ } {
62
+ const selectionShape = selection.getShape();
63
+ return {
64
+ columns: selectionShape.toX - selectionShape.fromX + 1,
65
+ rows: selectionShape.toY - selectionShape.fromY + 1,
66
+ };
67
+ }
68
+
69
+ function $canUnmerge(): boolean {
70
+ const selection = $getSelection();
71
+ if (
72
+ ($isRangeSelection(selection) && !selection.isCollapsed()) ||
73
+ ($isTableSelection(selection) && !selection.anchor.is(selection.focus)) ||
74
+ (!$isRangeSelection(selection) && !$isTableSelection(selection))
75
+ ) {
76
+ return false;
77
+ }
78
+ const [cell] = $getNodeTriplet(selection.anchor);
79
+ return cell.__colSpan > 1 || cell.__rowSpan > 1;
80
+ }
81
+
82
+ function $cellContainsEmptyParagraph(cell: TableCellNode): boolean {
83
+ if (cell.getChildrenSize() !== 1) {
84
+ return false;
85
+ }
86
+ const firstChild = cell.getFirstChildOrThrow();
87
+ if (!$isParagraphNode(firstChild) || !firstChild.isEmpty()) {
88
+ return false;
89
+ }
90
+ return true;
91
+ }
92
+
93
+ function $selectLastDescendant(node: ElementNode): void {
94
+ const lastDescendant = node.getLastDescendant();
95
+ if ($isTextNode(lastDescendant)) {
96
+ lastDescendant.select();
97
+ } else if ($isElementNode(lastDescendant)) {
98
+ lastDescendant.selectEnd();
99
+ } else if (lastDescendant !== null) {
100
+ lastDescendant.selectNext();
101
+ }
102
+ }
103
+
104
+ function currentCellBackgroundColor(editor: LexicalEditor): null | string {
105
+ return editor.getEditorState().read(() => {
106
+ const selection = $getSelection();
107
+ if ($isRangeSelection(selection) || $isTableSelection(selection)) {
108
+ const [cell] = $getNodeTriplet(selection.anchor);
109
+ if ($isTableCellNode(cell)) {
110
+ return cell.getBackgroundColor();
111
+ }
112
+ }
113
+ return null;
114
+ });
115
+ }
116
+
117
+ type TableCellActionMenuProps = Readonly<{
118
+ contextRef: { current: null | HTMLElement };
119
+ tableCellNode: TableCellNode;
120
+ cellMerge: boolean;
121
+ }>;
122
+
123
+ function TableActionMenu({
124
+ tableCellNode: _tableCellNode,
125
+ contextRef,
126
+ cellMerge,
127
+ }: TableCellActionMenuProps) {
128
+ const [editor] = useLexicalComposerContext();
129
+ const dropDownRef = useRef<HTMLDivElement | null>(null);
130
+ const [tableCellNode, updateTableCellNode] = useState(_tableCellNode);
131
+ const [selectionCounts, updateSelectionCounts] = useState({
132
+ columns: 1,
133
+ rows: 1,
134
+ });
135
+ const [canMergeCells, setCanMergeCells] = useState(false);
136
+ const [canUnmergeCell, setCanUnmergeCell] = useState(false);
137
+ const [backgroundColor, setBackgroundColor] = useState(
138
+ () => currentCellBackgroundColor(editor) || ""
139
+ );
140
+ const [model, showModal] = useModal();
141
+
142
+
143
+ useEffect(() => {
144
+ return editor.registerMutationListener(
145
+ TableCellNode,
146
+ (nodeMutations) => {
147
+ const nodeUpdated =
148
+ nodeMutations.get(tableCellNode.getKey()) === "updated";
149
+
150
+ if (nodeUpdated) {
151
+ editor.getEditorState().read(() => {
152
+ updateTableCellNode(tableCellNode.getLatest());
153
+ });
154
+ setBackgroundColor(currentCellBackgroundColor(editor) || "");
155
+ }
156
+ },
157
+ { skipInitialization: true }
158
+ );
159
+ }, [editor, tableCellNode]);
160
+
161
+ useEffect(() => {
162
+ editor.getEditorState().read(() => {
163
+ const selection = $getSelection();
164
+ // Merge cells
165
+ if ($isTableSelection(selection)) {
166
+ const currentSelectionCounts = computeSelectionCount(selection);
167
+ updateSelectionCounts(computeSelectionCount(selection));
168
+ setCanMergeCells(
169
+ currentSelectionCounts.columns > 1 || currentSelectionCounts.rows > 1
170
+ );
171
+ }
172
+ // Unmerge cell
173
+ setCanUnmergeCell($canUnmerge());
174
+ });
175
+ }, [editor]);
176
+
177
+ useEffect(() => {
178
+ const menuButtonElement = contextRef.current;
179
+ const dropDownElement = dropDownRef.current;
180
+ const rootElement = editor.getRootElement();
181
+
182
+ if (
183
+ menuButtonElement != null &&
184
+ dropDownElement != null &&
185
+ rootElement != null
186
+ ) {
187
+ const rootEleRect = rootElement.getBoundingClientRect();
188
+ const menuButtonRect = menuButtonElement.getBoundingClientRect();
189
+ dropDownElement.style.opacity = "1";
190
+ const dropDownElementRect = dropDownElement.getBoundingClientRect();
191
+ const margin = 5;
192
+ let leftPosition = menuButtonRect.right + margin;
193
+ if (
194
+ leftPosition + dropDownElementRect.width > window.innerWidth ||
195
+ leftPosition + dropDownElementRect.width > rootEleRect.right
196
+ ) {
197
+ const position =
198
+ menuButtonRect.left - dropDownElementRect.width - margin;
199
+ leftPosition = (position < 0 ? margin : position) + window.pageXOffset;
200
+ }
201
+ dropDownElement.style.left = `${leftPosition + window.pageXOffset}px`;
202
+
203
+ let topPosition = menuButtonRect.top;
204
+ if (topPosition + dropDownElementRect.height > window.innerHeight) {
205
+ const position = menuButtonRect.bottom - dropDownElementRect.height;
206
+ topPosition = (position < 0 ? margin : position) + window.pageYOffset;
207
+ }
208
+ dropDownElement.style.top = `${topPosition + +window.pageYOffset}px`;
209
+ }
210
+ }, [contextRef, dropDownRef, editor]);
211
+
212
+ const clearTableSelection = useCallback(() => {
213
+ editor.update(() => {
214
+ if (tableCellNode.isAttached()) {
215
+ const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
216
+ const tableElement = getTableElement(
217
+ tableNode,
218
+ editor.getElementByKey(tableNode.getKey())
219
+ );
220
+
221
+ invariant(
222
+ tableElement !== null,
223
+ "TableActionMenu: Expected to find tableElement in DOM"
224
+ );
225
+
226
+ const tableObserver = getTableObserverFromTableElement(tableElement);
227
+ if (tableObserver !== null) {
228
+ tableObserver.$clearHighlight();
229
+ }
230
+
231
+ tableNode.markDirty();
232
+ updateTableCellNode(tableCellNode.getLatest());
233
+ }
234
+
235
+ const rootNode = $getRoot();
236
+ rootNode.selectStart();
237
+ });
238
+ }, [editor, tableCellNode]);
239
+
240
+ const mergeTableCellsAtSelection = () => {
241
+ editor.update(() => {
242
+ const selection = $getSelection();
243
+ if ($isTableSelection(selection)) {
244
+ const { columns, rows } = computeSelectionCount(selection);
245
+ const nodes = selection.getNodes();
246
+ let firstCell: null | TableCellNode = null;
247
+ for (let i = 0; i < nodes.length; i++) {
248
+ const node = nodes[i];
249
+ if ($isTableCellNode(node)) {
250
+ if (firstCell === null) {
251
+ node.setColSpan(columns).setRowSpan(rows);
252
+ firstCell = node;
253
+ const isEmpty = $cellContainsEmptyParagraph(node);
254
+ let firstChild;
255
+ if (
256
+ isEmpty &&
257
+ $isParagraphNode((firstChild = node.getFirstChild()))
258
+ ) {
259
+ firstChild.remove();
260
+ }
261
+ } else if ($isTableCellNode(firstCell)) {
262
+ const isEmpty = $cellContainsEmptyParagraph(node);
263
+ if (!isEmpty) {
264
+ firstCell.append(...node.getChildren());
265
+ }
266
+ node.remove();
267
+ }
268
+ }
269
+ }
270
+ if (firstCell !== null) {
271
+ if (firstCell.getChildrenSize() === 0) {
272
+ firstCell.append($createParagraphNode());
273
+ }
274
+ $selectLastDescendant(firstCell);
275
+ }
276
+ }
277
+ });
278
+ };
279
+
280
+ const unmergeTableCellsAtSelection = () => {
281
+ editor.update(() => {
282
+ $unmergeCell();
283
+ });
284
+ };
285
+
286
+ const insertTableRowAtSelection = useCallback(
287
+ (shouldInsertAfter: boolean) => {
288
+ editor.update(() => {
289
+ for (let i = 0; i < selectionCounts.rows; i++) {
290
+ $insertTableRow__EXPERIMENTAL(shouldInsertAfter);
291
+ }
292
+ });
293
+ },
294
+ [editor, selectionCounts.rows]
295
+ );
296
+
297
+ const insertTableColumnAtSelection = useCallback(
298
+ (shouldInsertAfter: boolean) => {
299
+ editor.update(() => {
300
+ for (let i = 0; i < selectionCounts.columns; i++) {
301
+ $insertTableColumn__EXPERIMENTAL(shouldInsertAfter);
302
+ }
303
+ });
304
+ },
305
+ [editor, selectionCounts.columns]
306
+ );
307
+
308
+ const deleteTableRowAtSelection = useCallback(() => {
309
+ editor.update(() => {
310
+ $deleteTableRow__EXPERIMENTAL();
311
+ });
312
+ }, [editor]);
313
+
314
+ const deleteTableAtSelection = useCallback(() => {
315
+ editor.update(() => {
316
+ const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
317
+ tableNode.remove();
318
+
319
+ clearTableSelection();
320
+ });
321
+ }, [editor, tableCellNode, clearTableSelection]);
322
+
323
+ const deleteTableColumnAtSelection = useCallback(() => {
324
+ editor.update(() => {
325
+ $deleteTableColumn__EXPERIMENTAL();
326
+ });
327
+ }, [editor]);
328
+
329
+ const toggleTableRowIsHeader = useCallback(() => {
330
+ editor.update(() => {
331
+ const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
332
+
333
+ const tableRowIndex = $getTableRowIndexFromTableCellNode(tableCellNode);
334
+
335
+ const tableRows = tableNode.getChildren();
336
+
337
+ if (tableRowIndex >= tableRows.length || tableRowIndex < 0) {
338
+ throw new Error("Expected table cell to be inside of table row.");
339
+ }
340
+
341
+ const tableRow = tableRows[tableRowIndex];
342
+
343
+ if (!$isTableRowNode(tableRow)) {
344
+ throw new Error("Expected table row");
345
+ }
346
+
347
+ const newStyle =
348
+ tableCellNode.getHeaderStyles() ^ TableCellHeaderStates.ROW;
349
+ tableRow.getChildren().forEach((tableCell) => {
350
+ if (!$isTableCellNode(tableCell)) {
351
+ throw new Error("Expected table cell");
352
+ }
353
+
354
+ tableCell.setHeaderStyles(newStyle, TableCellHeaderStates.ROW);
355
+ });
356
+
357
+ clearTableSelection();
358
+ });
359
+ }, [editor, tableCellNode, clearTableSelection]);
360
+
361
+ const toggleTableColumnIsHeader = useCallback(() => {
362
+ editor.update(() => {
363
+ const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
364
+
365
+ const tableColumnIndex =
366
+ $getTableColumnIndexFromTableCellNode(tableCellNode);
367
+
368
+ const tableRows = tableNode.getChildren<TableRowNode>();
369
+ const maxRowsLength = Math.max(
370
+ ...tableRows.map((row) => row.getChildren().length)
371
+ );
372
+
373
+ if (tableColumnIndex >= maxRowsLength || tableColumnIndex < 0) {
374
+ throw new Error("Expected table cell to be inside of table row.");
375
+ }
376
+
377
+ const newStyle =
378
+ tableCellNode.getHeaderStyles() ^ TableCellHeaderStates.COLUMN;
379
+ for (let r = 0; r < tableRows.length; r++) {
380
+ const tableRow = tableRows[r];
381
+
382
+ if (!$isTableRowNode(tableRow)) {
383
+ throw new Error("Expected table row");
384
+ }
385
+
386
+ const tableCells = tableRow.getChildren();
387
+ if (tableColumnIndex >= tableCells.length) {
388
+ // if cell is outside of bounds for the current row (for example various merge cell cases) we shouldn't highlight it
389
+ continue;
390
+ }
391
+
392
+ const tableCell = tableCells[tableColumnIndex];
393
+
394
+ if (!$isTableCellNode(tableCell)) {
395
+ throw new Error("Expected table cell");
396
+ }
397
+
398
+ tableCell.setHeaderStyles(newStyle, TableCellHeaderStates.COLUMN);
399
+ }
400
+ clearTableSelection();
401
+ });
402
+ }, [editor, tableCellNode, clearTableSelection]);
403
+
404
+ const toggleRowStriping = useCallback(() => {
405
+ editor.update(() => {
406
+ if (tableCellNode.isAttached()) {
407
+ const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
408
+ if (tableNode) {
409
+ tableNode.setRowStriping(!tableNode.getRowStriping());
410
+ }
411
+ }
412
+ clearTableSelection();
413
+ });
414
+ }, [editor, tableCellNode, clearTableSelection]);
415
+
416
+ const handleCellBackgroundColor = useCallback(
417
+ (value: string) => {
418
+ editor.update(() => {
419
+ const selection = $getSelection();
420
+ if ($isRangeSelection(selection) || $isTableSelection(selection)) {
421
+ const [cell] = $getNodeTriplet(selection.anchor);
422
+ if ($isTableCellNode(cell)) {
423
+ cell.setBackgroundColor(value);
424
+ }
425
+
426
+ if ($isTableSelection(selection)) {
427
+ const nodes = selection.getNodes();
428
+
429
+ for (let i = 0; i < nodes.length; i++) {
430
+ const node = nodes[i];
431
+ if ($isTableCellNode(node)) {
432
+ node.setBackgroundColor(value);
433
+ }
434
+ }
435
+ }
436
+ }
437
+ });
438
+ },
439
+ [editor]
440
+ );
441
+
442
+ const commands = React.useMemo(
443
+ () => [
444
+ {
445
+ label: "Background",
446
+ func: () => {
447
+ showModal(
448
+ "Background",
449
+ "Select color",
450
+ (onClose) => (
451
+ <Compact
452
+ style={{background:"transparent",width:"100%" }}
453
+ color={backgroundColor}
454
+ onChange={(color) => {
455
+ handleCellBackgroundColor(color.hex)
456
+ onClose()
457
+ }}
458
+ />
459
+ ),
460
+ true
461
+ );
462
+ },
463
+ icon: <PaintBucket />,
464
+ style: { backgroundColor: `${currentCellBackgroundColor(editor)}` },
465
+ },
466
+ {
467
+ label: "Insert Row Above",
468
+ func: () => insertTableRowAtSelection(false),
469
+ icon: <TableRowsSplit />,
470
+ },
471
+ {
472
+ label: "Insert Row Below",
473
+ func: () => insertTableRowAtSelection(true),
474
+ icon: <TableRowsSplit />,
475
+ },
476
+ {
477
+ label: "Insert Column Left",
478
+ func: () => insertTableColumnAtSelection(false),
479
+ icon: <TableColumnsSplit />,
480
+ },
481
+ {
482
+ label: "Insert Column Right",
483
+ func: () => insertTableColumnAtSelection(true),
484
+ icon: <TableColumnsSplit />,
485
+ },
486
+ { label: "Delete Row", func: deleteTableRowAtSelection, icon: <Trash /> },
487
+ {
488
+ label: "Delete Column",
489
+ func: deleteTableColumnAtSelection,
490
+ icon: <Trash />,
491
+ },
492
+ { label: "Delete Table", func: deleteTableAtSelection, icon: <Trash /> },
493
+ {
494
+ label: "Toggle Row Header",
495
+ func: toggleTableRowIsHeader,
496
+ icon: <Table />,
497
+ },
498
+ {
499
+ label: "Toggle Column Header",
500
+ func: toggleTableColumnIsHeader,
501
+ icon: <Table />,
502
+ },
503
+ { label: "Toggle Striping", func: toggleRowStriping, icon: <Table /> },
504
+ ],
505
+ [
506
+ currentCellBackgroundColor(editor),
507
+ canMergeCells,
508
+ canUnmergeCell,
509
+ insertTableRowAtSelection,
510
+ insertTableColumnAtSelection,
511
+ deleteTableRowAtSelection,
512
+ deleteTableColumnAtSelection,
513
+ deleteTableAtSelection,
514
+ toggleTableRowIsHeader,
515
+ toggleTableColumnIsHeader,
516
+ toggleRowStriping,
517
+ backgroundColor,
518
+ editor,
519
+ tableCellNode,
520
+ cellMerge,
521
+ selectionCounts,
522
+
523
+ ]
524
+ );
525
+ if (cellMerge) {
526
+ if (canMergeCells) {
527
+ commands.push({
528
+ label: "Merge Cells",
529
+ func: mergeTableCellsAtSelection,
530
+ icon: <Merge />,
531
+ });
532
+ } else if (canUnmergeCell) {
533
+ commands.push({
534
+ label: "Unmerge Cells",
535
+ func: unmergeTableCellsAtSelection,
536
+ icon: <Split />,
537
+ });
538
+ }
539
+ }
540
+
541
+ return (
542
+ <>
543
+ <DropDown
544
+ values={commands}
545
+ TriggerLabel={<Menu className="w-4 h-4 " />}
546
+ TriggerClassName={{
547
+ height: "24px",
548
+ padding: "0px 4px",
549
+ position: "absolute",
550
+ top: 0,
551
+ right: "10px",
552
+ }}
553
+ ShowChevronsUpDown={false}
554
+ triggerVariants={"ghost"}
555
+ disabled={false}
556
+ />
557
+ {model}
558
+ </>
559
+ );
560
+ }
561
+
562
+ function TableCellActionMenuContainer({
563
+ anchorElem,
564
+ cellMerge,
565
+ }: {
566
+ anchorElem: HTMLElement;
567
+ cellMerge: boolean;
568
+ }): React.JSX.Element {
569
+ const [editor] = useLexicalComposerContext();
570
+
571
+ const menuButtonRef = useRef<HTMLDivElement | null>(null);
572
+ const menuRootRef = useRef<HTMLButtonElement | null>(null);
573
+
574
+ const [tableCellNode, setTableMenuCellNode] = useState<TableCellNode | null>(
575
+ null
576
+ );
577
+
578
+ const $moveMenu = useCallback(() => {
579
+ const menu = menuButtonRef.current;
580
+ const selection = $getSelection();
581
+ const nativeSelection = getDOMSelection(editor._window);
582
+ let activeElement;
583
+ if (typeof window !== "undefined") {
584
+ activeElement = document?.activeElement;
585
+ }
586
+ function disable() {
587
+ if (menu) {
588
+ menu.classList.remove("table-cell-action-button-container--active");
589
+ menu.classList.add("table-cell-action-button-container--inactive");
590
+ }
591
+ setTableMenuCellNode(null);
592
+ }
593
+
594
+ if (selection == null || menu == null) {
595
+ return disable();
596
+ }
597
+
598
+ const rootElement = editor.getRootElement();
599
+ let tableObserver: TableObserver | null = null;
600
+ let tableCellParentNodeDOM: HTMLElement | null = null;
601
+
602
+ if (
603
+ $isRangeSelection(selection) &&
604
+ rootElement !== null &&
605
+ nativeSelection !== null &&
606
+ rootElement.contains(nativeSelection.anchorNode)
607
+ ) {
608
+ const tableCellNodeFromSelection = $getTableCellNodeFromLexicalNode(
609
+ selection.anchor.getNode()
610
+ );
611
+
612
+ if (tableCellNodeFromSelection == null) {
613
+ return disable();
614
+ }
615
+
616
+ tableCellParentNodeDOM = editor.getElementByKey(
617
+ tableCellNodeFromSelection.getKey()
618
+ );
619
+
620
+ if (
621
+ tableCellParentNodeDOM == null ||
622
+ !tableCellNodeFromSelection.isAttached()
623
+ ) {
624
+ return disable();
625
+ }
626
+
627
+ const tableNode = $getTableNodeFromLexicalNodeOrThrow(
628
+ tableCellNodeFromSelection
629
+ );
630
+ const tableElement = getTableElement(
631
+ tableNode,
632
+ editor.getElementByKey(tableNode.getKey())
633
+ );
634
+
635
+ invariant(
636
+ tableElement !== null,
637
+ "TableActionMenu: Expected to find tableElement in DOM"
638
+ );
639
+
640
+ tableObserver = getTableObserverFromTableElement(tableElement);
641
+ setTableMenuCellNode(tableCellNodeFromSelection);
642
+ } else if ($isTableSelection(selection)) {
643
+ const anchorNode = $getTableCellNodeFromLexicalNode(
644
+ selection.anchor.getNode()
645
+ );
646
+ invariant(
647
+ $isTableCellNode(anchorNode),
648
+ "TableSelection anchorNode must be a TableCellNode"
649
+ );
650
+ const tableNode = $getTableNodeFromLexicalNodeOrThrow(anchorNode);
651
+ const tableElement = getTableElement(
652
+ tableNode,
653
+ editor.getElementByKey(tableNode.getKey())
654
+ );
655
+ invariant(
656
+ tableElement !== null,
657
+ "TableActionMenu: Expected to find tableElement in DOM"
658
+ );
659
+ tableObserver = getTableObserverFromTableElement(tableElement);
660
+ tableCellParentNodeDOM = editor.getElementByKey(anchorNode.getKey());
661
+ } else if (!activeElement) {
662
+ return disable();
663
+ }
664
+ if (tableObserver === null || tableCellParentNodeDOM === null) {
665
+ return disable();
666
+ }
667
+ const enabled = !tableObserver || !tableObserver.isSelecting;
668
+ menu.classList.toggle(
669
+ "table-cell-action-button-container--active",
670
+ enabled
671
+ );
672
+ menu.classList.toggle(
673
+ "table-cell-action-button-container--inactive",
674
+ !enabled
675
+ );
676
+ if (enabled) {
677
+ const tableCellRect = tableCellParentNodeDOM.getBoundingClientRect();
678
+ const anchorRect = anchorElem.getBoundingClientRect();
679
+ const top = tableCellRect.top - anchorRect.top;
680
+ const left = tableCellRect.right - anchorRect.left;
681
+ menu.style.transform = `translate(${left}px, ${top}px)`;
682
+ }
683
+ }, [editor, anchorElem]);
684
+
685
+ useEffect(() => {
686
+ // We call the $moveMenu callback every time the selection changes,
687
+ // once up front, and once after each mouseUp
688
+ let timeoutId: ReturnType<typeof setTimeout> | undefined = undefined;
689
+ const callback = () => {
690
+ timeoutId = undefined;
691
+ editor.getEditorState().read($moveMenu);
692
+ };
693
+ const delayedCallback = () => {
694
+ if (timeoutId === undefined) {
695
+ timeoutId = setTimeout(callback, 0);
696
+ }
697
+ return false;
698
+ };
699
+ return mergeRegister(
700
+ editor.registerUpdateListener(delayedCallback),
701
+ editor.registerCommand(
702
+ SELECTION_CHANGE_COMMAND,
703
+ delayedCallback,
704
+ COMMAND_PRIORITY_CRITICAL
705
+ ),
706
+ editor.registerRootListener((rootElement, prevRootElement) => {
707
+ if (prevRootElement) {
708
+ prevRootElement.removeEventListener("mouseup", delayedCallback);
709
+ }
710
+ if (rootElement) {
711
+ rootElement.addEventListener("mouseup", delayedCallback);
712
+ delayedCallback();
713
+ }
714
+ }),
715
+ () => clearTimeout(timeoutId)
716
+ );
717
+ });
718
+
719
+ const prevTableCellDOM = useRef(tableCellNode);
720
+
721
+ useEffect(() => {
722
+ prevTableCellDOM.current = tableCellNode;
723
+ }, [prevTableCellDOM, tableCellNode]);
724
+
725
+ return (
726
+ <div
727
+ className=" absolute top-1 left-1 will-change-transform"
728
+ ref={menuButtonRef}
729
+ >
730
+ {tableCellNode != null && (
731
+ <TableActionMenu
732
+ contextRef={menuRootRef}
733
+ tableCellNode={tableCellNode}
734
+ cellMerge={cellMerge}
735
+ />
736
+ )}
737
+ </div>
738
+ );
739
+ }
740
+
741
+ export default function TableActionMenuPlugin({
742
+ anchorElem = document.body,
743
+ cellMerge = false,
744
+ }: {
745
+ anchorElem?: HTMLElement;
746
+ cellMerge?: boolean;
747
+ }): null | ReactPortal {
748
+ const isEditable = useLexicalEditable();
749
+
750
+ return createPortal(
751
+ isEditable ? (
752
+ <TableCellActionMenuContainer
753
+ anchorElem={anchorElem}
754
+ cellMerge={cellMerge}
755
+ />
756
+ ) : null,
757
+ anchorElem
758
+ );
759
+ }