@portabletext/editor 1.11.3 → 1.12.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/README.md +11 -0
- package/lib/index.d.mts +26 -7
- package/lib/index.d.ts +26 -7
- package/lib/index.esm.js +317 -134
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +316 -133
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +317 -134
- package/lib/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/editor/behavior/behavior.action-utils.insert-block.ts +61 -0
- package/src/editor/behavior/behavior.action.insert-block-object.ts +25 -0
- package/src/editor/behavior/behavior.actions.ts +88 -32
- package/src/editor/behavior/behavior.core.block-objects.ts +5 -11
- package/src/editor/behavior/behavior.markdown.ts +149 -62
- package/src/editor/behavior/behavior.types.ts +22 -6
- package/src/editor/behavior/behavior.utils.block-offset.test.ts +143 -0
- package/src/editor/behavior/behavior.utils.block-offset.ts +101 -0
- package/src/editor/behavior/behavior.utils.ts +13 -2
- package/src/editor/plugins/createWithEditableAPI.ts +22 -87
- package/src/index.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@portabletext/editor",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
4
4
|
"description": "Portable Text Editor made in React",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -79,6 +79,7 @@
|
|
|
79
79
|
"eslint-plugin-react-compiler": "19.0.0-beta-df7b47d-20241124",
|
|
80
80
|
"eslint-plugin-react-hooks": "^5.0.0",
|
|
81
81
|
"jsdom": "^25.0.1",
|
|
82
|
+
"racejar": "1.0.0",
|
|
82
83
|
"react": "^18.3.1",
|
|
83
84
|
"react-dom": "^18.3.1",
|
|
84
85
|
"rxjs": "^7.8.1",
|
|
@@ -86,8 +87,7 @@
|
|
|
86
87
|
"typescript": "5.6.3",
|
|
87
88
|
"vite": "^5.4.11",
|
|
88
89
|
"vitest": "^2.1.5",
|
|
89
|
-
"vitest-browser-react": "^0.0.3"
|
|
90
|
-
"@sanity/gherkin-driver": "^0.0.1"
|
|
90
|
+
"vitest-browser-react": "^0.0.3"
|
|
91
91
|
},
|
|
92
92
|
"peerDependencies": {
|
|
93
93
|
"@sanity/block-tools": "^3.64.3",
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import {Editor, Transforms, type Descendant} from 'slate'
|
|
2
|
+
import type {
|
|
3
|
+
PortableTextMemberSchemaTypes,
|
|
4
|
+
PortableTextSlateEditor,
|
|
5
|
+
} from '../../types/editor'
|
|
6
|
+
import {isEqualToEmptyEditor} from '../../utils/values'
|
|
7
|
+
|
|
8
|
+
export function insertBlock({
|
|
9
|
+
block,
|
|
10
|
+
placement,
|
|
11
|
+
editor,
|
|
12
|
+
schema,
|
|
13
|
+
}: {
|
|
14
|
+
block: Descendant
|
|
15
|
+
placement: 'auto' | 'after'
|
|
16
|
+
editor: PortableTextSlateEditor
|
|
17
|
+
schema: PortableTextMemberSchemaTypes
|
|
18
|
+
}) {
|
|
19
|
+
if (!editor.selection) {
|
|
20
|
+
const lastBlock = Array.from(
|
|
21
|
+
Editor.nodes(editor, {
|
|
22
|
+
match: (n) => !Editor.isEditor(n),
|
|
23
|
+
at: [],
|
|
24
|
+
reverse: true,
|
|
25
|
+
}),
|
|
26
|
+
)[0]
|
|
27
|
+
|
|
28
|
+
// If there is no selection, let's just insert the new block at the
|
|
29
|
+
// end of the document
|
|
30
|
+
Editor.insertNode(editor, block)
|
|
31
|
+
|
|
32
|
+
if (lastBlock && isEqualToEmptyEditor([lastBlock[0]], schema)) {
|
|
33
|
+
// And if the last block was an empty text block, let's remove
|
|
34
|
+
// that too
|
|
35
|
+
Transforms.removeNodes(editor, {at: lastBlock[1]})
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
const [focusBlock, focusBlockPath] = Array.from(
|
|
39
|
+
Editor.nodes(editor, {
|
|
40
|
+
at: editor.selection.focus.path.slice(0, 1),
|
|
41
|
+
match: (n) => !Editor.isEditor(n),
|
|
42
|
+
}),
|
|
43
|
+
)[0] ?? [undefined, undefined]
|
|
44
|
+
|
|
45
|
+
if (placement === 'after') {
|
|
46
|
+
const nextPath = [focusBlockPath[0] + 1]
|
|
47
|
+
|
|
48
|
+
Transforms.insertNodes(editor, block, {at: nextPath})
|
|
49
|
+
Transforms.select(editor, {
|
|
50
|
+
anchor: {path: [nextPath[0], 0], offset: 0},
|
|
51
|
+
focus: {path: [nextPath[0], 0], offset: 0},
|
|
52
|
+
})
|
|
53
|
+
} else {
|
|
54
|
+
Editor.insertNode(editor, block)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (focusBlock && isEqualToEmptyEditor([focusBlock], schema)) {
|
|
58
|
+
Transforms.removeNodes(editor, {at: focusBlockPath})
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {toSlateValue} from '../../utils/values'
|
|
2
|
+
import {insertBlock} from './behavior.action-utils.insert-block'
|
|
3
|
+
import type {BehaviorActionImplementation} from './behavior.actions'
|
|
4
|
+
|
|
5
|
+
export const insertBlockObjectActionImplementation: BehaviorActionImplementation<
|
|
6
|
+
'insert block object'
|
|
7
|
+
> = ({context, action}) => {
|
|
8
|
+
const block = toSlateValue(
|
|
9
|
+
[
|
|
10
|
+
{
|
|
11
|
+
_key: context.keyGenerator(),
|
|
12
|
+
_type: action.blockObject.name,
|
|
13
|
+
...(action.blockObject.value ? action.blockObject.value : {}),
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
{schemaTypes: context.schema},
|
|
17
|
+
)[0]
|
|
18
|
+
|
|
19
|
+
insertBlock({
|
|
20
|
+
block,
|
|
21
|
+
placement: action.placement,
|
|
22
|
+
editor: action.editor,
|
|
23
|
+
schema: context.schema,
|
|
24
|
+
})
|
|
25
|
+
}
|
|
@@ -1,16 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
deleteBackward,
|
|
3
|
-
deleteForward,
|
|
4
|
-
Editor,
|
|
5
|
-
insertText,
|
|
6
|
-
Transforms,
|
|
7
|
-
} from 'slate'
|
|
1
|
+
import {deleteBackward, deleteForward, insertText, Transforms} from 'slate'
|
|
8
2
|
import {ReactEditor} from 'slate-react'
|
|
9
3
|
import type {PortableTextMemberSchemaTypes} from '../../types/editor'
|
|
10
4
|
import {toSlateRange} from '../../utils/ranges'
|
|
5
|
+
import {fromSlateValue, toSlateValue} from '../../utils/values'
|
|
6
|
+
import {KEY_TO_VALUE_ELEMENT} from '../../utils/weakMaps'
|
|
11
7
|
import {
|
|
12
8
|
addAnnotationActionImplementation,
|
|
13
|
-
insertBlockObjectActionImplementation,
|
|
14
9
|
removeAnnotationActionImplementation,
|
|
15
10
|
toggleAnnotationActionImplementation,
|
|
16
11
|
} from '../plugins/createWithEditableAPI'
|
|
@@ -19,6 +14,8 @@ import {
|
|
|
19
14
|
removeDecoratorActionImplementation,
|
|
20
15
|
toggleDecoratorActionImplementation,
|
|
21
16
|
} from '../plugins/createWithPortableTextMarkModel'
|
|
17
|
+
import {insertBlock} from './behavior.action-utils.insert-block'
|
|
18
|
+
import {insertBlockObjectActionImplementation} from './behavior.action.insert-block-object'
|
|
22
19
|
import {
|
|
23
20
|
insertBreakActionImplementation,
|
|
24
21
|
insertSoftBreakActionImplementation,
|
|
@@ -29,6 +26,7 @@ import type {
|
|
|
29
26
|
BehaviorEvent,
|
|
30
27
|
PickFromUnion,
|
|
31
28
|
} from './behavior.types'
|
|
29
|
+
import {blockOffsetToSpanSelectionPoint} from './behavior.utils.block-offset'
|
|
32
30
|
|
|
33
31
|
export type BehaviorActionContext = {
|
|
34
32
|
keyGenerator: () => string
|
|
@@ -94,25 +92,61 @@ const behaviorActionImplementations: BehaviorActionImplementations = {
|
|
|
94
92
|
'delete forward': ({action}) => {
|
|
95
93
|
deleteForward(action.editor, action.unit)
|
|
96
94
|
},
|
|
97
|
-
'delete': ({action}) => {
|
|
98
|
-
const
|
|
95
|
+
'delete block': ({action}) => {
|
|
96
|
+
const range = toSlateRange(
|
|
97
|
+
{
|
|
98
|
+
anchor: {path: action.blockPath, offset: 0},
|
|
99
|
+
focus: {path: action.blockPath, offset: 0},
|
|
100
|
+
},
|
|
101
|
+
action.editor,
|
|
102
|
+
)
|
|
99
103
|
|
|
100
|
-
if (!
|
|
101
|
-
console.error(
|
|
102
|
-
`Could not find Slate location from selection ${action.selection}`,
|
|
103
|
-
)
|
|
104
|
+
if (!range) {
|
|
105
|
+
console.error('Unable to find Slate range from selection points')
|
|
104
106
|
return
|
|
105
107
|
}
|
|
106
108
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
109
|
+
Transforms.removeNodes(action.editor, {
|
|
110
|
+
at: range,
|
|
111
|
+
})
|
|
112
|
+
},
|
|
113
|
+
'delete text': ({context, action}) => {
|
|
114
|
+
const value = fromSlateValue(
|
|
115
|
+
action.editor.children,
|
|
116
|
+
context.schema.block.name,
|
|
117
|
+
KEY_TO_VALUE_ELEMENT.get(action.editor),
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
const anchor = blockOffsetToSpanSelectionPoint({
|
|
121
|
+
value,
|
|
122
|
+
blockOffset: action.anchor,
|
|
123
|
+
})
|
|
124
|
+
const focus = blockOffsetToSpanSelectionPoint({
|
|
125
|
+
value,
|
|
126
|
+
blockOffset: action.focus,
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
if (!anchor || !focus) {
|
|
130
|
+
console.error('Unable to find anchor or focus selection point')
|
|
131
|
+
return
|
|
115
132
|
}
|
|
133
|
+
|
|
134
|
+
const range = toSlateRange(
|
|
135
|
+
{
|
|
136
|
+
anchor,
|
|
137
|
+
focus,
|
|
138
|
+
},
|
|
139
|
+
action.editor,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
if (!range) {
|
|
143
|
+
console.error('Unable to find Slate range from selection points')
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
Transforms.delete(action.editor, {
|
|
148
|
+
at: range,
|
|
149
|
+
})
|
|
116
150
|
},
|
|
117
151
|
'insert block object': insertBlockObjectActionImplementation,
|
|
118
152
|
'insert break': insertBreakActionImplementation,
|
|
@@ -122,18 +156,33 @@ const behaviorActionImplementations: BehaviorActionImplementations = {
|
|
|
122
156
|
insertText(action.editor, action.text)
|
|
123
157
|
},
|
|
124
158
|
'insert text block': ({context, action}) => {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
_type: context.schema.block.name,
|
|
128
|
-
style: context.schema.styles[0].value ?? 'normal',
|
|
129
|
-
markDefs: [],
|
|
130
|
-
children: [
|
|
159
|
+
const block = toSlateValue(
|
|
160
|
+
[
|
|
131
161
|
{
|
|
132
162
|
_key: context.keyGenerator(),
|
|
133
|
-
_type:
|
|
134
|
-
|
|
163
|
+
_type: context.schema.block.name,
|
|
164
|
+
style: context.schema.styles[0].value ?? 'normal',
|
|
165
|
+
markDefs: [],
|
|
166
|
+
children: action.textBlock?.children?.map((child) => ({
|
|
167
|
+
...child,
|
|
168
|
+
_key: context.keyGenerator(),
|
|
169
|
+
})) ?? [
|
|
170
|
+
{
|
|
171
|
+
_type: context.schema.span.name,
|
|
172
|
+
_key: context.keyGenerator(),
|
|
173
|
+
text: '',
|
|
174
|
+
},
|
|
175
|
+
],
|
|
135
176
|
},
|
|
136
177
|
],
|
|
178
|
+
{schemaTypes: context.schema},
|
|
179
|
+
)[0]
|
|
180
|
+
|
|
181
|
+
insertBlock({
|
|
182
|
+
block,
|
|
183
|
+
editor: action.editor,
|
|
184
|
+
schema: context.schema,
|
|
185
|
+
placement: action.placement,
|
|
137
186
|
})
|
|
138
187
|
},
|
|
139
188
|
'effect': ({action}) => {
|
|
@@ -169,8 +218,15 @@ export function performAction({
|
|
|
169
218
|
action: BehaviorAction
|
|
170
219
|
}) {
|
|
171
220
|
switch (action.type) {
|
|
172
|
-
case 'delete': {
|
|
173
|
-
behaviorActionImplementations
|
|
221
|
+
case 'delete block': {
|
|
222
|
+
behaviorActionImplementations['delete block']({
|
|
223
|
+
context,
|
|
224
|
+
action,
|
|
225
|
+
})
|
|
226
|
+
break
|
|
227
|
+
}
|
|
228
|
+
case 'delete text': {
|
|
229
|
+
behaviorActionImplementations['delete text']({
|
|
174
230
|
context,
|
|
175
231
|
action,
|
|
176
232
|
})
|
|
@@ -16,7 +16,7 @@ const breakingBlockObject = defineBehavior({
|
|
|
16
16
|
|
|
17
17
|
return !!focusBlockObject
|
|
18
18
|
},
|
|
19
|
-
actions: [() => [{type: 'insert text block',
|
|
19
|
+
actions: [() => [{type: 'insert text block', placement: 'after'}]],
|
|
20
20
|
})
|
|
21
21
|
|
|
22
22
|
const deletingEmptyTextBlockAfterBlockObject = defineBehavior({
|
|
@@ -42,11 +42,8 @@ const deletingEmptyTextBlockAfterBlockObject = defineBehavior({
|
|
|
42
42
|
actions: [
|
|
43
43
|
(_, {focusTextBlock, previousBlock}) => [
|
|
44
44
|
{
|
|
45
|
-
type: 'delete',
|
|
46
|
-
|
|
47
|
-
anchor: {path: focusTextBlock.path, offset: 0},
|
|
48
|
-
focus: {path: focusTextBlock.path, offset: 0},
|
|
49
|
-
},
|
|
45
|
+
type: 'delete block',
|
|
46
|
+
blockPath: focusTextBlock.path,
|
|
50
47
|
},
|
|
51
48
|
{
|
|
52
49
|
type: 'select',
|
|
@@ -82,11 +79,8 @@ const deletingEmptyTextBlockBeforeBlockObject = defineBehavior({
|
|
|
82
79
|
actions: [
|
|
83
80
|
(_, {focusTextBlock, nextBlock}) => [
|
|
84
81
|
{
|
|
85
|
-
type: 'delete',
|
|
86
|
-
|
|
87
|
-
anchor: {path: focusTextBlock.path, offset: 0},
|
|
88
|
-
focus: {path: focusTextBlock.path, offset: 0},
|
|
89
|
-
},
|
|
82
|
+
type: 'delete block',
|
|
83
|
+
blockPath: focusTextBlock.path,
|
|
90
84
|
},
|
|
91
85
|
{
|
|
92
86
|
type: 'select',
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import {isPortableTextSpan} from '@portabletext/toolkit'
|
|
2
|
+
import {isPortableTextTextBlock} from '@sanity/types'
|
|
2
3
|
import type {PortableTextMemberSchemaTypes} from '../../types/editor'
|
|
3
4
|
import {defineBehavior} from './behavior.types'
|
|
4
5
|
import {
|
|
6
|
+
getFocusBlock,
|
|
5
7
|
getFocusSpan,
|
|
6
8
|
getFocusTextBlock,
|
|
9
|
+
getTextBlockText,
|
|
7
10
|
selectionIsCollapsed,
|
|
8
11
|
} from './behavior.utils'
|
|
12
|
+
import {spanSelectionPointToBlockOffset} from './behavior.utils.block-offset'
|
|
9
13
|
|
|
10
14
|
/**
|
|
11
15
|
* @alpha
|
|
@@ -53,8 +57,25 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
|
|
|
53
57
|
return false
|
|
54
58
|
}
|
|
55
59
|
|
|
56
|
-
const
|
|
57
|
-
|
|
60
|
+
const blockOffset = spanSelectionPointToBlockOffset({
|
|
61
|
+
value: context.value,
|
|
62
|
+
selectionPoint: {
|
|
63
|
+
path: [
|
|
64
|
+
{_key: focusTextBlock.node._key},
|
|
65
|
+
'children',
|
|
66
|
+
{_key: focusSpan.node._key},
|
|
67
|
+
],
|
|
68
|
+
offset: context.selection.focus.offset,
|
|
69
|
+
},
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
if (!blockOffset) {
|
|
73
|
+
return false
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const blockText = getTextBlockText(focusTextBlock.node)
|
|
77
|
+
const caretAtTheEndOfQuote = blockOffset.offset === 1
|
|
78
|
+
const looksLikeMarkdownQuote = /^>/.test(blockText)
|
|
58
79
|
const blockquoteStyle = config.blockquoteStyle?.({schema: context.schema})
|
|
59
80
|
|
|
60
81
|
if (
|
|
@@ -62,7 +83,7 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
|
|
|
62
83
|
looksLikeMarkdownQuote &&
|
|
63
84
|
blockquoteStyle !== undefined
|
|
64
85
|
) {
|
|
65
|
-
return {focusTextBlock,
|
|
86
|
+
return {focusTextBlock, style: blockquoteStyle}
|
|
66
87
|
}
|
|
67
88
|
|
|
68
89
|
return false
|
|
@@ -74,7 +95,7 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
|
|
|
74
95
|
text: ' ',
|
|
75
96
|
},
|
|
76
97
|
],
|
|
77
|
-
(_, {focusTextBlock,
|
|
98
|
+
(_, {focusTextBlock, style}) => [
|
|
78
99
|
{
|
|
79
100
|
type: 'unset block',
|
|
80
101
|
props: ['listItem', 'level'],
|
|
@@ -86,22 +107,20 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
|
|
|
86
107
|
paths: [focusTextBlock.path],
|
|
87
108
|
},
|
|
88
109
|
{
|
|
89
|
-
type: 'delete',
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
offset: 2,
|
|
98
|
-
},
|
|
110
|
+
type: 'delete text',
|
|
111
|
+
anchor: {
|
|
112
|
+
path: focusTextBlock.path,
|
|
113
|
+
offset: 0,
|
|
114
|
+
},
|
|
115
|
+
focus: {
|
|
116
|
+
path: focusTextBlock.path,
|
|
117
|
+
offset: 2,
|
|
99
118
|
},
|
|
100
119
|
},
|
|
101
120
|
],
|
|
102
121
|
],
|
|
103
122
|
})
|
|
104
|
-
const
|
|
123
|
+
const automaticHr = defineBehavior({
|
|
105
124
|
on: 'insert text',
|
|
106
125
|
guard: ({context, event}) => {
|
|
107
126
|
const hrCharacter =
|
|
@@ -117,13 +136,13 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
|
|
|
117
136
|
return false
|
|
118
137
|
}
|
|
119
138
|
|
|
120
|
-
const
|
|
139
|
+
const hrObject = config.horizontalRuleObject?.({
|
|
121
140
|
schema: context.schema,
|
|
122
141
|
})
|
|
123
142
|
const focusBlock = getFocusTextBlock(context)
|
|
124
143
|
const selectionCollapsed = selectionIsCollapsed(context)
|
|
125
144
|
|
|
126
|
-
if (!
|
|
145
|
+
if (!hrObject || !focusBlock || !selectionCollapsed) {
|
|
127
146
|
return false
|
|
128
147
|
}
|
|
129
148
|
|
|
@@ -133,7 +152,7 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
|
|
|
133
152
|
.join('')
|
|
134
153
|
|
|
135
154
|
if (onlyText && blockText === `${hrCharacter}${hrCharacter}`) {
|
|
136
|
-
return {
|
|
155
|
+
return {hrObject, focusBlock, hrCharacter}
|
|
137
156
|
}
|
|
138
157
|
|
|
139
158
|
return false
|
|
@@ -145,31 +164,71 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
|
|
|
145
164
|
text: hrCharacter,
|
|
146
165
|
},
|
|
147
166
|
],
|
|
148
|
-
(_, {
|
|
167
|
+
(_, {hrObject, focusBlock}) => [
|
|
149
168
|
{
|
|
150
169
|
type: 'insert block object',
|
|
151
|
-
|
|
170
|
+
placement: 'after',
|
|
171
|
+
blockObject: hrObject,
|
|
152
172
|
},
|
|
153
173
|
{
|
|
154
|
-
type: 'delete',
|
|
155
|
-
|
|
156
|
-
anchor: {
|
|
157
|
-
path: focusBlock.path,
|
|
158
|
-
offset: 0,
|
|
159
|
-
},
|
|
160
|
-
focus: {
|
|
161
|
-
path: focusBlock.path,
|
|
162
|
-
offset: 0,
|
|
163
|
-
},
|
|
164
|
-
},
|
|
174
|
+
type: 'delete block',
|
|
175
|
+
blockPath: focusBlock.path,
|
|
165
176
|
},
|
|
166
177
|
{
|
|
167
178
|
type: 'insert text block',
|
|
168
|
-
|
|
179
|
+
placement: 'after',
|
|
169
180
|
},
|
|
170
181
|
],
|
|
171
182
|
],
|
|
172
183
|
})
|
|
184
|
+
const automaticHrOnPaste = defineBehavior({
|
|
185
|
+
on: 'paste',
|
|
186
|
+
guard: ({context, event}) => {
|
|
187
|
+
const text = event.clipboardData.getData('text/plain')
|
|
188
|
+
const hrRegExp = /^(---)$|(___)$|(\*\*\*)$/gm
|
|
189
|
+
const hrCharacters = text.match(hrRegExp)?.[0]
|
|
190
|
+
const hrObject = config.horizontalRuleObject?.({
|
|
191
|
+
schema: context.schema,
|
|
192
|
+
})
|
|
193
|
+
const focusBlock = getFocusBlock(context)
|
|
194
|
+
|
|
195
|
+
if (!hrCharacters || !hrObject || !focusBlock) {
|
|
196
|
+
return false
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return {hrCharacters, hrObject, focusBlock}
|
|
200
|
+
},
|
|
201
|
+
actions: [
|
|
202
|
+
(_, {hrCharacters}) => [
|
|
203
|
+
{
|
|
204
|
+
type: 'insert text',
|
|
205
|
+
text: hrCharacters,
|
|
206
|
+
},
|
|
207
|
+
],
|
|
208
|
+
(_, {hrObject, focusBlock}) =>
|
|
209
|
+
isPortableTextTextBlock(focusBlock.node)
|
|
210
|
+
? [
|
|
211
|
+
{
|
|
212
|
+
type: 'insert text block',
|
|
213
|
+
textBlock: {children: focusBlock.node.children},
|
|
214
|
+
placement: 'after',
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
type: 'insert block object',
|
|
218
|
+
blockObject: hrObject,
|
|
219
|
+
placement: 'after',
|
|
220
|
+
},
|
|
221
|
+
{type: 'delete block', blockPath: focusBlock.path},
|
|
222
|
+
]
|
|
223
|
+
: [
|
|
224
|
+
{
|
|
225
|
+
type: 'insert block object',
|
|
226
|
+
blockObject: hrObject,
|
|
227
|
+
placement: 'after',
|
|
228
|
+
},
|
|
229
|
+
],
|
|
230
|
+
],
|
|
231
|
+
})
|
|
173
232
|
const automaticHeadingOnSpace = defineBehavior({
|
|
174
233
|
on: 'insert text',
|
|
175
234
|
guard: ({context, event}) => {
|
|
@@ -187,11 +246,28 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
|
|
|
187
246
|
return false
|
|
188
247
|
}
|
|
189
248
|
|
|
190
|
-
const
|
|
249
|
+
const blockOffset = spanSelectionPointToBlockOffset({
|
|
250
|
+
value: context.value,
|
|
251
|
+
selectionPoint: {
|
|
252
|
+
path: [
|
|
253
|
+
{_key: focusTextBlock.node._key},
|
|
254
|
+
'children',
|
|
255
|
+
{_key: focusSpan.node._key},
|
|
256
|
+
],
|
|
257
|
+
offset: context.selection.focus.offset,
|
|
258
|
+
},
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
if (!blockOffset) {
|
|
262
|
+
return false
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const blockText = getTextBlockText(focusTextBlock.node)
|
|
266
|
+
const markdownHeadingSearch = /^#+/.exec(blockText)
|
|
191
267
|
const level = markdownHeadingSearch
|
|
192
268
|
? markdownHeadingSearch[0].length
|
|
193
269
|
: undefined
|
|
194
|
-
const caretAtTheEndOfHeading =
|
|
270
|
+
const caretAtTheEndOfHeading = blockOffset.offset === level
|
|
195
271
|
|
|
196
272
|
if (!caretAtTheEndOfHeading) {
|
|
197
273
|
return false
|
|
@@ -205,7 +281,6 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
|
|
|
205
281
|
if (level !== undefined && style !== undefined) {
|
|
206
282
|
return {
|
|
207
283
|
focusTextBlock,
|
|
208
|
-
focusSpan,
|
|
209
284
|
style: style,
|
|
210
285
|
level,
|
|
211
286
|
}
|
|
@@ -220,7 +295,7 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
|
|
|
220
295
|
text: ' ',
|
|
221
296
|
},
|
|
222
297
|
],
|
|
223
|
-
(_, {focusTextBlock,
|
|
298
|
+
(_, {focusTextBlock, style, level}) => [
|
|
224
299
|
{
|
|
225
300
|
type: 'unset block',
|
|
226
301
|
props: ['listItem', 'level'],
|
|
@@ -232,16 +307,14 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
|
|
|
232
307
|
paths: [focusTextBlock.path],
|
|
233
308
|
},
|
|
234
309
|
{
|
|
235
|
-
type: 'delete',
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
offset: level + 1,
|
|
244
|
-
},
|
|
310
|
+
type: 'delete text',
|
|
311
|
+
anchor: {
|
|
312
|
+
path: focusTextBlock.path,
|
|
313
|
+
offset: 0,
|
|
314
|
+
},
|
|
315
|
+
focus: {
|
|
316
|
+
path: focusTextBlock.path,
|
|
317
|
+
offset: level + 1,
|
|
245
318
|
},
|
|
246
319
|
},
|
|
247
320
|
],
|
|
@@ -301,12 +374,29 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
|
|
|
301
374
|
return false
|
|
302
375
|
}
|
|
303
376
|
|
|
377
|
+
const blockOffset = spanSelectionPointToBlockOffset({
|
|
378
|
+
value: context.value,
|
|
379
|
+
selectionPoint: {
|
|
380
|
+
path: [
|
|
381
|
+
{_key: focusTextBlock.node._key},
|
|
382
|
+
'children',
|
|
383
|
+
{_key: focusSpan.node._key},
|
|
384
|
+
],
|
|
385
|
+
offset: context.selection.focus.offset,
|
|
386
|
+
},
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
if (!blockOffset) {
|
|
390
|
+
return false
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const blockText = getTextBlockText(focusTextBlock.node)
|
|
304
394
|
const defaultStyle = config.defaultStyle?.({schema: context.schema})
|
|
305
|
-
const looksLikeUnorderedList = /^(-|\*)/.test(
|
|
395
|
+
const looksLikeUnorderedList = /^(-|\*)/.test(blockText)
|
|
306
396
|
const unorderedListStyle = config.unorderedListStyle?.({
|
|
307
397
|
schema: context.schema,
|
|
308
398
|
})
|
|
309
|
-
const caretAtTheEndOfUnorderedList =
|
|
399
|
+
const caretAtTheEndOfUnorderedList = blockOffset.offset === 1
|
|
310
400
|
|
|
311
401
|
if (
|
|
312
402
|
defaultStyle &&
|
|
@@ -316,7 +406,6 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
|
|
|
316
406
|
) {
|
|
317
407
|
return {
|
|
318
408
|
focusTextBlock,
|
|
319
|
-
focusSpan,
|
|
320
409
|
listItem: unorderedListStyle,
|
|
321
410
|
listItemLength: 1,
|
|
322
411
|
style: defaultStyle,
|
|
@@ -337,7 +426,6 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
|
|
|
337
426
|
) {
|
|
338
427
|
return {
|
|
339
428
|
focusTextBlock,
|
|
340
|
-
focusSpan,
|
|
341
429
|
listItem: orderedListStyle,
|
|
342
430
|
listItemLength: 2,
|
|
343
431
|
style: defaultStyle,
|
|
@@ -353,7 +441,7 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
|
|
|
353
441
|
text: ' ',
|
|
354
442
|
},
|
|
355
443
|
],
|
|
356
|
-
(_, {focusTextBlock,
|
|
444
|
+
(_, {focusTextBlock, style, listItem, listItemLength}) => [
|
|
357
445
|
{
|
|
358
446
|
type: 'set block',
|
|
359
447
|
listItem,
|
|
@@ -362,16 +450,14 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
|
|
|
362
450
|
paths: [focusTextBlock.path],
|
|
363
451
|
},
|
|
364
452
|
{
|
|
365
|
-
type: 'delete',
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
offset: listItemLength + 1,
|
|
374
|
-
},
|
|
453
|
+
type: 'delete text',
|
|
454
|
+
anchor: {
|
|
455
|
+
path: focusTextBlock.path,
|
|
456
|
+
offset: 0,
|
|
457
|
+
},
|
|
458
|
+
focus: {
|
|
459
|
+
path: focusTextBlock.path,
|
|
460
|
+
offset: listItemLength + 1,
|
|
375
461
|
},
|
|
376
462
|
},
|
|
377
463
|
],
|
|
@@ -380,8 +466,9 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
|
|
|
380
466
|
|
|
381
467
|
const markdownBehaviors = [
|
|
382
468
|
automaticBlockquoteOnSpace,
|
|
383
|
-
automaticBreak,
|
|
384
469
|
automaticHeadingOnSpace,
|
|
470
|
+
automaticHr,
|
|
471
|
+
automaticHrOnPaste,
|
|
385
472
|
clearStyleOnBackspace,
|
|
386
473
|
automaticListOnSpace,
|
|
387
474
|
]
|