@portabletext/editor 2.3.6 → 2.3.7
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/behavior.types.action.d.cts +9 -9
- package/lib/_chunks-dts/behavior.types.action.d.ts +9 -9
- package/lib/index.cjs +4 -19
- package/lib/index.cjs.map +1 -1
- package/lib/index.js +4 -19
- package/lib/index.js.map +1 -1
- package/lib/plugins/index.d.cts +3 -3
- package/lib/plugins/index.d.ts +3 -3
- package/package.json +5 -5
- package/src/editor/__tests__/self-solving.test.tsx +15 -3
- package/src/editor/plugins/createWithPortableTextMarkModel.ts +9 -5
- package/src/internal-utils/editor-selection.test.ts +7 -3
- package/src/internal-utils/editor-selection.ts +9 -13
- package/src/internal-utils/selection-focus-text.ts +9 -11
- package/src/internal-utils/selection-text.test.ts +9 -3
- package/src/internal-utils/selection-text.ts +6 -9
- package/src/internal-utils/terse-pt.test.ts +69 -46
- package/src/internal-utils/terse-pt.ts +5 -9
- package/src/internal-utils/text-block-key.test.ts +18 -7
- package/src/internal-utils/text-block-key.ts +6 -10
- package/src/internal-utils/text-marks.test.ts +12 -4
- package/src/internal-utils/text-marks.ts +6 -10
- package/src/internal-utils/text-selection.test.ts +44 -32
- package/src/internal-utils/text-selection.ts +11 -15
- package/src/internal-utils/value-annotations.ts +5 -3
- package/src/plugins/plugin.internal.auto-close-brackets.test.tsx +24 -24
- package/src/plugins/plugin.markdown.test.tsx +7 -10
package/lib/plugins/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Behavior, Editor, EditorEmittedEvent, EditorSchema } from "../_chunks-dts/behavior.types.action.cjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react22 from "react";
|
|
3
3
|
import React from "react";
|
|
4
4
|
/**
|
|
5
5
|
* @beta
|
|
@@ -181,7 +181,7 @@ type MarkdownPluginConfig = MarkdownBehaviorsConfig & {
|
|
|
181
181
|
*/
|
|
182
182
|
declare function MarkdownPlugin(props: {
|
|
183
183
|
config: MarkdownPluginConfig;
|
|
184
|
-
}):
|
|
184
|
+
}): react22.JSX.Element;
|
|
185
185
|
/**
|
|
186
186
|
* @beta
|
|
187
187
|
* Restrict the editor to one line. The plugin takes care of blocking
|
|
@@ -192,5 +192,5 @@ declare function MarkdownPlugin(props: {
|
|
|
192
192
|
*
|
|
193
193
|
* @deprecated Install the plugin from `@portabletext/plugin-one-line`
|
|
194
194
|
*/
|
|
195
|
-
declare function OneLinePlugin():
|
|
195
|
+
declare function OneLinePlugin(): react22.JSX.Element;
|
|
196
196
|
export { BehaviorPlugin, DecoratorShortcutPlugin, EditorRefPlugin, EventListenerPlugin, MarkdownPlugin, type MarkdownPluginConfig, OneLinePlugin };
|
package/lib/plugins/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Behavior, Editor, EditorEmittedEvent, EditorSchema } from "../_chunks-dts/behavior.types.action.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react22 from "react";
|
|
3
3
|
import React from "react";
|
|
4
4
|
/**
|
|
5
5
|
* @beta
|
|
@@ -181,7 +181,7 @@ type MarkdownPluginConfig = MarkdownBehaviorsConfig & {
|
|
|
181
181
|
*/
|
|
182
182
|
declare function MarkdownPlugin(props: {
|
|
183
183
|
config: MarkdownPluginConfig;
|
|
184
|
-
}):
|
|
184
|
+
}): react22.JSX.Element;
|
|
185
185
|
/**
|
|
186
186
|
* @beta
|
|
187
187
|
* Restrict the editor to one line. The plugin takes care of blocking
|
|
@@ -192,5 +192,5 @@ declare function MarkdownPlugin(props: {
|
|
|
192
192
|
*
|
|
193
193
|
* @deprecated Install the plugin from `@portabletext/plugin-one-line`
|
|
194
194
|
*/
|
|
195
|
-
declare function OneLinePlugin():
|
|
195
|
+
declare function OneLinePlugin(): react22.JSX.Element;
|
|
196
196
|
export { BehaviorPlugin, DecoratorShortcutPlugin, EditorRefPlugin, EventListenerPlugin, MarkdownPlugin, type MarkdownPluginConfig, OneLinePlugin };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@portabletext/editor",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.7",
|
|
4
4
|
"description": "Portable Text Editor made in React",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -79,14 +79,12 @@
|
|
|
79
79
|
"slate-dom": "^0.117.4",
|
|
80
80
|
"slate-react": "0.117.4",
|
|
81
81
|
"xstate": "^5.20.2",
|
|
82
|
-
"@portabletext/block-tools": "3.
|
|
83
|
-
"@portabletext/sanity-bridge": "1.1.1",
|
|
82
|
+
"@portabletext/block-tools": "3.2.0",
|
|
84
83
|
"@portabletext/keyboard-shortcuts": "1.1.1",
|
|
85
84
|
"@portabletext/patches": "1.1.6",
|
|
86
85
|
"@portabletext/schema": "1.0.0"
|
|
87
86
|
},
|
|
88
87
|
"devDependencies": {
|
|
89
|
-
"@portabletext/toolkit": "^2.0.17",
|
|
90
88
|
"@sanity/diff-match-patch": "^3.2.0",
|
|
91
89
|
"@sanity/pkg-utils": "^7.11.1",
|
|
92
90
|
"@sanity/schema": "^4.4.1",
|
|
@@ -113,13 +111,15 @@
|
|
|
113
111
|
"vite": "^7.0.6",
|
|
114
112
|
"vitest": "^3.2.4",
|
|
115
113
|
"vitest-browser-react": "^1.0.1",
|
|
114
|
+
"@portabletext/sanity-bridge": "1.1.1",
|
|
116
115
|
"racejar": "1.2.12"
|
|
117
116
|
},
|
|
118
117
|
"peerDependencies": {
|
|
119
118
|
"@sanity/schema": "^4.4.1",
|
|
120
119
|
"@sanity/types": "^4.4.1",
|
|
121
120
|
"react": "^18.3 || ^19",
|
|
122
|
-
"rxjs": "^7.8.2"
|
|
121
|
+
"rxjs": "^7.8.2",
|
|
122
|
+
"@portabletext/sanity-bridge": "1.1.1"
|
|
123
123
|
},
|
|
124
124
|
"engines": {
|
|
125
125
|
"node": ">=20.19"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type {JSONValue, Patch} from '@portabletext/patches'
|
|
2
|
+
import {compileSchema, defineSchema} from '@portabletext/schema'
|
|
2
3
|
import type {PortableTextBlock, PortableTextSpan} from '@sanity/types'
|
|
3
4
|
import {render, waitFor} from '@testing-library/react'
|
|
4
5
|
import {createRef, type ComponentProps, type RefObject} from 'react'
|
|
@@ -30,6 +31,7 @@ function span(
|
|
|
30
31
|
|
|
31
32
|
describe('Feature: Self-solving', () => {
|
|
32
33
|
it('Scenario: Missing .markDefs and .marks are added after the editor is made dirty', async () => {
|
|
34
|
+
const schemaDefinition = defineSchema({decorators: [{name: 'strong'}]})
|
|
33
35
|
const editorRef: RefObject<PortableTextEditor | null> = createRef()
|
|
34
36
|
const onChange = vi.fn<OnChange>()
|
|
35
37
|
const initialValue = [
|
|
@@ -77,6 +79,7 @@ describe('Feature: Self-solving', () => {
|
|
|
77
79
|
render(
|
|
78
80
|
<PortableTextEditorTester
|
|
79
81
|
ref={editorRef}
|
|
82
|
+
schemaDefinition={schemaDefinition}
|
|
80
83
|
keyGenerator={createTestKeyGenerator()}
|
|
81
84
|
value={initialValue}
|
|
82
85
|
onChange={onChange}
|
|
@@ -99,7 +102,10 @@ describe('Feature: Self-solving', () => {
|
|
|
99
102
|
if (editorRef.current) {
|
|
100
103
|
PortableTextEditor.select(
|
|
101
104
|
editorRef.current,
|
|
102
|
-
getTextSelection(
|
|
105
|
+
getTextSelection(
|
|
106
|
+
{schema: compileSchema(schemaDefinition), value: initialValue},
|
|
107
|
+
'foo',
|
|
108
|
+
),
|
|
103
109
|
)
|
|
104
110
|
PortableTextEditor.toggleMark(editorRef.current, 'strong')
|
|
105
111
|
}
|
|
@@ -110,7 +116,10 @@ describe('Feature: Self-solving', () => {
|
|
|
110
116
|
expect(onChange).toHaveBeenNthCalledWith(3, {
|
|
111
117
|
type: 'selection',
|
|
112
118
|
selection: {
|
|
113
|
-
...getTextSelection(
|
|
119
|
+
...getTextSelection(
|
|
120
|
+
{schema: compileSchema(schemaDefinition), value: initialValue},
|
|
121
|
+
'foo',
|
|
122
|
+
),
|
|
114
123
|
backward: false,
|
|
115
124
|
},
|
|
116
125
|
})
|
|
@@ -129,7 +138,10 @@ describe('Feature: Self-solving', () => {
|
|
|
129
138
|
expect(onChange).toHaveBeenNthCalledWith(7, {
|
|
130
139
|
type: 'selection',
|
|
131
140
|
selection: {
|
|
132
|
-
...getTextSelection(
|
|
141
|
+
...getTextSelection(
|
|
142
|
+
{schema: compileSchema(schemaDefinition), value: initialValue},
|
|
143
|
+
'foo',
|
|
144
|
+
),
|
|
133
145
|
backward: false,
|
|
134
146
|
},
|
|
135
147
|
})
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
*
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {isPortableTextBlock, isPortableTextSpan} from '@portabletext/toolkit'
|
|
8
7
|
import type {PortableTextObject, PortableTextSpan} from '@sanity/types'
|
|
9
8
|
import {isEqual, uniq} from 'lodash'
|
|
10
9
|
import {Editor, Element, Node, Path, Range, Text, Transforms} from 'slate'
|
|
11
10
|
import {debugWithName} from '../../internal-utils/debug'
|
|
11
|
+
import {isSpan, isTextBlock} from '../../internal-utils/parse-blocks'
|
|
12
12
|
import {getNextSpan, getPreviousSpan} from '../../internal-utils/sibling-utils'
|
|
13
13
|
import {isChangingRemotely} from '../../internal-utils/withChanges'
|
|
14
14
|
import {isRedoing, isUndoing} from '../../internal-utils/withUndoRedo'
|
|
@@ -356,7 +356,7 @@ export function createWithPortableTextMarkModel(
|
|
|
356
356
|
|
|
357
357
|
if (
|
|
358
358
|
atTheEndOfAnnotation &&
|
|
359
|
-
|
|
359
|
+
isSpan(editorActor.getSnapshot().context, op.node) &&
|
|
360
360
|
op.node.marks?.some((mark) => annotationsEnding.includes(mark))
|
|
361
361
|
) {
|
|
362
362
|
Transforms.insertNodes(editor, {
|
|
@@ -378,7 +378,7 @@ export function createWithPortableTextMarkModel(
|
|
|
378
378
|
|
|
379
379
|
if (
|
|
380
380
|
atTheStartOfAnnotation &&
|
|
381
|
-
|
|
381
|
+
isSpan(editorActor.getSnapshot().context, op.node) &&
|
|
382
382
|
op.node.marks?.some((mark) => annotationsStarting.includes(mark))
|
|
383
383
|
) {
|
|
384
384
|
Transforms.insertNodes(editor, {
|
|
@@ -400,7 +400,7 @@ export function createWithPortableTextMarkModel(
|
|
|
400
400
|
decoratorStarting &&
|
|
401
401
|
atTheEndOfAnnotation &&
|
|
402
402
|
!atTheStartOfAnnotation &&
|
|
403
|
-
|
|
403
|
+
isSpan(editorActor.getSnapshot().context, op.node) &&
|
|
404
404
|
op.node.marks?.length === 0
|
|
405
405
|
) {
|
|
406
406
|
Transforms.insertNodes(editor, {
|
|
@@ -458,7 +458,11 @@ export function createWithPortableTextMarkModel(
|
|
|
458
458
|
}),
|
|
459
459
|
)[0] ?? ([undefined, undefined] as const)
|
|
460
460
|
|
|
461
|
-
if (
|
|
461
|
+
if (
|
|
462
|
+
span &&
|
|
463
|
+
block &&
|
|
464
|
+
isTextBlock(editorActor.getSnapshot().context, block)
|
|
465
|
+
) {
|
|
462
466
|
const markDefs = block.markDefs ?? []
|
|
463
467
|
const marks = span.marks ?? []
|
|
464
468
|
const spanHasAnnotations = marks.some((mark) =>
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import {compileSchema, defineSchema} from '@portabletext/schema'
|
|
1
2
|
import {expect, test} from 'vitest'
|
|
2
3
|
import {getEditorSelection} from './editor-selection'
|
|
3
4
|
|
|
4
5
|
test(getEditorSelection.name, () => {
|
|
6
|
+
const schema = compileSchema(defineSchema({}))
|
|
5
7
|
const image = {
|
|
6
8
|
_type: 'image',
|
|
7
9
|
_key: 'i1',
|
|
@@ -25,15 +27,17 @@ test(getEditorSelection.name, () => {
|
|
|
25
27
|
],
|
|
26
28
|
}
|
|
27
29
|
|
|
28
|
-
expect(getEditorSelection([image, splitBlock])).toEqual({
|
|
30
|
+
expect(getEditorSelection({schema, value: [image, splitBlock]})).toEqual({
|
|
29
31
|
anchor: {path: [{_key: 'i1'}], offset: 0},
|
|
30
32
|
focus: {path: [{_key: 'b1'}, 'children', {_key: 's3'}], offset: 4},
|
|
31
33
|
})
|
|
32
|
-
expect(getEditorSelection([splitBlock, image])).toEqual({
|
|
34
|
+
expect(getEditorSelection({schema, value: [splitBlock, image]})).toEqual({
|
|
33
35
|
anchor: {path: [{_key: 'b1'}, 'children', {_key: 's1'}], offset: 0},
|
|
34
36
|
focus: {path: [{_key: 'i1'}], offset: 0},
|
|
35
37
|
})
|
|
36
|
-
expect(
|
|
38
|
+
expect(
|
|
39
|
+
getEditorSelection({schema, value: [blockWithStockTicker, splitBlock]}),
|
|
40
|
+
).toEqual({
|
|
37
41
|
anchor: {path: [{_key: 'b2'}, 'children', {_key: 's1'}], offset: 0},
|
|
38
42
|
focus: {path: [{_key: 'b1'}, 'children', {_key: 's3'}], offset: 4},
|
|
39
43
|
})
|
|
@@ -1,20 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type {PortableTextBlock} from '@sanity/types'
|
|
1
|
+
import type {EditorContext} from '../editor/editor-snapshot'
|
|
3
2
|
import type {EditorSelection, EditorSelectionPoint} from '../types/editor'
|
|
3
|
+
import {isSpan, isTextBlock} from './parse-blocks'
|
|
4
4
|
|
|
5
5
|
export function getEditorSelection(
|
|
6
|
-
|
|
6
|
+
context: Pick<EditorContext, 'schema' | 'value'>,
|
|
7
7
|
): EditorSelection {
|
|
8
|
-
if (!blocks) {
|
|
9
|
-
throw new Error('No value found')
|
|
10
|
-
}
|
|
11
|
-
|
|
12
8
|
let anchor: EditorSelectionPoint | undefined
|
|
13
9
|
let focus: EditorSelectionPoint | undefined
|
|
14
|
-
const firstBlock =
|
|
15
|
-
const lastBlock =
|
|
10
|
+
const firstBlock = context.value[0]
|
|
11
|
+
const lastBlock = context.value[context.value.length - 1]
|
|
16
12
|
|
|
17
|
-
if (
|
|
13
|
+
if (isTextBlock(context, firstBlock)) {
|
|
18
14
|
anchor = {
|
|
19
15
|
path: [
|
|
20
16
|
{_key: firstBlock._key},
|
|
@@ -30,13 +26,13 @@ export function getEditorSelection(
|
|
|
30
26
|
}
|
|
31
27
|
}
|
|
32
28
|
|
|
33
|
-
const lastChild =
|
|
29
|
+
const lastChild = isTextBlock(context, lastBlock)
|
|
34
30
|
? lastBlock.children[lastBlock.children.length - 1]
|
|
35
31
|
: undefined
|
|
36
32
|
if (
|
|
37
|
-
|
|
33
|
+
isTextBlock(context, lastBlock) &&
|
|
38
34
|
lastChild &&
|
|
39
|
-
|
|
35
|
+
isSpan(context, lastChild)
|
|
40
36
|
) {
|
|
41
37
|
focus = {
|
|
42
38
|
path: [{_key: lastBlock._key}, 'children', {_key: lastChild._key}],
|
|
@@ -1,21 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type {PortableTextBlock} from '@sanity/types'
|
|
1
|
+
import type {EditorContext} from '../editor/editor-snapshot'
|
|
3
2
|
import {
|
|
4
3
|
getBlockKeyFromSelectionPoint,
|
|
5
4
|
getChildKeyFromSelectionPoint,
|
|
6
5
|
} from '../selection/selection-point'
|
|
7
|
-
import
|
|
6
|
+
import {isSpan, isTextBlock} from './parse-blocks'
|
|
8
7
|
|
|
9
8
|
export function getSelectionFocusText(
|
|
10
|
-
|
|
11
|
-
selection: EditorSelection,
|
|
9
|
+
context: Pick<EditorContext, 'schema' | 'value' | 'selection'>,
|
|
12
10
|
) {
|
|
13
|
-
if (!
|
|
11
|
+
if (!context.selection) {
|
|
14
12
|
return undefined
|
|
15
13
|
}
|
|
16
14
|
|
|
17
|
-
const focusBlockKey = getBlockKeyFromSelectionPoint(selection.focus)
|
|
18
|
-
const focusChildKey = getChildKeyFromSelectionPoint(selection.focus)
|
|
15
|
+
const focusBlockKey = getBlockKeyFromSelectionPoint(context.selection.focus)
|
|
16
|
+
const focusChildKey = getChildKeyFromSelectionPoint(context.selection.focus)
|
|
19
17
|
|
|
20
18
|
if (focusBlockKey === undefined || focusChildKey === undefined) {
|
|
21
19
|
return undefined
|
|
@@ -23,11 +21,11 @@ export function getSelectionFocusText(
|
|
|
23
21
|
|
|
24
22
|
let text: string | undefined
|
|
25
23
|
|
|
26
|
-
for (const block of value) {
|
|
27
|
-
if (
|
|
24
|
+
for (const block of context.value) {
|
|
25
|
+
if (isTextBlock(context, block)) {
|
|
28
26
|
if (block._key === focusBlockKey) {
|
|
29
27
|
for (const child of block.children) {
|
|
30
|
-
if (
|
|
28
|
+
if (isSpan(context, child)) {
|
|
31
29
|
if (child._key === focusChildKey) {
|
|
32
30
|
text = child.text
|
|
33
31
|
break
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import {compileSchema, defineSchema} from '@portabletext/schema'
|
|
1
2
|
import {expect, test} from 'vitest'
|
|
2
3
|
import {getSelectionText} from './selection-text'
|
|
3
4
|
|
|
4
5
|
test(getSelectionText.name, () => {
|
|
6
|
+
const schema = compileSchema(defineSchema({}))
|
|
5
7
|
const splitBlock = {
|
|
6
8
|
_type: 'block',
|
|
7
9
|
_key: 'A-4',
|
|
@@ -15,9 +17,13 @@ test(getSelectionText.name, () => {
|
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
expect(
|
|
18
|
-
getSelectionText(
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
getSelectionText({
|
|
21
|
+
schema,
|
|
22
|
+
value: [splitBlock],
|
|
23
|
+
selection: {
|
|
24
|
+
anchor: {path: [{_key: 'A-4'}, 'children', {_key: 'A-7'}], offset: 0},
|
|
25
|
+
focus: {path: [{_key: 'A-4'}, 'children', {_key: 'A-7'}], offset: 3},
|
|
26
|
+
},
|
|
21
27
|
}),
|
|
22
28
|
).toEqual(['bar'])
|
|
23
29
|
})
|
|
@@ -1,21 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type {PortableTextBlock} from '@sanity/types'
|
|
3
|
-
import type {EditorSelection} from '../types/editor'
|
|
1
|
+
import type {EditorContext} from '../editor/editor-snapshot'
|
|
4
2
|
import {sliceBlocks} from '../utils/util.slice-blocks'
|
|
5
3
|
import {getTersePt} from './terse-pt'
|
|
6
4
|
|
|
7
5
|
export function getSelectionText(
|
|
8
|
-
|
|
9
|
-
selection: EditorSelection,
|
|
6
|
+
context: Pick<EditorContext, 'schema' | 'value' | 'selection'>,
|
|
10
7
|
) {
|
|
11
|
-
if (!
|
|
8
|
+
if (!context.selection) {
|
|
12
9
|
return []
|
|
13
10
|
}
|
|
14
11
|
|
|
15
12
|
const slice = sliceBlocks({
|
|
16
|
-
context: {schema:
|
|
17
|
-
blocks: value,
|
|
13
|
+
context: {schema: context.schema, selection: context.selection},
|
|
14
|
+
blocks: context.value,
|
|
18
15
|
})
|
|
19
16
|
|
|
20
|
-
return getTersePt(slice)
|
|
17
|
+
return getTersePt({schema: context.schema, value: slice})
|
|
21
18
|
}
|
|
@@ -6,6 +6,7 @@ import {getTersePt, parseTersePt, parseTersePtString} from './terse-pt'
|
|
|
6
6
|
const keyGenerator = createTestKeyGenerator()
|
|
7
7
|
|
|
8
8
|
test(getTersePt.name, () => {
|
|
9
|
+
const schema = compileSchema(defineSchema({}))
|
|
9
10
|
const fooBlock = {
|
|
10
11
|
_key: 'b1',
|
|
11
12
|
_type: 'block',
|
|
@@ -27,66 +28,88 @@ test(getTersePt.name, () => {
|
|
|
27
28
|
children: [{_key: 's4', _type: 'span', text: 'foo\nbar'}],
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
expect(getTersePt([fooBlock, barBlock])).toEqual([
|
|
31
|
-
expect(getTersePt([emptyBlock, barBlock])).toEqual(['', 'bar'])
|
|
32
|
-
expect(getTersePt([fooBlock, emptyBlock, barBlock])).toEqual([
|
|
31
|
+
expect(getTersePt({schema, value: [fooBlock, barBlock]})).toEqual([
|
|
33
32
|
'foo',
|
|
33
|
+
'bar',
|
|
34
|
+
])
|
|
35
|
+
expect(getTersePt({schema, value: [emptyBlock, barBlock]})).toEqual([
|
|
34
36
|
'',
|
|
35
37
|
'bar',
|
|
36
38
|
])
|
|
37
|
-
expect(getTersePt([fooBlock,
|
|
39
|
+
expect(getTersePt({schema, value: [fooBlock, emptyBlock, barBlock]})).toEqual(
|
|
40
|
+
['foo', '', 'bar'],
|
|
41
|
+
)
|
|
42
|
+
expect(getTersePt({schema, value: [fooBlock, softReturnBlock]})).toEqual([
|
|
43
|
+
'foo',
|
|
44
|
+
'foo\nbar',
|
|
45
|
+
])
|
|
38
46
|
|
|
39
47
|
expect(
|
|
40
|
-
getTersePt(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
48
|
+
getTersePt({
|
|
49
|
+
schema,
|
|
50
|
+
value: [
|
|
51
|
+
{
|
|
52
|
+
_key: keyGenerator(),
|
|
53
|
+
_type: 'block',
|
|
54
|
+
children: [{_key: keyGenerator(), _type: 'span', text: 'foo'}],
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
}),
|
|
47
58
|
).toEqual(['foo'])
|
|
48
59
|
expect(
|
|
49
|
-
getTersePt(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
60
|
+
getTersePt({
|
|
61
|
+
schema,
|
|
62
|
+
value: [
|
|
63
|
+
{
|
|
64
|
+
_key: keyGenerator(),
|
|
65
|
+
_type: 'block',
|
|
66
|
+
children: [{_key: keyGenerator(), _type: 'span', text: 'foo'}],
|
|
67
|
+
listItem: 'number',
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
}),
|
|
57
71
|
).toEqual(['#:foo'])
|
|
58
72
|
expect(
|
|
59
|
-
getTersePt(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
73
|
+
getTersePt({
|
|
74
|
+
schema,
|
|
75
|
+
value: [
|
|
76
|
+
{
|
|
77
|
+
_key: keyGenerator(),
|
|
78
|
+
_type: 'block',
|
|
79
|
+
children: [{_key: keyGenerator(), _type: 'span', text: 'foo'}],
|
|
80
|
+
listItem: 'number',
|
|
81
|
+
style: 'h3',
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
}),
|
|
68
85
|
).toEqual(['#h3:foo'])
|
|
69
86
|
expect(
|
|
70
|
-
getTersePt(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
87
|
+
getTersePt({
|
|
88
|
+
schema,
|
|
89
|
+
value: [
|
|
90
|
+
{
|
|
91
|
+
_key: keyGenerator(),
|
|
92
|
+
_type: 'block',
|
|
93
|
+
children: [{_key: keyGenerator(), _type: 'span', text: 'foo'}],
|
|
94
|
+
level: 2,
|
|
95
|
+
listItem: 'number',
|
|
96
|
+
style: 'h3',
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
}),
|
|
80
100
|
).toEqual(['>>#h3:foo'])
|
|
81
101
|
expect(
|
|
82
|
-
getTersePt(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
102
|
+
getTersePt({
|
|
103
|
+
schema,
|
|
104
|
+
value: [
|
|
105
|
+
{
|
|
106
|
+
_key: keyGenerator(),
|
|
107
|
+
_type: 'block',
|
|
108
|
+
children: [{_key: keyGenerator(), _type: 'span', text: 'foo'}],
|
|
109
|
+
style: 'h3',
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
}),
|
|
90
113
|
).toEqual(['h3:foo'])
|
|
91
114
|
})
|
|
92
115
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {isPortableTextBlock, isPortableTextSpan} from '@portabletext/toolkit'
|
|
2
1
|
import type {PortableTextBlock, PortableTextTextBlock} from '@sanity/types'
|
|
3
2
|
import type {EditorContext} from '../editor/editor-snapshot'
|
|
3
|
+
import {isSpan, isTextBlock} from './parse-blocks'
|
|
4
4
|
|
|
5
5
|
type TersePtConfig = {
|
|
6
6
|
style: (name?: string) => string
|
|
@@ -20,18 +20,14 @@ const defaultConfig: TersePtConfig = {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export function getTersePt(
|
|
23
|
-
|
|
23
|
+
context: Pick<EditorContext, 'schema' | 'value'>,
|
|
24
24
|
): Array<string> {
|
|
25
|
-
if (!value) {
|
|
26
|
-
return []
|
|
27
|
-
}
|
|
28
|
-
|
|
29
25
|
const blocks: Array<string> = []
|
|
30
26
|
|
|
31
|
-
for (const block of value) {
|
|
27
|
+
for (const block of context.value) {
|
|
32
28
|
let terseBlock = ''
|
|
33
29
|
|
|
34
|
-
if (
|
|
30
|
+
if (isTextBlock(context, block)) {
|
|
35
31
|
const blockPrefix = `${defaultConfig.level(block.level)}${defaultConfig.listItem(block.listItem)}${defaultConfig.style(block.style)}`
|
|
36
32
|
|
|
37
33
|
if (blockPrefix) {
|
|
@@ -42,7 +38,7 @@ export function getTersePt(
|
|
|
42
38
|
for (const child of block.children) {
|
|
43
39
|
index++
|
|
44
40
|
|
|
45
|
-
if (
|
|
41
|
+
if (isSpan(context, child)) {
|
|
46
42
|
terseBlock = `${terseBlock}${index > 0 ? ',' : ''}${child.text}`
|
|
47
43
|
} else {
|
|
48
44
|
terseBlock = `${terseBlock}${index > 0 ? ',' : ''}{${child._type}}`
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import {compileSchema, defineSchema} from '@portabletext/schema'
|
|
1
2
|
import {expect, test} from 'vitest'
|
|
2
3
|
import {getTextBlockKey} from './text-block-key'
|
|
3
4
|
|
|
4
5
|
test(getTextBlockKey.name, () => {
|
|
6
|
+
const schema = compileSchema(defineSchema({}))
|
|
5
7
|
const emptyBlock = {
|
|
6
8
|
_key: 'b1',
|
|
7
9
|
_type: 'block',
|
|
@@ -18,13 +20,22 @@ test(getTextBlockKey.name, () => {
|
|
|
18
20
|
children: [{_key: 's3', _type: 'span', text: 'foo\nbar'}],
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
expect(getTextBlockKey([emptyBlock, fooBlock, softReturnBlock], '')).toBe(
|
|
22
|
-
'b1',
|
|
23
|
-
)
|
|
24
|
-
expect(getTextBlockKey([emptyBlock, fooBlock, softReturnBlock], 'foo')).toBe(
|
|
25
|
-
'b2',
|
|
26
|
-
)
|
|
27
23
|
expect(
|
|
28
|
-
getTextBlockKey(
|
|
24
|
+
getTextBlockKey(
|
|
25
|
+
{schema, value: [emptyBlock, fooBlock, softReturnBlock]},
|
|
26
|
+
'',
|
|
27
|
+
),
|
|
28
|
+
).toBe('b1')
|
|
29
|
+
expect(
|
|
30
|
+
getTextBlockKey(
|
|
31
|
+
{schema, value: [emptyBlock, fooBlock, softReturnBlock]},
|
|
32
|
+
'foo',
|
|
33
|
+
),
|
|
34
|
+
).toBe('b2')
|
|
35
|
+
expect(
|
|
36
|
+
getTextBlockKey(
|
|
37
|
+
{schema, value: [emptyBlock, fooBlock, softReturnBlock]},
|
|
38
|
+
'foo\nbar',
|
|
39
|
+
),
|
|
29
40
|
).toBe('b3')
|
|
30
41
|
})
|
|
@@ -1,20 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import type {EditorContext} from '../editor/editor-snapshot'
|
|
2
|
+
import {isSpan, isTextBlock} from './parse-blocks'
|
|
3
3
|
|
|
4
4
|
export function getTextBlockKey(
|
|
5
|
-
|
|
5
|
+
context: Pick<EditorContext, 'schema' | 'value'>,
|
|
6
6
|
text: string,
|
|
7
7
|
) {
|
|
8
|
-
if (!value) {
|
|
9
|
-
throw new Error(`Unable to find block key for text "${text}"`)
|
|
10
|
-
}
|
|
11
|
-
|
|
12
8
|
let blockKey: string | undefined
|
|
13
9
|
|
|
14
|
-
for (const block of value) {
|
|
15
|
-
if (
|
|
10
|
+
for (const block of context.value) {
|
|
11
|
+
if (isTextBlock(context, block)) {
|
|
16
12
|
for (const child of block.children) {
|
|
17
|
-
if (
|
|
13
|
+
if (isSpan(context, child) && child.text === text) {
|
|
18
14
|
blockKey = block._key
|
|
19
15
|
break
|
|
20
16
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import {compileSchema, defineSchema} from '@portabletext/schema'
|
|
1
2
|
import {expect, test} from 'vitest'
|
|
2
3
|
import {getTextMarks} from './text-marks'
|
|
3
4
|
|
|
4
5
|
test(getTextMarks.name, () => {
|
|
6
|
+
const schema = compileSchema(defineSchema({}))
|
|
5
7
|
const fooBlock = {
|
|
6
8
|
_key: 'b1',
|
|
7
9
|
_type: 'block',
|
|
@@ -26,8 +28,14 @@ test(getTextMarks.name, () => {
|
|
|
26
28
|
],
|
|
27
29
|
}
|
|
28
30
|
|
|
29
|
-
expect(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
expect(getTextMarks([splitFooBarBazBlock], '
|
|
31
|
+
expect(
|
|
32
|
+
getTextMarks({schema, value: [fooBlock, splitBarBlock]}, 'ba'),
|
|
33
|
+
).toEqual(['strong'])
|
|
34
|
+
expect(getTextMarks({schema, value: [splitFooBarBazBlock]}, 'bar')).toEqual([
|
|
35
|
+
'strong',
|
|
36
|
+
])
|
|
37
|
+
expect(getTextMarks({schema, value: [splitFooBarBazBlock]}, ' ')).toEqual([])
|
|
38
|
+
expect(getTextMarks({schema, value: [splitFooBarBazBlock]}, 'baz')).toEqual([
|
|
39
|
+
'l1',
|
|
40
|
+
])
|
|
33
41
|
})
|