@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.
Files changed (99) hide show
  1. package/README.md +1 -1
  2. package/dist/blocknote.js +3454 -2426
  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 +79 -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/extensions/UniqueID/UniqueID.ts +0 -11
  32. package/src/shared/components/toolbar/Toolbar.tsx +8 -3
  33. package/src/shared/components/toolbar/ToolbarButton.tsx +57 -0
  34. package/src/shared/components/toolbar/ToolbarDropdown.tsx +35 -0
  35. package/src/shared/components/toolbar/ToolbarDropdownItem.tsx +35 -0
  36. package/src/shared/components/toolbar/ToolbarDropdownTarget.tsx +31 -0
  37. package/src/shared/plugins/suggestion/SuggestionItem.ts +3 -1
  38. package/src/shared/plugins/suggestion/{SuggestionListReactRenderer.ts → SuggestionListReactRenderer.tsx} +13 -4
  39. package/src/shared/plugins/suggestion/components/SuggestionGroup.tsx +6 -93
  40. package/src/shared/plugins/suggestion/components/SuggestionGroupItem.tsx +82 -0
  41. package/src/shared/plugins/suggestion/components/SuggestionList.tsx +24 -23
  42. package/src/utils.ts +12 -0
  43. package/types/src/BlockNoteTheme.d.ts +2 -0
  44. package/types/src/commands/indentation.d.ts +2 -0
  45. package/types/src/extensions/Blocks/BlockAttributes.d.ts +2 -0
  46. package/types/src/extensions/Blocks/MultipleNodeSelection.d.ts +24 -0
  47. package/types/src/extensions/Blocks/nodes/Block.d.ts +1 -1
  48. package/types/src/extensions/BubbleMenu/component/LinkToolbarButton.d.ts +2 -2
  49. package/types/src/extensions/Hyperlinks/menus/EditHyperlinkMenu.d.ts +11 -0
  50. package/types/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItem.d.ts +13 -0
  51. package/types/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItemIcon.d.ts +8 -0
  52. package/types/src/extensions/Hyperlinks/menus/EditHyperlinkMenuItemInput.d.ts +9 -0
  53. package/types/src/extensions/Hyperlinks/menus/HoverHyperlinkMenu.d.ts +12 -0
  54. package/types/src/extensions/Hyperlinks/menus/HyperlinkMenu.d.ts +21 -0
  55. package/types/src/extensions/Hyperlinks/menus/helpers/PanelTextInput.d.ts +39 -0
  56. package/types/src/extensions/Hyperlinks/menus/helpers/PanelTextInputStyles.d.ts +3 -0
  57. package/types/src/extensions/Hyperlinks/menus/helpers/ToolbarComponent.d.ts +13 -0
  58. package/types/src/extensions/SlashMenu/SlashMenuItem.d.ts +4 -7
  59. package/types/src/nodes/ChildgroupNode.d.ts +28 -0
  60. package/types/src/nodes/patchNodes.d.ts +1 -0
  61. package/types/src/plugins/TreeViewPlugin/index.d.ts +2 -0
  62. package/types/src/plugins/animation.d.ts +2 -0
  63. package/types/src/react/BlockNoteComposer.d.ts +17 -0
  64. package/types/src/react/BlockNotePlugin.d.ts +1 -0
  65. package/types/src/react/index.d.ts +3 -0
  66. package/types/src/react/useBlockNoteSetup.d.ts +2 -0
  67. package/types/src/registerBlockNote.d.ts +2 -0
  68. package/types/src/shared/components/toolbar/SimpleToolbarButton.d.ts +2 -3
  69. package/types/src/shared/components/toolbar/SimpleToolbarDropdown.d.ts +11 -0
  70. package/types/src/shared/components/toolbar/SimpleToolbarDropdownItem.d.ts +11 -0
  71. package/types/src/shared/components/toolbar/Toolbar.d.ts +2 -2
  72. package/types/src/shared/components/toolbar/ToolbarButton.d.ts +15 -0
  73. package/types/src/shared/components/toolbar/ToolbarDropdown.d.ts +17 -0
  74. package/types/src/shared/components/toolbar/ToolbarDropdownItem.d.ts +11 -0
  75. package/types/src/shared/components/toolbar/ToolbarDropdownTarget.d.ts +8 -0
  76. package/types/src/shared/plugins/suggestion/SuggestionItem.d.ts +2 -4
  77. package/types/src/shared/plugins/suggestion/components/SuggestionGroupItem.d.ts +9 -0
  78. package/types/src/shared/plugins/suggestion/components/SuggestionList.d.ts +0 -15
  79. package/types/src/themes/BlockNoteEditorTheme.d.ts +11 -0
  80. package/types/src/utils.d.ts +2 -0
  81. package/src/extensions/BubbleMenu/component/DropdownBlockItem.module.css +0 -13
  82. package/src/extensions/BubbleMenu/component/DropdownBlockItem.tsx +0 -25
  83. package/src/extensions/DraggableBlocks/components/DragHandle.module.css +0 -33
  84. package/src/extensions/DraggableBlocks/components/DragHandleMenu.module.css +0 -10
  85. package/src/extensions/Hyperlinks/menus/HyperlinkBasicMenu.tsx +0 -59
  86. package/src/extensions/Hyperlinks/menus/HyperlinkEditMenu.tsx +0 -72
  87. package/src/extensions/Hyperlinks/menus/atlaskit/PanelTextInput.tsx +0 -173
  88. package/src/extensions/Hyperlinks/menus/atlaskit/PanelTextInputStyles.ts +0 -36
  89. package/src/extensions/Hyperlinks/menus/atlaskit/README.md +0 -1
  90. package/src/extensions/Hyperlinks/menus/atlaskit/ToolbarComponent.tsx +0 -61
  91. package/src/extensions/helpers/formatKeyboardShortcut.ts +0 -9
  92. package/src/lib/atlaskit/browser.ts +0 -47
  93. package/src/shared/components/toolbar/SimpleToolbarButton.module.css +0 -13
  94. package/src/shared/components/toolbar/SimpleToolbarButton.tsx +0 -56
  95. package/src/shared/components/toolbar/Toolbar.module.css +0 -10
  96. package/src/shared/components/toolbar/ToolbarSeparator.module.css +0 -13
  97. package/src/shared/components/toolbar/ToolbarSeparator.tsx +0 -7
  98. package/src/shared/plugins/suggestion/components/SuggestionGroup.module.css +0 -45
  99. package/src/shared/plugins/suggestion/components/SuggestionList.module.css +0 -10
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "homepage": "https://github.com/yousefed/blocknote",
4
4
  "private": false,
