5htp-core 0.4.8 → 0.4.9-2

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 (190) hide show
  1. package/package.json +5 -1
  2. package/src/client/assets/css/components/table.less +3 -0
  3. package/src/client/components/Form.ts +2 -6
  4. package/src/client/components/Table/index.tsx +24 -79
  5. package/src/client/components/button.tsx +2 -1
  6. package/src/client/components/containers/Popover/index.tsx +2 -2
  7. package/src/client/components/dropdown/index.tsx +16 -6
  8. package/src/client/components/input/Slider/index.tsx +0 -2
  9. package/src/client/components/inputv3/Rte/Editor.tsx +271 -0
  10. package/src/client/components/inputv3/Rte/ToolbarPlugin/BlockFormat.tsx +220 -0
  11. package/src/client/components/inputv3/Rte/ToolbarPlugin/ElementFormat.tsx +107 -0
  12. package/src/client/components/inputv3/Rte/ToolbarPlugin/index.tsx +768 -0
  13. package/src/client/components/inputv3/Rte/appSettings.ts +36 -0
  14. package/src/client/components/inputv3/Rte/context/FlashMessageContext.tsx +68 -0
  15. package/src/client/components/inputv3/Rte/context/SettingsContext.tsx +71 -0
  16. package/src/client/components/inputv3/Rte/context/SharedAutocompleteContext.tsx +71 -0
  17. package/src/client/components/inputv3/Rte/context/SharedHistoryContext.tsx +35 -0
  18. package/src/client/components/inputv3/Rte/currentEditor.ts +42 -0
  19. package/src/client/components/inputv3/Rte/hooks/useFlashMessage.tsx +16 -0
  20. package/src/client/components/inputv3/Rte/hooks/useReport.ts +67 -0
  21. package/src/client/components/inputv3/Rte/images/emoji/1F600.png +0 -0
  22. package/src/client/components/inputv3/Rte/images/emoji/1F641.png +0 -0
  23. package/src/client/components/inputv3/Rte/images/emoji/1F642.png +0 -0
  24. package/src/client/components/inputv3/Rte/images/emoji/2764.png +0 -0
  25. package/src/client/components/inputv3/Rte/images/emoji/LICENSE.md +5 -0
  26. package/src/client/components/inputv3/Rte/images/icons/draggable-block-menu.svg +1 -0
  27. package/src/client/components/inputv3/Rte/images/icons/prettier-error.svg +1 -0
  28. package/src/client/components/inputv3/Rte/images/icons/prettier.svg +1 -0
  29. package/src/client/components/inputv3/Rte/images/image/LICENSE.md +5 -0
  30. package/src/client/components/inputv3/Rte/images/image-broken.svg +4 -0
  31. package/src/client/components/inputv3/Rte/images/logo.svg +1 -0
  32. package/src/client/components/inputv3/Rte/index.tsx +63 -79
  33. package/src/client/components/inputv3/Rte/nodes/AutocompleteNode.tsx +119 -0
  34. package/src/client/components/inputv3/Rte/nodes/EmojiNode.tsx +102 -0
  35. package/src/client/components/inputv3/Rte/nodes/EquationComponent.tsx +141 -0
  36. package/src/client/components/inputv3/Rte/nodes/EquationNode.tsx +174 -0
  37. package/src/client/components/inputv3/Rte/nodes/FigmaNode.tsx +135 -0
  38. package/src/client/components/inputv3/Rte/nodes/ImageComponent.tsx +468 -0
  39. package/src/client/components/inputv3/Rte/nodes/ImageNode.css +43 -0
  40. package/src/client/components/inputv3/Rte/nodes/ImageNode.tsx +266 -0
  41. package/src/client/components/inputv3/Rte/nodes/InlineImageNode/InlineImageComponent.tsx +402 -0
  42. package/src/client/components/inputv3/Rte/nodes/InlineImageNode/InlineImageNode.css +94 -0
  43. package/src/client/components/inputv3/Rte/nodes/InlineImageNode/InlineImageNode.tsx +294 -0
  44. package/src/client/components/inputv3/Rte/nodes/KeywordNode.ts +67 -0
  45. package/src/client/components/inputv3/Rte/nodes/LayoutContainerNode.ts +137 -0
  46. package/src/client/components/inputv3/Rte/nodes/LayoutItemNode.ts +71 -0
  47. package/src/client/components/inputv3/Rte/nodes/MentionNode.ts +130 -0
  48. package/src/client/components/inputv3/Rte/nodes/PageBreakNode/index.css +62 -0
  49. package/src/client/components/inputv3/Rte/nodes/PageBreakNode/index.tsx +170 -0
  50. package/src/client/components/inputv3/Rte/nodes/PlaygroundNodes.ts +76 -0
  51. package/src/client/components/inputv3/Rte/nodes/PollComponent.tsx +249 -0
  52. package/src/client/components/inputv3/Rte/nodes/PollNode.css +187 -0
  53. package/src/client/components/inputv3/Rte/nodes/PollNode.tsx +209 -0
  54. package/src/client/components/inputv3/Rte/nodes/StickyComponent.tsx +261 -0
  55. package/src/client/components/inputv3/Rte/nodes/StickyNode.css +37 -0
  56. package/src/client/components/inputv3/Rte/nodes/StickyNode.tsx +150 -0
  57. package/src/client/components/inputv3/Rte/nodes/TweetNode.tsx +223 -0
  58. package/src/client/components/inputv3/Rte/nodes/YouTubeNode.tsx +184 -0
  59. package/src/client/components/inputv3/Rte/plugins/ActionsPlugin/index.tsx +334 -0
  60. package/src/client/components/inputv3/Rte/plugins/AutoEmbedPlugin/index.tsx +352 -0
  61. package/src/client/components/inputv3/Rte/plugins/AutoLinkPlugin/index.tsx +32 -0
  62. package/src/client/components/inputv3/Rte/plugins/AutocompletePlugin/index.tsx +2529 -0
  63. package/src/client/components/inputv3/Rte/plugins/CodeActionMenuPlugin/components/CopyButton/index.tsx +70 -0
  64. package/src/client/components/inputv3/Rte/plugins/CodeActionMenuPlugin/components/PrettierButton/index.css +14 -0
  65. package/src/client/components/inputv3/Rte/plugins/CodeActionMenuPlugin/components/PrettierButton/index.tsx +156 -0
  66. package/src/client/components/inputv3/Rte/plugins/CodeActionMenuPlugin/index.css +54 -0
  67. package/src/client/components/inputv3/Rte/plugins/CodeActionMenuPlugin/index.tsx +190 -0
  68. package/src/client/components/inputv3/Rte/plugins/CodeActionMenuPlugin/utils.ts +33 -0
  69. package/src/client/components/inputv3/Rte/plugins/CodeHighlightPlugin/index.ts +21 -0
  70. package/src/client/components/inputv3/Rte/plugins/CollapsiblePlugin/Collapsible.css +57 -0
  71. package/src/client/components/inputv3/Rte/plugins/CollapsiblePlugin/CollapsibleContainerNode.ts +168 -0
  72. package/src/client/components/inputv3/Rte/plugins/CollapsiblePlugin/CollapsibleContentNode.ts +127 -0
  73. package/src/client/components/inputv3/Rte/plugins/CollapsiblePlugin/CollapsibleTitleNode.ts +152 -0
  74. package/src/client/components/inputv3/Rte/plugins/CollapsiblePlugin/CollapsibleUtils.ts +17 -0
  75. package/src/client/components/inputv3/Rte/plugins/CollapsiblePlugin/index.ts +284 -0
  76. package/src/client/components/inputv3/Rte/plugins/ComponentPickerPlugin/index.tsx +370 -0
  77. package/src/client/components/inputv3/Rte/plugins/ContextMenuPlugin/index.tsx +270 -0
  78. package/src/client/components/inputv3/Rte/plugins/DocsPlugin/index.tsx +20 -0
  79. package/src/client/components/inputv3/Rte/plugins/DragDropPastePlugin/index.ts +51 -0
  80. package/src/client/components/inputv3/Rte/plugins/DraggableBlockPlugin/index.css +36 -0
  81. package/src/client/components/inputv3/Rte/plugins/DraggableBlockPlugin/index.tsx +43 -0
  82. package/src/client/components/inputv3/Rte/plugins/EmojiPickerPlugin/index.tsx +198 -0
  83. package/src/client/components/inputv3/Rte/plugins/EmojisPlugin/index.ts +75 -0
  84. package/src/client/components/inputv3/Rte/plugins/EquationsPlugin/index.tsx +82 -0
  85. package/src/client/components/inputv3/Rte/plugins/FigmaPlugin/index.tsx +40 -0
  86. package/src/client/components/inputv3/Rte/plugins/FloatingLinkEditorPlugin/index.css +41 -0
  87. package/src/client/components/inputv3/Rte/plugins/FloatingLinkEditorPlugin/index.tsx +393 -0
  88. package/src/client/components/inputv3/Rte/plugins/FloatingTextFormatToolbarPlugin/index.css +141 -0
  89. package/src/client/components/inputv3/Rte/plugins/FloatingTextFormatToolbarPlugin/index.tsx +388 -0
  90. package/src/client/components/inputv3/Rte/plugins/ImagesPlugin/index.tsx +350 -0
  91. package/src/client/components/inputv3/Rte/plugins/InlineImagePlugin/index.tsx +336 -0
  92. package/src/client/components/inputv3/Rte/plugins/KeywordsPlugin/index.ts +56 -0
  93. package/src/client/components/inputv3/Rte/plugins/LayoutPlugin/InsertLayoutDialog.tsx +58 -0
  94. package/src/client/components/inputv3/Rte/plugins/LayoutPlugin/LayoutPlugin.tsx +219 -0
  95. package/src/client/components/inputv3/Rte/plugins/LinkPlugin/index.tsx +34 -0
  96. package/src/client/components/inputv3/Rte/plugins/ListMaxIndentLevelPlugin/index.ts +85 -0
  97. package/src/client/components/inputv3/Rte/plugins/MarkdownShortcutPlugin/index.tsx +16 -0
  98. package/src/client/components/inputv3/Rte/plugins/MarkdownTransformers/index.ts +324 -0
  99. package/src/client/components/inputv3/Rte/plugins/MaxLengthPlugin/index.tsx +53 -0
  100. package/src/client/components/inputv3/Rte/plugins/MentionsPlugin/index.tsx +696 -0
  101. package/src/client/components/inputv3/Rte/plugins/PageBreakPlugin/index.tsx +57 -0
  102. package/src/client/components/inputv3/Rte/plugins/PasteLogPlugin/index.tsx +54 -0
  103. package/src/client/components/inputv3/Rte/plugins/PollPlugin/index.tsx +86 -0
  104. package/src/client/components/inputv3/Rte/plugins/SpeechToTextPlugin/index.ts +125 -0
  105. package/src/client/components/inputv3/Rte/plugins/StickyPlugin/index.ts +22 -0
  106. package/src/client/components/inputv3/Rte/plugins/TabFocusPlugin/index.tsx +65 -0
  107. package/src/client/components/inputv3/Rte/plugins/TableActionMenuPlugin/index.tsx +773 -0
  108. package/src/client/components/inputv3/Rte/plugins/TableCellResizer/index.css +12 -0
  109. package/src/client/components/inputv3/Rte/plugins/TableCellResizer/index.tsx +436 -0
  110. package/src/client/components/inputv3/Rte/plugins/TableHoverActionsPlugin/index.tsx +287 -0
  111. package/src/client/components/inputv3/Rte/plugins/TableOfContentsPlugin/index.css +95 -0
  112. package/src/client/components/inputv3/Rte/plugins/TableOfContentsPlugin/index.tsx +197 -0
  113. package/src/client/components/inputv3/Rte/plugins/TablePlugin.tsx +178 -0
  114. package/src/client/components/inputv3/Rte/plugins/TestRecorderPlugin/index.tsx +468 -0
  115. package/src/client/components/inputv3/Rte/plugins/TreeViewPlugin/index.tsx +26 -0
  116. package/src/client/components/inputv3/Rte/plugins/TwitterPlugin/index.ts +41 -0
  117. package/src/client/components/inputv3/Rte/plugins/TypingPerfPlugin/index.ts +117 -0
  118. package/src/client/components/inputv3/Rte/plugins/YouTubePlugin/index.ts +41 -0
  119. package/src/client/components/inputv3/Rte/shared/canUseDOM.ts +4 -0
  120. package/src/client/components/inputv3/Rte/shared/caretFromPoint.ts +40 -0
  121. package/src/client/components/inputv3/Rte/shared/environment.ts +56 -0
  122. package/src/client/components/inputv3/Rte/shared/invariant.ts +26 -0
  123. package/src/client/components/inputv3/Rte/shared/normalizeClassNames.ts +21 -0
  124. package/src/client/components/inputv3/Rte/shared/react-test-utils.ts +18 -0
  125. package/src/client/components/inputv3/Rte/shared/reactPatches.ts +22 -0
  126. package/src/client/components/inputv3/Rte/shared/simpleDiffWithCursor.ts +49 -0
  127. package/src/client/components/inputv3/Rte/shared/useLayoutEffect.ts +19 -0
  128. package/src/client/components/inputv3/Rte/shared/warnOnlyOnce.ts +20 -0
  129. package/src/client/components/inputv3/Rte/style.less +30 -60
  130. package/src/client/components/inputv3/Rte/themes/CommentEditorTheme.css +13 -0
  131. package/src/client/components/inputv3/Rte/themes/CommentEditorTheme.ts +20 -0
  132. package/src/client/components/inputv3/Rte/themes/PlaygroundEditorTheme.css +447 -0
  133. package/src/client/components/inputv3/Rte/themes/PlaygroundEditorTheme.ts +120 -0
  134. package/src/client/components/inputv3/Rte/themes/StickyEditorTheme.css +13 -0
  135. package/src/client/components/inputv3/Rte/themes/StickyEditorTheme.ts +20 -0
  136. package/src/client/components/inputv3/Rte/ui/ColorPicker.css +88 -0
  137. package/src/client/components/inputv3/Rte/ui/ColorPicker.tsx +365 -0
  138. package/src/client/components/inputv3/Rte/ui/ContentEditable.css +44 -0
  139. package/src/client/components/inputv3/Rte/ui/ContentEditable.tsx +36 -0
  140. package/src/client/components/inputv3/Rte/ui/DropDown.tsx +259 -0
  141. package/src/client/components/inputv3/Rte/ui/DropdownColorPicker.tsx +41 -0
  142. package/src/client/components/inputv3/Rte/ui/EquationEditor.css +38 -0
  143. package/src/client/components/inputv3/Rte/ui/EquationEditor.tsx +56 -0
  144. package/src/client/components/inputv3/Rte/ui/FileInput.tsx +38 -0
  145. package/src/client/components/inputv3/Rte/ui/FlashMessage.css +28 -0
  146. package/src/client/components/inputv3/Rte/ui/FlashMessage.tsx +29 -0
  147. package/src/client/components/inputv3/Rte/ui/ImageResizer.tsx +316 -0
  148. package/src/client/components/inputv3/Rte/ui/Input.css +32 -0
  149. package/src/client/components/inputv3/Rte/ui/KatexRenderer.tsx +54 -0
  150. package/src/client/components/inputv3/Rte/ui/Switch.tsx +36 -0
  151. package/src/client/components/inputv3/Rte/utils/docSerialization.ts +77 -0
  152. package/src/client/components/inputv3/Rte/utils/emoji-list.ts +16615 -0
  153. package/src/client/components/inputv3/Rte/utils/getDOMRangeRect.ts +27 -0
  154. package/src/client/components/inputv3/Rte/utils/getSelectedNode.ts +27 -0
  155. package/src/client/components/inputv3/Rte/utils/guard.ts +10 -0
  156. package/src/client/components/inputv3/Rte/utils/isMobileWidth.ts +7 -0
  157. package/src/client/components/inputv3/Rte/utils/joinClasses.ts +13 -0
  158. package/src/client/components/inputv3/Rte/utils/setFloatingElemPosition.ts +51 -0
  159. package/src/client/components/inputv3/Rte/utils/setFloatingElemPositionForLinkEditor.ts +46 -0
  160. package/src/client/components/inputv3/Rte/utils/swipe.ts +127 -0
  161. package/src/client/components/inputv3/Rte/utils/url.ts +38 -0
  162. package/src/client/components/inputv3/base.tsx +10 -7
  163. package/src/client/components/inputv3/file/index.tsx +11 -5
  164. package/src/client/components/inputv3/index.tsx +2 -2
  165. package/src/common/data/rte/nodes.ts +60 -9
  166. package/src/common/validation/index.ts +21 -2
  167. package/src/common/validation/schema.ts +34 -11
  168. package/src/common/validation/validator.ts +12 -4
  169. package/src/common/validation/validators.ts +112 -63
  170. package/src/server/services/router/http/multipart.ts +0 -1
  171. package/src/server/services/router/request/index.ts +1 -1
  172. package/src/server/services/schema/index.ts +26 -4
  173. package/src/server/services/schema/request.ts +3 -2
  174. package/src/server/services/schema/rte.ts +110 -0
  175. package/src/{common/data/rte/index.ts → server/utils/rte.ts} +27 -16
  176. package/src/client/components/inputv3/Rte/ExampleTheme.tsx +0 -42
  177. package/src/client/components/inputv3/Rte/ToolbarPlugin.tsx +0 -167
  178. package/src/client/components/inputv3/Rte/icons/LICENSE.md +0 -5
  179. package/src/client/components/inputv3/Rte/icons/arrow-clockwise.svg +0 -4
  180. package/src/client/components/inputv3/Rte/icons/arrow-counterclockwise.svg +0 -4
  181. package/src/client/components/inputv3/Rte/icons/journal-text.svg +0 -5
  182. package/src/client/components/inputv3/Rte/icons/justify.svg +0 -3
  183. package/src/client/components/inputv3/Rte/icons/text-center.svg +0 -3
  184. package/src/client/components/inputv3/Rte/icons/text-left.svg +0 -3
  185. package/src/client/components/inputv3/Rte/icons/text-paragraph.svg +0 -3
  186. package/src/client/components/inputv3/Rte/icons/text-right.svg +0 -3
  187. package/src/client/components/inputv3/Rte/icons/type-bold.svg +0 -3
  188. package/src/client/components/inputv3/Rte/icons/type-italic.svg +0 -3
  189. package/src/client/components/inputv3/Rte/icons/type-strikethrough.svg +0 -3
  190. package/src/client/components/inputv3/Rte/icons/type-underline.svg +0 -3
