@jpmorganchase/mosaic-content-editor-plugin 0.1.0-beta.10

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/CHANGELOG.md +19 -0
  3. package/README.md +7 -0
  4. package/dist/api/save.d.ts +5 -0
  5. package/dist/api/save.js +1 -0
  6. package/dist/chunk-2N4TKE2M.js +1 -0
  7. package/dist/chunk-3FJGHVH5.js +1 -0
  8. package/dist/chunk-3ODP7WU3.js +1 -0
  9. package/dist/chunk-4EAOB7YQ.js +1 -0
  10. package/dist/chunk-4QQTGYY3.js +1 -0
  11. package/dist/chunk-5YCBSSDA.js +1 -0
  12. package/dist/chunk-6TM3EYFS.js +1 -0
  13. package/dist/chunk-7FEXXQT5.js +1 -0
  14. package/dist/chunk-7N3YX6NW.js +1 -0
  15. package/dist/chunk-BXNO7GED.js +1 -0
  16. package/dist/chunk-ENTOKO6G.js +1 -0
  17. package/dist/chunk-ESJNOEFD.js +1 -0
  18. package/dist/chunk-ESKW4XF6.js +1 -0
  19. package/dist/chunk-FFJYM47T.js +1 -0
  20. package/dist/chunk-FI57IKHZ.js +1 -0
  21. package/dist/chunk-FKL4ZGDB.js +1 -0
  22. package/dist/chunk-FW7CHFWO.js +1 -0
  23. package/dist/chunk-G4LBXKDE.js +1 -0
  24. package/dist/chunk-GDXWHJOU.js +1 -0
  25. package/dist/chunk-GGJCFLQH.js +1 -0
  26. package/dist/chunk-GWUYDDYS.js +1 -0
  27. package/dist/chunk-HB6ONQSD.js +1 -0
  28. package/dist/chunk-I7LEOMRH.js +1 -0
  29. package/dist/chunk-JQUDHJIQ.js +1 -0
  30. package/dist/chunk-JYGEHLYM.js +1 -0
  31. package/dist/chunk-KE4TEFFN.js +1 -0
  32. package/dist/chunk-KQ2ZCDYD.js +1 -0
  33. package/dist/chunk-KZXCNQER.js +1 -0
  34. package/dist/chunk-MJUOPD42.js +1 -0
  35. package/dist/chunk-NLBFMDUH.js +1 -0
  36. package/dist/chunk-NXV7KCC5.js +1 -0
  37. package/dist/chunk-P5OBPOC4.js +0 -0
  38. package/dist/chunk-PHAO5PCV.js +1 -0
  39. package/dist/chunk-PVPRWTXB.js +1 -0
  40. package/dist/chunk-PZQ26XWS.js +1 -0
  41. package/dist/chunk-QWW5D4V7.js +1 -0
  42. package/dist/chunk-RO6ARCLZ.js +1 -0
  43. package/dist/chunk-RT5CJ7T5.js +1 -0
  44. package/dist/chunk-U22HX567.js +1 -0
  45. package/dist/chunk-U7COOUR6.js +1 -0
  46. package/dist/chunk-VC2SMAQF.js +1 -0
  47. package/dist/chunk-WCG3U3X2.js +1 -0
  48. package/dist/chunk-WNTJMWYJ.js +2 -0
  49. package/dist/chunk-WTMALWJU.js +1 -0
  50. package/dist/chunk-WZYDYWT3.js +1 -0
  51. package/dist/chunk-XGBC7YGO.js +1 -0
  52. package/dist/chunk-XP37MJCS.js +1 -0
  53. package/dist/chunk-Y4VRZBBA.js +1 -0
  54. package/dist/chunk-YBFKG74H.js +1 -0
  55. package/dist/chunk-ZUGL4RMA.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 +66 -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 +56 -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 +26 -0
  195. package/src/components/ContentEditor.tsx +12 -0
  196. package/src/components/Editor.css.ts +64 -0
  197. package/src/components/Editor.tsx +145 -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 +109 -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 +274 -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,109 @@