5
5
  "license": "MPL-2.0",
6
- "version": "0.1.0",
6
+ "version": "0.1.2",
7
7
  "files": [
8
8
  "dist",
9
9
  "types",
@@ -45,10 +45,10 @@
45
45
  "lint": "eslint src --max-warnings 0"
46
46
  },
47
47
  "dependencies": {
48
- "@atlaskit/button": "^16.3.5",
49
- "@atlaskit/dropdown-menu": "^11.1.2",
50
- "@atlaskit/menu": "^1.3.0",
51
- "@atlaskit/theme": "^12.1.4",
48
+ "@emotion/cache": "^11.10.5",
49
+ "@emotion/serialize": "^1.1.1",
50
+ "@emotion/utils": "^1.2.0",
51
+ "@mantine/core": "^5.6.1",
52
52
  "@tippyjs/react": "^4.2.6",
53
53
  "@tiptap/core": "^2.0.0-beta.182",
54
54
  "@tiptap/extension-bold": "^2.0.0-beta.28",
@@ -89,6 +89,7 @@
89
89
  "@types/uuid": "^8.3.4",
90
90
  "eslint": "^8.10.0",
91
91
  "eslint-config-react-app": "^7.0.0",
92
+ "prettier": "^2.7.1",
92
93
  "typescript": "^4.5.4",
93
94
  "vite": "^3.0.5",
94
95
  "vite-plugin-eslint": "^1.7.0"
@@ -106,5 +107,5 @@
106
107
  "access": "public",
107
108
  "registry": "https://registry.npmjs.org/"
108
109
  },
109
- "gitHead": "511cb65b707ca40289d838c71ac6edd29d882eef"
110
+ "gitHead": "085650a27c92af2b54df6d8b0e73673180821ebe"
110
111
  }
