@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,178 @@
1
+ "use client"
2
+
3
+ import type { JSX } from "react"
4
+ import * as React from "react"
5
+ import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext"
6
+ import { mergeRegister } from "@lexical/utils"
7
+ import { COMMAND_PRIORITY_EDITOR, createCommand, $getNodeByKey } from "lexical"
8
+ import type { LexicalCommand, NodeKey } from "lexical"
9
+ import {
10
+ ColorPicker,
11
+ ColorPickerArea,
12
+ ColorPickerContent,
13
+ ColorPickerHueSlider,
14
+ ColorPickerInput,
15
+ ColorPickerPresets,
16
+ } from "../editor-ui/color-picker"
17
+ import { useEditorModal } from "../editor-hooks/use-modal"
18
+ import {
19
+ $isListWithColorNode,
20
+ } from "../nodes/list-with-color-node"
21
+ import { createListWithColorNodeFromRegistry } from "../editor-x/nodes"
22
+ import { $isListNode, ListNode } from "@lexical/list"
23
+ import { Button } from "../ui/button"
24
+ import { DialogFooter } from "../ui/dialog"
25
+ import { Flex } from "../ui/flex"
26
+
27
+ export const OPEN_LIST_COLOR_PICKER_COMMAND: LexicalCommand<{
28
+ listKey: NodeKey
29
+ }> = createCommand<{ listKey: NodeKey }>("OPEN_LIST_COLOR_PICKER_COMMAND")
30
+
31
+ const listColorStore = new Map<NodeKey, string>()
32
+
33
+ /** Patch serialized state: thêm listColor vào list nodes theo listColorStore (fallback khi load content cũ). */
34
+ export function patchListColorsInSerializedState(
35
+ json: Record<string, unknown>
36
+ ): void {
37
+ const root = json.root as Record<string, unknown> | undefined
38
+ if (!root || typeof root !== "object") return
39
+ const children = root.children as Array<Record<string, unknown>> | undefined
40
+ if (!Array.isArray(children)) return
41
+
42
+ function walk(nodes: Array<Record<string, unknown>>): void {
43
+ for (const node of nodes) {
44
+ const key = node.key as string | undefined
45
+ const type = node.type as string | undefined
46
+ if (type === "list" && key && listColorStore.has(key)) {
47
+ ;(node as Record<string, unknown>).listColor = listColorStore.get(key)
48
+ }
49
+ const childList = node.children as Array<Record<string, unknown>> | undefined
50
+ if (Array.isArray(childList)) walk(childList)
51
+ }
52
+ }
53
+ walk(children)
54
+ }
55
+
56
+ function getListElement(element: HTMLElement): HTMLElement | null {
57
+ const tag = element.tagName.toUpperCase()
58
+ if (tag === "UL" || tag === "OL") return element
59
+ const list = element.querySelector("ul, ol")
60
+ return list instanceof HTMLElement ? list : null
61
+ }
62
+
63
+ function applyListColor(editor: ReturnType<typeof useLexicalComposerContext>[0]) {
64
+ listColorStore.forEach((color, listKey) => {
65
+ const element = editor.getElementByKey(listKey)
66
+ if (!(element instanceof HTMLElement)) return
67
+ const listEl = getListElement(element)
68
+ if (!listEl) return
69
+ listEl.style.setProperty("--list-marker-color", color, "important")
70
+ listEl.setAttribute("data-list-color", color)
71
+ })
72
+ }
73
+
74
+ export function ListColorPlugin(): JSX.Element | null {
75
+ const [editor] = useLexicalComposerContext()
76
+ const [modal, showModal] = useEditorModal()
77
+
78
+ React.useEffect(() => {
79
+ const applyColor = (color: string, listKey: NodeKey) => {
80
+ listColorStore.set(listKey, color)
81
+ editor.update(() => {
82
+ const node = $getNodeByKey(listKey)
83
+ if ($isListWithColorNode(node)) {
84
+ node.setListColor(color)
85
+ return
86
+ }
87
+ if ($isListNode(node)) {
88
+ const listType = node.getListType()
89
+ const start = node.getStart()
90
+ const newList = createListWithColorNodeFromRegistry(editor, listType, start)
91
+ newList.setListColor(color)
92
+ const children = node.getChildren()
93
+ for (const c of children) newList.append(c)
94
+ node.replace(newList)
95
+ }
96
+ })
97
+ applyListColor(editor)
98
+ }
99
+
100
+ return mergeRegister(
101
+ editor.registerNodeTransform(ListNode, (node) => {
102
+ if ($isListWithColorNode(node)) return
103
+ const listType = node.getListType()
104
+ const start = node.getStart()
105
+ const newList = createListWithColorNodeFromRegistry(editor, listType, start)
106
+ const children = node.getChildren()
107
+ for (const c of children) newList.append(c)
108
+ node.replace(newList)
109
+ }),
110
+ editor.registerUpdateListener(() => {
111
+ applyListColor(editor)
112
+ }),
113
+ editor.registerCommand(
114
+ OPEN_LIST_COLOR_PICKER_COMMAND,
115
+ ({ listKey }) => {
116
+ const element = editor.getElementByKey(listKey)
117
+ if (!(element instanceof HTMLElement)) return true
118
+ const listEl = getListElement(element)
119
+ if (!listEl) return true
120
+
121
+ const fromStore = listColorStore.get(listKey)
122
+ const fromVar = listEl.style.getPropertyValue("--list-marker-color")
123
+ const fromAttr = listEl.getAttribute("data-list-color")
124
+ const initialColor =
125
+ fromStore || fromVar || fromAttr || "#000000"
126
+
127
+ function ListColorModalContent({ onClose }: { onClose: () => void }) {
128
+ const [color, setColor] = React.useState(initialColor)
129
+ return (
130
+ <div className="editor-list-color-dialog">
131
+ <Flex direction="column" gap={4}>
132
+ <div className="editor-text-xs-muted">
133
+ Chọn màu cho bullet hoặc số thứ tự của list.
134
+ </div>
135
+
136
+ <ColorPicker
137
+ inline
138
+ value={color}
139
+ onValueChange={(next) => {
140
+ setColor(next)
141
+ applyColor(next, listKey)
142
+ applyListColor(editor)
143
+ }}
144
+ >
145
+ <ColorPickerContent className="editor-w-full editor-border-0 editor-shadow-none editor-p-0">
146
+ <ColorPickerArea className="editor-h-40 editor-w-full editor-rounded-md" />
147
+ <Flex direction="column" gap={3} className="editor-mt-3">
148
+ <Flex direction="column" gap={2}>
149
+ <ColorPickerHueSlider className="editor-w-full" />
150
+ <ColorPickerInput className="editor-w-full" />
151
+ </Flex>
152
+ <ColorPickerPresets />
153
+ </Flex>
154
+ </ColorPickerContent>
155
+ </ColorPicker>
156
+
157
+ <DialogFooter className="editor-px-0">
158
+ <Button variant="outline" size="sm" onClick={onClose} className="editor-w-full">
159
+ Hoàn tất
160
+ </Button>
161
+ </DialogFooter>
162
+ </Flex>
163
+ </div>
164
+ )
165
+ }
166
+
167
+ showModal("Đổi màu list", (onClose) => (
168
+ <ListColorModalContent onClose={onClose} />
169
+ ))
170
+ return true
171
+ },
172
+ COMMAND_PRIORITY_EDITOR
173
+ )
174
+ )
175
+ }, [editor, showModal])
176
+
177
+ return <>{modal}</>
178
+ }
@@ -0,0 +1,85 @@
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 { $getListDepth, $isListItemNode, $isListNode } from "@lexical/list"
12
+ import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext"
13
+ import type { ElementNode, RangeSelection } from "lexical"
14
+ import {
15
+ $getSelection,
16
+ $isElementNode,
17
+ $isRangeSelection,
18
+ COMMAND_PRIORITY_CRITICAL,
19
+ INDENT_CONTENT_COMMAND,
20
+ } from "lexical"
21
+
22
+ function getElementNodesInSelection(
23
+ selection: RangeSelection
24
+ ): Set<ElementNode> {
25
+ const nodesInSelection = selection.getNodes()
26
+
27
+ if (nodesInSelection.length === 0) {
28
+ return new Set([
29
+ selection.anchor.getNode().getParentOrThrow(),
30
+ selection.focus.getNode().getParentOrThrow(),
31
+ ])
32
+ }
33
+
34
+ return new Set(
35
+ nodesInSelection.map((n) => ($isElementNode(n) ? n : n.getParentOrThrow()))
36
+ )
37
+ }
38
+
39
+ function $shouldPreventIndent(maxDepth: number): boolean {
40
+ const selection = $getSelection()
41
+
42
+ if (!$isRangeSelection(selection)) {
43
+ return false
44
+ }
45
+
46
+ const elementNodesInSelection: Set<ElementNode> =
47
+ getElementNodesInSelection(selection)
48
+
49
+ let totalDepth = 0
50
+
51
+ for (const elementNode of Array.from(elementNodesInSelection)) {
52
+ if ($isListNode(elementNode)) {
53
+ totalDepth = Math.max($getListDepth(elementNode) + 1, totalDepth)
54
+ } else if ($isListItemNode(elementNode)) {
55
+ const parent = elementNode.getParent()
56
+
57
+ if (!$isListNode(parent)) {
58
+ throw new Error(
59
+ "ListMaxIndentLevelPlugin: A ListItemNode must have a ListNode for a parent."
60
+ )
61
+ }
62
+
63
+ totalDepth = Math.max($getListDepth(parent) + 1, totalDepth)
64
+ }
65
+ }
66
+
67
+ return totalDepth > maxDepth
68
+ }
69
+
70
+ export function ListMaxIndentLevelPlugin({
71
+ maxDepth = 7,
72
+ }: {
73
+ maxDepth?: number
74
+ }): null {
75
+ const [editor] = useLexicalComposerContext()
76
+
77
+ useEffect(() => {
78
+ return editor.registerCommand(
79
+ INDENT_CONTENT_COMMAND,
80
+ () => $shouldPreventIndent(maxDepth),
81
+ COMMAND_PRIORITY_CRITICAL
82
+ )
83
+ }, [editor, maxDepth])
84
+ return null
85
+ }