@portabletext/editor 1.0.2 → 1.0.4
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 +9 -2
- package/lib/index.d.mts +22 -97
- package/lib/index.d.ts +22 -97
- package/lib/index.esm.js +42 -58
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +42 -58
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +42 -58
- package/lib/index.mjs.map +1 -1
- package/package.json +38 -20
- package/src/editor/Editable.tsx +32 -38
- package/src/editor/components/Synchronizer.tsx +1 -1
- package/src/editor/plugins/__tests__/createWithInsertData.test.tsx +166 -0
- package/src/editor/plugins/createWithInsertData.ts +34 -1
- package/src/editor/plugins/createWithPatches.ts +2 -2
- package/src/editor/plugins/createWithUndoRedo.ts +1 -1
- package/src/index.ts +1 -2
- package/src/types/editor.ts +4 -3
- package/src/utils/__tests__/dmpToOperations.test.ts +1 -1
- package/src/utils/applyPatch.ts +8 -8
- package/src/utils/operationToPatches.ts +2 -2
- package/src/utils/validateValue.ts +1 -1
- package/src/patch/PatchEvent.ts +0 -33
- package/src/patch/applyPatch.ts +0 -29
- package/src/patch/array.ts +0 -89
- package/src/patch/arrayInsert.ts +0 -27
- package/src/patch/object.ts +0 -39
- package/src/patch/patches.ts +0 -53
- package/src/patch/primitive.ts +0 -43
- package/src/patch/string.ts +0 -51
- package/src/types/patch.ts +0 -65
- package/src/utils/patches.ts +0 -36
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@portabletext/editor",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Portable Text Editor made in React",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -45,10 +45,6 @@
|
|
|
45
45
|
"src"
|
|
46
46
|
],
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@sanity/block-tools": "^3.47.0",
|
|
49
|
-
"@sanity/schema": "^3.47.0",
|
|
50
|
-
"@sanity/types": "^3.47.0",
|
|
51
|
-
"@sanity/util": "^3.47.0",
|
|
52
48
|
"debug": "^3.2.7",
|
|
53
49
|
"is-hotkey-esm": "^1.0.0",
|
|
54
50
|
"lodash": "^4.17.21",
|
|
@@ -58,28 +54,46 @@
|
|
|
58
54
|
"devDependencies": {
|
|
59
55
|
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
|
60
56
|
"@jest/globals": "^29.7.0",
|
|
61
|
-
"@playwright/test": "1.
|
|
57
|
+
"@playwright/test": "1.45.0",
|
|
62
58
|
"@portabletext/toolkit": "^2.0.15",
|
|
59
|
+
"@sanity/block-tools": "^3.47.1",
|
|
63
60
|
"@sanity/diff-match-patch": "^3.1.1",
|
|
61
|
+
"@sanity/eslint-config-i18n": "^1.1.0",
|
|
62
|
+
"@sanity/eslint-config-studio": "^4.0.0",
|
|
64
63
|
"@sanity/pkg-utils": "^6.10.0",
|
|
64
|
+
"@sanity/schema": "^3.47.1",
|
|
65
65
|
"@sanity/test": "0.0.1-alpha.1",
|
|
66
|
-
"@sanity/
|
|
66
|
+
"@sanity/types": "^3.47.1",
|
|
67
|
+
"@sanity/ui": "^2.5.0",
|
|
68
|
+
"@sanity/util": "^3.47.1",
|
|
67
69
|
"@testing-library/react": "^13.4.0",
|
|
68
70
|
"@types/debug": "^4.1.5",
|
|
69
71
|
"@types/express": "^4.17.21",
|
|
70
|
-
"@types/express-ws": "^3.0.
|
|
71
|
-
"@types/lodash": "^4.
|
|
72
|
+
"@types/express-ws": "^3.0.4",
|
|
73
|
+
"@types/lodash": "^4.17.5",
|
|
72
74
|
"@types/node": "^18.19.8",
|
|
73
|
-
"@types/node-ipc": "^9.2.
|
|
75
|
+
"@types/node-ipc": "^9.2.3",
|
|
74
76
|
"@types/react": "^18.3.3",
|
|
75
77
|
"@types/react-dom": "^18.3.0",
|
|
76
|
-
"@types/ws": "~8.5.
|
|
78
|
+
"@types/ws": "~8.5.10",
|
|
79
|
+
"@typescript-eslint/eslint-plugin": "^7.14.1",
|
|
80
|
+
"@typescript-eslint/parser": "^7.14.1",
|
|
77
81
|
"@vitejs/plugin-react": "^4.3.1",
|
|
78
82
|
"dotenv": "^16.4.5",
|
|
79
|
-
"
|
|
83
|
+
"eslint": "^8.57.0",
|
|
84
|
+
"eslint-config-prettier": "^9.1.0",
|
|
85
|
+
"eslint-config-sanity": "^7.1.2",
|
|
86
|
+
"eslint-import-resolver-typescript": "^3.6.1",
|
|
87
|
+
"eslint-plugin-import": "^2.29.1",
|
|
88
|
+
"eslint-plugin-prettier": "^5.1.3",
|
|
89
|
+
"eslint-plugin-react-compiler": "0.0.0-experimental-51a85ea-20240601",
|
|
90
|
+
"eslint-plugin-tsdoc": "^0.3.0",
|
|
91
|
+
"eslint-plugin-unicorn": "^54.0.0",
|
|
92
|
+
"eslint-plugin-unused-imports": "^4.0.0",
|
|
93
|
+
"express": "^4.19.2",
|
|
80
94
|
"express-ws": "^5.0.2",
|
|
81
95
|
"jest": "^29.7.0",
|
|
82
|
-
"jest-dev-server": "^9.0.
|
|
96
|
+
"jest-dev-server": "^9.0.2",
|
|
83
97
|
"jest-environment-jsdom": "^29.7.0",
|
|
84
98
|
"jest-environment-node": "^29.7.0",
|
|
85
99
|
"node-ipc": "npm:@node-ipc/compat@9.2.5",
|
|
@@ -88,13 +102,16 @@
|
|
|
88
102
|
"rimraf": "^3.0.2",
|
|
89
103
|
"rxjs": "^7.8.1",
|
|
90
104
|
"styled-components": "^6.1.11",
|
|
91
|
-
"tsx": "^4.
|
|
105
|
+
"tsx": "^4.15.7",
|
|
92
106
|
"typescript": "^5.4.5",
|
|
93
107
|
"vite": "^4.5.3",
|
|
94
|
-
"@
|
|
95
|
-
"@repo/package.config": "^0.0.0"
|
|
108
|
+
"@portabletext/patches": "^0.0.0"
|
|
96
109
|
},
|
|
97
110
|
"peerDependencies": {
|
|
111
|
+
"@sanity/block-tools": "^3.47.1",
|
|
112
|
+
"@sanity/schema": "^3.47.1",
|
|
113
|
+
"@sanity/types": "^3.47.1",
|
|
114
|
+
"@sanity/util": "^3.47.1",
|
|
98
115
|
"react": "^16.9 || ^17 || ^18",
|
|
99
116
|
"rxjs": "^7",
|
|
100
117
|
"styled-components": "^6.1"
|
|
@@ -107,13 +124,14 @@
|
|
|
107
124
|
},
|
|
108
125
|
"scripts": {
|
|
109
126
|
"build": "pkg-utils build --strict --check --clean",
|
|
127
|
+
"check:lint": "eslint .",
|
|
110
128
|
"check:types": "tsc --project tsconfig.lib.json",
|
|
111
129
|
"clean": "rimraf lib",
|
|
112
|
-
"dev": "
|
|
113
|
-
"
|
|
130
|
+
"dev": "pkg-utils watch",
|
|
131
|
+
"dev:e2e-server": "cd ./e2e-tests/ && tsx serve",
|
|
132
|
+
"lint:fix": "eslint . --fix",
|
|
114
133
|
"test": "jest",
|
|
115
134
|
"test:e2e": "jest --config=e2e-tests/e2e.config.cjs",
|
|
116
|
-
"test:watch": "jest --watch"
|
|
117
|
-
"watch": "pkg-utils watch"
|
|
135
|
+
"test:watch": "jest --watch"
|
|
118
136
|
}
|
|
119
137
|
}
|
package/src/editor/Editable.tsx
CHANGED
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
type HTMLProps,
|
|
10
10
|
type KeyboardEvent,
|
|
11
11
|
type MutableRefObject,
|
|
12
|
-
type ReactNode,
|
|
13
12
|
type TextareaHTMLAttributes,
|
|
14
13
|
useCallback,
|
|
15
14
|
useEffect,
|
|
@@ -42,13 +41,13 @@ import {
|
|
|
42
41
|
type EditorSelection,
|
|
43
42
|
type OnCopyFn,
|
|
44
43
|
type OnPasteFn,
|
|
45
|
-
type OnPasteResult,
|
|
46
44
|
type RangeDecoration,
|
|
47
45
|
type RenderAnnotationFunction,
|
|
48
46
|
type RenderBlockFunction,
|
|
49
47
|
type RenderChildFunction,
|
|
50
48
|
type RenderDecoratorFunction,
|
|
51
49
|
type RenderListItemFunction,
|
|
50
|
+
type RenderPlaceholderFunction,
|
|
52
51
|
type RenderStyleFunction,
|
|
53
52
|
type ScrollSelectionIntoViewFunction,
|
|
54
53
|
} from '../types/editor'
|
|
@@ -100,7 +99,7 @@ export type PortableTextEditableProps = Omit<
|
|
|
100
99
|
renderChild?: RenderChildFunction
|
|
101
100
|
renderDecorator?: RenderDecoratorFunction
|
|
102
101
|
renderListItem?: RenderListItemFunction
|
|
103
|
-
renderPlaceholder?:
|
|
102
|
+
renderPlaceholder?: RenderPlaceholderFunction
|
|
104
103
|
renderStyle?: RenderStyleFunction
|
|
105
104
|
scrollSelectionIntoView?: ScrollSelectionIntoViewFunction
|
|
106
105
|
selection?: EditorSelection
|
|
@@ -390,42 +389,37 @@ export const PortableTextEditable = forwardRef(function PortableTextEditable(
|
|
|
390
389
|
slateEditor.insertData(event.clipboardData)
|
|
391
390
|
return
|
|
392
391
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
)
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
392
|
+
|
|
393
|
+
const value = PortableTextEditor.getValue(portableTextEditor)
|
|
394
|
+
const ptRange = toPortableTextRange(value, slateEditor.selection, schemaTypes)
|
|
395
|
+
const path = ptRange?.focus.path || []
|
|
396
|
+
const onPasteResult = onPaste({event, value, path, schemaTypes})
|
|
397
|
+
|
|
398
|
+
if (onPasteResult === undefined) {
|
|
399
|
+
debug('No result from custom paste handler, pasting normally')
|
|
400
|
+
slateEditor.insertData(event.clipboardData)
|
|
401
|
+
} else {
|
|
402
|
+
// Resolve it as promise (can be either async promise or sync return value)
|
|
403
|
+
change$.next({type: 'loading', isLoading: true})
|
|
404
|
+
Promise.resolve(onPasteResult)
|
|
405
|
+
.then((result) => {
|
|
406
|
+
debug('Custom paste function from client resolved', result)
|
|
407
|
+
if (result && result.insert) {
|
|
408
|
+
slateEditor.insertFragment(
|
|
409
|
+
toSlateValue(result.insert as PortableTextBlock[], {schemaTypes}),
|
|
410
|
+
)
|
|
411
|
+
} else {
|
|
412
|
+
console.warn('Your onPaste function returned something unexpected:', result)
|
|
413
|
+
}
|
|
414
|
+
})
|
|
415
|
+
.catch((error) => {
|
|
416
|
+
console.error(error) // eslint-disable-line no-console
|
|
417
|
+
return error
|
|
418
|
+
})
|
|
419
|
+
.finally(() => {
|
|
419
420
|
change$.next({type: 'loading', isLoading: false})
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
console.warn('Your onPaste function returned something unexpected:', result)
|
|
423
|
-
})
|
|
424
|
-
.catch((error) => {
|
|
425
|
-
change$.next({type: 'loading', isLoading: false})
|
|
426
|
-
console.error(error) // eslint-disable-line no-console
|
|
427
|
-
return error
|
|
428
|
-
})
|
|
421
|
+
})
|
|
422
|
+
}
|
|
429
423
|
},
|
|
430
424
|
[change$, onPaste, portableTextEditor, schemaTypes, slateEditor],
|
|
431
425
|
)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {type Patch} from '@portabletext/patches/types'
|
|
1
2
|
import {type PortableTextBlock} from '@sanity/types'
|
|
2
3
|
import {throttle} from 'lodash'
|
|
3
4
|
import {
|
|
@@ -13,7 +14,6 @@ import {Editor} from 'slate'
|
|
|
13
14
|
import {useSlate} from 'slate-react'
|
|
14
15
|
|
|
15
16
|
import {type EditorChange, type EditorChanges, type EditorSelection} from '../../types/editor'
|
|
16
|
-
import {type Patch} from '../../types/patch'
|
|
17
17
|
import {debugWithName} from '../../utils/debug'
|
|
18
18
|
import {IS_PROCESSING_LOCAL_CHANGES} from '../../utils/weakMaps'
|
|
19
19
|
import {PortableTextEditorContext} from '../hooks/usePortableTextEditor'
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import {describe, expect, it} from '@jest/globals'
|
|
2
|
+
import {isPortableTextSpan, isPortableTextTextBlock} from '@sanity/types'
|
|
3
|
+
import {type Descendant} from 'slate'
|
|
4
|
+
|
|
5
|
+
import {exportedForTesting} from '../createWithInsertData'
|
|
6
|
+
|
|
7
|
+
const initialValue = [
|
|
8
|
+
{
|
|
9
|
+
_key: 'a',
|
|
10
|
+
_type: 'myTestBlockType',
|
|
11
|
+
children: [
|
|
12
|
+
{
|
|
13
|
+
_key: 'a1',
|
|
14
|
+
_type: 'span',
|
|
15
|
+
marks: ['link1'],
|
|
16
|
+
text: 'Block A',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
_key: 'a2',
|
|
20
|
+
_type: 'span',
|
|
21
|
+
marks: ['colour1'],
|
|
22
|
+
text: 'Block B',
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
markDefs: [
|
|
26
|
+
{
|
|
27
|
+
_key: 'link1',
|
|
28
|
+
_type: 'link',
|
|
29
|
+
href: 'google.com',
|
|
30
|
+
newTab: false,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
_key: 'colour1',
|
|
34
|
+
_type: 'color',
|
|
35
|
+
color: 'red',
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
style: 'normal',
|
|
39
|
+
},
|
|
40
|
+
] satisfies Array<Descendant>
|
|
41
|
+
|
|
42
|
+
describe('plugin: createWithInsertData _regenerateKeys', () => {
|
|
43
|
+
it('has MarkDefs that are allowed annotations', async () => {
|
|
44
|
+
const {_regenerateKeys} = exportedForTesting
|
|
45
|
+
let keyCursor = 0
|
|
46
|
+
|
|
47
|
+
const generatedValue = _regenerateKeys(
|
|
48
|
+
{
|
|
49
|
+
isTextBlock: isPortableTextTextBlock,
|
|
50
|
+
isTextSpan: isPortableTextSpan,
|
|
51
|
+
},
|
|
52
|
+
initialValue,
|
|
53
|
+
() => {
|
|
54
|
+
keyCursor++
|
|
55
|
+
return `k${keyCursor}`
|
|
56
|
+
},
|
|
57
|
+
'span',
|
|
58
|
+
{
|
|
59
|
+
annotations: [
|
|
60
|
+
// eslint-disable-next-line camelcase
|
|
61
|
+
{name: 'color', jsonType: 'object', fields: [], __experimental_search: []},
|
|
62
|
+
// eslint-disable-next-line camelcase
|
|
63
|
+
{name: 'link', jsonType: 'object', fields: [], __experimental_search: []},
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
// the keys are not important here as it's not what we are testing here
|
|
69
|
+
expect(generatedValue).toStrictEqual([
|
|
70
|
+
{
|
|
71
|
+
_key: 'k3',
|
|
72
|
+
_type: 'myTestBlockType',
|
|
73
|
+
children: [
|
|
74
|
+
{_key: 'k4', _type: 'span', marks: ['k1'], text: 'Block A'},
|
|
75
|
+
{
|
|
76
|
+
_key: 'k5',
|
|
77
|
+
_type: 'span',
|
|
78
|
+
marks: ['k2'],
|
|
79
|
+
text: 'Block B',
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
markDefs: [
|
|
83
|
+
{_key: 'k1', _type: 'link', href: 'google.com', newTab: false},
|
|
84
|
+
{_key: 'k2', _type: 'color', color: 'red'},
|
|
85
|
+
],
|
|
86
|
+
style: 'normal',
|
|
87
|
+
},
|
|
88
|
+
])
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('removes MarkDefs when no annotations are allowed', async () => {
|
|
92
|
+
const {_regenerateKeys} = exportedForTesting
|
|
93
|
+
let keyCursor = 0
|
|
94
|
+
|
|
95
|
+
const generatedValue = _regenerateKeys(
|
|
96
|
+
{
|
|
97
|
+
isTextBlock: isPortableTextTextBlock,
|
|
98
|
+
isTextSpan: isPortableTextSpan,
|
|
99
|
+
},
|
|
100
|
+
initialValue,
|
|
101
|
+
() => {
|
|
102
|
+
keyCursor++
|
|
103
|
+
return `k${keyCursor}`
|
|
104
|
+
},
|
|
105
|
+
'span',
|
|
106
|
+
{annotations: []},
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
// orphaned children marks are removed later in the normalize function
|
|
110
|
+
expect(generatedValue).toStrictEqual([
|
|
111
|
+
{
|
|
112
|
+
_key: 'k1',
|
|
113
|
+
_type: 'myTestBlockType',
|
|
114
|
+
children: [
|
|
115
|
+
{_key: 'a1', _type: 'span', marks: ['link1'], text: 'Block A'},
|
|
116
|
+
{
|
|
117
|
+
_key: 'a2',
|
|
118
|
+
_type: 'span',
|
|
119
|
+
marks: ['colour1'],
|
|
120
|
+
text: 'Block B',
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
style: 'normal',
|
|
124
|
+
},
|
|
125
|
+
])
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('updates MarkDefs when one annotations is allowed but one is not allowed', async () => {
|
|
129
|
+
const {_regenerateKeys} = exportedForTesting
|
|
130
|
+
let keyCursor = 0
|
|
131
|
+
|
|
132
|
+
const generatedValue = _regenerateKeys(
|
|
133
|
+
{
|
|
134
|
+
isTextBlock: isPortableTextTextBlock,
|
|
135
|
+
isTextSpan: isPortableTextSpan,
|
|
136
|
+
},
|
|
137
|
+
initialValue,
|
|
138
|
+
() => {
|
|
139
|
+
keyCursor++
|
|
140
|
+
return `k${keyCursor}`
|
|
141
|
+
},
|
|
142
|
+
'span',
|
|
143
|
+
// eslint-disable-next-line camelcase
|
|
144
|
+
{annotations: [{name: 'color', jsonType: 'object', fields: [], __experimental_search: []}]},
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
// orphaned children marks are removed later in the normalize function
|
|
148
|
+
expect(generatedValue).toStrictEqual([
|
|
149
|
+
{
|
|
150
|
+
_key: 'k1',
|
|
151
|
+
_type: 'myTestBlockType',
|
|
152
|
+
children: [
|
|
153
|
+
{_key: 'a1', _type: 'span', marks: ['link1'], text: 'Block A'},
|
|
154
|
+
{
|
|
155
|
+
_key: 'a2',
|
|
156
|
+
_type: 'span',
|
|
157
|
+
marks: ['colour1'],
|
|
158
|
+
text: 'Block B',
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
markDefs: [{_key: 'colour1', _type: 'color', color: 'red'}],
|
|
162
|
+
style: 'normal',
|
|
163
|
+
},
|
|
164
|
+
])
|
|
165
|
+
})
|
|
166
|
+
})
|
|
@@ -138,6 +138,7 @@ export function createWithInsertData(
|
|
|
138
138
|
toSlateValue(parsed, {schemaTypes}),
|
|
139
139
|
keyGenerator,
|
|
140
140
|
spanTypeName,
|
|
141
|
+
schemaTypes,
|
|
141
142
|
)
|
|
142
143
|
// Validate the result
|
|
143
144
|
const validation = validateValue(parsed, schemaTypes, keyGenerator)
|
|
@@ -269,15 +270,39 @@ function escapeHtml(str: string) {
|
|
|
269
270
|
* @internal
|
|
270
271
|
*/
|
|
271
272
|
function _regenerateKeys(
|
|
272
|
-
editor: PortableTextSlateEditor,
|
|
273
|
+
editor: Pick<PortableTextSlateEditor, 'isTextBlock' | 'isTextSpan'>,
|
|
273
274
|
fragment: Descendant[],
|
|
274
275
|
keyGenerator: () => string,
|
|
275
276
|
spanTypeName: string,
|
|
277
|
+
editorTypes: Pick<PortableTextMemberSchemaTypes, 'annotations'>,
|
|
276
278
|
): Descendant[] {
|
|
277
279
|
return fragment.map((node) => {
|
|
278
280
|
const newNode: Descendant = {...node}
|
|
279
281
|
// Ensure the copy has new keys
|
|
280
282
|
if (editor.isTextBlock(newNode)) {
|
|
283
|
+
const annotations = editorTypes.annotations.map((t) => t.name)
|
|
284
|
+
|
|
285
|
+
// Ensure that if there are no annotations, we remove the markDefs
|
|
286
|
+
if (annotations.length === 0) {
|
|
287
|
+
const {markDefs, ...NewNodeNoDefs} = newNode
|
|
288
|
+
|
|
289
|
+
return {...NewNodeNoDefs, _key: keyGenerator()}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Ensure that all annotations are allowed
|
|
293
|
+
const hasForbiddenAnnotations = (newNode.markDefs || []).some((def) => {
|
|
294
|
+
return !annotations.includes(def._type)
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
// if they have forbidden annotations, we remove them and keep the rest
|
|
298
|
+
if (hasForbiddenAnnotations) {
|
|
299
|
+
const allowedAnnotations = (newNode.markDefs || []).filter((def) => {
|
|
300
|
+
return annotations.includes(def._type)
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
return {...newNode, markDefs: allowedAnnotations, _key: keyGenerator()}
|
|
304
|
+
}
|
|
305
|
+
|
|
281
306
|
newNode.markDefs = (newNode.markDefs || []).map((def) => {
|
|
282
307
|
const oldKey = def._key
|
|
283
308
|
const newKey = keyGenerator()
|
|
@@ -357,3 +382,11 @@ function _insertFragment(
|
|
|
357
382
|
|
|
358
383
|
editor.onChange()
|
|
359
384
|
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* functions we don't want to export but want to test
|
|
388
|
+
* @internal
|
|
389
|
+
*/
|
|
390
|
+
export const exportedForTesting = {
|
|
391
|
+
_regenerateKeys,
|
|
392
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
/* eslint-disable max-nested-callbacks */
|
|
2
|
+
import {insert, setIfMissing, unset} from '@portabletext/patches/patch-event'
|
|
3
|
+
import {type Patch} from '@portabletext/patches/types'
|
|
2
4
|
import {type Subject} from 'rxjs'
|
|
3
5
|
import {
|
|
4
6
|
type Descendant,
|
|
@@ -14,14 +16,12 @@ import {
|
|
|
14
16
|
type SplitNodeOperation,
|
|
15
17
|
} from 'slate'
|
|
16
18
|
|
|
17
|
-
import {insert, setIfMissing, unset} from '../../patch/PatchEvent'
|
|
18
19
|
import {
|
|
19
20
|
type EditorChange,
|
|
20
21
|
type PatchObservable,
|
|
21
22
|
type PortableTextMemberSchemaTypes,
|
|
22
23
|
type PortableTextSlateEditor,
|
|
23
24
|
} from '../../types/editor'
|
|
24
|
-
import {type Patch} from '../../types/patch'
|
|
25
25
|
import {createApplyPatch} from '../../utils/applyPatch'
|
|
26
26
|
import {debugWithName} from '../../utils/debug'
|
|
27
27
|
import {fromSlateValue, isEqualToEmptyEditor} from '../../utils/values'
|
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
* The undo/redo steps are rebased against incoming patches since the step occurred.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import {type Patch} from '@portabletext/patches/types'
|
|
6
7
|
import {DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, parsePatch} from '@sanity/diff-match-patch'
|
|
7
8
|
import {type ObjectSchemaType, type PortableTextBlock} from '@sanity/types'
|
|
8
9
|
import {flatten, isEqual} from 'lodash'
|
|
9
10
|
import {type Descendant, Editor, Operation, Path, type SelectionOperation, Transforms} from 'slate'
|
|
10
11
|
|
|
11
12
|
import {type PatchObservable, type PortableTextSlateEditor} from '../../types/editor'
|
|
12
|
-
import {type Patch} from '../../types/patch'
|
|
13
13
|
import {debugWithName} from '../../utils/debug'
|
|
14
14
|
import {fromSlateValue} from '../../utils/values'
|
|
15
15
|
import {withPreserveKeys} from '../../utils/withPreserveKeys'
|
package/src/index.ts
CHANGED
|
@@ -7,5 +7,4 @@ export type {PortableTextEditorProps} from './editor/PortableTextEditor'
|
|
|
7
7
|
export {PortableTextEditor} from './editor/PortableTextEditor'
|
|
8
8
|
export * from './types/editor'
|
|
9
9
|
export * from './types/options'
|
|
10
|
-
export * from '
|
|
11
|
-
export {compactPatches} from './utils/patches'
|
|
10
|
+
export * from '@portabletext/patches/types'
|
package/src/types/editor.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {type Patch} from '@portabletext/patches/types'
|
|
1
2
|
import {
|
|
2
3
|
type ArraySchemaType,
|
|
3
4
|
type BlockDecoratorDefinition,
|
|
@@ -30,7 +31,6 @@ import {type DOMNode} from 'slate-react/dist/utils/dom'
|
|
|
30
31
|
|
|
31
32
|
import {type PortableTextEditableProps} from '../editor/Editable'
|
|
32
33
|
import {type PortableTextEditor} from '../editor/PortableTextEditor'
|
|
33
|
-
import {type Patch} from '../types/patch'
|
|
34
34
|
|
|
35
35
|
/** @beta */
|
|
36
36
|
export interface EditableAPIDeleteOptions {
|
|
@@ -467,7 +467,6 @@ export interface BlockDecoratorRenderProps {
|
|
|
467
467
|
value: string
|
|
468
468
|
}
|
|
469
469
|
/** @beta */
|
|
470
|
-
|
|
471
470
|
export interface BlockListItemRenderProps {
|
|
472
471
|
block: PortableTextTextBlock
|
|
473
472
|
children: ReactElement
|
|
@@ -493,10 +492,12 @@ export type RenderEditableFunction = (props: PortableTextEditableProps) => JSX.E
|
|
|
493
492
|
export type RenderAnnotationFunction = (props: BlockAnnotationRenderProps) => JSX.Element
|
|
494
493
|
|
|
495
494
|
/** @beta */
|
|
496
|
-
export type
|
|
495
|
+
export type RenderPlaceholderFunction = () => React.ReactNode
|
|
497
496
|
|
|
498
497
|
/** @beta */
|
|
498
|
+
export type RenderStyleFunction = (props: BlockStyleRenderProps) => JSX.Element
|
|
499
499
|
|
|
500
|
+
/** @beta */
|
|
500
501
|
export interface BlockStyleRenderProps {
|
|
501
502
|
block: PortableTextTextBlock
|
|
502
503
|
children: ReactElement
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {describe, expect, test} from '@jest/globals'
|
|
2
|
+
import {type DiffMatchPatch} from '@portabletext/patches/types'
|
|
2
3
|
import {makeDiff, makePatches, stringifyPatches} from '@sanity/diff-match-patch'
|
|
3
4
|
import {
|
|
4
5
|
isPortableTextSpan,
|
|
@@ -11,7 +12,6 @@ import {
|
|
|
11
12
|
import {type Descendant, type Operation} from 'slate'
|
|
12
13
|
|
|
13
14
|
import {type PortableTextSlateEditor} from '../../types/editor'
|
|
14
|
-
import {type DiffMatchPatch} from '../../types/patch'
|
|
15
15
|
import {diffMatchPatch} from '../applyPatch'
|
|
16
16
|
|
|
17
17
|
describe('operationToPatches: diffMatchPatch', () => {
|
package/src/utils/applyPatch.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
/* eslint-disable max-statements */
|
|
2
|
+
import {applyAll} from '@portabletext/patches/apply'
|
|
3
|
+
import {
|
|
4
|
+
type DiffMatchPatch,
|
|
5
|
+
type InsertPatch,
|
|
6
|
+
type Patch,
|
|
7
|
+
type SetPatch,
|
|
8
|
+
type UnsetPatch,
|
|
9
|
+
} from '@portabletext/patches/types'
|
|
2
10
|
import {
|
|
3
11
|
applyPatches as diffMatchPatchApplyPatches,
|
|
4
12
|
cleanupEfficiency,
|
|
@@ -17,15 +25,7 @@ import {
|
|
|
17
25
|
} from '@sanity/types'
|
|
18
26
|
import {type Descendant, Element, type Node, type Path as SlatePath, Text, Transforms} from 'slate'
|
|
19
27
|
|
|
20
|
-
import {applyAll} from '../patch/applyPatch'
|
|
21
28
|
import {type PortableTextMemberSchemaTypes, type PortableTextSlateEditor} from '../types/editor'
|
|
22
|
-
import {
|
|
23
|
-
type DiffMatchPatch,
|
|
24
|
-
type InsertPatch,
|
|
25
|
-
type Patch,
|
|
26
|
-
type SetPatch,
|
|
27
|
-
type UnsetPatch,
|
|
28
|
-
} from '../types/patch'
|
|
29
29
|
import {debugWithName} from './debug'
|
|
30
30
|
import {toSlateValue} from './values'
|
|
31
31
|
import {KEY_TO_SLATE_ELEMENT} from './weakMaps'
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import {diffMatchPatch, insert, set, setIfMissing, unset} from '@portabletext/patches/patch-event'
|
|
2
|
+
import {type InsertPosition, type Patch} from '@portabletext/patches/types'
|
|
1
3
|
import {type Path, type PortableTextSpan, type PortableTextTextBlock} from '@sanity/types'
|
|
2
4
|
import {get, isUndefined, omitBy} from 'lodash'
|
|
3
5
|
import {
|
|
@@ -14,9 +16,7 @@ import {
|
|
|
14
16
|
} from 'slate'
|
|
15
17
|
|
|
16
18
|
import {type PatchFunctions} from '../editor/plugins/createWithPatches'
|
|
17
|
-
import {diffMatchPatch, insert, set, setIfMissing, unset} from '../patch/PatchEvent'
|
|
18
19
|
import {type PortableTextMemberSchemaTypes, type PortableTextSlateEditor} from '../types/editor'
|
|
19
|
-
import {type InsertPosition, type Patch} from '../types/patch'
|
|
20
20
|
import {debugWithName} from './debug'
|
|
21
21
|
import {fromSlateValue} from './values'
|
|
22
22
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {insert, set, setIfMissing, unset} from '@portabletext/patches/patch-event'
|
|
1
2
|
import {
|
|
2
3
|
isPortableTextTextBlock,
|
|
3
4
|
type PortableTextBlock,
|
|
@@ -6,7 +7,6 @@ import {
|
|
|
6
7
|
} from '@sanity/types'
|
|
7
8
|
import {flatten, isPlainObject, uniq} from 'lodash'
|
|
8
9
|
|
|
9
|
-
import {insert, set, setIfMissing, unset} from '../patch/PatchEvent'
|
|
10
10
|
import {type InvalidValueResolution, type PortableTextMemberSchemaTypes} from '../types/editor'
|
|
11
11
|
import {EMPTY_MARKDEFS} from './values'
|
|
12
12
|
|
package/src/patch/PatchEvent.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import {type PathSegment} from '@sanity/types'
|
|
2
|
-
import {flatten} from 'lodash'
|
|
3
|
-
|
|
4
|
-
import {type Patch} from '../types/patch'
|
|
5
|
-
import {diffMatchPatch, insert, prefixPath, set, setIfMissing, unset} from './patches'
|
|
6
|
-
|
|
7
|
-
type PatchArg = Patch | Array<Patch>
|
|
8
|
-
|
|
9
|
-
export default class PatchEvent {
|
|
10
|
-
static from(...patches: Array<PatchArg>) {
|
|
11
|
-
return new PatchEvent(flatten(patches))
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
patches: Array<Patch>
|
|
15
|
-
|
|
16
|
-
constructor(patches: Array<Patch>) {
|
|
17
|
-
this.patches = patches
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
prepend(...patches: Array<PatchArg>): PatchEvent {
|
|
21
|
-
return PatchEvent.from([...flatten(patches), ...this.patches])
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
append(...patches: Array<PatchArg>): PatchEvent {
|
|
25
|
-
return PatchEvent.from([...this.patches, ...flatten(patches)])
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
prefixAll(segment: PathSegment): PatchEvent {
|
|
29
|
-
return PatchEvent.from(this.patches.map((patch) => prefixPath(patch, segment)))
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export {diffMatchPatch, insert, PatchEvent, set, setIfMissing, unset}
|
package/src/patch/applyPatch.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import {isObject, isString} from 'lodash'
|
|
2
|
-
|
|
3
|
-
import applyArrayPatch from './array'
|
|
4
|
-
import applyObjectPatch from './object'
|
|
5
|
-
import applyPrimitivePatch from './primitive'
|
|
6
|
-
import applyStringPatch from './string'
|
|
7
|
-
|
|
8
|
-
export function applyAll(value: any, patches: any[]) {
|
|
9
|
-
return patches.reduce(_apply, value)
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function applyPatch(value: string, patch: {type: string; path: any[]; value: any}) {
|
|
13
|
-
if (Array.isArray(value)) {
|
|
14
|
-
return applyArrayPatch(value, patch as any)
|
|
15
|
-
}
|
|
16
|
-
if (isString(value)) {
|
|
17
|
-
return applyStringPatch(value, patch)
|
|
18
|
-
}
|
|
19
|
-
if (isObject(value)) {
|
|
20
|
-
return applyObjectPatch(value, patch)
|
|
21
|
-
}
|
|
22
|
-
return applyPrimitivePatch(value, patch)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export default function _apply(value: string, patch: {type: string; path: any[]; value: any}) {
|
|
26
|
-
const res = applyPatch(value, patch)
|
|
27
|
-
// console.log('applyPatch(%o, %o) : %o (noop? %o)', value, patch, res, value === res)
|
|
28
|
-
return res
|
|
29
|
-
}
|