@@ -0,0 +1,150 @@
1
+ import { MantineThemeOverride } from "@mantine/core";
2
+
3
+ export const BlockNoteTheme: MantineThemeOverride = {
4
+ activeStyles: {
5
+ // Removes button press effect.
6
+ transform: "none",
7
+ },
8
+ colorScheme: "light",
9
+ colors: {
10
+ brandFinal: [
11
+ "#F6F6F8",
12
+ "#ECEDF0",
13
+ "#DFE1E6",
14
+ "#C2C7D0",
15
+ "#A6ADBA",
16
+ "#8993A4",
17
+ "#6D798F",
18
+ "#505F79",
19
+ "#344563",
20
+ "#172B4D",
21
+ ],
22
+ },
23
+ components: {
24
+ Menu: {
25
+ styles: (theme) => ({
26
+ dropdown: {
27
+ backgroundColor: "white",
28
+ boxShadow: `0px 4px 8px ${theme.colors.brandFinal[2]}, 0px 0px 1px ${theme.colors.brandFinal[2]}`,
29
+ border: `1px solid ${theme.colors.brandFinal[1]}`,
30
+ borderRadius: "6px",
31
+ padding: "2px",
32
+ },
33
+ }),
34
+ },
35
+ DragHandleMenu: {
36
+ styles: (theme) => ({
37
+ root: {
38
+ ".mantine-Menu-item": {
39
+ color: theme.colors.brandFinal,
40
+ fontSize: "12px",
41
+ height: "34px",
42
+ },
43
+ },
44
+ }),
45
+ },
46
+ EditHyperlinkMenu: {
47
+ styles: (theme) => ({
48
+ root: {
49
+ backgroundColor: "white",
50
+ boxShadow: `0px 4px 8px ${theme.colors.brandFinal[2]}, 0px 0px 1px ${theme.colors.brandFinal[2]}`,
51
+ border: `1px solid ${theme.colors.brandFinal[1]}`,
52
+ borderRadius: "6px",
53
+ gap: "4px",
54
+ minWidth: "145px",
55
+ padding: "2px",
56
+ // Row
57
+ ".mantine-Group-root": {
58
+ flexWrap: "nowrap",
59
+ gap: "8px",
60
+ paddingInline: "6px",
61
+ // Row icon
62
+ ".mantine-Container-root": {
63
+ color: theme.colors.brandFinal,
64
+ display: "flex",
65
+ justifyContent: "center",
66
+ padding: "0",
67
+ width: "fit-content",
68
+ },
69
+ // Row input field
70
+ ".mantine-TextInput-root": {
71
+ background: "transparent",
72
+ width: "300px",
73
+ ".mantine-TextInput-wrapper": {
74
+ ".mantine-TextInput-input": {
75
+ fontSize: "12px",
76
+ border: 0,
77
+ padding: 0,
78
+ },
79
+ },
80
+ },
81
+ },
82
+ },
83
+ }),
84
+ },
85
+ Toolbar: {
86
+ styles: (theme) => ({
87
+ root: {
88
+ backgroundColor: "white",
89
+ boxShadow: `0px 4px 8px ${theme.colors.brandFinal[2]}, 0px 0px 1px ${theme.colors.brandFinal[2]}`,
90
+ border: `1px solid ${theme.colors.brandFinal[1]}`,
91
+ borderRadius: "6px",
92
+ flexWrap: "nowrap",
93
+ gap: "2px",
94
+ padding: "2px",
95
+ width: "fit-content",
96
+ // Button (including dropdown target)
97
+ ".mantine-UnstyledButton-root": {
98
+ borderRadius: "4px",
99
+ },
100
+ // Dropdown
101
+ ".mantine-Menu-dropdown": {
102
+ // Dropdown item
103
+ ".mantine-Menu-item": {
104
+ color: theme.colors.brandFinal,
105
+ fontSize: "12px",
106
+ height: "34px",
107
+ ".mantine-Menu-itemRightSection": {
108
+ paddingLeft: "5px",
109
+ },
110
+ },
111
+ },
112
+ },
113
+ }),
114
+ },
115
+ SuggestionList: {
116
+ styles: (theme) => ({
117
+ root: {
118
+ // ...theme.other.defaultMenuStyles(theme),
119
+ ".mantine-Menu-item": {
120
+ // Icon
121
+ ".mantine-Menu-itemIcon": {
122
+ padding: "8px",
123
+ border: `1px solid ${theme.colors.brandFinal[2]}`,
124
+ backgroundColor: theme.colors.brandFinal[0],
125
+ borderRadius: "4px",
126
+ color: theme.colors.brandFinal,
127
+ },
128
+ // Text
129
+ ".mantine-Menu-itemLabel": {
130
+ color: theme.colors.brandFinal,
131
+ paddingRight: "16px",
132
+ ".mantine-Stack-root": {
133
+ gap: "0",
134
+ },
135
+ },
136
+ // Badge (keyboard shortcut)
137
+ ".mantine-Menu-itemRightSection": {
138
+ ".mantine-Badge-root": {
139
+ border: `1px solid ${theme.colors.brandFinal[2]}`,
140
+ },
141
+ },
142
+ },
143
+ },
144
+ }),
145
+ },
146
+ },
147
+ fontFamily: "Inter",
148
+ primaryColor: "brandFinal",
149
+ primaryShade: 9,
150
+ };
@@ -0,0 +1,12 @@
1
+ // Object containing all possible block attributes.
2
+ const BlockAttributes: Record<string, string> = {
3
+ listType: "data-list-type",
4
+ blockColor: "data-block-color",
5
+ blockStyle: "data-block-style",
6
+ headingType: "data-heading-type",
7
+ id: "data-id",
8
+ depth: "data-depth",
9
+ depthChange: "data-depth-change",
10
+ };
11
+
12
+ export default BlockAttributes;
@@ -0,0 +1,87 @@
1
+ import { Selection } from "prosemirror-state";
2
+ import { Fragment, Node, ResolvedPos, Slice } from "prosemirror-model";
3
+ import { Mappable } from "prosemirror-transform";
4
+
5
+ /**
6
+ * This class represents an editor selection which spans multiple nodes/blocks. It's currently only used to allow users
7
+ * to drag multiple blocks at the same time. Expects the selection anchor and head to be between nodes, i.e. just before
8
+ * the first target node and just after the last, and that anchor and head are at the same nesting level.
9
+ *
10
+ * Partially based on ProseMirror's NodeSelection implementation:
11
+ * (https://github.com/ProseMirror/prosemirror-state/blob/master/src/selection.ts)
12
+ * MultipleNodeSelection differs from NodeSelection in the following ways:
13
+ * 1. Stores which nodes are included in the selection instead of just a single node.
14
+ * 2. Already expects the selection to start just before the first target node and ends just after the last, while a
15
+ * NodeSelection automatically sets both anchor and head to just before the single target node.
16
+ */
17
+ export class MultipleNodeSelection extends Selection {
18
+ nodes: Array<Node>;
19
+
20
+ constructor($anchor: ResolvedPos, $head: ResolvedPos) {
21
+ super($anchor, $head);
22
+
23
+ // Parent is at the same nesting level as anchor/head since they are just before/ just after target nodes.
24
+ const parentNode = $anchor.node();
25
+
26
+ this.nodes = [];
27
+ $anchor.doc.nodesBetween($anchor.pos, $head.pos, (node, _pos, parent) => {
28
+ if (parent !== null && parent.eq(parentNode)) {
29
+ this.nodes.push(node);
30
+ return false;
31
+ }
32
+ return;
33
+ });
34
+ }
35
+
36
+ static create(doc: Node, from: number, to = from): MultipleNodeSelection {
37
+ return new MultipleNodeSelection(doc.resolve(from), doc.resolve(to));
38
+ }
39
+
40
+ content(): Slice {
41
+ return new Slice(Fragment.from(this.nodes), 0, 0);
42
+ }
43
+
44
+ eq(selection: Selection): boolean {
45
+ if (!(selection instanceof MultipleNodeSelection)) {
46
+ return false;
47
+ }
48
+
49
+ if (this.nodes.length !== selection.nodes.length) {
50
+ return false;
51
+ }
52
+
53
+ if (this.from !== selection.from || this.to !== selection.to) {
54
+ return false;
55
+ }
56
+
57
+ for (let i = 0; i < this.nodes.length; i++) {
58
+ if (!this.nodes[i].eq(selection.nodes[i])) {
59
+ return false;
60
+ }
61
+ }
62
+
63
+ return true;
64
+ }
65
+
66
+ map(doc: Node, mapping: Mappable): Selection {
67
+ let fromResult = mapping.mapResult(this.from);
68
+ let toResult = mapping.mapResult(this.to);
69
+
70
+ if (toResult.deleted) {
71
+ return Selection.near(doc.resolve(fromResult.pos));
72
+ }
73
+
74
+ if (fromResult.deleted) {
75
+ return Selection.near(doc.resolve(toResult.pos));
76
+ }
77
+
78
+ return new MultipleNodeSelection(
79
+ doc.resolve(fromResult.pos),
80
+ doc.resolve(toResult.pos)
81
+ );
82
+ }
83
+
84
+ toJSON(): any {
85
+ return { type: "node", anchor: this.anchor, head: this.head };
86
+ }
87
+ }
@@ -5,9 +5,14 @@ import {
5
5
  } from "@tiptap/core";
