@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,40 @@
1
+ import { FORMAT_ELEMENT_COMMAND } from "lexical"
2
+ import {
3
+ AlignCenterIcon,
4
+ AlignJustifyIcon,
5
+ AlignLeftIcon,
6
+ AlignRightIcon,
7
+ } from "lucide-react"
8
+
9
+ import { ComponentPickerOption } from "../../plugins/picker/component-picker-option"
10
+ import { IconSize } from "../../ui/typography"
11
+
12
+ export function AlignmentPickerPlugin({
13
+ alignment,
14
+ }: {
15
+ alignment: "left" | "center" | "right" | "justify"
16
+ }) {
17
+ return new ComponentPickerOption(`Align ${alignment}`, {
18
+ icon: <AlignIcons alignment={alignment} />,
19
+ keywords: ["align", "justify", alignment],
20
+ onSelect: (_, editor) =>
21
+ editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, alignment),
22
+ })
23
+ }
24
+
25
+ function AlignIcons({
26
+ alignment,
27
+ }: {
28
+ alignment: "left" | "center" | "right" | "justify"
29
+ }) {
30
+ switch (alignment) {
31
+ case "left":
32
+ return <IconSize size="sm"><AlignLeftIcon /></IconSize>
33
+ case "center":
34
+ return <IconSize size="sm"><AlignCenterIcon /></IconSize>
35
+ case "right":
36
+ return <IconSize size="sm"><AlignRightIcon /></IconSize>
37
+ case "justify":
38
+ return <IconSize size="sm"><AlignJustifyIcon /></IconSize>
39
+ }
40
+ }
@@ -0,0 +1,14 @@
1
+ import { INSERT_UNORDERED_LIST_COMMAND } from "@lexical/list"
2
+ import { ListIcon } from "lucide-react"
3
+
4
+ import { ComponentPickerOption } from "../../plugins/picker/component-picker-option"
5
+ import { IconSize } from "../../ui/typography"
6
+
7
+ export function BulletedListPickerPlugin() {
8
+ return new ComponentPickerOption("Bulleted List", {
9
+ icon: <IconSize size="sm"><ListIcon /></IconSize>,
10
+ keywords: ["bulleted list", "unordered list", "ul", "- list", "+ list"],
11
+ onSelect: (_, editor) =>
12
+ editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined),
13
+ })
14
+ }
@@ -0,0 +1,14 @@
1
+ import { INSERT_CHECK_LIST_COMMAND } from "@lexical/list"
2
+ import { ListTodoIcon } from "lucide-react"
3
+
4
+ import { ComponentPickerOption } from "../../plugins/picker/component-picker-option"
5
+ import { IconSize } from "../../ui/typography"
6
+
7
+ export function CheckListPickerPlugin() {
8
+ return new ComponentPickerOption("Check List", {
9
+ icon: <IconSize size="sm"><ListTodoIcon /></IconSize>,
10
+ keywords: ["check list", "todo list"],
11
+ onSelect: (_, editor) =>
12
+ editor.dispatchCommand(INSERT_CHECK_LIST_COMMAND, undefined),
13
+ })
14
+ }
@@ -0,0 +1,30 @@
1
+ import { $createCodeNode } from "@lexical/code"
2
+ import { $setBlocksType } from "@lexical/selection"
3
+ import { $getSelection, $isRangeSelection } from "lexical"
4
+ import { CodeIcon } from "lucide-react"
5
+
6
+ import { ComponentPickerOption } from "../../plugins/picker/component-picker-option"
7
+ import { IconSize } from "../../ui/typography"
8
+
9
+ export function CodePickerPlugin() {
10
+ return new ComponentPickerOption("Code", {
11
+ icon: <IconSize size="sm"><CodeIcon /></IconSize>,
12
+ keywords: ["javascript", "python", "js", "codeblock"],
13
+ onSelect: (_, editor) =>
14
+ editor.update(() => {
15
+ const selection = $getSelection()
16
+
17
+ if ($isRangeSelection(selection)) {
18
+ if (selection.isCollapsed()) {
19
+ $setBlocksType(selection, () => $createCodeNode())
20
+ } else {
21
+ // Will this ever happen?
22
+ const textContent = selection.getTextContent()
23
+ const codeNode = $createCodeNode()
24
+ selection.insertNodes([codeNode])
25
+ selection.insertRawText(textContent)
26
+ }
27
+ }
28
+ }),
29
+ })
30
+ }
@@ -0,0 +1,16 @@
1
+ import { Columns3Icon } from "lucide-react"
2
+
3
+ import { InsertLayoutDialog } from "../../plugins/layout-plugin"
4
+ import { ComponentPickerOption } from "../../plugins/picker/component-picker-option"
5
+ import { IconSize } from "../../ui/typography"
6
+
7
+ export function ColumnsLayoutPickerPlugin() {
8
+ return new ComponentPickerOption("Columns Layout", {
9
+ icon: <IconSize size="sm"><Columns3Icon /></IconSize>,
10
+ keywords: ["columns", "layout", "grid"],
11
+ onSelect: (_, editor, showModal) =>
12
+ showModal("Insert Columns Layout", (onClose) => (
13
+ <InsertLayoutDialog activeEditor={editor} onClose={onClose} />
14
+ )),
15
+ })
16
+ }
@@ -0,0 +1,47 @@
1
+ import { JSX } from "react"
2
+ import { MenuOption } from "@lexical/react/LexicalTypeaheadMenuPlugin"
3
+ import { LexicalEditor } from "lexical"
4
+
5
+ export class ComponentPickerOption extends MenuOption {
6
+ // What shows up in the editor
7
+ title: string
8
+ // Icon for display
9
+ icon?: JSX.Element
10
+ // For extra searching.
11
+ keywords: Array<string>
12
+ // TBD
13
+ keyboardShortcut?: string
14
+ // What happens when you select this option?
15
+ onSelect: (
16
+ queryString: string,
17
+ editor: LexicalEditor,
18
+ showModal: (
19
+ title: string,
20
+ showModal: (onClose: () => void) => JSX.Element
21
+ ) => void
22
+ ) => void
23
+
24
+ constructor(
25
+ title: string,
26
+ options: {
27
+ icon?: JSX.Element
28
+ keywords?: Array<string>
29
+ keyboardShortcut?: string
30
+ onSelect: (
31
+ queryString: string,
32
+ editor: LexicalEditor,
33
+ showModal: (
34
+ title: string,
35
+ showModal: (onClose: () => void) => JSX.Element
36
+ ) => void
37
+ ) => void
38
+ }
39
+ ) {
40
+ super(title)
41
+ this.title = title
42
+ this.keywords = options.keywords || []
43
+ this.icon = options.icon
44
+ this.keyboardShortcut = options.keyboardShortcut
45
+ this.onSelect = options.onSelect.bind(this)
46
+ }
47
+ }
@@ -0,0 +1,14 @@
1
+ import { INSERT_HORIZONTAL_RULE_COMMAND } from "@lexical/react/LexicalHorizontalRuleNode"
2
+ import { MinusIcon } from "lucide-react"
3
+
4
+ import { ComponentPickerOption } from "../../plugins/picker/component-picker-option"
5
+ import { IconSize } from "../../ui/typography"
6
+
7
+ export function DividerPickerPlugin() {
8
+ return new ComponentPickerOption("Divider", {
9
+ icon: <IconSize size="sm"><MinusIcon /></IconSize>,
10
+ keywords: ["horizontal rule", "divider", "hr"],
11
+ onSelect: (_, editor) =>
12
+ editor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, undefined),
13
+ })
14
+ }
@@ -0,0 +1,24 @@
1
+ import { INSERT_EMBED_COMMAND } from "@lexical/react/LexicalAutoEmbedPlugin"
2
+
3
+ import {
4
+ CustomEmbedConfig,
5
+ EmbedConfigs,
6
+ } from "../../plugins/embeds/auto-embed-plugin"
7
+ import { ComponentPickerOption } from "../../plugins/picker/component-picker-option"
8
+
9
+ export function EmbedsPickerPlugin({
10
+ embed,
11
+ }: {
12
+ embed: "tweet" | "youtube-video"
13
+ }) {
14
+ const embedConfig = EmbedConfigs.find(
15
+ (config) => config.type === embed
16
+ ) as CustomEmbedConfig
17
+
18
+ return new ComponentPickerOption(`Embed ${embedConfig.contentName}`, {
19
+ icon: embedConfig.icon,
20
+ keywords: [...embedConfig.keywords, "embed"],
21
+ onSelect: (_, editor) =>
22
+ editor.dispatchCommand(INSERT_EMBED_COMMAND, embedConfig.type),
23
+ })
24
+ }
@@ -0,0 +1,32 @@
1
+ import { $createHeadingNode } from "@lexical/rich-text"
2
+ import { $setBlocksType } from "@lexical/selection"
3
+ import { $getSelection, $isRangeSelection } from "lexical"
4
+ import { Heading1Icon, Heading2Icon, Heading3Icon } from "lucide-react"
5
+
6
+ import { ComponentPickerOption } from "../../plugins/picker/component-picker-option"
7
+ import { IconSize } from "../../ui/typography"
8
+
9
+ export function HeadingPickerPlugin({ n }: { n: 1 | 2 | 3 }) {
10
+ return new ComponentPickerOption(`Heading ${n}`, {
11
+ icon: <HeadingIcons n={n} />,
12
+ keywords: ["heading", "header", `h${n}`],
13
+ onSelect: (_, editor) =>
14
+ editor.update(() => {
15
+ const selection = $getSelection()
16
+ if ($isRangeSelection(selection)) {
17
+ $setBlocksType(selection, () => $createHeadingNode(`h${n}`))
18
+ }
19
+ }),
20
+ })
21
+ }
22
+
23
+ function HeadingIcons({ n }: { n: number }) {
24
+ switch (n) {
25
+ case 1:
26
+ return <IconSize size="sm"><Heading1Icon /></IconSize>
27
+ case 2:
28
+ return <IconSize size="sm"><Heading2Icon /></IconSize>
29
+ case 3:
30
+ return <IconSize size="sm"><Heading3Icon /></IconSize>
31
+ }
32
+ }
@@ -0,0 +1,16 @@
1
+ import { ImageIcon } from "lucide-react"
2
+
3
+ import { InsertImageDialog } from "../../plugins/images-plugin"
4
+ import { ComponentPickerOption } from "../../plugins/picker/component-picker-option"
5
+ import { IconSize } from "../../ui/typography"
6
+
7
+ export function ImagePickerPlugin() {
8
+ return new ComponentPickerOption("Image", {
9
+ icon: <IconSize size="sm"><ImageIcon /></IconSize>,
10
+ keywords: ["image", "photo", "picture", "file"],
11
+ onSelect: (_, editor, showModal) =>
12
+ showModal("Insert Image", (onClose) => (
13
+ <InsertImageDialog activeEditor={editor} onClose={onClose} />
14
+ )),
15
+ })
16
+ }
@@ -0,0 +1,14 @@
1
+ import { INSERT_ORDERED_LIST_COMMAND } from "@lexical/list"
2
+ import { ListOrderedIcon } from "lucide-react"
3
+
4
+ import { ComponentPickerOption } from "../../plugins/picker/component-picker-option"
5
+ import { IconSize } from "../../ui/typography"
6
+
7
+ export function NumberedListPickerPlugin() {
8
+ return new ComponentPickerOption("Numbered List", {
9
+ icon: <IconSize size="sm"><ListOrderedIcon /></IconSize>,
10
+ keywords: ["numbered list", "ordered list", "ol"],
11
+ onSelect: (_, editor) =>
12
+ editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined),
13
+ })
14
+ }
@@ -0,0 +1,20 @@
1
+ import { $setBlocksType } from "@lexical/selection"
2
+ import { $createParagraphNode, $getSelection, $isRangeSelection } from "lexical"
3
+ import { TextIcon } from "lucide-react"
4
+
5
+ import { ComponentPickerOption } from "../../plugins/picker/component-picker-option"
6
+ import { IconSize } from "../../ui/typography"
7
+
8
+ export function ParagraphPickerPlugin() {
9
+ return new ComponentPickerOption("Paragraph", {
10
+ icon: <IconSize size="sm"><TextIcon /></IconSize>,
11
+ keywords: ["normal", "paragraph", "p", "text"],
12
+ onSelect: (_, editor) =>
13
+ editor.update(() => {
14
+ const selection = $getSelection()
15
+ if ($isRangeSelection(selection)) {
16
+ $setBlocksType(selection, () => $createParagraphNode())
17
+ }
18
+ }),
19
+ })
20
+ }
@@ -0,0 +1,21 @@
1
+ import { $createQuoteNode } from "@lexical/rich-text"
2
+ import { $setBlocksType } from "@lexical/selection"
3
+ import { $getSelection, $isRangeSelection } from "lexical"
4
+ import { QuoteIcon } from "lucide-react"
5
+
6
+ import { ComponentPickerOption } from "../../plugins/picker/component-picker-option"
7
+ import { IconSize } from "../../ui/typography"
8
+
9
+ export function QuotePickerPlugin() {
10
+ return new ComponentPickerOption("Quote", {
11
+ icon: <IconSize size="sm"><QuoteIcon /></IconSize>,
12
+ keywords: ["block quote"],
13
+ onSelect: (_, editor) =>
14
+ editor.update(() => {
15
+ const selection = $getSelection()
16
+ if ($isRangeSelection(selection)) {
17
+ $setBlocksType(selection, () => $createQuoteNode())
18
+ }
19
+ }),
20
+ })
21
+ }
@@ -0,0 +1,56 @@
1
+ import { INSERT_TABLE_COMMAND } from "@lexical/table"
2
+ import { TableIcon } from "lucide-react"
3
+
4
+ import { ComponentPickerOption } from "../../plugins/picker/component-picker-option"
5
+ import { InsertTableDialog } from "../../plugins/table-plugin"
6
+ import { IconSize } from "../../ui/typography"
7
+
8
+ export function TablePickerPlugin() {
9
+ return new ComponentPickerOption("Table", {
10
+ icon: <IconSize size="sm"><TableIcon /></IconSize>,
11
+ keywords: ["table", "grid", "spreadsheet", "rows", "columns"],
12
+ onSelect: (_, editor, showModal) =>
13
+ showModal("Insert Table", (onClose) => (
14
+ <InsertTableDialog activeEditor={editor} onClose={onClose} />
15
+ )),
16
+ })
17
+ }
18
+
19
+ export function DynamicTablePickerPlugin({
20
+ queryString,
21
+ }: {
22
+ queryString: string
23
+ }) {
24
+ const options: Array<ComponentPickerOption> = []
25
+
26
+ if (queryString == null) {
27
+ return options
28
+ }
29
+
30
+ const tableMatch = queryString.match(/^([1-9]\d?)(?:x([1-9]\d?)?)?$/)
31
+
32
+ if (tableMatch !== null && tableMatch[1]) {
33
+ const rows = tableMatch[1]
34
+ const colOptions = tableMatch[2]
35
+ ? [tableMatch[2]]
36
+ : [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(String)
37
+
38
+ options.push(
39
+ ...colOptions.map(
40
+ (columns) =>
41
+ new ComponentPickerOption(`${rows}x${columns} Table`, {
42
+ icon: (
43
+ <IconSize size="sm">
44
+ <TableIcon />
45
+ </IconSize>
46
+ ),
47
+ keywords: ["table"],
48
+ onSelect: (_, editor) =>
49
+ editor.dispatchCommand(INSERT_TABLE_COMMAND, { columns: columns || "1", rows }),
50
+ })
51
+ )
52
+ )
53
+ }
54
+
55
+ return options
56
+ }
@@ -0,0 +1,66 @@
1
+ "use client"
2
+
3
+ /**
4
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE file in the root directory of this source tree.
8
+ *
9
+ */
10
+ import { useEffect } from "react"
11
+ import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext"
12
+ import {
13
+ $getSelection,
14
+ $isRangeSelection,
15
+ $setSelection,
16
+ FOCUS_COMMAND,
17
+ } from "lexical"
18
+
19
+ const COMMAND_PRIORITY_LOW = 1
20
+ const TAB_TO_FOCUS_INTERVAL = 100
21
+
22
+ let lastTabKeyDownTimestamp = 0
23
+ let hasRegisteredKeyDownListener = false
24
+
25
+ function registerKeyTimeStampTracker() {
26
+ window.addEventListener(
27
+ "keydown",
28
+ (event: KeyboardEvent) => {
29
+ // Tab
30
+ if (event.key === "Tab") {
31
+ lastTabKeyDownTimestamp = event.timeStamp
32
+ }
33
+ },
34
+ true
35
+ )
36
+ }
37
+
38
+ export function TabFocusPlugin(): null {
39
+ const [editor] = useLexicalComposerContext()
40
+
41
+ useEffect(() => {
42
+ if (!hasRegisteredKeyDownListener) {
43
+ registerKeyTimeStampTracker()
44
+ hasRegisteredKeyDownListener = true
45
+ }
46
+
47
+ return editor.registerCommand(
48
+ FOCUS_COMMAND,
49
+ (event: FocusEvent) => {
50
+ const selection = $getSelection()
51
+ if ($isRangeSelection(selection)) {
52
+ if (
53
+ lastTabKeyDownTimestamp + TAB_TO_FOCUS_INTERVAL >
54
+ event.timeStamp
55
+ ) {
56
+ $setSelection(selection.clone())
57
+ }
58
+ }
59
+ return false
60
+ },
61
+ COMMAND_PRIORITY_LOW
62
+ )
63
+ }, [editor])
64
+
65
+ return null
66
+ }