@portabletext/block-tools 0.0.0

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.js ADDED
@@ -0,0 +1,1056 @@
1
+ import flatten from "lodash/flatten.js";
2
+ import { isBlockSchemaType, isBlockChildrenObjectField, isObjectSchemaType, isBlockStyleObjectField, isBlockListObjectField, isTitledListValue, isPortableTextTextBlock, isPortableTextSpan } from "@sanity/types";
3
+ import isEqual from "lodash/isEqual.js";
4
+ import uniq from "lodash/uniq.js";
5
+ import getRandomValues from "get-random-values-esm";
6
+ function findBlockType(type) {
7
+ return type.type ? findBlockType(type.type) : type.name === "block";
8
+ }
9
+ const objectToString = Object.prototype.toString;
10
+ function resolveJsType(val) {
11
+ switch (objectToString.call(val)) {
12
+ case "[object Function]":
13
+ return "function";
14
+ case "[object Date]":
15
+ return "date";
16
+ case "[object RegExp]":
17
+ return "regexp";
18
+ case "[object Arguments]":
19
+ return "arguments";
20
+ case "[object Array]":
21
+ return "array";
22
+ case "[object String]":
23
+ return "string";
24
+ }
25
+ 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;
26
+ }
27
+ 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 };
28
+ new Array(4).fill(String.fromCodePoint(c[0])).join("");
29
+ Object.fromEntries(Object.entries(c).map((t) => t.reverse()));
30
+ Object.fromEntries(Object.entries(s).map((t) => t.reverse()));
31
+ var S = `${Object.values(s).map((t) => `\\u{${t.toString(16)}}`).join("")}`, f = new RegExp(`[${S}]{4,}`, "gu");
32
+ function _(t) {
33
+ var e;
34
+ return { cleaned: t.replace(f, ""), encoded: ((e = t.match(f)) == null ? void 0 : e[0]) || "" };
35
+ }
36
+ function O(t) {
37
+ return t && JSON.parse(_(JSON.stringify(t)).cleaned);
38
+ }
39
+ const PRESERVE_WHITESPACE_TAGS = ["pre", "textarea", "code"], BLOCK_DEFAULT_STYLE = "normal", DEFAULT_BLOCK = Object.freeze({
40
+ _type: "block",
41
+ markDefs: [],
42
+ style: BLOCK_DEFAULT_STYLE
43
+ }), DEFAULT_SPAN = Object.freeze({
44
+ _type: "span",
45
+ marks: []
46
+ }), HTML_BLOCK_TAGS = {
47
+ p: DEFAULT_BLOCK,
48
+ blockquote: { ...DEFAULT_BLOCK, style: "blockquote" }
49
+ }, HTML_SPAN_TAGS = {
50
+ span: { object: "text" }
51
+ }, HTML_LIST_CONTAINER_TAGS = {
52
+ ol: { object: null },
53
+ ul: { object: null }
54
+ }, HTML_HEADER_TAGS = {
55
+ h1: { ...DEFAULT_BLOCK, style: "h1" },
56
+ h2: { ...DEFAULT_BLOCK, style: "h2" },
57
+ h3: { ...DEFAULT_BLOCK, style: "h3" },
58
+ h4: { ...DEFAULT_BLOCK, style: "h4" },
59
+ h5: { ...DEFAULT_BLOCK, style: "h5" },
60
+ h6: { ...DEFAULT_BLOCK, style: "h6" }
61
+ }, HTML_MISC_TAGS = {
62
+ br: { ...DEFAULT_BLOCK, style: BLOCK_DEFAULT_STYLE }
63
+ }, HTML_DECORATOR_TAGS = {
64
+ b: "strong",
65
+ strong: "strong",
66
+ i: "em",
67
+ em: "em",
68
+ u: "underline",
69
+ s: "strike-through",
70
+ strike: "strike-through",
71
+ del: "strike-through",
72
+ code: "code",
73
+ sup: "sup",
74
+ sub: "sub",
75
+ ins: "ins",
76
+ mark: "mark",
77
+ small: "small"
78
+ }, HTML_LIST_ITEM_TAGS = {
79
+ li: {
80
+ ...DEFAULT_BLOCK,
81
+ style: BLOCK_DEFAULT_STYLE,
82
+ level: 1,
83
+ listItem: "bullet"
84
+ }
85
+ }, ELEMENT_MAP = {
86
+ ...HTML_BLOCK_TAGS,
87
+ ...HTML_SPAN_TAGS,
88
+ ...HTML_LIST_CONTAINER_TAGS,
89
+ ...HTML_LIST_ITEM_TAGS,
90
+ ...HTML_HEADER_TAGS,
91
+ ...HTML_MISC_TAGS
92
+ };
93
+ uniq(
94
+ Object.values(ELEMENT_MAP).filter((tag) => "style" in tag).map((tag) => tag.style)
95
+ );
96
+ uniq(
97
+ Object.values(HTML_DECORATOR_TAGS)
98
+ );
99
+ function blockContentFeatures(blockContentType) {
100
+ if (!blockContentType)
101
+ throw new Error("Parameter 'blockContentType' required");
102
+ const blockType = blockContentType.of.find(findBlockType);
103
+ if (!isBlockSchemaType(blockType))
104
+ throw new Error("'block' type is not defined in this schema (required).");
105
+ const ofType = blockType.fields.find(isBlockChildrenObjectField)?.type?.of;
106
+ if (!ofType)
107
+ throw new Error("No `of` declaration found for blocks `children` field");
108
+ const spanType = ofType.find(
109
+ (member) => member.name === "span"
110
+ );
111
+ if (!spanType)
112
+ throw new Error(
113
+ "No `span` type found in `block` schema type `children` definition"
114
+ );
115
+ const inlineObjectTypes = ofType.filter(
116
+ (inlineType) => inlineType.name !== "span" && isObjectSchemaType(inlineType)
117
+ ), blockObjectTypes = blockContentType.of.filter(
118
+ (memberType) => memberType.name !== blockType.name && isObjectSchemaType(memberType)
119
+ );
120
+ return {
121
+ styles: resolveEnabledStyles(blockType),
122
+ decorators: resolveEnabledDecorators(spanType),
123
+ annotations: resolveEnabledAnnotationTypes(spanType),
124
+ lists: resolveEnabledListItems(blockType),
125
+ types: {
126
+ block: blockContentType,
127
+ span: spanType,
128
+ inlineObjects: inlineObjectTypes,
129
+ blockObjects: blockObjectTypes
130
+ }
131
+ };
132
+ }
133
+ function resolveEnabledStyles(blockType) {
134
+ const styleField = blockType.fields.find(isBlockStyleObjectField);
135
+ if (!styleField)
136
+ throw new Error(
137
+ "A field with name 'style' is not defined in the block type (required)."
138
+ );
139
+ const textStyles = getTitledListValuesFromEnumListOptions(
140
+ styleField.type.options
141
+ );
142
+ if (textStyles.length === 0)
143
+ throw new Error(
144
+ "The style fields need at least one style defined. I.e: {title: 'Normal', value: 'normal'}."
145
+ );
146
+ return textStyles;
147
+ }
148
+ function resolveEnabledAnnotationTypes(spanType) {
149
+ return spanType.annotations.map((annotation) => ({
150
+ title: annotation.title,
151
+ type: annotation,
152
+ value: annotation.name,
153
+ icon: annotation.icon
154
+ }));
155
+ }
156
+ function resolveEnabledDecorators(spanType) {
157
+ return spanType.decorators;
158
+ }
159
+ function resolveEnabledListItems(blockType) {
160
+ const listField = blockType.fields.find(isBlockListObjectField);
161
+ if (!listField)
162
+ throw new Error(
163
+ "A field with name 'list' is not defined in the block type (required)."
164
+ );
165
+ const listItems = getTitledListValuesFromEnumListOptions(
166
+ listField.type.options
167
+ );
168
+ if (!listItems)
169
+ throw new Error("The list field need at least to be an empty array");
170
+ return listItems;
171
+ }
172
+ function getTitledListValuesFromEnumListOptions(options) {
173
+ const list = options ? options.list : void 0;
174
+ return Array.isArray(list) ? list.map(
175
+ (item) => isTitledListValue(item) ? item : { title: item, value: item }
176
+ ) : [];
177
+ }
178
+ const _XPathResult = {
179
+ ANY_TYPE: 0,
180
+ NUMBER_TYPE: 1,
181
+ STRING_TYPE: 2,
182
+ BOOLEAN_TYPE: 3,
183
+ UNORDERED_NODE_ITERATOR_TYPE: 4,
184
+ ORDERED_NODE_ITERATOR_TYPE: 5,
185
+ UNORDERED_NODE_SNAPSHOT_TYPE: 6,
186
+ ORDERED_NODE_SNAPSHOT_TYPE: 7,
187
+ ANY_UNORDERED_NODE_TYPE: 8,
188
+ FIRST_ORDERED_NODE_TYPE: 9
189
+ };
190
+ var preprocessGDocs = (_html, doc, options) => {
191
+ const whitespaceOnPasteMode = options?.unstable_whitespaceOnPasteMode || "preserve";
192
+ let gDocsRootOrSiblingNode = doc.evaluate(
193
+ '//*[@id and contains(@id, "docs-internal-guid")]',
194
+ doc,
195
+ null,
196
+ _XPathResult.ORDERED_NODE_ITERATOR_TYPE,
197
+ null
198
+ ).iterateNext();
199
+ if (gDocsRootOrSiblingNode) {
200
+ const isWrappedRootTag = tagName(gDocsRootOrSiblingNode) === "b";
201
+ switch (isWrappedRootTag || (gDocsRootOrSiblingNode = doc.body), whitespaceOnPasteMode) {
202
+ case "normalize":
203
+ normalizeWhitespace(gDocsRootOrSiblingNode);
204
+ break;
205
+ case "remove":
206
+ removeAllWhitespace(gDocsRootOrSiblingNode);
207
+ break;
208
+ }
209
+ const childNodes = doc.evaluate(
210
+ "//*",
211
+ doc,
212
+ null,
213
+ _XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
214
+ null
215
+ );
216
+ for (let i = childNodes.snapshotLength - 1; i >= 0; i--) {
217
+ const elm = childNodes.snapshotItem(i);
218
+ elm?.setAttribute("data-is-google-docs", "true"), (elm?.parentElement === gDocsRootOrSiblingNode || !isWrappedRootTag && elm.parentElement === doc.body) && (elm?.setAttribute("data-is-root-node", "true"), tagName(elm)), tagName(elm) === "li" && elm.firstChild && tagName(elm?.firstChild) === "img" && elm.removeChild(elm.firstChild);
219
+ }
220
+ return isWrappedRootTag && doc.body.firstElementChild?.replaceWith(
221
+ ...Array.from(gDocsRootOrSiblingNode.childNodes)
222
+ ), doc;
223
+ }
224
+ return doc;
225
+ };
226
+ const unwantedWordDocumentPaths = [
227
+ "/html/text()",
228
+ "/html/head/text()",
229
+ "/html/body/text()",
230
+ "/html/body/ul/text()",
231
+ "/html/body/ol/text()",
232
+ "//comment()",
233
+ "//style",
234
+ "//xml",
235
+ "//script",
236
+ "//meta",
237
+ "//link"
238
+ ];
239
+ var preprocessHTML = (_html, doc) => {
240
+ const bodyTextNodes = doc.evaluate(
241
+ "/html/body/text()",
242
+ doc,
243
+ null,
244
+ _XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
245
+ null
246
+ );
247
+ for (let i = bodyTextNodes.snapshotLength - 1; i >= 0; i--) {
248
+ const node = bodyTextNodes.snapshotItem(i), text = node.textContent || "";
249
+ if (text.replace(/[^\S\n]+$/g, "")) {
250
+ const newNode = doc.createElement("span");
251
+ newNode.appendChild(doc.createTextNode(text)), node.parentNode?.replaceChild(newNode, node);
252
+ } else
253
+ node.parentNode?.removeChild(node);
254
+ }
255
+ const unwantedNodes = doc.evaluate(
256
+ unwantedWordDocumentPaths.join("|"),
257
+ doc,
258
+ null,
259
+ _XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
260
+ null
261
+ );
262
+ for (let i = unwantedNodes.snapshotLength - 1; i >= 0; i--) {
263
+ const unwanted = unwantedNodes.snapshotItem(i);
264
+ unwanted && unwanted.parentNode?.removeChild(unwanted);
265
+ }
266
+ return doc;
267
+ }, preprocessNotion = (html, doc) => {
268
+ const NOTION_REGEX = /<!-- notionvc:.*?-->/g;
269
+ if (html.match(NOTION_REGEX)) {
270
+ const childNodes = doc.evaluate(
271
+ "//*",
272
+ doc,
273
+ null,
274
+ _XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
275
+ null
276
+ );
277
+ for (let i = childNodes.snapshotLength - 1; i >= 0; i--)
278
+ childNodes.snapshotItem(i)?.setAttribute("data-is-notion", "true");
279
+ return doc;
280
+ }
281
+ return doc;
282
+ }, preprocessWhitespace = (_2, doc) => {
283
+ function processNode(node) {
284
+ if (node.nodeType === _XPathResult.BOOLEAN_TYPE && !PRESERVE_WHITESPACE_TAGS.includes(
285
+ node.parentElement?.tagName.toLowerCase() || ""
286
+ ))
287
+ node.textContent = node.textContent?.replace(/\s\s+/g, " ").replace(/[\r\n]+/g, " ") || "";
288
+ else
289
+ for (let i = 0; i < node.childNodes.length; i++)
290
+ processNode(node.childNodes[i]);
291
+ }
292
+ return processNode(doc.body), doc;
293
+ };
294
+ const WORD_HTML_REGEX = /(class="?Mso|style=(?:"|')[^"]*?\bmso-|w:WordDocument|<o:\w+>|<\/font>)/, unwantedPaths = [
295
+ "//o:p",
296
+ "//span[@style='mso-list:Ignore']",
297
+ "//span[@style='mso-list: Ignore']"
298
+ ], mappedPaths = [
299
+ "//p[@class='MsoTocHeading']",
300
+ "//p[@class='MsoTitle']",
301
+ "//p[@class='MsoToaHeading']",
302
+ "//p[@class='MsoSubtitle']",
303
+ "//span[@class='MsoSubtleEmphasis']",
304
+ "//span[@class='MsoIntenseEmphasis']"
305
+ ], elementMap = {
306
+ MsoTocHeading: ["h3"],
307
+ MsoTitle: ["h1"],
308
+ MsoToaHeading: ["h2"],
309
+ MsoSubtitle: ["h5"],
310
+ MsoSubtleEmphasis: ["span", "em"],
311
+ MsoIntenseEmphasis: ["span", "em", "strong"]
312
+ // Remove cruft
313
+ };
314
+ function isWordHtml(html) {
315
+ return WORD_HTML_REGEX.test(html);
316
+ }
317
+ var preprocessWord = (html, doc) => {
318
+ if (!isWordHtml(html))
319
+ return doc;
320
+ const unwantedNodes = doc.evaluate(
321
+ unwantedPaths.join("|"),
322
+ doc,
323
+ (prefix) => prefix === "o" ? "urn:schemas-microsoft-com:office:office" : null,
324
+ _XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
325
+ null
326
+ );
327
+ for (let i = unwantedNodes.snapshotLength - 1; i >= 0; i--) {
328
+ const unwanted = unwantedNodes.snapshotItem(i);
329
+ unwanted?.parentNode && unwanted.parentNode.removeChild(unwanted);
330
+ }
331
+ const mappedElements = doc.evaluate(
332
+ mappedPaths.join("|"),
333
+ doc,
334
+ null,
335
+ _XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
336
+ null
337
+ );
338
+ for (let i = mappedElements.snapshotLength - 1; i >= 0; i--) {
339
+ const mappedElm = mappedElements.snapshotItem(i), tags = elementMap[mappedElm.className], text = doc.createTextNode(mappedElm.textContent || "");
340
+ if (!tags)
341
+ continue;
342
+ const parentElement = doc.createElement(tags[0]);
343
+ let parent = parentElement, child = parentElement;
344
+ tags.slice(1).forEach((tag) => {
345
+ child = doc.createElement(tag), parent.appendChild(child), parent = child;
346
+ }), child.appendChild(text), mappedElm?.parentNode?.replaceChild(parentElement, mappedElm);
347
+ }
348
+ return doc;
349
+ }, preprocessors = [
350
+ preprocessWhitespace,
351
+ preprocessNotion,
352
+ preprocessWord,
353
+ preprocessGDocs,
354
+ preprocessHTML
355
+ ];
356
+ function createRuleOptions(blockContentType) {
357
+ const features = blockContentFeatures(blockContentType), enabledBlockStyles = features.styles.map(
358
+ (item) => item.value || item.title
359
+ ), enabledSpanDecorators = features.decorators.map(
360
+ (item) => item.value || item.title
361
+ ), enabledBlockAnnotations = features.annotations.map(
362
+ (item) => item.value || item.title || ""
363
+ ), enabledListTypes = features.lists.map(
364
+ (item) => item.value || item.title || ""
365
+ );
366
+ return {
367
+ enabledBlockStyles,
368
+ enabledSpanDecorators,
369
+ enabledBlockAnnotations,
370
+ enabledListTypes
371
+ };
372
+ }
373
+ function tagName(el) {
374
+ if (el && "tagName" in el)
375
+ return el.tagName.toLowerCase();
376
+ }
377
+ function preprocess(html, parseHtml, options) {
378
+ const cleanHTML = O(html), doc = parseHtml(normalizeHtmlBeforePreprocess(cleanHTML));
379
+ return preprocessors.forEach((processor) => {
380
+ processor(cleanHTML, doc, options);
381
+ }), doc;
382
+ }
383
+ function normalizeHtmlBeforePreprocess(html) {
384
+ return html.trim();
385
+ }
386
+ function defaultParseHtml() {
387
+ if (resolveJsType(DOMParser) === "undefined")
388
+ throw new Error(
389
+ "The native `DOMParser` global which the `Html` deserializer uses by default is not present in this environment. You must supply the `options.parseHtml` function instead."
390
+ );
391
+ return (html) => new DOMParser().parseFromString(html, "text/html");
392
+ }
393
+ function flattenNestedBlocks(blocks2) {
394
+ let depth = 0;
395
+ const flattened = [], traverse = (nodes) => {
396
+ const toRemove = [];
397
+ nodes.forEach((node) => {
398
+ depth === 0 && flattened.push(node), isPortableTextTextBlock(node) && (depth > 0 && (toRemove.push(node), flattened.push(node)), depth++, traverse(node.children)), node._type === "__block" && (toRemove.push(node), flattened.push(node.block));
399
+ }), toRemove.forEach((node) => {
400
+ nodes.splice(nodes.indexOf(node), 1);
401
+ }), depth--;
402
+ };
403
+ return traverse(blocks2), flattened;
404
+ }
405
+ function nextSpan(block, index) {
406
+ const next = block.children[index + 1];
407
+ return next && next._type === "span" ? next : null;
408
+ }
409
+ function prevSpan(block, index) {
410
+ const prev = block.children[index - 1];
411
+ return prev && prev._type === "span" ? prev : null;
412
+ }
413
+ function isWhiteSpaceChar(text) {
414
+ return ["\xA0", " "].includes(text);
415
+ }
416
+ function trimWhitespace(blocks2) {
417
+ return blocks2.forEach((block) => {
418
+ isPortableTextTextBlock(block) && block.children.forEach((child, index) => {
419
+ if (!isMinimalSpan(child))
420
+ return;
421
+ const nextChild = nextSpan(block, index), prevChild = prevSpan(block, index);
422
+ index === 0 && (child.text = child.text.replace(/^[^\S\n]+/g, "")), index === block.children.length - 1 && (child.text = child.text.replace(/[^\S\n]+$/g, "")), /\s/.test(child.text.slice(Math.max(0, child.text.length - 1))) && nextChild && isMinimalSpan(nextChild) && /\s/.test(nextChild.text.slice(0, 1)) && (child.text = child.text.replace(/[^\S\n]+$/g, "")), /\s/.test(child.text.slice(0, 1)) && prevChild && isMinimalSpan(prevChild) && /\s/.test(prevChild.text.slice(Math.max(0, prevChild.text.length - 1))) && (child.text = child.text.replace(/^[^\S\n]+/g, "")), child.text || block.children.splice(index, 1), prevChild && isEqual(prevChild.marks, child.marks) && isWhiteSpaceChar(child.text) ? (prevChild.text += " ", block.children.splice(index, 1)) : nextChild && isEqual(nextChild.marks, child.marks) && isWhiteSpaceChar(child.text) && (nextChild.text = ` ${nextChild.text}`, block.children.splice(index, 1));
423
+ });
424
+ }), blocks2;
425
+ }
426
+ function ensureRootIsBlocks(blocks2) {
427
+ return blocks2.reduce((memo, node, i, original) => {
428
+ if (node._type === "block")
429
+ return memo.push(node), memo;
430
+ if (node._type === "__block")
431
+ return memo.push(node.block), memo;
432
+ const lastBlock = memo[memo.length - 1];
433
+ if (i > 0 && !isPortableTextTextBlock(original[i - 1]) && isPortableTextTextBlock(lastBlock))
434
+ return lastBlock.children.push(node), memo;
435
+ const block = {
436
+ ...DEFAULT_BLOCK,
437
+ children: [node]
438
+ };
439
+ return memo.push(block), memo;
440
+ }, []);
441
+ }
442
+ function isNodeList(node) {
443
+ return Object.prototype.toString.call(node) === "[object NodeList]";
444
+ }
445
+ function isMinimalSpan(node) {
446
+ return node._type === "span";
447
+ }
448
+ function isMinimalBlock(node) {
449
+ return node._type === "block";
450
+ }
451
+ function isPlaceholderDecorator(node) {
452
+ return node._type === "__decorator";
453
+ }
454
+ function isPlaceholderAnnotation(node) {
455
+ return node._type === "__annotation";
456
+ }
457
+ function isElement(node) {
458
+ return node.nodeType === 1;
459
+ }
460
+ function normalizeWhitespace(rootNode) {
461
+ let emptyBlockCount = 0, lastParent = null;
462
+ const nodesToRemove = [];
463
+ for (let child = rootNode.firstChild; child; child = child.nextSibling) {
464
+ if (!isElement(child)) {
465
+ normalizeWhitespace(child), emptyBlockCount = 0;
466
+ continue;
467
+ }
468
+ const elm = child;
469
+ isWhitespaceBlock(elm) ? (lastParent && elm.parentElement === lastParent ? (emptyBlockCount++, emptyBlockCount > 1 && nodesToRemove.push(elm)) : emptyBlockCount = 1, lastParent = elm.parentElement) : (normalizeWhitespace(child), emptyBlockCount = 0);
470
+ }
471
+ nodesToRemove.forEach((node) => node.parentElement?.removeChild(node));
472
+ }
473
+ function removeAllWhitespace(rootNode) {
474
+ const nodesToRemove = [];
475
+ function collectNodesToRemove(currentNode) {
476
+ if (isElement(currentNode)) {
477
+ const elm = currentNode;
478
+ if (tagName(elm) === "br" && (tagName(elm.nextElementSibling) === "p" || tagName(elm.previousElementSibling) === "p")) {
479
+ nodesToRemove.push(elm);
480
+ return;
481
+ }
482
+ if ((tagName(elm) === "p" || tagName(elm) === "br") && elm?.firstChild?.textContent?.trim() === "") {
483
+ nodesToRemove.push(elm);
484
+ return;
485
+ }
486
+ for (let child = elm.firstChild; child; child = child.nextSibling)
487
+ collectNodesToRemove(child);
488
+ }
489
+ }
490
+ collectNodesToRemove(rootNode), nodesToRemove.forEach((node) => node.parentElement?.removeChild(node));
491
+ }
492
+ function isWhitespaceBlock(elm) {
493
+ return ["p", "br"].includes(tagName(elm) || "") && !elm.textContent?.trim();
494
+ }
495
+ const LIST_CONTAINER_TAGS = Object.keys(HTML_LIST_CONTAINER_TAGS);
496
+ function isEmphasis$1(el) {
497
+ const style = isElement(el) && el.getAttribute("style");
498
+ return /font-style\s*:\s*italic/.test(style || "");
499
+ }
500
+ function isStrong$1(el) {
501
+ const style = isElement(el) && el.getAttribute("style");
502
+ return /font-weight\s*:\s*700/.test(style || "");
503
+ }
504
+ function isUnderline$1(el) {
505
+ if (!isElement(el) || tagName(el.parentNode) === "a")
506
+ return !1;
507
+ const style = isElement(el) && el.getAttribute("style");
508
+ return /text-decoration\s*:\s*underline/.test(style || "");
509
+ }
510
+ function isStrikethrough(el) {
511
+ const style = isElement(el) && el.getAttribute("style");
512
+ return /text-decoration\s*:\s*(?:.*line-through.*;)/.test(style || "");
513
+ }
514
+ function isGoogleDocs(el) {
515
+ return isElement(el) && !!el.getAttribute("data-is-google-docs");
516
+ }
517
+ function isRootNode(el) {
518
+ return isElement(el) && !!el.getAttribute("data-is-root-node");
519
+ }
520
+ function getListItemStyle$1(el) {
521
+ const parentTag = tagName(el.parentNode);
522
+ if (!(parentTag && !LIST_CONTAINER_TAGS.includes(parentTag)))
523
+ return tagName(el.parentNode) === "ul" ? "bullet" : "number";
524
+ }
525
+ function getListItemLevel$1(el) {
526
+ let level = 0;
527
+ if (tagName(el) === "li") {
528
+ let parentNode = el.parentNode;
529
+ for (; parentNode; ) {
530
+ const parentTag = tagName(parentNode);
531
+ parentTag && LIST_CONTAINER_TAGS.includes(parentTag) && level++, parentNode = parentNode.parentNode;
532
+ }
533
+ } else
534
+ level = 1;
535
+ return level;
536
+ }
537
+ const blocks = {
538
+ ...HTML_BLOCK_TAGS,
539
+ ...HTML_HEADER_TAGS
540
+ };
541
+ function getBlockStyle(el, enabledBlockStyles) {
542
+ const childTag = tagName(el.firstChild), block = childTag && blocks[childTag];
543
+ return block && enabledBlockStyles.includes(block.style) ? block.style : BLOCK_DEFAULT_STYLE;
544
+ }
545
+ function createGDocsRules(_blockContentType, options) {
546
+ return [
547
+ {
548
+ deserialize(el) {
549
+ if (isElement(el) && tagName(el) === "span" && isGoogleDocs(el)) {
550
+ const span = {
551
+ ...DEFAULT_SPAN,
552
+ marks: [],
553
+ text: el.textContent
554
+ };
555
+ return isStrong$1(el) && span.marks.push("strong"), isUnderline$1(el) && span.marks.push("underline"), isStrikethrough(el) && span.marks.push("strike-through"), isEmphasis$1(el) && span.marks.push("em"), span;
556
+ }
557
+ }
558
+ },
559
+ {
560
+ deserialize(el, next) {
561
+ if (tagName(el) === "li" && isGoogleDocs(el))
562
+ return {
563
+ ...DEFAULT_BLOCK,
564
+ listItem: getListItemStyle$1(el),
565
+ level: getListItemLevel$1(el),
566
+ style: getBlockStyle(el, options.enabledBlockStyles),
567
+ children: next(el.firstChild?.childNodes || [])
568
+ };
569
+ }
570
+ },
571
+ {
572
+ deserialize(el) {
573
+ if (tagName(el) === "br" && isGoogleDocs(el) && isElement(el) && el.classList.contains("apple-interchange-newline"))
574
+ return {
575
+ ...DEFAULT_SPAN,
576
+ text: ""
577
+ };
578
+ if (tagName(el) === "br" && isGoogleDocs(el) && isElement(el) && el?.parentNode?.textContent === "")
579
+ return {
580
+ ...DEFAULT_SPAN,
581
+ text: ""
582
+ };
583
+ if (tagName(el) === "br" && isGoogleDocs(el) && isElement(el) && isRootNode(el))
584
+ return {
585
+ ...DEFAULT_SPAN,
586
+ text: ""
587
+ };
588
+ }
589
+ }
590
+ ];
591
+ }
592
+ function whatwgRNG(length = 16) {
593
+ const rnds8 = new Uint8Array(length);
594
+ return getRandomValues(rnds8), rnds8;
595
+ }
596
+ const byteToHex = [];
597
+ for (let i = 0; i < 256; ++i)
598
+ byteToHex[i] = (i + 256).toString(16).slice(1);
599
+ function randomKey(length) {
600
+ return whatwgRNG(length).reduce((str, n) => str + byteToHex[n], "").slice(0, length);
601
+ }
602
+ function resolveListItem(listNodeTagName, enabledListTypes) {
603
+ if (listNodeTagName === "ul" && enabledListTypes.includes("bullet"))
604
+ return "bullet";
605
+ if (listNodeTagName === "ol" && enabledListTypes.includes("number"))
606
+ return "number";
607
+ }
608
+ function createHTMLRules(_blockContentType, options) {
609
+ return [
610
+ // Text nodes
611
+ {
612
+ deserialize(el) {
613
+ if (tagName(el) === "pre")
614
+ return;
615
+ const isValidText = (el.nodeType === 3 && (el.textContent || "").replace(/[\r\n]/g, " ").replace(/\s\s+/g, " ") === " " && el.nextSibling && el.nextSibling.nodeType !== 3 && el.previousSibling && el.previousSibling.nodeType !== 3 || el.textContent !== " ") && tagName(el.parentNode) !== "body";
616
+ if (el.nodeName === "#text" && isValidText)
617
+ return {
618
+ ...DEFAULT_SPAN,
619
+ marks: [],
620
+ text: (el.textContent || "").replace(/\s\s+/g, " ")
621
+ };
622
+ }
623
+ },
624
+ // Pre element
625
+ {
626
+ deserialize(el) {
627
+ if (tagName(el) !== "pre")
628
+ return;
629
+ const isCodeEnabled = options.enabledBlockStyles.includes("code");
630
+ return {
631
+ _type: "block",
632
+ style: "normal",
633
+ markDefs: [],
634
+ children: [
635
+ {
636
+ ...DEFAULT_SPAN,
637
+ marks: isCodeEnabled ? ["code"] : [],
638
+ text: el.textContent || ""
639
+ }
640
+ ]
641
+ };
642
+ }
643
+ },
644
+ // Blockquote element
645
+ {
646
+ deserialize(el, next) {
647
+ if (tagName(el) !== "blockquote")
648
+ return;
649
+ const blocks2 = {
650
+ ...HTML_BLOCK_TAGS,
651
+ ...HTML_HEADER_TAGS
652
+ };
653
+ delete blocks2.blockquote;
654
+ const children = [];
655
+ return el.childNodes.forEach((node, index) => {
656
+ if (node.nodeType === 1 && Object.keys(blocks2).includes(
657
+ node.localName.toLowerCase()
658
+ )) {
659
+ if (!el.ownerDocument)
660
+ return;
661
+ const span = el.ownerDocument.createElement("span");
662
+ span.appendChild(el.ownerDocument.createTextNode("\r")), node.childNodes.forEach((cn) => {
663
+ span.appendChild(cn.cloneNode(!0));
664
+ }), index !== el.childNodes.length && span.appendChild(el.ownerDocument.createTextNode("\r")), children.push(span);
665
+ } else
666
+ children.push(node);
667
+ }), {
668
+ _type: "block",
669
+ style: "blockquote",
670
+ markDefs: [],
671
+ children: next(children)
672
+ };
673
+ }
674
+ },
675
+ // Block elements
676
+ {
677
+ deserialize(el, next) {
678
+ const blocks2 = {
679
+ ...HTML_BLOCK_TAGS,
680
+ ...HTML_HEADER_TAGS
681
+ }, tag = tagName(el);
682
+ let block = tag ? blocks2[tag] : void 0;
683
+ if (block)
684
+ return el.parentNode && tagName(el.parentNode) === "li" ? next(el.childNodes) : (options.enabledBlockStyles.includes(block.style) || (block = DEFAULT_BLOCK), {
685
+ ...block,
686
+ children: next(el.childNodes)
687
+ });
688
+ }
689
+ },
690
+ // Ignore span tags
691
+ {
692
+ deserialize(el, next) {
693
+ const tag = tagName(el);
694
+ if (!(!tag || !(tag in HTML_SPAN_TAGS)))
695
+ return next(el.childNodes);
696
+ }
697
+ },
698
+ // Ignore div tags
699
+ {
700
+ deserialize(el, next) {
701
+ if (tagName(el) === "div")
702
+ return next(el.childNodes);
703
+ }
704
+ },
705
+ // Ignore list containers
706
+ {
707
+ deserialize(el, next) {
708
+ const tag = tagName(el);
709
+ if (!(!tag || !(tag in HTML_LIST_CONTAINER_TAGS)))
710
+ return next(el.childNodes);
711
+ }
712
+ },
713
+ // Deal with br's
714
+ {
715
+ deserialize(el) {
716
+ if (tagName(el) === "br")
717
+ return {
718
+ ...DEFAULT_SPAN,
719
+ text: `
720
+ `
721
+ };
722
+ }
723
+ },
724
+ // Deal with list items
725
+ {
726
+ deserialize(el, next, block) {
727
+ const tag = tagName(el), listItem = tag ? HTML_LIST_ITEM_TAGS[tag] : void 0, parentTag = tagName(el.parentNode) || "";
728
+ if (!listItem || !el.parentNode || !HTML_LIST_CONTAINER_TAGS[parentTag])
729
+ return;
730
+ const enabledListItem = resolveListItem(
731
+ parentTag,
732
+ options.enabledListTypes
733
+ );
734
+ return enabledListItem ? (listItem.listItem = enabledListItem, {
735
+ ...listItem,
736
+ children: next(el.childNodes)
737
+ }) : block({ _type: "block", children: next(el.childNodes) });
738
+ }
739
+ },
740
+ // Deal with decorators - this is a limited set of known html elements that we know how to deserialize
741
+ {
742
+ deserialize(el, next) {
743
+ const decorator = HTML_DECORATOR_TAGS[tagName(el) || ""];
744
+ if (!(!decorator || !options.enabledSpanDecorators.includes(decorator)))
745
+ return {
746
+ _type: "__decorator",
747
+ name: decorator,
748
+ children: next(el.childNodes)
749
+ };
750
+ }
751
+ },
752
+ // Special case for hyperlinks, add annotation (if allowed by schema),
753
+ // If not supported just write out the link text and href in plain text.
754
+ {
755
+ deserialize(el, next) {
756
+ if (tagName(el) !== "a")
757
+ return;
758
+ const linkEnabled = options.enabledBlockAnnotations.includes("link"), href = isElement(el) && el.getAttribute("href");
759
+ if (!href)
760
+ return next(el.childNodes);
761
+ let markDef;
762
+ return linkEnabled ? (markDef = {
763
+ _key: randomKey(12),
764
+ _type: "link",
765
+ href
766
+ }, {
767
+ _type: "__annotation",
768
+ markDef,
769
+ children: next(el.childNodes)
770
+ }) : el.appendChild(el.ownerDocument.createTextNode(` (${href})`)) && next(el.childNodes);
771
+ }
772
+ }
773
+ ];
774
+ }
775
+ function isEmphasis(el) {
776
+ const style = isElement(el) && el.getAttribute("style");
777
+ return /font-style:italic/.test(style || "");
778
+ }
779
+ function isStrong(el) {
780
+ const style = isElement(el) && el.getAttribute("style");
781
+ return /font-weight:700/.test(style || "") || /font-weight:600/.test(style || "");
782
+ }
783
+ function isUnderline(el) {
784
+ const style = isElement(el) && el.getAttribute("style");
785
+ return /text-decoration:underline/.test(style || "");
786
+ }
787
+ function isNotion(el) {
788
+ return isElement(el) && !!el.getAttribute("data-is-notion");
789
+ }
790
+ function createNotionRules(_blockContentType) {
791
+ return [
792
+ {
793
+ deserialize(el) {
794
+ if (isElement(el) && tagName(el) === "span" && isNotion(el)) {
795
+ const span = {
796
+ ...DEFAULT_SPAN,
797
+ marks: [],
798
+ text: el.textContent
799
+ };
800
+ return isStrong(el) && span.marks.push("strong"), isUnderline(el) && span.marks.push("underline"), isEmphasis(el) && span.marks.push("em"), span;
801
+ }
802
+ }
803
+ }
804
+ ];
805
+ }
806
+ function getListItemStyle(el) {
807
+ const style = isElement(el) && el.getAttribute("style");
808
+ if (style && style.match(/lfo\d+/))
809
+ return style.match("lfo1") ? "bullet" : "number";
810
+ }
811
+ function getListItemLevel(el) {
812
+ const style = isElement(el) && el.getAttribute("style");
813
+ if (!style)
814
+ return;
815
+ const levelMatch = style.match(/level\d+/);
816
+ if (!levelMatch)
817
+ return;
818
+ const [level] = levelMatch[0].match(/\d/) || [];
819
+ return (level ? Number.parseInt(level, 10) : 1) || 1;
820
+ }
821
+ function isWordListElement(el) {
822
+ return isElement(el) && el.className ? el.className === "MsoListParagraphCxSpFirst" || el.className === "MsoListParagraphCxSpMiddle" || el.className === "MsoListParagraphCxSpLast" : !1;
823
+ }
824
+ function createWordRules() {
825
+ return [
826
+ {
827
+ deserialize(el, next) {
828
+ if (tagName(el) === "p" && isWordListElement(el))
829
+ return {
830
+ ...DEFAULT_BLOCK,
831
+ listItem: getListItemStyle(el),
832
+ level: getListItemLevel(el),
833
+ style: BLOCK_DEFAULT_STYLE,
834
+ children: next(el.childNodes)
835
+ };
836
+ }
837
+ }
838
+ ];
839
+ }
840
+ function createRules(blockContentType, options) {
841
+ return [
842
+ ...createWordRules(),
843
+ ...createNotionRules(),
844
+ ...createGDocsRules(blockContentType, options),
845
+ ...createHTMLRules(blockContentType, options)
846
+ ];
847
+ }
848
+ class HtmlDeserializer {
849
+ blockContentType;
850
+ rules;
851
+ parseHtml;
852
+ _markDefs = [];
853
+ /**
854
+ * Create a new serializer respecting a Sanity block content type's schema
855
+ *
856
+ * @param blockContentType - Schema type for array containing _at least_ a block child type
857
+ * @param options - Options for the deserialization process
858
+ */
859
+ constructor(blockContentType, options = {}) {
860
+ const { rules = [], unstable_whitespaceOnPasteMode = "preserve" } = options;
861
+ if (!blockContentType)
862
+ throw new Error("Parameter 'blockContentType' is required");
863
+ const standardRules = createRules(
864
+ blockContentType,
865
+ createRuleOptions(blockContentType)
866
+ );
867
+ this.rules = [...rules, ...standardRules];
868
+ const parseHtml = options.parseHtml || defaultParseHtml();
869
+ this.blockContentType = blockContentType, this.parseHtml = (html) => preprocess(html, parseHtml, { unstable_whitespaceOnPasteMode }).body;
870
+ }
871
+ /**
872
+ * Deserialize HTML.
873
+ *
874
+ * @param html - The HTML to deserialize, as a string
875
+ * @returns Array of blocks - either portable text blocks or other allowed blocks
876
+ */
877
+ deserialize = (html) => {
878
+ this._markDefs = [];
879
+ const { parseHtml } = this, fragment = parseHtml(html), children = Array.from(fragment.childNodes), blocks2 = trimWhitespace(
880
+ flattenNestedBlocks(
881
+ ensureRootIsBlocks(this.deserializeElements(children))
882
+ )
883
+ );
884
+ this._markDefs.length > 0 && blocks2.filter(
885
+ (block) => block._type === "block"
886
+ ).forEach((block) => {
887
+ block.markDefs = block.markDefs || [], block.markDefs = block.markDefs.concat(
888
+ this._markDefs.filter((def) => flatten(
889
+ block.children.map((child) => child.marks || [])
890
+ ).includes(def._key))
891
+ );
892
+ });
893
+ const type = this.blockContentType.of.find(findBlockType);
894
+ return type ? blocks2.map((block) => (block._type === "block" && (block._type = type.name), block)) : blocks2;
895
+ };
896
+ /**
897
+ * Deserialize an array of DOM elements.
898
+ *
899
+ * @param elements - Array of DOM elements to deserialize
900
+ * @returns
901
+ */
902
+ deserializeElements = (elements = []) => {
903
+ let nodes = [];
904
+ return elements.forEach((element) => {
905
+ nodes = nodes.concat(this.deserializeElement(element));
906
+ }), nodes;
907
+ };
908
+ /**
909
+ * Deserialize a DOM element
910
+ *
911
+ * @param element - Deserialize a DOM element
912
+ * @returns
913
+ */
914
+ deserializeElement = (element) => {
915
+ const next = (elements) => {
916
+ if (isNodeList(elements))
917
+ return this.deserializeElements(Array.from(elements));
918
+ if (Array.isArray(elements))
919
+ return this.deserializeElements(elements);
920
+ if (elements)
921
+ return this.deserializeElement(elements);
922
+ }, block = (props) => ({
923
+ _type: "__block",
924
+ block: props
925
+ });
926
+ let node;
927
+ for (let i = 0; i < this.rules.length; i++) {
928
+ const rule = this.rules[i];
929
+ if (!rule.deserialize)
930
+ continue;
931
+ const ret = rule.deserialize(element, next, block), type = resolveJsType(ret);
932
+ if (type !== "array" && type !== "object" && type !== "null" && type !== "undefined")
933
+ throw new Error(
934
+ `A rule returned an invalid deserialized representation: "${node}".`
935
+ );
936
+ if (ret !== void 0) {
937
+ {
938
+ if (ret === null)
939
+ throw new Error("Deserializer rule returned `null`");
940
+ Array.isArray(ret) ? node = ret : isPlaceholderDecorator(ret) ? node = this.deserializeDecorator(ret) : isPlaceholderAnnotation(ret) ? node = this.deserializeAnnotation(ret) : node = ret;
941
+ }
942
+ if (ret && !Array.isArray(ret) && isMinimalBlock(ret) && "listItem" in ret) {
943
+ let parent = element.parentNode?.parentNode;
944
+ for (; parent && tagName(parent) === "li"; )
945
+ parent = parent.parentNode?.parentNode, ret.level = ret.level ? ret.level + 1 : 1;
946
+ }
947
+ ret && !Array.isArray(ret) && isMinimalBlock(ret) && ret.style === "blockquote" && ret.children.forEach((child, index) => {
948
+ isMinimalSpan(child) && child.text === "\r" && (child.text = `
949
+
950
+ `, (index === 0 || index === ret.children.length - 1) && ret.children.splice(index, 1));
951
+ });
952
+ break;
953
+ }
954
+ }
955
+ return node || next(element.childNodes) || [];
956
+ };
957
+ /**
958
+ * Deserialize a `__decorator` type
959
+ * (an internal made up type to process decorators exclusively)
960
+ *
961
+ * @param decorator -
962
+ * @returns array of ...
963
+ */
964
+ deserializeDecorator = (decorator) => {
965
+ const { name } = decorator, applyDecorator = (node) => {
966
+ if (isPlaceholderDecorator(node))
967
+ return this.deserializeDecorator(node);
968
+ if (isMinimalSpan(node))
969
+ node.marks = node.marks || [], node.text.trim() && node.marks.unshift(name);
970
+ else if ("children" in node && Array.isArray(node.children)) {
971
+ const block = node;
972
+ block.children = block.children.map(applyDecorator);
973
+ }
974
+ return node;
975
+ };
976
+ return decorator.children.reduce((children, node) => {
977
+ const ret = applyDecorator(node);
978
+ return Array.isArray(ret) ? children.concat(ret) : (children.push(ret), children);
979
+ }, []);
980
+ };
981
+ /**
982
+ * Deserialize a `__annotation` object.
983
+ * (an internal made up type to process annotations exclusively)
984
+ *
985
+ * @param annotation -
986
+ * @returns Array of...
987
+ */
988
+ deserializeAnnotation = (annotation) => {
989
+ const { markDef } = annotation;
990
+ this._markDefs.push(markDef);
991
+ const applyAnnotation = (node) => {
992
+ if (isPlaceholderAnnotation(node))
993
+ return this.deserializeAnnotation(node);
994
+ if (isMinimalSpan(node))
995
+ node.marks = node.marks || [], node.text.trim() && node.marks.unshift(markDef._key);
996
+ else if ("children" in node && Array.isArray(node.children)) {
997
+ const block = node;
998
+ block.children = block.children.map(applyAnnotation);
999
+ }
1000
+ return node;
1001
+ };
1002
+ return annotation.children.reduce((children, node) => {
1003
+ const ret = applyAnnotation(node);
1004
+ return Array.isArray(ret) ? children.concat(ret) : (children.push(ret), children);
1005
+ }, []);
1006
+ };
1007
+ }
1008
+ function normalizeBlock(node, options = {}) {
1009
+ if (node._type !== (options.blockTypeName || "block"))
1010
+ return "_key" in node ? node : { ...node, _key: randomKey(12) };
1011
+ const block = {
1012
+ _key: randomKey(12),
1013
+ children: [],
1014
+ markDefs: [],
1015
+ ...node
1016
+ }, lastChild = block.children[block.children.length - 1];
1017
+ if (!lastChild)
1018
+ return block.children = [
1019
+ {
1020
+ _type: "span",
1021
+ _key: `${block._key}0`,
1022
+ text: "",
1023
+ marks: []
1024
+ }
1025
+ ], block;
1026
+ const usedMarkDefs = [], allowedDecorators = options.allowedDecorators && Array.isArray(options.allowedDecorators) ? options.allowedDecorators : !1;
1027
+ return block.children = block.children.reduce(
1028
+ (acc, child) => {
1029
+ const previousChild = acc[acc.length - 1];
1030
+ return previousChild && isPortableTextSpan(child) && isPortableTextSpan(previousChild) && isEqual(previousChild.marks, child.marks) ? (lastChild && lastChild === child && child.text === "" && block.children.length > 1 || (previousChild.text += child.text), acc) : (acc.push(child), acc);
1031
+ },
1032
+ []
1033
+ ).map((child, index) => {
1034
+ if (!child)
1035
+ throw new Error("missing child");
1036
+ return child._key = `${block._key}${index}`, isPortableTextSpan(child) && (child.marks ? allowedDecorators && (child.marks = child.marks.filter((mark) => {
1037
+ const isAllowed = allowedDecorators.includes(mark), isUsed = block.markDefs?.some((def) => def._key === mark);
1038
+ return isAllowed || isUsed;
1039
+ })) : child.marks = [], usedMarkDefs.push(...child.marks)), child;
1040
+ }), block.markDefs = (block.markDefs || []).filter(
1041
+ (markDef) => usedMarkDefs.includes(markDef._key)
1042
+ ), block;
1043
+ }
1044
+ function htmlToBlocks(html, blockContentType, options = {}) {
1045
+ return new HtmlDeserializer(blockContentType, options).deserialize(html).map((block) => normalizeBlock(block));
1046
+ }
1047
+ function getBlockContentFeatures(blockContentType) {
1048
+ return blockContentFeatures(blockContentType);
1049
+ }
1050
+ export {
1051
+ getBlockContentFeatures,
1052
+ htmlToBlocks,
1053
+ normalizeBlock,
1054
+ randomKey
1055
+ };
1056
+ //# sourceMappingURL=index.js.map