@jpmorganchase/mosaic-content-editor-plugin 0.1.0-beta.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 (249) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/README.md +7 -0
  3. package/dist/api/save.d.ts +5 -0
  4. package/dist/api/save.js +1 -0
  5. package/dist/chunk-3FJGHVH5.js +1 -0
  6. package/dist/chunk-3TDCKWYF.js +1 -0
  7. package/dist/chunk-57FSKQGH.js +1 -0
  8. package/dist/chunk-5GT4QAIQ.js +1 -0
  9. package/dist/chunk-6TM3EYFS.js +1 -0
  10. package/dist/chunk-76US6CXQ.js +1 -0
  11. package/dist/chunk-A6ZSQMSC.js +1 -0
  12. package/dist/chunk-ACFFLNAA.js +1 -0
  13. package/dist/chunk-BTI67NTQ.js +1 -0
  14. package/dist/chunk-FCCNBA44.js +1 -0
  15. package/dist/chunk-FGP7KEKC.js +1 -0
  16. package/dist/chunk-FI57IKHZ.js +1 -0
  17. package/dist/chunk-FM63P66J.js +1 -0
  18. package/dist/chunk-GR3B6XH6.js +1 -0
  19. package/dist/chunk-HL7D7RIZ.js +1 -0
  20. package/dist/chunk-HROTQ6LG.js +1 -0
  21. package/dist/chunk-I3Z6UENR.js +1 -0
  22. package/dist/chunk-IO4745QD.js +1 -0
  23. package/dist/chunk-IXKHPWRK.js +1 -0
  24. package/dist/chunk-JKQCT2R7.js +1 -0
  25. package/dist/chunk-JQUDHJIQ.js +1 -0
  26. package/dist/chunk-JVJ4H6LB.js +1 -0
  27. package/dist/chunk-JX5HLHOC.js +1 -0
  28. package/dist/chunk-KMK5XOLZ.js +1 -0
  29. package/dist/chunk-KYX7ZWMX.js +1 -0
  30. package/dist/chunk-LOZ4MGDL.js +1 -0
  31. package/dist/chunk-M5W2GPOK.js +1 -0
  32. package/dist/chunk-MXR5SEUG.js +1 -0
  33. package/dist/chunk-N3NO7L4H.js +1 -0
  34. package/dist/chunk-NXV7KCC5.js +1 -0
  35. package/dist/chunk-OFDMXZXG.js +1 -0
  36. package/dist/chunk-P3NF2XWE.js +1 -0
  37. package/dist/chunk-P5OBPOC4.js +0 -0
  38. package/dist/chunk-PPEVCQGG.js +1 -0
  39. package/dist/chunk-RHRDHZ4F.js +1 -0
  40. package/dist/chunk-RN54LVW4.js +1 -0
  41. package/dist/chunk-T2M7535Y.js +1 -0
  42. package/dist/chunk-UA5IM6NC.js +1 -0
  43. package/dist/chunk-UVZXP5EW.js +1 -0
  44. package/dist/chunk-UXLYVZ46.js +1 -0
  45. package/dist/chunk-V5Z3VNEW.js +1 -0
  46. package/dist/chunk-W44IYDHL.js +1 -0
  47. package/dist/chunk-WBUBW4PH.js +2 -0
  48. package/dist/chunk-WHPZZWKO.js +1 -0
  49. package/dist/chunk-WQSFQBP7.js +1 -0
  50. package/dist/chunk-WXWEGQ2Z.js +1 -0
  51. package/dist/chunk-XX4EWSFM.js +1 -0
  52. package/dist/chunk-YPBKG7ME.js +1 -0
  53. package/dist/chunk-ZCWG7UN7.js +1 -0
  54. package/dist/chunk-ZF6MK5QB.js +1 -0
  55. package/dist/chunk-ZK6NLAGG.js +1 -0
  56. package/dist/components/ActionMenu/ActionMenu.css.css +1 -0
  57. package/dist/components/ActionMenu/ActionMenu.css.d.ts +4 -0
  58. package/dist/components/ActionMenu/ActionMenu.css.js +1 -0
  59. package/dist/components/ActionMenu/ActionMenu.d.ts +10 -0
  60. package/dist/components/ActionMenu/ActionMenu.js +1 -0
  61. package/dist/components/ContentEditor.d.ts +3 -0
  62. package/dist/components/ContentEditor.js +1 -0
  63. package/dist/components/Editor.css +1 -0
  64. package/dist/components/Editor.css.css +1 -0
  65. package/dist/components/Editor.css.d.ts +10 -0
  66. package/dist/components/Editor.css.js +1 -0
  67. package/dist/components/Editor.d.ts +14 -0
  68. package/dist/components/Editor.js +1 -0
  69. package/dist/components/EditorControls.css +1 -0
  70. package/dist/components/EditorControls.css.css +1 -0
  71. package/dist/components/EditorControls.css.d.ts +4 -0
  72. package/dist/components/EditorControls.css.js +1 -0
  73. package/dist/components/EditorControls.d.ts +4 -0
  74. package/dist/components/EditorControls.js +1 -0
  75. package/dist/components/LinkEditor/EditLinkButton.d.ts +3 -0
  76. package/dist/components/LinkEditor/EditLinkButton.js +1 -0
  77. package/dist/components/LinkEditor/LinkEditor.css +1 -0
  78. package/dist/components/LinkEditor/LinkEditor.css.css +1 -0
  79. package/dist/components/LinkEditor/LinkEditor.css.d.ts +4 -0
  80. package/dist/components/LinkEditor/LinkEditor.css.js +1 -0
  81. package/dist/components/LinkEditor/LinkEditor.d.ts +2 -0
  82. package/dist/components/LinkEditor/LinkEditor.js +1 -0
  83. package/dist/components/LinkEditor/SaveAdornment.d.ts +7 -0
  84. package/dist/components/LinkEditor/SaveAdornment.js +1 -0
  85. package/dist/components/PersistEditDialog/PersistStatus.css +1 -0
  86. package/dist/components/PersistEditDialog/PersistStatus.css.css +1 -0
  87. package/dist/components/PersistEditDialog/PersistStatus.css.d.ts +7 -0
  88. package/dist/components/PersistEditDialog/PersistStatus.css.js +1 -0
  89. package/dist/components/PersistEditDialog/PersistStatus.d.ts +7 -0
  90. package/dist/components/PersistEditDialog/PersistStatus.js +1 -0
  91. package/dist/components/PersistEditDialog/index.css +1 -0
  92. package/dist/components/PersistEditDialog/index.d.ts +4 -0
  93. package/dist/components/PersistEditDialog/index.js +1 -0
  94. package/dist/components/Popper/Popper.css +1 -0
  95. package/dist/components/Popper/Popper.css.css +1 -0
  96. package/dist/components/Popper/Popper.css.d.ts +5 -0
  97. package/dist/components/Popper/Popper.css.js +1 -0
  98. package/dist/components/Popper/Popper.d.ts +11 -0
  99. package/dist/components/Popper/Popper.js +1 -0
  100. package/dist/components/SaveButton.d.ts +2 -0
  101. package/dist/components/SaveButton.js +1 -0
  102. package/dist/components/ScrollableSection/ScrollableSection.css +1 -0
  103. package/dist/components/ScrollableSection/ScrollableSection.css.css +1 -0
  104. package/dist/components/ScrollableSection/ScrollableSection.css.d.ts +4 -0
  105. package/dist/components/ScrollableSection/ScrollableSection.css.js +1 -0
  106. package/dist/components/ScrollableSection/ScrollableSection.d.ts +7 -0
  107. package/dist/components/ScrollableSection/ScrollableSection.js +1 -0
  108. package/dist/components/StatusBanner.d.ts +3 -0
  109. package/dist/components/StatusBanner.js +1 -0
  110. package/dist/components/Toolbar/InsertBlockDropdown.d.ts +6 -0
  111. package/dist/components/Toolbar/InsertBlockDropdown.js +1 -0
  112. package/dist/components/Toolbar/InsertHorizontalRule.d.ts +2 -0
  113. package/dist/components/Toolbar/InsertHorizontalRule.js +1 -0
  114. package/dist/components/Toolbar/InsertImage.css +1 -0
  115. package/dist/components/Toolbar/InsertImage.css.css +1 -0
  116. package/dist/components/Toolbar/InsertImage.css.d.ts +4 -0
  117. package/dist/components/Toolbar/InsertImage.css.js +1 -0
  118. package/dist/components/Toolbar/InsertImage.d.ts +2 -0
  119. package/dist/components/Toolbar/InsertImage.js +1 -0
  120. package/dist/components/Toolbar/InsertLink.css +1 -0
  121. package/dist/components/Toolbar/InsertLink.css.css +1 -0
  122. package/dist/components/Toolbar/InsertLink.css.d.ts +4 -0
  123. package/dist/components/Toolbar/InsertLink.css.js +1 -0
  124. package/dist/components/Toolbar/InsertLink.d.ts +3 -0
  125. package/dist/components/Toolbar/InsertLink.js +1 -0
  126. package/dist/components/Toolbar/InsertTable.css +1 -0
  127. package/dist/components/Toolbar/InsertTable.css.css +1 -0
  128. package/dist/components/Toolbar/InsertTable.css.d.ts +13 -0
  129. package/dist/components/Toolbar/InsertTable.css.js +1 -0
  130. package/dist/components/Toolbar/InsertTable.d.ts +10 -0
  131. package/dist/components/Toolbar/InsertTable.js +1 -0
  132. package/dist/components/Toolbar/TextFormatToolbarButton.css +1 -0
  133. package/dist/components/Toolbar/TextFormatToolbarButton.css.css +1 -0
  134. package/dist/components/Toolbar/TextFormatToolbarButton.css.d.ts +5 -0
  135. package/dist/components/Toolbar/TextFormatToolbarButton.css.js +1 -0
  136. package/dist/components/Toolbar/TextFormatToolbarButton.d.ts +7 -0
  137. package/dist/components/Toolbar/TextFormatToolbarButton.js +1 -0
  138. package/dist/components/Toolbar/TextFormatTooltray.css +1 -0
  139. package/dist/components/Toolbar/TextFormatTooltray.css.css +1 -0
  140. package/dist/components/Toolbar/TextFormatTooltray.css.d.ts +8 -0
  141. package/dist/components/Toolbar/TextFormatTooltray.css.js +1 -0
  142. package/dist/components/Toolbar/TextFormatTooltray.d.ts +4 -0
  143. package/dist/components/Toolbar/TextFormatTooltray.js +1 -0
  144. package/dist/components/Toolbar/Toolbar.css +1 -0
  145. package/dist/components/Toolbar/Toolbar.d.ts +3 -0
  146. package/dist/components/Toolbar/Toolbar.js +1 -0
  147. package/dist/hooks/useEditHistory.d.ts +6 -0
  148. package/dist/hooks/useEditHistory.js +1 -0
  149. package/dist/index.css +1 -0
  150. package/dist/index.d.ts +4 -0
  151. package/dist/index.js +1 -0
  152. package/dist/nodes/index.d.ts +8 -0
  153. package/dist/nodes/index.js +1 -0
  154. package/dist/plugins/FloatingToolbarPlugin.css +1 -0
  155. package/dist/plugins/FloatingToolbarPlugin.d.ts +2 -0
  156. package/dist/plugins/FloatingToolbarPlugin.js +1 -0
  157. package/dist/plugins/HorizontalRulePlugin.d.ts +1 -0
  158. package/dist/plugins/HorizontalRulePlugin.js +1 -0
  159. package/dist/plugins/MarkdownImagePlugin.d.ts +7 -0
  160. package/dist/plugins/MarkdownImagePlugin.js +1 -0
  161. package/dist/plugins/MarkdownLinkPlugin.css +1 -0
  162. package/dist/plugins/MarkdownLinkPlugin.d.ts +8 -0
  163. package/dist/plugins/MarkdownLinkPlugin.js +1 -0
  164. package/dist/plugins/PreviewPlugin.css +1 -0
  165. package/dist/plugins/PreviewPlugin.d.ts +5 -0
  166. package/dist/plugins/PreviewPlugin.js +1 -0
  167. package/dist/plugins/TableActionMenuPlugin.css +1 -0
  168. package/dist/plugins/TableActionMenuPlugin.css.css +1 -0
  169. package/dist/plugins/TableActionMenuPlugin.css.d.ts +60 -0
  170. package/dist/plugins/TableActionMenuPlugin.css.js +1 -0
  171. package/dist/plugins/TableActionMenuPlugin.d.ts +2 -0
  172. package/dist/plugins/TableActionMenuPlugin.js +1 -0
  173. package/dist/store/index.d.ts +38 -0
  174. package/dist/store/index.js +1 -0
  175. package/dist/theme/index.css +1 -0
  176. package/dist/theme/index.css.css +1 -0
  177. package/dist/theme/index.css.d.ts +15 -0
  178. package/dist/theme/index.css.js +1 -0
  179. package/dist/theme/index.d.ts +3 -0
  180. package/dist/theme/index.js +1 -0
  181. package/dist/transformers/horizontalRule.d.ts +2 -0
  182. package/dist/transformers/horizontalRule.js +1 -0
  183. package/dist/transformers/index.d.ts +2 -0
  184. package/dist/transformers/index.js +1 -0
  185. package/dist/transformers/link.d.ts +2 -0
  186. package/dist/transformers/link.js +1 -0
  187. package/dist/transformers/tableRule.d.ts +2 -0
  188. package/dist/transformers/tableRule.js +1 -0
  189. package/dist/utils/getSelectedNode.d.ts +2 -0
  190. package/dist/utils/getSelectedNode.js +1 -0
  191. package/package.json +54 -0
  192. package/src/api/save.ts +12 -0
  193. package/src/components/ActionMenu/ActionMenu.css.ts +9 -0
  194. package/src/components/ActionMenu/ActionMenu.tsx +28 -0
  195. package/src/components/ContentEditor.tsx +12 -0
  196. package/src/components/Editor.css.ts +64 -0
  197. package/src/components/Editor.tsx +143 -0
  198. package/src/components/EditorControls.css.ts +9 -0
  199. package/src/components/EditorControls.tsx +31 -0
  200. package/src/components/LinkEditor/EditLinkButton.tsx +9 -0
  201. package/src/components/LinkEditor/LinkEditor.css.ts +15 -0
  202. package/src/components/LinkEditor/LinkEditor.tsx +136 -0
  203. package/src/components/LinkEditor/SaveAdornment.tsx +16 -0
  204. package/src/components/PersistEditDialog/PersistStatus.css.ts +21 -0
  205. package/src/components/PersistEditDialog/PersistStatus.tsx +78 -0
  206. package/src/components/PersistEditDialog/__tests__/handlers.ts +9 -0
  207. package/src/components/PersistEditDialog/__tests__/index.test.tsx +93 -0
  208. package/src/components/PersistEditDialog/index.tsx +114 -0
  209. package/src/components/Popper/Popper.css.ts +12 -0
  210. package/src/components/Popper/Popper.tsx +31 -0
  211. package/src/components/SaveButton.tsx +23 -0
  212. package/src/components/ScrollableSection/ScrollableSection.css.ts +10 -0
  213. package/src/components/ScrollableSection/ScrollableSection.tsx +20 -0
  214. package/src/components/StatusBanner.tsx +16 -0
  215. package/src/components/Toolbar/InsertBlockDropdown.tsx +134 -0
  216. package/src/components/Toolbar/InsertHorizontalRule.tsx +18 -0
  217. package/src/components/Toolbar/InsertImage.css.ts +7 -0
  218. package/src/components/Toolbar/InsertImage.tsx +132 -0
  219. package/src/components/Toolbar/InsertLink.css.ts +7 -0
  220. package/src/components/Toolbar/InsertLink.tsx +145 -0
  221. package/src/components/Toolbar/InsertTable.css.ts +46 -0
  222. package/src/components/Toolbar/InsertTable.tsx +190 -0
  223. package/src/components/Toolbar/TextFormatToolbarButton.css.ts +10 -0
  224. package/src/components/Toolbar/TextFormatToolbarButton.tsx +29 -0
  225. package/src/components/Toolbar/TextFormatTooltray.css.ts +27 -0
  226. package/src/components/Toolbar/TextFormatTooltray.tsx +118 -0
  227. package/src/components/Toolbar/Toolbar.tsx +47 -0
  228. package/src/components/__tests__/EditorControls.test.tsx +69 -0
  229. package/src/components/__tests__/StatusBanner.test.tsx +33 -0
  230. package/src/hooks/useEditHistory.ts +49 -0
  231. package/src/index.ts +5 -0
  232. package/src/nodes/index.ts +23 -0
  233. package/src/plugins/FloatingToolbarPlugin.tsx +100 -0
  234. package/src/plugins/HorizontalRulePlugin.tsx +39 -0
  235. package/src/plugins/MarkdownImagePlugin.tsx +58 -0
  236. package/src/plugins/MarkdownLinkPlugin.tsx +69 -0
  237. package/src/plugins/PreviewPlugin.tsx +46 -0
  238. package/src/plugins/TableActionMenuPlugin.css.ts +29 -0
  239. package/src/plugins/TableActionMenuPlugin.tsx +278 -0
  240. package/src/store/__tests__/index.test.ts +171 -0
  241. package/src/store/index.ts +84 -0
  242. package/src/theme/index.css.ts +39 -0
  243. package/src/theme/index.ts +22 -0
  244. package/src/transformers/horizontalRule.ts +25 -0
  245. package/src/transformers/index.ts +6 -0
  246. package/src/transformers/link.ts +3 -0
  247. package/src/transformers/tableRule.ts +193 -0
  248. package/src/utils/getSelectedNode.ts +16 -0
  249. package/tsconfig.json +8 -0
