@portabletext/editor 1.5.6 → 1.6.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portabletext/editor",
3
- "version": "1.5.6",
3
+ "version": "1.6.0",
4
4
  "description": "Portable Text Editor made in React",
5
5
  "keywords": [
6
6
  "sanity",
@@ -169,8 +169,8 @@ export const PortableTextEditable = forwardRef<
169
169
  [editorActor, schemaTypes],
170
170
  )
171
171
  const withHotKeys = useMemo(
172
- () => createWithHotkeys(portableTextEditor, hotkeys),
173
- [hotkeys, portableTextEditor],
172
+ () => createWithHotkeys(editorActor, portableTextEditor, hotkeys),
173
+ [editorActor, hotkeys, portableTextEditor],
174
174
  )
175
175
 
176
176
  // Output a minimal React editor inside Editable when in readOnly mode.
@@ -127,23 +127,23 @@ describe('Feature: Self-solving', () => {
127
127
  },
128
128
  })
129
129
  expect(onChange).toHaveBeenNthCalledWith(4, {
130
- type: 'patch',
131
- patch: spanPatch,
130
+ type: 'selection',
131
+ selection: {
132
+ ...getTextSelection(initialValue, 'foo'),
133
+ backward: false,
134
+ },
132
135
  })
133
136
  expect(onChange).toHaveBeenNthCalledWith(5, {
134
137
  type: 'patch',
135
- patch: blockPatch,
138
+ patch: spanPatch,
136
139
  })
137
140
  expect(onChange).toHaveBeenNthCalledWith(6, {
138
141
  type: 'patch',
139
- patch: strongPatch,
142
+ patch: blockPatch,
140
143
  })
141
144
  expect(onChange).toHaveBeenNthCalledWith(7, {
142
- type: 'selection',
143
- selection: {
144
- ...getTextSelection(initialValue, 'foo'),
145
- backward: false,
146
- },
145
+ type: 'patch',
146
+ patch: strongPatch,
147
147
  })
