@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,171 @@
|
|
|
1
|
+
import { act, renderHook } from '@testing-library/react-hooks';
|
|
2
|
+
|
|
3
|
+
import useStore, {
|
|
4
|
+
ContentEditorState,
|
|
5
|
+
useEditorUser,
|
|
6
|
+
useIsInsertingLink,
|
|
7
|
+
usePageState,
|
|
8
|
+
usePreviewContent,
|
|
9
|
+
useSetContent
|
|
10
|
+
} from '../index';
|
|
11
|
+
|
|
12
|
+
describe('GIVEN the Content Editor store', () => {
|
|
13
|
+
describe('WHEN initialized with the default state', () => {
|
|
14
|
+
let currentState: ContentEditorState;
|
|
15
|
+
beforeAll(() => {
|
|
16
|
+
const { result } = renderHook(() => useStore());
|
|
17
|
+
currentState = result.current;
|
|
18
|
+
});
|
|
19
|
+
test('THEN the PageState is VIEW', () => {
|
|
20
|
+
expect(currentState.pageState).toEqual('VIEW');
|
|
21
|
+
});
|
|
22
|
+
test('THEN the user is undefined', () => {
|
|
23
|
+
expect(currentState.user).toEqual(undefined);
|
|
24
|
+
});
|
|
25
|
+
test('THEN the previewContent is undefined', () => {
|
|
26
|
+
expect(currentState.previewContent).toEqual(undefined);
|
|
27
|
+
});
|
|
28
|
+
test('THEN the error message is undefined', () => {
|
|
29
|
+
expect(currentState.errorMessage).toEqual(undefined);
|
|
30
|
+
});
|
|
31
|
+
test('THEN isInsertlingLink is false', () => {
|
|
32
|
+
expect(currentState.isInsertingLink).toEqual(false);
|
|
33
|
+
});
|
|
34
|
+
test('THEN there is a setter for pageState', () => {
|
|
35
|
+
expect(currentState.setPageState).toBeDefined();
|
|
36
|
+
});
|
|
37
|
+
test('THEN there is a setter for errorMessage', () => {
|
|
38
|
+
expect(currentState.setErrorMessage).toBeDefined();
|
|
39
|
+
});
|
|
40
|
+
test('THEN there is a setter for user', () => {
|
|
41
|
+
expect(currentState.setUser).toBeDefined();
|
|
42
|
+
});
|
|
43
|
+
test('THEN there is a setter for previewContent', () => {
|
|
44
|
+
expect(currentState.setPreviewContent).toBeDefined();
|
|
45
|
+
});
|
|
46
|
+
test('THEN you can start editing', () => {
|
|
47
|
+
expect(currentState.startEditing).toBeDefined();
|
|
48
|
+
});
|
|
49
|
+
test('THEN you can stop editing', () => {
|
|
50
|
+
expect(currentState.stopEditing).toBeDefined();
|
|
51
|
+
});
|
|
52
|
+
test('THEN there is a setter for isInsertingLink', () => {
|
|
53
|
+
expect(currentState.setIsInsertingLink).toBeDefined();
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('AND WHEN setPageState is called', () => {
|
|
58
|
+
test('THEN the pageState is updated', async () => {
|
|
59
|
+
const { result } = renderHook(() => useStore());
|
|
60
|
+
act(() => result.current.setPageState('EDIT'));
|
|
61
|
+
expect(result.current.pageState).toEqual('EDIT');
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe('AND WHEN startEditing is called ', () => {
|
|
66
|
+
test('THEN editing is started', async () => {
|
|
67
|
+
const { result } = renderHook(() => useStore());
|
|
68
|
+
act(() => result.current.startEditing());
|
|
69
|
+
expect(result.current.pageState).toEqual('EDIT');
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('AND WHEN stopEditing is called', () => {
|
|
74
|
+
test('THEN editing is stopped', () => {
|
|
75
|
+
const { result } = renderHook(() => useStore());
|
|
76
|
+
act(() => result.current.stopEditing());
|
|
77
|
+
expect(result.current.pageState).toEqual('VIEW');
|
|
78
|
+
expect(result.current.previewContent).toBeUndefined();
|
|
79
|
+
expect(result.current.errorMessage).toBeUndefined();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('AND WHEN setPreviewContent is called', () => {
|
|
84
|
+
test('THEN the previewContent is updated', async () => {
|
|
85
|
+
const { result } = renderHook(() => useStore());
|
|
86
|
+
act(() => result.current.setPreviewContent('some markdown content'));
|
|
87
|
+
expect(result.current.previewContent).toEqual('some markdown content');
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('AND WHEN setErrorMessage is called', () => {
|
|
92
|
+
test('THEN the errorMessage is updated', async () => {
|
|
93
|
+
const { result } = renderHook(() => useStore());
|
|
94
|
+
act(() => result.current.setErrorMessage('an error'));
|
|
95
|
+
expect(result.current.errorMessage).toEqual('an error');
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe('AND WHEN setUser is called', () => {
|
|
100
|
+
test('THEN the user is updated', async () => {
|
|
101
|
+
const user = { sid: 'S12345', displayName: 'name', email: 'email@domain.com' };
|
|
102
|
+
const { result } = renderHook(() => useStore());
|
|
103
|
+
act(() => result.current.setUser(user));
|
|
104
|
+
expect(result.current.user).toEqual(user);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('GIVEN the usePreviewContent hook', () => {
|
|
109
|
+
test('WHEN called provides the previewContent', async () => {
|
|
110
|
+
const { setPreviewContent } = useStore.getState();
|
|
111
|
+
const { result } = renderHook(() => usePreviewContent());
|
|
112
|
+
|
|
113
|
+
expect(result.current).toBeUndefined();
|
|
114
|
+
await act(() => setPreviewContent('some content'));
|
|
115
|
+
expect(result.current).toEqual('some content');
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('GIVEN the useSetContent hook', () => {
|
|
120
|
+
test('WHEN called updates the previewContent', () => {
|
|
121
|
+
const { result } = renderHook(() => useSetContent());
|
|
122
|
+
expect(useStore.getState().previewContent).toBeUndefined();
|
|
123
|
+
|
|
124
|
+
act(() => result.current('some content'));
|
|
125
|
+
expect(useStore.getState().previewContent).toEqual('some content');
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe('GIVEN the usePageState hook', () => {
|
|
130
|
+
test('WHEN called provides the page state and an updater', () => {
|
|
131
|
+
const { result } = renderHook(() => usePageState());
|
|
132
|
+
expect(result.current.pageState).toEqual('VIEW');
|
|
133
|
+
expect(result.current.setPageState).toBeDefined();
|
|
134
|
+
});
|
|
135
|
+
test('WHEN called provides the error message and an updater', () => {
|
|
136
|
+
const { result } = renderHook(() => usePageState());
|
|
137
|
+
expect(result.current.errorMessage).toBeUndefined();
|
|
138
|
+
act(() => result.current.setErrorMessage('an error'));
|
|
139
|
+
expect(result.current.errorMessage).toEqual('an error');
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe('GIVEN the useEditorUser hook', () => {
|
|
144
|
+
test('WHEN called provides the user and an updater', () => {
|
|
145
|
+
const { result } = renderHook(() => useEditorUser());
|
|
146
|
+
expect(result.current.user).toEqual(undefined);
|
|
147
|
+
expect(result.current.setUser).toBeDefined();
|
|
148
|
+
});
|
|
149
|
+
test('AND when updater is called, user is updated', () => {
|
|
150
|
+
const user = { sid: 'S12345', displayName: 'name', email: 'email@domain.com' };
|
|
151
|
+
const { result } = renderHook(() => useEditorUser());
|
|
152
|
+
expect(result.current.user).toEqual(undefined);
|
|
153
|
+
act(() => result.current.setUser(user));
|
|
154
|
+
expect(result.current.user).toEqual(user);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe('GIVEN the useIsInsertingLink hook', () => {
|
|
159
|
+
test('WHEN called provides the state and an updater', () => {
|
|
160
|
+
const { result } = renderHook(() => useIsInsertingLink());
|
|
161
|
+
expect(result.current.isInsertingLink).toEqual(false);
|
|
162
|
+
expect(result.current.setIsInsertingLink).toBeDefined();
|
|
163
|
+
});
|
|
164
|
+
test('AND when updater is called, isInsertingLink is updated', () => {
|
|
165
|
+
const { result } = renderHook(() => useIsInsertingLink());
|
|
166
|
+
expect(result.current.isInsertingLink).toEqual(false);
|
|
167
|
+
act(() => result.current.setIsInsertingLink(true));
|
|
168
|
+
expect(result.current.isInsertingLink).toEqual(true);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import create from 'zustand';
|
|
3
|
+
|
|
4
|
+
type PageState = 'VIEW' | 'EDIT' | 'REVIEW' | 'SAVING' | 'ERROR';
|
|
5
|
+
type UserType = { sid: string; displayName: string; email: string };
|
|
6
|
+
|
|
7
|
+
export interface ContentEditorState {
|
|
8
|
+
pageState: PageState;
|
|
9
|
+
user?: UserType;
|
|
10
|
+
previewContent?: any;
|
|
11
|
+
errorMessage?: string;
|
|
12
|
+
isInsertingLink: boolean;
|
|
13
|
+
setPageState: (state: PageState) => void;
|
|
14
|
+
startEditing: () => void;
|
|
15
|
+
stopEditing: () => void;
|
|
16
|
+
setPreviewContent: (content: any) => void;
|
|
17
|
+
setErrorMessage: (errorMessage: string) => void;
|
|
18
|
+
setUser: (user: UserType) => void;
|
|
19
|
+
setIsInsertingLink: (isInsertingLink: boolean) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const initialState: Pick<
|
|
23
|
+
ContentEditorState,
|
|
24
|
+
'pageState' | 'user' | 'previewContent' | 'errorMessage' | 'isInsertingLink'
|
|
25
|
+
> = {
|
|
26
|
+
pageState: 'VIEW',
|
|
27
|
+
user: undefined,
|
|
28
|
+
previewContent: undefined,
|
|
29
|
+
errorMessage: undefined,
|
|
30
|
+
isInsertingLink: false
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const useStore = create<ContentEditorState>(set => ({
|
|
34
|
+
...initialState,
|
|
35
|
+
setPageState: state => set({ pageState: state }),
|
|
36
|
+
startEditing: () => set({ ...initialState, pageState: 'EDIT' }),
|
|
37
|
+
stopEditing: () => set({ pageState: initialState.pageState }),
|
|
38
|
+
setPreviewContent: (content: string) =>
|
|
39
|
+
set({ pageState: 'EDIT', previewContent: content, errorMessage: undefined }),
|
|
40
|
+
setErrorMessage: (errorMessage: string) => set({ errorMessage, pageState: 'ERROR' }),
|
|
41
|
+
setUser: (user: any) => set({ user }),
|
|
42
|
+
setIsInsertingLink: (isInsertingLink: boolean) => set({ isInsertingLink })
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
export const usePreviewContent = () => useStore(useCallback(state => state.previewContent, []));
|
|
46
|
+
|
|
47
|
+
export const useSetContent = () => useStore(useCallback(state => state.setPreviewContent, []));
|
|
48
|
+
|
|
49
|
+
export const usePageState = () =>
|
|
50
|
+
useStore(
|
|
51
|
+
useCallback(
|
|
52
|
+
state => ({
|
|
53
|
+
pageState: state.pageState,
|
|
54
|
+
setPageState: state.setPageState,
|
|
55
|
+
errorMessage: state.errorMessage,
|
|
56
|
+
setErrorMessage: state.setErrorMessage
|
|
57
|
+
}),
|
|
58
|
+
[]
|
|
59
|
+
)
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
export const useEditorUser = () =>
|
|
63
|
+
useStore(
|
|
64
|
+
useCallback(
|
|
65
|
+
state => ({
|
|
66
|
+
user: state.user,
|
|
67
|
+
setUser: state.setUser
|
|
68
|
+
}),
|
|
69
|
+
[]
|
|
70
|
+
)
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
export const useIsInsertingLink = () =>
|
|
74
|
+
useStore(
|
|
75
|
+
useCallback(
|
|
76
|
+
state => ({
|
|
77
|
+
isInsertingLink: state.isInsertingLink,
|
|
78
|
+
setIsInsertingLink: state.setIsInsertingLink
|
|
79
|
+
}),
|
|
80
|
+
[]
|
|
81
|
+
)
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
export default useStore;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { style } from '@vanilla-extract/css';
|
|
2
|
+
import {
|
|
3
|
+
vars,
|
|
4
|
+
link,
|
|
5
|
+
blockquote,
|
|
6
|
+
table,
|
|
7
|
+
td,
|
|
8
|
+
th,
|
|
9
|
+
tr,
|
|
10
|
+
neutralBorder,
|
|
11
|
+
paragraph
|
|
12
|
+
} from '@jpmorganchase/mosaic-theme';
|
|
13
|
+
|
|
14
|
+
export default {
|
|
15
|
+
bold: style({ fontWeight: vars.fontWeight.bold }),
|
|
16
|
+
italic: style({ fontStyle: 'italic' }),
|
|
17
|
+
underline: style({ textDecoration: 'underline' }),
|
|
18
|
+
strikeThrough: style({ textDecoration: 'line-through' }),
|
|
19
|
+
underlineStrikeThrough: style({ textDecoration: 'underline line-through' }),
|
|
20
|
+
link: style([link({ variant: 'document' })]),
|
|
21
|
+
paragraph: style([paragraph({ variant: 'paragraph2' })]),
|
|
22
|
+
quote: style([blockquote({ variant: 'regular', context: 'component' })]),
|
|
23
|
+
table: style([table()]),
|
|
24
|
+
tableCell: style([
|
|
25
|
+
td(),
|
|
26
|
+
neutralBorder({ variant: 'low', borderWidth: 'thin' }),
|
|
27
|
+
style({ fontWeight: 'inherit' })
|
|
28
|
+
]),
|
|
29
|
+
tableHeader: style([th()]),
|
|
30
|
+
tableRow: style([
|
|
31
|
+
tr(),
|
|
32
|
+
neutralBorder({ variant: 'low', borderWidth: 'thin' }),
|
|
33
|
+
style({
|
|
34
|
+
selectors: {
|
|
35
|
+
'&:first-of-type': { fontWeight: vars.fontWeight.bold }
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
])
|
|
39
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { EditorThemeClasses } from 'lexical';
|
|
2
|
+
|
|
3
|
+
import styles from './index.css';
|
|
4
|
+
|
|
5
|
+
const theme: EditorThemeClasses = {
|
|
6
|
+
paragraph: styles.paragraph,
|
|
7
|
+
quote: styles.quote,
|
|
8
|
+
link: styles.link,
|
|
9
|
+
table: styles.table,
|
|
10
|
+
tableCell: styles.tableCell,
|
|
11
|
+
tableCellHeader: styles.tableHeader,
|
|
12
|
+
tableRow: styles.tableRow,
|
|
13
|
+
text: {
|
|
14
|
+
bold: styles.bold,
|
|
15
|
+
italic: styles.italic,
|
|
16
|
+
strikethrough: styles.strikeThrough,
|
|
17
|
+
underline: styles.underline,
|
|
18
|
+
underlineStrikethrough: styles.underlineStrikeThrough
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default theme;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ElementTransformer } from '@lexical/markdown';
|
|
2
|
+
import {
|
|
3
|
+
$createHorizontalRuleNode,
|
|
4
|
+
$isHorizontalRuleNode,
|
|
5
|
+
HorizontalRuleNode
|
|
6
|
+
} from '@lexical/react/LexicalHorizontalRuleNode';
|
|
7
|
+
import { LexicalNode } from 'lexical';
|
|
8
|
+
|
|
9
|
+
export const HORIZONTAL_RULE: ElementTransformer = {
|
|
10
|
+
dependencies: [HorizontalRuleNode],
|
|
11
|
+
export: (node: LexicalNode) => ($isHorizontalRuleNode(node) ? '---' : null),
|
|
12
|
+
regExp: /^(---|\*\*\*|___)\s?$/,
|
|
13
|
+
replace: parentNode => {
|
|
14
|
+
const line = $createHorizontalRuleNode();
|
|
15
|
+
|
|
16
|
+
if (parentNode.getNextSibling() != null) {
|
|
17
|
+
parentNode.replace(line);
|
|
18
|
+
} else {
|
|
19
|
+
parentNode.insertBefore(line);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
line.selectNext();
|
|
23
|
+
},
|
|
24
|
+
type: 'element'
|
|
25
|
+
};
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { ElementTransformer } from '@lexical/markdown';
|
|
2
|
+
import type { ElementNode, LexicalNode } from 'lexical';
|
|
3
|
+
import {
|
|
4
|
+
$createTableCellNode,
|
|
5
|
+
$createTableNode,
|
|
6
|
+
$createTableRowNode,
|
|
7
|
+
$isTableNode,
|
|
8
|
+
$isTableRowNode,
|
|
9
|
+
TableCellHeaderStates,
|
|
10
|
+
TableCellNode,
|
|
11
|
+
TableNode,
|
|
12
|
+
TableRowNode
|
|
13
|
+
} from '@lexical/table';
|
|
14
|
+
import {
|
|
15
|
+
$createParagraphNode,
|
|
16
|
+
$createTextNode,
|
|
17
|
+
$isElementNode,
|
|
18
|
+
$isParagraphNode,
|
|
19
|
+
$isTextNode
|
|
20
|
+
} from 'lexical';
|
|
21
|
+
|
|
22
|
+
import LINK_TRANSFORMER from './link';
|
|
23
|
+
|
|
24
|
+
const TABLE_ROW_REG_EXP = /^(?:\|)(.+)(?:\|)\s?$/;
|
|
25
|
+
const TABLE_HEADER_REG_EXP = /(---)/;
|
|
26
|
+
|
|
27
|
+
export const TABLE_RULE: ElementTransformer = {
|
|
28
|
+
dependencies: [TableNode, TableRowNode, TableCellNode],
|
|
29
|
+
export: (node: LexicalNode, exportChildren: (elementNode: ElementNode) => string) => {
|
|
30
|
+
if (!$isTableNode(node)) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const output: string[] = [];
|
|
35
|
+
let columnCount: number | null = null;
|
|
36
|
+
|
|
37
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
38
|
+
for (const row of node.getChildren()) {
|
|
39
|
+
const rowOutput: string[] = [];
|
|
40
|
+
|
|
41
|
+
if ($isTableRowNode(row)) {
|
|
42
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
43
|
+
for (const cell of row.getChildren()) {
|
|
44
|
+
if ($isElementNode(cell)) {
|
|
45
|
+
rowOutput.push(exportChildren(cell));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (columnCount === null) {
|
|
49
|
+
columnCount = rowOutput.length;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
output.push(`| ${rowOutput.join(' | ')} |`);
|
|
53
|
+
}
|
|
54
|
+
addHeaderDividerToOutput(output, columnCount);
|
|
55
|
+
return output.join('\n');
|
|
56
|
+
},
|
|
57
|
+
regExp: TABLE_ROW_REG_EXP,
|
|
58
|
+
replace: (parentNode, _1, match) => {
|
|
59
|
+
const matchCells = mapToTableCells(match[0]);
|
|
60
|
+
|
|
61
|
+
if (matchCells == null) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const rows = handleHeaderDashes(matchCells);
|
|
66
|
+
let sibling = parentNode.getPreviousSibling();
|
|
67
|
+
let maxCells = matchCells.length;
|
|
68
|
+
|
|
69
|
+
while (sibling) {
|
|
70
|
+
if (!$isParagraphNode(sibling)) {
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (sibling.getChildrenSize() !== 1) {
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const firstChild = sibling.getFirstChild();
|
|
79
|
+
|
|
80
|
+
if (!$isTextNode(firstChild)) {
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const cells = mapToTableCells(firstChild.getTextContent());
|
|
85
|
+
|
|
86
|
+
if (cells == null) {
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
maxCells = Math.max(maxCells, cells.length);
|
|
91
|
+
rows.unshift(cells);
|
|
92
|
+
const previousSibling = sibling.getPreviousSibling();
|
|
93
|
+
sibling.remove();
|
|
94
|
+
sibling = previousSibling;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const table = createTableNode(rows, maxCells);
|
|
98
|
+
|
|
99
|
+
const previousSibling = parentNode.getPreviousSibling();
|
|
100
|
+
if ($isTableNode(previousSibling) && getTableColumnsSize(previousSibling) === maxCells) {
|
|
101
|
+
previousSibling.append(...table.getChildren());
|
|
102
|
+
parentNode.remove();
|
|
103
|
+
} else {
|
|
104
|
+
parentNode.replace(table);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
table.selectEnd();
|
|
108
|
+
},
|
|
109
|
+
type: 'element'
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
function getTableColumnsSize(table: TableNode) {
|
|
113
|
+
const row = table.getFirstChild();
|
|
114
|
+
return $isTableRowNode(row) ? row.getChildrenSize() : 0;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const createTableCell = (textContent: string | null | undefined): TableCellNode => {
|
|
118
|
+
const cell = $createTableCellNode(TableCellHeaderStates.NO_STATUS);
|
|
119
|
+
const paragraph = $createParagraphNode();
|
|
120
|
+
|
|
121
|
+
if (textContent != null) {
|
|
122
|
+
const textNode = $createTextNode(textContent.trim());
|
|
123
|
+
paragraph.append(textNode);
|
|
124
|
+
/**
|
|
125
|
+
* Table cells are treated as text only.
|
|
126
|
+
* However, it is very common to see links inside tables
|
|
127
|
+
* so we check if the content is a markdown link and render a link accordingly
|
|
128
|
+
*/
|
|
129
|
+
const linkMatch = textContent.trim().match(LINK_TRANSFORMER.regExp);
|
|
130
|
+
if (linkMatch) {
|
|
131
|
+
LINK_TRANSFORMER.replace(textNode, linkMatch);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
cell.append(paragraph);
|
|
136
|
+
return cell;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const mapToTableCells = (textContent: string): Array<TableCellNode> | null => {
|
|
140
|
+
const match = textContent.match(TABLE_ROW_REG_EXP);
|
|
141
|
+
|
|
142
|
+
if (!match || !match[1]) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return match[1].split('|').map(text => createTableCell(text));
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const addHeaderDividerToOutput = (output: string[], columnCount?: number | null) => {
|
|
150
|
+
let divider = '';
|
|
151
|
+
|
|
152
|
+
if (!columnCount) {
|
|
153
|
+
return output;
|
|
154
|
+
}
|
|
155
|
+
for (let i = 0; i < columnCount + 1; i++) {
|
|
156
|
+
divider = divider.concat('|');
|
|
157
|
+
|
|
158
|
+
if (i < columnCount) {
|
|
159
|
+
divider = divider.concat('----');
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const headerMatch = output[1]?.match(TABLE_HEADER_REG_EXP);
|
|
164
|
+
if (headerMatch) {
|
|
165
|
+
// output already has the header dashes
|
|
166
|
+
return output;
|
|
167
|
+
}
|
|
168
|
+
return output.splice(1, 0, divider);
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* if a table has the markdown dashes then
|
|
173
|
+
* we don't want to render them as part of the table
|
|
174
|
+
*/
|
|
175
|
+
const handleHeaderDashes = (matchCells: TableCellNode[]) => {
|
|
176
|
+
const test = matchCells[0].getFirstChild()?.getTextContent();
|
|
177
|
+
const headerMatch = test?.match(TABLE_HEADER_REG_EXP);
|
|
178
|
+
return headerMatch === null ? [matchCells] : [];
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const createTableNode = (rows: TableCellNode[][], maxCells: number) => {
|
|
182
|
+
const table = $createTableNode();
|
|
183
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
184
|
+
for (const cells of rows) {
|
|
185
|
+
const tableRow = $createTableRowNode();
|
|
186
|
+
table.append(tableRow);
|
|
187
|
+
|
|
188
|
+
for (let i = 0; i < maxCells; i++) {
|
|
189
|
+
tableRow.append(i < cells.length ? cells[i] : createTableCell(null));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return table;
|
|
193
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { $isAtNodeEnd } from '@lexical/selection';
|
|
2
|
+
import { ElementNode, RangeSelection, TextNode } from 'lexical';
|
|
3
|
+
|
|
4
|
+
export function getSelectedNode(selection: RangeSelection): TextNode | ElementNode {
|
|
5
|
+
const { anchor, focus } = selection;
|
|
6
|
+
const anchorNode = selection.anchor.getNode();
|
|
7
|
+
const focusNode = selection.focus.getNode();
|
|
8
|
+
if (anchorNode === focusNode) {
|
|
9
|
+
return anchorNode;
|
|
10
|
+
}
|
|
11
|
+
const isBackward = selection.isBackward();
|
|
12
|
+
if (isBackward) {
|
|
13
|
+
return $isAtNodeEnd(focus) ? anchorNode : focusNode;
|
|
14
|
+
}
|
|
15
|
+
return $isAtNodeEnd(anchor) ? focusNode : anchorNode;
|
|
16
|
+
}
|