Skip to content

Commit 32e6229

Browse files
committed
Merge branch 'main' into new-releases
2 parents 5bdbc6a + 6309de0 commit 32e6229

File tree

65 files changed

+1275
-517
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+1275
-517
lines changed

examples/editor/src/App.tsx

+5-4
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@ function App() {
1010
onEditorContentChange: (editor) => {
1111
console.log(editor.topLevelBlocks);
1212
},
13-
editorDOMAttributes: {
14-
class: styles.editor,
15-
"data-test": "editor",
13+
domAttributes: {
14+
editor: {
15+
class: styles.editor,
16+
"data-test": "editor",
17+
},
1618
},
17-
theme: "light",
1819
});
1920

2021
// Give tests a way to get prosemirror instance

examples/vanilla/src/main.tsx

+4-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ const editor = new BlockNoteEditor({
1010
onEditorContentChange: () => {
1111
console.log(editor.topLevelBlocks);
1212
},
13-
editorDOMAttributes: {
14-
class: "editor",
13+
domAttributes: {
14+
editor: {
15+
class: "editor",
16+
},
1517
},
1618
});
1719

packages/core/src/BlockNoteEditor.ts

+11-15
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ import {
1111
updateBlock,
1212
} from "./api/blockManipulation/blockManipulation";
1313
import {
14-
HTMLToBlocks,
1514
blocksToHTML,
1615
blocksToMarkdown,
16+
HTMLToBlocks,
1717
markdownToBlocks,
1818
} from "./api/formatConversions/formatConversions";
1919
import {
@@ -25,6 +25,7 @@ import styles from "./editor.module.css";
2525
import {
2626
Block,
2727
BlockIdentifier,
28+
BlockNoteDOMAttributes,
2829
BlockSchema,
2930
PartialBlock,
3031
} from "./extensions/Blocks/api/blockTypes";
@@ -48,6 +49,7 @@ import { BaseSlashMenuItem } from "./extensions/SlashMenu/BaseSlashMenuItem";
4849
import { SlashMenuProsemirrorPlugin } from "./extensions/SlashMenu/SlashMenuPlugin";
4950
import { getDefaultSlashMenuItems } from "./extensions/SlashMenu/defaultSlashMenuItems";
5051
import { UniqueID } from "./extensions/UniqueID/UniqueID";
52+
import { mergeCSSClasses } from "./shared/utils";
5153

5254
export type BlockNoteEditorOptions<BSchema extends BlockSchema> = {
5355
// TODO: Figure out if enableBlockNoteExtensions/disableHistoryExtension are needed and document them.
@@ -67,11 +69,11 @@ export type BlockNoteEditorOptions<BSchema extends BlockSchema> = {
6769
*/
6870
parentElement: HTMLElement;
6971
/**
70-
* An object containing attributes that should be added to the editor's HTML element.
72+
* An object containing attributes that should be added to HTML elements of the editor.
7173
*
72-
* @example { class: "my-editor-class" }
74+
* @example { editor: { class: "my-editor-class" } }
7375
*/
74-
editorDOMAttributes: Record<string, string>;
76+
domAttributes: Partial<BlockNoteDOMAttributes>;
7577
/**
7678
* A callback function that runs when the editor is ready to be used.
7779
*/
@@ -98,12 +100,6 @@ export type BlockNoteEditorOptions<BSchema extends BlockSchema> = {
98100
* @default true
99101
*/
100102
defaultStyles: boolean;
101-
/**
102-
* Whether to use the light or dark theme.
103-
*
104-
* @default "light"
105-
*/
106-
theme: "light" | "dark";
107103

108104
/**
109105
* A list of block types that should be available in the editor.
@@ -185,6 +181,7 @@ export class BlockNoteEditor<BSchema extends BlockSchema = DefaultBlockSchema> {
185181

186182
const extensions = getBlockNoteExtensions<BSchema>({
187183
editor: this,
184+
domAttributes: newOptions.domAttributes || {},
188185
blockSchema: newOptions.blockSchema,
189186
collaboration: newOptions.collaboration,
190187
});
@@ -266,14 +263,13 @@ export class BlockNoteEditor<BSchema extends BlockSchema = DefaultBlockSchema> {
266263
: [...(newOptions._tiptapOptions?.extensions || []), ...extensions],
267264
editorProps: {
268265
attributes: {
269-
"data-theme": options.theme || "light",
270-
...(newOptions.editorDOMAttributes || {}),
271-
class: [
266+
...newOptions.domAttributes?.editor,
267+
class: mergeCSSClasses(
272268
styles.bnEditor,
273269
styles.bnRoot,
274270
newOptions.defaultStyles ? styles.defaultStyles : "",
275-
newOptions.editorDOMAttributes?.class || "",
276-
].join(" "),
271+
newOptions.domAttributes?.editor?.class || ""
272+
),
277273
},
278274
},
279275
};

packages/core/src/BlockNoteExtensions.ts

+18-5
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@ import * as Y from "yjs";
1919
import styles from "./editor.module.css";
2020
import { BackgroundColorExtension } from "./extensions/BackgroundColor/BackgroundColorExtension";
2121
import { BackgroundColorMark } from "./extensions/BackgroundColor/BackgroundColorMark";
22-
import { blocks } from "./extensions/Blocks";
23-
import { BlockSchema } from "./extensions/Blocks/api/blockTypes";
22+
import { BlockContainer, BlockGroup, Doc } from "./extensions/Blocks";
23+
import {
24+
BlockNoteDOMAttributes,
25+
BlockSchema,
26+
} from "./extensions/Blocks/api/blockTypes";
2427
import { CustomBlockSerializerExtension } from "./extensions/Blocks/api/serialization";
2528
import blockStyles from "./extensions/Blocks/nodes/Block.module.css";
2629
import { Placeholder } from "./extensions/Placeholder/PlaceholderExtension";
@@ -35,6 +38,7 @@ import UniqueID from "./extensions/UniqueID/UniqueID";
3538
*/
3639
export const getBlockNoteExtensions = <BSchema extends BlockSchema>(opts: {
3740
editor: BlockNoteEditor<BSchema>;
41+
domAttributes: Partial<BlockNoteDOMAttributes>;
3842
blockSchema: BSchema;
3943
collaboration?: {
4044
fragment: Y.XmlFragment;
@@ -86,10 +90,19 @@ export const getBlockNoteExtensions = <BSchema extends BlockSchema>(opts: {
8690
BackgroundColorExtension,
8791
TextAlignmentExtension,
8892

89-
// custom blocks:
90-
...blocks,
93+
// nodes
94+
Doc,
95+
BlockContainer.configure({
96+
domAttributes: opts.domAttributes,
97+
}),
98+
BlockGroup.configure({
99+
domAttributes: opts.domAttributes,
100+
}),
91101
...Object.values(opts.blockSchema).map((blockSpec) =>
92-
blockSpec.node.configure({ editor: opts.editor })
102+
blockSpec.node.configure({
103+
editor: opts.editor,
104+
domAttributes: opts.domAttributes,
105+
})
93106
),
94107
CustomBlockSerializerExtension,
95108

packages/core/src/editor.module.css

-11
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
.bnEditor {
44
outline: none;
55
padding-inline: 54px;
6-
border-radius: 8px;
76

87
/* Define a set of colors to be used throughout the app for consistency
98
see https://atlassian.design/foundations/color for more info */
@@ -55,16 +54,6 @@ Tippy popups that are appended to document.body directly
5554
-moz-osx-font-smoothing: grayscale;
5655
}
5756

58-
[data-theme="light"] {
59-
background-color: #FFFFFF;
60-
color: #3F3F3F;
61-
}
62-
63-
[data-theme="dark"] {
64-
background-color: #1F1F1F;
65-
color: #CFCFCF;
66-
}
67-
6857
.dragPreview {
6958
position: absolute;
7059
top: -1000px;

packages/core/src/extensions/Blocks/api/block.ts

+55-22
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Attribute, Node } from "@tiptap/core";
2-
import { BlockNoteEditor } from "../../..";
2+
import { BlockNoteDOMAttributes, BlockNoteEditor } from "../../..";
33
import styles from "../nodes/Block.module.css";
44
import {
55
BlockConfig,
@@ -9,6 +9,7 @@ import {
99
TipTapNode,
1010
TipTapNodeConfig,
1111
} from "./blockTypes";
12+
import { mergeCSSClasses } from "../../../shared/utils";
1213

1314
export function camelToDataKebab(str: string): string {
1415
return "data-" + str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
@@ -124,17 +125,17 @@ export function createBlockSpec<
124125
>(
125126
blockConfig: BlockConfig<BType, PSchema, ContainsInlineContent, BSchema>
126127
): BlockSpec<BType, PSchema> {
127-
const node = createTipTapBlock<BType>({
128+
const node = createTipTapBlock<
129+
BType,
130+
{
131+
editor: BlockNoteEditor<BSchema>;
132+
domAttributes?: BlockNoteDOMAttributes;
133+
}
134+
>({
128135
name: blockConfig.type,
129136
content: blockConfig.containsInlineContent ? "inline*" : "",
130137
selectable: blockConfig.containsInlineContent,
131138

132-
addOptions() {
133-
return {
134-
editor: undefined,
135-
};
136-
},
137-
138139
addAttributes() {
139140
return propsToAttributes(blockConfig);
140141
},
@@ -151,8 +152,21 @@ export function createBlockSpec<
151152
return ({ HTMLAttributes, getPos }) => {
152153
// Create blockContent element
153154
const blockContent = document.createElement("div");
154-
// Sets blockContent class
155-
blockContent.className = styles.blockContent;
155+
// Add custom HTML attributes
156+
const blockContentDOMAttributes =
157+
this.options.domAttributes?.blockContent || {};
158+
for (const [attribute, value] of Object.entries(
159+
blockContentDOMAttributes
160+
)) {
161+
if (attribute !== "class") {
162+
blockContent.setAttribute(attribute, value);
163+
}
164+
}
165+
// Set blockContent & custom classes
166+
blockContent.className = mergeCSSClasses(
167+
styles.blockContent,
168+
blockContentDOMAttributes.class
169+
);
156170
// Add blockContent HTML attribute
157171
blockContent.setAttribute("data-content-type", blockConfig.type);
158172
// Add props as HTML attributes in kebab-case with "data-" prefix
@@ -186,13 +200,24 @@ export function createBlockSpec<
186200

187201
// Render elements
188202
const rendered = blockConfig.render(block as any, editor);
189-
// Add inlineContent class to inline content
203+
// Add HTML attributes to contentDOM
190204
if ("contentDOM" in rendered) {
191-
rendered.contentDOM.className = `${
192-
rendered.contentDOM.className
193-
? rendered.contentDOM.className + " "
194-
: ""
195-
}${styles.inlineContent}`;
205+
const inlineContentDOMAttributes =
206+
this.options.domAttributes?.inlineContent || {};
207+
// Add custom HTML attributes
208+
for (const [attribute, value] of Object.entries(
209+
inlineContentDOMAttributes
210+
)) {
211+
if (attribute !== "class") {
212+
rendered.contentDOM.setAttribute(attribute, value);
213+
}
214+
}
215+
// Merge existing classes with inlineContent & custom classes
216+
rendered.contentDOM.className = mergeCSSClasses(
217+
rendered.contentDOM.className,
218+
styles.inlineContent,
219+
inlineContentDOMAttributes.class
220+
);
196221
}
197222
// Add elements to blockContent
198223
blockContent.appendChild(rendered.dom);
@@ -210,20 +235,28 @@ export function createBlockSpec<
210235
});
211236

212237
return {
213-
node: node,
238+
node: node as TipTapNode<BType>,
214239
propSchema: blockConfig.propSchema,
215240
};
216241
}
217242

218-
export function createTipTapBlock<Type extends string>(
219-
config: TipTapNodeConfig<Type>
220-
): TipTapNode<Type> {
243+
export function createTipTapBlock<
244+
Type extends string,
245+
Options extends {
246+
domAttributes?: BlockNoteDOMAttributes;
247+
} = {
248+
domAttributes?: BlockNoteDOMAttributes;
249+
},
250+
Storage = any
251+
>(
252+
config: TipTapNodeConfig<Type, Options, Storage>
253+
): TipTapNode<Type, Options, Storage> {
221254
// Type cast is needed as Node.name is mutable, though there is basically no
222255
// reason to change it after creation. Alternative is to wrap Node in a new
223256
// class, which I don't think is worth it since we'd only be changing 1
224257
// attribute to be read only.
225-
return Node.create({
258+
return Node.create<Options, Storage>({
226259
...config,
227260
group: "blockContent",
228-
}) as TipTapNode<Type>;
261+
}) as TipTapNode<Type, Options, Storage>;
229262
}

packages/core/src/extensions/Blocks/api/blockTypes.ts

+22-3
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,28 @@ import { BlockNoteEditor } from "../../../BlockNoteEditor";
44
import { InlineContent, PartialInlineContent } from "./inlineContentTypes";
55
import { DefaultBlockSchema } from "./defaultBlocks";
66

7+
export type BlockNoteDOMElement =
8+
| "editor"
9+
| "blockContainer"
10+
| "blockGroup"
11+
| "blockContent"
12+
| "inlineContent";
13+
14+
export type BlockNoteDOMAttributes = Partial<{
15+
[DOMElement in BlockNoteDOMElement]: Record<string, string>;
16+
}>;
17+
718
// A configuration for a TipTap node, but with stricter type constraints on the
819
// "name" and "group" properties. The "name" property is now always a string
920
// literal type, and the "blockGroup" property cannot be configured as it should
1021
// always be "blockContent". Used as the parameter in `createTipTapNode`.
1122
export type TipTapNodeConfig<
1223
Name extends string,
13-
Options = any,
24+
Options extends {
25+
domAttributes?: BlockNoteDOMAttributes;
26+
} = {
27+
domAttributes?: BlockNoteDOMAttributes;
28+
},
1429
Storage = any
1530
> = {
1631
[K in keyof NodeConfig<Options, Storage>]: K extends "name"
@@ -25,7 +40,11 @@ export type TipTapNodeConfig<
2540
// "blockGroup" property is now "blockContent". Returned by `createTipTapNode`.
2641
export type TipTapNode<
2742
Name extends string,
28-
Options = any,
43+
Options extends {
44+
domAttributes?: BlockNoteDOMAttributes;
45+
} = {
46+
domAttributes?: BlockNoteDOMAttributes;
47+
},
2948
Storage = any
3049
> = Node<Options, Storage> & {
3150
name: Name;
@@ -104,7 +123,7 @@ export type BlockConfig<
104123
// allowing for more advanced custom blocks.
105124
export type BlockSpec<Type extends string, PSchema extends PropSchema> = {
106125
readonly propSchema: PSchema;
107-
node: TipTapNode<Type>;
126+
node: TipTapNode<Type, any>;
108127
};
109128

110129
// Utility type. For a given object block schema, ensures that the key of each
+7-12
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
11
import { Node } from "@tiptap/core";
2-
import { BlockContainer } from "./nodes/BlockContainer";
3-
import { BlockGroup } from "./nodes/BlockGroup";
4-
5-
export const blocks: any[] = [
6-
BlockContainer,
7-
BlockGroup,
8-
Node.create({
9-
name: "doc",
10-
topNode: true,
11-
content: "blockGroup",
12-
}),
13-
];
2+
export { BlockContainer } from "./nodes/BlockContainer";
3+
export { BlockGroup } from "./nodes/BlockGroup";
4+
export const Doc = Node.create({
5+
name: "doc",
6+
topNode: true,
7+
content: "blockGroup",
8+
});

packages/core/src/extensions/Blocks/nodes/Block.module.css

+1-1
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ NESTED BLOCKS
260260

261261
/* TODO: would be nicer if defined from code */
262262

263-
.isEmpty.hasAnchor .inlineContent:before {
263+
.blockContent.isEmpty.hasAnchor .inlineContent:before {
264264
content: "Enter text or type '/' for commands";
265265
}
266266

0 commit comments

Comments
 (0)