@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,46 @@
|
|
|
1
|
+
import { style } from '@vanilla-extract/css';
|
|
2
|
+
import {
|
|
3
|
+
selectableColor,
|
|
4
|
+
table,
|
|
5
|
+
neutralBorder,
|
|
6
|
+
vars,
|
|
7
|
+
backgroundColor,
|
|
8
|
+
foregroundColor
|
|
9
|
+
} from '@jpmorganchase/mosaic-theme';
|
|
10
|
+
|
|
11
|
+
export default {
|
|
12
|
+
tableContainer: style({
|
|
13
|
+
position: 'relative',
|
|
14
|
+
margin: vars.space.horizontal.x2
|
|
15
|
+
}),
|
|
16
|
+
table: table({ context: 'component' }),
|
|
17
|
+
addColumnArea: style({
|
|
18
|
+
position: 'absolute',
|
|
19
|
+
top: 0,
|
|
20
|
+
width: '25px'
|
|
21
|
+
}),
|
|
22
|
+
addRowArea: style({
|
|
23
|
+
position: 'absolute',
|
|
24
|
+
left: 0,
|
|
25
|
+
height: '25px'
|
|
26
|
+
}),
|
|
27
|
+
headerRow: selectableColor({ variant: 'inEdit' }),
|
|
28
|
+
row: selectableColor({ variant: 'hover' }),
|
|
29
|
+
cell: style([
|
|
30
|
+
neutralBorder({
|
|
31
|
+
variant: 'high',
|
|
32
|
+
borderWidth: 'thin'
|
|
33
|
+
})
|
|
34
|
+
]),
|
|
35
|
+
dimensions: style([
|
|
36
|
+
style({
|
|
37
|
+
display: 'flex',
|
|
38
|
+
justifyContent: 'center',
|
|
39
|
+
alignItems: 'center',
|
|
40
|
+
paddingTop: vars.space.vertical.x2
|
|
41
|
+
}),
|
|
42
|
+
foregroundColor({ variant: 'high' })
|
|
43
|
+
]),
|
|
44
|
+
popper: style([style({ zIndex: 1600 }), backgroundColor({ variant: 'regular' })]),
|
|
45
|
+
spacer: style({ marginLeft: vars.space.horizontal.x1 })
|
|
46
|
+
};
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import React, { ReactElement, FC, MouseEventHandler, useRef, useState } from 'react';
|
|
2
|
+
import { Icon } from '@jpmorganchase/mosaic-components';
|
|
3
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
4
|
+
import { INSERT_TABLE_COMMAND } from '@lexical/table';
|
|
5
|
+
|
|
6
|
+
import { useFloatingUI } from '@salt-ds/lab';
|
|
7
|
+
import { useDismiss, useInteractions } from '@floating-ui/react-dom-interactions';
|
|
8
|
+
import { Popper } from '../Popper/Popper';
|
|
9
|
+
import { TextFormatToolbarButton } from './TextFormatToolbarButton';
|
|
10
|
+
import styles from './InsertTable.css';
|
|
11
|
+
|
|
12
|
+
interface IndexableEntity {
|
|
13
|
+
index: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const Cell: FC<IndexableEntity> = ({ index, ...rest }) => (
|
|
17
|
+
<td className={styles.cell} data-column={index} {...rest} />
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const Row: FC<IndexableEntity> = ({ children, index }) => (
|
|
21
|
+
<tr className={index === 1 ? styles.headerRow : styles.row} data-row={index}>
|
|
22
|
+
{children}
|
|
23
|
+
</tr>
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
interface TableBodyProps {
|
|
27
|
+
rowCount: number;
|
|
28
|
+
columnCount: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const TableBody: FC<TableBodyProps> = ({ rowCount, columnCount }) => {
|
|
32
|
+
const cells: ReactElement[] = [];
|
|
33
|
+
const rows: ReactElement[] = [];
|
|
34
|
+
for (let i = 1; i <= columnCount; i++) {
|
|
35
|
+
cells.push(<Cell key={i} index={i} />);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for (let i = 1; i <= rowCount; i++) {
|
|
39
|
+
rows.push(
|
|
40
|
+
<Row key={i} index={i}>
|
|
41
|
+
{cells}
|
|
42
|
+
</Row>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
return <tbody>{rows}</tbody>;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
function getPosition(node: HTMLElement) {
|
|
49
|
+
const columnIndex = parseInt(node?.dataset?.column || '0', 10);
|
|
50
|
+
const parentRow = node.parentNode as HTMLTableRowElement;
|
|
51
|
+
const rowIndex = parseInt(parentRow?.dataset?.row || '0', 10);
|
|
52
|
+
|
|
53
|
+
return { columnIndex, rowIndex };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface InsertTableProps {
|
|
57
|
+
minRows?: number;
|
|
58
|
+
minColumns?: number;
|
|
59
|
+
maxColumns?: number;
|
|
60
|
+
initialRowCount?: number;
|
|
61
|
+
initialColumnCount?: number;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const InsertTable: FC<InsertTableProps> = ({
|
|
65
|
+
minRows = 2,
|
|
66
|
+
minColumns = 2,
|
|
67
|
+
maxColumns = 10,
|
|
68
|
+
initialRowCount = 4,
|
|
69
|
+
initialColumnCount = 4
|
|
70
|
+
}) => {
|
|
71
|
+
const [editor] = useLexicalComposerContext();
|
|
72
|
+
const tableRef = useRef<HTMLTableElement>(null);
|
|
73
|
+
const boundaryTouchedRef = useRef(false);
|
|
74
|
+
const [columnCount, setColumnCount] = useState(initialColumnCount);
|
|
75
|
+
const [rowCount, setRowCount] = useState(initialRowCount);
|
|
76
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
77
|
+
|
|
78
|
+
const togglePopper = () => {
|
|
79
|
+
reset();
|
|
80
|
+
setIsOpen(prevState => !prevState);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const { context, floating, reference, strategy, x, y } = useFloatingUI({
|
|
84
|
+
placement: 'bottom',
|
|
85
|
+
strategy: 'absolute',
|
|
86
|
+
open: isOpen,
|
|
87
|
+
onOpenChange: togglePopper
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const { getFloatingProps } = useInteractions([
|
|
91
|
+
useDismiss(context, {
|
|
92
|
+
ancestorScroll: true
|
|
93
|
+
})
|
|
94
|
+
]);
|
|
95
|
+
|
|
96
|
+
const handleCellMouseOver: MouseEventHandler<HTMLDivElement> = event => {
|
|
97
|
+
const target = event.nativeEvent.target as HTMLElement;
|
|
98
|
+
if (!tableRef.current?.contains(target) || !boundaryTouchedRef.current) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const { columnIndex, rowIndex } = getPosition(target);
|
|
103
|
+
|
|
104
|
+
if (columnIndex < columnCount && columnIndex >= minColumns) {
|
|
105
|
+
setColumnCount(columnIndex);
|
|
106
|
+
}
|
|
107
|
+
if (rowIndex < rowCount && rowIndex >= minRows) {
|
|
108
|
+
setRowCount(rowIndex);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
function reset() {
|
|
113
|
+
setColumnCount(initialColumnCount);
|
|
114
|
+
setRowCount(initialRowCount);
|
|
115
|
+
boundaryTouchedRef.current = false;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const addColumn = () => {
|
|
119
|
+
boundaryTouchedRef.current = true;
|
|
120
|
+
if (columnCount + 1 <= maxColumns) {
|
|
121
|
+
setColumnCount(prevState => prevState + 1);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const addRow = () => {
|
|
126
|
+
boundaryTouchedRef.current = true;
|
|
127
|
+
setRowCount(prevState => prevState + 1);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const handleSelect = () => {
|
|
131
|
+
editor.dispatchCommand(INSERT_TABLE_COMMAND, {
|
|
132
|
+
columns: columnCount.toString(),
|
|
133
|
+
rows: rowCount.toString()
|
|
134
|
+
});
|
|
135
|
+
reset();
|
|
136
|
+
setIsOpen(false);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const cellSize = 25;
|
|
140
|
+
const tableHeight = rowCount * cellSize;
|
|
141
|
+
const tableWidth = columnCount * cellSize;
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<>
|
|
145
|
+
<TextFormatToolbarButton ref={reference} onClick={togglePopper} active={isOpen}>
|
|
146
|
+
<Icon name="grid" />
|
|
147
|
+
<div className={styles.spacer} />
|
|
148
|
+
<Icon name="chevronDown" />
|
|
149
|
+
</TextFormatToolbarButton>
|
|
150
|
+
<Popper
|
|
151
|
+
className={styles.popper}
|
|
152
|
+
ref={floating}
|
|
153
|
+
open={isOpen}
|
|
154
|
+
style={{ top: y ?? '', left: x ?? '', position: strategy }}
|
|
155
|
+
{...getFloatingProps({})}
|
|
156
|
+
>
|
|
157
|
+
<div
|
|
158
|
+
className={styles.tableContainer}
|
|
159
|
+
onMouseOver={handleCellMouseOver}
|
|
160
|
+
onClick={handleSelect}
|
|
161
|
+
>
|
|
162
|
+
<table
|
|
163
|
+
className={styles.table}
|
|
164
|
+
style={{ width: tableWidth, height: tableHeight }}
|
|
165
|
+
ref={tableRef}
|
|
166
|
+
>
|
|
167
|
+
<TableBody rowCount={rowCount} columnCount={columnCount} />
|
|
168
|
+
</table>
|
|
169
|
+
<div className={styles.dimensions}>{`${columnCount} x ${rowCount}`}</div>
|
|
170
|
+
<div
|
|
171
|
+
className={styles.addColumnArea}
|
|
172
|
+
style={{
|
|
173
|
+
left: tableWidth,
|
|
174
|
+
height: tableHeight + cellSize
|
|
175
|
+
}}
|
|
176
|
+
onMouseOver={addColumn}
|
|
177
|
+
/>
|
|
178
|
+
<div
|
|
179
|
+
className={styles.addRowArea}
|
|
180
|
+
style={{
|
|
181
|
+
top: tableHeight,
|
|
182
|
+
width: tableWidth + cellSize
|
|
183
|
+
}}
|
|
184
|
+
onMouseOver={addRow}
|
|
185
|
+
/>
|
|
186
|
+
</div>
|
|
187
|
+
</Popper>
|
|
188
|
+
</>
|
|
189
|
+
);
|
|
190
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React, { forwardRef, Ref } from 'react';
|
|
2
|
+
import classnames from 'classnames';
|
|
3
|
+
import { ToolbarButton, ToolbarButtonProps } from '@salt-ds/lab';
|
|
4
|
+
|
|
5
|
+
import styles from './TextFormatToolbarButton.css';
|
|
6
|
+
|
|
7
|
+
interface TextFormatToolbarButtonProps extends ToolbarButtonProps {
|
|
8
|
+
active: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const TextFormatToolbarButton = forwardRef(function TextFormatToolbarButton(
|
|
12
|
+
{ active, className, onClick, children, ...rest }: TextFormatToolbarButtonProps,
|
|
13
|
+
ref: Ref<HTMLButtonElement>
|
|
14
|
+
) {
|
|
15
|
+
return (
|
|
16
|
+
<ToolbarButton
|
|
17
|
+
ref={ref}
|
|
18
|
+
onClick={onClick}
|
|
19
|
+
// TODO remove once Salt ToolbarButton supports className overrides
|
|
20
|
+
style={{ width: 28, height: 28 }}
|
|
21
|
+
className={classnames(className, styles.root, {
|
|
22
|
+
[styles.active]: active
|
|
23
|
+
})}
|
|
24
|
+
{...rest}
|
|
25
|
+
>
|
|
26
|
+
{children}
|
|
27
|
+
</ToolbarButton>
|
|
28
|
+
);
|
|
29
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { style } from '@vanilla-extract/css';
|
|
2
|
+
import { vars, code } from '@jpmorganchase/mosaic-theme';
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
// Salt wraps all Tooltray items in a Formfields which adds default padding
|
|
6
|
+
// Tooltray needs refactoring so the user can compose Formfields themselves.
|
|
7
|
+
root: style({
|
|
8
|
+
margin: '-8px 0px 0px 0px'
|
|
9
|
+
}),
|
|
10
|
+
icon: style({
|
|
11
|
+
width: '14px',
|
|
12
|
+
fontSize: '12px'
|
|
13
|
+
}),
|
|
14
|
+
bold: style({ fontWeight: vars.fontWeight.bold }),
|
|
15
|
+
italic: style({ fontStyle: 'italic' }),
|
|
16
|
+
code: style([
|
|
17
|
+
code({ variant: 'regular' }),
|
|
18
|
+
style({
|
|
19
|
+
width: '22px',
|
|
20
|
+
height: '18px',
|
|
21
|
+
backgroundColor: 'inherit!important',
|
|
22
|
+
display: 'inline-flex',
|
|
23
|
+
justifyContent: 'center',
|
|
24
|
+
alignItems: 'center'
|
|
25
|
+
})
|
|
26
|
+
])
|
|
27
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import classnames from 'classnames';
|
|
3
|
+
import {
|
|
4
|
+
FORMAT_TEXT_COMMAND,
|
|
5
|
+
SELECTION_CHANGE_COMMAND,
|
|
6
|
+
COMMAND_PRIORITY_CRITICAL,
|
|
7
|
+
$getSelection,
|
|
8
|
+
$isRangeSelection,
|
|
9
|
+
TextFormatType
|
|
10
|
+
} from 'lexical';
|
|
11
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
12
|
+
import { $isListNode, ListNode } from '@lexical/list';
|
|
13
|
+
import { $getNearestNodeOfType } from '@lexical/utils';
|
|
14
|
+
import { $isHeadingNode } from '@lexical/rich-text';
|
|
15
|
+
import { $isCodeNode } from '@lexical/code';
|
|
16
|
+
import { Icon } from '@jpmorganchase/mosaic-components';
|
|
17
|
+
|
|
18
|
+
import { Tooltray } from '@salt-ds/lab';
|
|
19
|
+
import { TextFormatToolbarButton } from './TextFormatToolbarButton';
|
|
20
|
+
import styles from './TextFormatTooltray.css';
|
|
21
|
+
import { InsertBlockDropdown } from './InsertBlockDropdown';
|
|
22
|
+
|
|
23
|
+
export function TextFormatTooltray({ floating = false }) {
|
|
24
|
+
const [editor] = useLexicalComposerContext();
|
|
25
|
+
const [activeEditor, setActiveEditor] = useState(editor);
|
|
26
|
+
const [isBold, setIsBold] = useState(false);
|
|
27
|
+
const [isItalic, setIsItalic] = useState(false);
|
|
28
|
+
const [isCode, setIsCode] = useState(false);
|
|
29
|
+
const [blockType, setBlockType] = useState<string>('paragraph');
|
|
30
|
+
|
|
31
|
+
const handleFormat = (format: TextFormatType) => {
|
|
32
|
+
activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, format);
|
|
33
|
+
};
|
|
34
|
+
const updateToolbar = useCallback(() => {
|
|
35
|
+
const selection = $getSelection();
|
|
36
|
+
if ($isRangeSelection(selection)) {
|
|
37
|
+
setIsBold(selection.hasFormat('bold'));
|
|
38
|
+
setIsItalic(selection.hasFormat('italic'));
|
|
39
|
+
setIsCode(selection.hasFormat('code'));
|
|
40
|
+
|
|
41
|
+
const anchorNode = selection.anchor.getNode();
|
|
42
|
+
const element =
|
|
43
|
+
anchorNode.getKey() === 'root' ? anchorNode : anchorNode.getTopLevelElementOrThrow();
|
|
44
|
+
const elementKey = element.getKey();
|
|
45
|
+
const elementDOM = activeEditor.getElementByKey(elementKey);
|
|
46
|
+
|
|
47
|
+
if (elementDOM !== null) {
|
|
48
|
+
if ($isListNode(element)) {
|
|
49
|
+
const parentList = $getNearestNodeOfType<ListNode>(anchorNode, ListNode);
|
|
50
|
+
const type = parentList ? parentList.getListType() : element.getListType();
|
|
51
|
+
setBlockType(type);
|
|
52
|
+
} else {
|
|
53
|
+
const type = $isHeadingNode(element) ? element.getTag() : element.getType();
|
|
54
|
+
if (type) {
|
|
55
|
+
setBlockType(type);
|
|
56
|
+
}
|
|
57
|
+
if ($isCodeNode(element)) {
|
|
58
|
+
setIsCode(true);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}, [activeEditor]);
|
|
64
|
+
|
|
65
|
+
useEffect(
|
|
66
|
+
() =>
|
|
67
|
+
activeEditor.registerUpdateListener(({ editorState }) => {
|
|
68
|
+
editorState.read(() => {
|
|
69
|
+
updateToolbar();
|
|
70
|
+
});
|
|
71
|
+
}),
|
|
72
|
+
|
|
73
|
+
[activeEditor, updateToolbar]
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
useEffect(
|
|
77
|
+
() =>
|
|
78
|
+
editor.registerCommand(
|
|
79
|
+
SELECTION_CHANGE_COMMAND,
|
|
80
|
+
(_payload, newEditor) => {
|
|
81
|
+
updateToolbar();
|
|
82
|
+
setActiveEditor(newEditor);
|
|
83
|
+
return false;
|
|
84
|
+
},
|
|
85
|
+
COMMAND_PRIORITY_CRITICAL
|
|
86
|
+
),
|
|
87
|
+
[editor, updateToolbar]
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div className={styles.root}>
|
|
92
|
+
{!floating ? <span /> : null}
|
|
93
|
+
<Tooltray data-collapsible="instant">
|
|
94
|
+
{!floating && <InsertBlockDropdown editor={activeEditor} type={blockType} />}
|
|
95
|
+
<TextFormatToolbarButton active={isBold} onClick={() => handleFormat('bold')} label="Bold">
|
|
96
|
+
<span className={classnames(styles.icon, styles.bold)}>B</span>
|
|
97
|
+
</TextFormatToolbarButton>
|
|
98
|
+
<TextFormatToolbarButton
|
|
99
|
+
active={isItalic}
|
|
100
|
+
onClick={() => handleFormat('italic')}
|
|
101
|
+
label="Italic"
|
|
102
|
+
>
|
|
103
|
+
<span className={classnames(styles.icon, styles.italic)}>I</span>
|
|
104
|
+
</TextFormatToolbarButton>
|
|
105
|
+
<TextFormatToolbarButton
|
|
106
|
+
active={isCode}
|
|
107
|
+
onClick={() => handleFormat('code')}
|
|
108
|
+
label="Inline Code"
|
|
109
|
+
>
|
|
110
|
+
<span className={classnames(styles.code)}>
|
|
111
|
+
<Icon name="chevronLeft" />
|
|
112
|
+
<Icon name="chevronRight" />
|
|
113
|
+
</span>
|
|
114
|
+
</TextFormatToolbarButton>
|
|
115
|
+
</Tooltray>
|
|
116
|
+
</div>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Toolbar as SaltToolbar, ToolbarButton, Tooltray } from '@salt-ds/lab';
|
|
3
|
+
import { Icon } from '@jpmorganchase/mosaic-components';
|
|
4
|
+
|
|
5
|
+
import { useEditHistory } from '../../hooks/useEditHistory';
|
|
6
|
+
import { usePageState } from '../../store';
|
|
7
|
+
import { SaveButton } from '../SaveButton';
|
|
8
|
+
import { TextFormatTooltray } from './TextFormatTooltray';
|
|
9
|
+
import { InsertTable } from './InsertTable';
|
|
10
|
+
import { InsertImage } from './InsertImage';
|
|
11
|
+
import { InsertLinkButton } from './InsertLink';
|
|
12
|
+
import { InsertHorizontalRule } from './InsertHorizontalRule';
|
|
13
|
+
|
|
14
|
+
const Toolbar = () => {
|
|
15
|
+
const { canRedo, canUndo, redoEdit, undoEdit } = useEditHistory();
|
|
16
|
+
const { setPageState } = usePageState();
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<SaltToolbar aria-label="page editing toolbar" style={{ minWidth: '100px' }}>
|
|
20
|
+
<Tooltray aria-label="history tooltray">
|
|
21
|
+
<ToolbarButton label="Undo" disabled={!canUndo} onClick={undoEdit}>
|
|
22
|
+
<Icon name="undo" />
|
|
23
|
+
</ToolbarButton>
|
|
24
|
+
<ToolbarButton label="Redo" disabled={!canRedo} onClick={redoEdit}>
|
|
25
|
+
<Icon name="redo" />
|
|
26
|
+
</ToolbarButton>
|
|
27
|
+
</Tooltray>
|
|
28
|
+
<TextFormatTooltray />
|
|
29
|
+
<Tooltray aria-label="text format tooltray">
|
|
30
|
+
<InsertLinkButton />
|
|
31
|
+
<InsertTable />
|
|
32
|
+
<InsertImage />
|
|
33
|
+
<InsertHorizontalRule />
|
|
34
|
+
</Tooltray>
|
|
35
|
+
<Tooltray aria-label="stop editing tooltray" alignEnd>
|
|
36
|
+
<ToolbarButton label="Cancel Editing" onClick={() => setPageState('VIEW')}>
|
|
37
|
+
<Icon name="delete" />
|
|
38
|
+
</ToolbarButton>
|
|
39
|
+
</Tooltray>
|
|
40
|
+
<Tooltray aria-label="copy tooltray" data-pad-start>
|
|
41
|
+
<SaveButton />
|
|
42
|
+
</Tooltray>
|
|
43
|
+
</SaltToolbar>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export default Toolbar;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
|
|
5
|
+
import { EditorControls } from '../EditorControls';
|
|
6
|
+
import store from '../../store';
|
|
7
|
+
|
|
8
|
+
jest.mock('../../store', () => ({
|
|
9
|
+
...jest.requireActual('../../store'),
|
|
10
|
+
__esModule: true,
|
|
11
|
+
default: jest.fn()
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
const startEditingSpy = jest.fn();
|
|
15
|
+
const stopEditingSpy = jest.fn();
|
|
16
|
+
|
|
17
|
+
describe('GIVEN an EditorControls component', () => {
|
|
18
|
+
describe('WHEN **NOT** logged in', () => {
|
|
19
|
+
beforeAll(() => {
|
|
20
|
+
jest.mocked(store).mockReturnValue({
|
|
21
|
+
startEditing: startEditingSpy,
|
|
22
|
+
isEditing: false
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
test('THEN the edit button is disabled', () => {
|
|
26
|
+
render(<EditorControls isLoggedIn={false} />);
|
|
27
|
+
const tooltips = screen.queryAllByRole('tooltip');
|
|
28
|
+
expect(tooltips.length).toBe(0);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('WHEN logged in', () => {
|
|
33
|
+
test('THEN the edit button is enabled', () => {
|
|
34
|
+
render(<EditorControls isLoggedIn />);
|
|
35
|
+
const button = screen.getByRole('button');
|
|
36
|
+
expect(button).not.toBeDisabled();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('AND WHEN **NOT** editing and the edit button is clicked', () => {
|
|
40
|
+
beforeAll(() => {
|
|
41
|
+
jest.mocked(store).mockReturnValue({
|
|
42
|
+
startEditing: startEditingSpy,
|
|
43
|
+
pageState: 'VIEW'
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
test('THEN editing is started', async () => {
|
|
47
|
+
render(<EditorControls isLoggedIn />);
|
|
48
|
+
const button = screen.getByLabelText('start editing');
|
|
49
|
+
await userEvent.click(button);
|
|
50
|
+
expect(startEditingSpy).toHaveBeenCalledTimes(1);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('AND WHEN editing and the edit button is clicked', () => {
|
|
55
|
+
beforeAll(() => {
|
|
56
|
+
jest.mocked(store).mockReturnValue({
|
|
57
|
+
stopEditing: stopEditingSpy,
|
|
58
|
+
pageState: 'EDIT'
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
test('THEN editing is stopped', async () => {
|
|
62
|
+
render(<EditorControls isLoggedIn />);
|
|
63
|
+
const button = screen.getByLabelText('cancel editing');
|
|
64
|
+
await userEvent.click(button);
|
|
65
|
+
expect(stopEditingSpy).toHaveBeenCalledTimes(1);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
3
|
+
|
|
4
|
+
import * as store from '../../store';
|
|
5
|
+
import StatusBanner from '../StatusBanner';
|
|
6
|
+
|
|
7
|
+
jest.mock('../../store');
|
|
8
|
+
|
|
9
|
+
describe('GIVEN a StatusBanner ', () => {
|
|
10
|
+
const { usePageState } = store;
|
|
11
|
+
|
|
12
|
+
test('THEN it does not report on non-error states', () => {
|
|
13
|
+
usePageState.mockReturnValue({
|
|
14
|
+
pageState: 'WARNING',
|
|
15
|
+
errorMessage: 'some warning'
|
|
16
|
+
});
|
|
17
|
+
// arrange
|
|
18
|
+
render(<StatusBanner />);
|
|
19
|
+
// assert
|
|
20
|
+
expect(screen.queryByText('some warning')).not.toBeInTheDocument();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('THEN it does report on error states', () => {
|
|
24
|
+
usePageState.mockReturnValue({
|
|
25
|
+
pageState: 'ERROR',
|
|
26
|
+
errorMessage: 'some error'
|
|
27
|
+
});
|
|
28
|
+
// arrange
|
|
29
|
+
render(<StatusBanner />);
|
|
30
|
+
// assert
|
|
31
|
+
expect(screen.getByText('some error')).toBeDefined();
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
3
|
+
import {
|
|
4
|
+
CAN_REDO_COMMAND,
|
|
5
|
+
CAN_UNDO_COMMAND,
|
|
6
|
+
REDO_COMMAND,
|
|
7
|
+
UNDO_COMMAND,
|
|
8
|
+
COMMAND_PRIORITY_CRITICAL
|
|
9
|
+
} from 'lexical';
|
|
10
|
+
import { mergeRegister } from '@lexical/utils';
|
|
11
|
+
|
|
12
|
+
export function useEditHistory() {
|
|
13
|
+
const [editor] = useLexicalComposerContext();
|
|
14
|
+
const [canUndo, setCanUndo] = useState(false);
|
|
15
|
+
const [canRedo, setCanRedo] = useState(false);
|
|
16
|
+
|
|
17
|
+
useEffect(
|
|
18
|
+
() =>
|
|
19
|
+
mergeRegister(
|
|
20
|
+
editor.registerCommand<boolean>(
|
|
21
|
+
CAN_UNDO_COMMAND,
|
|
22
|
+
payload => {
|
|
23
|
+
setCanUndo(payload);
|
|
24
|
+
return false;
|
|
25
|
+
},
|
|
26
|
+
COMMAND_PRIORITY_CRITICAL
|
|
27
|
+
),
|
|
28
|
+
editor.registerCommand<boolean>(
|
|
29
|
+
CAN_REDO_COMMAND,
|
|
30
|
+
payload => {
|
|
31
|
+
setCanRedo(payload);
|
|
32
|
+
return false;
|
|
33
|
+
},
|
|
34
|
+
COMMAND_PRIORITY_CRITICAL
|
|
35
|
+
)
|
|
36
|
+
),
|
|
37
|
+
[editor]
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const undoEdit = useCallback(() => editor.dispatchCommand(UNDO_COMMAND, undefined), [editor]);
|
|
41
|
+
const redoEdit = useCallback(() => editor.dispatchCommand(REDO_COMMAND, undefined), [editor]);
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
canRedo,
|
|
45
|
+
canUndo,
|
|
46
|
+
undoEdit,
|
|
47
|
+
redoEdit
|
|
48
|
+
};
|
|
49
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { HeadingNode, QuoteNode } from '@lexical/rich-text';
|
|
2
|
+
import { ListNode, ListItemNode } from '@lexical/list';
|
|
3
|
+
import { AutoLinkNode, LinkNode } from '@lexical/link';
|
|
4
|
+
import { TableCellNode, TableNode, TableRowNode } from '@lexical/table';
|
|
5
|
+
import { CodeHighlightNode, CodeNode } from '@lexical/code';
|
|
6
|
+
import { HorizontalRuleNode } from '@lexical/react/LexicalHorizontalRuleNode';
|
|
7
|
+
|
|
8
|
+
const nodes = [
|
|
9
|
+
AutoLinkNode,
|
|
10
|
+
CodeHighlightNode,
|
|
11
|
+
CodeNode,
|
|
12
|
+
HeadingNode,
|
|
13
|
+
HorizontalRuleNode,
|
|
14
|
+
LinkNode,
|
|
15
|
+
ListItemNode,
|
|
16
|
+
ListNode,
|
|
17
|
+
QuoteNode,
|
|
18
|
+
TableCellNode,
|
|
19
|
+
TableNode,
|
|
20
|
+
TableRowNode
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
export { nodes };
|