@portabletext/editor 1.32.0 → 1.33.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 (69) hide show
  1. package/lib/_chunks-cjs/behavior.core.cjs +4 -4
  2. package/lib/_chunks-cjs/behavior.core.cjs.map +1 -1
  3. package/lib/_chunks-cjs/behavior.markdown.cjs +19 -11
  4. package/lib/_chunks-cjs/behavior.markdown.cjs.map +1 -1
  5. package/lib/_chunks-cjs/plugin.event-listener.cjs +127 -88
  6. package/lib/_chunks-cjs/plugin.event-listener.cjs.map +1 -1
  7. package/lib/_chunks-cjs/selector.get-trimmed-selection.cjs +97 -0
  8. package/lib/_chunks-cjs/selector.get-trimmed-selection.cjs.map +1 -0
  9. package/lib/_chunks-cjs/{parse-blocks.cjs → util.block-offsets-to-selection.cjs} +21 -2
  10. package/lib/_chunks-cjs/util.block-offsets-to-selection.cjs.map +1 -0
  11. package/lib/_chunks-cjs/util.reverse-selection.cjs +11 -0
  12. package/lib/_chunks-cjs/util.reverse-selection.cjs.map +1 -1
  13. package/lib/_chunks-es/behavior.core.js +1 -1
  14. package/lib/_chunks-es/behavior.core.js.map +1 -1
  15. package/lib/_chunks-es/behavior.markdown.js +18 -11
  16. package/lib/_chunks-es/behavior.markdown.js.map +1 -1
  17. package/lib/_chunks-es/plugin.event-listener.js +127 -87
  18. package/lib/_chunks-es/plugin.event-listener.js.map +1 -1
  19. package/lib/_chunks-es/selector.get-trimmed-selection.js +100 -0
  20. package/lib/_chunks-es/selector.get-trimmed-selection.js.map +1 -0
  21. package/lib/_chunks-es/{parse-blocks.js → util.block-offsets-to-selection.js} +21 -1
  22. package/lib/_chunks-es/util.block-offsets-to-selection.js.map +1 -0
  23. package/lib/_chunks-es/util.reverse-selection.js +11 -0
  24. package/lib/_chunks-es/util.reverse-selection.js.map +1 -1
  25. package/lib/behaviors/index.d.cts +1 -0
  26. package/lib/behaviors/index.d.ts +1 -0
  27. package/lib/index.d.cts +60 -0
  28. package/lib/index.d.ts +60 -0
  29. package/lib/plugins/index.cjs +295 -3
  30. package/lib/plugins/index.cjs.map +1 -1
  31. package/lib/plugins/index.d.cts +74 -1
  32. package/lib/plugins/index.d.ts +74 -1
  33. package/lib/plugins/index.js +300 -4
  34. package/lib/plugins/index.js.map +1 -1
  35. package/lib/selectors/index.cjs +51 -1
  36. package/lib/selectors/index.cjs.map +1 -1
  37. package/lib/selectors/index.d.cts +67 -0
  38. package/lib/selectors/index.d.ts +67 -0
  39. package/lib/selectors/index.js +53 -2
  40. package/lib/selectors/index.js.map +1 -1
  41. package/lib/utils/index.cjs +5 -4
  42. package/lib/utils/index.cjs.map +1 -1
  43. package/lib/utils/index.d.cts +16 -0
  44. package/lib/utils/index.d.ts +16 -0
  45. package/lib/utils/index.js +4 -3
  46. package/package.json +2 -2
  47. package/src/behavior-actions/behavior.action.decorator.add.ts +161 -0
  48. package/src/behavior-actions/behavior.action.delete.text.ts +54 -0
  49. package/src/behavior-actions/behavior.actions.ts +5 -43
  50. package/src/behaviors/behavior.markdown-emphasis.ts +395 -0
  51. package/src/behaviors/behavior.markdown.ts +11 -4
  52. package/src/behaviors/behavior.types.ts +1 -0
  53. package/src/editor/plugins/createWithPortableTextMarkModel.ts +2 -97
  54. package/src/plugins/plugin.markdown.tsx +11 -1
  55. package/src/selectors/index.ts +5 -0
  56. package/src/selectors/selector.get-anchor-block.ts +22 -0
  57. package/src/selectors/selector.get-anchor-child.ts +36 -0
  58. package/src/selectors/selector.get-anchor-span.ts +18 -0
  59. package/src/selectors/selector.get-anchor-text-block.ts +20 -0
  60. package/src/selectors/selector.get-trimmed-selection.test.ts +658 -0
  61. package/src/selectors/selector.get-trimmed-selection.ts +175 -0
  62. package/src/utils/index.ts +1 -0
  63. package/src/utils/util.block-offsets-to-selection.ts +36 -0
  64. package/lib/_chunks-cjs/parse-blocks.cjs.map +0 -1
  65. package/lib/_chunks-cjs/util.is-empty-text-block.cjs +0 -14
  66. package/lib/_chunks-cjs/util.is-empty-text-block.cjs.map +0 -1
  67. package/lib/_chunks-es/parse-blocks.js.map +0 -1
  68. package/lib/_chunks-es/util.is-empty-text-block.js +0 -15
  69. package/lib/_chunks-es/util.is-empty-text-block.js.map +0 -1
