@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.
Files changed (143) hide show
  1. package/README.md +99 -0
  2. package/dist/blocknote.js +4485 -0
  3. package/dist/blocknote.js.map +1 -0
  4. package/dist/blocknote.umd.cjs +90 -0
  5. package/dist/blocknote.umd.cjs.map +1 -0
  6. package/dist/style.css +1 -0
  7. package/package.json +109 -0
  8. package/src/BlockNoteExtensions.ts +90 -0
  9. package/src/EditorContent.tsx +1 -0
  10. package/src/assets/inter-v12-latin/inter-v12-latin-100.woff +0 -0
  11. package/src/assets/inter-v12-latin/inter-v12-latin-100.woff2 +0 -0
  12. package/src/assets/inter-v12-latin/inter-v12-latin-200.woff +0 -0
  13. package/src/assets/inter-v12-latin/inter-v12-latin-200.woff2 +0 -0
  14. package/src/assets/inter-v12-latin/inter-v12-latin-300.woff +0 -0
  15. package/src/assets/inter-v12-latin/inter-v12-latin-300.woff2 +0 -0
  16. package/src/assets/inter-v12-latin/inter-v12-latin-500.woff +0 -0
  17. package/src/assets/inter-v12-latin/inter-v12-latin-500.woff2 +0 -0
  18. package/src/assets/inter-v12-latin/inter-v12-latin-600.woff +0 -0
  19. package/src/assets/inter-v12-latin/inter-v12-latin-600.woff2 +0 -0
  20. package/src/assets/inter-v12-latin/inter-v12-latin-700.woff +0 -0
  21. package/src/assets/inter-v12-latin/inter-v12-latin-700.woff2 +0 -0
  22. package/src/assets/inter-v12-latin/inter-v12-latin-800.woff +0 -0
  23. package/src/assets/inter-v12-latin/inter-v12-latin-800.woff2 +0 -0
  24. package/src/assets/inter-v12-latin/inter-v12-latin-900.woff +0 -0
  25. package/src/assets/inter-v12-latin/inter-v12-latin-900.woff2 +0 -0
  26. package/src/assets/inter-v12-latin/inter-v12-latin-regular.woff +0 -0
  27. package/src/assets/inter-v12-latin/inter-v12-latin-regular.woff2 +0 -0
  28. package/src/editor.module.css +3 -0
  29. package/src/extensions/Blocks/OrderedListPlugin.ts +46 -0
  30. package/src/extensions/Blocks/PreviousBlockTypePlugin.ts +146 -0
  31. package/src/extensions/Blocks/commands/joinBackward.ts +274 -0
  32. package/src/extensions/Blocks/helpers/findBlock.ts +3 -0
  33. package/src/extensions/Blocks/helpers/setBlockHeading.ts +30 -0
  34. package/src/extensions/Blocks/index.ts +15 -0
  35. package/src/extensions/Blocks/nodes/Block.module.css +226 -0
  36. package/src/extensions/Blocks/nodes/Block.ts +390 -0
  37. package/src/extensions/Blocks/nodes/BlockGroup.ts +28 -0
  38. package/src/extensions/Blocks/nodes/Content.ts +50 -0
  39. package/src/extensions/Blocks/nodes/README.md +26 -0
  40. package/src/extensions/Blocks/rule.ts +48 -0
  41. package/src/extensions/BubbleMenu/BubbleMenuExtension.tsx +28 -0
  42. package/src/extensions/BubbleMenu/BubbleMenuPlugin.ts +245 -0
  43. package/src/extensions/BubbleMenu/component/BubbleMenu.tsx +216 -0
  44. package/src/extensions/BubbleMenu/component/DropdownBlockItem.module.css +13 -0
  45. package/src/extensions/BubbleMenu/component/DropdownBlockItem.tsx +25 -0
  46. package/src/extensions/BubbleMenu/component/LinkToolbarButton.tsx +67 -0
  47. package/src/extensions/DraggableBlocks/DraggableBlocksExtension.ts +15 -0
  48. package/src/extensions/DraggableBlocks/DraggableBlocksPlugin.tsx +266 -0
  49. package/src/extensions/DraggableBlocks/components/DragHandle.module.css +33 -0
  50. package/src/extensions/DraggableBlocks/components/DragHandle.tsx +108 -0
  51. package/src/extensions/DraggableBlocks/components/DragHandleMenu.module.css +10 -0
  52. package/src/extensions/DraggableBlocks/components/DragHandleMenu.tsx +18 -0
  53. package/src/extensions/Hyperlinks/HyperlinkMark.tsx +16 -0
  54. package/src/extensions/Hyperlinks/HyperlinkMenuPlugin.tsx +200 -0
  55. package/src/extensions/Hyperlinks/menus/HyperlinkBasicMenu.tsx +59 -0
  56. package/src/extensions/Hyperlinks/menus/HyperlinkEditMenu.tsx +72 -0
  57. package/src/extensions/Hyperlinks/menus/atlaskit/PanelTextInput.tsx +173 -0
  58. package/src/extensions/Hyperlinks/menus/atlaskit/PanelTextInputStyles.ts +36 -0
  59. package/src/extensions/Hyperlinks/menus/atlaskit/README.md +1 -0
  60. package/src/extensions/Hyperlinks/menus/atlaskit/ToolbarComponent.tsx +61 -0
  61. package/src/extensions/Paragraph/FixedParagraph.ts +12 -0
  62. package/src/extensions/Placeholder/PlaceholderExtension.ts +127 -0
  63. package/src/extensions/SlashMenu/SlashMenuExtension.ts +43 -0
  64. package/src/extensions/SlashMenu/SlashMenuItem.ts +56 -0
  65. package/src/extensions/SlashMenu/defaultCommands.tsx +229 -0
  66. package/src/extensions/SlashMenu/index.ts +11 -0
  67. package/src/extensions/TrailingNode/TrailingNodeExtension.ts +70 -0
  68. package/src/extensions/UniqueID/UniqueID.ts +281 -0
  69. package/src/extensions/helpers/formatKeyboardShortcut.ts +9 -0
  70. package/src/fonts-inter.css +94 -0
  71. package/src/globals.css +28 -0
  72. package/src/index.ts +5 -0
  73. package/src/lib/atlaskit/browser.ts +47 -0
  74. package/src/root.module.css +19 -0
  75. package/src/shared/components/toolbar/SimpleToolbarButton.module.css +13 -0
  76. package/src/shared/components/toolbar/SimpleToolbarButton.tsx +56 -0
  77. package/src/shared/components/toolbar/Toolbar.module.css +10 -0
  78. package/src/shared/components/toolbar/Toolbar.tsx +5 -0
  79. package/src/shared/components/toolbar/ToolbarSeparator.module.css +13 -0
  80. package/src/shared/components/toolbar/ToolbarSeparator.tsx +7 -0
  81. package/src/shared/components/tooltip/TooltipContent.module.css +15 -0
  82. package/src/shared/components/tooltip/TooltipContent.tsx +23 -0
  83. package/src/shared/hooks/useEditorForceUpdate.tsx +30 -0
  84. package/src/shared/plugins/suggestion/SuggestionItem.ts +31 -0
  85. package/src/shared/plugins/suggestion/SuggestionListReactRenderer.ts +227 -0
  86. package/src/shared/plugins/suggestion/SuggestionPlugin.ts +365 -0
  87. package/src/shared/plugins/suggestion/components/SuggestionGroup.module.css +45 -0
  88. package/src/shared/plugins/suggestion/components/SuggestionGroup.tsx +134 -0
  89. package/src/shared/plugins/suggestion/components/SuggestionList.module.css +10 -0
  90. package/src/shared/plugins/suggestion/components/SuggestionList.tsx +91 -0
  91. package/src/style.css +7 -0
  92. package/src/useEditor.ts +47 -0
  93. package/src/vite-env.d.ts +1 -0
  94. package/types/src/BlockNoteExtensions.d.ts +4 -0
  95. package/types/src/EditorContent.d.ts +1 -0
  96. package/types/src/extensions/Blocks/OrderedListPlugin.d.ts +2 -0
  97. package/types/src/extensions/Blocks/PreviousBlockTypePlugin.d.ts +13 -0
  98. package/types/src/extensions/Blocks/commands/joinBackward.d.ts +14 -0
  99. package/types/src/extensions/Blocks/helpers/findBlock.d.ts +6 -0
  100. package/types/src/extensions/Blocks/helpers/setBlockHeading.d.ts +5 -0
  101. package/types/src/extensions/Blocks/index.d.ts +1 -0
  102. package/types/src/extensions/Blocks/nodes/Block.d.ts +32 -0
  103. package/types/src/extensions/Blocks/nodes/BlockGroup.d.ts +2 -0
  104. package/types/src/extensions/Blocks/nodes/Content.d.ts +5 -0
  105. package/types/src/extensions/Blocks/rule.d.ts +16 -0
  106. package/types/src/extensions/BubbleMenu/BubbleMenuExtension.d.ts +5 -0
  107. package/types/src/extensions/BubbleMenu/BubbleMenuPlugin.d.ts +46 -0
  108. package/types/src/extensions/BubbleMenu/component/BubbleMenu.d.ts +5 -0
  109. package/types/src/extensions/BubbleMenu/component/DropdownBlockItem.d.ts +10 -0
  110. package/types/src/extensions/BubbleMenu/component/LinkToolbarButton.d.ts +11 -0
  111. package/types/src/extensions/DraggableBlocks/DraggableBlocksExtension.d.ts +7 -0
  112. package/types/src/extensions/DraggableBlocks/DraggableBlocksPlugin.d.ts +18 -0
  113. package/types/src/extensions/DraggableBlocks/components/DragHandle.d.ts +12 -0
  114. package/types/src/extensions/DraggableBlocks/components/DragHandleMenu.d.ts +6 -0
  115. package/types/src/extensions/Hyperlinks/HyperlinkMark.d.ts +7 -0
  116. package/types/src/extensions/Hyperlinks/HyperlinkMenuPlugin.d.ts +2 -0
  117. package/types/src/extensions/Hyperlinks/menus/HyperlinkBasicMenu.d.ts +12 -0
  118. package/types/src/extensions/Hyperlinks/menus/HyperlinkEditMenu.d.ts +10 -0
  119. package/types/src/extensions/Hyperlinks/menus/atlaskit/PanelTextInput.d.ts +39 -0
  120. package/types/src/extensions/Hyperlinks/menus/atlaskit/PanelTextInputStyles.d.ts +1 -0
  121. package/types/src/extensions/Hyperlinks/menus/atlaskit/ToolbarComponent.d.ts +11 -0
  122. package/types/src/extensions/Paragraph/FixedParagraph.d.ts +1 -0
  123. package/types/src/extensions/Placeholder/PlaceholderExtension.d.ts +25 -0
  124. package/types/src/extensions/SlashMenu/SlashMenuExtension.d.ts +10 -0
  125. package/types/src/extensions/SlashMenu/SlashMenuItem.d.ts +43 -0
  126. package/types/src/extensions/SlashMenu/defaultCommands.d.ts +8 -0
  127. package/types/src/extensions/SlashMenu/index.d.ts +5 -0
  128. package/types/src/extensions/TrailingNode/TrailingNodeExtension.d.ts +10 -0
  129. package/types/src/extensions/UniqueID/UniqueID.d.ts +3 -0
  130. package/types/src/extensions/helpers/formatKeyboardShortcut.d.ts +1 -0
  131. package/types/src/index.d.ts +4 -0
  132. package/types/src/lib/atlaskit/browser.d.ts +12 -0
  133. package/types/src/shared/components/toolbar/SimpleToolbarButton.d.ts +16 -0
  134. package/types/src/shared/components/toolbar/Toolbar.d.ts +4 -0
  135. package/types/src/shared/components/toolbar/ToolbarSeparator.d.ts +2 -0
  136. package/types/src/shared/components/tooltip/TooltipContent.d.ts +15 -0
  137. package/types/src/shared/hooks/useEditorForceUpdate.d.ts +2 -0
  138. package/types/src/shared/plugins/suggestion/SuggestionItem.d.ts +29 -0
  139. package/types/src/shared/plugins/suggestion/SuggestionListReactRenderer.d.ts +71 -0
  140. package/types/src/shared/plugins/suggestion/SuggestionPlugin.d.ts +74 -0
  141. package/types/src/shared/plugins/suggestion/components/SuggestionGroup.d.ts +23 -0
  142. package/types/src/shared/plugins/suggestion/components/SuggestionList.d.ts +26 -0
  143. 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
+ }