@portabletext/editor 1.0.18 → 1.1.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 +140 -66
- package/lib/index.d.ts +140 -66
- package/lib/index.esm.js +1164 -410
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +1164 -410
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +1164 -410
- package/lib/index.mjs.map +1 -1
- package/package.json +8 -4
- package/src/editor/Editable.tsx +107 -36
- package/src/editor/PortableTextEditor.tsx +47 -12
- package/src/editor/__tests__/PortableTextEditor.test.tsx +42 -15
- package/src/editor/__tests__/PortableTextEditorTester.tsx +50 -38
- package/src/editor/__tests__/RangeDecorations.test.tsx +0 -1
- package/src/editor/__tests__/handleClick.test.tsx +28 -9
- package/src/editor/__tests__/insert-block.test.tsx +22 -6
- package/src/editor/__tests__/pteWarningsSelfSolving.test.tsx +30 -62
- package/src/editor/__tests__/utils.ts +10 -3
- package/src/editor/components/DraggableBlock.tsx +36 -13
- package/src/editor/components/Element.tsx +59 -17
- package/src/editor/components/Leaf.tsx +106 -68
- package/src/editor/components/SlateContainer.tsx +12 -5
- package/src/editor/components/Synchronizer.tsx +5 -2
- package/src/editor/hooks/usePortableTextEditor.ts +2 -2
- package/src/editor/hooks/usePortableTextEditorSelection.tsx +9 -3
- package/src/editor/hooks/useSyncValue.test.tsx +9 -4
- package/src/editor/hooks/useSyncValue.ts +199 -130
- package/src/editor/nodes/DefaultAnnotation.tsx +6 -3
- package/src/editor/plugins/__tests__/createWithInsertData.test.tsx +25 -7
- package/src/editor/plugins/__tests__/withEditableAPIDelete.test.tsx +26 -9
- package/src/editor/plugins/__tests__/withEditableAPIGetFragment.test.tsx +15 -5
- package/src/editor/plugins/__tests__/withEditableAPIInsert.test.tsx +60 -19
- package/src/editor/plugins/__tests__/withEditableAPISelectionsOverlapping.test.tsx +4 -2
- package/src/editor/plugins/__tests__/withPortableTextLists.test.tsx +4 -2
- package/src/editor/plugins/__tests__/withPortableTextMarkModel.test.tsx +61 -550
- package/src/editor/plugins/__tests__/withPortableTextSelections.test.tsx +6 -3
- package/src/editor/plugins/__tests__/withUndoRedo.test.tsx +30 -13
- package/src/editor/plugins/createWithEditableAPI.ts +354 -115
- package/src/editor/plugins/createWithHotKeys.ts +41 -121
- package/src/editor/plugins/createWithInsertBreak.ts +166 -27
- package/src/editor/plugins/createWithInsertData.ts +60 -23
- package/src/editor/plugins/createWithMaxBlocks.ts +5 -2
- package/src/editor/plugins/createWithObjectKeys.ts +7 -3
- package/src/editor/plugins/createWithPatches.ts +60 -16
- package/src/editor/plugins/createWithPlaceholderBlock.ts +7 -3
- package/src/editor/plugins/createWithPortableTextBlockStyle.ts +17 -7
- package/src/editor/plugins/createWithPortableTextLists.ts +21 -8
- package/src/editor/plugins/createWithPortableTextMarkModel.ts +301 -155
- package/src/editor/plugins/createWithPortableTextSelections.ts +4 -2
- package/src/editor/plugins/createWithSchemaTypes.ts +25 -9
- package/src/editor/plugins/createWithUndoRedo.ts +107 -24
- package/src/editor/plugins/createWithUtils.ts +32 -10
- package/src/editor/plugins/index.ts +31 -10
- package/src/types/editor.ts +44 -15
- package/src/types/options.ts +4 -2
- package/src/types/slate.ts +2 -2
- package/src/utils/__tests__/dmpToOperations.test.ts +38 -13
- package/src/utils/__tests__/operationToPatches.test.ts +3 -2
- package/src/utils/__tests__/patchToOperations.test.ts +15 -4
- package/src/utils/__tests__/ranges.test.ts +8 -3
- package/src/utils/__tests__/valueNormalization.test.tsx +12 -4
- package/src/utils/__tests__/values.test.ts +0 -1
- package/src/utils/applyPatch.ts +71 -20
- package/src/utils/getPortableTextMemberSchemaTypes.ts +30 -15
- package/src/utils/operationToPatches.ts +126 -43
- package/src/utils/paths.ts +24 -7
- package/src/utils/ranges.ts +12 -5
- package/src/utils/selection.ts +19 -7
- package/src/utils/validateValue.ts +118 -45
- package/src/utils/values.ts +31 -10
- package/src/utils/weakMaps.ts +18 -8
- package/src/utils/withChanges.ts +4 -2
- package/src/editor/plugins/__tests__/withHotkeys.test.tsx +0 -212
- package/src/editor/plugins/__tests__/withInsertBreak.test.tsx +0 -220
- package/src/editor/plugins/__tests__/withPlaceholderBlock.test.tsx +0 -133
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
type PortableTextTextBlock,
|
|
11
11
|
} from '@sanity/types'
|
|
12
12
|
import {type Descendant, type Operation} from 'slate'
|
|
13
|
-
|
|
14
13
|
import {type PortableTextSlateEditor} from '../../types/editor'
|
|
15
14
|
import {diffMatchPatch} from '../applyPatch'
|
|
16
15
|
|
|
@@ -23,7 +22,9 @@ describe('operationToPatches: diffMatchPatch', () => {
|
|
|
23
22
|
test('should apply the most basic additive operation correctly', () => {
|
|
24
23
|
const source = 'Hello'
|
|
25
24
|
const target = 'Hello there'
|
|
26
|
-
const patch = getPteDmpPatch(
|
|
25
|
+
const patch = getPteDmpPatch(
|
|
26
|
+
stringifyPatches(makePatches(makeDiff(source, target))),
|
|
27
|
+
)
|
|
27
28
|
const editor = getMockEditor({text: source})
|
|
28
29
|
expect(diffMatchPatch(editor, patch)).toBe(true)
|
|
29
30
|
expect(editor.getText()).toBe(target)
|
|
@@ -32,7 +33,9 @@ describe('operationToPatches: diffMatchPatch', () => {
|
|
|
32
33
|
test('should apply the most basic removal operation correctly', () => {
|
|
33
34
|
const source = 'Hello there'
|
|
34
35
|
const target = 'Hello'
|
|
35
|
-
const patch = getPteDmpPatch(
|
|
36
|
+
const patch = getPteDmpPatch(
|
|
37
|
+
stringifyPatches(makePatches(makeDiff(source, target))),
|
|
38
|
+
)
|
|
36
39
|
const editor = getMockEditor({text: source})
|
|
37
40
|
expect(diffMatchPatch(editor, patch)).toBe(true)
|
|
38
41
|
expect(editor.getText()).toBe(target)
|
|
@@ -41,7 +44,9 @@ describe('operationToPatches: diffMatchPatch', () => {
|
|
|
41
44
|
test('should treat equality as noops', () => {
|
|
42
45
|
const source = 'Hello'
|
|
43
46
|
const target = 'Hello'
|
|
44
|
-
const patch = getPteDmpPatch(
|
|
47
|
+
const patch = getPteDmpPatch(
|
|
48
|
+
stringifyPatches(makePatches(makeDiff(source, target))),
|
|
49
|
+
)
|
|
45
50
|
const editor = getMockEditor({text: source})
|
|
46
51
|
expect(diffMatchPatch(editor, patch)).toBe(true)
|
|
47
52
|
expect(editor.getText()).toBe(target)
|
|
@@ -50,7 +55,9 @@ describe('operationToPatches: diffMatchPatch', () => {
|
|
|
50
55
|
test('should apply combined add + remove operations', () => {
|
|
51
56
|
const source = 'A quick brown fox jumps over the very lazy dog'
|
|
52
57
|
const target = 'The quick brown fox jumps over the lazy dog'
|
|
53
|
-
const patch = getPteDmpPatch(
|
|
58
|
+
const patch = getPteDmpPatch(
|
|
59
|
+
stringifyPatches(makePatches(makeDiff(source, target))),
|
|
60
|
+
)
|
|
54
61
|
const editor = getMockEditor({text: source})
|
|
55
62
|
expect(diffMatchPatch(editor, patch)).toBe(true)
|
|
56
63
|
expect(editor.getText()).toBe(target)
|
|
@@ -58,8 +65,11 @@ describe('operationToPatches: diffMatchPatch', () => {
|
|
|
58
65
|
|
|
59
66
|
test('should apply combined add + remove operations', () => {
|
|
60
67
|
const source = 'Many quick brown fox jumps over the very lazy dog'
|
|
61
|
-
const target =
|
|
62
|
-
|
|
68
|
+
const target =
|
|
69
|
+
'The many, quick, brown, foxes jumps over all of the lazy dogs'
|
|
70
|
+
const patch = getPteDmpPatch(
|
|
71
|
+
stringifyPatches(makePatches(makeDiff(source, target))),
|
|
72
|
+
)
|
|
63
73
|
const editor = getMockEditor({text: source})
|
|
64
74
|
expect(diffMatchPatch(editor, patch)).toBe(true)
|
|
65
75
|
expect(editor.getText()).toBe(target)
|
|
@@ -70,7 +80,9 @@ describe('operationToPatches: diffMatchPatch', () => {
|
|
|
70
80
|
const line2 = 'But the slow green frog jumps over the wild cat'
|
|
71
81
|
const source = [line1, line2, line1, line2].join('\n')
|
|
72
82
|
const target = [line2, line1, line2, line1].join('\n')
|
|
73
|
-
const patch = getPteDmpPatch(
|
|
83
|
+
const patch = getPteDmpPatch(
|
|
84
|
+
stringifyPatches(makePatches(makeDiff(source, target))),
|
|
85
|
+
)
|
|
74
86
|
const editor = getMockEditor({text: source})
|
|
75
87
|
expect(diffMatchPatch(editor, patch)).toBe(true)
|
|
76
88
|
expect(editor.getText()).toBe(target)
|
|
@@ -79,7 +91,9 @@ describe('operationToPatches: diffMatchPatch', () => {
|
|
|
79
91
|
test('should apply larger text differences correctly', () => {
|
|
80
92
|
const source = `Portable Text is a agnostic abstraction of "rich text" that can be stringified into any markup language, for instance HTML, Markdown, SSML, XML, etc. It's designed to be efficient for collaboration, and makes it possible to enrich rich text with data structures in depth.\n\nPortable Text is built on the idea of rich text as an array of blocks, themselves arrays of children spans. Each block can have a style and a set of mark dfinitions, which describe data structures distributed on the children spans. Portable Text also allows for inserting arbitrary data objects in the array, only requiring _type-key. Portable Text also allows for custom objects in the root array, enabling rendering environments to mix rich text with custom content types.\n\nPortable Text is a combination of arrays and objects. In its simplest form it's an array of objects with an array of children. Some definitions: \n- Block: Typically recognized as a section of a text, e.g. a paragraph or a heading.\n- Span: Piece of text with a set of marks, e.g. bold or italic.\n- Mark: A mark is a data structure that can be appliad to a span, e.g. a link or a comment.\n- Mark definition: A mark definition is a structure that describes a mark, a link or a comment.`
|
|
81
93
|
const target = `Portable Text is an agnostic abstraction of rich text that can be serialized into pretty much any markup language, be it HTML, Markdown, SSML, XML, etc. It is designed to be efficient for real-time collaborative interfaces, and makes it possible to annotate rich text with additional data structures recursively.\n\nPortable Text is built on the idea of rich text as an array of blocks, themselves arrays of child spans. Each block can have a style and a set of mark definitions, which describe data structures that can be applied on the children spans. Portable Text also allows for inserting arbitrary data objects in the array, only requiring _type-key. Portable Text also allows for custom content objects in the root array, enabling editing- and rendering environments to mix rich text with custom content types.\n\nPortable Text is a recursive composition of arrays and objects. In its simplest form it's an array of objects of a type with an array of children. Some definitions: \n- Block: A block is what's typically recognized as a section of a text, e.g. a paragraph or a heading.\n- Span: A span is a piece of text with a set of marks, e.g. bold or italic.\n- Mark: A mark is a data structure that can be applied to a span, e.g. a link or a comment.\n- Mark definition: A mark definition is a data structure that describes a mark, e.g. a link or a comment.`
|
|
82
|
-
const patch = getPteDmpPatch(
|
|
94
|
+
const patch = getPteDmpPatch(
|
|
95
|
+
stringifyPatches(makePatches(makeDiff(source, target))),
|
|
96
|
+
)
|
|
83
97
|
const editor = getMockEditor({text: source})
|
|
84
98
|
expect(diffMatchPatch(editor, patch)).toBe(true)
|
|
85
99
|
expect(editor.getText()).toBe(target)
|
|
@@ -88,7 +102,9 @@ describe('operationToPatches: diffMatchPatch', () => {
|
|
|
88
102
|
test('should apply offset text differences correctly', () => {
|
|
89
103
|
const source = `This string has changes, but they occur somewhere near the end. That means we need to use an offset to get at the change, we cannot just rely on equality segaments in the generated diff.`
|
|
90
104
|
const target = `This string has changes, but they occur somewhere near the end. That means we need to use an offset to get at the change, we cannot just rely on equality segments in the generated diff.`
|
|
91
|
-
const patch = getPteDmpPatch(
|
|
105
|
+
const patch = getPteDmpPatch(
|
|
106
|
+
stringifyPatches(makePatches(makeDiff(source, target))),
|
|
107
|
+
)
|
|
92
108
|
const editor = getMockEditor({text: source})
|
|
93
109
|
expect(diffMatchPatch(editor, patch)).toBe(true)
|
|
94
110
|
expect(editor.getText()).toBe(target)
|
|
@@ -115,13 +131,20 @@ function getMockEditor(options: MockEditorOptions): Pick<
|
|
|
115
131
|
> & {
|
|
116
132
|
getText: () => string
|
|
117
133
|
} {
|
|
118
|
-
let children: PortableTextBlock[] =
|
|
134
|
+
let children: PortableTextBlock[] =
|
|
135
|
+
'children' in options ? options.children : []
|
|
119
136
|
if (!('children' in options)) {
|
|
120
137
|
children = [
|
|
121
138
|
{
|
|
122
139
|
_type: 'block',
|
|
123
140
|
_key: 'bA',
|
|
124
|
-
children: [
|
|
141
|
+
children: [
|
|
142
|
+
{
|
|
143
|
+
_type: 'span',
|
|
144
|
+
_key: 's1',
|
|
145
|
+
text: 'text' in options ? options.text : '',
|
|
146
|
+
},
|
|
147
|
+
],
|
|
125
148
|
markDefs: [],
|
|
126
149
|
},
|
|
127
150
|
]
|
|
@@ -129,7 +152,9 @@ function getMockEditor(options: MockEditorOptions): Pick<
|
|
|
129
152
|
|
|
130
153
|
function getText(blockKey?: string) {
|
|
131
154
|
return children
|
|
132
|
-
.filter((child): child is PortableTextTextBlock =>
|
|
155
|
+
.filter((child): child is PortableTextTextBlock =>
|
|
156
|
+
isPortableTextTextBlock(child),
|
|
157
|
+
)
|
|
133
158
|
.filter((child) => (blockKey ? child._key === blockKey : true))
|
|
134
159
|
.flatMap((block) =>
|
|
135
160
|
block.children
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {beforeEach, describe, expect, it} from '@jest/globals'
|
|
2
2
|
import {type PortableTextTextBlock} from '@sanity/types'
|
|
3
3
|
import {createEditor, type Descendant} from 'slate'
|
|
4
|
-
|
|
5
4
|
import {PortableTextEditor, type PortableTextEditorProps} from '../..'
|
|
6
5
|
import {schemaType} from '../../editor/__tests__/PortableTextEditorTester'
|
|
7
6
|
import {defaultKeyGenerator} from '../../editor/hooks/usePortableTextEditorKeyGenerator'
|
|
@@ -14,7 +13,9 @@ const portableTextFeatures = getPortableTextMemberSchemaTypes(schemaType)
|
|
|
14
13
|
const operationToPatches = createOperationToPatches(portableTextFeatures)
|
|
15
14
|
|
|
16
15
|
const {editor} = withPlugins(createEditor(), {
|
|
17
|
-
portableTextEditor: new PortableTextEditor({
|
|
16
|
+
portableTextEditor: new PortableTextEditor({
|
|
17
|
+
schemaType,
|
|
18
|
+
} as PortableTextEditorProps),
|
|
18
19
|
keyGenerator: defaultKeyGenerator,
|
|
19
20
|
readOnly: false,
|
|
20
21
|
})
|
|
@@ -2,7 +2,6 @@ import {beforeEach, describe, expect, it} from '@jest/globals'
|
|
|
2
2
|
import {type Patch} from '@portabletext/patches'
|
|
3
3
|
import {noop} from 'lodash'
|
|
4
4
|
import {createEditor, type Descendant} from 'slate'
|
|
5
|
-
|
|
6
5
|
import {keyGenerator, PortableTextEditor} from '../..'
|
|
7
6
|
import {schemaType} from '../../editor/__tests__/PortableTextEditorTester'
|
|
8
7
|
import {withPlugins} from '../../editor/plugins'
|
|
@@ -51,7 +50,11 @@ describe('operationToPatches', () => {
|
|
|
51
50
|
it('makes the correct operations for block objects', () => {
|
|
52
51
|
editor.children = createDefaultValue()
|
|
53
52
|
const patches = [
|
|
54
|
-
{
|
|
53
|
+
{
|
|
54
|
+
type: 'unset',
|
|
55
|
+
path: [{_key: 'c01739b0d03b'}, 'hotspot'],
|
|
56
|
+
origin: 'remote',
|
|
57
|
+
},
|
|
55
58
|
{type: 'unset', path: [{_key: 'c01739b0d03b'}, 'crop'], origin: 'remote'},
|
|
56
59
|
{
|
|
57
60
|
type: 'set',
|
|
@@ -114,7 +117,11 @@ describe('operationToPatches', () => {
|
|
|
114
117
|
},
|
|
115
118
|
]
|
|
116
119
|
const patches = [
|
|
117
|
-
{
|
|
120
|
+
{
|
|
121
|
+
type: 'insert',
|
|
122
|
+
path: [{_key: 'c01739b0d03b'}, 'nestedArray'],
|
|
123
|
+
origin: 'remote',
|
|
124
|
+
},
|
|
118
125
|
] as Patch[]
|
|
119
126
|
patches.forEach((p) => {
|
|
120
127
|
patchToOperations(editor, p)
|
|
@@ -173,7 +180,11 @@ describe('operationToPatches', () => {
|
|
|
173
180
|
},
|
|
174
181
|
]
|
|
175
182
|
const patches = [
|
|
176
|
-
{
|
|
183
|
+
{
|
|
184
|
+
type: 'unset',
|
|
185
|
+
path: [{_key: 'c01739b0d03b'}, 'nestedArray', 0],
|
|
186
|
+
origin: 'remote',
|
|
187
|
+
},
|
|
177
188
|
] as Patch[]
|
|
178
189
|
patches.forEach((p) => {
|
|
179
190
|
patchToOperations(editor, p)
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import {describe, expect, it} from '@jest/globals'
|
|
2
2
|
import {type InsertTextOperation, type Range} from 'slate'
|
|
3
|
-
|
|
4
3
|
import {moveRangeByOperation} from '../ranges'
|
|
5
4
|
|
|
6
5
|
describe('moveRangeByOperation', () => {
|
|
7
6
|
it('should move range when inserting text in front of it', () => {
|
|
8
|
-
const range: Range = {
|
|
7
|
+
const range: Range = {
|
|
8
|
+
anchor: {path: [0, 0], offset: 1},
|
|
9
|
+
focus: {path: [0, 0], offset: 3},
|
|
10
|
+
}
|
|
9
11
|
const operation: InsertTextOperation = {
|
|
10
12
|
type: 'insert_text',
|
|
11
13
|
path: [0, 0],
|
|
@@ -13,6 +15,9 @@ describe('moveRangeByOperation', () => {
|
|
|
13
15
|
text: 'foo',
|
|
14
16
|
}
|
|
15
17
|
const newRange = moveRangeByOperation(range, operation)
|
|
16
|
-
expect(newRange).toEqual({
|
|
18
|
+
expect(newRange).toEqual({
|
|
19
|
+
anchor: {path: [0, 0], offset: 4},
|
|
20
|
+
focus: {path: [0, 0], offset: 6},
|
|
21
|
+
})
|
|
17
22
|
})
|
|
18
23
|
})
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import {describe, expect, it, jest} from '@jest/globals'
|
|
2
2
|
import {render, waitFor} from '@testing-library/react'
|
|
3
3
|
import {createRef, type RefObject} from 'react'
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
PortableTextEditorTester,
|
|
6
|
+
schemaType,
|
|
7
|
+
} from '../../editor/__tests__/PortableTextEditorTester'
|
|
6
8
|
import {PortableTextEditor} from '../../editor/PortableTextEditor'
|
|
7
9
|
|
|
8
10
|
describe('values: normalization', () => {
|
|
@@ -36,8 +38,14 @@ describe('values: normalization', () => {
|
|
|
36
38
|
if (editorRef.current) {
|
|
37
39
|
PortableTextEditor.focus(editorRef.current)
|
|
38
40
|
PortableTextEditor.select(editorRef.current, {
|
|
39
|
-
focus: {
|
|
40
|
-
|
|
41
|
+
focus: {
|
|
42
|
+
path: [{_key: '5fc57af23597'}, 'children', {_key: 'be1c67c6971a'}],
|
|
43
|
+
offset: 0,
|
|
44
|
+
},
|
|
45
|
+
anchor: {
|
|
46
|
+
path: [{_key: '5fc57af23597'}, 'children', {_key: 'be1c67c6971a'}],
|
|
47
|
+
offset: 5,
|
|
48
|
+
},
|
|
41
49
|
})
|
|
42
50
|
PortableTextEditor.toggleMark(editorRef.current, 'strong')
|
|
43
51
|
expect(PortableTextEditor.getValue(editorRef.current)).toEqual([
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {describe, expect, it} from '@jest/globals'
|
|
2
|
-
|
|
3
2
|
import {schemaType} from '../../editor/__tests__/PortableTextEditorTester'
|
|
4
3
|
import {getPortableTextMemberSchemaTypes} from '../getPortableTextMemberSchemaTypes'
|
|
5
4
|
import {fromSlateValue, toSlateValue} from '../values'
|
package/src/utils/applyPatch.ts
CHANGED
|
@@ -8,11 +8,11 @@ import {
|
|
|
8
8
|
type UnsetPatch,
|
|
9
9
|
} from '@portabletext/patches'
|
|
10
10
|
import {
|
|
11
|
-
applyPatches as diffMatchPatchApplyPatches,
|
|
12
11
|
cleanupEfficiency,
|
|
13
12
|
DIFF_DELETE,
|
|
14
13
|
DIFF_EQUAL,
|
|
15
14
|
DIFF_INSERT,
|
|
15
|
+
applyPatches as diffMatchPatchApplyPatches,
|
|
16
16
|
makeDiff,
|
|
17
17
|
parsePatch,
|
|
18
18
|
} from '@sanity/diff-match-patch'
|
|
@@ -23,9 +23,18 @@ import {
|
|
|
23
23
|
type PortableTextBlock,
|
|
24
24
|
type PortableTextChild,
|
|
25
25
|
} from '@sanity/types'
|
|
26
|
-
import {
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
import {
|
|
27
|
+
Element,
|
|
28
|
+
Text,
|
|
29
|
+
Transforms,
|
|
30
|
+
type Descendant,
|
|
31
|
+
type Node,
|
|
32
|
+
type Path as SlatePath,
|
|
33
|
+
} from 'slate'
|
|
34
|
+
import {
|
|
35
|
+
type PortableTextMemberSchemaTypes,
|
|
36
|
+
type PortableTextSlateEditor,
|
|
37
|
+
} from '../types/editor'
|
|
29
38
|
import {debugWithName} from './debug'
|
|
30
39
|
import {toSlateValue} from './values'
|
|
31
40
|
import {KEY_TO_SLATE_ELEMENT} from './weakMaps'
|
|
@@ -46,7 +55,9 @@ export function createApplyPatch(
|
|
|
46
55
|
|
|
47
56
|
// Save some CPU cycles by not stringifying unless enabled
|
|
48
57
|
if (debugVerbose) {
|
|
49
|
-
debug(
|
|
58
|
+
debug(
|
|
59
|
+
'\n\nNEW PATCH =============================================================',
|
|
60
|
+
)
|
|
50
61
|
debug(JSON.stringify(patch, null, 2))
|
|
51
62
|
}
|
|
52
63
|
|
|
@@ -91,7 +102,10 @@ export function diffMatchPatch(
|
|
|
91
102
|
>,
|
|
92
103
|
patch: DiffMatchPatch,
|
|
93
104
|
): boolean {
|
|
94
|
-
const {block, child, childPath} = findBlockAndChildFromPath(
|
|
105
|
+
const {block, child, childPath} = findBlockAndChildFromPath(
|
|
106
|
+
editor,
|
|
107
|
+
patch.path,
|
|
108
|
+
)
|
|
95
109
|
if (!block) {
|
|
96
110
|
debug('Block not found')
|
|
97
111
|
return false
|
|
@@ -112,7 +126,9 @@ export function diffMatchPatch(
|
|
|
112
126
|
}
|
|
113
127
|
|
|
114
128
|
const patches = parsePatch(patch.value)
|
|
115
|
-
const [newValue] = diffMatchPatchApplyPatches(patches, child.text, {
|
|
129
|
+
const [newValue] = diffMatchPatchApplyPatches(patches, child.text, {
|
|
130
|
+
allowExceedingIndices: true,
|
|
131
|
+
})
|
|
116
132
|
const diff = cleanupEfficiency(makeDiff(child.text, newValue), 5)
|
|
117
133
|
|
|
118
134
|
debugState(editor, 'before')
|
|
@@ -160,7 +176,8 @@ function insertPatch(
|
|
|
160
176
|
KEY_TO_SLATE_ELEMENT.get(editor),
|
|
161
177
|
) as Descendant[]
|
|
162
178
|
const targetBlockIndex = targetBlockPath[0]
|
|
163
|
-
const normalizedIdx =
|
|
179
|
+
const normalizedIdx =
|
|
180
|
+
position === 'after' ? targetBlockIndex + 1 : targetBlockIndex
|
|
164
181
|
debug(`Inserting blocks at path [${normalizedIdx}]`)
|
|
165
182
|
debugState(editor, 'before')
|
|
166
183
|
Transforms.insertNodes(editor, blocksToInsert, {at: [normalizedIdx]})
|
|
@@ -181,12 +198,15 @@ function insertPatch(
|
|
|
181
198
|
KEY_TO_SLATE_ELEMENT.get(editor),
|
|
182
199
|
)
|
|
183
200
|
const targetChildIndex = targetChildPath[1]
|
|
184
|
-
const normalizedIdx =
|
|
201
|
+
const normalizedIdx =
|
|
202
|
+
position === 'after' ? targetChildIndex + 1 : targetChildIndex
|
|
185
203
|
const childInsertPath = [targetChildPath[0], normalizedIdx]
|
|
186
204
|
debug(`Inserting children at path ${childInsertPath}`)
|
|
187
205
|
debugState(editor, 'before')
|
|
188
206
|
if (childrenToInsert && Element.isElement(childrenToInsert[0])) {
|
|
189
|
-
Transforms.insertNodes(editor, childrenToInsert[0].children, {
|
|
207
|
+
Transforms.insertNodes(editor, childrenToInsert[0].children, {
|
|
208
|
+
at: childInsertPath,
|
|
209
|
+
})
|
|
190
210
|
}
|
|
191
211
|
debugState(editor, 'after')
|
|
192
212
|
return true
|
|
@@ -198,7 +218,10 @@ function setPatch(editor: PortableTextSlateEditor, patch: SetPatch) {
|
|
|
198
218
|
value = {}
|
|
199
219
|
value[patch.path[3]] = patch.value
|
|
200
220
|
}
|
|
201
|
-
const {block, blockPath, child, childPath} = findBlockAndChildFromPath(
|
|
221
|
+
const {block, blockPath, child, childPath} = findBlockAndChildFromPath(
|
|
222
|
+
editor,
|
|
223
|
+
patch.path,
|
|
224
|
+
)
|
|
202
225
|
|
|
203
226
|
if (!block) {
|
|
204
227
|
debug('Block not found')
|
|
@@ -287,7 +310,11 @@ function setPatch(editor: PortableTextSlateEditor, patch: SetPatch) {
|
|
|
287
310
|
return true
|
|
288
311
|
}
|
|
289
312
|
|
|
290
|
-
function unsetPatch(
|
|
313
|
+
function unsetPatch(
|
|
314
|
+
editor: PortableTextSlateEditor,
|
|
315
|
+
patch: UnsetPatch,
|
|
316
|
+
previousPatch?: Patch,
|
|
317
|
+
) {
|
|
291
318
|
// Value
|
|
292
319
|
if (patch.path.length === 0) {
|
|
293
320
|
debug('Removing everything')
|
|
@@ -297,7 +324,7 @@ function unsetPatch(editor: PortableTextSlateEditor, patch: UnsetPatch, previous
|
|
|
297
324
|
editor.children.forEach((c, i) => {
|
|
298
325
|
Transforms.removeNodes(editor, {at: [i]})
|
|
299
326
|
})
|
|
300
|
-
Transforms.insertNodes(editor, editor.
|
|
327
|
+
Transforms.insertNodes(editor, editor.pteCreateTextBlock({decorators: []}))
|
|
301
328
|
if (previousSelection) {
|
|
302
329
|
Transforms.select(editor, {
|
|
303
330
|
anchor: {path: [0, 0], offset: 0},
|
|
@@ -309,7 +336,10 @@ function unsetPatch(editor: PortableTextSlateEditor, patch: UnsetPatch, previous
|
|
|
309
336
|
debugState(editor, 'after')
|
|
310
337
|
return true
|
|
311
338
|
}
|
|
312
|
-
const {block, blockPath, child, childPath} = findBlockAndChildFromPath(
|
|
339
|
+
const {block, blockPath, child, childPath} = findBlockAndChildFromPath(
|
|
340
|
+
editor,
|
|
341
|
+
patch.path,
|
|
342
|
+
)
|
|
313
343
|
|
|
314
344
|
// Single blocks
|
|
315
345
|
if (patch.path.length === 1) {
|
|
@@ -327,7 +357,11 @@ function unsetPatch(editor: PortableTextSlateEditor, patch: UnsetPatch, previous
|
|
|
327
357
|
}
|
|
328
358
|
|
|
329
359
|
// Unset on text block children
|
|
330
|
-
if (
|
|
360
|
+
if (
|
|
361
|
+
editor.isTextBlock(block) &&
|
|
362
|
+
patch.path[1] === 'children' &&
|
|
363
|
+
patch.path.length === 3
|
|
364
|
+
) {
|
|
331
365
|
if (!child || !childPath) {
|
|
332
366
|
debug('Child not found')
|
|
333
367
|
return false
|
|
@@ -349,7 +383,10 @@ function isKeyedSegment(segment: PathSegment): segment is KeyedSegment {
|
|
|
349
383
|
}
|
|
350
384
|
|
|
351
385
|
function debugState(
|
|
352
|
-
editor: Pick<
|
|
386
|
+
editor: Pick<
|
|
387
|
+
PortableTextSlateEditor,
|
|
388
|
+
'children' | 'isTextBlock' | 'apply' | 'selection'
|
|
389
|
+
>,
|
|
353
390
|
stateName: string,
|
|
354
391
|
) {
|
|
355
392
|
if (!debugVerbose) {
|
|
@@ -369,7 +406,9 @@ function findBlockFromPath(
|
|
|
369
406
|
): {block?: Descendant; path?: SlatePath} {
|
|
370
407
|
let blockIndex = -1
|
|
371
408
|
const block = editor.children.find((node: Descendant, index: number) => {
|
|
372
|
-
const isMatch = isKeyedSegment(path[0])
|
|
409
|
+
const isMatch = isKeyedSegment(path[0])
|
|
410
|
+
? node._key === path[0]._key
|
|
411
|
+
: index === path[0]
|
|
373
412
|
if (isMatch) {
|
|
374
413
|
blockIndex = index
|
|
375
414
|
}
|
|
@@ -387,14 +426,21 @@ function findBlockAndChildFromPath(
|
|
|
387
426
|
'children' | 'isTextBlock' | 'apply' | 'selection' | 'onChange'
|
|
388
427
|
>,
|
|
389
428
|
path: Path,
|
|
390
|
-
): {
|
|
429
|
+
): {
|
|
430
|
+
child?: Descendant
|
|
431
|
+
childPath?: SlatePath
|
|
432
|
+
block?: Descendant
|
|
433
|
+
blockPath?: SlatePath
|
|
434
|
+
} {
|
|
391
435
|
const {block, path: blockPath} = findBlockFromPath(editor, path)
|
|
392
436
|
if (!(Element.isElement(block) && path[1] === 'children')) {
|
|
393
437
|
return {block, blockPath, child: undefined, childPath: undefined}
|
|
394
438
|
}
|
|
395
439
|
let childIndex = -1
|
|
396
440
|
const child = block.children.find((node, index: number) => {
|
|
397
|
-
const isMatch = isKeyedSegment(path[2])
|
|
441
|
+
const isMatch = isKeyedSegment(path[2])
|
|
442
|
+
? node._key === path[2]._key
|
|
443
|
+
: index === path[2]
|
|
398
444
|
if (isMatch) {
|
|
399
445
|
childIndex = index
|
|
400
446
|
}
|
|
@@ -403,5 +449,10 @@ function findBlockAndChildFromPath(
|
|
|
403
449
|
if (!child) {
|
|
404
450
|
return {block, blockPath, child: undefined, childPath: undefined}
|
|
405
451
|
}
|
|
406
|
-
return {
|
|
452
|
+
return {
|
|
453
|
+
block,
|
|
454
|
+
child,
|
|
455
|
+
blockPath,
|
|
456
|
+
childPath: blockPath?.concat(childIndex) as SlatePath,
|
|
457
|
+
}
|
|
407
458
|
}
|
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
type SchemaType,
|
|
7
7
|
type SpanSchemaType,
|
|
8
8
|
} from '@sanity/types'
|
|
9
|
-
|
|
10
9
|
import {type PortableTextMemberSchemaTypes} from '../types/editor'
|
|
11
10
|
|
|
12
11
|
export function getPortableTextMemberSchemaTypes(
|
|
@@ -15,19 +14,23 @@ export function getPortableTextMemberSchemaTypes(
|
|
|
15
14
|
if (!portableTextType) {
|
|
16
15
|
throw new Error("Parameter 'portabletextType' missing (required)")
|
|
17
16
|
}
|
|
18
|
-
const blockType = portableTextType.of?.find(findBlockType) as
|
|
17
|
+
const blockType = portableTextType.of?.find(findBlockType) as
|
|
18
|
+
| BlockSchemaType
|
|
19
|
+
| undefined
|
|
19
20
|
if (!blockType) {
|
|
20
21
|
throw new Error('Block type is not defined in this schema (required)')
|
|
21
22
|
}
|
|
22
|
-
const childrenField = blockType.fields?.find(
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
const childrenField = blockType.fields?.find(
|
|
24
|
+
(field) => field.name === 'children',
|
|
25
|
+
) as {type: ArraySchemaType} | undefined
|
|
25
26
|
if (!childrenField) {
|
|
26
27
|
throw new Error('Children field for block type found in schema (required)')
|
|
27
28
|
}
|
|
28
29
|
const ofType = childrenField.type.of
|
|
29
30
|
if (!ofType) {
|
|
30
|
-
throw new Error(
|
|
31
|
+
throw new Error(
|
|
32
|
+
'Valid types for block children not found in schema (required)',
|
|
33
|
+
)
|
|
31
34
|
}
|
|
32
35
|
const spanType = ofType.find((memberType) => memberType.name === 'span') as
|
|
33
36
|
| ObjectSchemaType
|
|
@@ -35,10 +38,12 @@ export function getPortableTextMemberSchemaTypes(
|
|
|
35
38
|
if (!spanType) {
|
|
36
39
|
throw new Error('Span type not found in schema (required)')
|
|
37
40
|
}
|
|
38
|
-
const inlineObjectTypes = (ofType.filter(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
const inlineObjectTypes = (ofType.filter(
|
|
42
|
+
(memberType) => memberType.name !== 'span',
|
|
43
|
+
) || []) as ObjectSchemaType[]
|
|
44
|
+
const blockObjectTypes = (portableTextType.of?.filter(
|
|
45
|
+
(field) => field.name !== blockType.name,
|
|
46
|
+
) || []) as ObjectSchemaType[]
|
|
42
47
|
return {
|
|
43
48
|
styles: resolveEnabledStyles(blockType),
|
|
44
49
|
decorators: resolveEnabledDecorators(spanType),
|
|
@@ -53,13 +58,19 @@ export function getPortableTextMemberSchemaTypes(
|
|
|
53
58
|
}
|
|
54
59
|
|
|
55
60
|
function resolveEnabledStyles(blockType: ObjectSchemaType) {
|
|
56
|
-
const styleField = blockType.fields?.find(
|
|
61
|
+
const styleField = blockType.fields?.find(
|
|
62
|
+
(btField) => btField.name === 'style',
|
|
63
|
+
)
|
|
57
64
|
if (!styleField) {
|
|
58
|
-
throw new Error(
|
|
65
|
+
throw new Error(
|
|
66
|
+
"A field with name 'style' is not defined in the block type (required).",
|
|
67
|
+
)
|
|
59
68
|
}
|
|
60
69
|
const textStyles =
|
|
61
70
|
styleField.type.options?.list &&
|
|
62
|
-
styleField.type.options.list?.filter(
|
|
71
|
+
styleField.type.options.list?.filter(
|
|
72
|
+
(style: {value: string}) => style.value,
|
|
73
|
+
)
|
|
63
74
|
if (!textStyles || textStyles.length === 0) {
|
|
64
75
|
throw new Error(
|
|
65
76
|
'The style fields need at least one style ' +
|
|
@@ -74,9 +85,13 @@ function resolveEnabledDecorators(spanType: ObjectSchemaType) {
|
|
|
74
85
|
}
|
|
75
86
|
|
|
76
87
|
function resolveEnabledListItems(blockType: ObjectSchemaType) {
|
|
77
|
-
const listField = blockType.fields?.find(
|
|
88
|
+
const listField = blockType.fields?.find(
|
|
89
|
+
(btField) => btField.name === 'listItem',
|
|
90
|
+
)
|
|
78
91
|
if (!listField) {
|
|
79
|
-
throw new Error(
|
|
92
|
+
throw new Error(
|
|
93
|
+
"A field with name 'listItem' is not defined in the block type (required).",
|
|
94
|
+
)
|
|
80
95
|
}
|
|
81
96
|
const listItems =
|
|
82
97
|
listField.type.options?.list &&
|