@portabletext/editor
Advanced tools
Comparing version 1.33.5 to 1.33.6
@@ -450,3 +450,3 @@ import { isSelectionCollapsed, getCaretWordSelection, isSelectionExpanded, getFocusBlockObject, getNextBlock, getPreviousBlock, getFocusTextBlock, isAtTheEndOfBlock, getFocusSpan, isAtTheStartOfBlock, getFocusListBlock, getSelectedBlocks, createGuards } from "./selector.is-at-the-start-of-block.js"; | ||
}) => [raise({ | ||
type: "text block.unset", | ||
type: "block.unset", | ||
props: ["listItem", "level"], | ||
@@ -476,4 +476,6 @@ at: focusTextBlock.path | ||
}) => [raise({ | ||
type: "text block.set", | ||
level, | ||
type: "block.set", | ||
props: { | ||
level | ||
}, | ||
at: focusTextBlock.path | ||
@@ -498,3 +500,3 @@ })]] | ||
}) => [raise({ | ||
type: "text block.unset", | ||
type: "block.unset", | ||
props: ["listItem", "level"], | ||
@@ -524,4 +526,6 @@ at: focusListBlock.path | ||
}) => selectedListBlocks.map((selectedListBlock) => raise({ | ||
type: "text block.set", | ||
level: Math.min(MAX_LIST_LEVEL, Math.max(1, selectedListBlock.node.level + 1)), | ||
type: "block.set", | ||
props: { | ||
level: Math.min(MAX_LIST_LEVEL, Math.max(1, selectedListBlock.node.level + 1)) | ||
}, | ||
at: selectedListBlock.path | ||
@@ -550,4 +554,6 @@ }))] | ||
}) => selectedListBlocks.map((selectedListBlock) => raise({ | ||
type: "text block.set", | ||
level: Math.min(MAX_LIST_LEVEL, Math.max(1, selectedListBlock.node.level - 1)), | ||
type: "block.set", | ||
props: { | ||
level: Math.min(MAX_LIST_LEVEL, Math.max(1, selectedListBlock.node.level - 1)) | ||
}, | ||
at: selectedListBlock.path | ||
@@ -554,0 +560,0 @@ }))] |
@@ -52,8 +52,10 @@ import { isPortableTextTextBlock } from "@sanity/types"; | ||
}) => [{ | ||
type: "text block.unset", | ||
type: "block.unset", | ||
props: ["listItem", "level"], | ||
at: focusTextBlock.path | ||
}, { | ||
type: "text block.set", | ||
style, | ||
type: "block.set", | ||
props: { | ||
style | ||
}, | ||
at: focusTextBlock.path | ||
@@ -217,8 +219,10 @@ }, { | ||
}) => [{ | ||
type: "text block.unset", | ||
type: "block.unset", | ||
props: ["listItem", "level"], | ||
at: focusTextBlock.path | ||
}, { | ||
type: "text block.set", | ||
style, | ||
type: "block.set", | ||
props: { | ||
style | ||
}, | ||
at: focusTextBlock.path | ||
@@ -260,4 +264,6 @@ }, { | ||
}) => [{ | ||
type: "text block.set", | ||
style: defaultStyle, | ||
type: "block.set", | ||
props: { | ||
style: defaultStyle | ||
}, | ||
at: focusTextBlock.path | ||
@@ -321,6 +327,8 @@ }]] | ||
}) => [{ | ||
type: "text block.set", | ||
listItem, | ||
level: 1, | ||
style, | ||
type: "block.set", | ||
props: { | ||
listItem, | ||
level: 1, | ||
style | ||
}, | ||
at: focusTextBlock.path | ||
@@ -327,0 +335,0 @@ }, { |
@@ -1,2 +0,1 @@ | ||
import { isPortableTextTextBlock, isPortableTextSpan } from "@sanity/types"; | ||
import { blockOffsetToSpanSelectionPoint } from "./util.slice-blocks.js"; | ||
@@ -14,52 +13,70 @@ function isTypedObject(object) { | ||
}) { | ||
if (!isTypedObject(block) || block._type !== context.schema.block.name && !context.schema.blockObjects.some((blockObject) => blockObject.name === block._type)) | ||
return; | ||
if (block._type !== context.schema.block.name) { | ||
const _key = options.refreshKeys ? context.keyGenerator() : typeof block._key == "string" ? block._key : context.keyGenerator(); | ||
return parseTextBlock({ | ||
block, | ||
context, | ||
options | ||
}) ?? parseBlockObject({ | ||
blockObject: block, | ||
context, | ||
options | ||
}); | ||
} | ||
function parseBlockObject({ | ||
blockObject, | ||
context, | ||
options | ||
}) { | ||
if (isTypedObject(blockObject) && !(blockObject._type === context.schema.block.name || blockObject._type === "block" || !context.schema.blockObjects.some(({ | ||
name | ||
}) => name === blockObject._type))) | ||
return { | ||
...block, | ||
_key | ||
...blockObject, | ||
_key: options.refreshKeys ? context.keyGenerator() : typeof blockObject._key == "string" ? blockObject._key : context.keyGenerator() | ||
}; | ||
} | ||
if (!isPortableTextTextBlock(block)) | ||
return { | ||
_type: context.schema.block.name, | ||
_key: options.refreshKeys ? context.keyGenerator() : typeof block._key == "string" ? block._key : context.keyGenerator(), | ||
children: [{ | ||
_key: context.keyGenerator(), | ||
_type: context.schema.span.name, | ||
text: "", | ||
marks: [] | ||
}], | ||
markDefs: [], | ||
style: context.schema.styles[0].value | ||
}; | ||
const markDefKeyMap = /* @__PURE__ */ new Map(), markDefs = (block.markDefs ?? []).flatMap((markDef) => { | ||
} | ||
function isTextBlock(schema, block) { | ||
return parseTextBlock({ | ||
block, | ||
context: { | ||
schema, | ||
keyGenerator: () => "" | ||
}, | ||
options: { | ||
refreshKeys: !1 | ||
} | ||
}) !== void 0; | ||
} | ||
function parseTextBlock({ | ||
block, | ||
context, | ||
options | ||
}) { | ||
if (!isTypedObject(block) || block._type !== context.schema.block.name) | ||
return; | ||
const _key = options.refreshKeys ? context.keyGenerator() : typeof block._key == "string" ? block._key : context.keyGenerator(), unparsedMarkDefs = Array.isArray(block.markDefs) ? block.markDefs : [], markDefKeyMap = /* @__PURE__ */ new Map(), markDefs = unparsedMarkDefs.flatMap((markDef) => { | ||
if (!isTypedObject(markDef)) | ||
return []; | ||
if (typeof markDef._key != "string") | ||
return []; | ||
if (context.schema.annotations.some((annotation) => annotation.name === markDef._type)) { | ||
const _key = options.refreshKeys ? context.keyGenerator() : markDef._key; | ||
return markDefKeyMap.set(markDef._key, _key), [{ | ||
const _key2 = options.refreshKeys ? context.keyGenerator() : markDef._key; | ||
return markDefKeyMap.set(markDef._key, _key2), [{ | ||
...markDef, | ||
_key | ||
_key: _key2 | ||
}]; | ||
} | ||
return []; | ||
}), children = block.children.flatMap((child) => { | ||
if (!isTypedObject(child)) | ||
return []; | ||
if (child._type !== context.schema.span.name && !context.schema.inlineObjects.some((inlineObject) => inlineObject.name === child._type)) | ||
return []; | ||
if (!isPortableTextSpan(child)) | ||
return [{ | ||
...child, | ||
_key: options.refreshKeys ? context.keyGenerator() : child._key | ||
}]; | ||
const marks = (child.marks ?? []).flatMap((mark) => markDefKeyMap.has(mark) ? [markDefKeyMap.get(mark)] : context.schema.decorators.some((decorator) => decorator.value === mark) ? [mark] : []); | ||
return [{ | ||
...child, | ||
_key: options.refreshKeys ? context.keyGenerator() : child._key, | ||
marks | ||
}]; | ||
}), parsedBlock = { | ||
}), children = (Array.isArray(block.children) ? block.children : []).map((child) => parseSpan({ | ||
span: child, | ||
context, | ||
markDefKeyMap, | ||
options | ||
}) ?? parseInlineObject({ | ||
inlineObject: child, | ||
context, | ||
options | ||
})).filter((child) => child !== void 0), parsedBlock = { | ||
// Spread the entire block to allow custom properties on it | ||
...block, | ||
_key: options.refreshKeys ? context.keyGenerator() : block._key, | ||
_key, | ||
children: children.length > 0 ? children : [{ | ||
@@ -73,8 +90,46 @@ _key: context.keyGenerator(), | ||
}; | ||
if (!context.schema.styles.find((style) => style.value === block.style)) { | ||
const defaultStyle = context.schema.styles[0].value; | ||
if (typeof parsedBlock.style != "string" || !context.schema.styles.find((style) => style.value === block.style)) { | ||
const defaultStyle = context.schema.styles.at(0)?.value; | ||
defaultStyle !== void 0 ? parsedBlock.style = defaultStyle : delete parsedBlock.style; | ||
} | ||
return context.schema.lists.find((list) => list.value === block.listItem) || (delete parsedBlock.listItem, delete parsedBlock.level), parsedBlock; | ||
return (typeof parsedBlock.listItem != "string" || !context.schema.lists.find((list) => list.value === block.listItem)) && delete parsedBlock.listItem, typeof parsedBlock.level != "number" && delete parsedBlock.level, parsedBlock; | ||
} | ||
function parseSpan({ | ||
span, | ||
context, | ||
markDefKeyMap, | ||
options | ||
}) { | ||
if (!isTypedObject(span) || span._type !== context.schema.span.name || span._type !== "span") | ||
return; | ||
const marks = (Array.isArray(span.marks) ? span.marks : []).flatMap((mark) => { | ||
if (typeof mark != "string") | ||
return []; | ||
const markDefKey = markDefKeyMap.get(mark); | ||
return markDefKey !== void 0 ? [markDefKey] : context.schema.decorators.some((decorator) => decorator.value === mark) ? [mark] : []; | ||
}); | ||
return { | ||
// Spread the entire span to allow custom properties on it | ||
...span, | ||
_type: "span", | ||
_key: options.refreshKeys ? context.keyGenerator() : typeof span._key == "string" ? span._key : context.keyGenerator(), | ||
text: typeof span.text == "string" ? span.text : "", | ||
marks | ||
}; | ||
} | ||
function parseInlineObject({ | ||
inlineObject, | ||
context, | ||
options | ||
}) { | ||
if (isTypedObject(inlineObject) && !(inlineObject._type === context.schema.span.name || inlineObject._type === "span" || // Respect the schema definition and don't parse inline objects that are not defined | ||
!context.schema.inlineObjects.some(({ | ||
name | ||
}) => name === inlineObject._type))) | ||
return { | ||
// Spread the entire inline object to allow custom properties on it | ||
...inlineObject, | ||
_key: options.refreshKeys ? context.keyGenerator() : typeof inlineObject._key == "string" ? inlineObject._key : context.keyGenerator() | ||
}; | ||
} | ||
function blockOffsetsToSelection({ | ||
@@ -102,4 +157,5 @@ value, | ||
blockOffsetsToSelection, | ||
isTextBlock, | ||
parseBlock | ||
}; | ||
//# sourceMappingURL=util.block-offsets-to-selection.js.map |
@@ -201,4 +201,2 @@ import type { | ||
| 'style.toggle' | ||
| 'text block.set' | ||
| 'text block.unset' | ||
| 'key.down' | ||
@@ -258,4 +256,2 @@ | 'key.up' | ||
| 'style.toggle' | ||
| 'text block.set' | ||
| 'text block.unset' | ||
| 'key.down' | ||
@@ -320,4 +316,2 @@ | 'key.up' | ||
| 'style.toggle' | ||
| 'text block.set' | ||
| 'text block.unset' | ||
| 'key.down' | ||
@@ -377,4 +371,2 @@ | 'key.up' | ||
| 'style.toggle' | ||
| 'text block.set' | ||
| 'text block.unset' | ||
| 'key.down' | ||
@@ -439,4 +431,2 @@ | 'key.up' | ||
| 'style.toggle' | ||
| 'text block.set' | ||
| 'text block.unset' | ||
| 'key.down' | ||
@@ -496,4 +486,2 @@ | 'key.up' | ||
| 'style.toggle' | ||
| 'text block.set' | ||
| 'text block.unset' | ||
| 'key.down' | ||
@@ -558,4 +546,2 @@ | 'key.up' | ||
| 'style.toggle' | ||
| 'text block.set' | ||
| 'text block.unset' | ||
| 'key.down' | ||
@@ -615,4 +601,2 @@ | 'key.up' | ||
| 'style.toggle' | ||
| 'text block.set' | ||
| 'text block.unset' | ||
| 'key.down' | ||
@@ -719,4 +703,2 @@ | 'key.up' | ||
| 'style.toggle' | ||
| 'text block.set' | ||
| 'text block.unset' | ||
| 'key.down' | ||
@@ -776,4 +758,2 @@ | 'key.up' | ||
| 'style.toggle' | ||
| 'text block.set' | ||
| 'text block.unset' | ||
| 'key.down' | ||
@@ -1036,3 +1016,3 @@ | 'key.up' | ||
at: [KeyedSegment] | ||
[props: string]: unknown | ||
props: Record<string, unknown> | ||
} | ||
@@ -1200,14 +1180,2 @@ | { | ||
} | ||
| { | ||
type: 'text block.set' | ||
at: [KeyedSegment] | ||
level?: number | ||
listItem?: string | ||
style?: string | ||
} | ||
| { | ||
type: 'text block.unset' | ||
at: [KeyedSegment] | ||
props: Array<'level' | 'listItem' | 'style'> | ||
} | ||
| (PickFromUnion< | ||
@@ -1214,0 +1182,0 @@ ConverterEvent, |
{ | ||
"name": "@portabletext/editor", | ||
"version": "1.33.5", | ||
"version": "1.33.6", | ||
"description": "Portable Text Editor made in React", | ||
@@ -82,3 +82,3 @@ "keywords": [ | ||
"xstate": "^5.19.2", | ||
"@portabletext/block-tools": "1.1.7", | ||
"@portabletext/block-tools": "1.1.8", | ||
"@portabletext/patches": "1.1.3" | ||
@@ -90,4 +90,4 @@ }, | ||
"@sanity/pkg-utils": "^7.0.4", | ||
"@sanity/schema": "^3.75.0", | ||
"@sanity/types": "^3.75.0", | ||
"@sanity/schema": "^3.75.1", | ||
"@sanity/types": "^3.75.1", | ||
"@testing-library/jest-dom": "^6.6.3", | ||
@@ -120,4 +120,4 @@ "@testing-library/react": "^16.2.0", | ||
"peerDependencies": { | ||
"@sanity/schema": "^3.75.0", | ||
"@sanity/types": "^3.75.0", | ||
"@sanity/schema": "^3.75.1", | ||
"@sanity/types": "^3.75.1", | ||
"react": "^16.9 || ^17 || ^18 || ^19", | ||
@@ -124,0 +124,0 @@ "rxjs": "^7.8.1" |
@@ -1,3 +0,6 @@ | ||
import {Transforms} from 'slate' | ||
import {Editor, Transforms, type Element as SlateElement} from 'slate' | ||
import {parseBlock} from '../internal-utils/parse-blocks' | ||
import {toSlateRange} from '../internal-utils/ranges' | ||
import {fromSlateValue, toSlateValue} from '../internal-utils/values' | ||
import {KEY_TO_VALUE_ELEMENT} from '../internal-utils/weakMaps' | ||
import type {BehaviorActionImplementation} from './behavior.actions' | ||
@@ -7,3 +10,3 @@ | ||
'block.set' | ||
> = ({action}) => { | ||
> = ({context, action}) => { | ||
const location = toSlateRange( | ||
@@ -18,8 +21,48 @@ { | ||
if (!location) { | ||
return | ||
throw new Error( | ||
`Unable to convert ${JSON.stringify(action.at)} into a Slate Range`, | ||
) | ||
} | ||
const {at, editor, type, ...payload} = action | ||
const blockEntry = Editor.node(action.editor, location, {depth: 1}) | ||
const block = blockEntry?.[0] | ||
Transforms.setNodes(action.editor, payload, {at: location}) | ||
if (!block) { | ||
throw new Error(`Unable to find block at ${JSON.stringify(action.at)}`) | ||
} | ||
const parsedBlock = fromSlateValue( | ||
[block], | ||
context.schema.block.name, | ||
KEY_TO_VALUE_ELEMENT.get(action.editor), | ||
).at(0) | ||
if (!parsedBlock) { | ||
throw new Error(`Unable to parse block at ${JSON.stringify(action.at)}`) | ||
} | ||
const {_type, ...filteredProps} = action.props | ||
const updatedBlock = parseBlock({ | ||
context, | ||
block: { | ||
...parsedBlock, | ||
...filteredProps, | ||
}, | ||
options: {refreshKeys: false}, | ||
}) | ||
if (!updatedBlock) { | ||
throw new Error(`Unable to update block at ${JSON.stringify(action.at)}`) | ||
} | ||
const slateBlock = toSlateValue([updatedBlock], { | ||
schemaTypes: context.schema, | ||
})?.at(0) as SlateElement | undefined | ||
if (!slateBlock) { | ||
throw new Error(`Unable to convert block to Slate value`) | ||
} | ||
Transforms.setNodes(action.editor, slateBlock, {at: location}) | ||
} |
@@ -1,3 +0,7 @@ | ||
import {Transforms} from 'slate' | ||
import {omit} from 'lodash' | ||
import {Editor, Transforms} from 'slate' | ||
import {isTextBlock, parseBlock} from '../internal-utils/parse-blocks' | ||
import {toSlateRange} from '../internal-utils/ranges' | ||
import {fromSlateValue} from '../internal-utils/values' | ||
import {KEY_TO_VALUE_ELEMENT} from '../internal-utils/weakMaps' | ||
import type {BehaviorActionImplementation} from './behavior.actions' | ||
@@ -7,3 +11,3 @@ | ||
'block.unset' | ||
> = ({action}) => { | ||
> = ({context, action}) => { | ||
const location = toSlateRange( | ||
@@ -18,6 +22,76 @@ { | ||
if (!location) { | ||
throw new Error( | ||
`Unable to convert ${JSON.stringify(action.at)} into a Slate Range`, | ||
) | ||
} | ||
const blockEntry = Editor.node(action.editor, location, {depth: 1}) | ||
const block = blockEntry?.[0] | ||
if (!block) { | ||
throw new Error(`Unable to find block at ${JSON.stringify(action.at)}`) | ||
} | ||
const parsedBlock = fromSlateValue( | ||
[block], | ||
context.schema.block.name, | ||
KEY_TO_VALUE_ELEMENT.get(action.editor), | ||
).at(0) | ||
if (!parsedBlock) { | ||
throw new Error(`Unable to parse block at ${JSON.stringify(action.at)}`) | ||
} | ||
if (isTextBlock(context.schema, parsedBlock)) { | ||
const propsToRemove = action.props.filter((prop) => prop !== '_type') | ||
const updatedTextBlock = parseBlock({ | ||
context, | ||
block: omit(parsedBlock, propsToRemove), | ||
options: {refreshKeys: false}, | ||
}) | ||
if (!updatedTextBlock) { | ||
throw new Error(`Unable to update block at ${JSON.stringify(action.at)}`) | ||
} | ||
const propsToSet: Record<string, unknown> = {} | ||
for (const prop of propsToRemove) { | ||
if (!(prop in updatedTextBlock)) { | ||
propsToSet[prop] = undefined | ||
} else { | ||
propsToSet[prop] = (updatedTextBlock as Record<string, unknown>)[prop] | ||
} | ||
} | ||
Transforms.setNodes(action.editor, propsToSet, {at: location}) | ||
return | ||
} | ||
Transforms.unsetNodes(action.editor, action.props, {at: location}) | ||
const updatedBlockObject = parseBlock({ | ||
context, | ||
block: omit( | ||
parsedBlock, | ||
action.props.filter((prop) => prop !== '_type'), | ||
), | ||
options: {refreshKeys: false}, | ||
}) | ||
if (!updatedBlockObject) { | ||
throw new Error(`Unable to update block at ${JSON.stringify(action.at)}`) | ||
} | ||
const {_type, _key, ...props} = updatedBlockObject | ||
Transforms.setNodes( | ||
action.editor, | ||
{ | ||
_type, | ||
_key, | ||
value: props, | ||
}, | ||
{at: location}, | ||
) | ||
} |
@@ -51,4 +51,2 @@ import {deleteForward, insertText, Path, Transforms} from 'slate' | ||
} from './behavior.action.style' | ||
import {textBlockSetActionImplementation} from './behavior.action.text-block.set' | ||
import {textBlockUnsetActionImplementation} from './behavior.action.text-block.unset' | ||
@@ -270,4 +268,2 @@ export type BehaviorActionImplementationContext = Pick< | ||
'style.remove': removeStyleActionImplementation, | ||
'text block.set': textBlockSetActionImplementation, | ||
'text block.unset': textBlockUnsetActionImplementation, | ||
} | ||
@@ -605,3 +601,3 @@ | ||
} | ||
case 'style.toggle': { | ||
default: { | ||
behaviorActionImplementations['style.toggle']({ | ||
@@ -613,16 +609,3 @@ context, | ||
} | ||
case 'text block.set': { | ||
behaviorActionImplementations['text block.set']({ | ||
context, | ||
action, | ||
}) | ||
break | ||
} | ||
default: { | ||
behaviorActionImplementations['text block.unset']({ | ||
context, | ||
action, | ||
}) | ||
} | ||
} | ||
} |
@@ -33,3 +33,3 @@ import {createGuards} from '../behavior-actions/behavior.guards' | ||
raise({ | ||
type: 'text block.unset', | ||
type: 'block.unset', | ||
props: ['listItem', 'level'], | ||
@@ -70,4 +70,4 @@ at: focusTextBlock.path, | ||
raise({ | ||
type: 'text block.set', | ||
level, | ||
type: 'block.set', | ||
props: {level}, | ||
at: focusTextBlock.path, | ||
@@ -98,3 +98,3 @@ }), | ||
raise({ | ||
type: 'text block.unset', | ||
type: 'block.unset', | ||
props: ['listItem', 'level'], | ||
@@ -139,7 +139,9 @@ at: focusListBlock.path, | ||
raise({ | ||
type: 'text block.set', | ||
level: Math.min( | ||
MAX_LIST_LEVEL, | ||
Math.max(1, selectedListBlock.node.level + 1), | ||
), | ||
type: 'block.set', | ||
props: { | ||
level: Math.min( | ||
MAX_LIST_LEVEL, | ||
Math.max(1, selectedListBlock.node.level + 1), | ||
), | ||
}, | ||
at: selectedListBlock.path, | ||
@@ -183,7 +185,9 @@ }), | ||
raise({ | ||
type: 'text block.set', | ||
level: Math.min( | ||
MAX_LIST_LEVEL, | ||
Math.max(1, selectedListBlock.node.level - 1), | ||
), | ||
type: 'block.set', | ||
props: { | ||
level: Math.min( | ||
MAX_LIST_LEVEL, | ||
Math.max(1, selectedListBlock.node.level - 1), | ||
), | ||
}, | ||
at: selectedListBlock.path, | ||
@@ -190,0 +194,0 @@ }), |
@@ -130,3 +130,3 @@ import {isPortableTextTextBlock} from '@sanity/types' | ||
{ | ||
type: 'text block.unset', | ||
type: 'block.unset', | ||
props: ['listItem', 'level'], | ||
@@ -136,4 +136,4 @@ at: focusTextBlock.path, | ||
{ | ||
type: 'text block.set', | ||
style, | ||
type: 'block.set', | ||
props: {style}, | ||
at: focusTextBlock.path, | ||
@@ -331,3 +331,3 @@ }, | ||
{ | ||
type: 'text block.unset', | ||
type: 'block.unset', | ||
props: ['listItem', 'level'], | ||
@@ -337,4 +337,4 @@ at: focusTextBlock.path, | ||
{ | ||
type: 'text block.set', | ||
style, | ||
type: 'block.set', | ||
props: {style}, | ||
at: focusTextBlock.path, | ||
@@ -386,4 +386,4 @@ }, | ||
{ | ||
type: 'text block.set', | ||
style: defaultStyle, | ||
type: 'block.set', | ||
props: {style: defaultStyle}, | ||
at: focusTextBlock.path, | ||
@@ -472,6 +472,8 @@ }, | ||
{ | ||
type: 'text block.set', | ||
listItem, | ||
level: 1, | ||
style, | ||
type: 'block.set', | ||
props: { | ||
listItem, | ||
level: 1, | ||
style, | ||
}, | ||
at: focusTextBlock.path, | ||
@@ -478,0 +480,0 @@ }, |
@@ -41,3 +41,3 @@ import type { | ||
at: [KeyedSegment] | ||
[props: string]: unknown | ||
props: Record<string, unknown> | ||
} | ||
@@ -199,14 +199,2 @@ | { | ||
} | ||
| { | ||
type: 'text block.set' | ||
at: [KeyedSegment] | ||
level?: number | ||
listItem?: string | ||
style?: string | ||
} | ||
| { | ||
type: 'text block.unset' | ||
at: [KeyedSegment] | ||
props: Array<'level' | 'listItem' | 'style'> | ||
} | ||
| (PickFromUnion< | ||
@@ -213,0 +201,0 @@ ConverterEvent, |
@@ -415,2 +415,3 @@ import {assert, describe, expect, test} from 'vitest' | ||
markDefs: [], | ||
level: 1, | ||
style: 'normal', | ||
@@ -572,3 +573,3 @@ }, | ||
text: 'foo', | ||
marks: ['k0'], | ||
marks: ['k1'], | ||
}, | ||
@@ -583,3 +584,3 @@ { | ||
{ | ||
_key: 'k0', | ||
_key: 'k1', | ||
_type: 'link', | ||
@@ -586,0 +587,0 @@ href: 'https://example.com', |
@@ -1,6 +0,8 @@ | ||
import { | ||
isPortableTextSpan, | ||
isPortableTextTextBlock, | ||
type PortableTextBlock, | ||
import type { | ||
PortableTextBlock, | ||
PortableTextObject, | ||
PortableTextSpan, | ||
PortableTextTextBlock, | ||
} from '@sanity/types' | ||
import type {EditorSchema} from '../editor/define-schema' | ||
import type {EditorContext} from '../editor/editor-snapshot' | ||
@@ -20,3 +22,18 @@ import {isTypedObject} from './asserters' | ||
}): PortableTextBlock | undefined { | ||
if (!isTypedObject(block)) { | ||
return ( | ||
parseTextBlock({block, context, options}) ?? | ||
parseBlockObject({blockObject: block, context, options}) | ||
) | ||
} | ||
function parseBlockObject({ | ||
blockObject, | ||
context, | ||
options, | ||
}: { | ||
blockObject: unknown | ||
context: Pick<EditorContext, 'keyGenerator' | 'schema'> | ||
options: {refreshKeys: boolean} | ||
}): PortableTextObject | undefined { | ||
if (!isTypedObject(blockObject)) { | ||
return undefined | ||
@@ -26,6 +43,5 @@ } | ||
if ( | ||
block._type !== context.schema.block.name && | ||
!context.schema.blockObjects.some( | ||
(blockObject) => blockObject.name === block._type, | ||
) | ||
blockObject._type === context.schema.block.name || | ||
blockObject._type === 'block' || | ||
!context.schema.blockObjects.some(({name}) => name === blockObject._type) | ||
) { | ||
@@ -35,37 +51,61 @@ return undefined | ||
if (block._type !== context.schema.block.name) { | ||
const _key = options.refreshKeys | ||
return { | ||
...blockObject, | ||
_key: options.refreshKeys | ||
? context.keyGenerator() | ||
: typeof block._key === 'string' | ||
? block._key | ||
: context.keyGenerator() | ||
return { | ||
...block, | ||
_key, | ||
} | ||
: typeof blockObject._key === 'string' | ||
? blockObject._key | ||
: context.keyGenerator(), | ||
} | ||
} | ||
if (!isPortableTextTextBlock(block)) { | ||
return { | ||
_type: context.schema.block.name, | ||
_key: options.refreshKeys | ||
? context.keyGenerator() | ||
: typeof block._key === 'string' | ||
? block._key | ||
: context.keyGenerator(), | ||
children: [ | ||
{ | ||
_key: context.keyGenerator(), | ||
_type: context.schema.span.name, | ||
text: '', | ||
marks: [], | ||
}, | ||
], | ||
markDefs: [], | ||
style: context.schema.styles[0].value, | ||
} | ||
export function isTextBlock( | ||
schema: EditorSchema, | ||
block: unknown, | ||
): block is PortableTextTextBlock { | ||
return ( | ||
parseTextBlock({ | ||
block, | ||
context: {schema, keyGenerator: () => ''}, | ||
options: {refreshKeys: false}, | ||
}) !== undefined | ||
) | ||
} | ||
function parseTextBlock({ | ||
block, | ||
context, | ||
options, | ||
}: { | ||
block: unknown | ||
context: Pick<EditorContext, 'keyGenerator' | 'schema'> | ||
options: {refreshKeys: boolean} | ||
}): PortableTextTextBlock | undefined { | ||
if (!isTypedObject(block)) { | ||
return undefined | ||
} | ||
if (block._type !== context.schema.block.name) { | ||
return undefined | ||
} | ||
const _key = options.refreshKeys | ||
? context.keyGenerator() | ||
: typeof block._key === 'string' | ||
? block._key | ||
: context.keyGenerator() | ||
const unparsedMarkDefs: Array<unknown> = Array.isArray(block.markDefs) | ||
? block.markDefs | ||
: [] | ||
const markDefKeyMap = new Map<string, string>() | ||
const markDefs = (block.markDefs ?? []).flatMap((markDef) => { | ||
const markDefs = unparsedMarkDefs.flatMap((markDef) => { | ||
if (!isTypedObject(markDef)) { | ||
return [] | ||
} | ||
if (typeof markDef._key !== 'string') { | ||
return [] | ||
} | ||
if ( | ||
@@ -90,51 +130,18 @@ context.schema.annotations.some( | ||
const children = block.children.flatMap((child) => { | ||
if (!isTypedObject(child)) { | ||
return [] | ||
} | ||
const unparsedChildren: Array<unknown> = Array.isArray(block.children) | ||
? block.children | ||
: [] | ||
if ( | ||
child._type !== context.schema.span.name && | ||
!context.schema.inlineObjects.some( | ||
(inlineObject) => inlineObject.name === child._type, | ||
) | ||
) { | ||
return [] | ||
} | ||
const children = unparsedChildren | ||
.map( | ||
(child) => | ||
parseSpan({span: child, context, markDefKeyMap, options}) ?? | ||
parseInlineObject({inlineObject: child, context, options}), | ||
) | ||
.filter((child) => child !== undefined) | ||
if (!isPortableTextSpan(child)) { | ||
return [ | ||
{ | ||
...child, | ||
_key: options.refreshKeys ? context.keyGenerator() : child._key, | ||
}, | ||
] | ||
} | ||
const marks = (child.marks ?? []).flatMap((mark) => { | ||
if (markDefKeyMap.has(mark)) { | ||
return [markDefKeyMap.get(mark)] | ||
} | ||
if ( | ||
context.schema.decorators.some((decorator) => decorator.value === mark) | ||
) { | ||
return [mark] | ||
} | ||
return [] | ||
}) | ||
return [ | ||
{ | ||
...child, | ||
_key: options.refreshKeys ? context.keyGenerator() : child._key, | ||
marks, | ||
}, | ||
] | ||
}) | ||
const parsedBlock = { | ||
const parsedBlock: PortableTextTextBlock = { | ||
// Spread the entire block to allow custom properties on it | ||
...block, | ||
_key: options.refreshKeys ? context.keyGenerator() : block._key, | ||
_key, | ||
children: | ||
@@ -154,4 +161,10 @@ children.length > 0 | ||
if (!context.schema.styles.find((style) => style.value === block.style)) { | ||
const defaultStyle = context.schema.styles[0].value | ||
/** | ||
* Reset text block .style if it's somehow set to an invalid type | ||
*/ | ||
if ( | ||
typeof parsedBlock.style !== 'string' || | ||
!context.schema.styles.find((style) => style.value === block.style) | ||
) { | ||
const defaultStyle = context.schema.styles.at(0)?.value | ||
@@ -165,4 +178,16 @@ if (defaultStyle !== undefined) { | ||
if (!context.schema.lists.find((list) => list.value === block.listItem)) { | ||
/** | ||
* Reset text block .listItem if it's somehow set to an invalid type | ||
*/ | ||
if ( | ||
typeof parsedBlock.listItem !== 'string' || | ||
!context.schema.lists.find((list) => list.value === block.listItem) | ||
) { | ||
delete parsedBlock.listItem | ||
} | ||
/** | ||
* Reset text block .level if it's somehow set to an invalid type | ||
*/ | ||
if (typeof parsedBlock.level !== 'number') { | ||
delete parsedBlock.level | ||
@@ -173,1 +198,91 @@ } | ||
} | ||
export function parseSpan({ | ||
span, | ||
context, | ||
markDefKeyMap, | ||
options, | ||
}: { | ||
span: unknown | ||
context: Pick<EditorContext, 'keyGenerator' | 'schema'> | ||
markDefKeyMap: Map<string, string> | ||
options: {refreshKeys: boolean} | ||
}): PortableTextSpan | undefined { | ||
if (!isTypedObject(span)) { | ||
return undefined | ||
} | ||
// In reality, the span schema name is always 'span', but we only the check here anyway | ||
if (span._type !== context.schema.span.name || span._type !== 'span') { | ||
return undefined | ||
} | ||
const unparsedMarks: Array<unknown> = Array.isArray(span.marks) | ||
? span.marks | ||
: [] | ||
const marks = unparsedMarks.flatMap((mark) => { | ||
if (typeof mark !== 'string') { | ||
return [] | ||
} | ||
const markDefKey = markDefKeyMap.get(mark) | ||
if (markDefKey !== undefined) { | ||
return [markDefKey] | ||
} | ||
if ( | ||
context.schema.decorators.some((decorator) => decorator.value === mark) | ||
) { | ||
return [mark] | ||
} | ||
return [] | ||
}) | ||
return { | ||
// Spread the entire span to allow custom properties on it | ||
...span, | ||
_type: 'span', | ||
_key: options.refreshKeys | ||
? context.keyGenerator() | ||
: typeof span._key === 'string' | ||
? span._key | ||
: context.keyGenerator(), | ||
text: typeof span.text === 'string' ? span.text : '', | ||
marks, | ||
} | ||
} | ||
function parseInlineObject({ | ||
inlineObject, | ||
context, | ||
options, | ||
}: { | ||
inlineObject: unknown | ||
context: Pick<EditorContext, 'keyGenerator' | 'schema'> | ||
options: {refreshKeys: boolean} | ||
}): PortableTextObject | undefined { | ||
if (!isTypedObject(inlineObject)) { | ||
return undefined | ||
} | ||
if ( | ||
inlineObject._type === context.schema.span.name || | ||
inlineObject._type === 'span' || | ||
// Respect the schema definition and don't parse inline objects that are not defined | ||
!context.schema.inlineObjects.some(({name}) => name === inlineObject._type) | ||
) { | ||
return undefined | ||
} | ||
return { | ||
// Spread the entire inline object to allow custom properties on it | ||
...inlineObject, | ||
_key: options.refreshKeys | ||
? context.keyGenerator() | ||
: typeof inlineObject._key === 'string' | ||
? inlineObject._key | ||
: context.keyGenerator(), | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
5965101
269
92284
+ Added@portabletext/block-tools@1.1.8(transitive)
- Removed@portabletext/block-tools@1.1.7(transitive)