@portabletext/editor 2.21.4 → 2.21.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portabletext/editor",
3
- "version": "2.21.4",
3
+ "version": "2.21.5",
4
4
  "description": "Portable Text Editor made in React",
5
5
  "keywords": [
6
6
  "sanity",
@@ -73,10 +73,10 @@
73
73
  "slate-dom": "^0.119.0",
74
74
  "slate-react": "0.119.0",
75
75
  "xstate": "^5.24.0",
76
- "@portabletext/keyboard-shortcuts": "^2.1.0",
77
- "@portabletext/patches": "^2.0.0",
78
76
  "@portabletext/block-tools": "^4.0.2",
79
- "@portabletext/schema": "^2.0.0"
77
+ "@portabletext/keyboard-shortcuts": "^2.1.0",
78
+ "@portabletext/schema": "^2.0.0",
79
+ "@portabletext/patches": "^2.0.0"
80
80
  },
81
81
  "devDependencies": {
82
82
  "@sanity/diff-match-patch": "^3.2.0",
@@ -107,8 +107,8 @@
107
107
  "vitest": "^4.0.8",
108
108
  "vitest-browser-react": "^2.0.2",
109
109
  "@portabletext/sanity-bridge": "1.2.2",
110
- "racejar": "2.0.0",
111
- "@portabletext/test": "^1.0.0"
110
+ "@portabletext/test": "^1.0.0",
111
+ "racejar": "2.0.0"
112
112
  },
113
113
  "peerDependencies": {
114
114
  "@portabletext/sanity-bridge": "^1.2.2",
@@ -172,4 +172,95 @@ describe(applyOperationToPortableText.name, () => {
172
172
  },
173
173
  ])
174
174
  })
175
+
176
+ test('updating block object with a field named "text"', () => {
177
+ const keyGenerator = createTestKeyGenerator()
178
+ const k0 = keyGenerator()
179
+
180
+ expect(
181
+ applyOperationToPortableText(
182
+ createContext(),
183
+ [
184
+ {
185
+ _type: 'quote',
186
+ _key: k0,
187
+ text: 'h',
188
+ },
189
+ ],
190
+ {
191
+ type: 'set_node',
192
+ path: [0],
193
+ properties: {
194
+ value: {text: 'h'},
195
+ },
196
+ newProperties: {
197
+ value: {text: 'hello'},
198
+ },
199
+ },
200
+ ),
201
+ ).toEqual([
202
+ {
203
+ _type: 'quote',
204
+ _key: k0,
205
+ text: 'hello',
206
+ },
207
+ ])
208
+ })
209
+
210
+ test('updating inline object with a field named "text"', () => {
211
+ const keyGenerator = createTestKeyGenerator()
212
+ const k0 = keyGenerator()
213
+ const k1 = keyGenerator()
214
+ const k2 = keyGenerator()
215
+ const k3 = keyGenerator()
216
+
217
+ expect(
218
+ applyOperationToPortableText(
219
+ createContext(),
220
+ [
221
+ {
222
+ _key: k0,
223
+ _type: 'block',
224
+ children: [
225
+ {
226
+ _key: k1,
227
+ _type: 'span',
228
+ text: '',
229
+ },
230
+ {
231
+ _key: k2,
232
+ _type: 'mention',
233
+ text: 'J',
234
+ },
235
+ {
236
+ _key: k3,
237
+ _type: 'span',
238
+ text: '',
239
+ },
240
+ ],
241
+ },
242
+ ],
243
+ {
244
+ type: 'set_node',
245
+ path: [0, 1],
246
+ properties: {
247
+ value: {text: 'J'},
248
+ },
249
+ newProperties: {
250
+ value: {text: 'John Doe'},
251
+ },
252
+ },
253
+ ),
254
+ ).toEqual([
255
+ {
256
+ _type: 'block',
257
+ _key: k0,
258
+ children: [
259
+ {_type: 'span', _key: k1, text: ''},
260
+ {_type: 'mention', _key: k2, text: 'John Doe'},
261
+ {_type: 'span', _key: k3, text: ''},
262
+ ],
263
+ },
264
+ ])
265
+ })
175
266
  })
@@ -107,7 +107,7 @@ function applyOperationToPortableTextDraft(
107
107
  break
108
108
  }
109
109
 
