Skip to content

Commit 6c77c66

Browse files
committed
fix: some more targeted fixes for rendering
1 parent 2c45342 commit 6c77c66

3 files changed

Lines changed: 83 additions & 27 deletions

File tree

packages/core/src/editor/Block.css

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -768,7 +768,6 @@ div[data-type="modification"] {
768768
border-radius: 2px;
769769
}
770770
[data-diff-type="block-delete"] {
771-
display: block;
772771
padding: 2px 4px;
773772
margin: 2px 0;
774773
}

packages/core/src/y/extensions/YSuggestions.test.ts

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -487,11 +487,38 @@ describe("defaultMapDiffToDecorations", () => {
487487

488488

489489

490-
// ── block-delete with table cells (column delete) ─────────────────
491-
it("renders block-delete with table cells (simulating column delete)", () => {
490+
// ── block-delete with a single table cell (real-world column delete) ──
491+
it("renders block-delete with a single tableCell without extra wrapper", () => {
492+
const { editor, schema, doc } = setup();
493+
494+
// Each cell in a column delete is a separate diff with one tableCell
495+
const cell = schema.nodes.tableCell.create(null, [
496+
schema.nodes.tableParagraph.create(null, [schema.text("cell A")]),
497+
]);
498+
const fragment = Fragment.from(cell);
499+
500+
const diff: Diff = {
501+
type: "block-delete",
502+
from: 0,
503+
to: 0,
504+
content: fragment,
505+
attribution: baseAttribution,
506+
};
507+
508+
const mapper = defaultMapDiffToDecorations(editor);
509+
const result = mapper({ diff, doc, schema, index: 0 });
510+
const el = (result as any).type.toDOM() as HTMLElement;
511+
512+
// Attributes applied directly to the <td>, no wrapper div
513+
expect(el.tagName).toBe("TD");
514+
expect(el.className).toContain("pm-suggest--delete");
515+
expect(el.textContent).toContain("cell A");
516+
});
517+
518+
// ── block-delete with multiple table cells ───────────────────────────
519+
it("renders block-delete with multiple tableCells in a wrapper", () => {
492520
const { editor, schema, doc } = setup();
493521

494-
// A column delete in a table produces tableCell fragments
495522
const cell1 = schema.nodes.tableCell.create(null, [
496523
schema.nodes.tableParagraph.create(null, [schema.text("cell A")]),
497524
]);
@@ -512,7 +539,7 @@ describe("defaultMapDiffToDecorations", () => {
512539
const result = mapper({ diff, doc, schema, index: 0 });
513540
const el = (result as any).type.toDOM() as HTMLElement;
514541

515-
console.log("Table column delete HTML:", el.outerHTML);
542+
// Multiple cells need a wrapper
516543
expect(el.tagName).toBe("DIV");
517544
expect(el.textContent).toContain("cell A");
518545
expect(el.textContent).toContain("cell B");

packages/core/src/y/extensions/YSuggestions.ts

Lines changed: 52 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,10 @@ const fragmentHasNodeView = (
255255
* using BlockNote's `blocksToFullHTML` pipeline when possible, falling back
256256
* to `DOMSerializer`.
257257
*
258+
* Diff attributes (class, data-diff-type, author info, etc.) are applied
259+
* directly to the rendered root element rather than wrapping in an extra
260+
* container, keeping the DOM flat.
261+
*
258262
* For inline fragments the content is first wrapped in a paragraph node so
259263
* it can be converted to blocks; the rendered inline content is then extracted
260264
* from the `.bn-inline-content` wrapper so it stays inline in the document.
@@ -273,20 +277,24 @@ const renderDeletedFragment = (
273277
const tag = opts.isInline ? "span" : "div";
274278
const diffType = opts.isInline ? "inline-delete" : "block-delete";
275279

276-
const container = document.createElement(tag);
277-
container.className = "pm-suggest pm-suggest--delete";
278-
container.setAttribute("data-diff-type", diffType);
279-
if (opts.authorIds.length) {
280-
container.setAttribute("data-diff-user-id", opts.authorIds.join(","));
281-
}
282-
if (opts.color) {
283-
container.style.setProperty("--author-color", opts.color);
284-
}
285-
container.setAttribute("title", opts.title);
286-
container.contentEditable = "false";
280+
/** Apply diff attributes to an element in-place. */
281+
const applyDiffAttrs = (el: HTMLElement) => {
282+
el.classList.add("pm-suggest", "pm-suggest--delete");
283+
el.setAttribute("data-diff-type", diffType);
284+
if (opts.authorIds.length) {
285+
el.setAttribute("data-diff-user-id", opts.authorIds.join(","));
286+
}
287+
if (opts.color) {
288+
el.style.setProperty("--author-color", opts.color);
289+
}
290+
el.setAttribute("title", opts.title);
291+
el.contentEditable = "false";
292+
};
287293

288294
if (fragment.size === 0) {
289-
return container;
295+
const empty = document.createElement(tag);
296+
applyDiffAttrs(empty);
297+
return empty;
290298
}
291299

292300
// For inline content, wrap in a paragraph so it forms a valid block tree.
@@ -298,6 +306,8 @@ const renderDeletedFragment = (
298306
blockFragment = Fragment.from(paragraphNode);
299307
} else {
300308
// Can't wrap in paragraph — fall back to DOMSerializer
309+
const container = document.createElement(tag);
310+
applyDiffAttrs(container);
301311
const serializer = DOMSerializer.fromSchema(schema);
302312
container.appendChild(
303313
serializer.serializeFragment(fragment, { document }),
@@ -316,7 +326,7 @@ const renderDeletedFragment = (
316326
: null;
317327
const isSubBlockContent = wrappingPath && wrappingPath.length > 3;
318328

319-
let rendered = false;
329+
let rendered: HTMLElement | null = null;
320330

321331
if (!isSubBlockContent) {
322332
const ghostDoc = wrapFragmentInDoc(blockFragment, schema);
@@ -328,23 +338,31 @@ const renderDeletedFragment = (
328338
editor.pmSchema,
329339
);
330340
const html = editor.blocksToFullHTML(slicedBlocks.blocks);
341+
const temp = document.createElement("div");
342+
temp.innerHTML = html;
331343

332344
if (opts.isInline) {
333345
// Extract just the inline content from the block wrapper.
334-
const temp = document.createElement("div");
335-
temp.innerHTML = html;
336346
const inlineContentEl = temp.querySelector(".bn-inline-content");
337347
if (inlineContentEl) {
348+
const span = document.createElement("span");
338349
while (inlineContentEl.firstChild) {
339-
container.appendChild(inlineContentEl.firstChild);
350+
span.appendChild(inlineContentEl.firstChild);
340351
}
352+
rendered = span;
341353
} else {
342-
container.innerHTML = html;
354+
// No .bn-inline-content found — use the root element
355+
rendered = temp.firstElementChild as HTMLElement | null;
343356
}
344357
} else {
345-
container.innerHTML = html;
358+
// Extract the .bn-block-outer element so we don't add an extra
359+
// bn-block-group wrapper — the widget is already inserted inside
360+
// an existing block-group in the document.
361+
const blockOuter = temp.querySelector(
362+
".bn-block-outer",
363+
) as HTMLElement | null;
364+
rendered = blockOuter ?? (temp.firstElementChild as HTMLElement | null);
346365
}
347-
rendered = true;
348366
} catch (e) {
349367
// prosemirrorSliceToSlicedBlocks doesn't support all node structures.
350368
// Fall through to DOMSerializer fallback.
@@ -360,12 +378,24 @@ const renderDeletedFragment = (
360378
// Fallback: use DOMSerializer for sub-block nodes (tableCell, etc.)
361379
// or when wrapping/conversion failed.
362380
const serializer = DOMSerializer.fromSchema(schema);
363-
container.appendChild(
364-
serializer.serializeFragment(fragment, { document }),
381+
const serialized = serializer.serializeFragment(fragment, { document });
382+
383+
// If the fragment serializes to a single element, use it directly
384+
// to avoid an extra wrapper (e.g. <td> stays as <td>, not <div><td>).
385+
const children = Array.from(serialized.childNodes).filter(
386+
(n): n is HTMLElement => n.nodeType === 1, // ELEMENT_NODE
365387
);
388+
if (children.length === 1 && serialized.childNodes.length === 1) {
389+
rendered = children[0];
390+
} else {
391+
const container = document.createElement(tag);
392+
container.appendChild(serialized);
393+
rendered = container;
394+
}
366395
}
367396

368-
return container;
397+
applyDiffAttrs(rendered);
398+
return rendered;
369399
};
370400

371401
/**

0 commit comments

Comments
 (0)