@portabletext/editor 1.7.1 → 1.9.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/lib/index.d.mts +97 -23
- package/lib/index.d.ts +97 -23
- package/lib/index.esm.js +244 -73
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +244 -73
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +244 -73
- package/lib/index.mjs.map +1 -1
- package/package.json +10 -10
- package/src/editor/Editable.tsx +46 -31
- package/src/editor/behavior/behavior.action.insert-span.ts +48 -0
- package/src/editor/behavior/behavior.actions.ts +29 -1
- package/src/editor/behavior/behavior.links.ts +91 -0
- package/src/editor/behavior/behavior.markdown.ts +78 -11
- package/src/editor/behavior/behavior.types.ts +18 -0
- package/src/editor/plugins/createWithEditableAPI.ts +91 -71
- package/src/editor/plugins/createWithPortableTextMarkModel.ts +6 -2
- package/src/editor/plugins/with-plugins.ts +7 -2
- package/src/index.ts +5 -1
- package/src/types/options.ts +0 -9
- package/src/utils/__tests__/operationToPatches.test.ts +1 -1
- package/src/utils/__tests__/patchToOperations.test.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@portabletext/editor",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "Portable Text Editor made in React",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -58,11 +58,11 @@
|
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@portabletext/toolkit": "^2.0.16",
|
|
61
|
-
"@sanity/block-tools": "^3.64.
|
|
61
|
+
"@sanity/block-tools": "^3.64.1",
|
|
62
62
|
"@sanity/diff-match-patch": "^3.1.1",
|
|
63
|
-
"@sanity/pkg-utils": "^6.11.
|
|
64
|
-
"@sanity/schema": "^3.64.
|
|
65
|
-
"@sanity/types": "^3.64.
|
|
63
|
+
"@sanity/pkg-utils": "^6.11.11",
|
|
64
|
+
"@sanity/schema": "^3.64.1",
|
|
65
|
+
"@sanity/types": "^3.64.1",
|
|
66
66
|
"@testing-library/jest-dom": "^6.6.3",
|
|
67
67
|
"@testing-library/react": "^16.0.1",
|
|
68
68
|
"@types/debug": "^4.1.5",
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"@typescript-eslint/eslint-plugin": "^8.14.0",
|
|
74
74
|
"@typescript-eslint/parser": "^8.14.0",
|
|
75
75
|
"@vitejs/plugin-react": "^4.3.3",
|
|
76
|
-
"@vitest/browser": "^2.1.
|
|
76
|
+
"@vitest/browser": "^2.1.5",
|
|
77
77
|
"babel-plugin-react-compiler": "19.0.0-beta-a7bf2bd-20241110",
|
|
78
78
|
"eslint": "8.57.1",
|
|
79
79
|
"eslint-plugin-react-compiler": "19.0.0-beta-a7bf2bd-20241110",
|
|
@@ -85,14 +85,14 @@
|
|
|
85
85
|
"styled-components": "^6.1.13",
|
|
86
86
|
"typescript": "5.6.3",
|
|
87
87
|
"vite": "^5.4.11",
|
|
88
|
-
"vitest": "^2.1.
|
|
88
|
+
"vitest": "^2.1.5",
|
|
89
89
|
"vitest-browser-react": "^0.0.3",
|
|
90
90
|
"@sanity/gherkin-driver": "^0.0.1"
|
|
91
91
|
},
|
|
92
92
|
"peerDependencies": {
|
|
93
|
-
"@sanity/block-tools": "^3.64.
|
|
94
|
-
"@sanity/schema": "^3.64.
|
|
95
|
-
"@sanity/types": "^3.64.
|
|
93
|
+
"@sanity/block-tools": "^3.64.1",
|
|
94
|
+
"@sanity/schema": "^3.64.1",
|
|
95
|
+
"@sanity/types": "^3.64.1",
|
|
96
96
|
"react": "^16.9 || ^17 || ^18",
|
|
97
97
|
"rxjs": "^7.8.1",
|
|
98
98
|
"styled-components": "^6.1.13"
|
package/src/editor/Editable.tsx
CHANGED
|
@@ -428,16 +428,6 @@ export const PortableTextEditable = forwardRef<
|
|
|
428
428
|
// Handle incoming pasting events in the editor
|
|
429
429
|
const handlePaste = useCallback(
|
|
430
430
|
(event: ClipboardEvent<HTMLDivElement>): Promise<void> | void => {
|
|
431
|
-
event.preventDefault()
|
|
432
|
-
if (!slateEditor.selection) {
|
|
433
|
-
return
|
|
434
|
-
}
|
|
435
|
-
if (!onPaste) {
|
|
436
|
-
debug('Pasting normally')
|
|
437
|
-
slateEditor.insertData(event.clipboardData)
|
|
438
|
-
return
|
|
439
|
-
}
|
|
440
|
-
|
|
441
431
|
const value = PortableTextEditor.getValue(portableTextEditor)
|
|
442
432
|
const ptRange = toPortableTextRange(
|
|
443
433
|
value,
|
|
@@ -445,19 +435,21 @@ export const PortableTextEditable = forwardRef<
|
|
|
445
435
|
schemaTypes,
|
|
446
436
|
)
|
|
447
437
|
const path = ptRange?.focus.path || []
|
|
448
|
-
const onPasteResult = onPaste({event, value, path, schemaTypes})
|
|
438
|
+
const onPasteResult = onPaste?.({event, value, path, schemaTypes})
|
|
439
|
+
|
|
440
|
+
if (onPasteResult || !slateEditor.selection) {
|
|
441
|
+
event.preventDefault()
|
|
449
442
|
|
|
450
|
-
if (onPasteResult === undefined) {
|
|
451
|
-
debug('No result from custom paste handler, pasting normally')
|
|
452
|
-
slateEditor.insertData(event.clipboardData)
|
|
453
|
-
} else {
|
|
454
443
|
// Resolve it as promise (can be either async promise or sync return value)
|
|
455
444
|
editorActor.send({type: 'loading'})
|
|
445
|
+
|
|
456
446
|
Promise.resolve(onPasteResult)
|
|
457
447
|
.then((result) => {
|
|
458
448
|
debug('Custom paste function from client resolved', result)
|
|
449
|
+
|
|
459
450
|
if (!result || !result.insert) {
|
|
460
451
|
debug('No result from custom paste handler, pasting normally')
|
|
452
|
+
|
|
461
453
|
slateEditor.insertData(event.clipboardData)
|
|
462
454
|
} else if (result.insert) {
|
|
463
455
|
slateEditor.insertFragment(
|
|
@@ -474,12 +466,26 @@ export const PortableTextEditable = forwardRef<
|
|
|
474
466
|
})
|
|
475
467
|
.catch((error) => {
|
|
476
468
|
console.error(error)
|
|
469
|
+
|
|
477
470
|
return error
|
|
478
471
|
})
|
|
479
472
|
.finally(() => {
|
|
480
473
|
editorActor.send({type: 'done loading'})
|
|
481
474
|
})
|
|
475
|
+
} else if (event.nativeEvent.clipboardData) {
|
|
476
|
+
event.preventDefault()
|
|
477
|
+
|
|
478
|
+
editorActor.send({
|
|
479
|
+
type: 'behavior event',
|
|
480
|
+
behaviorEvent: {
|
|
481
|
+
type: 'paste',
|
|
482
|
+
clipboardData: event.nativeEvent.clipboardData,
|
|
483
|
+
},
|
|
484
|
+
editor: slateEditor,
|
|
485
|
+
})
|
|
482
486
|
}
|
|
487
|
+
|
|
488
|
+
debug('No result from custom paste handler, pasting normally')
|
|
483
489
|
},
|
|
484
490
|
[editorActor, onPaste, portableTextEditor, schemaTypes, slateEditor],
|
|
485
491
|
)
|
|
@@ -515,23 +521,32 @@ export const PortableTextEditable = forwardRef<
|
|
|
515
521
|
if (onClick) {
|
|
516
522
|
onClick(event)
|
|
517
523
|
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
const node = Node.descendant(slateEditor, path.slice(0, 1)) as
|
|
524
|
+
|
|
525
|
+
const focusBlockPath = slateEditor.selection
|
|
526
|
+
? slateEditor.selection.focus.path.slice(0, 1)
|
|
527
|
+
: undefined
|
|
528
|
+
const focusBlock = focusBlockPath
|
|
529
|
+
? (Node.descendant(slateEditor, focusBlockPath) as
|
|
525
530
|
| SlateTextBlock
|
|
526
|
-
| VoidElement
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
531
|
+
| VoidElement)
|
|
532
|
+
: undefined
|
|
533
|
+
const [_, lastNodePath] = Node.last(slateEditor, [])
|
|
534
|
+
const lastBlockPath = lastNodePath.slice(0, 1)
|
|
535
|
+
const lastNodeFocused = focusBlockPath
|
|
536
|
+
? Path.equals(lastBlockPath, focusBlockPath)
|
|
537
|
+
: false
|
|
538
|
+
const lastBlockIsVoid = focusBlock
|
|
539
|
+
? !slateEditor.isTextBlock(focusBlock)
|
|
540
|
+
: false
|
|
541
|
+
const collapsedSelection =
|
|
542
|
+
slateEditor.selection && SlateRange.isCollapsed(slateEditor.selection)
|
|
543
|
+
|
|
544
|
+
if (collapsedSelection && lastNodeFocused && lastBlockIsVoid) {
|
|
545
|
+
Transforms.insertNodes(
|
|
546
|
+
slateEditor,
|
|
547
|
+
slateEditor.pteCreateTextBlock({decorators: []}),
|
|
548
|
+
)
|
|
549
|
+
slateEditor.onChange()
|
|
535
550
|
}
|
|
536
551
|
},
|
|
537
552
|
[onClick, slateEditor],
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {Editor, Transforms} from 'slate'
|
|
2
|
+
import type {BehaviourActionImplementation} from './behavior.actions'
|
|
3
|
+
|
|
4
|
+
export const insertSpanActionImplementation: BehaviourActionImplementation<
|
|
5
|
+
'insert span'
|
|
6
|
+
> = ({context, action}) => {
|
|
7
|
+
if (!action.editor.selection) {
|
|
8
|
+
console.error('Unable to perform action without selection', action)
|
|
9
|
+
return
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const [focusBlock, focusBlockPath] = Array.from(
|
|
13
|
+
Editor.nodes(action.editor, {
|
|
14
|
+
at: action.editor.selection.focus.path,
|
|
15
|
+
match: (node) => action.editor.isTextBlock(node),
|
|
16
|
+
}),
|
|
17
|
+
)[0] ?? [undefined, undefined]
|
|
18
|
+
|
|
19
|
+
if (!focusBlock || !focusBlockPath) {
|
|
20
|
+
console.error('Unable to perform action without focus block', action)
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const markDefs = focusBlock.markDefs ?? []
|
|
25
|
+
const annotations = action.annotations
|
|
26
|
+
? action.annotations.map((annotation) => ({
|
|
27
|
+
_type: annotation.name,
|
|
28
|
+
_key: context.keyGenerator(),
|
|
29
|
+
...annotation.value,
|
|
30
|
+
}))
|
|
31
|
+
: undefined
|
|
32
|
+
|
|
33
|
+
if (annotations && annotations.length > 0) {
|
|
34
|
+
Transforms.setNodes(action.editor, {
|
|
35
|
+
markDefs: [...markDefs, ...annotations],
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
Transforms.insertNodes(action.editor, {
|
|
40
|
+
_type: 'span',
|
|
41
|
+
_key: context.keyGenerator(),
|
|
42
|
+
text: action.text,
|
|
43
|
+
marks: [
|
|
44
|
+
...(annotations?.map((annotation) => annotation._key) ?? []),
|
|
45
|
+
...(action.decorators ?? []),
|
|
46
|
+
],
|
|
47
|
+
})
|
|
48
|
+
}
|
|
@@ -10,6 +10,7 @@ import type {PortableTextMemberSchemaTypes} from '../../types/editor'
|
|
|
10
10
|
import {toSlateRange} from '../../utils/ranges'
|
|
11
11
|
import {
|
|
12
12
|
addAnnotationActionImplementation,
|
|
13
|
+
insertBlockObjectActionImplementation,
|
|
13
14
|
removeAnnotationActionImplementation,
|
|
14
15
|
toggleAnnotationActionImplementation,
|
|
15
16
|
} from '../plugins/createWithEditableAPI'
|
|
@@ -22,6 +23,7 @@ import {
|
|
|
22
23
|
insertBreakActionImplementation,
|
|
23
24
|
insertSoftBreakActionImplementation,
|
|
24
25
|
} from './behavior.action.insert-break'
|
|
26
|
+
import {insertSpanActionImplementation} from './behavior.action.insert-span'
|
|
25
27
|
import type {
|
|
26
28
|
BehaviorAction,
|
|
27
29
|
BehaviorEvent,
|
|
@@ -112,8 +114,10 @@ const behaviorActionImplementations: BehaviourActionImplementations = {
|
|
|
112
114
|
})
|
|
113
115
|
}
|
|
114
116
|
},
|
|
117
|
+
'insert block object': insertBlockObjectActionImplementation,
|
|
115
118
|
'insert break': insertBreakActionImplementation,
|
|
116
119
|
'insert soft break': insertSoftBreakActionImplementation,
|
|
120
|
+
'insert span': insertSpanActionImplementation,
|
|
117
121
|
'insert text': ({action}) => {
|
|
118
122
|
insertText(action.editor, action.text)
|
|
119
123
|
},
|
|
@@ -135,6 +139,9 @@ const behaviorActionImplementations: BehaviourActionImplementations = {
|
|
|
135
139
|
'effect': ({action}) => {
|
|
136
140
|
action.effect()
|
|
137
141
|
},
|
|
142
|
+
'paste': ({action}) => {
|
|
143
|
+
action.editor.insertData(action.clipboardData)
|
|
144
|
+
},
|
|
138
145
|
'select': ({action}) => {
|
|
139
146
|
const newSelection = toSlateRange(action.selection, action.editor)
|
|
140
147
|
|
|
@@ -169,6 +176,20 @@ export function performAction({
|
|
|
169
176
|
})
|
|
170
177
|
break
|
|
171
178
|
}
|
|
179
|
+
case 'insert block object': {
|
|
180
|
+
behaviorActionImplementations['insert block object']({
|
|
181
|
+
context,
|
|
182
|
+
action,
|
|
183
|
+
})
|
|
184
|
+
break
|
|
185
|
+
}
|
|
186
|
+
case 'insert span': {
|
|
187
|
+
behaviorActionImplementations['insert span']({
|
|
188
|
+
context,
|
|
189
|
+
action,
|
|
190
|
+
})
|
|
191
|
+
break
|
|
192
|
+
}
|
|
172
193
|
case 'insert text block': {
|
|
173
194
|
behaviorActionImplementations['insert text block']({
|
|
174
195
|
context,
|
|
@@ -302,11 +323,18 @@ function performDefaultAction({
|
|
|
302
323
|
})
|
|
303
324
|
break
|
|
304
325
|
}
|
|
305
|
-
|
|
326
|
+
case 'insert text': {
|
|
306
327
|
behaviorActionImplementations['insert text']({
|
|
307
328
|
context,
|
|
308
329
|
action,
|
|
309
330
|
})
|
|
331
|
+
break
|
|
332
|
+
}
|
|
333
|
+
default: {
|
|
334
|
+
behaviorActionImplementations.paste({
|
|
335
|
+
context,
|
|
336
|
+
action,
|
|
337
|
+
})
|
|
310
338
|
}
|
|
311
339
|
}
|
|
312
340
|
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type {PortableTextMemberSchemaTypes} from '../../types/editor'
|
|
2
|
+
import {defineBehavior} from './behavior.types'
|
|
3
|
+
import {getFocusSpan, selectionIsCollapsed} from './behavior.utils'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @alpha
|
|
7
|
+
*/
|
|
8
|
+
export type LinkBehaviorsConfig = {
|
|
9
|
+
mapLinkAnnotation?: (config: {
|
|
10
|
+
schema: PortableTextMemberSchemaTypes
|
|
11
|
+
url: string
|
|
12
|
+
}) => {name: string; value: {[prop: string]: unknown}} | undefined
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @alpha
|
|
17
|
+
*/
|
|
18
|
+
export function createLinkBehaviors(config: LinkBehaviorsConfig) {
|
|
19
|
+
const pasteLinkOnSelection = defineBehavior({
|
|
20
|
+
on: 'paste',
|
|
21
|
+
guard: ({context, event}) => {
|
|
22
|
+
const selectionCollapsed = selectionIsCollapsed(context)
|
|
23
|
+
const text = event.clipboardData.getData('text/plain')
|
|
24
|
+
const url = looksLikeUrl(text) ? text : undefined
|
|
25
|
+
const annotation =
|
|
26
|
+
url !== undefined
|
|
27
|
+
? config.mapLinkAnnotation?.({url, schema: context.schema})
|
|
28
|
+
: undefined
|
|
29
|
+
|
|
30
|
+
if (annotation && !selectionCollapsed) {
|
|
31
|
+
return {annotation}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return false
|
|
35
|
+
},
|
|
36
|
+
actions: [
|
|
37
|
+
(_, {annotation}) => [
|
|
38
|
+
{
|
|
39
|
+
type: 'annotation.add',
|
|
40
|
+
annotation,
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
],
|
|
44
|
+
})
|
|
45
|
+
const pasteLinkAtCaret = defineBehavior({
|
|
46
|
+
on: 'paste',
|
|
47
|
+
guard: ({context, event}) => {
|
|
48
|
+
const focusSpan = getFocusSpan(context)
|
|
49
|
+
const selectionCollapsed = selectionIsCollapsed(context)
|
|
50
|
+
|
|
51
|
+
if (!focusSpan || !selectionCollapsed) {
|
|
52
|
+
return false
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const text = event.clipboardData.getData('text/plain')
|
|
56
|
+
const url = looksLikeUrl(text) ? text : undefined
|
|
57
|
+
const annotation =
|
|
58
|
+
url !== undefined
|
|
59
|
+
? config.mapLinkAnnotation?.({url, schema: context.schema})
|
|
60
|
+
: undefined
|
|
61
|
+
|
|
62
|
+
if (url && annotation && selectionCollapsed) {
|
|
63
|
+
return {focusSpan, annotation, url}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return false
|
|
67
|
+
},
|
|
68
|
+
actions: [
|
|
69
|
+
(_, {annotation, url}) => [
|
|
70
|
+
{
|
|
71
|
+
type: 'insert span',
|
|
72
|
+
text: url,
|
|
73
|
+
annotations: [annotation],
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
],
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
const linkBehaviors = [pasteLinkOnSelection, pasteLinkAtCaret]
|
|
80
|
+
|
|
81
|
+
return linkBehaviors
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function looksLikeUrl(text: string) {
|
|
85
|
+
let looksLikeUrl = false
|
|
86
|
+
try {
|
|
87
|
+
new URL(text)
|
|
88
|
+
looksLikeUrl = true
|
|
89
|
+
} catch {}
|
|
90
|
+
return looksLikeUrl
|
|
91
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {isPortableTextSpan} from '@portabletext/toolkit'
|
|
1
2
|
import type {PortableTextMemberSchemaTypes} from '../../types/editor'
|
|
2
3
|
import {defineBehavior} from './behavior.types'
|
|
3
4
|
import {
|
|
@@ -10,18 +11,23 @@ import {
|
|
|
10
11
|
* @alpha
|
|
11
12
|
*/
|
|
12
13
|
export type MarkdownBehaviorsConfig = {
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
mapBreakObject?: (
|
|
15
|
+
schema: PortableTextMemberSchemaTypes,
|
|
16
|
+
) => {name: string; value?: {[prop: string]: unknown}} | undefined
|
|
17
|
+
mapDefaultStyle?: (
|
|
18
|
+
schema: PortableTextMemberSchemaTypes,
|
|
19
|
+
) => string | undefined
|
|
20
|
+
mapHeadingStyle?: (
|
|
15
21
|
schema: PortableTextMemberSchemaTypes,
|
|
16
22
|
level: number,
|
|
17
23
|
) => string | undefined
|
|
18
|
-
mapBlockquoteStyle
|
|
24
|
+
mapBlockquoteStyle?: (
|
|
19
25
|
schema: PortableTextMemberSchemaTypes,
|
|
20
26
|
) => string | undefined
|
|
21
|
-
mapUnorderedListStyle
|
|
27
|
+
mapUnorderedListStyle?: (
|
|
22
28
|
schema: PortableTextMemberSchemaTypes,
|
|
23
29
|
) => string | undefined
|
|
24
|
-
mapOrderedListStyle
|
|
30
|
+
mapOrderedListStyle?: (
|
|
25
31
|
schema: PortableTextMemberSchemaTypes,
|
|
26
32
|
) => string | undefined
|
|
27
33
|
}
|
|
@@ -49,7 +55,7 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
|
|
|
49
55
|
|
|
50
56
|
const caretAtTheEndOfQuote = context.selection.focus.offset === 1
|
|
51
57
|
const looksLikeMarkdownQuote = /^>/.test(focusSpan.node.text)
|
|
52
|
-
const blockquoteStyle = config.mapBlockquoteStyle(context.schema)
|
|
58
|
+
const blockquoteStyle = config.mapBlockquoteStyle?.(context.schema)
|
|
53
59
|
|
|
54
60
|
if (
|
|
55
61
|
caretAtTheEndOfQuote &&
|
|
@@ -95,6 +101,66 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
|
|
|
95
101
|
],
|
|
96
102
|
],
|
|
97
103
|
})
|
|
104
|
+
const automaticBreak = defineBehavior({
|
|
105
|
+
on: 'insert text',
|
|
106
|
+
guard: ({context, event}) => {
|
|
107
|
+
const isDash = event.text === '-'
|
|
108
|
+
|
|
109
|
+
if (!isDash) {
|
|
110
|
+
return false
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const breakObject = config.mapBreakObject?.(context.schema)
|
|
114
|
+
const focusBlock = getFocusTextBlock(context)
|
|
115
|
+
const selectionCollapsed = selectionIsCollapsed(context)
|
|
116
|
+
|
|
117
|
+
if (!breakObject || !focusBlock || !selectionCollapsed) {
|
|
118
|
+
return false
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const onlyText = focusBlock.node.children.every(isPortableTextSpan)
|
|
122
|
+
const blockText = focusBlock.node.children
|
|
123
|
+
.map((child) => child.text ?? '')
|
|
124
|
+
.join('')
|
|
125
|
+
|
|
126
|
+
if (onlyText && blockText === '--') {
|
|
127
|
+
return {breakObject, focusBlock}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return false
|
|
131
|
+
},
|
|
132
|
+
actions: [
|
|
133
|
+
() => [
|
|
134
|
+
{
|
|
135
|
+
type: 'insert text',
|
|
136
|
+
text: '-',
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
(_, {breakObject, focusBlock}) => [
|
|
140
|
+
{
|
|
141
|
+
type: 'insert block object',
|
|
142
|
+
...breakObject,
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
type: 'delete',
|
|
146
|
+
selection: {
|
|
147
|
+
anchor: {
|
|
148
|
+
path: focusBlock.path,
|
|
149
|
+
offset: 0,
|
|
150
|
+
},
|
|
151
|
+
focus: {
|
|
152
|
+
path: focusBlock.path,
|
|
153
|
+
offset: 0,
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
type: 'insert text block',
|
|
159
|
+
decorators: [],
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
],
|
|
163
|
+
})
|
|
98
164
|
const automaticHeadingOnSpace = defineBehavior({
|
|
99
165
|
on: 'insert text',
|
|
100
166
|
guard: ({context, event}) => {
|
|
@@ -125,7 +191,7 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
|
|
|
125
191
|
|
|
126
192
|
const headingStyle =
|
|
127
193
|
headingLevel !== undefined
|
|
128
|
-
? config.mapHeadingStyle(context.schema, headingLevel)
|
|
194
|
+
? config.mapHeadingStyle?.(context.schema, headingLevel)
|
|
129
195
|
: undefined
|
|
130
196
|
|
|
131
197
|
if (headingLevel !== undefined && headingStyle !== undefined) {
|
|
@@ -188,7 +254,7 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
|
|
|
188
254
|
focusTextBlock.node.children[0]._key === focusSpan.node._key &&
|
|
189
255
|
context.selection.focus.offset === 0
|
|
190
256
|
|
|
191
|
-
const defaultStyle = config.mapDefaultStyle(context.schema)
|
|
257
|
+
const defaultStyle = config.mapDefaultStyle?.(context.schema)
|
|
192
258
|
|
|
193
259
|
if (
|
|
194
260
|
atTheBeginningOfBLock &&
|
|
@@ -227,9 +293,9 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
|
|
|
227
293
|
return false
|
|
228
294
|
}
|
|
229
295
|
|
|
230
|
-
const defaultStyle = config.mapDefaultStyle(context.schema)
|
|
296
|
+
const defaultStyle = config.mapDefaultStyle?.(context.schema)
|
|
231
297
|
const looksLikeUnorderedList = /^(-|\*)/.test(focusSpan.node.text)
|
|
232
|
-
const unorderedListStyle = config.mapUnorderedListStyle(context.schema)
|
|
298
|
+
const unorderedListStyle = config.mapUnorderedListStyle?.(context.schema)
|
|
233
299
|
const caretAtTheEndOfUnorderedList = context.selection.focus.offset === 1
|
|
234
300
|
|
|
235
301
|
if (
|
|
@@ -248,7 +314,7 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
|
|
|
248
314
|
}
|
|
249
315
|
|
|
250
316
|
const looksLikeOrderedList = /^1./.test(focusSpan.node.text)
|
|
251
|
-
const orderedListStyle = config.mapOrderedListStyle(context.schema)
|
|
317
|
+
const orderedListStyle = config.mapOrderedListStyle?.(context.schema)
|
|
252
318
|
const caretAtTheEndOfOrderedList = context.selection.focus.offset === 2
|
|
253
319
|
|
|
254
320
|
if (
|
|
@@ -302,6 +368,7 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
|
|
|
302
368
|
|
|
303
369
|
const markdownBehaviors = [
|
|
304
370
|
automaticBlockquoteOnSpace,
|
|
371
|
+
automaticBreak,
|
|
305
372
|
automaticHeadingOnSpace,
|
|
306
373
|
clearStyleOnBackspace,
|
|
307
374
|
automaticListOnSpace,
|
|
@@ -74,6 +74,10 @@ export type BehaviorEvent =
|
|
|
74
74
|
text: string
|
|
75
75
|
options?: TextInsertTextOptions
|
|
76
76
|
}
|
|
77
|
+
| {
|
|
78
|
+
type: 'paste'
|
|
79
|
+
clipboardData: NonNullable<ClipboardEvent['clipboardData']>
|
|
80
|
+
}
|
|
77
81
|
|
|
78
82
|
/**
|
|
79
83
|
* @alpha
|
|
@@ -94,6 +98,20 @@ export type BehaviorGuard<
|
|
|
94
98
|
*/
|
|
95
99
|
export type BehaviorActionIntend =
|
|
96
100
|
| BehaviorEvent
|
|
101
|
+
| {
|
|
102
|
+
type: 'insert block object'
|
|
103
|
+
name: string
|
|
104
|
+
value?: {[prop: string]: unknown}
|
|
105
|
+
}
|
|
106
|
+
| {
|
|
107
|
+
type: 'insert span'
|
|
108
|
+
text: string
|
|
109
|
+
annotations?: Array<{
|
|
110
|
+
name: string
|
|
111
|
+
value: {[prop: string]: unknown}
|
|
112
|
+
}>
|
|
113
|
+
decorators?: Array<string>
|
|
114
|
+
}
|
|
97
115
|
| {
|
|
98
116
|
type: 'insert text block'
|
|
99
117
|
decorators: Array<string>
|