@@ -0,0 +1,70 @@
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 {$isCodeNode} from '@lexical/code';
9
+ import {
10
+ $getNearestNodeFromDOMNode,
11
+ $getSelection,
12
+ $setSelection,
13
+ LexicalEditor,
14
+ } from 'lexical';
15
+ import * as React from 'react';
16
+ import {useState} from 'react';
17
+
18
+ import {useDebounce} from '../../utils';
19
+
20
+ interface Props {
21
+ editor: LexicalEditor;
22
+ getCodeDOMNode: () => HTMLElement | null;
23
+ }
24
+
25
+ export function CopyButton({editor, getCodeDOMNode}: Props) {
26
+ const [isCopyCompleted, setCopyCompleted] = useState<boolean>(false);
27
+
28
+ const removeSuccessIcon = useDebounce(() => {
29
+ setCopyCompleted(false);
30
+ }, 1000);
31
+
32
+ async function handleClick(): Promise<void> {
33
+ const codeDOMNode = getCodeDOMNode();
34
+
35
+ if (!codeDOMNode) {
36
+ return;
37
+ }
38
+
39
+ let content = '';
40
+
41
+ editor.update(() => {
42
+ const codeNode = $getNearestNodeFromDOMNode(codeDOMNode);
43
+
44
+ if ($isCodeNode(codeNode)) {
45
+ content = codeNode.getTextContent();
46
+ }
47
+
48
+ const selection = $getSelection();
49
+ $setSelection(selection);
50
+ });
51
+
52
+ try {
53
+ await navigator.clipboard.writeText(content);
54
+ setCopyCompleted(true);
55
+ removeSuccessIcon();
56
+ } catch (err) {
57
+ console.error('Failed to copy: ', err);
58
+ }
59
+ }
60
+
61
+ return (
62
+ <button className="menu-item" onClick={handleClick} aria-label="copy">
63
+ {isCopyCompleted ? (
64
+ <i className="format success" />
65
+ ) : (
66
+ <i className="format copy" />
67
+ )}
68
+ </button>
69
+ );
70
+ }
@@ -0,0 +1,14 @@
1
+ .code-action-menu-container .prettier-wrapper {
2
+ position: relative;
3
+ }
4
+
5
+ .code-action-menu-container .prettier-wrapper .code-error-tips {
6
+ padding: 5px;
7
+ border-radius: 4px;
8
+ color: #fff;
9
+ background: #222;
10
+ margin-top: 4px;
11
+ position: absolute;
12
+ top: 26px;
13
+ right: 0;
14
+ }
@@ -0,0 +1,156 @@
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 {$isCodeNode} from '@lexical/code';
11
+ import {$getNearestNodeFromDOMNode, LexicalEditor} from 'lexical';
12
+ import {Options} from 'prettier';
13
+ import * as React from 'react';
14
+ import {useState} from 'react';
15
+
16
+ interface Props {
17
+ lang: string;
18
+ editor: LexicalEditor;
19
+ getCodeDOMNode: () => HTMLElement | null;
20
+ }
21
+
22
+ const PRETTIER_PARSER_MODULES = {
23
+ css: () => import('prettier/parser-postcss'),
24
+ html: () => import('prettier/parser-html'),
25
+ js: () => import('prettier/parser-babel'),
26
+ markdown: () => import('prettier/parser-markdown'),
27
+ } as const;
28
+
29
+ type LanguagesType = keyof typeof PRETTIER_PARSER_MODULES;
30
+
31
+ async function loadPrettierParserByLang(lang: string) {
32
+ const dynamicImport = PRETTIER_PARSER_MODULES[lang as LanguagesType];
33
+ return await dynamicImport();
34
+ }
35
+
36
+ async function loadPrettierFormat() {
37
+ const {format} = await import('prettier/standalone');
38
+ return format;
39
+ }
40
+
41
+ const PRETTIER_OPTIONS_BY_LANG: Record<string, Options> = {
42
+ css: {
43
+ parser: 'css',
44
+ },
45
+ html: {
46
+ parser: 'html',
47
+ },
48
+ js: {
49
+ parser: 'babel',
50
+ },
51
+ markdown: {
52
+ parser: 'markdown',
53
+ },
54
+ };
55
+
56
+ const LANG_CAN_BE_PRETTIER = Object.keys(PRETTIER_OPTIONS_BY_LANG);
57
+
58
+ export function canBePrettier(lang: string): boolean {
59
+ return LANG_CAN_BE_PRETTIER.includes(lang);
60
+ }
61
+
62
+ function getPrettierOptions(lang: string): Options {
63
+ const options = PRETTIER_OPTIONS_BY_LANG[lang];
64
+ if (!options) {
65
+ throw new Error(
66
+ `CodeActionMenuPlugin: Prettier does not support this language: ${lang}`,
67
+ );
68
+ }
69
+
70
+ return options;
71
+ }
72
+
73
+ export function PrettierButton({lang, editor, getCodeDOMNode}: Props) {
74
+ const [syntaxError, setSyntaxError] = useState<string>('');
75
+ const [tipsVisible, setTipsVisible] = useState<boolean>(false);
76
+
77
+ async function handleClick(): Promise<void> {
78
+ const codeDOMNode = getCodeDOMNode();
79
+
80
+ try {
81
+ const format = await loadPrettierFormat();
82
+ const options = getPrettierOptions(lang);
83
+ options.plugins = [await loadPrettierParserByLang(lang)];
84
+
85
+ if (!codeDOMNode) {
86
+ return;
87
+ }
88
+
89
+ editor.update(() => {
90
+ const codeNode = $getNearestNodeFromDOMNode(codeDOMNode);
91
+
92
+ if ($isCodeNode(codeNode)) {
93
+ const content = codeNode.getTextContent();
94
+
95
+ let parsed = '';
96
+
97
+ try {
98
+ parsed = format(content, options);
99
+ } catch (error: unknown) {
100
+ setError(error);
101
+ }
102
+
103
+ if (parsed !== '') {
104
+ const selection = codeNode.select(0);
105
+ selection.insertText(parsed);
106
+ setSyntaxError('');
107
+ setTipsVisible(false);
108
+ }
109
+ }
110
+ });
111
+ } catch (error: unknown) {
112
+ setError(error);
113
+ }
114
+ }
115
+
116
+ function setError(error: unknown) {
117
+ if (error instanceof Error) {
118
+ setSyntaxError(error.message);
119
+ setTipsVisible(true);
120
+ } else {
121
+ console.error('Unexpected error: ', error);
122
+ }
123
+ }
124
+
125
+ function handleMouseEnter() {
126
+ if (syntaxError !== '') {
127
+ setTipsVisible(true);
128
+ }
129
+ }
130
+
131
+ function handleMouseLeave() {
132
+ if (syntaxError !== '') {
133
+ setTipsVisible(false);
134
+ }
135
+ }
136
+
137
+ return (
138
+ <div className="prettier-wrapper">
139
+ <button
140
+ className="menu-item"
141
+ onClick={handleClick}
142
+ onMouseEnter={handleMouseEnter}
143
+ onMouseLeave={handleMouseLeave}
144
+ aria-label="prettier">
145
+ {syntaxError ? (
146
+ <i className="format prettier-error" />
147
+ ) : (
148
+ <i className="format prettier" />
149
+ )}
150
+ </button>
151
+ {tipsVisible ? (
152
+ <pre className="code-error-tips">{syntaxError}</pre>
153
+ ) : null}
154
+ </div>
155
+ );
156
+ }
@@ -0,0 +1,54 @@
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
+ .code-action-menu-container {
10
+ height: 35.8px;
11
+ font-size: 10px;
12
+ color: rgba(0, 0, 0, 0.5);
13
+ position: absolute;
14
+ display: flex;
15
+ align-items: center;
16
+ flex-direction: row;
17
+ user-select: none;
18
+ }
19
+
20
+ .code-action-menu-container .code-highlight-language {
21
+ margin-right: 4px;
22
+ }
23
+
24
+ .code-action-menu-container button.menu-item {
25
+ border: 1px solid transparent;
26
+ border-radius: 4px;
27
+ padding: 4px;
28
+ background: none;
29
+ cursor: pointer;
30
+ flex-shrink: 0;
31
+ display: flex;
32
+ align-items: center;
33
+ color: rgba(0, 0, 0, 0.5);
34
+ text-transform: uppercase;
35
+ }
36
+
37
+ .code-action-menu-container button.menu-item i.format {
38
+ height: 16px;
39
+ width: 16px;
40
+ opacity: 0.6;
41
+ display: flex;
42
+ color: rgba(0, 0, 0, 0.5);
43
+ background-size: contain;
44
+ }
45
+
46
+ .code-action-menu-container button.menu-item:hover {
47
+ border: 1px solid rgba(0, 0, 0, 0.3);
48
+ opacity: 0.9;
49
+ }
50
+
51
+ .code-action-menu-container button.menu-item:active {
52
+ background-color: rgba(223, 232, 250);
53
+ border: 1px solid rgba(0, 0, 0, 0.45);
54
+ }
@@ -0,0 +1,190 @@
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 './index.css';
10
+
11
+ import {
12
+ $isCodeNode,
13
+ CodeNode,
14
+ getLanguageFriendlyName,
15
+ normalizeCodeLang,
16
+ } from '@lexical/code';
17
+ import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
18
+ import {$getNearestNodeFromDOMNode} from 'lexical';
19
+ import {useEffect, useRef, useState} from 'react';
20
+ import * as React from 'react';
21
+ import {createPortal} from 'react-dom';
22
+
23
+ import {CopyButton} from './components/CopyButton';
24
+ import {canBePrettier, PrettierButton} from './components/PrettierButton';
25
+ import {useDebounce} from './utils';
26
+
27
+ const CODE_PADDING = 8;
28
+
29
+ interface Position {
30
+ top: string;
31
+ right: string;
32
+ }
33
+
34
+ function CodeActionMenuContainer({
35
+ anchorElem,
36
+ }: {
37
+ anchorElem: HTMLElement;
38
+ }): JSX.Element {
39
+ const [editor] = useLexicalComposerContext();
40
+
41
+ const [lang, setLang] = useState('');
42
+ const [isShown, setShown] = useState<boolean>(false);
43
+ const [shouldListenMouseMove, setShouldListenMouseMove] =
44
+ useState<boolean>(false);
45
+ const [position, setPosition] = useState<Position>({
46
+ right: '0',
47
+ top: '0',
48
+ });
49
+ const codeSetRef = useRef<Set<string>>(new Set());
50
+ const codeDOMNodeRef = useRef<HTMLElement | null>(null);
51
+
52
+ function getCodeDOMNode(): HTMLElement | null {
53
+ return codeDOMNodeRef.current;
54
+ }
55
+
56
+ const debouncedOnMouseMove = useDebounce(
57
+ (event: MouseEvent) => {
58
+ const {codeDOMNode, isOutside} = getMouseInfo(event);
59
+ if (isOutside) {
60
+ setShown(false);
61
+ return;
62
+ }
63
+
64
+ if (!codeDOMNode) {
65
+ return;
66
+ }
67
+
68
+ codeDOMNodeRef.current = codeDOMNode;
69
+
70
+ let codeNode: CodeNode | null = null;
71
+ let _lang = '';
72
+
73
+ editor.update(() => {
74
+ const maybeCodeNode = $getNearestNodeFromDOMNode(codeDOMNode);
75
+
76
+ if ($isCodeNode(maybeCodeNode)) {
77
+ codeNode = maybeCodeNode;
78
+ _lang = codeNode.getLanguage() || '';
79
+ }
80
+ });
81
+
82
+ if (codeNode) {
83
+ const {y: editorElemY, right: editorElemRight} =
84
+ anchorElem.getBoundingClientRect();
85
+ const {y, right} = codeDOMNode.getBoundingClientRect();
86
+ setLang(_lang);
87
+ setShown(true);
88
+ setPosition({
89
+ right: `${editorElemRight - right + CODE_PADDING}px`,
90
+ top: `${y - editorElemY}px`,
91
+ });
92
+ }
93
+ },
94
+ 50,
95
+ 1000,
96
+ );
97
+
98
+ useEffect(() => {
99
+ if (!shouldListenMouseMove) {
100
+ return;
101
+ }
102
+
103
+ document.addEventListener('mousemove', debouncedOnMouseMove);
104
+
105
+ return () => {
106
+ setShown(false);
107
+ debouncedOnMouseMove.cancel();
108
+ document.removeEventListener('mousemove', debouncedOnMouseMove);
109
+ };
110
+ }, [shouldListenMouseMove, debouncedOnMouseMove]);
111
+
112
+ useEffect(() => {
113
+ return editor.registerMutationListener(
114
+ CodeNode,
115
+ (mutations) => {
116
+ editor.getEditorState().read(() => {
117
+ for (const [key, type] of mutations) {
118
+ switch (type) {
119
+ case 'created':
120
+ codeSetRef.current.add(key);
121
+ break;
122
+
123
+ case 'destroyed':
124
+ codeSetRef.current.delete(key);
125
+ break;
126
+
127
+ default:
128
+ break;
129
+ }
130
+ }
131
+ });
132
+ setShouldListenMouseMove(codeSetRef.current.size > 0);
133
+ },
134
+ {skipInitialization: false},
135
+ );
136
+ }, [editor]);
137
+
138
+ const normalizedLang = normalizeCodeLang(lang);
139
+ const codeFriendlyName = getLanguageFriendlyName(lang);
140
+
141
+ return (
142
+ <>
143
+ {isShown ? (
144
+ <div className="code-action-menu-container" style={{...position}}>
145
+ <div className="code-highlight-language">{codeFriendlyName}</div>
146
+ <CopyButton editor={editor} getCodeDOMNode={getCodeDOMNode} />
147
+ {canBePrettier(normalizedLang) ? (
148
+ <PrettierButton
149
+ editor={editor}
150
+ getCodeDOMNode={getCodeDOMNode}
151
+ lang={normalizedLang}
152
+ />
153
+ ) : null}
154
+ </div>
155
+ ) : null}
156
+ </>
157
+ );
158
+ }
159
+
160
+ function getMouseInfo(event: MouseEvent): {
161
+ codeDOMNode: HTMLElement | null;
162
+ isOutside: boolean;
163
+ } {
164
+ const target = event.target;
165
+
166
+ if (target && target instanceof HTMLElement) {
167
+ const codeDOMNode = target.closest<HTMLElement>(
168
+ 'code.PlaygroundEditorTheme__code',
169
+ );
170
+ const isOutside = !(
171
+ codeDOMNode ||
172
+ target.closest<HTMLElement>('div.code-action-menu-container')
173
+ );
174
+
175
+ return {codeDOMNode, isOutside};
176
+ } else {
177
+ return {codeDOMNode: null, isOutside: true};
178
+ }
179
+ }
180
+
181
+ export default function CodeActionMenuPlugin({
182
+ anchorElem = document.body,
183
+ }: {
184
+ anchorElem?: HTMLElement;
185
+ }): React.ReactPortal | null {
186
+ return createPortal(
187
+ <CodeActionMenuContainer anchorElem={anchorElem} />,
188
+ anchorElem,
189
+ );
190
+ }
@@ -0,0 +1,33 @@
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 { debounce } from 'lodash';
10
+ import {useMemo, useRef} from 'react';
11
+
12
+ export function useDebounce<T extends (...args: never[]) => void>(
13
+ fn: T,
14
+ ms: number,
15
+ maxWait?: number,
16
+ ) {
17
+ const funcRef = useRef<T | null>(null);
18
+ funcRef.current = fn;
19
+
20
+ return useMemo(
21
+ () =>
22
+ debounce(
23
+ (...args: Parameters<T>) => {
24
+ if (funcRef.current) {
25
+ funcRef.current(...args);
26
+ }
27
+ },
28
+ ms,
29
+ {maxWait},
30
+ ),
31
+ [ms, maxWait],
32
+ );
33
+ }
@@ -0,0 +1,21 @@
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 {registerCodeHighlighting} from '@lexical/code';
10
+ import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
11
+ import {useEffect} from 'react';
12
+
13
+ export default function CodeHighlightPlugin(): JSX.Element | null {
14
+ const [editor] = useLexicalComposerContext();
15
+
16
+ useEffect(() => {
17
+ return registerCodeHighlighting(editor);
18
+ }, [editor]);
19
+
20
+ return null;
21
+ }
@@ -0,0 +1,57 @@
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
+
10
+ .Collapsible__container {
11
+ background: #fcfcfc;
12
+ border: 1px solid #eee;
13
+ border-radius: 10px;
14
+ margin-bottom: 8px;
15
+ }
16
+
17
+ .Collapsible__title {
18
+ cursor: pointer;
19
+ padding: 5px 5px 5px 20px;
20
+ position: relative;
21
+ font-weight: bold;
22
+ list-style: none;
23
+ outline: none;
24
+ }
25
+
26
+ .Collapsible__title::marker,
27
+ .Collapsible__title::-webkit-details-marker {
28
+ display: none;
29
+ }
30
+
31
+ .Collapsible__title:before {
32
+ border-style: solid;
33
+ border-color: transparent;
34
+ border-width: 4px 6px 4px 6px;
35
+ border-left-color: #000;
36
+ display: block;
37
+ content: '';
38
+ position: absolute;
39
+ left: 7px;
40
+ top: 50%;
41
+ transform: translateY(-50%);
42
+ }
43
+
44
+ .Collapsible__container[open] > .Collapsible__title:before {
45
+ border-color: transparent;
46
+ border-width: 6px 4px 0 4px;
47
+ border-top-color: #000;
48
+ }
49
+
50
+ .Collapsible__content {
51
+ padding: 0 5px 5px 20px;
52
+ }
53
+
54
+ .Collapsible__collapsed .Collapsible__content {
55
+ display: none;
56
+ user-select: none;
57
+ }