@portabletext/plugin-character-pair-decorator 4.0.22 → 4.0.24
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/plugin-character-pair-decorator",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.24",
|
|
4
4
|
"description": "Automatically match a pair of characters and decorate the text in between",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"portabletext",
|
|
@@ -32,30 +32,29 @@
|
|
|
32
32
|
"main": "./dist/index.js",
|
|
33
33
|
"types": "./dist/index.d.ts",
|
|
34
34
|
"files": [
|
|
35
|
-
"src",
|
|
36
35
|
"dist"
|
|
37
36
|
],
|
|
38
37
|
"dependencies": {
|
|
39
38
|
"@xstate/react": "^6.0.0",
|
|
40
|
-
"react-compiler-runtime": "1.0.0",
|
|
39
|
+
"react-compiler-runtime": "^1.0.0",
|
|
41
40
|
"remeda": "^2.32.0",
|
|
42
41
|
"xstate": "^5.24.0"
|
|
43
42
|
},
|
|
44
43
|
"devDependencies": {
|
|
45
44
|
"@sanity/tsconfig": "^1.0.0",
|
|
46
45
|
"@types/react": "^19.2.7",
|
|
47
|
-
"babel-plugin-react-compiler": "1.0.0",
|
|
46
|
+
"babel-plugin-react-compiler": "^1.0.0",
|
|
48
47
|
"eslint": "^9.39.1",
|
|
49
|
-
"eslint-plugin-react-hooks": "7.0.1",
|
|
48
|
+
"eslint-plugin-react-hooks": "^7.0.1",
|
|
50
49
|
"react": "^19.2.1",
|
|
51
50
|
"typescript": "5.9.3",
|
|
52
51
|
"typescript-eslint": "^8.48.0",
|
|
53
52
|
"vitest": "^4.0.14",
|
|
54
|
-
"@portabletext/editor": "^3.3.
|
|
53
|
+
"@portabletext/editor": "^3.3.4"
|
|
55
54
|
},
|
|
56
55
|
"peerDependencies": {
|
|
57
56
|
"react": "^18.3 || ^19",
|
|
58
|
-
"@portabletext/editor": "^3.3.
|
|
57
|
+
"@portabletext/editor": "^3.3.4"
|
|
59
58
|
},
|
|
60
59
|
"engines": {
|
|
61
60
|
"node": ">=20.19 <22 || >=22.12"
|
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
import type {BlockOffset, EditorContext} from '@portabletext/editor'
|
|
2
|
-
import {
|
|
3
|
-
defineBehavior,
|
|
4
|
-
effect,
|
|
5
|
-
forward,
|
|
6
|
-
raise,
|
|
7
|
-
} from '@portabletext/editor/behaviors'
|
|
8
|
-
import * as selectors from '@portabletext/editor/selectors'
|
|
9
|
-
import * as utils from '@portabletext/editor/utils'
|
|
10
|
-
import {createCharacterPairRegex} from './regex.character-pair'
|
|
11
|
-
|
|
12
|
-
export function createCharacterPairDecoratorBehavior(config: {
|
|
13
|
-
decorator: ({
|
|
14
|
-
context,
|
|
15
|
-
schema,
|
|
16
|
-
}: {
|
|
17
|
-
context: Pick<EditorContext, 'schema'>
|
|
18
|
-
/**
|
|
19
|
-
* @deprecated Use `context.schema` instead
|
|
20
|
-
*/
|
|
21
|
-
schema: EditorContext['schema']
|
|
22
|
-
}) => string | undefined
|
|
23
|
-
pair: {char: string; amount: number}
|
|
24
|
-
onDecorate: (offset: BlockOffset) => void
|
|
25
|
-
}) {
|
|
26
|
-
if (config.pair.amount < 1) {
|
|
27
|
-
console.warn(
|
|
28
|
-
`The amount of characters in the pair should be greater than 0`,
|
|
29
|
-
)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const pairRegex = createCharacterPairRegex(
|
|
33
|
-
config.pair.char,
|
|
34
|
-
config.pair.amount,
|
|
35
|
-
)
|
|
36
|
-
const regEx = new RegExp(`(${pairRegex})$`)
|
|
37
|
-
|
|
38
|
-
return defineBehavior({
|
|
39
|
-
on: 'insert.text',
|
|
40
|
-
guard: ({snapshot, event}) => {
|
|
41
|
-
if (config.pair.amount < 1) {
|
|
42
|
-
return false
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const decorator = config.decorator({
|
|
46
|
-
context: {schema: snapshot.context.schema},
|
|
47
|
-
schema: snapshot.context.schema,
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
if (decorator === undefined) {
|
|
51
|
-
return false
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const focusTextBlock = selectors.getFocusTextBlock(snapshot)
|
|
55
|
-
const selectionStartPoint = selectors.getSelectionStartPoint(snapshot)
|
|
56
|
-
const selectionStartOffset = selectionStartPoint
|
|
57
|
-
? utils.spanSelectionPointToBlockOffset({
|
|
58
|
-
context: snapshot.context,
|
|
59
|
-
selectionPoint: selectionStartPoint,
|
|
60
|
-
})
|
|
61
|
-
: undefined
|
|
62
|
-
|
|
63
|
-
if (!focusTextBlock || !selectionStartOffset) {
|
|
64
|
-
return false
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const textBefore = selectors.getBlockTextBefore(snapshot)
|
|
68
|
-
const newText = `${textBefore}${event.text}`
|
|
69
|
-
const textToDecorate = newText.match(regEx)?.at(0)
|
|
70
|
-
|
|
71
|
-
if (textToDecorate === undefined) {
|
|
72
|
-
return false
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const prefixOffsets = {
|
|
76
|
-
anchor: {
|
|
77
|
-
path: focusTextBlock.path,
|
|
78
|
-
// Example: "foo **bar**".length - "**bar**".length = 4
|
|
79
|
-
offset: newText.length - textToDecorate.length,
|
|
80
|
-
},
|
|
81
|
-
focus: {
|
|
82
|
-
path: focusTextBlock.path,
|
|
83
|
-
// Example: "foo **bar**".length - "**bar**".length + "*".length * 2 = 6
|
|
84
|
-
offset:
|
|
85
|
-
newText.length -
|
|
86
|
-
textToDecorate.length +
|
|
87
|
-
config.pair.char.length * config.pair.amount,
|
|
88
|
-
},
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const suffixOffsets = {
|
|
92
|
-
anchor: {
|
|
93
|
-
path: focusTextBlock.path,
|
|
94
|
-
// Example: "foo **bar*|" (10) + "*".length - 2 = 9
|
|
95
|
-
offset:
|
|
96
|
-
selectionStartOffset.offset +
|
|
97
|
-
event.text.length -
|
|
98
|
-
config.pair.char.length * config.pair.amount,
|
|
99
|
-
},
|
|
100
|
-
focus: {
|
|
101
|
-
path: focusTextBlock.path,
|
|
102
|
-
// Example: "foo **bar*|" (10) + "*".length = 11
|
|
103
|
-
offset: selectionStartOffset.offset + event.text.length,
|
|
104
|
-
},
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// If the prefix is more than one character, then we need to check if
|
|
108
|
-
// there is an inline object inside it
|
|
109
|
-
if (prefixOffsets.focus.offset - prefixOffsets.anchor.offset > 1) {
|
|
110
|
-
const prefixSelection = utils.blockOffsetsToSelection({
|
|
111
|
-
context: snapshot.context,
|
|
112
|
-
offsets: prefixOffsets,
|
|
113
|
-
})
|
|
114
|
-
const inlineObjectBeforePrefixFocus = selectors.getPreviousInlineObject(
|
|
115
|
-
{
|
|
116
|
-
...snapshot,
|
|
117
|
-
context: {
|
|
118
|
-
...snapshot.context,
|
|
119
|
-
selection: prefixSelection
|
|
120
|
-
? {
|
|
121
|
-
anchor: prefixSelection.focus,
|
|
122
|
-
focus: prefixSelection.focus,
|
|
123
|
-
}
|
|
124
|
-
: null,
|
|
125
|
-
},
|
|
126
|
-
},
|
|
127
|
-
)
|
|
128
|
-
const inlineObjectBeforePrefixFocusOffset =
|
|
129
|
-
inlineObjectBeforePrefixFocus
|
|
130
|
-
? utils.childSelectionPointToBlockOffset({
|
|
131
|
-
context: snapshot.context,
|
|
132
|
-
selectionPoint: {
|
|
133
|
-
path: inlineObjectBeforePrefixFocus.path,
|
|
134
|
-
offset: 0,
|
|
135
|
-
},
|
|
136
|
-
})
|
|
137
|
-
: undefined
|
|
138
|
-
|
|
139
|
-
if (
|
|
140
|
-
inlineObjectBeforePrefixFocusOffset &&
|
|
141
|
-
inlineObjectBeforePrefixFocusOffset.offset >
|
|
142
|
-
prefixOffsets.anchor.offset &&
|
|
143
|
-
inlineObjectBeforePrefixFocusOffset.offset <
|
|
144
|
-
prefixOffsets.focus.offset
|
|
145
|
-
) {
|
|
146
|
-
return false
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// If the suffix is more than one character, then we need to check if
|
|
151
|
-
// there is an inline object inside it
|
|
152
|
-
if (suffixOffsets.focus.offset - suffixOffsets.anchor.offset > 1) {
|
|
153
|
-
const previousInlineObject = selectors.getPreviousInlineObject(snapshot)
|
|
154
|
-
const previousInlineObjectOffset = previousInlineObject
|
|
155
|
-
? utils.childSelectionPointToBlockOffset({
|
|
156
|
-
context: snapshot.context,
|
|
157
|
-
selectionPoint: {
|
|
158
|
-
path: previousInlineObject.path,
|
|
159
|
-
offset: 0,
|
|
160
|
-
},
|
|
161
|
-
})
|
|
162
|
-
: undefined
|
|
163
|
-
|
|
164
|
-
if (
|
|
165
|
-
previousInlineObjectOffset &&
|
|
166
|
-
previousInlineObjectOffset.offset > suffixOffsets.anchor.offset &&
|
|
167
|
-
previousInlineObjectOffset.offset < suffixOffsets.focus.offset
|
|
168
|
-
) {
|
|
169
|
-
return false
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return {
|
|
174
|
-
prefixOffsets,
|
|
175
|
-
suffixOffsets,
|
|
176
|
-
decorator,
|
|
177
|
-
}
|
|
178
|
-
},
|
|
179
|
-
actions: [
|
|
180
|
-
// Insert the text as usual in its own undo step
|
|
181
|
-
({event}) => [forward(event)],
|
|
182
|
-
(_, {prefixOffsets, suffixOffsets, decorator}) => [
|
|
183
|
-
// Decorate the text between the prefix and suffix
|
|
184
|
-
raise({
|
|
185
|
-
type: 'decorator.add',
|
|
186
|
-
decorator,
|
|
187
|
-
at: {
|
|
188
|
-
anchor: prefixOffsets.focus,
|
|
189
|
-
focus: suffixOffsets.anchor,
|
|
190
|
-
},
|
|
191
|
-
}),
|
|
192
|
-
// Delete the suffix
|
|
193
|
-
raise({
|
|
194
|
-
type: 'delete.text',
|
|
195
|
-
at: suffixOffsets,
|
|
196
|
-
}),
|
|
197
|
-
// Delete the prefix
|
|
198
|
-
raise({
|
|
199
|
-
type: 'delete.text',
|
|
200
|
-
at: prefixOffsets,
|
|
201
|
-
}),
|
|
202
|
-
// Toggle the decorator off so the next inserted text isn't emphasized
|
|
203
|
-
raise({
|
|
204
|
-
type: 'decorator.remove',
|
|
205
|
-
decorator,
|
|
206
|
-
}),
|
|
207
|
-
effect(() => {
|
|
208
|
-
config.onDecorate({
|
|
209
|
-
...suffixOffsets.anchor,
|
|
210
|
-
offset:
|
|
211
|
-
suffixOffsets.anchor.offset -
|
|
212
|
-
(prefixOffsets.focus.offset - prefixOffsets.anchor.offset),
|
|
213
|
-
})
|
|
214
|
-
}),
|
|
215
|
-
],
|
|
216
|
-
],
|
|
217
|
-
})
|
|
218
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './plugin.character-pair-decorator'
|
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
import type {BlockOffset, Editor, EditorContext} from '@portabletext/editor'
|
|
2
|
-
import {useEditor} from '@portabletext/editor'
|
|
3
|
-
import {
|
|
4
|
-
defineBehavior,
|
|
5
|
-
effect,
|
|
6
|
-
forward,
|
|
7
|
-
raise,
|
|
8
|
-
} from '@portabletext/editor/behaviors'
|
|
9
|
-
import * as utils from '@portabletext/editor/utils'
|
|
10
|
-
import {useActorRef} from '@xstate/react'
|
|
11
|
-
import {isDeepEqual} from 'remeda'
|
|
12
|
-
import {
|
|
13
|
-
assign,
|
|
14
|
-
fromCallback,
|
|
15
|
-
setup,
|
|
16
|
-
type AnyEventObject,
|
|
17
|
-
type CallbackLogicFunction,
|
|
18
|
-
} from 'xstate'
|
|
19
|
-
import {createCharacterPairDecoratorBehavior} from './behavior.character-pair-decorator'
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* @public
|
|
23
|
-
*/
|
|
24
|
-
export function CharacterPairDecoratorPlugin(props: {
|
|
25
|
-
decorator: ({
|
|
26
|
-
context,
|
|
27
|
-
schema,
|
|
28
|
-
}: {
|
|
29
|
-
context: Pick<EditorContext, 'schema'>
|
|
30
|
-
/**
|
|
31
|
-
* @deprecated Use `context.schema` instead
|
|
32
|
-
*/
|
|
33
|
-
schema: EditorContext['schema']
|
|
34
|
-
}) => string | undefined
|
|
35
|
-
pair: {char: string; amount: number}
|
|
36
|
-
}) {
|
|
37
|
-
const editor = useEditor()
|
|
38
|
-
|
|
39
|
-
useActorRef(decoratorPairMachine, {
|
|
40
|
-
input: {
|
|
41
|
-
editor,
|
|
42
|
-
decorator: props.decorator,
|
|
43
|
-
pair: props.pair,
|
|
44
|
-
},
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
return null
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
type DecoratorPairEvent =
|
|
51
|
-
| {
|
|
52
|
-
type: 'decorator.add'
|
|
53
|
-
blockOffset: BlockOffset
|
|
54
|
-
}
|
|
55
|
-
| {
|
|
56
|
-
type: 'selection'
|
|
57
|
-
blockOffsets?: {
|
|
58
|
-
anchor: BlockOffset
|
|
59
|
-
focus: BlockOffset
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
| {
|
|
63
|
-
type: 'delete.backward'
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const decorateListener: CallbackLogicFunction<
|
|
67
|
-
AnyEventObject,
|
|
68
|
-
DecoratorPairEvent,
|
|
69
|
-
{
|
|
70
|
-
decorator: ({
|
|
71
|
-
context,
|
|
72
|
-
schema,
|
|
73
|
-
}: {
|
|
74
|
-
context: Pick<EditorContext, 'schema'>
|
|
75
|
-
/**
|
|
76
|
-
* @deprecated Use `context.schema` instead
|
|
77
|
-
*/
|
|
78
|
-
schema: EditorContext['schema']
|
|
79
|
-
}) => string | undefined
|
|
80
|
-
editor: Editor
|
|
81
|
-
pair: {char: string; amount: number}
|
|
82
|
-
}
|
|
83
|
-
> = ({sendBack, input}) => {
|
|
84
|
-
const unregister = input.editor.registerBehavior({
|
|
85
|
-
behavior: createCharacterPairDecoratorBehavior({
|
|
86
|
-
decorator: input.decorator,
|
|
87
|
-
pair: input.pair,
|
|
88
|
-
onDecorate: (offset) => {
|
|
89
|
-
sendBack({type: 'decorator.add', blockOffset: offset})
|
|
90
|
-
},
|
|
91
|
-
}),
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
return unregister
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const selectionListenerCallback: CallbackLogicFunction<
|
|
98
|
-
AnyEventObject,
|
|
99
|
-
DecoratorPairEvent,
|
|
100
|
-
{editor: Editor}
|
|
101
|
-
> = ({sendBack, input}) => {
|
|
102
|
-
const unregister = input.editor.registerBehavior({
|
|
103
|
-
behavior: defineBehavior({
|
|
104
|
-
on: 'select',
|
|
105
|
-
guard: ({snapshot, event}) => {
|
|
106
|
-
if (!event.at) {
|
|
107
|
-
return {blockOffsets: undefined}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const anchor = utils.spanSelectionPointToBlockOffset({
|
|
111
|
-
context: snapshot.context,
|
|
112
|
-
selectionPoint: event.at.anchor,
|
|
113
|
-
})
|
|
114
|
-
const focus = utils.spanSelectionPointToBlockOffset({
|
|
115
|
-
context: snapshot.context,
|
|
116
|
-
selectionPoint: event.at.focus,
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
if (!anchor || !focus) {
|
|
120
|
-
return {blockOffsets: undefined}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return {
|
|
124
|
-
blockOffsets: {
|
|
125
|
-
anchor,
|
|
126
|
-
focus,
|
|
127
|
-
},
|
|
128
|
-
}
|
|
129
|
-
},
|
|
130
|
-
actions: [
|
|
131
|
-
({event}, {blockOffsets}) => [
|
|
132
|
-
{
|
|
133
|
-
type: 'effect',
|
|
134
|
-
effect: () => {
|
|
135
|
-
sendBack({type: 'selection', blockOffsets})
|
|
136
|
-
},
|
|
137
|
-
},
|
|
138
|
-
forward(event),
|
|
139
|
-
],
|
|
140
|
-
],
|
|
141
|
-
}),
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
return unregister
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const deleteBackwardListenerCallback: CallbackLogicFunction<
|
|
148
|
-
AnyEventObject,
|
|
149
|
-
DecoratorPairEvent,
|
|
150
|
-
{editor: Editor}
|
|
151
|
-
> = ({sendBack, input}) => {
|
|
152
|
-
const unregister = input.editor.registerBehavior({
|
|
153
|
-
behavior: defineBehavior({
|
|
154
|
-
on: 'delete.backward',
|
|
155
|
-
actions: [
|
|
156
|
-
() => [
|
|
157
|
-
raise({
|
|
158
|
-
type: 'history.undo',
|
|
159
|
-
}),
|
|
160
|
-
effect(() => {
|
|
161
|
-
sendBack({type: 'delete.backward'})
|
|
162
|
-
}),
|
|
163
|
-
],
|
|
164
|
-
],
|
|
165
|
-
}),
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
return unregister
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const decoratorPairMachine = setup({
|
|
172
|
-
types: {
|
|
173
|
-
context: {} as {
|
|
174
|
-
decorator: ({
|
|
175
|
-
context,
|
|
176
|
-
schema,
|
|
177
|
-
}: {
|
|
178
|
-
context: Pick<EditorContext, 'schema'>
|
|
179
|
-
/**
|
|
180
|
-
* @deprecated Use `context.schema` instead
|
|
181
|
-
*/
|
|
182
|
-
schema: EditorContext['schema']
|
|
183
|
-
}) => string | undefined
|
|
184
|
-
editor: Editor
|
|
185
|
-
offsetAfterDecorator?: BlockOffset
|
|
186
|
-
pair: {char: string; amount: number}
|
|
187
|
-
},
|
|
188
|
-
input: {} as {
|
|
189
|
-
decorator: ({
|
|
190
|
-
context,
|
|
191
|
-
schema,
|
|
192
|
-
}: {
|
|
193
|
-
context: Pick<EditorContext, 'schema'>
|
|
194
|
-
/**
|
|
195
|
-
* @deprecated Use `context.schema` instead
|
|
196
|
-
*/
|
|
197
|
-
schema: EditorContext['schema']
|
|
198
|
-
}) => string | undefined
|
|
199
|
-
editor: Editor
|
|
200
|
-
pair: {char: string; amount: number}
|
|
201
|
-
},
|
|
202
|
-
events: {} as DecoratorPairEvent,
|
|
203
|
-
},
|
|
204
|
-
actors: {
|
|
205
|
-
'decorate listener': fromCallback(decorateListener),
|
|
206
|
-
'delete.backward listener': fromCallback(deleteBackwardListenerCallback),
|
|
207
|
-
'selection listener': fromCallback(selectionListenerCallback),
|
|
208
|
-
},
|
|
209
|
-
}).createMachine({
|
|
210
|
-
id: 'decorator pair',
|
|
211
|
-
context: ({input}) => ({
|
|
212
|
-
decorator: input.decorator,
|
|
213
|
-
editor: input.editor,
|
|
214
|
-
pair: input.pair,
|
|
215
|
-
}),
|
|
216
|
-
initial: 'idle',
|
|
217
|
-
states: {
|
|
218
|
-
'idle': {
|
|
219
|
-
invoke: [
|
|
220
|
-
{
|
|
221
|
-
src: 'decorate listener',
|
|
222
|
-
input: ({context}) => ({
|
|
223
|
-
decorator: context.decorator,
|
|
224
|
-
editor: context.editor,
|
|
225
|
-
pair: context.pair,
|
|
226
|
-
}),
|
|
227
|
-
},
|
|
228
|
-
],
|
|
229
|
-
on: {
|
|
230
|
-
'decorator.add': {
|
|
231
|
-
target: 'decorator added',
|
|
232
|
-
actions: assign({
|
|
233
|
-
offsetAfterDecorator: ({event}) => event.blockOffset,
|
|
234
|
-
}),
|
|
235
|
-
},
|
|
236
|
-
},
|
|
237
|
-
},
|
|
238
|
-
'decorator added': {
|
|
239
|
-
exit: [
|
|
240
|
-
assign({
|
|
241
|
-
offsetAfterDecorator: undefined,
|
|
242
|
-
}),
|
|
243
|
-
],
|
|
244
|
-
invoke: [
|
|
245
|
-
{
|
|
246
|
-
src: 'selection listener',
|
|
247
|
-
input: ({context}) => ({editor: context.editor}),
|
|
248
|
-
},
|
|
249
|
-
{
|
|
250
|
-
src: 'delete.backward listener',
|
|
251
|
-
input: ({context}) => ({editor: context.editor}),
|
|
252
|
-
},
|
|
253
|
-
],
|
|
254
|
-
on: {
|
|
255
|
-
'selection': {
|
|
256
|
-
target: 'idle',
|
|
257
|
-
guard: ({context, event}) => {
|
|
258
|
-
const selectionChanged = !isDeepEqual(
|
|
259
|
-
{
|
|
260
|
-
anchor: context.offsetAfterDecorator,
|
|
261
|
-
focus: context.offsetAfterDecorator,
|
|
262
|
-
},
|
|
263
|
-
event.blockOffsets,
|
|
264
|
-
)
|
|
265
|
-
|
|
266
|
-
return selectionChanged
|
|
267
|
-
},
|
|
268
|
-
},
|
|
269
|
-
'delete.backward': {
|
|
270
|
-
target: 'idle',
|
|
271
|
-
},
|
|
272
|
-
},
|
|
273
|
-
},
|
|
274
|
-
},
|
|
275
|
-
})
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import {expect, test} from 'vitest'
|
|
2
|
-
import {createCharacterPairRegex} from './regex.character-pair'
|
|
3
|
-
|
|
4
|
-
const italicRegex = new RegExp(
|
|
5
|
-
`(${createCharacterPairRegex('*', 1)}|${createCharacterPairRegex('_', 1)})$`,
|
|
6
|
-
)
|
|
7
|
-
function getTextToItalic(text: string) {
|
|
8
|
-
return text.match(italicRegex)?.at(0)
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const boldRegex = new RegExp(
|
|
12
|
-
`(${createCharacterPairRegex('*', 2)}|${createCharacterPairRegex('_', 2)})$`,
|
|
13
|
-
)
|
|
14
|
-
function getTextToBold(text: string) {
|
|
15
|
-
return text.match(boldRegex)?.at(0)
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
test(getTextToItalic.name, () => {
|
|
19
|
-
expect(getTextToItalic('Hello *world*')).toBe('*world*')
|
|
20
|
-
expect(getTextToItalic('Hello _world_')).toBe('_world_')
|
|
21
|
-
expect(getTextToItalic('*Hello*world*')).toBe('*world*')
|
|
22
|
-
expect(getTextToItalic('_Hello_world_')).toBe('_world_')
|
|
23
|
-
|
|
24
|
-
expect(getTextToItalic('* Hello world *')).toBe(undefined)
|
|
25
|
-
expect(getTextToItalic('* Hello world*')).toBe(undefined)
|
|
26
|
-
expect(getTextToItalic('*Hello world *')).toBe(undefined)
|
|
27
|
-
expect(getTextToItalic('_ Hello world _')).toBe(undefined)
|
|
28
|
-
expect(getTextToItalic('_ Hello world_')).toBe(undefined)
|
|
29
|
-
expect(getTextToItalic('_Hello world _')).toBe(undefined)
|
|
30
|
-
|
|
31
|
-
expect(getTextToItalic('Hello *world')).toBe(undefined)
|
|
32
|
-
expect(getTextToItalic('Hello world*')).toBe(undefined)
|
|
33
|
-
expect(getTextToItalic('Hello *world* *')).toBe(undefined)
|
|
34
|
-
|
|
35
|
-
expect(getTextToItalic('_Hello*world_')).toBe('_Hello*world_')
|
|
36
|
-
expect(getTextToItalic('*Hello_world*')).toBe('*Hello_world*')
|
|
37
|
-
|
|
38
|
-
expect(getTextToItalic('*hello\nworld*')).toBe(undefined)
|
|
39
|
-
expect(getTextToItalic('_hello\nworld_')).toBe(undefined)
|
|
40
|
-
|
|
41
|
-
expect(getTextToItalic('*')).toBe(undefined)
|
|
42
|
-
expect(getTextToItalic('_')).toBe(undefined)
|
|
43
|
-
expect(getTextToItalic('**')).toBe(undefined)
|
|
44
|
-
expect(getTextToItalic('__')).toBe(undefined)
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
test(getTextToBold.name, () => {
|
|
48
|
-
expect(getTextToBold('Hello **world**')).toBe('**world**')
|
|
49
|
-
expect(getTextToBold('Hello __world__')).toBe('__world__')
|
|
50
|
-
expect(getTextToBold('**Hello**world**')).toBe('**world**')
|
|
51
|
-
expect(getTextToBold('__Hello__world__')).toBe('__world__')
|
|
52
|
-
|
|
53
|
-
expect(getTextToBold('** Hello world **')).toBe(undefined)
|
|
54
|
-
expect(getTextToBold('** Hello world**')).toBe(undefined)
|
|
55
|
-
expect(getTextToBold('**Hello world **')).toBe(undefined)
|
|
56
|
-
expect(getTextToBold('__ Hello world __')).toBe(undefined)
|
|
57
|
-
expect(getTextToBold('__ Hello world__')).toBe(undefined)
|
|
58
|
-
expect(getTextToBold('__Hello world __')).toBe(undefined)
|
|
59
|
-
|
|
60
|
-
expect(getTextToBold('Hello **world')).toBe(undefined)
|
|
61
|
-
expect(getTextToBold('Hello world**')).toBe(undefined)
|
|
62
|
-
expect(getTextToBold('Hello **world** **')).toBe(undefined)
|
|
63
|
-
|
|
64
|
-
expect(getTextToBold('__Hello**world__')).toBe('__Hello**world__')
|
|
65
|
-
expect(getTextToBold('**Hello__world**')).toBe('**Hello__world**')
|
|
66
|
-
|
|
67
|
-
expect(getTextToBold('**hello\nworld**')).toBe(undefined)
|
|
68
|
-
expect(getTextToBold('__hello\nworld__')).toBe(undefined)
|
|
69
|
-
|
|
70
|
-
expect(getTextToBold('**')).toBe(undefined)
|
|
71
|
-
expect(getTextToBold('__')).toBe(undefined)
|
|
72
|
-
expect(getTextToBold('****')).toBe(undefined)
|
|
73
|
-
expect(getTextToBold('____')).toBe(undefined)
|
|
74
|
-
})
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
export function createCharacterPairRegex(char: string, amount: number) {
|
|
2
|
-
// Negative lookbehind: Ensures that the matched sequence is not preceded by the same character
|
|
3
|
-
const prePrefix = `(?<!\\${char})`
|
|
4
|
-
|
|
5
|
-
// Repeats the character `amount` times
|
|
6
|
-
const prefix = `\\${char}`.repeat(Math.max(amount, 1))
|
|
7
|
-
|
|
8
|
-
// Negative lookahead: Ensures that the opening pair (**, *, etc.) is not followed by a space
|
|
9
|
-
const postPrefix = `(?!\\s)`
|
|
10
|
-
|
|
11
|
-
// Captures the content inside the pair
|
|
12
|
-
const content = `([^${char}\\n]+?)`
|
|
13
|
-
|
|
14
|
-
// Negative lookbehind: Ensures that the content is not followed by a space
|
|
15
|
-
const preSuffix = `(?<!\\s)`
|
|
16
|
-
|
|
17
|
-
// Repeats the character `amount` times
|
|
18
|
-
const suffix = `\\${char}`.repeat(Math.max(amount, 1))
|
|
19
|
-
|
|
20
|
-
// Negative lookahead: Ensures that the matched sequence is not followed by the same character
|
|
21
|
-
const postSuffix = `(?!\\${char})`
|
|
22
|
-
|
|
23
|
-
return `${prePrefix}${prefix}${postPrefix}${content}${preSuffix}${suffix}${postSuffix}`
|
|
24
|
-
}
|