@portabletext/editor 1.1.3 → 1.1.5

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 (32) hide show
  1. package/lib/index.d.mts +228 -30
  2. package/lib/index.d.ts +228 -30
  3. package/lib/index.esm.js +169 -112
  4. package/lib/index.esm.js.map +1 -1
  5. package/lib/index.js +169 -112
  6. package/lib/index.js.map +1 -1
  7. package/lib/index.mjs +169 -112
  8. package/lib/index.mjs.map +1 -1
  9. package/package.json +24 -19
  10. package/src/editor/Editable.tsx +6 -11
  11. package/src/editor/PortableTextEditor.tsx +26 -30
  12. package/src/editor/__tests__/self-solving.test.tsx +1 -1
  13. package/src/editor/components/SlateContainer.tsx +2 -13
  14. package/src/editor/components/Synchronizer.tsx +0 -3
  15. package/src/editor/editor-machine.ts +7 -2
  16. package/src/editor/hooks/useSyncValue.ts +3 -5
  17. package/src/editor/key-generator.ts +6 -0
  18. package/src/editor/plugins/createWithEditableAPI.ts +8 -5
  19. package/src/editor/plugins/createWithHotKeys.ts +1 -0
  20. package/src/editor/plugins/createWithInsertBreak.ts +57 -9
  21. package/src/editor/plugins/createWithInsertData.ts +7 -4
  22. package/src/editor/plugins/createWithObjectKeys.ts +12 -5
  23. package/src/editor/plugins/createWithPatches.ts +0 -1
  24. package/src/editor/plugins/createWithPortableTextMarkModel.ts +106 -23
  25. package/src/editor/plugins/createWithSchemaTypes.ts +3 -4
  26. package/src/editor/plugins/createWithUtils.ts +5 -4
  27. package/src/editor/plugins/index.ts +5 -13
  28. package/src/index.ts +2 -2
  29. package/src/types/options.ts +0 -1
  30. package/src/utils/__tests__/operationToPatches.test.ts +0 -2
  31. package/src/utils/__tests__/patchToOperations.test.ts +1 -2
  32. package/src/editor/hooks/usePortableTextEditorKeyGenerator.ts +0 -27
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import {isPortableTextBlock, isPortableTextSpan} from '@portabletext/toolkit'
8
- import type {PortableTextObject} from '@sanity/types'
8
+ import type {PortableTextObject, PortableTextSpan} from '@sanity/types'
9
9
  import {isEqual, uniq} from 'lodash'
10
10
  import {Editor, Element, Node, Path, Range, Text, Transforms} from 'slate'
