@thangph2146/lexical-editor 0.0.3 → 0.0.5
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.
- package/dist/editor-x/editor.cjs +724 -435
- package/dist/editor-x/editor.cjs.map +1 -1
- package/dist/editor-x/editor.css +1391 -1091
- package/dist/editor-x/editor.css.map +1 -1
- package/dist/editor-x/editor.js +728 -439
- package/dist/editor-x/editor.js.map +1 -1
- package/dist/index.cjs +760 -472
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +1391 -1091
- package/dist/index.css.map +1 -1
- package/dist/index.js +763 -475
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/lexical-editor.tsx +138 -123
- package/src/editor-ui/content-editable.tsx +3 -3
- package/src/editor-x/editor.tsx +16 -3
- package/src/editor-x/plugins.tsx +385 -380
- package/src/nodes/list-with-color-node.tsx +160 -160
- package/src/plugins/autocomplete-plugin.tsx +2574 -2574
- package/src/plugins/context-menu-plugin.tsx +239 -9
- package/src/plugins/floating-text-format-plugin.tsx +84 -92
- package/src/plugins/images-plugin.tsx +4 -4
- package/src/plugins/list-color-plugin.tsx +178 -178
- package/src/plugins/tab-focus-plugin.tsx +66 -66
- package/src/plugins/table-column-resizer-plugin.tsx +329 -190
- package/src/plugins/toolbar/block-format/block-format-data.tsx +4 -0
- package/src/plugins/toolbar/block-format/format-bulleted-list.tsx +40 -40
- package/src/plugins/toolbar/block-format/format-list-with-marker.tsx +74 -74
- package/src/plugins/toolbar/block-format/format-numbered-list.tsx +40 -40
- package/src/plugins/toolbar/block-format-toolbar-plugin.tsx +118 -117
- package/src/plugins/toolbar/element-format-toolbar-plugin.tsx +37 -53
- package/src/plugins/toolbar/font-format-toolbar-plugin.tsx +8 -15
- package/src/plugins/toolbar/font-size-toolbar-plugin.tsx +2 -3
- package/src/plugins/toolbar/history-toolbar-plugin.tsx +2 -5
- package/src/plugins/toolbar/subsuper-toolbar-plugin.tsx +15 -23
- package/src/themes/_mixins.scss +158 -10
- package/src/themes/_variables.scss +168 -0
- package/src/themes/core/_code.scss +59 -0
- package/src/themes/core/_images.scss +80 -0
- package/src/themes/core/_lists.scss +214 -0
- package/src/themes/core/_misc.scss +46 -0
- package/src/themes/core/_reset.scss +119 -0
- package/src/themes/core/_tables.scss +145 -0
- package/src/themes/core/_text.scss +35 -0
- package/src/themes/core/_typography.scss +73 -0
- package/src/themes/editor-theme copy.scss +763 -0
- package/src/themes/editor-theme.scss +9 -621
- package/src/themes/editor-theme.ts +118 -118
- package/src/themes/plugins/_auto-embed.scss +11 -0
- package/src/themes/plugins/_color-picker.scss +103 -0
- package/src/themes/plugins/_draggable-block.scss +32 -0
- package/src/themes/plugins/_floating-link-editor.scss +47 -0
- package/src/themes/plugins/_floating-toolbars.scss +61 -0
- package/src/themes/plugins/_image-resizer.scss +38 -0
- package/src/themes/plugins/_image.scss +57 -0
- package/src/themes/plugins/_layout.scss +39 -0
- package/src/themes/plugins/_list-color.scss +23 -0
- package/src/themes/plugins/_mentions.scss +21 -0
- package/src/themes/plugins/_menus-and-pickers.scss +153 -0
- package/src/themes/plugins/_table.scss +20 -0
- package/src/themes/plugins/_toolbar.scss +36 -0
- package/src/themes/plugins/_tree-view.scss +11 -0
- package/src/themes/plugins copy.scss +656 -0
- package/src/themes/plugins.scss +20 -1165
- package/src/themes/ui-components/_animations.scss +31 -0
- package/src/themes/ui-components/_backgrounds.scss +27 -0
- package/src/themes/ui-components/_borders.scss +20 -0
- package/src/themes/ui-components/_button.scss +176 -0
- package/src/themes/ui-components/_checkbox.scss +14 -0
- package/src/themes/ui-components/_cursors.scss +31 -0
- package/src/themes/ui-components/_dialog.scss +86 -0
- package/src/themes/ui-components/_display-sizing.scss +100 -0
- package/src/themes/ui-components/_flex.scss +124 -0
- package/src/themes/ui-components/_form-layout.scss +15 -0
- package/src/themes/ui-components/_icons.scss +23 -0
- package/src/themes/ui-components/_input.scss +86 -0
- package/src/themes/ui-components/_label.scss +19 -0
- package/src/themes/ui-components/_loader.scss +9 -0
- package/src/themes/ui-components/_margins-paddings.scss +45 -0
- package/src/themes/ui-components/_popover.scss +16 -0
- package/src/themes/ui-components/_positioning.scss +73 -0
- package/src/themes/ui-components/_rounded.scss +19 -0
- package/src/themes/ui-components/_scroll-area.scss +11 -0
- package/src/themes/ui-components/_select.scss +110 -0
- package/src/themes/ui-components/_separator.scss +19 -0
- package/src/themes/ui-components/_shadow.scss +15 -0
- package/src/themes/ui-components/_tabs.scss +46 -0
- package/src/themes/ui-components/_text-utilities.scss +48 -0
- package/src/themes/ui-components/_toggle-toolbar.scss +128 -0
- package/src/themes/ui-components/_toggle.scss +80 -0
- package/src/themes/ui-components/_typography.scss +22 -0
- package/src/themes/ui-components copy.scss +1335 -0
- package/src/themes/ui-components.scss +27 -937
- package/src/transformers/markdown-list-transformer.ts +51 -51
- package/src/ui/button.tsx +11 -2
- package/src/ui/collapsible.tsx +1 -1
- package/src/ui/dialog.tsx +2 -2
- package/src/ui/flex.tsx +4 -4
- package/src/ui/popover.tsx +1 -1
- package/src/ui/tooltip.tsx +2 -2
|
@@ -1,178 +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
|
-
}
|
|
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
|
+
}
|
|
@@ -1,66 +1,66 @@
|
|
|
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 {
|
|
13
|
-
$getSelection,
|
|
14
|
-
$isRangeSelection,
|
|
15
|
-
$setSelection,
|
|
16
|
-
FOCUS_COMMAND,
|
|
17
|
-
} from "lexical"
|
|
18
|
-
|
|
19
|
-
const COMMAND_PRIORITY_LOW = 1
|
|
20
|
-
const TAB_TO_FOCUS_INTERVAL = 100
|
|
21
|
-
|
|
22
|
-
let lastTabKeyDownTimestamp = 0
|
|
23
|
-
let hasRegisteredKeyDownListener = false
|
|
24
|
-
|
|
25
|
-
function registerKeyTimeStampTracker() {
|
|
26
|
-
window.addEventListener(
|
|
27
|
-
"keydown",
|
|
28
|
-
(event: KeyboardEvent) => {
|
|
29
|
-
// Tab
|
|
30
|
-
if (event.key === "Tab") {
|
|
31
|
-
lastTabKeyDownTimestamp = event.timeStamp
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
|
-
true
|
|
35
|
-
)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function TabFocusPlugin(): null {
|
|
39
|
-
const [editor] = useLexicalComposerContext()
|
|
40
|
-
|
|
41
|
-
useEffect(() => {
|
|
42
|
-
if (!hasRegisteredKeyDownListener) {
|
|
43
|
-
registerKeyTimeStampTracker()
|
|
44
|
-
hasRegisteredKeyDownListener = true
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return editor.registerCommand(
|
|
48
|
-
FOCUS_COMMAND,
|
|
49
|
-
(event: FocusEvent) => {
|
|
50
|
-
const selection = $getSelection()
|
|
51
|
-
if ($isRangeSelection(selection)) {
|
|
52
|
-
if (
|
|
53
|
-
lastTabKeyDownTimestamp + TAB_TO_FOCUS_INTERVAL >
|
|
54
|
-
event.timeStamp
|
|
55
|
-
) {
|
|
56
|
-
$setSelection(selection.clone())
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return false
|
|
60
|
-
},
|
|
61
|
-
COMMAND_PRIORITY_LOW
|
|
62
|
-
)
|
|
63
|
-
}, [editor])
|
|
64
|
-
|
|
65
|
-
return null
|
|
66
|
-
}
|
|
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 {
|
|
13
|
+
$getSelection,
|
|
14
|
+
$isRangeSelection,
|
|
15
|
+
$setSelection,
|
|
16
|
+
FOCUS_COMMAND,
|
|
17
|
+
} from "lexical"
|
|
18
|
+
|
|
19
|
+
const COMMAND_PRIORITY_LOW = 1
|
|
20
|
+
const TAB_TO_FOCUS_INTERVAL = 100
|
|
21
|
+
|
|
22
|
+
let lastTabKeyDownTimestamp = 0
|
|
23
|
+
let hasRegisteredKeyDownListener = false
|
|
24
|
+
|
|
25
|
+
function registerKeyTimeStampTracker() {
|
|
26
|
+
window.addEventListener(
|
|
27
|
+
"keydown",
|
|
28
|
+
(event: KeyboardEvent) => {
|
|
29
|
+
// Tab
|
|
30
|
+
if (event.key === "Tab") {
|
|
31
|
+
lastTabKeyDownTimestamp = event.timeStamp
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
true
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function TabFocusPlugin(): null {
|
|
39
|
+
const [editor] = useLexicalComposerContext()
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (!hasRegisteredKeyDownListener) {
|
|
43
|
+
registerKeyTimeStampTracker()
|
|
44
|
+
hasRegisteredKeyDownListener = true
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return editor.registerCommand(
|
|
48
|
+
FOCUS_COMMAND,
|
|
49
|
+
(event: FocusEvent) => {
|
|
50
|
+
const selection = $getSelection()
|
|
51
|
+
if ($isRangeSelection(selection)) {
|
|
52
|
+
if (
|
|
53
|
+
lastTabKeyDownTimestamp + TAB_TO_FOCUS_INTERVAL >
|
|
54
|
+
event.timeStamp
|
|
55
|
+
) {
|
|
56
|
+
$setSelection(selection.clone())
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return false
|
|
60
|
+
},
|
|
61
|
+
COMMAND_PRIORITY_LOW
|
|
62
|
+
)
|
|
63
|
+
}, [editor])
|
|
64
|
+
|
|
65
|
+
return null
|
|
66
|
+
}
|