@portabletext/editor 2.21.2 → 3.0.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.
Files changed (79) hide show
  1. package/lib/_chunks-dts/index.d.ts +50 -210
  2. package/lib/_chunks-es/selector.is-at-the-start-of-block.js +103 -20
  3. package/lib/_chunks-es/selector.is-at-the-start-of-block.js.map +1 -1
  4. package/lib/_chunks-es/{util.get-text-block-text.js → util.slice-blocks.js} +29 -5
  5. package/lib/_chunks-es/util.slice-blocks.js.map +1 -0
  6. package/lib/_chunks-es/util.slice-text-block.js +13 -2
  7. package/lib/_chunks-es/util.slice-text-block.js.map +1 -1
  8. package/lib/behaviors/index.d.ts +1 -1
  9. package/lib/index.d.ts +2 -2
  10. package/lib/index.js +323 -320
  11. package/lib/index.js.map +1 -1
  12. package/lib/plugins/index.d.ts +2 -133
  13. package/lib/plugins/index.js +2 -796
  14. package/lib/plugins/index.js.map +1 -1
  15. package/lib/selectors/index.d.ts +2 -24
  16. package/lib/selectors/index.js +28 -130
  17. package/lib/selectors/index.js.map +1 -1
  18. package/lib/utils/index.d.ts +3 -3
  19. package/lib/utils/index.js +97 -9
  20. package/lib/utils/index.js.map +1 -1
  21. package/package.json +7 -9
  22. package/src/behaviors/behavior.perform-event.ts +7 -7
  23. package/src/editor/PortableTextEditor.tsx +0 -19
  24. package/src/editor/create-editor.ts +0 -3
  25. package/src/editor/editor-machine.ts +0 -10
  26. package/src/editor/event-to-change.tsx +5 -1
  27. package/src/editor/plugins/create-with-event-listeners.ts +0 -4
  28. package/src/editor/plugins/createWithObjectKeys.ts +2 -1
  29. package/src/editor/plugins/createWithPatches.ts +3 -3
  30. package/src/editor/plugins/createWithPlaceholderBlock.ts +2 -1
  31. package/src/editor/plugins/createWithPortableTextMarkModel.ts +2 -1
  32. package/src/editor/plugins/with-plugins.ts +10 -14
  33. package/src/editor/relay-machine.ts +0 -4
  34. package/src/editor/sync-machine.ts +2 -2
  35. package/src/editor.ts +0 -4
  36. package/src/history/behavior.operation.history.redo.ts +67 -0
  37. package/src/history/behavior.operation.history.undo.ts +71 -0
  38. package/src/history/event.history.undo.test.tsx +672 -0
  39. package/src/history/history.preserving-keys.test.tsx +112 -0
  40. package/src/history/remote-patches.ts +20 -0
  41. package/src/history/slate-plugin.history.ts +146 -0
  42. package/src/history/slate-plugin.redoing.ts +21 -0
  43. package/src/history/slate-plugin.undoing.ts +21 -0
  44. package/src/history/slate-plugin.without-history.ts +23 -0
  45. package/src/history/transform-operation.ts +245 -0
  46. package/src/history/undo-redo-collaboration.test.tsx +541 -0
  47. package/src/history/undo-redo.feature +125 -0
  48. package/src/history/undo-redo.test.tsx +195 -0
  49. package/src/history/undo-step.ts +148 -0
  50. package/src/index.ts +0 -1
  51. package/src/internal-utils/applyPatch.ts +46 -1
  52. package/src/operations/behavior.operations.ts +2 -4
  53. package/src/plugins/index.ts +0 -3
  54. package/src/selectors/index.ts +0 -3
  55. package/src/test/vitest/step-definitions.tsx +88 -8
  56. package/src/test/vitest/test-editor.tsx +1 -1
  57. package/lib/_chunks-es/selector.get-selection-text.js +0 -92
  58. package/lib/_chunks-es/selector.get-selection-text.js.map +0 -1
  59. package/lib/_chunks-es/selector.get-text-before.js +0 -36
  60. package/lib/_chunks-es/selector.get-text-before.js.map +0 -1
  61. package/lib/_chunks-es/util.get-text-block-text.js.map +0 -1
  62. package/lib/_chunks-es/util.is-empty-text-block.js +0 -40
  63. package/lib/_chunks-es/util.is-empty-text-block.js.map +0 -1
  64. package/lib/_chunks-es/util.merge-text-blocks.js +0 -101
  65. package/lib/_chunks-es/util.merge-text-blocks.js.map +0 -1
  66. package/src/editor/plugins/createWithMaxBlocks.ts +0 -53
  67. package/src/editor/plugins/createWithUndoRedo.ts +0 -628
  68. package/src/editor/with-undo-step.ts +0 -37
  69. package/src/editor/withUndoRedo.ts +0 -34
  70. package/src/editor-event-listener.tsx +0 -28
  71. package/src/plugins/plugin.decorator-shortcut.ts +0 -238
  72. package/src/plugins/plugin.markdown.test.tsx +0 -42
  73. package/src/plugins/plugin.markdown.tsx +0 -131
  74. package/src/plugins/plugin.one-line.tsx +0 -123
  75. package/src/selectors/selector.get-list-state.test.ts +0 -189
  76. package/src/selectors/selector.get-list-state.ts +0 -96
  77. package/src/selectors/selector.get-selected-slice.ts +0 -13
  78. package/src/selectors/selector.get-trimmed-selection.test.ts +0 -657
  79. package/src/selectors/selector.get-trimmed-selection.ts +0 -189