11
11
  import type {
@@ -23,7 +23,6 @@ const debug = debugWithName('plugin:withPortableTextMarkModel')
23
23
  export function createWithPortableTextMarkModel(
24
24
  editorActor: EditorActor,
25
25
  types: PortableTextMemberSchemaTypes,
26
- keyGenerator: () => string,
27
26
  ): (editor: PortableTextSlateEditor) => PortableTextSlateEditor {
28
27
  return function withPortableTextMarkModel(editor: PortableTextSlateEditor) {
29
28
  const {apply, normalizeNode} = editor
@@ -288,14 +287,20 @@ export function createWithPortableTextMarkModel(
288
287
  : false
289
288
 
290
289
  if (selection && collapsedSelection) {
291
- const [span] = Array.from(
292
- Editor.nodes(editor, {
293
- mode: 'lowest',
294
- at: selection.focus,
295
- match: (n) => editor.isTextSpan(n),
296
- voids: false,
297
- }),
298
- )[0]
290
+ const [_block, blockPath] = Editor.node(editor, selection, {
291
+ depth: 1,
292
+ })
293
+
294
+ const [span, spanPath] =
295
+ Array.from(
296
+ Editor.nodes(editor, {
297
+ mode: 'lowest',
298
+ at: selection.focus,
299
+ match: (n) => editor.isTextSpan(n),
300
+ voids: false,
301
+ }),
302
+ )[0] ?? ([undefined, undefined] as const)
303
+
299
304
  const marks = span.marks ?? []
300
305
  const marksWithoutAnnotations = marks.filter((mark) =>
301
306
  decorators.includes(mark),
@@ -303,19 +308,97 @@ export function createWithPortableTextMarkModel(
303
308
  const spanHasAnnotations =
304
309
  marks.length > marksWithoutAnnotations.length
305
310
 
306
- if (
307
- spanHasAnnotations &&
308
- (selection.anchor.offset === 0 ||
309
- span.text.length === selection.focus.offset)
310
- ) {
311
- Transforms.insertNodes(editor, {
312
- _type: 'span',
313
- _key: keyGenerator(),
314
- text: op.text,
315
- marks: marksWithoutAnnotations,
316
- })
317
- debug('Inserting text at end of annotation')
318
- return
311
+ const spanIsEmpty = span.text.length === 0
312
+
313
+ const atTheBeginningOfSpan = selection.anchor.offset === 0
314
+ const atTheEndOfSpan = selection.anchor.offset === span.text.length
315
+
316
+ let previousSpan: PortableTextSpan | undefined
317
+ let nextSpan: PortableTextSpan | undefined
318
+
319
+ for (const [child, childPath] of Node.children(editor, blockPath, {
320
+ reverse: true,
321
+ })) {
322
+ if (!editor.isTextSpan(child)) {
323
+ continue
324
+ }
325
+
326
+ if (Path.isBefore(childPath, spanPath)) {
327
+ previousSpan = child
328
+ break
329
+ }
330
+ }
331
+
332
+ for (const [child, childPath] of Node.children(editor, blockPath)) {
333
+ if (!editor.isTextSpan(child)) {
334
+ continue
335
+ }
336
+
337
+ if (Path.isAfter(childPath, spanPath)) {
338
+ nextSpan = child
339
+ break
340
+ }
341
+ }
342
+
343
+ const previousSpanHasSameAnnotation = previousSpan
344
+ ? previousSpan.marks?.some(
345
+ (mark) => !decorators.includes(mark) && marks.includes(mark),
346
+ )
347
+ : false
348
+ const previousSpanHasSameMarks = previousSpan
349
+ ? previousSpan.marks?.every((mark) => marks.includes(mark))
350
+ : false
351
+ const nextSpanHasSameAnnotation = nextSpan
352
+ ? nextSpan.marks?.some(
353
+ (mark) => !decorators.includes(mark) && marks.includes(mark),
354
+ )
355
+ : false
356
+ const nextSpanHasSameMarks = nextSpan
357
+ ? nextSpan.marks?.every((mark) => marks.includes(mark))
358
+ : false
359
+
360
+ if (spanHasAnnotations && !spanIsEmpty) {
361
+ if (atTheBeginningOfSpan) {
362
+ if (previousSpanHasSameMarks) {
363
+ Transforms.insertNodes(editor, {
364
+ _type: 'span',
365
+ _key: editorActor.getSnapshot().context.keyGenerator(),
366
+ text: op.text,
367
+ marks: previousSpan?.marks ?? [],
368
+ })
369
+ } else if (previousSpanHasSameAnnotation) {
370
+ apply(op)
371
+ } else {
372
+ Transforms.insertNodes(editor, {
373
+ _type: 'span',
374
+ _key: editorActor.getSnapshot().context.keyGenerator(),
375
+ text: op.text,
376
+ marks: [],
377
+ })
378
+ }
379
+ return
380
+ }
381
+
382
+ if (atTheEndOfSpan) {
383
+ if (nextSpanHasSameMarks) {
384
+ Transforms.insertNodes(editor, {
385
+ _type: 'span',
386
+ _key: editorActor.getSnapshot().context.keyGenerator(),
387
+ text: op.text,
388
+ marks: nextSpan?.marks ?? [],
389
+ })
390
+ } else if (nextSpanHasSameAnnotation) {
391
+ apply(op)
392
+ } else {
393
+ Transforms.insertNodes(editor, {
394
+ _type: 'span',
395
+ _key: editorActor.getSnapshot().context.keyGenerator(),
396
+ text: op.text,
397
+ marks: [],
398
+ })
399
+ }
400
+ return
401
+ }
319
402
  }
320
403
  }
321
404
  }
@@ -22,11 +22,9 @@ const debug = debugWithName('plugin:withSchemaTypes')
22
22
  export function createWithSchemaTypes({
23
23
  editorActor,
24
24
  schemaTypes,
25
- keyGenerator,
26
25
  }: {
27
26
  editorActor: EditorActor
28
27
  schemaTypes: PortableTextMemberSchemaTypes
29
- keyGenerator: () => string
30
28
  }) {
31
29
  return function withSchemaTypes(
32
30
  editor: PortableTextSlateEditor,
@@ -73,7 +71,8 @@ export function createWithSchemaTypes({
73
71
  if (node._type === undefined && path.length === 2) {
74
72
  debug('Setting span type on text node without a type')
75
73
  const span = node as PortableTextSpan
76
- const key = span._key || keyGenerator()
74
+ const key =
75
+ span._key || editorActor.getSnapshot().context.keyGenerator()
77
76
  editorActor.send({type: 'normalizing'})
78
77
  Transforms.setNodes(
79
78
  editor,
@@ -87,7 +86,7 @@ export function createWithSchemaTypes({
87
86
  // catches cases when the children are missing keys but excludes it when the normalize is running the node as the editor object
88
87
  if (node._key === undefined && (path.length === 1 || path.length === 2)) {
89
88
  debug('Setting missing key on child node without a key')
90
- const key = keyGenerator()
89
+ const key = editorActor.getSnapshot().context.keyGenerator()
91
90
  editorActor.send({type: 'normalizing'})
92
91
  Transforms.setNodes(editor, {_key: key}, {at: path})
93
92
  editorActor.send({type: 'done normalizing'})
@@ -5,13 +5,14 @@ import type {
5
5
  } from '../../types/editor'
6
6
  import {debugWithName} from '../../utils/debug'
7
7
  import {toSlateValue} from '../../utils/values'
8
+ import type {EditorActor} from '../editor-machine'
8
9
  import type {PortableTextEditor} from '../PortableTextEditor'
9
10
 
10
11
  const debug = debugWithName('plugin:withUtils')
11
12
 
12
13
  interface Options {
14
+ editorActor: EditorActor
13
15
  schemaTypes: PortableTextMemberSchemaTypes
14
- keyGenerator: () => string
15
16
  portableTextEditor: PortableTextEditor
16
17
  }
17
18
  /**
@@ -19,8 +20,8 @@ interface Options {
19
20
  *
20
21
  */
21
22
  export function createWithUtils({
23
+ editorActor,
22
24
  schemaTypes,
23
- keyGenerator,
24
25
  portableTextEditor,
25
26
  }: Options) {
26
27
  return function withUtils(
@@ -79,13 +80,13 @@ export function createWithUtils({
79
80
  [
80
81
  {
81
82
  _type: schemaTypes.block.name,
82
- _key: keyGenerator(),
83
+ _key: editorActor.getSnapshot().context.keyGenerator(),
83
84
  style: schemaTypes.styles[0].value || 'normal',
84
85
  markDefs: [],
85
86
  children: [
86
87
  {
87
88
  _type: 'span',
88
- _key: keyGenerator(),
89
+ _key: editorActor.getSnapshot().context.keyGenerator(),
89
90
  text: '',
90
91
  marks: options.decorators.filter((decorator) =>
91
92
  schemaTypes.decorators.find(({value}) => value === decorator),
@@ -47,8 +47,7 @@ export const withPlugins = <T extends Editor>(
47
47
  options: createEditorOptions,
48
48
  ): {editor: PortableTextSlateEditor; subscribe: () => () => void} => {
49
49
  const e = editor as T & PortableTextSlateEditor
50
- const {keyGenerator, portableTextEditor, patches$, readOnly, maxBlocks} =
51
- options
50
+ const {portableTextEditor, patches$, readOnly, maxBlocks} = options
52
51
  const {editorActor, schemaTypes} = portableTextEditor
53
52
  e.subscriptions = []
54
53
  if (e.destroy) {
@@ -63,24 +62,18 @@ export const withPlugins = <T extends Editor>(
63
62
  })
64
63
  }
65
64
  const operationToPatches = createOperationToPatches(schemaTypes)
66
- const withObjectKeys = createWithObjectKeys(
67
- editorActor,
68
- schemaTypes,
69
- keyGenerator,
70
- )
65
+ const withObjectKeys = createWithObjectKeys(editorActor, schemaTypes)
71
66
  const withSchemaTypes = createWithSchemaTypes({
72
67
  editorActor,
73
68
  schemaTypes,
74
- keyGenerator,
75
69
  })
76
70
  const withEditableAPI = createWithEditableAPI(
71
+ editorActor,
77
72
  portableTextEditor,
78
73
  schemaTypes,
79
- keyGenerator,
80
74
  )
81
75
  const withPatches = createWithPatches({
82
76
  editorActor,
83
- keyGenerator,
84
77
  patches$,
85
78
  patchFunctions: operationToPatches,
86
79
  readOnly,
@@ -96,7 +89,6 @@ export const withPlugins = <T extends Editor>(
96
89
  const withPortableTextMarkModel = createWithPortableTextMarkModel(
97
90
  editorActor,
98
91
  schemaTypes,
99
- keyGenerator,
100
92
  )
101
93
  const withPortableTextBlockStyle = createWithPortableTextBlockStyle(
102
94
  editorActor,
@@ -105,10 +97,10 @@ export const withPlugins = <T extends Editor>(
105
97
 
106
98
  const withPlaceholderBlock = createWithPlaceholderBlock()
107
99
 
108
- const withInsertBreak = createWithInsertBreak(schemaTypes, keyGenerator)
100
+ const withInsertBreak = createWithInsertBreak(editorActor, schemaTypes)
109
101
 
110
102
  const withUtils = createWithUtils({
111
- keyGenerator,
103
+ editorActor,
112
104
  schemaTypes,
113
105
  portableTextEditor,
114
106
  })
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- export {type Patch} from '@portabletext/patches'
1
+ export type {Patch} from '@portabletext/patches'
2
2
  export {PortableTextEditable} from './editor/Editable'
3
3
  export type {PortableTextEditableProps} from './editor/Editable'
4
4
  export {
@@ -8,7 +8,7 @@ export {
8
8
  type PatchEvent,
9
9
  } from './editor/editor-machine'
10
10
  export {usePortableTextEditor} from './editor/hooks/usePortableTextEditor'
11
- export {defaultKeyGenerator as keyGenerator} from './editor/hooks/usePortableTextEditorKeyGenerator'
11
+ export {defaultKeyGenerator as keyGenerator} from './editor/key-generator'
12
12
  export {usePortableTextEditorSelection} from './editor/hooks/usePortableTextEditorSelection'
13
13
  export {PortableTextEditor} from './editor/PortableTextEditor'
14
14
  export type {PortableTextEditorProps} from './editor/PortableTextEditor'
@@ -6,7 +6,6 @@ import type {PatchObservable} from './editor'
6
6
  * @internal
7
7
  */
8
8
  export type createEditorOptions = {
9
- keyGenerator: () => string
10
9
  patches$?: PatchObservable
11
10
  portableTextEditor: PortableTextEditor
12
11
  readOnly: boolean
@@ -3,7 +3,6 @@ import {createEditor, type Descendant} from 'slate'
3
3
  import {beforeEach, describe, expect, it} from 'vitest'
4
4
  import {PortableTextEditor, type PortableTextEditorProps} from '../..'
5
5
  import {schemaType} from '../../editor/__tests__/PortableTextEditorTester'
6
- import {defaultKeyGenerator} from '../../editor/hooks/usePortableTextEditorKeyGenerator'
7
6
  import {withPlugins} from '../../editor/plugins'
8
7
  import {getPortableTextMemberSchemaTypes} from '../getPortableTextMemberSchemaTypes'
9
8
  import {createOperationToPatches} from '../operationToPatches'
@@ -16,7 +15,6 @@ const {editor} = withPlugins(createEditor(), {
16
15
  portableTextEditor: new PortableTextEditor({
17
16
  schemaType,
18
17
  } as PortableTextEditorProps),
19
- keyGenerator: defaultKeyGenerator,
20
18
  readOnly: false,
21
19
  })
22
20
 
@@ -2,7 +2,7 @@ import type {Patch} from '@portabletext/patches'
2
2
  import {noop} from 'lodash'
3
3
  import {createEditor, type Descendant} from 'slate'
4
4
  import {beforeEach, describe, expect, it} from 'vitest'
5
- import {keyGenerator, PortableTextEditor} from '../..'
5
+ import {PortableTextEditor} from '../..'
6
6
  import {schemaType} from '../../editor/__tests__/PortableTextEditorTester'
7
7
  import {withPlugins} from '../../editor/plugins'
8
8
  import {createApplyPatch} from '../applyPatch'
@@ -16,7 +16,6 @@ const portableTextEditor = new PortableTextEditor({schemaType, onChange: noop})
16
16
 
17
17
  const {editor} = withPlugins(createEditor(), {
18
18
  portableTextEditor,
19
- keyGenerator,
20
19
  readOnly: false,
21
20
  })
22
21
 
@@ -1,27 +0,0 @@
1
- import {randomKey} from '@sanity/util/content'
2
- import {createContext, useContext} from 'react'
3
-
4
- /**
5
- * @public
6
- */
7
- export const defaultKeyGenerator = (): string => randomKey(12)
8
-
9
- /**
10
- * A React context for sharing the editor's keyGenerator.
11
- */
12
- export const PortableTextEditorKeyGeneratorContext =
13
- createContext<() => string>(defaultKeyGenerator)
14
-
15
- /**
16
- * Get the current editor selection from the React context.
17
- */
18
- export const usePortableTextEditorKeyGenerator = (): (() => string) => {
19
- const keyGenerator = useContext(PortableTextEditorKeyGeneratorContext)
20
-
21
- if (keyGenerator === undefined) {
22
- throw new Error(
23
- `The \`usePortableTextEditorKeyGenerator\` hook must be used inside the <PortableTextEditor> component's context.`,
24
- )
25
- }
26
- return keyGenerator
27
- }