@portabletext/editor 3.0.7 → 3.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/_chunks-es/util.slice-blocks.js +45 -33
- package/lib/_chunks-es/util.slice-blocks.js.map +1 -1
- package/lib/_chunks-es/util.slice-text-block.js.map +1 -1
- package/lib/index.js +93 -78
- package/lib/index.js.map +1 -1
- package/lib/utils/index.d.ts +1 -1
- package/package.json +8 -8
- package/src/converters/converter.portable-text.deserialize.test.ts +0 -13
- package/src/editor/Editable.tsx +5 -4
- package/src/editor/plugins/createWithPortableTextMarkModel.ts +19 -0
- package/src/internal-utils/__tests__/values.test.ts +0 -2
- package/src/internal-utils/values.test.ts +0 -7
- package/src/internal-utils/values.ts +70 -26
- package/src/operations/behavior.operation.block.set.ts +82 -27
- package/src/operations/behavior.operation.block.unset.ts +26 -58
- package/src/operations/behavior.operation.insert.block.ts +4 -4
- package/src/utils/parse-blocks.test.ts +0 -16
- package/src/utils/parse-blocks.ts +70 -50
- package/src/utils/util.is-empty-text-block.ts +1 -1
- package/src/behaviors/behavior.decorator-pair.ts +0 -212
- package/src/behaviors/behavior.markdown.ts +0 -478
- package/src/internal-utils/get-text-to-emphasize.test.ts +0 -60
- package/src/internal-utils/get-text-to-emphasize.ts +0 -40
|
@@ -129,8 +129,6 @@ describe(parseBlock.name, () => {
|
|
|
129
129
|
marks: [],
|
|
130
130
|
},
|
|
131
131
|
],
|
|
132
|
-
markDefs: [],
|
|
133
|
-
style: 'normal',
|
|
134
132
|
})
|
|
135
133
|
})
|
|
136
134
|
|
|
@@ -160,8 +158,6 @@ describe(parseBlock.name, () => {
|
|
|
160
158
|
marks: [],
|
|
161
159
|
},
|
|
162
160
|
],
|
|
163
|
-
markDefs: [],
|
|
164
|
-
style: 'normal',
|
|
165
161
|
})
|
|
166
162
|
})
|
|
167
163
|
|
|
@@ -271,8 +267,6 @@ describe(parseBlock.name, () => {
|
|
|
271
267
|
marks: ['em'],
|
|
272
268
|
},
|
|
273
269
|
],
|
|
274
|
-
markDefs: [],
|
|
275
|
-
style: 'normal',
|
|
276
270
|
})
|
|
277
271
|
})
|
|
278
272
|
|
|
@@ -301,9 +295,7 @@ describe(parseBlock.name, () => {
|
|
|
301
295
|
marks: [],
|
|
302
296
|
},
|
|
303
297
|
],
|
|
304
|
-
markDefs: [],
|
|
305
298
|
listItem: 'bullet',
|
|
306
|
-
style: 'normal',
|
|
307
299
|
})
|
|
308
300
|
})
|
|
309
301
|
|
|
@@ -332,8 +324,6 @@ describe(parseBlock.name, () => {
|
|
|
332
324
|
marks: [],
|
|
333
325
|
},
|
|
334
326
|
],
|
|
335
|
-
markDefs: [],
|
|
336
|
-
style: 'normal',
|
|
337
327
|
})
|
|
338
328
|
})
|
|
339
329
|
|
|
@@ -363,8 +353,6 @@ describe(parseBlock.name, () => {
|
|
|
363
353
|
marks: [],
|
|
364
354
|
},
|
|
365
355
|
],
|
|
366
|
-
markDefs: [],
|
|
367
|
-
style: 'normal',
|
|
368
356
|
})
|
|
369
357
|
})
|
|
370
358
|
|
|
@@ -398,8 +386,6 @@ describe(parseBlock.name, () => {
|
|
|
398
386
|
marks: [],
|
|
399
387
|
},
|
|
400
388
|
],
|
|
401
|
-
markDefs: [],
|
|
402
|
-
style: 'normal',
|
|
403
389
|
})
|
|
404
390
|
})
|
|
405
391
|
|
|
@@ -432,8 +418,6 @@ describe(parseBlock.name, () => {
|
|
|
432
418
|
marks: [],
|
|
433
419
|
},
|
|
434
420
|
],
|
|
435
|
-
markDefs: [],
|
|
436
|
-
style: 'normal',
|
|
437
421
|
})
|
|
438
422
|
})
|
|
439
423
|
})
|
|
@@ -144,45 +144,10 @@ export function parseTextBlock({
|
|
|
144
144
|
const _key =
|
|
145
145
|
typeof block._key === 'string' ? block._key : context.keyGenerator()
|
|
146
146
|
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
:
|
|
150
|
-
|
|
151
|
-
const markDefs = unparsedMarkDefs.flatMap((markDef) => {
|
|
152
|
-
if (!isTypedObject(markDef)) {
|
|
153
|
-
return []
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const schemaType = context.schema.annotations.find(
|
|
157
|
-
({name}) => name === markDef._type,
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
if (!schemaType) {
|
|
161
|
-
return []
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (typeof markDef._key !== 'string') {
|
|
165
|
-
// If the `markDef` doesn't have a `_key` then we don't know what spans
|
|
166
|
-
// it belongs to and therefore we have to discard it.
|
|
167
|
-
return []
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const parsedAnnotation = parseObject({
|
|
171
|
-
object: markDef,
|
|
172
|
-
context: {
|
|
173
|
-
schemaType,
|
|
174
|
-
keyGenerator: context.keyGenerator,
|
|
175
|
-
},
|
|
176
|
-
options,
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
if (!parsedAnnotation) {
|
|
180
|
-
return []
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
markDefKeyMap.set(markDef._key, parsedAnnotation._key)
|
|
184
|
-
|
|
185
|
-
return [parsedAnnotation]
|
|
147
|
+
const {markDefs, markDefKeyMap} = parseMarkDefs({
|
|
148
|
+
context,
|
|
149
|
+
markDefs: block.markDefs,
|
|
150
|
+
options,
|
|
186
151
|
})
|
|
187
152
|
|
|
188
153
|
const unparsedChildren: Array<unknown> = Array.isArray(block.children)
|
|
@@ -249,25 +214,20 @@ export function parseTextBlock({
|
|
|
249
214
|
_type: context.schema.block.name,
|
|
250
215
|
_key,
|
|
251
216
|
children: normalizedChildren,
|
|
252
|
-
markDefs: options.removeUnusedMarkDefs
|
|
253
|
-
? markDefs.filter((markDef) => marks.includes(markDef._key))
|
|
254
|
-
: markDefs,
|
|
255
217
|
...customFields,
|
|
256
218
|
}
|
|
257
219
|
|
|
220
|
+
if (typeof block.markDefs === 'object' && block.markDefs !== null) {
|
|
221
|
+
parsedBlock.markDefs = options.removeUnusedMarkDefs
|
|
222
|
+
? markDefs.filter((markDef) => marks.includes(markDef._key))
|
|
223
|
+
: markDefs
|
|
224
|
+
}
|
|
225
|
+
|
|
258
226
|
if (
|
|
259
227
|
typeof block.style === 'string' &&
|
|
260
228
|
context.schema.styles.find((style) => style.name === block.style)
|
|
261
229
|
) {
|
|
262
230
|
parsedBlock.style = block.style
|
|
263
|
-
} else {
|
|
264
|
-
const defaultStyle = context.schema.styles.at(0)?.name
|
|
265
|
-
|
|
266
|
-
if (defaultStyle !== undefined) {
|
|
267
|
-
parsedBlock.style = defaultStyle
|
|
268
|
-
} else {
|
|
269
|
-
console.error('Expected default style')
|
|
270
|
-
}
|
|
271
231
|
}
|
|
272
232
|
|
|
273
233
|
if (
|
|
@@ -284,6 +244,66 @@ export function parseTextBlock({
|
|
|
284
244
|
return parsedBlock
|
|
285
245
|
}
|
|
286
246
|
|
|
247
|
+
export function parseMarkDefs({
|
|
248
|
+
context,
|
|
249
|
+
markDefs,
|
|
250
|
+
options,
|
|
251
|
+
}: {
|
|
252
|
+
context: Pick<EditorContext, 'keyGenerator' | 'schema'>
|
|
253
|
+
markDefs: unknown
|
|
254
|
+
options: {validateFields: boolean}
|
|
255
|
+
}): {
|
|
256
|
+
markDefs: Array<PortableTextObject>
|
|
257
|
+
markDefKeyMap: Map<string, string>
|
|
258
|
+
} {
|
|
259
|
+
const unparsedMarkDefs: Array<unknown> = Array.isArray(markDefs)
|
|
260
|
+
? markDefs
|
|
261
|
+
: []
|
|
262
|
+
const markDefKeyMap = new Map<string, string>()
|
|
263
|
+
|
|
264
|
+
const parsedMarkDefs = unparsedMarkDefs.flatMap((markDef) => {
|
|
265
|
+
if (!isTypedObject(markDef)) {
|
|
266
|
+
return []
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const schemaType = context.schema.annotations.find(
|
|
270
|
+
({name}) => name === markDef._type,
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
if (!schemaType) {
|
|
274
|
+
return []
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (typeof markDef._key !== 'string') {
|
|
278
|
+
// If the `markDef` doesn't have a `_key` then we don't know what spans
|
|
279
|
+
// it belongs to and therefore we have to discard it.
|
|
280
|
+
return []
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const parsedAnnotation = parseObject({
|
|
284
|
+
object: markDef,
|
|
285
|
+
context: {
|
|
286
|
+
schemaType,
|
|
287
|
+
keyGenerator: context.keyGenerator,
|
|
288
|
+
},
|
|
289
|
+
options,
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
if (!parsedAnnotation) {
|
|
293
|
+
return []
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
markDefKeyMap.set(markDef._key, parsedAnnotation._key)
|
|
297
|
+
|
|
298
|
+
return [parsedAnnotation]
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
markDefs: parsedMarkDefs,
|
|
303
|
+
markDefKeyMap,
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
287
307
|
export function parseChild({
|
|
288
308
|
child,
|
|
289
309
|
context,
|
|
@@ -8,7 +8,7 @@ import {getTextBlockText} from './util.get-text-block-text'
|
|
|
8
8
|
*/
|
|
9
9
|
export function isEmptyTextBlock(
|
|
10
10
|
context: Pick<EditorContext, 'schema'>,
|
|
11
|
-
block: PortableTextBlock,
|
|
11
|
+
block: PortableTextBlock | unknown,
|
|
12
12
|
) {
|
|
13
13
|
if (!isTextBlock(context, block)) {
|
|
14
14
|
return false
|
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
import type {EditorSchema} from '../editor/editor-schema'
|
|
2
|
-
import {createPairRegex} from '../internal-utils/get-text-to-emphasize'
|
|
3
|
-
import {getFocusTextBlock} from '../selectors/selector.get-focus-text-block'
|
|
4
|
-
import {getPreviousInlineObject} from '../selectors/selector.get-previous-inline-object'
|
|
5
|
-
import {getSelectionStartPoint} from '../selectors/selector.get-selection-start-point'
|
|
6
|
-
import {getBlockTextBefore} from '../selectors/selector.get-text-before'
|
|
7
|
-
import type {BlockOffset} from '../types/block-offset'
|
|
8
|
-
import {spanSelectionPointToBlockOffset} from '../utils/util.block-offset'
|
|
9
|
-
import {blockOffsetsToSelection} from '../utils/util.block-offsets-to-selection'
|
|
10
|
-
import {childSelectionPointToBlockOffset} from '../utils/util.child-selection-point-to-block-offset'
|
|
11
|
-
import {effect, execute} from './behavior.types.action'
|
|
12
|
-
import {defineBehavior} from './behavior.types.behavior'
|
|
13
|
-
|
|
14
|
-
export function createDecoratorPairBehavior(config: {
|
|
15
|
-
decorator: ({schema}: {schema: EditorSchema}) => string | undefined
|
|
16
|
-
pair: {char: string; amount: number}
|
|
17
|
-
onDecorate: (offset: BlockOffset) => void
|
|
18
|
-
}) {
|
|
19
|
-
if (config.pair.amount < 1) {
|
|
20
|
-
console.warn(
|
|
21
|
-
`The amount of characters in the pair should be greater than 0`,
|
|
22
|
-
)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const pairRegex = createPairRegex(config.pair.char, config.pair.amount)
|
|
26
|
-
const regEx = new RegExp(`(${pairRegex})$`)
|
|
27
|
-
|
|
28
|
-
return defineBehavior({
|
|
29
|
-
on: 'insert.text',
|
|
30
|
-
guard: ({snapshot, event}) => {
|
|
31
|
-
if (config.pair.amount < 1) {
|
|
32
|
-
return false
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const decorator = config.decorator({schema: snapshot.context.schema})
|
|
36
|
-
|
|
37
|
-
if (decorator === undefined) {
|
|
38
|
-
return false
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const focusTextBlock = getFocusTextBlock(snapshot)
|
|
42
|
-
const selectionStartPoint = getSelectionStartPoint(snapshot)
|
|
43
|
-
const selectionStartOffset = selectionStartPoint
|
|
44
|
-
? spanSelectionPointToBlockOffset({
|
|
45
|
-
context: {
|
|
46
|
-
schema: snapshot.context.schema,
|
|
47
|
-
value: snapshot.context.value,
|
|
48
|
-
},
|
|
49
|
-
selectionPoint: selectionStartPoint,
|
|
50
|
-
})
|
|
51
|
-
: undefined
|
|
52
|
-
|
|
53
|
-
if (!focusTextBlock || !selectionStartOffset) {
|
|
54
|
-
return false
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const textBefore = getBlockTextBefore(snapshot)
|
|
58
|
-
const newText = `${textBefore}${event.text}`
|
|
59
|
-
const textToDecorate = newText.match(regEx)?.at(0)
|
|
60
|
-
|
|
61
|
-
if (textToDecorate === undefined) {
|
|
62
|
-
return false
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const prefixOffsets = {
|
|
66
|
-
anchor: {
|
|
67
|
-
path: focusTextBlock.path,
|
|
68
|
-
// Example: "foo **bar**".length - "**bar**".length = 4
|
|
69
|
-
offset: newText.length - textToDecorate.length,
|
|
70
|
-
},
|
|
71
|
-
focus: {
|
|
72
|
-
path: focusTextBlock.path,
|
|
73
|
-
// Example: "foo **bar**".length - "**bar**".length + "*".length * 2 = 6
|
|
74
|
-
offset:
|
|
75
|
-
newText.length -
|
|
76
|
-
textToDecorate.length +
|
|
77
|
-
config.pair.char.length * config.pair.amount,
|
|
78
|
-
},
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const suffixOffsets = {
|
|
82
|
-
anchor: {
|
|
83
|
-
path: focusTextBlock.path,
|
|
84
|
-
// Example: "foo **bar*|" (10) + "*".length - 2 = 9
|
|
85
|
-
offset:
|
|
86
|
-
selectionStartOffset.offset +
|
|
87
|
-
event.text.length -
|
|
88
|
-
config.pair.char.length * config.pair.amount,
|
|
89
|
-
},
|
|
90
|
-
focus: {
|
|
91
|
-
path: focusTextBlock.path,
|
|
92
|
-
// Example: "foo **bar*|" (10) + "*".length = 11
|
|
93
|
-
offset: selectionStartOffset.offset + event.text.length,
|
|
94
|
-
},
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// If the prefix is more than one character, then we need to check if
|
|
98
|
-
// there is an inline object inside it
|
|
99
|
-
if (prefixOffsets.focus.offset - prefixOffsets.anchor.offset > 1) {
|
|
100
|
-
const prefixSelection = blockOffsetsToSelection({
|
|
101
|
-
context: snapshot.context,
|
|
102
|
-
offsets: prefixOffsets,
|
|
103
|
-
})
|
|
104
|
-
const inlineObjectBeforePrefixFocus = getPreviousInlineObject({
|
|
105
|
-
...snapshot,
|
|
106
|
-
context: {
|
|
107
|
-
...snapshot.context,
|
|
108
|
-
selection: prefixSelection
|
|
109
|
-
? {
|
|
110
|
-
anchor: prefixSelection.focus,
|
|
111
|
-
focus: prefixSelection.focus,
|
|
112
|
-
}
|
|
113
|
-
: null,
|
|
114
|
-
},
|
|
115
|
-
})
|
|
116
|
-
const inlineObjectBeforePrefixFocusOffset =
|
|
117
|
-
inlineObjectBeforePrefixFocus
|
|
118
|
-
? childSelectionPointToBlockOffset({
|
|
119
|
-
context: {
|
|
120
|
-
schema: snapshot.context.schema,
|
|
121
|
-
value: snapshot.context.value,
|
|
122
|
-
},
|
|
123
|
-
selectionPoint: {
|
|
124
|
-
path: inlineObjectBeforePrefixFocus.path,
|
|
125
|
-
offset: 0,
|
|
126
|
-
},
|
|
127
|
-
})
|
|
128
|
-
: undefined
|
|
129
|
-
|
|
130
|
-
if (
|
|
131
|
-
inlineObjectBeforePrefixFocusOffset &&
|
|
132
|
-
inlineObjectBeforePrefixFocusOffset.offset >
|
|
133
|
-
prefixOffsets.anchor.offset &&
|
|
134
|
-
inlineObjectBeforePrefixFocusOffset.offset <
|
|
135
|
-
prefixOffsets.focus.offset
|
|
136
|
-
) {
|
|
137
|
-
return false
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// If the suffix is more than one character, then we need to check if
|
|
142
|
-
// there is an inline object inside it
|
|
143
|
-
if (suffixOffsets.focus.offset - suffixOffsets.anchor.offset > 1) {
|
|
144
|
-
const previousInlineObject = getPreviousInlineObject(snapshot)
|
|
145
|
-
const previousInlineObjectOffset = previousInlineObject
|
|
146
|
-
? childSelectionPointToBlockOffset({
|
|
147
|
-
context: {
|
|
148
|
-
schema: snapshot.context.schema,
|
|
149
|
-
value: snapshot.context.value,
|
|
150
|
-
},
|
|
151
|
-
selectionPoint: {
|
|
152
|
-
path: previousInlineObject.path,
|
|
153
|
-
offset: 0,
|
|
154
|
-
},
|
|
155
|
-
})
|
|
156
|
-
: undefined
|
|
157
|
-
|
|
158
|
-
if (
|
|
159
|
-
previousInlineObjectOffset &&
|
|
160
|
-
previousInlineObjectOffset.offset > suffixOffsets.anchor.offset &&
|
|
161
|
-
previousInlineObjectOffset.offset < suffixOffsets.focus.offset
|
|
162
|
-
) {
|
|
163
|
-
return false
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return {
|
|
168
|
-
prefixOffsets,
|
|
169
|
-
suffixOffsets,
|
|
170
|
-
decorator,
|
|
171
|
-
}
|
|
172
|
-
},
|
|
173
|
-
actions: [
|
|
174
|
-
// Insert the text as usual in its own undo step
|
|
175
|
-
({event}) => [execute(event)],
|
|
176
|
-
(_, {prefixOffsets, suffixOffsets, decorator}) => [
|
|
177
|
-
// Decorate the text between the prefix and suffix
|
|
178
|
-
execute({
|
|
179
|
-
type: 'decorator.add',
|
|
180
|
-
decorator,
|
|
181
|
-
at: {
|
|
182
|
-
anchor: prefixOffsets.focus,
|
|
183
|
-
focus: suffixOffsets.anchor,
|
|
184
|
-
},
|
|
185
|
-
}),
|
|
186
|
-
// Delete the suffix
|
|
187
|
-
execute({
|
|
188
|
-
type: 'delete.text',
|
|
189
|
-
at: suffixOffsets,
|
|
190
|
-
}),
|
|
191
|
-
// Delete the prefix
|
|
192
|
-
execute({
|
|
193
|
-
type: 'delete.text',
|
|
194
|
-
at: prefixOffsets,
|
|
195
|
-
}),
|
|
196
|
-
// Toggle the decorator off so the next inserted text isn't emphasized
|
|
197
|
-
execute({
|
|
198
|
-
type: 'decorator.remove',
|
|
199
|
-
decorator,
|
|
200
|
-
}),
|
|
201
|
-
effect(() => {
|
|
202
|
-
config.onDecorate({
|
|
203
|
-
...suffixOffsets.anchor,
|
|
204
|
-
offset:
|
|
205
|
-
suffixOffsets.anchor.offset -
|
|
206
|
-
(prefixOffsets.focus.offset - prefixOffsets.anchor.offset),
|
|
207
|
-
})
|
|
208
|
-
}),
|
|
209
|
-
],
|
|
210
|
-
],
|
|
211
|
-
})
|
|
212
|
-
}
|