@jackuait/blok 0.4.1-beta.12 → 0.4.1-beta.14
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-BU6NwVkN.mjs → blok-CqdUPkVp.mjs} +405 -401
- package/dist/chunks/{i18next-loader-D8GzSwio.mjs → i18next-loader-DGwfKvj7.mjs} +1 -1
- package/dist/chunks/{index-C5e_WLFg.mjs → index-Bq6UT5hl.mjs} +1 -1
- package/dist/chunks/{inline-tool-convert-CLUxkCe_.mjs → inline-tool-convert-CmxgdS16.mjs} +1 -1
- package/dist/full.mjs +2 -2
- package/dist/tools.mjs +43 -26
- package/package.json +25 -7
- package/src/components/modules/ui.ts +10 -6
- package/src/components/utils/data-model-transform.ts +59 -37
- package/src/tools/list/index.ts +23 -0
|
@@ -18,7 +18,7 @@ let nt = (o = 21) => {
|
|
|
18
18
|
return t;
|
|
19
19
|
};
|
|
20
20
|
var ot = /* @__PURE__ */ ((o) => (o.VERBOSE = "VERBOSE", o.INFO = "INFO", o.WARN = "WARN", o.ERROR = "ERROR", o))(ot || {});
|
|
21
|
-
const rt = () => "0.4.1-beta.
|
|
21
|
+
const rt = () => "0.4.1-beta.14", Ct = {
|
|
22
22
|
BACKSPACE: 8,
|
|
23
23
|
TAB: 9,
|
|
24
24
|
ENTER: 13,
|
package/dist/full.mjs
CHANGED
|
@@ -10,10 +10,10 @@ var e = (a, l, o) => l in a ? n(a, l, { enumerable: !0, configurable: !0, writab
|
|
|
10
10
|
d.call(l, o) && e(a, o, l[o]);
|
|
11
11
|
return a;
|
|
12
12
|
}, r = (a, l) => t(a, c(l));
|
|
13
|
-
import { B as v, v as A } from "./chunks/blok-
|
|
13
|
+
import { B as v, v as A } from "./chunks/blok-CqdUPkVp.mjs";
|
|
14
14
|
import { List as p, Header as f, Paragraph as I, Link as k, Italic as u, Bold as B } from "./tools.mjs";
|
|
15
15
|
import { defaultBlockTools as H, defaultInlineTools as P } from "./tools.mjs";
|
|
16
|
-
import { D as _ } from "./chunks/inline-tool-convert-
|
|
16
|
+
import { D as _ } from "./chunks/inline-tool-convert-CmxgdS16.mjs";
|
|
17
17
|
const m = {
|
|
18
18
|
paragraph: {
|
|
19
19
|
class: I,
|
package/dist/tools.mjs
CHANGED
|
@@ -2,7 +2,7 @@ var nt = Object.defineProperty, rt = Object.defineProperties;
|
|
|
2
2
|
var st = Object.getOwnPropertyDescriptors;
|
|
3
3
|
var K = Object.getOwnPropertySymbols;
|
|
4
4
|
var ot = Object.prototype.hasOwnProperty, it = Object.prototype.propertyIsEnumerable;
|
|
5
|
-
var U = (f, t, e) => t in f ? nt(f, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : f[t] = e,
|
|
5
|
+
var U = (f, t, e) => t in f ? nt(f, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : f[t] = e, O = (f, t) => {
|
|
6
6
|
for (var e in t || (t = {}))
|
|
7
7
|
ot.call(t, e) && U(f, e, t[e]);
|
|
8
8
|
if (K)
|
|
@@ -10,8 +10,8 @@ var U = (f, t, e) => t in f ? nt(f, t, { enumerable: !0, configurable: !0, writa
|
|
|
10
10
|
it.call(t, e) && U(f, e, t[e]);
|
|
11
11
|
return f;
|
|
12
12
|
}, P = (f, t) => rt(f, st(t));
|
|
13
|
-
import { t as x, D as m, a9 as et, aa as at, ab as lt, A as ct, ac as dt, ad as ut, ae as ht, af as ft, ag as pt, ah as mt, ai as G, aj as j, ak as $, f as A, al as gt, am as Et, S as H, P as Tt, an as Ct, l as At, J as yt } from "./chunks/inline-tool-convert-
|
|
14
|
-
import { a0 as Dt } from "./chunks/inline-tool-convert-
|
|
13
|
+
import { t as x, D as m, a9 as et, aa as at, ab as lt, A as ct, ac as dt, ad as ut, ae as ht, af as ft, ag as pt, ah as mt, ai as G, aj as j, ak as $, f as A, al as gt, am as Et, S as H, P as Tt, an as Ct, l as At, J as yt } from "./chunks/inline-tool-convert-CmxgdS16.mjs";
|
|
14
|
+
import { a0 as Dt } from "./chunks/inline-tool-convert-CmxgdS16.mjs";
|
|
15
15
|
const W = [
|
|
16
16
|
"empty:before:pointer-events-none",
|
|
17
17
|
"empty:before:text-gray-text",
|
|
@@ -555,15 +555,32 @@ const u = class u {
|
|
|
555
555
|
})));
|
|
556
556
|
}, this.api = n, this.readOnly = r, this._settings = e || {}, this._data = this.normalizeData(t), s && (this.blockId = s.id), this._data.style === "ordered" && this.api.events.on("block changed", this.handleBlockChanged);
|
|
557
557
|
}
|
|
558
|
+
/**
|
|
559
|
+
* Legacy list item structure for backward compatibility
|
|
560
|
+
*/
|
|
561
|
+
static isLegacyFormat(t) {
|
|
562
|
+
return typeof t == "object" && t !== null && "items" in t && Array.isArray(t.items);
|
|
563
|
+
}
|
|
558
564
|
normalizeData(t) {
|
|
559
565
|
var n;
|
|
560
566
|
const e = this._settings.defaultStyle || "unordered";
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
+
if (!t || typeof t != "object")
|
|
568
|
+
return {
|
|
569
|
+
text: "",
|
|
570
|
+
style: e,
|
|
571
|
+
checked: !1,
|
|
572
|
+
depth: 0
|
|
573
|
+
};
|
|
574
|
+
if (u.isLegacyFormat(t)) {
|
|
575
|
+
const r = t.items[0], s = (r == null ? void 0 : r.content) || "", o = (r == null ? void 0 : r.checked) || !1;
|
|
576
|
+
return O({
|
|
577
|
+
text: s,
|
|
578
|
+
style: t.style || e,
|
|
579
|
+
checked: !!o,
|
|
580
|
+
depth: 0
|
|
581
|
+
}, t.start !== void 0 && t.start !== 1 ? { start: t.start } : {});
|
|
582
|
+
}
|
|
583
|
+
return O({
|
|
567
584
|
text: t.text || "",
|
|
568
585
|
style: t.style || e,
|
|
569
586
|
checked: !!t.checked,
|
|
@@ -1155,7 +1172,7 @@ const u = class u {
|
|
|
1155
1172
|
charCount: r.charCount,
|
|
1156
1173
|
node: s,
|
|
1157
1174
|
offset: e - r.charCount
|
|
1158
|
-
} : P(
|
|
1175
|
+
} : P(O({}, r), {
|
|
1159
1176
|
charCount: r.charCount + o
|
|
1160
1177
|
});
|
|
1161
1178
|
},
|
|
@@ -1202,7 +1219,7 @@ const u = class u {
|
|
|
1202
1219
|
this.syncContentFromDOM();
|
|
1203
1220
|
const s = n + 1;
|
|
1204
1221
|
this._data.depth = s;
|
|
1205
|
-
const o = await this.api.blocks.update(this.blockId || "", P(
|
|
1222
|
+
const o = await this.api.blocks.update(this.blockId || "", P(O({}, this._data), {
|
|
1206
1223
|
depth: s
|
|
1207
1224
|
}));
|
|
1208
1225
|
this.setCaretToBlockContent(o);
|
|
@@ -1213,7 +1230,7 @@ const u = class u {
|
|
|
1213
1230
|
this.syncContentFromDOM();
|
|
1214
1231
|
const e = t - 1;
|
|
1215
1232
|
this._data.depth = e;
|
|
1216
|
-
const n = await this.api.blocks.update(this.blockId || "", P(
|
|
1233
|
+
const n = await this.api.blocks.update(this.blockId || "", P(O({}, this._data), {
|
|
1217
1234
|
depth: e
|
|
1218
1235
|
}));
|
|
1219
1236
|
this.setCaretToBlockContent(n);
|
|
@@ -2090,7 +2107,7 @@ const i = class i {
|
|
|
2090
2107
|
return;
|
|
2091
2108
|
const s = `strong[${i.DATA_ATTR_COLLAPSED_ACTIVE}="true"]`;
|
|
2092
2109
|
r.querySelectorAll(s).forEach((l) => {
|
|
2093
|
-
var k, I,
|
|
2110
|
+
var k, I, _, z, q;
|
|
2094
2111
|
const d = l.getAttribute(i.DATA_ATTR_PREV_LENGTH), c = l.previousSibling;
|
|
2095
2112
|
if (!d || !c || c.nodeType !== Node.TEXT_NODE)
|
|
2096
2113
|
return;
|
|
@@ -2105,13 +2122,13 @@ const i = class i {
|
|
|
2105
2122
|
const C = E.match(/^[\u00A0\s]+/);
|
|
2106
2123
|
if (C && !l.hasAttribute(i.DATA_ATTR_LEADING_WHITESPACE) && l.setAttribute(i.DATA_ATTR_LEADING_WHITESPACE, C[0]), E.length === 0)
|
|
2107
2124
|
return;
|
|
2108
|
-
const T = (I = l.textContent) != null ? I : "", N = T + E, B = (
|
|
2125
|
+
const T = (I = l.textContent) != null ? I : "", N = T + E, B = (_ = l.getAttribute(i.DATA_ATTR_LEADING_WHITESPACE)) != null ? _ : "", R = B.length > 0 && T.length === 0 && !N.startsWith(B) ? B + N : N, b = document.createTextNode(R);
|
|
2109
2126
|
for (; l.firstChild; )
|
|
2110
2127
|
l.removeChild(l.firstChild);
|
|
2111
2128
|
if (l.appendChild(b), !(t != null && t.isCollapsed) || !i.isNodeWithin(t.focusNode, p))
|
|
2112
2129
|
return;
|
|
2113
|
-
const S = document.createRange(),
|
|
2114
|
-
S.setStart(b,
|
|
2130
|
+
const S = document.createRange(), F = (q = (z = b.textContent) == null ? void 0 : z.length) != null ? q : 0;
|
|
2131
|
+
S.setStart(b, F), S.collapse(!0), t.removeAllRanges(), t.addRange(S);
|
|
2115
2132
|
});
|
|
2116
2133
|
}
|
|
2117
2134
|
/**
|
|
@@ -2320,7 +2337,7 @@ const i = class i {
|
|
|
2320
2337
|
if (!r)
|
|
2321
2338
|
return;
|
|
2322
2339
|
r.querySelectorAll(`strong[${i.DATA_ATTR_COLLAPSED_LENGTH}]`).forEach((a) => {
|
|
2323
|
-
var v, R, b, S,
|
|
2340
|
+
var v, R, b, S, F;
|
|
2324
2341
|
const l = a, d = l.getAttribute(i.DATA_ATTR_COLLAPSED_LENGTH);
|
|
2325
2342
|
if (!d)
|
|
2326
2343
|
return;
|
|
@@ -2331,11 +2348,11 @@ const i = class i {
|
|
|
2331
2348
|
if (B && T) {
|
|
2332
2349
|
const k = N.slice(0, E), I = N.slice(E);
|
|
2333
2350
|
T.textContent = k;
|
|
2334
|
-
const
|
|
2335
|
-
(b = l.parentNode) == null || b.insertBefore(
|
|
2351
|
+
const _ = document.createTextNode(I);
|
|
2352
|
+
(b = l.parentNode) == null || b.insertBefore(_, l.nextSibling);
|
|
2336
2353
|
}
|
|
2337
2354
|
if (B && l.removeAttribute(i.DATA_ATTR_PREV_LENGTH), t != null && t.isCollapsed && g && i.isNodeWithin(t.focusNode, l)) {
|
|
2338
|
-
const k = document.createRange(), I = (
|
|
2355
|
+
const k = document.createRange(), I = (F = (S = g.textContent) == null ? void 0 : S.length) != null ? F : 0;
|
|
2339
2356
|
k.setStart(g, I), k.collapse(!0), t.removeAllRanges(), t.addRange(k);
|
|
2340
2357
|
}
|
|
2341
2358
|
p && l.removeAttribute(i.DATA_ATTR_COLLAPSED_LENGTH);
|
|
@@ -2602,7 +2619,7 @@ const i = class i {
|
|
|
2602
2619
|
};
|
|
2603
2620
|
i.isInline = !0, i.title = "Bold", i.titleKey = "bold", i.shortcutListenerRegistered = !1, i.selectionListenerRegistered = !1, i.inputListenerRegistered = !1, i.beforeInputListenerRegistered = !1, i.globalListenersInitialized = i.initializeGlobalListeners(), i.collapsedExitRecords = /* @__PURE__ */ new Set(), i.markerSequence = 0, i.isProcessingMutation = !1, i.DATA_ATTR_COLLAPSED_LENGTH = "data-blok-bold-collapsed-length", i.DATA_ATTR_COLLAPSED_ACTIVE = "data-blok-bold-collapsed-active", i.DATA_ATTR_PREV_LENGTH = "data-blok-bold-prev-length", i.DATA_ATTR_LEADING_WHITESPACE = "data-blok-bold-leading-ws", i.instances = /* @__PURE__ */ new Set(), i.pendingBoundaryCaretAdjustments = /* @__PURE__ */ new WeakSet(), i.shortcut = "CMD+B";
|
|
2604
2621
|
let Z = i;
|
|
2605
|
-
const
|
|
2622
|
+
const D = class D {
|
|
2606
2623
|
/**
|
|
2607
2624
|
* Sanitizer Rule
|
|
2608
2625
|
* Leave <i> and <em> tags
|
|
@@ -2873,9 +2890,9 @@ const O = class O {
|
|
|
2873
2890
|
n.insertBefore(a, e.nextSibling), n.insertBefore(t, a);
|
|
2874
2891
|
}
|
|
2875
2892
|
};
|
|
2876
|
-
|
|
2877
|
-
let Q =
|
|
2878
|
-
const
|
|
2893
|
+
D.isInline = !0, D.title = "Italic", D.titleKey = "italic", D.shortcut = "CMD+I";
|
|
2894
|
+
let Q = D;
|
|
2895
|
+
const w = class w {
|
|
2879
2896
|
/**
|
|
2880
2897
|
* @param api - Blok API
|
|
2881
2898
|
*/
|
|
@@ -3102,8 +3119,8 @@ const D = class D {
|
|
|
3102
3119
|
t && t.setAttribute(e, n ? "true" : "false");
|
|
3103
3120
|
}
|
|
3104
3121
|
};
|
|
3105
|
-
|
|
3106
|
-
let tt =
|
|
3122
|
+
w.isInline = !0, w.title = "Link", w.titleKey = "link", w.shortcut = "CMD+K";
|
|
3123
|
+
let tt = w;
|
|
3107
3124
|
const vt = {
|
|
3108
3125
|
paragraph: { preserveBlank: !0 },
|
|
3109
3126
|
header: {},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jackuait/blok",
|
|
3
|
-
"version": "0.4.1-beta.
|
|
3
|
+
"version": "0.4.1-beta.14",
|
|
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",
|
|
@@ -96,14 +96,12 @@
|
|
|
96
96
|
"verify:package:local": "npm pack && node scripts/verify-published-package.mjs --local",
|
|
97
97
|
"verify:version": "node scripts/verify-version.mjs",
|
|
98
98
|
"unpublish": "node scripts/unpublish-package.mjs",
|
|
99
|
-
"bundle:track": "node scripts/track-bundle-size.mjs --verbose",
|
|
100
|
-
"bundle:variants": "node scripts/build-bundle-variants.mjs --verbose",
|
|
101
|
-
"bundle:trends": "node scripts/view-bundle-trends.mjs --trends",
|
|
102
|
-
"bundle:history": "node scripts/view-bundle-trends.mjs",
|
|
103
99
|
"perf:analyze": "node scripts/analyze-performance.mjs",
|
|
104
100
|
"perf:compare": "node scripts/analyze-performance.mjs --baseline",
|
|
105
101
|
"perf:dashboard": "node scripts/generate-performance-dashboard.mjs",
|
|
106
|
-
"e2e:validate-categories": "node scripts/validate-test-categories.mjs"
|
|
102
|
+
"e2e:validate-categories": "node scripts/validate-test-categories.mjs",
|
|
103
|
+
"size": "size-limit",
|
|
104
|
+
"size:why": "size-limit --why"
|
|
107
105
|
},
|
|
108
106
|
"author": "JackUait",
|
|
109
107
|
"contributors": [
|
|
@@ -123,6 +121,8 @@
|
|
|
123
121
|
"@playwright/test": "1.57.0",
|
|
124
122
|
"@semantic-release/changelog": "^6.0.3",
|
|
125
123
|
"@semantic-release/git": "^10.0.1",
|
|
124
|
+
"@size-limit/esbuild-why": "^12.0.0",
|
|
125
|
+
"@size-limit/preset-small-lib": "^12.0.0",
|
|
126
126
|
"@storybook/addon-a11y": "10.1.1",
|
|
127
127
|
"@storybook/addon-vitest": "10.1.1",
|
|
128
128
|
"@storybook/global": "5.0.0",
|
|
@@ -150,6 +150,7 @@
|
|
|
150
150
|
"postcss": "8.5.6",
|
|
151
151
|
"rollup-plugin-license": "3.6.0",
|
|
152
152
|
"semantic-release": "^25.0.2",
|
|
153
|
+
"size-limit": "^12.0.0",
|
|
153
154
|
"storybook": "10.1.1",
|
|
154
155
|
"tailwindcss": "3",
|
|
155
156
|
"tslint": "6.1.3",
|
|
@@ -163,5 +164,22 @@
|
|
|
163
164
|
"dependencies": {
|
|
164
165
|
"i18next": "^25.7.3",
|
|
165
166
|
"nanoid": "^5.1.6"
|
|
166
|
-
}
|
|
167
|
+
},
|
|
168
|
+
"size-limit": [
|
|
169
|
+
{
|
|
170
|
+
"name": "Minimum (core only)",
|
|
171
|
+
"path": "src/variants/blok-minimum.ts",
|
|
172
|
+
"limit": "300 KB"
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"name": "Normal (with tools)",
|
|
176
|
+
"path": "src/blok.ts",
|
|
177
|
+
"limit": "300 KB"
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"name": "Maximum (all locales)",
|
|
181
|
+
"path": "src/variants/blok-maximum.ts",
|
|
182
|
+
"limit": "300 KB"
|
|
183
|
+
}
|
|
184
|
+
]
|
|
167
185
|
}
|
|
@@ -789,6 +789,16 @@ export class UI extends Module<UINodes> {
|
|
|
789
789
|
return;
|
|
790
790
|
}
|
|
791
791
|
|
|
792
|
+
/**
|
|
793
|
+
* Close BlockSettings first if it's open, regardless of selection state.
|
|
794
|
+
* This prevents navigation mode from being enabled when the user closes block tunes with Escape.
|
|
795
|
+
*/
|
|
796
|
+
if (this.Blok.BlockSettings.opened) {
|
|
797
|
+
this.Blok.BlockSettings.close();
|
|
798
|
+
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
|
|
792
802
|
/**
|
|
793
803
|
* Clear blocks selection by ESC (but not when entering navigation mode)
|
|
794
804
|
*/
|
|
@@ -806,12 +816,6 @@ export class UI extends Module<UINodes> {
|
|
|
806
816
|
return;
|
|
807
817
|
}
|
|
808
818
|
|
|
809
|
-
if (this.Blok.BlockSettings.opened) {
|
|
810
|
-
this.Blok.BlockSettings.close();
|
|
811
|
-
|
|
812
|
-
return;
|
|
813
|
-
}
|
|
814
|
-
|
|
815
819
|
/**
|
|
816
820
|
* If a nested popover is open (like convert-to dropdown),
|
|
817
821
|
* close only the nested popover, not the entire inline toolbar.
|
|
@@ -42,20 +42,26 @@ export interface DataFormatAnalysis {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
|
-
* Recursively check if any list item has nested items
|
|
45
|
+
* Recursively check if any list item has nested items (for hasHierarchy flag)
|
|
46
46
|
*/
|
|
47
47
|
const hasNestedListItems = (items: LegacyListItem[]): boolean => {
|
|
48
48
|
return items.some(item => item.items !== undefined && item.items.length > 0);
|
|
49
49
|
};
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
|
-
* Check if a block
|
|
53
|
-
*
|
|
52
|
+
* Check if a block is in legacy list format (has items[] array with content field)
|
|
53
|
+
* Legacy format: { items: [{ content: "text" }], style: "unordered" }
|
|
54
|
+
* Flat format: { text: "text", style: "unordered" }
|
|
54
55
|
*/
|
|
55
|
-
const
|
|
56
|
-
|
|
56
|
+
const isLegacyListBlock = (block: OutputBlockData): boolean => {
|
|
57
|
+
return block.type === 'list' && Array.isArray(block.data?.items);
|
|
58
|
+
};
|
|
57
59
|
|
|
58
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Check if a block contains nested hierarchy in its items
|
|
62
|
+
*/
|
|
63
|
+
const hasNestedItems = (block: OutputBlockData): boolean => {
|
|
64
|
+
if (!isLegacyListBlock(block)) {
|
|
59
65
|
return false;
|
|
60
66
|
}
|
|
61
67
|
|
|
@@ -81,10 +87,14 @@ export const analyzeDataFormat = (blocks: OutputBlockData[]): DataFormatAnalysis
|
|
|
81
87
|
return { format: 'hierarchical', hasHierarchy: true };
|
|
82
88
|
}
|
|
83
89
|
|
|
84
|
-
|
|
90
|
+
// Check if any block uses legacy list format (items[] array)
|
|
91
|
+
const foundLegacyList = blocks.some(isLegacyListBlock);
|
|
92
|
+
|
|
93
|
+
if (foundLegacyList) {
|
|
94
|
+
// Check if there's actual nesting for the hasHierarchy flag
|
|
95
|
+
const hasNesting = blocks.some(hasNestedItems);
|
|
85
96
|
|
|
86
|
-
|
|
87
|
-
return { format: 'legacy', hasHierarchy: true };
|
|
97
|
+
return { format: 'legacy', hasHierarchy: hasNesting };
|
|
88
98
|
}
|
|
89
99
|
|
|
90
100
|
return { format: 'flat', hasHierarchy: false };
|
|
@@ -109,37 +119,49 @@ const expandListItems = (
|
|
|
109
119
|
|
|
110
120
|
childIds.push(itemId);
|
|
111
121
|
|
|
112
|
-
// Recursively expand nested items first to get their IDs
|
|
113
|
-
const nestedChildIds = item.items && item.items.length > 0
|
|
114
|
-
? expandListItems(item.items, itemId, depth + 1, style, undefined, tunes, blocks)
|
|
115
|
-
: [];
|
|
116
|
-
|
|
117
122
|
// Determine if we should include start (only for first root item of ordered lists)
|
|
118
123
|
const includeStart = style === 'ordered' && depth === 0 && index === 0 && start !== undefined && start !== 1;
|
|
119
124
|
|
|
120
|
-
//
|
|
125
|
+
// Check if there are nested items (we'll get their IDs after pushing the parent)
|
|
126
|
+
const hasNestedItems = item.items && item.items.length > 0;
|
|
127
|
+
|
|
128
|
+
// Create the list block (flat model - each item is a separate 'list' block)
|
|
129
|
+
// We'll update with content IDs after processing children
|
|
121
130
|
const itemBlock: OutputBlockData = {
|
|
122
131
|
id: itemId,
|
|
123
|
-
type: '
|
|
132
|
+
type: 'list',
|
|
124
133
|
data: {
|
|
125
134
|
text: item.content,
|
|
126
135
|
checked: item.checked,
|
|
127
136
|
style,
|
|
137
|
+
...(depth > 0 ? { depth } : {}),
|
|
128
138
|
...(includeStart ? { start } : {}),
|
|
129
139
|
},
|
|
130
140
|
...(tunes !== undefined ? { tunes } : {}),
|
|
131
141
|
...(parentId !== undefined ? { parent: parentId } : {}),
|
|
132
|
-
...(nestedChildIds.length > 0 ? { content: nestedChildIds } : {}),
|
|
133
142
|
};
|
|
134
143
|
|
|
144
|
+
// Push parent block first to maintain correct order (parent before children)
|
|
135
145
|
blocks.push(itemBlock);
|
|
146
|
+
|
|
147
|
+
// Now recursively expand nested items (they will be added after the parent)
|
|
148
|
+
if (!hasNestedItems) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const nestedChildIds = expandListItems(item.items!, itemId, depth + 1, style, undefined, tunes, blocks);
|
|
153
|
+
|
|
154
|
+
// Update the parent block with content IDs (only if children exist)
|
|
155
|
+
if (nestedChildIds.length > 0) {
|
|
156
|
+
itemBlock.content = nestedChildIds;
|
|
157
|
+
}
|
|
136
158
|
});
|
|
137
159
|
|
|
138
160
|
return childIds;
|
|
139
161
|
};
|
|
140
162
|
|
|
141
163
|
/**
|
|
142
|
-
* Expand a List block with nested items into flat
|
|
164
|
+
* Expand a List block with nested items into flat list blocks
|
|
143
165
|
*/
|
|
144
166
|
const expandListToHierarchical = (
|
|
145
167
|
listData: LegacyListData,
|
|
@@ -164,12 +186,7 @@ export const expandToHierarchical = (blocks: OutputBlockData[]): OutputBlockData
|
|
|
164
186
|
const expandedBlocks: OutputBlockData[] = [];
|
|
165
187
|
|
|
166
188
|
for (const block of blocks) {
|
|
167
|
-
|
|
168
|
-
const blockId = block.id ?? generateBlockId();
|
|
169
|
-
|
|
170
|
-
const isListBlock = block.type === 'list' && block.data?.items;
|
|
171
|
-
|
|
172
|
-
if (isListBlock) {
|
|
189
|
+
if (isLegacyListBlock(block)) {
|
|
173
190
|
// Expand List tool nested items to flat blocks
|
|
174
191
|
const listData = block.data as LegacyListData;
|
|
175
192
|
const expanded = expandListToHierarchical(listData, block.tunes);
|
|
@@ -179,7 +196,7 @@ export const expandToHierarchical = (blocks: OutputBlockData[]): OutputBlockData
|
|
|
179
196
|
// Non-list blocks pass through unchanged (with guaranteed ID)
|
|
180
197
|
expandedBlocks.push({
|
|
181
198
|
...block,
|
|
182
|
-
id:
|
|
199
|
+
id: block.id ?? generateBlockId(),
|
|
183
200
|
});
|
|
184
201
|
}
|
|
185
202
|
}
|
|
@@ -217,7 +234,7 @@ const collectChildItems = (
|
|
|
217
234
|
|
|
218
235
|
for (const childId of contentIds) {
|
|
219
236
|
const childBlock = blockMap.get(childId);
|
|
220
|
-
const isListItem = childBlock && childBlock.type === '
|
|
237
|
+
const isListItem = childBlock && childBlock.type === 'list';
|
|
221
238
|
|
|
222
239
|
if (isListItem) {
|
|
223
240
|
const items = collectListItems(childBlock, blockMap, processedIds);
|
|
@@ -269,7 +286,7 @@ const collectListItems = (
|
|
|
269
286
|
};
|
|
270
287
|
|
|
271
288
|
/**
|
|
272
|
-
* Process a root
|
|
289
|
+
* Process a root list block (flat model) and convert to a legacy List block
|
|
273
290
|
*/
|
|
274
291
|
const processRootListItem = (
|
|
275
292
|
block: OutputBlockData,
|
|
@@ -294,6 +311,13 @@ const processRootListItem = (
|
|
|
294
311
|
return listBlock;
|
|
295
312
|
};
|
|
296
313
|
|
|
314
|
+
/**
|
|
315
|
+
* Check if a block is a flat-model list block (has 'text' field instead of 'items')
|
|
316
|
+
*/
|
|
317
|
+
const isFlatModelListBlock = (block: OutputBlockData): boolean => {
|
|
318
|
+
return block.type === 'list' && block.data?.text !== undefined && block.data?.items === undefined;
|
|
319
|
+
};
|
|
320
|
+
|
|
297
321
|
/**
|
|
298
322
|
* Collapse hierarchical flat-with-references format back to legacy nested format
|
|
299
323
|
* @param blocks - array of flat blocks with parent/content references
|
|
@@ -302,24 +326,21 @@ const processRootListItem = (
|
|
|
302
326
|
export const collapseToLegacy = (blocks: OutputBlockData[]): OutputBlockData[] => {
|
|
303
327
|
// Build a map of blocks by ID for quick lookup
|
|
304
328
|
const blockMap = new Map<BlockId, OutputBlockData>();
|
|
305
|
-
const listItemBlocks: OutputBlockData[] = [];
|
|
306
329
|
|
|
307
330
|
for (const block of blocks) {
|
|
308
331
|
if (block.id) {
|
|
309
332
|
blockMap.set(block.id, block);
|
|
310
333
|
}
|
|
311
|
-
|
|
312
|
-
if (block.type === 'list_item') {
|
|
313
|
-
listItemBlocks.push(block);
|
|
314
|
-
}
|
|
315
334
|
}
|
|
316
335
|
|
|
317
|
-
// If no
|
|
318
|
-
|
|
336
|
+
// If no flat-model list blocks, just strip hierarchy fields and return
|
|
337
|
+
const hasFlatListBlocks = blocks.some(isFlatModelListBlock);
|
|
338
|
+
|
|
339
|
+
if (!hasFlatListBlocks) {
|
|
319
340
|
return blocks.map(stripHierarchyFields);
|
|
320
341
|
}
|
|
321
342
|
|
|
322
|
-
// Process blocks, converting root
|
|
343
|
+
// Process blocks, converting root flat-model list blocks to legacy List blocks
|
|
323
344
|
const result: OutputBlockData[] = [];
|
|
324
345
|
const processedIds = new Set<BlockId>();
|
|
325
346
|
|
|
@@ -330,8 +351,9 @@ export const collapseToLegacy = (blocks: OutputBlockData[]): OutputBlockData[] =
|
|
|
330
351
|
continue;
|
|
331
352
|
}
|
|
332
353
|
|
|
333
|
-
const
|
|
334
|
-
const
|
|
354
|
+
const isFlatListBlock = isFlatModelListBlock(block);
|
|
355
|
+
const isRootListItem = isFlatListBlock && !block.parent;
|
|
356
|
+
const isNonListItem = !isFlatListBlock;
|
|
335
357
|
|
|
336
358
|
if (isRootListItem) {
|
|
337
359
|
const listBlock = processRootListItem(block, blockMap, processedIds);
|
package/src/tools/list/index.ts
CHANGED
|
@@ -185,6 +185,13 @@ export class ListItem implements BlockTool {
|
|
|
185
185
|
|
|
186
186
|
sanitize?: ToolSanitizerConfig | undefined;
|
|
187
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Legacy list item structure for backward compatibility
|
|
190
|
+
*/
|
|
191
|
+
private static isLegacyFormat(data: unknown): data is { items: Array<{ content: string; checked?: boolean }>, style?: ListItemStyle, start?: number } {
|
|
192
|
+
return typeof data === 'object' && data !== null && 'items' in data && Array.isArray((data as { items: unknown }).items);
|
|
193
|
+
}
|
|
194
|
+
|
|
188
195
|
private normalizeData(data: ListItemData | Record<string, never>): ListItemData {
|
|
189
196
|
const defaultStyle = this._settings.defaultStyle || 'unordered';
|
|
190
197
|
|
|
@@ -197,6 +204,22 @@ export class ListItem implements BlockTool {
|
|
|
197
204
|
};
|
|
198
205
|
}
|
|
199
206
|
|
|
207
|
+
// Handle legacy format with items[] array - extract first item's content
|
|
208
|
+
// This provides backward compatibility when legacy data is passed directly to the tool
|
|
209
|
+
if (ListItem.isLegacyFormat(data)) {
|
|
210
|
+
const firstItem = data.items[0];
|
|
211
|
+
const text = firstItem?.content || '';
|
|
212
|
+
const checked = firstItem?.checked || false;
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
text,
|
|
216
|
+
style: data.style || defaultStyle,
|
|
217
|
+
checked: Boolean(checked),
|
|
218
|
+
depth: 0,
|
|
219
|
+
...(data.start !== undefined && data.start !== 1 ? { start: data.start } : {}),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
200
223
|
return {
|
|
201
224
|
text: data.text || '',
|
|
202
225
|
style: data.style || defaultStyle,
|