@@ -0,0 +1,100 @@
1
+ import React, { useCallback, useEffect, useState } from 'react';
2
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
3
+ import { mergeRegister } from '@lexical/utils';
4
+ import {
5
+ $getSelection,
6
+ $isRangeSelection,
7
+ COMMAND_PRIORITY_LOW,
8
+ SELECTION_CHANGE_COMMAND
9
+ } from 'lexical';
10
+ import { Toolbar as ToolkitToolbar, Tooltray as ToolkitTooltray } from '@jpmorganchase/uitk-lab';
11
+ import { flip, inline } from '@floating-ui/react-dom';
12
+ import { useFloatingUI, UseFloatingUIProps } from '@jpmorganchase/uitk-core';
13
+ import { useInteractions, useDismiss } from '@floating-ui/react-dom-interactions';
14
+ import { Popper } from '../components/Popper/Popper';
15
+ import { TextFormatTooltray } from '../components/Toolbar/TextFormatTooltray';
16
+ import { InsertLinkButton } from '../components/Toolbar/InsertLink';
17
+
18
+ export function FloatingToolbarPlugin(): JSX.Element | null {
19
+ const [editor] = useLexicalComposerContext();
20
+ const [floatingPosition, setFloatingPosition] = useState<{ x: number; y: number } | null>(null);
21
+ const [open, setOpen] = useState<boolean>(false);
22
+
23
+ const FloatingProps: UseFloatingUIProps = floatingPosition
24
+ ? {
25
+ middleware: [
26
+ inline(floatingPosition),
27
+ flip({
28
+ fallbackPlacements: ['bottom-start', 'top-start']
29
+ })
30
+ ]
31
+ }
32
+ : {
33
+ placement: 'bottom-start',
34
+ strategy: 'absolute'
35
+ };
36
+ const { context, floating, reference, strategy } = useFloatingUI({
37
+ placement: 'bottom-start',
38
+ open,
39
+ onOpenChange: setOpen,
40
+ strategy: 'absolute',
41
+ ...FloatingProps
42
+ });
43
+ const { getFloatingProps } = useInteractions([
44
+ useDismiss(context, {
45
+ ancestorScroll: true
46
+ })
47
+ ]);
48
+
49
+ const updateToolbar = useCallback(() => {
50
+ const selection = $getSelection();
51
+ const nativeSelection = window.getSelection();
52
+ const anchorEl = nativeSelection?.anchorNode?.parentElement;
53
+ if ($isRangeSelection(selection) && anchorEl) {
54
+ reference(anchorEl);
55
+ const getRange = nativeSelection.getRangeAt(0);
56
+ const { x, y, height } = getRange.getBoundingClientRect();
57
+ setFloatingPosition({ x, y: y + height });
58
+ setOpen(true);
59
+ } else {
60
+ setFloatingPosition(null);
61
+ setOpen(false);
62
+ }
63
+ }, [reference]);
64
+
65
+ useEffect(
66
+ () =>
67
+ mergeRegister(
68
+ editor.registerUpdateListener(({ editorState }) => {
69
+ editorState.read(() => {
70
+ updateToolbar();
71
+ });
72
+ }),
73
+
74
+ editor.registerCommand(
75
+ SELECTION_CHANGE_COMMAND,
76
+ () => {
77
+ updateToolbar();
78
+ return false;
79
+ },
80
+ COMMAND_PRIORITY_LOW
81
+ )
82
+ ),
83
+ [editor, updateToolbar]
84
+ );
85
+
86
+ let style: Record<string, any> = { position: strategy };
87
+ if (floatingPosition) {
88
+ style = { ...style, top: floatingPosition.y, left: floatingPosition.x };
89
+ }
90
+ return (
91
+ <Popper ref={floating} open={open} style={style} {...getFloatingProps({})}>
92
+ <ToolkitToolbar aria-label="page editing toolbar" style={{ minWidth: '100px' }}>
93
+ <TextFormatTooltray floating />
94
+ <ToolkitTooltray aria-label="text format tooltray">
95
+ <InsertLinkButton />
96
+ </ToolkitTooltray>
97
+ </ToolkitToolbar>
98
+ </Popper>
99
+ );
100
+ }
@@ -0,0 +1,39 @@
1
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
2
+ import {
3
+ $createHorizontalRuleNode,
4
+ INSERT_HORIZONTAL_RULE_COMMAND
5
+ } from '@lexical/react/LexicalHorizontalRuleNode';
6
+ import { $getSelection, $isRangeSelection, COMMAND_PRIORITY_EDITOR } from 'lexical';
7
+ import { useEffect } from 'react';
8
+
9
+ export default function HorizontalRulePlugin(): null {
10
+ const [editor] = useLexicalComposerContext();
11
+
12
+ useEffect(
13
+ () =>
14
+ editor.registerCommand(
15
+ INSERT_HORIZONTAL_RULE_COMMAND,
16
+ () => {
17
+ const selection = $getSelection();
18
+
19
+ if (!$isRangeSelection(selection)) {
20
+ return false;
21
+ }
22
+
23
+ const focusNode = selection.focus.getNode();
24
+
25
+ if (focusNode !== null) {
26
+ const horizontalRuleNode = $createHorizontalRuleNode();
27
+ selection.insertParagraph();
28
+ selection.focus.getNode().getTopLevelElementOrThrow().insertBefore(horizontalRuleNode);
29
+ }
30
+
31
+ return true;
32
+ },
33
+ COMMAND_PRIORITY_EDITOR
34
+ ),
35
+ [editor]
36
+ );
37
+
38
+ return null;
39
+ }
@@ -0,0 +1,58 @@
1
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
2
+ import {
3
+ $createParagraphNode,
4
+ $createTextNode,
5
+ $getSelection,
6
+ $isRangeSelection,
7
+ COMMAND_PRIORITY_EDITOR,
8
+ createCommand,
9
+ LexicalCommand
10
+ } from 'lexical';
11
+ import { useCallback, useEffect } from 'react';
12
+
13
+ export interface InsertImagePayload {
14
+ url: string | null;
15
+ alt: string | null;
16
+ }
17
+
18
+ export const INSERT_MARKDOWN_IMAGE_COMMAND: LexicalCommand<InsertImagePayload> = createCommand();
19
+
20
+ function useMarkdownImagePlugin() {
21
+ const [editor] = useLexicalComposerContext();
22
+
23
+ const createImage = useCallback(
24
+ ({ url, alt }: InsertImagePayload) => {
25
+ editor.update(() => {
26
+ const selection = $getSelection();
27
+
28
+ if ($isRangeSelection(selection) && url !== undefined && alt !== undefined) {
29
+ const imageNode = $createParagraphNode().append($createTextNode(`![${alt}](${url})`));
30
+
31
+ if (selection.focus.getNode().canInsertTextAfter()) {
32
+ selection.insertNodes([imageNode]);
33
+ }
34
+ }
35
+ });
36
+ },
37
+ [editor]
38
+ );
39
+
40
+ useEffect(
41
+ () =>
42
+ editor.registerCommand(
43
+ INSERT_MARKDOWN_IMAGE_COMMAND,
44
+ (payload: InsertImagePayload) => {
45
+ createImage(payload);
46
+ return true;
47
+ },
48
+ COMMAND_PRIORITY_EDITOR
49
+ ),
50
+ [editor, createImage]
51
+ );
52
+ }
53
+
54
+ export function MarkdownImagePlugin() {
55
+ useMarkdownImagePlugin();
56
+
57
+ return null;
58
+ }
@@ -0,0 +1,69 @@
1
+ import { $createLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link';
2
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
3
+ import { $wrapNodes } from '@lexical/selection';
4
+ import {
5
+ $createParagraphNode,
6
+ $createTextNode,
7
+ $getSelection,
8
+ $isRangeSelection,
9
+ $isTextNode,
10
+ COMMAND_PRIORITY_EDITOR,
11
+ createCommand,
12
+ LexicalCommand
13
+ } from 'lexical';
14
+ import React, { useCallback, useEffect } from 'react';
15
+
16
+ import { InsertLinkDialog } from '../components/Toolbar/InsertLink';
17
+
18
+ export interface InsertLinkPayload {
19
+ url?: string;
20
+ text?: string;
21
+ }
22
+
23
+ export const INSERT_MARKDOWN_LINK_COMMAND: LexicalCommand<InsertLinkPayload> = createCommand();
24
+
25
+ function useMarkdownLinkPlugin() {
26
+ const [editor] = useLexicalComposerContext();
27
+
28
+ const createLink = useCallback(
29
+ ({ url, text }: InsertLinkPayload) => {
30
+ editor.update(() => {
31
+ const selection = $getSelection();
32
+
33
+ if ($isRangeSelection(selection) && url !== undefined && text !== undefined) {
34
+ const focusNode = selection.focus.getNode();
35
+
36
+ if ($isTextNode(focusNode)) {
37
+ editor.dispatchCommand(TOGGLE_LINK_COMMAND, url);
38
+ } else {
39
+ const linkNode = $createLinkNode(url);
40
+ const linkTextNode = $createTextNode(text);
41
+ linkTextNode.setFormat(selection.focus.getNode().getFormat());
42
+ linkNode.append(linkTextNode);
43
+ $wrapNodes(selection, () => linkNode, $createParagraphNode());
44
+ }
45
+ }
46
+ });
47
+ },
48
+ [editor]
49
+ );
50
+
51
+ useEffect(
52
+ () =>
53
+ editor.registerCommand(
54
+ INSERT_MARKDOWN_LINK_COMMAND,
55
+ (payload: InsertLinkPayload) => {
56
+ createLink(payload);
57
+ return true;
58
+ },
59
+ COMMAND_PRIORITY_EDITOR
60
+ ),
61
+ [editor, createLink]
62
+ );
63
+ }
64
+
65
+ export function MarkdownLinkPlugin() {
66
+ useMarkdownLinkPlugin();
67
+
68
+ return <InsertLinkDialog />;
69
+ }
@@ -0,0 +1,46 @@
1
+ import React from 'react';
2
+ import { $convertToMarkdownString } from '@lexical/markdown';
3
+ import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin';
4
+ import type { EditorState } from 'lexical';
5
+ import debounce from 'lodash/debounce';
6
+
7
+ import transformers from '../transformers';
8
+ import { useContentEditor } from '../index';
9
+
10
+ function usePreview(onContentChange: (markdown: string) => void) {
11
+ const handleContentChange = debounce(onContentChange, 250, { maxWait: 500 });
12
+
13
+ const onChange = (editorState: EditorState) => {
14
+ editorState.read(() => {
15
+ const markdown = $convertToMarkdownString(transformers);
16
+ if (markdown) {
17
+ handleContentChange(markdown);
18
+ }
19
+ });
20
+ };
21
+
22
+ return { onChange };
23
+ }
24
+
25
+ export type PreviewPluginProps = {
26
+ compileMDX: (source: string, parseFrontMatter: boolean) => Promise<any>;
27
+ };
28
+
29
+ export const PreviewPlugin: React.FC<PreviewPluginProps> = ({ compileMDX }) => {
30
+ const { setErrorMessage, setPreviewContent } = useContentEditor();
31
+
32
+ const handleContentChange = async (content: string) => {
33
+ try {
34
+ const data = await compileMDX(content, false /** Don't parse frontmatter */);
35
+ setPreviewContent(data);
36
+ } catch (e) {
37
+ if (e instanceof Error) {
38
+ setErrorMessage(`MDX Error: ${e.message}`);
39
+ }
40
+ }
41
+ };
42
+
43
+ const { onChange } = usePreview(handleContentChange);
44
+
45
+ return <OnChangePlugin onChange={onChange} ignoreSelectionChange />;
46
+ };
@@ -0,0 +1,29 @@
1
+ import { lightModeConditions, vars } from '@jpmorganchase/mosaic-theme';
2
+ import { defineProperties, createSprinkles } from '@vanilla-extract/sprinkles';
3
+
4
+ export const outlineProperties = defineProperties({
5
+ conditions: {
6
+ ...lightModeConditions
7
+ },
8
+ defaultCondition: 'lightMode',
9
+ properties: {
10
+ outlineColor: [vars.color.light.selectable.inEdit, vars.color.dark.selectable.inEdit],
11
+ outlineStyle: ['solid', 'dashed'],
12
+ outlineWidth: ['thin', 'medium', 'thick']
13
+ }
14
+ });
15
+
16
+ const outlineSprinkles = createSprinkles(outlineProperties);
17
+
18
+ export default {
19
+ focused: outlineSprinkles({
20
+ outlineStyle: 'dashed',
21
+ outlineWidth: 'thin',
22
+ outlineColor: {
23
+ lightMode: vars.color.light.selectable.inEdit,
24
+ darkMode: vars.color.dark.selectable.inEdit
25
+ }
26
+ })
27
+ .trim()
28
+ .split(' ')
29
+ };
@@ -0,0 +1,278 @@
1
+ import React, { useCallback, useEffect, useState } from 'react';
2
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
3
+ import { $getSelection, $isRangeSelection, LexicalEditor } from 'lexical';
4
+ import {
5
+ $deleteTableColumn,
6
+ $getElementGridForTableNode,
7
+ $getTableCellNodeFromLexicalNode,
8
+ $getTableColumnIndexFromTableCellNode,
9
+ $getTableNodeFromLexicalNodeOrThrow,
10
+ $getTableRowIndexFromTableCellNode,
11
+ $insertTableColumn,
12
+ $insertTableRow,
13
+ $isTableCellNode,
14
+ $removeTableRowAtIndex,
15
+ TableCellNode
16
+ } from '@lexical/table';
17
+ import { useFloatingUI } from '@jpmorganchase/uitk-core';
18
+ import { useDismiss, useInteractions } from '@floating-ui/react-dom-interactions';
19
+ import {
20
+ ArrowDownIcon,
21
+ ArrowLeftIcon,
22
+ ArrowRightIcon,
23
+ ArrowUpIcon,
24
+ DeleteIcon,
25
+ DeleteSolidIcon
26
+ } from '@jpmorganchase/uitk-icons';
27
+ import type { MenuDescriptor } from '@jpmorganchase/uitk-lab';
28
+
29
+ import { Popper } from '../components/Popper/Popper';
30
+ import { ActionMenu, ActionMenuSource } from '../components/ActionMenu/ActionMenu';
31
+ import styles from './TableActionMenuPlugin.css';
32
+
33
+ const initialSource: ActionMenuSource = {
34
+ menuItems: [
35
+ {
36
+ title: 'Insert Row Above',
37
+ icon: ArrowUpIcon
38
+ },
39
+ {
40
+ title: 'Insert Row Below',
41
+ icon: ArrowDownIcon
42
+ },
43
+ {
44
+ title: 'Insert Column Left',
45
+ icon: ArrowLeftIcon
46
+ },
47
+ {
48
+ title: 'Insert Column Right',
49
+ icon: ArrowRightIcon
50
+ },
51
+ {
52
+ title: 'Delete Row',
53
+ icon: DeleteIcon
54
+ },
55
+ {
56
+ title: 'Delete Column',
57
+ icon: DeleteIcon
58
+ },
59
+ {
60
+ title: 'Delete Table',
61
+ icon: DeleteSolidIcon
62
+ }
63
+ ]
64
+ };
65
+
66
+ interface TableActionMenuProps {
67
+ editor: LexicalEditor;
68
+ tableCellNode: TableCellNode | null;
69
+ onComplete: () => void;
70
+ }
71
+
72
+ function TableActionMenu({ editor, tableCellNode, onComplete }: TableActionMenuProps) {
73
+ const handleMenuSelect = (item: MenuDescriptor | null) => {
74
+ if (!item) {
75
+ return;
76
+ }
77
+ if (item.title === 'Delete Row') {
78
+ deleteTableRowAtSelection();
79
+ return;
80
+ }
81
+
82
+ if (item.title === 'Insert Row Below') {
83
+ insertTableRow(true);
84
+ return;
85
+ }
86
+
87
+ if (item.title === 'Insert Row Above') {
88
+ insertTableRow(false);
89
+ return;
90
+ }
91
+
92
+ if (item.title === 'Insert Column Left') {
93
+ insertTableColumn(false);
94
+ return;
95
+ }
96
+
97
+ if (item.title === 'Insert Column Right') {
98
+ insertTableColumn(true);
99
+ return;
100
+ }
101
+
102
+ if (item.title === 'Delete Column') {
103
+ deleteTableColumnAtSelection();
104
+ return;
105
+ }
106
+ if (item.title === 'Delete Table') {
107
+ deleteTable();
108
+ }
109
+ };
110
+
111
+ const deleteTableRowAtSelection = useCallback(() => {
112
+ editor.update(() => {
113
+ if ($isTableCellNode(tableCellNode)) {
114
+ const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
115
+ const tableRowIndex = $getTableRowIndexFromTableCellNode(tableCellNode);
116
+
117
+ $removeTableRowAtIndex(tableNode, tableRowIndex);
118
+ onComplete();
119
+ }
120
+ });
121
+ }, [editor, onComplete, tableCellNode]);
122
+
123
+ const insertTableRow = useCallback(
124
+ shouldInsertAfter => {
125
+ editor.update(() => {
126
+ if ($isTableCellNode(tableCellNode)) {
127
+ const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
128
+ const tableRowIndex = $getTableRowIndexFromTableCellNode(tableCellNode);
129
+ const grid = $getElementGridForTableNode(editor, tableNode);
130
+
131
+ $insertTableRow(tableNode, tableRowIndex, shouldInsertAfter, 1, grid);
132
+ }
133
+
134
+ onComplete();
135
+ });
136
+ },
137
+ [editor, tableCellNode, onComplete]
138
+ );
139
+
140
+ const insertTableColumn = useCallback(
141
+ shouldInsertAfter => {
142
+ editor.update(() => {
143
+ if ($isTableCellNode(tableCellNode)) {
144
+ const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
145
+ const tableColumnIndex = $getTableColumnIndexFromTableCellNode(tableCellNode);
146
+ const grid = $getElementGridForTableNode(editor, tableNode);
147
+
148
+ $insertTableColumn(tableNode, tableColumnIndex, shouldInsertAfter, 1, grid);
149
+ }
150
+ onComplete();
151
+ });
152
+ },
153
+ [editor, tableCellNode, onComplete]
154
+ );
155
+
156
+ const deleteTableColumnAtSelection = useCallback(() => {
157
+ editor.update(() => {
158
+ if ($isTableCellNode(tableCellNode)) {
159
+ const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
160
+ const tableColumnIndex = $getTableColumnIndexFromTableCellNode(tableCellNode);
161
+ $deleteTableColumn(tableNode, tableColumnIndex);
162
+ onComplete();
163
+ }
164
+ });
165
+ }, [editor, tableCellNode, onComplete]);
166
+
167
+ const deleteTable = useCallback(() => {
168
+ editor.update(() => {
169
+ if ($isTableCellNode(tableCellNode)) {
170
+ const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
171
+ tableNode.remove();
172
+ onComplete();
173
+ }
174
+ });
175
+ }, [editor, tableCellNode, onComplete]);
176
+
177
+ return <ActionMenu initialSource={initialSource} onItemClick={handleMenuSelect} />;
178
+ }
179
+
180
+ export function TableActionMenuPlugin() {
181
+ const [editor] = useLexicalComposerContext();
182
+ const [tableCellNode, setTableMenuCellNode] = useState<TableCellNode | null>(null);
183
+ const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
184
+ const { context, floating, reference, strategy, x, y } = useFloatingUI({
185
+ placement: 'right',
186
+ strategy: 'absolute',
187
+ open: tableCellNode !== null,
188
+ onOpenChange: () => setTableMenuCellNode(null)
189
+ });
190
+ const { getFloatingProps } = useInteractions([
191
+ useDismiss(context, {
192
+ ancestorScroll: true
193
+ })
194
+ ]);
195
+
196
+ const updatePosition = useCallback(() => {
197
+ const selection = $getSelection();
198
+ const nativeSelection = window.getSelection();
199
+ const { activeElement } = document;
200
+
201
+ if (selection == null) {
202
+ setTableMenuCellNode(null);
203
+ return;
204
+ }
205
+
206
+ const rootElement = editor.getRootElement();
207
+
208
+ if (
209
+ $isRangeSelection(selection) &&
210
+ rootElement !== null &&
211
+ nativeSelection !== null &&
212
+ rootElement.contains(nativeSelection.anchorNode)
213
+ ) {
214
+ const tableCellNodeFromSelection = $getTableCellNodeFromLexicalNode(
215
+ selection.anchor.getNode()
216
+ );
217
+
218
+ if (tableCellNodeFromSelection == null) {
219
+ setTableMenuCellNode(null);
220
+ return;
221
+ }
222
+
223
+ const tableCellParentNodeDOM = editor.getElementByKey(tableCellNodeFromSelection.getKey());
224
+
225
+ if (tableCellParentNodeDOM == null) {
226
+ setTableMenuCellNode(null);
227
+ return;
228
+ }
229
+
230
+ tableCellParentNodeDOM.classList.add(...styles.focused);
231
+ setTableMenuCellNode(tableCellNodeFromSelection);
232
+ reference(tableCellParentNodeDOM);
233
+ setAnchorEl(tableCellParentNodeDOM);
234
+ } else if (!activeElement) {
235
+ setTableMenuCellNode(null);
236
+ }
237
+ }, [editor, reference]);
238
+
239
+ useEffect(
240
+ () =>
241
+ editor.registerUpdateListener(() => {
242
+ editor.getEditorState().read(() => {
243
+ updatePosition();
244
+ });
245
+ }),
246
+ [editor, updatePosition]
247
+ );
248
+
249
+ useEffect(
250
+ function applyFocusRing() {
251
+ anchorEl?.classList.add(...styles.focused);
252
+ return function removeFocusRing() {
253
+ anchorEl?.classList.remove(...styles.focused);
254
+ };
255
+ },
256
+ [anchorEl?.classList, tableCellNode]
257
+ );
258
+
259
+ const closePopper = () => {
260
+ setAnchorEl(null);
261
+ };
262
+
263
+ const handleComplete = () => {
264
+ setAnchorEl(null);
265
+ setTableMenuCellNode(null);
266
+ };
267
+
268
+ return (
269
+ <Popper
270
+ ref={floating}
271
+ open={tableCellNode !== null}
272
+ style={{ top: y ?? '', left: x ?? '', position: strategy }}
273
+ {...getFloatingProps({})}
274
+ >
275
+ <TableActionMenu editor={editor} tableCellNode={tableCellNode} onComplete={handleComplete} />
276
+ </Popper>
277
+ );
278
+ }