@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.
- package/.turbo/turbo-build.log +4 -0
- package/CHANGELOG.md +19 -0
- package/README.md +7 -0
- package/dist/api/save.d.ts +5 -0
- package/dist/api/save.js +1 -0
- package/dist/chunk-2N4TKE2M.js +1 -0
- package/dist/chunk-3FJGHVH5.js +1 -0
- package/dist/chunk-3ODP7WU3.js +1 -0
- package/dist/chunk-4EAOB7YQ.js +1 -0
- package/dist/chunk-4QQTGYY3.js +1 -0
- package/dist/chunk-5YCBSSDA.js +1 -0
- package/dist/chunk-6TM3EYFS.js +1 -0
- package/dist/chunk-7FEXXQT5.js +1 -0
- package/dist/chunk-7N3YX6NW.js +1 -0
- package/dist/chunk-BXNO7GED.js +1 -0
- package/dist/chunk-ENTOKO6G.js +1 -0
- package/dist/chunk-ESJNOEFD.js +1 -0
- package/dist/chunk-ESKW4XF6.js +1 -0
- package/dist/chunk-FFJYM47T.js +1 -0
- package/dist/chunk-FI57IKHZ.js +1 -0
- package/dist/chunk-FKL4ZGDB.js +1 -0
- package/dist/chunk-FW7CHFWO.js +1 -0
- package/dist/chunk-G4LBXKDE.js +1 -0
- package/dist/chunk-GDXWHJOU.js +1 -0
- package/dist/chunk-GGJCFLQH.js +1 -0
- package/dist/chunk-GWUYDDYS.js +1 -0
- package/dist/chunk-HB6ONQSD.js +1 -0
- package/dist/chunk-I7LEOMRH.js +1 -0
- package/dist/chunk-JQUDHJIQ.js +1 -0
- package/dist/chunk-JYGEHLYM.js +1 -0
- package/dist/chunk-KE4TEFFN.js +1 -0
- package/dist/chunk-KQ2ZCDYD.js +1 -0
- package/dist/chunk-KZXCNQER.js +1 -0
- package/dist/chunk-MJUOPD42.js +1 -0
- package/dist/chunk-NLBFMDUH.js +1 -0
- package/dist/chunk-NXV7KCC5.js +1 -0
- package/dist/chunk-P5OBPOC4.js +0 -0
- package/dist/chunk-PHAO5PCV.js +1 -0
- package/dist/chunk-PVPRWTXB.js +1 -0
- package/dist/chunk-PZQ26XWS.js +1 -0
- package/dist/chunk-QWW5D4V7.js +1 -0
- package/dist/chunk-RO6ARCLZ.js +1 -0
- package/dist/chunk-RT5CJ7T5.js +1 -0
- package/dist/chunk-U22HX567.js +1 -0
- package/dist/chunk-U7COOUR6.js +1 -0
- package/dist/chunk-VC2SMAQF.js +1 -0
- package/dist/chunk-WCG3U3X2.js +1 -0
- package/dist/chunk-WNTJMWYJ.js +2 -0
- package/dist/chunk-WTMALWJU.js +1 -0
- package/dist/chunk-WZYDYWT3.js +1 -0
- package/dist/chunk-XGBC7YGO.js +1 -0
- package/dist/chunk-XP37MJCS.js +1 -0
- package/dist/chunk-Y4VRZBBA.js +1 -0
- package/dist/chunk-YBFKG74H.js +1 -0
- package/dist/chunk-ZUGL4RMA.js +1 -0
- package/dist/components/ActionMenu/ActionMenu.css.css +1 -0
- package/dist/components/ActionMenu/ActionMenu.css.d.ts +4 -0
- package/dist/components/ActionMenu/ActionMenu.css.js +1 -0
- package/dist/components/ActionMenu/ActionMenu.d.ts +10 -0
- package/dist/components/ActionMenu/ActionMenu.js +1 -0
- package/dist/components/ContentEditor.d.ts +3 -0
- package/dist/components/ContentEditor.js +1 -0
- package/dist/components/Editor.css +1 -0
- package/dist/components/Editor.css.css +1 -0
- package/dist/components/Editor.css.d.ts +10 -0
- package/dist/components/Editor.css.js +1 -0
- package/dist/components/Editor.d.ts +14 -0
- package/dist/components/Editor.js +1 -0
- package/dist/components/EditorControls.css +1 -0
- package/dist/components/EditorControls.css.css +1 -0
- package/dist/components/EditorControls.css.d.ts +4 -0
- package/dist/components/EditorControls.css.js +1 -0
- package/dist/components/EditorControls.d.ts +4 -0
- package/dist/components/EditorControls.js +1 -0
- package/dist/components/LinkEditor/EditLinkButton.d.ts +3 -0
- package/dist/components/LinkEditor/EditLinkButton.js +1 -0
- package/dist/components/LinkEditor/LinkEditor.css +1 -0
- package/dist/components/LinkEditor/LinkEditor.css.css +1 -0
- package/dist/components/LinkEditor/LinkEditor.css.d.ts +4 -0
- package/dist/components/LinkEditor/LinkEditor.css.js +1 -0
- package/dist/components/LinkEditor/LinkEditor.d.ts +2 -0
- package/dist/components/LinkEditor/LinkEditor.js +1 -0
- package/dist/components/LinkEditor/SaveAdornment.d.ts +7 -0
- package/dist/components/LinkEditor/SaveAdornment.js +1 -0
- package/dist/components/PersistEditDialog/PersistStatus.css +1 -0
- package/dist/components/PersistEditDialog/PersistStatus.css.css +1 -0
- package/dist/components/PersistEditDialog/PersistStatus.css.d.ts +7 -0
- package/dist/components/PersistEditDialog/PersistStatus.css.js +1 -0
- package/dist/components/PersistEditDialog/PersistStatus.d.ts +7 -0
- package/dist/components/PersistEditDialog/PersistStatus.js +1 -0
- package/dist/components/PersistEditDialog/index.css +1 -0
- package/dist/components/PersistEditDialog/index.d.ts +4 -0
- package/dist/components/PersistEditDialog/index.js +1 -0
- package/dist/components/Popper/Popper.css +1 -0
- package/dist/components/Popper/Popper.css.css +1 -0
- package/dist/components/Popper/Popper.css.d.ts +5 -0
- package/dist/components/Popper/Popper.css.js +1 -0
- package/dist/components/Popper/Popper.d.ts +11 -0
- package/dist/components/Popper/Popper.js +1 -0
- package/dist/components/SaveButton.d.ts +2 -0
- package/dist/components/SaveButton.js +1 -0
- package/dist/components/ScrollableSection/ScrollableSection.css +1 -0
- package/dist/components/ScrollableSection/ScrollableSection.css.css +1 -0
- package/dist/components/ScrollableSection/ScrollableSection.css.d.ts +4 -0
- package/dist/components/ScrollableSection/ScrollableSection.css.js +1 -0
- package/dist/components/ScrollableSection/ScrollableSection.d.ts +7 -0
- package/dist/components/ScrollableSection/ScrollableSection.js +1 -0
- package/dist/components/StatusBanner.d.ts +3 -0
- package/dist/components/StatusBanner.js +1 -0
- package/dist/components/Toolbar/InsertBlockDropdown.d.ts +6 -0
- package/dist/components/Toolbar/InsertBlockDropdown.js +1 -0
- package/dist/components/Toolbar/InsertHorizontalRule.d.ts +2 -0
- package/dist/components/Toolbar/InsertHorizontalRule.js +1 -0
- package/dist/components/Toolbar/InsertImage.css +1 -0
- package/dist/components/Toolbar/InsertImage.css.css +1 -0
- package/dist/components/Toolbar/InsertImage.css.d.ts +4 -0
- package/dist/components/Toolbar/InsertImage.css.js +1 -0
- package/dist/components/Toolbar/InsertImage.d.ts +2 -0
- package/dist/components/Toolbar/InsertImage.js +1 -0
- package/dist/components/Toolbar/InsertLink.css +1 -0
- package/dist/components/Toolbar/InsertLink.css.css +1 -0
- package/dist/components/Toolbar/InsertLink.css.d.ts +4 -0
- package/dist/components/Toolbar/InsertLink.css.js +1 -0
- package/dist/components/Toolbar/InsertLink.d.ts +3 -0
- package/dist/components/Toolbar/InsertLink.js +1 -0
- package/dist/components/Toolbar/InsertTable.css +1 -0
- package/dist/components/Toolbar/InsertTable.css.css +1 -0
- package/dist/components/Toolbar/InsertTable.css.d.ts +13 -0
- package/dist/components/Toolbar/InsertTable.css.js +1 -0
- package/dist/components/Toolbar/InsertTable.d.ts +10 -0
- package/dist/components/Toolbar/InsertTable.js +1 -0
- package/dist/components/Toolbar/TextFormatToolbarButton.css +1 -0
- package/dist/components/Toolbar/TextFormatToolbarButton.css.css +1 -0
- package/dist/components/Toolbar/TextFormatToolbarButton.css.d.ts +5 -0
- package/dist/components/Toolbar/TextFormatToolbarButton.css.js +1 -0
- package/dist/components/Toolbar/TextFormatToolbarButton.d.ts +7 -0
- package/dist/components/Toolbar/TextFormatToolbarButton.js +1 -0
- package/dist/components/Toolbar/TextFormatTooltray.css +1 -0
- package/dist/components/Toolbar/TextFormatTooltray.css.css +1 -0
- package/dist/components/Toolbar/TextFormatTooltray.css.d.ts +8 -0
- package/dist/components/Toolbar/TextFormatTooltray.css.js +1 -0
- package/dist/components/Toolbar/TextFormatTooltray.d.ts +4 -0
- package/dist/components/Toolbar/TextFormatTooltray.js +1 -0
- package/dist/components/Toolbar/Toolbar.css +1 -0
- package/dist/components/Toolbar/Toolbar.d.ts +3 -0
- package/dist/components/Toolbar/Toolbar.js +1 -0
- package/dist/hooks/useEditHistory.d.ts +6 -0
- package/dist/hooks/useEditHistory.js +1 -0
- package/dist/index.css +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +1 -0
- package/dist/nodes/index.d.ts +8 -0
- package/dist/nodes/index.js +1 -0
- package/dist/plugins/FloatingToolbarPlugin.css +1 -0
- package/dist/plugins/FloatingToolbarPlugin.d.ts +2 -0
- package/dist/plugins/FloatingToolbarPlugin.js +1 -0
- package/dist/plugins/HorizontalRulePlugin.d.ts +1 -0
- package/dist/plugins/HorizontalRulePlugin.js +1 -0
- package/dist/plugins/MarkdownImagePlugin.d.ts +7 -0
- package/dist/plugins/MarkdownImagePlugin.js +1 -0
- package/dist/plugins/MarkdownLinkPlugin.css +1 -0
- package/dist/plugins/MarkdownLinkPlugin.d.ts +8 -0
- package/dist/plugins/MarkdownLinkPlugin.js +1 -0
- package/dist/plugins/PreviewPlugin.css +1 -0
- package/dist/plugins/PreviewPlugin.d.ts +5 -0
- package/dist/plugins/PreviewPlugin.js +1 -0
- package/dist/plugins/TableActionMenuPlugin.css +1 -0
- package/dist/plugins/TableActionMenuPlugin.css.css +1 -0
- package/dist/plugins/TableActionMenuPlugin.css.d.ts +66 -0
- package/dist/plugins/TableActionMenuPlugin.css.js +1 -0
- package/dist/plugins/TableActionMenuPlugin.d.ts +2 -0
- package/dist/plugins/TableActionMenuPlugin.js +1 -0
- package/dist/store/index.d.ts +38 -0
- package/dist/store/index.js +1 -0
- package/dist/theme/index.css +1 -0
- package/dist/theme/index.css.css +1 -0
- package/dist/theme/index.css.d.ts +15 -0
- package/dist/theme/index.css.js +1 -0
- package/dist/theme/index.d.ts +3 -0
- package/dist/theme/index.js +1 -0
- package/dist/transformers/horizontalRule.d.ts +2 -0
- package/dist/transformers/horizontalRule.js +1 -0
- package/dist/transformers/index.d.ts +2 -0
- package/dist/transformers/index.js +1 -0
- package/dist/transformers/link.d.ts +2 -0
- package/dist/transformers/link.js +1 -0
- package/dist/transformers/tableRule.d.ts +2 -0
- package/dist/transformers/tableRule.js +1 -0
- package/dist/utils/getSelectedNode.d.ts +2 -0
- package/dist/utils/getSelectedNode.js +1 -0
- package/package.json +56 -0
- package/src/api/save.ts +12 -0
- package/src/components/ActionMenu/ActionMenu.css.ts +9 -0
- package/src/components/ActionMenu/ActionMenu.tsx +26 -0
- package/src/components/ContentEditor.tsx +12 -0
- package/src/components/Editor.css.ts +64 -0
- package/src/components/Editor.tsx +145 -0
- package/src/components/EditorControls.css.ts +9 -0
- package/src/components/EditorControls.tsx +31 -0
- package/src/components/LinkEditor/EditLinkButton.tsx +9 -0
- package/src/components/LinkEditor/LinkEditor.css.ts +15 -0
- package/src/components/LinkEditor/LinkEditor.tsx +136 -0
- package/src/components/LinkEditor/SaveAdornment.tsx +16 -0
- package/src/components/PersistEditDialog/PersistStatus.css.ts +21 -0
- package/src/components/PersistEditDialog/PersistStatus.tsx +78 -0
- package/src/components/PersistEditDialog/__tests__/handlers.ts +9 -0
- package/src/components/PersistEditDialog/__tests__/index.test.tsx +93 -0
- package/src/components/PersistEditDialog/index.tsx +109 -0
- package/src/components/Popper/Popper.css.ts +12 -0
- package/src/components/Popper/Popper.tsx +31 -0
- package/src/components/SaveButton.tsx +23 -0
- package/src/components/ScrollableSection/ScrollableSection.css.ts +10 -0
- package/src/components/ScrollableSection/ScrollableSection.tsx +20 -0
- package/src/components/StatusBanner.tsx +16 -0
- package/src/components/Toolbar/InsertBlockDropdown.tsx +134 -0
- package/src/components/Toolbar/InsertHorizontalRule.tsx +18 -0
- package/src/components/Toolbar/InsertImage.css.ts +7 -0
- package/src/components/Toolbar/InsertImage.tsx +132 -0
- package/src/components/Toolbar/InsertLink.css.ts +7 -0
- package/src/components/Toolbar/InsertLink.tsx +145 -0
- package/src/components/Toolbar/InsertTable.css.ts +46 -0
- package/src/components/Toolbar/InsertTable.tsx +190 -0
- package/src/components/Toolbar/TextFormatToolbarButton.css.ts +10 -0
- package/src/components/Toolbar/TextFormatToolbarButton.tsx +29 -0
- package/src/components/Toolbar/TextFormatTooltray.css.ts +27 -0
- package/src/components/Toolbar/TextFormatTooltray.tsx +118 -0
- package/src/components/Toolbar/Toolbar.tsx +47 -0
- package/src/components/__tests__/EditorControls.test.tsx +69 -0
- package/src/components/__tests__/StatusBanner.test.tsx +33 -0
- package/src/hooks/useEditHistory.ts +49 -0
- package/src/index.ts +5 -0
- package/src/nodes/index.ts +23 -0
- package/src/plugins/FloatingToolbarPlugin.tsx +100 -0
- package/src/plugins/HorizontalRulePlugin.tsx +39 -0
- package/src/plugins/MarkdownImagePlugin.tsx +58 -0
- package/src/plugins/MarkdownLinkPlugin.tsx +69 -0
- package/src/plugins/PreviewPlugin.tsx +46 -0
- package/src/plugins/TableActionMenuPlugin.css.ts +29 -0
- package/src/plugins/TableActionMenuPlugin.tsx +274 -0
- package/src/store/__tests__/index.test.ts +171 -0
- package/src/store/index.ts +84 -0
- package/src/theme/index.css.ts +39 -0
- package/src/theme/index.ts +22 -0
- package/src/transformers/horizontalRule.ts +25 -0
- package/src/transformers/index.ts +6 -0
- package/src/transformers/link.ts +3 -0
- package/src/transformers/tableRule.ts +193 -0
- package/src/utils/getSelectedNode.ts +16 -0
- 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" /> Save
|
|
20
|
+
</Button>
|
|
21
|
+
</>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
@@ -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,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,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
|
+
};
|