@blocknote/core 0.1.0 → 0.1.2
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/README.md +1 -1
- package/dist/blocknote.js +3454 -2426
- package/dist/blocknote.js.map +1 -1
- package/dist/blocknote.umd.cjs +35 -71
- package/dist/blocknote.umd.cjs.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +7 -6
- package/src/BlockNoteTheme.ts +150 -0
- package/src/extensions/Blocks/BlockAttributes.ts +12 -0
- package/src/extensions/Blocks/MultipleNodeSelection.ts +87 -0
- package/src/extensions/Blocks/PreviousBlockTypePlugin.ts +8 -2
- package/src/extensions/Blocks/nodes/Block.module.css +37 -37
- package/src/extensions/Blocks/nodes/Block.ts +79 -32
- package/src/extensions/Blocks/nodes/BlockGroup.ts +18 -1
- package/src/extensions/Blocks/nodes/Content.ts +14 -1
- package/src/extensions/BubbleMenu/BubbleMenuExtension.tsx +8 -1
- package/src/extensions/BubbleMenu/component/BubbleMenu.tsx +116 -88
- package/src/extensions/BubbleMenu/component/LinkToolbarButton.tsx +8 -8
- package/src/extensions/DraggableBlocks/DraggableBlocksPlugin.tsx +143 -33
- package/src/extensions/DraggableBlocks/components/DragHandle.tsx +14 -19
- package/src/extensions/DraggableBlocks/components/DragHandleMenu.tsx +8 -7
- package/src/extensions/Hyperlinks/HyperlinkMenuPlugin.tsx +31 -66
- package/src/extensions/Hyperlinks/menus/EditHyperlinkMenu.tsx +44 -0
- package/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItem.tsx +34 -0
- package/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItemIcon.tsx +31 -0
- package/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItemInput.tsx +40 -0
- package/src/extensions/Hyperlinks/menus/HoverHyperlinkMenu.tsx +37 -0
- package/src/extensions/Hyperlinks/menus/HyperlinkMenu.tsx +63 -0
- package/src/extensions/SlashMenu/SlashMenuItem.ts +3 -1
- package/src/extensions/SlashMenu/defaultCommands.tsx +4 -4
- package/src/extensions/UniqueID/UniqueID.ts +0 -11
- package/src/shared/components/toolbar/Toolbar.tsx +8 -3
- package/src/shared/components/toolbar/ToolbarButton.tsx +57 -0
- package/src/shared/components/toolbar/ToolbarDropdown.tsx +35 -0
- package/src/shared/components/toolbar/ToolbarDropdownItem.tsx +35 -0
- package/src/shared/components/toolbar/ToolbarDropdownTarget.tsx +31 -0
- package/src/shared/plugins/suggestion/SuggestionItem.ts +3 -1
- package/src/shared/plugins/suggestion/{SuggestionListReactRenderer.ts → SuggestionListReactRenderer.tsx} +13 -4
- package/src/shared/plugins/suggestion/components/SuggestionGroup.tsx +6 -93
- package/src/shared/plugins/suggestion/components/SuggestionGroupItem.tsx +82 -0
- package/src/shared/plugins/suggestion/components/SuggestionList.tsx +24 -23
- package/src/utils.ts +12 -0
- package/types/src/BlockNoteTheme.d.ts +2 -0
- package/types/src/commands/indentation.d.ts +2 -0
- package/types/src/extensions/Blocks/BlockAttributes.d.ts +2 -0
- package/types/src/extensions/Blocks/MultipleNodeSelection.d.ts +24 -0
- package/types/src/extensions/Blocks/nodes/Block.d.ts +1 -1
- package/types/src/extensions/BubbleMenu/component/LinkToolbarButton.d.ts +2 -2
- package/types/src/extensions/Hyperlinks/menus/EditHyperlinkMenu.d.ts +11 -0
- package/types/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItem.d.ts +13 -0
- package/types/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItemIcon.d.ts +8 -0
- package/types/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItemInput.d.ts +9 -0
- package/types/src/extensions/Hyperlinks/menus/HoverHyperlinkMenu.d.ts +12 -0
- package/types/src/extensions/Hyperlinks/menus/HyperlinkMenu.d.ts +21 -0
- package/types/src/extensions/Hyperlinks/menus/helpers/PanelTextInput.d.ts +39 -0
- package/types/src/extensions/Hyperlinks/menus/helpers/PanelTextInputStyles.d.ts +3 -0
- package/types/src/extensions/Hyperlinks/menus/helpers/ToolbarComponent.d.ts +13 -0
- package/types/src/extensions/SlashMenu/SlashMenuItem.d.ts +4 -7
- package/types/src/nodes/ChildgroupNode.d.ts +28 -0
- package/types/src/nodes/patchNodes.d.ts +1 -0
- package/types/src/plugins/TreeViewPlugin/index.d.ts +2 -0
- package/types/src/plugins/animation.d.ts +2 -0
- package/types/src/react/BlockNoteComposer.d.ts +17 -0
- package/types/src/react/BlockNotePlugin.d.ts +1 -0
- package/types/src/react/index.d.ts +3 -0
- package/types/src/react/useBlockNoteSetup.d.ts +2 -0
- package/types/src/registerBlockNote.d.ts +2 -0
- package/types/src/shared/components/toolbar/SimpleToolbarButton.d.ts +2 -3
- package/types/src/shared/components/toolbar/SimpleToolbarDropdown.d.ts +11 -0
- package/types/src/shared/components/toolbar/SimpleToolbarDropdownItem.d.ts +11 -0
- package/types/src/shared/components/toolbar/Toolbar.d.ts +2 -2
- package/types/src/shared/components/toolbar/ToolbarButton.d.ts +15 -0
- package/types/src/shared/components/toolbar/ToolbarDropdown.d.ts +17 -0
- package/types/src/shared/components/toolbar/ToolbarDropdownItem.d.ts +11 -0
- package/types/src/shared/components/toolbar/ToolbarDropdownTarget.d.ts +8 -0
- package/types/src/shared/plugins/suggestion/SuggestionItem.d.ts +2 -4
- package/types/src/shared/plugins/suggestion/components/SuggestionGroupItem.d.ts +9 -0
- package/types/src/shared/plugins/suggestion/components/SuggestionList.d.ts +0 -15
- package/types/src/themes/BlockNoteEditorTheme.d.ts +11 -0
- package/types/src/utils.d.ts +2 -0
- package/src/extensions/BubbleMenu/component/DropdownBlockItem.module.css +0 -13
- package/src/extensions/BubbleMenu/component/DropdownBlockItem.tsx +0 -25
- package/src/extensions/DraggableBlocks/components/DragHandle.module.css +0 -33
- package/src/extensions/DraggableBlocks/components/DragHandleMenu.module.css +0 -10
- package/src/extensions/Hyperlinks/menus/HyperlinkBasicMenu.tsx +0 -59
- package/src/extensions/Hyperlinks/menus/HyperlinkEditMenu.tsx +0 -72
- package/src/extensions/Hyperlinks/menus/atlaskit/PanelTextInput.tsx +0 -173
- package/src/extensions/Hyperlinks/menus/atlaskit/PanelTextInputStyles.ts +0 -36
- package/src/extensions/Hyperlinks/menus/atlaskit/README.md +0 -1
- package/src/extensions/Hyperlinks/menus/atlaskit/ToolbarComponent.tsx +0 -61
- package/src/extensions/helpers/formatKeyboardShortcut.ts +0 -9
- package/src/lib/atlaskit/browser.ts +0 -47
- package/src/shared/components/toolbar/SimpleToolbarButton.module.css +0 -13
- package/src/shared/components/toolbar/SimpleToolbarButton.tsx +0 -56
- package/src/shared/components/toolbar/Toolbar.module.css +0 -10
- package/src/shared/components/toolbar/ToolbarSeparator.module.css +0 -13
- package/src/shared/components/toolbar/ToolbarSeparator.tsx +0 -7
- package/src/shared/plugins/suggestion/components/SuggestionGroup.module.css +0 -45
- package/src/shared/plugins/suggestion/components/SuggestionList.module.css +0 -10
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import Tippy from "@tippyjs/react";
|
|
2
1
|
import { TextSelection } from "prosemirror-state";
|
|
3
2
|
import { EditorView } from "prosemirror-view";
|
|
4
3
|
import { useState } from "react";
|
|
5
4
|
import { AiOutlinePlus } from "react-icons/ai";
|
|
6
5
|
import { findBlock } from "../../Blocks/helpers/findBlock";
|
|
7
6
|
import { SlashMenuPluginKey } from "../../SlashMenu/SlashMenuExtension";
|
|
8
|
-
import
|
|
7
|
+
import { Menu } from "@mantine/core";
|
|
8
|
+
import { MdDragIndicator } from "react-icons/all";
|
|
9
|
+
import { ActionIcon } from "@mantine/core";
|
|
9
10
|
import DragHandleMenu from "./DragHandleMenu";
|
|
10
11
|
|
|
11
12
|
export const DragHandle = (props: {
|
|
@@ -85,23 +86,17 @@ export const DragHandle = (props: {
|
|
|
85
86
|
|
|
86
87
|
return (
|
|
87
88
|
<div style={{ display: "flex", flexDirection: "row" }}>
|
|
88
|
-
<
|
|
89
|
-
size={24}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
interactiveBorder={100}
|
|
100
|
-
interactive={true}
|
|
101
|
-
onShow={props.onShow}
|
|
102
|
-
onHide={props.onHide}>
|
|
103
|
-
<div className={styles.dragHandle} />
|
|
104
|
-
</Tippy>
|
|
89
|
+
<ActionIcon size={24} color={"brandFinal.3"} data-test={"dragHandleAdd"}>
|
|
90
|
+
{<AiOutlinePlus size={24} onClick={onAddClick} />}
|
|
91
|
+
</ActionIcon>
|
|
92
|
+
<Menu onOpen={props.onShow} onClose={props.onHide} position={"left"}>
|
|
93
|
+
<Menu.Target>
|
|
94
|
+
<ActionIcon size={24} color={"brandFinal.3"} data-test={"dragHandle"}>
|
|
95
|
+
{<MdDragIndicator size={24} />}
|
|
96
|
+
</ActionIcon>
|
|
97
|
+
</Menu.Target>
|
|
98
|
+
<DragHandleMenu onDelete={onDelete} />
|
|
99
|
+
</Menu>
|
|
105
100
|
</div>
|
|
106
101
|
);
|
|
107
102
|
};
|
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { MenuGroup, ButtonItem } from "@atlaskit/menu";
|
|
1
|
+
import { createStyles, Menu } from "@mantine/core";
|
|
3
2
|
|
|
4
3
|
type Props = {
|
|
5
4
|
onDelete: () => void;
|
|
6
5
|
};
|
|
7
6
|
|
|
8
7
|
const DragHandleMenu = (props: Props) => {
|
|
8
|
+
const { classes } = createStyles({ root: {} })(undefined, {
|
|
9
|
+
name: "DragHandleMenu",
|
|
10
|
+
});
|
|
11
|
+
|
|
9
12
|
return (
|
|
10
|
-
<
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
</MenuGroup>
|
|
14
|
-
</div>
|
|
13
|
+
<Menu.Dropdown className={classes.root}>
|
|
14
|
+
<Menu.Item onClick={props.onDelete}>Delete</Menu.Item>
|
|
15
|
+
</Menu.Dropdown>
|
|
15
16
|
);
|
|
16
17
|
};
|
|
17
18
|
|
|
@@ -1,43 +1,13 @@
|
|
|
1
|
+
import { MantineProvider } from "@mantine/core";
|
|
1
2
|
import Tippy from "@tippyjs/react";
|
|
2
3
|
import { getMarkRange } from "@tiptap/core";
|
|
3
4
|
import { Mark, ResolvedPos } from "prosemirror-model";
|
|
4
5
|
import { Plugin, PluginKey } from "prosemirror-state";
|
|
5
6
|
import ReactDOM from "react-dom";
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
HyperlinkEditMenu,
|
|
10
|
-
HyperlinkEditorMenuProps,
|
|
11
|
-
} from "./menus/HyperlinkEditMenu";
|
|
7
|
+
import { BlockNoteTheme } from "../../BlockNoteTheme";
|
|
8
|
+
import { HyperlinkMenu } from "./menus/HyperlinkMenu";
|
|
12
9
|
const PLUGIN_KEY = new PluginKey("HyperlinkMenuPlugin");
|
|
13
10
|
|
|
14
|
-
/**
|
|
15
|
-
* a helper function that wraps a Tippy around a HyperlinkEditMenu
|
|
16
|
-
* @param props has {text, url, onSubmit and anchorPos}
|
|
17
|
-
* @returns a Tippy instance whose content is a editMenu
|
|
18
|
-
*/
|
|
19
|
-
const tippyWrapperHyperlinkEditMenu = (
|
|
20
|
-
props: HyperlinkEditorMenuProps & {
|
|
21
|
-
anchorPos: { left: number; top: number; width: number; height: number };
|
|
22
|
-
}
|
|
23
|
-
) => {
|
|
24
|
-
const { anchorPos, ...editMenuProps } = props;
|
|
25
|
-
return (
|
|
26
|
-
<Tippy
|
|
27
|
-
getReferenceClientRect={() => anchorPos as any}
|
|
28
|
-
content={<HyperlinkEditMenu {...editMenuProps}></HyperlinkEditMenu>}
|
|
29
|
-
interactive={true}
|
|
30
|
-
interactiveBorder={30}
|
|
31
|
-
showOnCreate={true}
|
|
32
|
-
trigger={"click"} // so that we don't hide on mouse out
|
|
33
|
-
hideOnClick
|
|
34
|
-
className={rootStyles.bnRoot}
|
|
35
|
-
appendTo={document.body}>
|
|
36
|
-
<div></div>
|
|
37
|
-
</Tippy>
|
|
38
|
-
);
|
|
39
|
-
};
|
|
40
|
-
|
|
41
11
|
export const createHyperlinkMenuPlugin = () => {
|
|
42
12
|
// as we always use Tippy appendTo(document.body), we can just create an element
|
|
43
13
|
// that we use for ReactDOM, but it isn't used anywhere (except by React internally)
|
|
@@ -135,40 +105,35 @@ export const createHyperlinkMenuPlugin = () => {
|
|
|
135
105
|
);
|
|
136
106
|
};
|
|
137
107
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
triggerTarget={hoveredLink}
|
|
166
|
-
showOnCreate={basedOnCursorPos}
|
|
167
|
-
appendTo={document.body}>
|
|
168
|
-
<div></div>
|
|
169
|
-
</Tippy>
|
|
108
|
+
const hyperlinkMenu = (
|
|
109
|
+
<MantineProvider theme={BlockNoteTheme}>
|
|
110
|
+
<Tippy
|
|
111
|
+
key={nextTippyKey + ""} // it could be tippy has "hidden" itself after mouseout. We use a key to get a new instance with a clean state.
|
|
112
|
+
getReferenceClientRect={() => anchorPos as any}
|
|
113
|
+
content={
|
|
114
|
+
<HyperlinkMenu
|
|
115
|
+
update={editHandler}
|
|
116
|
+
pos={anchorPos}
|
|
117
|
+
remove={removeHandler}
|
|
118
|
+
text={text}
|
|
119
|
+
url={url}
|
|
120
|
+
/>
|
|
121
|
+
}
|
|
122
|
+
onHide={() => {
|
|
123
|
+
nextTippyKey++;
|
|
124
|
+
menuState = "hidden";
|
|
125
|
+
}}
|
|
126
|
+
aria={{ expanded: false }}
|
|
127
|
+
interactive={true}
|
|
128
|
+
interactiveBorder={30}
|
|
129
|
+
triggerTarget={hoveredLink}
|
|
130
|
+
showOnCreate={basedOnCursorPos}
|
|
131
|
+
appendTo={document.body}>
|
|
132
|
+
<div></div>
|
|
133
|
+
</Tippy>
|
|
134
|
+
</MantineProvider>
|
|
170
135
|
);
|
|
171
|
-
ReactDOM.render(
|
|
136
|
+
ReactDOM.render(hyperlinkMenu, fakeRenderTarget);
|
|
172
137
|
menuState = basedOnCursorPos ? "cursor-based" : "mouse-based";
|
|
173
138
|
},
|
|
174
139
|
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { createStyles, Stack } from "@mantine/core";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { RiLink, RiText } from "react-icons/ri";
|
|
4
|
+
import { EditHyperlinkMenuItem } from "./EditHyperlinkMenuItem";
|
|
5
|
+
|
|
6
|
+
export type EditHyperlinkMenuProps = {
|
|
7
|
+
url: string;
|
|
8
|
+
text: string;
|
|
9
|
+
update: (url: string, text: string) => void;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Menu which opens when editing an existing hyperlink or creating a new one.
|
|
14
|
+
* Provides input fields for setting the hyperlink URL and title.
|
|
15
|
+
*/
|
|
16
|
+
export const EditHyperlinkMenu = (props: EditHyperlinkMenuProps) => {
|
|
17
|
+
const [url, setUrl] = useState(props.url);
|
|
18
|
+
const [title, setTitle] = useState(props.text);
|
|
19
|
+
const { classes } = createStyles({ root: {} })(undefined, {
|
|
20
|
+
name: "EditHyperlinkMenu",
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<Stack className={classes.root}>
|
|
25
|
+
<EditHyperlinkMenuItem
|
|
26
|
+
icon={RiLink}
|
|
27
|
+
mainIconTooltip={"Edit URL"}
|
|
28
|
+
autofocus={true}
|
|
29
|
+
placeholder={"Edit URL"}
|
|
30
|
+
value={url}
|
|
31
|
+
onChange={(value) => setUrl(value)}
|
|
32
|
+
onSubmit={() => props.update(url, title)}
|
|
33
|
+
/>
|
|
34
|
+
<EditHyperlinkMenuItem
|
|
35
|
+
icon={RiText}
|
|
36
|
+
mainIconTooltip={"Edit Title"}
|
|
37
|
+
placeholder={"Edit Title"}
|
|
38
|
+
value={title}
|
|
39
|
+
onChange={(value) => setTitle(value)}
|
|
40
|
+
onSubmit={() => props.update(url, title)}
|
|
41
|
+
/>
|
|
42
|
+
</Stack>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { IconType } from "react-icons";
|
|
2
|
+
import { EditHyperlinkMenuItemIcon } from "./EditHyperlinkMenuItemIcon";
|
|
3
|
+
import { EditHyperlinkMenuItemInput } from "./EditHyperlinkMenuItemInput";
|
|
4
|
+
import { Group } from "@mantine/core";
|
|
5
|
+
|
|
6
|
+
export type EditHyperlinkMenuItemProps = {
|
|
7
|
+
icon: IconType;
|
|
8
|
+
mainIconTooltip: string;
|
|
9
|
+
secondaryIconTooltip?: string;
|
|
10
|
+
autofocus?: boolean;
|
|
11
|
+
placeholder?: string;
|
|
12
|
+
value?: string;
|
|
13
|
+
onChange: (value: string) => void;
|
|
14
|
+
onSubmit: () => void;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function EditHyperlinkMenuItem(props: EditHyperlinkMenuItemProps) {
|
|
18
|
+
return (
|
|
19
|
+
<Group>
|
|
20
|
+
<EditHyperlinkMenuItemIcon
|
|
21
|
+
icon={props.icon}
|
|
22
|
+
mainTooltip={props.mainIconTooltip}
|
|
23
|
+
secondaryTooltip={props.secondaryIconTooltip}
|
|
24
|
+
/>
|
|
25
|
+
<EditHyperlinkMenuItemInput
|
|
26
|
+
autofocus={props.autofocus}
|
|
27
|
+
placeholder={props.placeholder}
|
|
28
|
+
value={props.value}
|
|
29
|
+
onChange={props.onChange}
|
|
30
|
+
onSubmit={props.onSubmit}
|
|
31
|
+
/>
|
|
32
|
+
</Group>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { IconType } from "react-icons";
|
|
2
|
+
import Tippy from "@tippyjs/react";
|
|
3
|
+
import { TooltipContent } from "../../../shared/components/tooltip/TooltipContent";
|
|
4
|
+
import { Container } from "@mantine/core";
|
|
5
|
+
|
|
6
|
+
export type EditHyperlinkMenuItemIconProps = {
|
|
7
|
+
icon: IconType;
|
|
8
|
+
mainTooltip: string;
|
|
9
|
+
secondaryTooltip?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function EditHyperlinkMenuItemIcon(
|
|
13
|
+
props: EditHyperlinkMenuItemIconProps
|
|
14
|
+
) {
|
|
15
|
+
const Icon = props.icon;
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<Tippy
|
|
19
|
+
content={
|
|
20
|
+
<TooltipContent
|
|
21
|
+
mainTooltip={props.mainTooltip}
|
|
22
|
+
secondaryTooltip={props.secondaryTooltip}
|
|
23
|
+
/>
|
|
24
|
+
}
|
|
25
|
+
placement="left">
|
|
26
|
+
<Container>
|
|
27
|
+
<Icon size={16}></Icon>
|
|
28
|
+
</Container>
|
|
29
|
+
</Tippy>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { KeyboardEvent, useEffect, useRef } from "react";
|
|
2
|
+
import { TextInput } from "@mantine/core";
|
|
3
|
+
|
|
4
|
+
export type EditHyperlinkMenuItemInputProps = {
|
|
5
|
+
autofocus?: boolean;
|
|
6
|
+
placeholder?: string;
|
|
7
|
+
value?: string;
|
|
8
|
+
onChange: (value: string) => void;
|
|
9
|
+
onSubmit: () => void;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function EditHyperlinkMenuItemInput(
|
|
13
|
+
props: EditHyperlinkMenuItemInputProps
|
|
14
|
+
) {
|
|
15
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
setTimeout(() => {
|
|
19
|
+
props.autofocus && inputRef.current?.focus();
|
|
20
|
+
});
|
|
21
|
+
}, [props.autofocus]);
|
|
22
|
+
|
|
23
|
+
function handleEnter(event: KeyboardEvent) {
|
|
24
|
+
if (event.key === "Enter") {
|
|
25
|
+
event.preventDefault();
|
|
26
|
+
props.onSubmit();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<TextInput
|
|
32
|
+
size={"xs"}
|
|
33
|
+
value={props.value}
|
|
34
|
+
onChange={(event) => props.onChange(event.currentTarget.value)}
|
|
35
|
+
onKeyDown={handleEnter}
|
|
36
|
+
placeholder={props.placeholder}
|
|
37
|
+
ref={inputRef}
|
|
38
|
+
/>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { RiExternalLinkFill, RiLinkUnlink } from "react-icons/ri";
|
|
2
|
+
import { Toolbar } from "../../../shared/components/toolbar/Toolbar";
|
|
3
|
+
import { ToolbarButton } from "../../../shared/components/toolbar/ToolbarButton";
|
|
4
|
+
|
|
5
|
+
type HoverHyperlinkMenuProps = {
|
|
6
|
+
url: string;
|
|
7
|
+
edit: () => void;
|
|
8
|
+
remove: () => void;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Menu which opens when hovering an existing hyperlink.
|
|
13
|
+
* Provides buttons for editing, opening, and removing the hyperlink.
|
|
14
|
+
*/
|
|
15
|
+
export const HoverHyperlinkMenu = (props: HoverHyperlinkMenuProps) => {
|
|
16
|
+
return (
|
|
17
|
+
<Toolbar>
|
|
18
|
+
<ToolbarButton mainTooltip="Edit" isSelected={false} onClick={props.edit}>
|
|
19
|
+
Edit Link
|
|
20
|
+
</ToolbarButton>
|
|
21
|
+
<ToolbarButton
|
|
22
|
+
mainTooltip="Open in new tab"
|
|
23
|
+
isSelected={false}
|
|
24
|
+
onClick={() => {
|
|
25
|
+
window.open(props.url, "_blank");
|
|
26
|
+
}}
|
|
27
|
+
icon={RiExternalLinkFill}
|
|
28
|
+
/>
|
|
29
|
+
<ToolbarButton
|
|
30
|
+
mainTooltip="Remove link"
|
|
31
|
+
isSelected={false}
|
|
32
|
+
onClick={props.remove}
|
|
33
|
+
icon={RiLinkUnlink}
|
|
34
|
+
/>
|
|
35
|
+
</Toolbar>
|
|
36
|
+
);
|
|
37
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import Tippy from "@tippyjs/react";
|
|
3
|
+
import { EditHyperlinkMenu } from "./EditHyperlinkMenu";
|
|
4
|
+
import { HoverHyperlinkMenu } from "./HoverHyperlinkMenu";
|
|
5
|
+
import rootStyles from "../../../root.module.css";
|
|
6
|
+
|
|
7
|
+
type HyperlinkMenuProps = {
|
|
8
|
+
url: string;
|
|
9
|
+
text: string;
|
|
10
|
+
pos: {
|
|
11
|
+
height: number;
|
|
12
|
+
width: number;
|
|
13
|
+
left: number;
|
|
14
|
+
right: number;
|
|
15
|
+
top: number;
|
|
16
|
+
bottom: number;
|
|
17
|
+
};
|
|
18
|
+
update: (url: string, text: string) => void;
|
|
19
|
+
remove: () => void;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Main menu component for the hyperlink extension.
|
|
24
|
+
* Either renders a menu to create/edit a hyperlink, or a menu to interact with it on mouse hover.
|
|
25
|
+
*/
|
|
26
|
+
export const HyperlinkMenu = (props: HyperlinkMenuProps) => {
|
|
27
|
+
const [isEditing, setIsEditing] = useState(false);
|
|
28
|
+
|
|
29
|
+
const editHyperlinkMenu = (
|
|
30
|
+
<Tippy
|
|
31
|
+
getReferenceClientRect={() => props.pos as any}
|
|
32
|
+
content={
|
|
33
|
+
<EditHyperlinkMenu
|
|
34
|
+
url={props.url}
|
|
35
|
+
text={props.text}
|
|
36
|
+
update={props.update}
|
|
37
|
+
/>
|
|
38
|
+
}
|
|
39
|
+
interactive={true}
|
|
40
|
+
interactiveBorder={30}
|
|
41
|
+
showOnCreate={true}
|
|
42
|
+
trigger={"click"} // so that we don't hide on mouse out
|
|
43
|
+
hideOnClick
|
|
44
|
+
className={rootStyles.bnRoot}
|
|
45
|
+
appendTo={document.body}>
|
|
46
|
+
<div></div>
|
|
47
|
+
</Tippy>
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const hoverHyperlinkMenu = (
|
|
51
|
+
<HoverHyperlinkMenu
|
|
52
|
+
url={props.url}
|
|
53
|
+
edit={() => setIsEditing(true)}
|
|
54
|
+
remove={props.remove}
|
|
55
|
+
/>
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
if (isEditing) {
|
|
59
|
+
return editHyperlinkMenu;
|
|
60
|
+
} else {
|
|
61
|
+
return hoverHyperlinkMenu;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Editor, Range } from "@tiptap/core";
|
|
2
2
|
import SuggestionItem from "../../shared/plugins/suggestion/SuggestionItem";
|
|
3
|
+
import { IconType } from "react-icons";
|
|
3
4
|
|
|
4
5
|
export type SlashMenuCallback = (editor: Editor, range: Range) => boolean;
|
|
5
6
|
|
|
@@ -31,6 +32,7 @@ export class SlashMenuItem implements SuggestionItem {
|
|
|
31
32
|
* @param execute The callback for creating a new node
|
|
32
33
|
* @param aliases Aliases for this command
|
|
33
34
|
* @param icon To be shown next to the name in the menu
|
|
35
|
+
* @param hint Short description of command
|
|
34
36
|
* @param shortcut Info about keyboard shortcut that would activate this command
|
|
35
37
|
*/
|
|
36
38
|
constructor(
|
|
@@ -38,7 +40,7 @@ export class SlashMenuItem implements SuggestionItem {
|
|
|
38
40
|
public readonly group: SlashMenuGroups,
|
|
39
41
|
public readonly execute: SlashMenuCallback,
|
|
40
42
|
public readonly aliases: string[] = [],
|
|
41
|
-
public readonly icon?:
|
|
43
|
+
public readonly icon?: IconType,
|
|
42
44
|
public readonly hint?: string,
|
|
43
45
|
public readonly shortcut?: string
|
|
44
46
|
) {
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
RiListUnordered,
|
|
7
7
|
RiText,
|
|
8
8
|
} from "react-icons/ri";
|
|
9
|
-
import formatKeyboardShortcut from "
|
|
9
|
+
import { formatKeyboardShortcut } from "../../utils";
|
|
10
10
|
import { SlashMenuGroups, SlashMenuItem } from "./SlashMenuItem";
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -22,7 +22,7 @@ const defaultCommands: { [key: string]: SlashMenuItem } = {
|
|
|
22
22
|
.chain()
|
|
23
23
|
.focus()
|
|
24
24
|
.deleteRange(range)
|
|
25
|
-
.addNewBlockAsSibling({ headingType: 1 })
|
|
25
|
+
.addNewBlockAsSibling({ headingType: "1" })
|
|
26
26
|
.run();
|
|
27
27
|
},
|
|
28
28
|
["h", "heading1", "h1"],
|
|
@@ -40,7 +40,7 @@ const defaultCommands: { [key: string]: SlashMenuItem } = {
|
|
|
40
40
|
.chain()
|
|
41
41
|
.focus()
|
|
42
42
|
.deleteRange(range)
|
|
43
|
-
.addNewBlockAsSibling({ headingType: 2 })
|
|
43
|
+
.addNewBlockAsSibling({ headingType: "2" })
|
|
44
44
|
.run();
|
|
45
45
|
},
|
|
46
46
|
["h2", "heading2", "subheading"],
|
|
@@ -58,7 +58,7 @@ const defaultCommands: { [key: string]: SlashMenuItem } = {
|
|
|
58
58
|
.chain()
|
|
59
59
|
.focus()
|
|
60
60
|
.deleteRange(range)
|
|
61
|
-
.addNewBlockAsSibling({ headingType: 3 })
|
|
61
|
+
.addNewBlockAsSibling({ headingType: "3" })
|
|
62
62
|
.run();
|
|
63
63
|
},
|
|
64
64
|
["h3", "heading3", "subheading"],
|
|
@@ -62,17 +62,6 @@ const UniqueID = Extension.create({
|
|
|
62
62
|
attributes: {
|
|
63
63
|
[this.options.attributeName]: {
|
|
64
64
|
default: null,
|
|
65
|
-
parseHTML: (element) =>
|
|
66
|
-
element.getAttribute(`data-${this.options.attributeName}`),
|
|
67
|
-
renderHTML: (attributes) => {
|
|
68
|
-
if (!attributes[this.options.attributeName]) {
|
|
69
|
-
return {};
|
|
70
|
-
}
|
|
71
|
-
return {
|
|
72
|
-
[`data-${this.options.attributeName}`]:
|
|
73
|
-
attributes[this.options.attributeName],
|
|
74
|
-
};
|
|
75
|
-
},
|
|
76
65
|
},
|
|
77
66
|
},
|
|
78
67
|
},
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { createStyles, Group } from "@mantine/core";
|
|
2
|
+
import { ReactNode } from "react";
|
|
2
3
|
|
|
3
|
-
export const Toolbar = (props: { children:
|
|
4
|
-
|
|
4
|
+
export const Toolbar = (props: { children: ReactNode }) => {
|
|
5
|
+
const { classes } = createStyles({ root: {} })(undefined, {
|
|
6
|
+
name: "Toolbar",
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
return <Group className={classes.root}>{props.children}</Group>;
|
|
5
10
|
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { ActionIcon, Button } from "@mantine/core";
|
|
2
|
+
import Tippy from "@tippyjs/react";
|
|
3
|
+
import { forwardRef } from "react";
|
|
4
|
+
import { TooltipContent } from "../tooltip/TooltipContent";
|
|
5
|
+
import React from "react";
|
|
6
|
+
import { IconType } from "react-icons";
|
|
7
|
+
|
|
8
|
+
export type ToolbarButtonProps = {
|
|
9
|
+
onClick?: (e: React.MouseEvent) => void;
|
|
10
|
+
icon?: IconType;
|
|
11
|
+
mainTooltip: string;
|
|
12
|
+
secondaryTooltip?: string;
|
|
13
|
+
isSelected?: boolean;
|
|
14
|
+
children?: any;
|
|
15
|
+
isDisabled?: boolean;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Helper for basic buttons that show in the inline bubble menu.
|
|
20
|
+
*/
|
|
21
|
+
export const ToolbarButton = forwardRef((props: ToolbarButtonProps, ref) => {
|
|
22
|
+
const ButtonIcon = props.icon;
|
|
23
|
+
return (
|
|
24
|
+
<Tippy
|
|
25
|
+
content={
|
|
26
|
+
<TooltipContent
|
|
27
|
+
mainTooltip={props.mainTooltip}
|
|
28
|
+
secondaryTooltip={props.secondaryTooltip}
|
|
29
|
+
/>
|
|
30
|
+
}
|
|
31
|
+
trigger={"mouseenter"}>
|
|
32
|
+
{/*Creates an ActionIcon instead of a Button if only an icon is provided as content.*/}
|
|
33
|
+
{props.children ? (
|
|
34
|
+
<Button
|
|
35
|
+
onClick={props.onClick}
|
|
36
|
+
color={"brandFinal"}
|
|
37
|
+
size={"xs"}
|
|
38
|
+
variant={props.isSelected ? "filled" : "subtle"}
|
|
39
|
+
disabled={props.isDisabled || false}
|
|
40
|
+
ref={ref as any}>
|
|
41
|
+
{ButtonIcon && <ButtonIcon />}
|
|
42
|
+
{props.children}
|
|
43
|
+
</Button>
|
|
44
|
+
) : (
|
|
45
|
+
<ActionIcon
|
|
46
|
+
onClick={props.onClick}
|
|
47
|
+
color={"brandFinal"}
|
|
48
|
+
size={30}
|
|
49
|
+
variant={props.isSelected ? "filled" : "subtle"}
|
|
50
|
+
disabled={props.isDisabled || false}
|
|
51
|
+
ref={ref as any}>
|
|
52
|
+
{ButtonIcon && <ButtonIcon />}
|
|
53
|
+
</ActionIcon>
|
|
54
|
+
)}
|
|
55
|
+
</Tippy>
|
|
56
|
+
);
|
|
57
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Menu } from "@mantine/core";
|
|
2
|
+
import { IconType } from "react-icons";
|
|
3
|
+
import { ToolbarDropdownTarget } from "./ToolbarDropdownTarget";
|
|
4
|
+
import { MouseEvent } from "react";
|
|
5
|
+
import { ToolbarDropdownItem } from "./ToolbarDropdownItem";
|
|
6
|
+
|
|
7
|
+
export type ToolbarDropdownProps = {
|
|
8
|
+
text: string;
|
|
9
|
+
icon?: IconType;
|
|
10
|
+
items: Array<{
|
|
11
|
+
onClick?: (e: MouseEvent) => void;
|
|
12
|
+
text: string;
|
|
13
|
+
icon?: IconType;
|
|
14
|
+
isSelected?: boolean;
|
|
15
|
+
children?: any;
|
|
16
|
+
isDisabled?: boolean;
|
|
17
|
+
}>;
|
|
18
|
+
children?: any;
|
|
19
|
+
isDisabled?: boolean;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export function ToolbarDropdown(props: ToolbarDropdownProps) {
|
|
23
|
+
return (
|
|
24
|
+
<Menu exitTransitionDuration={0}>
|
|
25
|
+
<Menu.Target>
|
|
26
|
+
<ToolbarDropdownTarget {...props} />
|
|
27
|
+
</Menu.Target>
|
|
28
|
+
<Menu.Dropdown>
|
|
29
|
+
{props.items.map((item) => (
|
|
30
|
+
<ToolbarDropdownItem key={item.text} {...item} />
|
|
31
|
+
))}
|
|
32
|
+
</Menu.Dropdown>
|
|
33
|
+
</Menu>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Menu } from "@mantine/core";
|
|
2
|
+
import { IconType } from "react-icons";
|
|
3
|
+
import { TiTick } from "react-icons/ti";
|
|
4
|
+
import { MouseEvent } from "react";
|
|
5
|
+
|
|
6
|
+
export type ToolbarDropdownItemProps = {
|
|
7
|
+
onClick?: (e: MouseEvent) => void;
|
|
8
|
+
text: string;
|
|
9
|
+
icon?: IconType;
|
|
10
|
+
isSelected?: boolean;
|
|
11
|
+
children?: any;
|
|
12
|
+
isDisabled?: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function ToolbarDropdownItem(props: ToolbarDropdownItemProps) {
|
|
16
|
+
const ItemIcon = props.icon;
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<Menu.Item
|
|
20
|
+
key={props.text}
|
|
21
|
+
onClick={props.onClick}
|
|
22
|
+
icon={ItemIcon && <ItemIcon size={16} />}
|
|
23
|
+
rightSection={
|
|
24
|
+
props.isSelected ? (
|
|
25
|
+
<TiTick size={16} />
|
|
26
|
+
) : (
|
|
27
|
+
// Ensures space for tick even if item isn't currently selected.
|
|
28
|
+
<div style={{ width: "16px", padding: "0" }} />
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
disabled={props.isDisabled}>
|
|
32
|
+
{props.text}
|
|
33
|
+
</Menu.Item>
|
|
34
|
+
);
|
|
35
|
+
}
|