@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,380 @@
1
+ "use client"
2
+ import { useState } from "react"
3
+ import {
4
+ CHECK_LIST,
5
+ ELEMENT_TRANSFORMERS,
6
+ MULTILINE_ELEMENT_TRANSFORMERS,
7
+ TEXT_FORMAT_TRANSFORMERS,
8
+ TEXT_MATCH_TRANSFORMERS,
9
+ } from "@lexical/markdown"
10
+ import { CheckListPlugin } from "@lexical/react/LexicalCheckListPlugin"
11
+ import { ClearEditorPlugin } from "@lexical/react/LexicalClearEditorPlugin"
12
+ import { ClickableLinkPlugin } from "@lexical/react/LexicalClickableLinkPlugin"
13
+ import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary"
14
+ import { HashtagPlugin } from "@lexical/react/LexicalHashtagPlugin"
15
+ import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin"
16
+ import { HorizontalRulePlugin } from "@lexical/react/LexicalHorizontalRulePlugin"
17
+ import { ListPlugin } from "@lexical/react/LexicalListPlugin"
18
+ import { MarkdownShortcutPlugin } from "@lexical/react/LexicalMarkdownShortcutPlugin"
19
+ import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin"
20
+ import { TabIndentationPlugin } from "@lexical/react/LexicalTabIndentationPlugin"
21
+ import { TablePlugin } from "@lexical/react/LexicalTablePlugin"
22
+
23
+ import { ContentEditable } from "../editor-ui/content-editable"
24
+ import { cn } from "../lib/utils"
25
+ import { ActionsPlugin } from "../plugins/actions/actions-plugin"
26
+ import { AlignPlugin } from "../plugins/align-plugin"
27
+ import { ClearEditorActionPlugin } from "../plugins/actions/clear-editor-plugin"
28
+ import { CounterCharacterPlugin } from "../plugins/actions/counter-character-plugin"
29
+ import { EditModeTogglePlugin } from "../plugins/actions/edit-mode-toggle-plugin"
30
+ import { ImportExportPlugin } from "../plugins/actions/import-export-plugin"
31
+ import { MarkdownTogglePlugin } from "../plugins/actions/markdown-toggle-plugin"
32
+ import { ShareContentPlugin } from "../plugins/actions/share-content-plugin"
33
+ import { SpeechToTextPlugin } from "../plugins/actions/speech-to-text-plugin"
34
+ import { TreeViewPlugin } from "../plugins/actions/tree-view-plugin"
35
+ import { AutoLinkPlugin } from "../plugins/auto-link-plugin"
36
+ import { AutocompletePlugin } from "../plugins/autocomplete-plugin"
37
+ import { CodeActionMenuPlugin } from "../plugins/code-action-menu-plugin"
38
+ import { CodeHighlightPlugin } from "../plugins/code-highlight-plugin"
39
+ import { ComponentPickerMenuPlugin } from "../plugins/component-picker-menu-plugin"
40
+ import { ContextMenuPlugin } from "../plugins/context-menu-plugin"
41
+ import { DragDropPastePlugin } from "../plugins/drag-drop-paste-plugin"
42
+ import { DraggableBlockPlugin } from "../plugins/draggable-block-plugin"
43
+ import { AutoEmbedPlugin } from "../plugins/embeds/auto-embed-plugin"
44
+ import { TwitterPlugin } from "../plugins/embeds/twitter-plugin"
45
+ import { YouTubePlugin } from "../plugins/embeds/youtube-plugin"
46
+ import { EmojiPickerPlugin } from "../plugins/emoji-picker-plugin"
47
+ import { EmojisPlugin } from "../plugins/emojis-plugin"
48
+ import { FloatingLinkEditorPlugin } from "../plugins/floating-link-editor-plugin"
49
+ import { FloatingTextFormatToolbarPlugin } from "../plugins/floating-text-format-plugin"
50
+ import { ImagesPlugin } from "../plugins/images-plugin"
51
+ import { KeywordsPlugin } from "../plugins/keywords-plugin"
52
+ import { LayoutPlugin } from "../plugins/layout-plugin"
53
+ import { LinkPlugin } from "../plugins/link-plugin"
54
+ import { ListColorPlugin } from "../plugins/list-color-plugin"
55
+ import { ListMaxIndentLevelPlugin } from "../plugins/list-max-indent-level-plugin"
56
+ import { MentionsPlugin } from "../plugins/mentions-plugin"
57
+ import { AlignmentPickerPlugin } from "../plugins/picker/alignment-picker-plugin"
58
+ import { BulletedListPickerPlugin } from "../plugins/picker/bulleted-list-picker-plugin"
59
+ import { CheckListPickerPlugin } from "../plugins/picker/check-list-picker-plugin"
60
+ import { CodePickerPlugin } from "../plugins/picker/code-picker-plugin"
61
+ import { ColumnsLayoutPickerPlugin } from "../plugins/picker/columns-layout-picker-plugin"
62
+ import { DividerPickerPlugin } from "../plugins/picker/divider-picker-plugin"
63
+ import { EmbedsPickerPlugin } from "../plugins/picker/embeds-picker-plugin"
64
+ import { HeadingPickerPlugin } from "../plugins/picker/heading-picker-plugin"
65
+ import { ImagePickerPlugin } from "../plugins/picker/image-picker-plugin"
66
+ import { NumberedListPickerPlugin } from "../plugins/picker/numbered-list-picker-plugin"
67
+ import { ParagraphPickerPlugin } from "../plugins/picker/paragraph-picker-plugin"
68
+ import { QuotePickerPlugin } from "../plugins/picker/quote-picker-plugin"
69
+ import {
70
+ DynamicTablePickerPlugin,
71
+ TablePickerPlugin,
72
+ } from "../plugins/picker/table-picker-plugin"
73
+ import { TabFocusPlugin } from "../plugins/tab-focus-plugin"
74
+ import { TableColumnResizerPlugin } from "../plugins/table-column-resizer-plugin"
75
+ import {
76
+ InsertTableCommandPlugin,
77
+ } from "../plugins/table-plugin"
78
+ import { BlockFormatDropDown } from "../plugins/toolbar/block-format-toolbar-plugin"
79
+ import { FormatBulletedList } from "../plugins/toolbar/block-format/format-bulleted-list"
80
+ import { FormatCheckList } from "../plugins/toolbar/block-format/format-check-list"
81
+ import { FormatCodeBlock } from "../plugins/toolbar/block-format/format-code-block"
82
+ import { FormatHeading } from "../plugins/toolbar/block-format/format-heading"
83
+ import { FormatListWithMarker } from "../plugins/toolbar/block-format/format-list-with-marker"
84
+ import { FormatNumberedList } from "../plugins/toolbar/block-format/format-numbered-list"
85
+ import { FormatParagraph } from "../plugins/toolbar/block-format/format-paragraph"
86
+ import { FormatQuote } from "../plugins/toolbar/block-format/format-quote"
87
+ import { BlockInsertPlugin } from "../plugins/toolbar/block-insert-plugin"
88
+ import { InsertColumnsLayout } from "../plugins/toolbar/block-insert/insert-columns-layout"
89
+ import { InsertEmbeds } from "../plugins/toolbar/block-insert/insert-embeds"
90
+ import { InsertHorizontalRule } from "../plugins/toolbar/block-insert/insert-horizontal-rule"
91
+ import { InsertImage } from "../plugins/toolbar/block-insert/insert-image"
92
+ import { InsertTable } from "../plugins/toolbar/block-insert/insert-table"
93
+ import { ClearFormattingToolbarPlugin } from "../plugins/toolbar/clear-formatting-toolbar-plugin"
94
+ import { CodeLanguageToolbarPlugin } from "../plugins/toolbar/code-language-toolbar-plugin"
95
+ import { ElementFormatToolbarPlugin } from "../plugins/toolbar/element-format-toolbar-plugin"
96
+ import { FontBackgroundToolbarPlugin } from "../plugins/toolbar/font-background-toolbar-plugin"
97
+ import { FontColorToolbarPlugin } from "../plugins/toolbar/font-color-toolbar-plugin"
98
+ import { FontFamilyToolbarPlugin } from "../plugins/toolbar/font-family-toolbar-plugin"
99
+ import { FontFormatToolbarPlugin } from "../plugins/toolbar/font-format-toolbar-plugin"
100
+ import { FontSizeToolbarPlugin } from "../plugins/toolbar/font-size-toolbar-plugin"
101
+ import { HistoryToolbarPlugin } from "../plugins/toolbar/history-toolbar-plugin"
102
+ import { LinkToolbarPlugin } from "../plugins/toolbar/link-toolbar-plugin"
103
+ import { SubSuperToolbarPlugin } from "../plugins/toolbar/subsuper-toolbar-plugin"
104
+ import { ToolbarPlugin } from "../plugins/toolbar/toolbar-plugin"
105
+ import { TypingPerfPlugin } from "../plugins/typing-pref-plugin"
106
+ import { EMOJI } from "../transformers/markdown-emoji-transformer"
107
+ import { HR } from "../transformers/markdown-hr-transformer"
108
+ import { IMAGE } from "../transformers/markdown-image-transformer"
109
+ import { TABLE } from "../transformers/markdown-table-transformer"
110
+ import { TWEET } from "../transformers/markdown-tweet-transformer"
111
+ import { UNORDERED_LIST } from "../transformers/markdown-list-transformer"
112
+ import { Separator } from "../ui/separator"
113
+
114
+ const placeholder = "Press / for commands..."
115
+
116
+ export function Plugins({
117
+ readOnly = false,
118
+ }: {
119
+ readOnly?: boolean
120
+ }) {
121
+ const [floatingAnchorElem, setFloatingAnchorElem] =
122
+ useState<HTMLDivElement | null>(null)
123
+ const [isLinkEditMode, setIsLinkEditMode] = useState<boolean>(false)
124
+
125
+ const onRef = (_floatingAnchorElem: HTMLDivElement) => {
126
+ if (_floatingAnchorElem !== null) {
127
+ setFloatingAnchorElem(_floatingAnchorElem)
128
+ }
129
+ }
130
+
131
+ return (
132
+ <div className="editor-relative-full">
133
+ {!readOnly && (
134
+ <ToolbarPlugin
135
+ className="editor-toolbar"
136
+ >
137
+ {({ blockType }) => (
138
+ <>
139
+ <div className="editor-toolbar-group">
140
+ <HistoryToolbarPlugin />
141
+ </div>
142
+ <Separator orientation="vertical" className="editor-toolbar-separator" />
143
+ <div className="editor-toolbar-group">
144
+ <BlockInsertPlugin>
145
+ <InsertHorizontalRule />
146
+ <InsertImage />
147
+ <InsertTable />
148
+ <InsertColumnsLayout />
149
+ <InsertEmbeds />
150
+ </BlockInsertPlugin>
151
+ </div>
152
+ <Separator orientation="vertical" className="editor-toolbar-separator" />
153
+ <div className="editor-toolbar-group">
154
+ <BlockFormatDropDown>
155
+ <FormatParagraph />
156
+ <FormatHeading levels={["h1", "h2", "h3"]} />
157
+ <FormatNumberedList />
158
+ <FormatListWithMarker
159
+ blockFormatValue="number-alpha"
160
+ listType="number"
161
+ markerType="alpha"
162
+ />
163
+ <FormatBulletedList />
164
+ <FormatListWithMarker
165
+ blockFormatValue="bullet-dash"
166
+ listType="bullet"
167
+ markerType="-"
168
+ />
169
+ <FormatListWithMarker
170
+ blockFormatValue="bullet-plus"
171
+ listType="bullet"
172
+ markerType="+"
173
+ />
174
+ <FormatCheckList />
175
+ <FormatCodeBlock />
176
+ <FormatQuote />
177
+ </BlockFormatDropDown>
178
+ </div>
179
+ {blockType === "code" ? (
180
+ <div className="editor-toolbar-group">
181
+ <CodeLanguageToolbarPlugin />
182
+ </div>
183
+ ) : (
184
+ <>
185
+ <div className="editor-toolbar-group">
186
+ <FontFamilyToolbarPlugin />
187
+ <FontSizeToolbarPlugin />
188
+ </div>
189
+ <Separator orientation="vertical" className="editor-toolbar-separator" />
190
+ <div className="editor-toolbar-group">
191
+ <FontFormatToolbarPlugin />
192
+ </div>
193
+ <Separator orientation="vertical" className="editor-toolbar-separator" />
194
+ <div className="editor-toolbar-group">
195
+ <SubSuperToolbarPlugin />
196
+ <LinkToolbarPlugin setIsLinkEditMode={setIsLinkEditMode} />
197
+ </div>
198
+ <Separator orientation="vertical" className="editor-toolbar-separator" />
199
+ <div className="editor-toolbar-group">
200
+ <ClearFormattingToolbarPlugin />
201
+ </div>
202
+ <Separator orientation="vertical" className="editor-toolbar-separator" />
203
+ <div className="editor-toolbar-group">
204
+ <FontColorToolbarPlugin />
205
+ <FontBackgroundToolbarPlugin />
206
+ </div>
207
+ <Separator orientation="vertical" className="editor-toolbar-separator" />
208
+ <div className="editor-toolbar-group">
209
+ <ElementFormatToolbarPlugin />
210
+ </div>
211
+ </>
212
+ )}
213
+ </>
214
+ )}
215
+ </ToolbarPlugin>
216
+ )}
217
+
218
+ <div className="editor-relative-full">
219
+ {/* AutoFocusPlugin removed to prevent auto-scroll on page load */}
220
+ <RichTextPlugin
221
+ contentEditable={
222
+ <div className="editor-relative-full">
223
+ <div className="editor-relative-full" ref={onRef}>
224
+ <ContentEditable
225
+ placeholder={readOnly ? "" : placeholder}
226
+ className={cn(
227
+ "editor-content-editable",
228
+ readOnly && "editor-content-editable--readonly"
229
+ )}
230
+ />
231
+ </div>
232
+ </div>
233
+ }
234
+ ErrorBoundary={LexicalErrorBoundary}
235
+ placeholder={readOnly ? null : <div className="editor-placeholder">{placeholder}</div>}
236
+ />
237
+
238
+ <ClickableLinkPlugin />
239
+ {!readOnly && (
240
+ <>
241
+ <CheckListPlugin />
242
+ <AlignPlugin />
243
+ <HorizontalRulePlugin />
244
+ <TablePlugin hasHorizontalScroll={true} />
245
+ <InsertTableCommandPlugin />
246
+ <TableColumnResizerPlugin />
247
+ <ListPlugin />
248
+ <TabIndentationPlugin />
249
+ <HistoryPlugin />
250
+ </>
251
+ )}
252
+ <HashtagPlugin />
253
+
254
+ <MentionsPlugin />
255
+ {!readOnly && <DraggableBlockPlugin anchorElem={floatingAnchorElem} />}
256
+ <KeywordsPlugin />
257
+ <EmojisPlugin />
258
+ {!readOnly && <ImagesPlugin />}
259
+
260
+ {!readOnly && <LayoutPlugin />}
261
+ {!readOnly && <ListColorPlugin />}
262
+
263
+ {!readOnly && (
264
+ <>
265
+ <AutoEmbedPlugin />
266
+ <TwitterPlugin />
267
+ <YouTubePlugin />
268
+ </>
269
+ )}
270
+
271
+ <CodeHighlightPlugin />
272
+ {!readOnly && <CodeActionMenuPlugin anchorElem={floatingAnchorElem} />}
273
+
274
+ {!readOnly && (
275
+ <>
276
+ <MarkdownShortcutPlugin
277
+ transformers={[
278
+ TABLE,
279
+ HR,
280
+ IMAGE,
281
+ EMOJI,
282
+ TWEET,
283
+ CHECK_LIST,
284
+ UNORDERED_LIST,
285
+ ...ELEMENT_TRANSFORMERS.filter(
286
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
287
+ (t) => t.type !== "element" || (t as any).regExp?.toString().indexOf("[*+-]") === -1
288
+ ),
289
+ ...MULTILINE_ELEMENT_TRANSFORMERS,
290
+ ...TEXT_FORMAT_TRANSFORMERS,
291
+ ...TEXT_MATCH_TRANSFORMERS,
292
+ ]}
293
+ />
294
+ <TypingPerfPlugin />
295
+ <TabFocusPlugin />
296
+ <AutocompletePlugin />
297
+ <AutoLinkPlugin />
298
+ <LinkPlugin />
299
+ <ClearEditorPlugin />
300
+
301
+ <ComponentPickerMenuPlugin
302
+ baseOptions={[
303
+ ParagraphPickerPlugin(),
304
+ HeadingPickerPlugin({ n: 1 }),
305
+ HeadingPickerPlugin({ n: 2 }),
306
+ HeadingPickerPlugin({ n: 3 }),
307
+ TablePickerPlugin(),
308
+ CheckListPickerPlugin(),
309
+ NumberedListPickerPlugin(),
310
+ BulletedListPickerPlugin(),
311
+ QuotePickerPlugin(),
312
+ CodePickerPlugin(),
313
+ DividerPickerPlugin(),
314
+ EmbedsPickerPlugin({ embed: "tweet" }),
315
+ EmbedsPickerPlugin({ embed: "youtube-video" }),
316
+ ImagePickerPlugin(),
317
+ ColumnsLayoutPickerPlugin(),
318
+ AlignmentPickerPlugin({ alignment: "left" }),
319
+ AlignmentPickerPlugin({ alignment: "center" }),
320
+ AlignmentPickerPlugin({ alignment: "right" }),
321
+ AlignmentPickerPlugin({ alignment: "justify" }),
322
+ ]}
323
+ dynamicOptionsFn={DynamicTablePickerPlugin}
324
+ />
325
+
326
+ <ContextMenuPlugin />
327
+ <DragDropPastePlugin />
328
+ <EmojiPickerPlugin />
329
+
330
+ <FloatingLinkEditorPlugin
331
+ anchorElem={floatingAnchorElem}
332
+ isLinkEditMode={isLinkEditMode}
333
+ setIsLinkEditMode={setIsLinkEditMode}
334
+ />
335
+ <FloatingTextFormatToolbarPlugin
336
+ anchorElem={floatingAnchorElem}
337
+ setIsLinkEditMode={setIsLinkEditMode}
338
+ />
339
+
340
+ <ListMaxIndentLevelPlugin />
341
+ </>
342
+ )}
343
+ </div>
344
+ {!readOnly && (
345
+ <ActionsPlugin>
346
+ <div className="editor-actions-bar">
347
+ <div className="editor-flex-shrink-0">
348
+ <CounterCharacterPlugin charset="UTF-16" />
349
+ </div>
350
+ <div className="editor-flex-1" />
351
+ <div className="editor-flex-shrink-0 editor-flex-end">
352
+ <SpeechToTextPlugin />
353
+ <ShareContentPlugin />
354
+ <ImportExportPlugin />
355
+ <MarkdownTogglePlugin
356
+ shouldPreserveNewLinesInMarkdown={true}
357
+ transformers={[
358
+ TABLE,
359
+ HR,
360
+ IMAGE,
361
+ EMOJI,
362
+ TWEET,
363
+ CHECK_LIST,
364
+ UNORDERED_LIST,
365
+ ...ELEMENT_TRANSFORMERS,
366
+ ...MULTILINE_ELEMENT_TRANSFORMERS,
367
+ ...TEXT_FORMAT_TRANSFORMERS,
368
+ ...TEXT_MATCH_TRANSFORMERS,
369
+ ]}
370
+ />
371
+ <EditModeTogglePlugin />
372
+ <ClearEditorActionPlugin />
373
+ <TreeViewPlugin />
374
+ </div>
375
+ </div>
376
+ </ActionsPlugin>
377
+ )}
378
+ </div>
379
+ )
380
+ }
@@ -0,0 +1,27 @@
1
+ import { useEffect, useRef } from "react"
2
+
3
+ export function useClickOutside<T extends HTMLElement>(
4
+ handler: (event: MouseEvent | TouchEvent) => void
5
+ ) {
6
+ const ref = useRef<T>(null)
7
+
8
+ useEffect(() => {
9
+ const listener = (event: MouseEvent | TouchEvent) => {
10
+ const el = ref.current
11
+ if (!el || el.contains(event.target as Node)) {
12
+ return
13
+ }
14
+ handler(event)
15
+ }
16
+
17
+ document.addEventListener("mousedown", listener)
18
+ document.addEventListener("touchstart", listener)
19
+
20
+ return () => {
21
+ document.removeEventListener("mousedown", listener)
22
+ document.removeEventListener("touchstart", listener)
23
+ }
24
+ }, [handler])
25
+
26
+ return ref
27
+ }
@@ -0,0 +1,54 @@
1
+ import { useCallback, useState } from "react"
2
+ import { useIsomorphicLayoutEffect } from "./use-isomorphic-layout-effect"
3
+
4
+ interface Size {
5
+ width: number
6
+ height: number
7
+ }
8
+
9
+ export function useElementSize<T extends HTMLElement = HTMLDivElement>() {
10
+ // Mutable values like 'ref.current' aren't valid dependencies
11
+ // instead we manage the current node in state
12
+ const [node, setNode] = useState<T | null>(null)
13
+ const [size, setSize] = useState<Size>({
14
+ width: 0,
15
+ height: 0,
16
+ })
17
+
18
+ // Prevent too many renders with useCallback
19
+ const handleSize = useCallback(() => {
20
+ if (!node) return
21
+
22
+ setSize({
23
+ width: node.offsetWidth || 0,
24
+ height: node.offsetHeight || 0,
25
+ })
26
+ }, [node])
27
+
28
+ useIsomorphicLayoutEffect(() => {
29
+ if (!node) {
30
+ return
31
+ }
32
+
33
+ handleSize()
34
+
35
+ // Use ResizeObserver for better accuracy and performance
36
+ if (typeof ResizeObserver !== 'undefined') {
37
+ const observer = new ResizeObserver(() => {
38
+ handleSize()
39
+ })
40
+
41
+ observer.observe(node)
42
+
43
+ return () => {
44
+ observer.disconnect()
45
+ }
46
+ }
47
+
48
+ // Fallback to resize event
49
+ window.addEventListener("resize", handleSize)
50
+ return () => window.removeEventListener("resize", handleSize)
51
+ }, [node, handleSize])
52
+
53
+ return { ref: setNode, ...size }
54
+ }
@@ -0,0 +1,95 @@
1
+ import { useEffect, useState, useCallback } from "react"
2
+ import { useElementSize } from "./use-element-size"
3
+
4
+ const PUBLIC_HEADER_SELECTOR = 'header[data-public-header="true"]'
5
+ const ADMIN_HEADER_SELECTOR = 'header[data-admin-header="true"]'
6
+
7
+ /**
8
+ * Hook để đo chiều cao của header động trong editor
9
+ */
10
+ export function useHeaderHeight() {
11
+ const { ref: headerRef, height: headerHeightValue } = useElementSize<HTMLElement>()
12
+ const [headerHeight, setHeaderHeight] = useState(0)
13
+
14
+ const findAndSetHeader = useCallback(() => {
15
+ if (typeof document === "undefined") {
16
+ return null
17
+ }
18
+
19
+ // Ưu tiên tìm header với data-admin-header="true" hoặc data-public-header="true"
20
+ let header = document.querySelector<HTMLElement>(ADMIN_HEADER_SELECTOR) ||
21
+ document.querySelector<HTMLElement>(PUBLIC_HEADER_SELECTOR)
22
+
23
+ // Fallback: đo header bằng cách tìm element header đầu tiên
24
+ if (!header) {
25
+ header = document.querySelector<HTMLElement>("header")
26
+ }
27
+
28
+ if (header) {
29
+ headerRef(header)
30
+ return header
31
+ }
32
+
33
+ return null
34
+ }, [headerRef])
35
+
36
+ useEffect(() => {
37
+ if (typeof document === "undefined") {
38
+ return
39
+ }
40
+
41
+ // Tìm header ngay lập tức
42
+ findAndSetHeader()
43
+
44
+ // Retry tìm header nếu chưa tìm thấy (có thể do SSR hoặc component chưa mount)
45
+ let retryCount = 0
46
+ const maxRetries = 10
47
+ const retryInterval = 100
48
+
49
+ const retryTimer = setInterval(() => {
50
+ const header = findAndSetHeader()
51
+ if (header || retryCount >= maxRetries) {
52
+ clearInterval(retryTimer)
53
+ }
54
+ retryCount++
55
+ }, retryInterval)
56
+
57
+ // Sử dụng MutationObserver để theo dõi khi header được thêm vào DOM
58
+ const observer = new MutationObserver(() => {
59
+ findAndSetHeader()
60
+ })
61
+
62
+ observer.observe(document.body, {
63
+ childList: true,
64
+ subtree: true,
65
+ })
66
+
67
+ return () => {
68
+ clearInterval(retryTimer)
69
+ observer.disconnect()
70
+ headerRef(null)
71
+ }
72
+ }, [findAndSetHeader, headerRef])
73
+
74
+ useEffect(() => {
75
+ // Cập nhật headerHeight khi headerSize.height thay đổi
76
+ if (headerHeightValue > 0) {
77
+ setHeaderHeight(headerHeightValue)
78
+ } else {
79
+ // Nếu chưa có height, thử đo lại bằng getBoundingClientRect
80
+ if (typeof document !== "undefined") {
81
+ const header = document.querySelector<HTMLElement>(ADMIN_HEADER_SELECTOR) ||
82
+ document.querySelector<HTMLElement>(PUBLIC_HEADER_SELECTOR) ||
83
+ document.querySelector<HTMLElement>("header")
84
+ if (header) {
85
+ const rect = header.getBoundingClientRect()
86
+ if (rect.height > 0) {
87
+ setHeaderHeight(rect.height)
88
+ }
89
+ }
90
+ }
91
+ }
92
+ }, [headerHeightValue])
93
+
94
+ return { headerHeight }
95
+ }
@@ -0,0 +1,4 @@
1
+ import { useEffect, useLayoutEffect } from 'react'
2
+
3
+ export const useIsomorphicLayoutEffect =
4
+ typeof window !== 'undefined' ? useLayoutEffect : useEffect
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ "use client"
2
+ export * from "./editor-x/editor"
3
+ export * from "./components/lexical-editor"
4
+ export type { SerializedEditorState } from "lexical"
@@ -0,0 +1,6 @@
1
+ export const logger = {
2
+ debug: console.debug,
3
+ info: console.info,
4
+ warn: console.warn,
5
+ error: console.error,
6
+ }
@@ -0,0 +1,19 @@
1
+ export function cn(...inputs: (string | undefined | null | boolean | Record<string, boolean>)[]): string {
2
+ const classes: string[] = []
3
+
4
+ for (const input of inputs) {
5
+ if (!input) continue
6
+
7
+ if (typeof input === "string") {
8
+ classes.push(input)
9
+ } else if (typeof input === "object") {
10
+ for (const key in input) {
11
+ if (input[key]) {
12
+ classes.push(key)
13
+ }
14
+ }
15
+ }
16
+ }
17
+
18
+ return classes.join(" ")
19
+ }