5htp-core 0.4.8 → 0.4.9

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 (187) hide show
  1. package/package.json +5 -1
  2. package/src/client/assets/css/components/table.less +2 -0
  3. package/src/client/components/Form.ts +1 -1
  4. package/src/client/components/button.tsx +2 -1
  5. package/src/client/components/containers/Popover/index.tsx +2 -2
  6. package/src/client/components/dropdown/index.tsx +16 -6
  7. package/src/client/components/input/Slider/index.tsx +0 -2
  8. package/src/client/components/inputv3/Rte/Editor.tsx +271 -0
  9. package/src/client/components/inputv3/Rte/ToolbarPlugin/BlockFormat.tsx +220 -0
  10. package/src/client/components/inputv3/Rte/ToolbarPlugin/ElementFormat.tsx +107 -0
  11. package/src/client/components/inputv3/Rte/ToolbarPlugin/index.tsx +768 -0
  12. package/src/client/components/inputv3/Rte/appSettings.ts +36 -0
  13. package/src/client/components/inputv3/Rte/context/FlashMessageContext.tsx +68 -0
  14. package/src/client/components/inputv3/Rte/context/SettingsContext.tsx +71 -0
  15. package/src/client/components/inputv3/Rte/context/SharedAutocompleteContext.tsx +71 -0
  16. package/src/client/components/inputv3/Rte/context/SharedHistoryContext.tsx +35 -0
  17. package/src/client/components/inputv3/Rte/currentEditor.ts +42 -0
  18. package/src/client/components/inputv3/Rte/hooks/useFlashMessage.tsx +16 -0
  19. package/src/client/components/inputv3/Rte/hooks/useReport.ts +67 -0
  20. package/src/client/components/inputv3/Rte/images/emoji/1F600.png +0 -0
  21. package/src/client/components/inputv3/Rte/images/emoji/1F641.png +0 -0
  22. package/src/client/components/inputv3/Rte/images/emoji/1F642.png +0 -0
  23. package/src/client/components/inputv3/Rte/images/emoji/2764.png +0 -0
  24. package/src/client/components/inputv3/Rte/images/emoji/LICENSE.md +5 -0
  25. package/src/client/components/inputv3/Rte/images/icons/draggable-block-menu.svg +1 -0
  26. package/src/client/components/inputv3/Rte/images/icons/prettier-error.svg +1 -0
  27. package/src/client/components/inputv3/Rte/images/icons/prettier.svg +1 -0
  28. package/src/client/components/inputv3/Rte/images/image/LICENSE.md +5 -0
  29. package/src/client/components/inputv3/Rte/images/image-broken.svg +4 -0
  30. package/src/client/components/inputv3/Rte/images/logo.svg +1 -0
  31. package/src/client/components/inputv3/Rte/index.tsx +63 -79
  32. package/src/client/components/inputv3/Rte/nodes/AutocompleteNode.tsx +119 -0
  33. package/src/client/components/inputv3/Rte/nodes/EmojiNode.tsx +102 -0
  34. package/src/client/components/inputv3/Rte/nodes/EquationComponent.tsx +141 -0
  35. package/src/client/components/inputv3/Rte/nodes/EquationNode.tsx +174 -0
  36. package/src/client/components/inputv3/Rte/nodes/FigmaNode.tsx +135 -0
  37. package/src/client/components/inputv3/Rte/nodes/ImageComponent.tsx +468 -0
  38. package/src/client/components/inputv3/Rte/nodes/ImageNode.css +43 -0
  39. package/src/client/components/inputv3/Rte/nodes/ImageNode.tsx +266 -0
  40. package/src/client/components/inputv3/Rte/nodes/InlineImageNode/InlineImageComponent.tsx +402 -0
  41. package/src/client/components/inputv3/Rte/nodes/InlineImageNode/InlineImageNode.css +94 -0
  42. package/src/client/components/inputv3/Rte/nodes/InlineImageNode/InlineImageNode.tsx +294 -0
  43. package/src/client/components/inputv3/Rte/nodes/KeywordNode.ts +67 -0
  44. package/src/client/components/inputv3/Rte/nodes/LayoutContainerNode.ts +137 -0
  45. package/src/client/components/inputv3/Rte/nodes/LayoutItemNode.ts +71 -0
  46. package/src/client/components/inputv3/Rte/nodes/MentionNode.ts +130 -0
  47. package/src/client/components/inputv3/Rte/nodes/PageBreakNode/index.css +62 -0
  48. package/src/client/components/inputv3/Rte/nodes/PageBreakNode/index.tsx +170 -0
  49. package/src/client/components/inputv3/Rte/nodes/PlaygroundNodes.ts +76 -0
  50. package/src/client/components/inputv3/Rte/nodes/PollComponent.tsx +249 -0
  51. package/src/client/components/inputv3/Rte/nodes/PollNode.css +187 -0
  52. package/src/client/components/inputv3/Rte/nodes/PollNode.tsx +209 -0
  53. package/src/client/components/inputv3/Rte/nodes/StickyComponent.tsx +261 -0
  54. package/src/client/components/inputv3/Rte/nodes/StickyNode.css +37 -0
  55. package/src/client/components/inputv3/Rte/nodes/StickyNode.tsx +150 -0
  56. package/src/client/components/inputv3/Rte/nodes/TweetNode.tsx +223 -0
  57. package/src/client/components/inputv3/Rte/nodes/YouTubeNode.tsx +184 -0
  58. package/src/client/components/inputv3/Rte/plugins/ActionsPlugin/index.tsx +334 -0
  59. package/src/client/components/inputv3/Rte/plugins/AutoEmbedPlugin/index.tsx +352 -0
  60. package/src/client/components/inputv3/Rte/plugins/AutoLinkPlugin/index.tsx +32 -0
  61. package/src/client/components/inputv3/Rte/plugins/AutocompletePlugin/index.tsx +2529 -0
  62. package/src/client/components/inputv3/Rte/plugins/CodeActionMenuPlugin/components/CopyButton/index.tsx +70 -0
  63. package/src/client/components/inputv3/Rte/plugins/CodeActionMenuPlugin/components/PrettierButton/index.css +14 -0
  64. package/src/client/components/inputv3/Rte/plugins/CodeActionMenuPlugin/components/PrettierButton/index.tsx +156 -0
  65. package/src/client/components/inputv3/Rte/plugins/CodeActionMenuPlugin/index.css +54 -0
  66. package/src/client/components/inputv3/Rte/plugins/CodeActionMenuPlugin/index.tsx +190 -0
  67. package/src/client/components/inputv3/Rte/plugins/CodeActionMenuPlugin/utils.ts +33 -0
  68. package/src/client/components/inputv3/Rte/plugins/CodeHighlightPlugin/index.ts +21 -0
  69. package/src/client/components/inputv3/Rte/plugins/CollapsiblePlugin/Collapsible.css +57 -0
  70. package/src/client/components/inputv3/Rte/plugins/CollapsiblePlugin/CollapsibleContainerNode.ts +168 -0
  71. package/src/client/components/inputv3/Rte/plugins/CollapsiblePlugin/CollapsibleContentNode.ts +127 -0
  72. package/src/client/components/inputv3/Rte/plugins/CollapsiblePlugin/CollapsibleTitleNode.ts +152 -0
  73. package/src/client/components/inputv3/Rte/plugins/CollapsiblePlugin/CollapsibleUtils.ts +17 -0
  74. package/src/client/components/inputv3/Rte/plugins/CollapsiblePlugin/index.ts +284 -0
  75. package/src/client/components/inputv3/Rte/plugins/ComponentPickerPlugin/index.tsx +370 -0
  76. package/src/client/components/inputv3/Rte/plugins/ContextMenuPlugin/index.tsx +270 -0
  77. package/src/client/components/inputv3/Rte/plugins/DocsPlugin/index.tsx +20 -0
  78. package/src/client/components/inputv3/Rte/plugins/DragDropPastePlugin/index.ts +51 -0
  79. package/src/client/components/inputv3/Rte/plugins/DraggableBlockPlugin/index.css +36 -0
  80. package/src/client/components/inputv3/Rte/plugins/DraggableBlockPlugin/index.tsx +43 -0
  81. package/src/client/components/inputv3/Rte/plugins/EmojiPickerPlugin/index.tsx +198 -0
  82. package/src/client/components/inputv3/Rte/plugins/EmojisPlugin/index.ts +75 -0
  83. package/src/client/components/inputv3/Rte/plugins/EquationsPlugin/index.tsx +82 -0
  84. package/src/client/components/inputv3/Rte/plugins/FigmaPlugin/index.tsx +40 -0
  85. package/src/client/components/inputv3/Rte/plugins/FloatingLinkEditorPlugin/index.css +41 -0
  86. package/src/client/components/inputv3/Rte/plugins/FloatingLinkEditorPlugin/index.tsx +393 -0
  87. package/src/client/components/inputv3/Rte/plugins/FloatingTextFormatToolbarPlugin/index.css +141 -0
  88. package/src/client/components/inputv3/Rte/plugins/FloatingTextFormatToolbarPlugin/index.tsx +388 -0
  89. package/src/client/components/inputv3/Rte/plugins/ImagesPlugin/index.tsx +350 -0
  90. package/src/client/components/inputv3/Rte/plugins/InlineImagePlugin/index.tsx +336 -0
  91. package/src/client/components/inputv3/Rte/plugins/KeywordsPlugin/index.ts +56 -0
  92. package/src/client/components/inputv3/Rte/plugins/LayoutPlugin/InsertLayoutDialog.tsx +58 -0
  93. package/src/client/components/inputv3/Rte/plugins/LayoutPlugin/LayoutPlugin.tsx +219 -0
  94. package/src/client/components/inputv3/Rte/plugins/LinkPlugin/index.tsx +34 -0
  95. package/src/client/components/inputv3/Rte/plugins/ListMaxIndentLevelPlugin/index.ts +85 -0
  96. package/src/client/components/inputv3/Rte/plugins/MarkdownShortcutPlugin/index.tsx +16 -0
  97. package/src/client/components/inputv3/Rte/plugins/MarkdownTransformers/index.ts +324 -0
  98. package/src/client/components/inputv3/Rte/plugins/MaxLengthPlugin/index.tsx +53 -0
  99. package/src/client/components/inputv3/Rte/plugins/MentionsPlugin/index.tsx +696 -0
  100. package/src/client/components/inputv3/Rte/plugins/PageBreakPlugin/index.tsx +57 -0
  101. package/src/client/components/inputv3/Rte/plugins/PasteLogPlugin/index.tsx +54 -0
  102. package/src/client/components/inputv3/Rte/plugins/PollPlugin/index.tsx +86 -0
  103. package/src/client/components/inputv3/Rte/plugins/SpeechToTextPlugin/index.ts +125 -0
  104. package/src/client/components/inputv3/Rte/plugins/StickyPlugin/index.ts +22 -0
  105. package/src/client/components/inputv3/Rte/plugins/TabFocusPlugin/index.tsx +65 -0
  106. package/src/client/components/inputv3/Rte/plugins/TableActionMenuPlugin/index.tsx +773 -0
  107. package/src/client/components/inputv3/Rte/plugins/TableCellResizer/index.css +12 -0
  108. package/src/client/components/inputv3/Rte/plugins/TableCellResizer/index.tsx +436 -0
  109. package/src/client/components/inputv3/Rte/plugins/TableHoverActionsPlugin/index.tsx +287 -0
  110. package/src/client/components/inputv3/Rte/plugins/TableOfContentsPlugin/index.css +95 -0
  111. package/src/client/components/inputv3/Rte/plugins/TableOfContentsPlugin/index.tsx +197 -0
  112. package/src/client/components/inputv3/Rte/plugins/TablePlugin.tsx +178 -0
  113. package/src/client/components/inputv3/Rte/plugins/TestRecorderPlugin/index.tsx +468 -0
  114. package/src/client/components/inputv3/Rte/plugins/TreeViewPlugin/index.tsx +26 -0
  115. package/src/client/components/inputv3/Rte/plugins/TwitterPlugin/index.ts +41 -0
  116. package/src/client/components/inputv3/Rte/plugins/TypingPerfPlugin/index.ts +117 -0
  117. package/src/client/components/inputv3/Rte/plugins/YouTubePlugin/index.ts +41 -0
  118. package/src/client/components/inputv3/Rte/shared/canUseDOM.ts +4 -0
  119. package/src/client/components/inputv3/Rte/shared/caretFromPoint.ts +40 -0
  120. package/src/client/components/inputv3/Rte/shared/environment.ts +56 -0
  121. package/src/client/components/inputv3/Rte/shared/invariant.ts +26 -0
  122. package/src/client/components/inputv3/Rte/shared/normalizeClassNames.ts +21 -0
  123. package/src/client/components/inputv3/Rte/shared/react-test-utils.ts +18 -0
  124. package/src/client/components/inputv3/Rte/shared/reactPatches.ts +22 -0
  125. package/src/client/components/inputv3/Rte/shared/simpleDiffWithCursor.ts +49 -0
  126. package/src/client/components/inputv3/Rte/shared/useLayoutEffect.ts +19 -0
  127. package/src/client/components/inputv3/Rte/shared/warnOnlyOnce.ts +20 -0
  128. package/src/client/components/inputv3/Rte/style.less +30 -60
  129. package/src/client/components/inputv3/Rte/themes/CommentEditorTheme.css +13 -0
  130. package/src/client/components/inputv3/Rte/themes/CommentEditorTheme.ts +20 -0
  131. package/src/client/components/inputv3/Rte/themes/PlaygroundEditorTheme.css +447 -0
  132. package/src/client/components/inputv3/Rte/themes/PlaygroundEditorTheme.ts +120 -0
  133. package/src/client/components/inputv3/Rte/themes/StickyEditorTheme.css +13 -0
  134. package/src/client/components/inputv3/Rte/themes/StickyEditorTheme.ts +20 -0
  135. package/src/client/components/inputv3/Rte/ui/ColorPicker.css +88 -0
  136. package/src/client/components/inputv3/Rte/ui/ColorPicker.tsx +365 -0
  137. package/src/client/components/inputv3/Rte/ui/ContentEditable.css +44 -0
  138. package/src/client/components/inputv3/Rte/ui/ContentEditable.tsx +36 -0
  139. package/src/client/components/inputv3/Rte/ui/DropDown.tsx +259 -0
  140. package/src/client/components/inputv3/Rte/ui/DropdownColorPicker.tsx +41 -0
  141. package/src/client/components/inputv3/Rte/ui/EquationEditor.css +38 -0
  142. package/src/client/components/inputv3/Rte/ui/EquationEditor.tsx +56 -0
  143. package/src/client/components/inputv3/Rte/ui/FileInput.tsx +38 -0
  144. package/src/client/components/inputv3/Rte/ui/FlashMessage.css +28 -0
  145. package/src/client/components/inputv3/Rte/ui/FlashMessage.tsx +29 -0
  146. package/src/client/components/inputv3/Rte/ui/ImageResizer.tsx +316 -0
  147. package/src/client/components/inputv3/Rte/ui/Input.css +32 -0
  148. package/src/client/components/inputv3/Rte/ui/KatexRenderer.tsx +54 -0
  149. package/src/client/components/inputv3/Rte/ui/Switch.tsx +36 -0
  150. package/src/client/components/inputv3/Rte/utils/docSerialization.ts +77 -0
  151. package/src/client/components/inputv3/Rte/utils/emoji-list.ts +16615 -0
  152. package/src/client/components/inputv3/Rte/utils/getDOMRangeRect.ts +27 -0
  153. package/src/client/components/inputv3/Rte/utils/getSelectedNode.ts +27 -0
  154. package/src/client/components/inputv3/Rte/utils/guard.ts +10 -0
  155. package/src/client/components/inputv3/Rte/utils/isMobileWidth.ts +7 -0
  156. package/src/client/components/inputv3/Rte/utils/joinClasses.ts +13 -0
  157. package/src/client/components/inputv3/Rte/utils/setFloatingElemPosition.ts +51 -0
  158. package/src/client/components/inputv3/Rte/utils/setFloatingElemPositionForLinkEditor.ts +46 -0
  159. package/src/client/components/inputv3/Rte/utils/swipe.ts +127 -0
  160. package/src/client/components/inputv3/Rte/utils/url.ts +38 -0
  161. package/src/client/components/inputv3/base.tsx +8 -5
  162. package/src/client/components/inputv3/file/index.tsx +11 -5
  163. package/src/common/data/rte/nodes.ts +60 -9
  164. package/src/common/validation/index.ts +21 -2
  165. package/src/common/validation/schema.ts +42 -10
  166. package/src/common/validation/validator.ts +12 -4
  167. package/src/common/validation/validators.ts +82 -53
  168. package/src/server/services/router/http/multipart.ts +0 -1
  169. package/src/server/services/schema/index.ts +24 -2
  170. package/src/server/services/schema/request.ts +3 -2
  171. package/src/server/services/schema/rte.ts +110 -0
  172. package/src/{common/data/rte/index.ts → server/utils/rte.ts} +27 -16
  173. package/src/client/components/inputv3/Rte/ExampleTheme.tsx +0 -42
  174. package/src/client/components/inputv3/Rte/ToolbarPlugin.tsx +0 -167
  175. package/src/client/components/inputv3/Rte/icons/LICENSE.md +0 -5
  176. package/src/client/components/inputv3/Rte/icons/arrow-clockwise.svg +0 -4
  177. package/src/client/components/inputv3/Rte/icons/arrow-counterclockwise.svg +0 -4
  178. package/src/client/components/inputv3/Rte/icons/journal-text.svg +0 -5
  179. package/src/client/components/inputv3/Rte/icons/justify.svg +0 -3
  180. package/src/client/components/inputv3/Rte/icons/text-center.svg +0 -3
  181. package/src/client/components/inputv3/Rte/icons/text-left.svg +0 -3
  182. package/src/client/components/inputv3/Rte/icons/text-paragraph.svg +0 -3
  183. package/src/client/components/inputv3/Rte/icons/text-right.svg +0 -3
  184. package/src/client/components/inputv3/Rte/icons/type-bold.svg +0 -3
  185. package/src/client/components/inputv3/Rte/icons/type-italic.svg +0 -3
  186. package/src/client/components/inputv3/Rte/icons/type-strikethrough.svg +0 -3
  187. package/src/client/components/inputv3/Rte/icons/type-underline.svg +0 -3
