@portabletext/editor 2.21.3 → 3.0.1
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-dts/index.d.ts +49 -209
- package/lib/_chunks-es/selector.is-at-the-start-of-block.js +103 -20
- package/lib/_chunks-es/selector.is-at-the-start-of-block.js.map +1 -1
- package/lib/_chunks-es/{util.get-text-block-text.js → util.slice-blocks.js} +73 -24
- package/lib/_chunks-es/util.slice-blocks.js.map +1 -0
- package/lib/_chunks-es/util.slice-text-block.js +13 -2
- package/lib/_chunks-es/util.slice-text-block.js.map +1 -1
- package/lib/behaviors/index.d.ts +1 -1
- package/lib/index.d.ts +2 -2
- package/lib/index.js +339 -341
- package/lib/index.js.map +1 -1
- package/lib/plugins/index.d.ts +2 -133
- package/lib/plugins/index.js +2 -796
- package/lib/plugins/index.js.map +1 -1
- package/lib/selectors/index.d.ts +2 -24
- package/lib/selectors/index.js +28 -130
- package/lib/selectors/index.js.map +1 -1
- package/lib/utils/index.d.ts +6 -4
- package/lib/utils/index.js +98 -9
- package/lib/utils/index.js.map +1 -1
- package/package.json +1 -3
- package/src/behaviors/behavior.abstract.split.ts +1 -0
- package/src/behaviors/behavior.perform-event.ts +7 -7
- package/src/converters/converter.portable-text.ts +1 -0
- package/src/converters/converter.text-html.ts +1 -0
- package/src/converters/converter.text-plain.ts +1 -0
- package/src/editor/Editable.tsx +1 -0
- package/src/editor/PortableTextEditor.tsx +0 -19
- package/src/editor/create-editor.ts +0 -3
- package/src/editor/editor-machine.ts +0 -10
- package/src/editor/event-to-change.tsx +5 -1
- package/src/editor/plugins/create-with-event-listeners.ts +30 -6
- package/src/editor/plugins/createWithObjectKeys.ts +2 -1
- package/src/editor/plugins/createWithPatches.ts +3 -3
- package/src/editor/plugins/createWithPlaceholderBlock.ts +2 -1
- package/src/editor/plugins/createWithPortableTextMarkModel.ts +2 -1
- package/src/editor/plugins/with-plugins.ts +10 -14
- package/src/editor/relay-machine.ts +0 -4
- package/src/editor/sync-machine.ts +2 -2
- package/src/editor.ts +0 -4
- package/src/history/behavior.operation.history.redo.ts +67 -0
- package/src/history/behavior.operation.history.undo.ts +71 -0
- package/src/history/event.history.undo.test.tsx +672 -0
- package/src/history/history.preserving-keys.test.tsx +112 -0
- package/src/history/remote-patches.ts +20 -0
- package/src/history/slate-plugin.history.ts +146 -0
- package/src/history/slate-plugin.redoing.ts +21 -0
- package/src/history/slate-plugin.undoing.ts +21 -0
- package/src/history/slate-plugin.without-history.ts +23 -0
- package/src/history/transform-operation.ts +245 -0
- package/src/history/undo-redo-collaboration.test.tsx +541 -0
- package/src/history/undo-redo.feature +125 -0
- package/src/history/undo-redo.test.tsx +195 -0
- package/src/history/undo-step.ts +148 -0
- package/src/index.ts +0 -1
- package/src/internal-utils/operation-to-patches.test.ts +23 -25
- package/src/internal-utils/operation-to-patches.ts +31 -22
- package/src/internal-utils/selection-text.test.ts +3 -0
- package/src/internal-utils/selection-text.ts +5 -2
- package/src/internal-utils/values.ts +23 -11
- package/src/operations/behavior.operation.block.set.ts +1 -0
- package/src/operations/behavior.operation.block.unset.ts +2 -0
- package/src/operations/behavior.operation.insert.block.ts +1 -0
- package/src/operations/behavior.operations.ts +2 -4
- package/src/plugins/index.ts +0 -3
- package/src/selectors/index.ts +0 -3
- package/src/test/vitest/step-definitions.tsx +57 -0
- package/src/test/vitest/test-editor.tsx +1 -1
- package/src/utils/parse-blocks.test.ts +296 -16
- package/src/utils/parse-blocks.ts +81 -22
- package/src/utils/util.merge-text-blocks.ts +5 -1
- package/src/utils/util.slice-blocks.ts +24 -10
- package/lib/_chunks-es/selector.get-selection-text.js +0 -92
- package/lib/_chunks-es/selector.get-selection-text.js.map +0 -1
- package/lib/_chunks-es/selector.get-text-before.js +0 -36
- package/lib/_chunks-es/selector.get-text-before.js.map +0 -1
- package/lib/_chunks-es/util.get-text-block-text.js.map +0 -1
- package/lib/_chunks-es/util.is-empty-text-block.js +0 -40
- package/lib/_chunks-es/util.is-empty-text-block.js.map +0 -1
- package/lib/_chunks-es/util.merge-text-blocks.js +0 -101
- package/lib/_chunks-es/util.merge-text-blocks.js.map +0 -1
- package/src/editor/plugins/createWithMaxBlocks.ts +0 -53
- package/src/editor/plugins/createWithUndoRedo.ts +0 -628
- package/src/editor/with-undo-step.ts +0 -37
- package/src/editor/withUndoRedo.ts +0 -34
- package/src/editor-event-listener.tsx +0 -28
- package/src/plugins/plugin.decorator-shortcut.ts +0 -238
- package/src/plugins/plugin.markdown.test.tsx +0 -42
- package/src/plugins/plugin.markdown.tsx +0 -131
- package/src/plugins/plugin.one-line.tsx +0 -123
- package/src/selectors/selector.get-list-state.test.ts +0 -189
- package/src/selectors/selector.get-list-state.ts +0 -96
- package/src/selectors/selector.get-selected-slice.ts +0 -13
- package/src/selectors/selector.get-trimmed-selection.test.ts +0 -657
- package/src/selectors/selector.get-trimmed-selection.ts +0 -189
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import {Before} from 'racejar'
|
|
2
|
+
import {Feature} from 'racejar/vitest'
|
|
3
|
+
import {defineSchema} from '..'
|
|
4
|
+
import {defineBehavior, forward, raise} from '../behaviors'
|
|
5
|
+
import {BehaviorPlugin} from '../plugins/plugin.behavior'
|
|
6
|
+
import {getFocusTextBlock, isSelectionExpanded} from '../selectors'
|
|
7
|
+
import {parameterTypes} from '../test'
|
|
8
|
+
import {createTestEditor, stepDefinitions, type Context} from '../test/vitest'
|
|
9
|
+
import undoRedoFeature from './undo-redo.feature?raw'
|
|
10
|
+
|
|
11
|
+
Feature({
|
|
12
|
+
featureText: undoRedoFeature,
|
|
13
|
+
stepDefinitions,
|
|
14
|
+
parameterTypes,
|
|
15
|
+
hooks: [
|
|
16
|
+
Before(async (context: Context) => {
|
|
17
|
+
const {editor, locator} = await createTestEditor({
|
|
18
|
+
schemaDefinition: defineSchema({
|
|
19
|
+
annotations: [{name: 'link'}, {name: 'comment'}],
|
|
20
|
+
}),
|
|
21
|
+
children: (
|
|
22
|
+
<>
|
|
23
|
+
<ArrowTransformPlugin />
|
|
24
|
+
<CopyrightTransformPlugin />
|
|
25
|
+
</>
|
|
26
|
+
),
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
context.locator = locator
|
|
30
|
+
context.editor = editor
|
|
31
|
+
}),
|
|
32
|
+
],
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Native behavior to transform `>` into `→`.
|
|
37
|
+
* Ony for testing purposes.
|
|
38
|
+
*/
|
|
39
|
+
function ArrowTransformPlugin() {
|
|
40
|
+
return (
|
|
41
|
+
<BehaviorPlugin
|
|
42
|
+
behaviors={[
|
|
43
|
+
defineBehavior({
|
|
44
|
+
on: 'insert.text',
|
|
45
|
+
guard: ({snapshot, event}) => {
|
|
46
|
+
if (event.text !== '>' || isSelectionExpanded(snapshot)) {
|
|
47
|
+
return false
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const focusTextBlock = getFocusTextBlock(snapshot)
|
|
51
|
+
|
|
52
|
+
if (!focusTextBlock) {
|
|
53
|
+
return false
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {focusTextBlock}
|
|
57
|
+
},
|
|
58
|
+
actions: [
|
|
59
|
+
({event}) => [forward(event)],
|
|
60
|
+
({snapshot}, {focusTextBlock}) => [
|
|
61
|
+
raise({
|
|
62
|
+
type: 'select',
|
|
63
|
+
at: {
|
|
64
|
+
anchor: {
|
|
65
|
+
path: focusTextBlock.path,
|
|
66
|
+
offset: 0,
|
|
67
|
+
},
|
|
68
|
+
focus: {
|
|
69
|
+
path: focusTextBlock.path,
|
|
70
|
+
offset: 2,
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
}),
|
|
74
|
+
raise({
|
|
75
|
+
type: 'delete',
|
|
76
|
+
at: {
|
|
77
|
+
anchor: {
|
|
78
|
+
path: focusTextBlock.path,
|
|
79
|
+
offset: 0,
|
|
80
|
+
},
|
|
81
|
+
focus: {
|
|
82
|
+
path: focusTextBlock.path,
|
|
83
|
+
offset: 2,
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
}),
|
|
87
|
+
raise({
|
|
88
|
+
type: 'insert.child',
|
|
89
|
+
child: {
|
|
90
|
+
_type: snapshot.context.schema.span.name,
|
|
91
|
+
text: '→',
|
|
92
|
+
marks: [],
|
|
93
|
+
},
|
|
94
|
+
}),
|
|
95
|
+
raise({
|
|
96
|
+
type: 'select',
|
|
97
|
+
at: {
|
|
98
|
+
anchor: {
|
|
99
|
+
path: focusTextBlock.path,
|
|
100
|
+
offset: 2,
|
|
101
|
+
},
|
|
102
|
+
focus: {
|
|
103
|
+
path: focusTextBlock.path,
|
|
104
|
+
offset: 2,
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
}),
|
|
108
|
+
],
|
|
109
|
+
],
|
|
110
|
+
}),
|
|
111
|
+
]}
|
|
112
|
+
/>
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Native behavior to transform `(c)` into `©`.
|
|
118
|
+
* Ony for testing purposes.
|
|
119
|
+
*/
|
|
120
|
+
function CopyrightTransformPlugin() {
|
|
121
|
+
return (
|
|
122
|
+
<BehaviorPlugin
|
|
123
|
+
behaviors={[
|
|
124
|
+
defineBehavior({
|
|
125
|
+
on: 'insert.text',
|
|
126
|
+
guard: ({snapshot, event}) => {
|
|
127
|
+
if (event.text !== ')' || isSelectionExpanded(snapshot)) {
|
|
128
|
+
return false
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const focusTextBlock = getFocusTextBlock(snapshot)
|
|
132
|
+
|
|
133
|
+
if (!focusTextBlock) {
|
|
134
|
+
return false
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return {focusTextBlock}
|
|
138
|
+
},
|
|
139
|
+
actions: [
|
|
140
|
+
({event}) => [forward(event)],
|
|
141
|
+
({snapshot}, {focusTextBlock}) => [
|
|
142
|
+
raise({
|
|
143
|
+
type: 'select',
|
|
144
|
+
at: {
|
|
145
|
+
anchor: {
|
|
146
|
+
path: focusTextBlock.path,
|
|
147
|
+
offset: 0,
|
|
148
|
+
},
|
|
149
|
+
focus: {
|
|
150
|
+
path: focusTextBlock.path,
|
|
151
|
+
offset: 3,
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
}),
|
|
155
|
+
raise({
|
|
156
|
+
type: 'delete',
|
|
157
|
+
at: {
|
|
158
|
+
anchor: {
|
|
159
|
+
path: focusTextBlock.path,
|
|
160
|
+
offset: 0,
|
|
161
|
+
},
|
|
162
|
+
focus: {
|
|
163
|
+
path: focusTextBlock.path,
|
|
164
|
+
offset: 3,
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
}),
|
|
168
|
+
raise({
|
|
169
|
+
type: 'insert.child',
|
|
170
|
+
child: {
|
|
171
|
+
_type: snapshot.context.schema.span.name,
|
|
172
|
+
text: '©',
|
|
173
|
+
marks: [],
|
|
174
|
+
},
|
|
175
|
+
}),
|
|
176
|
+
raise({
|
|
177
|
+
type: 'select',
|
|
178
|
+
at: {
|
|
179
|
+
anchor: {
|
|
180
|
+
path: focusTextBlock.path,
|
|
181
|
+
offset: 3,
|
|
182
|
+
},
|
|
183
|
+
focus: {
|
|
184
|
+
path: focusTextBlock.path,
|
|
185
|
+
offset: 3,
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
}),
|
|
189
|
+
],
|
|
190
|
+
],
|
|
191
|
+
}),
|
|
192
|
+
]}
|
|
193
|
+
/>
|
|
194
|
+
)
|
|
195
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import {Path, type Editor, type Operation} from 'slate'
|
|
2
|
+
import {isNormalizingNode} from '../editor/with-normalizing-node'
|
|
3
|
+
import {defaultKeyGenerator} from '../utils/key-generator'
|
|
4
|
+
|
|
5
|
+
const CURRENT_UNDO_STEP_ID: WeakMap<Editor, {undoStepId: string} | undefined> =
|
|
6
|
+
new WeakMap()
|
|
7
|
+
|
|
8
|
+
export function getCurrentUndoStepId(editor: Editor) {
|
|
9
|
+
return CURRENT_UNDO_STEP_ID.get(editor)?.undoStepId
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function createUndoStepId(editor: Editor) {
|
|
13
|
+
CURRENT_UNDO_STEP_ID.set(editor, {
|
|
14
|
+
undoStepId: defaultKeyGenerator(),
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function clearUndoStepId(editor: Editor) {
|
|
19
|
+
CURRENT_UNDO_STEP_ID.set(editor, undefined)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type UndoStep = {
|
|
23
|
+
operations: Array<Operation>
|
|
24
|
+
timestamp: Date
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function createUndoSteps({
|
|
28
|
+
steps,
|
|
29
|
+
op,
|
|
30
|
+
editor,
|
|
31
|
+
currentUndoStepId,
|
|
32
|
+
previousUndoStepId,
|
|
33
|
+
}: {
|
|
34
|
+
steps: Array<UndoStep>
|
|
35
|
+
op: Operation
|
|
36
|
+
editor: Editor
|
|
37
|
+
currentUndoStepId: string | undefined
|
|
38
|
+
previousUndoStepId: string | undefined
|
|
39
|
+
}): Array<UndoStep> {
|
|
40
|
+
const lastStep = steps.at(-1)
|
|
41
|
+
|
|
42
|
+
if (!lastStep) {
|
|
43
|
+
return createNewStep(steps, op, editor)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (editor.operations.length > 0) {
|
|
47
|
+
// The editor has operations in progress
|
|
48
|
+
|
|
49
|
+
if (currentUndoStepId === previousUndoStepId || isNormalizingNode(editor)) {
|
|
50
|
+
return mergeIntoLastStep(steps, lastStep, op)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return createNewStep(steps, op, editor)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (
|
|
57
|
+
op.type === 'set_selection' &&
|
|
58
|
+
currentUndoStepId === undefined &&
|
|
59
|
+
previousUndoStepId !== undefined
|
|
60
|
+
) {
|
|
61
|
+
// Selecting without undo step ID
|
|
62
|
+
return mergeIntoLastStep(steps, lastStep, op)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (
|
|
66
|
+
op.type === 'set_selection' &&
|
|
67
|
+
currentUndoStepId !== undefined &&
|
|
68
|
+
previousUndoStepId !== undefined &&
|
|
69
|
+
previousUndoStepId !== currentUndoStepId
|
|
70
|
+
) {
|
|
71
|
+
// Selecting with different undo step ID
|
|
72
|
+
return mergeIntoLastStep(steps, lastStep, op)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Handle case when both IDs are undefined
|
|
76
|
+
if (currentUndoStepId === undefined && previousUndoStepId === undefined) {
|
|
77
|
+
if (op.type === 'set_selection') {
|
|
78
|
+
return mergeIntoLastStep(steps, lastStep, op)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const lastOp = lastStep.operations.at(-1)
|
|
82
|
+
|
|
83
|
+
if (
|
|
84
|
+
lastOp &&
|
|
85
|
+
op.type === 'insert_text' &&
|
|
86
|
+
lastOp.type === 'insert_text' &&
|
|
87
|
+
op.offset === lastOp.offset + lastOp.text.length &&
|
|
88
|
+
Path.equals(op.path, lastOp.path) &&
|
|
89
|
+
op.text !== ' '
|
|
90
|
+
) {
|
|
91
|
+
return mergeIntoLastStep(steps, lastStep, op)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (
|
|
95
|
+
lastOp &&
|
|
96
|
+
op.type === 'remove_text' &&
|
|
97
|
+
lastOp.type === 'remove_text' &&
|
|
98
|
+
op.offset + op.text.length === lastOp.offset &&
|
|
99
|
+
Path.equals(op.path, lastOp.path)
|
|
100
|
+
) {
|
|
101
|
+
return mergeIntoLastStep(steps, lastStep, op)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return createNewStep(steps, op, editor)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return createNewStep(steps, op, editor)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function createNewStep(
|
|
111
|
+
steps: Array<UndoStep>,
|
|
112
|
+
op: Operation,
|
|
113
|
+
editor: Editor,
|
|
114
|
+
): Array<UndoStep> {
|
|
115
|
+
const operations =
|
|
116
|
+
editor.selection === null
|
|
117
|
+
? [op]
|
|
118
|
+
: [
|
|
119
|
+
{
|
|
120
|
+
type: 'set_selection' as const,
|
|
121
|
+
properties: {...editor.selection},
|
|
122
|
+
newProperties: {...editor.selection},
|
|
123
|
+
},
|
|
124
|
+
op,
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
return [
|
|
128
|
+
...steps,
|
|
129
|
+
{
|
|
130
|
+
operations,
|
|
131
|
+
timestamp: new Date(),
|
|
132
|
+
},
|
|
133
|
+
]
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function mergeIntoLastStep(
|
|
137
|
+
steps: Array<UndoStep>,
|
|
138
|
+
lastStep: UndoStep,
|
|
139
|
+
op: Operation,
|
|
140
|
+
): Array<UndoStep> {
|
|
141
|
+
return [
|
|
142
|
+
...steps.slice(0, -1),
|
|
143
|
+
{
|
|
144
|
+
timestamp: lastStep.timestamp,
|
|
145
|
+
operations: [...lastStep.operations, op],
|
|
146
|
+
},
|
|
147
|
+
]
|
|
148
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -7,7 +7,6 @@ export type {
|
|
|
7
7
|
PortableTextSpan,
|
|
8
8
|
} from '@sanity/types'
|
|
9
9
|
export type {Editor, EditorConfig, EditorEvent} from './editor'
|
|
10
|
-
export {EditorEventListener} from './editor-event-listener'
|
|
11
10
|
export {PortableTextEditable} from './editor/Editable'
|
|
12
11
|
export type {PortableTextEditableProps} from './editor/Editable'
|
|
13
12
|
export type {PatchesEvent} from './editor/editor-machine'
|
|
@@ -264,7 +264,7 @@ describe('operationToPatches', () => {
|
|
|
264
264
|
`)
|
|
265
265
|
})
|
|
266
266
|
|
|
267
|
-
|
|
267
|
+
test('produce correct insert child patch', () => {
|
|
268
268
|
expect(
|
|
269
269
|
insertNodePatch(
|
|
270
270
|
schema,
|
|
@@ -283,30 +283,28 @@ describe('operationToPatches', () => {
|
|
|
283
283
|
|
|
284
284
|
createDefaultValue(),
|
|
285
285
|
),
|
|
286
|
-
).
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
]
|
|
309
|
-
`)
|
|
286
|
+
).toEqual([
|
|
287
|
+
{
|
|
288
|
+
items: [
|
|
289
|
+
{
|
|
290
|
+
_key: 'c130395c640c',
|
|
291
|
+
_type: 'someObject',
|
|
292
|
+
title: 'The Object',
|
|
293
|
+
},
|
|
294
|
+
],
|
|
295
|
+
path: [
|
|
296
|
+
{
|
|
297
|
+
_key: '1f2e64b47787',
|
|
298
|
+
},
|
|
299
|
+
'children',
|
|
300
|
+
{
|
|
301
|
+
_key: 'fd9b4a4e6c0b',
|
|
302
|
+
},
|
|
303
|
+
],
|
|
304
|
+
position: 'after',
|
|
305
|
+
type: 'insert',
|
|
306
|
+
},
|
|
307
|
+
])
|
|
310
308
|
})
|
|
311
309
|
|
|
312
310
|
it('produce correct insert text patch', () => {
|
|
@@ -309,32 +309,41 @@ export function insertNodePatch(
|
|
|
309
309
|
block.children.length === 0 || !block.children[operation.path[1] - 1]
|
|
310
310
|
? 'before'
|
|
311
311
|
: 'after'
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
312
|
+
const path =
|
|
313
|
+
block.children.length <= 1 || !block.children[operation.path[1] - 1]
|
|
314
|
+
? [{_key: block._key}, 'children', 0]
|
|
315
|
+
: [
|
|
316
|
+
{_key: block._key},
|
|
317
|
+
'children',
|
|
318
|
+
{_key: block.children[operation.path[1] - 1]._key},
|
|
319
|
+
]
|
|
320
|
+
|
|
321
|
+
if (Text.isText(operation.node)) {
|
|
322
|
+
return [insert([operation.node], position, path)]
|
|
316
323
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
},
|
|
324
|
-
|
|
325
|
-
schema.block.name,
|
|
326
|
-
)[0] as PortableTextTextBlock
|
|
327
|
-
const child = blk.children[0]
|
|
324
|
+
|
|
325
|
+
const _type = operation.node._type
|
|
326
|
+
const _key = operation.node._key
|
|
327
|
+
const value =
|
|
328
|
+
'value' in operation.node && typeof operation.node.value === 'object'
|
|
329
|
+
? operation.node.value
|
|
330
|
+
: ({} satisfies Record<string, unknown>)
|
|
331
|
+
|
|
328
332
|
return [
|
|
329
|
-
insert(
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
333
|
+
insert(
|
|
334
|
+
[
|
|
335
|
+
{
|
|
336
|
+
_type,
|
|
337
|
+
_key,
|
|
338
|
+
...value,
|
|
339
|
+
},
|
|
340
|
+
],
|
|
341
|
+
position,
|
|
342
|
+
path,
|
|
343
|
+
),
|
|
336
344
|
]
|
|
337
345
|
}
|
|
346
|
+
|
|
338
347
|
return []
|
|
339
348
|
}
|
|
340
349
|
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import {compileSchema, defineSchema} from '@portabletext/schema'
|
|
2
|
+
import {createTestKeyGenerator} from '@portabletext/test'
|
|
2
3
|
import {expect, test} from 'vitest'
|
|
3
4
|
import {getSelectionText} from './selection-text'
|
|
4
5
|
|
|
5
6
|
test(getSelectionText.name, () => {
|
|
7
|
+
const keyGenerator = createTestKeyGenerator()
|
|
6
8
|
const schema = compileSchema(defineSchema({}))
|
|
7
9
|
const splitBlock = {
|
|
8
10
|
_type: 'block',
|
|
@@ -18,6 +20,7 @@ test(getSelectionText.name, () => {
|
|
|
18
20
|
|
|
19
21
|
expect(
|
|
20
22
|
getSelectionText({
|
|
23
|
+
keyGenerator,
|
|
21
24
|
schema,
|
|
22
25
|
value: [splitBlock],
|
|
23
26
|
selection: {
|
|
@@ -3,14 +3,17 @@ import type {EditorContext} from '../editor/editor-snapshot'
|
|
|
3
3
|
import {sliceBlocks} from '../utils/util.slice-blocks'
|
|
4
4
|
|
|
5
5
|
export function getSelectionText(
|
|
6
|
-
context: Pick<
|
|
6
|
+
context: Pick<
|
|
7
|
+
EditorContext,
|
|
8
|
+
'keyGenerator' | 'schema' | 'value' | 'selection'
|
|
9
|
+
>,
|
|
7
10
|
) {
|
|
8
11
|
if (!context.selection) {
|
|
9
12
|
return []
|
|
10
13
|
}
|
|
11
14
|
|
|
12
15
|
const slice = sliceBlocks({
|
|
13
|
-
context
|
|
16
|
+
context,
|
|
14
17
|
blocks: context.value,
|
|
15
18
|
})
|
|
16
19
|
|
|
@@ -42,6 +42,7 @@ export function toSlateBlock(
|
|
|
42
42
|
): Descendant {
|
|
43
43
|
const {_type, _key, ...rest} = block
|
|
44
44
|
const isPortableText = block && block._type === schemaTypes.block.name
|
|
45
|
+
|
|
45
46
|
if (isPortableText) {
|
|
46
47
|
const textBlock = block as PortableTextTextBlock
|
|
47
48
|
let hasInlines = false
|
|
@@ -50,35 +51,44 @@ export function toSlateBlock(
|
|
|
50
51
|
const hasMissingChildren = typeof textBlock.children === 'undefined'
|
|
51
52
|
|
|
52
53
|
const children = (textBlock.children || []).map((child) => {
|
|
53
|
-
const {_type:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
54
|
+
const {_type: childType, _key: childKey, ...childProps} = child
|
|
55
|
+
const propKeys = Object.keys(childProps)
|
|
56
|
+
|
|
57
|
+
if (childType !== schemaTypes.span.name) {
|
|
58
|
+
if (propKeys.length === 1 && propKeys.at(0) === 'text') {
|
|
59
|
+
return child
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Return 'slate' version of inline object where the actual
|
|
63
|
+
// value is stored in the `value` property.
|
|
64
|
+
// In slate, inline objects are represented as regular
|
|
65
|
+
// children with actual text node in order to be able to
|
|
66
|
+
// be selected the same way as the rest of the (text) content.
|
|
60
67
|
hasInlines = true
|
|
68
|
+
|
|
61
69
|
return keepObjectEquality(
|
|
62
70
|
{
|
|
63
|
-
_type:
|
|
64
|
-
_key:
|
|
71
|
+
_type: childType,
|
|
72
|
+
_key: childKey,
|
|
65
73
|
children: [
|
|
66
74
|
{
|
|
67
75
|
_key: VOID_CHILD_KEY,
|
|
68
|
-
_type:
|
|
76
|
+
_type: schemaTypes.span.name,
|
|
69
77
|
text: '',
|
|
70
78
|
marks: [],
|
|
71
79
|
},
|
|
72
80
|
],
|
|
73
|
-
value:
|
|
81
|
+
value: childProps,
|
|
74
82
|
__inline: true,
|
|
75
83
|
},
|
|
76
84
|
keyMap,
|
|
77
85
|
)
|
|
78
86
|
}
|
|
87
|
+
|
|
79
88
|
// Original child object (span)
|
|
80
89
|
return child
|
|
81
90
|
})
|
|
91
|
+
|
|
82
92
|
// Return original block
|
|
83
93
|
if (
|
|
84
94
|
!hasMissingStyle &&
|
|
@@ -90,10 +100,12 @@ export function toSlateBlock(
|
|
|
90
100
|
// Original object
|
|
91
101
|
return block
|
|
92
102
|
}
|
|
103
|
+
|
|
93
104
|
// TODO: remove this when we have a better way to handle missing style
|
|
94
105
|
if (hasMissingStyle) {
|
|
95
106
|
rest.style = schemaTypes.styles[0].name
|
|
96
107
|
}
|
|
108
|
+
|
|
97
109
|
return keepObjectEquality(
|
|
98
110
|
{_type, _key, ...rest, children},
|
|
99
111
|
keyMap,
|
|
@@ -52,6 +52,7 @@ export const blockUnsetOperationImplementation: BehaviorOperationImplementation<
|
|
|
52
52
|
context,
|
|
53
53
|
block: omit(parsedBlock, propsToRemove),
|
|
54
54
|
options: {
|
|
55
|
+
normalize: false,
|
|
55
56
|
removeUnusedMarkDefs: true,
|
|
56
57
|
validateFields: true,
|
|
57
58
|
},
|
|
@@ -85,6 +86,7 @@ export const blockUnsetOperationImplementation: BehaviorOperationImplementation<
|
|
|
85
86
|
operation.props.filter((prop) => prop !== '_type'),
|
|
86
87
|
),
|
|
87
88
|
options: {
|
|
89
|
+
normalize: false,
|
|
88
90
|
removeUnusedMarkDefs: true,
|
|
89
91
|
validateFields: true,
|
|
90
92
|
},
|
|
@@ -5,10 +5,8 @@ import type {
|
|
|
5
5
|
} from '../behaviors/behavior.types.event'
|
|
6
6
|
import type {EditorContext} from '../editor/editor-snapshot'
|
|
7
7
|
import {removeDecoratorOperationImplementation} from '../editor/plugins/createWithPortableTextMarkModel'
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
historyUndoOperationImplementation,
|
|
11
|
-
} from '../editor/plugins/createWithUndoRedo'
|
|
8
|
+
import {historyRedoOperationImplementation} from '../history/behavior.operation.history.redo'
|
|
9
|
+
import {historyUndoOperationImplementation} from '../history/behavior.operation.history.undo'
|
|
12
10
|
import type {OmitFromUnion, PickFromUnion} from '../type-utils'
|
|
13
11
|
import type {PortableTextSlateEditor} from '../types/editor'
|
|
14
12
|
import {addAnnotationOperationImplementation} from './behavior.operation.annotation.add'
|
package/src/plugins/index.ts
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
1
|
export {BehaviorPlugin} from './plugin.behavior'
|
|
2
|
-
export {DecoratorShortcutPlugin} from './plugin.decorator-shortcut'
|
|
3
2
|
export {EditorRefPlugin} from './plugin.editor-ref'
|
|
4
3
|
export {EventListenerPlugin} from './plugin.event-listener'
|
|
5
|
-
export {MarkdownPlugin, type MarkdownPluginConfig} from './plugin.markdown'
|
|
6
|
-
export {OneLinePlugin} from './plugin.one-line'
|
package/src/selectors/index.ts
CHANGED
|
@@ -16,7 +16,6 @@ export {getFocusListBlock} from './selector.get-focus-list-block'
|
|
|
16
16
|
export {getFocusSpan} from './selector.get-focus-span'
|
|
17
17
|
export {getFocusTextBlock} from './selector.get-focus-text-block'
|
|
18
18
|
export {getLastBlock} from './selector.get-last-block'
|
|
19
|
-
export {getListIndex} from './selector.get-list-state'
|
|
20
19
|
export {getMarkState, type MarkState} from './selector.get-mark-state'
|
|
21
20
|
export {getNextBlock} from './selector.get-next-block'
|
|
22
21
|
export {getNextInlineObject} from './selector.get-next-inline-object'
|
|
@@ -27,7 +26,6 @@ export {getPreviousInlineObject} from './selector.get-previous-inline-object'
|
|
|
27
26
|
export {getPreviousInlineObjects} from './selector.get-previous-inline-objects'
|
|
28
27
|
export {getPreviousSpan} from './selector.get-previous-span'
|
|
29
28
|
export {getSelectedBlocks} from './selector.get-selected-blocks'
|
|
30
|
-
export {getSelectedSlice} from './selector.get-selected-slice'
|
|
31
29
|
export {getSelectedSpans} from './selector.get-selected-spans'
|
|
32
30
|
export {getSelectedTextBlocks} from './selector.get-selected-text-blocks'
|
|
33
31
|
export {getSelectedValue} from './selector.get-selected-value'
|
|
@@ -41,7 +39,6 @@ export {getSelectionStartPoint} from './selector.get-selection-start-point'
|
|
|
41
39
|
export {getSelectionText} from './selector.get-selection-text'
|
|
42
40
|
export {getBlockTextAfter} from './selector.get-text-after'
|
|
43
41
|
export {getBlockTextBefore} from './selector.get-text-before'
|
|
44
|
-
export {getTrimmedSelection} from './selector.get-trimmed-selection'
|
|
45
42
|
export {getValue} from './selector.get-value'
|
|
46
43
|
export {isActiveAnnotation} from './selector.is-active-annotation'
|
|
47
44
|
export {isActiveDecorator} from './selector.is-active-decorator'
|