@portabletext/block-tools 2.0.7 → 3.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/README.md +1 -31
- package/lib/index.cjs +159 -162
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +24 -39
- package/lib/index.d.ts +24 -39
- package/lib/index.js +159 -162
- package/lib/index.js.map +1 -1
- package/package.json +3 -2
- package/src/HtmlDeserializer/helpers.ts +23 -46
- package/src/HtmlDeserializer/index.ts +15 -27
- package/src/HtmlDeserializer/rules/gdocs.ts +6 -7
- package/src/HtmlDeserializer/rules/html.ts +35 -24
- package/src/HtmlDeserializer/rules/index.ts +7 -7
- package/src/HtmlDeserializer/rules/notion.ts +1 -4
- package/src/index.ts +13 -28
- package/src/types.portable-text.ts +79 -0
- package/src/types.ts +11 -57
- package/src/util/normalizeBlock.ts +21 -7
- package/src/util/{blockContentTypeFeatures.ts → portable-text-schema.ts} +72 -39
package/lib/index.d.ts
CHANGED
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
import { ArraySchemaType
|
|
2
|
-
import { ComponentType } from "react";
|
|
1
|
+
import { ArraySchemaType } from "@sanity/types";
|
|
3
2
|
/**
|
|
4
3
|
* @public
|
|
5
4
|
*/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
5
|
+
type PortableTextBlock = PortableTextTextBlock | PortableTextObject;
|
|
6
|
+
/**
|
|
7
|
+
* @public
|
|
8
|
+
*/
|
|
9
|
+
interface PortableTextTextBlock<TChild = PortableTextSpan | PortableTextObject> {
|
|
10
|
+
_type: string;
|
|
11
|
+
_key: string;
|
|
12
|
+
children: TChild[];
|
|
13
|
+
markDefs?: PortableTextObject[];
|
|
14
|
+
listItem?: string;
|
|
15
|
+
style?: string;
|
|
16
|
+
level?: number;
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
|
-
* @
|
|
19
|
+
* @public
|
|
20
20
|
*/
|
|
21
|
-
interface
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
interface PortableTextSpan {
|
|
22
|
+
_key: string;
|
|
23
|
+
_type: 'span';
|
|
24
|
+
text: string;
|
|
25
|
+
marks?: string[];
|
|
24
26
|
}
|
|
25
27
|
/**
|
|
26
28
|
* @public
|
|
27
29
|
*/
|
|
28
|
-
interface
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
type: ObjectSchemaType;
|
|
33
|
-
icon: ComponentType | undefined;
|
|
30
|
+
interface PortableTextObject {
|
|
31
|
+
_type: string;
|
|
32
|
+
_key: string;
|
|
33
|
+
[other: string]: unknown;
|
|
34
34
|
}
|
|
35
35
|
/**
|
|
36
36
|
* @public
|
|
@@ -62,10 +62,6 @@ interface HtmlDeserializerOptions {
|
|
|
62
62
|
parseHtml?: HtmlParser;
|
|
63
63
|
unstable_whitespaceOnPasteMode?: WhiteSpacePasteMode;
|
|
64
64
|
}
|
|
65
|
-
/**
|
|
66
|
-
* @public
|
|
67
|
-
*/
|
|
68
|
-
|
|
69
65
|
/**
|
|
70
66
|
* @public
|
|
71
67
|
*/
|
|
@@ -75,9 +71,6 @@ interface DeserializerRule {
|
|
|
75
71
|
block: ArbitraryTypedObject;
|
|
76
72
|
}) => TypedObject | TypedObject[] | undefined;
|
|
77
73
|
}
|
|
78
|
-
/**
|
|
79
|
-
* @public
|
|
80
|
-
*/
|
|
81
74
|
/**
|
|
82
75
|
* Block normalization options
|
|
83
76
|
*
|
|
@@ -133,12 +126,4 @@ declare function randomKey(length: number): string;
|
|
|
133
126
|
* @public
|
|
134
127
|
*/
|
|
135
128
|
declare function htmlToBlocks(html: string, blockContentType: ArraySchemaType, options?: HtmlDeserializerOptions): (TypedObject | PortableTextTextBlock)[];
|
|
136
|
-
|
|
137
|
-
* Normalize and extract features of an schema type containing a block type
|
|
138
|
-
*
|
|
139
|
-
* @param blockContentType - Schema type for the block type
|
|
140
|
-
* @returns Returns the featureset of a compiled block content type.
|
|
141
|
-
* @public
|
|
142
|
-
*/
|
|
143
|
-
declare function getBlockContentFeatures(blockContentType: ArraySchemaType): BlockContentFeatures;
|
|
144
|
-
export { type ArbitraryTypedObject, type BlockContentFeatures, type BlockEditorSchemaProps, type BlockNormalizationOptions, type DeserializerRule, type HtmlDeserializerOptions, type HtmlParser, type ResolvedAnnotationType, type TypedObject, getBlockContentFeatures, htmlToBlocks, normalizeBlock, randomKey };
|
|
129
|
+
export { type ArbitraryTypedObject, type BlockNormalizationOptions, type DeserializerRule, type HtmlDeserializerOptions, type HtmlParser, type PortableTextBlock, type PortableTextObject, type PortableTextSpan, type PortableTextTextBlock, type TypedObject, htmlToBlocks, normalizeBlock, randomKey };
|
package/lib/index.js
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import flatten from "lodash/flatten.js";
|
|
2
|
-
import { isBlockSchemaType, isBlockChildrenObjectField, isObjectSchemaType, isBlockListObjectField, isBlockStyleObjectField, isTitledListValue, isPortableTextTextBlock, isPortableTextSpan } from "@sanity/types";
|
|
3
2
|
import isEqual from "lodash/isEqual.js";
|
|
4
3
|
import uniq from "lodash/uniq.js";
|
|
5
4
|
import getRandomValues from "get-random-values-esm";
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
import { isBlockSchemaType, isBlockChildrenObjectField, isBlockListObjectField, isBlockStyleObjectField, isTitledListValue } from "@sanity/types";
|
|
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");
|
|
8
17
|
}
|
|
9
18
|
const objectToString = Object.prototype.toString;
|
|
10
19
|
function resolveJsType(val) {
|
|
@@ -96,85 +105,6 @@ uniq(
|
|
|
96
105
|
uniq(
|
|
97
106
|
Object.values(HTML_DECORATOR_TAGS)
|
|
98
107
|
);
|
|
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
108
|
const _XPathResult = {
|
|
179
109
|
BOOLEAN_TYPE: 3,
|
|
180
110
|
ORDERED_NODE_ITERATOR_TYPE: 5,
|
|
@@ -346,23 +276,6 @@ var preprocessWord = (html, doc) => {
|
|
|
346
276
|
preprocessGDocs,
|
|
347
277
|
preprocessHTML
|
|
348
278
|
];
|
|
349
|
-
function createRuleOptions(blockContentType) {
|
|
350
|
-
const features = blockContentFeatures(blockContentType), enabledBlockStyles = features.styles.map(
|
|
351
|
-
(item) => item.value || item.title
|
|
352
|
-
), enabledSpanDecorators = features.decorators.map(
|
|
353
|
-
(item) => item.value || item.title
|
|
354
|
-
), enabledBlockAnnotations = features.annotations.map(
|
|
355
|
-
(item) => item.value || item.title || ""
|
|
356
|
-
), enabledListTypes = features.lists.map(
|
|
357
|
-
(item) => item.value || item.title || ""
|
|
358
|
-
);
|
|
359
|
-
return {
|
|
360
|
-
enabledBlockStyles,
|
|
361
|
-
enabledSpanDecorators,
|
|
362
|
-
enabledBlockAnnotations,
|
|
363
|
-
enabledListTypes
|
|
364
|
-
};
|
|
365
|
-
}
|
|
366
279
|
function tagName(el) {
|
|
367
280
|
if (el && "tagName" in el)
|
|
368
281
|
return el.tagName.toLowerCase();
|
|
@@ -383,12 +296,12 @@ function defaultParseHtml() {
|
|
|
383
296
|
);
|
|
384
297
|
return (html) => new DOMParser().parseFromString(html, "text/html");
|
|
385
298
|
}
|
|
386
|
-
function flattenNestedBlocks(blocks2) {
|
|
299
|
+
function flattenNestedBlocks(schema, blocks2) {
|
|
387
300
|
let depth = 0;
|
|
388
301
|
const flattened = [], traverse = (nodes) => {
|
|
389
302
|
const toRemove = [];
|
|
390
303
|
nodes.forEach((node) => {
|
|
391
|
-
depth === 0 && flattened.push(node),
|
|
304
|
+
depth === 0 && flattened.push(node), isTextBlock(schema, node) && (depth > 0 && (toRemove.push(node), flattened.push(node)), depth++, traverse(node.children)), node._type === "__block" && (toRemove.push(node), flattened.push(node.block));
|
|
392
305
|
}), toRemove.forEach((node) => {
|
|
393
306
|
nodes.splice(nodes.indexOf(node), 1);
|
|
394
307
|
}), depth--;
|
|
@@ -406,9 +319,9 @@ function prevSpan(block, index) {
|
|
|
406
319
|
function isWhiteSpaceChar(text) {
|
|
407
320
|
return ["\xA0", " "].includes(text);
|
|
408
321
|
}
|
|
409
|
-
function trimWhitespace(blocks2) {
|
|
322
|
+
function trimWhitespace(schema, blocks2) {
|
|
410
323
|
return blocks2.forEach((block) => {
|
|
411
|
-
|
|
324
|
+
isTextBlock(schema, block) && block.children.forEach((child, index) => {
|
|
412
325
|
if (!isMinimalSpan(child))
|
|
413
326
|
return;
|
|
414
327
|
const nextChild = nextSpan(block, index), prevChild = prevSpan(block, index);
|
|
@@ -416,14 +329,14 @@ function trimWhitespace(blocks2) {
|
|
|
416
329
|
});
|
|
417
330
|
}), blocks2;
|
|
418
331
|
}
|
|
419
|
-
function ensureRootIsBlocks(blocks2) {
|
|
332
|
+
function ensureRootIsBlocks(schema, blocks2) {
|
|
420
333
|
return blocks2.reduce((memo, node, i, original) => {
|
|
421
334
|
if (node._type === "block")
|
|
422
335
|
return memo.push(node), memo;
|
|
423
336
|
if (node._type === "__block")
|
|
424
337
|
return memo.push(node.block), memo;
|
|
425
338
|
const lastBlock = memo[memo.length - 1];
|
|
426
|
-
if (i > 0 && !
|
|
339
|
+
if (i > 0 && !isTextBlock(schema, original[i - 1]) && isTextBlock(schema, lastBlock))
|
|
427
340
|
return lastBlock.children.push(node), memo;
|
|
428
341
|
const block = {
|
|
429
342
|
...DEFAULT_BLOCK,
|
|
@@ -531,11 +444,11 @@ const blocks = {
|
|
|
531
444
|
...HTML_BLOCK_TAGS,
|
|
532
445
|
...HTML_HEADER_TAGS
|
|
533
446
|
};
|
|
534
|
-
function getBlockStyle(
|
|
447
|
+
function getBlockStyle(schema, el) {
|
|
535
448
|
const childTag = tagName(el.firstChild), block = childTag && blocks[childTag];
|
|
536
|
-
return block &&
|
|
449
|
+
return block && schema.styles.some((style) => style.name === block.style) ? block.style : BLOCK_DEFAULT_STYLE;
|
|
537
450
|
}
|
|
538
|
-
function createGDocsRules(
|
|
451
|
+
function createGDocsRules(schema) {
|
|
539
452
|
return [
|
|
540
453
|
{
|
|
541
454
|
deserialize(el) {
|
|
@@ -556,7 +469,7 @@ function createGDocsRules(_blockContentType, options) {
|
|
|
556
469
|
...DEFAULT_BLOCK,
|
|
557
470
|
listItem: getListItemStyle$1(el),
|
|
558
471
|
level: getListItemLevel$1(el),
|
|
559
|
-
style: getBlockStyle(
|
|
472
|
+
style: getBlockStyle(schema, el),
|
|
560
473
|
children: next(el.firstChild?.childNodes || [])
|
|
561
474
|
};
|
|
562
475
|
}
|
|
@@ -607,13 +520,13 @@ const whitespaceTextNodeRule = {
|
|
|
607
520
|
function isWhitespaceTextNode(node) {
|
|
608
521
|
return (node.nodeType === 3 && (node.textContent || "").replace(/[\r\n]/g, " ").replace(/\s\s+/g, " ") === " " && node.nextSibling && node.nextSibling.nodeType !== 3 && node.previousSibling && node.previousSibling.nodeType !== 3 || node.textContent !== " ") && tagName(node.parentNode) !== "body";
|
|
609
522
|
}
|
|
610
|
-
function resolveListItem(
|
|
611
|
-
if (listNodeTagName === "ul" &&
|
|
523
|
+
function resolveListItem(schema, listNodeTagName) {
|
|
524
|
+
if (listNodeTagName === "ul" && schema.lists.some((list) => list.name === "bullet"))
|
|
612
525
|
return "bullet";
|
|
613
|
-
if (listNodeTagName === "ol" &&
|
|
526
|
+
if (listNodeTagName === "ol" && schema.lists.some((list) => list.name === "number"))
|
|
614
527
|
return "number";
|
|
615
528
|
}
|
|
616
|
-
function createHTMLRules(
|
|
529
|
+
function createHTMLRules(schema, options) {
|
|
617
530
|
return [
|
|
618
531
|
whitespaceTextNodeRule,
|
|
619
532
|
{
|
|
@@ -621,7 +534,9 @@ function createHTMLRules(_blockContentType, options) {
|
|
|
621
534
|
deserialize(el) {
|
|
622
535
|
if (tagName(el) !== "pre")
|
|
623
536
|
return;
|
|
624
|
-
const isCodeEnabled =
|
|
537
|
+
const isCodeEnabled = schema.styles.some(
|
|
538
|
+
(style) => style.name === "code"
|
|
539
|
+
);
|
|
625
540
|
return {
|
|
626
541
|
_type: "block",
|
|
627
542
|
style: "normal",
|
|
@@ -674,11 +589,15 @@ function createHTMLRules(_blockContentType, options) {
|
|
|
674
589
|
...HTML_HEADER_TAGS
|
|
675
590
|
}, tag = tagName(el);
|
|
676
591
|
let block = tag ? blocks2[tag] : void 0;
|
|
677
|
-
if (block)
|
|
678
|
-
return
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
592
|
+
if (!block)
|
|
593
|
+
return;
|
|
594
|
+
if (el.parentNode && tagName(el.parentNode) === "li")
|
|
595
|
+
return next(el.childNodes);
|
|
596
|
+
const blockStyle = block.style;
|
|
597
|
+
return schema.styles.some((style) => style.name === blockStyle) || (block = DEFAULT_BLOCK), {
|
|
598
|
+
...block,
|
|
599
|
+
children: next(el.childNodes)
|
|
600
|
+
};
|
|
682
601
|
}
|
|
683
602
|
},
|
|
684
603
|
// Ignore span tags
|
|
@@ -721,10 +640,7 @@ function createHTMLRules(_blockContentType, options) {
|
|
|
721
640
|
const tag = tagName(el), listItem = tag ? HTML_LIST_ITEM_TAGS[tag] : void 0, parentTag = tagName(el.parentNode) || "";
|
|
722
641
|
if (!listItem || !el.parentNode || !HTML_LIST_CONTAINER_TAGS[parentTag])
|
|
723
642
|
return;
|
|
724
|
-
const enabledListItem = resolveListItem(
|
|
725
|
-
parentTag,
|
|
726
|
-
options.enabledListTypes
|
|
727
|
-
);
|
|
643
|
+
const enabledListItem = resolveListItem(schema, parentTag);
|
|
728
644
|
return enabledListItem ? (listItem.listItem = enabledListItem, {
|
|
729
645
|
...listItem,
|
|
730
646
|
children: next(el.childNodes)
|
|
@@ -735,7 +651,9 @@ function createHTMLRules(_blockContentType, options) {
|
|
|
735
651
|
{
|
|
736
652
|
deserialize(el, next) {
|
|
737
653
|
const decorator = HTML_DECORATOR_TAGS[tagName(el) || ""];
|
|
738
|
-
if (!(!decorator || !
|
|
654
|
+
if (!(!decorator || !schema.decorators.some(
|
|
655
|
+
(decoratorType) => decoratorType.name === decorator
|
|
656
|
+
)))
|
|
739
657
|
return {
|
|
740
658
|
_type: "__decorator",
|
|
741
659
|
name: decorator,
|
|
@@ -749,19 +667,18 @@ function createHTMLRules(_blockContentType, options) {
|
|
|
749
667
|
deserialize(el, next) {
|
|
750
668
|
if (tagName(el) !== "a")
|
|
751
669
|
return;
|
|
752
|
-
const linkEnabled =
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
return linkEnabled ? (markDef = {
|
|
757
|
-
_key: options.keyGenerator ? options.keyGenerator() : keyGenerator(),
|
|
758
|
-
_type: "link",
|
|
759
|
-
href
|
|
760
|
-
}, {
|
|
670
|
+
const linkEnabled = schema.annotations.some(
|
|
671
|
+
(annotation) => annotation.name === "link"
|
|
672
|
+
), href = isElement(el) && el.getAttribute("href");
|
|
673
|
+
return href ? linkEnabled ? {
|
|
761
674
|
_type: "__annotation",
|
|
762
|
-
markDef
|
|
675
|
+
markDef: {
|
|
676
|
+
_key: options.keyGenerator ? options.keyGenerator() : keyGenerator(),
|
|
677
|
+
_type: "link",
|
|
678
|
+
href
|
|
679
|
+
},
|
|
763
680
|
children: next(el.childNodes)
|
|
764
|
-
}
|
|
681
|
+
} : el.appendChild(el.ownerDocument.createTextNode(` (${href})`)) && next(el.childNodes) : next(el.childNodes);
|
|
765
682
|
}
|
|
766
683
|
}
|
|
767
684
|
];
|
|
@@ -781,7 +698,7 @@ function isUnderline(el) {
|
|
|
781
698
|
function isNotion(el) {
|
|
782
699
|
return isElement(el) && !!el.getAttribute("data-is-notion");
|
|
783
700
|
}
|
|
784
|
-
function createNotionRules(
|
|
701
|
+
function createNotionRules() {
|
|
785
702
|
return [
|
|
786
703
|
{
|
|
787
704
|
deserialize(el) {
|
|
@@ -831,16 +748,16 @@ function createWordRules() {
|
|
|
831
748
|
}
|
|
832
749
|
];
|
|
833
750
|
}
|
|
834
|
-
function createRules(
|
|
751
|
+
function createRules(schema, options) {
|
|
835
752
|
return [
|
|
836
753
|
...createWordRules(),
|
|
837
754
|
...createNotionRules(),
|
|
838
|
-
...createGDocsRules(
|
|
839
|
-
...createHTMLRules(
|
|
755
|
+
...createGDocsRules(schema),
|
|
756
|
+
...createHTMLRules(schema, options)
|
|
840
757
|
];
|
|
841
758
|
}
|
|
842
759
|
class HtmlDeserializer {
|
|
843
|
-
|
|
760
|
+
schema;
|
|
844
761
|
rules;
|
|
845
762
|
parseHtml;
|
|
846
763
|
_markDefs = [];
|
|
@@ -850,17 +767,13 @@ class HtmlDeserializer {
|
|
|
850
767
|
* @param blockContentType - Schema type for array containing _at least_ a block child type
|
|
851
768
|
* @param options - Options for the deserialization process
|
|
852
769
|
*/
|
|
853
|
-
constructor(
|
|
854
|
-
const { rules = [], unstable_whitespaceOnPasteMode = "preserve" } = options
|
|
855
|
-
if (!blockContentType)
|
|
856
|
-
throw new Error("Parameter 'blockContentType' is required");
|
|
857
|
-
const standardRules = createRules(blockContentType, {
|
|
858
|
-
...createRuleOptions(blockContentType),
|
|
770
|
+
constructor(schema, options = {}) {
|
|
771
|
+
const { rules = [], unstable_whitespaceOnPasteMode = "preserve" } = options, standardRules = createRules(schema, {
|
|
859
772
|
keyGenerator: options.keyGenerator
|
|
860
773
|
});
|
|
861
|
-
this.rules = [...rules, ...standardRules];
|
|
774
|
+
this.schema = schema, this.rules = [...rules, ...standardRules];
|
|
862
775
|
const parseHtml = options.parseHtml || defaultParseHtml();
|
|
863
|
-
this.
|
|
776
|
+
this.parseHtml = (html) => preprocess(html, parseHtml, { unstable_whitespaceOnPasteMode }).body;
|
|
864
777
|
}
|
|
865
778
|
/**
|
|
866
779
|
* Deserialize HTML.
|
|
@@ -871,21 +784,19 @@ class HtmlDeserializer {
|
|
|
871
784
|
deserialize = (html) => {
|
|
872
785
|
this._markDefs = [];
|
|
873
786
|
const { parseHtml } = this, fragment = parseHtml(html), children = Array.from(fragment.childNodes), blocks2 = trimWhitespace(
|
|
787
|
+
this.schema,
|
|
874
788
|
flattenNestedBlocks(
|
|
875
|
-
|
|
789
|
+
this.schema,
|
|
790
|
+
ensureRootIsBlocks(this.schema, this.deserializeElements(children))
|
|
876
791
|
)
|
|
877
792
|
);
|
|
878
|
-
this._markDefs.length > 0 && blocks2.filter(
|
|
879
|
-
(block) => block._type === "block"
|
|
880
|
-
).forEach((block) => {
|
|
793
|
+
return this._markDefs.length > 0 && blocks2.filter((block) => isTextBlock(this.schema, block)).forEach((block) => {
|
|
881
794
|
block.markDefs = block.markDefs || [], block.markDefs = block.markDefs.concat(
|
|
882
795
|
this._markDefs.filter((def) => flatten(
|
|
883
796
|
block.children.map((child) => child.marks || [])
|
|
884
797
|
).includes(def._key))
|
|
885
798
|
);
|
|
886
|
-
});
|
|
887
|
-
const type = this.blockContentType.of.find(findBlockType);
|
|
888
|
-
return type ? blocks2.map((block) => (block._type === "block" && (block._type = type.name), block)) : blocks2;
|
|
799
|
+
}), blocks2.map((block) => (block._type === "block" && (block._type = this.schema.block.name), block));
|
|
889
800
|
};
|
|
890
801
|
/**
|
|
891
802
|
* Deserialize an array of DOM elements.
|
|
@@ -999,6 +910,11 @@ class HtmlDeserializer {
|
|
|
999
910
|
};
|
|
1000
911
|
}
|
|
1001
912
|
function normalizeBlock(node, options = {}) {
|
|
913
|
+
const schema = {
|
|
914
|
+
span: {
|
|
915
|
+
name: "span"
|
|
916
|
+
}
|
|
917
|
+
};
|
|
1002
918
|
if (node._type !== (options.blockTypeName || "block"))
|
|
1003
919
|
return "_key" in node ? node : {
|
|
1004
920
|
...node,
|
|
@@ -1023,13 +939,13 @@ function normalizeBlock(node, options = {}) {
|
|
|
1023
939
|
return block.children = block.children.reduce(
|
|
1024
940
|
(acc, child) => {
|
|
1025
941
|
const previousChild = acc[acc.length - 1];
|
|
1026
|
-
return previousChild &&
|
|
942
|
+
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);
|
|
1027
943
|
},
|
|
1028
944
|
[]
|
|
1029
945
|
).map((child) => {
|
|
1030
946
|
if (!child)
|
|
1031
947
|
throw new Error("missing child");
|
|
1032
|
-
return child._key = options.keyGenerator ? options.keyGenerator() : keyGenerator(),
|
|
948
|
+
return child._key = options.keyGenerator ? options.keyGenerator() : keyGenerator(), isSpan(schema, child) && (child.marks ? allowedDecorators && (child.marks = child.marks.filter((mark) => {
|
|
1033
949
|
const isAllowed = allowedDecorators.includes(mark), isUsed = block.markDefs?.some((def) => def._key === mark);
|
|
1034
950
|
return isAllowed || isUsed;
|
|
1035
951
|
})) : child.marks = [], usedMarkDefs.push(...child.marks)), child;
|
|
@@ -1037,14 +953,95 @@ function normalizeBlock(node, options = {}) {
|
|
|
1037
953
|
(markDef) => usedMarkDefs.includes(markDef._key)
|
|
1038
954
|
), block;
|
|
1039
955
|
}
|
|
1040
|
-
function
|
|
1041
|
-
return
|
|
956
|
+
function findBlockType(type) {
|
|
957
|
+
return type.type ? findBlockType(type.type) : type.name === "block";
|
|
1042
958
|
}
|
|
1043
|
-
function
|
|
1044
|
-
|
|
959
|
+
function getPortableTextSchema(blockContentType) {
|
|
960
|
+
if (!blockContentType)
|
|
961
|
+
throw new Error("Parameter 'blockContentType' required");
|
|
962
|
+
const blockType = blockContentType.of.find(findBlockType);
|
|
963
|
+
if (!isBlockSchemaType(blockType))
|
|
964
|
+
throw new Error("'block' type is not defined in this schema (required).");
|
|
965
|
+
const ofType = blockType.fields.find(isBlockChildrenObjectField)?.type?.of;
|
|
966
|
+
if (!ofType)
|
|
967
|
+
throw new Error("No `of` declaration found for blocks `children` field");
|
|
968
|
+
const spanType = ofType.find(
|
|
969
|
+
(member) => member.name === "span"
|
|
970
|
+
);
|
|
971
|
+
if (!spanType)
|
|
972
|
+
throw new Error(
|
|
973
|
+
"No `span` type found in `block` schema type `children` definition"
|
|
974
|
+
);
|
|
975
|
+
const blockName = blockContentType.of.find(findBlockType)?.name;
|
|
976
|
+
if (!blockName)
|
|
977
|
+
throw new Error("No `block` type found in schema type");
|
|
978
|
+
return {
|
|
979
|
+
styles: resolveEnabledStyles(blockType),
|
|
980
|
+
decorators: resolveEnabledDecorators(spanType),
|
|
981
|
+
annotations: resolveEnabledAnnotationTypes(spanType),
|
|
982
|
+
lists: resolveEnabledListItems(blockType),
|
|
983
|
+
block: {
|
|
984
|
+
name: blockName
|
|
985
|
+
},
|
|
986
|
+
span: {
|
|
987
|
+
name: spanType.name
|
|
988
|
+
}
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
function resolveEnabledStyles(blockType) {
|
|
992
|
+
const styleField = blockType.fields.find(isBlockStyleObjectField);
|
|
993
|
+
if (!styleField)
|
|
994
|
+
throw new Error(
|
|
995
|
+
"A field with name 'style' is not defined in the block type (required)."
|
|
996
|
+
);
|
|
997
|
+
const textStyles = getTitledListValuesFromEnumListOptions(
|
|
998
|
+
styleField.type.options
|
|
999
|
+
);
|
|
1000
|
+
if (textStyles.length === 0)
|
|
1001
|
+
throw new Error(
|
|
1002
|
+
"The style fields need at least one style defined. I.e: {title: 'Normal', value: 'normal'}."
|
|
1003
|
+
);
|
|
1004
|
+
return textStyles;
|
|
1005
|
+
}
|
|
1006
|
+
function resolveEnabledAnnotationTypes(spanType) {
|
|
1007
|
+
return spanType.annotations.map((annotation) => ({
|
|
1008
|
+
name: annotation.name,
|
|
1009
|
+
title: annotation.title
|
|
1010
|
+
}));
|
|
1011
|
+
}
|
|
1012
|
+
function resolveEnabledDecorators(spanType) {
|
|
1013
|
+
return spanType.decorators.map((decorator) => ({
|
|
1014
|
+
name: decorator.value,
|
|
1015
|
+
title: decorator.title
|
|
1016
|
+
}));
|
|
1017
|
+
}
|
|
1018
|
+
function resolveEnabledListItems(blockType) {
|
|
1019
|
+
const listField = blockType.fields.find(isBlockListObjectField);
|
|
1020
|
+
if (!listField)
|
|
1021
|
+
throw new Error(
|
|
1022
|
+
"A field with name 'list' is not defined in the block type (required)."
|
|
1023
|
+
);
|
|
1024
|
+
const listItems = getTitledListValuesFromEnumListOptions(
|
|
1025
|
+
listField.type.options
|
|
1026
|
+
);
|
|
1027
|
+
if (!listItems)
|
|
1028
|
+
throw new Error("The list field need at least to be an empty array");
|
|
1029
|
+
return listItems;
|
|
1030
|
+
}
|
|
1031
|
+
function getTitledListValuesFromEnumListOptions(options) {
|
|
1032
|
+
const list = options ? options.list : void 0;
|
|
1033
|
+
return Array.isArray(list) ? list.map((item) => isTitledListValue(item) ? {
|
|
1034
|
+
name: item.value ?? item.title,
|
|
1035
|
+
title: item.title
|
|
1036
|
+
} : {
|
|
1037
|
+
name: item
|
|
1038
|
+
}) : [];
|
|
1039
|
+
}
|
|
1040
|
+
function htmlToBlocks(html, blockContentType, options = {}) {
|
|
1041
|
+
const schema = getPortableTextSchema(blockContentType);
|
|
1042
|
+
return new HtmlDeserializer(schema, options).deserialize(html).map((block) => normalizeBlock(block, { keyGenerator: options.keyGenerator }));
|
|
1045
1043
|
}
|
|
1046
1044
|
export {
|
|
1047
|
-
getBlockContentFeatures,
|
|
1048
1045
|
htmlToBlocks,
|
|
1049
1046
|
normalizeBlock,
|
|
1050
1047
|
randomKey
|