6
6
  import { Plugin, PluginKey } from "prosemirror-state";
7
7
  import { Decoration, DecorationSet } from "prosemirror-view";
8
+ import BlockAttributes from "./BlockAttributes";
8
9
 
9
10
  const PLUGIN_KEY = new PluginKey(`previous-blocks`);
10
11
 
12
+ // Inserts "prev-" string into an HTML attribute name with a "data-" prefix, e.g. "data-depth" -> "data-prev-depth".
13
+ // Assumes "data-" prefix is in the attribute name.
14
+ const insertPrev = (attr: string) => attr.slice(0, 5) + "prev-" + attr.slice(5);
15
+
11
16
  /**
12
17
  * This plugin tracks transformation of Block node attributes, so we can support CSS transitions.
13
18
  *
@@ -129,8 +134,9 @@ export const PreviousBlockTypePlugin = () => {
129
134
  }
130
135
 
131
136
  const decorationAttributes: any = {};
132
- for (let [key, val] of Object.entries(prevAttrs)) {
133
- decorationAttributes["data-prev-" + key] = val || "none";
137
+ for (let [nodeAttr, val] of Object.entries(prevAttrs)) {
138
+ decorationAttributes[insertPrev(BlockAttributes[nodeAttr])] =
139
+ val || "none";
134
140
  }
135
141
  const decoration = Decoration.node(pos, pos + node.nodeSize, {
136
142
  ...decorationAttributes,
@@ -40,7 +40,7 @@ NESTED BLOCKS
40
40
  position: relative;
41
41
  }
42
42
 
43
- .blockGroup .blockGroup > .blockOuter:not([data-prev-depthchanged])::before {
43
+ .blockGroup .blockGroup > .blockOuter:not([data-prev-depth-changed])::before {
44
44
  content: " ";
45
45
  display: inline;
46
46
  border-left: 1px solid #ccc;
@@ -50,71 +50,71 @@ NESTED BLOCKS
50
50
  transition: all 0.2s 0.1s;
51
51
  }
52
52
 
53
- .blockGroup .blockGroup > .blockOuter[data-prev-depthchange="-2"]::before {
53
+ .blockGroup .blockGroup > .blockOuter[data-prev-depth-change="-2"]::before {
54
54
  height: 0;
55
55
  }
56
56
 
57
57
  /* NESTED BLOCK ANIMATIONS */
