@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/LICENSE +21 -0
- package/README.md +226 -0
- package/lib/index.cjs +1056 -0
- package/lib/index.cjs.map +1 -0
- package/lib/index.d.cts +172 -0
- package/lib/index.d.ts +172 -0
- package/lib/index.js +1056 -0
- package/lib/index.js.map +1 -0
- package/package.json +71 -0
- package/src/HtmlDeserializer/helpers.ts +363 -0
- package/src/HtmlDeserializer/index.ts +313 -0
- package/src/HtmlDeserializer/preprocessors/gdocs.ts +86 -0
- package/src/HtmlDeserializer/preprocessors/html.ts +57 -0
- package/src/HtmlDeserializer/preprocessors/index.ts +13 -0
- package/src/HtmlDeserializer/preprocessors/notion.ts +25 -0
- package/src/HtmlDeserializer/preprocessors/whitespace.ts +31 -0
- package/src/HtmlDeserializer/preprocessors/word.ts +92 -0
- package/src/HtmlDeserializer/preprocessors/xpathResult.ts +13 -0
- package/src/HtmlDeserializer/rules/gdocs.ts +183 -0
- package/src/HtmlDeserializer/rules/html.ts +264 -0
- package/src/HtmlDeserializer/rules/index.ts +18 -0
- package/src/HtmlDeserializer/rules/notion.ts +60 -0
- package/src/HtmlDeserializer/rules/word.ts +59 -0
- package/src/constants.ts +104 -0
- package/src/index.ts +52 -0
- package/src/types.ts +139 -0
- package/src/util/blockContentTypeFeatures.ts +141 -0
- package/src/util/findBlockType.ts +13 -0
- package/src/util/normalizeBlock.ts +142 -0
- package/src/util/randomKey.ts +26 -0
- package/src/util/resolveJsType.ts +44 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type {ArraySchemaType, PortableTextTextBlock} from '@sanity/types'
|
|
2
|
+
import HtmlDeserializer from './HtmlDeserializer'
|
|
3
|
+
import type {
|
|
4
|
+
BlockContentFeatures,
|
|
5
|
+
HtmlDeserializerOptions,
|
|
6
|
+
TypedObject,
|
|
7
|
+
} from './types'
|
|
8
|
+
import blockContentTypeFeatures from './util/blockContentTypeFeatures'
|
|
9
|
+
import {normalizeBlock} from './util/normalizeBlock'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Convert HTML to blocks respecting the block content type's schema
|
|
13
|
+
*
|
|
14
|
+
* @param html - The HTML to convert to blocks
|
|
15
|
+
* @param blockContentType - A compiled version of the schema type for the block content
|
|
16
|
+
* @param options - Options for deserializing HTML to blocks
|
|
17
|
+
* @returns Array of blocks
|
|
18
|
+
* @public
|
|
19
|
+
*/
|
|
20
|
+
export function htmlToBlocks(
|
|
21
|
+
html: string,
|
|
22
|
+
blockContentType: ArraySchemaType,
|
|
23
|
+
options: HtmlDeserializerOptions = {},
|
|
24
|
+
): (TypedObject | PortableTextTextBlock)[] {
|
|
25
|
+
const deserializer = new HtmlDeserializer(blockContentType, options)
|
|
26
|
+
return deserializer.deserialize(html).map((block) => normalizeBlock(block))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Normalize and extract features of an schema type containing a block type
|
|
31
|
+
*
|
|
32
|
+
* @param blockContentType - Schema type for the block type
|
|
33
|
+
* @returns Returns the featureset of a compiled block content type.
|
|
34
|
+
* @public
|
|
35
|
+
*/
|
|
36
|
+
export function getBlockContentFeatures(
|
|
37
|
+
blockContentType: ArraySchemaType,
|
|
38
|
+
): BlockContentFeatures {
|
|
39
|
+
return blockContentTypeFeatures(blockContentType)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export {normalizeBlock}
|
|
43
|
+
export {randomKey} from './util/randomKey'
|
|
44
|
+
export type {BlockContentFeatures, HtmlDeserializerOptions, TypedObject}
|
|
45
|
+
export type {
|
|
46
|
+
ArbitraryTypedObject,
|
|
47
|
+
BlockEditorSchemaProps,
|
|
48
|
+
DeserializerRule,
|
|
49
|
+
HtmlParser,
|
|
50
|
+
ResolvedAnnotationType,
|
|
51
|
+
} from './types'
|
|
52
|
+
export type {BlockNormalizationOptions} from './util/normalizeBlock'
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ArraySchemaType,
|
|
3
|
+
I18nTitledListValue,
|
|
4
|
+
ObjectSchemaType,
|
|
5
|
+
PortableTextObject,
|
|
6
|
+
SpanSchemaType,
|
|
7
|
+
TitledListValue,
|
|
8
|
+
} from '@sanity/types'
|
|
9
|
+
import type {ComponentType} from 'react'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @public
|
|
13
|
+
*/
|
|
14
|
+
export interface BlockContentFeatures {
|
|
15
|
+
styles: TitledListValue<string>[]
|
|
16
|
+
decorators: TitledListValue<string>[]
|
|
17
|
+
annotations: ResolvedAnnotationType[]
|
|
18
|
+
lists: I18nTitledListValue<string>[]
|
|
19
|
+
types: {
|
|
20
|
+
block: ArraySchemaType
|
|
21
|
+
span: SpanSchemaType
|
|
22
|
+
inlineObjects: ObjectSchemaType[]
|
|
23
|
+
blockObjects: ObjectSchemaType[]
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @beta
|
|
29
|
+
*/
|
|
30
|
+
export interface BlockEditorSchemaProps {
|
|
31
|
+
icon?: string | ComponentType
|
|
32
|
+
render?: ComponentType
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @public
|
|
37
|
+
*/
|
|
38
|
+
export interface ResolvedAnnotationType {
|
|
39
|
+
blockEditor?: BlockEditorSchemaProps
|
|
40
|
+
title: string | undefined
|
|
41
|
+
value: string
|
|
42
|
+
type: ObjectSchemaType
|
|
43
|
+
icon: ComponentType | undefined
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @public
|
|
48
|
+
*/
|
|
49
|
+
export interface TypedObject {
|
|
50
|
+
_type: string
|
|
51
|
+
_key?: string
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @public
|
|
56
|
+
*/
|
|
57
|
+
export interface ArbitraryTypedObject extends TypedObject {
|
|
58
|
+
[key: string]: unknown
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface MinimalSpan {
|
|
62
|
+
_type: 'span'
|
|
63
|
+
_key?: string
|
|
64
|
+
text: string
|
|
65
|
+
marks?: string[]
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface MinimalBlock extends TypedObject {
|
|
69
|
+
_type: 'block'
|
|
70
|
+
children: TypedObject[]
|
|
71
|
+
markDefs?: TypedObject[]
|
|
72
|
+
style?: string
|
|
73
|
+
level?: number
|
|
74
|
+
listItem?: string
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface PlaceholderDecorator {
|
|
78
|
+
_type: '__decorator'
|
|
79
|
+
name: string
|
|
80
|
+
children: TypedObject[]
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface PlaceholderAnnotation {
|
|
84
|
+
_type: '__annotation'
|
|
85
|
+
markDef: PortableTextObject
|
|
86
|
+
children: TypedObject[]
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @public
|
|
91
|
+
*/
|
|
92
|
+
export type HtmlParser = (html: string) => Document
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @public
|
|
96
|
+
*/
|
|
97
|
+
export type WhiteSpacePasteMode = 'preserve' | 'remove' | 'normalize'
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @public
|
|
101
|
+
*/
|
|
102
|
+
export interface HtmlDeserializerOptions {
|
|
103
|
+
rules?: DeserializerRule[]
|
|
104
|
+
parseHtml?: HtmlParser
|
|
105
|
+
unstable_whitespaceOnPasteMode?: WhiteSpacePasteMode
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* @public
|
|
110
|
+
*/
|
|
111
|
+
export interface HtmlPreprocessorOptions {
|
|
112
|
+
unstable_whitespaceOnPasteMode?: WhiteSpacePasteMode
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @public
|
|
117
|
+
*/
|
|
118
|
+
export interface DeserializerRule {
|
|
119
|
+
deserialize: (
|
|
120
|
+
el: Node,
|
|
121
|
+
next: (
|
|
122
|
+
elements: Node | Node[] | NodeList,
|
|
123
|
+
) => TypedObject | TypedObject[] | undefined,
|
|
124
|
+
createBlock: (props: ArbitraryTypedObject) => {
|
|
125
|
+
_type: string
|
|
126
|
+
block: ArbitraryTypedObject
|
|
127
|
+
},
|
|
128
|
+
) => TypedObject | TypedObject[] | undefined
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @public
|
|
133
|
+
*/
|
|
134
|
+
export interface BlockEnabledFeatures {
|
|
135
|
+
enabledBlockStyles: string[]
|
|
136
|
+
enabledSpanDecorators: string[]
|
|
137
|
+
enabledListTypes: string[]
|
|
138
|
+
enabledBlockAnnotations: string[]
|
|
139
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isBlockChildrenObjectField,
|
|
3
|
+
isBlockListObjectField,
|
|
4
|
+
isBlockSchemaType,
|
|
5
|
+
isBlockStyleObjectField,
|
|
6
|
+
isObjectSchemaType,
|
|
7
|
+
isTitledListValue,
|
|
8
|
+
type ArraySchemaType,
|
|
9
|
+
type BlockSchemaType,
|
|
10
|
+
type EnumListProps,
|
|
11
|
+
type I18nTitledListValue,
|
|
12
|
+
type ObjectSchemaType,
|
|
13
|
+
type SpanSchemaType,
|
|
14
|
+
type TitledListValue,
|
|
15
|
+
} from '@sanity/types'
|
|
16
|
+
import type {BlockContentFeatures, ResolvedAnnotationType} from '../types'
|
|
17
|
+
import {findBlockType} from './findBlockType'
|
|
18
|
+
|
|
19
|
+
// Helper method for describing a blockContentType's feature set
|
|
20
|
+
export default function blockContentFeatures(
|
|
21
|
+
blockContentType: ArraySchemaType,
|
|
22
|
+
): BlockContentFeatures {
|
|
23
|
+
if (!blockContentType) {
|
|
24
|
+
throw new Error("Parameter 'blockContentType' required")
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const blockType = blockContentType.of.find(findBlockType)
|
|
28
|
+
if (!isBlockSchemaType(blockType)) {
|
|
29
|
+
throw new Error("'block' type is not defined in this schema (required).")
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const ofType = blockType.fields.find(isBlockChildrenObjectField)?.type?.of
|
|
33
|
+
if (!ofType) {
|
|
34
|
+
throw new Error('No `of` declaration found for blocks `children` field')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const spanType = ofType.find(
|
|
38
|
+
(member): member is SpanSchemaType => member.name === 'span',
|
|
39
|
+
)
|
|
40
|
+
if (!spanType) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
'No `span` type found in `block` schema type `children` definition',
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const inlineObjectTypes = ofType.filter(
|
|
47
|
+
(inlineType): inlineType is ObjectSchemaType =>
|
|
48
|
+
inlineType.name !== 'span' && isObjectSchemaType(inlineType),
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
const blockObjectTypes = blockContentType.of.filter(
|
|
52
|
+
(memberType): memberType is ObjectSchemaType =>
|
|
53
|
+
memberType.name !== blockType.name && isObjectSchemaType(memberType),
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
styles: resolveEnabledStyles(blockType),
|
|
58
|
+
decorators: resolveEnabledDecorators(spanType),
|
|
59
|
+
annotations: resolveEnabledAnnotationTypes(spanType),
|
|
60
|
+
lists: resolveEnabledListItems(blockType),
|
|
61
|
+
types: {
|
|
62
|
+
block: blockContentType,
|
|
63
|
+
span: spanType,
|
|
64
|
+
inlineObjects: inlineObjectTypes,
|
|
65
|
+
blockObjects: blockObjectTypes,
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function resolveEnabledStyles(
|
|
71
|
+
blockType: BlockSchemaType,
|
|
72
|
+
): TitledListValue<string>[] {
|
|
73
|
+
const styleField = blockType.fields.find(isBlockStyleObjectField)
|
|
74
|
+
if (!styleField) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
"A field with name 'style' is not defined in the block type (required).",
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const textStyles = getTitledListValuesFromEnumListOptions(
|
|
81
|
+
styleField.type.options,
|
|
82
|
+
)
|
|
83
|
+
if (textStyles.length === 0) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
'The style fields need at least one style ' +
|
|
86
|
+
"defined. I.e: {title: 'Normal', value: 'normal'}.",
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return textStyles
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function resolveEnabledAnnotationTypes(
|
|
94
|
+
spanType: SpanSchemaType,
|
|
95
|
+
): ResolvedAnnotationType[] {
|
|
96
|
+
return spanType.annotations.map((annotation) => ({
|
|
97
|
+
title: annotation.title,
|
|
98
|
+
type: annotation,
|
|
99
|
+
value: annotation.name,
|
|
100
|
+
icon: annotation.icon,
|
|
101
|
+
}))
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function resolveEnabledDecorators(
|
|
105
|
+
spanType: SpanSchemaType,
|
|
106
|
+
): TitledListValue<string>[] {
|
|
107
|
+
return spanType.decorators
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function resolveEnabledListItems(
|
|
111
|
+
blockType: BlockSchemaType,
|
|
112
|
+
): I18nTitledListValue<string>[] {
|
|
113
|
+
const listField = blockType.fields.find(isBlockListObjectField)
|
|
114
|
+
if (!listField) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
"A field with name 'list' is not defined in the block type (required).",
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const listItems = getTitledListValuesFromEnumListOptions(
|
|
121
|
+
listField.type.options,
|
|
122
|
+
)
|
|
123
|
+
if (!listItems) {
|
|
124
|
+
throw new Error('The list field need at least to be an empty array')
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return listItems
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function getTitledListValuesFromEnumListOptions(
|
|
131
|
+
options: EnumListProps<string> | undefined,
|
|
132
|
+
): I18nTitledListValue<string>[] {
|
|
133
|
+
const list = options ? options.list : undefined
|
|
134
|
+
if (!Array.isArray(list)) {
|
|
135
|
+
return []
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return list.map((item) =>
|
|
139
|
+
isTitledListValue(item) ? item : {title: item, value: item},
|
|
140
|
+
)
|
|
141
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type {BlockSchemaType, SchemaType} from '@sanity/types'
|
|
2
|
+
|
|
3
|
+
export function findBlockType(type: SchemaType): type is BlockSchemaType {
|
|
4
|
+
if (type.type) {
|
|
5
|
+
return findBlockType(type.type)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
if (type.name === 'block') {
|
|
9
|
+
return true
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return false
|
|
13
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isPortableTextSpan,
|
|
3
|
+
type PortableTextSpan,
|
|
4
|
+
type PortableTextTextBlock,
|
|
5
|
+
} from '@sanity/types'
|
|
6
|
+
import {isEqual} from 'lodash'
|
|
7
|
+
import type {TypedObject} from '../types'
|
|
8
|
+
import {randomKey} from './randomKey'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Block normalization options
|
|
12
|
+
*
|
|
13
|
+
* @public
|
|
14
|
+
*/
|
|
15
|
+
export interface BlockNormalizationOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Decorator names that are allowed within portable text blocks, eg `em`, `strong`
|
|
18
|
+
*/
|
|
19
|
+
allowedDecorators?: string[]
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Name of the portable text block type, if not `block`
|
|
23
|
+
*/
|
|
24
|
+
blockTypeName?: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Normalizes a block by ensuring it has a `_key` property. If the block is a
|
|
29
|
+
* portable text block, additional normalization is applied:
|
|
30
|
+
*
|
|
31
|
+
* - Ensures it has `children` and `markDefs` properties
|
|
32
|
+
* - Ensures it has at least one child (adds an empty span if empty)
|
|
33
|
+
* - Joins sibling spans that has the same marks
|
|
34
|
+
* - Removes decorators that are not allowed according to the schema
|
|
35
|
+
* - Removes marks that have no annotation definition
|
|
36
|
+
*
|
|
37
|
+
* @param node - The block to normalize
|
|
38
|
+
* @param options - Options for normalization process. See {@link BlockNormalizationOptions}
|
|
39
|
+
* @returns Normalized block
|
|
40
|
+
* @public
|
|
41
|
+
*/
|
|
42
|
+
export function normalizeBlock(
|
|
43
|
+
node: TypedObject,
|
|
44
|
+
options: BlockNormalizationOptions = {},
|
|
45
|
+
): Omit<
|
|
46
|
+
TypedObject | PortableTextTextBlock<TypedObject | PortableTextSpan>,
|
|
47
|
+
'_key'
|
|
48
|
+
> & {
|
|
49
|
+
_key: string
|
|
50
|
+
} {
|
|
51
|
+
if (node._type !== (options.blockTypeName || 'block')) {
|
|
52
|
+
return '_key' in node
|
|
53
|
+
? (node as TypedObject & {_key: string})
|
|
54
|
+
: {...node, _key: randomKey(12)}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const block: Omit<
|
|
58
|
+
PortableTextTextBlock<TypedObject | PortableTextSpan>,
|
|
59
|
+
'style'
|
|
60
|
+
> = {
|
|
61
|
+
_key: randomKey(12),
|
|
62
|
+
children: [],
|
|
63
|
+
markDefs: [],
|
|
64
|
+
...node,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const lastChild = block.children[block.children.length - 1]
|
|
68
|
+
if (!lastChild) {
|
|
69
|
+
// A block must at least have an empty span type child
|
|
70
|
+
block.children = [
|
|
71
|
+
{
|
|
72
|
+
_type: 'span',
|
|
73
|
+
_key: `${block._key}${0}`,
|
|
74
|
+
text: '',
|
|
75
|
+
marks: [],
|
|
76
|
+
},
|
|
77
|
+
]
|
|
78
|
+
return block
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const usedMarkDefs: string[] = []
|
|
82
|
+
const allowedDecorators =
|
|
83
|
+
options.allowedDecorators && Array.isArray(options.allowedDecorators)
|
|
84
|
+
? options.allowedDecorators
|
|
85
|
+
: false
|
|
86
|
+
|
|
87
|
+
block.children = block.children
|
|
88
|
+
.reduce(
|
|
89
|
+
(acc, child) => {
|
|
90
|
+
const previousChild = acc[acc.length - 1]
|
|
91
|
+
if (
|
|
92
|
+
previousChild &&
|
|
93
|
+
isPortableTextSpan(child) &&
|
|
94
|
+
isPortableTextSpan(previousChild) &&
|
|
95
|
+
isEqual(previousChild.marks, child.marks)
|
|
96
|
+
) {
|
|
97
|
+
if (
|
|
98
|
+
lastChild &&
|
|
99
|
+
lastChild === child &&
|
|
100
|
+
child.text === '' &&
|
|
101
|
+
block.children.length > 1
|
|
102
|
+
) {
|
|
103
|
+
return acc
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
previousChild.text += child.text
|
|
107
|
+
return acc
|
|
108
|
+
}
|
|
109
|
+
acc.push(child)
|
|
110
|
+
return acc
|
|
111
|
+
},
|
|
112
|
+
[] as (TypedObject | PortableTextSpan)[],
|
|
113
|
+
)
|
|
114
|
+
.map((child, index) => {
|
|
115
|
+
if (!child) {
|
|
116
|
+
throw new Error('missing child')
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
child._key = `${block._key}${index}`
|
|
120
|
+
if (isPortableTextSpan(child)) {
|
|
121
|
+
if (!child.marks) {
|
|
122
|
+
child.marks = []
|
|
123
|
+
} else if (allowedDecorators) {
|
|
124
|
+
child.marks = child.marks.filter((mark) => {
|
|
125
|
+
const isAllowed = allowedDecorators.includes(mark)
|
|
126
|
+
const isUsed = block.markDefs?.some((def) => def._key === mark)
|
|
127
|
+
return isAllowed || isUsed
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
usedMarkDefs.push(...child.marks)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return child
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
// Remove leftover (unused) markDefs
|
|
138
|
+
block.markDefs = (block.markDefs || []).filter((markDef) =>
|
|
139
|
+
usedMarkDefs.includes(markDef._key),
|
|
140
|
+
)
|
|
141
|
+
return block
|
|
142
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import getRandomValues from 'get-random-values-esm'
|
|
2
|
+
|
|
3
|
+
// WHATWG crypto RNG - https://w3c.github.io/webcrypto/Overview.html
|
|
4
|
+
function whatwgRNG(length = 16) {
|
|
5
|
+
const rnds8 = new Uint8Array(length)
|
|
6
|
+
getRandomValues(rnds8)
|
|
7
|
+
return rnds8
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const byteToHex: string[] = []
|
|
11
|
+
for (let i = 0; i < 256; ++i) {
|
|
12
|
+
byteToHex[i] = (i + 0x100).toString(16).slice(1)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Generate a random key of the given length
|
|
17
|
+
*
|
|
18
|
+
* @param length - Length of string to generate
|
|
19
|
+
* @returns A string of the given length
|
|
20
|
+
* @public
|
|
21
|
+
*/
|
|
22
|
+
export function randomKey(length: number): string {
|
|
23
|
+
return whatwgRNG(length)
|
|
24
|
+
.reduce((str, n) => str + byteToHex[n], '')
|
|
25
|
+
.slice(0, length)
|
|
26
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const objectToString = Object.prototype.toString
|
|
2
|
+
|
|
3
|
+
// Copied from https://github.com/ForbesLindesay/type-of
|
|
4
|
+
// but inlined to have fine grained control
|
|
5
|
+
export function resolveJsType(val: unknown) {
|
|
6
|
+
switch (objectToString.call(val)) {
|
|
7
|
+
case '[object Function]':
|
|
8
|
+
return 'function'
|
|
9
|
+
case '[object Date]':
|
|
10
|
+
return 'date'
|
|
11
|
+
case '[object RegExp]':
|
|
12
|
+
return 'regexp'
|
|
13
|
+
case '[object Arguments]':
|
|
14
|
+
return 'arguments'
|
|
15
|
+
case '[object Array]':
|
|
16
|
+
return 'array'
|
|
17
|
+
case '[object String]':
|
|
18
|
+
return 'string'
|
|
19
|
+
default:
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (val === null) {
|
|
23
|
+
return 'null'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (val === undefined) {
|
|
27
|
+
return 'undefined'
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (
|
|
31
|
+
val &&
|
|
32
|
+
typeof val === 'object' &&
|
|
33
|
+
'nodeType' in val &&
|
|
34
|
+
(val as {nodeType: unknown}).nodeType === 1
|
|
35
|
+
) {
|
|
36
|
+
return 'element'
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (val === Object(val)) {
|
|
40
|
+
return 'object'
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return typeof val
|
|
44
|
+
}
|