@thangph2146/lexical-editor 0.0.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 (183) hide show
  1. package/dist/editor-x/editor.cjs +33121 -0
  2. package/dist/editor-x/editor.cjs.map +1 -0
  3. package/dist/editor-x/editor.css +2854 -0
  4. package/dist/editor-x/editor.css.map +1 -0
  5. package/dist/editor-x/editor.d.cts +12 -0
  6. package/dist/editor-x/editor.d.ts +12 -0
  7. package/dist/editor-x/editor.js +33095 -0
  8. package/dist/editor-x/editor.js.map +1 -0
  9. package/dist/index.cjs +33210 -0
  10. package/dist/index.cjs.map +1 -0
  11. package/dist/index.css +2854 -0
  12. package/dist/index.css.map +1 -0
  13. package/dist/index.d.cts +15 -0
  14. package/dist/index.d.ts +15 -0
  15. package/dist/index.js +33183 -0
  16. package/dist/index.js.map +1 -0
  17. package/package.json +84 -0
  18. package/src/components/lexical-editor.tsx +123 -0
  19. package/src/context/editor-container-context.tsx +29 -0
  20. package/src/context/priority-image-context.tsx +7 -0
  21. package/src/context/toolbar-context.tsx +60 -0
  22. package/src/context/uploads-context.tsx +53 -0
  23. package/src/editor-hooks/use-debounce.ts +80 -0
  24. package/src/editor-hooks/use-modal.tsx +64 -0
  25. package/src/editor-hooks/use-report.ts +57 -0
  26. package/src/editor-hooks/use-update-toolbar.ts +41 -0
  27. package/src/editor-ui/broken-image.tsx +18 -0
  28. package/src/editor-ui/caption-composer.tsx +45 -0
  29. package/src/editor-ui/code-button.tsx +75 -0
  30. package/src/editor-ui/color-picker.tsx +2010 -0
  31. package/src/editor-ui/content-editable.tsx +37 -0
  32. package/src/editor-ui/hooks/use-image-caption-controls.ts +118 -0
  33. package/src/editor-ui/hooks/use-image-node-interactions.ts +245 -0
  34. package/src/editor-ui/hooks/use-responsive-image-dimensions.ts +202 -0
  35. package/src/editor-ui/image-component.tsx +321 -0
  36. package/src/editor-ui/image-placeholder.tsx +57 -0
  37. package/src/editor-ui/image-resizer.tsx +499 -0
  38. package/src/editor-ui/image-sizing.ts +120 -0
  39. package/src/editor-ui/lazy-image.tsx +136 -0
  40. package/src/editor-x/editor.tsx +117 -0
  41. package/src/editor-x/nodes.ts +79 -0
  42. package/src/editor-x/plugins.tsx +380 -0
  43. package/src/hooks/use-click-outside.ts +27 -0
  44. package/src/hooks/use-element-size.ts +54 -0
  45. package/src/hooks/use-header-height.ts +95 -0
  46. package/src/hooks/use-isomorphic-layout-effect.ts +4 -0
  47. package/src/index.ts +4 -0
  48. package/src/lib/logger.ts +6 -0
  49. package/src/lib/utils.ts +19 -0
  50. package/src/nodes/autocomplete-node.tsx +94 -0
  51. package/src/nodes/embeds/tweet-node.tsx +224 -0
  52. package/src/nodes/embeds/youtube-node.tsx +519 -0
  53. package/src/nodes/emoji-node.tsx +83 -0
  54. package/src/nodes/image-node.tsx +328 -0
  55. package/src/nodes/keyword-node.tsx +58 -0
  56. package/src/nodes/layout-container-node.tsx +128 -0
  57. package/src/nodes/layout-item-node.tsx +118 -0
  58. package/src/nodes/list-with-color-node.tsx +160 -0
  59. package/src/nodes/mention-node.ts +122 -0
  60. package/src/plugins/actions/actions-plugin.tsx +3 -0
  61. package/src/plugins/actions/character-limit-plugin.tsx +27 -0
  62. package/src/plugins/actions/clear-editor-plugin.tsx +70 -0
  63. package/src/plugins/actions/counter-character-plugin.tsx +80 -0
  64. package/src/plugins/actions/edit-mode-toggle-plugin.tsx +49 -0
  65. package/src/plugins/actions/import-export-plugin.tsx +61 -0
  66. package/src/plugins/actions/markdown-toggle-plugin.tsx +78 -0
  67. package/src/plugins/actions/max-length-plugin.tsx +59 -0
  68. package/src/plugins/actions/share-content-plugin.tsx +72 -0
  69. package/src/plugins/actions/speech-to-text-plugin.tsx +159 -0
  70. package/src/plugins/actions/tree-view-plugin.tsx +63 -0
  71. package/src/plugins/align-plugin.tsx +86 -0
  72. package/src/plugins/auto-link-plugin.tsx +34 -0
  73. package/src/plugins/autocomplete-plugin.tsx +2574 -0
  74. package/src/plugins/code-action-menu-plugin.tsx +240 -0
  75. package/src/plugins/code-highlight-plugin.tsx +22 -0
  76. package/src/plugins/component-picker-menu-plugin.tsx +427 -0
  77. package/src/plugins/context-menu-plugin.tsx +311 -0
  78. package/src/plugins/drag-drop-paste-plugin.tsx +52 -0
  79. package/src/plugins/draggable-block-plugin.tsx +50 -0
  80. package/src/plugins/embeds/auto-embed-plugin.tsx +324 -0
  81. package/src/plugins/embeds/twitter-plugin.tsx +45 -0
  82. package/src/plugins/embeds/youtube-plugin.tsx +84 -0
  83. package/src/plugins/emoji-picker-plugin.tsx +206 -0
  84. package/src/plugins/emojis-plugin.tsx +84 -0
  85. package/src/plugins/floating-link-editor-plugin.tsx +791 -0
  86. package/src/plugins/floating-text-format-plugin.tsx +710 -0
  87. package/src/plugins/images-plugin.tsx +671 -0
  88. package/src/plugins/keywords-plugin.tsx +59 -0
  89. package/src/plugins/layout-plugin.tsx +658 -0
  90. package/src/plugins/link-plugin.tsx +18 -0
  91. package/src/plugins/list-color-plugin.tsx +178 -0
  92. package/src/plugins/list-max-indent-level-plugin.tsx +85 -0
  93. package/src/plugins/mentions-plugin.tsx +714 -0
  94. package/src/plugins/picker/alignment-picker-plugin.tsx +40 -0
  95. package/src/plugins/picker/bulleted-list-picker-plugin.tsx +14 -0
  96. package/src/plugins/picker/check-list-picker-plugin.tsx +14 -0
  97. package/src/plugins/picker/code-picker-plugin.tsx +30 -0
  98. package/src/plugins/picker/columns-layout-picker-plugin.tsx +16 -0
  99. package/src/plugins/picker/component-picker-option.tsx +47 -0
  100. package/src/plugins/picker/divider-picker-plugin.tsx +14 -0
  101. package/src/plugins/picker/embeds-picker-plugin.tsx +24 -0
  102. package/src/plugins/picker/heading-picker-plugin.tsx +32 -0
  103. package/src/plugins/picker/image-picker-plugin.tsx +16 -0
  104. package/src/plugins/picker/numbered-list-picker-plugin.tsx +14 -0
  105. package/src/plugins/picker/paragraph-picker-plugin.tsx +20 -0
  106. package/src/plugins/picker/quote-picker-plugin.tsx +21 -0
  107. package/src/plugins/picker/table-picker-plugin.tsx +56 -0
  108. package/src/plugins/tab-focus-plugin.tsx +66 -0
  109. package/src/plugins/table-column-resizer-plugin.tsx +309 -0
  110. package/src/plugins/table-plugin.tsx +299 -0
  111. package/src/plugins/toolbar/block-format/block-format-data.tsx +69 -0
  112. package/src/plugins/toolbar/block-format/format-bulleted-list.tsx +40 -0
  113. package/src/plugins/toolbar/block-format/format-check-list.tsx +40 -0
  114. package/src/plugins/toolbar/block-format/format-code-block.tsx +45 -0
  115. package/src/plugins/toolbar/block-format/format-heading.tsx +34 -0
  116. package/src/plugins/toolbar/block-format/format-list-with-marker.tsx +74 -0
  117. package/src/plugins/toolbar/block-format/format-numbered-list.tsx +40 -0
  118. package/src/plugins/toolbar/block-format/format-paragraph.tsx +31 -0
  119. package/src/plugins/toolbar/block-format/format-quote.tsx +32 -0
  120. package/src/plugins/toolbar/block-format-toolbar-plugin.tsx +117 -0
  121. package/src/plugins/toolbar/block-insert/insert-columns-layout.tsx +32 -0
  122. package/src/plugins/toolbar/block-insert/insert-embeds.tsx +31 -0
  123. package/src/plugins/toolbar/block-insert/insert-horizontal-rule.tsx +30 -0
  124. package/src/plugins/toolbar/block-insert/insert-image.tsx +32 -0
  125. package/src/plugins/toolbar/block-insert/insert-table.tsx +32 -0
  126. package/src/plugins/toolbar/block-insert-plugin.tsx +30 -0
  127. package/src/plugins/toolbar/clear-formatting-toolbar-plugin.tsx +92 -0
  128. package/src/plugins/toolbar/code-language-toolbar-plugin.tsx +121 -0
  129. package/src/plugins/toolbar/element-format-toolbar-plugin.tsx +251 -0
  130. package/src/plugins/toolbar/font-background-toolbar-plugin.tsx +179 -0
  131. package/src/plugins/toolbar/font-color-toolbar-plugin.tsx +101 -0
  132. package/src/plugins/toolbar/font-family-toolbar-plugin.tsx +91 -0
  133. package/src/plugins/toolbar/font-format-toolbar-plugin.tsx +85 -0
  134. package/src/plugins/toolbar/font-size-toolbar-plugin.tsx +177 -0
  135. package/src/plugins/toolbar/history-toolbar-plugin.tsx +87 -0
  136. package/src/plugins/toolbar/link-toolbar-plugin.tsx +90 -0
  137. package/src/plugins/toolbar/subsuper-toolbar-plugin.tsx +69 -0
  138. package/src/plugins/toolbar/toolbar-plugin.tsx +66 -0
  139. package/src/plugins/typing-pref-plugin.tsx +118 -0
  140. package/src/shared/can-use-dom.ts +4 -0
  141. package/src/shared/environment.ts +47 -0
  142. package/src/shared/invariant.ts +16 -0
  143. package/src/shared/use-layout-effect.ts +12 -0
  144. package/src/themes/_mixins.scss +107 -0
  145. package/src/themes/_variables.scss +33 -0
  146. package/src/themes/editor-theme.scss +622 -0
  147. package/src/themes/editor-theme.ts +118 -0
  148. package/src/themes/plugins.scss +1180 -0
  149. package/src/themes/ui-components.scss +936 -0
  150. package/src/transformers/markdown-emoji-transformer.ts +20 -0
  151. package/src/transformers/markdown-hr-transformer.ts +28 -0
  152. package/src/transformers/markdown-image-transformer.ts +31 -0
  153. package/src/transformers/markdown-list-transformer.ts +51 -0
  154. package/src/transformers/markdown-table-transformer.ts +200 -0
  155. package/src/transformers/markdown-tweet-transformer.ts +26 -0
  156. package/src/ui/button-group.tsx +10 -0
  157. package/src/ui/button.tsx +29 -0
  158. package/src/ui/collapsible.tsx +67 -0
  159. package/src/ui/command.tsx +48 -0
  160. package/src/ui/dialog.tsx +146 -0
  161. package/src/ui/flex.tsx +38 -0
  162. package/src/ui/input.tsx +20 -0
  163. package/src/ui/label.tsx +20 -0
  164. package/src/ui/popover.tsx +128 -0
  165. package/src/ui/scroll-area.tsx +17 -0
  166. package/src/ui/select.tsx +171 -0
  167. package/src/ui/separator.tsx +20 -0
  168. package/src/ui/slider.tsx +14 -0
  169. package/src/ui/slot.tsx +3 -0
  170. package/src/ui/tabs.tsx +87 -0
  171. package/src/ui/toggle-group.tsx +109 -0
  172. package/src/ui/toggle.tsx +28 -0
  173. package/src/ui/tooltip.tsx +28 -0
  174. package/src/ui/typography.tsx +44 -0
  175. package/src/utils/doc-serialization.ts +68 -0
  176. package/src/utils/emoji-list.ts +16604 -0
  177. package/src/utils/get-dom-range-rect.ts +20 -0
  178. package/src/utils/get-selected-node.ts +20 -0
  179. package/src/utils/is-mobile-width.ts +0 -0
  180. package/src/utils/set-floating-elem-position-for-link-editor.ts +39 -0
  181. package/src/utils/set-floating-elem-position.ts +44 -0
  182. package/src/utils/swipe.ts +119 -0
  183. package/src/utils/url.ts +32 -0