58
58
 
59
- [data-prev-depthchange="1"] {
59
+ [data-prev-depth-change="1"] {
60
60
  --x: 1;
61
61
  }
62
- [data-prev-depthchange="2"] {
62
+ [data-prev-depth-change="2"] {
63
63
  --x: 2;
64
64
  }
65
- [data-prev-depthchange="3"] {
65
+ [data-prev-depth-change="3"] {
66
66
  --x: 3;
67
67
  }
68
- [data-prev-depthchange="4"] {
68
+ [data-prev-depth-change="4"] {
69
69
  --x: 4;
70
70
  }
71
- [data-prev-depthchange="5"] {
71
+ [data-prev-depth-change="5"] {
72
72
  --x: 5;
73
73
  }
74
74
 
75
- [data-prev-depthchange="-1"] {
75
+ [data-prev-depth-change="-1"] {
76
76
  --x: -1;
77
77
  }
78
- [data-prev-depthchange="-2"] {
78
+ [data-prev-depth-change="-2"] {
79
79
  --x: -2;
80
80
  }
81
- [data-prev-depthchange="-3"] {
81
+ [data-prev-depth-change="-3"] {
82
82
  --x: -3;
83
83
  }
84
- [data-prev-depthchange="-4"] {
84
+ [data-prev-depth-change="-4"] {
85
85
  --x: -4;
86
86
  }
87
- [data-prev-depthchange="-5"] {
87
+ [data-prev-depth-change="-5"] {
88
88
  --x: -5;
89
89
  }
90
90
 
91
- .blockOuter[data-prev-depthchange] {
91
+ .blockOuter[data-prev-depth-change] {
92
92
  margin-left: calc(10px * var(--x));
93
93
  }
94
94
 
95
- .blockOuter[data-prev-depthchange] .blockOuter[data-prev-depthchange] {
95
+ .blockOuter[data-prev-depth-change] .blockOuter[data-prev-depth-change] {
96
96
  margin-left: 0;
97
97
  }
98
98
 
99
99
  /* HEADINGS*/
100
- .blockOuter[data-prev-headingtype="1"] > .block > div:first-child,
101
- .blockOuter[data-headingtype="1"]:not([data-prev-headingtype])
100
+ .blockOuter[data-prev-heading-type="1"] > .block > div:first-child,
101
+ .blockOuter[data-heading-type="1"]:not([data-prev-heading-type])
102
102
  > .block
103
103
  > div:first-child {
104
104
  font-size: 3em;
105
105
  font-weight: bold;
106
106
  }
107
107
 
108
- .blockOuter[data-prev-headingtype="2"] > .block > div:first-child,
109
- .blockOuter[data-headingtype="2"]:not([data-prev-headingtype])
108
+ .blockOuter[data-prev-heading-type="2"] > .block > div:first-child,
109
+ .blockOuter[data-heading-type="2"]:not([data-prev-heading-type])
110
110
  > .block
111
111
  > div:first-child {
112
112
  font-size: 2em;
113
113
  font-weight: bold;
114
114
  }
115
115
 
116
- .blockOuter[data-prev-headingtype="3"] > .block > div:first-child,
117
- .blockOuter[data-headingtype="3"]:not([data-prev-headingtype])
116
+ .blockOuter[data-prev-heading-type="3"] > .block > div:first-child,
117
+ .blockOuter[data-heading-type="3"]:not([data-prev-heading-type])
118
118
  > .block
119
119
  > div:first-child {
120
120
  font-size: 1.3em;
@@ -129,8 +129,8 @@ NESTED BLOCKS
129
129
  margin-left: 0px;
130
130
  }
131
131
 
132
- .blockOuter[data-prev-listtype="oli"] > .block > div:first-child::before,
133
- .blockOuter[data-listtype="oli"]:not([data-prev-listtype])
132
+ .blockOuter[data-prev-list-type="oli"] > .block > div:first-child::before,
133
+ .blockOuter[data-list-type="oli"]:not([data-prev-list-type])
134
134
  > .block
135
135
  > div:first-child::before {
136
136
  content: attr(data-position);
@@ -138,8 +138,8 @@ NESTED BLOCKS
138
138
  padding-left: 0px;
139
139
  }
140
140
 
141
- .blockOuter[data-prev-listtype="li"] > .block > div:first-child::before,
142
- .blockOuter[data-listtype="li"]:not([data-prev-listtype])
141
+ .blockOuter[data-prev-list-type="li"] > .block > div:first-child::before,
142
+ .blockOuter[data-list-type="li"]:not([data-prev-list-type])
143
143
  > .block
144
144
  > div:first-child::before {
145
145
  content: "•";
@@ -151,16 +151,16 @@ NESTED BLOCKS
151
151
  /* margin-left: 0.2em; */
152
152
  }
153
153
 
154
- .blockOuter[data-listtype="li"]
154
+ .blockOuter[data-list-type="li"]
155
155
  > .block
156
156
  > .blockGroup
157
- > .blockOuter[data-prev-listtype="li"]
157
+ > .blockOuter[data-prev-list-type="li"]
158
158
  > .block
159
159
  > div:first-child::before,
160
- .blockOuter[data-listtype="li"]
160
+ .blockOuter[data-list-type="li"]
161
161
  > .block
162
162
  > .blockGroup
163
- > .blockOuter[data-listtype="li"]:not([data-prev-listtype])
163
+ > .blockOuter[data-list-type="li"]:not([data-prev-list-type])
164
164
  > .block
165
165
  > div:first-child::before {
166
166
  content: "◦";
@@ -168,22 +168,22 @@ NESTED BLOCKS
168
168
  font-family: arial;
169
169
  }
170
170
 
171
- .blockOuter[data-listtype="li"]
171
+ .blockOuter[data-list-type="li"]
172
172
  > .block
173
173
  > .blockGroup
174
- > .blockOuter[data-listtype="li"]
174
+ > .blockOuter[data-list-type="li"]
175
175
  > .block
176
176
  > .blockGroup
177
- .blockOuter[data-prev-listtype="li"]
177
+ .blockOuter[data-prev-list-type="li"]
178
178
  > .block
179
179
  > div:first-child::before,
180
- .blockOuter[data-listtype="li"]
180
+ .blockOuter[data-list-type="li"]
181
181
  > .block
182
182
  > .blockGroup
183
- > .blockOuter[data-listtype="li"]
183
+ > .blockOuter[data-list-type="li"]
184
184
  > .block
185
185
  > .blockGroup
186
- .blockOuter[data-listtype="li"]:not([data-prev-listtype])
186
+ .blockOuter[data-list-type="li"]:not([data-prev-list-type])
187
187
  > .block
188
188
  > div:first-child::before {
189
189
  content: "▪";
@@ -217,10 +217,10 @@ NESTED BLOCKS
217
217
  content: "Type to filter";
218
218
  }
219
219
 
220
- [data-headingtype] > .blockContent.isEmpty div::before {
220
+ [data-heading-type] > .blockContent.isEmpty div::before {
221
221
  content: "Heading";
222
222
  }
223
223
 
224
- [data-listtype] > .blockContent.isEmpty div::before {
224
+ [data-list-type] > .blockContent.isEmpty div::before {
225
225
  content: "List";
226
- }
226
+ }
@@ -7,12 +7,13 @@ import { OrderedListPlugin } from "../OrderedListPlugin";
7
7
  import { PreviousBlockTypePlugin } from "../PreviousBlockTypePlugin";
8
8
  import { textblockTypeInputRuleSameNodeType } from "../rule";
9
9
  import styles from "./Block.module.css";
10
+ import BlockAttributes from "../BlockAttributes";
10
11
 
11
12
  export interface IBlock {
12
13
  HTMLAttributes: Record<string, any>;
13
14
  }
14
15
 
15
- export type Level = 1 | 2 | 3;
16
+ export type Level = "1" | "2" | "3";
16
17
  export type ListType = "li" | "oli";
17
18
 
18
19
  declare module "@tiptap/core" {
@@ -60,63 +61,109 @@ export const Block = Node.create<IBlock>({
60
61
  return {
61
62
  listType: {
62
63
  default: undefined,
63
- renderHTML: (attributes) => {
64
- return {
65
- "data-listType": attributes.listType,
66
- };
67
- },
68
- parseHTML: (element) => element.getAttribute("data-listType"),
69
64
  },
70
65
  blockColor: {
71
66
  default: undefined,
72
- renderHTML: (attributes) => {
73
- return {
74
- "data-blockColor": attributes.blockColor,
75
- };
76
- },
77
- parseHTML: (element) => element.getAttribute("data-blockColor"),
78
67
  },
79
68
  blockStyle: {
80
69
  default: undefined,
81
- renderHTML: (attributes) => {
82
- return {
83
- "data-blockStyle": attributes.blockStyle,
84
- };
85
- },
86
- parseHTML: (element) => element.getAttribute("data-blockStyle"),
87
70
  },
88
71
  headingType: {
89
72
  default: undefined,
90
73
  keepOnSplit: false,
91
- renderHTML: (attributes) => {
92
- return {
93
- "data-headingType": attributes.headingType,
94
- };
95
- },
96
- parseHTML: (element) => element.getAttribute("data-headingType"),
97
74
  },
98
75
  };
99
76
  },
100
77
 
101
- // TODO: should we parse <li>, <ol>, <h1>, etc?
102
78
  parseHTML() {
103
79
  return [
80
+ // For parsing blocks within the editor.
104
81
  {
105
82
  tag: "div",
83
+ getAttrs: (element) => {
84
+ if (typeof element === "string") {
85
+ return false;
86
+ }
87
+
88
+ const attrs: Record<string, string> = {};
89
+ for (let [nodeAttr, HTMLAttr] of Object.entries(BlockAttributes)) {
90
+ if (element.getAttribute(HTMLAttr)) {
91
+ attrs[nodeAttr] = element.getAttribute(HTMLAttr)!;
92
+ }
93
+ }
94
+
95
+ if (element.getAttribute("data-node-type") === "block") {
96
+ return attrs;
97
+ }
98
+
99
+ return false;
100
+ },
101
+ },
102
+ // For parsing headings & paragraphs copied from outside the editor.
103
+ {
104
+ tag: "p",
105
+ priority: 100,
106
+ },
107
+ {
108
+ tag: "h1",
109
+ attrs: { headingType: "1" },
110
+ },
111
+ {
112
+ tag: "h2",
113
+ attrs: { headingType: "2" },
114
+ },
115
+ {
116
+ tag: "h3",
117
+ attrs: { headingType: "3" },
118
+ },
119
+ // For parsing list items copied from outside the editor.
120
+ {
121
+ tag: "li",
122
+ getAttrs: (element) => {
123
+ if (typeof element === "string") {
124
+ return false;
125
+ }
126
+
127
+ const parent = element.parentElement;
128
+
129
+ if (parent === null) {
130
+ return false;
131
+ }
132
+
133
+ // Gets type of list item (ordered/unordered) based on parent element's tag ("ol"/"ul").
134
+ if (parent.tagName === "UL") {
135
+ return { listType: "li" };
136
+ }
137
+
138
+ if (parent.tagName === "OL") {
139
+ return { listType: "oli" };
140
+ }
141
+
142
+ return false;
143
+ },
106
144
  },
107
145
  ];
108
146
  },
109
147
 
110
148
  renderHTML({ HTMLAttributes }) {
149
+ const attrs: Record<string, string> = {};
150
+ for (let [nodeAttr, HTMLAttr] of Object.entries(BlockAttributes)) {
151
+ if (HTMLAttributes[nodeAttr]) {
152
+ attrs[HTMLAttr] = HTMLAttributes[nodeAttr];
153
+ }
154
+ }
155
+
111
156
  return [
112
157
  "div",
113
- mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
158
+ mergeAttributes(attrs, {
114
159
  class: styles.blockOuter,
160
+ "data-node-type": "block-outer",
115
161
  }),
116
162
  [
117
163
  "div",
118
- mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
164
+ mergeAttributes(attrs, {
119
165
  class: styles.block,
166
+ "data-node-type": "block",
120
167
  }),
121
168
  0,
122
169
  ],
@@ -125,7 +172,7 @@ export const Block = Node.create<IBlock>({
125
172
 
126
173
  addInputRules() {
127
174
  return [
128
- ...[1, 2, 3].map((level) => {
175
+ ...["1", "2", "3"].map((level) => {
129
176
  // Create a heading when starting with "#", "##", or "###""
130
177
  return textblockTypeInputRuleSameNodeType({
131
178
  find: new RegExp(`^(#{1,${level}})\\s$`),
@@ -378,9 +425,9 @@ export const Block = Node.create<IBlock>({
378
425
  },
379
426
  "Mod-Alt-0": () =>
380
427
  this.editor.chain().unsetList().unsetBlockHeading().run(),
381
- "Mod-Alt-1": () => this.editor.commands.setBlockHeading({ level: 1 }),
382
- "Mod-Alt-2": () => this.editor.commands.setBlockHeading({ level: 2 }),
383
- "Mod-Alt-3": () => this.editor.commands.setBlockHeading({ level: 3 }),
428
+ "Mod-Alt-1": () => this.editor.commands.setBlockHeading({ level: "1" }),
429
+ "Mod-Alt-2": () => this.editor.commands.setBlockHeading({ level: "2" }),
430
+ "Mod-Alt-3": () => this.editor.commands.setBlockHeading({ level: "3" }),
384
431
  "Mod-Shift-7": () => this.editor.commands.setBlockList("li"),
385
432
  "Mod-Shift-8": () => this.editor.commands.setBlockList("oli"),
386
433
  // TODO: Add shortcuts for numbered and bullet list