@portabletext/plugin-input-rule 0.5.7 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/index.d.ts +4 -4
- package/dist/index.js +13 -13
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/input-rule-match-location.ts +6 -6
- package/src/input-rule.ts +4 -4
- package/src/plugin.input-rule.tsx +7 -7
- package/src/rule.stock-ticker.ts +2 -2
- package/src/text-transform-rule.ts +1 -1
package/README.md
CHANGED
|
@@ -40,16 +40,16 @@ const unorderedListRule = defineInputRule({
|
|
|
40
40
|
raise({
|
|
41
41
|
type: 'block.unset',
|
|
42
42
|
props: ['style'],
|
|
43
|
-
at: event.
|
|
43
|
+
at: event.focusBlock.path,
|
|
44
44
|
}),
|
|
45
45
|
// Then, turn it into a list item
|
|
46
46
|
raise({
|
|
47
47
|
type: 'block.set',
|
|
48
48
|
props: {
|
|
49
49
|
listItem: 'bullet',
|
|
50
|
-
level: event.
|
|
50
|
+
level: event.focusBlock.node.level ?? 1,
|
|
51
51
|
},
|
|
52
|
-
at: event.
|
|
52
|
+
at: event.focusBlock.path,
|
|
53
53
|
}),
|
|
54
54
|
// Finally, delete the matched text
|
|
55
55
|
raise({
|
|
@@ -159,7 +159,7 @@ const markdownLinkRule = defineInputRule({
|
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
const endCaretPosition = {
|
|
162
|
-
path: event.
|
|
162
|
+
path: event.focusBlock.path,
|
|
163
163
|
offset: newText.length - textLengthDelta * -1,
|
|
164
164
|
}
|
|
165
165
|
|
|
@@ -222,7 +222,7 @@ const stockTickerRule = defineInputRule({
|
|
|
222
222
|
at: {
|
|
223
223
|
anchor: {
|
|
224
224
|
path: [
|
|
225
|
-
{_key: event.
|
|
225
|
+
{_key: event.focusBlock.node._key},
|
|
226
226
|
'children',
|
|
227
227
|
{_key: stockTickerKey},
|
|
228
228
|
],
|
|
@@ -230,7 +230,7 @@ const stockTickerRule = defineInputRule({
|
|
|
230
230
|
},
|
|
231
231
|
focus: {
|
|
232
232
|
path: [
|
|
233
|
-
{_key: event.
|
|
233
|
+
{_key: event.focusBlock.node._key},
|
|
234
234
|
'children',
|
|
235
235
|
{_key: stockTickerKey},
|
|
236
236
|
],
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import {BlockOffset} from '@portabletext/editor'
|
|
|
2
2
|
import type {
|
|
3
3
|
BlockPath,
|
|
4
4
|
EditorSelection,
|
|
5
|
-
|
|
5
|
+
PortableTextBlock,
|
|
6
6
|
} from '@portabletext/editor'
|
|
7
7
|
import {Behavior, BehaviorEvent} from '@portabletext/editor/behaviors'
|
|
8
8
|
import type {
|
|
@@ -169,11 +169,11 @@ export declare type InputRuleEvent = {
|
|
|
169
169
|
*/
|
|
170
170
|
textInserted: string
|
|
171
171
|
/**
|
|
172
|
-
* The
|
|
172
|
+
* The block where the insertion takes place
|
|
173
173
|
*/
|
|
174
|
-
|
|
174
|
+
focusBlock: {
|
|
175
175
|
path: BlockPath
|
|
176
|
-
node:
|
|
176
|
+
node: PortableTextBlock
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
179
|
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { c } from "react-compiler-runtime";
|
|
2
2
|
import { useEditor } from "@portabletext/editor";
|
|
3
3
|
import { defineBehavior, effect, forward, raise } from "@portabletext/editor/behaviors";
|
|
4
|
-
import { getNextInlineObjects, getPreviousInlineObjects, getBlockOffsets,
|
|
4
|
+
import { getNextInlineObjects, getPreviousInlineObjects, getBlockOffsets, getFocusBlock, getBlockTextBefore, getMarkState } from "@portabletext/editor/selectors";
|
|
5
5
|
import { blockOffsetToSpanSelectionPoint, isSelectionCollapsed } from "@portabletext/editor/utils";
|
|
6
6
|
import { useActorRef } from "@xstate/react";
|
|
7
7
|
import { setup, fromCallback } from "xstate";
|
|
@@ -12,26 +12,26 @@ function getInputRuleMatchLocation({
|
|
|
12
12
|
match,
|
|
13
13
|
adjustIndexBy,
|
|
14
14
|
snapshot,
|
|
15
|
-
|
|
15
|
+
focusBlock,
|
|
16
16
|
originalTextBefore
|
|
17
17
|
}) {
|
|
18
18
|
const [text, start, end] = match, adjustedIndex = start + adjustIndexBy, targetOffsets = {
|
|
19
19
|
anchor: {
|
|
20
|
-
path:
|
|
20
|
+
path: focusBlock.path,
|
|
21
21
|
offset: adjustedIndex
|
|
22
22
|
},
|
|
23
23
|
focus: {
|
|
24
|
-
path:
|
|
24
|
+
path: focusBlock.path,
|
|
25
25
|
offset: adjustedIndex + end - start
|
|
26
26
|
},
|
|
27
27
|
backward: !1
|
|
28
28
|
}, normalizedOffsets = {
|
|
29
29
|
anchor: {
|
|
30
|
-
path:
|
|
30
|
+
path: focusBlock.path,
|
|
31
31
|
offset: Math.min(targetOffsets.anchor.offset, originalTextBefore.length)
|
|
32
32
|
},
|
|
33
33
|
focus: {
|
|
34
|
-
path:
|
|
34
|
+
path: focusBlock.path,
|
|
35
35
|
offset: Math.min(targetOffsets.focus.offset, originalTextBefore.length)
|
|
36
36
|
}
|
|
37
37
|
}, anchorBackwards = blockOffsetToSpanSelectionPoint({
|
|
@@ -75,8 +75,8 @@ function defineInputRuleBehavior(config) {
|
|
|
75
75
|
}) => {
|
|
76
76
|
if (!snapshot.context.selection || !isSelectionCollapsed(snapshot.context.selection))
|
|
77
77
|
return !1;
|
|
78
|
-
const
|
|
79
|
-
if (!
|
|
78
|
+
const focusBlock = getFocusBlock(snapshot);
|
|
79
|
+
if (!focusBlock)
|
|
80
80
|
return !1;
|
|
81
81
|
const originalTextBefore = getBlockTextBefore(snapshot);
|
|
82
82
|
let textBefore = originalTextBefore;
|
|
@@ -96,7 +96,7 @@ function defineInputRuleBehavior(config) {
|
|
|
96
96
|
match: [regExpMatch.at(0) ?? "", ...match],
|
|
97
97
|
adjustIndexBy: originalNewText.length - newText.length,
|
|
98
98
|
snapshot,
|
|
99
|
-
|
|
99
|
+
focusBlock,
|
|
100
100
|
originalTextBefore
|
|
101
101
|
});
|
|
102
102
|
if (!matchLocation)
|
|
@@ -116,7 +116,7 @@ function defineInputRuleBehavior(config) {
|
|
|
116
116
|
match: [text, ...match2],
|
|
117
117
|
adjustIndexBy: originalNewText.length - newText.length,
|
|
118
118
|
snapshot,
|
|
119
|
-
|
|
119
|
+
focusBlock,
|
|
120
120
|
originalTextBefore
|
|
121
121
|
}) || [];
|
|
122
122
|
})
|
|
@@ -128,7 +128,7 @@ function defineInputRuleBehavior(config) {
|
|
|
128
128
|
event: {
|
|
129
129
|
type: "custom.input rule",
|
|
130
130
|
matches: ruleMatches,
|
|
131
|
-
|
|
131
|
+
focusBlock,
|
|
132
132
|
textBefore: originalTextBefore,
|
|
133
133
|
textInserted: event.text
|
|
134
134
|
},
|
|
@@ -141,7 +141,7 @@ function defineInputRuleBehavior(config) {
|
|
|
141
141
|
event: {
|
|
142
142
|
type: "custom.input rule",
|
|
143
143
|
matches: ruleMatches,
|
|
144
|
-
|
|
144
|
+
focusBlock,
|
|
145
145
|
textBefore: originalTextBefore,
|
|
146
146
|
textInserted: event.text
|
|
147
147
|
},
|
|
@@ -375,7 +375,7 @@ function defineTextTransformRule(config) {
|
|
|
375
375
|
}));
|
|
376
376
|
}
|
|
377
377
|
const endCaretPosition = {
|
|
378
|
-
path: event.
|
|
378
|
+
path: event.focusBlock.path,
|
|
379
379
|
offset: newText.length - textLengthDelta
|
|
380
380
|
};
|
|
381
381
|
return [...actions, raise({
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/input-rule.ts","../src/input-rule-match-location.ts","../src/plugin.input-rule.tsx","../src/text-transform-rule.ts"],"sourcesContent":["import type {BlockPath, PortableTextTextBlock} from '@portabletext/editor'\nimport type {\n BehaviorActionSet,\n BehaviorGuard,\n} from '@portabletext/editor/behaviors'\nimport type {InputRuleMatchLocation} from './input-rule-match-location'\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<TGuardResponse = true> = BehaviorGuard<\n InputRuleEvent,\n TGuardResponse\n>\n\n/**\n * @alpha\n */\nexport type InputRule<TGuardResponse = true> = {\n on: RegExp\n guard?: InputRuleGuard<TGuardResponse>\n actions: Array<BehaviorActionSet<InputRuleEvent, TGuardResponse>>\n}\n\n/**\n * @alpha\n */\nexport function defineInputRule<TGuardResponse = true>(\n config: InputRule<TGuardResponse>,\n): InputRule<TGuardResponse> {\n return config\n}\n","import type {\n BlockOffset,\n BlockPath,\n EditorSelection,\n EditorSnapshot,\n} from '@portabletext/editor'\nimport {\n getNextInlineObjects,\n getPreviousInlineObjects,\n} from '@portabletext/editor/selectors'\nimport {blockOffsetToSpanSelectionPoint} from '@portabletext/editor/utils'\n\nexport type InputRuleMatchLocation = {\n /**\n * The matched text\n */\n text: string\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\nexport function getInputRuleMatchLocation({\n match,\n adjustIndexBy,\n snapshot,\n focusTextBlock,\n originalTextBefore,\n}: {\n match: [string, number, number]\n adjustIndexBy: number\n snapshot: EditorSnapshot\n focusTextBlock: {\n path: BlockPath\n }\n originalTextBefore: string\n}): InputRuleMatchLocation | undefined {\n const [text, start, end] = match\n const adjustedIndex = start + adjustIndexBy\n\n const targetOffsets = {\n anchor: {\n path: focusTextBlock.path,\n offset: adjustedIndex,\n },\n focus: {\n path: focusTextBlock.path,\n offset: adjustedIndex + end - start,\n },\n backward: false,\n }\n const normalizedOffsets = {\n anchor: {\n path: focusTextBlock.path,\n offset: Math.min(targetOffsets.anchor.offset, originalTextBefore.length),\n },\n focus: {\n path: focusTextBlock.path,\n offset: Math.min(targetOffsets.focus.offset, originalTextBefore.length),\n },\n backward: false,\n }\n\n const anchorBackwards = blockOffsetToSpanSelectionPoint({\n context: snapshot.context,\n blockOffset: normalizedOffsets.anchor,\n direction: 'backward',\n })\n const focusForwards = blockOffsetToSpanSelectionPoint({\n context: snapshot.context,\n blockOffset: normalizedOffsets.focus,\n direction: 'forward',\n })\n\n if (!anchorBackwards || !focusForwards) {\n return undefined\n }\n\n const selection = {\n anchor: anchorBackwards,\n focus: focusForwards,\n }\n\n const inlineObjectsAfterMatch = getNextInlineObjects({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: {\n anchor: selection.anchor,\n focus: selection.anchor,\n },\n },\n })\n const inlineObjectsBefore = getPreviousInlineObjects(snapshot)\n\n if (\n inlineObjectsAfterMatch.some((inlineObjectAfter) =>\n inlineObjectsBefore.some(\n (inlineObjectBefore) =>\n inlineObjectAfter.node._key === inlineObjectBefore.node._key,\n ),\n )\n ) {\n return undefined\n }\n\n return {\n text,\n selection,\n targetOffsets,\n }\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 {isSelectionCollapsed} 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'\nimport {getInputRuleMatchLocation} from './input-rule-match-location'\n\n/**\n * @alpha\n */\nexport function defineInputRuleBehavior(config: {\n rules: Array<InputRule<any>>\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 if (\n !snapshot.context.selection ||\n !isSelectionCollapsed(snapshot.context.selection)\n ) {\n return false\n }\n\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 after the insertion\n const ruleMatches = [...newText.matchAll(matcher)].flatMap(\n (regExpMatch) => {\n if (regExpMatch.indices === undefined) {\n return []\n }\n\n const match = regExpMatch.indices.at(0)\n\n if (!match) {\n return []\n }\n\n const matchLocation = getInputRuleMatchLocation({\n match: [regExpMatch.at(0) ?? '', ...match],\n adjustIndexBy: originalNewText.length - newText.length,\n snapshot,\n focusTextBlock,\n originalTextBefore,\n })\n\n if (!matchLocation) {\n return []\n }\n\n const existsInTextBefore =\n matchLocation.targetOffsets.focus.offset <=\n originalTextBefore.length\n\n // Ignore if this match occurs in the text before the insertion\n if (existsInTextBefore) {\n return []\n }\n\n const alreadyFound = foundMatches.some(\n (foundMatch) =>\n foundMatch.targetOffsets.anchor.offset ===\n matchLocation.targetOffsets.anchor.offset,\n )\n\n // Ignore if this match has already been found\n if (alreadyFound) {\n return []\n }\n\n const groupMatches =\n regExpMatch.indices.length > 1\n ? regExpMatch.indices\n .slice(1)\n .filter((indices) => indices !== undefined)\n : []\n\n const ruleMatch = {\n text: matchLocation.text,\n selection: matchLocation.selection,\n targetOffsets: matchLocation.targetOffsets,\n groupMatches: groupMatches.flatMap((match, index) => {\n const text = regExpMatch.at(index + 1) ?? ''\n const groupMatchLocation = getInputRuleMatchLocation({\n match: [text, ...match],\n adjustIndexBy: originalNewText.length - newText.length,\n snapshot,\n focusTextBlock,\n originalTextBefore,\n })\n\n if (!groupMatchLocation) {\n return []\n }\n\n return groupMatchLocation\n }),\n }\n\n return [ruleMatch]\n },\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\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<any>>\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: defineInputRuleBehavior({\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, type BehaviorAction} from '@portabletext/editor/behaviors'\nimport {getMarkState} from '@portabletext/editor/selectors'\nimport type {InputRule, InputRuleGuard} from './input-rule'\nimport type {InputRuleMatchLocation} from './input-rule-match-location'\n\n/**\n * @alpha\n */\nexport type TextTransformRule<TGuardResponse = true> = {\n on: RegExp\n guard?: InputRuleGuard<TGuardResponse>\n transform: (\n {location}: {location: InputRuleMatchLocation},\n guardResponse: TGuardResponse,\n ) => 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<TGuardResponse = true>(\n config: TextTransformRule<TGuardResponse>,\n): InputRule<TGuardResponse> {\n return {\n on: config.on,\n guard: config.guard ?? (() => true as TGuardResponse),\n actions: [\n ({snapshot, event}, guardResponse) => {\n const locations = event.matches.flatMap((match) =>\n match.groupMatches.length === 0 ? [match] : match.groupMatches,\n )\n const newText = event.textBefore + event.textInserted\n\n let textLengthDelta = 0\n const actions: Array<BehaviorAction> = []\n\n for (const location of locations.reverse()) {\n const text = config.transform({location}, guardResponse)\n\n textLengthDelta =\n textLengthDelta -\n (text.length -\n (location.targetOffsets.focus.offset -\n location.targetOffsets.anchor.offset))\n\n actions.push(raise({type: 'select', at: location.targetOffsets}))\n actions.push(raise({type: 'delete', at: location.targetOffsets}))\n actions.push(\n raise({\n type: 'insert.child',\n child: {\n _type: snapshot.context.schema.span.name,\n text,\n marks:\n getMarkState({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: {\n anchor: location.selection.anchor,\n focus: {\n path: location.selection.focus.path,\n offset: Math.min(\n location.selection.focus.offset,\n event.textBefore.length,\n ),\n },\n },\n },\n })?.marks ?? [],\n },\n }),\n )\n }\n\n const endCaretPosition = {\n path: event.focusTextBlock.path,\n offset: newText.length - textLengthDelta,\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","getInputRuleMatchLocation","match","adjustIndexBy","snapshot","focusTextBlock","originalTextBefore","text","start","end","adjustedIndex","targetOffsets","anchor","path","offset","focus","backward","normalizedOffsets","Math","min","length","anchorBackwards","blockOffsetToSpanSelectionPoint","context","blockOffset","direction","focusForwards","selection","inlineObjectsAfterMatch","getNextInlineObjects","inlineObjectsBefore","getPreviousInlineObjects","some","inlineObjectAfter","inlineObjectBefore","node","_key","defineInputRuleBehavior","defineBehavior","on","guard","event","dom","isSelectionCollapsed","getFocusTextBlock","getBlockTextBefore","textBefore","originalNewText","newText","foundMatches","foundActions","rule","rules","matcher","RegExp","source","ruleMatches","matchAll","flatMap","regExpMatch","indices","undefined","at","matchLocation","foundMatch","groupMatches","slice","filter","index","guardResult","type","matches","textInserted","actionSets","actions","map","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","endChanged","assignEndOffsets","assign","createMachine","id","initial","invoke","src","target","states","defineTextTransformRule","guardResponse","locations","textLengthDelta","location","reverse","transform","child","_type","schema","span","name","marks","getMarkState","endCaretPosition"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/input-rule.ts","../src/input-rule-match-location.ts","../src/plugin.input-rule.tsx","../src/text-transform-rule.ts"],"sourcesContent":["import type {BlockPath, PortableTextBlock} from '@portabletext/editor'\nimport type {\n BehaviorActionSet,\n BehaviorGuard,\n} from '@portabletext/editor/behaviors'\nimport type {InputRuleMatchLocation} from './input-rule-match-location'\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 block where the insertion takes place\n */\n focusBlock: {\n path: BlockPath\n node: PortableTextBlock\n }\n}\n\n/**\n * @alpha\n */\nexport type InputRuleGuard<TGuardResponse = true> = BehaviorGuard<\n InputRuleEvent,\n TGuardResponse\n>\n\n/**\n * @alpha\n */\nexport type InputRule<TGuardResponse = true> = {\n on: RegExp\n guard?: InputRuleGuard<TGuardResponse>\n actions: Array<BehaviorActionSet<InputRuleEvent, TGuardResponse>>\n}\n\n/**\n * @alpha\n */\nexport function defineInputRule<TGuardResponse = true>(\n config: InputRule<TGuardResponse>,\n): InputRule<TGuardResponse> {\n return config\n}\n","import type {\n BlockOffset,\n BlockPath,\n EditorSelection,\n EditorSnapshot,\n} from '@portabletext/editor'\nimport {\n getNextInlineObjects,\n getPreviousInlineObjects,\n} from '@portabletext/editor/selectors'\nimport {blockOffsetToSpanSelectionPoint} from '@portabletext/editor/utils'\n\nexport type InputRuleMatchLocation = {\n /**\n * The matched text\n */\n text: string\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\nexport function getInputRuleMatchLocation({\n match,\n adjustIndexBy,\n snapshot,\n focusBlock,\n originalTextBefore,\n}: {\n match: [string, number, number]\n adjustIndexBy: number\n snapshot: EditorSnapshot\n focusBlock: {\n path: BlockPath\n }\n originalTextBefore: string\n}): InputRuleMatchLocation | undefined {\n const [text, start, end] = match\n const adjustedIndex = start + adjustIndexBy\n\n const targetOffsets = {\n anchor: {\n path: focusBlock.path,\n offset: adjustedIndex,\n },\n focus: {\n path: focusBlock.path,\n offset: adjustedIndex + end - start,\n },\n backward: false,\n }\n const normalizedOffsets = {\n anchor: {\n path: focusBlock.path,\n offset: Math.min(targetOffsets.anchor.offset, originalTextBefore.length),\n },\n focus: {\n path: focusBlock.path,\n offset: Math.min(targetOffsets.focus.offset, originalTextBefore.length),\n },\n backward: false,\n }\n\n const anchorBackwards = blockOffsetToSpanSelectionPoint({\n context: snapshot.context,\n blockOffset: normalizedOffsets.anchor,\n direction: 'backward',\n })\n const focusForwards = blockOffsetToSpanSelectionPoint({\n context: snapshot.context,\n blockOffset: normalizedOffsets.focus,\n direction: 'forward',\n })\n\n if (!anchorBackwards || !focusForwards) {\n return undefined\n }\n\n const selection = {\n anchor: anchorBackwards,\n focus: focusForwards,\n }\n\n const inlineObjectsAfterMatch = getNextInlineObjects({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: {\n anchor: selection.anchor,\n focus: selection.anchor,\n },\n },\n })\n const inlineObjectsBefore = getPreviousInlineObjects(snapshot)\n\n if (\n inlineObjectsAfterMatch.some((inlineObjectAfter) =>\n inlineObjectsBefore.some(\n (inlineObjectBefore) =>\n inlineObjectAfter.node._key === inlineObjectBefore.node._key,\n ),\n )\n ) {\n return undefined\n }\n\n return {\n text,\n selection,\n targetOffsets,\n }\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 getFocusBlock,\n} from '@portabletext/editor/selectors'\nimport {isSelectionCollapsed} 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'\nimport {getInputRuleMatchLocation} from './input-rule-match-location'\n\n/**\n * @alpha\n */\nexport function defineInputRuleBehavior(config: {\n rules: Array<InputRule<any>>\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 if (\n !snapshot.context.selection ||\n !isSelectionCollapsed(snapshot.context.selection)\n ) {\n return false\n }\n\n const focusBlock = getFocusBlock(snapshot)\n\n if (!focusBlock) {\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 after the insertion\n const ruleMatches = [...newText.matchAll(matcher)].flatMap(\n (regExpMatch) => {\n if (regExpMatch.indices === undefined) {\n return []\n }\n\n const match = regExpMatch.indices.at(0)\n\n if (!match) {\n return []\n }\n\n const matchLocation = getInputRuleMatchLocation({\n match: [regExpMatch.at(0) ?? '', ...match],\n adjustIndexBy: originalNewText.length - newText.length,\n snapshot,\n focusBlock,\n originalTextBefore,\n })\n\n if (!matchLocation) {\n return []\n }\n\n const existsInTextBefore =\n matchLocation.targetOffsets.focus.offset <=\n originalTextBefore.length\n\n // Ignore if this match occurs in the text before the insertion\n if (existsInTextBefore) {\n return []\n }\n\n const alreadyFound = foundMatches.some(\n (foundMatch) =>\n foundMatch.targetOffsets.anchor.offset ===\n matchLocation.targetOffsets.anchor.offset,\n )\n\n // Ignore if this match has already been found\n if (alreadyFound) {\n return []\n }\n\n const groupMatches =\n regExpMatch.indices.length > 1\n ? regExpMatch.indices\n .slice(1)\n .filter((indices) => indices !== undefined)\n : []\n\n const ruleMatch = {\n text: matchLocation.text,\n selection: matchLocation.selection,\n targetOffsets: matchLocation.targetOffsets,\n groupMatches: groupMatches.flatMap((match, index) => {\n const text = regExpMatch.at(index + 1) ?? ''\n const groupMatchLocation = getInputRuleMatchLocation({\n match: [text, ...match],\n adjustIndexBy: originalNewText.length - newText.length,\n snapshot,\n focusBlock,\n originalTextBefore,\n })\n\n if (!groupMatchLocation) {\n return []\n }\n\n return groupMatchLocation\n }),\n }\n\n return [ruleMatch]\n },\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 focusBlock,\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 focusBlock,\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\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<any>>\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: defineInputRuleBehavior({\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, type BehaviorAction} from '@portabletext/editor/behaviors'\nimport {getMarkState} from '@portabletext/editor/selectors'\nimport type {InputRule, InputRuleGuard} from './input-rule'\nimport type {InputRuleMatchLocation} from './input-rule-match-location'\n\n/**\n * @alpha\n */\nexport type TextTransformRule<TGuardResponse = true> = {\n on: RegExp\n guard?: InputRuleGuard<TGuardResponse>\n transform: (\n {location}: {location: InputRuleMatchLocation},\n guardResponse: TGuardResponse,\n ) => 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<TGuardResponse = true>(\n config: TextTransformRule<TGuardResponse>,\n): InputRule<TGuardResponse> {\n return {\n on: config.on,\n guard: config.guard ?? (() => true as TGuardResponse),\n actions: [\n ({snapshot, event}, guardResponse) => {\n const locations = event.matches.flatMap((match) =>\n match.groupMatches.length === 0 ? [match] : match.groupMatches,\n )\n const newText = event.textBefore + event.textInserted\n\n let textLengthDelta = 0\n const actions: Array<BehaviorAction> = []\n\n for (const location of locations.reverse()) {\n const text = config.transform({location}, guardResponse)\n\n textLengthDelta =\n textLengthDelta -\n (text.length -\n (location.targetOffsets.focus.offset -\n location.targetOffsets.anchor.offset))\n\n actions.push(raise({type: 'select', at: location.targetOffsets}))\n actions.push(raise({type: 'delete', at: location.targetOffsets}))\n actions.push(\n raise({\n type: 'insert.child',\n child: {\n _type: snapshot.context.schema.span.name,\n text,\n marks:\n getMarkState({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: {\n anchor: location.selection.anchor,\n focus: {\n path: location.selection.focus.path,\n offset: Math.min(\n location.selection.focus.offset,\n event.textBefore.length,\n ),\n },\n },\n },\n })?.marks ?? [],\n },\n }),\n )\n }\n\n const endCaretPosition = {\n path: event.focusBlock.path,\n offset: newText.length - textLengthDelta,\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","getInputRuleMatchLocation","match","adjustIndexBy","snapshot","focusBlock","originalTextBefore","text","start","end","adjustedIndex","targetOffsets","anchor","path","offset","focus","backward","normalizedOffsets","Math","min","length","anchorBackwards","blockOffsetToSpanSelectionPoint","context","blockOffset","direction","focusForwards","selection","inlineObjectsAfterMatch","getNextInlineObjects","inlineObjectsBefore","getPreviousInlineObjects","some","inlineObjectAfter","inlineObjectBefore","node","_key","defineInputRuleBehavior","defineBehavior","on","guard","event","dom","isSelectionCollapsed","getFocusBlock","getBlockTextBefore","textBefore","originalNewText","newText","foundMatches","foundActions","rule","rules","matcher","RegExp","source","ruleMatches","matchAll","flatMap","regExpMatch","indices","undefined","at","matchLocation","foundMatch","groupMatches","slice","filter","index","guardResult","type","matches","textInserted","actionSets","actions","map","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","endChanged","assignEndOffsets","assign","createMachine","id","initial","invoke","src","target","states","defineTextTransformRule","guardResponse","locations","textLengthDelta","location","reverse","transform","child","_type","schema","span","name","marks","getMarkState","endCaretPosition"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@portabletext/plugin-input-rule",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Easily configure input rules in the Portable Text Editor",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"portabletext",
|
|
@@ -52,12 +52,12 @@
|
|
|
52
52
|
"typescript": "5.9.3",
|
|
53
53
|
"typescript-eslint": "^8.46.1",
|
|
54
54
|
"vitest": "^4.0.6",
|
|
55
|
-
"@portabletext/editor": "2.21.
|
|
55
|
+
"@portabletext/editor": "2.21.1",
|
|
56
56
|
"@portabletext/schema": "2.0.0",
|
|
57
57
|
"racejar": "2.0.0"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
|
-
"@portabletext/editor": "^2.21.
|
|
60
|
+
"@portabletext/editor": "^2.21.1",
|
|
61
61
|
"react": "^18.3 || ^19"
|
|
62
62
|
},
|
|
63
63
|
"engines": {
|
|
@@ -35,13 +35,13 @@ export function getInputRuleMatchLocation({
|
|
|
35
35
|
match,
|
|
36
36
|
adjustIndexBy,
|
|
37
37
|
snapshot,
|
|
38
|
-
|
|
38
|
+
focusBlock,
|
|
39
39
|
originalTextBefore,
|
|
40
40
|
}: {
|
|
41
41
|
match: [string, number, number]
|
|
42
42
|
adjustIndexBy: number
|
|
43
43
|
snapshot: EditorSnapshot
|
|
44
|
-
|
|
44
|
+
focusBlock: {
|
|
45
45
|
path: BlockPath
|
|
46
46
|
}
|
|
47
47
|
originalTextBefore: string
|
|
@@ -51,22 +51,22 @@ export function getInputRuleMatchLocation({
|
|
|
51
51
|
|
|
52
52
|
const targetOffsets = {
|
|
53
53
|
anchor: {
|
|
54
|
-
path:
|
|
54
|
+
path: focusBlock.path,
|
|
55
55
|
offset: adjustedIndex,
|
|
56
56
|
},
|
|
57
57
|
focus: {
|
|
58
|
-
path:
|
|
58
|
+
path: focusBlock.path,
|
|
59
59
|
offset: adjustedIndex + end - start,
|
|
60
60
|
},
|
|
61
61
|
backward: false,
|
|
62
62
|
}
|
|
63
63
|
const normalizedOffsets = {
|
|
64
64
|
anchor: {
|
|
65
|
-
path:
|
|
65
|
+
path: focusBlock.path,
|
|
66
66
|
offset: Math.min(targetOffsets.anchor.offset, originalTextBefore.length),
|
|
67
67
|
},
|
|
68
68
|
focus: {
|
|
69
|
-
path:
|
|
69
|
+
path: focusBlock.path,
|
|
70
70
|
offset: Math.min(targetOffsets.focus.offset, originalTextBefore.length),
|
|
71
71
|
},
|
|
72
72
|
backward: false,
|
package/src/input-rule.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {BlockPath,
|
|
1
|
+
import type {BlockPath, PortableTextBlock} from '@portabletext/editor'
|
|
2
2
|
import type {
|
|
3
3
|
BehaviorActionSet,
|
|
4
4
|
BehaviorGuard,
|
|
@@ -31,11 +31,11 @@ export type InputRuleEvent = {
|
|
|
31
31
|
*/
|
|
32
32
|
textInserted: string
|
|
33
33
|
/**
|
|
34
|
-
* The
|
|
34
|
+
* The block where the insertion takes place
|
|
35
35
|
*/
|
|
36
|
-
|
|
36
|
+
focusBlock: {
|
|
37
37
|
path: BlockPath
|
|
38
|
-
node:
|
|
38
|
+
node: PortableTextBlock
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
import {
|
|
10
10
|
getBlockOffsets,
|
|
11
11
|
getBlockTextBefore,
|
|
12
|
-
|
|
12
|
+
getFocusBlock,
|
|
13
13
|
} from '@portabletext/editor/selectors'
|
|
14
14
|
import {isSelectionCollapsed} from '@portabletext/editor/utils'
|
|
15
15
|
import {useActorRef} from '@xstate/react'
|
|
@@ -43,9 +43,9 @@ export function defineInputRuleBehavior(config: {
|
|
|
43
43
|
return false
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
const
|
|
46
|
+
const focusBlock = getFocusBlock(snapshot)
|
|
47
47
|
|
|
48
|
-
if (!
|
|
48
|
+
if (!focusBlock) {
|
|
49
49
|
return false
|
|
50
50
|
}
|
|
51
51
|
|
|
@@ -78,7 +78,7 @@ export function defineInputRuleBehavior(config: {
|
|
|
78
78
|
match: [regExpMatch.at(0) ?? '', ...match],
|
|
79
79
|
adjustIndexBy: originalNewText.length - newText.length,
|
|
80
80
|
snapshot,
|
|
81
|
-
|
|
81
|
+
focusBlock,
|
|
82
82
|
originalTextBefore,
|
|
83
83
|
})
|
|
84
84
|
|
|
@@ -123,7 +123,7 @@ export function defineInputRuleBehavior(config: {
|
|
|
123
123
|
match: [text, ...match],
|
|
124
124
|
adjustIndexBy: originalNewText.length - newText.length,
|
|
125
125
|
snapshot,
|
|
126
|
-
|
|
126
|
+
focusBlock,
|
|
127
127
|
originalTextBefore,
|
|
128
128
|
})
|
|
129
129
|
|
|
@@ -146,7 +146,7 @@ export function defineInputRuleBehavior(config: {
|
|
|
146
146
|
event: {
|
|
147
147
|
type: 'custom.input rule',
|
|
148
148
|
matches: ruleMatches,
|
|
149
|
-
|
|
149
|
+
focusBlock,
|
|
150
150
|
textBefore: originalTextBefore,
|
|
151
151
|
textInserted: event.text,
|
|
152
152
|
},
|
|
@@ -164,7 +164,7 @@ export function defineInputRuleBehavior(config: {
|
|
|
164
164
|
event: {
|
|
165
165
|
type: 'custom.input rule',
|
|
166
166
|
matches: ruleMatches,
|
|
167
|
-
|
|
167
|
+
focusBlock,
|
|
168
168
|
textBefore: originalTextBefore,
|
|
169
169
|
textInserted: event.text,
|
|
170
170
|
},
|
package/src/rule.stock-ticker.ts
CHANGED
|
@@ -56,7 +56,7 @@ export function createStockTickerRule(config: {
|
|
|
56
56
|
at: {
|
|
57
57
|
anchor: {
|
|
58
58
|
path: [
|
|
59
|
-
{_key: event.
|
|
59
|
+
{_key: event.focusBlock.node._key},
|
|
60
60
|
'children',
|
|
61
61
|
{_key: stockTickerKey},
|
|
62
62
|
],
|
|
@@ -64,7 +64,7 @@ export function createStockTickerRule(config: {
|
|
|
64
64
|
},
|
|
65
65
|
focus: {
|
|
66
66
|
path: [
|
|
67
|
-
{_key: event.
|
|
67
|
+
{_key: event.focusBlock.node._key},
|
|
68
68
|
'children',
|
|
69
69
|
{_key: stockTickerKey},
|
|
70
70
|
],
|