1
+ import React, { FC, useState } from 'react';
2
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
3
+ import { $convertToMarkdownString } from '@lexical/markdown';
4
+ import { Link, P2, Button } from '@jpmorganchase/mosaic-components';
5
+ import { Dialog as SaltDialog, DialogTitle, DialogContent, DialogActions } from '@salt-ds/lab';
6
+
7
+ import { useEditorUser, usePageState } from '../../store';
8
+ import { save } from '../../api/save';
9
+ import transformers from '../../transformers';
10
+ import { PersistStatus } from './PersistStatus';
11
+
12
+ interface InfoProps {
13
+ isRaising: boolean;
14
+ prHref: string | null;
15
+ error: string | null;
16
+ }
17
+
18
+ const Info: FC<InfoProps> = ({ isRaising, prHref, error }) =>
19
+ !isRaising && !prHref && !error ? (
20
+ <>
21
+ <P2>
22
+ The content of this page resides in a Git repository and to update it requires a Pull
23
+ Request which will be reviewed by the content owners.
24
+ </P2>
25
+ <br />
26
+ <P2>
27
+ Should you decide to stop editing before creating the Pull Request then all changes will be
28
+ lost.
29
+ </P2>
30
+ </>
31
+ ) : null;
32
+
33
+ export const PersistDialog = ({ meta }: { meta: any }) => {
34
+ const { pageState, setPageState } = usePageState();
35
+ const { user } = useEditorUser();
36
+ const [editor] = useLexicalComposerContext();
37
+ const [isRaising, setIsRaising] = useState(false);
38
+ const [prHref, setPrHref] = useState<string | null>(null);
39
+ const [error, setError] = useState<string | null>(null);
40
+
41
+ const open = pageState === 'SAVING';
42
+ const state = prHref !== null ? 'success' : 'info';
43
+
44
+ const handleClose = () => {
45
+ setPageState('EDIT');
46
+ setPrHref(null);
47
+ setIsRaising(false);
48
+ };
49
+
50
+ const handleRaisePr = () => {
51
+ setIsRaising(true);
52
+ setPrHref(null);
53
+ setError(null);
54
+ try {
55
+ editor.update(async () => {
56
+ const markdown = $convertToMarkdownString(transformers);
57
+ if (markdown && user) {
58
+ const { sid, displayName, email } = user;
59
+ const response = await save({ sid, name: displayName, email }, meta.route, markdown);
60
+
61
+ if (response.ok) {
62
+ const data = await response.json();
63
+ if (data.error) {
64
+ setError(data.error);
65
+ setPrHref(null);
66
+ setIsRaising(false);
67
+ } else {
68
+ setPrHref(data?.links?.self[0]?.href);
69
+ setIsRaising(false);
70
+ }
71
+ }
72
+
73
+ if (response.status === 500) {
74
+ setError('An unexpected error has occurred.');
75
+ setPrHref(null);
76
+ setIsRaising(false);
77
+ }
78
+ }
79
+ });
80
+ } catch (e) {
81
+ setError('Sorry');
82
+ setPrHref(null);
83
+ setIsRaising(false);
84
+ }
85
+ };
86
+
87
+ return (
88
+ <SaltDialog onClose={handleClose} open={open} status={error ? 'error' : state} width="50%">
89
+ <DialogTitle>{!prHref ? 'Save Changes' : 'Pull Request Created Successfully'}</DialogTitle>
90
+ <DialogContent>
91
+ {(isRaising || error) && !prHref && <PersistStatus isRaising={isRaising} error={error} />}
92
+ <Info isRaising={isRaising} prHref={prHref} error={error} />
93
+ {!isRaising && prHref && (
94
+ <Link href={prHref} target="_blank">
95
+ A Pull Request for your changes has been created
96
+ </Link>
97
+ )}
98
+ </DialogContent>
99
+ <DialogActions>
100
+ <Button disabled={isRaising} onClick={handleClose}>
101
+ Cancel
102
+ </Button>
103
+ <Button disabled={isRaising || prHref !== null} onClick={handleRaisePr} variant="cta">
104
+ Raise Pull Request
105
+ </Button>
106
+ </DialogActions>
107
+ </SaltDialog>
108
+ );
109
+ };
@@ -0,0 +1,12 @@
1
+ import { style } from '@vanilla-extract/css';
2
+ import { backgroundColor, shadow } from '@jpmorganchase/mosaic-theme';
3
+
4
+ export const POPPER_Z_INDEX = 10;
5
+
6
+ export default {
7
+ root: style([
8
+ style({ zIndex: POPPER_Z_INDEX }),
9
+ backgroundColor({ variant: 'regular' }),
10
+ shadow({ variant: 'elevation3' })
11
+ ])
12
+ };
@@ -0,0 +1,31 @@
1
+ import React, { forwardRef, HTMLAttributes, ReactNode } from 'react';
2
+ import { Portal, useWindow } from '@salt-ds/lab';
3
+ import classnames from 'classnames';
4
+
5
+ import styles from './Popper.css';
6
+
7
+ interface PopperProps extends HTMLAttributes<HTMLDivElement> {
8
+ /** Floating contents */
9
+ children?: ReactNode;
10
+ /** Class name */
11
+ className?: string;
12
+ /** Flag indicating whether the floating content is visible */
13
+ open?: boolean;
14
+ }
15
+
16
+ export const Popper = forwardRef<HTMLDivElement, PopperProps>(function Popper(props, forwardedRef) {
17
+ const { children, className, ...rest } = props;
18
+ const Window = useWindow();
19
+
20
+ if (!rest.open) {
21
+ return null;
22
+ }
23
+
24
+ return (
25
+ <Portal>
26
+ <Window className={classnames(className, styles.root)} ref={forwardedRef} {...rest}>
27
+ {children}
28
+ </Window>
29
+ </Portal>
30
+ );
31
+ });
@@ -0,0 +1,23 @@
1
+ import React, { useState } from 'react';
2
+ import { Button, Icon } from '@jpmorganchase/mosaic-components';
3
+ import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin';
4
+
5
+ import { usePageState } from '../store';
6
+
7
+ export const SaveButton = () => {
8
+ const { setPageState } = usePageState();
9
+ const [isDisabled, setIsDisabled] = useState(true);
10
+
11
+ const onChange = () => {
12
+ setIsDisabled(false);
13
+ };
14
+
15
+ return (
16
+ <>
17
+ <OnChangePlugin onChange={onChange} ignoreSelectionChange />
18
+ <Button disabled={isDisabled} variant="cta" onClick={() => setPageState('SAVING')}>
19
+ <Icon name="save" /> &nbsp; Save
20
+ </Button>
21
+ </>
22
+ );
23
+ };
@@ -0,0 +1,10 @@
1
+ import { style } from '@vanilla-extract/css';
2
+ import { vars } from '@jpmorganchase/mosaic-theme';
3
+
4
+ export default {
5
+ root: style({
6
+ padding: vars.space.horizontal.x3,
7
+ maxHeight: '100vh',
8
+ overflow: 'scroll'
9
+ })
10
+ };
@@ -0,0 +1,20 @@
1
+ import React, { Ref, forwardRef } from 'react';
2
+ import classnames from 'classnames';
3
+
4
+ import styles from './ScrollableSection.css';
5
+
6
+ interface ScrollableSectionProps {
7
+ className?: string;
8
+ children: React.ReactNode;
9
+ }
10
+
11
+ export const ScrollableSection = forwardRef(function ScrollableSection(
12
+ { className, children }: ScrollableSectionProps,
13
+ ref: Ref<HTMLDivElement>
14
+ ) {
15
+ return (
16
+ <section ref={ref} className={classnames(styles.root, className)}>
17
+ {children}
18
+ </section>
19
+ );
20
+ });
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ import { Banner } from '@salt-ds/lab';
3
+
4
+ import { usePageState } from '../store';
5
+
6
+ const StatusBanner = () => {
7
+ const { pageState, errorMessage } = usePageState();
8
+ const isError = pageState === 'ERROR';
9
+ return isError ? (
10
+ <Banner announcement={errorMessage} status="error">
11
+ {errorMessage}
12
+ </Banner>
13
+ ) : null;
14
+ };
15
+
16
+ export default StatusBanner;
@@ -0,0 +1,134 @@
1
+ import React from 'react';
2
+ import { Dropdown, SelectionChangeHandler } from '@salt-ds/lab';
3
+ import { $createCodeNode } from '@lexical/code';
4
+ import { INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND } from '@lexical/list';
5
+ import { $createHeadingNode, $createQuoteNode, HeadingTagType } from '@lexical/rich-text';
6
+ import { $wrapNodes } from '@lexical/selection';
7
+ import { $createParagraphNode, $getSelection, $isRangeSelection, LexicalEditor } from 'lexical';
8
+
9
+ interface BlockSourceType {
10
+ name: string;
11
+ type: string;
12
+ }
13
+
14
+ const itemToString = (item?: BlockSourceType | null) => (item ? item.name : '');
15
+
16
+ const headings = [
17
+ { name: 'Heading 1', type: 'h1' },
18
+ { name: 'Heading 2', type: 'h2' },
19
+ { name: 'Heading 3', type: 'h3' },
20
+ { name: 'Heading 4', type: 'h4' },
21
+ { name: 'Heading 5', type: 'h5' },
22
+ { name: 'Heading 6', type: 'h6' }
23
+ ];
24
+
25
+ const source = [
26
+ { name: 'Bulleted List', type: 'bullet' },
27
+ { name: 'Code Block', type: 'code' },
28
+ ...headings,
29
+ { name: 'Numbered List', type: 'number' },
30
+ { name: 'Paragraph', type: 'paragraph' },
31
+ { name: 'Quote', type: 'quote' }
32
+ ];
33
+
34
+ export function InsertBlockDropdown({
35
+ editor,
36
+ type: blockSourceType
37
+ }: {
38
+ editor: LexicalEditor;
39
+ type: string;
40
+ }): JSX.Element {
41
+ const formatParagraph = (item: BlockSourceType) => {
42
+ if (item.type === 'paragraph') {
43
+ editor.update(() => {
44
+ const selection = $getSelection();
45
+
46
+ if ($isRangeSelection(selection)) {
47
+ $wrapNodes(selection, () => $createParagraphNode());
48
+ }
49
+ });
50
+ }
51
+ };
52
+
53
+ const formatHeading = (item: BlockSourceType) => {
54
+ if (headings.findIndex(heading => heading.type === item.type) >= 0) {
55
+ editor.update(() => {
56
+ const selection = $getSelection();
57
+
58
+ if ($isRangeSelection(selection)) {
59
+ $wrapNodes(selection, () => $createHeadingNode(item.type as HeadingTagType));
60
+ }
61
+ });
62
+ }
63
+ };
64
+
65
+ const formatBulletList = (item: BlockSourceType) => {
66
+ if (item.type === 'bullet') {
67
+ editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined);
68
+ }
69
+ };
70
+
71
+ const formatNumberedList = (item: BlockSourceType) => {
72
+ if (item.type === 'number') {
73
+ editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined);
74
+ }
75
+ };
76
+
77
+ const formatQuote = (item: BlockSourceType) => {
78
+ if (item.type === 'quote') {
79
+ editor.update(() => {
80
+ const selection = $getSelection();
81
+
82
+ if ($isRangeSelection(selection)) {
83
+ $wrapNodes(selection, () => $createQuoteNode());
84
+ }
85
+ });
86
+ }
87
+ };
88
+
89
+ const formatCode = (item: BlockSourceType) => {
90
+ if (item.type === 'code') {
91
+ editor.update(() => {
92
+ const selection = $getSelection();
93
+
94
+ if ($isRangeSelection(selection)) {
95
+ if (selection.isCollapsed()) {
96
+ $wrapNodes(selection, () => $createCodeNode());
97
+ } else {
98
+ const textContent = selection.getTextContent();
99
+ const codeNode = $createCodeNode();
100
+ selection.removeText();
101
+ selection.insertNodes([codeNode]);
102
+ selection.insertRawText(textContent);
103
+ }
104
+ }
105
+ });
106
+ }
107
+ };
108
+
109
+ const handleSelect: SelectionChangeHandler<BlockSourceType, 'default'> = (_event, item) => {
110
+ if (!item) {
111
+ return;
112
+ }
113
+ formatParagraph(item);
114
+ formatHeading(item);
115
+ formatBulletList(item);
116
+ formatNumberedList(item);
117
+ formatQuote(item);
118
+ formatCode(item);
119
+ };
120
+
121
+ const selectedBlockSourceIndex = source.findIndex(item => item.type === blockSourceType);
122
+ return (
123
+ <Dropdown
124
+ ListProps={{
125
+ width: 200
126
+ }}
127
+ defaultSelected={source[selectedBlockSourceIndex]}
128
+ itemToString={itemToString}
129
+ onSelectionChange={handleSelect}
130
+ source={source}
131
+ width={132}
132
+ />
133
+ );
134
+ }
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import { ToolbarButton } from '@salt-ds/lab';
3
+ import { Icon } from '@jpmorganchase/mosaic-components';
4
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
5
+ import { INSERT_HORIZONTAL_RULE_COMMAND } from '@lexical/react/LexicalHorizontalRuleNode';
6
+
7
+ export const InsertHorizontalRule = () => {
8
+ const [editor] = useLexicalComposerContext();
9
+
10
+ const handleClick = () => {
11
+ editor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, undefined);
12
+ };
13
+ return (
14
+ <ToolbarButton onClick={handleClick}>
15
+ <Icon name="minimize" />
16
+ </ToolbarButton>
17
+ );
18
+ };
@@ -0,0 +1,7 @@
1
+ import { style } from '@vanilla-extract/css';
2
+
3
+ export default {
4
+ fullWidth: style({
5
+ width: '100%'
6
+ })
7
+ };
@@ -0,0 +1,132 @@
1
+ import React, { useState } from 'react';
2
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
3
+ import { Button, Icon } from '@jpmorganchase/mosaic-components';
4
+ import { string, object } from 'yup';
5
+ import {
6
+ ButtonBar,
7
+ Dialog as SaltDialog,
8
+ DialogTitle,
9
+ DialogContent,
10
+ DialogActions
11
+ } from '@salt-ds/lab';
12
+ import { FormField, Input } from '@salt-ds/lab';
13
+
14
+ import { TextFormatToolbarButton } from './TextFormatToolbarButton';
15
+ import {
16
+ INSERT_MARKDOWN_IMAGE_COMMAND,
17
+ InsertImagePayload
18
+ } from '../../plugins/MarkdownImagePlugin';
19
+ import styles from './InsertImage.css';
20
+
21
+ const validationSchema = object({
22
+ alt: string()
23
+ .required('Alternative Information is a required field')
24
+ .max(100, 'Alternative Information must be fewer than 100 characters'),
25
+ url: string().required('Url is required').url('Must be a valid Url')
26
+ });
27
+
28
+ const initialState = {
29
+ url: 'https://'
30
+ };
31
+
32
+ type FormValueState =
33
+ | {
34
+ alt?: string;
35
+ url?: string;
36
+ }
37
+ | undefined;
38
+
39
+ type FormErrorState =
40
+ | {
41
+ alt?: string;
42
+ url?: string;
43
+ }
44
+ | undefined;
45
+
46
+ export const InsertImage = () => {
47
+ const [editor] = useLexicalComposerContext();
48
+ const [isOpen, setIsOpen] = useState(false);
49
+ const [values, setValues] = useState<FormValueState>(initialState);
50
+ const [errors, setErrors] = useState<FormErrorState>();
51
+
52
+ const handleClose = () => {
53
+ setIsOpen(false);
54
+ };
55
+
56
+ const handleOpen = () => setIsOpen(true);
57
+
58
+ const processErrors = validationErrors => {
59
+ const newErrors = validationErrors.inner.reduce(
60
+ (acc, { path, message }) => ({
61
+ ...acc,
62
+ [path]: message
63
+ }),
64
+ {}
65
+ );
66
+ setErrors(newErrors);
67
+ };
68
+
69
+ const handleChange = (event, value) => {
70
+ const { name } = event.target;
71
+ const newValues = { ...values, [name]: value };
72
+ validationSchema.validateAt(name, newValues, { abortEarly: false }).then(() => {
73
+ setErrors({ ...errors, [name]: undefined });
74
+ }, processErrors);
75
+ setValues(newValues);
76
+ };
77
+
78
+ const handleSubmit = async event => {
79
+ event.preventDefault();
80
+ validationSchema.validate(values, { abortEarly: false }).then(() => {
81
+ const payload: InsertImagePayload = {
82
+ alt: values && values.alt !== undefined ? values.alt : null,
83
+ url: values && values.url !== undefined ? values.url : null
84
+ };
85
+ editor.dispatchCommand(INSERT_MARKDOWN_IMAGE_COMMAND, payload);
86
+ handleClose();
87
+ }, processErrors);
88
+ };
89
+
90
+ return (
91
+ <>
92
+ <TextFormatToolbarButton active={isOpen} onClick={handleOpen} label="Insert Image">
93
+ <Icon name="addDocument" />
94
+ </TextFormatToolbarButton>
95
+
96
+ <SaltDialog onClose={handleClose} open={isOpen} width={600}>
97
+ <form onSubmit={handleSubmit} noValidate>
98
+ <DialogTitle>Insert Image</DialogTitle>
99
+ <DialogContent>
100
+ <div className={styles.fullWidth}>
101
+ <FormField
102
+ label="Url for image"
103
+ validationStatus={errors?.url ? 'error' : undefined}
104
+ helperText={errors?.url}
105
+ >
106
+ <Input value={values?.url} inputProps={{ name: 'url' }} onChange={handleChange} />
107
+ </FormField>
108
+ <FormField
109
+ label="Alternative Information (alt)"
110
+ validationStatus={errors?.alt ? 'error' : undefined}
111
+ helperText={
112
+ errors?.alt ||
113
+ 'Provides alternative information for the image if for some reason it cannot be viewed'
114
+ }
115
+ >
116
+ <Input value={values?.alt} inputProps={{ name: 'alt' }} onChange={handleChange} />
117
+ </FormField>
118
+ </div>
119
+ </DialogContent>
120
+ <DialogActions>
121
+ <ButtonBar>
122
+ <Button onClick={handleClose}>Cancel</Button>
123
+ <Button variant="cta" type="submit">
124
+ Insert
125
+ </Button>
126
+ </ButtonBar>
127
+ </DialogActions>
128
+ </form>
129
+ </SaltDialog>
130
+ </>
131
+ );
132
+ };
@@ -0,0 +1,7 @@
1
+ import { style } from '@vanilla-extract/css';
2
+
3
+ export default {
4
+ fullWidth: style({
5
+ width: '100%'
6
+ })
7
+ };
@@ -0,0 +1,145 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
3
+ import { Button, Icon } from '@jpmorganchase/mosaic-components';
4
+ import { string, object } from 'yup';
5
+ import { FormField, Input } from '@salt-ds/lab';
6
+ import {
7
+ ButtonBar,
8
+ Dialog as SaltDialog,
9
+ DialogTitle,
10
+ DialogContent,
11
+ DialogActions
12
+ } from '@salt-ds/lab';
13
+ import { $getSelection, $isRangeSelection } from 'lexical';
14
+
15
+ import { TextFormatToolbarButton } from './TextFormatToolbarButton';
16
+ import { INSERT_MARKDOWN_LINK_COMMAND, InsertLinkPayload } from '../../plugins/MarkdownLinkPlugin';
17
+ import styles from './InsertLink.css';
18
+ import { useIsInsertingLink } from '../../store';
19
+
20
+ const validationSchema = object({
21
+ url: string().required('Url is required'),
22
+ text: string().required('Text is required').max(10, 'Text must be fewer than 100 characters')
23
+ });
24
+
25
+ export const InsertLinkButton = () => {
26
+ const { isInsertingLink, setIsInsertingLink } = useIsInsertingLink();
27
+ return (
28
+ <TextFormatToolbarButton
29
+ active={isInsertingLink}
30
+ onClick={() => setIsInsertingLink(true)}
31
+ label="Insert Link"
32
+ >
33
+ <Icon name="linked" />
34
+ </TextFormatToolbarButton>
35
+ );
36
+ };
37
+
38
+ const initialState = {
39
+ url: 'https://'
40
+ };
41
+
42
+ type FormValueState =
43
+ | {
44
+ text?: string;
45
+ url?: string;
46
+ }
47
+ | undefined;
48
+
49
+ type FormErrorState =
50
+ | {
51
+ text?: string;
52
+ url?: string;
53
+ }
54
+ | undefined;
55
+
56
+ export const InsertLinkDialog = () => {
57
+ const [editor] = useLexicalComposerContext();
58
+ const { isInsertingLink, setIsInsertingLink } = useIsInsertingLink();
59
+ const [values, setValues] = useState<FormValueState>(initialState);
60
+ const [errors, setErrors] = useState<FormErrorState>();
61
+
62
+ const handleClose = () => {
63
+ setIsInsertingLink(false);
64
+ setErrors(undefined);
65
+ setValues(initialState);
66
+ };
67
+
68
+ useEffect(() => {
69
+ editor.getEditorState().read(() => {
70
+ if (isInsertingLink) {
71
+ const selection = $getSelection();
72
+ if ($isRangeSelection(selection)) {
73
+ const textContent = selection.getTextContent();
74
+ setValues(values => ({ ...values, text: textContent }));
75
+ }
76
+ }
77
+ });
78
+ }, [editor, isInsertingLink]);
79
+
80
+ const processErrors = validationErrors => {
81
+ const newErrors = validationErrors.inner.reduce(
82
+ (acc, { path, message }) => ({
83
+ ...acc,
84
+ [path]: message
85
+ }),
86
+ {}
87
+ );
88
+ setErrors(newErrors);
89
+ };
90
+
91
+ const handleChange = (event, value) => {
92
+ const { name } = event.target;
93
+ const newValues = { ...values, [name]: value };
94
+ validationSchema.validateAt(name, newValues, { abortEarly: false }).then(() => {
95
+ setErrors({ ...errors, [name]: undefined });
96
+ }, processErrors);
97
+ setValues(newValues);
98
+ };
99
+
100
+ const handleSubmit = async event => {
101
+ event.preventDefault();
102
+ validationSchema.validate(values, { abortEarly: false }).then(() => {
103
+ const payload: InsertLinkPayload = {
104
+ url: values?.url,
105
+ text: values?.text
106
+ };
107
+ editor.dispatchCommand(INSERT_MARKDOWN_LINK_COMMAND, payload);
108
+ handleClose();
109
+ }, processErrors);
110
+ };
111
+
112
+ return (
113
+ <SaltDialog onClose={handleClose} open={isInsertingLink} width={600}>
114
+ <form onSubmit={handleSubmit} noValidate>
115
+ <DialogTitle>Insert Link</DialogTitle>
116
+ <DialogContent>
117
+ <div className={styles.fullWidth}>
118
+ <FormField
119
+ label="Image URL text"
120
+ validationStatus={errors?.url ? 'error' : undefined}
121
+ helperText={errors?.url}
122
+ >
123
+ <Input value={values?.url} inputProps={{ name: 'url' }} onChange={handleChange} />
124
+ </FormField>
125
+ <FormField
126
+ label="Link text"
127
+ validationStatus={errors?.text ? 'error' : undefined}
128
+ helperText={errors?.text}
129
+ >
130
+ <Input value={values?.text} inputProps={{ name: 'text' }} onChange={handleChange} />
131
+ </FormField>
132
+ </div>
133
+ </DialogContent>
134
+ <DialogActions>
135
+ <ButtonBar>
136
+ <Button onClick={handleClose}>Cancel</Button>
137
+ <Button variant="cta" type="submit">
138
+ Insert
139
+ </Button>
140
+ </ButtonBar>
141
+ </DialogActions>
142
+ </form>
143
+ </SaltDialog>
144
+ );
145
+ };