Skip to content

Commit 3ddae25

Browse files
committed
Merge branch 'main' into new-releases
2 parents 4d18bc8 + 9a10c68 commit 3ddae25

File tree

35 files changed

+1590
-134
lines changed

35 files changed

+1590
-134
lines changed

packages/core/src/extensions/Blocks/nodes/BlockContainer.ts

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -192,18 +192,42 @@ export const BlockContainer = Node.create<{
192192
);
193193
}
194194

195-
// Changes the blockContent node type and adds the provided props as attributes. Also preserves all existing
196-
// attributes that are compatible with the new type.
197-
state.tr.setNodeMarkup(
198-
startPos,
199-
block.type === undefined
200-
? undefined
201-
: state.schema.nodes[block.type],
202-
{
203-
...contentNode.attrs,
204-
...block.props,
205-
}
206-
);
195+
// Since some block types contain inline content and others don't,
196+
// we either need to call setNodeMarkup to just update type &
197+
// attributes, or replaceWith to replace the whole blockContent.
198+
const oldType = contentNode.type.name;
199+
const newType = block.type || oldType;
200+
201+
const oldContentType = state.schema.nodes[oldType].spec.content;
202+
const newContentType = state.schema.nodes[newType].spec.content;
203+
204+
if (oldContentType === "inline*" && newContentType === "") {
205+
// Replaces the blockContent node with one of the new type and
206+
// adds the provided props as attributes. Also preserves all
207+
// existing attributes that are compatible with the new type.
208+
state.tr.replaceWith(
209+
startPos,
210+
endPos,
211+
state.schema.nodes[newType].create({
212+
...contentNode.attrs,
213+
...block.props,
214+
})
215+
);
216+
} else {
217+
// Changes the blockContent node type and adds the provided props
218+
// as attributes. Also preserves all existing attributes that are
219+
// compatible with the new type.
220+
state.tr.setNodeMarkup(
221+
startPos,
222+
block.type === undefined
223+
? undefined
224+
: state.schema.nodes[block.type],
225+
{
226+
...contentNode.attrs,
227+
...block.props,
228+
}
229+
);
230+
}
207231

208232
// Adds all provided props as attributes to the parent blockContainer node too, and also preserves existing
209233
// attributes.

