@portabletext/plugin-input-rule 0.1.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.
- package/LICENSE +21 -0
- package/README.md +5 -0
- package/dist/index.cjs +433 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +129 -0
- package/dist/index.d.ts +129 -0
- package/dist/index.js +439 -0
- package/dist/index.js.map +1 -0
- package/package.json +85 -0
- package/src/edge-cases.feature +81 -0
- package/src/edge-cases.test.tsx +59 -0
- package/src/global.d.ts +4 -0
- package/src/index.ts +3 -0
- package/src/input-rule.ts +82 -0
- package/src/plugin.input-rule.tsx +603 -0
- package/src/text-transform-rule.ts +94 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BlockOffset,
|
|
3
|
+
BlockPath,
|
|
4
|
+
EditorSelection,
|
|
5
|
+
PortableTextTextBlock,
|
|
6
|
+
} from '@portabletext/editor'
|
|
7
|
+
import type {
|
|
8
|
+
BehaviorActionSet,
|
|
9
|
+
BehaviorGuard,
|
|
10
|
+
} from '@portabletext/editor/behaviors'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @alpha
|
|
14
|
+
*/
|
|
15
|
+
export declare function defineInputRule(config: InputRule): InputRule
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Define an `InputRule` specifically designed to transform matched text into
|
|
19
|
+
* some other text.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```tsx
|
|
23
|
+
* const transformRule = defineTextTransformRule({
|
|
24
|
+
* on: /--/,
|
|
25
|
+
* transform: () => '—',
|
|
26
|
+
* })
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @alpha
|
|
30
|
+
*/
|
|
31
|
+
export declare function defineTextTransformRule(
|
|
32
|
+
config: TextTransformRule,
|
|
33
|
+
): InputRule
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @alpha
|
|
37
|
+
*/
|
|
38
|
+
export declare type InputRule = {
|
|
39
|
+
on: RegExp
|
|
40
|
+
guard?: InputRuleGuard
|
|
41
|
+
actions: Array<BehaviorActionSet<InputRuleEvent, boolean>>
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @alpha
|
|
46
|
+
*/
|
|
47
|
+
export declare type InputRuleEvent = {
|
|
48
|
+
type: 'custom.input rule'
|
|
49
|
+
/**
|
|
50
|
+
* Matches found by the input rule
|
|
51
|
+
*/
|
|
52
|
+
matches: Array<InputRuleMatch>
|
|
53
|
+
/**
|
|
54
|
+
* The text before the insertion
|
|
55
|
+
*/
|
|
56
|
+
textBefore: string
|
|
57
|
+
/**
|
|
58
|
+
* The text is destined to be inserted
|
|
59
|
+
*/
|
|
60
|
+
textInserted: string
|
|
61
|
+
/**
|
|
62
|
+
* The text block where the insertion takes place
|
|
63
|
+
*/
|
|
64
|
+
focusTextBlock: {
|
|
65
|
+
path: BlockPath
|
|
66
|
+
node: PortableTextTextBlock
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @alpha
|
|
72
|
+
*/
|
|
73
|
+
export declare type InputRuleGuard = BehaviorGuard<InputRuleEvent, boolean>
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Match found in the text after the insertion
|
|
77
|
+
* @alpha
|
|
78
|
+
*/
|
|
79
|
+
export declare type InputRuleMatch = InputRuleMatchLocation & {
|
|
80
|
+
groupMatches: Array<InputRuleMatchLocation>
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
declare type InputRuleMatchLocation = {
|
|
84
|
+
/**
|
|
85
|
+
* Estimated selection of where in the original text the match is located.
|
|
86
|
+
* The selection is estimated since the match is found in the text after
|
|
87
|
+
* insertion.
|
|
88
|
+
*/
|
|
89
|
+
selection: NonNullable<EditorSelection>
|
|
90
|
+
/**
|
|
91
|
+
* Block offsets of the match in the text after the insertion
|
|
92
|
+
*/
|
|
93
|
+
targetOffsets: {
|
|
94
|
+
anchor: BlockOffset
|
|
95
|
+
focus: BlockOffset
|
|
96
|
+
backward: boolean
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Turn an array of `InputRule`s into a Behavior that can be used to apply the
|
|
102
|
+
* rules to the editor.
|
|
103
|
+
*
|
|
104
|
+
* The plugin handles undo/redo out of the box including smart undo with
|
|
105
|
+
* Backspace.
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```tsx
|
|
109
|
+
* <InputRulePlugin rules={smartQuotesRules} />
|
|
110
|
+
* ```
|
|
111
|
+
*
|
|
112
|
+
* @alpha
|
|
113
|
+
*/
|
|
114
|
+
export declare function InputRulePlugin(props: InputRulePluginProps): null
|
|
115
|
+
|
|
116
|
+
declare type InputRulePluginProps = {
|
|
117
|
+
rules: Array<InputRule>
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* @alpha
|
|
122
|
+
*/
|
|
123
|
+
export declare type TextTransformRule = {
|
|
124
|
+
on: RegExp
|
|
125
|
+
guard?: InputRuleGuard
|
|
126
|
+
transform: () => string
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export {}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
import { c } from "react/compiler-runtime";
|
|
2
|
+
import { useEditor } from "@portabletext/editor";
|
|
3
|
+
import { defineBehavior, effect, forward, raise } from "@portabletext/editor/behaviors";
|
|
4
|
+
import { getBlockOffsets, getFocusTextBlock, getBlockTextBefore, getMarkState } from "@portabletext/editor/selectors";
|
|
5
|
+
import { blockOffsetsToSelection } from "@portabletext/editor/utils";
|
|
6
|
+
import { useActorRef } from "@xstate/react";
|
|
7
|
+
import { setup, fromCallback } from "xstate";
|
|
8
|
+
function defineInputRule(config) {
|
|
9
|
+
return config;
|
|
10
|
+
}
|
|
11
|
+
function createInputRuleBehavior(config) {
|
|
12
|
+
return defineBehavior({
|
|
13
|
+
on: "insert.text",
|
|
14
|
+
guard: ({
|
|
15
|
+
snapshot,
|
|
16
|
+
event,
|
|
17
|
+
dom
|
|
18
|
+
}) => {
|
|
19
|
+
const focusTextBlock = getFocusTextBlock(snapshot);
|
|
20
|
+
if (!focusTextBlock)
|
|
21
|
+
return !1;
|
|
22
|
+
const originalTextBefore = getBlockTextBefore(snapshot);
|
|
23
|
+
let textBefore = originalTextBefore;
|
|
24
|
+
const originalNewText = textBefore + event.text;
|
|
25
|
+
let newText = originalNewText;
|
|
26
|
+
const foundMatches = [], foundActions = [];
|
|
27
|
+
for (const rule of config.rules) {
|
|
28
|
+
const matcher = new RegExp(rule.on.source, "gd");
|
|
29
|
+
for (; ; ) {
|
|
30
|
+
const matchesInTextBefore = [...textBefore.matchAll(matcher)].flatMap((regExpMatch) => {
|
|
31
|
+
if (regExpMatch.indices === void 0)
|
|
32
|
+
return [];
|
|
33
|
+
const [index] = regExpMatch.indices.at(0) ?? [void 0, void 0];
|
|
34
|
+
if (index === void 0)
|
|
35
|
+
return [];
|
|
36
|
+
const [firstMatchStart, firstMatchEnd] = regExpMatch.indices.at(0) ?? [void 0, void 0];
|
|
37
|
+
if (firstMatchStart === void 0 || firstMatchEnd === void 0)
|
|
38
|
+
return [];
|
|
39
|
+
const match = {
|
|
40
|
+
index: firstMatchStart,
|
|
41
|
+
length: firstMatchEnd - firstMatchStart
|
|
42
|
+
}, adjustedIndex = match.index + originalNewText.length - newText.length, targetOffsets = {
|
|
43
|
+
anchor: {
|
|
44
|
+
path: focusTextBlock.path,
|
|
45
|
+
offset: adjustedIndex
|
|
46
|
+
},
|
|
47
|
+
focus: {
|
|
48
|
+
path: focusTextBlock.path,
|
|
49
|
+
offset: adjustedIndex + match.length
|
|
50
|
+
},
|
|
51
|
+
backward: !1
|
|
52
|
+
}, selection = blockOffsetsToSelection({
|
|
53
|
+
context: snapshot.context,
|
|
54
|
+
offsets: targetOffsets,
|
|
55
|
+
backward: !1
|
|
56
|
+
});
|
|
57
|
+
if (!selection)
|
|
58
|
+
return [];
|
|
59
|
+
const groupMatches = regExpMatch.indices.length > 1 ? regExpMatch.indices.slice(1).map(([start, end]) => ({
|
|
60
|
+
index: start,
|
|
61
|
+
length: end - start
|
|
62
|
+
})) : [];
|
|
63
|
+
return [{
|
|
64
|
+
selection,
|
|
65
|
+
targetOffsets,
|
|
66
|
+
groupMatches: groupMatches.flatMap((groupMatch) => {
|
|
67
|
+
const adjustedIndex2 = groupMatch.index + originalNewText.length - newText.length, targetOffsets2 = {
|
|
68
|
+
anchor: {
|
|
69
|
+
path: focusTextBlock.path,
|
|
70
|
+
offset: adjustedIndex2
|
|
71
|
+
},
|
|
72
|
+
focus: {
|
|
73
|
+
path: focusTextBlock.path,
|
|
74
|
+
offset: adjustedIndex2 + groupMatch.length
|
|
75
|
+
},
|
|
76
|
+
backward: !1
|
|
77
|
+
}, normalizedOffsets = {
|
|
78
|
+
anchor: {
|
|
79
|
+
path: focusTextBlock.path,
|
|
80
|
+
offset: Math.min(targetOffsets2.anchor.offset, originalTextBefore.length)
|
|
81
|
+
},
|
|
82
|
+
focus: {
|
|
83
|
+
path: focusTextBlock.path,
|
|
84
|
+
offset: Math.min(targetOffsets2.focus.offset, originalTextBefore.length)
|
|
85
|
+
},
|
|
86
|
+
backward: !1
|
|
87
|
+
}, selection2 = blockOffsetsToSelection({
|
|
88
|
+
context: snapshot.context,
|
|
89
|
+
offsets: normalizedOffsets,
|
|
90
|
+
backward: !1
|
|
91
|
+
});
|
|
92
|
+
return selection2 ? {
|
|
93
|
+
selection: selection2,
|
|
94
|
+
targetOffsets: targetOffsets2
|
|
95
|
+
} : [];
|
|
96
|
+
})
|
|
97
|
+
}];
|
|
98
|
+
}), ruleMatches = [...newText.matchAll(matcher)].flatMap((regExpMatch) => {
|
|
99
|
+
if (regExpMatch.indices === void 0)
|
|
100
|
+
return [];
|
|
101
|
+
const [index] = regExpMatch.indices.at(0) ?? [void 0, void 0];
|
|
102
|
+
if (index === void 0)
|
|
103
|
+
return [];
|
|
104
|
+
const [firstMatchStart, firstMatchEnd] = regExpMatch.indices.at(0) ?? [void 0, void 0];
|
|
105
|
+
if (firstMatchStart === void 0 || firstMatchEnd === void 0)
|
|
106
|
+
return [];
|
|
107
|
+
const match = {
|
|
108
|
+
index: firstMatchStart,
|
|
109
|
+
length: firstMatchEnd - firstMatchStart
|
|
110
|
+
}, adjustedIndex = match.index + originalNewText.length - newText.length, targetOffsets = {
|
|
111
|
+
anchor: {
|
|
112
|
+
path: focusTextBlock.path,
|
|
113
|
+
offset: adjustedIndex
|
|
114
|
+
},
|
|
115
|
+
focus: {
|
|
116
|
+
path: focusTextBlock.path,
|
|
117
|
+
offset: adjustedIndex + match.length
|
|
118
|
+
},
|
|
119
|
+
backward: !1
|
|
120
|
+
}, normalizedOffsets = {
|
|
121
|
+
anchor: {
|
|
122
|
+
path: focusTextBlock.path,
|
|
123
|
+
offset: Math.min(targetOffsets.anchor.offset, originalTextBefore.length)
|
|
124
|
+
},
|
|
125
|
+
focus: {
|
|
126
|
+
path: focusTextBlock.path,
|
|
127
|
+
offset: Math.min(targetOffsets.focus.offset, originalTextBefore.length)
|
|
128
|
+
},
|
|
129
|
+
backward: !1
|
|
130
|
+
}, selection = blockOffsetsToSelection({
|
|
131
|
+
context: snapshot.context,
|
|
132
|
+
offsets: normalizedOffsets,
|
|
133
|
+
backward: !1
|
|
134
|
+
});
|
|
135
|
+
if (!selection)
|
|
136
|
+
return [];
|
|
137
|
+
const groupMatches = regExpMatch.indices.length > 1 ? regExpMatch.indices.slice(1).map(([start, end]) => ({
|
|
138
|
+
index: start,
|
|
139
|
+
length: end - start
|
|
140
|
+
})) : [], ruleMatch = {
|
|
141
|
+
selection,
|
|
142
|
+
targetOffsets,
|
|
143
|
+
groupMatches: groupMatches.flatMap((groupMatch) => {
|
|
144
|
+
const adjustedIndex2 = groupMatch.index + originalNewText.length - newText.length, targetOffsets2 = {
|
|
145
|
+
anchor: {
|
|
146
|
+
path: focusTextBlock.path,
|
|
147
|
+
offset: adjustedIndex2
|
|
148
|
+
},
|
|
149
|
+
focus: {
|
|
150
|
+
path: focusTextBlock.path,
|
|
151
|
+
offset: adjustedIndex2 + groupMatch.length
|
|
152
|
+
},
|
|
153
|
+
backward: !1
|
|
154
|
+
}, normalizedOffsets2 = {
|
|
155
|
+
anchor: {
|
|
156
|
+
path: focusTextBlock.path,
|
|
157
|
+
offset: Math.min(targetOffsets2.anchor.offset, originalTextBefore.length)
|
|
158
|
+
},
|
|
159
|
+
focus: {
|
|
160
|
+
path: focusTextBlock.path,
|
|
161
|
+
offset: Math.min(targetOffsets2.focus.offset, originalTextBefore.length)
|
|
162
|
+
},
|
|
163
|
+
backward: !1
|
|
164
|
+
}, selection2 = blockOffsetsToSelection({
|
|
165
|
+
context: snapshot.context,
|
|
166
|
+
offsets: normalizedOffsets2,
|
|
167
|
+
backward: !1
|
|
168
|
+
});
|
|
169
|
+
return selection2 ? [{
|
|
170
|
+
targetOffsets: targetOffsets2,
|
|
171
|
+
selection: selection2
|
|
172
|
+
}] : [];
|
|
173
|
+
})
|
|
174
|
+
};
|
|
175
|
+
return foundMatches.some((foundMatch) => foundMatch.targetOffsets.anchor.offset === adjustedIndex) ? [] : matchesInTextBefore.some((matchInTextBefore) => matchInTextBefore.targetOffsets.anchor.offset === adjustedIndex) ? [] : [ruleMatch];
|
|
176
|
+
});
|
|
177
|
+
if (ruleMatches.length > 0) {
|
|
178
|
+
const guardResult = rule.guard?.({
|
|
179
|
+
snapshot,
|
|
180
|
+
event: {
|
|
181
|
+
type: "custom.input rule",
|
|
182
|
+
matches: ruleMatches,
|
|
183
|
+
focusTextBlock,
|
|
184
|
+
textBefore: originalTextBefore,
|
|
185
|
+
textInserted: event.text
|
|
186
|
+
},
|
|
187
|
+
dom
|
|
188
|
+
}) ?? !0;
|
|
189
|
+
if (!guardResult)
|
|
190
|
+
break;
|
|
191
|
+
const actionSets = rule.actions.map((action) => action({
|
|
192
|
+
snapshot,
|
|
193
|
+
event: {
|
|
194
|
+
type: "custom.input rule",
|
|
195
|
+
matches: ruleMatches,
|
|
196
|
+
focusTextBlock,
|
|
197
|
+
textBefore: originalTextBefore,
|
|
198
|
+
textInserted: event.text
|
|
199
|
+
},
|
|
200
|
+
dom
|
|
201
|
+
}, guardResult));
|
|
202
|
+
for (const actionSet of actionSets)
|
|
203
|
+
for (const action of actionSet)
|
|
204
|
+
foundActions.push(action);
|
|
205
|
+
const matches = ruleMatches.flatMap((match) => match.groupMatches.length === 0 ? [match] : match.groupMatches);
|
|
206
|
+
for (const match of matches)
|
|
207
|
+
foundMatches.push(match), textBefore = newText.slice(0, match.targetOffsets.focus.offset ?? 0), newText = originalNewText.slice(match.targetOffsets.focus.offset ?? 0);
|
|
208
|
+
} else
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return foundActions.length === 0 ? !1 : {
|
|
213
|
+
actions: foundActions
|
|
214
|
+
};
|
|
215
|
+
},
|
|
216
|
+
actions: [({
|
|
217
|
+
event
|
|
218
|
+
}) => [forward(event)], (_, {
|
|
219
|
+
actions
|
|
220
|
+
}) => actions, ({
|
|
221
|
+
snapshot
|
|
222
|
+
}) => [effect(() => {
|
|
223
|
+
const blockOffsets = getBlockOffsets(snapshot);
|
|
224
|
+
config.onApply({
|
|
225
|
+
endOffsets: blockOffsets
|
|
226
|
+
});
|
|
227
|
+
})]]
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
function InputRulePlugin(props) {
|
|
231
|
+
const $ = c(3), editor = useEditor();
|
|
232
|
+
let t0;
|
|
233
|
+
return $[0] !== editor || $[1] !== props.rules ? (t0 = {
|
|
234
|
+
input: {
|
|
235
|
+
editor,
|
|
236
|
+
rules: props.rules
|
|
237
|
+
}
|
|
238
|
+
}, $[0] = editor, $[1] = props.rules, $[2] = t0) : t0 = $[2], useActorRef(inputRuleMachine, t0), null;
|
|
239
|
+
}
|
|
240
|
+
const inputRuleListenerCallback = ({
|
|
241
|
+
input,
|
|
242
|
+
sendBack
|
|
243
|
+
}) => {
|
|
244
|
+
const unregister = input.editor.registerBehavior({
|
|
245
|
+
behavior: createInputRuleBehavior({
|
|
246
|
+
rules: input.rules,
|
|
247
|
+
onApply: ({
|
|
248
|
+
endOffsets
|
|
249
|
+
}) => {
|
|
250
|
+
sendBack({
|
|
251
|
+
type: "input rule raised",
|
|
252
|
+
endOffsets
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
})
|
|
256
|
+
});
|
|
257
|
+
return () => {
|
|
258
|
+
unregister();
|
|
259
|
+
};
|
|
260
|
+
}, deleteBackwardListenerCallback = ({
|
|
261
|
+
input,
|
|
262
|
+
sendBack
|
|
263
|
+
}) => input.editor.registerBehavior({
|
|
264
|
+
behavior: defineBehavior({
|
|
265
|
+
on: "delete.backward",
|
|
266
|
+
actions: [() => [raise({
|
|
267
|
+
type: "history.undo"
|
|
268
|
+
}), effect(() => {
|
|
269
|
+
sendBack({
|
|
270
|
+
type: "history.undo raised"
|
|
271
|
+
});
|
|
272
|
+
})]]
|
|
273
|
+
})
|
|
274
|
+
}), selectionListenerCallback = ({
|
|
275
|
+
sendBack,
|
|
276
|
+
input
|
|
277
|
+
}) => input.editor.registerBehavior({
|
|
278
|
+
behavior: defineBehavior({
|
|
279
|
+
on: "select",
|
|
280
|
+
guard: ({
|
|
281
|
+
snapshot,
|
|
282
|
+
event
|
|
283
|
+
}) => ({
|
|
284
|
+
blockOffsets: getBlockOffsets({
|
|
285
|
+
...snapshot,
|
|
286
|
+
context: {
|
|
287
|
+
...snapshot.context,
|
|
288
|
+
selection: event.at
|
|
289
|
+
}
|
|
290
|
+
})
|
|
291
|
+
}),
|
|
292
|
+
actions: [({
|
|
293
|
+
event
|
|
294
|
+
}, {
|
|
295
|
+
blockOffsets
|
|
296
|
+
}) => [effect(() => {
|
|
297
|
+
sendBack({
|
|
298
|
+
type: "selection changed",
|
|
299
|
+
blockOffsets
|
|
300
|
+
});
|
|
301
|
+
}), forward(event)]]
|
|
302
|
+
})
|
|
303
|
+
}), inputRuleSetup = setup({
|
|
304
|
+
types: {
|
|
305
|
+
context: {},
|
|
306
|
+
input: {},
|
|
307
|
+
events: {}
|
|
308
|
+
},
|
|
309
|
+
actors: {
|
|
310
|
+
"delete.backward listener": fromCallback(deleteBackwardListenerCallback),
|
|
311
|
+
"input rule listener": fromCallback(inputRuleListenerCallback),
|
|
312
|
+
"selection listener": fromCallback(selectionListenerCallback)
|
|
313
|
+
},
|
|
314
|
+
guards: {
|
|
315
|
+
"block offset changed": ({
|
|
316
|
+
context,
|
|
317
|
+
event
|
|
318
|
+
}) => {
|
|
319
|
+
if (event.type !== "selection changed")
|
|
320
|
+
return !1;
|
|
321
|
+
if (!event.blockOffsets || !context.endOffsets)
|
|
322
|
+
return !0;
|
|
323
|
+
const startChanged = context.endOffsets.start.path[0]._key !== event.blockOffsets.start.path[0]._key || context.endOffsets.start.offset !== event.blockOffsets.start.offset, endChanged = context.endOffsets.end.path[0]._key !== event.blockOffsets.end.path[0]._key || context.endOffsets.end.offset !== event.blockOffsets.end.offset;
|
|
324
|
+
return startChanged || endChanged;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}), assignEndOffsets = inputRuleSetup.assign({
|
|
328
|
+
endOffsets: ({
|
|
329
|
+
context,
|
|
330
|
+
event
|
|
331
|
+
}) => event.type === "input rule raised" ? event.endOffsets : context.endOffsets
|
|
332
|
+
}), inputRuleMachine = inputRuleSetup.createMachine({
|
|
333
|
+
id: "input rule",
|
|
334
|
+
context: ({
|
|
335
|
+
input
|
|
336
|
+
}) => ({
|
|
337
|
+
editor: input.editor,
|
|
338
|
+
rules: input.rules,
|
|
339
|
+
endOffsets: void 0
|
|
340
|
+
}),
|
|
341
|
+
initial: "idle",
|
|
342
|
+
invoke: {
|
|
343
|
+
src: "input rule listener",
|
|
344
|
+
input: ({
|
|
345
|
+
context
|
|
346
|
+
}) => ({
|
|
347
|
+
editor: context.editor,
|
|
348
|
+
rules: context.rules
|
|
349
|
+
})
|
|
350
|
+
},
|
|
351
|
+
on: {
|
|
352
|
+
"input rule raised": {
|
|
353
|
+
target: ".input rule applied",
|
|
354
|
+
actions: assignEndOffsets
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
states: {
|
|
358
|
+
idle: {},
|
|
359
|
+
"input rule applied": {
|
|
360
|
+
invoke: [{
|
|
361
|
+
src: "delete.backward listener",
|
|
362
|
+
input: ({
|
|
363
|
+
context
|
|
364
|
+
}) => ({
|
|
365
|
+
editor: context.editor
|
|
366
|
+
})
|
|
367
|
+
}, {
|
|
368
|
+
src: "selection listener",
|
|
369
|
+
input: ({
|
|
370
|
+
context
|
|
371
|
+
}) => ({
|
|
372
|
+
editor: context.editor
|
|
373
|
+
})
|
|
374
|
+
}],
|
|
375
|
+
on: {
|
|
376
|
+
"selection changed": {
|
|
377
|
+
target: "idle",
|
|
378
|
+
guard: "block offset changed"
|
|
379
|
+
},
|
|
380
|
+
"history.undo raised": {
|
|
381
|
+
target: "idle"
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
function defineTextTransformRule(config) {
|
|
388
|
+
return {
|
|
389
|
+
on: config.on,
|
|
390
|
+
guard: config.guard ?? (() => !0),
|
|
391
|
+
actions: [({
|
|
392
|
+
snapshot,
|
|
393
|
+
event
|
|
394
|
+
}) => {
|
|
395
|
+
const matches = event.matches.flatMap((match) => match.groupMatches.length === 0 ? [match] : match.groupMatches), textLengthDelta = matches.reduce((length, match) => length - (config.transform().length - (match.targetOffsets.focus.offset - match.targetOffsets.anchor.offset)), 0), newText = event.textBefore + event.textInserted, endCaretPosition = {
|
|
396
|
+
path: event.focusTextBlock.path,
|
|
397
|
+
offset: newText.length - textLengthDelta
|
|
398
|
+
};
|
|
399
|
+
return [...matches.reverse().flatMap((match) => [raise({
|
|
400
|
+
type: "select",
|
|
401
|
+
at: match.targetOffsets
|
|
402
|
+
}), raise({
|
|
403
|
+
type: "delete",
|
|
404
|
+
at: match.targetOffsets
|
|
405
|
+
}), raise({
|
|
406
|
+
type: "insert.child",
|
|
407
|
+
child: {
|
|
408
|
+
_type: snapshot.context.schema.span.name,
|
|
409
|
+
text: config.transform(),
|
|
410
|
+
marks: getMarkState({
|
|
411
|
+
...snapshot,
|
|
412
|
+
context: {
|
|
413
|
+
...snapshot.context,
|
|
414
|
+
selection: {
|
|
415
|
+
anchor: match.selection.anchor,
|
|
416
|
+
focus: {
|
|
417
|
+
path: match.selection.focus.path,
|
|
418
|
+
offset: Math.min(match.selection.focus.offset, event.textBefore.length)
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
})?.marks ?? []
|
|
423
|
+
}
|
|
424
|
+
})]), raise({
|
|
425
|
+
type: "select",
|
|
426
|
+
at: {
|
|
427
|
+
anchor: endCaretPosition,
|
|
428
|
+
focus: endCaretPosition
|
|
429
|
+
}
|
|
430
|
+
})];
|
|
431
|
+
}]
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
export {
|
|
435
|
+
InputRulePlugin,
|
|
436
|
+
defineInputRule,
|
|
437
|
+
defineTextTransformRule
|
|
438
|
+
};
|
|
439
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/input-rule.ts","../src/plugin.input-rule.tsx","../src/text-transform-rule.ts"],"sourcesContent":["import type {\n BlockOffset,\n BlockPath,\n EditorSelection,\n PortableTextTextBlock,\n} from '@portabletext/editor'\nimport type {\n BehaviorActionSet,\n BehaviorGuard,\n} from '@portabletext/editor/behaviors'\n\ntype InputRuleMatchLocation = {\n /**\n * Estimated selection of where in the original text the match is located.\n * The selection is estimated since the match is found in the text after\n * insertion.\n */\n selection: NonNullable<EditorSelection>\n /**\n * Block offsets of the match in the text after the insertion\n */\n targetOffsets: {\n anchor: BlockOffset\n focus: BlockOffset\n backward: boolean\n }\n}\n\n/**\n * Match found in the text after the insertion\n * @alpha\n */\nexport type InputRuleMatch = InputRuleMatchLocation & {\n groupMatches: Array<InputRuleMatchLocation>\n}\n\n/**\n * @alpha\n */\nexport type InputRuleEvent = {\n type: 'custom.input rule'\n /**\n * Matches found by the input rule\n */\n matches: Array<InputRuleMatch>\n /**\n * The text before the insertion\n */\n textBefore: string\n /**\n * The text is destined to be inserted\n */\n textInserted: string\n /**\n * The text block where the insertion takes place\n */\n focusTextBlock: {\n path: BlockPath\n node: PortableTextTextBlock\n }\n}\n\n/**\n * @alpha\n */\nexport type InputRuleGuard = BehaviorGuard<InputRuleEvent, boolean>\n\n/**\n * @alpha\n */\nexport type InputRule = {\n on: RegExp\n guard?: InputRuleGuard\n actions: Array<BehaviorActionSet<InputRuleEvent, boolean>>\n}\n\n/**\n * @alpha\n */\nexport function defineInputRule(config: InputRule): InputRule {\n return config\n}\n","import {useEditor, type BlockOffset, type Editor} from '@portabletext/editor'\nimport {\n defineBehavior,\n effect,\n forward,\n raise,\n type BehaviorAction,\n} from '@portabletext/editor/behaviors'\nimport {\n getBlockOffsets,\n getBlockTextBefore,\n getFocusTextBlock,\n} from '@portabletext/editor/selectors'\nimport {blockOffsetsToSelection} from '@portabletext/editor/utils'\nimport {useActorRef} from '@xstate/react'\nimport {\n fromCallback,\n setup,\n type AnyEventObject,\n type CallbackLogicFunction,\n} from 'xstate'\nimport type {InputRule, InputRuleMatch} from './input-rule'\n\nfunction createInputRuleBehavior(config: {\n rules: Array<InputRule>\n onApply: ({\n endOffsets,\n }: {\n endOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n }) => void\n}) {\n return defineBehavior({\n on: 'insert.text',\n guard: ({snapshot, event, dom}) => {\n const focusTextBlock = getFocusTextBlock(snapshot)\n\n if (!focusTextBlock) {\n return false\n }\n\n const originalTextBefore = getBlockTextBefore(snapshot)\n let textBefore = originalTextBefore\n const originalNewText = textBefore + event.text\n let newText = originalNewText\n\n const foundMatches: Array<InputRuleMatch['groupMatches'][number]> = []\n const foundActions: Array<BehaviorAction> = []\n\n for (const rule of config.rules) {\n const matcher = new RegExp(rule.on.source, 'gd')\n\n while (true) {\n // Find matches in the text before the insertion\n const matchesInTextBefore: Array<InputRuleMatch> = [\n ...textBefore.matchAll(matcher),\n ].flatMap((regExpMatch) => {\n if (regExpMatch.indices === undefined) {\n return []\n }\n\n const [index] = regExpMatch.indices.at(0) ?? [undefined, undefined]\n\n if (index === undefined) {\n return []\n }\n\n const [firstMatchStart, firstMatchEnd] = regExpMatch.indices.at(\n 0,\n ) ?? [undefined, undefined]\n\n if (firstMatchStart === undefined || firstMatchEnd === undefined) {\n return []\n }\n\n const match = {\n index: firstMatchStart,\n length: firstMatchEnd - firstMatchStart,\n }\n const adjustedIndex =\n match.index + originalNewText.length - newText.length\n const targetOffsets = {\n anchor: {\n path: focusTextBlock.path,\n offset: adjustedIndex,\n },\n focus: {\n path: focusTextBlock.path,\n offset: adjustedIndex + match.length,\n },\n backward: false,\n }\n const selection = blockOffsetsToSelection({\n context: snapshot.context,\n offsets: targetOffsets,\n backward: false,\n })\n\n if (!selection) {\n return []\n }\n\n const groupMatches =\n regExpMatch.indices.length > 1\n ? regExpMatch.indices.slice(1).map(([start, end]) => ({\n index: start,\n length: end - start,\n }))\n : []\n const ruleMatch = {\n selection,\n targetOffsets,\n groupMatches: groupMatches.flatMap((groupMatch) => {\n const adjustedIndex =\n groupMatch.index + originalNewText.length - newText.length\n\n const targetOffsets = {\n anchor: {\n path: focusTextBlock.path,\n offset: adjustedIndex,\n },\n focus: {\n path: focusTextBlock.path,\n offset: adjustedIndex + groupMatch.length,\n },\n backward: false,\n }\n const normalizedOffsets = {\n anchor: {\n path: focusTextBlock.path,\n offset: Math.min(\n targetOffsets.anchor.offset,\n originalTextBefore.length,\n ),\n },\n focus: {\n path: focusTextBlock.path,\n offset: Math.min(\n targetOffsets.focus.offset,\n originalTextBefore.length,\n ),\n },\n backward: false,\n }\n const selection = blockOffsetsToSelection({\n context: snapshot.context,\n offsets: normalizedOffsets,\n backward: false,\n })\n\n if (!selection) {\n return []\n }\n\n return {\n selection,\n targetOffsets,\n }\n }),\n }\n\n return [ruleMatch]\n })\n const matchesInNewText = [...newText.matchAll(matcher)]\n // Find matches in the text after the insertion\n const ruleMatches = matchesInNewText.flatMap((regExpMatch) => {\n if (regExpMatch.indices === undefined) {\n return []\n }\n\n const [index] = regExpMatch.indices.at(0) ?? [undefined, undefined]\n\n if (index === undefined) {\n return []\n }\n\n const [firstMatchStart, firstMatchEnd] = regExpMatch.indices.at(\n 0,\n ) ?? [undefined, undefined]\n\n if (firstMatchStart === undefined || firstMatchEnd === undefined) {\n return []\n }\n\n const match = {\n index: firstMatchStart,\n length: firstMatchEnd - firstMatchStart,\n }\n const adjustedIndex =\n match.index + originalNewText.length - newText.length\n const targetOffsets = {\n anchor: {\n path: focusTextBlock.path,\n offset: adjustedIndex,\n },\n focus: {\n path: focusTextBlock.path,\n offset: adjustedIndex + match.length,\n },\n backward: false,\n }\n const normalizedOffsets = {\n anchor: {\n path: focusTextBlock.path,\n offset: Math.min(\n targetOffsets.anchor.offset,\n originalTextBefore.length,\n ),\n },\n focus: {\n path: focusTextBlock.path,\n offset: Math.min(\n targetOffsets.focus.offset,\n originalTextBefore.length,\n ),\n },\n backward: false,\n }\n const selection = blockOffsetsToSelection({\n context: snapshot.context,\n offsets: normalizedOffsets,\n backward: false,\n })\n\n if (!selection) {\n return []\n }\n\n const groupMatches =\n regExpMatch.indices.length > 1\n ? regExpMatch.indices.slice(1).map(([start, end]) => ({\n index: start,\n length: end - start,\n }))\n : []\n\n const ruleMatch = {\n selection,\n targetOffsets,\n groupMatches: groupMatches.flatMap((groupMatch) => {\n const adjustedIndex =\n groupMatch.index + originalNewText.length - newText.length\n\n const targetOffsets = {\n anchor: {\n path: focusTextBlock.path,\n offset: adjustedIndex,\n },\n focus: {\n path: focusTextBlock.path,\n offset: adjustedIndex + groupMatch.length,\n },\n backward: false,\n }\n const normalizedOffsets = {\n anchor: {\n path: focusTextBlock.path,\n offset: Math.min(\n targetOffsets.anchor.offset,\n originalTextBefore.length,\n ),\n },\n focus: {\n path: focusTextBlock.path,\n offset: Math.min(\n targetOffsets.focus.offset,\n originalTextBefore.length,\n ),\n },\n backward: false,\n }\n const selection = blockOffsetsToSelection({\n context: snapshot.context,\n offsets: normalizedOffsets,\n backward: false,\n })\n\n if (!selection) {\n return []\n }\n\n return [\n {\n targetOffsets,\n selection,\n },\n ]\n }),\n }\n\n const alreadyFound = foundMatches.some(\n (foundMatch) =>\n foundMatch.targetOffsets.anchor.offset === adjustedIndex,\n )\n\n // Ignore if this match has already been found\n if (alreadyFound) {\n return []\n }\n\n const existsInTextBefore = matchesInTextBefore.some(\n (matchInTextBefore) =>\n matchInTextBefore.targetOffsets.anchor.offset === adjustedIndex,\n )\n\n // Ignore if this match occurs in the text before the insertion\n if (existsInTextBefore) {\n return []\n }\n\n return [ruleMatch]\n })\n\n if (ruleMatches.length > 0) {\n const guardResult =\n rule.guard?.({\n snapshot,\n event: {\n type: 'custom.input rule',\n matches: ruleMatches,\n focusTextBlock,\n textBefore: originalTextBefore,\n textInserted: event.text,\n },\n dom,\n }) ?? true\n\n if (!guardResult) {\n break\n }\n\n const actionSets = rule.actions.map((action) =>\n action(\n {\n snapshot,\n event: {\n type: 'custom.input rule',\n matches: ruleMatches,\n focusTextBlock,\n textBefore: originalTextBefore,\n textInserted: event.text,\n },\n dom,\n },\n guardResult,\n ),\n )\n\n for (const actionSet of actionSets) {\n for (const action of actionSet) {\n foundActions.push(action)\n }\n }\n\n const matches = ruleMatches.flatMap((match) =>\n match.groupMatches.length === 0 ? [match] : match.groupMatches,\n )\n for (const match of matches) {\n // Remember each match and adjust `textBefore` and `newText` so\n // no subsequent matches can overlap with this one\n foundMatches.push(match)\n textBefore = newText.slice(\n 0,\n match.targetOffsets.focus.offset ?? 0,\n )\n newText = originalNewText.slice(\n match.targetOffsets.focus.offset ?? 0,\n )\n }\n } else {\n // If no match was found, break out of the loop to try the next\n // rule\n break\n }\n }\n }\n\n if (foundActions.length === 0) {\n return false\n }\n\n return {actions: foundActions}\n },\n actions: [\n ({event}) => [forward(event)],\n (_, {actions}) => actions,\n ({snapshot}) => [\n effect(() => {\n const blockOffsets = getBlockOffsets(snapshot)\n\n config.onApply({endOffsets: blockOffsets})\n }),\n ],\n ],\n })\n}\n\ntype InputRulePluginProps = {\n rules: Array<InputRule>\n}\n\n/**\n * Turn an array of `InputRule`s into a Behavior that can be used to apply the\n * rules to the editor.\n *\n * The plugin handles undo/redo out of the box including smart undo with\n * Backspace.\n *\n * @example\n * ```tsx\n * <InputRulePlugin rules={smartQuotesRules} />\n * ```\n *\n * @alpha\n */\nexport function InputRulePlugin(props: InputRulePluginProps) {\n const editor = useEditor()\n\n useActorRef(inputRuleMachine, {\n input: {editor, rules: props.rules},\n })\n\n return null\n}\n\ntype InputRuleMachineEvent =\n | {\n type: 'input rule raised'\n endOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n }\n | {type: 'history.undo raised'}\n | {\n type: 'selection changed'\n blockOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n }\n\nconst inputRuleListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n InputRuleMachineEvent,\n {\n editor: Editor\n rules: Array<InputRule>\n }\n> = ({input, sendBack}) => {\n const unregister = input.editor.registerBehavior({\n behavior: createInputRuleBehavior({\n rules: input.rules,\n onApply: ({endOffsets}) => {\n sendBack({type: 'input rule raised', endOffsets})\n },\n }),\n })\n\n return () => {\n unregister()\n }\n}\n\nconst deleteBackwardListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n InputRuleMachineEvent,\n {editor: Editor}\n> = ({input, sendBack}) => {\n return input.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'delete.backward',\n actions: [\n () => [\n raise({type: 'history.undo'}),\n effect(() => {\n sendBack({type: 'history.undo raised'})\n }),\n ],\n ],\n }),\n })\n}\n\nconst selectionListenerCallback: CallbackLogicFunction<\n AnyEventObject,\n InputRuleMachineEvent,\n {editor: Editor}\n> = ({sendBack, input}) => {\n const unregister = input.editor.registerBehavior({\n behavior: defineBehavior({\n on: 'select',\n guard: ({snapshot, event}) => {\n const blockOffsets = getBlockOffsets({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: event.at,\n },\n })\n\n return {blockOffsets}\n },\n actions: [\n ({event}, {blockOffsets}) => [\n effect(() => {\n sendBack({type: 'selection changed', blockOffsets})\n }),\n forward(event),\n ],\n ],\n }),\n })\n\n return unregister\n}\n\nconst inputRuleSetup = setup({\n types: {\n context: {} as {\n editor: Editor\n rules: Array<InputRule>\n endOffsets: {start: BlockOffset; end: BlockOffset} | undefined\n },\n input: {} as {\n editor: Editor\n rules: Array<InputRule>\n },\n events: {} as InputRuleMachineEvent,\n },\n actors: {\n 'delete.backward listener': fromCallback(deleteBackwardListenerCallback),\n 'input rule listener': fromCallback(inputRuleListenerCallback),\n 'selection listener': fromCallback(selectionListenerCallback),\n },\n guards: {\n 'block offset changed': ({context, event}) => {\n if (event.type !== 'selection changed') {\n return false\n }\n\n if (!event.blockOffsets || !context.endOffsets) {\n return true\n }\n\n const startChanged =\n context.endOffsets.start.path[0]._key !==\n event.blockOffsets.start.path[0]._key ||\n context.endOffsets.start.offset !== event.blockOffsets.start.offset\n const endChanged =\n context.endOffsets.end.path[0]._key !==\n event.blockOffsets.end.path[0]._key ||\n context.endOffsets.end.offset !== event.blockOffsets.end.offset\n\n return startChanged || endChanged\n },\n },\n})\n\nconst assignEndOffsets = inputRuleSetup.assign({\n endOffsets: ({context, event}) =>\n event.type === 'input rule raised' ? event.endOffsets : context.endOffsets,\n})\n\nconst inputRuleMachine = inputRuleSetup.createMachine({\n id: 'input rule',\n context: ({input}) => ({\n editor: input.editor,\n rules: input.rules,\n endOffsets: undefined,\n }),\n initial: 'idle',\n invoke: {\n src: 'input rule listener',\n input: ({context}) => ({\n editor: context.editor,\n rules: context.rules,\n }),\n },\n on: {\n 'input rule raised': {\n target: '.input rule applied',\n actions: assignEndOffsets,\n },\n },\n states: {\n 'idle': {},\n 'input rule applied': {\n invoke: [\n {\n src: 'delete.backward listener',\n input: ({context}) => ({editor: context.editor}),\n },\n {\n src: 'selection listener',\n input: ({context}) => ({editor: context.editor}),\n },\n ],\n on: {\n 'selection changed': {\n target: 'idle',\n guard: 'block offset changed',\n },\n 'history.undo raised': {\n target: 'idle',\n },\n },\n },\n },\n})\n","import {raise} from '@portabletext/editor/behaviors'\nimport {getMarkState} from '@portabletext/editor/selectors'\nimport type {InputRule, InputRuleGuard} from './input-rule'\n\n/**\n * @alpha\n */\nexport type TextTransformRule = {\n on: RegExp\n guard?: InputRuleGuard\n transform: () => string\n}\n\n/**\n * Define an `InputRule` specifically designed to transform matched text into\n * some other text.\n *\n * @example\n * ```tsx\n * const transformRule = defineTextTransformRule({\n * on: /--/,\n * transform: () => '—',\n * })\n * ```\n *\n * @alpha\n */\nexport function defineTextTransformRule(config: TextTransformRule): InputRule {\n return {\n on: config.on,\n guard: config.guard ?? (() => true),\n actions: [\n ({snapshot, event}) => {\n const matches = event.matches.flatMap((match) =>\n match.groupMatches.length === 0 ? [match] : match.groupMatches,\n )\n const textLengthDelta = matches.reduce((length, match) => {\n return (\n length -\n (config.transform().length -\n (match.targetOffsets.focus.offset -\n match.targetOffsets.anchor.offset))\n )\n }, 0)\n\n const newText = event.textBefore + event.textInserted\n const endCaretPosition = {\n path: event.focusTextBlock.path,\n offset: newText.length - textLengthDelta,\n }\n\n const actions = matches.reverse().flatMap((match) => [\n raise({type: 'select', at: match.targetOffsets}),\n raise({type: 'delete', at: match.targetOffsets}),\n raise({\n type: 'insert.child',\n child: {\n _type: snapshot.context.schema.span.name,\n text: config.transform(),\n marks:\n getMarkState({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: {\n anchor: match.selection.anchor,\n focus: {\n path: match.selection.focus.path,\n offset: Math.min(\n match.selection.focus.offset,\n event.textBefore.length,\n ),\n },\n },\n },\n })?.marks ?? [],\n },\n }),\n ])\n\n return [\n ...actions,\n raise({\n type: 'select',\n at: {\n anchor: endCaretPosition,\n focus: endCaretPosition,\n },\n }),\n ]\n },\n ],\n }\n}\n"],"names":["defineInputRule","config","createInputRuleBehavior","defineBehavior","on","guard","snapshot","event","dom","focusTextBlock","getFocusTextBlock","originalTextBefore","getBlockTextBefore","textBefore","originalNewText","text","newText","foundMatches","foundActions","rule","rules","matcher","RegExp","source","matchesInTextBefore","matchAll","flatMap","regExpMatch","indices","undefined","index","at","firstMatchStart","firstMatchEnd","match","length","adjustedIndex","targetOffsets","anchor","path","offset","focus","backward","selection","blockOffsetsToSelection","context","offsets","groupMatches","slice","map","start","end","groupMatch","normalizedOffsets","Math","min","ruleMatches","ruleMatch","some","foundMatch","matchInTextBefore","guardResult","type","matches","textInserted","actionSets","actions","action","actionSet","push","forward","_","effect","blockOffsets","getBlockOffsets","onApply","endOffsets","InputRulePlugin","props","$","_c","editor","useEditor","t0","input","useActorRef","inputRuleMachine","inputRuleListenerCallback","sendBack","unregister","registerBehavior","behavior","deleteBackwardListenerCallback","raise","selectionListenerCallback","inputRuleSetup","setup","types","events","actors","fromCallback","guards","block offset changed","startChanged","_key","endChanged","assignEndOffsets","assign","createMachine","id","initial","invoke","src","target","states","defineTextTransformRule","textLengthDelta","reduce","transform","endCaretPosition","reverse","child","_type","schema","span","name","marks","getMarkState"],"mappings":";;;;;;;AA+EO,SAASA,gBAAgBC,QAA8B;AAC5D,SAAOA;AACT;AC1DA,SAASC,wBAAwBD,QAO9B;AACD,SAAOE,eAAe;AAAA,IACpBC,IAAI;AAAA,IACJC,OAAOA,CAAC;AAAA,MAACC;AAAAA,MAAUC;AAAAA,MAAOC;AAAAA,IAAAA,MAAS;AACjC,YAAMC,iBAAiBC,kBAAkBJ,QAAQ;AAEjD,UAAI,CAACG;AACH,eAAO;AAGT,YAAME,qBAAqBC,mBAAmBN,QAAQ;AACtD,UAAIO,aAAaF;AACjB,YAAMG,kBAAkBD,aAAaN,MAAMQ;AAC3C,UAAIC,UAAUF;AAEd,YAAMG,eAA8D,IAC9DC,eAAsC,CAAA;AAE5C,iBAAWC,QAAQlB,OAAOmB,OAAO;AAC/B,cAAMC,UAAU,IAAIC,OAAOH,KAAKf,GAAGmB,QAAQ,IAAI;AAE/C,mBAAa;AAEX,gBAAMC,sBAA6C,CACjD,GAAGX,WAAWY,SAASJ,OAAO,CAAC,EAC/BK,QAASC,CAAAA,gBAAgB;AACzB,gBAAIA,YAAYC,YAAYC;AAC1B,qBAAO,CAAA;AAGT,kBAAM,CAACC,KAAK,IAAIH,YAAYC,QAAQG,GAAG,CAAC,KAAK,CAACF,QAAWA,MAAS;AAElE,gBAAIC,UAAUD;AACZ,qBAAO,CAAA;AAGT,kBAAM,CAACG,iBAAiBC,aAAa,IAAIN,YAAYC,QAAQG,GAC3D,CACF,KAAK,CAACF,QAAWA,MAAS;AAE1B,gBAAIG,oBAAoBH,UAAaI,kBAAkBJ;AACrD,qBAAO,CAAA;AAGT,kBAAMK,QAAQ;AAAA,cACZJ,OAAOE;AAAAA,cACPG,QAAQF,gBAAgBD;AAAAA,YAAAA,GAEpBI,gBACJF,MAAMJ,QAAQhB,gBAAgBqB,SAASnB,QAAQmB,QAC3CE,gBAAgB;AAAA,cACpBC,QAAQ;AAAA,gBACNC,MAAM9B,eAAe8B;AAAAA,gBACrBC,QAAQJ;AAAAA,cAAAA;AAAAA,cAEVK,OAAO;AAAA,gBACLF,MAAM9B,eAAe8B;AAAAA,gBACrBC,QAAQJ,gBAAgBF,MAAMC;AAAAA,cAAAA;AAAAA,cAEhCO,UAAU;AAAA,YAAA,GAENC,YAAYC,wBAAwB;AAAA,cACxCC,SAASvC,SAASuC;AAAAA,cAClBC,SAAST;AAAAA,cACTK,UAAU;AAAA,YAAA,CACX;AAED,gBAAI,CAACC;AACH,qBAAO,CAAA;AAGT,kBAAMI,eACJpB,YAAYC,QAAQO,SAAS,IACzBR,YAAYC,QAAQoB,MAAM,CAAC,EAAEC,IAAI,CAAC,CAACC,OAAOC,GAAG,OAAO;AAAA,cAClDrB,OAAOoB;AAAAA,cACPf,QAAQgB,MAAMD;AAAAA,YAAAA,EACd,IACF,CAAA;AAqDN,mBAAO,CApDW;AAAA,cAChBP;AAAAA,cACAN;AAAAA,cACAU,cAAcA,aAAarB,QAAS0B,CAAAA,eAAe;AACjD,sBAAMhB,iBACJgB,WAAWtB,QAAQhB,gBAAgBqB,SAASnB,QAAQmB,QAEhDE,iBAAgB;AAAA,kBACpBC,QAAQ;AAAA,oBACNC,MAAM9B,eAAe8B;AAAAA,oBACrBC,QAAQJ;AAAAA,kBAAAA;AAAAA,kBAEVK,OAAO;AAAA,oBACLF,MAAM9B,eAAe8B;AAAAA,oBACrBC,QAAQJ,iBAAgBgB,WAAWjB;AAAAA,kBAAAA;AAAAA,kBAErCO,UAAU;AAAA,gBAAA,GAENW,oBAAoB;AAAA,kBACxBf,QAAQ;AAAA,oBACNC,MAAM9B,eAAe8B;AAAAA,oBACrBC,QAAQc,KAAKC,IACXlB,eAAcC,OAAOE,QACrB7B,mBAAmBwB,MACrB;AAAA,kBAAA;AAAA,kBAEFM,OAAO;AAAA,oBACLF,MAAM9B,eAAe8B;AAAAA,oBACrBC,QAAQc,KAAKC,IACXlB,eAAcI,MAAMD,QACpB7B,mBAAmBwB,MACrB;AAAA,kBAAA;AAAA,kBAEFO,UAAU;AAAA,gBAAA,GAENC,aAAYC,wBAAwB;AAAA,kBACxCC,SAASvC,SAASuC;AAAAA,kBAClBC,SAASO;AAAAA,kBACTX,UAAU;AAAA,gBAAA,CACX;AAED,uBAAKC,aAIE;AAAA,kBACLA,WAAAA;AAAAA,kBACAN,eAAAA;AAAAA,gBAAAA,IALO,CAAA;AAAA,cAOX,CAAC;AAAA,YAAA,CAGc;AAAA,UACnB,CAAC,GAGKmB,cAFmB,CAAC,GAAGxC,QAAQS,SAASJ,OAAO,CAAC,EAEjBK,QAASC,CAAAA,gBAAgB;AAC5D,gBAAIA,YAAYC,YAAYC;AAC1B,qBAAO,CAAA;AAGT,kBAAM,CAACC,KAAK,IAAIH,YAAYC,QAAQG,GAAG,CAAC,KAAK,CAACF,QAAWA,MAAS;AAElE,gBAAIC,UAAUD;AACZ,qBAAO,CAAA;AAGT,kBAAM,CAACG,iBAAiBC,aAAa,IAAIN,YAAYC,QAAQG,GAC3D,CACF,KAAK,CAACF,QAAWA,MAAS;AAE1B,gBAAIG,oBAAoBH,UAAaI,kBAAkBJ;AACrD,qBAAO,CAAA;AAGT,kBAAMK,QAAQ;AAAA,cACZJ,OAAOE;AAAAA,cACPG,QAAQF,gBAAgBD;AAAAA,YAAAA,GAEpBI,gBACJF,MAAMJ,QAAQhB,gBAAgBqB,SAASnB,QAAQmB,QAC3CE,gBAAgB;AAAA,cACpBC,QAAQ;AAAA,gBACNC,MAAM9B,eAAe8B;AAAAA,gBACrBC,QAAQJ;AAAAA,cAAAA;AAAAA,cAEVK,OAAO;AAAA,gBACLF,MAAM9B,eAAe8B;AAAAA,gBACrBC,QAAQJ,gBAAgBF,MAAMC;AAAAA,cAAAA;AAAAA,cAEhCO,UAAU;AAAA,YAAA,GAENW,oBAAoB;AAAA,cACxBf,QAAQ;AAAA,gBACNC,MAAM9B,eAAe8B;AAAAA,gBACrBC,QAAQc,KAAKC,IACXlB,cAAcC,OAAOE,QACrB7B,mBAAmBwB,MACrB;AAAA,cAAA;AAAA,cAEFM,OAAO;AAAA,gBACLF,MAAM9B,eAAe8B;AAAAA,gBACrBC,QAAQc,KAAKC,IACXlB,cAAcI,MAAMD,QACpB7B,mBAAmBwB,MACrB;AAAA,cAAA;AAAA,cAEFO,UAAU;AAAA,YAAA,GAENC,YAAYC,wBAAwB;AAAA,cACxCC,SAASvC,SAASuC;AAAAA,cAClBC,SAASO;AAAAA,cACTX,UAAU;AAAA,YAAA,CACX;AAED,gBAAI,CAACC;AACH,qBAAO,CAAA;AAGT,kBAAMI,eACJpB,YAAYC,QAAQO,SAAS,IACzBR,YAAYC,QAAQoB,MAAM,CAAC,EAAEC,IAAI,CAAC,CAACC,OAAOC,GAAG,OAAO;AAAA,cAClDrB,OAAOoB;AAAAA,cACPf,QAAQgB,MAAMD;AAAAA,YAAAA,EACd,IACF,CAAA,GAEAO,YAAY;AAAA,cAChBd;AAAAA,cACAN;AAAAA,cACAU,cAAcA,aAAarB,QAAS0B,CAAAA,eAAe;AACjD,sBAAMhB,iBACJgB,WAAWtB,QAAQhB,gBAAgBqB,SAASnB,QAAQmB,QAEhDE,iBAAgB;AAAA,kBACpBC,QAAQ;AAAA,oBACNC,MAAM9B,eAAe8B;AAAAA,oBACrBC,QAAQJ;AAAAA,kBAAAA;AAAAA,kBAEVK,OAAO;AAAA,oBACLF,MAAM9B,eAAe8B;AAAAA,oBACrBC,QAAQJ,iBAAgBgB,WAAWjB;AAAAA,kBAAAA;AAAAA,kBAErCO,UAAU;AAAA,gBAAA,GAENW,qBAAoB;AAAA,kBACxBf,QAAQ;AAAA,oBACNC,MAAM9B,eAAe8B;AAAAA,oBACrBC,QAAQc,KAAKC,IACXlB,eAAcC,OAAOE,QACrB7B,mBAAmBwB,MACrB;AAAA,kBAAA;AAAA,kBAEFM,OAAO;AAAA,oBACLF,MAAM9B,eAAe8B;AAAAA,oBACrBC,QAAQc,KAAKC,IACXlB,eAAcI,MAAMD,QACpB7B,mBAAmBwB,MACrB;AAAA,kBAAA;AAAA,kBAEFO,UAAU;AAAA,gBAAA,GAENC,aAAYC,wBAAwB;AAAA,kBACxCC,SAASvC,SAASuC;AAAAA,kBAClBC,SAASO;AAAAA,kBACTX,UAAU;AAAA,gBAAA,CACX;AAED,uBAAKC,aAIE,CACL;AAAA,kBACEN,eAAAA;AAAAA,kBACAM,WAAAA;AAAAA,gBAAAA,CACD,IAPM,CAAA;AAAA,cASX,CAAC;AAAA,YAAA;AASH,mBANqB1B,aAAayC,KAC/BC,CAAAA,eACCA,WAAWtB,cAAcC,OAAOE,WAAWJ,aAC/C,IAIS,CAAA,IAGkBZ,oBAAoBkC,KAC5CE,CAAAA,sBACCA,kBAAkBvB,cAAcC,OAAOE,WAAWJ,aACtD,IAIS,KAGF,CAACqB,SAAS;AAAA,UACnB,CAAC;AAED,cAAID,YAAYrB,SAAS,GAAG;AAC1B,kBAAM0B,cACJ1C,KAAKd,QAAQ;AAAA,cACXC;AAAAA,cACAC,OAAO;AAAA,gBACLuD,MAAM;AAAA,gBACNC,SAASP;AAAAA,gBACT/C;AAAAA,gBACAI,YAAYF;AAAAA,gBACZqD,cAAczD,MAAMQ;AAAAA,cAAAA;AAAAA,cAEtBP;AAAAA,YAAAA,CACD,KAAK;AAER,gBAAI,CAACqD;AACH;AAGF,kBAAMI,aAAa9C,KAAK+C,QAAQjB,IAAKkB,YACnCA,OACE;AAAA,cACE7D;AAAAA,cACAC,OAAO;AAAA,gBACLuD,MAAM;AAAA,gBACNC,SAASP;AAAAA,gBACT/C;AAAAA,gBACAI,YAAYF;AAAAA,gBACZqD,cAAczD,MAAMQ;AAAAA,cAAAA;AAAAA,cAEtBP;AAAAA,YAAAA,GAEFqD,WACF,CACF;AAEA,uBAAWO,aAAaH;AACtB,yBAAWE,UAAUC;AACnBlD,6BAAamD,KAAKF,MAAM;AAI5B,kBAAMJ,UAAUP,YAAY9B,QAASQ,CAAAA,UACnCA,MAAMa,aAAaZ,WAAW,IAAI,CAACD,KAAK,IAAIA,MAAMa,YACpD;AACA,uBAAWb,SAAS6B;AAGlB9C,2BAAaoD,KAAKnC,KAAK,GACvBrB,aAAaG,QAAQgC,MACnB,GACAd,MAAMG,cAAcI,MAAMD,UAAU,CACtC,GACAxB,UAAUF,gBAAgBkC,MACxBd,MAAMG,cAAcI,MAAMD,UAAU,CACtC;AAAA,UAEJ;AAGE;AAAA,QAEJ;AAAA,MACF;AAEA,aAAItB,aAAaiB,WAAW,IACnB,KAGF;AAAA,QAAC+B,SAAShD;AAAAA,MAAAA;AAAAA,IACnB;AAAA,IACAgD,SAAS,CACP,CAAC;AAAA,MAAC3D;AAAAA,IAAAA,MAAW,CAAC+D,QAAQ/D,KAAK,CAAC,GAC5B,CAACgE,GAAG;AAAA,MAACL;AAAAA,IAAAA,MAAaA,SAClB,CAAC;AAAA,MAAC5D;AAAAA,IAAAA,MAAc,CACdkE,OAAO,MAAM;AACX,YAAMC,eAAeC,gBAAgBpE,QAAQ;AAE7CL,aAAO0E,QAAQ;AAAA,QAACC,YAAYH;AAAAA,MAAAA,CAAa;AAAA,IAC3C,CAAC,CAAC,CACH;AAAA,EAAA,CAEJ;AACH;AAoBO,SAAAI,gBAAAC,OAAA;AAAA,QAAAC,IAAAC,EAAA,CAAA,GACLC,SAAeC,UAAAA;AAAW,MAAAC;AAAA,SAAAJ,SAAAE,UAAAF,EAAA,CAAA,MAAAD,MAAA1D,SAEI+D,KAAA;AAAA,IAAAC,OAAA;AAAA,MAAAH;AAAAA,MAAA7D,OACL0D,MAAK1D;AAAAA,IAAAA;AAAAA,EAAA,GAC7B2D,OAAAE,QAAAF,EAAA,CAAA,IAAAD,MAAA1D,OAAA2D,OAAAI,MAAAA,KAAAJ,EAAA,CAAA,GAFDM,YAAAC,kBAA8BH,EAE7B,GAAC;AAAA;AAgBJ,MAAMI,4BAOFA,CAAC;AAAA,EAACH;AAAAA,EAAOI;AAAQ,MAAM;AACzB,QAAMC,aAAaL,MAAMH,OAAOS,iBAAiB;AAAA,IAC/CC,UAAUzF,wBAAwB;AAAA,MAChCkB,OAAOgE,MAAMhE;AAAAA,MACbuD,SAASA,CAAC;AAAA,QAACC;AAAAA,MAAAA,MAAgB;AACzBY,iBAAS;AAAA,UAAC1B,MAAM;AAAA,UAAqBc;AAAAA,QAAAA,CAAW;AAAA,MAClD;AAAA,IAAA,CACD;AAAA,EAAA,CACF;AAED,SAAO,MAAM;AACXa,eAAAA;AAAAA,EACF;AACF,GAEMG,iCAIFA,CAAC;AAAA,EAACR;AAAAA,EAAOI;AAAQ,MACZJ,MAAMH,OAAOS,iBAAiB;AAAA,EACnCC,UAAUxF,eAAe;AAAA,IACvBC,IAAI;AAAA,IACJ8D,SAAS,CACP,MAAM,CACJ2B,MAAM;AAAA,MAAC/B,MAAM;AAAA,IAAA,CAAe,GAC5BU,OAAO,MAAM;AACXgB,eAAS;AAAA,QAAC1B,MAAM;AAAA,MAAA,CAAsB;AAAA,IACxC,CAAC,CAAC,CACH;AAAA,EAAA,CAEJ;AACH,CAAC,GAGGgC,4BAIFA,CAAC;AAAA,EAACN;AAAAA,EAAUJ;AAAK,MACAA,MAAMH,OAAOS,iBAAiB;AAAA,EAC/CC,UAAUxF,eAAe;AAAA,IACvBC,IAAI;AAAA,IACJC,OAAOA,CAAC;AAAA,MAACC;AAAAA,MAAUC;AAAAA,IAAAA,OASV;AAAA,MAACkE,cARaC,gBAAgB;AAAA,QACnC,GAAGpE;AAAAA,QACHuC,SAAS;AAAA,UACP,GAAGvC,SAASuC;AAAAA,UACZF,WAAWpC,MAAMwB;AAAAA,QAAAA;AAAAA,MACnB,CACD;AAAA,IAAA;AAAA,IAIHmC,SAAS,CACP,CAAC;AAAA,MAAC3D;AAAAA,IAAAA,GAAQ;AAAA,MAACkE;AAAAA,IAAAA,MAAkB,CAC3BD,OAAO,MAAM;AACXgB,eAAS;AAAA,QAAC1B,MAAM;AAAA,QAAqBW;AAAAA,MAAAA,CAAa;AAAA,IACpD,CAAC,GACDH,QAAQ/D,KAAK,CAAC,CACf;AAAA,EAAA,CAEJ;AACH,CAAC,GAKGwF,iBAAiBC,MAAM;AAAA,EAC3BC,OAAO;AAAA,IACLpD,SAAS,CAAA;AAAA,IAKTuC,OAAO,CAAA;AAAA,IAIPc,QAAQ,CAAA;AAAA,EAAC;AAAA,EAEXC,QAAQ;AAAA,IACN,4BAA4BC,aAAaR,8BAA8B;AAAA,IACvE,uBAAuBQ,aAAab,yBAAyB;AAAA,IAC7D,sBAAsBa,aAAaN,yBAAyB;AAAA,EAAA;AAAA,EAE9DO,QAAQ;AAAA,IACN,wBAAwBC,CAAC;AAAA,MAACzD;AAAAA,MAAStC;AAAAA,IAAAA,MAAW;AAC5C,UAAIA,MAAMuD,SAAS;AACjB,eAAO;AAGT,UAAI,CAACvD,MAAMkE,gBAAgB,CAAC5B,QAAQ+B;AAClC,eAAO;AAGT,YAAM2B,eACJ1D,QAAQ+B,WAAW1B,MAAMX,KAAK,CAAC,EAAEiE,SAC/BjG,MAAMkE,aAAavB,MAAMX,KAAK,CAAC,EAAEiE,QACnC3D,QAAQ+B,WAAW1B,MAAMV,WAAWjC,MAAMkE,aAAavB,MAAMV,QACzDiE,aACJ5D,QAAQ+B,WAAWzB,IAAIZ,KAAK,CAAC,EAAEiE,SAC7BjG,MAAMkE,aAAatB,IAAIZ,KAAK,CAAC,EAAEiE,QACjC3D,QAAQ+B,WAAWzB,IAAIX,WAAWjC,MAAMkE,aAAatB,IAAIX;AAE3D,aAAO+D,gBAAgBE;AAAAA,IACzB;AAAA,EAAA;AAEJ,CAAC,GAEKC,mBAAmBX,eAAeY,OAAO;AAAA,EAC7C/B,YAAYA,CAAC;AAAA,IAAC/B;AAAAA,IAAStC;AAAAA,EAAAA,MACrBA,MAAMuD,SAAS,sBAAsBvD,MAAMqE,aAAa/B,QAAQ+B;AACpE,CAAC,GAEKU,mBAAmBS,eAAea,cAAc;AAAA,EACpDC,IAAI;AAAA,EACJhE,SAASA,CAAC;AAAA,IAACuC;AAAAA,EAAAA,OAAY;AAAA,IACrBH,QAAQG,MAAMH;AAAAA,IACd7D,OAAOgE,MAAMhE;AAAAA,IACbwD,YAAY/C;AAAAA,EAAAA;AAAAA,EAEdiF,SAAS;AAAA,EACTC,QAAQ;AAAA,IACNC,KAAK;AAAA,IACL5B,OAAOA,CAAC;AAAA,MAACvC;AAAAA,IAAAA,OAAc;AAAA,MACrBoC,QAAQpC,QAAQoC;AAAAA,MAChB7D,OAAOyB,QAAQzB;AAAAA,IAAAA;AAAAA,EACjB;AAAA,EAEFhB,IAAI;AAAA,IACF,qBAAqB;AAAA,MACnB6G,QAAQ;AAAA,MACR/C,SAASwC;AAAAA,IAAAA;AAAAA,EACX;AAAA,EAEFQ,QAAQ;AAAA,IACN,MAAQ,CAAA;AAAA,IACR,sBAAsB;AAAA,MACpBH,QAAQ,CACN;AAAA,QACEC,KAAK;AAAA,QACL5B,OAAOA,CAAC;AAAA,UAACvC;AAAAA,QAAAA,OAAc;AAAA,UAACoC,QAAQpC,QAAQoC;AAAAA,QAAAA;AAAAA,MAAM,GAEhD;AAAA,QACE+B,KAAK;AAAA,QACL5B,OAAOA,CAAC;AAAA,UAACvC;AAAAA,QAAAA,OAAc;AAAA,UAACoC,QAAQpC,QAAQoC;AAAAA,QAAAA;AAAAA,MAAM,CAC/C;AAAA,MAEH7E,IAAI;AAAA,QACF,qBAAqB;AAAA,UACnB6G,QAAQ;AAAA,UACR5G,OAAO;AAAA,QAAA;AAAA,QAET,uBAAuB;AAAA,UACrB4G,QAAQ;AAAA,QAAA;AAAA,MACV;AAAA,IACF;AAAA,EACF;AAEJ,CAAC;AC/jBM,SAASE,wBAAwBlH,QAAsC;AAC5E,SAAO;AAAA,IACLG,IAAIH,OAAOG;AAAAA,IACXC,OAAOJ,OAAOI,UAAU,MAAM;AAAA,IAC9B6D,SAAS,CACP,CAAC;AAAA,MAAC5D;AAAAA,MAAUC;AAAAA,IAAAA,MAAW;AACrB,YAAMwD,UAAUxD,MAAMwD,QAAQrC,QAASQ,CAAAA,UACrCA,MAAMa,aAAaZ,WAAW,IAAI,CAACD,KAAK,IAAIA,MAAMa,YACpD,GACMqE,kBAAkBrD,QAAQsD,OAAO,CAAClF,QAAQD,UAE5CC,UACClC,OAAOqH,UAAAA,EAAYnF,UACjBD,MAAMG,cAAcI,MAAMD,SACzBN,MAAMG,cAAcC,OAAOE,UAEhC,CAAC,GAEExB,UAAUT,MAAMM,aAAaN,MAAMyD,cACnCuD,mBAAmB;AAAA,QACvBhF,MAAMhC,MAAME,eAAe8B;AAAAA,QAC3BC,QAAQxB,QAAQmB,SAASiF;AAAAA,MAAAA;AAgC3B,aAAO,CACL,GA9BcrD,QAAQyD,QAAAA,EAAU9F,QAASQ,CAAAA,UAAU,CACnD2D,MAAM;AAAA,QAAC/B,MAAM;AAAA,QAAU/B,IAAIG,MAAMG;AAAAA,MAAAA,CAAc,GAC/CwD,MAAM;AAAA,QAAC/B,MAAM;AAAA,QAAU/B,IAAIG,MAAMG;AAAAA,MAAAA,CAAc,GAC/CwD,MAAM;AAAA,QACJ/B,MAAM;AAAA,QACN2D,OAAO;AAAA,UACLC,OAAOpH,SAASuC,QAAQ8E,OAAOC,KAAKC;AAAAA,UACpC9G,MAAMd,OAAOqH,UAAAA;AAAAA,UACbQ,OACEC,aAAa;AAAA,YACX,GAAGzH;AAAAA,YACHuC,SAAS;AAAA,cACP,GAAGvC,SAASuC;AAAAA,cACZF,WAAW;AAAA,gBACTL,QAAQJ,MAAMS,UAAUL;AAAAA,gBACxBG,OAAO;AAAA,kBACLF,MAAML,MAAMS,UAAUF,MAAMF;AAAAA,kBAC5BC,QAAQc,KAAKC,IACXrB,MAAMS,UAAUF,MAAMD,QACtBjC,MAAMM,WAAWsB,MACnB;AAAA,gBAAA;AAAA,cACF;AAAA,YACF;AAAA,UACF,CACD,GAAG2F,SAAS,CAAA;AAAA,QAAA;AAAA,MACjB,CACD,CAAC,CACH,GAICjC,MAAM;AAAA,QACJ/B,MAAM;AAAA,QACN/B,IAAI;AAAA,UACFO,QAAQiF;AAAAA,UACR9E,OAAO8E;AAAAA,QAAAA;AAAAA,MACT,CACD,CAAC;AAAA,IAEN,CAAC;AAAA,EAAA;AAGP;"}
|