@portabletext/editor 2.15.4 → 2.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/lib/_chunks-cjs/selector.is-at-the-start-of-block.cjs +10 -4
- package/lib/_chunks-cjs/selector.is-at-the-start-of-block.cjs.map +1 -1
- package/lib/_chunks-es/selector.is-at-the-start-of-block.js +10 -4
- package/lib/_chunks-es/selector.is-at-the-start-of-block.js.map +1 -1
- package/lib/selectors/index.cjs +32 -1
- package/lib/selectors/index.cjs.map +1 -1
- package/lib/selectors/index.d.cts +13 -2
- package/lib/selectors/index.d.ts +13 -2
- package/lib/selectors/index.js +35 -3
- package/lib/selectors/index.js.map +1 -1
- package/package.json +16 -15
- package/src/editor/validate-selection-machine.test.ts +1 -1
- package/src/internal-utils/slate-utils.test.tsx +13 -11
- package/src/plugins/plugin.internal.auto-close-brackets.test.tsx +1 -1
- package/src/plugins/plugin.markdown.test.tsx +1 -1
- package/src/selectors/index.ts +2 -0
- package/src/selectors/selector.get-mark-state.test.ts +186 -0
- package/src/selectors/selector.get-mark-state.ts +15 -9
- package/src/selectors/selector.get-text-after.ts +46 -0
- package/src/test/vitest/step-context.ts +1 -1
- package/src/test/vitest/step-definitions.tsx +1 -1
- package/src/test/vitest/test-editor.tsx +6 -7
|
@@ -5,6 +5,69 @@ import {createTestSnapshot} from '../internal-utils/create-test-snapshot'
|
|
|
5
5
|
import {getMarkState} from './selector.get-mark-state'
|
|
6
6
|
|
|
7
7
|
describe(getMarkState.name, () => {
|
|
8
|
+
test('Scenario: Caret after annotation', () => {
|
|
9
|
+
const keyGenerator = createTestKeyGenerator()
|
|
10
|
+
const blockKey = keyGenerator()
|
|
11
|
+
const fooSpanKey = keyGenerator()
|
|
12
|
+
const barSpanKey = keyGenerator()
|
|
13
|
+
const bazSpanKey = keyGenerator()
|
|
14
|
+
const linkKey = keyGenerator()
|
|
15
|
+
const snapshot = createTestSnapshot({
|
|
16
|
+
context: {
|
|
17
|
+
keyGenerator,
|
|
18
|
+
value: [
|
|
19
|
+
{
|
|
20
|
+
_type: 'block',
|
|
21
|
+
_key: blockKey,
|
|
22
|
+
children: [
|
|
23
|
+
{
|
|
24
|
+
_type: 'span',
|
|
25
|
+
_key: fooSpanKey,
|
|
26
|
+
text: 'foo ',
|
|
27
|
+
marks: [],
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
_type: 'span',
|
|
31
|
+
_key: barSpanKey,
|
|
32
|
+
text: 'bar',
|
|
33
|
+
marks: [linkKey],
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
_type: 'span',
|
|
37
|
+
_key: bazSpanKey,
|
|
38
|
+
text: ' baz',
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
markDefs: [
|
|
42
|
+
{
|
|
43
|
+
_type: 'link',
|
|
44
|
+
_key: linkKey,
|
|
45
|
+
href: 'https://portabletext.org',
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
selection: {
|
|
51
|
+
anchor: {
|
|
52
|
+
path: [{_key: blockKey}, 'children', {_key: barSpanKey}],
|
|
53
|
+
offset: 3,
|
|
54
|
+
},
|
|
55
|
+
focus: {
|
|
56
|
+
path: [{_key: blockKey}, 'children', {_key: barSpanKey}],
|
|
57
|
+
offset: 3,
|
|
58
|
+
},
|
|
59
|
+
backward: false,
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
expect(getMarkState(snapshot)).toEqual({
|
|
65
|
+
state: 'changed',
|
|
66
|
+
marks: [],
|
|
67
|
+
previousMarks: [linkKey],
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
8
71
|
test('Scenario: Caret after annotated decorator', () => {
|
|
9
72
|
const keyGenerator = createTestKeyGenerator()
|
|
10
73
|
const blockKey = keyGenerator()
|
|
@@ -136,4 +199,127 @@ describe(getMarkState.name, () => {
|
|
|
136
199
|
previousMarks: [linkKey, 'strong'],
|
|
137
200
|
})
|
|
138
201
|
})
|
|
202
|
+
|
|
203
|
+
test('Scenario: Caret in annotation, at the edge of decorator', () => {
|
|
204
|
+
const keyGenerator = createTestKeyGenerator()
|
|
205
|
+
const blockKey = keyGenerator()
|
|
206
|
+
const fooSpanKey = keyGenerator()
|
|
207
|
+
const barSpanKey = keyGenerator()
|
|
208
|
+
const linkKey = keyGenerator()
|
|
209
|
+
const snapshot = createTestSnapshot({
|
|
210
|
+
context: {
|
|
211
|
+
keyGenerator,
|
|
212
|
+
value: [
|
|
213
|
+
{
|
|
214
|
+
_type: 'block',
|
|
215
|
+
_key: blockKey,
|
|
216
|
+
children: [
|
|
217
|
+
{
|
|
218
|
+
_type: 'span',
|
|
219
|
+
_key: fooSpanKey,
|
|
220
|
+
text: 'foo',
|
|
221
|
+
marks: [linkKey],
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
_type: 'span',
|
|
225
|
+
_key: barSpanKey,
|
|
226
|
+
text: 'bar',
|
|
227
|
+
marks: [linkKey, 'strong'],
|
|
228
|
+
},
|
|
229
|
+
],
|
|
230
|
+
},
|
|
231
|
+
],
|
|
232
|
+
selection: {
|
|
233
|
+
anchor: {
|
|
234
|
+
path: [{_key: blockKey}, 'children', {_key: fooSpanKey}],
|
|
235
|
+
offset: 3,
|
|
236
|
+
},
|
|
237
|
+
focus: {
|
|
238
|
+
path: [{_key: blockKey}, 'children', {_key: fooSpanKey}],
|
|
239
|
+
offset: 3,
|
|
240
|
+
},
|
|
241
|
+
backward: false,
|
|
242
|
+
},
|
|
243
|
+
schema: compileSchema(
|
|
244
|
+
defineSchema({
|
|
245
|
+
annotations: [{name: 'link'}],
|
|
246
|
+
decorators: [{name: 'strong'}],
|
|
247
|
+
}),
|
|
248
|
+
),
|
|
249
|
+
},
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
expect(getMarkState(snapshot)).toEqual({
|
|
253
|
+
state: 'unchanged',
|
|
254
|
+
marks: [linkKey],
|
|
255
|
+
})
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
test('Scenario: Caret between two annotations', () => {
|
|
259
|
+
const keyGenerator = createTestKeyGenerator()
|
|
260
|
+
const blockKey = keyGenerator()
|
|
261
|
+
const fooSpanKey = keyGenerator()
|
|
262
|
+
const barSpanKey = keyGenerator()
|
|
263
|
+
const linkKey = keyGenerator()
|
|
264
|
+
const commentKey = keyGenerator()
|
|
265
|
+
const snapshot = createTestSnapshot({
|
|
266
|
+
context: {
|
|
267
|
+
keyGenerator,
|
|
268
|
+
value: [
|
|
269
|
+
{
|
|
270
|
+
_type: 'block',
|
|
271
|
+
_key: blockKey,
|
|
272
|
+
children: [
|
|
273
|
+
{
|
|
274
|
+
_type: 'span',
|
|
275
|
+
_key: fooSpanKey,
|
|
276
|
+
text: 'foo',
|
|
277
|
+
marks: [linkKey],
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
_type: 'span',
|
|
281
|
+
_key: barSpanKey,
|
|
282
|
+
text: 'bar',
|
|
283
|
+
marks: [commentKey],
|
|
284
|
+
},
|
|
285
|
+
],
|
|
286
|
+
markDefs: [
|
|
287
|
+
{
|
|
288
|
+
_type: 'link',
|
|
289
|
+
_key: linkKey,
|
|
290
|
+
href: 'https://portabletext.org',
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
_type: 'comment',
|
|
294
|
+
_key: commentKey,
|
|
295
|
+
text: 'Comment A',
|
|
296
|
+
},
|
|
297
|
+
],
|
|
298
|
+
},
|
|
299
|
+
],
|
|
300
|
+
selection: {
|
|
301
|
+
anchor: {
|
|
302
|
+
path: [{_key: blockKey}, 'children', {_key: fooSpanKey}],
|
|
303
|
+
offset: 3,
|
|
304
|
+
},
|
|
305
|
+
focus: {
|
|
306
|
+
path: [{_key: blockKey}, 'children', {_key: fooSpanKey}],
|
|
307
|
+
offset: 3,
|
|
308
|
+
},
|
|
309
|
+
backward: false,
|
|
310
|
+
},
|
|
311
|
+
schema: compileSchema(
|
|
312
|
+
defineSchema({
|
|
313
|
+
annotations: [{name: 'link'}, {name: 'comment'}],
|
|
314
|
+
}),
|
|
315
|
+
),
|
|
316
|
+
},
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
expect(getMarkState(snapshot)).toEqual({
|
|
320
|
+
state: 'changed',
|
|
321
|
+
marks: [],
|
|
322
|
+
previousMarks: [linkKey],
|
|
323
|
+
})
|
|
324
|
+
})
|
|
139
325
|
})
|
|
@@ -196,7 +196,6 @@ export const getMarkState: EditorSelector<MarkState | undefined> = (
|
|
|
196
196
|
} else if (previousSpanHasSameAnnotation) {
|
|
197
197
|
return {
|
|
198
198
|
state: 'unchanged',
|
|
199
|
-
previousMarks: marks,
|
|
200
199
|
marks: focusSpan.node.marks ?? [],
|
|
201
200
|
}
|
|
202
201
|
} else if (!previousSpan) {
|
|
@@ -209,26 +208,33 @@ export const getMarkState: EditorSelector<MarkState | undefined> = (
|
|
|
209
208
|
}
|
|
210
209
|
|
|
211
210
|
if (atTheEndOfSpan) {
|
|
212
|
-
if (
|
|
213
|
-
(nextSpan &&
|
|
214
|
-
nextSpanSharesSomeAnnotations &&
|
|
215
|
-
nextSpanAnnotations.length < spanAnnotations.length) ||
|
|
216
|
-
!nextSpanSharesSomeAnnotations
|
|
217
|
-
) {
|
|
211
|
+
if (!nextSpan) {
|
|
218
212
|
return {
|
|
219
213
|
state: 'changed',
|
|
220
214
|
previousMarks: marks,
|
|
221
|
-
marks:
|
|
215
|
+
marks: [],
|
|
222
216
|
}
|
|
223
217
|
}
|
|
224
218
|
|
|
225
|
-
if (!
|
|
219
|
+
if (nextSpanAnnotations.length > 0 && !nextSpanSharesSomeAnnotations) {
|
|
226
220
|
return {
|
|
227
221
|
state: 'changed',
|
|
228
222
|
previousMarks: marks,
|
|
229
223
|
marks: [],
|
|
230
224
|
}
|
|
231
225
|
}
|
|
226
|
+
|
|
227
|
+
if (
|
|
228
|
+
(nextSpanSharesSomeAnnotations &&
|
|
229
|
+
nextSpanAnnotations.length < spanAnnotations.length) ||
|
|
230
|
+
!nextSpanSharesSomeAnnotations
|
|
231
|
+
) {
|
|
232
|
+
return {
|
|
233
|
+
state: 'changed',
|
|
234
|
+
previousMarks: marks,
|
|
235
|
+
marks: nextSpan?.node.marks ?? [],
|
|
236
|
+
}
|
|
237
|
+
}
|
|
232
238
|
}
|
|
233
239
|
}
|
|
234
240
|
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type {EditorSelector} from '../editor/editor-selector'
|
|
2
|
+
import {getBlockEndPoint} from '../utils/util.get-block-end-point'
|
|
3
|
+
import {getSelectionEndPoint} from '../utils/util.get-selection-end-point'
|
|
4
|
+
import {getFocusBlock} from './selector.get-focus-block'
|
|
5
|
+
import {getSelectionText} from './selector.get-selection-text'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
export const getBlockTextAfter: EditorSelector<string> = (snapshot) => {
|
|
11
|
+
if (!snapshot.context.selection) {
|
|
12
|
+
return ''
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const endPoint = getSelectionEndPoint(snapshot.context.selection)
|
|
16
|
+
const block = getFocusBlock({
|
|
17
|
+
...snapshot,
|
|
18
|
+
context: {
|
|
19
|
+
...snapshot.context,
|
|
20
|
+
selection: {
|
|
21
|
+
anchor: endPoint,
|
|
22
|
+
focus: endPoint,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
if (!block) {
|
|
28
|
+
return ''
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const endOfBlock = getBlockEndPoint({
|
|
32
|
+
context: snapshot.context,
|
|
33
|
+
block,
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
return getSelectionText({
|
|
37
|
+
...snapshot,
|
|
38
|
+
context: {
|
|
39
|
+
...snapshot.context,
|
|
40
|
+
selection: {
|
|
41
|
+
anchor: endPoint,
|
|
42
|
+
focus: endOfBlock,
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
})
|
|
46
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {defineSchema} from '@portabletext/schema'
|
|
2
2
|
import {getTersePt, parseTersePt} from '@portabletext/test'
|
|
3
|
-
import {userEvent} from '@vitest/browser/context'
|
|
4
3
|
import {Given, Then, When} from 'racejar'
|
|
5
4
|
import {assert, expect, vi} from 'vitest'
|
|
5
|
+
import {userEvent} from 'vitest/browser'
|
|
6
6
|
import {getEditorSelection} from '../../internal-utils/editor-selection'
|
|
7
7
|
import {getSelectionText} from '../../internal-utils/selection-text'
|
|
8
8
|
import {getTextBlockKey} from '../../internal-utils/text-block-key'
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {defineSchema, type SchemaDefinition} from '@portabletext/schema'
|
|
2
2
|
import {createTestKeyGenerator} from '@portabletext/test'
|
|
3
3
|
import type {PortableTextBlock} from '@sanity/types'
|
|
4
|
-
import {page} from '@vitest/browser/context'
|
|
5
4
|
import React from 'react'
|
|
6
5
|
import {expect, vi} from 'vitest'
|
|
7
6
|
import {render} from 'vitest-browser-react'
|
|
7
|
+
import {page} from 'vitest/browser'
|
|
8
8
|
import type {Editor} from '../../editor'
|
|
9
9
|
import {
|
|
10
10
|
PortableTextEditable,
|
|
@@ -30,13 +30,12 @@ export async function createTestEditor(
|
|
|
30
30
|
options: CreateTestEditorOptions = {},
|
|
31
31
|
): Promise<
|
|
32
32
|
Pick<Context, 'editor' | 'locator'> & {
|
|
33
|
-
rerender: (options?: CreateTestEditorOptions) => void
|
|
33
|
+
rerender: (options?: CreateTestEditorOptions) => Promise<void>
|
|
34
34
|
}
|
|
35
35
|
> {
|
|
36
36
|
const editorRef = React.createRef<Editor>()
|
|
37
37
|
const keyGenerator = options.keyGenerator ?? createTestKeyGenerator()
|
|
38
|
-
|
|
39
|
-
const renderResult = render(
|
|
38
|
+
const renderResult = await render(
|
|
40
39
|
<EditorProvider
|
|
41
40
|
initialConfig={{
|
|
42
41
|
keyGenerator: keyGenerator,
|
|
@@ -50,8 +49,8 @@ export async function createTestEditor(
|
|
|
50
49
|
</EditorProvider>,
|
|
51
50
|
)
|
|
52
51
|
|
|
53
|
-
function rerender(newOptions?: CreateTestEditorOptions) {
|
|
54
|
-
newOptions
|
|
52
|
+
async function rerender(newOptions?: CreateTestEditorOptions) {
|
|
53
|
+
await (newOptions
|
|
55
54
|
? renderResult.rerender(
|
|
56
55
|
<EditorProvider
|
|
57
56
|
initialConfig={{
|
|
@@ -77,7 +76,7 @@ export async function createTestEditor(
|
|
|
77
76
|
<PortableTextEditable {...options.editableProps} />
|
|
78
77
|
{options.children}
|
|
79
78
|
</EditorProvider>,
|
|
80
|
-
)
|
|
79
|
+
))
|
|
81
80
|
}
|
|
82
81
|
|
|
83
82
|
const locator = page.getByRole('textbox')
|