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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (249) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/README.md +7 -0
  3. package/dist/api/save.d.ts +5 -0
  4. package/dist/api/save.js +1 -0
  5. package/dist/chunk-3FJGHVH5.js +1 -0
  6. package/dist/chunk-3TDCKWYF.js +1 -0
  7. package/dist/chunk-57FSKQGH.js +1 -0
  8. package/dist/chunk-5GT4QAIQ.js +1 -0
  9. package/dist/chunk-6TM3EYFS.js +1 -0
  10. package/dist/chunk-76US6CXQ.js +1 -0
  11. package/dist/chunk-A6ZSQMSC.js +1 -0
  12. package/dist/chunk-ACFFLNAA.js +1 -0
  13. package/dist/chunk-BTI67NTQ.js +1 -0
  14. package/dist/chunk-FCCNBA44.js +1 -0
  15. package/dist/chunk-FGP7KEKC.js +1 -0
  16. package/dist/chunk-FI57IKHZ.js +1 -0
  17. package/dist/chunk-FM63P66J.js +1 -0
  18. package/dist/chunk-GR3B6XH6.js +1 -0
  19. package/dist/chunk-HL7D7RIZ.js +1 -0
  20. package/dist/chunk-HROTQ6LG.js +1 -0
  21. package/dist/chunk-I3Z6UENR.js +1 -0
  22. package/dist/chunk-IO4745QD.js +1 -0
  23. package/dist/chunk-IXKHPWRK.js +1 -0
  24. package/dist/chunk-JKQCT2R7.js +1 -0
  25. package/dist/chunk-JQUDHJIQ.js +1 -0
  26. package/dist/chunk-JVJ4H6LB.js +1 -0
  27. package/dist/chunk-JX5HLHOC.js +1 -0
  28. package/dist/chunk-KMK5XOLZ.js +1 -0
  29. package/dist/chunk-KYX7ZWMX.js +1 -0
  30. package/dist/chunk-LOZ4MGDL.js +1 -0
  31. package/dist/chunk-M5W2GPOK.js +1 -0
  32. package/dist/chunk-MXR5SEUG.js +1 -0
  33. package/dist/chunk-N3NO7L4H.js +1 -0
  34. package/dist/chunk-NXV7KCC5.js +1 -0
  35. package/dist/chunk-OFDMXZXG.js +1 -0
  36. package/dist/chunk-P3NF2XWE.js +1 -0
  37. package/dist/chunk-P5OBPOC4.js +0 -0
  38. package/dist/chunk-PPEVCQGG.js +1 -0
  39. package/dist/chunk-RHRDHZ4F.js +1 -0
  40. package/dist/chunk-RN54LVW4.js +1 -0
  41. package/dist/chunk-T2M7535Y.js +1 -0
  42. package/dist/chunk-UA5IM6NC.js +1 -0
  43. package/dist/chunk-UVZXP5EW.js +1 -0
  44. package/dist/chunk-UXLYVZ46.js +1 -0
  45. package/dist/chunk-V5Z3VNEW.js +1 -0
  46. package/dist/chunk-W44IYDHL.js +1 -0
  47. package/dist/chunk-WBUBW4PH.js +2 -0
  48. package/dist/chunk-WHPZZWKO.js +1 -0
  49. package/dist/chunk-WQSFQBP7.js +1 -0
  50. package/dist/chunk-WXWEGQ2Z.js +1 -0
  51. package/dist/chunk-XX4EWSFM.js +1 -0
  52. package/dist/chunk-YPBKG7ME.js +1 -0
  53. package/dist/chunk-ZCWG7UN7.js +1 -0
  54. package/dist/chunk-ZF6MK5QB.js +1 -0
  55. package/dist/chunk-ZK6NLAGG.js +1 -0
  56. package/dist/components/ActionMenu/ActionMenu.css.css +1 -0
  57. package/dist/components/ActionMenu/ActionMenu.css.d.ts +4 -0
  58. package/dist/components/ActionMenu/ActionMenu.css.js +1 -0
  59. package/dist/components/ActionMenu/ActionMenu.d.ts +10 -0
  60. package/dist/components/ActionMenu/ActionMenu.js +1 -0
  61. package/dist/components/ContentEditor.d.ts +3 -0
  62. package/dist/components/ContentEditor.js +1 -0
  63. package/dist/components/Editor.css +1 -0
  64. package/dist/components/Editor.css.css +1 -0
  65. package/dist/components/Editor.css.d.ts +10 -0
  66. package/dist/components/Editor.css.js +1 -0
  67. package/dist/components/Editor.d.ts +14 -0
  68. package/dist/components/Editor.js +1 -0
  69. package/dist/components/EditorControls.css +1 -0
  70. package/dist/components/EditorControls.css.css +1 -0
  71. package/dist/components/EditorControls.css.d.ts +4 -0
  72. package/dist/components/EditorControls.css.js +1 -0
  73. package/dist/components/EditorControls.d.ts +4 -0
  74. package/dist/components/EditorControls.js +1 -0
  75. package/dist/components/LinkEditor/EditLinkButton.d.ts +3 -0
  76. package/dist/components/LinkEditor/EditLinkButton.js +1 -0
  77. package/dist/components/LinkEditor/LinkEditor.css +1 -0
  78. package/dist/components/LinkEditor/LinkEditor.css.css +1 -0
  79. package/dist/components/LinkEditor/LinkEditor.css.d.ts +4 -0
  80. package/dist/components/LinkEditor/LinkEditor.css.js +1 -0
  81. package/dist/components/LinkEditor/LinkEditor.d.ts +2 -0
  82. package/dist/components/LinkEditor/LinkEditor.js +1 -0
  83. package/dist/components/LinkEditor/SaveAdornment.d.ts +7 -0
  84. package/dist/components/LinkEditor/SaveAdornment.js +1 -0
  85. package/dist/components/PersistEditDialog/PersistStatus.css +1 -0
  86. package/dist/components/PersistEditDialog/PersistStatus.css.css +1 -0
  87. package/dist/components/PersistEditDialog/PersistStatus.css.d.ts +7 -0
  88. package/dist/components/PersistEditDialog/PersistStatus.css.js +1 -0
  89. package/dist/components/PersistEditDialog/PersistStatus.d.ts +7 -0
  90. package/dist/components/PersistEditDialog/PersistStatus.js +1 -0
  91. package/dist/components/PersistEditDialog/index.css +1 -0
  92. package/dist/components/PersistEditDialog/index.d.ts +4 -0
  93. package/dist/components/PersistEditDialog/index.js +1 -0
  94. package/dist/components/Popper/Popper.css +1 -0
  95. package/dist/components/Popper/Popper.css.css +1 -0
  96. package/dist/components/Popper/Popper.css.d.ts +5 -0
  97. package/dist/components/Popper/Popper.css.js +1 -0
  98. package/dist/components/Popper/Popper.d.ts +11 -0
  99. package/dist/components/Popper/Popper.js +1 -0
  100. package/dist/components/SaveButton.d.ts +2 -0
  101. package/dist/components/SaveButton.js +1 -0
  102. package/dist/components/ScrollableSection/ScrollableSection.css +1 -0
  103. package/dist/components/ScrollableSection/ScrollableSection.css.css +1 -0
  104. package/dist/components/ScrollableSection/ScrollableSection.css.d.ts +4 -0
  105. package/dist/components/ScrollableSection/ScrollableSection.css.js +1 -0
  106. package/dist/components/ScrollableSection/ScrollableSection.d.ts +7 -0
  107. package/dist/components/ScrollableSection/ScrollableSection.js +1 -0
  108. package/dist/components/StatusBanner.d.ts +3 -0
  109. package/dist/components/StatusBanner.js +1 -0
  110. package/dist/components/Toolbar/InsertBlockDropdown.d.ts +6 -0
  111. package/dist/components/Toolbar/InsertBlockDropdown.js +1 -0
  112. package/dist/components/Toolbar/InsertHorizontalRule.d.ts +2 -0
  113. package/dist/components/Toolbar/InsertHorizontalRule.js +1 -0
  114. package/dist/components/Toolbar/InsertImage.css +1 -0
  115. package/dist/components/Toolbar/InsertImage.css.css +1 -0
  116. package/dist/components/Toolbar/InsertImage.css.d.ts +4 -0
  117. package/dist/components/Toolbar/InsertImage.css.js +1 -0
  118. package/dist/components/Toolbar/InsertImage.d.ts +2 -0
  119. package/dist/components/Toolbar/InsertImage.js +1 -0
  120. package/dist/components/Toolbar/InsertLink.css +1 -0
  121. package/dist/components/Toolbar/InsertLink.css.css +1 -0
  122. package/dist/components/Toolbar/InsertLink.css.d.ts +4 -0
  123. package/dist/components/Toolbar/InsertLink.css.js +1 -0
  124. package/dist/components/Toolbar/InsertLink.d.ts +3 -0
  125. package/dist/components/Toolbar/InsertLink.js +1 -0
  126. package/dist/components/Toolbar/InsertTable.css +1 -0
  127. package/dist/components/Toolbar/InsertTable.css.css +1 -0
  128. package/dist/components/Toolbar/InsertTable.css.d.ts +13 -0
  129. package/dist/components/Toolbar/InsertTable.css.js +1 -0
  130. package/dist/components/Toolbar/InsertTable.d.ts +10 -0
  131. package/dist/components/Toolbar/InsertTable.js +1 -0
  132. package/dist/components/Toolbar/TextFormatToolbarButton.css +1 -0
  133. package/dist/components/Toolbar/TextFormatToolbarButton.css.css +1 -0
  134. package/dist/components/Toolbar/TextFormatToolbarButton.css.d.ts +5 -0
  135. package/dist/components/Toolbar/TextFormatToolbarButton.css.js +1 -0
  136. package/dist/components/Toolbar/TextFormatToolbarButton.d.ts +7 -0
  137. package/dist/components/Toolbar/TextFormatToolbarButton.js +1 -0
  138. package/dist/components/Toolbar/TextFormatTooltray.css +1 -0
  139. package/dist/components/Toolbar/TextFormatTooltray.css.css +1 -0
  140. package/dist/components/Toolbar/TextFormatTooltray.css.d.ts +8 -0
  141. package/dist/components/Toolbar/TextFormatTooltray.css.js +1 -0
  142. package/dist/components/Toolbar/TextFormatTooltray.d.ts +4 -0
  143. package/dist/components/Toolbar/TextFormatTooltray.js +1 -0
  144. package/dist/components/Toolbar/Toolbar.css +1 -0
  145. package/dist/components/Toolbar/Toolbar.d.ts +3 -0
  146. package/dist/components/Toolbar/Toolbar.js +1 -0
  147. package/dist/hooks/useEditHistory.d.ts +6 -0
  148. package/dist/hooks/useEditHistory.js +1 -0
  149. package/dist/index.css +1 -0
  150. package/dist/index.d.ts +4 -0
  151. package/dist/index.js +1 -0
  152. package/dist/nodes/index.d.ts +8 -0
  153. package/dist/nodes/index.js +1 -0
  154. package/dist/plugins/FloatingToolbarPlugin.css +1 -0
  155. package/dist/plugins/FloatingToolbarPlugin.d.ts +2 -0
  156. package/dist/plugins/FloatingToolbarPlugin.js +1 -0
  157. package/dist/plugins/HorizontalRulePlugin.d.ts +1 -0
  158. package/dist/plugins/HorizontalRulePlugin.js +1 -0
  159. package/dist/plugins/MarkdownImagePlugin.d.ts +7 -0
  160. package/dist/plugins/MarkdownImagePlugin.js +1 -0
  161. package/dist/plugins/MarkdownLinkPlugin.css +1 -0
  162. package/dist/plugins/MarkdownLinkPlugin.d.ts +8 -0
  163. package/dist/plugins/MarkdownLinkPlugin.js +1 -0
  164. package/dist/plugins/PreviewPlugin.css +1 -0
  165. package/dist/plugins/PreviewPlugin.d.ts +5 -0
  166. package/dist/plugins/PreviewPlugin.js +1 -0
  167. package/dist/plugins/TableActionMenuPlugin.css +1 -0
  168. package/dist/plugins/TableActionMenuPlugin.css.css +1 -0
  169. package/dist/plugins/TableActionMenuPlugin.css.d.ts +60 -0
  170. package/dist/plugins/TableActionMenuPlugin.css.js +1 -0
  171. package/dist/plugins/TableActionMenuPlugin.d.ts +2 -0
  172. package/dist/plugins/TableActionMenuPlugin.js +1 -0
  173. package/dist/store/index.d.ts +38 -0
  174. package/dist/store/index.js +1 -0
  175. package/dist/theme/index.css +1 -0
  176. package/dist/theme/index.css.css +1 -0
  177. package/dist/theme/index.css.d.ts +15 -0
  178. package/dist/theme/index.css.js +1 -0
  179. package/dist/theme/index.d.ts +3 -0
  180. package/dist/theme/index.js +1 -0
  181. package/dist/transformers/horizontalRule.d.ts +2 -0
  182. package/dist/transformers/horizontalRule.js +1 -0
  183. package/dist/transformers/index.d.ts +2 -0
  184. package/dist/transformers/index.js +1 -0
  185. package/dist/transformers/link.d.ts +2 -0
  186. package/dist/transformers/link.js +1 -0
  187. package/dist/transformers/tableRule.d.ts +2 -0
  188. package/dist/transformers/tableRule.js +1 -0
  189. package/dist/utils/getSelectedNode.d.ts +2 -0
  190. package/dist/utils/getSelectedNode.js +1 -0
  191. package/package.json +54 -0
  192. package/src/api/save.ts +12 -0
  193. package/src/components/ActionMenu/ActionMenu.css.ts +9 -0
  194. package/src/components/ActionMenu/ActionMenu.tsx +28 -0
  195. package/src/components/ContentEditor.tsx +12 -0
  196. package/src/components/Editor.css.ts +64 -0
  197. package/src/components/Editor.tsx +143 -0
  198. package/src/components/EditorControls.css.ts +9 -0
  199. package/src/components/EditorControls.tsx +31 -0
  200. package/src/components/LinkEditor/EditLinkButton.tsx +9 -0
  201. package/src/components/LinkEditor/LinkEditor.css.ts +15 -0
  202. package/src/components/LinkEditor/LinkEditor.tsx +136 -0
  203. package/src/components/LinkEditor/SaveAdornment.tsx +16 -0
  204. package/src/components/PersistEditDialog/PersistStatus.css.ts +21 -0
  205. package/src/components/PersistEditDialog/PersistStatus.tsx +78 -0
  206. package/src/components/PersistEditDialog/__tests__/handlers.ts +9 -0
  207. package/src/components/PersistEditDialog/__tests__/index.test.tsx +93 -0
  208. package/src/components/PersistEditDialog/index.tsx +114 -0
  209. package/src/components/Popper/Popper.css.ts +12 -0
  210. package/src/components/Popper/Popper.tsx +31 -0
  211. package/src/components/SaveButton.tsx +23 -0
  212. package/src/components/ScrollableSection/ScrollableSection.css.ts +10 -0
  213. package/src/components/ScrollableSection/ScrollableSection.tsx +20 -0
  214. package/src/components/StatusBanner.tsx +16 -0
  215. package/src/components/Toolbar/InsertBlockDropdown.tsx +134 -0
  216. package/src/components/Toolbar/InsertHorizontalRule.tsx +18 -0
  217. package/src/components/Toolbar/InsertImage.css.ts +7 -0
  218. package/src/components/Toolbar/InsertImage.tsx +132 -0
  219. package/src/components/Toolbar/InsertLink.css.ts +7 -0
  220. package/src/components/Toolbar/InsertLink.tsx +145 -0
  221. package/src/components/Toolbar/InsertTable.css.ts +46 -0
  222. package/src/components/Toolbar/InsertTable.tsx +190 -0
  223. package/src/components/Toolbar/TextFormatToolbarButton.css.ts +10 -0
  224. package/src/components/Toolbar/TextFormatToolbarButton.tsx +29 -0
  225. package/src/components/Toolbar/TextFormatTooltray.css.ts +27 -0
  226. package/src/components/Toolbar/TextFormatTooltray.tsx +118 -0
  227. package/src/components/Toolbar/Toolbar.tsx +47 -0
  228. package/src/components/__tests__/EditorControls.test.tsx +69 -0
  229. package/src/components/__tests__/StatusBanner.test.tsx +33 -0
  230. package/src/hooks/useEditHistory.ts +49 -0
  231. package/src/index.ts +5 -0
  232. package/src/nodes/index.ts +23 -0
  233. package/src/plugins/FloatingToolbarPlugin.tsx +100 -0
  234. package/src/plugins/HorizontalRulePlugin.tsx +39 -0
  235. package/src/plugins/MarkdownImagePlugin.tsx +58 -0
  236. package/src/plugins/MarkdownLinkPlugin.tsx +69 -0
  237. package/src/plugins/PreviewPlugin.tsx +46 -0
  238. package/src/plugins/TableActionMenuPlugin.css.ts +29 -0
  239. package/src/plugins/TableActionMenuPlugin.tsx +278 -0
  240. package/src/store/__tests__/index.test.ts +171 -0
  241. package/src/store/index.ts +84 -0
  242. package/src/theme/index.css.ts +39 -0
  243. package/src/theme/index.ts +22 -0
  244. package/src/transformers/horizontalRule.ts +25 -0
  245. package/src/transformers/index.ts +6 -0
  246. package/src/transformers/link.ts +3 -0
  247. package/src/transformers/tableRule.ts +193 -0
  248. package/src/utils/getSelectedNode.ts +16 -0
  249. package/tsconfig.json +8 -0
@@ -0,0 +1,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,6 @@
1
+ import { TRANSFORMERS } from '@lexical/markdown';
2
+
3
+ import { HORIZONTAL_RULE } from './horizontalRule';
4
+ import { TABLE_RULE } from './tableRule';
5
+
6
+ export default [HORIZONTAL_RULE, TABLE_RULE, ...TRANSFORMERS];
@@ -0,0 +1,3 @@
1
+ import { LINK as LINK_TRANSFORMER } from '@lexical/markdown';
2
+
3
+ export default LINK_TRANSFORMER;
@@ -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
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.esm",
3
+ "compilerOptions": {
4
+ "rootDir": "./src",
5
+ "outDir": "./dist"
6
+ },
7
+ "include": ["src"]
8
+ }