110
- if (isPartialSpanNode(insertedNode)) {
110
+ if (isPartialSpanNode(context, insertedNode)) {
111
111
  // Text nodes can be inserted as is
112
112
 
113
113
  parent.children.splice(index, 0, insertedNode)
@@ -161,7 +161,10 @@ function applyOperationToPortableTextDraft(
161
161
 
162
162
  const index = path[path.length - 1]
163
163
 
164
- if (isPartialSpanNode(node) && isPartialSpanNode(prev)) {
164
+ if (
165
+ isPartialSpanNode(context, node) &&
166
+ isPartialSpanNode(context, prev)
167
+ ) {
165
168
  prev.text += node.text
166
169
  } else if (
167
170
  isTextBlockNode(context, node) &&
@@ -342,7 +345,7 @@ function applyOperationToPortableTextDraft(
342
345
  break
343
346
  }
344
347
 
345
- if (isPartialSpanNode(node)) {
348
+ if (isPartialSpanNode(context, node)) {
346
349
  for (const key in newProperties) {
347
350
  if (key === 'text') {
348
351
  break
@@ -0,0 +1,132 @@
1
+ import {compileSchema, defineSchema} from '@portabletext/schema'
2
+ import {describe, expect, test} from 'vitest'
3
+ import {isObjectNode, isPartialSpanNode, isSpanNode} from './portable-text-node'
4
+
5
+ const schema = compileSchema(defineSchema({}))
6
+
7
+ describe(isPartialSpanNode.name, () => {
8
+ test('object with only text property', () => {
9
+ expect(isPartialSpanNode({schema}, {text: 'Hello'})).toBe(true)
10
+ })
11
+
12
+ test('non-objects', () => {
13
+ expect(isPartialSpanNode({schema}, null)).toBe(false)
14
+ expect(isPartialSpanNode({schema}, undefined)).toBe(false)
15
+ expect(isPartialSpanNode({schema}, 'text')).toBe(false)
16
+ expect(isPartialSpanNode({schema}, 123)).toBe(false)
17
+ })
18
+
19
+ test('text is not a string', () => {
20
+ expect(isPartialSpanNode({schema}, {text: 123})).toBe(false)
21
+ expect(isPartialSpanNode({schema}, {text: null})).toBe(false)
22
+ expect(isPartialSpanNode({schema}, {text: undefined})).toBe(false)
23
+ })
24
+
25
+ test('inline object with text field and _type', () => {
26
+ expect(
27
+ isPartialSpanNode(
28
+ {schema},
29
+ {_type: 'mention', _key: 'abc123', text: 'John Doe'},
30
+ ),
31
+ ).toBe(false)
32
+ })
33
+
34
+ test('block object with text field and _type', () => {
35
+ expect(
36
+ isPartialSpanNode(
37
+ {schema},
38
+ {
39
+ _type: 'quote',
40
+ _key: 'abc123',
41
+ text: 'Hello world',
42
+ source: 'Anonymous',
43
+ },
44
+ ),
45
+ ).toBe(false)
46
+ })
47
+ })
48
+
49
+ describe(isSpanNode.name, () => {
50
+ test('span with _type="span"', () => {
51
+ expect(isSpanNode({schema}, {_type: 'span', text: 'Hello'})).toBe(true)
52
+ })
53
+
54
+ test('partial span (no _type, has text)', () => {
55
+ expect(isSpanNode({schema}, {text: 'Hello'})).toBe(true)
56
+ })
57
+
58
+ test('object with children', () => {
59
+ expect(
60
+ isSpanNode({schema}, {_type: 'span', text: 'Hello', children: []}),
61
+ ).toBe(false)
62
+ })
63
+
64
+ test('object with different _type', () => {
65
+ expect(isSpanNode({schema}, {_type: 'mention', text: 'Hello'})).toBe(false)
66
+ })
67
+ })
68
+
69
+ describe(isObjectNode.name, () => {
70
+ test('inline object', () => {
71
+ expect(
72
+ isObjectNode(
73
+ {schema},
74
+ {_type: 'stock-ticker', _key: 'abc', symbol: 'AAPL'},
75
+ ),
76
+ ).toBe(true)
77
+ })
78
+
79
+ test('block object', () => {
80
+ expect(
81
+ isObjectNode(
82
+ {schema},
83
+ {_type: 'image', _key: 'abc', src: 'https://example.com'},
84
+ ),
85
+ ).toBe(true)
86
+ })
87
+
88
+ test('inline object with text field', () => {
89
+ expect(
90
+ isObjectNode(
91
+ {schema},
92
+ {_type: 'mention', _key: 'abc123', text: 'John Doe'},
93
+ ),
94
+ ).toBe(true)
95
+ })
96
+
97
+ test('block object with text field', () => {
98
+ expect(
99
+ isObjectNode(
100
+ {schema},
101
+ {
102
+ _type: 'quote',
103
+ _key: 'abc123',
104
+ text: 'Hello world',
105
+ source: 'Anonymous',
106
+ },
107
+ ),
108
+ ).toBe(true)
109
+ })
110
+
111
+ test('span', () => {
112
+ expect(
113
+ isObjectNode(
114
+ {schema},
115
+ {_type: 'span', _key: 'abc', text: 'Hello', marks: []},
116
+ ),
117
+ ).toBe(false)
118
+ })
119
+
120
+ test('text block', () => {
121
+ expect(
122
+ isObjectNode(
123
+ {schema},
124
+ {
125
+ _type: 'block',
126
+ _key: 'abc',
127
+ children: [{_type: 'span', text: 'Hello'}],
128
+ },
129
+ ),
130
+ ).toBe(false)
131
+ })
132
+ })
@@ -79,13 +79,23 @@ export type PartialSpanNode = {
79
79
  [other: string]: unknown
80
80
  }
81
81
 
82
- export function isPartialSpanNode(node: unknown): node is PartialSpanNode {
83
- return (
84
- typeof node === 'object' &&
85
- node !== null &&
86
- 'text' in node &&
87
- typeof node.text === 'string'
88
- )
82
+ export function isPartialSpanNode(
83
+ context: {schema: EditorSchema},
84
+ node: unknown,
85
+ ): node is PartialSpanNode {
86
+ if (typeof node !== 'object' || node === null) {
87
+ return false
88
+ }
89
+
90
+ if (!('text' in node) || typeof node.text !== 'string') {
91
+ return false
92
+ }
93
+
94
+ if ('_type' in node && node._type !== context.schema.span.name) {
95
+ return false
96
+ }
97
+
98
+ return true
89
99
  }
90
100
 
91
101
  //////////
@@ -104,7 +114,7 @@ export function isObjectNode(
104
114
  !isEditorNode(node) &&
105
115
  !isTextBlockNode(context, node) &&
106
116
  !isSpanNode(context, node) &&
107
- !isPartialSpanNode(node)
117
+ !isPartialSpanNode(context, node)
108
118
  )
109
119
  }
110
120