@jackuait/blok 0.10.9 → 0.10.11
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.
- package/dist/blok.mjs +2 -2
- package/dist/chunks/{blok-DbRn9adY.mjs → blok-CX_pZ_qq.mjs} +1441 -1413
- package/dist/chunks/{constants-C9lsSOXl.mjs → constants-lxerM-Xa.mjs} +1 -1
- package/dist/chunks/{tools-D0W3_dlA.mjs → tools-CMNxZqJC.mjs} +52 -21
- package/dist/full.mjs +3 -3
- package/dist/react.mjs +2 -2
- package/dist/tools.mjs +2 -2
- package/package.json +1 -1
- package/src/components/utils/data-model-transform.ts +93 -42
- package/src/tools/list/data-normalizer.ts +12 -3
- package/src/tools/table/index.ts +6 -3
- package/src/tools/table/table-cell-blocks.ts +18 -6
- package/src/tools/table/table-model.ts +34 -1
|
@@ -130,7 +130,7 @@ var a = {
|
|
|
130
130
|
RIGHT: 2,
|
|
131
131
|
BACKWARD: 3,
|
|
132
132
|
FORWARD: 4
|
|
133
|
-
}, l = () => "0.10.
|
|
133
|
+
}, l = () => "0.10.11", 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,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-
|
|
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-lxerM-Xa.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
|
|
@@ -859,11 +859,27 @@ var Qt = "outline-hidden py-[7px] mt-[2px] mb-px", $t = "outline-hidden pl-0.5 l
|
|
|
859
859
|
depth: 0
|
|
860
860
|
};
|
|
861
861
|
if (En(e)) {
|
|
862
|
-
let
|
|
862
|
+
let { text: t, checked: r } = ((e) => {
|
|
863
|
+
if (typeof e == "string") return {
|
|
864
|
+
text: e,
|
|
865
|
+
checked: !1
|
|
866
|
+
};
|
|
867
|
+
if (typeof e == "object" && e) {
|
|
868
|
+
var t, n, r;
|
|
869
|
+
return {
|
|
870
|
+
text: (t = (n = e.content) == null ? e.text : n) == null ? "" : t,
|
|
871
|
+
checked: (r = e.checked) == null ? !1 : r
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
return {
|
|
875
|
+
text: "",
|
|
876
|
+
checked: !1
|
|
877
|
+
};
|
|
878
|
+
})(e.items[0]);
|
|
863
879
|
return A({
|
|
864
|
-
text:
|
|
880
|
+
text: t,
|
|
865
881
|
style: e.style || n,
|
|
866
|
-
checked: !!
|
|
882
|
+
checked: !!r,
|
|
867
883
|
depth: 0
|
|
868
884
|
}, e.start !== void 0 && e.start !== 1 ? { start: e.start } : {});
|
|
869
885
|
}
|
|
@@ -2283,14 +2299,14 @@ var Qt = "outline-hidden py-[7px] mt-[2px] mb-px", $t = "outline-hidden pl-0.5 l
|
|
|
2283
2299
|
let t = this.api.blocks.getBlockIndex(i);
|
|
2284
2300
|
if (t === void 0) continue;
|
|
2285
2301
|
let a = this.api.blocks.getBlockByIndex(t);
|
|
2286
|
-
if (a)
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
e.appendChild(a.holder), this.api.blocks.setBlockParent(i, this.tableBlockId), n.push(i);
|
|
2302
|
+
if (!a) continue;
|
|
2303
|
+
let o = a.parentId != null && a.parentId !== "" && a.parentId !== this.tableBlockId;
|
|
2304
|
+
if (a.holder.closest(`[${k.nestedBlocks}]`) || o) {
|
|
2305
|
+
let t = this.api.blocks.insert(a.name, a.preservedData, {}, this.api.blocks.getBlocksCount(), !1);
|
|
2306
|
+
e.appendChild(t.holder), this.api.blocks.setBlockParent(t.id, this.tableBlockId), n.push(t.id), r.set(i, t.id);
|
|
2307
|
+
continue;
|
|
2293
2308
|
}
|
|
2309
|
+
e.appendChild(a.holder), this.api.blocks.setBlockParent(i, this.tableBlockId), n.push(i);
|
|
2294
2310
|
}
|
|
2295
2311
|
return {
|
|
2296
2312
|
mountedIds: n,
|
|
@@ -2304,9 +2320,11 @@ var Qt = "outline-hidden py-[7px] mt-[2px] mb-px", $t = "outline-hidden pl-0.5 l
|
|
|
2304
2320
|
let a = this.api.blocks.getBlockIndex(t);
|
|
2305
2321
|
if (a === void 0) return;
|
|
2306
2322
|
let o = this.api.blocks.getBlockByIndex(a);
|
|
2307
|
-
if (!o || o.holder.contains(i)
|
|
2308
|
-
let s =
|
|
2309
|
-
|
|
2323
|
+
if (!o || o.holder.contains(i)) return;
|
|
2324
|
+
let s = o.parentId != null && o.parentId !== "" && o.parentId !== this.tableBlockId;
|
|
2325
|
+
if (o.holder.closest(`[${k.nestedBlocks}]`) || s) return;
|
|
2326
|
+
let c = this.api.blocks.getBlocksCount(), l = (n = (r = Array.from({ length: c - a - 1 }, (e, t) => this.api.blocks.getBlockByIndex(a + 1 + t)).find((e) => (e == null ? void 0 : e.holder.parentElement) === i)) == null ? void 0 : r.holder) == null ? null : n;
|
|
2327
|
+
i.insertBefore(o.holder, l), this.api.blocks.setBlockParent(t, this.tableBlockId), this.stripPlaceholders(i);
|
|
2310
2328
|
}
|
|
2311
2329
|
findCellForNewBlock(e) {
|
|
2312
2330
|
return this.findCellForAdjacentBlock(e - 1) || this.findCellForAdjacentBlock(e + 1);
|
|
@@ -3225,6 +3243,13 @@ var Ir = (e) => {
|
|
|
3225
3243
|
setCellBlocks(e, t, n) {
|
|
3226
3244
|
if (this.isInBounds(e, t) && !this.isSpannedCell(e, t)) {
|
|
3227
3245
|
for (let n of this.contentGrid[e][t].blocks) this.blockCellMap.delete(n);
|
|
3246
|
+
for (let r of n) {
|
|
3247
|
+
let n = this.blockCellMap.get(r);
|
|
3248
|
+
if (n !== void 0 && (n.row !== e || n.col !== t)) {
|
|
3249
|
+
let e = this.contentGrid[n.row][n.col];
|
|
3250
|
+
e.blocks = e.blocks.filter((e) => e !== r);
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3228
3253
|
this.contentGrid[e][t].blocks = [...n];
|
|
3229
3254
|
for (let r of n) this.blockCellMap.set(r, {
|
|
3230
3255
|
row: e,
|
|
@@ -3687,7 +3712,12 @@ var Ir = (e) => {
|
|
|
3687
3712
|
});
|
|
3688
3713
|
}
|
|
3689
3714
|
normalizeContent(e) {
|
|
3690
|
-
|
|
3715
|
+
if (!e || !Array.isArray(e)) return [];
|
|
3716
|
+
let t = /* @__PURE__ */ new Set();
|
|
3717
|
+
return e.map((e) => (e == null ? [] : e).map((e) => {
|
|
3718
|
+
let n = this.normalizeCell(e);
|
|
3719
|
+
return n.blocks = n.blocks.filter((e) => t.has(e) ? !1 : (t.add(e), !0)), n;
|
|
3720
|
+
}));
|
|
3691
3721
|
}
|
|
3692
3722
|
normalizeCell(e) {
|
|
3693
3723
|
if (R(e)) {
|
|
@@ -4912,16 +4942,17 @@ var Ir = (e) => {
|
|
|
4912
4942
|
for (let { index: e } of t) this.api.blocks.delete(e);
|
|
4913
4943
|
}
|
|
4914
4944
|
save(e) {
|
|
4915
|
-
|
|
4916
|
-
|
|
4945
|
+
var t;
|
|
4946
|
+
let n = this.model.snapshot(), r = (t = this.blockId) == null ? "" : t;
|
|
4947
|
+
return n.content = n.content.map((e) => e.map((e) => {
|
|
4917
4948
|
if (!R(e)) return e;
|
|
4918
4949
|
let t = e.blocks.filter((e) => {
|
|
4919
|
-
var t, n;
|
|
4920
|
-
let
|
|
4921
|
-
return
|
|
4950
|
+
var t, n, i;
|
|
4951
|
+
let a = (t = (n = this.api.blocks).getById) == null ? void 0 : t.call(n, e);
|
|
4952
|
+
return a != null && ((i = a.parentId) == null ? "" : i) === r;
|
|
4922
4953
|
});
|
|
4923
4954
|
return A(A({}, e), {}, { blocks: t });
|
|
4924
|
-
})),
|
|
4955
|
+
})), n;
|
|
4925
4956
|
}
|
|
4926
4957
|
validate(e) {
|
|
4927
4958
|
return e.content.length > 0;
|
package/dist/full.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { n as e, t } from "./chunks/blok-
|
|
2
|
-
import { ur as n } from "./chunks/constants-
|
|
1
|
+
import { n as e, t } from "./chunks/blok-CX_pZ_qq.mjs";
|
|
2
|
+
import { ur as n } from "./chunks/constants-lxerM-Xa.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-
|
|
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-CMNxZqJC.mjs";
|
|
5
5
|
//#region src/full.ts
|
|
6
6
|
var g = {
|
|
7
7
|
paragraph: {
|
package/dist/react.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as e } from "./chunks/blok-
|
|
2
|
-
import "./chunks/constants-
|
|
1
|
+
import { t as e } from "./chunks/blok-CX_pZ_qq.mjs";
|
|
2
|
+
import "./chunks/constants-lxerM-Xa.mjs";
|
|
3
3
|
import { t } from "./chunks/objectSpread2-CWwMYL_U.mjs";
|
|
4
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";
|
package/dist/tools.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { m as e } from "./chunks/constants-
|
|
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-
|
|
1
|
+
import { m as e } from "./chunks/constants-lxerM-Xa.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-CMNxZqJC.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.
|
|
3
|
+
"version": "0.10.11",
|
|
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",
|
|
@@ -380,6 +380,29 @@ const expandListToHierarchical = (
|
|
|
380
380
|
* @param bodyBlocks - legacy body blocks to expand
|
|
381
381
|
* @param parentId - id of the parent block (toggle/callout) that owns them
|
|
382
382
|
*/
|
|
383
|
+
/**
|
|
384
|
+
* Route one emitted block into the accumulator. Root-level blocks (no parent)
|
|
385
|
+
* become direct children of the legacy container; descendants keep their
|
|
386
|
+
* existing parent refs assigned during recursive expansion.
|
|
387
|
+
*/
|
|
388
|
+
const appendEmittedBlock = (
|
|
389
|
+
emitted: OutputBlockData,
|
|
390
|
+
parentId: BlockId,
|
|
391
|
+
childIds: BlockId[],
|
|
392
|
+
childBlocks: OutputBlockData[]
|
|
393
|
+
): void => {
|
|
394
|
+
if (emitted.parent !== undefined) {
|
|
395
|
+
childBlocks.push(emitted);
|
|
396
|
+
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const childId = emitted.id ?? generateBlockId();
|
|
401
|
+
|
|
402
|
+
childIds.push(childId);
|
|
403
|
+
childBlocks.push({ ...emitted, id: childId, parent: parentId });
|
|
404
|
+
};
|
|
405
|
+
|
|
383
406
|
const expandLegacyBodyBlocks = (
|
|
384
407
|
bodyBlocks: OutputBlockData[],
|
|
385
408
|
parentId: BlockId
|
|
@@ -390,18 +413,29 @@ const expandLegacyBodyBlocks = (
|
|
|
390
413
|
for (const childBlock of bodyBlocks) {
|
|
391
414
|
const expanded = expandToHierarchical([childBlock]);
|
|
392
415
|
|
|
393
|
-
|
|
394
|
-
|
|
416
|
+
// A single legacy block may expand into N root-level siblings (e.g. a list
|
|
417
|
+
// with N items). Every parent-less root becomes a direct child here so
|
|
418
|
+
// multi-item lists inside callout/toggle bodies aren't orphaned.
|
|
419
|
+
for (const emitted of expanded) {
|
|
420
|
+
appendEmittedBlock(emitted, parentId, childIds, childBlocks);
|
|
395
421
|
}
|
|
422
|
+
}
|
|
396
423
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
424
|
+
// Invariant: every emitted block must either carry a parent ref (descendant
|
|
425
|
+
// assigned during its own recursive expansion) or appear in childIds (root
|
|
426
|
+
// we just re-parented). If this ever trips, some expansion path is leaking
|
|
427
|
+
// orphans — exactly the regression this assertion exists to catch.
|
|
428
|
+
for (const block of childBlocks) {
|
|
429
|
+
const hasParent = block.parent !== undefined;
|
|
430
|
+
const hasId = block.id !== undefined;
|
|
431
|
+
const isDirectChild = hasId && childIds.includes(block.id as BlockId);
|
|
432
|
+
|
|
433
|
+
if (!hasParent && !isDirectChild) {
|
|
434
|
+
throw new Error(
|
|
435
|
+
`expandLegacyBodyBlocks: orphaned block emitted (type=${block.type}, id=${block.id ?? '<none>'}). ` +
|
|
436
|
+
`Every root-level expansion must be re-parented to ${parentId}.`
|
|
437
|
+
);
|
|
438
|
+
}
|
|
405
439
|
}
|
|
406
440
|
|
|
407
441
|
return { childIds, childBlocks };
|
|
@@ -646,6 +680,52 @@ const processRootListItem = (
|
|
|
646
680
|
return listBlock;
|
|
647
681
|
};
|
|
648
682
|
|
|
683
|
+
/**
|
|
684
|
+
* Recursively collapse a container's body: routes each direct child through the
|
|
685
|
+
* appropriate processRoot* helper based on its type so that grandchildren land in
|
|
686
|
+
* the correct nested legacy shape instead of being ejected to the document root.
|
|
687
|
+
*/
|
|
688
|
+
function collapseBodyBlocks(
|
|
689
|
+
contentIds: BlockId[],
|
|
690
|
+
blockMap: Map<BlockId, OutputBlockData>,
|
|
691
|
+
processedIds: Set<BlockId>
|
|
692
|
+
): OutputBlockData[] {
|
|
693
|
+
const result: OutputBlockData[] = [];
|
|
694
|
+
|
|
695
|
+
for (const childId of contentIds) {
|
|
696
|
+
if (processedIds.has(childId)) {
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
const childBlock = blockMap.get(childId);
|
|
700
|
+
|
|
701
|
+
if (childBlock === undefined) {
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
if (isFlatModelListBlock(childBlock)) {
|
|
706
|
+
result.push(processRootListItem(childBlock, blockMap, processedIds));
|
|
707
|
+
continue;
|
|
708
|
+
}
|
|
709
|
+
if (isFlatModelToggleBlock(childBlock)) {
|
|
710
|
+
result.push(processRootToggleItem(childBlock, blockMap, processedIds));
|
|
711
|
+
continue;
|
|
712
|
+
}
|
|
713
|
+
if (isToggleableHeaderBlock(childBlock)) {
|
|
714
|
+
result.push(processRootToggleableHeader(childBlock, blockMap, processedIds));
|
|
715
|
+
continue;
|
|
716
|
+
}
|
|
717
|
+
if (isFlatModelCalloutBlock(childBlock)) {
|
|
718
|
+
result.push(processRootCalloutItem(childBlock, blockMap, processedIds));
|
|
719
|
+
continue;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
markBlockAsProcessed(childBlock.id, processedIds);
|
|
723
|
+
result.push(stripHierarchyFields(childBlock));
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
return result;
|
|
727
|
+
}
|
|
728
|
+
|
|
649
729
|
/**
|
|
650
730
|
* Process a root toggle block and convert to a legacy toggleList block
|
|
651
731
|
*/
|
|
@@ -662,18 +742,8 @@ const processRootToggleItem = (
|
|
|
662
742
|
? (data as Record<string, unknown>).isOpen as boolean
|
|
663
743
|
: undefined;
|
|
664
744
|
|
|
665
|
-
// Collect child blocks
|
|
666
|
-
const childBlocks: OutputBlockData[] = [];
|
|
667
745
|
const contentIds = block.content ?? [];
|
|
668
|
-
|
|
669
|
-
for (const childId of contentIds) {
|
|
670
|
-
const childBlock = blockMap.get(childId);
|
|
671
|
-
|
|
672
|
-
if (childBlock) {
|
|
673
|
-
markBlockAsProcessed(childId, processedIds);
|
|
674
|
-
childBlocks.push(stripHierarchyFields(childBlock));
|
|
675
|
-
}
|
|
676
|
-
}
|
|
746
|
+
const childBlocks = collapseBodyBlocks(contentIds, blockMap, processedIds);
|
|
677
747
|
|
|
678
748
|
const legacyBlock: OutputBlockData = {
|
|
679
749
|
id: block.id,
|
|
@@ -732,17 +802,8 @@ const processRootToggleableHeader = (
|
|
|
732
802
|
? (data as Record<string, unknown>).isOpen as boolean
|
|
733
803
|
: undefined;
|
|
734
804
|
|
|
735
|
-
const childBlocks: OutputBlockData[] = [];
|
|
736
805
|
const contentIds = block.content ?? [];
|
|
737
|
-
|
|
738
|
-
for (const childId of contentIds) {
|
|
739
|
-
const childBlock = blockMap.get(childId);
|
|
740
|
-
|
|
741
|
-
if (childBlock) {
|
|
742
|
-
markBlockAsProcessed(childId, processedIds);
|
|
743
|
-
childBlocks.push(stripHierarchyFields(childBlock));
|
|
744
|
-
}
|
|
745
|
-
}
|
|
806
|
+
const childBlocks = collapseBodyBlocks(contentIds, blockMap, processedIds);
|
|
746
807
|
|
|
747
808
|
const legacyBlock: OutputBlockData = {
|
|
748
809
|
id: block.id,
|
|
@@ -810,18 +871,8 @@ const processRootCalloutItem = (
|
|
|
810
871
|
const isEmojiVisible = typeof emojiValue === 'string' && emojiValue.length > 0;
|
|
811
872
|
const emoji = isEmojiVisible ? emojiValue : null;
|
|
812
873
|
|
|
813
|
-
// Collect child blocks
|
|
814
|
-
const childBlocks: OutputBlockData[] = [];
|
|
815
874
|
const contentIds = block.content ?? [];
|
|
816
|
-
|
|
817
|
-
for (const childId of contentIds) {
|
|
818
|
-
const childBlock = blockMap.get(childId);
|
|
819
|
-
|
|
820
|
-
if (childBlock) {
|
|
821
|
-
markBlockAsProcessed(childId, processedIds);
|
|
822
|
-
childBlocks.push(stripHierarchyFields(childBlock));
|
|
823
|
-
}
|
|
824
|
-
}
|
|
875
|
+
const childBlocks = collapseBodyBlocks(contentIds, blockMap, processedIds);
|
|
825
876
|
|
|
826
877
|
const legacyBlock: OutputBlockData = {
|
|
827
878
|
id: block.id,
|
|
@@ -10,7 +10,7 @@ import type { ListItemConfig, ListItemData, ListItemStyle } from './types';
|
|
|
10
10
|
* Type for legacy list item format (used for type guard)
|
|
11
11
|
*/
|
|
12
12
|
type LegacyListItemFormat = {
|
|
13
|
-
items: Array<{ content
|
|
13
|
+
items: Array<string | { content?: string; text?: string; checked?: boolean | string }>;
|
|
14
14
|
style?: ListItemStyle;
|
|
15
15
|
start?: number;
|
|
16
16
|
};
|
|
@@ -117,8 +117,17 @@ export const normalizeListItemData = (
|
|
|
117
117
|
// This provides backward compatibility when legacy data is passed directly to the tool
|
|
118
118
|
if (isLegacyFormat(data)) {
|
|
119
119
|
const firstItem = data.items[0];
|
|
120
|
-
|
|
121
|
-
const
|
|
120
|
+
// handle string items and old {text,checked} shape
|
|
121
|
+
const extractLegacy = (item: typeof firstItem): { text: string; checked: boolean | string | undefined } => {
|
|
122
|
+
if (typeof item === 'string') {
|
|
123
|
+
return { text: item, checked: false };
|
|
124
|
+
}
|
|
125
|
+
if (item !== null && typeof item === 'object') {
|
|
126
|
+
return { text: item.content ?? item.text ?? '', checked: item.checked ?? false };
|
|
127
|
+
}
|
|
128
|
+
return { text: '', checked: false };
|
|
129
|
+
};
|
|
130
|
+
const { text, checked } = extractLegacy(firstItem);
|
|
122
131
|
|
|
123
132
|
return {
|
|
124
133
|
text,
|
package/src/tools/table/index.ts
CHANGED
|
@@ -671,8 +671,11 @@ export class Table implements BlockTool {
|
|
|
671
671
|
const data = this.model.snapshot();
|
|
672
672
|
|
|
673
673
|
// Filter out block IDs that don't belong to this table.
|
|
674
|
-
// Corrupted data may contain cross-table references
|
|
675
|
-
//
|
|
674
|
+
// Corrupted data may contain cross-table references or phantom IDs
|
|
675
|
+
// (blocks deleted but matrix not updated); persisting them causes DOM
|
|
676
|
+
// node stealing and data loss on subsequent renders.
|
|
677
|
+
const tableId = this.blockId ?? '';
|
|
678
|
+
|
|
676
679
|
data.content = data.content.map(row =>
|
|
677
680
|
row.map(cell => {
|
|
678
681
|
if (!isCellWithBlocks(cell)) {
|
|
@@ -682,7 +685,7 @@ export class Table implements BlockTool {
|
|
|
682
685
|
const filtered = cell.blocks.filter(blockId => {
|
|
683
686
|
const block = this.api.blocks.getById?.(blockId);
|
|
684
687
|
|
|
685
|
-
return
|
|
688
|
+
return block != null && (block.parentId ?? '') === tableId;
|
|
686
689
|
});
|
|
687
690
|
|
|
688
691
|
return { ...cell, blocks: filtered };
|
|
@@ -476,9 +476,15 @@ export class TableCellBlocks {
|
|
|
476
476
|
}
|
|
477
477
|
|
|
478
478
|
// Guard: if the block is already mounted in another nested container
|
|
479
|
-
// (table cell, toggle, callout, header),
|
|
480
|
-
//
|
|
481
|
-
|
|
479
|
+
// (table cell, toggle, callout, header), OR its parentId already points
|
|
480
|
+
// to a different owner (race window where another table has claimed it
|
|
481
|
+
// via flat-list parent field but has not yet mounted its DOM), create a
|
|
482
|
+
// duplicate with the same tool name and data rather than stealing.
|
|
483
|
+
const hasDifferentOwner = block.parentId != null
|
|
484
|
+
&& block.parentId !== ''
|
|
485
|
+
&& block.parentId !== this.tableBlockId;
|
|
486
|
+
|
|
487
|
+
if (block.holder.closest(`[${DATA_ATTR.nestedBlocks}]`) || hasDifferentOwner) {
|
|
482
488
|
const duplicate = this.api.blocks.insert(
|
|
483
489
|
block.name,
|
|
484
490
|
block.preservedData,
|
|
@@ -529,9 +535,15 @@ export class TableCellBlocks {
|
|
|
529
535
|
return;
|
|
530
536
|
}
|
|
531
537
|
|
|
532
|
-
// Guard: skip blocks already mounted in another nested container
|
|
533
|
-
//
|
|
534
|
-
|
|
538
|
+
// Guard: skip blocks already mounted in another nested container, or whose
|
|
539
|
+
// parentId already points to a different owner (race window where another
|
|
540
|
+
// table has claimed the block via flat-list parent field but has not yet
|
|
541
|
+
// mounted its DOM). Without this, insertBefore would steal the DOM node.
|
|
542
|
+
const hasDifferentOwner = block.parentId != null
|
|
543
|
+
&& block.parentId !== ''
|
|
544
|
+
&& block.parentId !== this.tableBlockId;
|
|
545
|
+
|
|
546
|
+
if (block.holder.closest(`[${DATA_ATTR.nestedBlocks}]`) || hasDifferentOwner) {
|
|
535
547
|
return;
|
|
536
548
|
}
|
|
537
549
|
|
|
@@ -234,6 +234,19 @@ export class TableModel {
|
|
|
234
234
|
this.blockCellMap.delete(oldId);
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
+
// Scrub each new ID from any other cell it previously occupied. Without this,
|
|
238
|
+
// reassigning a block ID from cell A to cell B would leave a dangling copy in A,
|
|
239
|
+
// producing the cross-cell duplicate references observed in the corrupted article.
|
|
240
|
+
for (const id of blockIds) {
|
|
241
|
+
const prior = this.blockCellMap.get(id);
|
|
242
|
+
|
|
243
|
+
if (prior !== undefined && (prior.row !== row || prior.col !== col)) {
|
|
244
|
+
const priorCell = this.contentGrid[prior.row][prior.col];
|
|
245
|
+
|
|
246
|
+
priorCell.blocks = priorCell.blocks.filter(existingId => existingId !== id);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
237
250
|
this.contentGrid[row][col].blocks = [...blockIds];
|
|
238
251
|
|
|
239
252
|
// Add new entries to map
|
|
@@ -1430,14 +1443,34 @@ export class TableModel {
|
|
|
1430
1443
|
|
|
1431
1444
|
/**
|
|
1432
1445
|
* Normalize legacy content (strings) into CellContent objects.
|
|
1446
|
+
*
|
|
1447
|
+
* Also scrubs duplicate block IDs: if the same block ID appears in more than
|
|
1448
|
+
* one cell (as in a corrupted saved document), the first occurrence in
|
|
1449
|
+
* row-major order keeps it and later occurrences drop it. This preserves the
|
|
1450
|
+
* invariant that every block belongs to at most one cell.
|
|
1433
1451
|
*/
|
|
1434
1452
|
private normalizeContent(content?: LegacyCellContent[][]): CellContent[][] {
|
|
1435
1453
|
if (!content || !Array.isArray(content)) {
|
|
1436
1454
|
return [];
|
|
1437
1455
|
}
|
|
1438
1456
|
|
|
1457
|
+
const seen = new Set<string>();
|
|
1458
|
+
|
|
1439
1459
|
return content.map(row =>
|
|
1440
|
-
(row ?? []).map(c =>
|
|
1460
|
+
(row ?? []).map(c => {
|
|
1461
|
+
const normalized = this.normalizeCell(c);
|
|
1462
|
+
|
|
1463
|
+
normalized.blocks = normalized.blocks.filter(blockId => {
|
|
1464
|
+
if (seen.has(blockId)) {
|
|
1465
|
+
return false;
|
|
1466
|
+
}
|
|
1467
|
+
seen.add(blockId);
|
|
1468
|
+
|
|
1469
|
+
return true;
|
|
1470
|
+
});
|
|
1471
|
+
|
|
1472
|
+
return normalized;
|
|
1473
|
+
})
|
|
1441
1474
|
);
|
|
1442
1475
|
}
|
|
1443
1476
|
|