@@ -0,0 +1,311 @@
1
+ import type { JSX } from "react"
2
+ import { useMemo } from "react"
3
+ import { $isLinkNode, TOGGLE_LINK_COMMAND } from "@lexical/link"
4
+ import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext"
5
+ import {
6
+ NodeContextMenuOption,
7
+ NodeContextMenuPlugin,
8
+ NodeContextMenuSeparator,
9
+ } from "@lexical/react/LexicalNodeContextMenuPlugin"
10
+ import { $mergeCells, $isTableCellNode, $isTableSelection, $unmergeCell, $insertTableColumnAtSelection, $insertTableRowAtSelection } from "@lexical/table"
11
+ import type { TableCellNode } from "@lexical/table"
12
+ import { $isListNode } from "@lexical/list"
13
+ import { $findMatchingParent } from "@lexical/utils"
14
+ import {
15
+ $getSelection,
16
+ $isDecoratorNode,
17
+ $isNodeSelection,
18
+ $isRangeSelection,
19
+ COPY_COMMAND,
20
+ CUT_COMMAND,
21
+ PASTE_COMMAND,
22
+ type LexicalNode,
23
+ } from "lexical"
24
+ import { $isLayoutItemNode } from "../nodes/layout-item-node"
25
+ import { OPEN_UPDATE_LAYOUT_MODAL_COMMAND } from "../plugins/layout-plugin"
26
+ import { OPEN_LIST_COLOR_PICKER_COMMAND } from "../plugins/list-color-plugin"
27
+ import {
28
+ Clipboard,
29
+ ClipboardType,
30
+ Copy,
31
+ Link2Off,
32
+ Scissors,
33
+ Trash2,
34
+ Combine,
35
+ SplitSquareVertical,
36
+ Columns3,
37
+ Rows3,
38
+ LayoutGrid,
39
+ Palette,
40
+ } from "lucide-react"
41
+ import { IconSize } from "../ui/typography"
42
+
43
+ export function ContextMenuPlugin(): JSX.Element {
44
+ const [editor] = useLexicalComposerContext()
45
+
46
+ const isInTable = (node: LexicalNode) =>
47
+ $findMatchingParent(node, $isTableCellNode) !== null
48
+
49
+ const isMergedCell = (node: LexicalNode) => {
50
+ const cell = $findMatchingParent(node, $isTableCellNode)
51
+ if (!cell || !$isTableCellNode(cell)) return false
52
+ const c = cell as TableCellNode
53
+ const colSpan =
54
+ typeof (c as { getColSpan?: () => number }).getColSpan === "function"
55
+ ? (c as { getColSpan: () => number }).getColSpan()
56
+ : (c as { __colSpan?: number }).__colSpan ?? 1
57
+ const rowSpan =
58
+ typeof (c as { getRowSpan?: () => number }).getRowSpan === "function"
59
+ ? (c as { getRowSpan: () => number }).getRowSpan()
60
+ : (c as { __rowSpan?: number }).__rowSpan ?? 1
61
+ return colSpan > 1 || rowSpan > 1
62
+ }
63
+
64
+ const isInLayoutItem = (node: LexicalNode) =>
65
+ $findMatchingParent(node, $isLayoutItemNode) !== null
66
+
67
+ const isInList = (node: LexicalNode) =>
68
+ $findMatchingParent(node, $isListNode) !== null
69
+
70
+ const items = useMemo(() => {
71
+ return [
72
+ new NodeContextMenuOption(`Gộp ô`, {
73
+ $onSelect: () => {
74
+ editor.update(() => {
75
+ const selection = $getSelection()
76
+ if (!$isTableSelection(selection)) return
77
+ const nodes = selection.getNodes()
78
+ const seen = new Set<string>()
79
+ const cellNodes: import("@lexical/table").TableCellNode[] = []
80
+ for (const n of nodes) {
81
+ const cell = $findMatchingParent(n, $isTableCellNode)
82
+ if (cell && !seen.has(cell.getKey())) {
83
+ seen.add(cell.getKey())
84
+ cellNodes.push(cell)
85
+ }
86
+ }
87
+ if (cellNodes.length >= 2) $mergeCells(cellNodes)
88
+ })
89
+ },
90
+ $showOn: isInTable,
91
+ disabled: false,
92
+ icon: <IconSize size="sm"><Combine /></IconSize>,
93
+ }),
94
+ new NodeContextMenuOption(`Tách ô`, {
95
+ $onSelect: () => {
96
+ editor.update(() => {
97
+ $unmergeCell()
98
+ })
99
+ },
100
+ $showOn: isMergedCell,
101
+ disabled: false,
102
+ icon: <IconSize size="sm"><SplitSquareVertical /></IconSize>,
103
+ }),
104
+ new NodeContextMenuOption(`Chèn cột bên phải`, {
105
+ $onSelect: () => {
106
+ editor.update(() => {
107
+ $insertTableColumnAtSelection(true)
108
+ })
109
+ },
110
+ $showOn: isInTable,
111
+ disabled: false,
112
+ icon: <IconSize size="sm"><Columns3 /></IconSize>,
113
+ }),
114
+ new NodeContextMenuOption(`Chèn cột bên trái`, {
115
+ $onSelect: () => {
116
+ editor.update(() => {
117
+ $insertTableColumnAtSelection(false)
118
+ })
119
+ },
120
+ $showOn: isInTable,
121
+ disabled: false,
122
+ icon: <IconSize size="sm"><Columns3 /></IconSize>,
123
+ }),
124
+ new NodeContextMenuOption(`Chèn dòng bên dưới`, {
125
+ $onSelect: () => {
126
+ editor.update(() => {
127
+ $insertTableRowAtSelection(true)
128
+ })
129
+ },
130
+ $showOn: isInTable,
131
+ disabled: false,
132
+ icon: <IconSize size="sm"><Rows3 /></IconSize>,
133
+ }),
134
+ new NodeContextMenuOption(`Chèn dòng bên trên`, {
135
+ $onSelect: () => {
136
+ editor.update(() => {
137
+ $insertTableRowAtSelection(false)
138
+ })
139
+ },
140
+ $showOn: isInTable,
141
+ disabled: false,
142
+ icon: <IconSize size="sm"><Rows3 /></IconSize>,
143
+ }),
144
+ new NodeContextMenuSeparator({
145
+ $showOn: isInTable,
146
+ }),
147
+ new NodeContextMenuOption(`Cập nhật column layout`, {
148
+ $onSelect: () => {
149
+ let layoutItemKey: string | null = null
150
+ editor.getEditorState().read(() => {
151
+ const selection = $getSelection()
152
+ if (!$isRangeSelection(selection)) return
153
+ const layoutItem = $findMatchingParent(
154
+ selection.anchor.getNode(),
155
+ $isLayoutItemNode
156
+ )
157
+ if ($isLayoutItemNode(layoutItem)) layoutItemKey = layoutItem.getKey()
158
+ })
159
+ if (layoutItemKey)
160
+ editor.dispatchCommand(OPEN_UPDATE_LAYOUT_MODAL_COMMAND, {
161
+ layoutItemKey,
162
+ })
163
+ },
164
+ $showOn: isInLayoutItem,
165
+ disabled: false,
166
+ icon: <IconSize size="sm"><LayoutGrid /></IconSize>,
167
+ }),
168
+ new NodeContextMenuSeparator({
169
+ $showOn: isInLayoutItem,
170
+ }),
171
+ new NodeContextMenuOption(`Đổi màu list`, {
172
+ $onSelect: () => {
173
+ let listKey: string | null = null
174
+ editor.getEditorState().read(() => {
175
+ const selection = $getSelection()
176
+ if (!$isRangeSelection(selection)) return
177
+ const listNode = $findMatchingParent(
178
+ selection.anchor.getNode(),
179
+ $isListNode
180
+ )
181
+ if ($isListNode(listNode)) listKey = listNode.getKey()
182
+ })
183
+ if (listKey)
184
+ editor.dispatchCommand(OPEN_LIST_COLOR_PICKER_COMMAND, {
185
+ listKey,
186
+ })
187
+ },
188
+ $showOn: isInList,
189
+ disabled: false,
190
+ icon: <IconSize size="sm"><Palette /></IconSize>,
191
+ }),
192
+ new NodeContextMenuSeparator({
193
+ $showOn: isInList,
194
+ }),
195
+ new NodeContextMenuOption(`Remove Link`, {
196
+ $onSelect: () => {
197
+ editor.dispatchCommand(TOGGLE_LINK_COMMAND, null)
198
+ },
199
+ $showOn: (node: LexicalNode) => $isLinkNode(node.getParent()),
200
+ disabled: false,
201
+ icon: <IconSize size="sm"><Link2Off /></IconSize>,
202
+ }),
203
+ new NodeContextMenuSeparator({
204
+ $showOn: (node: LexicalNode) => $isLinkNode(node.getParent()),
205
+ }),
206
+ new NodeContextMenuOption(`Cut`, {
207
+ $onSelect: () => {
208
+ editor.dispatchCommand(CUT_COMMAND, null)
209
+ },
210
+ disabled: false,
211
+ icon: <IconSize size="sm"><Scissors /></IconSize>,
212
+ }),
213
+ new NodeContextMenuOption(`Copy`, {
214
+ $onSelect: () => {
215
+ editor.dispatchCommand(COPY_COMMAND, null)
216
+ },
217
+ disabled: false,
218
+ icon: <IconSize size="sm"><Copy /></IconSize>,
219
+ }),
220
+ new NodeContextMenuOption(`Paste`, {
221
+ $onSelect: () => {
222
+ navigator.clipboard.read().then(async function (...args) {
223
+ void args
224
+ const data = new DataTransfer()
225
+
226
+ const readClipboardItems = await navigator.clipboard.read()
227
+ const item = readClipboardItems[0]
228
+ if (!item) return
229
+
230
+ const permission = await navigator.permissions.query({
231
+ name: "clipboard-read" as PermissionName,
232
+ })
233
+ if (permission.state === "denied") {
234
+ alert("Not allowed to paste from clipboard.")
235
+ return
236
+ }
237
+
238
+ for (const type of item.types) {
239
+ const dataString = await (await item.getType(type)).text()
240
+ data.setData(type, dataString)
241
+ }
242
+
243
+ const event = new ClipboardEvent("paste", {
244
+ clipboardData: data,
245
+ })
246
+
247
+ editor.dispatchCommand(PASTE_COMMAND, event)
248
+ })
249
+ },
250
+ disabled: false,
251
+ icon: <IconSize size="sm"><Clipboard /></IconSize>,
252
+ }),
253
+ new NodeContextMenuOption(`Paste as Plain Text`, {
254
+ $onSelect: () => {
255
+ navigator.clipboard.read().then(async function (...args) {
256
+ void args
257
+ const permission = await navigator.permissions.query({
258
+ name: "clipboard-read" as PermissionName,
259
+ })
260
+
261
+ if (permission.state === "denied") {
262
+ alert("Not allowed to paste from clipboard.")
263
+ return
264
+ }
265
+
266
+ const data = new DataTransfer()
267
+ const clipboardText = await navigator.clipboard.readText()
268
+ data.setData("text/plain", clipboardText)
269
+
270
+ const event = new ClipboardEvent("paste", {
271
+ clipboardData: data,
272
+ })
273
+ editor.dispatchCommand(PASTE_COMMAND, event)
274
+ })
275
+ },
276
+ disabled: false,
277
+ icon: <IconSize size="sm"><ClipboardType /></IconSize>,
278
+ }),
279
+ new NodeContextMenuSeparator(),
280
+ new NodeContextMenuOption(`Delete Node`, {
281
+ $onSelect: () => {
282
+ const selection = $getSelection()
283
+ if ($isRangeSelection(selection)) {
284
+ const currentNode = selection.anchor.getNode()
285
+ const ancestorNodeWithRootAsParent = currentNode.getParents().at(-2)
286
+
287
+ ancestorNodeWithRootAsParent?.remove()
288
+ } else if ($isNodeSelection(selection)) {
289
+ const selectedNodes = selection.getNodes()
290
+ selectedNodes.forEach((node) => {
291
+ if ($isDecoratorNode(node)) {
292
+ node.remove()
293
+ }
294
+ })
295
+ }
296
+ },
297
+ disabled: false,
298
+ icon: <IconSize size="sm"><Trash2 /></IconSize>,
299
+ }),
300
+ ]
301
+ }, [editor])
302
+
303
+ return (
304
+ <NodeContextMenuPlugin
305
+ className="editor-context-menu"
306
+ itemClassName="editor-context-menu-item"
307
+ separatorClassName="editor-context-menu-separator"
308
+ items={items}
309
+ />
310
+ )
311
+ }
@@ -0,0 +1,52 @@
1
+ "use client"
2
+
3
+ /**
4
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE file in the root directory of this source tree.
8
+ *
9
+ */
10
+ import { useEffect } from "react"
11
+ import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext"
12
+ import { DRAG_DROP_PASTE } from "@lexical/rich-text"
13
+ import { isMimeType, mediaFileReader } from "@lexical/utils"
14
+ import { COMMAND_PRIORITY_LOW } from "lexical"
15
+
16
+ import { INSERT_IMAGE_COMMAND } from "./images-plugin"
17
+
18
+ const ACCEPTABLE_IMAGE_TYPES = [
19
+ "image/",
20
+ "image/heic",
21
+ "image/heif",
22
+ "image/gif",
23
+ "image/webp",
24
+ ]
25
+
26
+ export function DragDropPastePlugin(): null {
27
+ const [editor] = useLexicalComposerContext()
28
+ useEffect(() => {
29
+ return editor.registerCommand(
30
+ DRAG_DROP_PASTE,
31
+ (files) => {
32
+ ;(async () => {
33
+ const filesResult = await mediaFileReader(
34
+ files,
35
+ [ACCEPTABLE_IMAGE_TYPES].flatMap((x) => x)
36
+ )
37
+ for (const { file, result } of filesResult) {
38
+ if (isMimeType(file, ACCEPTABLE_IMAGE_TYPES)) {
39
+ editor.dispatchCommand(INSERT_IMAGE_COMMAND, {
40
+ altText: file.name,
41
+ src: result,
42
+ })
43
+ }
44
+ }
45
+ })()
46
+ return true
47
+ },
48
+ COMMAND_PRIORITY_LOW
49
+ )
50
+ }, [editor])
51
+ return null
52
+ }
@@ -0,0 +1,50 @@
1
+ "use client"
2
+
3
+ import { JSX, useRef } from "react"
4
+ import { DraggableBlockPlugin_EXPERIMENTAL } from "@lexical/react/LexicalDraggableBlockPlugin"
5
+ import { GripVerticalIcon } from "lucide-react"
6
+ import { IconSize } from "../ui/typography"
7
+
8
+ const DRAGGABLE_BLOCK_MENU_CLASSNAME = "editor-draggable-menu"
9
+
10
+ function isOnMenu(element: HTMLElement): boolean {
11
+ return !!element.closest(`.${DRAGGABLE_BLOCK_MENU_CLASSNAME}`)
12
+ }
13
+
14
+ export function DraggableBlockPlugin({
15
+ anchorElem,
16
+ }: {
17
+ anchorElem: HTMLElement | null
18
+ }): JSX.Element | null {
19
+ const menuRef = useRef<HTMLDivElement>(null)
20
+ const targetLineRef = useRef<HTMLDivElement>(null)
21
+
22
+ if (!anchorElem) {
23
+ return null
24
+ }
25
+
26
+ return (
27
+ <DraggableBlockPlugin_EXPERIMENTAL
28
+ anchorElem={anchorElem}
29
+ menuRef={menuRef as React.RefObject<HTMLDivElement>}
30
+ targetLineRef={targetLineRef as React.RefObject<HTMLDivElement>}
31
+ menuComponent={
32
+ <div
33
+ ref={menuRef}
34
+ className="editor-draggable-menu"
35
+ >
36
+ <IconSize size="sm" className="editor-opacity-30">
37
+ <GripVerticalIcon />
38
+ </IconSize>
39
+ </div>
40
+ }
41
+ targetLineComponent={
42
+ <div
43
+ ref={targetLineRef}
44
+ className="editor-draggable-line"
45
+ />
46
+ }
47
+ isOnMenu={isOnMenu}
48
+ />
49
+ )
50
+ }