@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,20 @@
1
+ import { TextMatchTransformer } from "@lexical/markdown"
2
+ import { $createTextNode } from "lexical"
3
+
4
+ import emojiList from "../utils/emoji-list"
5
+
6
+ export const EMOJI: TextMatchTransformer = {
7
+ dependencies: [],
8
+ export: () => null,
9
+ importRegExp: /:([a-z0-9_]+):/,
10
+ regExp: /:([a-z0-9_]+):/,
11
+ replace: (textNode, [, name]) => {
12
+ if (!name) return
13
+ const emoji = emojiList.find((e) => e.aliases.includes(name))?.emoji
14
+ if (emoji) {
15
+ textNode.replace($createTextNode(emoji))
16
+ }
17
+ },
18
+ trigger: ":",
19
+ type: "text-match",
20
+ }
@@ -0,0 +1,28 @@
1
+ import { ElementTransformer } from "@lexical/markdown"
2
+ import {
3
+ $createHorizontalRuleNode,
4
+ $isHorizontalRuleNode,
5
+ HorizontalRuleNode,
6
+ } from "@lexical/react/LexicalHorizontalRuleNode"
7
+ import { LexicalNode } from "lexical"
8
+
9
+ export const HR: ElementTransformer = {
10
+ dependencies: [HorizontalRuleNode],
11
+ export: (node: LexicalNode) => {
12
+ return $isHorizontalRuleNode(node) ? "***" : null
13
+ },
14
+ regExp: /^(---|\*\*\*|___)\s?$/,
15
+ replace: (parentNode, _1, _2, isImport) => {
16
+ const line = $createHorizontalRuleNode()
17
+
18
+ // TODO: Get rid of isImport flag
19
+ if (isImport || parentNode.getNextSibling() != null) {
20
+ parentNode.replace(line)
21
+ } else {
22
+ parentNode.insertBefore(line)
23
+ }
24
+
25
+ line.selectNext()
26
+ },
27
+ type: "element",
28
+ }
@@ -0,0 +1,31 @@
1
+ import { TextMatchTransformer } from "@lexical/markdown"
2
+
3
+ import {
4
+ $createImageNode,
5
+ $isImageNode,
6
+ ImageNode,
7
+ } from "../nodes/image-node"
8
+
9
+ export const IMAGE: TextMatchTransformer = {
10
+ dependencies: [ImageNode],
11
+ export: (node) => {
12
+ if (!$isImageNode(node)) {
13
+ return null
14
+ }
15
+
16
+ return `![${node.getAltText()}](${node.getSrc()})`
17
+ },
18
+ importRegExp: /!(?:\[([^[]*)\])(?:\(([^(]+)\))/,
19
+ regExp: /!(?:\[([^[]*)\])(?:\(([^(]+)\))$/,
20
+ replace: (textNode, match) => {
21
+ const [, altText, src] = match
22
+ const imageNode = $createImageNode({
23
+ altText: altText || "",
24
+ maxWidth: 800,
25
+ src: src || "",
26
+ })
27
+ textNode.replace(imageNode)
28
+ },
29
+ trigger: ")",
30
+ type: "text-match",
31
+ }
@@ -0,0 +1,51 @@
1
+ import {
2
+ $createListItemNode,
3
+ $isListItemNode,
4
+ $isListNode,
5
+ ListItemNode,
6
+ ListNode,
7
+ } from "@lexical/list"
8
+ import { ElementTransformer } from "@lexical/markdown"
9
+
10
+ import {
11
+ $createListWithColorNode,
12
+ $isListWithColorNode,
13
+ ListWithColorNode,
14
+ } from "../nodes/list-with-color-node"
15
+
16
+ export const UNORDERED_LIST: ElementTransformer = {
17
+ dependencies: [ListWithColorNode, ListNode, ListItemNode],
18
+ export: (node) => {
19
+ if (!$isListNode(node) || node.getListType() !== "bullet") {
20
+ return null
21
+ }
22
+ const marker = $isListWithColorNode(node) ? node.getMarkerType() || "-" : "-"
23
+ const children = node.getChildren()
24
+ const output = []
25
+ for (const child of children) {
26
+ if ($isListItemNode(child)) {
27
+ output.push(`${marker} ${child.getTextContent()}`)
28
+ }
29
+ }
30
+ return output.join("\n")
31
+ },
32
+ regExp: /^(\s*)([*+-])\s/,
33
+ replace: (parentNode, _children, match, isImport) => {
34
+ const marker = match[2]
35
+ const list = $createListWithColorNode("bullet")
36
+
37
+ if (marker === "-" || marker === "+") {
38
+ list.setMarkerType(marker)
39
+ }
40
+
41
+ if (isImport || parentNode.getNextSibling() != null) {
42
+ parentNode.replace(list)
43
+ } else {
44
+ parentNode.insertBefore(list)
45
+ }
46
+ const item = $createListItemNode()
47
+ list.append(item)
48
+ item.select()
49
+ },
50
+ type: "element",
51
+ }
@@ -0,0 +1,200 @@
1
+ import {
2
+ $convertFromMarkdownString,
3
+ $convertToMarkdownString,
4
+ CHECK_LIST,
5
+ ELEMENT_TRANSFORMERS,
6
+ ElementTransformer,
7
+ MULTILINE_ELEMENT_TRANSFORMERS,
8
+ TEXT_FORMAT_TRANSFORMERS,
9
+ TEXT_MATCH_TRANSFORMERS,
10
+ } from "@lexical/markdown"
11
+ import {
12
+ $createTableCellNode,
13
+ $createTableNode,
14
+ $createTableRowNode,
15
+ $isTableCellNode,
16
+ $isTableNode,
17
+ $isTableRowNode,
18
+ TableCellHeaderStates,
19
+ TableCellNode,
20
+ TableNode,
21
+ TableRowNode,
22
+ } from "@lexical/table"
23
+ import { $isParagraphNode, $isTextNode, LexicalNode } from "lexical"
24
+
25
+ import { HR } from "../transformers/markdown-hr-transformer"
26
+ import { IMAGE } from "../transformers/markdown-image-transformer"
27
+
28
+ // Very primitive table setup
29
+ const TABLE_ROW_REG_EXP = /^(?:\|)(.+)(?:\|)\s?$/
30
+ const TABLE_ROW_DIVIDER_REG_EXP = /^(\| ?:?-*:? ?)+\|\s?$/
31
+
32
+ const OTHER_MARKDOWN_TRANSFORMERS = [
33
+ HR,
34
+ IMAGE,
35
+ // EMOJI,
36
+ // TWEET,
37
+ CHECK_LIST,
38
+ ...ELEMENT_TRANSFORMERS,
39
+ ...MULTILINE_ELEMENT_TRANSFORMERS,
40
+ ...TEXT_FORMAT_TRANSFORMERS,
41
+ ...TEXT_MATCH_TRANSFORMERS,
42
+ ]
43
+
44
+ export const TABLE: ElementTransformer = {
45
+ dependencies: [TableNode, TableRowNode, TableCellNode],
46
+ export: (node: LexicalNode) => {
47
+ if (!$isTableNode(node)) {
48
+ return null
49
+ }
50
+
51
+ const output: string[] = []
52
+
53
+ for (const row of node.getChildren()) {
54
+ const rowOutput = []
55
+ if (!$isTableRowNode(row)) {
56
+ continue
57
+ }
58
+
59
+ let isHeaderRow = false
60
+ for (const cell of row.getChildren()) {
61
+ // It's TableCellNode so it's just to make flow happy
62
+ if ($isTableCellNode(cell)) {
63
+ rowOutput.push(
64
+ $convertToMarkdownString(OTHER_MARKDOWN_TRANSFORMERS, cell).replace(
65
+ /\n/g,
66
+ "\\n"
67
+ )
68
+ )
69
+ if (cell.__headerState === TableCellHeaderStates.ROW) {
70
+ isHeaderRow = true
71
+ }
72
+ }
73
+ }
74
+
75
+ output.push(`| ${rowOutput.join(" | ")} |`)
76
+ if (isHeaderRow) {
77
+ output.push(`| ${rowOutput.map(() => "---").join(" | ")} |`)
78
+ }
79
+ }
80
+
81
+ return output.join("\n")
82
+ },
83
+ regExp: TABLE_ROW_REG_EXP,
84
+ replace: (parentNode, _1, match) => {
85
+ if (!match || !match[0]) return
86
+
87
+ // Header row
88
+ if (TABLE_ROW_DIVIDER_REG_EXP.test(match[0])) {
89
+ const table = parentNode.getPreviousSibling()
90
+ if (!table || !$isTableNode(table)) {
91
+ return
92
+ }
93
+
94
+ const rows = table.getChildren()
95
+ const lastRow = rows[rows.length - 1]
96
+ if (!lastRow || !$isTableRowNode(lastRow)) {
97
+ return
98
+ }
99
+
100
+ // Add header state to row cells
101
+ lastRow.getChildren().forEach((cell) => {
102
+ if (!$isTableCellNode(cell)) {
103
+ return
104
+ }
105
+ cell.setHeaderStyles(
106
+ TableCellHeaderStates.ROW,
107
+ TableCellHeaderStates.ROW
108
+ )
109
+ })
110
+
111
+ // Remove line
112
+ parentNode.remove()
113
+ return
114
+ }
115
+
116
+ const matchCells = mapToTableCells(match[0])
117
+
118
+ if (matchCells == null) {
119
+ return
120
+ }
121
+
122
+ const rows = [matchCells]
123
+ let sibling = parentNode.getPreviousSibling()
124
+ let maxCells = matchCells.length
125
+
126
+ while (sibling) {
127
+ if (!$isParagraphNode(sibling)) {
128
+ break
129
+ }
130
+
131
+ if (sibling.getChildrenSize() !== 1) {
132
+ break
133
+ }
134
+
135
+ const firstChild = sibling.getFirstChild()
136
+
137
+ if (!$isTextNode(firstChild)) {
138
+ break
139
+ }
140
+
141
+ const cells = mapToTableCells(firstChild.getTextContent())
142
+
143
+ if (cells == null) {
144
+ break
145
+ }
146
+
147
+ maxCells = Math.max(maxCells, cells.length)
148
+ rows.unshift(cells)
149
+ const previousSibling = sibling.getPreviousSibling()
150
+ sibling.remove()
151
+ sibling = previousSibling
152
+ }
153
+
154
+ const table = $createTableNode()
155
+
156
+ for (const cells of rows) {
157
+ const tableRow = $createTableRowNode()
158
+ table.append(tableRow)
159
+
160
+ for (let i = 0; i < maxCells; i++) {
161
+ const cell = cells[i] || $createTableCell("")
162
+ tableRow.append(cell)
163
+ }
164
+ }
165
+
166
+ const previousSibling = parentNode.getPreviousSibling()
167
+ if (
168
+ $isTableNode(previousSibling) &&
169
+ getTableColumnsSize(previousSibling) === maxCells
170
+ ) {
171
+ previousSibling.append(...table.getChildren())
172
+ parentNode.remove()
173
+ } else {
174
+ parentNode.replace(table)
175
+ }
176
+
177
+ table.selectEnd()
178
+ },
179
+ type: "element",
180
+ }
181
+
182
+ function getTableColumnsSize(table: TableNode) {
183
+ const row = table.getFirstChild()
184
+ return $isTableRowNode(row) ? row.getChildrenSize() : 0
185
+ }
186
+
187
+ const $createTableCell = (textContent: string): TableCellNode => {
188
+ textContent = textContent.replace(/\\n/g, "\n")
189
+ const cell = $createTableCellNode(TableCellHeaderStates.NO_STATUS)
190
+ $convertFromMarkdownString(textContent, OTHER_MARKDOWN_TRANSFORMERS, cell)
191
+ return cell
192
+ }
193
+
194
+ const mapToTableCells = (textContent: string): Array<TableCellNode> | null => {
195
+ const match = textContent.match(TABLE_ROW_REG_EXP)
196
+ if (!match || !match[1]) {
197
+ return null
198
+ }
199
+ return match[1].split("|").map((text) => $createTableCell(text))
200
+ }
@@ -0,0 +1,26 @@
1
+ import { ElementTransformer } from "@lexical/markdown"
2
+
3
+ import {
4
+ $createTweetNode,
5
+ $isTweetNode,
6
+ TweetNode,
7
+ } from "../nodes/embeds/tweet-node"
8
+
9
+ export const TWEET: ElementTransformer = {
10
+ dependencies: [TweetNode],
11
+ export: (node) => {
12
+ if (!$isTweetNode(node)) {
13
+ return null
14
+ }
15
+
16
+ return `<tweet id="${node.getId()}" />`
17
+ },
18
+ regExp: /<tweet id="([^"]+?)"\s?\/>\s?$/,
19
+ replace: (textNode, _1, match) => {
20
+ const [, id] = match
21
+ if (!id) return
22
+ const tweetNode = $createTweetNode(id)
23
+ textNode.replace(tweetNode)
24
+ },
25
+ type: "element",
26
+ }
@@ -0,0 +1,10 @@
1
+ import * as React from "react"
2
+ import { cn } from "../lib/utils"
3
+
4
+ export function ButtonGroup({ className, children, ...props }: React.HTMLAttributes<HTMLDivElement>) {
5
+ return (
6
+ <div className={cn("editor-button-group", className)} {...props}>
7
+ {children}
8
+ </div>
9
+ )
10
+ }
@@ -0,0 +1,29 @@
1
+ import * as React from "react"
2
+ import { cn } from "../lib/utils"
3
+
4
+ export interface ButtonProps
5
+ extends React.ButtonHTMLAttributes<HTMLButtonElement> {
6
+ variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link"
7
+ size?: "default" | "sm" | "md" | "lg" | "icon"
8
+ }
9
+
10
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
11
+ ({ className, variant = "default", size = "default", type = "button", ...props }, ref) => {
12
+ return (
13
+ <button
14
+ type={type}
15
+ className={cn(
16
+ "editor-btn",
17
+ variant !== "default" && `editor-btn--${variant}`,
18
+ size !== "default" && `editor-btn--size-${size}`,
19
+ className
20
+ )}
21
+ ref={ref}
22
+ {...props}
23
+ />
24
+ )
25
+ }
26
+ )
27
+ Button.displayName = "Button"
28
+
29
+ export { Button }
@@ -0,0 +1,67 @@
1
+ import * as React from "react"
2
+ import { cn } from "../lib/utils"
3
+
4
+ const CollapsibleContext = React.createContext<{
5
+ open: boolean
6
+ setOpen: (open: boolean) => void
7
+ } | null>(null)
8
+
9
+ export function Collapsible({
10
+ children,
11
+ open: controlledOpen,
12
+ onOpenChange,
13
+ className,
14
+ ...props
15
+ }: {
16
+ children: React.ReactNode
17
+ open?: boolean
18
+ onOpenChange?: (open: boolean) => void
19
+ className?: string
20
+ }) {
21
+ const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false)
22
+ const open = controlledOpen ?? uncontrolledOpen
23
+ const setOpen = onOpenChange ?? setUncontrolledOpen
24
+
25
+ return (
26
+ <CollapsibleContext.Provider value={{ open, setOpen }}>
27
+ <div className={cn("editor-collapsible", className)} data-state={open ? "open" : "closed"} {...props}>
28
+ {children}
29
+ </div>
30
+ </CollapsibleContext.Provider>
31
+ )
32
+ }
33
+
34
+ export function CollapsibleTrigger({ children, asChild, ...props }: React.HTMLAttributes<HTMLElement> & { asChild?: boolean, children: React.ReactNode }) {
35
+ const context = React.useContext(CollapsibleContext)
36
+ if (!context) throw new Error("CollapsibleTrigger must be used within Collapsible")
37
+
38
+ const handleClick = (e: React.MouseEvent<HTMLElement>) => {
39
+ context.setOpen(!context.open)
40
+ if (React.isValidElement(children)) {
41
+ (children as React.ReactElement<{ onClick?: React.MouseEventHandler<HTMLElement> }>).props.onClick?.(e)
42
+ }
43
+ }
44
+
45
+ if (asChild && React.isValidElement(children)) {
46
+ return React.cloneElement(children as React.ReactElement<any>, { onClick: handleClick, ...props })
47
+ }
48
+
49
+ return (
50
+ <button type="button" onClick={handleClick} {...props}>
51
+ {children}
52
+ </button>
53
+ )
54
+ }
55
+
56
+ export function CollapsibleContent({ children, className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
57
+ const context = React.useContext(CollapsibleContext)
58
+ if (!context) throw new Error("CollapsibleContent must be used within Collapsible")
59
+
60
+ if (!context.open) return null
61
+
62
+ return (
63
+ <div className={cn("editor-overflow-hidden", className)} {...props}>
64
+ {children}
65
+ </div>
66
+ )
67
+ }
@@ -0,0 +1,48 @@
1
+ import * as React from "react"
2
+ import { cn } from "../lib/utils"
3
+
4
+ export const Command = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
5
+ ({ className, ...props }, ref) => (
6
+ <div
7
+ ref={ref}
8
+ className={cn("editor-command", className)}
9
+ {...props}
10
+ />
11
+ )
12
+ )
13
+ Command.displayName = "Command"
14
+
15
+ export const CommandList = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
16
+ ({ className, ...props }, ref) => (
17
+ <div
18
+ ref={ref}
19
+ className={cn("editor-command-list", className)}
20
+ {...props}
21
+ />
22
+ )
23
+ )
24
+ CommandList.displayName = "CommandList"
25
+
26
+ export const CommandGroup = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
27
+ ({ className, ...props }, ref) => (
28
+ <div
29
+ ref={ref}
30
+ className={cn("editor-command-group", className)}
31
+ {...props}
32
+ />
33
+ )
34
+ )
35
+ CommandGroup.displayName = "CommandGroup"
36
+
37
+ export const CommandItem = React.forwardRef<
38
+ HTMLDivElement,
39
+ React.HTMLAttributes<HTMLDivElement> & { onSelect?: () => void; value?: string }
40
+ >(({ className, onSelect, ...props }, ref) => (
41
+ <div
42
+ ref={ref}
43
+ className={cn("editor-command-item", className)}
44
+ onClick={onSelect}
45
+ {...props}
46
+ />
47
+ ))
48
+ CommandItem.displayName = "CommandItem"
@@ -0,0 +1,146 @@
1
+ import * as React from "react"
2
+ import { createPortal } from "react-dom"
3
+ import { cn } from "../lib/utils"
4
+
5
+ interface DialogProps {
6
+ open?: boolean
7
+ onOpenChange?: (open: boolean) => void
8
+ children: React.ReactNode
9
+ }
10
+
11
+ interface DialogContextValue {
12
+ open: boolean
13
+ onOpenChange: (open: boolean) => void
14
+ }
15
+
16
+ const DialogContext = React.createContext<DialogContextValue | null>(null)
17
+
18
+ export function Dialog({ open = false, onOpenChange = () => {}, children }: DialogProps) {
19
+ return (
20
+ <DialogContext.Provider value={{ open, onOpenChange }}>
21
+ {children}
22
+ </DialogContext.Provider>
23
+ )
24
+ }
25
+
26
+ export function DialogTrigger({ asChild, children, className, ...props }: React.HTMLAttributes<HTMLElement> & { asChild?: boolean, children: React.ReactNode }) {
27
+ const context = React.useContext(DialogContext)
28
+
29
+ const handleClick = (e: React.MouseEvent<HTMLElement>) => {
30
+ context?.onOpenChange(true)
31
+ if (React.isValidElement(children)) {
32
+ (children as React.ReactElement<{ onClick?: React.MouseEventHandler<HTMLElement> }>).props.onClick?.(e)
33
+ }
34
+ }
35
+
36
+ if (asChild && React.isValidElement(children)) {
37
+ return React.cloneElement(children as React.ReactElement<any>, {
38
+ onClick: handleClick,
39
+ ...props
40
+ })
41
+ }
42
+
43
+ return (
44
+ <button type="button" onClick={handleClick} className={className} {...props}>
45
+ {children}
46
+ </button>
47
+ )
48
+ }
49
+
50
+ export function DialogClose({ asChild, children, className, ...props }: React.HTMLAttributes<HTMLElement> & { asChild?: boolean, children: React.ReactNode }) {
51
+ const context = React.useContext(DialogContext)
52
+
53
+ const handleClick = (e: React.MouseEvent<HTMLElement>) => {
54
+ context?.onOpenChange(false)
55
+ if (React.isValidElement(children)) {
56
+ (children as React.ReactElement<{ onClick?: React.MouseEventHandler<HTMLElement> }>).props.onClick?.(e)
57
+ }
58
+ }
59
+
60
+ if (asChild && React.isValidElement(children)) {
61
+ return React.cloneElement(children as React.ReactElement<any>, {
62
+ onClick: handleClick,
63
+ ...props
64
+ })
65
+ }
66
+
67
+ return (
68
+ <button type="button" onClick={handleClick} className={className} {...props}>
69
+ {children}
70
+ </button>
71
+ )
72
+ }
73
+
74
+ export interface DialogContentProps extends React.HTMLAttributes<HTMLDivElement> {
75
+ disableOutsideClick?: boolean
76
+ }
77
+
78
+ export function DialogContent({ children, className, disableOutsideClick, ...props }: DialogContentProps) {
79
+ const context = React.useContext(DialogContext)
80
+ const [mounted, setMounted] = React.useState(false)
81
+
82
+ React.useEffect(() => {
83
+ setMounted(true)
84
+ return () => setMounted(false)
85
+ }, [])
86
+
87
+ if (!mounted || !context?.open) return null
88
+
89
+ const handleOverlayClick = (e: React.MouseEvent) => {
90
+ if (e.target === e.currentTarget && !disableOutsideClick) {
91
+ context.onOpenChange(false)
92
+ }
93
+ }
94
+
95
+ const content = (
96
+ <div
97
+ className="editor-dialog-overlay"
98
+ data-state="open"
99
+ onClick={handleOverlayClick}
100
+ >
101
+ <div
102
+ className={cn("editor-dialog-content", className)}
103
+ data-state="open"
104
+ {...props}
105
+ >
106
+ {children}
107
+ <button
108
+ type="button"
109
+ className="editor-dialog-content__close"
110
+ onClick={() => context.onOpenChange(false)}
111
+ >
112
+ <svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" className="editor-icon-sm">
113
+ <path d="M11.7816 4.03157C12.0062 3.80702 12.0062 3.44295 11.7816 3.2184C11.5571 2.99385 11.193 2.99385 10.9685 3.2184L7.50005 6.68682L4.03164 3.2184C3.80708 2.99385 3.44301 2.99385 3.21846 3.2184C2.99391 3.44295 2.99391 3.80702 3.21846 4.03157L6.68688 7.49999L3.21846 10.9684C2.99391 11.193 2.99391 11.557 3.21846 11.7816C3.44301 12.0061 3.80708 12.0061 4.03164 11.7816L7.50005 8.31316L10.9685 11.7816C11.193 12.0061 11.5571 12.0061 11.7816 11.7816C12.0062 11.557 12.0062 11.193 11.7816 10.9684L8.31322 7.49999L11.7816 4.03157Z" fill="currentColor" fillRule="evenodd" clipRule="evenodd"></path>
114
+ </svg>
115
+ <span className="editor-sr-only">Close</span>
116
+ </button>
117
+ </div>
118
+ </div>
119
+ )
120
+
121
+ return createPortal(content, document.body)
122
+ }
123
+
124
+ export function DialogHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
125
+ return (
126
+ <div className={cn("editor-dialog-header", className)} {...props} />
127
+ )
128
+ }
129
+
130
+ export function DialogFooter({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
131
+ return (
132
+ <div className={cn("editor-dialog-footer", className)} {...props} />
133
+ )
134
+ }
135
+
136
+ export function DialogTitle({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) {
137
+ return (
138
+ <h3 className={cn("editor-dialog-header__title", className)} {...props} />
139
+ )
140
+ }
141
+
142
+ export function DialogDescription({ className, ...props }: React.HTMLAttributes<HTMLParagraphElement>) {
143
+ return (
144
+ <p className={cn("editor-dialog-header__description", className)} {...props} />
145
+ )
146
+ }