@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.
- package/.turbo/turbo-build.log +4 -0
- package/README.md +7 -0
- package/dist/api/save.d.ts +5 -0
- package/dist/api/save.js +1 -0
- package/dist/chunk-3FJGHVH5.js +1 -0
- package/dist/chunk-3TDCKWYF.js +1 -0
- package/dist/chunk-57FSKQGH.js +1 -0
- package/dist/chunk-5GT4QAIQ.js +1 -0
- package/dist/chunk-6TM3EYFS.js +1 -0
- package/dist/chunk-76US6CXQ.js +1 -0
- package/dist/chunk-A6ZSQMSC.js +1 -0
- package/dist/chunk-ACFFLNAA.js +1 -0
- package/dist/chunk-BTI67NTQ.js +1 -0
- package/dist/chunk-FCCNBA44.js +1 -0
- package/dist/chunk-FGP7KEKC.js +1 -0
- package/dist/chunk-FI57IKHZ.js +1 -0
- package/dist/chunk-FM63P66J.js +1 -0
- package/dist/chunk-GR3B6XH6.js +1 -0
- package/dist/chunk-HL7D7RIZ.js +1 -0
- package/dist/chunk-HROTQ6LG.js +1 -0
- package/dist/chunk-I3Z6UENR.js +1 -0
- package/dist/chunk-IO4745QD.js +1 -0
- package/dist/chunk-IXKHPWRK.js +1 -0
- package/dist/chunk-JKQCT2R7.js +1 -0
- package/dist/chunk-JQUDHJIQ.js +1 -0
- package/dist/chunk-JVJ4H6LB.js +1 -0
- package/dist/chunk-JX5HLHOC.js +1 -0
- package/dist/chunk-KMK5XOLZ.js +1 -0
- package/dist/chunk-KYX7ZWMX.js +1 -0
- package/dist/chunk-LOZ4MGDL.js +1 -0
- package/dist/chunk-M5W2GPOK.js +1 -0
- package/dist/chunk-MXR5SEUG.js +1 -0
- package/dist/chunk-N3NO7L4H.js +1 -0
- package/dist/chunk-NXV7KCC5.js +1 -0
- package/dist/chunk-OFDMXZXG.js +1 -0
- package/dist/chunk-P3NF2XWE.js +1 -0
- package/dist/chunk-P5OBPOC4.js +0 -0
- package/dist/chunk-PPEVCQGG.js +1 -0
- package/dist/chunk-RHRDHZ4F.js +1 -0
- package/dist/chunk-RN54LVW4.js +1 -0
- package/dist/chunk-T2M7535Y.js +1 -0
- package/dist/chunk-UA5IM6NC.js +1 -0
- package/dist/chunk-UVZXP5EW.js +1 -0
- package/dist/chunk-UXLYVZ46.js +1 -0
- package/dist/chunk-V5Z3VNEW.js +1 -0
- package/dist/chunk-W44IYDHL.js +1 -0
- package/dist/chunk-WBUBW4PH.js +2 -0
- package/dist/chunk-WHPZZWKO.js +1 -0
- package/dist/chunk-WQSFQBP7.js +1 -0
- package/dist/chunk-WXWEGQ2Z.js +1 -0
- package/dist/chunk-XX4EWSFM.js +1 -0
- package/dist/chunk-YPBKG7ME.js +1 -0
- package/dist/chunk-ZCWG7UN7.js +1 -0
- package/dist/chunk-ZF6MK5QB.js +1 -0
- package/dist/chunk-ZK6NLAGG.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 +60 -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 +54 -0
- package/src/api/save.ts +12 -0
- package/src/components/ActionMenu/ActionMenu.css.ts +9 -0
- package/src/components/ActionMenu/ActionMenu.tsx +28 -0
- package/src/components/ContentEditor.tsx +12 -0
- package/src/components/Editor.css.ts +64 -0
- package/src/components/Editor.tsx +143 -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 +114 -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 +278 -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,28 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { MenuButton, MenuDescriptor } from '@jpmorganchase/uitk-lab';
|
|
3
|
+
import { Icon } from '@jpmorganchase/mosaic-components';
|
|
4
|
+
|
|
5
|
+
import type { CascadingMenuProps } from '@jpmorganchase/uitk-lab';
|
|
6
|
+
|
|
7
|
+
import styles from './ActionMenu.css';
|
|
8
|
+
|
|
9
|
+
export type ActionMenuSource = MenuDescriptor;
|
|
10
|
+
|
|
11
|
+
interface ActionMenuProps {
|
|
12
|
+
initialSource: CascadingMenuProps['initialSource'];
|
|
13
|
+
onItemClick: CascadingMenuProps['onItemClick'];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function ActionMenu({ initialSource, onItemClick }: ActionMenuProps) {
|
|
17
|
+
return (
|
|
18
|
+
<MenuButton
|
|
19
|
+
hideCaret
|
|
20
|
+
CascadingMenuProps={{
|
|
21
|
+
initialSource,
|
|
22
|
+
onItemClick
|
|
23
|
+
}}
|
|
24
|
+
>
|
|
25
|
+
<Icon name="menu" />
|
|
26
|
+
</MenuButton>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
|
|
3
|
+
import { ContentEditable } from '@lexical/react/LexicalContentEditable';
|
|
4
|
+
|
|
5
|
+
const ContentEditor = () => (
|
|
6
|
+
<RichTextPlugin
|
|
7
|
+
contentEditable={<ContentEditable />}
|
|
8
|
+
placeholder={<div>Enter some text...</div>}
|
|
9
|
+
/>
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
export default ContentEditor;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { globalStyle, style } from '@vanilla-extract/css';
|
|
2
|
+
import {
|
|
3
|
+
vars,
|
|
4
|
+
backgroundColor,
|
|
5
|
+
neutralBorder,
|
|
6
|
+
navigableBorder,
|
|
7
|
+
config
|
|
8
|
+
} from '@jpmorganchase/mosaic-theme';
|
|
9
|
+
|
|
10
|
+
globalStyle('div[contenteditable]', {
|
|
11
|
+
outline: 'none'
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export default {
|
|
15
|
+
root: style({
|
|
16
|
+
display: 'grid',
|
|
17
|
+
gridTemplateColumns: '1fr',
|
|
18
|
+
gridTemplateRows: 'auto 1fr',
|
|
19
|
+
gridTemplateAreas: '"toolbar" "editor" '
|
|
20
|
+
}),
|
|
21
|
+
toolbarContainer: style([
|
|
22
|
+
style({
|
|
23
|
+
gridArea: 'toolbar',
|
|
24
|
+
position: 'sticky',
|
|
25
|
+
top: `${config.appHeader.height}px`,
|
|
26
|
+
zIndex: 2
|
|
27
|
+
}),
|
|
28
|
+
neutralBorder({
|
|
29
|
+
variant: 'low',
|
|
30
|
+
borderWidth: 'thin'
|
|
31
|
+
})
|
|
32
|
+
]),
|
|
33
|
+
editorRoot: style({
|
|
34
|
+
display: 'flex',
|
|
35
|
+
marginTop: vars.space.vertical.x3,
|
|
36
|
+
flexWrap: 'wrap',
|
|
37
|
+
gridArea: 'editor'
|
|
38
|
+
}),
|
|
39
|
+
splitter: style({
|
|
40
|
+
display: 'flex',
|
|
41
|
+
flexDirection: 'row'
|
|
42
|
+
}),
|
|
43
|
+
gutter: style([
|
|
44
|
+
{
|
|
45
|
+
backgroundRepeat: 'no-repeat',
|
|
46
|
+
backgroundPosition: '50%',
|
|
47
|
+
backgroundImage:
|
|
48
|
+
'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==")',
|
|
49
|
+
cursor: 'col-resize'
|
|
50
|
+
},
|
|
51
|
+
backgroundColor({ variant: 'emphasis' })
|
|
52
|
+
]),
|
|
53
|
+
unfocused: neutralBorder({
|
|
54
|
+
variant: 'low',
|
|
55
|
+
borderTopWidth: 'thin',
|
|
56
|
+
borderLeftWidth: 'thin',
|
|
57
|
+
borderRightWidth: 'thin',
|
|
58
|
+
borderBottomWidth: 'none'
|
|
59
|
+
}),
|
|
60
|
+
focused: navigableBorder({
|
|
61
|
+
variant: 'focusRing',
|
|
62
|
+
borderWidth: 'medium'
|
|
63
|
+
})
|
|
64
|
+
};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import React, { ComponentType, FC, useRef, useState } from 'react';
|
|
2
|
+
import classnames from 'classnames';
|
|
3
|
+
import matter from 'gray-matter';
|
|
4
|
+
import { LexicalComposer } from '@lexical/react/LexicalComposer';
|
|
5
|
+
import { ListPlugin } from '@lexical/react/LexicalListPlugin';
|
|
6
|
+
import { TablePlugin } from '@lexical/react/LexicalTablePlugin';
|
|
7
|
+
import { LinkPlugin } from '@lexical/react/LexicalLinkPlugin';
|
|
8
|
+
import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin';
|
|
9
|
+
import { MarkdownShortcutPlugin } from '@lexical/react/LexicalMarkdownShortcutPlugin';
|
|
10
|
+
import Split from 'react-split';
|
|
11
|
+
import { $convertFromMarkdownString } from '@lexical/markdown';
|
|
12
|
+
|
|
13
|
+
import transformers from '../transformers';
|
|
14
|
+
import ContentEditor from './ContentEditor';
|
|
15
|
+
import { nodes } from '../nodes';
|
|
16
|
+
import { useEditorUser, usePreviewContent } from '../store';
|
|
17
|
+
import { PreviewPlugin } from '../plugins/PreviewPlugin';
|
|
18
|
+
import styles from './Editor.css';
|
|
19
|
+
import Toolbar from './Toolbar/Toolbar';
|
|
20
|
+
import theme from '../theme';
|
|
21
|
+
import { PersistDialog } from './PersistEditDialog';
|
|
22
|
+
import StatusBanner from './StatusBanner';
|
|
23
|
+
import { MarkdownImagePlugin } from '../plugins/MarkdownImagePlugin';
|
|
24
|
+
import { MarkdownLinkPlugin } from '../plugins/MarkdownLinkPlugin';
|
|
25
|
+
import { LinkEditor } from './LinkEditor/LinkEditor';
|
|
26
|
+
import { ScrollableSection } from './ScrollableSection/ScrollableSection';
|
|
27
|
+
import HorizontalRulePlugin from '../plugins/HorizontalRulePlugin';
|
|
28
|
+
import { FloatingToolbarPlugin } from '../plugins/FloatingToolbarPlugin';
|
|
29
|
+
import { TableActionMenuPlugin } from '../plugins/TableActionMenuPlugin';
|
|
30
|
+
import { InsertLinkDialog } from './Toolbar/InsertLink';
|
|
31
|
+
import type { PreviewPluginProps } from '../plugins/PreviewPlugin';
|
|
32
|
+
|
|
33
|
+
function onError(error: Error) {
|
|
34
|
+
console.error(error);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const initialConfig = {
|
|
38
|
+
namespace: 'mosaic-content-editor',
|
|
39
|
+
onError,
|
|
40
|
+
nodes,
|
|
41
|
+
theme
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
interface PreviewComponentProps {
|
|
45
|
+
source: any;
|
|
46
|
+
meta: any;
|
|
47
|
+
components: any;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface EditorProps extends PreviewComponentProps, PreviewPluginProps {
|
|
51
|
+
content: string;
|
|
52
|
+
PreviewComponent: ComponentType<PreviewComponentProps>;
|
|
53
|
+
user?: any;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const gutter = () => {
|
|
57
|
+
const gutter = document.createElement('div');
|
|
58
|
+
gutter.className = styles.gutter;
|
|
59
|
+
return gutter;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const Editor: FC<EditorProps> = ({
|
|
63
|
+
compileMDX,
|
|
64
|
+
content,
|
|
65
|
+
source,
|
|
66
|
+
PreviewComponent,
|
|
67
|
+
user,
|
|
68
|
+
components
|
|
69
|
+
}) => {
|
|
70
|
+
const previewContent = usePreviewContent() || source;
|
|
71
|
+
const { setUser } = useEditorUser();
|
|
72
|
+
const [focused, setFocused] = useState(false);
|
|
73
|
+
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
74
|
+
const { data: meta, content: markdown } = matter(content);
|
|
75
|
+
|
|
76
|
+
const handleEditorFocus = () => {
|
|
77
|
+
setFocused(true);
|
|
78
|
+
setUser(user);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const handleEditorBlur = () => {
|
|
82
|
+
setFocused(false);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<LexicalComposer
|
|
87
|
+
initialConfig={{
|
|
88
|
+
...initialConfig,
|
|
89
|
+
editorState: () => $convertFromMarkdownString(markdown, transformers)
|
|
90
|
+
}}
|
|
91
|
+
>
|
|
92
|
+
<div className={styles.root} onFocus={handleEditorFocus} onBlur={handleEditorBlur}>
|
|
93
|
+
<div className={styles.toolbarContainer}>
|
|
94
|
+
<Toolbar />
|
|
95
|
+
<PersistDialog meta={meta} />
|
|
96
|
+
<StatusBanner />
|
|
97
|
+
</div>
|
|
98
|
+
<div className={styles.editorRoot}>
|
|
99
|
+
<Split
|
|
100
|
+
className={styles.splitter}
|
|
101
|
+
cursor="col-resize"
|
|
102
|
+
direction="horizontal"
|
|
103
|
+
dragInterval={1}
|
|
104
|
+
expandToMin={false}
|
|
105
|
+
gutter={gutter}
|
|
106
|
+
gutterAlign="center"
|
|
107
|
+
minSize={100}
|
|
108
|
+
sizes={[50, 50]}
|
|
109
|
+
snapOffset={30}
|
|
110
|
+
>
|
|
111
|
+
<ScrollableSection
|
|
112
|
+
className={classnames({
|
|
113
|
+
[styles.focused]: focused,
|
|
114
|
+
[styles.unfocused]: !focused
|
|
115
|
+
})}
|
|
116
|
+
ref={containerRef}
|
|
117
|
+
>
|
|
118
|
+
<ContentEditor />
|
|
119
|
+
</ScrollableSection>
|
|
120
|
+
<ScrollableSection>
|
|
121
|
+
<PreviewComponent source={previewContent} meta={meta} components={components} />
|
|
122
|
+
</ScrollableSection>
|
|
123
|
+
</Split>
|
|
124
|
+
<HistoryPlugin />
|
|
125
|
+
<ListPlugin />
|
|
126
|
+
<TablePlugin />
|
|
127
|
+
<LinkPlugin />
|
|
128
|
+
<MarkdownShortcutPlugin transformers={transformers} />
|
|
129
|
+
<PreviewPlugin compileMDX={compileMDX} />
|
|
130
|
+
<MarkdownImagePlugin />
|
|
131
|
+
<MarkdownLinkPlugin />
|
|
132
|
+
<LinkEditor />
|
|
133
|
+
<HorizontalRulePlugin />
|
|
134
|
+
<FloatingToolbarPlugin />
|
|
135
|
+
<TableActionMenuPlugin />
|
|
136
|
+
<InsertLinkDialog />
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
</LexicalComposer>
|
|
140
|
+
);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export default Editor;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Icon } from '@jpmorganchase/mosaic-components';
|
|
3
|
+
import { Toolbar as ToolkitToolbar, Tooltray, ToolbarButton } from '@jpmorganchase/uitk-lab';
|
|
4
|
+
|
|
5
|
+
import { default as useContentEditor } from '../store';
|
|
6
|
+
import styles from './EditorControls.css';
|
|
7
|
+
|
|
8
|
+
export const EditorControls = ({ isLoggedIn = false }) => {
|
|
9
|
+
const { pageState, startEditing, stopEditing } = useContentEditor();
|
|
10
|
+
const isEditing = pageState === 'EDIT';
|
|
11
|
+
|
|
12
|
+
const handleClick = () => (!isEditing ? startEditing() : stopEditing());
|
|
13
|
+
|
|
14
|
+
const loggedInLabel = isEditing ? 'Cancel Editing' : 'Edit Page';
|
|
15
|
+
const overflowLabel = !isLoggedIn ? 'Login Required to Edit' : loggedInLabel;
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<ToolkitToolbar aria-label="editor-controls" className={styles.root}>
|
|
19
|
+
<Tooltray aria-label="page editor controls tooltray">
|
|
20
|
+
<ToolbarButton
|
|
21
|
+
aria-label={isEditing ? 'cancel editing' : 'start editing'}
|
|
22
|
+
onClick={handleClick}
|
|
23
|
+
disabled={!isLoggedIn}
|
|
24
|
+
label={overflowLabel}
|
|
25
|
+
>
|
|
26
|
+
<Icon name={isEditing ? 'delete' : 'edit'} />
|
|
27
|
+
</ToolbarButton>
|
|
28
|
+
</Tooltray>
|
|
29
|
+
</ToolkitToolbar>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Button, Icon } from '@jpmorganchase/mosaic-components';
|
|
3
|
+
import type { ButtonProps } from '@jpmorganchase/mosaic-components';
|
|
4
|
+
|
|
5
|
+
export const EditLinkButton = (props: ButtonProps) => (
|
|
6
|
+
<Button {...props}>
|
|
7
|
+
<Icon name="edit" />
|
|
8
|
+
</Button>
|
|
9
|
+
);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { style } from '@vanilla-extract/css';
|
|
2
|
+
import { vars, neutralBorder } from '@jpmorganchase/mosaic-theme';
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
popper: style([
|
|
6
|
+
style({
|
|
7
|
+
padding: vars.space.vertical.x1,
|
|
8
|
+
marginLeft: vars.space.horizontal.x2
|
|
9
|
+
}),
|
|
10
|
+
neutralBorder({
|
|
11
|
+
variant: 'low',
|
|
12
|
+
borderWidth: 'thin'
|
|
13
|
+
})
|
|
14
|
+
])
|
|
15
|
+
};
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { mergeRegister } from '@lexical/utils';
|
|
2
|
+
import {
|
|
3
|
+
$getSelection,
|
|
4
|
+
$isRangeSelection,
|
|
5
|
+
COMMAND_PRIORITY_LOW,
|
|
6
|
+
RangeSelection,
|
|
7
|
+
SELECTION_CHANGE_COMMAND
|
|
8
|
+
} from 'lexical';
|
|
9
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
10
|
+
import { $isLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link';
|
|
11
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
12
|
+
import { Input, useFloatingUI } from '@jpmorganchase/uitk-core';
|
|
13
|
+
import { useDismiss, useInteractions } from '@floating-ui/react-dom-interactions';
|
|
14
|
+
|
|
15
|
+
import styles from './LinkEditor.css';
|
|
16
|
+
import { SaveAdornment } from './SaveAdornment';
|
|
17
|
+
import { EditLinkButton } from './EditLinkButton';
|
|
18
|
+
import { Popper } from '../Popper/Popper';
|
|
19
|
+
import { getSelectedNode } from '../../utils/getSelectedNode';
|
|
20
|
+
|
|
21
|
+
export function LinkEditor() {
|
|
22
|
+
const [editor] = useLexicalComposerContext();
|
|
23
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
24
|
+
const [linkUrl, setLinkUrl] = useState('');
|
|
25
|
+
const [isEdit, setIsEdit] = useState(false);
|
|
26
|
+
const [lastSelection, setLastSelection] = useState<RangeSelection | null>(null);
|
|
27
|
+
|
|
28
|
+
const reset = () => {
|
|
29
|
+
setLinkUrl('');
|
|
30
|
+
setIsEdit(false);
|
|
31
|
+
setLastSelection(null);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const { context, floating, reference, strategy, x, y } = useFloatingUI({
|
|
35
|
+
placement: 'right',
|
|
36
|
+
strategy: 'absolute',
|
|
37
|
+
open: linkUrl.length > 0,
|
|
38
|
+
onOpenChange: reset
|
|
39
|
+
});
|
|
40
|
+
const { getFloatingProps } = useInteractions([
|
|
41
|
+
useDismiss(context, {
|
|
42
|
+
ancestorScroll: true
|
|
43
|
+
})
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
const updateLinkEditor = useCallback(() => {
|
|
47
|
+
const selection = $getSelection();
|
|
48
|
+
if ($isRangeSelection(selection)) {
|
|
49
|
+
const node = getSelectedNode(selection);
|
|
50
|
+
const parent = node.getParent();
|
|
51
|
+
if ($isLinkNode(parent)) {
|
|
52
|
+
reference(editor.getElementByKey(parent.getKey()));
|
|
53
|
+
setLinkUrl(parent.getURL());
|
|
54
|
+
setLastSelection(selection);
|
|
55
|
+
} else if ($isLinkNode(node)) {
|
|
56
|
+
reference(editor.getElementByKey(node.getKey()));
|
|
57
|
+
setLinkUrl(node.getURL());
|
|
58
|
+
setLastSelection(selection);
|
|
59
|
+
} else {
|
|
60
|
+
// no links to edit on this selection
|
|
61
|
+
reset();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
setIsEdit(false);
|
|
65
|
+
return true;
|
|
66
|
+
}, [editor, reference]);
|
|
67
|
+
|
|
68
|
+
useEffect(
|
|
69
|
+
() =>
|
|
70
|
+
mergeRegister(
|
|
71
|
+
editor.registerUpdateListener(({ editorState }) => {
|
|
72
|
+
editorState.read(() => {
|
|
73
|
+
updateLinkEditor();
|
|
74
|
+
});
|
|
75
|
+
}),
|
|
76
|
+
|
|
77
|
+
editor.registerCommand(
|
|
78
|
+
SELECTION_CHANGE_COMMAND,
|
|
79
|
+
() => {
|
|
80
|
+
updateLinkEditor();
|
|
81
|
+
return true;
|
|
82
|
+
},
|
|
83
|
+
COMMAND_PRIORITY_LOW
|
|
84
|
+
)
|
|
85
|
+
),
|
|
86
|
+
[editor, updateLinkEditor]
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
editor.getEditorState().read(() => {
|
|
91
|
+
updateLinkEditor();
|
|
92
|
+
});
|
|
93
|
+
}, [editor, updateLinkEditor]);
|
|
94
|
+
|
|
95
|
+
const handleSave = () => {
|
|
96
|
+
if (lastSelection !== null) {
|
|
97
|
+
if (linkUrl !== '') {
|
|
98
|
+
editor.dispatchCommand(TOGGLE_LINK_COMMAND, linkUrl);
|
|
99
|
+
setIsEdit(false);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const handleEdit = () => setIsEdit(true);
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<Popper
|
|
108
|
+
className={styles.popper}
|
|
109
|
+
ref={floating}
|
|
110
|
+
open={linkUrl.length > 0}
|
|
111
|
+
style={{ top: y ?? '', left: x ?? '', position: strategy }}
|
|
112
|
+
{...getFloatingProps({})}
|
|
113
|
+
>
|
|
114
|
+
{!isEdit ? (
|
|
115
|
+
<EditLinkButton onClick={handleEdit} />
|
|
116
|
+
) : (
|
|
117
|
+
<Input
|
|
118
|
+
endAdornment={<SaveAdornment onSave={handleSave} />}
|
|
119
|
+
ref={inputRef}
|
|
120
|
+
value={linkUrl}
|
|
121
|
+
onChange={event => {
|
|
122
|
+
setLinkUrl(event.target.value);
|
|
123
|
+
}}
|
|
124
|
+
onKeyDown={event => {
|
|
125
|
+
if (event.key === 'Enter') {
|
|
126
|
+
event.preventDefault();
|
|
127
|
+
handleSave();
|
|
128
|
+
} else if (event.key === 'Escape') {
|
|
129
|
+
event.preventDefault();
|
|
130
|
+
}
|
|
131
|
+
}}
|
|
132
|
+
/>
|
|
133
|
+
)}
|
|
134
|
+
</Popper>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Button, Icon } from '@jpmorganchase/mosaic-components';
|
|
3
|
+
import type { ButtonProps } from '@jpmorganchase/mosaic-components';
|
|
4
|
+
import { StaticInputAdornment } from '@jpmorganchase/uitk-core';
|
|
5
|
+
|
|
6
|
+
interface SaveAdornmentProps {
|
|
7
|
+
onSave: ButtonProps['onClick'];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const SaveAdornment = ({ onSave }: SaveAdornmentProps) => (
|
|
11
|
+
<StaticInputAdornment>
|
|
12
|
+
<Button onClick={onSave}>
|
|
13
|
+
<Icon name="save" />
|
|
14
|
+
</Button>
|
|
15
|
+
</StaticInputAdornment>
|
|
16
|
+
);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { style } from '@vanilla-extract/css';
|
|
2
|
+
import { vars } from '@jpmorganchase/mosaic-theme';
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
step: style({
|
|
6
|
+
padding: vars.space.vertical.x2,
|
|
7
|
+
opacity: vars.opacity.light.disabled
|
|
8
|
+
}),
|
|
9
|
+
stepActive: style({
|
|
10
|
+
fontSize: vars.fontSize.s80,
|
|
11
|
+
fontStyle: vars.fontWeight.semibold,
|
|
12
|
+
opacity: 1
|
|
13
|
+
}),
|
|
14
|
+
stepComplete: style({
|
|
15
|
+
opacity: 1
|
|
16
|
+
}),
|
|
17
|
+
stepText: style({
|
|
18
|
+
marginLeft: vars.space.horizontal.x2,
|
|
19
|
+
marginRight: vars.space.horizontal.x2
|
|
20
|
+
})
|
|
21
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import React, { FC, useEffect, useState } from 'react';
|
|
2
|
+
import classnames from 'classnames';
|
|
3
|
+
import { P2 } from '@jpmorganchase/mosaic-components';
|
|
4
|
+
|
|
5
|
+
import styles from './PersistStatus.css';
|
|
6
|
+
|
|
7
|
+
const steps = ['Making a copy...', 'Applying changes...', 'Creating a Pull Request...'];
|
|
8
|
+
const lastStep = steps.length - 1;
|
|
9
|
+
|
|
10
|
+
interface StepProps {
|
|
11
|
+
isComplete: boolean;
|
|
12
|
+
isCurrent: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const Step: FC<StepProps> = ({ children, isComplete, isCurrent }) => (
|
|
16
|
+
<div
|
|
17
|
+
className={classnames(styles.step, {
|
|
18
|
+
[styles.stepComplete]: isComplete,
|
|
19
|
+
[styles.stepActive]: isCurrent
|
|
20
|
+
})}
|
|
21
|
+
>
|
|
22
|
+
<span role="img" aria-label="emoji with party hat">
|
|
23
|
+
{isComplete ? '🥳' : null}
|
|
24
|
+
</span>
|
|
25
|
+
<span role="img" aria-label="person grimacing emoji ">
|
|
26
|
+
{isCurrent ? '😃' : null}
|
|
27
|
+
</span>
|
|
28
|
+
<span role="img" aria-label="sleepy emoji">
|
|
29
|
+
{!isCurrent && !isComplete ? '💤' : null}
|
|
30
|
+
</span>
|
|
31
|
+
<span className={styles.stepText}>{children}</span>
|
|
32
|
+
<span role="img" aria-label="hourglass">
|
|
33
|
+
{isCurrent ? '⌛' : null}
|
|
34
|
+
</span>
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
interface StatusProps {
|
|
39
|
+
isRaising: boolean;
|
|
40
|
+
error: string | null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const PersistStatus = ({ isRaising, error }: StatusProps) => {
|
|
44
|
+
const [currentStep, setCurrentStep] = useState(0);
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
const stepInterval = setInterval(() => {
|
|
48
|
+
if (currentStep === steps.length - 1) {
|
|
49
|
+
clearInterval(stepInterval);
|
|
50
|
+
} else {
|
|
51
|
+
setCurrentStep(prevState => prevState + 1);
|
|
52
|
+
}
|
|
53
|
+
}, 2000);
|
|
54
|
+
return function cleanUpStepInterval() {
|
|
55
|
+
clearInterval(stepInterval);
|
|
56
|
+
};
|
|
57
|
+
}, [currentStep]);
|
|
58
|
+
|
|
59
|
+
if (error) {
|
|
60
|
+
return <P2>😢 Sorry something has went wrong. Please try again later.</P2>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<>
|
|
65
|
+
{steps.map((step, index) => (
|
|
66
|
+
<Step
|
|
67
|
+
key={`step -${index}`}
|
|
68
|
+
isComplete={
|
|
69
|
+
currentStep === lastStep && index === lastStep ? !isRaising : currentStep > index
|
|
70
|
+
}
|
|
71
|
+
isCurrent={currentStep === index}
|
|
72
|
+
>
|
|
73
|
+
{step}
|
|
74
|
+
</Step>
|
|
75
|
+
))}
|
|
76
|
+
</>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { rest } from 'msw';
|
|
2
|
+
|
|
3
|
+
export const postSaveContentSuccessHandler = rest.post('/api/content/save', (req, res, ctx) =>
|
|
4
|
+
res(ctx.status(500), ctx.json({ links: { self: [{ href: 'http://pull-request-url' }] } }))
|
|
5
|
+
);
|
|
6
|
+
|
|
7
|
+
export const postSaveContentFailureHandler = rest.post('/api/content/save', (req, res, ctx) =>
|
|
8
|
+
res(ctx.status(500))
|
|
9
|
+
);
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import { setupServer } from 'msw/node';
|
|
5
|
+
import { LexicalComposer } from '@lexical/react/LexicalComposer';
|
|
6
|
+
|
|
7
|
+
import { PersistDialog } from '../index';
|
|
8
|
+
import { usePageState } from '../../../store';
|
|
9
|
+
import { nodes } from '../../../nodes';
|
|
10
|
+
import { postSaveContentSuccessHandler } from './handlers';
|
|
11
|
+
|
|
12
|
+
jest.mock('../../../store', () => ({
|
|
13
|
+
...jest.requireActual('../../../store'),
|
|
14
|
+
__esModule: true,
|
|
15
|
+
usePageState: jest.fn()
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
const setPageStateSpy = jest.fn();
|
|
19
|
+
const setErrorMessageSpy = jest.fn();
|
|
20
|
+
const mockMeta = { sourceId: 'source-1', route: 'route' };
|
|
21
|
+
|
|
22
|
+
jest.mock('@lexical/markdown', () => ({
|
|
23
|
+
...jest.requireActual('@lexical/markdown'),
|
|
24
|
+
$convertToMarkdownString: jest.fn().mockReturnValue('## Test Markdown')
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
const server = setupServer();
|
|
28
|
+
|
|
29
|
+
const renderPersistDialog = (props = { meta: mockMeta }) => {
|
|
30
|
+
render(
|
|
31
|
+
<LexicalComposer
|
|
32
|
+
initialConfig={{ namespace: 'persist-dialog-test', nodes, onError: jest.fn() }}
|
|
33
|
+
>
|
|
34
|
+
<PersistDialog {...props} />
|
|
35
|
+
</LexicalComposer>
|
|
36
|
+
);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
describe('GIVEN a PersistEditDialog', () => {
|
|
40
|
+
beforeAll(() => {
|
|
41
|
+
server.listen({ onUnhandledRequest: 'error' });
|
|
42
|
+
|
|
43
|
+
jest.mocked(usePageState).mockReturnValue({
|
|
44
|
+
pageState: 'SAVING',
|
|
45
|
+
setErrorMessage: setErrorMessageSpy,
|
|
46
|
+
setPageState: setPageStateSpy,
|
|
47
|
+
errorMessage: undefined
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
afterAll(() => {
|
|
52
|
+
server.close();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('THEN the dialog is open when SAVING', () => {
|
|
56
|
+
// arrange
|
|
57
|
+
renderPersistDialog();
|
|
58
|
+
// assert
|
|
59
|
+
expect(
|
|
60
|
+
screen.getByText(
|
|
61
|
+
'The content of this page resides in a Git repository and to update it requires a Pull Request which will be reviewed by the content owners.'
|
|
62
|
+
)
|
|
63
|
+
).toBeDefined();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('THEN cancelling returns the page to edit mode', async () => {
|
|
67
|
+
// arrange
|
|
68
|
+
renderPersistDialog();
|
|
69
|
+
// act
|
|
70
|
+
const cancelButton = screen.getByText('Cancel');
|
|
71
|
+
await userEvent.click(cancelButton);
|
|
72
|
+
// assert
|
|
73
|
+
expect(setPageStateSpy).toBeCalledTimes(1);
|
|
74
|
+
expect(setPageStateSpy.mock.calls[0][0]).toEqual('EDIT');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe.skip('AND WHEN we can create a Pull Request', () => {
|
|
78
|
+
beforeAll(() => {
|
|
79
|
+
server.use(postSaveContentSuccessHandler);
|
|
80
|
+
});
|
|
81
|
+
test('THEN raising a PR provides a link to the PR', async () => {
|
|
82
|
+
// arrange
|
|
83
|
+
renderPersistDialog();
|
|
84
|
+
// act
|
|
85
|
+
const raisePrButton = screen.getByText('Raise Pull Request');
|
|
86
|
+
userEvent.click(raisePrButton);
|
|
87
|
+
// assert
|
|
88
|
+
await waitFor(() => {
|
|
89
|
+
expect(screen.getByText('A Pull Request for your changes has been created.'));
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
});
|