@portabletext/editor 1.32.0 → 1.33.1
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/lib/_chunks-cjs/behavior.core.cjs +4 -4
- package/lib/_chunks-cjs/behavior.core.cjs.map +1 -1
- package/lib/_chunks-cjs/behavior.markdown.cjs +19 -11
- package/lib/_chunks-cjs/behavior.markdown.cjs.map +1 -1
- package/lib/_chunks-cjs/plugin.event-listener.cjs +127 -88
- package/lib/_chunks-cjs/plugin.event-listener.cjs.map +1 -1
- package/lib/_chunks-cjs/selector.get-trimmed-selection.cjs +97 -0
- package/lib/_chunks-cjs/selector.get-trimmed-selection.cjs.map +1 -0
- package/lib/_chunks-cjs/{parse-blocks.cjs → util.block-offsets-to-selection.cjs} +21 -2
- package/lib/_chunks-cjs/util.block-offsets-to-selection.cjs.map +1 -0
- package/lib/_chunks-cjs/util.reverse-selection.cjs +11 -0
- package/lib/_chunks-cjs/util.reverse-selection.cjs.map +1 -1
- package/lib/_chunks-es/behavior.core.js +1 -1
- package/lib/_chunks-es/behavior.core.js.map +1 -1
- package/lib/_chunks-es/behavior.markdown.js +18 -11
- package/lib/_chunks-es/behavior.markdown.js.map +1 -1
- package/lib/_chunks-es/plugin.event-listener.js +127 -87
- package/lib/_chunks-es/plugin.event-listener.js.map +1 -1
- package/lib/_chunks-es/selector.get-trimmed-selection.js +100 -0
- package/lib/_chunks-es/selector.get-trimmed-selection.js.map +1 -0
- package/lib/_chunks-es/{parse-blocks.js → util.block-offsets-to-selection.js} +21 -1
- package/lib/_chunks-es/util.block-offsets-to-selection.js.map +1 -0
- package/lib/_chunks-es/util.reverse-selection.js +11 -0
- package/lib/_chunks-es/util.reverse-selection.js.map +1 -1
- package/lib/behaviors/index.d.cts +1 -0
- package/lib/behaviors/index.d.ts +1 -0
- package/lib/index.d.cts +60 -0
- package/lib/index.d.ts +60 -0
- package/lib/plugins/index.cjs +302 -3
- package/lib/plugins/index.cjs.map +1 -1
- package/lib/plugins/index.d.cts +74 -1
- package/lib/plugins/index.d.ts +74 -1
- package/lib/plugins/index.js +307 -4
- package/lib/plugins/index.js.map +1 -1
- package/lib/selectors/index.cjs +51 -1
- package/lib/selectors/index.cjs.map +1 -1
- package/lib/selectors/index.d.cts +67 -0
- package/lib/selectors/index.d.ts +67 -0
- package/lib/selectors/index.js +53 -2
- package/lib/selectors/index.js.map +1 -1
- package/lib/utils/index.cjs +5 -4
- package/lib/utils/index.cjs.map +1 -1
- package/lib/utils/index.d.cts +16 -0
- package/lib/utils/index.d.ts +16 -0
- package/lib/utils/index.js +4 -3
- package/package.json +7 -7
- package/src/behavior-actions/behavior.action.decorator.add.ts +161 -0
- package/src/behavior-actions/behavior.action.delete.text.ts +54 -0
- package/src/behavior-actions/behavior.actions.ts +5 -43
- package/src/behaviors/behavior.markdown-emphasis.ts +392 -0
- package/src/behaviors/behavior.markdown.ts +11 -4
- package/src/behaviors/behavior.types.ts +1 -0
- package/src/editor/plugins/createWithPortableTextMarkModel.ts +2 -97
- package/src/internal-utils/get-text-to-emphasize.test.ts +36 -0
- package/src/internal-utils/get-text-to-emphasize.ts +18 -0
- package/src/plugins/plugin.markdown.tsx +11 -1
- package/src/selectors/index.ts +5 -0
- package/src/selectors/selector.get-anchor-block.ts +22 -0
- package/src/selectors/selector.get-anchor-child.ts +36 -0
- package/src/selectors/selector.get-anchor-span.ts +18 -0
- package/src/selectors/selector.get-anchor-text-block.ts +20 -0
- package/src/selectors/selector.get-trimmed-selection.test.ts +658 -0
- package/src/selectors/selector.get-trimmed-selection.ts +175 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/util.block-offsets-to-selection.ts +36 -0
- package/lib/_chunks-cjs/parse-blocks.cjs.map +0 -1
- package/lib/_chunks-cjs/util.is-empty-text-block.cjs +0 -14
- package/lib/_chunks-cjs/util.is-empty-text-block.cjs.map +0 -1
- package/lib/_chunks-es/parse-blocks.js.map +0 -1
- package/lib/_chunks-es/util.is-empty-text-block.js +0 -15
- package/lib/_chunks-es/util.is-empty-text-block.js.map +0 -1
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import {Editor, Range, Text, Transforms} from 'slate'
|
|
2
|
+
import {toPortableTextRange, toSlateRange} from '../internal-utils/ranges'
|
|
3
|
+
import {fromSlateValue} from '../internal-utils/values'
|
|
4
|
+
import {KEY_TO_VALUE_ELEMENT} from '../internal-utils/weakMaps'
|
|
5
|
+
import * as selectors from '../selectors'
|
|
6
|
+
import * as utils from '../utils'
|
|
7
|
+
import type {BehaviorActionImplementation} from './behavior.actions'
|
|
8
|
+
|
|
9
|
+
export const decoratorAddActionImplementation: BehaviorActionImplementation<
|
|
10
|
+
'decorator.add'
|
|
11
|
+
> = ({context, action}) => {
|
|
12
|
+
const editor = action.editor
|
|
13
|
+
const mark = action.decorator
|
|
14
|
+
const selection = action.selection
|
|
15
|
+
? (toSlateRange(action.selection, action.editor) ?? editor.selection)
|
|
16
|
+
: editor.selection
|
|
17
|
+
|
|
18
|
+
if (!selection) {
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const value = fromSlateValue(
|
|
23
|
+
editor.children,
|
|
24
|
+
context.schema.block.name,
|
|
25
|
+
KEY_TO_VALUE_ELEMENT.get(editor),
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
const editorSelection = toPortableTextRange(value, selection, context.schema)
|
|
29
|
+
const anchorOffset = editorSelection
|
|
30
|
+
? utils.spanSelectionPointToBlockOffset({
|
|
31
|
+
value,
|
|
32
|
+
selectionPoint: editorSelection.anchor,
|
|
33
|
+
})
|
|
34
|
+
: undefined
|
|
35
|
+
const focusOffset = editorSelection
|
|
36
|
+
? utils.spanSelectionPointToBlockOffset({
|
|
37
|
+
value,
|
|
38
|
+
selectionPoint: editorSelection.focus,
|
|
39
|
+
})
|
|
40
|
+
: undefined
|
|
41
|
+
|
|
42
|
+
if (!anchorOffset || !focusOffset) {
|
|
43
|
+
throw new Error('Unable to find anchor or focus offset')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (Range.isExpanded(selection)) {
|
|
47
|
+
// Split if needed
|
|
48
|
+
Transforms.setNodes(
|
|
49
|
+
editor,
|
|
50
|
+
{},
|
|
51
|
+
{at: selection, match: Text.isText, split: true, hanging: true},
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
// The value might have changed after splitting
|
|
55
|
+
const newValue = fromSlateValue(
|
|
56
|
+
editor.children,
|
|
57
|
+
context.schema.block.name,
|
|
58
|
+
KEY_TO_VALUE_ELEMENT.get(editor),
|
|
59
|
+
)
|
|
60
|
+
// We need to find the new selection from the original offsets because the
|
|
61
|
+
// split operation might have changed the value.
|
|
62
|
+
const newSelection = utils.blockOffsetsToSelection({
|
|
63
|
+
value: newValue,
|
|
64
|
+
offsets: {anchor: anchorOffset, focus: focusOffset},
|
|
65
|
+
backward: editorSelection?.backward,
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
const trimmedSelection = selectors.getTrimmedSelection({
|
|
69
|
+
context: {
|
|
70
|
+
activeDecorators: [],
|
|
71
|
+
converters: [],
|
|
72
|
+
keyGenerator: context.keyGenerator,
|
|
73
|
+
schema: context.schema,
|
|
74
|
+
selection: newSelection,
|
|
75
|
+
value: newValue,
|
|
76
|
+
},
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
if (!trimmedSelection) {
|
|
80
|
+
throw new Error('Unable to find trimmed selection')
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const newRange = toSlateRange(trimmedSelection, editor)
|
|
84
|
+
|
|
85
|
+
if (!newRange) {
|
|
86
|
+
throw new Error('Unable to find new selection')
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Use new selection to find nodes to decorate
|
|
90
|
+
const splitTextNodes = Range.isRange(newRange)
|
|
91
|
+
? [
|
|
92
|
+
...Editor.nodes(editor, {
|
|
93
|
+
at: newRange,
|
|
94
|
+
match: (node) => Text.isText(node),
|
|
95
|
+
}),
|
|
96
|
+
]
|
|
97
|
+
: []
|
|
98
|
+
|
|
99
|
+
for (const [node, path] of splitTextNodes) {
|
|
100
|
+
const marks = [
|
|
101
|
+
...(Array.isArray(node.marks) ? node.marks : []).filter(
|
|
102
|
+
(eMark: string) => eMark !== mark,
|
|
103
|
+
),
|
|
104
|
+
mark,
|
|
105
|
+
]
|
|
106
|
+
Transforms.setNodes(
|
|
107
|
+
editor,
|
|
108
|
+
{marks},
|
|
109
|
+
{at: path, match: Text.isText, split: true, hanging: true},
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
const [block, blockPath] = Editor.node(editor, selection, {
|
|
114
|
+
depth: 1,
|
|
115
|
+
})
|
|
116
|
+
const lonelyEmptySpan =
|
|
117
|
+
editor.isTextBlock(block) &&
|
|
118
|
+
block.children.length === 1 &&
|
|
119
|
+
editor.isTextSpan(block.children[0]) &&
|
|
120
|
+
block.children[0].text === ''
|
|
121
|
+
? block.children[0]
|
|
122
|
+
: undefined
|
|
123
|
+
|
|
124
|
+
if (lonelyEmptySpan) {
|
|
125
|
+
const existingMarks = lonelyEmptySpan.marks ?? []
|
|
126
|
+
const existingMarksWithoutDecorator = existingMarks.filter(
|
|
127
|
+
(existingMark) => existingMark !== mark,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
Transforms.setNodes(
|
|
131
|
+
editor,
|
|
132
|
+
{
|
|
133
|
+
marks:
|
|
134
|
+
existingMarks.length === existingMarksWithoutDecorator.length
|
|
135
|
+
? [...existingMarks, mark]
|
|
136
|
+
: existingMarksWithoutDecorator,
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
at: blockPath,
|
|
140
|
+
match: (node) => editor.isTextSpan(node),
|
|
141
|
+
},
|
|
142
|
+
)
|
|
143
|
+
} else {
|
|
144
|
+
const existingMarks: string[] =
|
|
145
|
+
{
|
|
146
|
+
...(Editor.marks(editor) || {}),
|
|
147
|
+
}.marks || []
|
|
148
|
+
const marks = {
|
|
149
|
+
...(Editor.marks(editor) || {}),
|
|
150
|
+
marks: [...existingMarks, mark],
|
|
151
|
+
}
|
|
152
|
+
editor.marks = marks as Text
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (editor.selection) {
|
|
157
|
+
// Reselect
|
|
158
|
+
const selection = editor.selection
|
|
159
|
+
editor.selection = {...selection}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import {Transforms} from 'slate'
|
|
2
|
+
import {toSlateRange} from '../internal-utils/ranges'
|
|
3
|
+
import {fromSlateValue} from '../internal-utils/values'
|
|
4
|
+
import {KEY_TO_VALUE_ELEMENT} from '../internal-utils/weakMaps'
|
|
5
|
+
import * as selectors from '../selectors'
|
|
6
|
+
import * as utils from '../utils'
|
|
7
|
+
import type {BehaviorActionImplementation} from './behavior.actions'
|
|
8
|
+
|
|
9
|
+
export const deleteTextActionImplementation: BehaviorActionImplementation<
|
|
10
|
+
'delete.text'
|
|
11
|
+
> = ({context, action}) => {
|
|
12
|
+
const value = fromSlateValue(
|
|
13
|
+
action.editor.children,
|
|
14
|
+
context.schema.block.name,
|
|
15
|
+
KEY_TO_VALUE_ELEMENT.get(action.editor),
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
const selection = utils.blockOffsetsToSelection({
|
|
19
|
+
value,
|
|
20
|
+
offsets: {
|
|
21
|
+
anchor: action.anchor,
|
|
22
|
+
focus: action.focus,
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
if (!selection) {
|
|
27
|
+
throw new Error('Unable to find selection from block offsets')
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const trimmedSelection = selectors.getTrimmedSelection({
|
|
31
|
+
context: {
|
|
32
|
+
converters: [],
|
|
33
|
+
schema: context.schema,
|
|
34
|
+
keyGenerator: context.keyGenerator,
|
|
35
|
+
activeDecorators: [],
|
|
36
|
+
value,
|
|
37
|
+
selection,
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
if (!trimmedSelection) {
|
|
42
|
+
throw new Error('Unable to find trimmed selection')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const range = toSlateRange(trimmedSelection, action.editor)
|
|
46
|
+
|
|
47
|
+
if (!range) {
|
|
48
|
+
throw new Error('Unable to find Slate range from trimmed selection')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
Transforms.delete(action.editor, {
|
|
52
|
+
at: range,
|
|
53
|
+
})
|
|
54
|
+
}
|
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
toggleAnnotationActionImplementation,
|
|
18
18
|
} from '../editor/plugins/createWithEditableAPI'
|
|
19
19
|
import {
|
|
20
|
-
addDecoratorActionImplementation,
|
|
21
20
|
removeDecoratorActionImplementation,
|
|
22
21
|
toggleDecoratorActionImplementation,
|
|
23
22
|
} from '../editor/plugins/createWithPortableTextMarkModel'
|
|
@@ -27,15 +26,15 @@ import {
|
|
|
27
26
|
} from '../editor/plugins/createWithUndoRedo'
|
|
28
27
|
import {toSlatePath} from '../internal-utils/paths'
|
|
29
28
|
import {toSlateRange} from '../internal-utils/ranges'
|
|
30
|
-
import {
|
|
31
|
-
import {KEY_TO_VALUE_ELEMENT} from '../internal-utils/weakMaps'
|
|
29
|
+
import {toSlateValue} from '../internal-utils/values'
|
|
32
30
|
import type {PickFromUnion} from '../type-utils'
|
|
33
|
-
import {blockOffsetToSpanSelectionPoint} from '../utils/util.block-offset'
|
|
34
31
|
import {insertBlock} from './behavior.action-utils.insert-block'
|
|
35
32
|
import {blockSetBehaviorActionImplementation} from './behavior.action.block.set'
|
|
36
33
|
import {blockUnsetBehaviorActionImplementation} from './behavior.action.block.unset'
|
|
37
34
|
import {dataTransferSetActionImplementation} from './behavior.action.data-transfer-set'
|
|
35
|
+
import {decoratorAddActionImplementation} from './behavior.action.decorator.add'
|
|
38
36
|
import {deleteActionImplementation} from './behavior.action.delete'
|
|
37
|
+
import {deleteTextActionImplementation} from './behavior.action.delete.text'
|
|
39
38
|
import {insertBlockObjectActionImplementation} from './behavior.action.insert-block-object'
|
|
40
39
|
import {insertBlocksActionImplementation} from './behavior.action.insert-blocks'
|
|
41
40
|
import {
|
|
@@ -88,7 +87,7 @@ const behaviorActionImplementations: BehaviorActionImplementations = {
|
|
|
88
87
|
ReactEditor.blur(action.editor)
|
|
89
88
|
},
|
|
90
89
|
'data transfer.set': dataTransferSetActionImplementation,
|
|
91
|
-
'decorator.add':
|
|
90
|
+
'decorator.add': decoratorAddActionImplementation,
|
|
92
91
|
'decorator.remove': removeDecoratorActionImplementation,
|
|
93
92
|
'decorator.toggle': toggleDecoratorActionImplementation,
|
|
94
93
|
'focus': ({action}) => {
|
|
@@ -119,44 +118,7 @@ const behaviorActionImplementations: BehaviorActionImplementations = {
|
|
|
119
118
|
at: range,
|
|
120
119
|
})
|
|
121
120
|
},
|
|
122
|
-
'delete.text':
|
|
123
|
-
const value = fromSlateValue(
|
|
124
|
-
action.editor.children,
|
|
125
|
-
context.schema.block.name,
|
|
126
|
-
KEY_TO_VALUE_ELEMENT.get(action.editor),
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
const anchor = blockOffsetToSpanSelectionPoint({
|
|
130
|
-
value,
|
|
131
|
-
blockOffset: action.anchor,
|
|
132
|
-
})
|
|
133
|
-
const focus = blockOffsetToSpanSelectionPoint({
|
|
134
|
-
value,
|
|
135
|
-
blockOffset: action.focus,
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
if (!anchor || !focus) {
|
|
139
|
-
console.error('Unable to find anchor or focus selection point')
|
|
140
|
-
return
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const range = toSlateRange(
|
|
144
|
-
{
|
|
145
|
-
anchor,
|
|
146
|
-
focus,
|
|
147
|
-
},
|
|
148
|
-
action.editor,
|
|
149
|
-
)
|
|
150
|
-
|
|
151
|
-
if (!range) {
|
|
152
|
-
console.error('Unable to find Slate range from selection points')
|
|
153
|
-
return
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
Transforms.delete(action.editor, {
|
|
157
|
-
at: range,
|
|
158
|
-
})
|
|
159
|
-
},
|
|
121
|
+
'delete.text': deleteTextActionImplementation,
|
|
160
122
|
'deserialization.failure': ({action}) => {
|
|
161
123
|
console.error(
|
|
162
124
|
`Deserialization of ${action.mimeType} failed with reason ${action.reason}`,
|
|
@@ -0,0 +1,392 @@
|
|
|
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 type {Editor} from '../editor/create-editor'
|
|
11
|
+
import {useEditor} from '../editor/editor-provider'
|
|
12
|
+
import {
|
|
13
|
+
getTextToBold,
|
|
14
|
+
getTextToItalic,
|
|
15
|
+
} from '../internal-utils/get-text-to-emphasize'
|
|
16
|
+
import type {EditorSchema} from '../selectors'
|
|
17
|
+
import * as selectors from '../selectors'
|
|
18
|
+
import * as utils from '../utils'
|
|
19
|
+
import {defineBehavior} from './behavior.types'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @beta
|
|
23
|
+
*/
|
|
24
|
+
export type MarkdownEmphasisBehaviorsConfig = {
|
|
25
|
+
boldDecorator?: ({schema}: {schema: EditorSchema}) => string | undefined
|
|
26
|
+
italicDecorator?: ({schema}: {schema: EditorSchema}) => string | undefined
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @beta
|
|
31
|
+
*/
|
|
32
|
+
export function useMarkdownEmphasisBehaviors(props: {
|
|
33
|
+
config: MarkdownEmphasisBehaviorsConfig
|
|
34
|
+
}) {
|
|
35
|
+
const editor = useEditor()
|
|
36
|
+
|
|
37
|
+
useActorRef(emphasisMachine, {
|
|
38
|
+
input: {
|
|
39
|
+
editor,
|
|
40
|
+
boldDecorator: props.config.boldDecorator?.({
|
|
41
|
+
schema: editor.getSnapshot().context.schema,
|
|
42
|
+
}),
|
|
43
|
+
italicDecorator: props.config.italicDecorator?.({
|
|
44
|
+
schema: editor.getSnapshot().context.schema,
|
|
45
|
+
}),
|
|
46
|
+
},
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type MarkdownEmphasisEvent =
|
|
51
|
+
| {
|
|
52
|
+
type: 'emphasis.add'
|
|
53
|
+
blockOffset: utils.BlockOffset
|
|
54
|
+
}
|
|
55
|
+
| {
|
|
56
|
+
type: 'selection'
|
|
57
|
+
blockOffsets?: {
|
|
58
|
+
anchor: utils.BlockOffset
|
|
59
|
+
focus: utils.BlockOffset
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
| {
|
|
63
|
+
type: 'delete.backward'
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const emphasisListener: CallbackLogicFunction<
|
|
67
|
+
AnyEventObject,
|
|
68
|
+
MarkdownEmphasisEvent,
|
|
69
|
+
{editor: Editor; boldDecorator?: string; italicDecorator?: string}
|
|
70
|
+
> = ({sendBack, input}) => {
|
|
71
|
+
const unregister = input.editor.registerBehavior({
|
|
72
|
+
behavior: defineBehavior({
|
|
73
|
+
on: 'insert.text',
|
|
74
|
+
guard: ({context, event}) => {
|
|
75
|
+
const boldDecorator = input.boldDecorator
|
|
76
|
+
const italicDecorator = input.italicDecorator
|
|
77
|
+
|
|
78
|
+
if (boldDecorator === undefined && italicDecorator === undefined) {
|
|
79
|
+
return false
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (event.text !== '*' && event.text !== '_') {
|
|
83
|
+
return false
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const focusTextBlock = selectors.getFocusTextBlock({context})
|
|
87
|
+
const selectionStartPoint = selectors.getSelectionStartPoint({context})
|
|
88
|
+
const selectionStartOffset = selectionStartPoint
|
|
89
|
+
? utils.spanSelectionPointToBlockOffset({
|
|
90
|
+
value: context.value,
|
|
91
|
+
selectionPoint: selectionStartPoint,
|
|
92
|
+
})
|
|
93
|
+
: undefined
|
|
94
|
+
|
|
95
|
+
if (!focusTextBlock || !selectionStartOffset) {
|
|
96
|
+
return false
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const textBefore = selectors.getBlockTextBefore({context})
|
|
100
|
+
|
|
101
|
+
const textToItalic = getTextToItalic(`${textBefore}${event.text}`)
|
|
102
|
+
|
|
103
|
+
if (textToItalic !== undefined && italicDecorator !== undefined) {
|
|
104
|
+
const prefixOffsets = {
|
|
105
|
+
anchor: {
|
|
106
|
+
path: focusTextBlock.path,
|
|
107
|
+
offset: textBefore.length - textToItalic.length + 1,
|
|
108
|
+
},
|
|
109
|
+
focus: {
|
|
110
|
+
path: focusTextBlock.path,
|
|
111
|
+
offset: textBefore.length - textToItalic.length + 1 + 1,
|
|
112
|
+
},
|
|
113
|
+
}
|
|
114
|
+
const suffixOffsets = {
|
|
115
|
+
anchor: {
|
|
116
|
+
path: focusTextBlock.path,
|
|
117
|
+
offset: selectionStartOffset.offset,
|
|
118
|
+
},
|
|
119
|
+
focus: {
|
|
120
|
+
path: focusTextBlock.path,
|
|
121
|
+
offset: selectionStartOffset.offset + 1,
|
|
122
|
+
},
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const anchor = utils.blockOffsetToSpanSelectionPoint({
|
|
126
|
+
value: context.value,
|
|
127
|
+
blockOffset: prefixOffsets.focus,
|
|
128
|
+
})
|
|
129
|
+
const focus = utils.blockOffsetToSpanSelectionPoint({
|
|
130
|
+
value: context.value,
|
|
131
|
+
blockOffset: suffixOffsets.anchor,
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
if (!anchor || !focus) {
|
|
135
|
+
return false
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
prefixOffsets,
|
|
140
|
+
suffixOffsets,
|
|
141
|
+
decorator: italicDecorator,
|
|
142
|
+
selection: {anchor, focus},
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const textToBold = getTextToBold(`${textBefore}${event.text}`)
|
|
147
|
+
|
|
148
|
+
if (textToBold !== undefined && boldDecorator !== undefined) {
|
|
149
|
+
const prefixOffsets = {
|
|
150
|
+
anchor: {
|
|
151
|
+
path: focusTextBlock.path,
|
|
152
|
+
offset: textBefore.length - textToBold.length + 1,
|
|
153
|
+
},
|
|
154
|
+
focus: {
|
|
155
|
+
path: focusTextBlock.path,
|
|
156
|
+
offset: textBefore.length - textToBold.length + 1 + 2,
|
|
157
|
+
},
|
|
158
|
+
}
|
|
159
|
+
const suffixOffsets = {
|
|
160
|
+
anchor: {
|
|
161
|
+
path: focusTextBlock.path,
|
|
162
|
+
offset: selectionStartOffset.offset - 1,
|
|
163
|
+
},
|
|
164
|
+
focus: {
|
|
165
|
+
path: focusTextBlock.path,
|
|
166
|
+
offset: selectionStartOffset.offset + 1,
|
|
167
|
+
},
|
|
168
|
+
}
|
|
169
|
+
const anchor = utils.blockOffsetToSpanSelectionPoint({
|
|
170
|
+
value: context.value,
|
|
171
|
+
blockOffset: prefixOffsets.focus,
|
|
172
|
+
})
|
|
173
|
+
const focus = utils.blockOffsetToSpanSelectionPoint({
|
|
174
|
+
value: context.value,
|
|
175
|
+
blockOffset: suffixOffsets.anchor,
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
if (!anchor || !focus) {
|
|
179
|
+
return false
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
prefixOffsets,
|
|
184
|
+
suffixOffsets,
|
|
185
|
+
decorator: boldDecorator,
|
|
186
|
+
selection: {anchor, focus},
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return false
|
|
191
|
+
},
|
|
192
|
+
actions: [
|
|
193
|
+
({event}) => [event],
|
|
194
|
+
(_, {prefixOffsets, suffixOffsets, decorator, selection}) => [
|
|
195
|
+
{
|
|
196
|
+
type: 'decorator.add',
|
|
197
|
+
decorator,
|
|
198
|
+
selection,
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
type: 'delete.text',
|
|
202
|
+
...suffixOffsets,
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
type: 'delete.text',
|
|
206
|
+
...prefixOffsets,
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
type: 'effect',
|
|
210
|
+
effect: () => {
|
|
211
|
+
sendBack({
|
|
212
|
+
type: 'emphasis.add',
|
|
213
|
+
blockOffset: {
|
|
214
|
+
...suffixOffsets.anchor,
|
|
215
|
+
offset:
|
|
216
|
+
suffixOffsets.anchor.offset -
|
|
217
|
+
(prefixOffsets.focus.offset - prefixOffsets.anchor.offset),
|
|
218
|
+
},
|
|
219
|
+
})
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
],
|
|
223
|
+
],
|
|
224
|
+
}),
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
return unregister
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const selectionListenerCallback: CallbackLogicFunction<
|
|
231
|
+
AnyEventObject,
|
|
232
|
+
MarkdownEmphasisEvent,
|
|
233
|
+
{editor: Editor}
|
|
234
|
+
> = ({sendBack, input}) => {
|
|
235
|
+
const unregister = input.editor.registerBehavior({
|
|
236
|
+
behavior: defineBehavior({
|
|
237
|
+
on: 'select',
|
|
238
|
+
guard: ({context, event}) => {
|
|
239
|
+
if (!event.selection) {
|
|
240
|
+
return {blockOffsets: undefined}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const anchor = utils.spanSelectionPointToBlockOffset({
|
|
244
|
+
value: context.value,
|
|
245
|
+
selectionPoint: event.selection.anchor,
|
|
246
|
+
})
|
|
247
|
+
const focus = utils.spanSelectionPointToBlockOffset({
|
|
248
|
+
value: context.value,
|
|
249
|
+
selectionPoint: event.selection.focus,
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
if (!anchor || !focus) {
|
|
253
|
+
return {blockOffsets: undefined}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
blockOffsets: {
|
|
258
|
+
anchor,
|
|
259
|
+
focus,
|
|
260
|
+
},
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
actions: [
|
|
264
|
+
(_, {blockOffsets}) => [
|
|
265
|
+
{
|
|
266
|
+
type: 'effect',
|
|
267
|
+
effect: () => {
|
|
268
|
+
sendBack({type: 'selection', blockOffsets})
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
],
|
|
272
|
+
],
|
|
273
|
+
}),
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
return unregister
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const deleteBackwardListenerCallback: CallbackLogicFunction<
|
|
280
|
+
AnyEventObject,
|
|
281
|
+
MarkdownEmphasisEvent,
|
|
282
|
+
{editor: Editor}
|
|
283
|
+
> = ({sendBack, input}) => {
|
|
284
|
+
const unregister = input.editor.registerBehavior({
|
|
285
|
+
behavior: defineBehavior({
|
|
286
|
+
on: 'delete.backward',
|
|
287
|
+
actions: [
|
|
288
|
+
() => [
|
|
289
|
+
{
|
|
290
|
+
type: 'history.undo',
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
type: 'effect',
|
|
294
|
+
effect: () => {
|
|
295
|
+
sendBack({type: 'delete.backward'})
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
],
|
|
299
|
+
],
|
|
300
|
+
}),
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
return unregister
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const emphasisMachine = setup({
|
|
307
|
+
types: {
|
|
308
|
+
context: {} as {
|
|
309
|
+
boldDecorator?: string
|
|
310
|
+
italicDecorator?: string
|
|
311
|
+
offsetAfterEmphasis?: utils.BlockOffset
|
|
312
|
+
editor: Editor
|
|
313
|
+
},
|
|
314
|
+
input: {} as {
|
|
315
|
+
boldDecorator?: string
|
|
316
|
+
italicDecorator?: string
|
|
317
|
+
editor: Editor
|
|
318
|
+
},
|
|
319
|
+
events: {} as MarkdownEmphasisEvent,
|
|
320
|
+
},
|
|
321
|
+
actors: {
|
|
322
|
+
'emphasis listener': fromCallback(emphasisListener),
|
|
323
|
+
'delete.backward listener': fromCallback(deleteBackwardListenerCallback),
|
|
324
|
+
'selection listener': fromCallback(selectionListenerCallback),
|
|
325
|
+
},
|
|
326
|
+
}).createMachine({
|
|
327
|
+
id: 'emphasis',
|
|
328
|
+
context: ({input}) => ({
|
|
329
|
+
boldDecorator: input.boldDecorator,
|
|
330
|
+
italicDecorator: input.italicDecorator,
|
|
331
|
+
editor: input.editor,
|
|
332
|
+
}),
|
|
333
|
+
initial: 'idle',
|
|
334
|
+
states: {
|
|
335
|
+
'idle': {
|
|
336
|
+
invoke: [
|
|
337
|
+
{
|
|
338
|
+
src: 'emphasis listener',
|
|
339
|
+
input: ({context}) => ({
|
|
340
|
+
editor: context.editor,
|
|
341
|
+
boldDecorator: context.boldDecorator,
|
|
342
|
+
italicDecorator: context.italicDecorator,
|
|
343
|
+
}),
|
|
344
|
+
},
|
|
345
|
+
],
|
|
346
|
+
on: {
|
|
347
|
+
'emphasis.add': {
|
|
348
|
+
target: 'emphasis added',
|
|
349
|
+
actions: assign({
|
|
350
|
+
offsetAfterEmphasis: ({event}) => event.blockOffset,
|
|
351
|
+
}),
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
'emphasis added': {
|
|
356
|
+
exit: [
|
|
357
|
+
assign({
|
|
358
|
+
offsetAfterEmphasis: undefined,
|
|
359
|
+
}),
|
|
360
|
+
],
|
|
361
|
+
invoke: [
|
|
362
|
+
{
|
|
363
|
+
src: 'selection listener',
|
|
364
|
+
input: ({context}) => ({editor: context.editor}),
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
src: 'delete.backward listener',
|
|
368
|
+
input: ({context}) => ({editor: context.editor}),
|
|
369
|
+
},
|
|
370
|
+
],
|
|
371
|
+
on: {
|
|
372
|
+
'selection': {
|
|
373
|
+
target: 'idle',
|
|
374
|
+
guard: ({context, event}) => {
|
|
375
|
+
const selectionChanged = !isEqual(
|
|
376
|
+
{
|
|
377
|
+
anchor: context.offsetAfterEmphasis,
|
|
378
|
+
focus: context.offsetAfterEmphasis,
|
|
379
|
+
},
|
|
380
|
+
event.blockOffsets,
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
return selectionChanged
|
|
384
|
+
},
|
|
385
|
+
},
|
|
386
|
+
'delete.backward': {
|
|
387
|
+
target: 'idle',
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
},
|
|
391
|
+
},
|
|
392
|
+
})
|