@portabletext/block-tools 3.3.3 → 3.4.1
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/lib/index.cjs +142 -124
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +4 -36
- package/lib/index.d.ts +4 -36
- package/lib/index.js +114 -95
- package/lib/index.js.map +1 -1
- package/package.json +10 -9
- package/src/HtmlDeserializer/flatten-nested-blocks.test.ts +112 -1
- package/src/HtmlDeserializer/flatten-nested-blocks.ts +174 -0
- package/src/HtmlDeserializer/helpers.ts +24 -159
- package/src/HtmlDeserializer/index.ts +7 -7
- package/src/HtmlDeserializer/rules/html.ts +12 -0
- package/src/index.ts +1 -1
- package/src/types.ts +1 -1
- package/src/util/normalizeBlock.ts +6 -6
- package/src/types.portable-text.ts +0 -79
package/lib/index.js
CHANGED
|
@@ -1,20 +1,9 @@
|
|
|
1
1
|
import { sanitySchemaToPortableTextSchema } from "@portabletext/sanity-bridge";
|
|
2
|
+
import { isTextBlock, isSpan } from "@portabletext/schema";
|
|
2
3
|
import flatten from "lodash/flatten.js";
|
|
3
4
|
import getRandomValues from "get-random-values-esm";
|
|
4
5
|
import isEqual from "lodash/isEqual.js";
|
|
5
6
|
import uniq from "lodash/uniq.js";
|
|
6
|
-
function isArbitraryTypedObject(object) {
|
|
7
|
-
return isRecord(object) && typeof object._type == "string";
|
|
8
|
-
}
|
|
9
|
-
function isRecord(value) {
|
|
10
|
-
return !!value && (typeof value == "object" || typeof value == "function");
|
|
11
|
-
}
|
|
12
|
-
function isTextBlock(schema, block) {
|
|
13
|
-
return !(!isArbitraryTypedObject(block) || block._type !== schema.block.name || !Array.isArray(block.children));
|
|
14
|
-
}
|
|
15
|
-
function isSpan(schema, child) {
|
|
16
|
-
return !(!isArbitraryTypedObject(child) || child._type !== schema.span.name || typeof child.text != "string");
|
|
17
|
-
}
|
|
18
7
|
function keyGenerator() {
|
|
19
8
|
return randomKey(12);
|
|
20
9
|
}
|
|
@@ -46,6 +35,81 @@ function resolveJsType(val) {
|
|
|
46
35
|
}
|
|
47
36
|
return val === null ? "null" : val === void 0 ? "undefined" : val && typeof val == "object" && "nodeType" in val && val.nodeType === 1 ? "element" : val === Object(val) ? "object" : typeof val;
|
|
48
37
|
}
|
|
38
|
+
function isArbitraryTypedObject(object) {
|
|
39
|
+
return isRecord(object) && typeof object._type == "string";
|
|
40
|
+
}
|
|
41
|
+
function isRecord(value) {
|
|
42
|
+
return !!value && (typeof value == "object" || typeof value == "function");
|
|
43
|
+
}
|
|
44
|
+
function flattenNestedBlocks(context, blocks2) {
|
|
45
|
+
return blocks2.flatMap((block) => {
|
|
46
|
+
if (isBlockContainer(block))
|
|
47
|
+
return flattenNestedBlocks(context, [block.block]);
|
|
48
|
+
if (isTextBlock(context, block)) {
|
|
49
|
+
const hasBlockObjects = block.children.some((child) => context.schema.blockObjects.some(
|
|
50
|
+
(blockObject) => blockObject.name === child._type
|
|
51
|
+
)), hasBlocks = block.children.some(
|
|
52
|
+
(child) => child._type === "__block" || child._type === "block"
|
|
53
|
+
);
|
|
54
|
+
if (hasBlockObjects || hasBlocks) {
|
|
55
|
+
const splitChildren = getSplitChildren(context, block);
|
|
56
|
+
return splitChildren.length === 1 && splitChildren[0].type === "children" && isEqual(splitChildren[0].children, block.children) ? [block] : splitChildren.flatMap((slice) => slice.type === "block object" ? [slice.block] : slice.type === "block" ? flattenNestedBlocks(context, [
|
|
57
|
+
slice.block
|
|
58
|
+
]) : slice.children.length > 0 ? slice.children.every(
|
|
59
|
+
(child) => isSpan(context, child) && child.text.trim() === ""
|
|
60
|
+
) ? [] : flattenNestedBlocks(context, [
|
|
61
|
+
{
|
|
62
|
+
...block,
|
|
63
|
+
children: slice.children
|
|
64
|
+
}
|
|
65
|
+
]) : []);
|
|
66
|
+
}
|
|
67
|
+
return [block];
|
|
68
|
+
}
|
|
69
|
+
return [block];
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function isBlockContainer(block) {
|
|
73
|
+
return block._type === "__block" && isArbitraryTypedObject(block.block);
|
|
74
|
+
}
|
|
75
|
+
function getSplitChildren(context, block) {
|
|
76
|
+
return block.children.reduce(
|
|
77
|
+
(slices, child) => {
|
|
78
|
+
const knownInlineObject = context.schema.inlineObjects.some(
|
|
79
|
+
(inlineObject) => inlineObject.name === child._type
|
|
80
|
+
), knownBlockObject = context.schema.blockObjects.some(
|
|
81
|
+
(blockObject) => blockObject.name === child._type
|
|
82
|
+
), lastSlice = slices.pop();
|
|
83
|
+
return !isSpan(context, child) && !knownInlineObject && knownBlockObject ? [
|
|
84
|
+
...slices,
|
|
85
|
+
...lastSlice ? [lastSlice] : [],
|
|
86
|
+
{ type: "block object", block: child }
|
|
87
|
+
] : child._type === "__block" ? [
|
|
88
|
+
...slices,
|
|
89
|
+
...lastSlice ? [lastSlice] : [],
|
|
90
|
+
{
|
|
91
|
+
type: "block object",
|
|
92
|
+
block: child.block
|
|
93
|
+
}
|
|
94
|
+
] : child._type === "block" ? [
|
|
95
|
+
...slices,
|
|
96
|
+
...lastSlice ? [lastSlice] : [],
|
|
97
|
+
{ type: "block", block: child }
|
|
98
|
+
] : lastSlice && lastSlice.type === "children" ? [
|
|
99
|
+
...slices,
|
|
100
|
+
{
|
|
101
|
+
type: "children",
|
|
102
|
+
children: [...lastSlice.children, child]
|
|
103
|
+
}
|
|
104
|
+
] : [
|
|
105
|
+
...slices,
|
|
106
|
+
...lastSlice ? [lastSlice] : [],
|
|
107
|
+
{ type: "children", children: [child] }
|
|
108
|
+
];
|
|
109
|
+
},
|
|
110
|
+
[]
|
|
111
|
+
);
|
|
112
|
+
}
|
|
49
113
|
var s = { 0: 8203, 1: 8204, 2: 8205, 3: 8290, 4: 8291, 5: 8288, 6: 65279, 7: 8289, 8: 119155, 9: 119156, a: 119157, b: 119158, c: 119159, d: 119160, e: 119161, f: 119162 }, c = { 0: 8203, 1: 8204, 2: 8205, 3: 65279 };
|
|
50
114
|
new Array(4).fill(String.fromCodePoint(c[0])).join("");
|
|
51
115
|
Object.fromEntries(Object.entries(c).map((t) => t.reverse()));
|
|
@@ -309,73 +373,6 @@ function defaultParseHtml() {
|
|
|
309
373
|
);
|
|
310
374
|
return (html) => new DOMParser().parseFromString(html, "text/html");
|
|
311
375
|
}
|
|
312
|
-
function flattenNestedBlocks(context, blocks2) {
|
|
313
|
-
let depth = 0;
|
|
314
|
-
const flattened = [], traverse = (nodes) => {
|
|
315
|
-
const toRemove = [];
|
|
316
|
-
nodes.forEach((node) => {
|
|
317
|
-
if (depth === 0)
|
|
318
|
-
if (context.schema.blockObjects.length > 0 && isTextBlock(context.schema, node)) {
|
|
319
|
-
const hasBlockObjects = node.children.some((child) => context.schema.blockObjects.some(
|
|
320
|
-
(blockObject) => blockObject.name === child._type
|
|
321
|
-
)), hasBlocks = node.children.some(
|
|
322
|
-
(child) => child._type === "__block"
|
|
323
|
-
);
|
|
324
|
-
if (hasBlockObjects || hasBlocks) {
|
|
325
|
-
node.children.reduce(
|
|
326
|
-
(slices, child) => {
|
|
327
|
-
const knownInlineObject = context.schema.inlineObjects.some(
|
|
328
|
-
(inlineObject) => inlineObject.name === child._type
|
|
329
|
-
), knownBlockObject = context.schema.blockObjects.some(
|
|
330
|
-
(blockObject) => blockObject.name === child._type
|
|
331
|
-
), lastSlice = slices.pop();
|
|
332
|
-
return !isSpan(context.schema, child) && !knownInlineObject && knownBlockObject ? [
|
|
333
|
-
...slices,
|
|
334
|
-
...lastSlice ? [lastSlice] : [],
|
|
335
|
-
{ type: "block object", block: child }
|
|
336
|
-
] : child._type === "__block" ? [
|
|
337
|
-
...slices,
|
|
338
|
-
...lastSlice ? [lastSlice] : [],
|
|
339
|
-
{
|
|
340
|
-
type: "block object",
|
|
341
|
-
block: child.block
|
|
342
|
-
}
|
|
343
|
-
] : lastSlice && lastSlice.type === "children" ? [
|
|
344
|
-
...slices,
|
|
345
|
-
{
|
|
346
|
-
type: "children",
|
|
347
|
-
children: [...lastSlice.children, child]
|
|
348
|
-
}
|
|
349
|
-
] : [
|
|
350
|
-
...slices,
|
|
351
|
-
...lastSlice ? [lastSlice] : [],
|
|
352
|
-
{ type: "children", children: [child] }
|
|
353
|
-
];
|
|
354
|
-
},
|
|
355
|
-
[]
|
|
356
|
-
).forEach((slice) => {
|
|
357
|
-
if (slice.type === "block object")
|
|
358
|
-
flattened.push(slice.block);
|
|
359
|
-
else if (slice.children.length > 0) {
|
|
360
|
-
const newBlock = {
|
|
361
|
-
...node,
|
|
362
|
-
children: slice.children
|
|
363
|
-
};
|
|
364
|
-
flattened.push(newBlock);
|
|
365
|
-
}
|
|
366
|
-
});
|
|
367
|
-
return;
|
|
368
|
-
} else
|
|
369
|
-
flattened.push(node);
|
|
370
|
-
} else
|
|
371
|
-
flattened.push(node);
|
|
372
|
-
isTextBlock(context.schema, node) && (depth > 0 && (toRemove.push(node), flattened.push(node)), depth++, traverse(node.children)), node._type === "__block" && (toRemove.push(node), flattened.push(node.block));
|
|
373
|
-
}), toRemove.forEach((node) => {
|
|
374
|
-
nodes.splice(nodes.indexOf(node), 1);
|
|
375
|
-
}), depth--;
|
|
376
|
-
};
|
|
377
|
-
return traverse(blocks2), flattened;
|
|
378
|
-
}
|
|
379
376
|
function nextSpan(block, index) {
|
|
380
377
|
const next = block.children[index + 1];
|
|
381
378
|
return next && next._type === "span" ? next : null;
|
|
@@ -389,7 +386,7 @@ function isWhiteSpaceChar(text) {
|
|
|
389
386
|
}
|
|
390
387
|
function trimWhitespace(schema, blocks2) {
|
|
391
388
|
return blocks2.forEach((block) => {
|
|
392
|
-
isTextBlock(schema, block) && block.children.forEach((child, index) => {
|
|
389
|
+
isTextBlock({ schema }, block) && block.children.forEach((child, index) => {
|
|
393
390
|
if (!isMinimalSpan(child))
|
|
394
391
|
return;
|
|
395
392
|
const nextChild = nextSpan(block, index), prevChild = prevSpan(block, index);
|
|
@@ -397,20 +394,20 @@ function trimWhitespace(schema, blocks2) {
|
|
|
397
394
|
});
|
|
398
395
|
}), blocks2;
|
|
399
396
|
}
|
|
400
|
-
function ensureRootIsBlocks(schema,
|
|
401
|
-
return
|
|
397
|
+
function ensureRootIsBlocks(schema, objects) {
|
|
398
|
+
return objects.reduce((blocks2, node, i, original) => {
|
|
402
399
|
if (node._type === "block")
|
|
403
|
-
return
|
|
400
|
+
return blocks2.push(node), blocks2;
|
|
404
401
|
if (node._type === "__block")
|
|
405
|
-
return
|
|
406
|
-
const lastBlock =
|
|
407
|
-
if (i > 0 && !isTextBlock(schema, original[i - 1]) && isTextBlock(schema, lastBlock))
|
|
408
|
-
return lastBlock.children.push(node),
|
|
402
|
+
return blocks2.push(node.block), blocks2;
|
|
403
|
+
const lastBlock = blocks2[blocks2.length - 1];
|
|
404
|
+
if (i > 0 && !isTextBlock({ schema }, original[i - 1]) && isTextBlock({ schema }, lastBlock))
|
|
405
|
+
return lastBlock.children.push(node), blocks2;
|
|
409
406
|
const block = {
|
|
410
407
|
...DEFAULT_BLOCK,
|
|
411
408
|
children: [node]
|
|
412
409
|
};
|
|
413
|
-
return
|
|
410
|
+
return blocks2.push(block), blocks2;
|
|
414
411
|
}, []);
|
|
415
412
|
}
|
|
416
413
|
function isNodeList(node) {
|
|
@@ -442,7 +439,9 @@ function normalizeWhitespace(rootNode) {
|
|
|
442
439
|
const elm = child;
|
|
443
440
|
isWhitespaceBlock(elm) ? (lastParent && elm.parentElement === lastParent ? (emptyBlockCount++, emptyBlockCount > 1 && nodesToRemove.push(elm)) : emptyBlockCount = 1, lastParent = elm.parentElement) : (normalizeWhitespace(child), emptyBlockCount = 0);
|
|
444
441
|
}
|
|
445
|
-
nodesToRemove.forEach((node) =>
|
|
442
|
+
nodesToRemove.forEach((node) => {
|
|
443
|
+
node.parentElement?.removeChild(node);
|
|
444
|
+
});
|
|
446
445
|
}
|
|
447
446
|
function removeAllWhitespace(rootNode) {
|
|
448
447
|
const nodesToRemove = [];
|
|
@@ -461,7 +460,9 @@ function removeAllWhitespace(rootNode) {
|
|
|
461
460
|
collectNodesToRemove(child);
|
|
462
461
|
}
|
|
463
462
|
}
|
|
464
|
-
collectNodesToRemove(rootNode), nodesToRemove.forEach((node) =>
|
|
463
|
+
collectNodesToRemove(rootNode), nodesToRemove.forEach((node) => {
|
|
464
|
+
node.parentElement?.removeChild(node);
|
|
465
|
+
});
|
|
465
466
|
}
|
|
466
467
|
function isWhitespaceBlock(elm) {
|
|
467
468
|
return ["p", "br"].includes(tagName(elm) || "") && !elm.textContent?.trim();
|
|
@@ -738,6 +739,15 @@ function createHTMLRules(schema, options) {
|
|
|
738
739
|
} : el.appendChild(el.ownerDocument.createTextNode(` (${href})`)) && next(el.childNodes) : next(el.childNodes);
|
|
739
740
|
}
|
|
740
741
|
},
|
|
742
|
+
{
|
|
743
|
+
deserialize(el, next) {
|
|
744
|
+
if (isElement(el) && (tagName(el) === "td" || tagName(el) === "th"))
|
|
745
|
+
return {
|
|
746
|
+
...DEFAULT_BLOCK,
|
|
747
|
+
children: next(el.childNodes)
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
},
|
|
741
751
|
{
|
|
742
752
|
deserialize(el) {
|
|
743
753
|
if (isElement(el) && tagName(el) === "img") {
|
|
@@ -901,14 +911,14 @@ class HtmlDeserializer {
|
|
|
901
911
|
const { parseHtml } = this, fragment = parseHtml(html), children = Array.from(fragment.childNodes), blocks2 = trimWhitespace(
|
|
902
912
|
this.schema,
|
|
903
913
|
flattenNestedBlocks(
|
|
904
|
-
{ schema: this.schema },
|
|
914
|
+
{ schema: this.schema, keyGenerator: this.keyGenerator },
|
|
905
915
|
ensureRootIsBlocks(
|
|
906
916
|
this.schema,
|
|
907
917
|
this.deserializeElements(children)
|
|
908
918
|
)
|
|
909
919
|
)
|
|
910
920
|
);
|
|
911
|
-
return this._markDefs.length > 0 && blocks2.filter((block) => isTextBlock(this.schema, block)).forEach((block) => {
|
|
921
|
+
return this._markDefs.length > 0 && blocks2.filter((block) => isTextBlock({ schema: this.schema }, block)).forEach((block) => {
|
|
912
922
|
block.markDefs = block.markDefs || [], block.markDefs = block.markDefs.concat(
|
|
913
923
|
this._markDefs.filter((def) => flatten(
|
|
914
924
|
block.children.map((child) => child.marks || [])
|
|
@@ -1029,9 +1039,18 @@ class HtmlDeserializer {
|
|
|
1029
1039
|
}
|
|
1030
1040
|
function normalizeBlock(node, options = {}) {
|
|
1031
1041
|
const schema = {
|
|
1042
|
+
block: {
|
|
1043
|
+
name: options.blockTypeName || "block"
|
|
1044
|
+
},
|
|
1032
1045
|
span: {
|
|
1033
1046
|
name: "span"
|
|
1034
|
-
}
|
|
1047
|
+
},
|
|
1048
|
+
styles: [],
|
|
1049
|
+
lists: [],
|
|
1050
|
+
decorators: [],
|
|
1051
|
+
annotations: [],
|
|
1052
|
+
blockObjects: [],
|
|
1053
|
+
inlineObjects: []
|
|
1035
1054
|
};
|
|
1036
1055
|
if (node._type !== (options.blockTypeName || "block"))
|
|
1037
1056
|
return "_key" in node ? node : {
|
|
@@ -1057,13 +1076,13 @@ function normalizeBlock(node, options = {}) {
|
|
|
1057
1076
|
return block.children = block.children.reduce(
|
|
1058
1077
|
(acc, child) => {
|
|
1059
1078
|
const previousChild = acc[acc.length - 1];
|
|
1060
|
-
return previousChild && isSpan(schema, child) && isSpan(schema, previousChild) && isEqual(previousChild.marks, child.marks) ? (lastChild && lastChild === child && child.text === "" && block.children.length > 1 || (previousChild.text += child.text), acc) : (acc.push(child), acc);
|
|
1079
|
+
return previousChild && isSpan({ schema }, child) && isSpan({ schema }, previousChild) && isEqual(previousChild.marks, child.marks) ? (lastChild && lastChild === child && child.text === "" && block.children.length > 1 || (previousChild.text += child.text), acc) : (acc.push(child), acc);
|
|
1061
1080
|
},
|
|
1062
1081
|
[]
|
|
1063
1082
|
).map((child) => {
|
|
1064
1083
|
if (!child)
|
|
1065
1084
|
throw new Error("missing child");
|
|
1066
|
-
return child._key = options.keyGenerator ? options.keyGenerator() : keyGenerator(), isSpan(schema, child) && (child.marks ? allowedDecorators && (child.marks = child.marks.filter((mark) => {
|
|
1085
|
+
return child._key = options.keyGenerator ? options.keyGenerator() : keyGenerator(), isSpan({ schema }, child) && (child.marks ? allowedDecorators && (child.marks = child.marks.filter((mark) => {
|
|
1067
1086
|
const isAllowed = allowedDecorators.includes(mark), isUsed = block.markDefs?.some((def) => def._key === mark);
|
|
1068
1087
|
return isAllowed || isUsed;
|
|
1069
1088
|
})) : child.marks = [], usedMarkDefs.push(...child.marks)), child;
|