@blocknote/core 0.1.0-alpha.3
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 +99 -0
- package/dist/blocknote.js +4485 -0
- package/dist/blocknote.js.map +1 -0
- package/dist/blocknote.umd.cjs +90 -0
- package/dist/blocknote.umd.cjs.map +1 -0
- package/dist/style.css +1 -0
- package/package.json +109 -0
- package/src/BlockNoteExtensions.ts +90 -0
- package/src/EditorContent.tsx +1 -0
- package/src/assets/inter-v12-latin/inter-v12-latin-100.woff +0 -0
- package/src/assets/inter-v12-latin/inter-v12-latin-100.woff2 +0 -0
- package/src/assets/inter-v12-latin/inter-v12-latin-200.woff +0 -0
- package/src/assets/inter-v12-latin/inter-v12-latin-200.woff2 +0 -0
- package/src/assets/inter-v12-latin/inter-v12-latin-300.woff +0 -0
- package/src/assets/inter-v12-latin/inter-v12-latin-300.woff2 +0 -0
- package/src/assets/inter-v12-latin/inter-v12-latin-500.woff +0 -0
- package/src/assets/inter-v12-latin/inter-v12-latin-500.woff2 +0 -0
- package/src/assets/inter-v12-latin/inter-v12-latin-600.woff +0 -0
- package/src/assets/inter-v12-latin/inter-v12-latin-600.woff2 +0 -0
- package/src/assets/inter-v12-latin/inter-v12-latin-700.woff +0 -0
- package/src/assets/inter-v12-latin/inter-v12-latin-700.woff2 +0 -0
- package/src/assets/inter-v12-latin/inter-v12-latin-800.woff +0 -0
- package/src/assets/inter-v12-latin/inter-v12-latin-800.woff2 +0 -0
- package/src/assets/inter-v12-latin/inter-v12-latin-900.woff +0 -0
- package/src/assets/inter-v12-latin/inter-v12-latin-900.woff2 +0 -0
- package/src/assets/inter-v12-latin/inter-v12-latin-regular.woff +0 -0
- package/src/assets/inter-v12-latin/inter-v12-latin-regular.woff2 +0 -0
- package/src/editor.module.css +3 -0
- package/src/extensions/Blocks/OrderedListPlugin.ts +46 -0
- package/src/extensions/Blocks/PreviousBlockTypePlugin.ts +146 -0
- package/src/extensions/Blocks/commands/joinBackward.ts +274 -0
- package/src/extensions/Blocks/helpers/findBlock.ts +3 -0
- package/src/extensions/Blocks/helpers/setBlockHeading.ts +30 -0
- package/src/extensions/Blocks/index.ts +15 -0
- package/src/extensions/Blocks/nodes/Block.module.css +226 -0
- package/src/extensions/Blocks/nodes/Block.ts +390 -0
- package/src/extensions/Blocks/nodes/BlockGroup.ts +28 -0
- package/src/extensions/Blocks/nodes/Content.ts +50 -0
- package/src/extensions/Blocks/nodes/README.md +26 -0
- package/src/extensions/Blocks/rule.ts +48 -0
- package/src/extensions/BubbleMenu/BubbleMenuExtension.tsx +28 -0
- package/src/extensions/BubbleMenu/BubbleMenuPlugin.ts +245 -0
- package/src/extensions/BubbleMenu/component/BubbleMenu.tsx +216 -0
- package/src/extensions/BubbleMenu/component/DropdownBlockItem.module.css +13 -0
- package/src/extensions/BubbleMenu/component/DropdownBlockItem.tsx +25 -0
- package/src/extensions/BubbleMenu/component/LinkToolbarButton.tsx +67 -0
- package/src/extensions/DraggableBlocks/DraggableBlocksExtension.ts +15 -0
- package/src/extensions/DraggableBlocks/DraggableBlocksPlugin.tsx +266 -0
- package/src/extensions/DraggableBlocks/components/DragHandle.module.css +33 -0
- package/src/extensions/DraggableBlocks/components/DragHandle.tsx +108 -0
- package/src/extensions/DraggableBlocks/components/DragHandleMenu.module.css +10 -0
- package/src/extensions/DraggableBlocks/components/DragHandleMenu.tsx +18 -0
- package/src/extensions/Hyperlinks/HyperlinkMark.tsx +16 -0
- package/src/extensions/Hyperlinks/HyperlinkMenuPlugin.tsx +200 -0
- package/src/extensions/Hyperlinks/menus/HyperlinkBasicMenu.tsx +59 -0
- package/src/extensions/Hyperlinks/menus/HyperlinkEditMenu.tsx +72 -0
- package/src/extensions/Hyperlinks/menus/atlaskit/PanelTextInput.tsx +173 -0
- package/src/extensions/Hyperlinks/menus/atlaskit/PanelTextInputStyles.ts +36 -0
- package/src/extensions/Hyperlinks/menus/atlaskit/README.md +1 -0
- package/src/extensions/Hyperlinks/menus/atlaskit/ToolbarComponent.tsx +61 -0
- package/src/extensions/Paragraph/FixedParagraph.ts +12 -0
- package/src/extensions/Placeholder/PlaceholderExtension.ts +127 -0
- package/src/extensions/SlashMenu/SlashMenuExtension.ts +43 -0
- package/src/extensions/SlashMenu/SlashMenuItem.ts +56 -0
- package/src/extensions/SlashMenu/defaultCommands.tsx +229 -0
- package/src/extensions/SlashMenu/index.ts +11 -0
- package/src/extensions/TrailingNode/TrailingNodeExtension.ts +70 -0
- package/src/extensions/UniqueID/UniqueID.ts +281 -0
- package/src/extensions/helpers/formatKeyboardShortcut.ts +9 -0
- package/src/fonts-inter.css +94 -0
- package/src/globals.css +28 -0
- package/src/index.ts +5 -0
- package/src/lib/atlaskit/browser.ts +47 -0
- package/src/root.module.css +19 -0
- package/src/shared/components/toolbar/SimpleToolbarButton.module.css +13 -0
- package/src/shared/components/toolbar/SimpleToolbarButton.tsx +56 -0
- package/src/shared/components/toolbar/Toolbar.module.css +10 -0
- package/src/shared/components/toolbar/Toolbar.tsx +5 -0
- package/src/shared/components/toolbar/ToolbarSeparator.module.css +13 -0
- package/src/shared/components/toolbar/ToolbarSeparator.tsx +7 -0
- package/src/shared/components/tooltip/TooltipContent.module.css +15 -0
- package/src/shared/components/tooltip/TooltipContent.tsx +23 -0
- package/src/shared/hooks/useEditorForceUpdate.tsx +30 -0
- package/src/shared/plugins/suggestion/SuggestionItem.ts +31 -0
- package/src/shared/plugins/suggestion/SuggestionListReactRenderer.ts +227 -0
- package/src/shared/plugins/suggestion/SuggestionPlugin.ts +365 -0
- package/src/shared/plugins/suggestion/components/SuggestionGroup.module.css +45 -0
- package/src/shared/plugins/suggestion/components/SuggestionGroup.tsx +134 -0
- package/src/shared/plugins/suggestion/components/SuggestionList.module.css +10 -0
- package/src/shared/plugins/suggestion/components/SuggestionList.tsx +91 -0
- package/src/style.css +7 -0
- package/src/useEditor.ts +47 -0
- package/src/vite-env.d.ts +1 -0
- package/types/src/BlockNoteExtensions.d.ts +4 -0
- package/types/src/EditorContent.d.ts +1 -0
- package/types/src/extensions/Blocks/OrderedListPlugin.d.ts +2 -0
- package/types/src/extensions/Blocks/PreviousBlockTypePlugin.d.ts +13 -0
- package/types/src/extensions/Blocks/commands/joinBackward.d.ts +14 -0
- package/types/src/extensions/Blocks/helpers/findBlock.d.ts +6 -0
- package/types/src/extensions/Blocks/helpers/setBlockHeading.d.ts +5 -0
- package/types/src/extensions/Blocks/index.d.ts +1 -0
- package/types/src/extensions/Blocks/nodes/Block.d.ts +32 -0
- package/types/src/extensions/Blocks/nodes/BlockGroup.d.ts +2 -0
- package/types/src/extensions/Blocks/nodes/Content.d.ts +5 -0
- package/types/src/extensions/Blocks/rule.d.ts +16 -0
- package/types/src/extensions/BubbleMenu/BubbleMenuExtension.d.ts +5 -0
- package/types/src/extensions/BubbleMenu/BubbleMenuPlugin.d.ts +46 -0
- package/types/src/extensions/BubbleMenu/component/BubbleMenu.d.ts +5 -0
- package/types/src/extensions/BubbleMenu/component/DropdownBlockItem.d.ts +10 -0
- package/types/src/extensions/BubbleMenu/component/LinkToolbarButton.d.ts +11 -0
- package/types/src/extensions/DraggableBlocks/DraggableBlocksExtension.d.ts +7 -0
- package/types/src/extensions/DraggableBlocks/DraggableBlocksPlugin.d.ts +18 -0
- package/types/src/extensions/DraggableBlocks/components/DragHandle.d.ts +12 -0
- package/types/src/extensions/DraggableBlocks/components/DragHandleMenu.d.ts +6 -0
- package/types/src/extensions/Hyperlinks/HyperlinkMark.d.ts +7 -0
- package/types/src/extensions/Hyperlinks/HyperlinkMenuPlugin.d.ts +2 -0
- package/types/src/extensions/Hyperlinks/menus/HyperlinkBasicMenu.d.ts +12 -0
- package/types/src/extensions/Hyperlinks/menus/HyperlinkEditMenu.d.ts +10 -0
- package/types/src/extensions/Hyperlinks/menus/atlaskit/PanelTextInput.d.ts +39 -0
- package/types/src/extensions/Hyperlinks/menus/atlaskit/PanelTextInputStyles.d.ts +1 -0
- package/types/src/extensions/Hyperlinks/menus/atlaskit/ToolbarComponent.d.ts +11 -0
- package/types/src/extensions/Paragraph/FixedParagraph.d.ts +1 -0
- package/types/src/extensions/Placeholder/PlaceholderExtension.d.ts +25 -0
- package/types/src/extensions/SlashMenu/SlashMenuExtension.d.ts +10 -0
- package/types/src/extensions/SlashMenu/SlashMenuItem.d.ts +43 -0
- package/types/src/extensions/SlashMenu/defaultCommands.d.ts +8 -0
- package/types/src/extensions/SlashMenu/index.d.ts +5 -0
- package/types/src/extensions/TrailingNode/TrailingNodeExtension.d.ts +10 -0
- package/types/src/extensions/UniqueID/UniqueID.d.ts +3 -0
- package/types/src/extensions/helpers/formatKeyboardShortcut.d.ts +1 -0
- package/types/src/index.d.ts +4 -0
- package/types/src/lib/atlaskit/browser.d.ts +12 -0
- package/types/src/shared/components/toolbar/SimpleToolbarButton.d.ts +16 -0
- package/types/src/shared/components/toolbar/Toolbar.d.ts +4 -0
- package/types/src/shared/components/toolbar/ToolbarSeparator.d.ts +2 -0
- package/types/src/shared/components/tooltip/TooltipContent.d.ts +15 -0
- package/types/src/shared/hooks/useEditorForceUpdate.d.ts +2 -0
- package/types/src/shared/plugins/suggestion/SuggestionItem.d.ts +29 -0
- package/types/src/shared/plugins/suggestion/SuggestionListReactRenderer.d.ts +71 -0
- package/types/src/shared/plugins/suggestion/SuggestionPlugin.d.ts +74 -0
- package/types/src/shared/plugins/suggestion/components/SuggestionGroup.d.ts +23 -0
- package/types/src/shared/plugins/suggestion/components/SuggestionList.d.ts +26 -0
- package/types/src/useEditor.d.ts +8 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { RiExternalLinkFill, RiLinkUnlink } from "react-icons/ri";
|
|
3
|
+
import { SimpleToolbarButton } from "../../../shared/components/toolbar/SimpleToolbarButton";
|
|
4
|
+
import { Toolbar } from "../../../shared/components/toolbar/Toolbar";
|
|
5
|
+
import { ToolbarSeparator } from "../../../shared/components/toolbar/ToolbarSeparator";
|
|
6
|
+
import React from "react";
|
|
7
|
+
|
|
8
|
+
type HyperlinkMenuProps = {
|
|
9
|
+
href: string;
|
|
10
|
+
removeHandler: () => void;
|
|
11
|
+
editMenu: React.ReactElement;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A hyperlink menu shown when an anchor is hovered over.
|
|
16
|
+
* It shows options to edit / remove / open the link
|
|
17
|
+
*/
|
|
18
|
+
export const HyperlinkBasicMenu = (props: HyperlinkMenuProps) => {
|
|
19
|
+
const [isEditing, setIsEditing] = useState(false);
|
|
20
|
+
if (isEditing) {
|
|
21
|
+
return props.editMenu;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function onEditClick(e: React.MouseEvent) {
|
|
25
|
+
setIsEditing(true);
|
|
26
|
+
e.stopPropagation();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<Toolbar>
|
|
31
|
+
<SimpleToolbarButton
|
|
32
|
+
mainTooltip="Edit"
|
|
33
|
+
isSelected={false}
|
|
34
|
+
onClick={onEditClick}>
|
|
35
|
+
Edit Link
|
|
36
|
+
</SimpleToolbarButton>
|
|
37
|
+
|
|
38
|
+
<ToolbarSeparator />
|
|
39
|
+
|
|
40
|
+
<SimpleToolbarButton
|
|
41
|
+
mainTooltip="Open in new tab"
|
|
42
|
+
isSelected={false}
|
|
43
|
+
onClick={() => {
|
|
44
|
+
window.open(props.href, "_blank");
|
|
45
|
+
}}
|
|
46
|
+
icon={RiExternalLinkFill}
|
|
47
|
+
/>
|
|
48
|
+
|
|
49
|
+
<ToolbarSeparator />
|
|
50
|
+
|
|
51
|
+
<SimpleToolbarButton
|
|
52
|
+
mainTooltip="Remove link"
|
|
53
|
+
isSelected={false}
|
|
54
|
+
onClick={props.removeHandler}
|
|
55
|
+
icon={RiLinkUnlink}
|
|
56
|
+
/>
|
|
57
|
+
</Toolbar>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import Tippy from "@tippyjs/react";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { RiLink, RiText } from "react-icons/ri";
|
|
4
|
+
import { TooltipContent } from "../../../shared/components/tooltip/TooltipContent";
|
|
5
|
+
import PanelTextInput from "./atlaskit/PanelTextInput";
|
|
6
|
+
import {
|
|
7
|
+
Container,
|
|
8
|
+
ContainerWrapper,
|
|
9
|
+
IconWrapper,
|
|
10
|
+
TextInputWrapper,
|
|
11
|
+
UrlInputWrapper,
|
|
12
|
+
} from "./atlaskit/ToolbarComponent";
|
|
13
|
+
|
|
14
|
+
export type HyperlinkEditorMenuProps = {
|
|
15
|
+
url: string;
|
|
16
|
+
text: string;
|
|
17
|
+
onSubmit: (url: string, text: string) => void;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The sub menu for editing an anchor element
|
|
22
|
+
*/
|
|
23
|
+
export const HyperlinkEditMenu = (props: HyperlinkEditorMenuProps) => {
|
|
24
|
+
const [url, setUrl] = useState(props.url);
|
|
25
|
+
const [text, setText] = useState(props.text);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<ContainerWrapper>
|
|
29
|
+
<Container provider={false}>
|
|
30
|
+
<UrlInputWrapper>
|
|
31
|
+
<IconWrapper>
|
|
32
|
+
<Tippy
|
|
33
|
+
content={<TooltipContent mainTooltip="Edit URL" />}
|
|
34
|
+
placement="left">
|
|
35
|
+
<span>
|
|
36
|
+
<RiLink size={20}></RiLink>
|
|
37
|
+
</span>
|
|
38
|
+
</Tippy>
|
|
39
|
+
</IconWrapper>
|
|
40
|
+
<PanelTextInput
|
|
41
|
+
defaultValue={url}
|
|
42
|
+
autoFocus={true}
|
|
43
|
+
onSubmit={(value) => {
|
|
44
|
+
props.onSubmit(value, text);
|
|
45
|
+
}}
|
|
46
|
+
onChange={(value) => {
|
|
47
|
+
setUrl(value);
|
|
48
|
+
}}></PanelTextInput>
|
|
49
|
+
</UrlInputWrapper>
|
|
50
|
+
<TextInputWrapper>
|
|
51
|
+
<IconWrapper>
|
|
52
|
+
<Tippy
|
|
53
|
+
content={<TooltipContent mainTooltip="Edit title" />}
|
|
54
|
+
placement="left">
|
|
55
|
+
<span>
|
|
56
|
+
<RiText size={20} />
|
|
57
|
+
</span>
|
|
58
|
+
</Tippy>
|
|
59
|
+
</IconWrapper>
|
|
60
|
+
<PanelTextInput
|
|
61
|
+
defaultValue={text!}
|
|
62
|
+
onSubmit={(value) => {
|
|
63
|
+
props.onSubmit(url, value);
|
|
64
|
+
}}
|
|
65
|
+
onChange={(value) => {
|
|
66
|
+
setText(value);
|
|
67
|
+
}}></PanelTextInput>
|
|
68
|
+
</TextInputWrapper>
|
|
69
|
+
</Container>
|
|
70
|
+
</ContainerWrapper>
|
|
71
|
+
);
|
|
72
|
+
};
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { KeyboardEvent, PureComponent } from "react";
|
|
2
|
+
import { Input } from "./PanelTextInputStyles";
|
|
3
|
+
import { FocusEvent } from "react";
|
|
4
|
+
import browser from "../../../../lib/atlaskit/browser";
|
|
5
|
+
|
|
6
|
+
// code adapted from https://bitbucket.org/atlassian/design-system-mirror/src/master/editor/editor-core/src/ui/PanelTextInput/index.tsx
|
|
7
|
+
|
|
8
|
+
export interface Props {
|
|
9
|
+
autoFocus?: boolean | FocusOptions;
|
|
10
|
+
defaultValue?: string;
|
|
11
|
+
onChange?: (value: string) => void;
|
|
12
|
+
onSubmit?: (value: string) => void;
|
|
13
|
+
onCancel?: (e: KeyboardEvent) => void;
|
|
14
|
+
placeholder?: string;
|
|
15
|
+
onMouseDown?: Function;
|
|
16
|
+
onKeyDown?: (e: KeyboardEvent<any>) => void;
|
|
17
|
+
// overrides default browser undo behaviour (cmd/ctrl + z) with that function
|
|
18
|
+
onUndo?: Function;
|
|
19
|
+
// overrides default browser redo behaviour (cm + shift + z / ctrl + y) with that function
|
|
20
|
+
onRedo?: Function;
|
|
21
|
+
onBlur?: Function;
|
|
22
|
+
width?: number;
|
|
23
|
+
maxLength?: number;
|
|
24
|
+
testId?: string;
|
|
25
|
+
ariaLabel?: string;
|
|
26
|
+
id?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface State {
|
|
30
|
+
value?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const KeyZCode = 90;
|
|
34
|
+
const KeyYCode = 89;
|
|
35
|
+
|
|
36
|
+
// returns an <Input> Component, used as an input field in HyperlinkEditMenu
|
|
37
|
+
export default class PanelTextInput extends PureComponent<Props, State> {
|
|
38
|
+
private input?: HTMLInputElement;
|
|
39
|
+
private focusTimeoutId: number | undefined;
|
|
40
|
+
|
|
41
|
+
constructor(props: Props) {
|
|
42
|
+
super(props);
|
|
43
|
+
|
|
44
|
+
this.state = {
|
|
45
|
+
value: props.defaultValue || "",
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
UNSAFE_componentWillReceiveProps(nextProps: Props) {
|
|
50
|
+
if (nextProps.defaultValue !== this.props.defaultValue) {
|
|
51
|
+
this.setState({
|
|
52
|
+
value: nextProps.defaultValue,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
componentWillUnmount() {
|
|
58
|
+
window.clearTimeout(this.focusTimeoutId);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
onMouseDown = () => {
|
|
62
|
+
const { onMouseDown } = this.props;
|
|
63
|
+
if (onMouseDown) {
|
|
64
|
+
onMouseDown();
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
onBlur = (e: FocusEvent<any>) => {
|
|
69
|
+
const { onBlur } = this.props;
|
|
70
|
+
if (onBlur) {
|
|
71
|
+
onBlur(e);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
render() {
|
|
76
|
+
const { placeholder, width, maxLength, testId, ariaLabel } = this.props;
|
|
77
|
+
const { value } = this.state;
|
|
78
|
+
return (
|
|
79
|
+
<Input
|
|
80
|
+
data-testid={testId || ""}
|
|
81
|
+
type="text"
|
|
82
|
+
placeholder={placeholder}
|
|
83
|
+
value={value}
|
|
84
|
+
onChange={this.handleChange}
|
|
85
|
+
onKeyDown={this.handleKeydown}
|
|
86
|
+
onMouseDown={this.onMouseDown}
|
|
87
|
+
onBlur={this.onBlur}
|
|
88
|
+
ref={this.handleRef}
|
|
89
|
+
width={width}
|
|
90
|
+
maxLength={maxLength}
|
|
91
|
+
aria-label={ariaLabel}
|
|
92
|
+
id={this.props.id}
|
|
93
|
+
style={{ fontSize: `0.8rem` }}
|
|
94
|
+
/>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
focus() {
|
|
99
|
+
const { input } = this;
|
|
100
|
+
if (input) {
|
|
101
|
+
const focusOpts =
|
|
102
|
+
typeof this.props.autoFocus === "object" ? this.props.autoFocus : {};
|
|
103
|
+
input.focus(focusOpts);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private handleChange = () => {
|
|
108
|
+
const { onChange } = this.props;
|
|
109
|
+
if (this.input) {
|
|
110
|
+
this.setState({
|
|
111
|
+
value: this.input.value,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (onChange && this.input) {
|
|
116
|
+
onChange(this.input.value);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
private handleKeydown = (e: KeyboardEvent<any>) => {
|
|
121
|
+
const { onUndo, onRedo, onSubmit, onCancel } = this.props;
|
|
122
|
+
if (e.keyCode === 13 && onSubmit) {
|
|
123
|
+
e.preventDefault(); // Prevent from submitting if an editor is inside a form.
|
|
124
|
+
onSubmit(this.input!.value);
|
|
125
|
+
} else if (e.keyCode === 27 && onCancel) {
|
|
126
|
+
onCancel(e);
|
|
127
|
+
} else if (typeof onUndo === "function" && this.isUndoEvent(e)) {
|
|
128
|
+
e.preventDefault();
|
|
129
|
+
onUndo();
|
|
130
|
+
} else if (typeof onRedo === "function" && this.isRedoEvent(e)) {
|
|
131
|
+
e.preventDefault();
|
|
132
|
+
onRedo();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (this.props.onKeyDown) {
|
|
136
|
+
this.props.onKeyDown(e);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
private isUndoEvent(event: KeyboardEvent<any>) {
|
|
141
|
+
return (
|
|
142
|
+
event.keyCode === KeyZCode &&
|
|
143
|
+
// cmd + z for mac
|
|
144
|
+
((browser.mac && event.metaKey && !event.shiftKey) ||
|
|
145
|
+
// ctrl + z for non-mac
|
|
146
|
+
(!browser.mac && event.ctrlKey))
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private isRedoEvent(event: KeyboardEvent<any>) {
|
|
151
|
+
return (
|
|
152
|
+
// ctrl + y for non-mac
|
|
153
|
+
(!browser.mac && event.ctrlKey && event.keyCode === KeyYCode) ||
|
|
154
|
+
(browser.mac &&
|
|
155
|
+
event.metaKey &&
|
|
156
|
+
event.shiftKey &&
|
|
157
|
+
event.keyCode === KeyZCode) ||
|
|
158
|
+
(event.ctrlKey && event.shiftKey && event.keyCode === KeyZCode)
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private handleRef = (input: HTMLInputElement | null) => {
|
|
163
|
+
if (input instanceof HTMLInputElement) {
|
|
164
|
+
this.input = input;
|
|
165
|
+
if (this.props.autoFocus) {
|
|
166
|
+
// Need this to prevent jumping when we render TextInput inside Portal @see ED-2992
|
|
167
|
+
this.focusTimeoutId = window.setTimeout(() => this.focus());
|
|
168
|
+
}
|
|
169
|
+
} else {
|
|
170
|
+
this.input = undefined;
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { N400, N800 } from "@atlaskit/theme/colors";
|
|
2
|
+
import styled from "styled-components";
|
|
3
|
+
|
|
4
|
+
// code taken from https://bitbucket.org/atlassian/design-system-mirror/src/master/editor/editor-core/src/ui/PanelTextInput/styles.ts
|
|
5
|
+
|
|
6
|
+
// Normal .className gets overridden by input[type=text] hence this hack to produce input.className
|
|
7
|
+
export const Input = styled.input`
|
|
8
|
+
input& {
|
|
9
|
+
autofocus: true;
|
|
10
|
+
background: transparent;
|
|
11
|
+
border: 0;
|
|
12
|
+
border-radius: 0;
|
|
13
|
+
box-sizing: content-box;
|
|
14
|
+
color: ${N400};
|
|
15
|
+
flex-grow: 1;
|
|
16
|
+
font-size: 13px;
|
|
17
|
+
line-height: 20px;
|
|
18
|
+
padding: 0;
|
|
19
|
+
${(props) => (props.width ? `width: ${props.width}px` : "")};
|
|
20
|
+
min-width: 145px;
|
|
21
|
+
|
|
22
|
+
/* Hides IE10+ built-in [x] clear input button */
|
|
23
|
+
&::-ms-clear {
|
|
24
|
+
display: none;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
&:focus {
|
|
28
|
+
outline: none;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
&::placeholder {
|
|
32
|
+
color: ${N800};
|
|
33
|
+
opacity: 0.5;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Helper code copied from AtlasKit
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import styled, { css } from "styled-components";
|
|
2
|
+
import { N80, N30 } from "@atlaskit/theme/colors";
|
|
3
|
+
|
|
4
|
+
// code taken from https://bitbucket.org/atlassian/design-system-mirror/src/master/editor/editor-core/src/ui/LinkSearch/ToolbarComponents.tsx
|
|
5
|
+
// and https://bitbucket.org/atlassian/design-system-mirror/src/master/editor/editor-core/src/plugins/hyperlink/ui/HyperlinkAddToolbar/HyperlinkAddToolbar.tsx
|
|
6
|
+
|
|
7
|
+
export const RECENT_SEARCH_WIDTH_IN_PX = 420;
|
|
8
|
+
export const RECENT_SEARCH_WIDTH_WITHOUT_ITEMS_IN_PX = 360;
|
|
9
|
+
export const RECENT_SEARCH_HEIGHT_IN_PX = 360;
|
|
10
|
+
|
|
11
|
+
// These components below are mainly used in HyperlinkEditMenu as wrapper components for input fields
|
|
12
|
+
|
|
13
|
+
export const InputWrapper = `
|
|
14
|
+
display: flex;
|
|
15
|
+
line-height: 0;
|
|
16
|
+
padding: 4px 0;
|
|
17
|
+
align-items: center;
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
export const UrlInputWrapper = styled.div`
|
|
21
|
+
${InputWrapper}
|
|
22
|
+
border-bottom: none !important;
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
export const Container = styled.div`
|
|
26
|
+
width: ${RECENT_SEARCH_WIDTH_IN_PX}px;
|
|
27
|
+
display: flex;
|
|
28
|
+
flex-direction: column;
|
|
29
|
+
overflow: auto;
|
|
30
|
+
padding: 0;
|
|
31
|
+
|
|
32
|
+
${({ provider }: { provider: boolean }) =>
|
|
33
|
+
css`
|
|
34
|
+
width: ${provider
|
|
35
|
+
? RECENT_SEARCH_WIDTH_IN_PX
|
|
36
|
+
: RECENT_SEARCH_WIDTH_WITHOUT_ITEMS_IN_PX}px;
|
|
37
|
+
`};
|
|
38
|
+
line-height: initial;
|
|
39
|
+
`;
|
|
40
|
+
|
|
41
|
+
export const TextInputWrapper = styled.div`
|
|
42
|
+
${InputWrapper};
|
|
43
|
+
border-top: 1px solid ${N30};
|
|
44
|
+
`;
|
|
45
|
+
|
|
46
|
+
export const IconWrapper = styled.span`
|
|
47
|
+
color: ${N80};
|
|
48
|
+
padding: 3px 6px;
|
|
49
|
+
width: 32px;
|
|
50
|
+
`;
|
|
51
|
+
|
|
52
|
+
export const ContainerWrapper = styled.div`
|
|
53
|
+
background-color: white;
|
|
54
|
+
border-radius: 3px;
|
|
55
|
+
box-shadow: rgb(9 30 66 / 31%) 0px 0px 1px,
|
|
56
|
+
rgb(9 30 66 / 25%) 0px 4px 8px -2px;
|
|
57
|
+
padding: 3px 6px;
|
|
58
|
+
display: flex;
|
|
59
|
+
line-height: 1;
|
|
60
|
+
box-sizing: border-box;
|
|
61
|
+
`;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import Paragraph from "@tiptap/extension-paragraph";
|
|
2
|
+
|
|
3
|
+
// Override paragraph to disable "Mod-Alt-0" shortcut throw invalid content for doc type error
|
|
4
|
+
export const FixedParagraph = Paragraph.extend({
|
|
5
|
+
addKeyboardShortcuts: () => {
|
|
6
|
+
return {
|
|
7
|
+
"Mod-Alt-0": () => {
|
|
8
|
+
return false;
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
},
|
|
12
|
+
});
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { Editor, Extension } from "@tiptap/core";
|
|
2
|
+
import { Node as ProsemirrorNode } from "prosemirror-model";
|
|
3
|
+
import { Decoration, DecorationSet } from "prosemirror-view";
|
|
4
|
+
import { Plugin } from "prosemirror-state";
|
|
5
|
+
import { SlashMenuPluginKey } from "../SlashMenu/SlashMenuExtension";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* This is a modified version of the tiptap
|
|
9
|
+
* placeholder plugin, that also sets hasAnchorClass
|
|
10
|
+
*
|
|
11
|
+
* It does not set a data-placeholder (text is currently done in css)
|
|
12
|
+
*
|
|
13
|
+
*/
|
|
14
|
+
export interface PlaceholderOptions {
|
|
15
|
+
emptyEditorClass: string;
|
|
16
|
+
emptyNodeClass: string;
|
|
17
|
+
isFilterClass: string;
|
|
18
|
+
hasAnchorClass: string;
|
|
19
|
+
placeholder:
|
|
20
|
+
| ((PlaceholderProps: {
|
|
21
|
+
editor: Editor;
|
|
22
|
+
node: ProsemirrorNode;
|
|
23
|
+
pos: number;
|
|
24
|
+
hasAnchor: boolean;
|
|
25
|
+
}) => string)
|
|
26
|
+
| string;
|
|
27
|
+
showOnlyWhenEditable: boolean;
|
|
28
|
+
showOnlyCurrent: boolean;
|
|
29
|
+
includeChildren: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const Placeholder = Extension.create<PlaceholderOptions>({
|
|
33
|
+
name: "placeholder",
|
|
34
|
+
|
|
35
|
+
addOptions() {
|
|
36
|
+
return {
|
|
37
|
+
emptyEditorClass: "is-editor-empty",
|
|
38
|
+
emptyNodeClass: "is-empty",
|
|
39
|
+
isFilterClass: "is-filter",
|
|
40
|
+
hasAnchorClass: "has-anchor",
|
|
41
|
+
placeholder: "Write something …",
|
|
42
|
+
showOnlyWhenEditable: true,
|
|
43
|
+
showOnlyCurrent: true,
|
|
44
|
+
includeChildren: false,
|
|
45
|
+
};
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
addProseMirrorPlugins() {
|
|
49
|
+
return [
|
|
50
|
+
new Plugin({
|
|
51
|
+
props: {
|
|
52
|
+
decorations: (state) => {
|
|
53
|
+
const { doc, selection } = state;
|
|
54
|
+
// Get state of slash menu
|
|
55
|
+
const menuState = SlashMenuPluginKey.getState(state);
|
|
56
|
+
const active =
|
|
57
|
+
this.editor.isEditable || !this.options.showOnlyWhenEditable;
|
|
58
|
+
const { anchor } = selection;
|
|
59
|
+
const decorations: Decoration[] = [];
|
|
60
|
+
|
|
61
|
+
if (!active) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
doc.descendants((node, pos) => {
|
|
66
|
+
const hasAnchor = anchor >= pos && anchor <= pos + node.nodeSize;
|
|
67
|
+
const isEmpty = !node.isLeaf && !node.childCount;
|
|
68
|
+
|
|
69
|
+
if ((hasAnchor || !this.options.showOnlyCurrent) && isEmpty) {
|
|
70
|
+
const classes = [this.options.emptyNodeClass];
|
|
71
|
+
|
|
72
|
+
if (this.editor.isEmpty) {
|
|
73
|
+
classes.push(this.options.emptyEditorClass);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (hasAnchor) {
|
|
77
|
+
classes.push(this.options.hasAnchorClass);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// If slash menu is of drag type and active, show the filter placeholder
|
|
81
|
+
if (menuState.type === "drag" && menuState.active) {
|
|
82
|
+
classes.push(this.options.isFilterClass);
|
|
83
|
+
}
|
|
84
|
+
// using widget, didn't work (caret position bug)
|
|
85
|
+
// const decoration = Decoration.widget(
|
|
86
|
+
// pos + 1,
|
|
87
|
+
// () => {
|
|
88
|
+
// const el = document.createElement("span");
|
|
89
|
+
// el.innerText = "hello";
|
|
90
|
+
// return el;
|
|
91
|
+
// },
|
|
92
|
+
// { side: 0 }
|
|
93
|
+
|
|
94
|
+
// Code that sets variables / classes
|
|
95
|
+
// const ph =
|
|
96
|
+
// typeof this.options.placeholder === "function"
|
|
97
|
+
// ? this.options.placeholder({
|
|
98
|
+
// editor: this.editor,
|
|
99
|
+
// node,
|
|
100
|
+
// pos,
|
|
101
|
+
// hasAnchor,
|
|
102
|
+
// })
|
|
103
|
+
// : this.options.placeholder;
|
|
104
|
+
// const decoration = Decoration.node(pos, pos + node.nodeSize, {
|
|
105
|
+
// class: classes.join(" "),
|
|
106
|
+
// style: `--placeholder:'${ph.replaceAll("'", "\\'")}';`,
|
|
107
|
+
// "data-placeholder": ph,
|
|
108
|
+
// });
|
|
109
|
+
|
|
110
|
+
// Latest version, only set isEmpty and hasAnchor, rest is done via CSS
|
|
111
|
+
|
|
112
|
+
const decoration = Decoration.node(pos, pos + node.nodeSize, {
|
|
113
|
+
class: classes.join(" "),
|
|
114
|
+
});
|
|
115
|
+
decorations.push(decoration);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return this.options.includeChildren;
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return DecorationSet.create(doc, decorations);
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
}),
|
|
125
|
+
];
|
|
126
|
+
},
|
|
127
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Extension } from "@tiptap/core";
|
|
2
|
+
import { createSuggestionPlugin } from "../../shared/plugins/suggestion/SuggestionPlugin";
|
|
3
|
+
import defaultCommands from "./defaultCommands";
|
|
4
|
+
import { SlashMenuItem } from "./SlashMenuItem";
|
|
5
|
+
import { PluginKey } from "prosemirror-state";
|
|
6
|
+
|
|
7
|
+
export type SlashMenuOptions = {
|
|
8
|
+
commands: { [key: string]: SlashMenuItem };
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const SlashMenuPluginKey = new PluginKey("suggestions-slash-commands");
|
|
12
|
+
|
|
13
|
+
export const SlashMenuExtension = Extension.create<SlashMenuOptions>({
|
|
14
|
+
name: "slash-command",
|
|
15
|
+
|
|
16
|
+
addOptions() {
|
|
17
|
+
return {
|
|
18
|
+
commands: defaultCommands,
|
|
19
|
+
};
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
addProseMirrorPlugins() {
|
|
23
|
+
return [
|
|
24
|
+
createSuggestionPlugin<SlashMenuItem>({
|
|
25
|
+
pluginKey: SlashMenuPluginKey,
|
|
26
|
+
editor: this.editor,
|
|
27
|
+
char: "/",
|
|
28
|
+
items: (query) => {
|
|
29
|
+
const commands = [];
|
|
30
|
+
|
|
31
|
+
for (const key in this.options.commands) {
|
|
32
|
+
commands.push(this.options.commands[key]);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return commands.filter((cmd: SlashMenuItem) => cmd.match(query));
|
|
36
|
+
},
|
|
37
|
+
onSelectItem: ({ item, editor, range }) => {
|
|
38
|
+
item.execute(editor, range);
|
|
39
|
+
},
|
|
40
|
+
}),
|
|
41
|
+
];
|
|
42
|
+
},
|
|
43
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Editor, Range } from "@tiptap/core";
|
|
2
|
+
import SuggestionItem from "../../shared/plugins/suggestion/SuggestionItem";
|
|
3
|
+
|
|
4
|
+
export type SlashMenuCallback = (editor: Editor, range: Range) => boolean;
|
|
5
|
+
|
|
6
|
+
export enum SlashMenuGroups {
|
|
7
|
+
HEADINGS = "Headings",
|
|
8
|
+
BASIC_BLOCKS = "Basic Blocks",
|
|
9
|
+
CODE = "Code Blocks",
|
|
10
|
+
|
|
11
|
+
// Just some examples, that are not currently in use
|
|
12
|
+
INLINE = "Inline",
|
|
13
|
+
EMBED = "Embed",
|
|
14
|
+
PLUGIN = "Plugin",
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A class that defines a slash command (/<command>).
|
|
19
|
+
*
|
|
20
|
+
* Not to be confused with ProseMirror commands nor TipTap commands.
|
|
21
|
+
*/
|
|
22
|
+
export class SlashMenuItem implements SuggestionItem {
|
|
23
|
+
groupName: string;
|
|
24
|
+
// other parameters initialized in the constructor
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Constructs a new slash-command.
|
|
28
|
+
*
|
|
29
|
+
* @param name The name of the command
|
|
30
|
+
* @param group Used to organize the menu
|
|
31
|
+
* @param execute The callback for creating a new node
|
|
32
|
+
* @param aliases Aliases for this command
|
|
33
|
+
* @param icon To be shown next to the name in the menu
|
|
34
|
+
* @param shortcut Info about keyboard shortcut that would activate this command
|
|
35
|
+
*/
|
|
36
|
+
constructor(
|
|
37
|
+
public readonly name: string,
|
|
38
|
+
public readonly group: SlashMenuGroups,
|
|
39
|
+
public readonly execute: SlashMenuCallback,
|
|
40
|
+
public readonly aliases: string[] = [],
|
|
41
|
+
public readonly icon?: React.ComponentType<{ className: string }>,
|
|
42
|
+
public readonly hint?: string,
|
|
43
|
+
public readonly shortcut?: string
|
|
44
|
+
) {
|
|
45
|
+
this.groupName = group;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
match(query: string): boolean {
|
|
49
|
+
return (
|
|
50
|
+
this.name.toLowerCase().startsWith(query.toLowerCase()) ||
|
|
51
|
+
this.aliases.filter((alias) =>
|
|
52
|
+
alias.toLowerCase().startsWith(query.toLowerCase())
|
|
53
|
+
).length !== 0
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
}
|