@@ -1,28 +0,0 @@
1
- import {useEffect} from 'react'
2
- import type {EditorEmittedEvent} from './editor/relay-machine'
3
- import {useEditor} from './editor/use-editor'
4
-
5
- /**
6
- * @public
7
- * @deprecated
8
- * This component has been renamed. Use `EventListenerPlugin` instead.
9
- *
10
- * ```
11
- * import {EventListenerPlugin} from '@portabletext/editor/plugins'
12
- * ```
13
- */
14
- export function EditorEventListener(props: {
15
- on: (event: EditorEmittedEvent) => void
16
- }) {
17
- const editor = useEditor()
18
-
19
- useEffect(() => {
20
- const subscription = editor.on('*', props.on)
21
-
22
- return () => {
23
- subscription.unsubscribe()
24
- }
25
- }, [editor, props.on])
26
-
27
- return null
28
- }
@@ -1,238 +0,0 @@
1
- import {useActorRef} from '@xstate/react'
2
- import {isEqual} from 'lodash'
3
- import {
4
- assign,
5
- fromCallback,
6
- setup,
7
- type AnyEventObject,
8
- type CallbackLogicFunction,
9
- } from 'xstate'
10
- import {createDecoratorPairBehavior} from '../behaviors/behavior.decorator-pair'
11
- import {effect, execute, forward} from '../behaviors/behavior.types.action'
12
- import {defineBehavior} from '../behaviors/behavior.types.behavior'
13
- import type {Editor} from '../editor'
14
- import type {EditorSchema} from '../editor/editor-schema'
15
- import {useEditor} from '../editor/use-editor'
16
- import type {BlockOffset} from '../types/block-offset'
17
- import {spanSelectionPointToBlockOffset} from '../utils/util.block-offset'
18
-
19
- /**
20
- * @beta
21
- * @deprecated Install the plugin from `@portabletext/plugin-character-pair-decorator`
22
- */
23
- export function DecoratorShortcutPlugin(config: {
24
- decorator: ({schema}: {schema: EditorSchema}) => string | undefined
25
- pair: {char: string; amount: number}
26
- }) {
27
- const editor = useEditor()
28
-
29
- useActorRef(decoratorPairMachine, {
30
- input: {
31
- editor,
32
- decorator: config.decorator,
33
- pair: config.pair,
34
- },
35
- })
36
-
37
- return null
38
- }
39
-
40
- type MarkdownEmphasisEvent =
41
- | {
42
- type: 'emphasis.add'
43
- blockOffset: BlockOffset
44
- }
45
- | {
46
- type: 'selection'
47
- blockOffsets?: {
48
- anchor: BlockOffset
49
- focus: BlockOffset
50
- }
51
- }
52
- | {
53
- type: 'delete.backward'
54
- }
55
-
56
- const emphasisListener: CallbackLogicFunction<
57
- AnyEventObject,
58
- MarkdownEmphasisEvent,
59
- {
60
- decorator: ({schema}: {schema: EditorSchema}) => string | undefined
61
- editor: Editor
62
- pair: {char: string; amount: number}
63
- }
64
- > = ({sendBack, input}) => {
65
- const unregister = input.editor.registerBehavior({
66
- behavior: createDecoratorPairBehavior({
67
- decorator: input.decorator,
68
- pair: input.pair,
69
- onDecorate: (offset) => {
70
- sendBack({type: 'emphasis.add', blockOffset: offset})
71
- },
72
- }),
73
- })
74
-
75
- return unregister
76
- }
77
-
78
- const selectionListenerCallback: CallbackLogicFunction<
79
- AnyEventObject,
80
- MarkdownEmphasisEvent,
81
- {editor: Editor}
82
- > = ({sendBack, input}) => {
83
- const unregister = input.editor.registerBehavior({
84
- behavior: defineBehavior({
85
- on: 'select',
86
- guard: ({snapshot, event}) => {
87
- if (!event.at) {
88
- return {blockOffsets: undefined}
89
- }
90
-
91
- const anchor = spanSelectionPointToBlockOffset({
92
- context: snapshot.context,
93
- selectionPoint: event.at.anchor,
94
- })
95
- const focus = spanSelectionPointToBlockOffset({
96
- context: snapshot.context,
97
- selectionPoint: event.at.focus,
98
- })
99
-
100
- if (!anchor || !focus) {
101
- return {blockOffsets: undefined}
102
- }
103
-
104
- return {
105
- blockOffsets: {
106
- anchor,
107
- focus,
108
- },
109
- }
110
- },
111
- actions: [
112
- ({event}, {blockOffsets}) => [
113
- {
114
- type: 'effect',
115
- effect: () => {
116
- sendBack({type: 'selection', blockOffsets})
117
- },
118
- },
119
- forward(event),
120
- ],
121
- ],
122
- }),
123
- })
124
-
125
- return unregister
126
- }
127
-
128
- const deleteBackwardListenerCallback: CallbackLogicFunction<
129
- AnyEventObject,
130
- MarkdownEmphasisEvent,
131
- {editor: Editor}
132
- > = ({sendBack, input}) => {
133
- const unregister = input.editor.registerBehavior({
134
- behavior: defineBehavior({
135
- on: 'delete.backward',
136
- actions: [
137
- () => [
138
- execute({
139
- type: 'history.undo',
140
- }),
141
- effect(() => {
142
- sendBack({type: 'delete.backward'})
143
- }),
144
- ],
145
- ],
146
- }),
147
- })
148
-
149
- return unregister
150
- }
151
-
152
- const decoratorPairMachine = setup({
153
- types: {
154
- context: {} as {
155
- decorator: ({schema}: {schema: EditorSchema}) => string | undefined
156
- editor: Editor
157
- offsetAfterEmphasis?: BlockOffset
158
- pair: {char: string; amount: number}
159
- },
160
- input: {} as {
161
- decorator: ({schema}: {schema: EditorSchema}) => string | undefined
162
- editor: Editor
163
- pair: {char: string; amount: number}
164
- },
165
- events: {} as MarkdownEmphasisEvent,
166
- },
167
- actors: {
168
- 'emphasis listener': fromCallback(emphasisListener),
169
- 'delete.backward listener': fromCallback(deleteBackwardListenerCallback),
170
- 'selection listener': fromCallback(selectionListenerCallback),
171
- },
172
- }).createMachine({
173
- id: 'decorator pair',
174
- context: ({input}) => ({
175
- decorator: input.decorator,
176
- editor: input.editor,
177
- pair: input.pair,
178
- }),
179
- initial: 'idle',
180
- states: {
181
- 'idle': {
182
- invoke: [
183
- {
184
- src: 'emphasis listener',
185
- input: ({context}) => ({
186
- decorator: context.decorator,
187
- editor: context.editor,
188
- pair: context.pair,
189
- }),
190
- },
191
- ],
192
- on: {
193
- 'emphasis.add': {
194
- target: 'emphasis added',
195
- actions: assign({
196
- offsetAfterEmphasis: ({event}) => event.blockOffset,
197
- }),
198
- },
199
- },
200
- },
201
- 'emphasis added': {
202
- exit: [
203
- assign({
204
- offsetAfterEmphasis: undefined,
205
- }),
206
- ],
207
- invoke: [
208
- {
209
- src: 'selection listener',
210
- input: ({context}) => ({editor: context.editor}),
211
- },
212
- {
213
- src: 'delete.backward listener',
214
- input: ({context}) => ({editor: context.editor}),
215
- },
216
- ],
217
- on: {
218
- 'selection': {
219
- target: 'idle',
220
- guard: ({context, event}) => {
221
- const selectionChanged = !isEqual(
222
- {
223
- anchor: context.offsetAfterEmphasis,
224
- focus: context.offsetAfterEmphasis,
225
- },
226
- event.blockOffsets,
227
- )
228
-
229
- return selectionChanged
230
- },
231
- },
232
- 'delete.backward': {
233
- target: 'idle',
234
- },
235
- },
236
- },
237
- },
238
- })
@@ -1,42 +0,0 @@
1
- import {defineSchema} from '@portabletext/schema'
2
- import {getTersePt} from '@portabletext/test'
3
- import {describe, expect, test, vi} from 'vitest'
4
- import {userEvent} from 'vitest/browser'
5
- import {getTextMarks} from '../internal-utils/text-marks'
6
- import {createTestEditor} from '../test/vitest'
7
- import {MarkdownPlugin} from './plugin.markdown'
8
-
9
- describe(MarkdownPlugin.name, () => {
10
- test('Scenario: Undoing bold shortcut', async () => {
11
- const {editor, locator} = await createTestEditor({
12
- children: (
13
- <MarkdownPlugin
14
- config={{
15
- boldDecorator: () => 'strong',
16
- }}
17
- />
18
- ),
19
- schemaDefinition: defineSchema({decorators: [{name: 'strong'}]}),
20
- })
21
-
22
- await userEvent.type(locator, '**Hello world!**')
23
-
24
- await vi.waitFor(() => {
25
- expect(getTersePt(editor.getSnapshot().context)).toEqual(['Hello world!'])
26
- })
27
-
28
- await vi.waitFor(() => {
29
- expect(
30
- getTextMarks(editor.getSnapshot().context, 'Hello world!'),
31
- ).toEqual(['strong'])
32
- })
33
-
34
- editor.send({type: 'history.undo'})
35
-
36
- await vi.waitFor(() => {
37
- expect(getTersePt(editor.getSnapshot().context)).toEqual([
38
- '**Hello world!**',
39
- ])
40
- })
41
- })
42
- })
@@ -1,131 +0,0 @@
1
- import {useEffect} from 'react'
2
- import {
3
- createMarkdownBehaviors,
4
- type MarkdownBehaviorsConfig,
5
- } from '../behaviors/behavior.markdown'
6
- import type {EditorSchema} from '../editor/editor-schema'
7
- import {useEditor} from '../editor/use-editor'
8
- import {DecoratorShortcutPlugin} from './plugin.decorator-shortcut'
9
-
10
- /**
11
- * @beta
12
- */
13
- export type MarkdownPluginConfig = MarkdownBehaviorsConfig & {
14
- boldDecorator?: ({schema}: {schema: EditorSchema}) => string | undefined
15
- codeDecorator?: ({schema}: {schema: EditorSchema}) => string | undefined
16
- italicDecorator?: ({schema}: {schema: EditorSchema}) => string | undefined
17
- strikeThroughDecorator?: ({
18
- schema,
19
- }: {
20
- schema: EditorSchema
21
- }) => string | undefined
22
- }
23
-
24
- /**
25
- * @beta
26
- * Add markdown behaviors for common markdown actions such as converting ### to headings, --- to HRs, and more.
27
- *
28
- * @example
29
- * Configure the bundled markdown behaviors
30
- * ```ts
31
- * import {EditorProvider} from '@portabletext/editor'
32
- * import {MarkdownPlugin} from '@portabletext/editor/plugins'
33
- *
34
- * function App() {
35
- * return (
36
- * <EditorProvider>
37
- * <MarkdownPlugin
38
- * config={{
39
- * boldDecorator: ({schema}) =>
40
- * schema.decorators.find((decorator) => decorator.value === 'strong')?.value,
41
- * codeDecorator: ({schema}) =>
42
- * schema.decorators.find((decorator) => decorator.value === 'code')?.value,
43
- * italicDecorator: ({schema}) =>
44
- * schema.decorators.find((decorator) => decorator.value === 'em')?.value,
45
- * strikeThroughDecorator: ({schema}) =>
46
- * schema.decorators.find((decorator) => decorator.value === 'strike-through')?.value,
47
- * horizontalRuleObject: ({schema}) => {
48
- * const name = schema.blockObjects.find(
49
- * (object) => object.name === 'break',
50
- * )?.name
51
- * return name ? {name} : undefined
52
- * },
53
- * defaultStyle: ({schema}) => schema.styles[0].value,
54
- * headingStyle: ({schema, level}) =>
55
- * schema.styles.find((style) => style.value === `h${level}`)
56
- * ?.value,
57
- * blockquoteStyle: ({schema}) =>
58
- * schema.styles.find((style) => style.value === 'blockquote')
59
- * ?.value,
60
- * unorderedListStyle: ({schema}) =>
61
- * schema.lists.find((list) => list.value === 'bullet')?.value,
62
- * orderedListStyle: ({schema}) =>
63
- * schema.lists.find((list) => list.value === 'number')?.value,
64
- * }}
65
- * />
66
- * {...}
67
- * </EditorProvider>
68
- * )
69
- * }
70
- * ```
71
- *
72
- * @deprecated Install the plugin from `@portabletext/plugin-markdown-shortcuts`
73
- */
74
- export function MarkdownPlugin(props: {config: MarkdownPluginConfig}) {
75
- const editor = useEditor()
76
-
77
- useEffect(() => {
78
- const behaviors = createMarkdownBehaviors(props.config)
79
-
80
- const unregisterBehaviors = behaviors.map((behavior) =>
81
- editor.registerBehavior({behavior}),
82
- )
83
-
84
- return () => {
85
- for (const unregisterBehavior of unregisterBehaviors) {
86
- unregisterBehavior()
87
- }
88
- }
89
- }, [editor, props.config])
90
-
91
- return (
92
- <>
93
- {props.config.boldDecorator ? (
94
- <>
95
- <DecoratorShortcutPlugin
96
- decorator={props.config.boldDecorator}
97
- pair={{char: '*', amount: 2}}
98
- />
99
- <DecoratorShortcutPlugin
100
- decorator={props.config.boldDecorator}
101
- pair={{char: '_', amount: 2}}
102
- />
103
- </>
104
- ) : null}
105
- {props.config.codeDecorator ? (
106
- <DecoratorShortcutPlugin
107
- decorator={props.config.codeDecorator}
108
- pair={{char: '`', amount: 1}}
109
- />
110
- ) : null}
111
- {props.config.italicDecorator ? (
112
- <>
113
- <DecoratorShortcutPlugin
114
- decorator={props.config.italicDecorator}
115
- pair={{char: '*', amount: 1}}
116
- />
117
- <DecoratorShortcutPlugin
118
- decorator={props.config.italicDecorator}
119
- pair={{char: '_', amount: 1}}
120
- />
121
- </>
122
- ) : null}
123
- {props.config.strikeThroughDecorator ? (
124
- <DecoratorShortcutPlugin
125
- decorator={props.config.strikeThroughDecorator}
126
- pair={{char: '~', amount: 2}}
127
- />
128
- ) : null}
129
- </>
130
- )
131
- }
@@ -1,123 +0,0 @@
1
- import {isTextBlock} from '@portabletext/schema'
2
- import {execute, raise} from '../behaviors/behavior.types.action'
3
- import {defineBehavior} from '../behaviors/behavior.types.behavior'
4
- import {getFocusTextBlock} from '../selectors/selector.get-focus-text-block'
5
- import {isSelectionExpanded} from '../selectors/selector.is-selection-expanded'
6
- import {mergeTextBlocks} from '../utils/util.merge-text-blocks'
7
- import {BehaviorPlugin} from './plugin.behavior'
8
-
9
- const oneLineBehaviors = [
10
- /**
11
- * Hitting Enter on an expanded selection should just delete that selection
12
- * without causing a line break.
13
- */
14
- defineBehavior({
15
- on: 'insert.break',
16
- guard: ({snapshot}) =>
17
- snapshot.context.selection && isSelectionExpanded(snapshot)
18
- ? {selection: snapshot.context.selection}
19
- : false,
20
- actions: [(_, {selection}) => [execute({type: 'delete', at: selection})]],
21
- }),
22
- /**
23
- * All other cases of `insert.break` should be aborted.
24
- */
25
- defineBehavior({
26
- on: 'insert.break',
27
- actions: [],
28
- }),
29
- /**
30
- * `insert.block` `before` or `after` is not allowed in a one-line editor.
31
- */
32
- defineBehavior({
33
- on: 'insert.block',
34
- guard: ({event}) =>
35
- event.placement === 'before' || event.placement === 'after',
36
- actions: [],
37
- }),
38
- /**
39
- * An ordinary `insert.block` is acceptable if it's a text block. In that
40
- * case it will get merged into the existing text block.
41
- */
42
- defineBehavior({
43
- on: 'insert.block',
44
- guard: ({snapshot, event}) => {
45
- const focusTextBlock = getFocusTextBlock(snapshot)
46
-
47
- if (!focusTextBlock || !isTextBlock(snapshot.context, event.block)) {
48
- return false
49
- }
50
-
51
- return true
52
- },
53
- actions: [
54
- ({event}) => [
55
- execute({
56
- type: 'insert.block',
57
- block: event.block,
58
- placement: 'auto',
59
- select: 'end',
60
- }),
61
- ],
62
- ],
63
- }),
64
- /**
65
- * Fallback Behavior to avoid `insert.block` in case the Behaviors above all
66
- * end up with a falsy guard.
67
- */
68
- defineBehavior({
69
- on: 'insert.block',
70
- actions: [],
71
- }),
72
- /**
73
- * If multiple blocks are inserted, then the non-text blocks are filtered out
74
- * and the text blocks are merged into one block
75
- */
76
- defineBehavior({
77
- on: 'insert.blocks',
78
- guard: ({snapshot, event}) => {
79
- const textBlocks = event.blocks.filter((block) =>
80
- isTextBlock(snapshot.context, block),
81
- )
82
-
83
- if (textBlocks.length === 0) {
84
- return false
85
- }
86
-
87
- return textBlocks.reduce((targetBlock, incomingBlock) => {
88
- return mergeTextBlocks({
89
- context: snapshot.context,
90
- targetBlock,
91
- incomingBlock,
92
- })
93
- })
94
- },
95
- actions: [
96
- // `insert.block` is raised so the Behavior above can handle the
97
- // insertion
98
- (_, block) => [raise({type: 'insert.block', block, placement: 'auto'})],
99
- ],
100
- }),
101
- /**
102
- * Fallback Behavior to avoid `insert.blocks` in case the Behavior above
103
- * ends up with a falsy guard.
104
- */
105
- defineBehavior({
106
- on: 'insert.blocks',
107
- actions: [],
108
- }),
109
- ]
110
-
111
- /**
112
- * @beta
113
- * Restrict the editor to one line. The plugin takes care of blocking
114
- * `insert.break` events and smart handling of other `insert.*` events.
115
- *
116
- * Place it with as high priority as possible to make sure other plugins don't
117
- * overwrite `insert.*` events before this plugin gets a chance to do so.
118
- *
119
- * @deprecated Install the plugin from `@portabletext/plugin-one-line`
120
- */
121
- export function OneLinePlugin() {
122
- return <BehaviorPlugin behaviors={oneLineBehaviors} />
123
- }