@portabletext/editor 3.0.8 → 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 +44 -25
- 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 +69 -72
- package/lib/index.js.map +1 -1
- package/lib/utils/index.d.ts +1 -1
- package/package.json +1 -1
- package/src/editor/Editable.tsx +5 -4
- 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.ts +64 -39
- 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
package/lib/utils/index.d.ts
CHANGED
|
@@ -115,7 +115,7 @@ declare function getTextBlockText(block: PortableTextTextBlock): string;
|
|
|
115
115
|
/**
|
|
116
116
|
* @public
|
|
117
117
|
*/
|
|
118
|
-
declare function isEmptyTextBlock(context: Pick<EditorContext, 'schema'>, block: PortableTextBlock): boolean;
|
|
118
|
+
declare function isEmptyTextBlock(context: Pick<EditorContext, 'schema'>, block: PortableTextBlock | unknown): boolean;
|
|
119
119
|
/**
|
|
120
120
|
* @public
|
|
121
121
|
*/
|
package/package.json
CHANGED
package/src/editor/Editable.tsx
CHANGED
|
@@ -25,7 +25,6 @@ import {getEventPosition} from '../internal-utils/event-position'
|
|
|
25
25
|
import {normalizeSelection} from '../internal-utils/selection'
|
|
26
26
|
import {slateRangeToSelection} from '../internal-utils/slate-utils'
|
|
27
27
|
import {toSlateRange} from '../internal-utils/to-slate-range'
|
|
28
|
-
import {isEqualToEmptyEditor} from '../internal-utils/values'
|
|
29
28
|
import type {
|
|
30
29
|
EditorSelection,
|
|
31
30
|
OnCopyFn,
|
|
@@ -41,6 +40,7 @@ import type {
|
|
|
41
40
|
ScrollSelectionIntoViewFunction,
|
|
42
41
|
} from '../types/editor'
|
|
43
42
|
import type {HotkeyOptions} from '../types/options'
|
|
43
|
+
import {isEmptyTextBlock} from '../utils'
|
|
44
44
|
import {parseBlocks} from '../utils/parse-blocks'
|
|
45
45
|
import {RenderElement} from './components/render-element'
|
|
46
46
|
import {RenderLeaf} from './components/render-leaf'
|
|
@@ -526,9 +526,10 @@ export const PortableTextEditable = forwardRef<
|
|
|
526
526
|
|
|
527
527
|
if (
|
|
528
528
|
!slateEditor.selection &&
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
editorActor.getSnapshot().context
|
|
529
|
+
slateEditor.children.length === 1 &&
|
|
530
|
+
isEmptyTextBlock(
|
|
531
|
+
editorActor.getSnapshot().context,
|
|
532
|
+
slateEditor.value.at(0),
|
|
532
533
|
)
|
|
533
534
|
) {
|
|
534
535
|
Transforms.select(slateEditor, Editor.start(slateEditor, []))
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import {applyAll, set} from '@portabletext/patches'
|
|
2
|
+
import {isTextBlock} from '@portabletext/schema'
|
|
3
|
+
import {Transforms, type Node} from 'slate'
|
|
4
|
+
import {parseMarkDefs} from '../utils/parse-blocks'
|
|
4
5
|
import type {BehaviorOperationImplementation} from './behavior.operations'
|
|
5
6
|
|
|
6
7
|
export const blockSetOperationImplementation: BehaviorOperationImplementation<
|
|
@@ -14,36 +15,90 @@ export const blockSetOperationImplementation: BehaviorOperationImplementation<
|
|
|
14
15
|
)
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
const
|
|
18
|
+
const slateBlock = operation.editor.children.at(blockIndex)
|
|
18
19
|
|
|
19
|
-
if (!
|
|
20
|
+
if (!slateBlock) {
|
|
20
21
|
throw new Error(`Unable to find block at ${JSON.stringify(operation.at)}`)
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
if (isTextBlock(context, slateBlock)) {
|
|
25
|
+
const filteredProps: Record<string, unknown> = {}
|
|
24
26
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
for (const key of Object.keys(operation.props)) {
|
|
28
|
+
if (key === '_type' || key === 'children') {
|
|
29
|
+
continue
|
|
30
|
+
}
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
32
|
+
if (key === 'style') {
|
|
33
|
+
if (
|
|
34
|
+
context.schema.styles.some(
|
|
35
|
+
(style) => style.name === operation.props[key],
|
|
36
|
+
)
|
|
37
|
+
) {
|
|
38
|
+
filteredProps[key] = operation.props[key]
|
|
39
|
+
}
|
|
40
|
+
continue
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (key === 'listItem') {
|
|
44
|
+
if (
|
|
45
|
+
context.schema.lists.some(
|
|
46
|
+
(list) => list.name === operation.props[key],
|
|
47
|
+
)
|
|
48
|
+
) {
|
|
49
|
+
filteredProps[key] = operation.props[key]
|
|
50
|
+
}
|
|
51
|
+
continue
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (key === 'level') {
|
|
55
|
+
filteredProps[key] = operation.props[key]
|
|
56
|
+
continue
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (key === 'markDefs') {
|
|
60
|
+
const {markDefs} = parseMarkDefs({
|
|
61
|
+
context,
|
|
62
|
+
markDefs: operation.props[key],
|
|
63
|
+
options: {validateFields: true},
|
|
64
|
+
})
|
|
65
|
+
filteredProps[key] = markDefs
|
|
66
|
+
continue
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (context.schema.block.fields?.some((field) => field.name === key)) {
|
|
70
|
+
filteredProps[key] = operation.props[key]
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
Transforms.setNodes(operation.editor, filteredProps, {at: [blockIndex]})
|
|
75
|
+
} else {
|
|
76
|
+
const schemaDefinition = context.schema.blockObjects.find(
|
|
77
|
+
(definition) => definition.name === slateBlock._type,
|
|
78
|
+
)
|
|
79
|
+
const filteredProps: Record<string, unknown> = {}
|
|
80
|
+
|
|
81
|
+
for (const key of Object.keys(operation.props)) {
|
|
82
|
+
if (key === '_type') {
|
|
83
|
+
continue
|
|
84
|
+
}
|
|
43
85
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
86
|
+
if (key === '_key') {
|
|
87
|
+
filteredProps[key] = operation.props[key]
|
|
88
|
+
continue
|
|
89
|
+
}
|
|
47
90
|
|
|
48
|
-
|
|
91
|
+
if (schemaDefinition?.fields.some((field) => field.name === key)) {
|
|
92
|
+
filteredProps[key] = operation.props[key]
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const patches = Object.entries(filteredProps).map(([key, value]) =>
|
|
97
|
+
key === '_key' ? set(value, ['_key']) : set(value, ['value', key]),
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
const updatedSlateBlock = applyAll(slateBlock, patches) as Partial<Node>
|
|
101
|
+
|
|
102
|
+
Transforms.setNodes(operation.editor, updatedSlateBlock, {at: [blockIndex]})
|
|
103
|
+
}
|
|
49
104
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
+
import {applyAll, set, unset} from '@portabletext/patches'
|
|
1
2
|
import {isTextBlock} from '@portabletext/schema'
|
|
2
|
-
import {
|
|
3
|
-
import {Transforms} from 'slate'
|
|
4
|
-
import {parseBlock} from '../utils/parse-blocks'
|
|
3
|
+
import {Transforms, type Node} from 'slate'
|
|
5
4
|
import type {BehaviorOperationImplementation} from './behavior.operations'
|
|
6
5
|
|
|
7
6
|
export const blockUnsetOperationImplementation: BehaviorOperationImplementation<
|
|
@@ -14,73 +13,42 @@ export const blockUnsetOperationImplementation: BehaviorOperationImplementation<
|
|
|
14
13
|
throw new Error(`Unable to find block index for block key ${blockKey}`)
|
|
15
14
|
}
|
|
16
15
|
|
|
17
|
-
const
|
|
18
|
-
blockIndex !== undefined
|
|
16
|
+
const slateBlock =
|
|
17
|
+
blockIndex !== undefined
|
|
18
|
+
? operation.editor.children.at(blockIndex)
|
|
19
|
+
: undefined
|
|
19
20
|
|
|
20
|
-
if (!
|
|
21
|
+
if (!slateBlock) {
|
|
21
22
|
throw new Error(`Unable to find block at ${JSON.stringify(operation.at)}`)
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
if (isTextBlock(context,
|
|
25
|
-
const propsToRemove = operation.props.filter(
|
|
25
|
+
if (isTextBlock(context, slateBlock)) {
|
|
26
|
+
const propsToRemove = operation.props.filter(
|
|
27
|
+
(prop) => prop !== '_type' && prop !== '_key' && prop !== 'children',
|
|
28
|
+
)
|
|
26
29
|
|
|
27
|
-
|
|
28
|
-
context,
|
|
29
|
-
block: omit(block, propsToRemove),
|
|
30
|
-
options: {
|
|
31
|
-
normalize: false,
|
|
32
|
-
removeUnusedMarkDefs: true,
|
|
33
|
-
validateFields: true,
|
|
34
|
-
},
|
|
35
|
-
})
|
|
30
|
+
Transforms.unsetNodes(operation.editor, propsToRemove, {at: [blockIndex]})
|
|
36
31
|
|
|
37
|
-
if (
|
|
38
|
-
|
|
39
|
-
|
|
32
|
+
if (operation.props.includes('_key')) {
|
|
33
|
+
Transforms.setNodes(
|
|
34
|
+
operation.editor,
|
|
35
|
+
{_key: context.keyGenerator()},
|
|
36
|
+
{at: [blockIndex]},
|
|
40
37
|
)
|
|
41
38
|
}
|
|
42
39
|
|
|
43
|
-
const propsToSet: Record<string, unknown> = {}
|
|
44
|
-
|
|
45
|
-
for (const prop of propsToRemove) {
|
|
46
|
-
if (!(prop in updatedTextBlock)) {
|
|
47
|
-
propsToSet[prop] = undefined
|
|
48
|
-
} else {
|
|
49
|
-
propsToSet[prop] = (updatedTextBlock as Record<string, unknown>)[prop]
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
Transforms.setNodes(operation.editor, propsToSet, {at: [blockIndex]})
|
|
54
|
-
|
|
55
40
|
return
|
|
56
41
|
}
|
|
57
42
|
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
normalize: false,
|
|
66
|
-
removeUnusedMarkDefs: true,
|
|
67
|
-
validateFields: true,
|
|
68
|
-
},
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
if (!updatedBlockObject) {
|
|
72
|
-
throw new Error(`Unable to update block at ${JSON.stringify(operation.at)}`)
|
|
73
|
-
}
|
|
43
|
+
const patches = operation.props.flatMap((key) =>
|
|
44
|
+
key === '_type'
|
|
45
|
+
? []
|
|
46
|
+
: key === '_key'
|
|
47
|
+
? set(context.keyGenerator(), ['_key'])
|
|
48
|
+
: unset(['value', key]),
|
|
49
|
+
)
|
|
74
50
|
|
|
75
|
-
const
|
|
51
|
+
const updatedSlateBlock = applyAll(slateBlock, patches) as Partial<Node>
|
|
76
52
|
|
|
77
|
-
Transforms.setNodes(
|
|
78
|
-
operation.editor,
|
|
79
|
-
{
|
|
80
|
-
_type,
|
|
81
|
-
_key,
|
|
82
|
-
value: props,
|
|
83
|
-
},
|
|
84
|
-
{at: [blockIndex]},
|
|
85
|
-
)
|
|
53
|
+
Transforms.setNodes(operation.editor, updatedSlateBlock, {at: [blockIndex]})
|
|
86
54
|
}
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
import {DOMEditor} from 'slate-dom'
|
|
13
13
|
import {getFocusBlock, getFocusChild} from '../internal-utils/slate-utils'
|
|
14
14
|
import {toSlateRange} from '../internal-utils/to-slate-range'
|
|
15
|
-
import {
|
|
15
|
+
import {toSlateBlock} from '../internal-utils/values'
|
|
16
16
|
import type {EditorSelection, PortableTextSlateEditor} from '../types/editor'
|
|
17
17
|
import {parseBlock} from '../utils/parse-blocks'
|
|
18
18
|
import {isEmptyTextBlock} from '../utils/util.is-empty-text-block'
|
|
@@ -121,7 +121,7 @@ export function insertBlock(options: {
|
|
|
121
121
|
} else {
|
|
122
122
|
// placement === 'auto'
|
|
123
123
|
|
|
124
|
-
if (
|
|
124
|
+
if (isEmptyTextBlock(context, endBlock)) {
|
|
125
125
|
Transforms.insertNodes(editor, [block], {
|
|
126
126
|
at: endBlockPath,
|
|
127
127
|
select: false,
|
|
@@ -234,7 +234,7 @@ export function insertBlock(options: {
|
|
|
234
234
|
Transforms.select(editor, atAfterInsert)
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
-
if (
|
|
237
|
+
if (isEmptyTextBlock(context, focusBlock)) {
|
|
238
238
|
Transforms.removeNodes(editor, {at: focusBlockPath})
|
|
239
239
|
}
|
|
240
240
|
|
|
@@ -244,7 +244,7 @@ export function insertBlock(options: {
|
|
|
244
244
|
if (editor.isTextBlock(endBlock) && editor.isTextBlock(block)) {
|
|
245
245
|
const selectionStartPoint = Range.start(at)
|
|
246
246
|
|
|
247
|
-
if (
|
|
247
|
+
if (isEmptyTextBlock(context, endBlock)) {
|
|
248
248
|
Transforms.insertNodes(editor, [block], {
|
|
249
249
|
at: endBlockPath,
|
|
250
250
|
select: false,
|
|
@@ -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)
|
|
@@ -279,6 +244,66 @@ export function parseTextBlock({
|
|
|
279
244
|
return parsedBlock
|
|
280
245
|
}
|
|
281
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
|
+
|
|
282
307
|
export function parseChild({
|
|
283
308
|
child,
|
|
284
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
|
-
}
|