148
148
  expect(onChange).toHaveBeenNthCalledWith(8, {
149
149
  type: 'selection',
@@ -7,6 +7,11 @@ import {
7
7
  } from 'slate'
8
8
  import type {PortableTextMemberSchemaTypes} from '../../types/editor'
9
9
  import {toSlateRange} from '../../utils/ranges'
10
+ import {
11
+ addDecoratorActionImplementation,
12
+ removeDecoratorActionImplementation,
13
+ toggleDecoratorActionImplementation,
14
+ } from '../plugins/createWithPortableTextMarkModel'
10
15
  import {insertBreakActionImplementation} from './behavior.action.insert-break'
11
16
  import type {
12
17
  BehaviorAction,
@@ -36,6 +41,9 @@ type BehaviourActionImplementations = {
36
41
  }
37
42
 
38
43
  const behaviorActionImplementations: BehaviourActionImplementations = {
44
+ 'decorator.add': addDecoratorActionImplementation,
45
+ 'decorator.remove': removeDecoratorActionImplementation,
46
+ 'decorator.toggle': toggleDecoratorActionImplementation,
39
47
  'set block': ({action}) => {
40
48
  for (const path of action.paths) {
41
49
  const at = toSlateRange(
@@ -116,10 +124,21 @@ const behaviorActionImplementations: BehaviourActionImplementations = {
116
124
  action.effect()
117
125
  },
118
126
  'select': ({action}) => {
119
- Transforms.select(
120
- action.editor,
121
- toSlateRange(action.selection, action.editor)!,
122
- )
127
+ const newSelection = toSlateRange(action.selection, action.editor)
128
+
129
+ if (newSelection) {
130
+ Transforms.select(action.editor, newSelection)
131
+ } else {
132
+ Transforms.deselect(action.editor)
133
+ }
134
+ },
135
+ 'reselect': ({action}) => {
136
+ const selection = action.editor.selection
137
+
138
+ if (selection) {
139
+ Transforms.select(action.editor, {...selection})
140
+ action.editor.selection = {...selection}
141
+ }
123
142
  },
124
143
  }
125
144
 
@@ -173,6 +192,13 @@ export function performAction({
173
192
  })
174
193
  break
175
194
  }
195
+ case 'reselect': {
196
+ behaviorActionImplementations.reselect({
197
+ context,
198
+ action,
199
+ })
200
+ break
201
+ }
176
202
  default: {
177
203
  performDefaultAction({context, action})
178
204
  }
@@ -187,6 +213,27 @@ export function performDefaultAction({
187
213
  action: PickFromUnion<BehaviorAction, 'type', BehaviorEvent['type']>
188
214
  }) {
189
215
  switch (action.type) {
216
+ case 'decorator.add': {
217
+ behaviorActionImplementations['decorator.add']({
218
+ context,
219
+ action,
220
+ })
221
+ break
222
+ }
223
+ case 'decorator.remove': {
224
+ behaviorActionImplementations['decorator.remove']({
225
+ context,
226
+ action,
227
+ })
228
+ break
229
+ }
230
+ case 'decorator.toggle': {
231
+ behaviorActionImplementations['decorator.toggle']({
232
+ context,
233
+ action,
234
+ })
235
+ break
236
+ }
190
237
  case 'delete backward': {
191
238
  behaviorActionImplementations['delete backward']({
192
239
  context,
@@ -0,0 +1,52 @@
1
+ import {defineBehavior} from './behavior.types'
2
+
3
+ const decoratorAdd = defineBehavior({
4
+ on: 'decorator.add',
5
+ actions: [
6
+ ({event}) => [
7
+ {
8
+ type: 'decorator.add',
9
+ decorator: event.decorator,
10
+ },
11
+ {
12
+ type: 'reselect',
13
+ },
14
+ ],
15
+ ],
16
+ })
17
+
18
+ const decoratorRemove = defineBehavior({
19
+ on: 'decorator.remove',
20
+ actions: [
21
+ ({event}) => [
22
+ {
23
+ type: 'decorator.remove',
24
+ decorator: event.decorator,
25
+ },
26
+ {
27
+ type: 'reselect',
28
+ },
29
+ ],
30
+ ],
31
+ })
32
+
33
+ const decoratorToggle = defineBehavior({
34
+ on: 'decorator.toggle',
35
+ actions: [
36
+ ({event}) => [
37
+ {
38
+ type: 'decorator.toggle',
39
+ decorator: event.decorator,
40
+ },
41
+ {
42
+ type: 'reselect',
43
+ },
44
+ ],
45
+ ],
46
+ })
47
+
48
+ export const coreDecoratorBehaviors = {
49
+ decoratorAdd,
50
+ decoratorRemove,
51
+ decoratorToggle,
52
+ }
@@ -1,4 +1,5 @@
1
1
  import {coreBlockObjectBehaviors} from './behavior.core.block-objects'
2
+ import {coreDecoratorBehaviors} from './behavior.core.decorators'
2
3
  import {coreListBehaviors} from './behavior.core.lists'
3
4
  import {defineBehavior} from './behavior.types'
4
5
 
@@ -12,6 +13,9 @@ const softReturn = defineBehavior({
12
13
  */
13
14
  export const coreBehaviors = [
14
15
  softReturn,
16
+ coreDecoratorBehaviors.decoratorAdd,
17
+ coreDecoratorBehaviors.decoratorRemove,
18
+ coreDecoratorBehaviors.decoratorToggle,
15
19
  coreBlockObjectBehaviors.breakingBlockObject,
16
20
  coreBlockObjectBehaviors.deletingEmptyTextBlockAfterBlockObject,
17
21
  coreBlockObjectBehaviors.deletingEmptyTextBlockBeforeBlockObject,
@@ -24,6 +28,7 @@ export const coreBehaviors = [
24
28
  */
25
29
  export const coreBehavior = {
26
30
  softReturn,
31
+ decorators: coreDecoratorBehaviors,
27
32
  blockObjects: coreBlockObjectBehaviors,
28
33
  lists: coreListBehaviors,
29
34
  }
@@ -227,11 +227,13 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
227
227
  return false
228
228
  }
229
229
 
230
+ const defaultStyle = config.mapDefaultStyle(context.schema)
230
231
  const looksLikeUnorderedList = /^(-|\*)/.test(focusSpan.node.text)
231
232
  const unorderedListStyle = config.mapUnorderedListStyle(context.schema)
232
233
  const caretAtTheEndOfUnorderedList = context.selection.focus.offset === 1
233
234
 
234
235
  if (
236
+ defaultStyle &&
235
237
  caretAtTheEndOfUnorderedList &&
236
238
  looksLikeUnorderedList &&
237
239
  unorderedListStyle !== undefined
@@ -241,6 +243,7 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
241
243
  focusSpan,
242
244
  listItem: unorderedListStyle,
243
245
  listItemLength: 1,
246
+ style: defaultStyle,
244
247
  }
245
248
  }
246
249
 
@@ -249,6 +252,7 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
249
252
  const caretAtTheEndOfOrderedList = context.selection.focus.offset === 2
250
253
 
251
254
  if (
255
+ defaultStyle &&
252
256
  caretAtTheEndOfOrderedList &&
253
257
  looksLikeOrderedList &&
254
258
  orderedListStyle !== undefined
@@ -258,6 +262,7 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
258
262
  focusSpan,
259
263
  listItem: orderedListStyle,
260
264
  listItemLength: 2,
265
+ style: defaultStyle,
261
266
  }
262
267
  }
263
268
 
@@ -270,16 +275,12 @@ export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
270
275
  text: ' ',
271
276
  },
272
277
  ],
273
- (_, {focusTextBlock, focusSpan, listItem, listItemLength}) => [
274
- {
275
- type: 'unset block',
276
- props: ['style'],
277
- paths: [focusTextBlock.path],
278
- },
278
+ (_, {focusTextBlock, focusSpan, style, listItem, listItemLength}) => [
279
279
  {
280
280
  type: 'set block',
281
281
  listItem,
282
282
  level: 1,
283
+ style,
283
284
  paths: [focusTextBlock.path],
284
285
  },
285
286
  {
@@ -20,6 +20,18 @@ export type BehaviorContext = {
20
20
  * @alpha
21
21
  */
22
22
  export type BehaviorEvent =
23
+ | {
24
+ type: 'decorator.add'
25
+ decorator: string
26
+ }
27
+ | {
28
+ type: 'decorator.remove'
29
+ decorator: string
30
+ }
31
+ | {
32
+ type: 'decorator.toggle'
33
+ decorator: string
34
+ }
23
35
  | {
24
36
  type: 'delete backward'
25
37
  unit: TextUnit
@@ -87,6 +99,9 @@ export type BehaviorActionIntend =
87
99
  type: 'select'
88
100
  selection: EditorSelection
89
101
  }
102
+ | {
103
+ type: 'reselect'
104
+ }
90
105
 
91
106
  /**
92
107
  * @alpha
@@ -20,7 +20,7 @@ import type {
20
20
  import {toPortableTextRange} from '../utils/ranges'
21
21
  import {fromSlateValue} from '../utils/values'
22
22
  import {KEY_TO_VALUE_ELEMENT} from '../utils/weakMaps'
23
- import {performAction, performDefaultAction} from './behavior/behavior.actions'
23
+ import {performAction} from './behavior/behavior.actions'
24
24
  import {coreBehaviors} from './behavior/behavior.core'
25
25
  import type {
26
26
  Behavior,
@@ -197,7 +197,11 @@ export const editorMachine = setup({
197
197
  )
198
198
 
199
199
  if (eventBehaviors.length === 0) {
200
- performDefaultAction({context, action: defaultAction})
200
+ enqueue.raise({
201
+ type: 'behavior action intends',
202
+ editor: event.editor,
203
+ actionIntends: [defaultAction],
204
+ })
201
205
  return
202
206
  }
203
207
 
@@ -216,7 +220,11 @@ export const editorMachine = setup({
216
220
  console.warn(
217
221
  `Unable to handle event ${event.type} due to missing selection`,
218
222
  )
219
- performDefaultAction({context, action: defaultAction})
223
+ enqueue.raise({
224
+ type: 'behavior action intends',
225
+ editor: event.editor,
226
+ actionIntends: [defaultAction],
227
+ })
220
228
  return
221
229
  }
222
230
 
@@ -264,7 +272,11 @@ export const editorMachine = setup({
264
272
  }
265
273
 
266
274
  if (!behaviorOverwritten) {
267
- performDefaultAction({context, action: defaultAction})
275
+ enqueue.raise({
276
+ type: 'behavior action intends',
277
+ editor: event.editor,
278
+ actionIntends: [defaultAction],
279
+ })
268
280
  }
269
281
  }),
270
282
  },
@@ -315,6 +327,22 @@ export const editorMachine = setup({
315
327
  })
316
328
  event.editor.onChange()
317
329
  },
330
+ enqueueActions(({context, event, enqueue}) => {
331
+ if (
332
+ event.actionIntends.some(
333
+ (actionIntend) => actionIntend.type === 'reselect',
334
+ )
335
+ ) {
336
+ enqueue.raise({
337
+ type: 'selection',
338
+ selection: toPortableTextRange(
339
+ event.editor.children,
340
+ event.editor.selection,
341
+ context.schema,
342
+ ),
343
+ })
344
+ }
345
+ }),
318
346
  ],
319
347
  },
320
348
  },
@@ -3,6 +3,30 @@ import type {EditorActor} from '../editor-machine'
3
3
 
4
4
  export function createWithEventListeners(editorActor: EditorActor) {
5
5
  return function withEventListeners(editor: Editor) {
6
+ editor.addMark = (mark) => {
7
+ editorActor.send({
8
+ type: 'behavior event',
9
+ behaviorEvent: {
10
+ type: 'decorator.add',
11
+ decorator: mark,
12
+ },
13
+ editor,
14
+ })
15
+ return
16
+ }
17
+
18
+ editor.removeMark = (mark) => {
19
+ editorActor.send({
20
+ type: 'behavior event',
21
+ behaviorEvent: {
22
+ type: 'decorator.remove',
23
+ decorator: mark,
24
+ },
25
+ editor,
26
+ })
27
+ return
28
+ }
29
+
6
30
  editor.deleteBackward = (unit) => {
7
31
  editorActor.send({
8
32
  type: 'behavior event',
@@ -38,6 +38,7 @@ import {
38
38
  } from '../../utils/weakMaps'
39
39
  import type {EditorActor} from '../editor-machine'
40
40
  import type {PortableTextEditor} from '../PortableTextEditor'
41
+ import {isDecoratorActive} from './createWithPortableTextMarkModel'
41
42
 
42
43
  const debug = debugWithName('API:editable')
43
44
 
@@ -57,7 +58,14 @@ export function createWithEditableAPI(
57
58
  ReactEditor.blur(editor)
58
59
  },
59
60
  toggleMark: (mark: string): void => {
60
- editor.pteToggleMark(mark)
61
+ editorActor.send({
62
+ type: 'behavior event',
63
+ behaviorEvent: {
64
+ type: 'decorator.toggle',
65
+ decorator: mark,
66
+ },
67
+ editor,
68
+ })
61
69
  },
62
70
  toggleList: (listStyle: string): void => {
63
71
  editor.pteToggleListItem(listStyle)
@@ -69,7 +77,7 @@ export function createWithEditableAPI(
69
77
  // Try/catch this, as Slate may error because the selection is currently wrong
70
78
  // TODO: catch only relevant error from Slate
71
79
  try {
72
- return editor.pteIsMarkActive(mark)
80
+ return isDecoratorActive({editor, decorator: mark})
73
81
  } catch (err) {
74
82
  console.warn(err)
75
83
  return false
@@ -7,6 +7,7 @@ import type {PortableTextSlateEditor} from '../../types/editor'
7
7
  import type {HotkeyOptions} from '../../types/options'
8
8
  import type {SlateTextBlock, VoidElement} from '../../types/slate'
9
9
  import {debugWithName} from '../../utils/debug'
10
+ import type {EditorActor} from '../editor-machine'
10
11
  import type {PortableTextEditor} from '../PortableTextEditor'
11
12
 
12
13
  const debug = debugWithName('plugin:withHotKeys')
@@ -26,6 +27,7 @@ const DEFAULT_HOTKEYS: HotkeyOptions = {
26
27
  *
27
28
  */
28
29
  export function createWithHotkeys(
30
+ editorActor: EditorActor,
29
31
  portableTextEditor: PortableTextEditor,
30
32
  hotkeysFromOptions?: HotkeyOptions,
31
33
  ): (editor: PortableTextSlateEditor & ReactEditor) => any {
@@ -46,7 +48,14 @@ export function createWithHotkeys(
46
48
  if (possibleMark) {
47
49
  const mark = possibleMark[hotkey]
48
50
  debug(`HotKey ${hotkey} to toggle ${mark}`)
49
- editor.pteToggleMark(mark)
51
+ editorActor.send({
52
+ type: 'behavior event',
53
+ behaviorEvent: {
54
+ type: 'decorator.toggle',
55
+ decorator: mark,
56
+ },
57
+ editor,
58
+ })
50
59
  }
51
60
  }
52
61
  }