@thangph2146/lexical-editor 0.0.4 → 0.0.6
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/README.md +47 -0
- package/dist/editor-x/editor.cjs +732 -443
- package/dist/editor-x/editor.cjs.map +1 -1
- package/dist/editor-x/editor.css +1418 -1120
- package/dist/editor-x/editor.css.map +1 -1
- package/dist/editor-x/editor.d.cts +2 -1
- package/dist/editor-x/editor.d.ts +2 -1
- package/dist/editor-x/editor.js +736 -447
- package/dist/editor-x/editor.js.map +1 -1
- package/dist/index.cjs +772 -482
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +1418 -1120
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +775 -485
- package/dist/index.js.map +1 -1
- package/package.json +86 -84
- package/src/components/lexical-editor.tsx +140 -123
- package/src/editor-x/editor.tsx +20 -5
- 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.scss +9 -623
- 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.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.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,5 +1,5 @@
|
|
|
1
1
|
import type { JSX } from "react"
|
|
2
|
-
import { useMemo } from "react"
|
|
2
|
+
import { useCallback, useEffect, useMemo, useState } from "react"
|
|
3
3
|
import { $isLinkNode, TOGGLE_LINK_COMMAND } from "@lexical/link"
|
|
4
4
|
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext"
|
|
5
5
|
import {
|
|
@@ -9,10 +9,11 @@ import {
|
|
|
9
9
|
} from "@lexical/react/LexicalNodeContextMenuPlugin"
|
|
10
10
|
import { $mergeCells, $isTableCellNode, $isTableSelection, $unmergeCell, $insertTableColumnAtSelection, $insertTableRowAtSelection } from "@lexical/table"
|
|
11
11
|
import type { TableCellNode } from "@lexical/table"
|
|
12
|
-
import { $isListNode } from "@lexical/list"
|
|
12
|
+
import { $isListNode, ListNode } from "@lexical/list"
|
|
13
13
|
import { $findMatchingParent } from "@lexical/utils"
|
|
14
14
|
import {
|
|
15
15
|
$getSelection,
|
|
16
|
+
$getNearestNodeFromDOMNode,
|
|
16
17
|
$isDecoratorNode,
|
|
17
18
|
$isNodeSelection,
|
|
18
19
|
$isRangeSelection,
|
|
@@ -22,6 +23,8 @@ import {
|
|
|
22
23
|
type LexicalNode,
|
|
23
24
|
} from "lexical"
|
|
24
25
|
import { $isLayoutItemNode } from "../nodes/layout-item-node"
|
|
26
|
+
import { createListWithColorNodeFromRegistry } from "../editor-x/nodes"
|
|
27
|
+
import { $isListWithColorNode, $createListWithColorNode } from "../nodes/list-with-color-node"
|
|
25
28
|
import { OPEN_UPDATE_LAYOUT_MODAL_COMMAND } from "../plugins/layout-plugin"
|
|
26
29
|
import { OPEN_LIST_COLOR_PICKER_COMMAND } from "../plugins/list-color-plugin"
|
|
27
30
|
import {
|
|
@@ -37,11 +40,21 @@ import {
|
|
|
37
40
|
Rows3,
|
|
38
41
|
LayoutGrid,
|
|
39
42
|
Palette,
|
|
43
|
+
ListOrderedIcon,
|
|
44
|
+
Hash,
|
|
45
|
+
ListIcon,
|
|
40
46
|
} from "lucide-react"
|
|
41
47
|
import { IconSize } from "../ui/typography"
|
|
48
|
+
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "../ui/dialog"
|
|
49
|
+
import { Input } from "../ui/input"
|
|
50
|
+
import { Button } from "../ui/button"
|
|
51
|
+
import { Label } from "../ui/label"
|
|
42
52
|
|
|
43
53
|
export function ContextMenuPlugin(): JSX.Element {
|
|
44
54
|
const [editor] = useLexicalComposerContext()
|
|
55
|
+
const [contextTarget, setContextTarget] = useState<HTMLElement | null>(null)
|
|
56
|
+
const [showMarkerDialog, setShowMarkerDialog] = useState(false)
|
|
57
|
+
const [customMarker, setCustomMarker] = useState("")
|
|
45
58
|
|
|
46
59
|
const isInTable = (node: LexicalNode) =>
|
|
47
60
|
$findMatchingParent(node, $isTableCellNode) !== null
|
|
@@ -67,6 +80,123 @@ export function ContextMenuPlugin(): JSX.Element {
|
|
|
67
80
|
const isInList = (node: LexicalNode) =>
|
|
68
81
|
$findMatchingParent(node, $isListNode) !== null
|
|
69
82
|
|
|
83
|
+
const isInNumberList = (node: LexicalNode) => {
|
|
84
|
+
const listNode = $findMatchingParent(node, $isListNode)
|
|
85
|
+
return $isListNode(listNode) && listNode.getListType() === "number"
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
const rootElement = editor.getRootElement()
|
|
90
|
+
if (!rootElement) return
|
|
91
|
+
|
|
92
|
+
const onContextMenu = (event: MouseEvent) => {
|
|
93
|
+
const target = event.target
|
|
94
|
+
if (!(target instanceof HTMLElement)) return
|
|
95
|
+
setContextTarget(target)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
rootElement.addEventListener("contextmenu", onContextMenu)
|
|
99
|
+
return () => {
|
|
100
|
+
rootElement.removeEventListener("contextmenu", onContextMenu)
|
|
101
|
+
}
|
|
102
|
+
}, [editor])
|
|
103
|
+
|
|
104
|
+
const getContextAnchorNode = useCallback(() => {
|
|
105
|
+
if (contextTarget) {
|
|
106
|
+
const nearestNode = $getNearestNodeFromDOMNode(contextTarget)
|
|
107
|
+
if (nearestNode) return nearestNode
|
|
108
|
+
}
|
|
109
|
+
const selection = $getSelection()
|
|
110
|
+
if ($isRangeSelection(selection)) {
|
|
111
|
+
return selection.anchor.getNode()
|
|
112
|
+
}
|
|
113
|
+
return null
|
|
114
|
+
}, [contextTarget])
|
|
115
|
+
|
|
116
|
+
const updateNumberListMarkerType = useCallback((markerType: string | undefined) => {
|
|
117
|
+
editor.update(() => {
|
|
118
|
+
const anchorNode = getContextAnchorNode()
|
|
119
|
+
if (!anchorNode) return
|
|
120
|
+
const nearestListNode = $findMatchingParent(
|
|
121
|
+
anchorNode,
|
|
122
|
+
(node): node is ListNode => $isListNode(node) && node.getListType() === "number"
|
|
123
|
+
)
|
|
124
|
+
if (!nearestListNode) return
|
|
125
|
+
let listNode: ListNode = nearestListNode
|
|
126
|
+
let parent = listNode.getParent()
|
|
127
|
+
while (parent) {
|
|
128
|
+
if ($isListNode(parent) && parent.getListType() === "number") {
|
|
129
|
+
listNode = parent
|
|
130
|
+
parent = parent.getParent()
|
|
131
|
+
continue
|
|
132
|
+
}
|
|
133
|
+
break
|
|
134
|
+
}
|
|
135
|
+
if ($isListWithColorNode(listNode)) {
|
|
136
|
+
listNode.setMarkerType(markerType)
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
const newList = createListWithColorNodeFromRegistry(
|
|
140
|
+
editor,
|
|
141
|
+
listNode.getListType(),
|
|
142
|
+
listNode.getStart()
|
|
143
|
+
)
|
|
144
|
+
newList.setMarkerType(markerType)
|
|
145
|
+
const children = listNode.getChildren()
|
|
146
|
+
for (const child of children) newList.append(child)
|
|
147
|
+
listNode.replace(newList)
|
|
148
|
+
})
|
|
149
|
+
}, [editor, getContextAnchorNode])
|
|
150
|
+
|
|
151
|
+
const syncMarkerToSameLevelLists = useCallback((targetListNode: ListNode, markerType: string | undefined) => {
|
|
152
|
+
editor.update(() => {
|
|
153
|
+
// Lấy parent của list hiện tại
|
|
154
|
+
const parent = targetListNode.getParent()
|
|
155
|
+
if (!parent) return
|
|
156
|
+
|
|
157
|
+
// Tìm tất cả các list cùng cấp với list hiện tại
|
|
158
|
+
const siblings = parent.getChildren()
|
|
159
|
+
|
|
160
|
+
for (const sibling of siblings) {
|
|
161
|
+
if ($isListNode(sibling) && sibling.getListType() === "number" && sibling !== targetListNode) {
|
|
162
|
+
// Cập nhật marker cho list cùng cấp
|
|
163
|
+
if ($isListWithColorNode(sibling)) {
|
|
164
|
+
sibling.setMarkerType(markerType)
|
|
165
|
+
} else {
|
|
166
|
+
const newList = $createListWithColorNode("number", sibling.getStart())
|
|
167
|
+
newList.setMarkerType(markerType)
|
|
168
|
+
const children = sibling.getChildren()
|
|
169
|
+
for (const child of children) newList.append(child)
|
|
170
|
+
sibling.replace(newList)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
})
|
|
175
|
+
}, [editor])
|
|
176
|
+
|
|
177
|
+
const handleCustomMarkerSubmit = () => {
|
|
178
|
+
if (customMarker.trim()) {
|
|
179
|
+
editor.update(() => {
|
|
180
|
+
const anchorNode = getContextAnchorNode()
|
|
181
|
+
if (!anchorNode) return
|
|
182
|
+
const nearestListNode = $findMatchingParent(
|
|
183
|
+
anchorNode,
|
|
184
|
+
(node): node is ListNode => $isListNode(node) && node.getListType() === "number"
|
|
185
|
+
)
|
|
186
|
+
if (!nearestListNode) return
|
|
187
|
+
|
|
188
|
+
// Cập nhật marker cho list hiện tại
|
|
189
|
+
updateNumberListMarkerType(customMarker.trim())
|
|
190
|
+
|
|
191
|
+
// Đồng bộ với các list cùng cấp
|
|
192
|
+
syncMarkerToSameLevelLists(nearestListNode, customMarker.trim())
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
setShowMarkerDialog(false)
|
|
196
|
+
setCustomMarker("")
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
70
200
|
const items = useMemo(() => {
|
|
71
201
|
return [
|
|
72
202
|
new NodeContextMenuOption(`Gộp ô`, {
|
|
@@ -192,6 +322,41 @@ export function ContextMenuPlugin(): JSX.Element {
|
|
|
192
322
|
new NodeContextMenuSeparator({
|
|
193
323
|
$showOn: isInList,
|
|
194
324
|
}),
|
|
325
|
+
new NodeContextMenuOption(`Đánh số 1, 2, 3`, {
|
|
326
|
+
$onSelect: () => {
|
|
327
|
+
updateNumberListMarkerType(undefined)
|
|
328
|
+
},
|
|
329
|
+
$showOn: isInNumberList,
|
|
330
|
+
disabled: false,
|
|
331
|
+
icon: <IconSize size="sm"><ListOrderedIcon /></IconSize>,
|
|
332
|
+
}),
|
|
333
|
+
new NodeContextMenuOption(`Đánh số đa cấp 1.1 / 1.1.1`, {
|
|
334
|
+
$onSelect: () => {
|
|
335
|
+
updateNumberListMarkerType("multi-level")
|
|
336
|
+
},
|
|
337
|
+
$showOn: isInNumberList,
|
|
338
|
+
disabled: false,
|
|
339
|
+
icon: <IconSize size="sm"><ListOrderedIcon /></IconSize>,
|
|
340
|
+
}),
|
|
341
|
+
new NodeContextMenuOption(`Đánh số a, b, c`, {
|
|
342
|
+
$onSelect: () => {
|
|
343
|
+
updateNumberListMarkerType("alpha")
|
|
344
|
+
},
|
|
345
|
+
$showOn: isInNumberList,
|
|
346
|
+
disabled: false,
|
|
347
|
+
icon: <IconSize size="sm"><ListOrderedIcon /></IconSize>,
|
|
348
|
+
}),
|
|
349
|
+
new NodeContextMenuOption(`Tùy chỉnh marker...`, {
|
|
350
|
+
$onSelect: () => {
|
|
351
|
+
setShowMarkerDialog(true)
|
|
352
|
+
},
|
|
353
|
+
$showOn: isInNumberList,
|
|
354
|
+
disabled: false,
|
|
355
|
+
icon: <IconSize size="sm"><Hash className="text-blue-600" /></IconSize>,
|
|
356
|
+
}),
|
|
357
|
+
new NodeContextMenuSeparator({
|
|
358
|
+
$showOn: isInNumberList,
|
|
359
|
+
}),
|
|
195
360
|
new NodeContextMenuOption(`Remove Link`, {
|
|
196
361
|
$onSelect: () => {
|
|
197
362
|
editor.dispatchCommand(TOGGLE_LINK_COMMAND, null)
|
|
@@ -298,14 +463,79 @@ export function ContextMenuPlugin(): JSX.Element {
|
|
|
298
463
|
icon: <IconSize size="sm"><Trash2 /></IconSize>,
|
|
299
464
|
}),
|
|
300
465
|
]
|
|
301
|
-
}, [editor])
|
|
466
|
+
}, [editor, updateNumberListMarkerType])
|
|
302
467
|
|
|
303
468
|
return (
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
469
|
+
<>
|
|
470
|
+
<NodeContextMenuPlugin
|
|
471
|
+
className="editor-context-menu"
|
|
472
|
+
itemClassName="editor-context-menu-item"
|
|
473
|
+
separatorClassName="editor-context-menu-separator"
|
|
474
|
+
items={items}
|
|
475
|
+
/>
|
|
476
|
+
<Dialog open={showMarkerDialog} onOpenChange={setShowMarkerDialog}>
|
|
477
|
+
<DialogContent style={{ maxWidth: '400px' }}>
|
|
478
|
+
<DialogHeader>
|
|
479
|
+
<DialogTitle>Tùy chỉnh Marker</DialogTitle>
|
|
480
|
+
<p className="editor-text-sm editor-text-muted-foreground">Nhập kiểu marker cho danh sách của bạn</p>
|
|
481
|
+
</DialogHeader>
|
|
482
|
+
|
|
483
|
+
<div className="editor-flex-col editor-py-4 editor-gap-4">
|
|
484
|
+
<div className="editor-flex-col editor-gap-2">
|
|
485
|
+
<Label htmlFor="marker-input" className="editor-font-medium">
|
|
486
|
+
Kiểu marker
|
|
487
|
+
</Label>
|
|
488
|
+
<div className="editor-relative editor-flex editor-items-center">
|
|
489
|
+
<Hash
|
|
490
|
+
className="editor-absolute editor-text-muted-foreground"
|
|
491
|
+
style={{ left: '0.75rem' }}
|
|
492
|
+
size={16}
|
|
493
|
+
/>
|
|
494
|
+
<Input
|
|
495
|
+
id="marker-input"
|
|
496
|
+
value={customMarker}
|
|
497
|
+
onChange={(e) => setCustomMarker(e.target.value)}
|
|
498
|
+
placeholder="Ví dụ: multi-level, alpha, 4.1, ..."
|
|
499
|
+
style={{ paddingLeft: '2.25rem' }}
|
|
500
|
+
onKeyDown={(e) => {
|
|
501
|
+
if (e.key === "Enter") {
|
|
502
|
+
handleCustomMarkerSubmit()
|
|
503
|
+
}
|
|
504
|
+
}}
|
|
505
|
+
/>
|
|
506
|
+
</div>
|
|
507
|
+
<div className="editor-flex-col editor-gap-1 editor-text-sm editor-text-muted-foreground editor-mt-2">
|
|
508
|
+
<p className="editor-flex editor-items-center editor-gap-2" style={{ margin: 0 }}>
|
|
509
|
+
<span className="editor-font-medium">multi-level</span> - Hiển thị dạng 4.1, 4.1.1
|
|
510
|
+
</p>
|
|
511
|
+
<p className="editor-flex editor-items-center editor-gap-2" style={{ margin: 0 }}>
|
|
512
|
+
<span className="editor-font-medium">alpha</span> - Hiển thị dạng a, b, c
|
|
513
|
+
</p>
|
|
514
|
+
<p className="editor-flex editor-items-center editor-gap-2" style={{ margin: 0 }}>
|
|
515
|
+
<span className="editor-font-medium">Để trống</span> - Về mặc định 1, 2, 3
|
|
516
|
+
</p>
|
|
517
|
+
</div>
|
|
518
|
+
</div>
|
|
519
|
+
</div>
|
|
520
|
+
|
|
521
|
+
<DialogFooter>
|
|
522
|
+
<Button
|
|
523
|
+
variant="outline"
|
|
524
|
+
onClick={() => setShowMarkerDialog(false)}
|
|
525
|
+
>
|
|
526
|
+
Hủy
|
|
527
|
+
</Button>
|
|
528
|
+
<Button
|
|
529
|
+
onClick={handleCustomMarkerSubmit}
|
|
530
|
+
disabled={!customMarker.trim()}
|
|
531
|
+
className="editor-gap-2"
|
|
532
|
+
>
|
|
533
|
+
<ListIcon size={16} />
|
|
534
|
+
<span>Áp dụng</span>
|
|
535
|
+
</Button>
|
|
536
|
+
</DialogFooter>
|
|
537
|
+
</DialogContent>
|
|
538
|
+
</Dialog>
|
|
539
|
+
</>
|
|
310
540
|
)
|
|
311
541
|
}
|
|
@@ -59,10 +59,9 @@ import { DialogFooter } from "../ui/dialog"
|
|
|
59
59
|
import { Flex } from "../ui/flex"
|
|
60
60
|
import { Separator } from "../ui/separator"
|
|
61
61
|
import {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
} from "../ui/toggle-group"
|
|
65
|
-
import { IconSize } from "../ui/typography"
|
|
62
|
+
IconSize,
|
|
63
|
+
} from "../ui/typography"
|
|
64
|
+
import { ToggleGroup, ToggleGroupItem } from "../ui/toggle-group"
|
|
66
65
|
|
|
67
66
|
function FontColorModalContent({
|
|
68
67
|
initialColor,
|
|
@@ -390,95 +389,88 @@ function FloatingTextFormat({
|
|
|
390
389
|
{editor.isEditable() && (
|
|
391
390
|
<Flex align="center" gap={1} className="editor-flex-nowrap">
|
|
392
391
|
<div className="editor-floating-group editor-flex editor-items-center">
|
|
393
|
-
<
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
isLink ? "link" : "",
|
|
403
|
-
].filter(Boolean)}
|
|
392
|
+
<Button
|
|
393
|
+
variant="ghost"
|
|
394
|
+
size="sm"
|
|
395
|
+
className="editor-toolbar-item"
|
|
396
|
+
data-state={isBold ? "on" : "off"}
|
|
397
|
+
onClick={() => {
|
|
398
|
+
editor.dispatchCommand(FORMAT_TEXT_COMMAND, "bold")
|
|
399
|
+
}}
|
|
400
|
+
aria-label="Toggle bold"
|
|
404
401
|
>
|
|
405
|
-
<
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
<IconSize size="sm">
|
|
478
|
-
<LinkIcon />
|
|
479
|
-
</IconSize>
|
|
480
|
-
</ToggleGroupItem>
|
|
481
|
-
</ToggleGroup>
|
|
402
|
+
<IconSize size="sm">
|
|
403
|
+
<BoldIcon />
|
|
404
|
+
</IconSize>
|
|
405
|
+
</Button>
|
|
406
|
+
<Button
|
|
407
|
+
variant="ghost"
|
|
408
|
+
size="sm"
|
|
409
|
+
className="editor-toolbar-item"
|
|
410
|
+
data-state={isItalic ? "on" : "off"}
|
|
411
|
+
onClick={() => {
|
|
412
|
+
editor.dispatchCommand(FORMAT_TEXT_COMMAND, "italic")
|
|
413
|
+
}}
|
|
414
|
+
aria-label="Toggle italic"
|
|
415
|
+
>
|
|
416
|
+
<IconSize size="sm">
|
|
417
|
+
<ItalicIcon />
|
|
418
|
+
</IconSize>
|
|
419
|
+
</Button>
|
|
420
|
+
<Button
|
|
421
|
+
variant="ghost"
|
|
422
|
+
size="sm"
|
|
423
|
+
className="editor-toolbar-item"
|
|
424
|
+
data-state={isUnderline ? "on" : "off"}
|
|
425
|
+
onClick={() => {
|
|
426
|
+
editor.dispatchCommand(FORMAT_TEXT_COMMAND, "underline")
|
|
427
|
+
}}
|
|
428
|
+
aria-label="Toggle underline"
|
|
429
|
+
>
|
|
430
|
+
<IconSize size="sm">
|
|
431
|
+
<UnderlineIcon />
|
|
432
|
+
</IconSize>
|
|
433
|
+
</Button>
|
|
434
|
+
<Button
|
|
435
|
+
variant="ghost"
|
|
436
|
+
size="sm"
|
|
437
|
+
className="editor-toolbar-item"
|
|
438
|
+
data-state={isStrikethrough ? "on" : "off"}
|
|
439
|
+
onClick={() => {
|
|
440
|
+
editor.dispatchCommand(FORMAT_TEXT_COMMAND, "strikethrough")
|
|
441
|
+
}}
|
|
442
|
+
aria-label="Toggle strikethrough"
|
|
443
|
+
>
|
|
444
|
+
<IconSize size="sm">
|
|
445
|
+
<StrikethroughIcon />
|
|
446
|
+
</IconSize>
|
|
447
|
+
</Button>
|
|
448
|
+
<Button
|
|
449
|
+
variant="ghost"
|
|
450
|
+
size="sm"
|
|
451
|
+
className="editor-toolbar-item"
|
|
452
|
+
data-state={isCode ? "on" : "off"}
|
|
453
|
+
onClick={() => {
|
|
454
|
+
editor.dispatchCommand(FORMAT_TEXT_COMMAND, "code")
|
|
455
|
+
}}
|
|
456
|
+
aria-label="Toggle code"
|
|
457
|
+
>
|
|
458
|
+
<IconSize size="sm">
|
|
459
|
+
<CodeIcon />
|
|
460
|
+
</IconSize>
|
|
461
|
+
</Button>
|
|
462
|
+
<Button
|
|
463
|
+
variant="ghost"
|
|
464
|
+
size="sm"
|
|
465
|
+
className="editor-toolbar-item"
|
|
466
|
+
data-state={isLink ? "on" : "off"}
|
|
467
|
+
onClick={insertLink}
|
|
468
|
+
aria-label="Toggle link"
|
|
469
|
+
>
|
|
470
|
+
<IconSize size="sm">
|
|
471
|
+
<LinkIcon />
|
|
472
|
+
</IconSize>
|
|
473
|
+
</Button>
|
|
482
474
|
</div>
|
|
483
475
|
|
|
484
476
|
<Separator orientation="vertical" className="editor-separator--vertical" />
|
|
@@ -220,10 +220,10 @@ function ImagePickerFolderTree({
|
|
|
220
220
|
{`${folder.images.length} hình${folder.subfolders.length > 0 ? `, ${folder.subfolders.length} thư mục` : ""}`}
|
|
221
221
|
</TypographySpanSmallMuted>
|
|
222
222
|
</CollapsibleTrigger>
|
|
223
|
-
<CollapsibleContent className="editor-ml-4 editor-mt-1">
|
|
223
|
+
<CollapsibleContent className="editor-ml-4 editor-mt-1 editor-flex editor-flex-col editor-gap-1">
|
|
224
224
|
{/* Render images in this folder */}
|
|
225
225
|
{folder.images.length > 0 && (
|
|
226
|
-
<div className="editor-image-grid">
|
|
226
|
+
<div className="editor-image-grid editor-gap-2">
|
|
227
227
|
{folder.images.map((image) => (
|
|
228
228
|
<button
|
|
229
229
|
key={image.fileName}
|
|
@@ -345,12 +345,12 @@ export function InsertImageUploadsDialogBody({
|
|
|
345
345
|
<TypographySpanSmallMuted>Chưa có hình ảnh nào được upload</TypographySpanSmallMuted>
|
|
346
346
|
</div>
|
|
347
347
|
) : (
|
|
348
|
-
<div className="editor-scroll-area">
|
|
348
|
+
<div className="editor-scroll-area editor-flex editor-flex-col editor-gap-1">
|
|
349
349
|
{/* Render root level images if any */}
|
|
350
350
|
{folderTree.images.length > 0 && (
|
|
351
351
|
<div className="editor-mb-3">
|
|
352
352
|
<TypographySpanSmallMuted className="editor-mb-2 editor-px-1">Root</TypographySpanSmallMuted>
|
|
353
|
-
<div className="editor-image-grid">
|
|
353
|
+
<div className="editor-image-grid editor-gap-2">
|
|
354
354
|
{folderTree.images.map((image) => (
|
|
355
355
|
<button
|
|
356
356
|
key={image.fileName}
|