@portabletext/editor 1.6.0 → 1.7.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 +5 -0
- package/lib/index.d.mts +3317 -3488
- package/lib/index.d.ts +3317 -3488
- package/lib/index.esm.js +4337 -4089
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +4325 -4077
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +4337 -4089
- package/lib/index.mjs.map +1 -1
- package/package.json +10 -11
- package/src/editor/Editable.tsx +26 -28
- package/src/editor/PortableTextEditor.tsx +90 -66
- package/src/editor/behavior/behavior.action.insert-break.ts +12 -2
- package/src/editor/behavior/behavior.actions.ts +51 -11
- package/src/editor/behavior/behavior.types.ts +23 -0
- package/src/editor/components/Synchronizer.tsx +11 -4
- package/src/editor/create-slate-editor.tsx +67 -0
- package/src/editor/editor-machine.ts +58 -8
- package/src/editor/key-generator.ts +30 -1
- package/src/editor/plugins/create-with-event-listeners.ts +62 -1
- package/src/editor/plugins/createWithEditableAPI.ts +800 -728
- package/src/editor/plugins/createWithMaxBlocks.ts +7 -2
- package/src/editor/plugins/createWithPatches.ts +4 -4
- package/src/editor/plugins/createWithPlaceholderBlock.ts +8 -3
- package/src/editor/plugins/createWithPortableTextMarkModel.ts +3 -4
- package/src/editor/plugins/createWithUndoRedo.ts +6 -7
- package/src/editor/plugins/createWithUtils.ts +2 -8
- package/src/editor/plugins/{index.ts → with-plugins.ts} +22 -79
- package/src/editor/use-editor.ts +46 -14
- package/src/editor/withSyncRangeDecorations.ts +20 -0
- package/src/index.ts +9 -1
- package/src/types/editor.ts +0 -1
- package/src/types/options.ts +1 -3
- package/src/utils/__tests__/operationToPatches.test.ts +7 -14
- package/src/utils/__tests__/patchToOperations.test.ts +4 -7
- package/src/editor/components/SlateContainer.tsx +0 -79
- package/src/editor/hooks/usePortableTextReadOnly.ts +0 -20
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {createEditor, type Descendant} from 'slate'
|
|
2
|
+
import {withReact} from 'slate-react'
|
|
3
|
+
import type {PortableTextSlateEditor} from '../types/editor'
|
|
4
|
+
import {debugWithName} from '../utils/debug'
|
|
5
|
+
import {KEY_TO_SLATE_ELEMENT, KEY_TO_VALUE_ELEMENT} from '../utils/weakMaps'
|
|
6
|
+
import type {EditorActor} from './editor-machine'
|
|
7
|
+
import {withPlugins} from './plugins/with-plugins'
|
|
8
|
+
|
|
9
|
+
const debug = debugWithName('component:PortableTextEditor:SlateContainer')
|
|
10
|
+
|
|
11
|
+
type SlateEditorConfig = {
|
|
12
|
+
editorActor: EditorActor
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type SlateEditor = {
|
|
16
|
+
instance: PortableTextSlateEditor
|
|
17
|
+
initialValue: Array<Descendant>
|
|
18
|
+
destroy: () => void
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const slateEditors = new WeakMap<EditorActor, SlateEditor>()
|
|
22
|
+
|
|
23
|
+
export function createSlateEditor(config: SlateEditorConfig): SlateEditor {
|
|
24
|
+
const existingSlateEditor = slateEditors.get(config.editorActor)
|
|
25
|
+
|
|
26
|
+
if (existingSlateEditor) {
|
|
27
|
+
debug('Reusing existing Slate editor instance', config.editorActor.id)
|
|
28
|
+
return existingSlateEditor
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
debug('Creating new Slate editor instance', config.editorActor.id)
|
|
32
|
+
|
|
33
|
+
let unsubscriptions: Array<() => void> = []
|
|
34
|
+
let subscriptions: Array<() => () => void> = []
|
|
35
|
+
|
|
36
|
+
const instance = withPlugins(withReact(createEditor()), {
|
|
37
|
+
editorActor: config.editorActor,
|
|
38
|
+
subscriptions,
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
KEY_TO_VALUE_ELEMENT.set(instance, {})
|
|
42
|
+
KEY_TO_SLATE_ELEMENT.set(instance, {})
|
|
43
|
+
|
|
44
|
+
for (const subscription of subscriptions) {
|
|
45
|
+
unsubscriptions.push(subscription())
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const initialValue = [instance.pteCreateTextBlock({decorators: []})]
|
|
49
|
+
|
|
50
|
+
const slateEditor: SlateEditor = {
|
|
51
|
+
instance,
|
|
52
|
+
initialValue,
|
|
53
|
+
destroy: () => {
|
|
54
|
+
debug('Destroying Slate editor')
|
|
55
|
+
instance.destroy()
|
|
56
|
+
for (const unsubscribe of unsubscriptions) {
|
|
57
|
+
unsubscribe()
|
|
58
|
+
}
|
|
59
|
+
subscriptions = []
|
|
60
|
+
unsubscriptions = []
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
slateEditors.set(config.editorActor, slateEditor)
|
|
65
|
+
|
|
66
|
+
return slateEditor
|
|
67
|
+
}
|
|
@@ -28,8 +28,11 @@ import type {
|
|
|
28
28
|
BehaviorActionIntend,
|
|
29
29
|
BehaviorContext,
|
|
30
30
|
BehaviorEvent,
|
|
31
|
+
PickFromUnion,
|
|
31
32
|
} from './behavior/behavior.types'
|
|
32
33
|
|
|
34
|
+
export * from 'xstate/guards'
|
|
35
|
+
|
|
33
36
|
/**
|
|
34
37
|
* @internal
|
|
35
38
|
*/
|
|
@@ -75,7 +78,10 @@ export type MutationEvent = {
|
|
|
75
78
|
snapshot: Array<PortableTextBlock> | undefined
|
|
76
79
|
}
|
|
77
80
|
|
|
78
|
-
|
|
81
|
+
/**
|
|
82
|
+
* @internal
|
|
83
|
+
*/
|
|
84
|
+
export type InternalEditorEvent =
|
|
79
85
|
| {type: 'normalizing'}
|
|
80
86
|
| {type: 'done normalizing'}
|
|
81
87
|
| {
|
|
@@ -96,9 +102,19 @@ type EditorEvent =
|
|
|
96
102
|
type: 'update behaviors'
|
|
97
103
|
behaviors: Array<Behavior>
|
|
98
104
|
}
|
|
99
|
-
|
|
|
105
|
+
| {
|
|
106
|
+
type: 'toggle readOnly'
|
|
107
|
+
}
|
|
108
|
+
| {
|
|
109
|
+
type: 'update maxBlocks'
|
|
110
|
+
maxBlocks: number | undefined
|
|
111
|
+
}
|
|
112
|
+
| InternalEditorEmittedEvent
|
|
100
113
|
|
|
101
|
-
|
|
114
|
+
/**
|
|
115
|
+
* @internal
|
|
116
|
+
*/
|
|
117
|
+
export type InternalEditorEmittedEvent =
|
|
102
118
|
| {type: 'ready'}
|
|
103
119
|
| PatchEvent
|
|
104
120
|
| PatchesEvent
|
|
@@ -124,11 +140,16 @@ type EditorEmittedEvent =
|
|
|
124
140
|
}
|
|
125
141
|
| {type: 'selection'; selection: EditorSelection}
|
|
126
142
|
| {type: 'blur'; event: FocusEvent<HTMLDivElement, Element>}
|
|
127
|
-
| {type: '
|
|
143
|
+
| {type: 'focused'; event: FocusEvent<HTMLDivElement, Element>}
|
|
128
144
|
| {type: 'online'}
|
|
129
145
|
| {type: 'offline'}
|
|
130
146
|
| {type: 'loading'}
|
|
131
147
|
| {type: 'done loading'}
|
|
148
|
+
| PickFromUnion<
|
|
149
|
+
BehaviorEvent,
|
|
150
|
+
'type',
|
|
151
|
+
'annotation.add' | 'annotation.remove' | 'annotation.toggle' | 'focus'
|
|
152
|
+
>
|
|
132
153
|
|
|
133
154
|
/**
|
|
134
155
|
* @internal
|
|
@@ -140,9 +161,11 @@ export const editorMachine = setup({
|
|
|
140
161
|
keyGenerator: () => string
|
|
141
162
|
pendingEvents: Array<PatchEvent | MutationEvent>
|
|
142
163
|
schema: PortableTextMemberSchemaTypes
|
|
164
|
+
readOnly: boolean
|
|
165
|
+
maxBlocks: number | undefined
|
|
143
166
|
},
|
|
144
|
-
events: {} as
|
|
145
|
-
emitted: {} as
|
|
167
|
+
events: {} as InternalEditorEvent,
|
|
168
|
+
emitted: {} as InternalEditorEmittedEvent,
|
|
146
169
|
input: {} as {
|
|
147
170
|
behaviors?: Array<Behavior>
|
|
148
171
|
keyGenerator: () => string
|
|
@@ -290,12 +313,30 @@ export const editorMachine = setup({
|
|
|
290
313
|
keyGenerator: input.keyGenerator,
|
|
291
314
|
pendingEvents: [],
|
|
292
315
|
schema: input.schema,
|
|
316
|
+
readOnly: false,
|
|
317
|
+
maxBlocks: undefined,
|
|
293
318
|
}),
|
|
294
319
|
invoke: {
|
|
295
320
|
id: 'networkLogic',
|
|
296
321
|
src: 'networkLogic',
|
|
297
322
|
},
|
|
298
323
|
on: {
|
|
324
|
+
'annotation.add': {
|
|
325
|
+
actions: emit(({event}) => event),
|
|
326
|
+
guard: ({context}) => !context.readOnly,
|
|
327
|
+
},
|
|
328
|
+
'annotation.remove': {
|
|
329
|
+
actions: emit(({event}) => event),
|
|
330
|
+
guard: ({context}) => !context.readOnly,
|
|
331
|
+
},
|
|
332
|
+
'annotation.toggle': {
|
|
333
|
+
actions: emit(({event}) => event),
|
|
334
|
+
guard: ({context}) => !context.readOnly,
|
|
335
|
+
},
|
|
336
|
+
'focus': {
|
|
337
|
+
actions: emit(({event}) => event),
|
|
338
|
+
guard: ({context}) => !context.readOnly,
|
|
339
|
+
},
|
|
299
340
|
'ready': {actions: emit(({event}) => event)},
|
|
300
341
|
'unset': {actions: emit(({event}) => event)},
|
|
301
342
|
'value changed': {actions: emit(({event}) => event)},
|
|
@@ -303,7 +344,7 @@ export const editorMachine = setup({
|
|
|
303
344
|
'error': {actions: emit(({event}) => event)},
|
|
304
345
|
'selection': {actions: emit(({event}) => event)},
|
|
305
346
|
'blur': {actions: emit(({event}) => event)},
|
|
306
|
-
'
|
|
347
|
+
'focused': {actions: emit(({event}) => event)},
|
|
307
348
|
'online': {actions: emit({type: 'online'})},
|
|
308
349
|
'offline': {actions: emit({type: 'offline'})},
|
|
309
350
|
'loading': {actions: emit({type: 'loading'})},
|
|
@@ -311,7 +352,16 @@ export const editorMachine = setup({
|
|
|
311
352
|
'done loading': {actions: emit({type: 'done loading'})},
|
|
312
353
|
'update behaviors': {actions: 'assign behaviors'},
|
|
313
354
|
'update schema': {actions: 'assign schema'},
|
|
314
|
-
'
|
|
355
|
+
'toggle readOnly': {
|
|
356
|
+
actions: assign({readOnly: ({context}) => !context.readOnly}),
|
|
357
|
+
},
|
|
358
|
+
'update maxBlocks': {
|
|
359
|
+
actions: assign({maxBlocks: ({event}) => event.maxBlocks}),
|
|
360
|
+
},
|
|
361
|
+
'behavior event': {
|
|
362
|
+
actions: 'handle behavior event',
|
|
363
|
+
guard: ({context}) => !context.readOnly,
|
|
364
|
+
},
|
|
315
365
|
'behavior action intends': {
|
|
316
366
|
actions: [
|
|
317
367
|
({context, event}) => {
|
|
@@ -1,6 +1,35 @@
|
|
|
1
|
-
import
|
|
1
|
+
import getRandomValues from 'get-random-values-esm'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @public
|
|
5
5
|
*/
|
|
6
6
|
export const defaultKeyGenerator = (): string => randomKey(12)
|
|
7
|
+
|
|
8
|
+
const getByteHexTable = (() => {
|
|
9
|
+
let table: any[]
|
|
10
|
+
return () => {
|
|
11
|
+
if (table) {
|
|
12
|
+
return table
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
table = []
|
|
16
|
+
for (let i = 0; i < 256; ++i) {
|
|
17
|
+
table[i] = (i + 0x100).toString(16).slice(1)
|
|
18
|
+
}
|
|
19
|
+
return table
|
|
20
|
+
}
|
|
21
|
+
})()
|
|
22
|
+
|
|
23
|
+
// WHATWG crypto RNG - https://w3c.github.io/webcrypto/Overview.html
|
|
24
|
+
function whatwgRNG(length = 16) {
|
|
25
|
+
const rnds8 = new Uint8Array(length)
|
|
26
|
+
getRandomValues(rnds8)
|
|
27
|
+
return rnds8
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function randomKey(length?: number): string {
|
|
31
|
+
const table = getByteHexTable()
|
|
32
|
+
return whatwgRNG(length)
|
|
33
|
+
.reduce((str, n) => str + table[n], '')
|
|
34
|
+
.slice(0, length)
|
|
35
|
+
}
|
|
@@ -1,8 +1,69 @@
|
|
|
1
1
|
import type {Editor} from 'slate'
|
|
2
2
|
import type {EditorActor} from '../editor-machine'
|
|
3
3
|
|
|
4
|
-
export function createWithEventListeners(
|
|
4
|
+
export function createWithEventListeners(
|
|
5
|
+
editorActor: EditorActor,
|
|
6
|
+
subscriptions: Array<() => () => void>,
|
|
7
|
+
) {
|
|
5
8
|
return function withEventListeners(editor: Editor) {
|
|
9
|
+
if (editorActor.getSnapshot().context.maxBlocks !== undefined) {
|
|
10
|
+
return editor
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
subscriptions.push(() => {
|
|
14
|
+
const subscription = editorActor.on('*', (event) => {
|
|
15
|
+
switch (event.type) {
|
|
16
|
+
case 'annotation.add': {
|
|
17
|
+
editorActor.send({
|
|
18
|
+
type: 'behavior event',
|
|
19
|
+
behaviorEvent: {
|
|
20
|
+
type: 'annotation.add',
|
|
21
|
+
annotation: event.annotation,
|
|
22
|
+
},
|
|
23
|
+
editor,
|
|
24
|
+
})
|
|
25
|
+
break
|
|
26
|
+
}
|
|
27
|
+
case 'annotation.remove': {
|
|
28
|
+
editorActor.send({
|
|
29
|
+
type: 'behavior event',
|
|
30
|
+
behaviorEvent: {
|
|
31
|
+
type: 'annotation.remove',
|
|
32
|
+
annotation: event.annotation,
|
|
33
|
+
},
|
|
34
|
+
editor,
|
|
35
|
+
})
|
|
36
|
+
break
|
|
37
|
+
}
|
|
38
|
+
case 'annotation.toggle': {
|
|
39
|
+
editorActor.send({
|
|
40
|
+
type: 'behavior event',
|
|
41
|
+
behaviorEvent: {
|
|
42
|
+
type: 'annotation.toggle',
|
|
43
|
+
annotation: event.annotation,
|
|
44
|
+
},
|
|
45
|
+
editor,
|
|
46
|
+
})
|
|
47
|
+
break
|
|
48
|
+
}
|
|
49
|
+
case 'focus': {
|
|
50
|
+
editorActor.send({
|
|
51
|
+
type: 'behavior event',
|
|
52
|
+
behaviorEvent: {
|
|
53
|
+
type: 'focus',
|
|
54
|
+
},
|
|
55
|
+
editor,
|
|
56
|
+
})
|
|
57
|
+
break
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
return () => {
|
|
63
|
+
subscription.unsubscribe()
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
|
|
6
67
|
editor.addMark = (mark) => {
|
|
7
68
|
editorActor.send({
|
|
8
69
|
type: 'behavior event',
|