packages/core/src/extensions/Blocks/nodes/BlockContent/HeadingBlockContent/HeadingBlockContent.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ export const HeadingBlockContent = createTipTapBlock<"heading">({
7373
return [
7474
"div",
7575
mergeAttributes(HTMLAttributes, {
76+
...blockContentDOMAttributes,
7677
class: mergeCSSClasses(
7778
styles.blockContent,
7879
blockContentDOMAttributes.class
@@ -82,6 +83,7 @@ export const HeadingBlockContent = createTipTapBlock<"heading">({
8283
[
8384
"h" + node.attrs.level,
8485
{
86+
...inlineContentDOMAttributes,
8587
class: mergeCSSClasses(
8688
styles.inlineContent,
8789
inlineContentDOMAttributes.class

packages/core/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ export const BulletListItemBlockContent = createTipTapBlock<"bulletListItem">({
9191
return [
9292
"div",
9393
mergeAttributes(HTMLAttributes, {
94+
...blockContentDOMAttributes,
9495
class: mergeCSSClasses(
9596
styles.blockContent,
9697
blockContentDOMAttributes.class
@@ -100,6 +101,7 @@ export const BulletListItemBlockContent = createTipTapBlock<"bulletListItem">({
100101
[
101102
"p",
102103
{
104+
...inlineContentDOMAttributes,
103105
class: mergeCSSClasses(
104106
styles.inlineContent,
105107
inlineContentDOMAttributes.class

packages/core/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ export const NumberedListItemBlockContent =
115115
return [
116116
"div",
117117
mergeAttributes(HTMLAttributes, {
118+
...blockContentDOMAttributes,
118119
class: mergeCSSClasses(
119120
styles.blockContent,
120121
blockContentDOMAttributes.class
@@ -126,6 +127,7 @@ export const NumberedListItemBlockContent =
126127
[
127128
"p",
128129
{
130+
...inlineContentDOMAttributes,
129131
class: mergeCSSClasses(
130132
styles.inlineContent,
131133
inlineContentDOMAttributes.class

packages/core/src/extensions/TrailingNode/TrailingNodeExtension.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,19 @@ export const TrailingNode = Extension.create<TrailingNodeOptions>({
6464
if (!lastNode || lastNode.type.name !== "blockContainer") {
6565
throw new Error("Expected blockContainer");
6666
}
67-
return lastNode.nodeSize > 4; // empty <block><content/></block> is length 4
67+
68+
const lastContentNode = lastNode.firstChild;
69+
70+
if (!lastContentNode) {
71+
throw new Error("Expected blockContent");
72+
}
73+
74+
// If last node is not empty (size > 4) or it doesn't contain
75+
// inline content, we need to add a trailing node.
76+
return (
77+
lastNode.nodeSize > 4 ||
78+
lastContentNode.type.spec.content !== "inline*"
79+
);
6880
},
6981
},
7082
}),

packages/react/src/BlockNoteTheme.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ export type ComponentStyles = Partial<{
4141
Editor: CSSObject;
4242
// Wraps Formatting Toolbar & Hyperlink Toolbar
4343
Toolbar: CSSObject;
44-
// Appears on hover for Formatting Toolbar & Hyperlink Toolbar buttons
44+
// Appears on hover for Formatting Toolbar
45+
// & Hyperlink Toolbar buttons
4546
Tooltip: CSSObject;
4647
SlashMenu: CSSObject;
4748
SideMenu: CSSObject;
@@ -105,6 +106,7 @@ export const blockNoteToMantineTheme = (theme: Theme): MantineThemeOverride => {
105106
boxShadow: shadow,
106107
color: theme.colors.menu.text,
107108
padding: "2px",
109+
overflowY: "scroll",
108110
".mantine-Menu-label": {
109111
backgroundColor: theme.colors.menu.background,
110112
color: theme.colors.menu.text,

packages/react/src/FormattingToolbar/components/DefaultButtons/ColorStyleButton.tsx

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { ColorIcon } from "../../../SharedComponents/ColorPicker/components/Colo
66
import { ColorPicker } from "../../../SharedComponents/ColorPicker/components/ColorPicker";
77
import { useEditorContentChange } from "../../../hooks/useEditorContentChange";
88
import { useEditorSelectionChange } from "../../../hooks/useEditorSelectionChange";
9+
import { usePreventMenuOverflow } from "../../../hooks/usePreventMenuOverflow";
910

1011
export const ColorStyleButton = <BSchema extends BlockSchema>(props: {
1112
editor: BlockNoteEditor<BSchema>;
@@ -51,8 +52,10 @@ export const ColorStyleButton = <BSchema extends BlockSchema>(props: {
5152
[props.editor]
5253
);
5354

55+
const { ref, updateMaxHeight } = usePreventMenuOverflow();
56+
5457
return (
55-
<Menu>
58+
<Menu onOpen={updateMaxHeight}>
5659
<Menu.Target>
5760
<ToolbarButton
5861
mainTooltip={"Colors"}
@@ -65,14 +68,16 @@ export const ColorStyleButton = <BSchema extends BlockSchema>(props: {
6568
)}
6669
/>
6770
</Menu.Target>
68-
<Menu.Dropdown>
69-
<ColorPicker
70-
textColor={currentTextColor}
71-
setTextColor={setTextColor}
72-
backgroundColor={currentBackgroundColor}
73-
setBackgroundColor={setBackgroundColor}
74-
/>
75-
</Menu.Dropdown>
71+
<div ref={ref}>
72+
<Menu.Dropdown>
73+
<ColorPicker
74+
textColor={currentTextColor}
75+
setTextColor={setTextColor}
76+
backgroundColor={currentBackgroundColor}
77+
setBackgroundColor={setBackgroundColor}
78+
/>
79+
</Menu.Dropdown>
80+
</div>
7681
</Menu>
7782
);
7883
};

packages/react/src/FormattingToolbar/components/DefaultDropdowns/BlockTypeDropdown.tsx

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { useMemo, useState } from "react";
2-
import { BlockNoteEditor, BlockSchema } from "@blocknote/core";
2+
import {
3+
Block,
4+
BlockNoteEditor,
5+
BlockSchema,
6+
PartialBlock,
7+
} from "@blocknote/core";
38
import { IconType } from "react-icons";
49
import {
510
RiH1,
@@ -20,41 +25,57 @@ export type BlockTypeDropdownItem = {
2025
type: string;
2126
props?: Record<string, string>;
2227
icon: IconType;
28+
isSelected: (block: Block<BlockSchema>) => boolean;
2329
};
2430

2531
export const defaultBlockTypeDropdownItems: BlockTypeDropdownItem[] = [
2632
{
2733
name: "Paragraph",
2834
type: "paragraph",
2935
icon: RiText,
36+
isSelected: (block) => block.type === "paragraph",
3037
},
3138
{
3239
name: "Heading 1",
3340
type: "heading",
3441
props: { level: "1" },
3542
icon: RiH1,
43+
isSelected: (block) =>
44+
block.type === "heading" &&
45+
"level" in block.props &&
46+
block.props.level === "1",
3647
},
3748
{
3849
name: "Heading 2",
3950
type: "heading",
4051
props: { level: "2" },
4152
icon: RiH2,
53+
isSelected: (block) =>
54+
block.type === "heading" &&
55+
"level" in block.props &&
56+
block.props.level === "2",
4257
},
4358
{
4459
name: "Heading 3",
4560
type: "heading",
4661
props: { level: "3" },
4762
icon: RiH3,
63+
isSelected: (block) =>
64+
block.type === "heading" &&
65+
"level" in block.props &&
66+
block.props.level === "3",
4867
},
4968
{
5069
name: "Bullet List",
5170
type: "bulletListItem",
5271
icon: RiListUnordered,
72+
isSelected: (block) => block.type === "bulletListItem",
5373
},
5474
{
5575
name: "Numbered List",
5676
type: "numberedListItem",
5777
icon: RiListOrdered,
78+
isSelected: (block) => block.type === "numberedListItem",
5879
},
5980
];
6081

@@ -100,22 +121,22 @@ export const BlockTypeDropdown = <BSchema extends BlockSchema>(props: {
100121
[block.type, filteredItems]
101122
);
102123

103-
const fullItems: ToolbarDropdownItemProps[] = useMemo(
104-
() =>
105-
filteredItems.map((item) => ({
106-
text: item.name,
107-
icon: item.icon,
108-
onClick: () => {
109-
props.editor.focus();
110-
props.editor.updateBlock(block, {
111-
type: item.type,
112-
props: {},
113-
});
114-
},
115-
isSelected: block.type === item.type,
116-
})),
117-
[block, filteredItems, props.editor]
118-
);
124+
const fullItems: ToolbarDropdownItemProps[] = useMemo(() => {
125+
const onClick = (item: BlockTypeDropdownItem) => {
126+
props.editor.focus();
127+
props.editor.updateBlock(block, {
128+
type: item.type,
129+
props: item.props,
130+
} as PartialBlock<BlockSchema>);
131+
};
132+
133+
return filteredItems.map((item) => ({
134+
text: item.name,
135+
icon: item.icon,
136+
onClick: () => onClick(item),
137+
isSelected: item.isSelected(block as Block<BlockSchema>),
138+
}));
139+
}, [block, filteredItems, props.editor]);
119140

120141
useEditorContentChange(props.editor, () => {
121142
setBlock(props.editor.getTextCursorPosition().block);

packages/react/src/SharedComponents/Toolbar/components/ToolbarDropdown.tsx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
ToolbarDropdownItemProps,
55
} from "./ToolbarDropdownItem";
66
import { ToolbarDropdownTarget } from "./ToolbarDropdownTarget";
7+
import { usePreventMenuOverflow } from "../../../hooks/usePreventMenuOverflow";
78

89
export type ToolbarDropdownProps = {
910
items: ToolbarDropdownItemProps[];
@@ -13,24 +14,31 @@ export type ToolbarDropdownProps = {
1314
export function ToolbarDropdown(props: ToolbarDropdownProps) {
1415
const selectedItem = props.items.filter((p) => p.isSelected)[0];
1516

17+
const { ref, updateMaxHeight } = usePreventMenuOverflow();
18+
1619
if (!selectedItem) {
1720
return null;
1821
}
1922

2023
return (
21-
<Menu exitTransitionDuration={0} disabled={props.isDisabled}>
24+
<Menu
25+
exitTransitionDuration={0}
26+
disabled={props.isDisabled}
27+
onOpen={updateMaxHeight}>
2228
<Menu.Target>
2329
<ToolbarDropdownTarget
2430
text={selectedItem.text}
2531
icon={selectedItem.icon}
2632
isDisabled={selectedItem.isDisabled}
2733
/>
2834
</Menu.Target>
29-
<Menu.Dropdown>
30-
{props.items.map((item) => (
31-
<ToolbarDropdownItem key={item.text} {...item} />
32-
))}
33-
</Menu.Dropdown>
35+
<div ref={ref}>
36+
<Menu.Dropdown>
37+
{props.items.map((item) => (
38+
<ToolbarDropdownItem key={item.text} {...item} />
39+
))}
40+
</Menu.Dropdown>
41+
</div>
3442
</Menu>
3543
);
3644
}

0 commit comments

Comments
 (0)