@portabletext/plugin-character-pair-decorator 8.0.29 → 8.0.31
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/dist/index.d.ts +2 -1
- package/dist/index.js +71 -285
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type {EditorContext} from '@portabletext/editor'
|
|
2
|
+
import {JSX} from 'react'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* @public
|
|
@@ -18,6 +19,6 @@ export declare function CharacterPairDecoratorPlugin(props: {
|
|
|
18
19
|
char: string
|
|
19
20
|
amount: number
|
|
20
21
|
}
|
|
21
|
-
}):
|
|
22
|
+
}): JSX.Element
|
|
22
23
|
|
|
23
24
|
export {}
|
package/dist/index.js
CHANGED
|
@@ -1,30 +1,35 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
1
2
|
import { c } from "react/compiler-runtime";
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import { useActorRef } from "@xstate/react";
|
|
6
|
-
import { setup, fromCallback, assign } from "xstate";
|
|
7
|
-
import * as selectors from "@portabletext/editor/selectors";
|
|
3
|
+
import { defineInputRule, InputRulePlugin } from "@portabletext/plugin-input-rule";
|
|
4
|
+
import "react";
|
|
5
|
+
import { raise } from "@portabletext/editor/behaviors";
|
|
8
6
|
import { getPathSubSchema } from "@portabletext/editor/traversal";
|
|
9
7
|
function createCharacterPairRegex(char, amount) {
|
|
10
|
-
const prePrefix = `(?<!\\${char})`, prefix = `\\${char}`.repeat(Math.max(amount, 1)), postPrefix = "(?!\\s)", content = `([^${char}\\n]+?)`, preSuffix = "(?<!\\s)", suffix = `\\${char}`.repeat(Math.max(amount, 1)), postSuffix = `(?!\\${char})`;
|
|
8
|
+
const prePrefix = `(?<!\\${char})`, prefix = `\\${char}`.repeat(Math.max(amount, 1)), postPrefix = "(?!\\s)", content = `(?<content>[^${char}\\n]+?)`, preSuffix = "(?<!\\s)", suffix = `\\${char}`.repeat(Math.max(amount, 1)), postSuffix = `(?!\\${char})`;
|
|
11
9
|
return `${prePrefix}${prefix}${postPrefix}${content}${preSuffix}${suffix}${postSuffix}`;
|
|
12
10
|
}
|
|
13
|
-
function
|
|
11
|
+
function createCharacterPairRule(config) {
|
|
14
12
|
config.pair.amount < 1 && console.warn("The amount of characters in the pair should be greater than 0");
|
|
15
|
-
const pairRegex = createCharacterPairRegex(config.pair.char, config.pair.amount)
|
|
16
|
-
return
|
|
17
|
-
|
|
13
|
+
const pairRegex = createCharacterPairRegex(config.pair.char, config.pair.amount);
|
|
14
|
+
return defineInputRule({
|
|
15
|
+
// `$`-anchored: the pair must close at the insertion point, so at most
|
|
16
|
+
// one match fires per insert.
|
|
17
|
+
on: new RegExp(`${pairRegex}$`),
|
|
18
|
+
// A pair only deletes its markers, so a match whose CONTENT spans an
|
|
19
|
+
// inline object is harmless and must decorate (`**bo`, insert an
|
|
20
|
+
// object, `ld**`). An inline object anywhere else in the match, between
|
|
21
|
+
// the marker characters, means this is not a pair, and the machinery
|
|
22
|
+
// drops the match.
|
|
23
|
+
inlineObjects: {
|
|
24
|
+
allow: ["content"]
|
|
25
|
+
},
|
|
18
26
|
guard: ({
|
|
19
27
|
snapshot,
|
|
20
28
|
event
|
|
21
29
|
}) => {
|
|
22
30
|
if (config.pair.amount < 1)
|
|
23
31
|
return !1;
|
|
24
|
-
const
|
|
25
|
-
if (!focusTextBlock)
|
|
26
|
-
return !1;
|
|
27
|
-
const subSchema = getPathSubSchema(snapshot, focusTextBlock.path), decorator = config.decorator({
|
|
32
|
+
const subSchema = getPathSubSchema(snapshot, event.focusBlock.path), decorator = config.decorator({
|
|
28
33
|
context: {
|
|
29
34
|
schema: subSchema
|
|
30
35
|
},
|
|
@@ -32,291 +37,72 @@ function createCharacterPairDecoratorBehavior(config) {
|
|
|
32
37
|
});
|
|
33
38
|
if (decorator === void 0)
|
|
34
39
|
return !1;
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
selectionPoint: selectionStartPoint
|
|
38
|
-
}) : void 0;
|
|
39
|
-
if (!selectionStartOffset)
|
|
40
|
+
const match = event.matches.at(0);
|
|
41
|
+
if (!match)
|
|
40
42
|
return !1;
|
|
41
|
-
const
|
|
42
|
-
if (
|
|
43
|
+
const content = match.groups.content;
|
|
44
|
+
if (!content)
|
|
43
45
|
return !1;
|
|
44
46
|
const prefixOffsets = {
|
|
45
|
-
anchor:
|
|
46
|
-
|
|
47
|
-
// Example: "foo **bar**".length - "**bar**".length = 4
|
|
48
|
-
offset: newText.length - textToDecorate.length
|
|
49
|
-
},
|
|
50
|
-
focus: {
|
|
51
|
-
path: focusTextBlock.path,
|
|
52
|
-
// Example: "foo **bar**".length - "**bar**".length + "*".length * 2 = 6
|
|
53
|
-
offset: newText.length - textToDecorate.length + config.pair.char.length * config.pair.amount
|
|
54
|
-
}
|
|
47
|
+
anchor: match.targetOffsets.anchor,
|
|
48
|
+
focus: content.targetOffsets.anchor
|
|
55
49
|
}, suffixOffsets = {
|
|
56
|
-
anchor:
|
|
57
|
-
|
|
58
|
-
// Example: "foo **bar*|" (10) + "*".length - 2 = 9
|
|
59
|
-
offset: selectionStartOffset.offset + event.text.length - config.pair.char.length * config.pair.amount
|
|
60
|
-
},
|
|
61
|
-
focus: {
|
|
62
|
-
path: focusTextBlock.path,
|
|
63
|
-
// Example: "foo **bar*|" (10) + "*".length = 11
|
|
64
|
-
offset: selectionStartOffset.offset + event.text.length
|
|
65
|
-
}
|
|
50
|
+
anchor: content.targetOffsets.focus,
|
|
51
|
+
focus: match.targetOffsets.focus
|
|
66
52
|
};
|
|
67
|
-
if (prefixOffsets.focus.offset - prefixOffsets.anchor.offset > 1) {
|
|
68
|
-
const prefixSelection = utils.blockOffsetsToSelection({
|
|
69
|
-
snapshot,
|
|
70
|
-
offsets: prefixOffsets
|
|
71
|
-
}), inlineObjectBeforePrefixFocus = selectors.getPreviousInlineObject({
|
|
72
|
-
...snapshot,
|
|
73
|
-
context: {
|
|
74
|
-
...snapshot.context,
|
|
75
|
-
selection: prefixSelection ? {
|
|
76
|
-
anchor: prefixSelection.focus,
|
|
77
|
-
focus: prefixSelection.focus
|
|
78
|
-
} : null
|
|
79
|
-
}
|
|
80
|
-
}), inlineObjectBeforePrefixFocusOffset = inlineObjectBeforePrefixFocus ? utils.childSelectionPointToBlockOffset({
|
|
81
|
-
snapshot,
|
|
82
|
-
selectionPoint: {
|
|
83
|
-
path: inlineObjectBeforePrefixFocus.path,
|
|
84
|
-
offset: 0
|
|
85
|
-
}
|
|
86
|
-
}) : void 0;
|
|
87
|
-
if (inlineObjectBeforePrefixFocusOffset && inlineObjectBeforePrefixFocusOffset.offset > prefixOffsets.anchor.offset && inlineObjectBeforePrefixFocusOffset.offset < prefixOffsets.focus.offset)
|
|
88
|
-
return !1;
|
|
89
|
-
}
|
|
90
|
-
if (suffixOffsets.focus.offset - suffixOffsets.anchor.offset > 1) {
|
|
91
|
-
const previousInlineObject = selectors.getPreviousInlineObject(snapshot), previousInlineObjectOffset = previousInlineObject ? utils.childSelectionPointToBlockOffset({
|
|
92
|
-
snapshot,
|
|
93
|
-
selectionPoint: {
|
|
94
|
-
path: previousInlineObject.path,
|
|
95
|
-
offset: 0
|
|
96
|
-
}
|
|
97
|
-
}) : void 0;
|
|
98
|
-
if (previousInlineObjectOffset && previousInlineObjectOffset.offset > suffixOffsets.anchor.offset && previousInlineObjectOffset.offset < suffixOffsets.focus.offset)
|
|
99
|
-
return !1;
|
|
100
|
-
}
|
|
101
53
|
return {
|
|
54
|
+
decorator,
|
|
55
|
+
contentOffsets: {
|
|
56
|
+
anchor: content.targetOffsets.anchor,
|
|
57
|
+
focus: content.targetOffsets.focus
|
|
58
|
+
},
|
|
102
59
|
prefixOffsets,
|
|
103
|
-
suffixOffsets
|
|
104
|
-
decorator
|
|
60
|
+
suffixOffsets
|
|
105
61
|
};
|
|
106
62
|
},
|
|
107
|
-
actions: [
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
63
|
+
actions: [(_, {
|
|
64
|
+
decorator,
|
|
65
|
+
contentOffsets,
|
|
66
|
+
prefixOffsets,
|
|
67
|
+
suffixOffsets
|
|
68
|
+
}) => [
|
|
69
|
+
// Decorate the text between the prefix and suffix
|
|
70
|
+
raise({
|
|
71
|
+
type: "decorator.add",
|
|
72
|
+
decorator,
|
|
73
|
+
at: contentOffsets
|
|
74
|
+
}),
|
|
75
|
+
// Delete the suffix, then the prefix (suffix first so the prefix
|
|
76
|
+
// offsets stay valid)
|
|
77
|
+
raise({
|
|
78
|
+
type: "delete.text",
|
|
79
|
+
at: suffixOffsets
|
|
80
|
+
}),
|
|
81
|
+
raise({
|
|
82
|
+
type: "delete.text",
|
|
83
|
+
at: prefixOffsets
|
|
84
|
+
}),
|
|
85
|
+
// Toggle the decorator off so the next inserted text isn't decorated
|
|
86
|
+
raise({
|
|
87
|
+
type: "decorator.remove",
|
|
115
88
|
decorator
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
raise({
|
|
119
|
-
type: "decorator.add",
|
|
120
|
-
decorator,
|
|
121
|
-
at: {
|
|
122
|
-
anchor: prefixOffsets.focus,
|
|
123
|
-
focus: suffixOffsets.anchor
|
|
124
|
-
}
|
|
125
|
-
}),
|
|
126
|
-
// Delete the suffix
|
|
127
|
-
raise({
|
|
128
|
-
type: "delete.text",
|
|
129
|
-
at: suffixOffsets
|
|
130
|
-
}),
|
|
131
|
-
// Delete the prefix
|
|
132
|
-
raise({
|
|
133
|
-
type: "delete.text",
|
|
134
|
-
at: prefixOffsets
|
|
135
|
-
}),
|
|
136
|
-
// Toggle the decorator off so the next inserted text isn't emphasized
|
|
137
|
-
raise({
|
|
138
|
-
type: "decorator.remove",
|
|
139
|
-
decorator
|
|
140
|
-
}),
|
|
141
|
-
effect(() => {
|
|
142
|
-
config.onDecorate({
|
|
143
|
-
...suffixOffsets.anchor,
|
|
144
|
-
offset: suffixOffsets.anchor.offset - (prefixOffsets.focus.offset - prefixOffsets.anchor.offset)
|
|
145
|
-
});
|
|
146
|
-
})
|
|
147
|
-
]
|
|
148
|
-
]
|
|
89
|
+
})
|
|
90
|
+
]]
|
|
149
91
|
});
|
|
150
92
|
}
|
|
151
93
|
function CharacterPairDecoratorPlugin(props) {
|
|
152
|
-
const $ = c(
|
|
94
|
+
const $ = c(7);
|
|
153
95
|
let t0;
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
96
|
+
$[0] !== props.decorator || $[1] !== props.pair ? (t0 = createCharacterPairRule({
|
|
97
|
+
decorator: props.decorator,
|
|
98
|
+
pair: props.pair
|
|
99
|
+
}), $[0] = props.decorator, $[1] = props.pair, $[2] = t0) : t0 = $[2];
|
|
100
|
+
let t1;
|
|
101
|
+
$[3] !== t0 ? (t1 = [t0], $[3] = t0, $[4] = t1) : t1 = $[4];
|
|
102
|
+
const rules = t1;
|
|
103
|
+
let t2;
|
|
104
|
+
return $[5] !== rules ? (t2 = /* @__PURE__ */ jsx(InputRulePlugin, { rules }), $[5] = rules, $[6] = t2) : t2 = $[6], t2;
|
|
161
105
|
}
|
|
162
|
-
const decorateListener = ({
|
|
163
|
-
sendBack,
|
|
164
|
-
input
|
|
165
|
-
}) => input.editor.registerBehavior({
|
|
166
|
-
behavior: createCharacterPairDecoratorBehavior({
|
|
167
|
-
decorator: input.decorator,
|
|
168
|
-
pair: input.pair,
|
|
169
|
-
onDecorate: (offset) => {
|
|
170
|
-
sendBack({
|
|
171
|
-
type: "decorator.add",
|
|
172
|
-
blockOffset: offset
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
})
|
|
176
|
-
}), selectionListenerCallback = ({
|
|
177
|
-
sendBack,
|
|
178
|
-
input
|
|
179
|
-
}) => {
|
|
180
|
-
const subscription = input.editor.on("selection", (event) => {
|
|
181
|
-
if (!event.selection) {
|
|
182
|
-
sendBack({
|
|
183
|
-
type: "selection",
|
|
184
|
-
blockOffsets: void 0,
|
|
185
|
-
selection: null
|
|
186
|
-
});
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
const snapshot = input.editor.getSnapshot(), anchor = utils.spanSelectionPointToBlockOffset({
|
|
190
|
-
snapshot,
|
|
191
|
-
selectionPoint: event.selection.anchor
|
|
192
|
-
}), focus = utils.spanSelectionPointToBlockOffset({
|
|
193
|
-
snapshot,
|
|
194
|
-
selectionPoint: event.selection.focus
|
|
195
|
-
});
|
|
196
|
-
if (!anchor || !focus) {
|
|
197
|
-
sendBack({
|
|
198
|
-
type: "selection",
|
|
199
|
-
blockOffsets: void 0,
|
|
200
|
-
selection: event.selection
|
|
201
|
-
});
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
sendBack({
|
|
205
|
-
type: "selection",
|
|
206
|
-
blockOffsets: {
|
|
207
|
-
anchor,
|
|
208
|
-
focus
|
|
209
|
-
},
|
|
210
|
-
selection: event.selection
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
return () => subscription.unsubscribe();
|
|
214
|
-
}, deleteBackwardListenerCallback = ({
|
|
215
|
-
sendBack,
|
|
216
|
-
input
|
|
217
|
-
}) => input.editor.registerBehavior({
|
|
218
|
-
behavior: defineBehavior({
|
|
219
|
-
on: "delete.backward",
|
|
220
|
-
actions: [() => [raise({
|
|
221
|
-
type: "history.undo"
|
|
222
|
-
}), effect(() => {
|
|
223
|
-
sendBack({
|
|
224
|
-
type: "delete.backward"
|
|
225
|
-
});
|
|
226
|
-
})]]
|
|
227
|
-
})
|
|
228
|
-
}), decoratorPairMachine = setup({
|
|
229
|
-
types: {
|
|
230
|
-
context: {},
|
|
231
|
-
input: {},
|
|
232
|
-
events: {}
|
|
233
|
-
},
|
|
234
|
-
actors: {
|
|
235
|
-
"decorate listener": fromCallback(decorateListener),
|
|
236
|
-
"delete.backward listener": fromCallback(deleteBackwardListenerCallback),
|
|
237
|
-
"selection listener": fromCallback(selectionListenerCallback)
|
|
238
|
-
}
|
|
239
|
-
}).createMachine({
|
|
240
|
-
id: "decorator pair",
|
|
241
|
-
context: ({
|
|
242
|
-
input
|
|
243
|
-
}) => ({
|
|
244
|
-
decorator: input.decorator,
|
|
245
|
-
editor: input.editor,
|
|
246
|
-
endSelection: null,
|
|
247
|
-
pair: input.pair
|
|
248
|
-
}),
|
|
249
|
-
initial: "idle",
|
|
250
|
-
states: {
|
|
251
|
-
idle: {
|
|
252
|
-
invoke: [{
|
|
253
|
-
src: "decorate listener",
|
|
254
|
-
input: ({
|
|
255
|
-
context
|
|
256
|
-
}) => ({
|
|
257
|
-
decorator: context.decorator,
|
|
258
|
-
editor: context.editor,
|
|
259
|
-
pair: context.pair
|
|
260
|
-
})
|
|
261
|
-
}],
|
|
262
|
-
on: {
|
|
263
|
-
"decorator.add": {
|
|
264
|
-
target: "decorator added",
|
|
265
|
-
actions: assign({
|
|
266
|
-
offsetAfterDecorator: ({
|
|
267
|
-
event
|
|
268
|
-
}) => event.blockOffset,
|
|
269
|
-
endSelection: ({
|
|
270
|
-
context
|
|
271
|
-
}) => context.editor.getSnapshot().context.selection
|
|
272
|
-
})
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
},
|
|
276
|
-
"decorator added": {
|
|
277
|
-
exit: [assign({
|
|
278
|
-
offsetAfterDecorator: void 0
|
|
279
|
-
})],
|
|
280
|
-
invoke: [{
|
|
281
|
-
src: "selection listener",
|
|
282
|
-
input: ({
|
|
283
|
-
context
|
|
284
|
-
}) => ({
|
|
285
|
-
editor: context.editor
|
|
286
|
-
})
|
|
287
|
-
}, {
|
|
288
|
-
src: "delete.backward listener",
|
|
289
|
-
input: ({
|
|
290
|
-
context
|
|
291
|
-
}) => ({
|
|
292
|
-
editor: context.editor
|
|
293
|
-
})
|
|
294
|
-
}],
|
|
295
|
-
on: {
|
|
296
|
-
selection: {
|
|
297
|
-
target: "idle",
|
|
298
|
-
guard: ({
|
|
299
|
-
context,
|
|
300
|
-
event
|
|
301
|
-
}) => {
|
|
302
|
-
const offsetAfterDecorator = context.offsetAfterDecorator;
|
|
303
|
-
if (event.blockOffsets && offsetAfterDecorator) {
|
|
304
|
-
const decoratorBlock = offsetAfterDecorator.path.at(-1), anchorBlock = event.blockOffsets.anchor.path.at(-1), focusBlock = event.blockOffsets.focus.path.at(-1);
|
|
305
|
-
if (!utils.isKeyedSegment(decoratorBlock) || !utils.isKeyedSegment(anchorBlock) || !utils.isKeyedSegment(focusBlock))
|
|
306
|
-
return !1;
|
|
307
|
-
const anchorChanged = decoratorBlock._key !== anchorBlock._key || offsetAfterDecorator.offset !== event.blockOffsets.anchor.offset, focusChanged = decoratorBlock._key !== focusBlock._key || offsetAfterDecorator.offset !== event.blockOffsets.focus.offset;
|
|
308
|
-
return anchorChanged || focusChanged;
|
|
309
|
-
}
|
|
310
|
-
return !utils.isEqualSelections(context.endSelection, event.selection);
|
|
311
|
-
}
|
|
312
|
-
},
|
|
313
|
-
"delete.backward": {
|
|
314
|
-
target: "idle"
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
});
|
|
320
106
|
export {
|
|
321
107
|
CharacterPairDecoratorPlugin
|
|
322
108
|
};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/regex.character-pair.ts","../src/behavior.character-pair-decorator.ts","../src/plugin.character-pair-decorator.ts"],"sourcesContent":["export function createCharacterPairRegex(char: string, amount: number) {\n // Negative lookbehind: Ensures that the matched sequence is not preceded by the same character\n const prePrefix = `(?<!\\\\${char})`\n\n // Repeats the character `amount` times\n const prefix = `\\\\${char}`.repeat(Math.max(amount, 1))\n\n // Negative lookahead: Ensures that the opening pair (**, *, etc.) is not followed by a space\n const postPrefix = `(?!\\\\s)`\n\n // Captures the content inside the pair\n const content = `([^${char}\\\\n]+?)`\n\n // Negative lookbehind: Ensures that the content is not followed by a space\n const preSuffix = `(?<!\\\\s)`\n\n // Repeats the character `amount` times\n const suffix = `\\\\${char}`.repeat(Math.max(amount, 1))\n\n // Negative lookahead: Ensures that the matched sequence is not followed by the same character\n const postSuffix = `(?!\\\\${char})`\n\n return `${prePrefix}${prefix}${postPrefix}${content}${preSuffix}${suffix}${postSuffix}`\n}\n","import type {BlockOffset, EditorContext} from '@portabletext/editor'\nimport {\n defineBehavior,\n effect,\n forward,\n raise,\n} from '@portabletext/editor/behaviors'\nimport * as selectors from '@portabletext/editor/selectors'\nimport {getPathSubSchema} from '@portabletext/editor/traversal'\nimport * as utils from '@portabletext/editor/utils'\nimport {createCharacterPairRegex} from './regex.character-pair'\n\nexport function createCharacterPairDecoratorBehavior(config: {\n decorator: ({\n context,\n schema,\n }: {\n context: Pick<EditorContext, 'schema'>\n /**\n * @deprecated Use `context.schema` instead\n */\n schema: EditorContext['schema']\n }) => string | undefined\n pair: {char: string; amount: number}\n onDecorate: (offset: BlockOffset) => void\n}) {\n if (config.pair.amount < 1) {\n console.warn(\n `The amount of characters in the pair should be greater than 0`,\n )\n }\n\n const pairRegex = createCharacterPairRegex(\n config.pair.char,\n config.pair.amount,\n )\n const regEx = new RegExp(`(${pairRegex})$`)\n\n return defineBehavior({\n on: 'insert.text',\n guard: ({snapshot, event}) => {\n if (config.pair.amount < 1) {\n return false\n }\n\n const focusTextBlock = selectors.getFocusTextBlock(snapshot)\n\n if (!focusTextBlock) {\n return false\n }\n\n const subSchema = getPathSubSchema(snapshot, focusTextBlock.path)\n const decorator = config.decorator({\n context: {schema: subSchema},\n schema: subSchema,\n })\n\n if (decorator === undefined) {\n return false\n }\n\n const selectionStartPoint = selectors.getSelectionStartPoint(snapshot)\n const selectionStartOffset = selectionStartPoint\n ? utils.spanSelectionPointToBlockOffset({\n snapshot,\n selectionPoint: selectionStartPoint,\n })\n : undefined\n\n if (!selectionStartOffset) {\n return false\n }\n\n const textBefore = selectors.getBlockTextBefore(snapshot)\n const newText = `${textBefore}${event.text}`\n const textToDecorate = newText.match(regEx)?.at(0)\n\n if (textToDecorate === undefined) {\n return false\n }\n\n const prefixOffsets = {\n anchor: {\n path: focusTextBlock.path,\n // Example: \"foo **bar**\".length - \"**bar**\".length = 4\n offset: newText.length - textToDecorate.length,\n },\n focus: {\n path: focusTextBlock.path,\n // Example: \"foo **bar**\".length - \"**bar**\".length + \"*\".length * 2 = 6\n offset:\n newText.length -\n textToDecorate.length +\n config.pair.char.length * config.pair.amount,\n },\n }\n\n const suffixOffsets = {\n anchor: {\n path: focusTextBlock.path,\n // Example: \"foo **bar*|\" (10) + \"*\".length - 2 = 9\n offset:\n selectionStartOffset.offset +\n event.text.length -\n config.pair.char.length * config.pair.amount,\n },\n focus: {\n path: focusTextBlock.path,\n // Example: \"foo **bar*|\" (10) + \"*\".length = 11\n offset: selectionStartOffset.offset + event.text.length,\n },\n }\n\n // If the prefix is more than one character, then we need to check if\n // there is an inline object inside it\n if (prefixOffsets.focus.offset - prefixOffsets.anchor.offset > 1) {\n const prefixSelection = utils.blockOffsetsToSelection({\n snapshot,\n offsets: prefixOffsets,\n })\n const inlineObjectBeforePrefixFocus = selectors.getPreviousInlineObject(\n {\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: prefixSelection\n ? {\n anchor: prefixSelection.focus,\n focus: prefixSelection.focus,\n }\n : null,\n },\n },\n )\n const inlineObjectBeforePrefixFocusOffset =\n inlineObjectBeforePrefixFocus\n ? utils.childSelectionPointToBlockOffset({\n snapshot,\n selectionPoint: {\n path: inlineObjectBeforePrefixFocus.path,\n offset: 0,\n },\n })\n : undefined\n\n if (\n inlineObjectBeforePrefixFocusOffset &&\n inlineObjectBeforePrefixFocusOffset.offset >\n prefixOffsets.anchor.offset &&\n inlineObjectBeforePrefixFocusOffset.offset <\n prefixOffsets.focus.offset\n ) {\n return false\n }\n }\n\n // If the suffix is more than one character, then we need to check if\n // there is an inline object inside it\n if (suffixOffsets.focus.offset - suffixOffsets.anchor.offset > 1) {\n const previousInlineObject = selectors.getPreviousInlineObject(snapshot)\n const previousInlineObjectOffset = previousInlineObject\n ? utils.childSelectionPointToBlockOffset({\n snapshot,\n selectionPoint: {\n path: previousInlineObject.path,\n offset: 0,\n },\n })\n : undefined\n\n if (\n previousInlineObjectOffset &&\n previousInlineObjectOffset.offset > suffixOffsets.anchor.offset &&\n previousInlineObjectOffset.offset < suffixOffsets.focus.offset\n ) {\n return false\n }\n }\n\n return {\n prefixOffsets,\n suffixOffsets,\n decorator,\n }\n },\n actions: [\n // Insert the text as usual in its own undo step\n ({event}) => [forward(event)],\n (_, {prefixOffsets, suffixOffsets, decorator}) => [\n // Decorate the text between the prefix and suffix\n raise({\n type: 'decorator.add',\n decorator,\n at: {\n anchor: prefixOffsets.focus,\n focus: suffixOffsets.anchor,\n },\n }),\n // Delete the suffix\n raise({\n type: 'delete.text',\n at: suffixOffsets,\n }),\n // Delete the prefix\n raise({\n type: 'delete.text',\n at: prefixOffsets,\n }),\n // Toggle the decorator off so the next inserted text isn't emphasized\n raise({\n type: 'decorator.remove',\n decorator,\n }),\n effect(() => {\n config.onDecorate({\n ...suffixOffsets.anchor,\n offset:\n suffixOffsets.anchor.offset -\n (prefixOffsets.focus.offset - prefixOffsets.anchor.offset),\n })\n }),\n ],\n ],\n })\n}\n","import type {\n BlockOffset,\n Editor,\n EditorContext,\n EditorSelection,\n} from '@portabletext/editor'\nimport {useEditor} from '@portabletext/editor'\nimport {defineBehavior, effect, raise} from '@portabletext/editor/behaviors'\nimport * as utils from '@portabletext/editor/utils'\nimport {useActorRef} from '@xstate/react'\nimport {\n assign,\n fromCallback,\n setup,\n type AnyEventObject,\n type CallbackLogicFunction,\n} from 'xstate'\nimport {createCharacterPairDecoratorBehavior} from './behavior.character-pair-decorator'\n\n/**\n * @public\n */\nexport function CharacterPairDecoratorPlugin(props: {\n decorator: ({\n context,\n schema,\n }: {\n context: Pick<EditorContext, 'schema'>\n /**\n * @deprecated Use `context.schema` instead\n */\n schema: EditorContext['schema']\n }) => string | undefined\n pair: {char: string; amount: number}\n}) {\n const editor = useEditor()\n\n useActorRef(decoratorPairMachine, {\n input: {\n editor,\n decorator: props.decorator,\n pair: props.pair,\n },\n })\n\n return null\n}\n\ntype DecoratorPairEvent =\n | {\n type: 'decorator.add'\n blockOffset: BlockOffset\n }\n | {\n type: 'selection'\n blockOffsets?: {\n anchor: BlockOffset\n focus: BlockOffset\n }\n selection: EditorSelection\n }\n | {\n type: 'delete.backward'\n }\n\nconst decorateListener: CallbackLogicFunction<\n AnyEventObject,\n DecoratorPairEvent,\n {\n decorator: ({\n context,\n schema,\n }: {\n context: Pick<EditorContext, 'schema'>\n /**\n * @deprecated Use `context.schema` instead\n */\n schema: EditorContext['schema']\n }) => string | undefined\n editor: Editor\n pair: {char: string; amount: number}\n }\n> = ({sendBack, input}) => {\n const unregister = input.editor.registerBehavior({\n behavior: createCharacterPairDecoratorBehavior({\n decorator: input.decorator,\n pair: input.pair,\n onDecorate: (offset) => {\n sendBack({type: 'decorator.add', blockOffset: offset})\n },\n }),\n })\n\n return unregister\n}\n\nconst selectionListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n DecoratorPairEvent,\n {editor: Editor}\n> = ({sendBack, input}) => {\n // Listen for the emitted 'selection' event which fires after ANY cursor\n // movement (typing, clicking, pasting, etc.) - not just explicit 'select'\n // behavior events.\n const subscription = input.editor.on('selection', (event) => {\n if (!event.selection) {\n sendBack({type: 'selection', blockOffsets: undefined, selection: null})\n return\n }\n\n const snapshot = input.editor.getSnapshot()\n const anchor = utils.spanSelectionPointToBlockOffset({\n snapshot,\n selectionPoint: event.selection.anchor,\n })\n const focus = utils.spanSelectionPointToBlockOffset({\n snapshot,\n selectionPoint: event.selection.focus,\n })\n\n if (!anchor || !focus) {\n sendBack({\n type: 'selection',\n blockOffsets: undefined,\n selection: event.selection,\n })\n return\n }\n\n sendBack({\n type: 'selection',\n blockOffsets: {anchor, focus},\n selection: event.selection,\n })\n })\n\n return () => subscription.unsubscribe()\n}\n\nconst deleteBackwardListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n DecoratorPairEvent,\n {editor: Editor}\n> = ({sendBack, input}) => {\n const unregister = input.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'delete.backward',\n actions: [\n () => [\n raise({\n type: 'history.undo',\n }),\n effect(() => {\n sendBack({type: 'delete.backward'})\n }),\n ],\n ],\n }),\n })\n\n return unregister\n}\n\nconst decoratorPairMachine = setup({\n types: {\n context: {} as {\n decorator: ({\n context,\n schema,\n }: {\n context: Pick<EditorContext, 'schema'>\n /**\n * @deprecated Use `context.schema` instead\n */\n schema: EditorContext['schema']\n }) => string | undefined\n editor: Editor\n offsetAfterDecorator?: BlockOffset\n endSelection: EditorSelection\n pair: {char: string; amount: number}\n },\n input: {} as {\n decorator: ({\n context,\n schema,\n }: {\n context: Pick<EditorContext, 'schema'>\n /**\n * @deprecated Use `context.schema` instead\n */\n schema: EditorContext['schema']\n }) => string | undefined\n editor: Editor\n pair: {char: string; amount: number}\n },\n events: {} as DecoratorPairEvent,\n },\n actors: {\n 'decorate listener': fromCallback(decorateListener),\n 'delete.backward listener': fromCallback(deleteBackwardListenerCallback),\n 'selection listener': fromCallback(selectionListenerCallback),\n },\n}).createMachine({\n id: 'decorator pair',\n context: ({input}) => ({\n decorator: input.decorator,\n editor: input.editor,\n endSelection: null,\n pair: input.pair,\n }),\n initial: 'idle',\n states: {\n 'idle': {\n invoke: [\n {\n src: 'decorate listener',\n input: ({context}) => ({\n decorator: context.decorator,\n editor: context.editor,\n pair: context.pair,\n }),\n },\n ],\n on: {\n 'decorator.add': {\n target: 'decorator added',\n actions: assign({\n offsetAfterDecorator: ({event}) => event.blockOffset,\n endSelection: ({context}) =>\n context.editor.getSnapshot().context.selection,\n }),\n },\n },\n },\n 'decorator added': {\n exit: [\n assign({\n offsetAfterDecorator: undefined,\n }),\n ],\n invoke: [\n {\n src: 'selection listener',\n input: ({context}) => ({editor: context.editor}),\n },\n {\n src: 'delete.backward listener',\n input: ({context}) => ({editor: context.editor}),\n },\n ],\n on: {\n 'selection': {\n target: 'idle',\n guard: ({context, event}) => {\n const offsetAfterDecorator = context.offsetAfterDecorator\n\n if (event.blockOffsets && offsetAfterDecorator) {\n // Block offsets are compared rather than raw selection points\n // because they normalize away span-level differences (e.g. the\n // cursor at the same position but in a different span after\n // normalization).\n const decoratorBlock = offsetAfterDecorator.path.at(-1)\n const anchorBlock = event.blockOffsets.anchor.path.at(-1)\n const focusBlock = event.blockOffsets.focus.path.at(-1)\n\n if (\n !utils.isKeyedSegment(decoratorBlock) ||\n !utils.isKeyedSegment(anchorBlock) ||\n !utils.isKeyedSegment(focusBlock)\n ) {\n return false\n }\n\n const anchorChanged =\n decoratorBlock._key !== anchorBlock._key ||\n offsetAfterDecorator.offset !== event.blockOffsets.anchor.offset\n const focusChanged =\n decoratorBlock._key !== focusBlock._key ||\n offsetAfterDecorator.offset !== event.blockOffsets.focus.offset\n\n return anchorChanged || focusChanged\n }\n\n // Block offsets can't be computed when the cursor is on an inline\n // object, so fall back to comparing the raw selections.\n return !utils.isEqualSelections(\n context.endSelection,\n event.selection,\n )\n },\n },\n 'delete.backward': {\n target: 'idle',\n },\n },\n },\n },\n})\n"],"names":["createCharacterPairRegex","char","amount","prePrefix","prefix","repeat","Math","max","postPrefix","content","preSuffix","suffix","postSuffix","createCharacterPairDecoratorBehavior","config","pair","console","warn","pairRegex","regEx","RegExp","defineBehavior","on","guard","snapshot","event","focusTextBlock","selectors","getFocusTextBlock","subSchema","getPathSubSchema","path","decorator","context","schema","undefined","selectionStartPoint","getSelectionStartPoint","selectionStartOffset","utils","spanSelectionPointToBlockOffset","selectionPoint","newText","getBlockTextBefore","text","textToDecorate","match","at","prefixOffsets","anchor","offset","length","focus","suffixOffsets","prefixSelection","blockOffsetsToSelection","offsets","inlineObjectBeforePrefixFocus","getPreviousInlineObject","selection","inlineObjectBeforePrefixFocusOffset","childSelectionPointToBlockOffset","previousInlineObject","previousInlineObjectOffset","actions","forward","_","raise","type","effect","onDecorate","CharacterPairDecoratorPlugin","props","$","_c","editor","useEditor","t0","input","useActorRef","decoratorPairMachine","decorateListener","sendBack","registerBehavior","behavior","blockOffset","selectionListenerCallback","subscription","blockOffsets","getSnapshot","unsubscribe","deleteBackwardListenerCallback","setup","types","events","actors","fromCallback","createMachine","id","endSelection","initial","states","invoke","src","target","assign","offsetAfterDecorator","exit","decoratorBlock","anchorBlock","focusBlock","isKeyedSegment","anchorChanged","_key","focusChanged","isEqualSelections"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/regex.character-pair.ts","../src/rule.character-pair.ts","../src/plugin.character-pair-decorator.tsx"],"sourcesContent":["export function createCharacterPairRegex(char: string, amount: number) {\n // Negative lookbehind: Ensures that the matched sequence is not preceded by the same character\n const prePrefix = `(?<!\\\\${char})`\n\n // Repeats the character `amount` times\n const prefix = `\\\\${char}`.repeat(Math.max(amount, 1))\n\n // Negative lookahead: Ensures that the opening pair (**, *, etc.) is not followed by a space\n const postPrefix = `(?!\\\\s)`\n\n // Captures the content inside the pair as the named group `content`\n const content = `(?<content>[^${char}\\\\n]+?)`\n\n // Negative lookbehind: Ensures that the content is not followed by a space\n const preSuffix = `(?<!\\\\s)`\n\n // Repeats the character `amount` times\n const suffix = `\\\\${char}`.repeat(Math.max(amount, 1))\n\n // Negative lookahead: Ensures that the matched sequence is not followed by the same character\n const postSuffix = `(?!\\\\${char})`\n\n return `${prePrefix}${prefix}${postPrefix}${content}${preSuffix}${suffix}${postSuffix}`\n}\n","import type {EditorContext} from '@portabletext/editor'\nimport {raise} from '@portabletext/editor/behaviors'\nimport {getPathSubSchema} from '@portabletext/editor/traversal'\nimport {defineInputRule} from '@portabletext/plugin-input-rule'\nimport {createCharacterPairRegex} from './regex.character-pair'\n\n/**\n * A character pair is an input rule: typing the closing half of a pair\n * (`**bold**`, `_em_`) decorates the content between the markers and deletes\n * the markers. `defineInputRule` supplies the match locations; the\n * `InputRulePlugin` machinery supplies smart-undo on Backspace and the\n * caret-departure tracking the plugin previously hand-rolled.\n */\nexport function createCharacterPairRule(config: {\n decorator: ({\n context,\n schema,\n }: {\n context: Pick<EditorContext, 'schema'>\n /**\n * @deprecated Use `context.schema` instead\n */\n schema: EditorContext['schema']\n }) => string | undefined\n pair: {char: string; amount: number}\n}) {\n if (config.pair.amount < 1) {\n console.warn(\n `The amount of characters in the pair should be greater than 0`,\n )\n }\n\n const pairRegex = createCharacterPairRegex(\n config.pair.char,\n config.pair.amount,\n )\n\n return defineInputRule({\n // `$`-anchored: the pair must close at the insertion point, so at most\n // one match fires per insert.\n on: new RegExp(`${pairRegex}$`),\n // A pair only deletes its markers, so a match whose CONTENT spans an\n // inline object is harmless and must decorate (`**bo`, insert an\n // object, `ld**`). An inline object anywhere else in the match, between\n // the marker characters, means this is not a pair, and the machinery\n // drops the match.\n inlineObjects: {allow: ['content']},\n guard: ({snapshot, event}) => {\n if (config.pair.amount < 1) {\n return false\n }\n\n const subSchema = getPathSubSchema(snapshot, event.focusBlock.path)\n const decorator = config.decorator({\n context: {schema: subSchema},\n schema: subSchema,\n })\n\n if (decorator === undefined) {\n return false\n }\n\n const match = event.matches.at(0)\n\n if (!match) {\n return false\n }\n\n // `createCharacterPairRegex` captures the content between the markers\n // as the named group `content`. Prefix and suffix offsets derive from\n // the whole match and the content group.\n const content = match.groups['content']\n\n if (!content) {\n return false\n }\n\n const prefixOffsets = {\n anchor: match.targetOffsets.anchor,\n focus: content.targetOffsets.anchor,\n }\n const suffixOffsets = {\n anchor: content.targetOffsets.focus,\n focus: match.targetOffsets.focus,\n }\n\n return {\n decorator,\n contentOffsets: {\n anchor: content.targetOffsets.anchor,\n focus: content.targetOffsets.focus,\n },\n prefixOffsets,\n suffixOffsets,\n }\n },\n actions: [\n (_, {decorator, contentOffsets, prefixOffsets, suffixOffsets}) => [\n // Decorate the text between the prefix and suffix\n raise({\n type: 'decorator.add',\n decorator,\n at: contentOffsets,\n }),\n // Delete the suffix, then the prefix (suffix first so the prefix\n // offsets stay valid)\n raise({\n type: 'delete.text',\n at: suffixOffsets,\n }),\n raise({\n type: 'delete.text',\n at: prefixOffsets,\n }),\n // Toggle the decorator off so the next inserted text isn't decorated\n raise({\n type: 'decorator.remove',\n decorator,\n }),\n ],\n ],\n })\n}\n","import type {EditorContext} from '@portabletext/editor'\nimport {InputRulePlugin} from '@portabletext/plugin-input-rule'\nimport {useMemo} from 'react'\nimport {createCharacterPairRule} from './rule.character-pair'\n\n/**\n * @public\n */\nexport function CharacterPairDecoratorPlugin(props: {\n decorator: ({\n context,\n schema,\n }: {\n context: Pick<EditorContext, 'schema'>\n /**\n * @deprecated Use `context.schema` instead\n */\n schema: EditorContext['schema']\n }) => string | undefined\n pair: {char: string; amount: number}\n}) {\n const rules = useMemo(\n () => [\n createCharacterPairRule({\n decorator: props.decorator,\n pair: props.pair,\n }),\n ],\n [props.decorator, props.pair],\n )\n\n return <InputRulePlugin rules={rules} />\n}\n"],"names":["createCharacterPairRegex","char","amount","prePrefix","prefix","repeat","Math","max","postPrefix","content","preSuffix","suffix","postSuffix","createCharacterPairRule","config","pair","console","warn","pairRegex","defineInputRule","on","RegExp","inlineObjects","allow","guard","snapshot","event","subSchema","getPathSubSchema","focusBlock","path","decorator","context","schema","undefined","match","matches","at","groups","prefixOffsets","anchor","targetOffsets","focus","suffixOffsets","contentOffsets","actions","_","raise","type","CharacterPairDecoratorPlugin","props","$","_c","t0","t1","rules","t2"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@portabletext/plugin-character-pair-decorator",
|
|
3
|
-
"version": "8.0.
|
|
3
|
+
"version": "8.0.31",
|
|
4
4
|
"description": "Automatically match a pair of characters and decorate the text in between",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"behaviors",
|
|
@@ -32,8 +32,7 @@
|
|
|
32
32
|
"./package.json": "./package.json"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@
|
|
36
|
-
"xstate": "^5.32.2"
|
|
35
|
+
"@portabletext/plugin-input-rule": "^6.0.0"
|
|
37
36
|
},
|
|
38
37
|
"devDependencies": {
|
|
39
38
|
"@sanity/pkg-utils": "^10.8.2",
|
|
@@ -49,13 +48,14 @@
|
|
|
49
48
|
"typescript": "6.0.3",
|
|
50
49
|
"typescript-eslint": "^8.62.0",
|
|
51
50
|
"vitest": "^4.1.9",
|
|
52
|
-
"@portabletext/editor": "^7.10.
|
|
51
|
+
"@portabletext/editor": "^7.10.3",
|
|
53
52
|
"@portabletext/schema": "2.2.2",
|
|
53
|
+
"@portabletext/test": "1.0.4",
|
|
54
54
|
"racejar": "2.0.9"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
57
|
"react": "^19.2",
|
|
58
|
-
"@portabletext/editor": "^7.10.
|
|
58
|
+
"@portabletext/editor": "^7.10.3"
|
|
59
59
|
},
|
|
60
60
|
"engines": {
|
|
61
61
|
"node": ">=20.19 <22 || >=22.12"
|