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,768 @@
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
+ // Core
10
+ import Button from '@client/components/button';
11
+ import DropDown from '@client/components/dropdown';
12
+ import useContext from '@/client/context';
13
+
14
+ // Local
15
+ import ElementFormatDropdown from './ElementFormat';
16
+ import BlockFormatDropDown, { blockTypeNames, rootTypeToRootName } from './BlockFormat';
17
+
18
+ import {
19
+ $createCodeNode,
20
+ $isCodeNode,
21
+ CODE_LANGUAGE_FRIENDLY_NAME_MAP,
22
+ CODE_LANGUAGE_MAP,
23
+ getLanguageFriendlyName,
24
+ } from '@lexical/code';
25
+ import { $isLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link';
26
+ import {
27
+ $isListNode,
28
+ INSERT_CHECK_LIST_COMMAND,
29
+ INSERT_ORDERED_LIST_COMMAND,
30
+ INSERT_UNORDERED_LIST_COMMAND,
31
+ ListNode,
32
+ } from '@lexical/list';
33
+ import { INSERT_EMBED_COMMAND } from '@lexical/react/LexicalAutoEmbedPlugin';
34
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
35
+ import { $isDecoratorBlockNode } from '@lexical/react/LexicalDecoratorBlockNode';
36
+ import { INSERT_HORIZONTAL_RULE_COMMAND } from '@lexical/react/LexicalHorizontalRuleNode';
37
+ import {
38
+ $createHeadingNode,
39
+ $createQuoteNode,
40
+ $isHeadingNode,
41
+ $isQuoteNode,
42
+ HeadingTagType,
43
+ } from '@lexical/rich-text';
44
+ import {
45
+ $getSelectionStyleValueForProperty,
46
+ $isParentElementRTL,
47
+ $patchStyleText,
48
+ $setBlocksType,
49
+ } from '@lexical/selection';
50
+ import { $isTableNode, $isTableSelection } from '@lexical/table';
51
+ import {
52
+ $findMatchingParent,
53
+ $getNearestBlockElementAncestorOrThrow,
54
+ $getNearestNodeOfType,
55
+ $isEditorIsNestedEditor,
56
+ mergeRegister,
57
+ } from '@lexical/utils';
58
+ import {
59
+ $createParagraphNode,
60
+ $getNodeByKey,
61
+ $getRoot,
62
+ $getSelection,
63
+ $isElementNode,
64
+ $isRangeSelection,
65
+ $isRootOrShadowRoot,
66
+ $isTextNode,
67
+ CAN_REDO_COMMAND,
68
+ CAN_UNDO_COMMAND,
69
+ COMMAND_PRIORITY_CRITICAL,
70
+ COMMAND_PRIORITY_NORMAL,
71
+ ElementFormatType,
72
+ FORMAT_ELEMENT_COMMAND,
73
+ FORMAT_TEXT_COMMAND,
74
+ INDENT_CONTENT_COMMAND,
75
+ KEY_MODIFIER_COMMAND,
76
+ LexicalEditor,
77
+ NodeKey,
78
+ OUTDENT_CONTENT_COMMAND,
79
+ REDO_COMMAND,
80
+ SELECTION_CHANGE_COMMAND,
81
+ UNDO_COMMAND,
82
+ } from 'lexical';
83
+ import { Dispatch, useCallback, useEffect, useState } from 'react';
84
+ import * as React from 'react';
85
+ import { IS_APPLE } from '../shared/environment';
86
+
87
+ import { $createStickyNode } from '../nodes/StickyNode';
88
+ //import DropdownColorPicker from '../../ui/DropdownColorPicker';
89
+ import { getSelectedNode } from '../utils/getSelectedNode';
90
+ import { sanitizeUrl } from '../utils/url';
91
+
92
+ import { EmbedConfigs } from '../plugins/AutoEmbedPlugin';
93
+ import { INSERT_COLLAPSIBLE_COMMAND } from '../plugins/CollapsiblePlugin';
94
+ import {
95
+ INSERT_IMAGE_COMMAND,
96
+ InsertImageDialog,
97
+ InsertImagePayload,
98
+ } from '../plugins/ImagesPlugin';
99
+ import { InsertInlineImageDialog } from '../plugins/InlineImagePlugin';
100
+ import InsertLayoutDialog from '../plugins/LayoutPlugin/InsertLayoutDialog';
101
+ import { INSERT_PAGE_BREAK } from '../plugins/PageBreakPlugin';
102
+ import { InsertPollDialog } from '../plugins/PollPlugin';
103
+ import { InsertTableDialog } from '../plugins/TablePlugin';
104
+
105
+ function getCodeLanguageOptions(): [string, string][] {
106
+ const options: [string, string][] = [];
107
+
108
+ for (const [lang, friendlyName] of Object.entries(
109
+ CODE_LANGUAGE_FRIENDLY_NAME_MAP,
110
+ )) {
111
+ options.push([lang, friendlyName]);
112
+ }
113
+
114
+ return options;
115
+ }
116
+
117
+ const CODE_LANGUAGE_OPTIONS = getCodeLanguageOptions();
118
+
119
+ function dropDownActiveClass(active: boolean) {
120
+ if (active) {
121
+ return 'active dropdown-item-active';
122
+ } else {
123
+ return '';
124
+ }
125
+ }
126
+
127
+
128
+
129
+ function Divider(): JSX.Element {
130
+ return <div className="divider" />;
131
+ }
132
+
133
+ export default function ToolbarPlugin({
134
+ setIsLinkEditMode,
135
+ }: {
136
+ setIsLinkEditMode: Dispatch<boolean>;
137
+ }): JSX.Element {
138
+
139
+ const { modal } = useContext();
140
+
141
+ const [editor] = useLexicalComposerContext();
142
+ const [activeEditor, setActiveEditor] = useState(editor);
143
+ const [blockType, setBlockType] =
144
+ useState<typeof blockTypeNames[number]>('paragraph');
145
+ const [rootType, setRootType] =
146
+ useState<keyof typeof rootTypeToRootName>('root');
147
+ const [selectedElementKey, setSelectedElementKey] = useState<NodeKey | null>(
148
+ null,
149
+ );
150
+ const [fontSize, setFontSize] = useState<string>('15px');
151
+ const [fontColor, setFontColor] = useState<string>('#000');
152
+ const [bgColor, setBgColor] = useState<string>('#fff');
153
+ const [fontFamily, setFontFamily] = useState<string>('Arial');
154
+ const [elementFormat, setElementFormat] = useState<ElementFormatType>('left');
155
+ const [isLink, setIsLink] = useState(false);
156
+ const [isBold, setIsBold] = useState(false);
157
+ const [isItalic, setIsItalic] = useState(false);
158
+ const [isUnderline, setIsUnderline] = useState(false);
159
+ const [isStrikethrough, setIsStrikethrough] = useState(false);
160
+ const [isSubscript, setIsSubscript] = useState(false);
161
+ const [isSuperscript, setIsSuperscript] = useState(false);
162
+ const [isCode, setIsCode] = useState(false);
163
+ const [canUndo, setCanUndo] = useState(false);
164
+ const [canRedo, setCanRedo] = useState(false);
165
+ const [isRTL, setIsRTL] = useState(false);
166
+ const [codeLanguage, setCodeLanguage] = useState<string>('');
167
+ const [isEditable, setIsEditable] = useState(() => editor.isEditable());
168
+ const [isImageCaption, setIsImageCaption] = useState(false);
169
+
170
+ const $updateToolbar = useCallback(() => {
171
+ const selection = $getSelection();
172
+ if ($isRangeSelection(selection)) {
173
+ if (activeEditor !== editor && $isEditorIsNestedEditor(activeEditor)) {
174
+ const rootElement = activeEditor.getRootElement();
175
+ setIsImageCaption(
176
+ !!rootElement?.parentElement?.classList.contains(
177
+ 'image-caption-container',
178
+ ),
179
+ );
180
+ } else {
181
+ setIsImageCaption(false);
182
+ }
183
+
184
+ const anchorNode = selection.anchor.getNode();
185
+ let element =
186
+ anchorNode.getKey() === 'root'
187
+ ? anchorNode
188
+ : $findMatchingParent(anchorNode, (e) => {
189
+ const parent = e.getParent();
190
+ return parent !== null && $isRootOrShadowRoot(parent);
191
+ });
192
+
193
+ if (element === null) {
194
+ element = anchorNode.getTopLevelElementOrThrow();
195
+ }
196
+
197
+ const elementKey = element.getKey();
198
+ const elementDOM = activeEditor.getElementByKey(elementKey);
199
+
200
+ setIsRTL($isParentElementRTL(selection));
201
+
202
+ // Update links
203
+ const node = getSelectedNode(selection);
204
+ const parent = node.getParent();
205
+ if ($isLinkNode(parent) || $isLinkNode(node)) {
206
+ setIsLink(true);
207
+ } else {
208
+ setIsLink(false);
209
+ }
210
+
211
+ const tableNode = $findMatchingParent(node, $isTableNode);
212
+ if ($isTableNode(tableNode)) {
213
+ setRootType('table');
214
+ } else {
215
+ setRootType('root');
216
+ }
217
+
218
+ if (elementDOM !== null) {
219
+ setSelectedElementKey(elementKey);
220
+ if ($isListNode(element)) {
221
+ const parentList = $getNearestNodeOfType<ListNode>(
222
+ anchorNode,
223
+ ListNode,
224
+ );
225
+ const type = parentList
226
+ ? parentList.getListType()
227
+ : element.getListType();
228
+ setBlockType(type);
229
+ } else {
230
+ const type = $isHeadingNode(element)
231
+ ? element.getTag()
232
+ : element.getType();
233
+ if (blockTypeNames.includes(type)) {
234
+ setBlockType(type as typeof blockTypeNames[number]);
235
+ }
236
+ if ($isCodeNode(element)) {
237
+ const language =
238
+ element.getLanguage() as keyof typeof CODE_LANGUAGE_MAP;
239
+ setCodeLanguage(
240
+ language ? CODE_LANGUAGE_MAP[language] || language : '',
241
+ );
242
+ return;
243
+ }
244
+ }
245
+ }
246
+ // Handle buttons
247
+ setFontColor(
248
+ $getSelectionStyleValueForProperty(selection, 'color', '#000'),
249
+ );
250
+ setBgColor(
251
+ $getSelectionStyleValueForProperty(
252
+ selection,
253
+ 'background-color',
254
+ '#fff',
255
+ ),
256
+ );
257
+ setFontFamily(
258
+ $getSelectionStyleValueForProperty(selection, 'font-family', 'Arial'),
259
+ );
260
+ let matchingParent;
261
+ if ($isLinkNode(parent)) {
262
+ // If node is a link, we need to fetch the parent paragraph node to set format
263
+ matchingParent = $findMatchingParent(
264
+ node,
265
+ (parentNode) => $isElementNode(parentNode) && !parentNode.isInline(),
266
+ );
267
+ }
268
+
269
+ // If matchingParent is a valid node, pass it's format type
270
+ setElementFormat(
271
+ $isElementNode(matchingParent)
272
+ ? matchingParent.getFormatType()
273
+ : $isElementNode(node)
274
+ ? node.getFormatType()
275
+ : parent?.getFormatType() || 'left',
276
+ );
277
+ }
278
+ if ($isRangeSelection(selection) || $isTableSelection(selection)) {
279
+ // Update text format
280
+ setIsBold(selection.hasFormat('bold'));
281
+ setIsItalic(selection.hasFormat('italic'));
282
+ setIsUnderline(selection.hasFormat('underline'));
283
+ setIsStrikethrough(selection.hasFormat('strikethrough'));
284
+ setIsSubscript(selection.hasFormat('subscript'));
285
+ setIsSuperscript(selection.hasFormat('superscript'));
286
+ setIsCode(selection.hasFormat('code'));
287
+
288
+ setFontSize(
289
+ $getSelectionStyleValueForProperty(selection, 'font-size', '15px'),
290
+ );
291
+ }
292
+ }, [activeEditor, editor]);
293
+
294
+ useEffect(() => {
295
+ return editor.registerCommand(
296
+ SELECTION_CHANGE_COMMAND,
297
+ (_payload, newEditor) => {
298
+ setActiveEditor(newEditor);
299
+ $updateToolbar();
300
+ return false;
301
+ },
302
+ COMMAND_PRIORITY_CRITICAL,
303
+ );
304
+ }, [editor, $updateToolbar]);
305
+
306
+ useEffect(() => {
307
+ activeEditor.getEditorState().read(() => {
308
+ $updateToolbar();
309
+ });
310
+ }, [activeEditor, $updateToolbar]);
311
+
312
+ useEffect(() => {
313
+ return mergeRegister(
314
+ editor.registerEditableListener((editable) => {
315
+ setIsEditable(editable);
316
+ }),
317
+ activeEditor.registerUpdateListener(({ editorState }) => {
318
+ editorState.read(() => {
319
+ $updateToolbar();
320
+ });
321
+ }),
322
+ activeEditor.registerCommand<boolean>(
323
+ CAN_UNDO_COMMAND,
324
+ (payload) => {
325
+ setCanUndo(payload);
326
+ return false;
327
+ },
328
+ COMMAND_PRIORITY_CRITICAL,
329
+ ),
330
+ activeEditor.registerCommand<boolean>(
331
+ CAN_REDO_COMMAND,
332
+ (payload) => {
333
+ setCanRedo(payload);
334
+ return false;
335
+ },
336
+ COMMAND_PRIORITY_CRITICAL,
337
+ ),
338
+ );
339
+ }, [$updateToolbar, activeEditor, editor]);
340
+
341
+ useEffect(() => {
342
+ return activeEditor.registerCommand(
343
+ KEY_MODIFIER_COMMAND,
344
+ (payload) => {
345
+ const event: KeyboardEvent = payload;
346
+ const { code, ctrlKey, metaKey } = event;
347
+
348
+ if (code === 'KeyK' && (ctrlKey || metaKey)) {
349
+ event.preventDefault();
350
+ let url: string | null;
351
+ if (!isLink) {
352
+ setIsLinkEditMode(true);
353
+ url = sanitizeUrl('https://');
354
+ } else {
355
+ setIsLinkEditMode(false);
356
+ url = null;
357
+ }
358
+ return activeEditor.dispatchCommand(TOGGLE_LINK_COMMAND, url);
359
+ }
360
+ return false;
361
+ },
362
+ COMMAND_PRIORITY_NORMAL,
363
+ );
364
+ }, [activeEditor, isLink, setIsLinkEditMode]);
365
+
366
+ const applyStyleText = useCallback(
367
+ (styles: Record<string, string>, skipHistoryStack?: boolean) => {
368
+ activeEditor.update(
369
+ () => {
370
+ const selection = $getSelection();
371
+ if (selection !== null) {
372
+ $patchStyleText(selection, styles);
373
+ }
374
+ },
375
+ skipHistoryStack ? { tag: 'historic' } : {},
376
+ );
377
+ },
378
+ [activeEditor],
379
+ );
380
+
381
+ const clearFormatting = useCallback(() => {
382
+ activeEditor.update(() => {
383
+ const selection = $getSelection();
384
+ if ($isRangeSelection(selection) || $isTableSelection(selection)) {
385
+ const anchor = selection.anchor;
386
+ const focus = selection.focus;
387
+ const nodes = selection.getNodes();
388
+ const extractedNodes = selection.extract();
389
+
390
+ if (anchor.key === focus.key && anchor.offset === focus.offset) {
391
+ return;
392
+ }
393
+
394
+ nodes.forEach((node, idx) => {
395
+ // We split the first and last node by the selection
396
+ // So that we don't format unselected text inside those nodes
397
+ if ($isTextNode(node)) {
398
+ // Use a separate variable to ensure TS does not lose the refinement
399
+ let textNode = node;
400
+ if (idx === 0 && anchor.offset !== 0) {
401
+ textNode = textNode.splitText(anchor.offset)[1] || textNode;
402
+ }
403
+ if (idx === nodes.length - 1) {
404
+ textNode = textNode.splitText(focus.offset)[0] || textNode;
405
+ }
406
+ /**
407
+ * If the selected text has one format applied
408
+ * selecting a portion of the text, could
409
+ * clear the format to the wrong portion of the text.
410
+ *
411
+ * The cleared text is based on the length of the selected text.
412
+ */
413
+ // We need this in case the selected text only has one format
414
+ const extractedTextNode = extractedNodes[0];
415
+ if (nodes.length === 1 && $isTextNode(extractedTextNode)) {
416
+ textNode = extractedTextNode;
417
+ }
418
+
419
+ if (textNode.__style !== '') {
420
+ textNode.setStyle('');
421
+ }
422
+ if (textNode.__format !== 0) {
423
+ textNode.setFormat(0);
424
+ $getNearestBlockElementAncestorOrThrow(textNode).setFormat('');
425
+ }
426
+ node = textNode;
427
+ } else if ($isHeadingNode(node) || $isQuoteNode(node)) {
428
+ node.replace($createParagraphNode(), true);
429
+ } else if ($isDecoratorBlockNode(node)) {
430
+ node.setFormat('');
431
+ }
432
+ });
433
+ }
434
+ });
435
+ }, [activeEditor]);
436
+
437
+ const onFontColorSelect = useCallback(
438
+ (value: string, skipHistoryStack: boolean) => {
439
+ applyStyleText({ color: value }, skipHistoryStack);
440
+ },
441
+ [applyStyleText],
442
+ );
443
+
444
+ const onBgColorSelect = useCallback(
445
+ (value: string, skipHistoryStack: boolean) => {
446
+ applyStyleText({ 'background-color': value }, skipHistoryStack);
447
+ },
448
+ [applyStyleText],
449
+ );
450
+
451
+ const insertLink = useCallback(() => {
452
+ if (!isLink) {
453
+ setIsLinkEditMode(true);
454
+ activeEditor.dispatchCommand(
455
+ TOGGLE_LINK_COMMAND,
456
+ sanitizeUrl('https://'),
457
+ );
458
+ } else {
459
+ setIsLinkEditMode(false);
460
+ activeEditor.dispatchCommand(TOGGLE_LINK_COMMAND, null);
461
+ }
462
+ }, [activeEditor, isLink, setIsLinkEditMode]);
463
+
464
+ const onCodeLanguageSelect = useCallback(
465
+ (value: string) => {
466
+ activeEditor.update(() => {
467
+ if (selectedElementKey !== null) {
468
+ const node = $getNodeByKey(selectedElementKey);
469
+ if ($isCodeNode(node)) {
470
+ node.setLanguage(value);
471
+ }
472
+ }
473
+ });
474
+ },
475
+ [activeEditor, selectedElementKey],
476
+ );
477
+ const insertGifOnClick = (payload: InsertImagePayload) => {
478
+ activeEditor.dispatchCommand(INSERT_IMAGE_COMMAND, payload);
479
+ };
480
+
481
+ const canViewerSeeInsertDropdown = !isImageCaption;
482
+ const canViewerSeeInsertCodeButton = !isImageCaption;
483
+
484
+ return (
485
+ <div className="row menu al-left">
486
+
487
+ {/* <Button icon="undo" size="s"
488
+ disabled={!canUndo || !isEditable}
489
+ title={IS_APPLE ? 'Undo (⌘Z)' : 'Undo (Ctrl+Z)'}
490
+ onClick={() => {
491
+ activeEditor.dispatchCommand(UNDO_COMMAND, undefined);
492
+ }}/>
493
+
494
+ <Button icon="redo" size="s"
495
+ disabled={!canRedo || !isEditable}
496
+ title={IS_APPLE ? 'Redo (⇧⌘Z)' : 'Redo (Ctrl+Y)'}
497
+ onClick={() => {
498
+ activeEditor.dispatchCommand(REDO_COMMAND, undefined);
499
+ }}/>
500
+
501
+ <Divider /> */}
502
+
503
+ {blockTypeNames.includes(blockType) && activeEditor === editor && (
504
+ <>
505
+ <BlockFormatDropDown
506
+ disabled={!isEditable}
507
+ blockType={blockType}
508
+ rootType={rootType}
509
+ editor={activeEditor}
510
+ />
511
+ <Divider />
512
+ </>
513
+ )}
514
+
515
+ {blockType === 'code' ? (
516
+ <DropDown popover={{ tag: 'li' }}
517
+ disabled={!isEditable}
518
+ icon="code"
519
+ content={(
520
+ <div class="card bg white col menu w-3">
521
+ {CODE_LANGUAGE_OPTIONS.map(([value, name]) => (
522
+ <Button size="s"
523
+ active={value === codeLanguage}
524
+ onClick={() => onCodeLanguageSelect(value)}
525
+ key={value}
526
+ >
527
+ {name}
528
+ </Button>
529
+ ))}
530
+ </div>
531
+ )}
532
+ >
533
+ {getLanguageFriendlyName(codeLanguage)}
534
+ </DropDown>
535
+ ) : (
536
+ <>
537
+ <Button icon="bold" size="s"
538
+ disabled={!isEditable}
539
+ onClick={() => {
540
+ activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, 'bold');
541
+ }}
542
+ active={isBold}
543
+ title={IS_APPLE ? 'Bold (⌘B)' : 'Bold (Ctrl+B)'}
544
+ />
545
+ <Button icon="italic" size="s"
546
+ disabled={!isEditable}
547
+ onClick={() => {
548
+ activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, 'italic');
549
+ }}
550
+ active={isItalic}
551
+ title={IS_APPLE ? 'Italic (⌘I)' : 'Italic (Ctrl+I)'}
552
+ />
553
+ <Button icon="underline" size="s"
554
+ disabled={!isEditable}
555
+ onClick={() => {
556
+ activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, 'underline');
557
+ }}
558
+ active={isUnderline}
559
+ title={IS_APPLE ? 'Underline (⌘U)' : 'Underline (Ctrl+U)'}
560
+ />
561
+
562
+ {canViewerSeeInsertCodeButton && (
563
+ <Button icon="code" size="s"
564
+ disabled={!isEditable}
565
+ onClick={() => {
566
+ activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, 'code');
567
+ }}
568
+ active={isCode}
569
+ title="Insert code block"
570
+ />
571
+ )}
572
+ <Button icon="link" size="s"
573
+ disabled={!isEditable}
574
+ onClick={insertLink}
575
+ active={isLink}
576
+ title="Insert link"
577
+ />
578
+
579
+ {/* <DropdownColorPicker
580
+ disabled={!isEditable}
581
+ buttonClassName="toolbar-item color-picker"
582
+ buttonAriaLabel="Formatting text color"
583
+ buttonIconClassName="icon font-color"
584
+ color={fontColor}
585
+ onChange={onFontColorSelect}
586
+ title="text color"
587
+ />
588
+ <DropdownColorPicker
589
+ disabled={!isEditable}
590
+ buttonClassName="toolbar-item color-picker"
591
+ buttonAriaLabel="Formatting background color"
592
+ buttonIconClassName="icon bg-color"
593
+ color={bgColor}
594
+ onChange={onBgColorSelect}
595
+ title="bg color"
596
+ /> */}
597
+
598
+ <DropDown popover={{ tag: 'li' }} icon="font" size="s"
599
+ disabled={!isEditable}
600
+ title="Formatting options for additional text styles"
601
+ buttonIconClassName="icon dropdown-more"
602
+ >
603
+
604
+ <Button icon="strikethrough" size="s"
605
+ onClick={() => {
606
+ activeEditor.dispatchCommand( FORMAT_TEXT_COMMAND, 'strikethrough');
607
+ }}
608
+ active={isStrikethrough}
609
+ title="Format text with a strikethrough"
610
+ >
611
+ Strikethrough
612
+ </Button>
613
+
614
+ <Button icon="subscript" size="s"
615
+ onClick={() => {
616
+ activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, 'subscript');
617
+ }}
618
+ active={isSubscript}
619
+ title="Format text with a subscript"
620
+ >
621
+ Subscript
622
+ </Button>
623
+
624
+ <Button icon="superscript" size="s"
625
+ onClick={() => {
626
+ activeEditor.dispatchCommand(
627
+ FORMAT_TEXT_COMMAND,
628
+ 'superscript',
629
+ );
630
+ }}
631
+ active={isSuperscript}
632
+ title="Format text with a superscript">
633
+ Superscript
634
+ </Button>
635
+
636
+ <Button icon="empty-set" size="s"
637
+ onClick={clearFormatting}
638
+ title="Clear all text formatting">
639
+ Clear Formatting
640
+ </Button>
641
+
642
+ </DropDown>
643
+
644
+ {canViewerSeeInsertDropdown && (
645
+ <>
646
+ <Divider />
647
+ <DropDown popover={{ tag: 'li' }}
648
+ disabled={!isEditable}
649
+ size="s"
650
+ label="Insert"
651
+ title="Insert specialized editor node"
652
+ >
653
+
654
+ <Button icon="horizontal-rule" size="s" onClick={() => {
655
+ activeEditor.dispatchCommand( INSERT_HORIZONTAL_RULE_COMMAND, undefined, );
656
+ }}>
657
+ Horizontal Rule
658
+ </Button>
659
+
660
+ <Button icon="page-break" size="s" onClick={() => {
661
+ activeEditor.dispatchCommand(INSERT_PAGE_BREAK, undefined);
662
+ }}>
663
+ Page Break
664
+ </Button>
665
+
666
+ <Button icon="image" size="s" onClick={() => {
667
+ modal.show('Insert Image', InsertImageDialog, { editor: activeEditor });
668
+ }}>
669
+ Image
670
+ </Button>
671
+
672
+ <Button icon="image" size="s" onClick={() => {
673
+ modal.show('Insert Inline Image', InsertInlineImageDialog, { editor: activeEditor });
674
+ }}>
675
+ Inline Image
676
+ </Button>
677
+
678
+ {/* <Button
679
+ onClick={() => {
680
+ activeEditor.dispatchCommand(
681
+ INSERT_EXCALIDRAW_COMMAND,
682
+ undefined,
683
+ );
684
+ }}
685
+ className="item">
686
+ <i className="icon diagram-2" />
687
+ <span className="text">Excalidraw</span>
688
+ </Button> */}
689
+
690
+ <Button icon="table" size="s" onClick={() => {
691
+ modal.show('Insert Table', InsertTableDialog, { editor: activeEditor });
692
+ }}>
693
+ Table
694
+ </Button>
695
+
696
+ <Button icon="poll" size="s" onClick={() => {
697
+ modal.show('Insert Poll', InsertPollDialog, { editor: activeEditor });
698
+ }}>
699
+ Poll
700
+ </Button>
701
+
702
+ <Button icon="columns" size="s" onClick={() => {
703
+ modal.show('Insert Columns Layout', InsertLayoutDialog, { editor: activeEditor });
704
+ }} >
705
+ Columns Layout
706
+ </Button>
707
+
708
+ {/* <Button
709
+ onClick={() => {
710
+ modal.show('Insert Equation', (onClose) => (
711
+ <InsertEquationDialog
712
+ activeEditor={activeEditor}
713
+ onClose={onClose}
714
+ />
715
+ ));
716
+ }}
717
+ className="item">
718
+ <i className="icon equation" />
719
+ <span className="text">Equation</span>
720
+ </Button> */}
721
+
722
+ <Button icon="sticky-note" size="s" onClick={() => {
723
+ editor.update(() => {
724
+ const root = $getRoot();
725
+ const stickyNode = $createStickyNode(0, 0);
726
+ root.append(stickyNode);
727
+ });
728
+ }}>
729
+ Sticky Note
730
+ </Button>
731
+
732
+ <Button icon="caret-right" size="s" onClick={() => {
733
+ editor.dispatchCommand(
734
+ INSERT_COLLAPSIBLE_COMMAND,
735
+ undefined,
736
+ );
737
+ }}>
738
+ Collapsible container
739
+ </Button>
740
+
741
+ {/*EmbedConfigs.map((embedConfig) => (
742
+ <Button
743
+ key={embedConfig.type}
744
+ onClick={() => {
745
+ activeEditor.dispatchCommand(
746
+ INSERT_EMBED_COMMAND,
747
+ embedConfig.type,
748
+ );
749
+ }}>
750
+ {embedConfig.icon}
751
+ <span className="text">{embedConfig.contentName}</span>
752
+ </Button>
753
+ ))*/}
754
+ </DropDown>
755
+ </>
756
+ )}
757
+ </>
758
+ )}
759
+ <Divider />
760
+ <ElementFormatDropdown
761
+ disabled={!isEditable}
762
+ value={elementFormat}
763
+ editor={activeEditor}
764
+ isRTL={isRTL}
765
+ />
766
+ </div>
767
+ );
768
+ }