@jackuait/blok 0.10.7 → 0.10.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -130,7 +130,7 @@ var a = {
130
130
  RIGHT: 2,
131
131
  BACKWARD: 3,
132
132
  FORWARD: 4
133
- }, l = () => "0.10.7", u = /* @__PURE__ */ function(e) {
133
+ }, l = () => "0.10.8", u = /* @__PURE__ */ function(e) {
134
134
  return e.VERBOSE = "VERBOSE", e.INFO = "INFO", e.WARN = "WARN", e.ERROR = "ERROR", e;
135
135
  }({}), d = (e, t, n = "log", r, i = "color: inherit") => {
136
136
  let a = typeof console > "u" ? void 0 : console;
@@ -1,6 +1,6 @@
1
1
  import { r as e } from "./chunk-D6kmoKXy.mjs";
2
2
  import { n as t, t as n } from "./objectSpread2-CWwMYL_U.mjs";
3
- import { t as r } from "./objectWithoutProperties-D0XxKB4n.mjs";
3
+ import { t as r } from "./objectWithoutProperties-Dci1-l7D.mjs";
4
4
  import { t as i } from "./ccount-C9Y7nqDe.mjs";
5
5
  //#region node_modules/zwitch/index.js
6
6
  var a = {}.hasOwnProperty;
@@ -1,5 +1,5 @@
1
1
  import { n as e, t } from "./objectSpread2-CWwMYL_U.mjs";
2
- import { t as n } from "./objectWithoutProperties-D0XxKB4n.mjs";
2
+ import { t as n } from "./objectWithoutProperties-Dci1-l7D.mjs";
3
3
  //#region node_modules/@shikijs/engine-javascript/dist/scanner-BFcBmQR1.mjs
4
4
  var r = 4294967295, i = class {
5
5
  constructor(t, n = {}) {
@@ -1,4 +1,4 @@
1
- import { n as e } from "./lightweight-i18n-DTYoSr_o.mjs";
1
+ import { n as e } from "./lightweight-i18n-DSjG0iTr.mjs";
2
2
  //#region src/components/i18n/i18next-loader.ts
3
3
  var t = async (t, n) => {
4
4
  let r = (await import("./i18next-CLUkHqmV.mjs")).default.createInstance(), i = {
@@ -1,4 +1,4 @@
1
- import { $ as e, $t as t, A as n, At as r, B as i, Bt as a, Ct as o, D as s, Et as c, F as l, Ft as u, G as d, Gt as f, H as p, Ht as m, I as h, It as g, J as _, Jt as v, K as y, Kt as b, L as ee, Lt as x, M as te, Mt as S, Nn as ne, Nt as re, O as ie, Ot as C, P as ae, Pt as oe, Q as se, Qt as ce, R as le, Rn as w, Rt as ue, St as de, Tt as fe, U as pe, Ut as me, V as he, W as ge, Wt as _e, X as ve, Xt as ye, Y as be, Yt as xe, Z as Se, Zt as Ce, _n as we, _t as Te, a as Ee, at as De, bt as Oe, c as ke, cn as T, ct as Ae, d as je, dr as E, dt as Me, en as Ne, et as Pe, f as Fe, ft as Ie, g as Le, gt as Re, h as ze, ht as Be, i as Ve, ir as He, it as Ue, j as We, jt as Ge, k as Ke, kt as qe, l as Je, ln as Ye, lt as Xe, mn as Ze, mt as Qe, n as $e, nt as et, o as tt, ot as nt, p as rt, pn as D, pt as it, q as at, qt as ot, r as st, rn as ct, rt as lt, s as ut, sn as dt, st as ft, t as pt, tt as mt, u as O, un as ht, ur as k, ut as gt, v as _t, vn as vt, vt as yt, wt as bt, xt, z as St, zt as Ct } from "./constants-BQ1-lyZI.mjs";
1
+ import { $ as e, $t as t, A as n, At as r, B as i, Bt as a, Ct as o, D as s, Et as c, F as l, Ft as u, G as d, Gt as f, H as p, Ht as m, I as h, It as g, J as _, Jt as v, K as y, Kt as b, L as ee, Lt as x, M as te, Mt as S, Nn as ne, Nt as re, O as ie, Ot as C, P as ae, Pt as oe, Q as se, Qt as ce, R as le, Rn as w, Rt as ue, St as de, Tt as fe, U as pe, Ut as me, V as he, W as ge, Wt as _e, X as ve, Xt as ye, Y as be, Yt as xe, Z as Se, Zt as Ce, _n as we, _t as Te, a as Ee, at as De, bt as Oe, c as ke, cn as T, ct as Ae, d as je, dr as E, dt as Me, en as Ne, et as Pe, f as Fe, ft as Ie, g as Le, gt as Re, h as ze, ht as Be, i as Ve, ir as He, it as Ue, j as We, jt as Ge, k as Ke, kt as qe, l as Je, ln as Ye, lt as Xe, mn as Ze, mt as Qe, n as $e, nt as et, o as tt, ot as nt, p as rt, pn as D, pt as it, q as at, qt as ot, r as st, rn as ct, rt as lt, s as ut, sn as dt, st as ft, t as pt, tt as mt, u as O, un as ht, ur as k, ut as gt, v as _t, vn as vt, vt as yt, wt as bt, xt, z as St, zt as Ct } from "./constants-BoE5frJm.mjs";
2
2
  import { t as A } from "./objectSpread2-CWwMYL_U.mjs";
3
3
  import { n as j } from "./tw-CqxBf-1Y.mjs";
4
4
  //#region src/components/utils/html.ts
@@ -3075,7 +3075,7 @@ var Ir = (e) => {
3075
3075
  let e = n.blocks.getBlockIndex(t);
3076
3076
  if (e === void 0) continue;
3077
3077
  let i = n.blocks.getBlockByIndex(e);
3078
- if (i && i.parentId === r) {
3078
+ if (i && !(i.parentId !== null && i.parentId !== void 0 && i.parentId !== r)) {
3079
3079
  if (i.holder.closest(`[${k.nestedBlocks}]`)) {
3080
3080
  s.appendChild(i.holder.cloneNode(!0));
3081
3081
  continue;
@@ -9051,7 +9051,7 @@ function ec(e) {
9051
9051
  }
9052
9052
  async function tc() {
9053
9053
  return $s.highlighterPromise || ($s.highlighterPromise = (async () => {
9054
- let { createHighlighterCore: e } = await import("./core-C942GvJO.mjs"), { createJavaScriptRegexEngine: t } = await import("./engine-javascript-Dd6ViPCH.mjs"), n = await e({
9054
+ let { createHighlighterCore: e } = await import("./core-B7mxBIHA.mjs"), { createJavaScriptRegexEngine: t } = await import("./engine-javascript-Bmmg8uL9.mjs"), n = await e({
9055
9055
  themes: [import("./one-light-Di_o5Kb7.mjs"), import("./vitesse-dark-B5oAIYZ5.mjs")],
9056
9056
  langs: [],
9057
9057
  engine: t()
package/dist/full.mjs CHANGED
@@ -1,7 +1,7 @@
1
- import { n as e, t } from "./chunks/blok-oWXfRfnM.mjs";
2
- import { ur as n } from "./chunks/constants-BQ1-lyZI.mjs";
1
+ import { n as e, t } from "./chunks/blok-ClCrnWuI.mjs";
2
+ import { ur as n } from "./chunks/constants-BoE5frJm.mjs";
3
3
  import { t as r } from "./chunks/objectSpread2-CWwMYL_U.mjs";
4
- import { a as i, b as a, c as o, g as s, i as c, l, n as u, o as d, s as f, t as p, v as m, y as h } from "./chunks/tools-MuBQQyZ-.mjs";
4
+ import { a as i, b as a, c as o, g as s, i as c, l, n as u, o as d, s as f, t as p, v as m, y as h } from "./chunks/tools-HQPJLj5m.mjs";
5
5
  //#region src/full.ts
6
6
  var g = {
7
7
  paragraph: {
package/dist/react.mjs CHANGED
@@ -1,7 +1,7 @@
1
- import { t as e } from "./chunks/blok-oWXfRfnM.mjs";
2
- import "./chunks/constants-BQ1-lyZI.mjs";
1
+ import { t as e } from "./chunks/blok-ClCrnWuI.mjs";
2
+ import "./chunks/constants-BoE5frJm.mjs";
3
3
  import { t } from "./chunks/objectSpread2-CWwMYL_U.mjs";
4
- import { t as n } from "./chunks/objectWithoutProperties-D0XxKB4n.mjs";
4
+ import { t as n } from "./chunks/objectWithoutProperties-Dci1-l7D.mjs";
5
5
  import { forwardRef as r, useEffect as i, useMemo as a, useRef as o, useState as s } from "react";
6
6
  import { jsx as c } from "react/jsx-runtime";
7
7
  //#region src/react/holder-map.ts
package/dist/tools.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { m as e } from "./chunks/constants-BQ1-lyZI.mjs";
2
- import { _ as t, a as n, b as r, c as i, d as a, f as o, g as s, h as c, i as l, l as u, m as d, n as f, o as p, p as m, r as h, s as g, t as _, u as v, v as y, y as b } from "./chunks/tools-MuBQQyZ-.mjs";
1
+ import { m as e } from "./chunks/constants-BoE5frJm.mjs";
2
+ import { _ as t, a as n, b as r, c as i, d as a, f as o, g as s, h as c, i as l, l as u, m as d, n as f, o as p, p as m, r as h, s as g, t as _, u as v, v as y, y as b } from "./chunks/tools-HQPJLj5m.mjs";
3
3
  export { u as Bold, c as Callout, v as Code, e as Convert, d as Database, m as DatabaseRow, o as Divider, b as Header, h as InlineCode, i as Italic, g as Link, y as List, p as Marker, r as Paragraph, a as Quote, l as Strikethrough, t as Table, s as Toggle, n as Underline, _ as defaultBlockTools, f as defaultInlineTools };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jackuait/blok",
3
- "version": "0.10.7",
3
+ "version": "0.10.8",
4
4
  "description": "Blok — headless, highly extensible rich text editor built for developers who need to implement a block-based editing experience (similar to Notion) without building it from scratch",
5
5
  "module": "dist/blok.mjs",
6
6
  "types": "./types/index.d.ts",
@@ -7,6 +7,7 @@ import { Module } from '../../__module';
7
7
  import { Block } from '../../block';
8
8
  import { BlockAPI } from '../../block/api';
9
9
  import { capitalize } from '../../utils';
10
+ import { normalizeTableChildParents } from '../../utils/data-model-transform';
10
11
 
11
12
  import { logLabeled } from './../../utils';
12
13
 
@@ -401,11 +402,23 @@ export class BlocksAPI extends Module {
401
402
  ): BlockAPIInterface[] => {
402
403
  this.validateIndex(index);
403
404
 
404
- const blocksToInsert = blocks.map(({ id, type, data }) => {
405
+ // Backfill `parent` on children referenced by table cells so that
406
+ // alternative load paths (any consumer of the public API) get the
407
+ // same hierarchical correctness as Renderer.render(). Without this,
408
+ // flat-array article shapes lose their cell→child relationship and
409
+ // children render as detached top-level blocks.
410
+ const normalizedBlocks = normalizeTableChildParents(blocks);
411
+
412
+ const blocksToInsert = normalizedBlocks.map(({ id, type, data, tunes, parent, content, lastEditedAt, lastEditedBy }) => {
405
413
  return this.Blok.BlockManager.composeBlock({
406
414
  id,
407
415
  tool: type || (this.config.defaultBlock as string),
408
416
  data: data as BlockToolData,
417
+ tunes,
418
+ parentId: parent,
419
+ contentIds: content,
420
+ lastEditedAt,
421
+ lastEditedBy,
409
422
  });
410
423
  });
411
424
 
@@ -142,10 +142,17 @@ export class BlokDataHandler extends BasePasteHandler implements PasteHandler {
142
142
  * accumulated old→new ID mapping.
143
143
  */
144
144
  private insertBlokBlocks(
145
- blocks: BlokClipboardBlock[],
145
+ rawBlocks: BlokClipboardBlock[],
146
146
  canReplace: boolean
147
147
  ): void {
148
148
  const { BlockManager, Caret, Tools } = this.Blok;
149
+
150
+ // Some article shapes (e.g. flat-array exports) reference table children
151
+ // ONLY via `data.content[r][c].blocks = [<id>]` and never set parentId
152
+ // on the children themselves. Backfill parentId before classification so
153
+ // those children get adopted by the table during the two-pass insert
154
+ // instead of becoming detached top-level paragraphs.
155
+ const blocks = backfillTableChildParents(rawBlocks);
149
156
  const sanitizedBlocks = sanitizeBlocks(
150
157
  blocks,
151
158
  (name) => Tools.blockTools.get(name)?.sanitizeConfig ?? {},
@@ -243,6 +250,91 @@ export class BlokDataHandler extends BasePasteHandler implements PasteHandler {
243
250
  }
244
251
  }
245
252
 
253
+ /**
254
+ * Records each cell-referenced child id under its owning table.
255
+ */
256
+ function collectCellChildIds(
257
+ cell: unknown,
258
+ tableId: string,
259
+ childToTable: Map<string, string>
260
+ ): void {
261
+ if (
262
+ typeof cell !== 'object' ||
263
+ cell === null ||
264
+ !Array.isArray((cell as { blocks?: unknown }).blocks)
265
+ ) {
266
+ return;
267
+ }
268
+ const ids = (cell as { blocks: unknown[] }).blocks;
269
+
270
+ ids.forEach(childId => {
271
+ if (typeof childId === 'string' && !childToTable.has(childId)) {
272
+ childToTable.set(childId, tableId);
273
+ }
274
+ });
275
+ }
276
+
277
+ /**
278
+ * Walks a clipboard table block's `data.content[r][c]` cells and records
279
+ * every child id referenced by `cell.blocks` under its owning table.
280
+ */
281
+ function collectTableCellRefs(
282
+ block: BlokClipboardBlock,
283
+ childToTable: Map<string, string>
284
+ ): void {
285
+ if (block.tool !== 'table' || block.id === undefined) {
286
+ return;
287
+ }
288
+ const data = block.data as { content?: unknown } | undefined;
289
+ const content = data?.content;
290
+
291
+ if (!Array.isArray(content)) {
292
+ return;
293
+ }
294
+
295
+ const tableId = block.id;
296
+
297
+ content.forEach(row => {
298
+ if (!Array.isArray(row)) {
299
+ return;
300
+ }
301
+ row.forEach(cell => collectCellChildIds(cell, tableId, childToTable));
302
+ });
303
+ }
304
+
305
+ /**
306
+ * Backfills `parentId` on clipboard blocks that are referenced by a sibling
307
+ * table's `data.content[r][c].blocks` array but never declare a parent of
308
+ * their own. Idempotent — never overwrites an explicit parentId.
309
+ */
310
+ function backfillTableChildParents(
311
+ blocks: BlokClipboardBlock[]
312
+ ): BlokClipboardBlock[] {
313
+ const childToTable = new Map<string, string>();
314
+
315
+ blocks.forEach(block => collectTableCellRefs(block, childToTable));
316
+
317
+ if (childToTable.size === 0) {
318
+ return blocks;
319
+ }
320
+
321
+ return blocks.map(block => {
322
+ if (block.id === undefined) {
323
+ return block;
324
+ }
325
+ const tableId = childToTable.get(block.id);
326
+
327
+ if (tableId === undefined) {
328
+ return block;
329
+ }
330
+ if (block.parentId !== undefined && block.parentId !== null) {
331
+ return block;
332
+ }
333
+
334
+ return { ...block, parentId: tableId };
335
+ });
336
+ }
337
+
246
338
  /**
247
339
  * Recursively walks `value` and replaces any string found as a key in `idMap`
248
340
  * with its mapped value. Used to remap old block IDs to new IDs within a
@@ -7,6 +7,7 @@ import { generateBlockId, log, logLabeled } from '../utils';
7
7
  import {
8
8
  analyzeDataFormat,
9
9
  expandToHierarchical,
10
+ normalizeTableChildParents,
10
11
  shouldExpandToHierarchical,
11
12
  type DataFormatAnalysis,
12
13
  } from '../utils/data-model-transform';
@@ -88,10 +89,17 @@ export class Renderer extends Module {
88
89
  this.detectedInputFormat = analysis.format;
89
90
 
90
91
  // Transform to hierarchical if config requires it
91
- const processedBlocks = shouldExpandToHierarchical(dataModelConfig, analysis.format)
92
+ const expandedBlocks = shouldExpandToHierarchical(dataModelConfig, analysis.format)
92
93
  ? expandToHierarchical(blocksData)
93
94
  : blocksData;
94
95
 
96
+ // Tables persist child references via `data.content[r][c].blocks = [<id>]`
97
+ // rather than an explicit `parent` field on each child. Pre-normalize
98
+ // those parent references so downstream code that gates on parentId
99
+ // (read-only cell mounter, saver filter, hierarchy queries) correctly
100
+ // recognizes the children as belonging to their table.
101
+ const processedBlocks = normalizeTableChildParents(expandedBlocks);
102
+
95
103
  // Note: Yjs data layer is loaded via BlockManager.insertMany() with the correct block IDs
96
104
 
97
105
  /**
@@ -368,6 +368,45 @@ const expandListToHierarchical = (
368
368
  return blocks;
369
369
  };
370
370
 
371
+ /**
372
+ * Recursively expand a list of legacy body blocks into hierarchical flat blocks.
373
+ * Each body block is routed through expandToHierarchical so that nested legacy
374
+ * toggleList/callout/list structures are fully flattened instead of passing
375
+ * through with their legacy type (which would hit Renderer's "unknown tool"
376
+ * fallback and render as a stub).
377
+ *
378
+ * Returns both the direct-child IDs (for the parent's `content` array) and
379
+ * the flattened descendant blocks in document order.
380
+ * @param bodyBlocks - legacy body blocks to expand
381
+ * @param parentId - id of the parent block (toggle/callout) that owns them
382
+ */
383
+ const expandLegacyBodyBlocks = (
384
+ bodyBlocks: OutputBlockData[],
385
+ parentId: BlockId
386
+ ): { childIds: BlockId[]; childBlocks: OutputBlockData[] } => {
387
+ const childIds: BlockId[] = [];
388
+ const childBlocks: OutputBlockData[] = [];
389
+
390
+ for (const childBlock of bodyBlocks) {
391
+ const expanded = expandToHierarchical([childBlock]);
392
+
393
+ if (expanded.length === 0) {
394
+ continue;
395
+ }
396
+
397
+ // The first emitted block corresponds to the original input block.
398
+ // Re-parent it to the legacy container; descendants already carry the
399
+ // correct parent refs assigned during their own recursive expansion.
400
+ const [first, ...rest] = expanded;
401
+ const childId = first.id ?? generateBlockId();
402
+
403
+ childIds.push(childId);
404
+ childBlocks.push({ ...first, id: childId, parent: parentId }, ...rest);
405
+ }
406
+
407
+ return { childIds, childBlocks };
408
+ };
409
+
371
410
  /**
372
411
  * Expand a legacy toggleList block into flat toggle block + child blocks
373
412
  */
@@ -378,20 +417,7 @@ const expandToggleListToHierarchical = (
378
417
  const toggleId = block.id ?? generateBlockId();
379
418
  const bodyBlocks = block.data.body?.blocks ?? [];
380
419
 
381
- // Collect child IDs, ensuring each child has an ID
382
- const childIds: BlockId[] = [];
383
- const childBlocks: OutputBlockData[] = [];
384
-
385
- for (const childBlock of bodyBlocks) {
386
- const childId = childBlock.id ?? generateBlockId();
387
-
388
- childIds.push(childId);
389
- childBlocks.push({
390
- ...childBlock,
391
- id: childId,
392
- parent: toggleId,
393
- });
394
- }
420
+ const { childIds, childBlocks } = expandLegacyBodyBlocks(bodyBlocks, toggleId);
395
421
 
396
422
  const sharedFields = {
397
423
  id: toggleId,
@@ -439,20 +465,7 @@ const expandCalloutToHierarchical = (
439
465
  const calloutId = block.id ?? generateBlockId();
440
466
  const bodyBlocks = block.data.body?.blocks ?? [];
441
467
 
442
- // Collect child IDs, ensuring each child has an ID
443
- const childIds: BlockId[] = [];
444
- const childBlocks: OutputBlockData[] = [];
445
-
446
- for (const childBlock of bodyBlocks) {
447
- const childId = childBlock.id ?? generateBlockId();
448
-
449
- childIds.push(childId);
450
- childBlocks.push({
451
- ...childBlock,
452
- id: childId,
453
- parent: calloutId,
454
- });
455
- }
468
+ const { childIds, childBlocks } = expandLegacyBodyBlocks(bodyBlocks, calloutId);
456
469
 
457
470
  // Map variant → backgroundColor preset
458
471
  const variant = block.data.variant ?? 'general';
@@ -905,6 +918,131 @@ export const collapseToLegacy = (blocks: OutputBlockData[]): OutputBlockData[] =
905
918
  return result;
906
919
  };
907
920
 
921
+ /**
922
+ * A table cell that references its content blocks by id.
923
+ * Tables persist their child blocks via `data.content[row][col].blocks = [<id>, ...]`
924
+ * rather than nesting block payloads inline, so the parent/content relationship
925
+ * is implicit in the table data instead of explicit on each child block.
926
+ */
927
+ interface CellWithBlockRefs {
928
+ blocks: string[];
929
+ }
930
+
931
+ const isCellWithBlockRefs = (cell: unknown): cell is CellWithBlockRefs => {
932
+ return (
933
+ typeof cell === 'object' &&
934
+ cell !== null &&
935
+ Array.isArray((cell as { blocks?: unknown }).blocks)
936
+ );
937
+ };
938
+
939
+ interface TableDataShape {
940
+ content?: unknown;
941
+ }
942
+
943
+ const getTableContentRows = (data: unknown): unknown[][] | null => {
944
+ if (typeof data !== 'object' || data === null) {
945
+ return null;
946
+ }
947
+ const content = (data as TableDataShape).content;
948
+
949
+ if (!Array.isArray(content)) {
950
+ return null;
951
+ }
952
+ return content as unknown[][];
953
+ };
954
+
955
+ /**
956
+ * When a flat block array contains `table` blocks that reference child blocks
957
+ * via `data.content[row][col].blocks = [<id>, ...]`, ensure each referenced
958
+ * child carries `parent: <tableId>`. This makes the parent/content invariant
959
+ * explicit even for externally-authored data shapes that omit the `parent`
960
+ * field on children.
961
+ *
962
+ * Without this normalization, downstream readers that key on parentId
963
+ * (`mountCellBlocksReadOnly`'s cross-table guard, the table saver's
964
+ * own-child filter, hierarchy queries, drag-and-drop) skip those children
965
+ * and leak them out of the table, rendering them at the bottom of the page
966
+ * instead of inside the cells.
967
+ *
968
+ * The function is idempotent, never mutates the input array, and leaves
969
+ * pre-existing `parent` fields unchanged. Children referenced by multiple
970
+ * tables get assigned to the first table that lists them (first-writer-wins);
971
+ * corrupted cross-table references are preserved as-is so defensive guards
972
+ * downstream can still reject them.
973
+ * @param blocks - flat block array potentially containing tables with cell refs
974
+ */
975
+ const collectCellChildRefs = (
976
+ cell: unknown,
977
+ tableId: BlockId,
978
+ childToTable: Map<BlockId, BlockId>
979
+ ): void => {
980
+ if (!isCellWithBlockRefs(cell)) {
981
+ return;
982
+ }
983
+ for (const childId of cell.blocks) {
984
+ if (typeof childId !== 'string' || childToTable.has(childId)) {
985
+ continue;
986
+ }
987
+ childToTable.set(childId, tableId);
988
+ }
989
+ };
990
+
991
+ const collectRowChildRefs = (
992
+ row: unknown,
993
+ tableId: BlockId,
994
+ childToTable: Map<BlockId, BlockId>
995
+ ): void => {
996
+ if (!Array.isArray(row)) {
997
+ return;
998
+ }
999
+ row.forEach(cell => collectCellChildRefs(cell, tableId, childToTable));
1000
+ };
1001
+
1002
+ const collectTableChildRefs = (
1003
+ tableBlock: OutputBlockData,
1004
+ childToTable: Map<BlockId, BlockId>
1005
+ ): void => {
1006
+ if (tableBlock.id === undefined || tableBlock.id === null) {
1007
+ return;
1008
+ }
1009
+ const rows = getTableContentRows(tableBlock.data);
1010
+
1011
+ if (rows === null) {
1012
+ return;
1013
+ }
1014
+ const tableId = tableBlock.id;
1015
+
1016
+ rows.forEach(row => collectRowChildRefs(row, tableId, childToTable));
1017
+ };
1018
+
1019
+ export const normalizeTableChildParents = (blocks: OutputBlockData[]): OutputBlockData[] => {
1020
+ const childToTable = new Map<BlockId, BlockId>();
1021
+
1022
+ blocks
1023
+ .filter(block => block.type === 'table')
1024
+ .forEach(tableBlock => collectTableChildRefs(tableBlock, childToTable));
1025
+
1026
+ if (childToTable.size === 0) {
1027
+ return blocks;
1028
+ }
1029
+
1030
+ return blocks.map(block => {
1031
+ if (block.id === undefined || block.id === null) {
1032
+ return block;
1033
+ }
1034
+ const tableId = childToTable.get(block.id);
1035
+
1036
+ if (tableId === undefined) {
1037
+ return block;
1038
+ }
1039
+ if (block.parent !== undefined && block.parent !== null) {
1040
+ return block;
1041
+ }
1042
+ return { ...block, parent: tableId };
1043
+ });
1044
+ };
1045
+
908
1046
  /**
909
1047
  * Check if transformation is needed based on config and detected format
910
1048
  */
@@ -6,6 +6,7 @@ import type { HandlerContext } from '../components/modules/paste/types';
6
6
  import type { PasteHandler } from '../components/modules/paste/handlers/base';
7
7
  import { BasePasteHandler } from '../components/modules/paste/handlers/base';
8
8
  import { Block } from '../components/block';
9
+ import { normalizeTableChildParents } from '../components/utils/data-model-transform';
9
10
 
10
11
  /**
11
12
  * Patterns that indicate text is likely Markdown rather than plain text.
@@ -66,12 +67,18 @@ export class MarkdownHandler extends BasePasteHandler implements PasteHandler {
66
67
  }
67
68
 
68
69
  const { markdownToBlocks } = await import('./index');
69
- const outputBlocks = await markdownToBlocks(data);
70
+ const rawOutputBlocks = await markdownToBlocks(data);
70
71
 
71
- if (!outputBlocks.length) {
72
+ if (!rawOutputBlocks.length) {
72
73
  return false;
73
74
  }
74
75
 
76
+ // Defense-in-depth: backfill `parent` on table cell children so that any
77
+ // future regression in mdast-to-blocks (or external converter) cannot
78
+ // produce the dodopizza shape (children referenced by table cells but
79
+ // lacking explicit parent), which would render them at page bottom.
80
+ const outputBlocks = normalizeTableChildParents(rawOutputBlocks);
81
+
75
82
  const { BlockManager, Caret } = this.Blok;
76
83
 
77
84
  // Replace empty default block if present
@@ -326,10 +326,15 @@ export const mountCellBlocksReadOnly = (
326
326
  continue;
327
327
  }
328
328
 
329
- // Skip blocks that don't belong to this table.
330
- // Corrupted data may contain cross-table references; mounting them
331
- // would steal (or clone) DOM nodes from the other table.
332
- if (block.parentId !== _tableBlockId) {
329
+ // Skip blocks whose parentId explicitly points to a DIFFERENT table
330
+ // (corrupted cross-table references). Blocks with null/undefined
331
+ // parentId are accepted: legitimate flat-array data shapes (e.g. the
332
+ // dodopizza article format) reference children by id from
333
+ // `cell.blocks` without setting `parent` on each child, and the
334
+ // Renderer's normalizeTableChildParents pre-step is the primary
335
+ // place that backfills parentId. This guard is defense-in-depth for
336
+ // load paths that bypass the Renderer (Yjs sync, direct insertion).
337
+ if (block.parentId !== null && block.parentId !== undefined && block.parentId !== _tableBlockId) {
333
338
  continue;
334
339
  }
335
340