@@ -0,0 +1,270 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ import {$isLinkNode, TOGGLE_LINK_COMMAND} from '@lexical/link';
10
+ import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
11
+ import {
12
+ LexicalContextMenuPlugin,
13
+ MenuOption,
14
+ } from '@lexical/react/LexicalContextMenuPlugin';
15
+ import {
16
+ $getNearestNodeFromDOMNode,
17
+ $getSelection,
18
+ $isRangeSelection,
19
+ COPY_COMMAND,
20
+ CUT_COMMAND,
21
+ type LexicalNode,
22
+ PASTE_COMMAND,
23
+ } from 'lexical';
24
+ import {useCallback, useMemo} from 'react';
25
+ import * as React from 'react';
26
+ import * as ReactDOM from 'react-dom';
27
+
28
+ function ContextMenuItem({
29
+ index,
30
+ isSelected,
31
+ onClick,
32
+ onMouseEnter,
33
+ option,
34
+ }: {
35
+ index: number;
36
+ isSelected: boolean;
37
+ onClick: () => void;
38
+ onMouseEnter: () => void;
39
+ option: ContextMenuOption;
40
+ }) {
41
+ let className = 'item';
42
+ if (isSelected) {
43
+ className += ' selected';
44
+ }
45
+ return (
46
+ <li
47
+ key={option.key}
48
+ tabIndex={-1}
49
+ className={className}
50
+ ref={option.setRefElement}
51
+ role="option"
52
+ aria-selected={isSelected}
53
+ id={'typeahead-item-' + index}
54
+ onMouseEnter={onMouseEnter}
55
+ onClick={onClick}>
56
+ <span className="text">{option.title}</span>
57
+ </li>
58
+ );
59
+ }
60
+
61
+ function ContextMenu({
62
+ options,
63
+ selectedItemIndex,
64
+ onOptionClick,
65
+ onOptionMouseEnter,
66
+ }: {
67
+ selectedItemIndex: number | null;
68
+ onOptionClick: (option: ContextMenuOption, index: number) => void;
69
+ onOptionMouseEnter: (index: number) => void;
70
+ options: Array<ContextMenuOption>;
71
+ }) {
72
+ return (
73
+ <div className="typeahead-popover">
74
+ <ul>
75
+ {options.map((option: ContextMenuOption, i: number) => (
76
+ <ContextMenuItem
77
+ index={i}
78
+ isSelected={selectedItemIndex === i}
79
+ onClick={() => onOptionClick(option, i)}
80
+ onMouseEnter={() => onOptionMouseEnter(i)}
81
+ key={option.key}
82
+ option={option}
83
+ />
84
+ ))}
85
+ </ul>
86
+ </div>
87
+ );
88
+ }
89
+
90
+ export class ContextMenuOption extends MenuOption {
91
+ title: string;
92
+ onSelect: (targetNode: LexicalNode | null) => void;
93
+ constructor(
94
+ title: string,
95
+ options: {
96
+ onSelect: (targetNode: LexicalNode | null) => void;
97
+ },
98
+ ) {
99
+ super(title);
100
+ this.title = title;
101
+ this.onSelect = options.onSelect.bind(this);
102
+ }
103
+ }
104
+
105
+ export default function ContextMenuPlugin(): JSX.Element {
106
+ const [editor] = useLexicalComposerContext();
107
+
108
+ const defaultOptions = useMemo(() => {
109
+ return [
110
+ new ContextMenuOption(`Copy`, {
111
+ onSelect: (_node) => {
112
+ editor.dispatchCommand(COPY_COMMAND, null);
113
+ },
114
+ }),
115
+ new ContextMenuOption(`Cut`, {
116
+ onSelect: (_node) => {
117
+ editor.dispatchCommand(CUT_COMMAND, null);
118
+ },
119
+ }),
120
+ new ContextMenuOption(`Paste`, {
121
+ onSelect: (_node) => {
122
+ navigator.clipboard.read().then(async function (...args) {
123
+ const data = new DataTransfer();
124
+
125
+ const items = await navigator.clipboard.read();
126
+ const item = items[0];
127
+
128
+ const permission = await navigator.permissions.query({
129
+ // @ts-expect-error These types are incorrect.
130
+ name: 'clipboard-read',
131
+ });
132
+ if (permission.state === 'denied') {
133
+ alert('Not allowed to paste from clipboard.');
134
+ return;
135
+ }
136
+
137
+ for (const type of item.types) {
138
+ const dataString = await (await item.getType(type)).text();
139
+ data.setData(type, dataString);
140
+ }
141
+
142
+ const event = new ClipboardEvent('paste', {
143
+ clipboardData: data,
144
+ });
145
+
146
+ editor.dispatchCommand(PASTE_COMMAND, event);
147
+ });
148
+ },
149
+ }),
150
+ new ContextMenuOption(`Paste as Plain Text`, {
151
+ onSelect: (_node) => {
152
+ navigator.clipboard.read().then(async function (...args) {
153
+ const permission = await navigator.permissions.query({
154
+ // @ts-expect-error These types are incorrect.
155
+ name: 'clipboard-read',
156
+ });
157
+
158
+ if (permission.state === 'denied') {
159
+ alert('Not allowed to paste from clipboard.');
160
+ return;
161
+ }
162
+
163
+ const data = new DataTransfer();
164
+ const items = await navigator.clipboard.readText();
165
+ data.setData('text/plain', items);
166
+
167
+ const event = new ClipboardEvent('paste', {
168
+ clipboardData: data,
169
+ });
170
+ editor.dispatchCommand(PASTE_COMMAND, event);
171
+ });
172
+ },
173
+ }),
174
+ new ContextMenuOption(`Delete Node`, {
175
+ onSelect: (_node) => {
176
+ const selection = $getSelection();
177
+ if ($isRangeSelection(selection)) {
178
+ const currentNode = selection.anchor.getNode();
179
+ const ancestorNodeWithRootAsParent = currentNode
180
+ .getParents()
181
+ .at(-2);
182
+
183
+ ancestorNodeWithRootAsParent?.remove();
184
+ }
185
+ },
186
+ }),
187
+ ];
188
+ }, [editor]);
189
+
190
+ const [options, setOptions] = React.useState(defaultOptions);
191
+
192
+ const onSelectOption = useCallback(
193
+ (
194
+ selectedOption: ContextMenuOption,
195
+ targetNode: LexicalNode | null,
196
+ closeMenu: () => void,
197
+ ) => {
198
+ editor.update(() => {
199
+ selectedOption.onSelect(targetNode);
200
+ closeMenu();
201
+ });
202
+ },
203
+ [editor],
204
+ );
205
+
206
+ const onWillOpen = (event: MouseEvent) => {
207
+ let newOptions = defaultOptions;
208
+ editor.update(() => {
209
+ const node = $getNearestNodeFromDOMNode(event.target as Element);
210
+ if (node) {
211
+ const parent = node.getParent();
212
+ if ($isLinkNode(parent)) {
213
+ newOptions = [
214
+ new ContextMenuOption(`Remove Link`, {
215
+ onSelect: (_node) => {
216
+ editor.dispatchCommand(TOGGLE_LINK_COMMAND, null);
217
+ },
218
+ }),
219
+ ...defaultOptions,
220
+ ];
221
+ }
222
+ }
223
+ });
224
+ setOptions(newOptions);
225
+ };
226
+
227
+ return (
228
+ <LexicalContextMenuPlugin
229
+ options={options}
230
+ onSelectOption={onSelectOption}
231
+ onWillOpen={onWillOpen}
232
+ menuRenderFn={(
233
+ anchorElementRef,
234
+ {
235
+ selectedIndex,
236
+ options: _options,
237
+ selectOptionAndCleanUp,
238
+ setHighlightedIndex,
239
+ },
240
+ {setMenuRef},
241
+ ) =>
242
+ anchorElementRef.current
243
+ ? ReactDOM.createPortal(
244
+ <div
245
+ className="typeahead-popover auto-embed-menu"
246
+ style={{
247
+ marginLeft: anchorElementRef.current.style.width,
248
+ userSelect: 'none',
249
+ width: 200,
250
+ }}
251
+ ref={setMenuRef}>
252
+ <ContextMenu
253
+ options={options}
254
+ selectedItemIndex={selectedIndex}
255
+ onOptionClick={(option: ContextMenuOption, index: number) => {
256
+ setHighlightedIndex(index);
257
+ selectOptionAndCleanUp(option);
258
+ }}
259
+ onOptionMouseEnter={(index: number) => {
260
+ setHighlightedIndex(index);
261
+ }}
262
+ />
263
+ </div>,
264
+ anchorElementRef.current,
265
+ )
266
+ : null
267
+ }
268
+ />
269
+ );
270
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+ import * as React from 'react';
9
+
10
+ export default function DocsPlugin(): JSX.Element {
11
+ return (
12
+ <a target="__blank" href="https://lexical.dev/docs/intro">
13
+ <button
14
+ id="docs-button"
15
+ className="editor-dev-button"
16
+ title="Lexical Docs"
17
+ />
18
+ </a>
19
+ );
20
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
10
+ import {DRAG_DROP_PASTE} from '@lexical/rich-text';
11
+ import {isMimeType, mediaFileReader} from '@lexical/utils';
12
+ import {COMMAND_PRIORITY_LOW} from 'lexical';
13
+ import {useEffect} from 'react';
14
+
15
+ import {INSERT_IMAGE_COMMAND} from '../ImagesPlugin';
16
+
17
+ const ACCEPTABLE_IMAGE_TYPES = [
18
+ 'image/',
19
+ 'image/heic',
20
+ 'image/heif',
21
+ 'image/gif',
22
+ 'image/webp',
23
+ ];
24
+
25
+ export default function DragDropPaste(): null {
26
+ const [editor] = useLexicalComposerContext();
27
+ useEffect(() => {
28
+ return editor.registerCommand(
29
+ DRAG_DROP_PASTE,
30
+ (files) => {
31
+ (async () => {
32
+ const filesResult = await mediaFileReader(
33
+ files,
34
+ [ACCEPTABLE_IMAGE_TYPES].flatMap((x) => x),
35
+ );
36
+ for (const {file, result} of filesResult) {
37
+ if (isMimeType(file, ACCEPTABLE_IMAGE_TYPES)) {
38
+ editor.dispatchCommand(INSERT_IMAGE_COMMAND, {
39
+ altText: file.name,
40
+ src: result,
41
+ });
42
+ }
43
+ }
44
+ })();
45
+ return true;
46
+ },
47
+ COMMAND_PRIORITY_LOW,
48
+ );
49
+ }, [editor]);
50
+ return null;
51
+ }
@@ -0,0 +1,36 @@
1
+ .draggable-block-menu {
2
+ border-radius: 4px;
3
+ padding: 2px 1px;
4
+ cursor: grab;
5
+ opacity: 0;
6
+ position: absolute;
7
+ left: 0;
8
+ top: 0;
9
+ will-change: transform;
10
+ }
11
+
12
+ .draggable-block-menu .icon {
13
+ width: 16px;
14
+ height: 16px;
15
+ opacity: 0.3;
16
+ background-image: url(../../images/icons/draggable-block-menu.svg);
17
+ }
18
+
19
+ .draggable-block-menu:active {
20
+ cursor: grabbing;
21
+ }
22
+
23
+ .draggable-block-menu:hover {
24
+ background-color: #efefef;
25
+ }
26
+
27
+ .draggable-block-target-line {
28
+ pointer-events: none;
29
+ background: deepskyblue;
30
+ height: 4px;
31
+ position: absolute;
32
+ left: 0;
33
+ top: 0;
34
+ opacity: 0;
35
+ will-change: transform;
36
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+ import './index.css';
9
+
10
+ import {DraggableBlockPlugin_EXPERIMENTAL} from '@lexical/react/LexicalDraggableBlockPlugin';
11
+ import {useRef} from 'react';
12
+
13
+ const DRAGGABLE_BLOCK_MENU_CLASSNAME = 'draggable-block-menu';
14
+
15
+ function isOnMenu(element: HTMLElement): boolean {
16
+ return !!element.closest(`.${DRAGGABLE_BLOCK_MENU_CLASSNAME}`);
17
+ }
18
+
19
+ export default function DraggableBlockPlugin({
20
+ anchorElem = document.body,
21
+ }: {
22
+ anchorElem?: HTMLElement;
23
+ }): JSX.Element {
24
+ const menuRef = useRef<HTMLDivElement>(null);
25
+ const targetLineRef = useRef<HTMLDivElement>(null);
26
+
27
+ return (
28
+ <DraggableBlockPlugin_EXPERIMENTAL
29
+ anchorElem={anchorElem}
30
+ menuRef={menuRef}
31
+ targetLineRef={targetLineRef}
32
+ menuComponent={
33
+ <div ref={menuRef} className="icon draggable-block-menu">
34
+ <div className="icon" />
35
+ </div>
36
+ }
37
+ targetLineComponent={
38
+ <div ref={targetLineRef} className="draggable-block-target-line" />
39
+ }
40
+ isOnMenu={isOnMenu}
41
+ />
42
+ );
43
+ }
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
10
+ import {
11
+ LexicalTypeaheadMenuPlugin,
12
+ MenuOption,
13
+ useBasicTypeaheadTriggerMatch,
14
+ } from '@lexical/react/LexicalTypeaheadMenuPlugin';
15
+ import {
16
+ $createTextNode,
17
+ $getSelection,
18
+ $isRangeSelection,
19
+ TextNode,
20
+ } from 'lexical';
21
+ import * as React from 'react';
22
+ import {useCallback, useEffect, useMemo, useState} from 'react';
23
+ import * as ReactDOM from 'react-dom';
24
+
25
+ class EmojiOption extends MenuOption {
26
+ title: string;
27
+ emoji: string;
28
+ keywords: Array<string>;
29
+
30
+ constructor(
31
+ title: string,
32
+ emoji: string,
33
+ options: {
34
+ keywords?: Array<string>;
35
+ },
36
+ ) {
37
+ super(title);
38
+ this.title = title;
39
+ this.emoji = emoji;
40
+ this.keywords = options.keywords || [];
41
+ }
42
+ }
43
+ function EmojiMenuItem({
44
+ index,
45
+ isSelected,
46
+ onClick,
47
+ onMouseEnter,
48
+ option,
49
+ }: {
50
+ index: number;
51
+ isSelected: boolean;
52
+ onClick: () => void;
53
+ onMouseEnter: () => void;
54
+ option: EmojiOption;
55
+ }) {
56
+ let className = 'item';
57
+ if (isSelected) {
58
+ className += ' selected';
59
+ }
60
+ return (
61
+ <li
62
+ key={option.key}
63
+ tabIndex={-1}
64
+ className={className}
65
+ ref={option.setRefElement}
66
+ role="option"
67
+ aria-selected={isSelected}
68
+ id={'typeahead-item-' + index}
69
+ onMouseEnter={onMouseEnter}
70
+ onClick={onClick}>
71
+ <span className="text">
72
+ {option.emoji} {option.title}
73
+ </span>
74
+ </li>
75
+ );
76
+ }
77
+
78
+ type Emoji = {
79
+ emoji: string;
80
+ description: string;
81
+ category: string;
82
+ aliases: Array<string>;
83
+ tags: Array<string>;
84
+ unicode_version: string;
85
+ ios_version: string;
86
+ skin_tones?: boolean;
87
+ };
88
+
89
+ const MAX_EMOJI_SUGGESTION_COUNT = 10;
90
+
91
+ export default function EmojiPickerPlugin() {
92
+ const [editor] = useLexicalComposerContext();
93
+ const [queryString, setQueryString] = useState<string | null>(null);
94
+ const [emojis, setEmojis] = useState<Array<Emoji>>([]);
95
+
96
+ useEffect(() => {
97
+ import('../../utils/emoji-list').then((file) => setEmojis(file.default));
98
+ }, []);
99
+
100
+ const emojiOptions = useMemo(
101
+ () =>
102
+ emojis != null
103
+ ? emojis.map(
104
+ ({emoji, aliases, tags}) =>
105
+ new EmojiOption(aliases[0], emoji, {
106
+ keywords: [...aliases, ...tags],
107
+ }),
108
+ )
109
+ : [],
110
+ [emojis],
111
+ );
112
+
113
+ const checkForTriggerMatch = useBasicTypeaheadTriggerMatch(':', {
114
+ minLength: 0,
115
+ });
116
+
117
+ const options: Array<EmojiOption> = useMemo(() => {
118
+ return emojiOptions
119
+ .filter((option: EmojiOption) => {
120
+ return queryString != null
121
+ ? new RegExp(queryString, 'gi').exec(option.title) ||
122
+ option.keywords != null
123
+ ? option.keywords.some((keyword: string) =>
124
+ new RegExp(queryString, 'gi').exec(keyword),
125
+ )
126
+ : false
127
+ : emojiOptions;
128
+ })
129
+ .slice(0, MAX_EMOJI_SUGGESTION_COUNT);
130
+ }, [emojiOptions, queryString]);
131
+
132
+ const onSelectOption = useCallback(
133
+ (
134
+ selectedOption: EmojiOption,
135
+ nodeToRemove: TextNode | null,
136
+ closeMenu: () => void,
137
+ ) => {
138
+ editor.update(() => {
139
+ const selection = $getSelection();
140
+
141
+ if (!$isRangeSelection(selection) || selectedOption == null) {
142
+ return;
143
+ }
144
+
145
+ if (nodeToRemove) {
146
+ nodeToRemove.remove();
147
+ }
148
+
149
+ selection.insertNodes([$createTextNode(selectedOption.emoji)]);
150
+
151
+ closeMenu();
152
+ });
153
+ },
154
+ [editor],
155
+ );
156
+
157
+ return (
158
+ <LexicalTypeaheadMenuPlugin
159
+ onQueryChange={setQueryString}
160
+ onSelectOption={onSelectOption}
161
+ triggerFn={checkForTriggerMatch}
162
+ options={options}
163
+ menuRenderFn={(
164
+ anchorElementRef,
165
+ {selectedIndex, selectOptionAndCleanUp, setHighlightedIndex},
166
+ ) => {
167
+ if (anchorElementRef.current == null || options.length === 0) {
168
+ return null;
169
+ }
170
+
171
+ return anchorElementRef.current && options.length
172
+ ? ReactDOM.createPortal(
173
+ <div className="typeahead-popover emoji-menu">
174
+ <ul>
175
+ {options.map((option: EmojiOption, index) => (
176
+ <EmojiMenuItem
177
+ key={option.key}
178
+ index={index}
179
+ isSelected={selectedIndex === index}
180
+ onClick={() => {
181
+ setHighlightedIndex(index);
182
+ selectOptionAndCleanUp(option);
183
+ }}
184
+ onMouseEnter={() => {
185
+ setHighlightedIndex(index);
186
+ }}
187
+ option={option}
188
+ />
189
+ ))}
190
+ </ul>
191
+ </div>,
192
+ anchorElementRef.current,
193
+ )
194
+ : null;
195
+ }}
196
+ />
197
+ );
198
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ import type {LexicalEditor} from 'lexical';
10
+
11
+ import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
12
+ import {TextNode} from 'lexical';
13
+ import {useEffect} from 'react';
14
+
15
+ import {$createEmojiNode, EmojiNode} from '../../nodes/EmojiNode';
16
+
17
+ const emojis: Map<string, [string, string]> = new Map([
18
+ [':)', ['emoji happysmile', '🙂']],
19
+ [':D', ['emoji veryhappysmile', '😀']],
20
+ [':(', ['emoji unhappysmile', '🙁']],
21
+ ['<3', ['emoji heart', '❤']],
22
+ ]);
23
+
24
+ function $findAndTransformEmoji(node: TextNode): null | TextNode {
25
+ const text = node.getTextContent();
26
+
27
+ for (let i = 0; i < text.length; i++) {
28
+ const emojiData = emojis.get(text[i]) || emojis.get(text.slice(i, i + 2));
29
+
30
+ if (emojiData !== undefined) {
31
+ const [emojiStyle, emojiText] = emojiData;
32
+ let targetNode;
33
+
34
+ if (i === 0) {
35
+ [targetNode] = node.splitText(i + 2);
36
+ } else {
37
+ [, targetNode] = node.splitText(i, i + 2);
38
+ }
39
+
40
+ const emojiNode = $createEmojiNode(emojiStyle, emojiText);
41
+ targetNode.replace(emojiNode);
42
+ return emojiNode;
43
+ }
44
+ }
45
+
46
+ return null;
47
+ }
48
+
49
+ function $textNodeTransform(node: TextNode): void {
50
+ let targetNode: TextNode | null = node;
51
+
52
+ while (targetNode !== null) {
53
+ if (!targetNode.isSimpleText()) {
54
+ return;
55
+ }
56
+
57
+ targetNode = $findAndTransformEmoji(targetNode);
58
+ }
59
+ }
60
+
61
+ function useEmojis(editor: LexicalEditor): void {
62
+ useEffect(() => {
63
+ if (!editor.hasNodes([EmojiNode])) {
64
+ throw new Error('EmojisPlugin: EmojiNode not registered on editor');
65
+ }
66
+
67
+ return editor.registerNodeTransform(TextNode, $textNodeTransform);
68
+ }, [editor]);
69
+ }
70
+
71
+ export default function EmojisPlugin(): JSX.Element | null {
72
+ const [editor] = useLexicalComposerContext();
73
+ useEmojis(editor);
74
+ return null;
75
+ }