Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,10 @@ describe("Test removeBlocks", () => {

expect(getEditor().document).toMatchSnapshot();
});

it("Remove all child blocks", () => {
removeBlocks(getEditor(), ["nested-paragraph-0"]);

expect(getEditor().document).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import { Node } from "prosemirror-model";
import { Transaction } from "prosemirror-state";

import { Block } from "../../../../blocks/defaultBlocks.js";
import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor";
import {
Expand All @@ -9,84 +6,7 @@ import {
InlineContentSchema,
StyleSchema,
} from "../../../../schema/index.js";
import { nodeToBlock } from "../../../nodeConversions/nodeToBlock.js";

export function removeBlocksWithCallback<
BSchema extends BlockSchema,
I extends InlineContentSchema,
S extends StyleSchema
>(
editor: BlockNoteEditor<BSchema, I, S>,
blocksToRemove: BlockIdentifier[],
// Should return new removedSize.
callback?: (
node: Node,
pos: number,
tr: Transaction,
removedSize: number
) => number
): Block<BSchema, I, S>[] {
const ttEditor = editor._tiptapEditor;
const tr = ttEditor.state.tr;

const idsOfBlocksToRemove = new Set<string>(
blocksToRemove.map((block) =>
typeof block === "string" ? block : block.id
)
);
const removedBlocks: Block<BSchema, I, S>[] = [];
let removedSize = 0;

ttEditor.state.doc.descendants((node, pos) => {
// Skips traversing nodes after all target blocks have been removed.
if (idsOfBlocksToRemove.size === 0) {
return false;
}

// Keeps traversing nodes if block with target ID has not been found.
if (
!node.type.isInGroup("bnBlock") ||
!idsOfBlocksToRemove.has(node.attrs.id)
) {
return true;
}

// Saves the block that is being deleted.
removedBlocks.push(
nodeToBlock(
node,
editor.schema.blockSchema,
editor.schema.inlineContentSchema,
editor.schema.styleSchema,
editor.blockCache
)
);
idsOfBlocksToRemove.delete(node.attrs.id);

// Removes the block and calculates the change in document size.
removedSize = callback?.(node, pos, tr, removedSize) || removedSize;
const oldDocSize = tr.doc.nodeSize;
tr.delete(pos - removedSize - 1, pos - removedSize + node.nodeSize + 1);
const newDocSize = tr.doc.nodeSize;
removedSize += oldDocSize - newDocSize;

return false;
});

// Throws an error if now all blocks could be found.
if (idsOfBlocksToRemove.size > 0) {
const notFoundIds = [...idsOfBlocksToRemove].join("\n");

throw Error(
"Blocks with the following IDs could not be found in the editor: " +
notFoundIds
);
}

editor.dispatch(tr);

return removedBlocks;
}
import { removeAndInsertBlocks } from "../replaceBlocks/replaceBlocks.js";

export function removeBlocks<
BSchema extends BlockSchema,
Expand All @@ -96,5 +16,5 @@ export function removeBlocks<
editor: BlockNoteEditor<BSchema, I, S>,
blocksToRemove: BlockIdentifier[]
): Block<BSchema, I, S>[] {
return removeBlocksWithCallback(editor, blocksToRemove);
return removeAndInsertBlocks(editor, blocksToRemove, []).removedBlocks;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { Node } from "prosemirror-model";

import { Block, PartialBlock } from "../../../../blocks/defaultBlocks.js";
import type { BlockNoteEditor } from "../../../../editor/BlockNoteEditor";
import {
Expand All @@ -8,11 +6,11 @@ import {
InlineContentSchema,
StyleSchema,
} from "../../../../schema/index.js";
import { Node } from "prosemirror-model";
import { blockToNode } from "../../../nodeConversions/blockToNode.js";
import { nodeToBlock } from "../../../nodeConversions/nodeToBlock.js";
import { removeBlocksWithCallback } from "../removeBlocks/removeBlocks.js";

export function replaceBlocks<
export function removeAndInsertBlocks<
BSchema extends BlockSchema,
I extends InlineContentSchema,
S extends StyleSchema
Expand All @@ -24,35 +22,98 @@ export function replaceBlocks<
insertedBlocks: Block<BSchema, I, S>[];
removedBlocks: Block<BSchema, I, S>[];
} {
const ttEditor = editor._tiptapEditor;
let tr = ttEditor.state.tr;

// Converts the `PartialBlock`s to ProseMirror nodes to insert them into the
// document.
const nodesToInsert: Node[] = [];
for (const block of blocksToInsert) {
nodesToInsert.push(
blockToNode(block, editor.pmSchema, editor.schema.styleSchema)
);
}

const idsOfBlocksToRemove = new Set<string>(
blocksToRemove.map((block) =>
typeof block === "string" ? block : block.id
)
);
const removedBlocks: Block<BSchema, I, S>[] = [];

const idOfFirstBlock =
typeof blocksToRemove[0] === "string"
? blocksToRemove[0]
: blocksToRemove[0].id;
const removedBlocks = removeBlocksWithCallback(
editor,
blocksToRemove,
(node, pos, tr, removedSize) => {
if (node.attrs.id === idOfFirstBlock) {
const oldDocSize = tr.doc.nodeSize;
tr.insert(pos, nodesToInsert);
const newDocSize = tr.doc.nodeSize;

return removedSize + oldDocSize - newDocSize;
}

return removedSize;
let removedSize = 0;

ttEditor.state.doc.descendants((node, pos) => {
// Skips traversing nodes after all target blocks have been removed.
if (idsOfBlocksToRemove.size === 0) {
return false;
}

// Keeps traversing nodes if block with target ID has not been found.
if (
!node.type.isInGroup("bnBlock") ||
!idsOfBlocksToRemove.has(node.attrs.id)
) {
return true;
}

// Saves the block that is being deleted.
removedBlocks.push(
nodeToBlock(
node,
editor.schema.blockSchema,
editor.schema.inlineContentSchema,
editor.schema.styleSchema,
editor.blockCache
)
);
idsOfBlocksToRemove.delete(node.attrs.id);

if (blocksToInsert.length > 0 && node.attrs.id === idOfFirstBlock) {
const oldDocSize = tr.doc.nodeSize;
tr = tr.insert(pos, nodesToInsert);
const newDocSize = tr.doc.nodeSize;

removedSize += oldDocSize - newDocSize;
}
);

// Now that the `PartialBlock`s have been converted to nodes, we can
// re-convert them into full `Block`s.
const oldDocSize = tr.doc.nodeSize;
// Checks if the block is the only child of its parent. In this case, we
// need to delete the parent `blockGroup` node instead of just the
// `blockContainer`.
const $pos = tr.doc.resolve(pos - removedSize);
if (
$pos.node().type.name === "blockGroup" &&
$pos.node($pos.depth - 1).type.name !== "doc" &&
$pos.node().childCount === 1
) {
tr = tr.delete($pos.before(), $pos.after());
} else {
tr = tr.delete(pos - removedSize, pos - removedSize + node.nodeSize);
}
const newDocSize = tr.doc.nodeSize;
removedSize += oldDocSize - newDocSize;

return false;
});

// Throws an error if now all blocks could be found.
if (idsOfBlocksToRemove.size > 0) {
const notFoundIds = [...idsOfBlocksToRemove].join("\n");

throw Error(
"Blocks with the following IDs could not be found in the editor: " +
notFoundIds
);
}

editor.dispatch(tr);

// Converts the nodes created from `blocksToInsert` into full `Block`s.
const insertedBlocks: Block<BSchema, I, S>[] = [];
for (const node of nodesToInsert) {
insertedBlocks.push(
Expand All @@ -68,3 +129,18 @@ export function replaceBlocks<

return { insertedBlocks, removedBlocks };
}

export function replaceBlocks<
BSchema extends BlockSchema,
I extends InlineContentSchema,
S extends StyleSchema
>(
editor: BlockNoteEditor<BSchema, I, S>,
blocksToRemove: BlockIdentifier[],
blocksToInsert: PartialBlock<BSchema, I, S>[]
): {
insertedBlocks: Block<BSchema, I, S>[];
removedBlocks: Block<BSchema, I, S>[];
} {
return removeAndInsertBlocks(editor, blocksToRemove, blocksToInsert);
}
Original file line number Diff line number Diff line change
Expand Up @@ -296,9 +296,11 @@ class ColumnResizePluginView implements PluginView {
this.editor.sideMenu.unfreezeMenu();
};

// This is a required method for PluginView, so we get a type error if we
// don't implement it.
update: undefined;
destroy() {
this.view.dom.removeEventListener("mousedown", this.mouseDownHandler);
document.body.removeEventListener("mousemove", this.mouseMoveHandler);
document.body.removeEventListener("mouseup", this.mouseUpHandler);
}
}

const createColumnResizePlugin = (editor: BlockNoteEditor<any, any, any>) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,17 +130,24 @@ export function multiColumnDropCursor(
(b) => b.id === blockInfo.bnBlock.node.attrs.id
);

const newChildren = columnList.children.toSpliced(
position === "left" ? index : index + 1,
0,
{
const newChildren = columnList.children
// If the dragged block is in one of the columns, remove it.
.map((column) => ({
...column,
children: column.children.filter(
(block) => block.id !== draggedBlock.id
),
}))
// Remove empty columns (can happen when dragged block is removed).
.filter((column) => column.children.length > 0)
// Insert the dragged block in the correct position.
.toSpliced(position === "left" ? index : index + 1, 0, {
type: "column",
children: [draggedBlock],
props: {},
content: undefined,
id: UniqueID.options.generateID(),
}
);
});

editor.removeBlocks([draggedBlock]);

Expand Down Expand Up @@ -201,14 +208,25 @@ class DropCursorView {
const handler = (e: Event) => {
(this as any)[name](e);
};
editorView.dom.addEventListener(name, handler);
editorView.dom.addEventListener(
name,
handler,
// drop event captured in bubbling phase to make sure
// "cursorPos" is set to undefined before the "handleDrop" handler is called
// (otherwise an error could be thrown, see https://github.com/TypeCellOS/BlockNote/pull/1240)
name === "drop" ? true : undefined
);
return { name, handler };
});
}

destroy() {
this.handlers.forEach(({ name, handler }) =>
this.editorView.dom.removeEventListener(name, handler)
this.editorView.dom.removeEventListener(
name,
handler,
name === "drop" ? true : undefined
)
);
}

Expand Down Expand Up @@ -267,6 +285,10 @@ class DropCursorView {
) {
const block = this.editorView.nodeDOM(this.cursorPos.pos);

if (!block) {
throw new Error("nodeDOM returned null in updateOverlay");
}

const blockRect = (block as HTMLElement).getBoundingClientRect();
const halfWidth = (this.width / 2) * scaleY;
const left =
Expand Down Expand Up @@ -434,7 +456,7 @@ class DropCursorView {
target = point;
}
}
// console.log("target", target);

this.setCursor({ pos: target, position });
this.scheduleRemoval(5000);
}
Expand All @@ -445,7 +467,7 @@ class DropCursorView {
}

drop() {
this.scheduleRemoval(20);
this.setCursor(undefined);
}

dragleave(event: DragEvent) {
Expand Down
Loading
Loading