@portabletext/editor 1.33.5 → 1.34.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.
Files changed (50) hide show
  1. package/lib/_chunks-cjs/behavior.core.cjs +14 -8
  2. package/lib/_chunks-cjs/behavior.core.cjs.map +1 -1
  3. package/lib/_chunks-cjs/behavior.markdown.cjs +20 -12
  4. package/lib/_chunks-cjs/behavior.markdown.cjs.map +1 -1
  5. package/lib/_chunks-cjs/plugin.event-listener.cjs +95 -70
  6. package/lib/_chunks-cjs/plugin.event-listener.cjs.map +1 -1
  7. package/lib/_chunks-cjs/util.block-offsets-to-selection.cjs +103 -46
  8. package/lib/_chunks-cjs/util.block-offsets-to-selection.cjs.map +1 -1
  9. package/lib/_chunks-es/behavior.core.js +14 -8
  10. package/lib/_chunks-es/behavior.core.js.map +1 -1
  11. package/lib/_chunks-es/behavior.markdown.js +20 -12
  12. package/lib/_chunks-es/behavior.markdown.js.map +1 -1
  13. package/lib/_chunks-es/plugin.event-listener.js +101 -75
  14. package/lib/_chunks-es/plugin.event-listener.js.map +1 -1
  15. package/lib/_chunks-es/util.block-offsets-to-selection.js +102 -46
  16. package/lib/_chunks-es/util.block-offsets-to-selection.js.map +1 -1
  17. package/lib/behaviors/index.d.cts +5 -34
  18. package/lib/behaviors/index.d.ts +5 -34
  19. package/lib/index.d.cts +300 -1460
  20. package/lib/index.d.ts +300 -1460
  21. package/lib/plugins/index.cjs +60 -43
  22. package/lib/plugins/index.cjs.map +1 -1
  23. package/lib/plugins/index.d.cts +300 -1460
  24. package/lib/plugins/index.d.ts +300 -1460
  25. package/lib/plugins/index.js +63 -45
  26. package/lib/plugins/index.js.map +1 -1
  27. package/lib/selectors/index.d.cts +12 -0
  28. package/lib/selectors/index.d.ts +12 -0
  29. package/lib/utils/index.cjs +23 -1
  30. package/lib/utils/index.cjs.map +1 -1
  31. package/lib/utils/index.d.cts +11 -0
  32. package/lib/utils/index.d.ts +11 -0
  33. package/lib/utils/index.js +25 -2
  34. package/lib/utils/index.js.map +1 -1
  35. package/package.json +6 -6
  36. package/src/behavior-actions/behavior.action.block.set.ts +48 -5
  37. package/src/behavior-actions/behavior.action.block.unset.ts +77 -3
  38. package/src/behavior-actions/behavior.action.decorator.add.ts +30 -8
  39. package/src/behavior-actions/behavior.actions.ts +1 -18
  40. package/src/behaviors/behavior.core.lists.ts +18 -14
  41. package/src/behaviors/behavior.markdown-emphasis.ts +82 -41
  42. package/src/behaviors/behavior.markdown.ts +14 -12
  43. package/src/behaviors/behavior.types.ts +2 -14
  44. package/src/converters/converter.portable-text.deserialize.test.ts +3 -2
  45. package/src/internal-utils/parse-blocks.test.ts +455 -0
  46. package/src/internal-utils/parse-blocks.ts +202 -87
  47. package/src/utils/index.ts +1 -0
  48. package/src/utils/util.child-selection-point-to-block-offset.ts +55 -0
  49. package/src/behavior-actions/behavior.action.text-block.set.ts +0 -25
  50. package/src/behavior-actions/behavior.action.text-block.unset.ts +0 -17
