Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
93e4c12
Adding H4/H5 blocks
DrewJohnsonGT Apr 22, 2025
4c893f5
Adding translations for h4/h5
DrewJohnsonGT Apr 22, 2025
285c600
Styles
DrewJohnsonGT Apr 22, 2025
75c144e
Font size and test
DrewJohnsonGT Apr 22, 2025
a969173
Update snapshot tests
DrewJohnsonGT Apr 22, 2025
86da278
locales and other h6 support updates
DrewJohnsonGT Apr 23, 2025
3eb5a97
Update snapshot tests with h6
DrewJohnsonGT Apr 23, 2025
9f47f75
Add the h6 icon for slash menu item
DrewJohnsonGT Apr 23, 2025
0cf25c0
Test H6 notion snapshot
DrewJohnsonGT Apr 23, 2025
37f7501
Merge branch 'main' into support-h4-h6
DrewJohnsonGT May 2, 2025
90cfa70
Merge branch 'main' into support-h4-h6
DrewJohnsonGT May 7, 2025
72f9a1d
Merge branch 'main' into support-h4-h6
DrewJohnsonGT May 13, 2025
ed36dc6
Merge branch 'main' into support-h4-h6
DrewJohnsonGT May 15, 2025
e4da76f
Merge branch 'main' into support-h4-h6
DrewJohnsonGT May 16, 2025
738984b
Merge branch 'main' into support-h4-h6
DrewJohnsonGT May 19, 2025
d06e86e
zh locale and pdf export
DrewJohnsonGT May 20, 2025
16d3b9d
Merge branch 'TypeCellOS:main' into support-h4-h6
DrewJohnsonGT May 20, 2025
1e0bd42
Merge branch 'main' into support-h4-h6
DrewJohnsonGT May 20, 2025
c424493
Merge branch 'main' into support-h4-h6
DrewJohnsonGT May 20, 2025
c0826e2
Update tests for new heading levels
DrewJohnsonGT May 21, 2025
fbb0bf9
Merge branch 'main' into support-h4-h6
DrewJohnsonGT May 22, 2025
5cabb32
Merge branch 'main' into support-h4-h6
DrewJohnsonGT May 23, 2025
5034f4d
Merge branch 'main' into support-h4-h6
DrewJohnsonGT Jun 2, 2025
fb109e5
Merge branch 'main' into support-h4-h6
DrewJohnsonGT Jun 9, 2025
aa73f29
Merge branch 'main' into support-h4-h6
DrewJohnsonGT Jun 10, 2025
a31f910
Merge branch 'main' into support-h4-h6
DrewJohnsonGT Jun 12, 2025
c4ba1bc
Merge branch 'main' into support-h4-h6
DrewJohnsonGT Jun 13, 2025
023cdbe
Merge branch 'main' into support-h4-h6
DrewJohnsonGT Jun 18, 2025
13a4202
Merge branch 'main' into support-h4-h6
nperez0111 Jun 20, 2025
9431c32
feat: make headings configurable up to 6 levels
nperez0111 Jun 23, 2025
f4d5a80
test: update snaps
nperez0111 Jun 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/pages/docs/editor-basics/setup.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ type BlockNoteEditorOptions = {
width?: number;
class?: string;
}) => Plugin;
heading?: {
levels?: number[];
};
initialContent?: PartialBlock[];
pasteHandler?: (context: {
event: ClipboardEvent;
Expand Down Expand Up @@ -73,6 +76,8 @@ The hook takes two optional parameters:

`initialContent:` The content that should be in the editor when it's created, represented as an array of [Partial Blocks](/docs/manipulating-blocks#partial-blocks).

`heading`: Configuration for headings. Allows you to configure the number of levels of headings that should be available in the editor. Defaults to `[1, 2, 3]`. Configurable up to 6 levels of headings.

`pasteHandler`: A function that can be used to override the default paste behavior. See [Paste Handling](/docs/advanced/paste-handling) for more.

`resolveFileUrl:` Function to resolve file URLs for display/download. Useful for creating authenticated URLs or implementing custom protocols.
Expand Down
111 changes: 37 additions & 74 deletions packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ import {
import { createDefaultBlockDOMOutputSpec } from "../defaultBlockHelpers.js";
import { defaultProps } from "../defaultProps.js";
import { createToggleWrapper } from "../ToggleWrapper/createToggleWrapper.js";
import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";

const HEADING_LEVELS = [1, 2, 3, 4, 5, 6] as const;

export const headingPropSchema = {
...defaultProps,
level: { default: 1, values: [1, 2, 3] as const },
level: { default: 1, values: HEADING_LEVELS },
isToggleable: { default: false },
} satisfies PropSchema;

Expand All @@ -28,8 +31,9 @@ const HeadingBlockContent = createStronglyTypedTiptapNode({
},

addInputRules() {
const editor = this.options.editor as BlockNoteEditor<any, any, any>;
return [
...[1, 2, 3].map((level) => {
...editor.settings.heading.levels.map((level) => {
// Creates a heading of appropriate level when starting with "#", "##", or "###".
return new InputRule({
find: new RegExp(`^(#{${level}})\\s$`),
Expand Down Expand Up @@ -61,87 +65,46 @@ const HeadingBlockContent = createStronglyTypedTiptapNode({
},

addKeyboardShortcuts() {
return {
"Mod-Alt-1": () => {
const blockInfo = getBlockInfoFromSelection(this.editor.state);
if (
!blockInfo.isBlockContainer ||
blockInfo.blockContent.node.type.spec.content !== "inline*"
) {
return true;
}

// call updateBlockCommand
return this.editor.commands.command(
updateBlockCommand(blockInfo.bnBlock.beforePos, {
type: "heading",
props: {
level: 1 as any,
},
}),
);
},
"Mod-Alt-2": () => {
const blockInfo = getBlockInfoFromSelection(this.editor.state);
if (
!blockInfo.isBlockContainer ||
blockInfo.blockContent.node.type.spec.content !== "inline*"
) {
return true;
}

return this.editor.commands.command(
updateBlockCommand(blockInfo.bnBlock.beforePos, {
type: "heading",
props: {
level: 2 as any,
},
}),
);
},
"Mod-Alt-3": () => {
const blockInfo = getBlockInfoFromSelection(this.editor.state);
if (
!blockInfo.isBlockContainer ||
blockInfo.blockContent.node.type.spec.content !== "inline*"
) {
return true;
}

return this.editor.commands.command(
updateBlockCommand(blockInfo.bnBlock.beforePos, {
type: "heading",
props: {
level: 3 as any,
},
}),
);
},
};
const editor = this.options.editor as BlockNoteEditor<any, any, any>;

return Object.fromEntries(
editor.settings.heading.levels.map((level) => [
`Mod-Alt-${level}`,
() => {
const blockInfo = getBlockInfoFromSelection(this.editor.state);
if (
!blockInfo.isBlockContainer ||
blockInfo.blockContent.node.type.spec.content !== "inline*"
) {
return true;
}

return this.editor.commands.command(
updateBlockCommand(blockInfo.bnBlock.beforePos, {
type: "heading",
props: {
level: level as any,
},
}),
);
},
]),
);
},
parseHTML() {
const editor = this.options.editor as BlockNoteEditor<any, any, any>;

return [
// Parse from internal HTML.
{
tag: "div[data-content-type=" + this.name + "]",
contentElement: ".bn-inline-content",
},
// Parse from external HTML.
{
tag: "h1",
attrs: { level: 1 },
node: "heading",
},
{
tag: "h2",
attrs: { level: 2 },
...editor.settings.heading.levels.map((level) => ({
tag: `h${level}`,
attrs: { level },
node: "heading",
},
{
tag: "h3",
attrs: { level: 3 },
node: "heading",
},
})),
];
},

Expand Down
18 changes: 18 additions & 0 deletions packages/core/src/editor/Block.css
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,15 @@ NESTED BLOCKS
[data-content-type="heading"][data-level="3"] {
--level: 1.3em;
}
[data-content-type="heading"][data-level="4"] {
--level: 1em;
}
[data-content-type="heading"][data-level="5"] {
--level: 0.9em;
}
[data-content-type="heading"][data-level="6"] {
--level: 0.8em;
}

[data-prev-level="1"] {
--prev-level: 3em;
Expand All @@ -140,6 +149,15 @@ NESTED BLOCKS
[data-prev-level="3"] {
--prev-level: 1.3em;
}
[data-prev-level="4"] {
--prev-level: 1em;
}
[data-prev-level="5"] {
--prev-level: 0.9em;
}
[data-prev-level="6"] {
--prev-level: 0.8em;
}

.bn-block-outer[data-prev-type="heading"] > .bn-block > .bn-block-content {
font-size: var(--prev-level);
Expand Down
18 changes: 18 additions & 0 deletions packages/core/src/editor/BlockNoteEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,18 @@ export type BlockNoteEditorOptions<
class?: string;
}) => Plugin;

/**
* Configuration for headings
*/
heading?: {
/**
* The levels of headings that should be available in the editor.
* @note Configurable up to 6 levels of headings.
* @default [1, 2, 3]
*/
levels?: (1 | 2 | 3 | 4 | 5 | 6)[];
};

/**
* The content that should be in the editor when it's created, represented as an array of partial block objects.
*/
Expand Down Expand Up @@ -527,6 +539,9 @@ export class BlockNoteEditor<
headers: boolean;
};
codeBlock: CodeBlockOptions;
heading: {
levels: (1 | 2 | 3 | 4 | 5 | 6)[];
};
};

public static create<
Expand Down Expand Up @@ -580,6 +595,9 @@ export class BlockNoteEditor<
supportedLanguages: options?.codeBlock?.supportedLanguages ?? {},
createHighlighter: options?.codeBlock?.createHighlighter ?? undefined,
},
heading: {
levels: options?.heading?.levels ?? [1, 2, 3],
},
};

// apply defaults
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/editor/editor.css
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ Tippy popups that are appended to document.body directly
.bn-default-styles h1,
.bn-default-styles h2,
.bn-default-styles h3,
.bn-default-styles h4,
.bn-default-styles h5,
.bn-default-styles h6,
.bn-default-styles li {
margin: 0;
padding: 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,37 +122,6 @@ export function getDefaultSlashMenuItems<
key: "heading_3",
...editor.dictionary.slash_menu.heading_3,
},
{
onItemClick: () => {
insertOrUpdateBlock(editor, {
type: "heading",
props: { level: 1, isToggleable: true },
});
},
key: "toggle_heading",
...editor.dictionary.slash_menu.toggle_heading,
},
{
onItemClick: () => {
insertOrUpdateBlock(editor, {
type: "heading",
props: { level: 2, isToggleable: true },
});
},

key: "toggle_heading_2",
...editor.dictionary.slash_menu.toggle_heading_2,
},
{
onItemClick: () => {
insertOrUpdateBlock(editor, {
type: "heading",
props: { level: 3, isToggleable: true },
});
},
key: "toggle_heading_3",
...editor.dictionary.slash_menu.toggle_heading_3,
},
);
}

Expand Down Expand Up @@ -346,6 +315,57 @@ export function getDefaultSlashMenuItems<
});
}

if (checkDefaultBlockTypeInSchema("heading", editor)) {
items.push(
{
onItemClick: () => {
insertOrUpdateBlock(editor, {
type: "heading",
props: { level: 1, isToggleable: true },
});
},
key: "toggle_heading",
...editor.dictionary.slash_menu.toggle_heading,
},
{
onItemClick: () => {
insertOrUpdateBlock(editor, {
type: "heading",
props: { level: 2, isToggleable: true },
});
},

key: "toggle_heading_2",
...editor.dictionary.slash_menu.toggle_heading_2,
},
{
onItemClick: () => {
insertOrUpdateBlock(editor, {
type: "heading",
props: { level: 3, isToggleable: true },
});
},
key: "toggle_heading_3",
...editor.dictionary.slash_menu.toggle_heading_3,
},
);

editor.settings.heading.levels
.filter((level): level is 4 | 5 | 6 => level > 3)
.forEach((level) => {
items.push({
onItemClick: () => {
insertOrUpdateBlock(editor, {
type: "heading",
props: { level: level },
});
},
key: `heading_${level}`,
...editor.dictionary.slash_menu[`heading_${level}`],
});
});
}

items.push({
onItemClick: () => {
editor.openSuggestionMenu(":", {
Expand Down
24 changes: 21 additions & 3 deletions packages/core/src/i18n/locales/ar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,41 @@ export const ar: Dictionary = {
aliases: ["ع3", "عنوان3", "عنوان فرعي"],
group: "العناوين",
},
heading_4: {
title: "عنوان 4",
subtext: "عنوان فرعي ثانوي صغير",
aliases: ["ع4", "عنوان4", "عنوان فرعي صغير"],
group: "العناوين الفرعية",
},
heading_5: {
title: "عنوان 5",
subtext: "عنوان فرعي صغير",
aliases: ["ع5", "عنوان5", "عنوان فرعي صغير"],
group: "العناوين الفرعية",
},
heading_6: {
title: "عنوان 6",
subtext: "أدنى مستوى للعناوين",
aliases: ["ع6", "عنوان6", "العنوان الفرعي الأدنى"],
group: "العناوين الفرعية",
},
toggle_heading: {
title: "عنوان قابل للطي 1",
subtext: "عنوان قابل للطي لإظهار وإخفاء المحتوى",
aliases: ["ع", "عنوان1", "ع1", "قابل للطي", "طي"],
group: "العناوين",
group: "العناوين الفرعية",
},
toggle_heading_2: {
title: "عنوان قابل للطي 2",
subtext: "عنوان فرعي قابل للطي لإظهار وإخفاء المحتوى",
aliases: ["ع2", "عنوان2", "عنوان فرعي", "قابل للطي", "طي"],
group: "العناوين",
group: "العناوين الفرعية",
},
toggle_heading_3: {
title: "عنوان قابل للطي 3",
subtext: "عنوان فرعي ثانوي قابل للطي لإظهار وإخفاء المحتوى",
aliases: ["ع3", "عنوان3", "عنوان فرعي", "قابل للطي", "طي"],
group: "العناوين",
group: "العناوين الفرعية",
},
quote: {
title: "اقتباس",
Expand Down
Loading
Loading