@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,94 @@
1
+ import type {
2
+ DOMExportOutput,
3
+ EditorConfig,
4
+ LexicalEditor,
5
+ NodeKey,
6
+ SerializedTextNode,
7
+ Spread,
8
+ } from "lexical"
9
+ import { TextNode } from "lexical"
10
+
11
+ import { uuid as UUID } from "../plugins/autocomplete-plugin"
12
+
13
+ export type SerializedAutocompleteNode = Spread<
14
+ {
15
+ uuid: string
16
+ },
17
+ SerializedTextNode
18
+ >
19
+
20
+ export class AutocompleteNode extends TextNode {
21
+ /**
22
+ * A unique uuid is generated for each session and assigned to the instance.
23
+ * This helps to:
24
+ * - Ensures max one Autocomplete node per session.
25
+ * - Ensure that when collaboration is enabled, this node is not shown in
26
+ * other sessions.
27
+ * See https://github.com/facebook/lexical/blob/main/packages/lexical-playground/src/plugins/AutocompletePlugin/index.tsx
28
+ */
29
+ __uuid: string
30
+
31
+ static clone(node: AutocompleteNode): AutocompleteNode {
32
+ return new AutocompleteNode(node.__text, node.__uuid, node.__key)
33
+ }
34
+
35
+ static getType(): "autocomplete" {
36
+ return "autocomplete"
37
+ }
38
+
39
+ static importDOM() {
40
+ // Never import from DOM
41
+ return null
42
+ }
43
+
44
+ static importJSON(
45
+ serializedNode: SerializedAutocompleteNode
46
+ ): AutocompleteNode {
47
+ return $createAutocompleteNode(
48
+ serializedNode.text,
49
+ serializedNode.uuid
50
+ ).updateFromJSON(serializedNode)
51
+ }
52
+
53
+ exportJSON(): SerializedAutocompleteNode {
54
+ return {
55
+ ...super.exportJSON(),
56
+ uuid: this.__uuid,
57
+ }
58
+ }
59
+
60
+ constructor(text: string, uuid: string, key?: NodeKey) {
61
+ super(text, key)
62
+ this.__uuid = uuid
63
+ }
64
+
65
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
66
+ updateDOM(prevNode: this, dom: HTMLElement, config: EditorConfig): boolean {
67
+ return false
68
+ }
69
+
70
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars -- Lexical exportDOM signature
71
+ exportDOM(_editor: LexicalEditor): DOMExportOutput {
72
+ return { element: null }
73
+ }
74
+
75
+ excludeFromCopy() {
76
+ return true
77
+ }
78
+
79
+ createDOM(config: EditorConfig): HTMLElement {
80
+ const dom = super.createDOM(config)
81
+ dom.classList.add(config.theme.autocomplete)
82
+ if (this.__uuid !== UUID) {
83
+ dom.style.display = "none"
84
+ }
85
+ return dom
86
+ }
87
+ }
88
+
89
+ export function $createAutocompleteNode(
90
+ text: string,
91
+ uuid: string
92
+ ): AutocompleteNode {
93
+ return new AutocompleteNode(text, uuid).setMode("token")
94
+ }
@@ -0,0 +1,224 @@
1
+ "use client"
2
+ import * as React from "react"
3
+ import { JSX, useCallback, useEffect, useRef, useState } from "react"
4
+ import { BlockWithAlignableContents } from "@lexical/react/LexicalBlockWithAlignableContents"
5
+ import {
6
+ DecoratorBlockNode,
7
+ SerializedDecoratorBlockNode,
8
+ } from "@lexical/react/LexicalDecoratorBlockNode"
9
+ import type {
10
+ DOMConversionMap,
11
+ DOMConversionOutput,
12
+ DOMExportOutput,
13
+ EditorConfig,
14
+ ElementFormatType,
15
+ LexicalEditor,
16
+ LexicalNode,
17
+ NodeKey,
18
+ Spread,
19
+ } from "lexical"
20
+
21
+ const WIDGET_SCRIPT_URL = "https://platform.twitter.com/widgets.js"
22
+
23
+ type TweetComponentProps = Readonly<{
24
+ className: Readonly<{
25
+ base: string
26
+ focus: string
27
+ }>
28
+ format: ElementFormatType | null
29
+ loadingComponent?: JSX.Element | string
30
+ nodeKey: NodeKey
31
+ onError?: (error: string) => void
32
+ onLoad?: () => void
33
+ tweetID: string
34
+ }>
35
+
36
+ function $convertTweetElement(
37
+ domNode: HTMLDivElement
38
+ ): DOMConversionOutput | null {
39
+ const id = domNode.getAttribute("data-lexical-tweet-id")
40
+ if (id) {
41
+ const node = $createTweetNode(id)
42
+ return { node }
43
+ }
44
+ return null
45
+ }
46
+
47
+ let isTwitterScriptLoading = true
48
+
49
+ function TweetComponent({
50
+ className,
51
+ format,
52
+ loadingComponent,
53
+ nodeKey,
54
+ onError,
55
+ onLoad,
56
+ tweetID,
57
+ }: TweetComponentProps) {
58
+ const containerRef = useRef<HTMLDivElement | null>(null)
59
+
60
+ const previousTweetIDRef = useRef<string>("")
61
+ const [isTweetLoading, setIsTweetLoading] = useState(false)
62
+
63
+ const createTweet = useCallback(async () => {
64
+ try {
65
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
66
+ await (window as any).twttr.widgets.createTweet(tweetID, containerRef.current)
67
+
68
+ setIsTweetLoading(false)
69
+ isTwitterScriptLoading = false
70
+
71
+ if (onLoad) {
72
+ onLoad()
73
+ }
74
+ } catch (error) {
75
+ if (onError) {
76
+ onError(String(error))
77
+ }
78
+ }
79
+ }, [onError, onLoad, tweetID])
80
+
81
+ useEffect(() => {
82
+ if (tweetID !== previousTweetIDRef.current) {
83
+ // Use setTimeout to avoid calling setState synchronously within effect
84
+ setTimeout(() => {
85
+ setIsTweetLoading(true)
86
+ }, 0)
87
+
88
+ if (isTwitterScriptLoading) {
89
+ const script = document.createElement("script")
90
+ script.src = WIDGET_SCRIPT_URL
91
+ script.async = true
92
+ document.body?.appendChild(script)
93
+ script.onload = createTweet
94
+ if (onError) {
95
+ script.onerror = onError as OnErrorEventHandler
96
+ }
97
+ } else {
98
+ // Use setTimeout to avoid calling setState synchronously within effect
99
+ setTimeout(() => {
100
+ createTweet()
101
+ }, 0)
102
+ }
103
+
104
+ if (previousTweetIDRef) {
105
+ previousTweetIDRef.current = tweetID
106
+ }
107
+ }
108
+ }, [createTweet, onError, tweetID])
109
+
110
+ return (
111
+ <BlockWithAlignableContents
112
+ className={className}
113
+ format={format}
114
+ nodeKey={nodeKey}
115
+ >
116
+ {isTweetLoading ? loadingComponent : null}
117
+ <div
118
+ className="editor-tweet-container"
119
+ ref={containerRef}
120
+ />
121
+ </BlockWithAlignableContents>
122
+ )
123
+ }
124
+
125
+ export type SerializedTweetNode = Spread<
126
+ {
127
+ id: string
128
+ },
129
+ SerializedDecoratorBlockNode
130
+ >
131
+
132
+ export class TweetNode extends DecoratorBlockNode {
133
+ __id: string
134
+
135
+ static getType(): string {
136
+ return "tweet"
137
+ }
138
+
139
+ static clone(node: TweetNode): TweetNode {
140
+ return new TweetNode(node.__id, node.__format, node.__key)
141
+ }
142
+
143
+ static importJSON(serializedNode: SerializedTweetNode): TweetNode {
144
+ const node = $createTweetNode(serializedNode.id)
145
+ node.setFormat(serializedNode.format)
146
+ return node
147
+ }
148
+
149
+ exportJSON(): SerializedTweetNode {
150
+ return {
151
+ ...super.exportJSON(),
152
+ id: this.getId(),
153
+ type: "tweet",
154
+ version: 1,
155
+ }
156
+ }
157
+
158
+ static importDOM(): DOMConversionMap<HTMLDivElement> | null {
159
+ return {
160
+ div: (domNode: HTMLDivElement) => {
161
+ if (!domNode.hasAttribute("data-lexical-tweet-id")) {
162
+ return null
163
+ }
164
+ return {
165
+ conversion: $convertTweetElement,
166
+ priority: 2,
167
+ }
168
+ },
169
+ }
170
+ }
171
+
172
+ exportDOM(): DOMExportOutput {
173
+ const element = document.createElement("div")
174
+ element.setAttribute("data-lexical-tweet-id", this.__id)
175
+ const text = document.createTextNode(this.getTextContent())
176
+ element.append(text)
177
+ return { element }
178
+ }
179
+
180
+ constructor(id: string, format?: ElementFormatType, key?: NodeKey) {
181
+ super(format, key)
182
+ this.__id = id
183
+ }
184
+
185
+ getId(): string {
186
+ return this.__id
187
+ }
188
+
189
+ getTextContent(
190
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars -- Lexical override signature
191
+ _includeInert?: boolean | undefined,
192
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars -- Lexical override signature
193
+ _includeDirectionless?: false | undefined
194
+ ): string {
195
+ return `https://x.com/i/web/status/${this.__id}`
196
+ }
197
+
198
+ decorate(editor: LexicalEditor, config: EditorConfig): JSX.Element {
199
+ const embedBlockTheme = config.theme.embedBlock || {}
200
+ const className = {
201
+ base: embedBlockTheme.base || "",
202
+ focus: embedBlockTheme.focus || "",
203
+ }
204
+ return (
205
+ <TweetComponent
206
+ className={className}
207
+ format={this.__format}
208
+ loadingComponent="Loading..."
209
+ nodeKey={this.getKey()}
210
+ tweetID={this.__id}
211
+ />
212
+ )
213
+ }
214
+ }
215
+
216
+ export function $createTweetNode(tweetID: string): TweetNode {
217
+ return new TweetNode(tweetID)
218
+ }
219
+
220
+ export function $isTweetNode(
221
+ node: TweetNode | LexicalNode | null | undefined
222
+ ): node is TweetNode {
223
+ return node instanceof TweetNode
224
+ }