@blocknote/core 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/README.md +1 -1
  2. package/dist/blocknote.js +3453 -2421
  3. package/dist/blocknote.js.map +1 -1
  4. package/dist/blocknote.umd.cjs +35 -71
  5. package/dist/blocknote.umd.cjs.map +1 -1
  6. package/dist/style.css +1 -1
  7. package/package.json +7 -6
  8. package/src/BlockNoteTheme.ts +150 -0
  9. package/src/extensions/Blocks/BlockAttributes.ts +12 -0
  10. package/src/extensions/Blocks/MultipleNodeSelection.ts +87 -0
  11. package/src/extensions/Blocks/PreviousBlockTypePlugin.ts +8 -2
  12. package/src/extensions/Blocks/nodes/Block.module.css +37 -37
  13. package/src/extensions/Blocks/nodes/Block.ts +77 -32
  14. package/src/extensions/Blocks/nodes/BlockGroup.ts +18 -1
  15. package/src/extensions/Blocks/nodes/Content.ts +14 -1
  16. package/src/extensions/BubbleMenu/BubbleMenuExtension.tsx +8 -1
  17. package/src/extensions/BubbleMenu/component/BubbleMenu.tsx +116 -88
  18. package/src/extensions/BubbleMenu/component/LinkToolbarButton.tsx +8 -8
  19. package/src/extensions/DraggableBlocks/DraggableBlocksPlugin.tsx +143 -33
  20. package/src/extensions/DraggableBlocks/components/DragHandle.tsx +14 -19
  21. package/src/extensions/DraggableBlocks/components/DragHandleMenu.tsx +8 -7
  22. package/src/extensions/Hyperlinks/HyperlinkMenuPlugin.tsx +31 -66
  23. package/src/extensions/Hyperlinks/menus/EditHyperlinkMenu.tsx +44 -0
  24. package/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItem.tsx +34 -0
  25. package/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItemIcon.tsx +31 -0
  26. package/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItemInput.tsx +40 -0
  27. package/src/extensions/Hyperlinks/menus/HoverHyperlinkMenu.tsx +37 -0
  28. package/src/extensions/Hyperlinks/menus/HyperlinkMenu.tsx +63 -0
  29. package/src/extensions/SlashMenu/SlashMenuItem.ts +3 -1
  30. package/src/extensions/SlashMenu/defaultCommands.tsx +4 -4
  31. package/src/shared/components/toolbar/Toolbar.tsx +8 -3
  32. package/src/shared/components/toolbar/ToolbarButton.tsx +57 -0
  33. package/src/shared/components/toolbar/ToolbarDropdown.tsx +35 -0
  34. package/src/shared/components/toolbar/ToolbarDropdownItem.tsx +35 -0
  35. package/src/shared/components/toolbar/ToolbarDropdownTarget.tsx +31 -0
  36. package/src/shared/plugins/suggestion/SuggestionItem.ts +3 -1
  37. package/src/shared/plugins/suggestion/{SuggestionListReactRenderer.ts → SuggestionListReactRenderer.tsx} +13 -4
  38. package/src/shared/plugins/suggestion/components/SuggestionGroup.tsx +6 -93
  39. package/src/shared/plugins/suggestion/components/SuggestionGroupItem.tsx +82 -0
  40. package/src/shared/plugins/suggestion/components/SuggestionList.tsx +24 -23
  41. package/src/utils.ts +12 -0
  42. package/types/src/BlockNoteTheme.d.ts +2 -0
  43. package/types/src/commands/indentation.d.ts +2 -0
  44. package/types/src/extensions/Blocks/BlockAttributes.d.ts +2 -0
  45. package/types/src/extensions/Blocks/MultipleNodeSelection.d.ts +24 -0
  46. package/types/src/extensions/Blocks/nodes/Block.d.ts +1 -1
  47. package/types/src/extensions/BubbleMenu/component/LinkToolbarButton.d.ts +2 -2
  48. package/types/src/extensions/Hyperlinks/menus/EditHyperlinkMenu.d.ts +11 -0
  49. package/types/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItem.d.ts +13 -0
  50. package/types/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItemIcon.d.ts +8 -0
  51. package/types/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItemInput.d.ts +9 -0
  52. package/types/src/extensions/Hyperlinks/menus/HoverHyperlinkMenu.d.ts +12 -0
  53. package/types/src/extensions/Hyperlinks/menus/HyperlinkMenu.d.ts +21 -0
  54. package/types/src/extensions/Hyperlinks/menus/helpers/PanelTextInput.d.ts +39 -0
  55. package/types/src/extensions/Hyperlinks/menus/helpers/PanelTextInputStyles.d.ts +3 -0
  56. package/types/src/extensions/Hyperlinks/menus/helpers/ToolbarComponent.d.ts +13 -0
  57. package/types/src/extensions/SlashMenu/SlashMenuItem.d.ts +4 -7
  58. package/types/src/nodes/ChildgroupNode.d.ts +28 -0
  59. package/types/src/nodes/patchNodes.d.ts +1 -0
  60. package/types/src/plugins/TreeViewPlugin/index.d.ts +2 -0
  61. package/types/src/plugins/animation.d.ts +2 -0
  62. package/types/src/react/BlockNoteComposer.d.ts +17 -0
  63. package/types/src/react/BlockNotePlugin.d.ts +1 -0
  64. package/types/src/react/index.d.ts +3 -0
  65. package/types/src/react/useBlockNoteSetup.d.ts +2 -0
  66. package/types/src/registerBlockNote.d.ts +2 -0
  67. package/types/src/shared/components/toolbar/SimpleToolbarButton.d.ts +2 -3
  68. package/types/src/shared/components/toolbar/SimpleToolbarDropdown.d.ts +11 -0
  69. package/types/src/shared/components/toolbar/SimpleToolbarDropdownItem.d.ts +11 -0
  70. package/types/src/shared/components/toolbar/Toolbar.d.ts +2 -2
  71. package/types/src/shared/components/toolbar/ToolbarButton.d.ts +15 -0
  72. package/types/src/shared/components/toolbar/ToolbarDropdown.d.ts +17 -0
  73. package/types/src/shared/components/toolbar/ToolbarDropdownItem.d.ts +11 -0
  74. package/types/src/shared/components/toolbar/ToolbarDropdownTarget.d.ts +8 -0
  75. package/types/src/shared/plugins/suggestion/SuggestionItem.d.ts +2 -4
  76. package/types/src/shared/plugins/suggestion/components/SuggestionGroupItem.d.ts +9 -0
  77. package/types/src/shared/plugins/suggestion/components/SuggestionList.d.ts +0 -15
  78. package/types/src/themes/BlockNoteEditorTheme.d.ts +11 -0
  79. package/types/src/utils.d.ts +2 -0
  80. package/src/extensions/BubbleMenu/component/DropdownBlockItem.module.css +0 -13
  81. package/src/extensions/BubbleMenu/component/DropdownBlockItem.tsx +0 -25
  82. package/src/extensions/DraggableBlocks/components/DragHandle.module.css +0 -33
  83. package/src/extensions/DraggableBlocks/components/DragHandleMenu.module.css +0 -10
  84. package/src/extensions/Hyperlinks/menus/HyperlinkBasicMenu.tsx +0 -59
  85. package/src/extensions/Hyperlinks/menus/HyperlinkEditMenu.tsx +0 -72
  86. package/src/extensions/Hyperlinks/menus/atlaskit/PanelTextInput.tsx +0 -173
  87. package/src/extensions/Hyperlinks/menus/atlaskit/PanelTextInputStyles.ts +0 -36
  88. package/src/extensions/Hyperlinks/menus/atlaskit/README.md +0 -1
  89. package/src/extensions/Hyperlinks/menus/atlaskit/ToolbarComponent.tsx +0 -61
  90. package/src/extensions/helpers/formatKeyboardShortcut.ts +0 -9
  91. package/src/lib/atlaskit/browser.ts +0 -47
  92. package/src/shared/components/toolbar/SimpleToolbarButton.module.css +0 -13
  93. package/src/shared/components/toolbar/SimpleToolbarButton.tsx +0 -56
  94. package/src/shared/components/toolbar/Toolbar.module.css +0 -10
  95. package/src/shared/components/toolbar/ToolbarSeparator.module.css +0 -13
  96. package/src/shared/components/toolbar/ToolbarSeparator.tsx +0 -7
  97. package/src/shared/plugins/suggestion/components/SuggestionGroup.module.css +0 -45
  98. 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 styles from "./DragHandle.module.css";
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
- <AiOutlinePlus
89
- size={24}
90
- fillOpacity={"0.25"}
91
- className={styles.dragHandleAdd}
92
- onClick={onAddClick}
93
- />
94
- <Tippy
95
- content={<DragHandleMenu onDelete={onDelete} />}
96
- placement={"left"}
97
- trigger={"click"}
98
- duration={0}
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 styles from "./DragHandleMenu.module.css";
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
- <div className={styles.menuList}>
11
- <MenuGroup>
12
- <ButtonItem onClick={props.onDelete}>Delete</ButtonItem>
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 rootStyles from "../../root.module.css";
7
- import { HyperlinkBasicMenu } from "./menus/HyperlinkBasicMenu";
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
- // the hyperlinkEditMenu will be positioned at the same place as hyperlinkBasicMenu
139
- // this is achieved by making this editMenu a property of the basicMenu below
140
- // and returning this editMenu directly by introducing another isEditing state
141
- const hyperlinkEditMenu = tippyWrapperHyperlinkEditMenu({
142
- anchorPos,
143
- text,
144
- url,
145
- onSubmit: editHandler,
146
- });
147
-
148
- const hyperlinkBasicMenu = (
149
- <Tippy
150
- key={nextTippyKey + ""} // it could be tippy has "hidden" itself after mouseout. We use a key to get a new instance with a clean state.
151
- getReferenceClientRect={() => anchorPos as any}
152
- content={
153
- <HyperlinkBasicMenu
154
- editMenu={hyperlinkEditMenu}
155
- removeHandler={removeHandler}
156
- href={url}></HyperlinkBasicMenu>
157
- }
158
- onHide={() => {
159
- nextTippyKey++;
160
- menuState = "hidden";
161
- }}
162
- aria={{ expanded: false }}
163
- interactive={true}
164
- interactiveBorder={30}
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(hyperlinkBasicMenu, fakeRenderTarget);
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?: React.ComponentType<{ className: string }>,
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 "../helpers/formatKeyboardShortcut";
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"],
@@ -1,5 +1,10 @@
1
- import styles from "./Toolbar.module.css";
1
+ import { createStyles, Group } from "@mantine/core";
2
+ import { ReactNode } from "react";
2
3
 
3
- export const Toolbar = (props: { children: any }) => {
4
- return <div className={styles.toolbar}>{props.children}</div>;
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
+ }