@@ -8,6 +8,7 @@ import {isPortableTextBlock, isPortableTextSpan} from '@portabletext/toolkit'
8
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
+ import {decoratorAddActionImplementation} from '../../behavior-actions/behavior.action.decorator.add'
11
12
  import type {BehaviorActionImplementation} from '../../behavior-actions/behavior.actions'
12
13
  import {debugWithName} from '../../internal-utils/debug'
13
14
  import {getNextSpan, getPreviousSpan} from '../../internal-utils/sibling-utils'
@@ -657,102 +658,6 @@ export function createWithPortableTextMarkModel(
657
658
  }
658
659
  }
659
660
 
660
- export const addDecoratorActionImplementation: BehaviorActionImplementation<
661
- 'decorator.add'
662
- > = ({action}) => {
663
- const editor = action.editor
664
- const mark = action.decorator
665
-
666
- if (editor.selection) {
667
- if (Range.isExpanded(editor.selection)) {
668
- // Split if needed
669
- Transforms.setNodes(
670
- editor,
671
- {},
672
- {match: Text.isText, split: true, hanging: true},
673
- )
674
- // Use new selection
675
- const splitTextNodes = Range.isRange(editor.selection)
676
- ? [
677
- ...Editor.nodes(editor, {
678
- at: editor.selection,
679
- match: Text.isText,
680
- }),
681
- ]
682
- : []
683
- const shouldRemoveMark =
684
- splitTextNodes.length > 1 &&
685
- splitTextNodes.every((node) => node[0].marks?.includes(mark))
686
-
687
- if (shouldRemoveMark) {
688
- editor.removeMark(mark)
689
- } else {
690
- splitTextNodes.forEach(([node, path]) => {
691
- const marks = [
692
- ...(Array.isArray(node.marks) ? node.marks : []).filter(
693
- (eMark: string) => eMark !== mark,
694
- ),
695
- mark,
696
- ]
697
- Transforms.setNodes(
698
- editor,
699
- {marks},
700
- {at: path, match: Text.isText, split: true, hanging: true},
701
- )
702
- })
703
- }
704
- } else {
705
- const [block, blockPath] = Editor.node(editor, editor.selection, {
706
- depth: 1,
707
- })
708
- const lonelyEmptySpan =
709
- editor.isTextBlock(block) &&
710
- block.children.length === 1 &&
711
- editor.isTextSpan(block.children[0]) &&
712
- block.children[0].text === ''
713
- ? block.children[0]
714
- : undefined
715
-
716
- if (lonelyEmptySpan) {
717
- const existingMarks = lonelyEmptySpan.marks ?? []
718
- const existingMarksWithoutDecorator = existingMarks.filter(
719
- (existingMark) => existingMark !== mark,
720
- )
721
-
722
- Transforms.setNodes(
723
- editor,
724
- {
725
- marks:
726
- existingMarks.length === existingMarksWithoutDecorator.length
727
- ? [...existingMarks, mark]
728
- : existingMarksWithoutDecorator,
729
- },
730
- {
731
- at: blockPath,
732
- match: (node) => editor.isTextSpan(node),
733
- },
734
- )
735
- } else {
736
- const existingMarks: string[] =
737
- {
738
- ...(Editor.marks(editor) || {}),
739
- }.marks || []
740
- const marks = {
741
- ...(Editor.marks(editor) || {}),
742
- marks: [...existingMarks, mark],
743
- }
744
- editor.marks = marks as Text
745
- }
746
- }
747
-
748
- if (editor.selection) {
749
- // Reselect
750
- const selection = editor.selection
751
- editor.selection = {...selection}
752
- }
753
- }
754
- }
755
-
756
661
  export const removeDecoratorActionImplementation: BehaviorActionImplementation<