@@ -1,8 +1,10 @@
1
- import {
2
- isPortableTextSpan,
3
- isPortableTextTextBlock,
4
- type PortableTextBlock,
1
+ import type {
2
+ PortableTextBlock,
3
+ PortableTextObject,
4
+ PortableTextSpan,
5
+ PortableTextTextBlock,
5
6
  } from '@sanity/types'
7
+ import type {EditorSchema} from '../editor/define-schema'
6
8
  import type {EditorContext} from '../editor/editor-snapshot'
7
9
  import {isTypedObject} from './asserters'
8
10
 
@@ -17,54 +19,92 @@ export function parseBlock({
17
19
  refreshKeys: boolean
18
20
  }
19
21
  }): PortableTextBlock | undefined {
20
- if (!isTypedObject(block)) {
22
+ return (
23
+ parseTextBlock({block, context, options}) ??
24
+ parseBlockObject({blockObject: block, context, options})
25
+ )
26
+ }
27
+
28
+ function parseBlockObject({
29
+ blockObject,
30
+ context,
31
+ options,
32
+ }: {
33
+ blockObject: unknown
34
+ context: Pick<EditorContext, 'keyGenerator' | 'schema'>
35
+ options: {refreshKeys: boolean}
36
+ }): PortableTextObject | undefined {
37
+ if (!isTypedObject(blockObject)) {
21
38
  return undefined
22
39
  }
23
40
 
24
41
  if (
25
- block._type !== context.schema.block.name &&
26
- !context.schema.blockObjects.some(
27
- (blockObject) => blockObject.name === block._type,
28
- )
42
+ blockObject._type === context.schema.block.name ||
43
+ blockObject._type === 'block' ||
44
+ !context.schema.blockObjects.some(({name}) => name === blockObject._type)
29
45
  ) {
30
46
  return undefined
31
47
  }
32
48
 
33
- if (block._type !== context.schema.block.name) {
34
- const _key = options.refreshKeys
49
+ return {
50
+ ...blockObject,
51
+ _key: options.refreshKeys
35
52
  ? context.keyGenerator()
36
- : typeof block._key === 'string'
37
- ? block._key
38
- : context.keyGenerator()
39
- return {
40
- ...block,
41
- _key,
42
- }
53
+ : typeof blockObject._key === 'string'
54
+ ? blockObject._key
55
+ : context.keyGenerator(),
43
56
  }
57
+ }
44
58
 
45
- if (!isPortableTextTextBlock(block)) {
46
- return {
47
- _type: context.schema.block.name,
48
- _key: options.refreshKeys
49
- ? context.keyGenerator()
50
- : typeof block._key === 'string'
51
- ? block._key
52
- : context.keyGenerator(),
53
- children: [
54
- {
55
- _key: context.keyGenerator(),
56
- _type: context.schema.span.name,
57
- text: '',
58
- marks: [],
59
- },
60
- ],
61
- markDefs: [],
62
- style: context.schema.styles[0].value,
63
- }
59
+ export function isTextBlock(
60
+ schema: EditorSchema,
61
+ block: unknown,
62
+ ): block is PortableTextTextBlock {
63
+ return (
64
+ parseTextBlock({
65
+ block,
66
+ context: {schema, keyGenerator: () => ''},
67
+ options: {refreshKeys: false},
68
+ }) !== undefined
69
+ )
70
+ }
71
+
72
+ function parseTextBlock({
73
+ block,
74
+ context,
75
+ options,
76
+ }: {
77
+ block: unknown
78
+ context: Pick<EditorContext, 'keyGenerator' | 'schema'>
79
+ options: {refreshKeys: boolean}
80
+ }): PortableTextTextBlock | undefined {
81
+ if (!isTypedObject(block)) {
82
+ return undefined
83
+ }
84
+
85
+ if (block._type !== context.schema.block.name) {
86
+ return undefined
64
87
  }
65
88
 
89
+ const _key = options.refreshKeys
90
+ ? context.keyGenerator()
91
+ : typeof block._key === 'string'
92
+ ? block._key
93
+ : context.keyGenerator()
94
+
95
+ const unparsedMarkDefs: Array<unknown> = Array.isArray(block.markDefs)
96
+ ? block.markDefs
97
+ : []
66
98
  const markDefKeyMap = new Map<string, string>()
67
- const markDefs = (block.markDefs ?? []).flatMap((markDef) => {
99
+ const markDefs = unparsedMarkDefs.flatMap((markDef) => {
100
+ if (!isTypedObject(markDef)) {
101
+ return []
102
+ }
103
+
104
+ if (typeof markDef._key !== 'string') {
105
+ return []
106
+ }
107
+
68
108
  if (
69
109
  context.schema.annotations.some(
70
110
  (annotation) => annotation.name === markDef._type,
@@ -84,55 +124,22 @@ export function parseBlock({
84
124
  return []
85
125
  })
86
126
 
87
- const children = block.children.flatMap((child) => {
88
- if (!isTypedObject(child)) {
89
- return []
90
- }
91
-
92
- if (
93
- child._type !== context.schema.span.name &&
94
- !context.schema.inlineObjects.some(
95
- (inlineObject) => inlineObject.name === child._type,
96
- )
97
- ) {
98
- return []
99
- }
100
-
101
- if (!isPortableTextSpan(child)) {
102
- return [
103
- {
104
- ...child,
105
- _key: options.refreshKeys ? context.keyGenerator() : child._key,
106
- },
107
- ]
108
- }
127
+ const unparsedChildren: Array<unknown> = Array.isArray(block.children)
128
+ ? block.children
129
+ : []
109
130
 
110
- const marks = (child.marks ?? []).flatMap((mark) => {
111
- if (markDefKeyMap.has(mark)) {
112
- return [markDefKeyMap.get(mark)]
113
- }
114
-
115
- if (
116
- context.schema.decorators.some((decorator) => decorator.value === mark)
117
- ) {
118
- return [mark]
119
- }
120
-
121
- return []
122
- })
123
-
124
- return [
125
- {
126
- ...child,
127
- _key: options.refreshKeys ? context.keyGenerator() : child._key,
128
- marks,
129
- },
130
- ]
131
- })
131
+ const children = unparsedChildren
132
+ .map(
133
+ (child) =>
134
+ parseSpan({span: child, context, markDefKeyMap, options}) ??
135
+ parseInlineObject({inlineObject: child, context, options}),
136
+ )
137
+ .filter((child) => child !== undefined)
132
138
 
133
- const parsedBlock = {
139
+ const parsedBlock: PortableTextTextBlock = {
140
+ // Spread the entire block to allow custom properties on it
134
141
  ...block,
135
- _key: options.refreshKeys ? context.keyGenerator() : block._key,
142
+ _key,
136
143
  children:
137
144
  children.length > 0
138
145
  ? children
@@ -147,8 +154,14 @@ export function parseBlock({
147
154
  markDefs,
148
155
  }
149
156
 
150
- if (!context.schema.styles.find((style) => style.value === block.style)) {
151
- const defaultStyle = context.schema.styles[0].value
157
+ /**
158
+ * Reset text block .style if it's somehow set to an invalid type
159
+ */
160
+ if (
161
+ typeof parsedBlock.style !== 'string' ||
162
+ !context.schema.styles.find((style) => style.value === block.style)
163
+ ) {
164
+ const defaultStyle = context.schema.styles.at(0)?.value
152
165
 
153
166
  if (defaultStyle !== undefined) {
154
167
  parsedBlock.style = defaultStyle
@@ -157,10 +170,112 @@ export function parseBlock({
157
170
  }
158
171
  }
159
172
 
160
- if (!context.schema.lists.find((list) => list.value === block.listItem)) {
173
+ /**
174
+ * Reset text block .listItem if it's somehow set to an invalid type
175
+ */
176
+ if (
177
+ typeof parsedBlock.listItem !== 'string' ||
178
+ !context.schema.lists.find((list) => list.value === block.listItem)
179
+ ) {
161
180
  delete parsedBlock.listItem
181
+ }
182
+
183
+ /**
184
+ * Reset text block .level if it's somehow set to an invalid type
185
+ */
186
+ if (typeof parsedBlock.level !== 'number') {
162
187
  delete parsedBlock.level
163
188
  }
164
189
 
165
190
  return parsedBlock
166
191
  }
192
+
193
+ export function parseSpan({
194
+ span,
195
+ context,
196
+ markDefKeyMap,
197
+ options,
198
+ }: {
199
+ span: unknown
200
+ context: Pick<EditorContext, 'keyGenerator' | 'schema'>
201
+ markDefKeyMap: Map<string, string>
202
+ options: {refreshKeys: boolean}
203
+ }): PortableTextSpan | undefined {
204
+ if (!isTypedObject(span)) {
205
+ return undefined
206
+ }
207
+
208
+ // In reality, the span schema name is always 'span', but we only the check here anyway
209
+ if (span._type !== context.schema.span.name || span._type !== 'span') {
210
+ return undefined
211
+ }
212
+
213
+ const unparsedMarks: Array<unknown> = Array.isArray(span.marks)
214
+ ? span.marks
215
+ : []
216
+ const marks = unparsedMarks.flatMap((mark) => {
217
+ if (typeof mark !== 'string') {
218
+ return []
219
+ }
220
+
221
+ const markDefKey = markDefKeyMap.get(mark)
222
+
223
+ if (markDefKey !== undefined) {
224
+ return [markDefKey]
225
+ }
226
+
227
+ if (
228
+ context.schema.decorators.some((decorator) => decorator.value === mark)
229
+ ) {
230
+ return [mark]
231
+ }
232
+
233
+ return []
234
+ })
235
+
236
+ return {
237
+ // Spread the entire span to allow custom properties on it
238
+ ...span,
239
+ _type: 'span',
240
+ _key: options.refreshKeys
241
+ ? context.keyGenerator()
242
+ : typeof span._key === 'string'
243
+ ? span._key
244
+ : context.keyGenerator(),
245
+ text: typeof span.text === 'string' ? span.text : '',
246
+ marks,
247
+ }
248
+ }
249
+
250
+ function parseInlineObject({
251
+ inlineObject,
252
+ context,
253
+ options,
254
+ }: {
255
+ inlineObject: unknown
256
+ context: Pick<EditorContext, 'keyGenerator' | 'schema'>
257
+ options: {refreshKeys: boolean}
258
+ }): PortableTextObject | undefined {
259
+ if (!isTypedObject(inlineObject)) {
260
+ return undefined
261
+ }
262
+
263
+ if (
264
+ inlineObject._type === context.schema.span.name ||
265
+ inlineObject._type === 'span' ||
266
+ // Respect the schema definition and don't parse inline objects that are not defined
267
+ !context.schema.inlineObjects.some(({name}) => name === inlineObject._type)
268
+ ) {
269
+ return undefined
270
+ }
271
+
272
+ return {
273
+ // Spread the entire inline object to allow custom properties on it
274
+ ...inlineObject,
275
+ _key: options.refreshKeys
276
+ ? context.keyGenerator()
277
+ : typeof inlineObject._key === 'string'
278
+ ? inlineObject._key
279
+ : context.keyGenerator(),
280
+ }
281
+ }
@@ -5,6 +5,7 @@ export {
5
5
  spanSelectionPointToBlockOffset,
6
6
  } from './util.block-offset'
7
7
  export {blockOffsetsToSelection} from './util.block-offsets-to-selection'
8
+ export {childSelectionPointToBlockOffset} from './util.child-selection-point-to-block-offset'
8
9
  export {getBlockEndPoint} from './util.get-block-end-point'
9
10
  export {getBlockStartPoint} from './util.get-block-start-point'
10
11
  export {getTextBlockText} from './util.get-text-block-text'
@@ -0,0 +1,55 @@
1
+ import {
2
+ isPortableTextSpan,
3
+ isPortableTextTextBlock,
4
+ type PortableTextBlock,
5
+ } from '@sanity/types'
6
+ import type {BlockOffset} from '../behaviors/behavior.types'
7
+ import type {EditorSelectionPoint} from '../types/editor'
8
+ import {isKeyedSegment} from './util.is-keyed-segment'
9
+
10
+ /**
11
+ * @public
12
+ */
13
+ export function childSelectionPointToBlockOffset({
14
+ value,
15
+ selectionPoint,
16
+ }: {
17
+ value: Array<PortableTextBlock>
18
+ selectionPoint: EditorSelectionPoint
19
+ }): BlockOffset | undefined {
20
+ let offset = 0
21
+
22
+ const blockKey = isKeyedSegment(selectionPoint.path[0])
23
+ ? selectionPoint.path[0]._key
24
+ : undefined
25
+ const childKey = isKeyedSegment(selectionPoint.path[2])
26
+ ? selectionPoint.path[2]._key
27
+ : undefined
28
+
29
+ if (!blockKey || !childKey) {
30
+ return undefined
31
+ }
32
+
33
+ for (const block of value) {
34
+ if (block._key !== blockKey) {
35
+ continue
36
+ }
37
+
38
+ if (!isPortableTextTextBlock(block)) {
39
+ continue
40
+ }
41
+
42
+ for (const child of block.children) {
43
+ if (child._key === childKey) {
44
+ return {
45
+ path: [{_key: block._key}],
46
+ offset: offset + selectionPoint.offset,
47
+ }
48
+ }
49
+
50
+ if (isPortableTextSpan(child)) {
51
+ offset += child.text.length
52
+ }
53
+ }
54
+ }
55
+ }
@@ -1,25 +0,0 @@
1
- import {Transforms} from 'slate'
2
- import {toSlateRange} from '../internal-utils/ranges'
3
- import type {BehaviorActionImplementation} from './behavior.actions'
4
-
5
- export const textBlockSetActionImplementation: BehaviorActionImplementation<
6
- 'text block.set'
7
- > = ({action}) => {
8
- const at = toSlateRange(
9
- {
10
- anchor: {path: action.at, offset: 0},
11
- focus: {path: action.at, offset: 0},
12
- },
13
- action.editor,
14
- )!
15
-
16
- Transforms.setNodes(
17
- action.editor,
18
- {
19
- ...(action.style ? {style: action.style} : {}),
20
- ...(action.listItem ? {listItem: action.listItem} : {}),
21
- ...(action.level ? {level: action.level} : {}),
22
- },
23
- {at},
24
- )
25
- }
@@ -1,17 +0,0 @@
1
- import {Transforms} from 'slate'
2
- import {toSlateRange} from '../internal-utils/ranges'
3
- import type {BehaviorActionImplementation} from './behavior.actions'
4
-
5
- export const textBlockUnsetActionImplementation: BehaviorActionImplementation<
6
- 'text block.unset'
7
- > = ({action}) => {
8
- const at = toSlateRange(
9
- {
10
- anchor: {path: action.at, offset: 0},
11
- focus: {path: action.at, offset: 0},
12
- },
13
- action.editor,
14
- )!
15
-
16
- Transforms.unsetNodes(action.editor, action.props, {at})
17
- }