@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,100 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
3
|
+
import { mergeRegister } from '@lexical/utils';
|
|
4
|
+
import {
|
|
5
|
+
$getSelection,
|
|
6
|
+
$isRangeSelection,
|
|
7
|
+
COMMAND_PRIORITY_LOW,
|
|
8
|
+
SELECTION_CHANGE_COMMAND
|
|
9
|
+
} from 'lexical';
|
|
10
|
+
import { Toolbar as SaltToolbar, Tooltray as SaltTooltray } from '@salt-ds/lab';
|
|
11
|
+
import { flip, inline } from '@floating-ui/react-dom';
|
|
12
|
+
import { useFloatingUI, UseFloatingUIProps } from '@salt-ds/lab';
|
|
13
|
+
import { useInteractions, useDismiss } from '@floating-ui/react-dom-interactions';
|
|
14
|
+
import { Popper } from '../components/Popper/Popper';
|
|
15
|
+
import { TextFormatTooltray } from '../components/Toolbar/TextFormatTooltray';
|
|
16
|
+
import { InsertLinkButton } from '../components/Toolbar/InsertLink';
|
|
17
|
+
|
|
18
|
+
export function FloatingToolbarPlugin(): JSX.Element | null {
|
|
19
|
+
const [editor] = useLexicalComposerContext();
|
|
20
|
+
const [floatingPosition, setFloatingPosition] = useState<{ x: number; y: number } | null>(null);
|
|
21
|
+
const [open, setOpen] = useState<boolean>(false);
|
|
22
|
+
|
|
23
|
+
const FloatingProps: UseFloatingUIProps = floatingPosition
|
|
24
|
+
? {
|
|
25
|
+
middleware: [
|
|
26
|
+
inline(floatingPosition),
|
|
27
|
+
flip({
|
|
28
|
+
fallbackPlacements: ['bottom-start', 'top-start']
|
|
29
|
+
})
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
: {
|
|
33
|
+
placement: 'bottom-start',
|
|
34
|
+
strategy: 'absolute'
|
|
35
|
+
};
|
|
36
|
+
const { context, floating, reference, strategy } = useFloatingUI({
|
|
37
|
+
placement: 'bottom-start',
|
|
38
|
+
open,
|
|
39
|
+
onOpenChange: setOpen,
|
|
40
|
+
strategy: 'absolute',
|
|
41
|
+
...FloatingProps
|
|
42
|
+
});
|
|
43
|
+
const { getFloatingProps } = useInteractions([
|
|
44
|
+
useDismiss(context, {
|
|
45
|
+
ancestorScroll: true
|
|
46
|
+
})
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
const updateToolbar = useCallback(() => {
|
|
50
|
+
const selection = $getSelection();
|
|
51
|
+
const nativeSelection = window.getSelection();
|
|
52
|
+
const anchorEl = nativeSelection?.anchorNode?.parentElement;
|
|
53
|
+
if ($isRangeSelection(selection) && anchorEl) {
|
|
54
|
+
reference(anchorEl);
|
|
55
|
+
const getRange = nativeSelection.getRangeAt(0);
|
|
56
|
+
const { x, y, height } = getRange.getBoundingClientRect();
|
|
57
|
+
setFloatingPosition({ x, y: y + height });
|
|
58
|
+
setOpen(true);
|
|
59
|
+
} else {
|
|
60
|
+
setFloatingPosition(null);
|
|
61
|
+
setOpen(false);
|
|
62
|
+
}
|
|
63
|
+
}, [reference]);
|
|
64
|
+
|
|
65
|
+
useEffect(
|
|
66
|
+
() =>
|
|
67
|
+
mergeRegister(
|
|
68
|
+
editor.registerUpdateListener(({ editorState }) => {
|
|
69
|
+
editorState.read(() => {
|
|
70
|
+
updateToolbar();
|
|
71
|
+
});
|
|
72
|
+
}),
|
|
73
|
+
|
|
74
|
+
editor.registerCommand(
|
|
75
|
+
SELECTION_CHANGE_COMMAND,
|
|
76
|
+
() => {
|
|
77
|
+
updateToolbar();
|
|
78
|
+
return false;
|
|
79
|
+
},
|
|
80
|
+
COMMAND_PRIORITY_LOW
|
|
81
|
+
)
|
|
82
|
+
),
|
|
83
|
+
[editor, updateToolbar]
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
let style: Record<string, any> = { position: strategy };
|
|
87
|
+
if (floatingPosition) {
|
|
88
|
+
style = { ...style, top: floatingPosition.y, left: floatingPosition.x };
|
|
89
|
+
}
|
|
90
|
+
return (
|
|
91
|
+
<Popper ref={floating} open={open} style={style} {...getFloatingProps({})}>
|
|
92
|
+
<SaltToolbar aria-label="page editing toolbar" style={{ minWidth: '100px' }}>
|
|
93
|
+
<TextFormatTooltray floating />
|
|
94
|
+
<SaltTooltray aria-label="text format tooltray">
|
|
95
|
+
<InsertLinkButton />
|
|
96
|
+
</SaltTooltray>
|
|
97
|
+
</SaltToolbar>
|
|
98
|
+
</Popper>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
2
|
+
import {
|
|
3
|
+
$createHorizontalRuleNode,
|
|
4
|
+
INSERT_HORIZONTAL_RULE_COMMAND
|
|
5
|
+
} from '@lexical/react/LexicalHorizontalRuleNode';
|
|
6
|
+
import { $getSelection, $isRangeSelection, COMMAND_PRIORITY_EDITOR } from 'lexical';
|
|
7
|
+
import { useEffect } from 'react';
|
|
8
|
+
|
|
9
|
+
export default function HorizontalRulePlugin(): null {
|
|
10
|
+
const [editor] = useLexicalComposerContext();
|
|
11
|
+
|
|
12
|
+
useEffect(
|
|
13
|
+
() =>
|
|
14
|
+
editor.registerCommand(
|
|
15
|
+
INSERT_HORIZONTAL_RULE_COMMAND,
|
|
16
|
+
() => {
|
|
17
|
+
const selection = $getSelection();
|
|
18
|
+
|
|
19
|
+
if (!$isRangeSelection(selection)) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const focusNode = selection.focus.getNode();
|
|
24
|
+
|
|
25
|
+
if (focusNode !== null) {
|
|
26
|
+
const horizontalRuleNode = $createHorizontalRuleNode();
|
|
27
|
+
selection.insertParagraph();
|
|
28
|
+
selection.focus.getNode().getTopLevelElementOrThrow().insertBefore(horizontalRuleNode);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return true;
|
|
32
|
+
},
|
|
33
|
+
COMMAND_PRIORITY_EDITOR
|
|
34
|
+
),
|
|
35
|
+
[editor]
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
2
|
+
import {
|
|
3
|
+
$createParagraphNode,
|
|
4
|
+
$createTextNode,
|
|
5
|
+
$getSelection,
|
|
6
|
+
$isRangeSelection,
|
|
7
|
+
COMMAND_PRIORITY_EDITOR,
|
|
8
|
+
createCommand,
|
|
9
|
+
LexicalCommand
|
|
10
|
+
} from 'lexical';
|
|
11
|
+
import { useCallback, useEffect } from 'react';
|
|
12
|
+
|
|
13
|
+
export interface InsertImagePayload {
|
|
14
|
+
url: string | null;
|
|
15
|
+
alt: string | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const INSERT_MARKDOWN_IMAGE_COMMAND: LexicalCommand<InsertImagePayload> = createCommand();
|
|
19
|
+
|
|
20
|
+
function useMarkdownImagePlugin() {
|
|
21
|
+
const [editor] = useLexicalComposerContext();
|
|
22
|
+
|
|
23
|
+
const createImage = useCallback(
|
|
24
|
+
({ url, alt }: InsertImagePayload) => {
|
|
25
|
+
editor.update(() => {
|
|
26
|
+
const selection = $getSelection();
|
|
27
|
+
|
|
28
|
+
if ($isRangeSelection(selection) && url !== undefined && alt !== undefined) {
|
|
29
|
+
const imageNode = $createParagraphNode().append($createTextNode(``));
|
|
30
|
+
|
|
31
|
+
if (selection.focus.getNode().canInsertTextAfter()) {
|
|
32
|
+
selection.insertNodes([imageNode]);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
},
|
|
37
|
+
[editor]
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
useEffect(
|
|
41
|
+
() =>
|
|
42
|
+
editor.registerCommand(
|
|
43
|
+
INSERT_MARKDOWN_IMAGE_COMMAND,
|
|
44
|
+
(payload: InsertImagePayload) => {
|
|
45
|
+
createImage(payload);
|
|
46
|
+
return true;
|
|
47
|
+
},
|
|
48
|
+
COMMAND_PRIORITY_EDITOR
|
|
49
|
+
),
|
|
50
|
+
[editor, createImage]
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function MarkdownImagePlugin() {
|
|
55
|
+
useMarkdownImagePlugin();
|
|
56
|
+
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { $createLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link';
|
|
2
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
3
|
+
import { $wrapNodes } from '@lexical/selection';
|
|
4
|
+
import {
|
|
5
|
+
$createParagraphNode,
|
|
6
|
+
$createTextNode,
|
|
7
|
+
$getSelection,
|
|
8
|
+
$isRangeSelection,
|
|
9
|
+
$isTextNode,
|
|
10
|
+
COMMAND_PRIORITY_EDITOR,
|
|
11
|
+
createCommand,
|
|
12
|
+
LexicalCommand
|
|
13
|
+
} from 'lexical';
|
|
14
|
+
import React, { useCallback, useEffect } from 'react';
|
|
15
|
+
|
|
16
|
+
import { InsertLinkDialog } from '../components/Toolbar/InsertLink';
|
|
17
|
+
|
|
18
|
+
export interface InsertLinkPayload {
|
|
19
|
+
url?: string;
|
|
20
|
+
text?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const INSERT_MARKDOWN_LINK_COMMAND: LexicalCommand<InsertLinkPayload> = createCommand();
|
|
24
|
+
|
|
25
|
+
function useMarkdownLinkPlugin() {
|
|
26
|
+
const [editor] = useLexicalComposerContext();
|
|
27
|
+
|
|
28
|
+
const createLink = useCallback(
|
|
29
|
+
({ url, text }: InsertLinkPayload) => {
|
|
30
|
+
editor.update(() => {
|
|
31
|
+
const selection = $getSelection();
|
|
32
|
+
|
|
33
|
+
if ($isRangeSelection(selection) && url !== undefined && text !== undefined) {
|
|
34
|
+
const focusNode = selection.focus.getNode();
|
|
35
|
+
|
|
36
|
+
if ($isTextNode(focusNode)) {
|
|
37
|
+
editor.dispatchCommand(TOGGLE_LINK_COMMAND, url);
|
|
38
|
+
} else {
|
|
39
|
+
const linkNode = $createLinkNode(url);
|
|
40
|
+
const linkTextNode = $createTextNode(text);
|
|
41
|
+
linkTextNode.setFormat(selection.focus.getNode().getFormat());
|
|
42
|
+
linkNode.append(linkTextNode);
|
|
43
|
+
$wrapNodes(selection, () => linkNode, $createParagraphNode());
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
[editor]
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
useEffect(
|
|
52
|
+
() =>
|
|
53
|
+
editor.registerCommand(
|
|
54
|
+
INSERT_MARKDOWN_LINK_COMMAND,
|
|
55
|
+
(payload: InsertLinkPayload) => {
|
|
56
|
+
createLink(payload);
|
|
57
|
+
return true;
|
|
58
|
+
},
|
|
59
|
+
COMMAND_PRIORITY_EDITOR
|
|
60
|
+
),
|
|
61
|
+
[editor, createLink]
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function MarkdownLinkPlugin() {
|
|
66
|
+
useMarkdownLinkPlugin();
|
|
67
|
+
|
|
68
|
+
return <InsertLinkDialog />;
|
|
69
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { $convertToMarkdownString } from '@lexical/markdown';
|
|
3
|
+
import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin';
|
|
4
|
+
import type { EditorState } from 'lexical';
|
|
5
|
+
import debounce from 'lodash/debounce';
|
|
6
|
+
|
|
7
|
+
import transformers from '../transformers';
|
|
8
|
+
import { useContentEditor } from '../index';
|
|
9
|
+
|
|
10
|
+
function usePreview(onContentChange: (markdown: string) => void) {
|
|
11
|
+
const handleContentChange = debounce(onContentChange, 250, { maxWait: 500 });
|
|
12
|
+
|
|
13
|
+
const onChange = (editorState: EditorState) => {
|
|
14
|
+
editorState.read(() => {
|
|
15
|
+
const markdown = $convertToMarkdownString(transformers);
|
|
16
|
+
if (markdown) {
|
|
17
|
+
handleContentChange(markdown);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
return { onChange };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type PreviewPluginProps = {
|
|
26
|
+
compileMDX: (source: string, parseFrontMatter: boolean) => Promise<any>;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const PreviewPlugin: React.FC<PreviewPluginProps> = ({ compileMDX }) => {
|
|
30
|
+
const { setErrorMessage, setPreviewContent } = useContentEditor();
|
|
31
|
+
|
|
32
|
+
const handleContentChange = async (content: string) => {
|
|
33
|
+
try {
|
|
34
|
+
const data = await compileMDX(content, false /** Don't parse frontmatter */);
|
|
35
|
+
setPreviewContent(data);
|
|
36
|
+
} catch (e) {
|
|
37
|
+
if (e instanceof Error) {
|
|
38
|
+
setErrorMessage(`MDX Error: ${e.message}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const { onChange } = usePreview(handleContentChange);
|
|
44
|
+
|
|
45
|
+
return <OnChangePlugin onChange={onChange} ignoreSelectionChange />;
|
|
46
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { lightModeConditions, vars } from '@jpmorganchase/mosaic-theme';
|
|
2
|
+
import { defineProperties, createSprinkles } from '@vanilla-extract/sprinkles';
|
|
3
|
+
|
|
4
|
+
export const outlineProperties = defineProperties({
|
|
5
|
+
conditions: {
|
|
6
|
+
...lightModeConditions
|
|
7
|
+
},
|
|
8
|
+
defaultCondition: 'lightMode',
|
|
9
|
+
properties: {
|
|
10
|
+
outlineColor: [vars.color.light.selectable.inEdit, vars.color.dark.selectable.inEdit],
|
|
11
|
+
outlineStyle: ['solid', 'dashed'],
|
|
12
|
+
outlineWidth: ['thin', 'medium', 'thick']
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const outlineSprinkles = createSprinkles(outlineProperties);
|
|
17
|
+
|
|
18
|
+
export default {
|
|
19
|
+
focused: outlineSprinkles({
|
|
20
|
+
outlineStyle: 'dashed',
|
|
21
|
+
outlineWidth: 'thin',
|
|
22
|
+
outlineColor: {
|
|
23
|
+
lightMode: vars.color.light.selectable.inEdit,
|
|
24
|
+
darkMode: vars.color.dark.selectable.inEdit
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
.trim()
|
|
28
|
+
.split(' ')
|
|
29
|
+
};
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
3
|
+
import { $getSelection, $isRangeSelection, LexicalEditor } from 'lexical';
|
|
4
|
+
import {
|
|
5
|
+
$deleteTableColumn,
|
|
6
|
+
$getElementGridForTableNode,
|
|
7
|
+
$getTableCellNodeFromLexicalNode,
|
|
8
|
+
$getTableColumnIndexFromTableCellNode,
|
|
9
|
+
$getTableNodeFromLexicalNodeOrThrow,
|
|
10
|
+
$getTableRowIndexFromTableCellNode,
|
|
11
|
+
$insertTableColumn,
|
|
12
|
+
$insertTableRow,
|
|
13
|
+
$isTableCellNode,
|
|
14
|
+
$removeTableRowAtIndex,
|
|
15
|
+
TableCellNode
|
|
16
|
+
} from '@lexical/table';
|
|
17
|
+
import { useFloatingUI } from '@salt-ds/lab';
|
|
18
|
+
import { useDismiss, useInteractions } from '@floating-ui/react-dom-interactions';
|
|
19
|
+
import {
|
|
20
|
+
ArrowDownIcon,
|
|
21
|
+
ArrowLeftIcon,
|
|
22
|
+
ArrowRightIcon,
|
|
23
|
+
ArrowUpIcon,
|
|
24
|
+
DeleteIcon,
|
|
25
|
+
DeleteSolidIcon
|
|
26
|
+
} from '@salt-ds/icons';
|
|
27
|
+
import type { MenuDescriptor } from '@salt-ds/lab';
|
|
28
|
+
|
|
29
|
+
import { Popper } from '../components/Popper/Popper';
|
|
30
|
+
import { ActionMenu, ActionMenuSource } from '../components/ActionMenu/ActionMenu';
|
|
31
|
+
import styles from './TableActionMenuPlugin.css';
|
|
32
|
+
|
|
33
|
+
const initialSource: ActionMenuSource = {
|
|
34
|
+
menuItems: [
|
|
35
|
+
{
|
|
36
|
+
title: 'Insert Row Above',
|
|
37
|
+
icon: ArrowUpIcon
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
title: 'Insert Row Below',
|
|
41
|
+
icon: ArrowDownIcon
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
title: 'Insert Column Left',
|
|
45
|
+
icon: ArrowLeftIcon
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
title: 'Insert Column Right',
|
|
49
|
+
icon: ArrowRightIcon
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
title: 'Delete Row',
|
|
53
|
+
icon: DeleteIcon
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
title: 'Delete Column',
|
|
57
|
+
icon: DeleteIcon
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
title: 'Delete Table',
|
|
61
|
+
icon: DeleteSolidIcon
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
interface TableActionMenuProps {
|
|
67
|
+
editor: LexicalEditor;
|
|
68
|
+
tableCellNode: TableCellNode | null;
|
|
69
|
+
onComplete: () => void;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function TableActionMenu({ editor, tableCellNode, onComplete }: TableActionMenuProps) {
|
|
73
|
+
const handleMenuSelect = (item: MenuDescriptor | null) => {
|
|
74
|
+
if (!item) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (item.title === 'Delete Row') {
|
|
78
|
+
deleteTableRowAtSelection();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (item.title === 'Insert Row Below') {
|
|
83
|
+
insertTableRow(true);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (item.title === 'Insert Row Above') {
|
|
88
|
+
insertTableRow(false);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (item.title === 'Insert Column Left') {
|
|
93
|
+
insertTableColumn(false);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (item.title === 'Insert Column Right') {
|
|
98
|
+
insertTableColumn(true);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (item.title === 'Delete Column') {
|
|
103
|
+
deleteTableColumnAtSelection();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (item.title === 'Delete Table') {
|
|
107
|
+
deleteTable();
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const deleteTableRowAtSelection = useCallback(() => {
|
|
112
|
+
editor.update(() => {
|
|
113
|
+
if ($isTableCellNode(tableCellNode)) {
|
|
114
|
+
const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
|
|
115
|
+
const tableRowIndex = $getTableRowIndexFromTableCellNode(tableCellNode);
|
|
116
|
+
|
|
117
|
+
$removeTableRowAtIndex(tableNode, tableRowIndex);
|
|
118
|
+
onComplete();
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}, [editor, onComplete, tableCellNode]);
|
|
122
|
+
|
|
123
|
+
const insertTableRow = useCallback(
|
|
124
|
+
shouldInsertAfter => {
|
|
125
|
+
editor.update(() => {
|
|
126
|
+
if ($isTableCellNode(tableCellNode)) {
|
|
127
|
+
const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
|
|
128
|
+
const tableRowIndex = $getTableRowIndexFromTableCellNode(tableCellNode);
|
|
129
|
+
const grid = $getElementGridForTableNode(editor, tableNode);
|
|
130
|
+
|
|
131
|
+
$insertTableRow(tableNode, tableRowIndex, shouldInsertAfter, 1, grid);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
onComplete();
|
|
135
|
+
});
|
|
136
|
+
},
|
|
137
|
+
[editor, tableCellNode, onComplete]
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
const insertTableColumn = useCallback(
|
|
141
|
+
shouldInsertAfter => {
|
|
142
|
+
editor.update(() => {
|
|
143
|
+
if ($isTableCellNode(tableCellNode)) {
|
|
144
|
+
const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
|
|
145
|
+
const tableColumnIndex = $getTableColumnIndexFromTableCellNode(tableCellNode);
|
|
146
|
+
const grid = $getElementGridForTableNode(editor, tableNode);
|
|
147
|
+
|
|
148
|
+
$insertTableColumn(tableNode, tableColumnIndex, shouldInsertAfter, 1, grid);
|
|
149
|
+
}
|
|
150
|
+
onComplete();
|
|
151
|
+
});
|
|
152
|
+
},
|
|
153
|
+
[editor, tableCellNode, onComplete]
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
const deleteTableColumnAtSelection = useCallback(() => {
|
|
157
|
+
editor.update(() => {
|
|
158
|
+
if ($isTableCellNode(tableCellNode)) {
|
|
159
|
+
const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
|
|
160
|
+
const tableColumnIndex = $getTableColumnIndexFromTableCellNode(tableCellNode);
|
|
161
|
+
$deleteTableColumn(tableNode, tableColumnIndex);
|
|
162
|
+
onComplete();
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}, [editor, tableCellNode, onComplete]);
|
|
166
|
+
|
|
167
|
+
const deleteTable = useCallback(() => {
|
|
168
|
+
editor.update(() => {
|
|
169
|
+
if ($isTableCellNode(tableCellNode)) {
|
|
170
|
+
const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode);
|
|
171
|
+
tableNode.remove();
|
|
172
|
+
onComplete();
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}, [editor, tableCellNode, onComplete]);
|
|
176
|
+
|
|
177
|
+
return <ActionMenu initialSource={initialSource} onItemClick={handleMenuSelect} />;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function TableActionMenuPlugin() {
|
|
181
|
+
const [editor] = useLexicalComposerContext();
|
|
182
|
+
const [tableCellNode, setTableMenuCellNode] = useState<TableCellNode | null>(null);
|
|
183
|
+
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
|
|
184
|
+
const { context, floating, reference, strategy, x, y } = useFloatingUI({
|
|
185
|
+
placement: 'right',
|
|
186
|
+
strategy: 'absolute',
|
|
187
|
+
open: tableCellNode !== null,
|
|
188
|
+
onOpenChange: () => setTableMenuCellNode(null)
|
|
189
|
+
});
|
|
190
|
+
const { getFloatingProps } = useInteractions([
|
|
191
|
+
useDismiss(context, {
|
|
192
|
+
ancestorScroll: true
|
|
193
|
+
})
|
|
194
|
+
]);
|
|
195
|
+
|
|
196
|
+
const updatePosition = useCallback(() => {
|
|
197
|
+
const selection = $getSelection();
|
|
198
|
+
const nativeSelection = window.getSelection();
|
|
199
|
+
const { activeElement } = document;
|
|
200
|
+
|
|
201
|
+
if (selection == null) {
|
|
202
|
+
setTableMenuCellNode(null);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const rootElement = editor.getRootElement();
|
|
207
|
+
|
|
208
|
+
if (
|
|
209
|
+
$isRangeSelection(selection) &&
|
|
210
|
+
rootElement !== null &&
|
|
211
|
+
nativeSelection !== null &&
|
|
212
|
+
rootElement.contains(nativeSelection.anchorNode)
|
|
213
|
+
) {
|
|
214
|
+
const tableCellNodeFromSelection = $getTableCellNodeFromLexicalNode(
|
|
215
|
+
selection.anchor.getNode()
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
if (tableCellNodeFromSelection == null) {
|
|
219
|
+
setTableMenuCellNode(null);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const tableCellParentNodeDOM = editor.getElementByKey(tableCellNodeFromSelection.getKey());
|
|
224
|
+
|
|
225
|
+
if (tableCellParentNodeDOM == null) {
|
|
226
|
+
setTableMenuCellNode(null);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
tableCellParentNodeDOM.classList.add(...styles.focused);
|
|
231
|
+
setTableMenuCellNode(tableCellNodeFromSelection);
|
|
232
|
+
reference(tableCellParentNodeDOM);
|
|
233
|
+
setAnchorEl(tableCellParentNodeDOM);
|
|
234
|
+
} else if (!activeElement) {
|
|
235
|
+
setTableMenuCellNode(null);
|
|
236
|
+
}
|
|
237
|
+
}, [editor, reference]);
|
|
238
|
+
|
|
239
|
+
useEffect(
|
|
240
|
+
() =>
|
|
241
|
+
editor.registerUpdateListener(() => {
|
|
242
|
+
editor.getEditorState().read(() => {
|
|
243
|
+
updatePosition();
|
|
244
|
+
});
|
|
245
|
+
}),
|
|
246
|
+
[editor, updatePosition]
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
useEffect(
|
|
250
|
+
function applyFocusRing() {
|
|
251
|
+
anchorEl?.classList.add(...styles.focused);
|
|
252
|
+
return function removeFocusRing() {
|
|
253
|
+
anchorEl?.classList.remove(...styles.focused);
|
|
254
|
+
};
|
|
255
|
+
},
|
|
256
|
+
[anchorEl?.classList, tableCellNode]
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
const handleComplete = () => {
|
|
260
|
+
setAnchorEl(null);
|
|
261
|
+
setTableMenuCellNode(null);
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
return (
|
|
265
|
+
<Popper
|
|
266
|
+
ref={floating}
|
|
267
|
+
open={tableCellNode !== null}
|
|
268
|
+
style={{ top: y ?? '', left: x ?? '', position: strategy }}
|
|
269
|
+
{...getFloatingProps({})}
|
|
270
|
+
>
|
|
271
|
+
<TableActionMenu editor={editor} tableCellNode={tableCellNode} onComplete={handleComplete} />
|
|
272
|
+
</Popper>
|
|
273
|
+
);
|
|
274
|
+
}
|