@portabletext/editor 1.25.0 → 1.26.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/_chunks-cjs/behavior.core.cjs +131 -36
- package/lib/_chunks-cjs/behavior.core.cjs.map +1 -1
- package/lib/_chunks-cjs/selector.get-text-before.cjs +8 -8
- package/lib/_chunks-cjs/selector.get-text-before.cjs.map +1 -1
- package/lib/_chunks-cjs/{selector.is-active-style.cjs → selector.is-at-the-start-of-block.cjs} +29 -3
- package/lib/_chunks-cjs/selector.is-at-the-start-of-block.cjs.map +1 -0
- package/lib/_chunks-cjs/util.is-empty-text-block.cjs +2 -2
- package/lib/_chunks-cjs/util.is-empty-text-block.cjs.map +1 -1
- package/lib/_chunks-cjs/util.is-equal-selection-points.cjs +46 -0
- package/lib/_chunks-cjs/util.is-equal-selection-points.cjs.map +1 -0
- package/lib/_chunks-cjs/util.reverse-selection.cjs +0 -16
- package/lib/_chunks-cjs/util.reverse-selection.cjs.map +1 -1
- package/lib/_chunks-es/behavior.core.js +99 -4
- package/lib/_chunks-es/behavior.core.js.map +1 -1
- package/lib/_chunks-es/selector.get-text-before.js +2 -2
- package/lib/_chunks-es/{selector.is-active-style.js → selector.is-at-the-start-of-block.js} +29 -2
- package/lib/_chunks-es/selector.is-at-the-start-of-block.js.map +1 -0
- package/lib/_chunks-es/util.is-empty-text-block.js +1 -1
- package/lib/_chunks-es/util.is-equal-selection-points.js +47 -0
- package/lib/_chunks-es/util.is-equal-selection-points.js.map +1 -0
- package/lib/_chunks-es/util.reverse-selection.js +0 -16
- package/lib/_chunks-es/util.reverse-selection.js.map +1 -1
- package/lib/behaviors/index.cjs +27 -27
- package/lib/behaviors/index.cjs.map +1 -1
- package/lib/behaviors/index.d.cts +413 -0
- package/lib/behaviors/index.d.ts +413 -0
- package/lib/behaviors/index.js +1 -1
- package/lib/index.cjs +147 -106
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +1653 -222
- package/lib/index.d.ts +1653 -222
- package/lib/index.js +143 -102
- package/lib/index.js.map +1 -1
- package/lib/selectors/index.cjs +25 -23
- package/lib/selectors/index.cjs.map +1 -1
- package/lib/selectors/index.d.cts +16 -0
- package/lib/selectors/index.d.ts +16 -0
- package/lib/selectors/index.js +3 -1
- package/lib/utils/index.cjs +5 -3
- package/lib/utils/index.cjs.map +1 -1
- package/lib/utils/index.d.cts +19 -0
- package/lib/utils/index.d.ts +19 -0
- package/lib/utils/index.js +4 -2
- package/package.json +1 -1
- package/src/behavior-actions/behavior.action-utils.insert-block.ts +3 -3
- package/src/behavior-actions/behavior.action.block.set.ts +23 -0
- package/src/behavior-actions/behavior.action.block.unset.ts +21 -0
- package/src/behavior-actions/behavior.action.insert-break.ts +2 -69
- package/src/behavior-actions/behavior.action.insert.block.ts +29 -0
- package/src/behavior-actions/behavior.actions.ts +28 -9
- package/src/behaviors/behavior.core.insert-break.ts +122 -0
- package/src/behaviors/behavior.core.ts +6 -2
- package/src/behaviors/behavior.types.ts +16 -1
- package/src/converters/converter.json.ts +4 -4
- package/src/converters/converter.portable-text.deserialize.test.ts +1 -1
- package/src/converters/converter.portable-text.ts +4 -4
- package/src/converters/converter.text-html.deserialize.test.ts +1 -1
- package/src/converters/converter.text-html.serialize.test.ts +1 -1
- package/src/converters/converter.text-html.ts +4 -4
- package/src/converters/converter.text-plain.test.ts +1 -1
- package/src/converters/converter.text-plain.ts +3 -3
- package/src/converters/{converter.ts → converter.types.ts} +6 -0
- package/src/editor/create-editor.ts +4 -1
- package/src/editor/editor-machine.ts +8 -2
- package/src/editor/editor-snapshot.ts +1 -1
- package/src/editor/plugins/__tests__/withPortableTextMarkModel.test.tsx +2 -2
- package/src/editor/plugins/create-with-event-listeners.ts +3 -0
- package/src/selectors/index.ts +2 -0
- package/src/selectors/selector.is-at-the-end-of-block.ts +22 -0
- package/src/selectors/selector.is-at-the-start-of-block.ts +25 -0
- package/src/selectors/selector.is-selection-collapsed.ts +6 -2
- package/src/utils/index.ts +2 -0
- package/src/utils/util.get-block-end-point.ts +34 -0
- package/src/utils/util.is-equal-selection-points.ts +13 -0
- package/lib/_chunks-cjs/selector.is-active-style.cjs.map +0 -1
- package/lib/_chunks-cjs/util.is-keyed-segment.cjs +0 -6
- package/lib/_chunks-cjs/util.is-keyed-segment.cjs.map +0 -1
- package/lib/_chunks-es/selector.is-active-style.js.map +0 -1
- package/lib/_chunks-es/util.is-keyed-segment.js +0 -7
- package/lib/_chunks-es/util.is-keyed-segment.js.map +0 -1
- /package/src/converters/{converters.ts → converters.core.ts} +0 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import * as selectors from '../selectors'
|
|
2
|
+
import {defineBehavior, raise} from './behavior.types'
|
|
3
|
+
|
|
4
|
+
const atTheEndOfTextBlock = defineBehavior({
|
|
5
|
+
on: 'insert.break',
|
|
6
|
+
guard: ({context}) => {
|
|
7
|
+
const focusTextBlock = selectors.getFocusTextBlock({context})
|
|
8
|
+
const selectionCollapsed = selectors.isSelectionCollapsed({context})
|
|
9
|
+
|
|
10
|
+
if (!context.selection || !focusTextBlock || !selectionCollapsed) {
|
|
11
|
+
return false
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const atTheEndOfBlock = selectors.isAtTheEndOfBlock(focusTextBlock)({
|
|
15
|
+
context,
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const focusListItem = focusTextBlock.node.listItem
|
|
19
|
+
const focusLevel = focusTextBlock.node.level
|
|
20
|
+
|
|
21
|
+
if (atTheEndOfBlock) {
|
|
22
|
+
return {focusListItem, focusLevel}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return false
|
|
26
|
+
},
|
|
27
|
+
actions: [
|
|
28
|
+
({context}, {focusListItem, focusLevel}) => [
|
|
29
|
+
raise({
|
|
30
|
+
type: 'insert.block',
|
|
31
|
+
block: {
|
|
32
|
+
_type: context.schema.block.name,
|
|
33
|
+
_key: context.keyGenerator(),
|
|
34
|
+
children: [
|
|
35
|
+
{
|
|
36
|
+
_key: context.keyGenerator(),
|
|
37
|
+
_type: context.schema.span.name,
|
|
38
|
+
text: '',
|
|
39
|
+
marks: [],
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
markDefs: [],
|
|
43
|
+
listItem: focusListItem,
|
|
44
|
+
level: focusLevel,
|
|
45
|
+
style: context.schema.styles[0]?.value,
|
|
46
|
+
},
|
|
47
|
+
placement: 'after',
|
|
48
|
+
}),
|
|
49
|
+
],
|
|
50
|
+
],
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
const atTheStartOfTextBlock = defineBehavior({
|
|
54
|
+
on: 'insert.break',
|
|
55
|
+
guard: ({context}) => {
|
|
56
|
+
const focusTextBlock = selectors.getFocusTextBlock({context})
|
|
57
|
+
const selectionCollapsed = selectors.isSelectionCollapsed({context})
|
|
58
|
+
|
|
59
|
+
if (!context.selection || !focusTextBlock || !selectionCollapsed) {
|
|
60
|
+
return false
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const focusSpan = selectors.getFocusSpan({context})
|
|
64
|
+
|
|
65
|
+
const focusDecorators = focusSpan?.node.marks?.filter(
|
|
66
|
+
(mark) =>
|
|
67
|
+
context.schema.decorators.some(
|
|
68
|
+
(decorator) => decorator.value === mark,
|
|
69
|
+
) ?? [],
|
|
70
|
+
)
|
|
71
|
+
const focusAnnotations =
|
|
72
|
+
focusSpan?.node.marks?.filter(
|
|
73
|
+
(mark) =>
|
|
74
|
+
!context.schema.decorators.some(
|
|
75
|
+
(decorator) => decorator.value === mark,
|
|
76
|
+
),
|
|
77
|
+
) ?? []
|
|
78
|
+
const focusListItem = focusTextBlock.node.listItem
|
|
79
|
+
const focusLevel = focusTextBlock.node.level
|
|
80
|
+
|
|
81
|
+
const atTheStartOfBlock = selectors.isAtTheStartOfBlock(focusTextBlock)({
|
|
82
|
+
context,
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
if (atTheStartOfBlock) {
|
|
86
|
+
return {focusAnnotations, focusDecorators, focusListItem, focusLevel}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return false
|
|
90
|
+
},
|
|
91
|
+
actions: [
|
|
92
|
+
(
|
|
93
|
+
{context},
|
|
94
|
+
{focusAnnotations, focusDecorators, focusListItem, focusLevel},
|
|
95
|
+
) => [
|
|
96
|
+
raise({
|
|
97
|
+
type: 'insert.block',
|
|
98
|
+
block: {
|
|
99
|
+
_key: context.keyGenerator(),
|
|
100
|
+
_type: context.schema.block.name,
|
|
101
|
+
children: [
|
|
102
|
+
{
|
|
103
|
+
_key: context.keyGenerator(),
|
|
104
|
+
_type: context.schema.span.name,
|
|
105
|
+
marks: focusAnnotations.length === 0 ? focusDecorators : [],
|
|
106
|
+
text: '',
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
listItem: focusListItem,
|
|
110
|
+
level: focusLevel,
|
|
111
|
+
style: context.schema.styles[0]?.value,
|
|
112
|
+
},
|
|
113
|
+
placement: 'before',
|
|
114
|
+
}),
|
|
115
|
+
],
|
|
116
|
+
],
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
export const coreInsertBreakBehaviors = {
|
|
120
|
+
atTheEndOfTextBlock,
|
|
121
|
+
atTheStartOfTextBlock,
|
|
122
|
+
}
|
|
@@ -2,14 +2,15 @@ import {coreAnnotationBehaviors} from './behavior.core.annotations'
|
|
|
2
2
|
import {coreBlockObjectBehaviors} from './behavior.core.block-objects'
|
|
3
3
|
import {coreDecoratorBehaviors} from './behavior.core.decorators'
|
|
4
4
|
import {coreDeserializeBehavior} from './behavior.core.deserialize'
|
|
5
|
+
import {coreInsertBreakBehaviors} from './behavior.core.insert-break'
|
|
5
6
|
import {coreListBehaviors} from './behavior.core.lists'
|
|
6
7
|
import {coreSerializeBehaviors} from './behavior.core.serialize'
|
|
7
8
|
import {coreStyleBehaviors} from './behavior.core.style'
|
|
8
|
-
import {defineBehavior} from './behavior.types'
|
|
9
|
+
import {defineBehavior, raise} from './behavior.types'
|
|
9
10
|
|
|
10
11
|
const softReturn = defineBehavior({
|
|
11
12
|
on: 'insert.soft break',
|
|
12
|
-
actions: [() => [{type: 'insert.text', text: '\n'}]],
|
|
13
|
+
actions: [() => [raise({type: 'insert.text', text: '\n'})]],
|
|
13
14
|
})
|
|
14
15
|
|
|
15
16
|
/**
|
|
@@ -38,6 +39,8 @@ export const coreBehaviors = [
|
|
|
38
39
|
coreListBehaviors.clearListOnEnter,
|
|
39
40
|
coreListBehaviors.indentListOnTab,
|
|
40
41
|
coreListBehaviors.unindentListOnShiftTab,
|
|
42
|
+
coreInsertBreakBehaviors.atTheEndOfTextBlock,
|
|
43
|
+
coreInsertBreakBehaviors.atTheStartOfTextBlock,
|
|
41
44
|
coreSerializeBehaviors.serialize,
|
|
42
45
|
coreSerializeBehaviors['serialization.success'],
|
|
43
46
|
coreStyleBehaviors.toggleStyleOff,
|
|
@@ -53,6 +56,7 @@ export const coreBehavior = {
|
|
|
53
56
|
decorators: coreDecoratorBehaviors,
|
|
54
57
|
deserialize: coreDeserializeBehavior,
|
|
55
58
|
blockObjects: coreBlockObjectBehaviors,
|
|
59
|
+
insertBreak: coreInsertBreakBehaviors,
|
|
56
60
|
lists: coreListBehaviors,
|
|
57
61
|
...coreSerializeBehaviors,
|
|
58
62
|
style: coreSerializeBehaviors,
|
|
@@ -5,7 +5,7 @@ import type {
|
|
|
5
5
|
} from '@sanity/types'
|
|
6
6
|
import type {TextUnit} from 'slate'
|
|
7
7
|
import type {TextInsertTextOptions} from 'slate/dist/interfaces/transforms/text'
|
|
8
|
-
import type {ConverterEvent} from '../converters/converter'
|
|
8
|
+
import type {ConverterEvent} from '../converters/converter.types'
|
|
9
9
|
import type {EditorContext} from '../editor/editor-snapshot'
|
|
10
10
|
import type {MIMEType} from '../internal-utils/mime-type'
|
|
11
11
|
import type {OmitFromUnion, PickFromUnion} from '../type-utils'
|
|
@@ -35,6 +35,16 @@ export type SyntheticBehaviorEvent =
|
|
|
35
35
|
value: {[prop: string]: unknown}
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
+
| {
|
|
39
|
+
type: 'block.set'
|
|
40
|
+
at: [KeyedSegment]
|
|
41
|
+
[props: string]: unknown
|
|
42
|
+
}
|
|
43
|
+
| {
|
|
44
|
+
type: 'block.unset'
|
|
45
|
+
at: [KeyedSegment]
|
|
46
|
+
props: Array<string>
|
|
47
|
+
}
|
|
38
48
|
| {
|
|
39
49
|
type: 'blur'
|
|
40
50
|
}
|
|
@@ -101,6 +111,11 @@ export type SyntheticBehaviorEvent =
|
|
|
101
111
|
| {
|
|
102
112
|
type: 'insert.soft break'
|
|
103
113
|
}
|
|
114
|
+
| {
|
|
115
|
+
type: 'insert.block'
|
|
116
|
+
block: PortableTextBlock
|
|
117
|
+
placement: 'auto' | 'after' | 'before'
|
|
118
|
+
}
|
|
104
119
|
| {
|
|
105
120
|
type: 'insert.span'
|
|
106
121
|
text: string
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {defineConverter} from './converter.types'
|
|
2
2
|
|
|
3
|
-
export const converterJson
|
|
3
|
+
export const converterJson = defineConverter({
|
|
4
|
+
mimeType: 'application/json',
|
|
4
5
|
serialize: ({context, event}) => {
|
|
5
6
|
const portableTextConverter = context.converters.find(
|
|
6
7
|
(converter) => converter.mimeType === 'application/x-portable-text',
|
|
@@ -49,5 +50,4 @@ export const converterJson: Converter<'application/json'> = {
|
|
|
49
50
|
mimeType: 'application/json',
|
|
50
51
|
}
|
|
51
52
|
},
|
|
52
|
-
|
|
53
|
-
}
|
|
53
|
+
})
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
} from '../editor/define-schema'
|
|
7
7
|
import {createTestKeyGenerator} from '../internal-utils/test-key-generator'
|
|
8
8
|
import {converterPortableText} from './converter.portable-text'
|
|
9
|
-
import {coreConverters} from './converters'
|
|
9
|
+
import {coreConverters} from './converters.core'
|
|
10
10
|
|
|
11
11
|
function createContext(schema: SchemaDefinition) {
|
|
12
12
|
return {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import {parseBlock} from '../internal-utils/parse-blocks'
|
|
2
2
|
import {sliceBlocks} from '../utils'
|
|
3
|
-
import
|
|
3
|
+
import {defineConverter} from './converter.types'
|
|
4
4
|
|
|
5
|
-
export const converterPortableText
|
|
5
|
+
export const converterPortableText = defineConverter({
|
|
6
|
+
mimeType: 'application/x-portable-text',
|
|
6
7
|
serialize: ({context, event}) => {
|
|
7
8
|
if (!context.selection) {
|
|
8
9
|
return {
|
|
@@ -55,5 +56,4 @@ export const converterPortableText: Converter<'application/x-portable-text'> = {
|
|
|
55
56
|
mimeType: 'application/x-portable-text',
|
|
56
57
|
}
|
|
57
58
|
},
|
|
58
|
-
|
|
59
|
-
}
|
|
59
|
+
})
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
} from '../editor/define-schema'
|
|
7
7
|
import {createTestKeyGenerator} from '../internal-utils/test-key-generator'
|
|
8
8
|
import {converterTextHtml} from './converter.text-html'
|
|
9
|
-
import {coreConverters} from './converters'
|
|
9
|
+
import {coreConverters} from './converters.core'
|
|
10
10
|
|
|
11
11
|
function createContext(schema: SchemaDefinition) {
|
|
12
12
|
return {
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
import {createTestKeyGenerator} from '../internal-utils/test-key-generator'
|
|
9
9
|
import type {EditorSelection} from '../utils'
|
|
10
10
|
import {converterTextHtml} from './converter.text-html'
|
|
11
|
-
import {coreConverters} from './converters'
|
|
11
|
+
import {coreConverters} from './converters.core'
|
|
12
12
|
|
|
13
13
|
const decoratedParagraph: PortableTextTextBlock = {
|
|
14
14
|
_key: 'k0',
|
|
@@ -2,9 +2,10 @@ import {htmlToBlocks} from '@portabletext/block-tools'
|
|
|
2
2
|
import {toHTML} from '@portabletext/to-html'
|
|
3
3
|
import type {PortableTextBlock} from '@sanity/types'
|
|
4
4
|
import {sliceBlocks} from '../utils'
|
|
5
|
-
import
|
|
5
|
+
import {defineConverter} from './converter.types'
|
|
6
6
|
|
|
7
|
-
export const converterTextHtml
|
|
7
|
+
export const converterTextHtml = defineConverter({
|
|
8
|
+
mimeType: 'text/html',
|
|
8
9
|
serialize: ({context, event}) => {
|
|
9
10
|
if (!context.selection) {
|
|
10
11
|
return {
|
|
@@ -57,5 +58,4 @@ export const converterTextHtml: Converter<'text/html'> = {
|
|
|
57
58
|
mimeType: 'text/html',
|
|
58
59
|
}
|
|
59
60
|
},
|
|
60
|
-
|
|
61
|
-
}
|
|
61
|
+
})
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
import type {EditorContext} from '../editor/editor-snapshot'
|
|
9
9
|
import type {EditorSelection} from '../utils'
|
|
10
10
|
import {converterTextPlain} from './converter.text-plain'
|
|
11
|
-
import {coreConverters} from './converters'
|
|
11
|
+
import {coreConverters} from './converters.core'
|
|
12
12
|
|
|
13
13
|
const b1: PortableTextTextBlock = {
|
|
14
14
|
_type: 'block',
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {htmlToBlocks} from '@portabletext/block-tools'
|
|
2
2
|
import {isPortableTextTextBlock, type PortableTextBlock} from '@sanity/types'
|
|
3
3
|
import {sliceBlocks} from '../utils'
|
|
4
|
-
import
|
|
4
|
+
import {defineConverter} from './converter.types'
|
|
5
5
|
|
|
6
|
-
export const converterTextPlain
|
|
6
|
+
export const converterTextPlain = defineConverter({
|
|
7
7
|
mimeType: 'text/plain',
|
|
8
8
|
serialize: ({context, event}) => {
|
|
9
9
|
if (!context.selection) {
|
|
@@ -73,7 +73,7 @@ export const converterTextPlain: Converter<'text/plain'> = {
|
|
|
73
73
|
mimeType: 'text/plain',
|
|
74
74
|
}
|
|
75
75
|
},
|
|
76
|
-
}
|
|
76
|
+
})
|
|
77
77
|
|
|
78
78
|
const entityMap: Record<string, string> = {
|
|
79
79
|
'&': '&',
|
|
@@ -9,6 +9,12 @@ export type Converter<TMIMEType extends MIMEType = MIMEType> = {
|
|
|
9
9
|
deserialize: Deserializer<TMIMEType>
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
export function defineConverter<TMIMEType extends MIMEType>(
|
|
13
|
+
converter: Converter<TMIMEType>,
|
|
14
|
+
): Converter<TMIMEType> {
|
|
15
|
+
return converter
|
|
16
|
+
}
|
|
17
|
+
|
|
12
18
|
export type ConverterEvent<TMIMEType extends MIMEType = MIMEType> =
|
|
13
19
|
| {
|
|
14
20
|
type: 'serialize'
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
type Snapshot,
|
|
13
13
|
} from 'xstate'
|
|
14
14
|
import type {Behavior, CustomBehaviorEvent} from '../behaviors/behavior.types'
|
|
15
|
-
import {coreConverters} from '../converters/converters'
|
|
15
|
+
import {coreConverters} from '../converters/converters.core'
|
|
16
16
|
import {compileType} from '../internal-utils/schema'
|
|
17
17
|
import type {PickFromUnion} from '../type-utils'
|
|
18
18
|
import type {EditableAPI} from '../types/editor'
|
|
@@ -66,6 +66,8 @@ export type EditorEvent =
|
|
|
66
66
|
| 'annotation.add'
|
|
67
67
|
| 'annotation.remove'
|
|
68
68
|
| 'annotation.toggle'
|
|
69
|
+
| 'block.set'
|
|
70
|
+
| 'block.unset'
|
|
69
71
|
| 'blur'
|
|
70
72
|
| 'data transfer.set'
|
|
71
73
|
| 'decorator.add'
|
|
@@ -76,6 +78,7 @@ export type EditorEvent =
|
|
|
76
78
|
| 'deserialization.failure'
|
|
77
79
|
| 'deserialization.success'
|
|
78
80
|
| 'focus'
|
|
81
|
+
| 'insert.block'
|
|
79
82
|
| 'insert.block object'
|
|
80
83
|
| 'insert.inline object'
|
|
81
84
|
| 'insert.span'
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
type NativeBehaviorEvent,
|
|
21
21
|
type SyntheticBehaviorEvent,
|
|
22
22
|
} from '../behaviors/behavior.types'
|
|
23
|
-
import type {Converter} from '../converters/converter'
|
|
23
|
+
import type {Converter} from '../converters/converter.types'
|
|
24
24
|
import type {OmitFromUnion, PickFromUnion} from '../type-utils'
|
|
25
25
|
import type {
|
|
26
26
|
EditorSelection,
|
|
@@ -172,7 +172,6 @@ export type InternalEditorEmittedEvent =
|
|
|
172
172
|
description: string
|
|
173
173
|
data: unknown
|
|
174
174
|
}
|
|
175
|
-
| {type: 'select'; selection: EditorSelection}
|
|
176
175
|
| {type: 'selection'; selection: EditorSelection}
|
|
177
176
|
| {type: 'blurred'; event: FocusEvent<HTMLDivElement, Element>}
|
|
178
177
|
| {type: 'focused'; event: FocusEvent<HTMLDivElement, Element>}
|
|
@@ -186,6 +185,8 @@ export type InternalEditorEmittedEvent =
|
|
|
186
185
|
| 'annotation.add'
|
|
187
186
|
| 'annotation.remove'
|
|
188
187
|
| 'annotation.toggle'
|
|
188
|
+
| 'block.set'
|
|
189
|
+
| 'block.unset'
|
|
189
190
|
| 'blur'
|
|
190
191
|
| 'data transfer.set'
|
|
191
192
|
| 'decorator.add'
|
|
@@ -198,6 +199,7 @@ export type InternalEditorEmittedEvent =
|
|
|
198
199
|
| 'deserialization.failure'
|
|
199
200
|
| 'deserialization.success'
|
|
200
201
|
| 'focus'
|
|
202
|
+
| 'insert.block'
|
|
201
203
|
| 'insert.block object'
|
|
202
204
|
| 'insert.inline object'
|
|
203
205
|
| 'insert.span'
|
|
@@ -208,6 +210,7 @@ export type InternalEditorEmittedEvent =
|
|
|
208
210
|
| 'move.block'
|
|
209
211
|
| 'move.block down'
|
|
210
212
|
| 'move.block up'
|
|
213
|
+
| 'select'
|
|
211
214
|
| 'select.next block'
|
|
212
215
|
| 'select.previous block'
|
|
213
216
|
| 'serialization.failure'
|
|
@@ -596,6 +599,9 @@ export const editorMachine = setup({
|
|
|
596
599
|
'annotation.*': {
|
|
597
600
|
actions: emit(({event}) => event),
|
|
598
601
|
},
|
|
602
|
+
'block.*': {
|
|
603
|
+
actions: emit(({event}) => event),
|
|
604
|
+
},
|
|
599
605
|
'blur': {
|
|
600
606
|
actions: emit(({event}) => event),
|
|
601
607
|
},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type {PortableTextBlock} from '@sanity/types'
|
|
2
|
-
import type {Converter} from '../converters/converter'
|
|
2
|
+
import type {Converter} from '../converters/converter.types'
|
|
3
3
|
import {toPortableTextRange} from '../internal-utils/ranges'
|
|
4
4
|
import {fromSlateValue} from '../internal-utils/values'
|
|
5
5
|
import {KEY_TO_VALUE_ELEMENT} from '../internal-utils/weakMaps'
|
|
@@ -461,11 +461,11 @@ describe('plugin:withPortableTextMarksModel', () => {
|
|
|
461
461
|
style: 'normal',
|
|
462
462
|
},
|
|
463
463
|
{
|
|
464
|
-
_key: '
|
|
464
|
+
_key: '5',
|
|
465
465
|
_type: 'myTestBlockType',
|
|
466
466
|
children: [
|
|
467
467
|
{
|
|
468
|
-
_key: '
|
|
468
|
+
_key: '3',
|
|
469
469
|
_type: 'span',
|
|
470
470
|
marks: [],
|
|
471
471
|
text: '',
|
|
@@ -28,6 +28,8 @@ export function createWithEventListeners(
|
|
|
28
28
|
case 'annotation.add':
|
|
29
29
|
case 'annotation.remove':
|
|
30
30
|
case 'annotation.toggle':
|
|
31
|
+
case 'block.set':
|
|
32
|
+
case 'block.unset':
|
|
31
33
|
case 'blur':
|
|
32
34
|
case 'data transfer.set':
|
|
33
35
|
case 'decorator.add':
|
|
@@ -40,6 +42,7 @@ export function createWithEventListeners(
|
|
|
40
42
|
case 'deserialization.failure':
|
|
41
43
|
case 'deserialization.success':
|
|
42
44
|
case 'focus':
|
|
45
|
+
case 'insert.block':
|
|
43
46
|
case 'insert.block object':
|
|
44
47
|
case 'insert.inline object':
|
|
45
48
|
case 'insert.span':
|
package/src/selectors/index.ts
CHANGED
|
@@ -17,6 +17,8 @@ export {isActiveAnnotation} from './selector.is-active-annotation'
|
|
|
17
17
|
export {isActiveDecorator} from './selector.is-active-decorator'
|
|
18
18
|
export {isActiveListItem} from './selector.is-active-list-item'
|
|
19
19
|
export {isActiveStyle} from './selector.is-active-style'
|
|
20
|
+
export {isAtTheEndOfBlock} from './selector.is-at-the-end-of-block'
|
|
21
|
+
export {isAtTheStartOfBlock} from './selector.is-at-the-start-of-block'
|
|
20
22
|
export {isPointAfterSelection} from './selector.is-point-after-selection'
|
|
21
23
|
export {isPointBeforeSelection} from './selector.is-point-before-selection'
|
|
22
24
|
export {isSelectionCollapsed} from './selector.is-selection-collapsed'
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type {KeyedSegment, PortableTextBlock} from '@sanity/types'
|
|
2
|
+
import type {EditorSelector} from '../editor/editor-selector'
|
|
3
|
+
import * as utils from '../utils'
|
|
4
|
+
import {isSelectionCollapsed} from './selector.is-selection-collapsed'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @public
|
|
8
|
+
*/
|
|
9
|
+
export function isAtTheEndOfBlock(block: {
|
|
10
|
+
node: PortableTextBlock
|
|
11
|
+
path: [KeyedSegment]
|
|
12
|
+
}): EditorSelector<boolean> {
|
|
13
|
+
return ({context}) => {
|
|
14
|
+
if (!context.selection || !isSelectionCollapsed({context})) {
|
|
15
|
+
return false
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const blockEndPoint = utils.getBlockEndPoint(block)
|
|
19
|
+
|
|
20
|
+
return utils.isEqualSelectionPoints(context.selection.focus, blockEndPoint)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type {KeyedSegment, PortableTextBlock} from '@sanity/types'
|
|
2
|
+
import type {EditorSelector} from '../editor/editor-selector'
|
|
3
|
+
import * as utils from '../utils'
|
|
4
|
+
import {isSelectionCollapsed} from './selector.is-selection-collapsed'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @public
|
|
8
|
+
*/
|
|
9
|
+
export function isAtTheStartOfBlock(block: {
|
|
10
|
+
node: PortableTextBlock
|
|
11
|
+
path: [KeyedSegment]
|
|
12
|
+
}): EditorSelector<boolean> {
|
|
13
|
+
return ({context}) => {
|
|
14
|
+
if (!context.selection || !isSelectionCollapsed({context})) {
|
|
15
|
+
return false
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const blockStartPoint = utils.getBlockStartPoint(block)
|
|
19
|
+
|
|
20
|
+
return utils.isEqualSelectionPoints(
|
|
21
|
+
context.selection.focus,
|
|
22
|
+
blockStartPoint,
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -4,9 +4,13 @@ import type {EditorSelector} from '../editor/editor-selector'
|
|
|
4
4
|
* @public
|
|
5
5
|
*/
|
|
6
6
|
export const isSelectionCollapsed: EditorSelector<boolean> = ({context}) => {
|
|
7
|
+
if (!context.selection) {
|
|
8
|
+
return false
|
|
9
|
+
}
|
|
10
|
+
|
|
7
11
|
return (
|
|
8
|
-
JSON.stringify(context.selection
|
|
9
|
-
JSON.stringify(context.selection
|
|
12
|
+
JSON.stringify(context.selection.anchor.path) ===
|
|
13
|
+
JSON.stringify(context.selection.focus.path) &&
|
|
10
14
|
context.selection?.anchor.offset === context.selection?.focus.offset
|
|
11
15
|
)
|
|
12
16
|
}
|
package/src/utils/index.ts
CHANGED
|
@@ -4,9 +4,11 @@ export {
|
|
|
4
4
|
blockOffsetToSpanSelectionPoint,
|
|
5
5
|
spanSelectionPointToBlockOffset,
|
|
6
6
|
} from './util.block-offset'
|
|
7
|
+
export {getBlockEndPoint} from './util.get-block-end-point'
|
|
7
8
|
export {getBlockStartPoint} from './util.get-block-start-point'
|
|
8
9
|
export {getTextBlockText} from './util.get-text-block-text'
|
|
9
10
|
export {isEmptyTextBlock} from './util.is-empty-text-block'
|
|
11
|
+
export {isEqualSelectionPoints} from './util.is-equal-selection-points'
|
|
10
12
|
export {isKeyedSegment} from './util.is-keyed-segment'
|
|
11
13
|
export {reverseSelection} from './util.reverse-selection'
|
|
12
14
|
export {sliceBlocks} from './util.slice-blocks'
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isPortableTextSpan,
|
|
3
|
+
isPortableTextTextBlock,
|
|
4
|
+
type KeyedSegment,
|
|
5
|
+
type PortableTextBlock,
|
|
6
|
+
} from '@sanity/types'
|
|
7
|
+
import type {EditorSelectionPoint} from '../types/editor'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @public
|
|
11
|
+
*/
|
|
12
|
+
export function getBlockEndPoint({
|
|
13
|
+
node,
|
|
14
|
+
path,
|
|
15
|
+
}: {
|
|
16
|
+
node: PortableTextBlock
|
|
17
|
+
path: [KeyedSegment]
|
|
18
|
+
}): EditorSelectionPoint {
|
|
19
|
+
if (isPortableTextTextBlock(node)) {
|
|
20
|
+
const lastChild = node.children[node.children.length - 1]
|
|
21
|
+
|
|
22
|
+
if (lastChild) {
|
|
23
|
+
return {
|
|
24
|
+
path: [...path, 'children', {_key: lastChild._key}],
|
|
25
|
+
offset: isPortableTextSpan(lastChild) ? lastChild.text.length : 0,
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
path,
|
|
32
|
+
offset: 0,
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type {EditorSelectionPoint} from '../types/editor'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @public
|
|
5
|
+
*/
|
|
6
|
+
export function isEqualSelectionPoints(
|
|
7
|
+
a: EditorSelectionPoint,
|
|
8
|
+
b: EditorSelectionPoint,
|
|
9
|
+
) {
|
|
10
|
+
return (
|
|
11
|
+
a.offset === b.offset && JSON.stringify(a.path) === JSON.stringify(b.path)
|
|
12
|
+
)
|
|
13
|
+
}
|