@portabletext/editor 1.49.0 → 1.49.2
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/editor-provider.cjs +22 -11
- package/lib/_chunks-cjs/editor-provider.cjs.map +1 -1
- package/lib/_chunks-cjs/util.merge-text-blocks.cjs +2 -1
- package/lib/_chunks-cjs/util.merge-text-blocks.cjs.map +1 -1
- package/lib/_chunks-cjs/util.slice-blocks.cjs +26 -8
- package/lib/_chunks-cjs/util.slice-blocks.cjs.map +1 -1
- package/lib/_chunks-es/editor-provider.js +22 -11
- package/lib/_chunks-es/editor-provider.js.map +1 -1
- package/lib/_chunks-es/util.merge-text-blocks.js +2 -1
- package/lib/_chunks-es/util.merge-text-blocks.js.map +1 -1
- package/lib/_chunks-es/util.slice-blocks.js +26 -8
- package/lib/_chunks-es/util.slice-blocks.js.map +1 -1
- package/lib/index.cjs +155 -158
- package/lib/index.cjs.map +1 -1
- package/lib/index.js +160 -163
- package/lib/index.js.map +1 -1
- package/package.json +6 -6
- package/src/behaviors/behavior.abstract.split.ts +2 -2
- package/src/converters/converter.portable-text.ts +1 -0
- package/src/converters/converter.text-html.ts +1 -0
- package/src/converters/converter.text-plain.ts +1 -0
- package/src/editor/Editable.tsx +20 -48
- package/src/editor/__tests__/PortableTextEditor.test.tsx +3 -3
- package/src/editor/__tests__/RangeDecorations.test.tsx +2 -2
- package/src/editor/components/render-block-object.tsx +0 -1
- package/src/editor/components/render-element.tsx +3 -3
- package/src/editor/components/render-inline-object.tsx +9 -12
- package/src/editor/components/render-leaf.tsx +64 -0
- package/src/editor/components/render-span.tsx +260 -0
- package/src/editor/components/render-text-block.tsx +0 -1
- package/src/editor/components/render-text.tsx +18 -0
- package/src/internal-utils/parse-blocks.test.ts +20 -20
- package/src/internal-utils/parse-blocks.ts +57 -20
- package/src/operations/behavior.operation.annotation.add.ts +1 -1
- package/src/operations/behavior.operation.block.set.ts +1 -1
- package/src/operations/behavior.operation.block.unset.ts +2 -2
- package/src/operations/behavior.operation.insert-inline-object.ts +1 -1
- package/src/operations/behavior.operation.insert.block.ts +1 -1
- package/src/selectors/selector.get-trimmed-selection.test.ts +1 -0
- package/src/utils/util.merge-text-blocks.ts +1 -1
- package/src/editor/components/Leaf.tsx +0 -336
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@portabletext/editor",
|
|
3
|
-
"version": "1.49.
|
|
3
|
+
"version": "1.49.2",
|
|
4
4
|
"description": "Portable Text Editor made in React",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -79,15 +79,15 @@
|
|
|
79
79
|
"slate-react": "0.114.2",
|
|
80
80
|
"use-effect-event": "^1.0.2",
|
|
81
81
|
"xstate": "^5.19.2",
|
|
82
|
-
"@portabletext/block-tools": "1.1.
|
|
82
|
+
"@portabletext/block-tools": "1.1.24",
|
|
83
83
|
"@portabletext/patches": "1.1.3"
|
|
84
84
|
},
|
|
85
85
|
"devDependencies": {
|
|
86
86
|
"@portabletext/toolkit": "^2.0.17",
|
|
87
87
|
"@sanity/diff-match-patch": "^3.2.0",
|
|
88
88
|
"@sanity/pkg-utils": "^7.2.2",
|
|
89
|
-
"@sanity/schema": "^3.
|
|
90
|
-
"@sanity/types": "^3.
|
|
89
|
+
"@sanity/schema": "^3.88.0",
|
|
90
|
+
"@sanity/types": "^3.88.0",
|
|
91
91
|
"@testing-library/jest-dom": "^6.6.3",
|
|
92
92
|
"@testing-library/react": "^16.3.0",
|
|
93
93
|
"@types/debug": "^4.1.12",
|
|
@@ -114,8 +114,8 @@
|
|
|
114
114
|
"racejar": "1.2.4"
|
|
115
115
|
},
|
|
116
116
|
"peerDependencies": {
|
|
117
|
-
"@sanity/schema": "^3.
|
|
118
|
-
"@sanity/types": "^3.
|
|
117
|
+
"@sanity/schema": "^3.88.0",
|
|
118
|
+
"@sanity/types": "^3.88.0",
|
|
119
119
|
"react": "^16.9 || ^17 || ^18 || ^19",
|
|
120
120
|
"rxjs": "^7.8.2"
|
|
121
121
|
},
|
|
@@ -49,7 +49,7 @@ export const abstractSplitBehaviors = [
|
|
|
49
49
|
blocks: [focusTextBlock.node],
|
|
50
50
|
}).at(0),
|
|
51
51
|
context: snapshot.context,
|
|
52
|
-
options: {refreshKeys: true},
|
|
52
|
+
options: {refreshKeys: true, validateFields: true},
|
|
53
53
|
})
|
|
54
54
|
|
|
55
55
|
if (!newTextBlock || !isTextBlock(snapshot.context, newTextBlock)) {
|
|
@@ -84,7 +84,7 @@ export const abstractSplitBehaviors = [
|
|
|
84
84
|
children: [],
|
|
85
85
|
},
|
|
86
86
|
context: snapshot.context,
|
|
87
|
-
options: {refreshKeys: true},
|
|
87
|
+
options: {refreshKeys: true, validateFields: true},
|
|
88
88
|
})
|
|
89
89
|
|
|
90
90
|
if (!newTextBlock) {
|
package/src/editor/Editable.tsx
CHANGED
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
useRef,
|
|
11
11
|
useState,
|
|
12
12
|
type ClipboardEvent,
|
|
13
|
-
type CSSProperties,
|
|
14
13
|
type FocusEventHandler,
|
|
15
14
|
type KeyboardEvent,
|
|
16
15
|
type MutableRefObject,
|
|
@@ -53,8 +52,9 @@ import type {
|
|
|
53
52
|
import type {HotkeyOptions} from '../types/options'
|
|
54
53
|
import {isSelectionCollapsed} from '../utils'
|
|
55
54
|
import {getSelectionEndPoint} from '../utils/util.get-selection-end-point'
|
|
56
|
-
import {Leaf} from './components/Leaf'
|
|
57
55
|
import {RenderElement} from './components/render-element'
|
|
56
|
+
import {RenderLeaf} from './components/render-leaf'
|
|
57
|
+
import {RenderText, type RenderTextProps} from './components/render-text'
|
|
58
58
|
import {EditorActorContext} from './editor-actor-context'
|
|
59
59
|
import {getEditorSnapshot} from './editor-selector'
|
|
60
60
|
import {usePortableTextEditor} from './hooks/usePortableTextEditor'
|
|
@@ -67,14 +67,6 @@ import {
|
|
|
67
67
|
|
|
68
68
|
const debug = debugWithName('component:Editable')
|
|
69
69
|
|
|
70
|
-
const PLACEHOLDER_STYLE: CSSProperties = {
|
|
71
|
-
position: 'absolute',
|
|
72
|
-
userSelect: 'none',
|
|
73
|
-
pointerEvents: 'none',
|
|
74
|
-
left: 0,
|
|
75
|
-
right: 0,
|
|
76
|
-
}
|
|
77
|
-
|
|
78
70
|
/**
|
|
79
71
|
* @public
|
|
80
72
|
*/
|
|
@@ -245,47 +237,20 @@ export const PortableTextEditable = forwardRef<
|
|
|
245
237
|
|
|
246
238
|
const renderLeaf = useCallback(
|
|
247
239
|
(
|
|
248
|
-
|
|
240
|
+
leafProps: RenderLeafProps & {
|
|
249
241
|
leaf: Text & {placeholder?: boolean; rangeDecoration?: RangeDecoration}
|
|
250
242
|
},
|
|
251
|
-
) =>
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
readOnly={readOnly}
|
|
262
|
-
/>
|
|
263
|
-
)
|
|
264
|
-
if (
|
|
265
|
-
renderPlaceholder &&
|
|
266
|
-
lProps.leaf.placeholder &&
|
|
267
|
-
lProps.text.text === ''
|
|
268
|
-
) {
|
|
269
|
-
return (
|
|
270
|
-
<>
|
|
271
|
-
<span style={PLACEHOLDER_STYLE} contentEditable={false}>
|
|
272
|
-
{renderPlaceholder()}
|
|
273
|
-
</span>
|
|
274
|
-
{rendered}
|
|
275
|
-
</>
|
|
276
|
-
)
|
|
277
|
-
}
|
|
278
|
-
const decoration = lProps.leaf.rangeDecoration
|
|
279
|
-
if (decoration) {
|
|
280
|
-
rendered = decoration.component({children: rendered})
|
|
281
|
-
}
|
|
282
|
-
return rendered
|
|
283
|
-
}
|
|
284
|
-
return lProps.children
|
|
285
|
-
},
|
|
243
|
+
) => (
|
|
244
|
+
<RenderLeaf
|
|
245
|
+
{...leafProps}
|
|
246
|
+
readOnly={readOnly}
|
|
247
|
+
renderAnnotation={renderAnnotation}
|
|
248
|
+
renderChild={renderChild}
|
|
249
|
+
renderDecorator={renderDecorator}
|
|
250
|
+
renderPlaceholder={renderPlaceholder}
|
|
251
|
+
/>
|
|
252
|
+
),
|
|
286
253
|
[
|
|
287
|
-
editorActor,
|
|
288
|
-
portableTextEditor,
|
|
289
254
|
readOnly,
|
|
290
255
|
renderAnnotation,
|
|
291
256
|
renderChild,
|
|
@@ -294,6 +259,11 @@ export const PortableTextEditable = forwardRef<
|
|
|
294
259
|
],
|
|
295
260
|
)
|
|
296
261
|
|
|
262
|
+
const renderText = useCallback(
|
|
263
|
+
(props: RenderTextProps) => <RenderText {...props} />,
|
|
264
|
+
[],
|
|
265
|
+
)
|
|
266
|
+
|
|
297
267
|
const restoreSelectionFromProps = useCallback(() => {
|
|
298
268
|
if (propsSelection) {
|
|
299
269
|
debug(`Selection from props ${JSON.stringify(propsSelection)}`)
|
|
@@ -510,6 +480,7 @@ export const PortableTextEditable = forwardRef<
|
|
|
510
480
|
blocks: result.insert,
|
|
511
481
|
options: {
|
|
512
482
|
refreshKeys: true,
|
|
483
|
+
validateFields: true,
|
|
513
484
|
},
|
|
514
485
|
}),
|
|
515
486
|
placement: 'auto',
|
|
@@ -1225,6 +1196,7 @@ export const PortableTextEditable = forwardRef<
|
|
|
1225
1196
|
renderPlaceholder={undefined}
|
|
1226
1197
|
renderElement={renderElement}
|
|
1227
1198
|
renderLeaf={renderLeaf}
|
|
1199
|
+
renderText={renderText}
|
|
1228
1200
|
scrollSelectionIntoView={scrollSelectionIntoViewToSlate}
|
|
1229
1201
|
/>
|
|
1230
1202
|
)
|
|
@@ -59,6 +59,9 @@ describe('initialization', () => {
|
|
|
59
59
|
>
|
|
60
60
|
<div>
|
|
61
61
|
<span
|
|
62
|
+
data-child-key="k1"
|
|
63
|
+
data-child-name="span"
|
|
64
|
+
data-child-type="span"
|
|
62
65
|
data-slate-node="text"
|
|
63
66
|
>
|
|
64
67
|
<span
|
|
@@ -68,9 +71,6 @@ describe('initialization', () => {
|
|
|
68
71
|
Jot something down here
|
|
69
72
|
</span>
|
|
70
73
|
<span
|
|
71
|
-
data-child-key="k1"
|
|
72
|
-
data-child-name="span"
|
|
73
|
-
data-child-type="span"
|
|
74
74
|
data-slate-leaf="true"
|
|
75
75
|
>
|
|
76
76
|
<span
|
|
@@ -125,7 +125,7 @@ describe('RangeDecorations', () => {
|
|
|
125
125
|
)
|
|
126
126
|
await waitFor(() => {
|
|
127
127
|
expect([rangeDecorationIteration, 'updated-with-different']).toEqual([
|
|
128
|
-
|
|
128
|
+
2,
|
|
129
129
|
'updated-with-different',
|
|
130
130
|
])
|
|
131
131
|
})
|
|
@@ -152,7 +152,7 @@ describe('RangeDecorations', () => {
|
|
|
152
152
|
)
|
|
153
153
|
await waitFor(() => {
|
|
154
154
|
expect([rangeDecorationIteration, 'updated-with-different']).toEqual([
|
|
155
|
-
|
|
155
|
+
3,
|
|
156
156
|
'updated-with-different',
|
|
157
157
|
])
|
|
158
158
|
})
|
|
@@ -40,7 +40,7 @@ export function RenderElement(props: {
|
|
|
40
40
|
keyGenerator: () => '',
|
|
41
41
|
schema,
|
|
42
42
|
},
|
|
43
|
-
options: {refreshKeys: false},
|
|
43
|
+
options: {refreshKeys: false, validateFields: false},
|
|
44
44
|
inlineObject: {
|
|
45
45
|
_key: props.element._key,
|
|
46
46
|
_type: props.element._type,
|
|
@@ -79,7 +79,7 @@ export function RenderElement(props: {
|
|
|
79
79
|
keyGenerator: () => '',
|
|
80
80
|
schema,
|
|
81
81
|
},
|
|
82
|
-
options: {refreshKeys: false},
|
|
82
|
+
options: {refreshKeys: false, validateFields: false},
|
|
83
83
|
block: props.element,
|
|
84
84
|
})
|
|
85
85
|
|
|
@@ -105,7 +105,7 @@ export function RenderElement(props: {
|
|
|
105
105
|
keyGenerator: () => '',
|
|
106
106
|
schema,
|
|
107
107
|
},
|
|
108
|
-
options: {refreshKeys: false},
|
|
108
|
+
options: {refreshKeys: false, validateFields: false},
|
|
109
109
|
blockObject: {
|
|
110
110
|
_key: props.element._key,
|
|
111
111
|
_type: props.element._type,
|
|
@@ -57,19 +57,16 @@ export function RenderInlineObject(props: {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
return (
|
|
60
|
-
<span
|
|
60
|
+
<span
|
|
61
|
+
{...props.attributes}
|
|
62
|
+
draggable={!props.readOnly}
|
|
63
|
+
className="pt-inline-object"
|
|
64
|
+
data-child-key={props.inlineObject._key}
|
|
65
|
+
data-child-name={props.inlineObject._type}
|
|
66
|
+
data-child-type="object"
|
|
67
|
+
>
|
|
61
68
|
{props.children}
|
|
62
|
-
<span
|
|
63
|
-
draggable={!props.readOnly}
|
|
64
|
-
className="pt-inline-object"
|
|
65
|
-
data-testid="pt-inline-object"
|
|
66
|
-
ref={inlineObjectRef}
|
|
67
|
-
key={props.element._key}
|
|
68
|
-
style={{display: 'inline-block'}}
|
|
69
|
-
data-child-key={props.inlineObject._key}
|
|
70
|
-
data-child-name={props.inlineObject._type}
|
|
71
|
-
data-child-type="object"
|
|
72
|
-
>
|
|
69
|
+
<span ref={inlineObjectRef} style={{display: 'inline-block'}}>
|
|
73
70
|
{props.renderChild && block && legacySchemaType ? (
|
|
74
71
|
props.renderChild({
|
|
75
72
|
annotations: [],
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import {useSelector} from '@xstate/react'
|
|
2
|
+
import {useContext, type CSSProperties} from 'react'
|
|
3
|
+
import type {Text} from 'slate'
|
|
4
|
+
import type {RenderLeafProps} from 'slate-react'
|
|
5
|
+
import type {
|
|
6
|
+
RangeDecoration,
|
|
7
|
+
RenderAnnotationFunction,
|
|
8
|
+
RenderChildFunction,
|
|
9
|
+
RenderDecoratorFunction,
|
|
10
|
+
RenderPlaceholderFunction,
|
|
11
|
+
} from '../../types/editor'
|
|
12
|
+
import {EditorActorContext} from '../editor-actor-context'
|
|
13
|
+
import {RenderSpan} from './render-span'
|
|
14
|
+
|
|
15
|
+
const PLACEHOLDER_STYLE: CSSProperties = {
|
|
16
|
+
position: 'absolute',
|
|
17
|
+
userSelect: 'none',
|
|
18
|
+
pointerEvents: 'none',
|
|
19
|
+
left: 0,
|
|
20
|
+
right: 0,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function RenderLeaf(
|
|
24
|
+
props: RenderLeafProps & {
|
|
25
|
+
leaf: Text & {placeholder?: boolean; rangeDecoration?: RangeDecoration}
|
|
26
|
+
readOnly: boolean
|
|
27
|
+
renderAnnotation?: RenderAnnotationFunction
|
|
28
|
+
renderChild?: RenderChildFunction
|
|
29
|
+
renderDecorator?: RenderDecoratorFunction
|
|
30
|
+
renderPlaceholder?: RenderPlaceholderFunction
|
|
31
|
+
},
|
|
32
|
+
) {
|
|
33
|
+
const editorActor = useContext(EditorActorContext)
|
|
34
|
+
const schema = useSelector(editorActor, (s) => s.context.schema)
|
|
35
|
+
|
|
36
|
+
if (props.leaf._type !== schema.span.name) {
|
|
37
|
+
return props.children
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let renderedSpan = <RenderSpan {...props} />
|
|
41
|
+
|
|
42
|
+
if (
|
|
43
|
+
props.renderPlaceholder &&
|
|
44
|
+
props.leaf.placeholder &&
|
|
45
|
+
props.text.text === ''
|
|
46
|
+
) {
|
|
47
|
+
return (
|
|
48
|
+
<>
|
|
49
|
+
<span style={PLACEHOLDER_STYLE} contentEditable={false}>
|
|
50
|
+
{props.renderPlaceholder()}
|
|
51
|
+
</span>
|
|
52
|
+
{renderedSpan}
|
|
53
|
+
</>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const rangeDecoration = props.leaf.rangeDecoration
|
|
58
|
+
|
|
59
|
+
if (rangeDecoration) {
|
|
60
|
+
renderedSpan = rangeDecoration.component({children: renderedSpan})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return renderedSpan
|
|
64
|
+
}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import {useSelector} from '@xstate/react'
|
|
2
|
+
import {isEqual, uniq} from 'lodash'
|
|
3
|
+
import {
|
|
4
|
+
startTransition,
|
|
5
|
+
useCallback,
|
|
6
|
+
useContext,
|
|
7
|
+
useEffect,
|
|
8
|
+
useMemo,
|
|
9
|
+
useRef,
|
|
10
|
+
useState,
|
|
11
|
+
type ReactElement,
|
|
12
|
+
} from 'react'
|
|
13
|
+
import {useSelected, useSlateStatic, type RenderLeafProps} from 'slate-react'
|
|
14
|
+
import type {
|
|
15
|
+
RenderAnnotationFunction,
|
|
16
|
+
RenderChildFunction,
|
|
17
|
+
RenderDecoratorFunction,
|
|
18
|
+
} from '../../types/editor'
|
|
19
|
+
import {EditorActorContext} from '../editor-actor-context'
|
|
20
|
+
import {usePortableTextEditor} from '../hooks/usePortableTextEditor'
|
|
21
|
+
import {PortableTextEditor} from '../PortableTextEditor'
|
|
22
|
+
|
|
23
|
+
export interface RenderSpanProps extends RenderLeafProps {
|
|
24
|
+
children: ReactElement<any>
|
|
25
|
+
renderAnnotation?: RenderAnnotationFunction
|
|
26
|
+
renderChild?: RenderChildFunction
|
|
27
|
+
renderDecorator?: RenderDecoratorFunction
|
|
28
|
+
readOnly: boolean
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function RenderSpan(props: RenderSpanProps) {
|
|
32
|
+
const slateEditor = useSlateStatic()
|
|
33
|
+
const editorActor = useContext(EditorActorContext)
|
|
34
|
+
const legacySchema = useSelector(editorActor, (s) =>
|
|
35
|
+
s.context.getLegacySchema(),
|
|
36
|
+
)
|
|
37
|
+
const spanRef = useRef<HTMLElement>(null)
|
|
38
|
+
const portableTextEditor = usePortableTextEditor()
|
|
39
|
+
const blockSelected = useSelected()
|
|
40
|
+
const [focused, setFocused] = useState(false)
|
|
41
|
+
const [selected, setSelected] = useState(false)
|
|
42
|
+
|
|
43
|
+
const parent = props.children.props.parent
|
|
44
|
+
const block = parent && slateEditor.isTextBlock(parent) ? parent : undefined
|
|
45
|
+
|
|
46
|
+
const path = useMemo(
|
|
47
|
+
() =>
|
|
48
|
+
block
|
|
49
|
+
? [{_key: block._key}, 'children', {_key: props.leaf._key}]
|
|
50
|
+
: undefined,
|
|
51
|
+
[block, props.leaf._key],
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
const decoratorSchemaTypes = editorActor
|
|
55
|
+
.getSnapshot()
|
|
56
|
+
.context.schema.decorators.map((decorator) => decorator.name)
|
|
57
|
+
|
|
58
|
+
const decorators = uniq(
|
|
59
|
+
(props.leaf.marks ?? []).filter((mark) =>
|
|
60
|
+
decoratorSchemaTypes.includes(mark),
|
|
61
|
+
),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
const annotationMarkDefs = (props.leaf.marks ?? []).flatMap((mark) => {
|
|
65
|
+
if (decoratorSchemaTypes.includes(mark)) {
|
|
66
|
+
return []
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const markDef = block?.markDefs?.find((markDef) => markDef._key === mark)
|
|
70
|
+
|
|
71
|
+
if (markDef) {
|
|
72
|
+
return [markDef]
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return []
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
const shouldTrackSelectionAndFocus =
|
|
79
|
+
annotationMarkDefs.length > 0 && blockSelected
|
|
80
|
+
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (!shouldTrackSelectionAndFocus) {
|
|
83
|
+
setFocused(false)
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const sel = PortableTextEditor.getSelection(portableTextEditor)
|
|
88
|
+
|
|
89
|
+
if (
|
|
90
|
+
sel &&
|
|
91
|
+
isEqual(sel.focus.path, path) &&
|
|
92
|
+
PortableTextEditor.isCollapsedSelection(portableTextEditor)
|
|
93
|
+
) {
|
|
94
|
+
startTransition(() => {
|
|
95
|
+
setFocused(true)
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
}, [shouldTrackSelectionAndFocus, path, portableTextEditor])
|
|
99
|
+
|
|
100
|
+
// Function to check if this leaf is currently inside the user's text selection
|
|
101
|
+
const setSelectedFromRange = useCallback(() => {
|
|
102
|
+
if (!shouldTrackSelectionAndFocus) {
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const winSelection = window.getSelection()
|
|
107
|
+
|
|
108
|
+
if (!winSelection) {
|
|
109
|
+
setSelected(false)
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (winSelection && winSelection.rangeCount > 0) {
|
|
114
|
+
const range = winSelection.getRangeAt(0)
|
|
115
|
+
|
|
116
|
+
if (spanRef.current && range.intersectsNode(spanRef.current)) {
|
|
117
|
+
setSelected(true)
|
|
118
|
+
} else {
|
|
119
|
+
setSelected(false)
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
setSelected(false)
|
|
123
|
+
}
|
|
124
|
+
}, [shouldTrackSelectionAndFocus])
|
|
125
|
+
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
if (!shouldTrackSelectionAndFocus) {
|
|
128
|
+
return undefined
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const onBlur = editorActor.on('blurred', () => {
|
|
132
|
+
setFocused(false)
|
|
133
|
+
setSelected(false)
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
const onFocus = editorActor.on('focused', () => {
|
|
137
|
+
const sel = PortableTextEditor.getSelection(portableTextEditor)
|
|
138
|
+
|
|
139
|
+
if (
|
|
140
|
+
sel &&
|
|
141
|
+
isEqual(sel.focus.path, path) &&
|
|
142
|
+
PortableTextEditor.isCollapsedSelection(portableTextEditor)
|
|
143
|
+
) {
|
|
144
|
+
setFocused(true)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
setSelectedFromRange()
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
const onSelection = editorActor.on('selection', (event) => {
|
|
151
|
+
if (
|
|
152
|
+
event.selection &&
|
|
153
|
+
isEqual(event.selection.focus.path, path) &&
|
|
154
|
+
PortableTextEditor.isCollapsedSelection(portableTextEditor)
|
|
155
|
+
) {
|
|
156
|
+
setFocused(true)
|
|
157
|
+
} else {
|
|
158
|
+
setFocused(false)
|
|
159
|
+
}
|
|
160
|
+
setSelectedFromRange()
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
return () => {
|
|
164
|
+
onBlur.unsubscribe()
|
|
165
|
+
onFocus.unsubscribe()
|
|
166
|
+
onSelection.unsubscribe()
|
|
167
|
+
}
|
|
168
|
+
}, [
|
|
169
|
+
editorActor,
|
|
170
|
+
path,
|
|
171
|
+
portableTextEditor,
|
|
172
|
+
setSelectedFromRange,
|
|
173
|
+
shouldTrackSelectionAndFocus,
|
|
174
|
+
])
|
|
175
|
+
|
|
176
|
+
useEffect(() => setSelectedFromRange(), [setSelectedFromRange])
|
|
177
|
+
|
|
178
|
+
let children = props.children
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Support `renderDecorator` render function for each Decorator
|
|
182
|
+
*/
|
|
183
|
+
for (const mark of decorators) {
|
|
184
|
+
const legacyDecoratorSchemaType = legacySchema.decorators.find(
|
|
185
|
+
(dec) => dec.value === mark,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
if (path && legacyDecoratorSchemaType && props.renderDecorator) {
|
|
189
|
+
children = props.renderDecorator({
|
|
190
|
+
children: children,
|
|
191
|
+
editorElementRef: spanRef,
|
|
192
|
+
focused,
|
|
193
|
+
path,
|
|
194
|
+
selected,
|
|
195
|
+
schemaType: legacyDecoratorSchemaType,
|
|
196
|
+
value: mark,
|
|
197
|
+
type: legacyDecoratorSchemaType,
|
|
198
|
+
})
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Support `renderAnnotation` render function for each Annotation
|
|
204
|
+
*/
|
|
205
|
+
for (const annotationMarkDef of annotationMarkDefs) {
|
|
206
|
+
const legacyAnnotationSchemaType = legacySchema.annotations.find(
|
|
207
|
+
(t) => t.name === annotationMarkDef._type,
|
|
208
|
+
)
|
|
209
|
+
if (legacyAnnotationSchemaType) {
|
|
210
|
+
if (block && path && props.renderAnnotation) {
|
|
211
|
+
children = (
|
|
212
|
+
<span ref={spanRef}>
|
|
213
|
+
{props.renderAnnotation({
|
|
214
|
+
block,
|
|
215
|
+
children: children,
|
|
216
|
+
editorElementRef: spanRef,
|
|
217
|
+
focused,
|
|
218
|
+
path,
|
|
219
|
+
selected,
|
|
220
|
+
schemaType: legacyAnnotationSchemaType,
|
|
221
|
+
value: annotationMarkDef,
|
|
222
|
+
type: legacyAnnotationSchemaType,
|
|
223
|
+
})}
|
|
224
|
+
</span>
|
|
225
|
+
)
|
|
226
|
+
} else {
|
|
227
|
+
children = <span ref={spanRef}>{children}</span>
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Support `renderChild` render function for the Span itself
|
|
234
|
+
*/
|
|
235
|
+
if (block && path && props.renderChild) {
|
|
236
|
+
const child = block.children.find(
|
|
237
|
+
(_child) => _child._key === props.leaf._key,
|
|
238
|
+
) // Ensure object equality
|
|
239
|
+
|
|
240
|
+
if (child) {
|
|
241
|
+
children = props.renderChild({
|
|
242
|
+
annotations: annotationMarkDefs,
|
|
243
|
+
children: children,
|
|
244
|
+
editorElementRef: spanRef,
|
|
245
|
+
focused,
|
|
246
|
+
path,
|
|
247
|
+
schemaType: legacySchema.span,
|
|
248
|
+
selected,
|
|
249
|
+
value: child,
|
|
250
|
+
type: legacySchema.span,
|
|
251
|
+
})
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return (
|
|
256
|
+
<span {...props.attributes} ref={spanRef}>
|
|
257
|
+
{children}
|
|
258
|
+
</span>
|
|
259
|
+
)
|
|
260
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type {Editable} from 'slate-react'
|
|
2
|
+
|
|
3
|
+
export type RenderTextProps = Parameters<
|
|
4
|
+
NonNullable<React.ComponentProps<typeof Editable>['renderText']>
|
|
5
|
+
>[0]
|
|
6
|
+
|
|
7
|
+
export function RenderText(props: RenderTextProps) {
|
|
8
|
+
return (
|
|
9
|
+
<span
|
|
10
|
+
{...props.attributes}
|
|
11
|
+
data-child-key={props.text._key}
|
|
12
|
+
data-child-name={props.text._type}
|
|
13
|
+
data-child-type="span"
|
|
14
|
+
>
|
|
15
|
+
{props.children}
|
|
16
|
+
</span>
|
|
17
|
+
)
|
|
18
|
+
}
|