757
662
  'decorator.remove'
758
663
  > = ({action}) => {
@@ -892,7 +797,7 @@ export const toggleDecoratorActionImplementation: BehaviorActionImplementation<
892
797
  },
893
798
  })
894
799
  } else {
895
- addDecoratorActionImplementation({
800
+ decoratorAddActionImplementation({
896
801
  context,
897
802
  action: {
898
803
  type: 'decorator.add',
@@ -3,12 +3,17 @@ import {
3
3
  createMarkdownBehaviors,
4
4
  type MarkdownBehaviorsConfig,
5
5
  } from '../behaviors/behavior.markdown'
6
+ import {
7
+ useMarkdownEmphasisBehaviors,
8
+ type MarkdownEmphasisBehaviorsConfig,
9
+ } from '../behaviors/behavior.markdown-emphasis'
6
10
  import {useEditor} from '../editor/editor-provider'
7
11
 
8
12
  /**
9
13
  * @beta
10
14
  */
11
- export type MarkdownPluginConfig = MarkdownBehaviorsConfig
15
+ export type MarkdownPluginConfig = MarkdownBehaviorsConfig &
16
+ MarkdownEmphasisBehaviorsConfig
12
17
 
13
18
  /**
14
19
  * @beta
@@ -25,6 +30,10 @@ export type MarkdownPluginConfig = MarkdownBehaviorsConfig
25
30
  * <EditorProvider>
26
31
  * <MarkdownPlugin
27
32
  * config={{
33
+ * boldDecorator: ({schema}) =>
34
+ * schema.decorators.find((decorator) => decorator.value === 'strong')?.value,
35
+ * italicDecorator: ({schema}) =>
36
+ * schema.decorators.find((decorator) => decorator.value === 'em')?.value,
28
37
  * horizontalRuleObject: ({schema}) => {
29
38
  * const name = schema.blockObjects.find(
30
39
  * (object) => object.name === 'break',
@@ -51,6 +60,7 @@ export type MarkdownPluginConfig = MarkdownBehaviorsConfig
51
60
  */
52
61
  export function MarkdownPlugin(props: {config: MarkdownPluginConfig}) {
53
62
  const editor = useEditor()
63
+ useMarkdownEmphasisBehaviors({config: props.config})
54
64
 
55
65
  useEffect(() => {
56
66
  const behaviors = createMarkdownBehaviors(props.config)
@@ -9,6 +9,10 @@ export type {
9
9
  export {getActiveAnnotations} from './selector.get-active-annotations'
10
10
  export {getActiveListItem} from './selector.get-active-list-item'
11
11
  export {getActiveStyle} from './selector.get-active-style'
12
+ export {getAnchorBlock} from './selector.get-anchor-block'
13
+ export {getAnchorChild} from './selector.get-anchor-child'
14
+ export {getAnchorSpan} from './selector.get-anchor-span'
15
+ export {getAnchorTextBlock} from './selector.get-anchor-text-block'
12
16
  export {getBlockOffsets} from './selector.get-block-offsets'
13
17
  export {getCaretWordSelection} from './selector.get-caret-word-selection'
14
18
  export {getNextInlineObject} from './selector.get-next-inline-object'
@@ -20,6 +24,7 @@ export {getSelectionEndPoint} from './selector.get-selection-end-point'
20
24
  export {getSelectionStartPoint} from './selector.get-selection-start-point'
21
25
  export {getSelectionText} from './selector.get-selection-text'
22
26
  export {getBlockTextBefore} from './selector.get-text-before'
27
+ export {getTrimmedSelection} from './selector.get-trimmed-selection'
23
28
  export {getValue} from './selector.get-value'
24
29
  export {isActiveAnnotation} from './selector.is-active-annotation'
25
30
  export {isActiveDecorator} from './selector.is-active-decorator'
@@ -0,0 +1,22 @@
1
+ import type {KeyedSegment, PortableTextBlock} from '@sanity/types'
2
+ import type {EditorSelector} from '../editor/editor-selector'
3
+ import {isKeyedSegment} from '../utils'
4
+
5
+ /**
6
+ * @public
7
+ */
8
+ export const getAnchorBlock: EditorSelector<
9
+ {node: PortableTextBlock; path: [KeyedSegment]} | undefined
10
+ > = ({context}) => {
11
+ const key = context.selection
12
+ ? isKeyedSegment(context.selection.anchor.path[0])
13
+ ? context.selection.anchor.path[0]._key
14
+ : undefined
15
+ : undefined
16
+
17
+ const node = key
18
+ ? context.value.find((block) => block._key === key)
19
+ : undefined
20
+
21
+ return node && key ? {node, path: [{_key: key}]} : undefined
22
+ }
@@ -0,0 +1,36 @@
1
+ import type {KeyedSegment} from '@portabletext/patches'
2
+ import type {PortableTextObject, PortableTextSpan} from '@sanity/types'
3
+ import type {EditorSelector} from '../editor/editor-selector'
4
+ import {isKeyedSegment} from '../utils'
5
+ import {getAnchorTextBlock} from './selector.get-anchor-text-block'
6
+
7
+ /**
8
+ * @public
9
+ */
10
+ export const getAnchorChild: EditorSelector<
11
+ | {
12
+ node: PortableTextObject | PortableTextSpan
13
+ path: [KeyedSegment, 'children', KeyedSegment]
14
+ }
15
+ | undefined
16
+ > = ({context}) => {
17
+ const anchorBlock = getAnchorTextBlock({context})
18
+
19
+ if (!anchorBlock) {
20
+ return undefined
21
+ }
22
+
23
+ const key = context.selection
24
+ ? isKeyedSegment(context.selection.anchor.path[2])
25
+ ? context.selection.anchor.path[2]._key
26
+ : undefined
27
+ : undefined
28
+
29
+ const node = key
30
+ ? anchorBlock.node.children.find((span) => span._key === key)
31
+ : undefined
32
+
33
+ return node && key
34
+ ? {node, path: [...anchorBlock.path, 'children', {_key: key}]}
35
+ : undefined
36
+ }
@@ -0,0 +1,18 @@
1
+ import type {KeyedSegment} from '@portabletext/patches'
2
+ import {isPortableTextSpan, type PortableTextSpan} from '@sanity/types'
3
+ import type {EditorSelector} from '../editor/editor-selector'
4
+ import {getAnchorChild} from './selector.get-anchor-child'
5
+
6
+ /**
7
+ * @public
8
+ */
9
+ export const getAnchorSpan: EditorSelector<
10
+ | {node: PortableTextSpan; path: [KeyedSegment, 'children', KeyedSegment]}
11
+ | undefined
12
+ > = ({context}) => {
13
+ const anchorChild = getAnchorChild({context})
14
+
15
+ return anchorChild && isPortableTextSpan(anchorChild.node)
16
+ ? {node: anchorChild.node, path: anchorChild.path}
17
+ : undefined
18
+ }
@@ -0,0 +1,20 @@
1
+ import {
2
+ isPortableTextTextBlock,
3
+ type KeyedSegment,
4
+ type PortableTextTextBlock,
5
+ } from '@sanity/types'
6
+ import type {EditorSelector} from '../editor/editor-selector'
7
+ import {getAnchorBlock} from './selector.get-anchor-block'
8
+
9
+ /**
10
+ * @public
11
+ */
12
+ export const getAnchorTextBlock: EditorSelector<
13
+ {node: PortableTextTextBlock; path: [KeyedSegment]} | undefined
14
+ > = ({context}) => {
15
+ const anchorBlock = getAnchorBlock({context})
16
+
17
+ return anchorBlock && isPortableTextTextBlock(anchorBlock.node)
18
+ ? {node: anchorBlock.node, path: anchorBlock.path}
19
+ : undefined
20
+ }