@prosekit/extensions 0.0.0-next-20240427133255 → 0.0.0-next-20240427202431

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.
@@ -24,13 +24,13 @@ import { Parser } from 'prosemirror-highlight';
24
24
  import { Plugin as Plugin_2 } from '@prosekit/pm/state';
25
25
  import { PluginKey } from '@prosekit/pm/state';
26
26
  import { ProseMirrorNode } from '@prosekit/pm/model';
27
- import type { ResolvedPos } from '@prosekit/pm/model';
28
- import type { Selection as Selection_2 } from '@prosekit/pm/state';
29
27
  import type { SpecialLanguage } from 'shiki';
30
28
  import { ToggleCollapsedOptions } from 'prosemirror-flat-list';
31
29
  import { Transaction } from '@prosekit/pm/state';
32
30
  import { UnwrapListOptions } from 'prosemirror-flat-list';
33
31
 
32
+ export declare function applyMarkRules(rules: MarkRule[], transactions: readonly Transaction[], oldState: EditorState, newState: EditorState): Transaction | null;
33
+
34
34
  declare class AutocompleteRule {
35
35
  readonly regex: RegExp;
36
36
  readonly onMatch: MatchHandler;
@@ -419,6 +419,8 @@ export declare function defineLinkEnterRule(): Extension<ExtensionTyping<string,
419
419
 
420
420
  export declare function defineLinkInputRule(): Extension<ExtensionTyping<string, string, CommandArgs>>;
421
421
 
422
+ export declare function defineLinkMarkRule(): Extension<ExtensionTyping<string, string, CommandArgs>>;
423
+
422
424
  export declare function defineLinkSpec(): Extension< {
423
425
  MARKS: "link";
424
426
  }>;
@@ -471,11 +473,8 @@ export declare function defineListSpec(): Extension<{
471
473
  }>;
472
474
 
473
475
  /**
474
- * A mark rule is something that can automatically apply marks to text if it matches a certain pattern, and remove them if it doesn't match anymore.
475
- *
476
- * For every transaction that changes the document, the mark rule will be applied to the changed text.
477
- *
478
- *
476
+ * A mark rule is something that can automatically apply marks to text if it
477
+ * matches a certain pattern, and remove them if it doesn't match anymore.
479
478
  */
480
479
  export declare function defineMarkRule(options: MarkRuleOptions): Extension<ExtensionTyping<string, string, CommandArgs>>;
481
480
 
@@ -770,20 +769,12 @@ export declare type EnterRuleOptions = {
770
769
  */
771
770
  export declare const exitTable: Command;
772
771
 
773
- /**
774
- * @internal
775
- */
776
- export declare function findChangedTextRanges(selection: Selection_2): Array<[from: number, to: number]>;
777
-
778
- /** @internal */
779
- export declare function getAffectedRange(transactions: readonly Transaction[], oldState: EditorState, newState: EditorState): readonly [number, number];
772
+ export declare function getCheckRanges(transactions: readonly Transaction[], oldState: EditorState, newState: EditorState): Array<[number, number]>;
780
773
 
781
774
  export { getHighlighter }
782
775
 
783
776
  export declare function getPluginState(state: EditorState): PredictionPluginState | undefined;
784
777
 
785
- export declare function getSpanTextRanges($from: ResolvedPos, $to: ResolvedPos): [number, number][];
786
-
787
778
  export declare function getTrMeta(tr: Transaction): PredictionPluginState;
788
779
 
789
780
  export declare interface HeadingAttrs {
@@ -834,9 +825,24 @@ export declare interface LinkAttrs {
834
825
 
835
826
  export { ListDOMSerializer }
836
827
 
837
- export declare interface MarkRuleOptions {
828
+ /**
829
+ * @internal
830
+ */
831
+ export declare class MarkRule {
832
+ readonly regex: RegExp;
833
+ readonly type: string | MarkType;
834
+ readonly getAttrs: (match: RegExpMatchArray) => Attrs | null;
835
+ constructor({ regex, type, attrs }: MarkRuleOptions);
836
+ }
837
+
838
+ /**
839
+ * The options for {@link defineMarkRule}.
840
+ *
841
+ * @public
842
+ */
843
+ declare interface MarkRuleOptions {
838
844
  /**
839
- * The regular expression to match against. It should have a `g` flag to match
845
+ * The regular expression to match against. It must has a `g` flag to match
840
846
  * all instances of the mark.
841
847
  */
842
848
  regex: RegExp;
@@ -847,9 +853,13 @@ export declare interface MarkRuleOptions {
847
853
  /**
848
854
  * Attributes to set on the mark. If a function is provided, it will be called
849
855
  * with the matched result from the regular expression.
856
+ *
857
+ * @default null
850
858
  */
851
859
  attrs?: Attrs | null | ((match: RegExpMatchArray) => Attrs | null);
852
860
  }
861
+ export { MarkRuleOptions }
862
+ export { MarkRuleOptions as MarkRuleOptions_alias_1 }
853
863
 
854
864
  declare type MatchHandler = (options: {
855
865
  state: EditorState;
@@ -0,0 +1,185 @@
1
+ // src/mark-rule/index.ts
2
+ import { Facet, pluginFacet } from "@prosekit/core";
3
+ import { ProseMirrorPlugin } from "@prosekit/pm/state";
4
+
5
+ // src/mark-rule/apply.ts
6
+ import { OBJECT_REPLACEMENT_CHARACTER, getMarkType } from "@prosekit/core";
7
+ import "@prosekit/pm/model";
8
+ import "@prosekit/pm/state";
9
+
10
+ // src/mark-rule/range.ts
11
+ import "@prosekit/pm/state";
12
+ function getSpanTextRanges($from, $to) {
13
+ const nodeRange = $from.blockRange($to);
14
+ if (!nodeRange) {
15
+ return [];
16
+ }
17
+ const stack = [];
18
+ let start = nodeRange.start;
19
+ for (let i = nodeRange.startIndex; i < nodeRange.endIndex; i++) {
20
+ const child = nodeRange.parent.child(i);
21
+ stack.push([start, child]);
22
+ start += child.nodeSize;
23
+ }
24
+ const ranges = [];
25
+ while (stack.length > 0) {
26
+ const [start2, node] = stack.pop();
27
+ if (node.type.spec.code) {
28
+ continue;
29
+ }
30
+ if (node.type.isTextblock) {
31
+ ranges.push([start2 + 1, start2 + 1 + node.content.size]);
32
+ continue;
33
+ }
34
+ node.forEach((child, offset) => {
35
+ stack.push([start2 + offset + 1, child]);
36
+ });
37
+ }
38
+ return ranges;
39
+ }
40
+ function getInlineTextRange($from, $to) {
41
+ return [$from.start(), $to.end()];
42
+ }
43
+ function getTextRanges(doc, from, to) {
44
+ const $from = doc.resolve(from);
45
+ const $to = doc.resolve(to);
46
+ if ($from.sameParent($to) && $from.parent.isTextblock) {
47
+ return [getInlineTextRange($from, $to)];
48
+ } else {
49
+ const nodeRange = $from.blockRange($to);
50
+ if (!nodeRange) {
51
+ return [];
52
+ }
53
+ return getSpanTextRanges($from, $to);
54
+ }
55
+ }
56
+ function getMapRange(transactions, oldState, newState) {
57
+ let lo = oldState.selection.from;
58
+ let hi = oldState.selection.to;
59
+ for (const tr of transactions) {
60
+ for (const map of tr.mapping.maps) {
61
+ lo = map.map(lo);
62
+ hi = map.map(hi);
63
+ map.forEach((_oldStart, _oldEnd, newStart, newEnd) => {
64
+ lo = Math.min(lo, hi, newStart);
65
+ hi = Math.max(hi, hi, newEnd);
66
+ });
67
+ }
68
+ }
69
+ lo = Math.min(lo, hi, newState.selection.from);
70
+ hi = Math.min(lo, hi, newState.selection.to);
71
+ return [lo, hi];
72
+ }
73
+ function getCheckRanges(transactions, oldState, newState) {
74
+ const [from, to] = getMapRange(transactions, oldState, newState);
75
+ return getTextRanges(newState.doc, from, to);
76
+ }
77
+
78
+ // src/mark-rule/apply.ts
79
+ function getExpectedMarkings(rules, doc, from, to) {
80
+ const text = doc.textBetween(from, to, OBJECT_REPLACEMENT_CHARACTER);
81
+ const result = [];
82
+ for (const rule of rules) {
83
+ rule.regex.lastIndex = 0;
84
+ const matches = text.matchAll(rule.regex);
85
+ const markType = getMarkType(doc.type.schema, rule.type);
86
+ for (const match of matches) {
87
+ const index = match.index;
88
+ if (index == null)
89
+ continue;
90
+ const attrs = rule.getAttrs(match);
91
+ const mark = markType.create(attrs);
92
+ result.push([mark, from + index, from + index + match[0].length]);
93
+ }
94
+ }
95
+ return result;
96
+ }
97
+ function getReceivedMarkings(rules, doc, from, to) {
98
+ const result = [];
99
+ const schema = doc.type.schema;
100
+ const markTypes = rules.map((rule) => getMarkType(schema, rule.type));
101
+ doc.nodesBetween(from, to, (node, pos) => {
102
+ if (!node.isInline) {
103
+ return;
104
+ }
105
+ for (const markType of markTypes) {
106
+ const mark = node.marks.find((mark2) => mark2.type === markType);
107
+ if (mark) {
108
+ result.push([mark, pos, pos + node.nodeSize]);
109
+ }
110
+ }
111
+ });
112
+ return result;
113
+ }
114
+ function markingEquals(a, b) {
115
+ return a[1] === b[1] && a[2] === b[2] && a[0].eq(b[0]);
116
+ }
117
+ function markingDiffs(a, b) {
118
+ return a.filter((x) => !b.some((y) => markingEquals(x, y)));
119
+ }
120
+ function applyMarkRules(rules, transactions, oldState, newState) {
121
+ if (transactions.length === 0 || transactions.every((tr2) => !tr2.docChanged)) {
122
+ return null;
123
+ }
124
+ const ranges = getCheckRanges(transactions, oldState, newState);
125
+ const toRemove = [];
126
+ const toCreate = [];
127
+ for (const [from, to] of ranges) {
128
+ const expected = getExpectedMarkings(rules, newState.doc, from, to);
129
+ const received = getReceivedMarkings(rules, newState.doc, from, to);
130
+ toRemove.push(...markingDiffs(received, expected));
131
+ toCreate.push(...markingDiffs(expected, received));
132
+ }
133
+ if (toCreate.length === 0 && toRemove.length === 0) {
134
+ return null;
135
+ }
136
+ const tr = newState.tr;
137
+ for (const [mark, from, to] of toRemove) {
138
+ tr.removeMark(from, to, mark);
139
+ }
140
+ for (const [mark, from, to] of toCreate) {
141
+ tr.addMark(from, to, mark);
142
+ }
143
+ return tr;
144
+ }
145
+
146
+ // src/mark-rule/rule.ts
147
+ import "@prosekit/pm/model";
148
+ var MarkRule = class {
149
+ constructor({ regex, type, attrs = null }) {
150
+ this.regex = regex;
151
+ this.type = type;
152
+ this.getAttrs = typeof attrs === "function" ? attrs : () => attrs;
153
+ }
154
+ };
155
+
156
+ // src/mark-rule/index.ts
157
+ function defineMarkRule(options) {
158
+ return markRuleFacet.extension([new MarkRule(options)]);
159
+ }
160
+ var markRuleFacet = Facet.define({
161
+ converter: () => {
162
+ let rules = [];
163
+ const plugin = new ProseMirrorPlugin({
164
+ appendTransaction: (transactions, oldState, newState) => {
165
+ return applyMarkRules(rules, transactions, oldState, newState);
166
+ }
167
+ });
168
+ const pluginFunc = () => [plugin];
169
+ return {
170
+ create: (inputs) => {
171
+ rules = inputs;
172
+ return pluginFunc;
173
+ },
174
+ update: (inputs) => {
175
+ rules = inputs;
176
+ return null;
177
+ }
178
+ };
179
+ },
180
+ next: pluginFacet
181
+ });
182
+
183
+ export {
184
+ defineMarkRule
185
+ };
@@ -2,5 +2,6 @@ export { defineLinkSpec } from './_tsup-dts-rollup';
2
2
  export { defineLinkCommands } from './_tsup-dts-rollup';
3
3
  export { defineLinkInputRule } from './_tsup-dts-rollup';
4
4
  export { defineLinkEnterRule } from './_tsup-dts-rollup';
5
+ export { defineLinkMarkRule } from './_tsup-dts-rollup';
5
6
  export { defineLink } from './_tsup-dts-rollup';
6
7
  export { LinkAttrs } from './_tsup-dts-rollup';
@@ -1,3 +1,6 @@
1
+ import {
2
+ defineMarkRule
3
+ } from "./chunk-TXF4SPMB.js";
1
4
  import {
2
5
  defineEnterRule
3
6
  } from "./chunk-ASTUC4KT.js";
@@ -85,6 +88,13 @@ function defineLinkEnterRule() {
85
88
  }
86
89
  });
87
90
  }
91
+ function defineLinkMarkRule() {
92
+ return defineMarkRule({
93
+ regex: LINK_RE,
94
+ type: "link",
95
+ attrs: (match) => ({ href: match[1] })
96
+ });
97
+ }
88
98
  function defineLink() {
89
99
  return union([
90
100
  defineLinkSpec(),
@@ -98,5 +108,6 @@ export {
98
108
  defineLinkCommands,
99
109
  defineLinkEnterRule,
100
110
  defineLinkInputRule,
111
+ defineLinkMarkRule,
101
112
  defineLinkSpec
102
113
  };
@@ -1,3 +1,2 @@
1
1
  export { defineMarkRule } from './_tsup-dts-rollup';
2
- export { getAffectedRange } from './_tsup-dts-rollup';
3
2
  export { MarkRuleOptions } from './_tsup-dts-rollup';
@@ -1,173 +1,6 @@
1
- // src/mark-rule/index.ts
2
1
  import {
3
- Facet,
4
- OBJECT_REPLACEMENT_CHARACTER,
5
- getMarkType,
6
- pluginFacet
7
- } from "@prosekit/core";
8
- import "@prosekit/pm/model";
9
- import { ProseMirrorPlugin } from "@prosekit/pm/state";
10
-
11
- // src/mark-rule/changed-range.ts
12
- import { isTextSelection } from "@prosekit/core";
13
- function getSpanTextRanges($from, $to) {
14
- const nodeRange = $from.blockRange($to);
15
- if (!nodeRange) {
16
- return [];
17
- }
18
- const stack = [];
19
- let start = nodeRange.start;
20
- for (let i = nodeRange.startIndex; i < nodeRange.endIndex; i++) {
21
- const child = nodeRange.parent.child(i);
22
- stack.push([start, child]);
23
- start += child.nodeSize;
24
- }
25
- const ranges = [];
26
- while (stack.length > 0) {
27
- const [start2, node] = stack.pop();
28
- if (node.type.spec.code) {
29
- continue;
30
- }
31
- if (node.type.isTextblock) {
32
- ranges.push([start2 + 1, start2 + 1 + node.content.size]);
33
- continue;
34
- }
35
- node.forEach((child, offset) => {
36
- stack.push([start2 + offset + 1, child]);
37
- });
38
- }
39
- return ranges;
40
- }
41
-
42
- // src/mark-rule/index.ts
43
- function defineMarkRule(options) {
44
- return markRuleFacet.extension([options]);
45
- }
46
- function getAffectedRange(transactions, oldState, newState) {
47
- let lo = oldState.selection.from;
48
- let hi = oldState.selection.to;
49
- for (const tr of transactions) {
50
- for (const map of tr.mapping.maps) {
51
- lo = map.map(lo);
52
- hi = map.map(hi);
53
- map.forEach((_oldStart, _oldEnd, newStart, newEnd) => {
54
- lo = Math.min(lo, hi, newStart);
55
- hi = Math.max(hi, hi, newEnd);
56
- });
57
- }
58
- }
59
- lo = Math.min(lo, hi, newState.selection.from);
60
- hi = Math.min(lo, hi, newState.selection.to);
61
- return [lo, hi];
62
- }
63
- function getCheckRanges(doc, from, to) {
64
- const $from = doc.resolve(from);
65
- const $to = doc.resolve(to);
66
- if ($from.sameParent($to)) {
67
- return [[$from.start(), $to.end()]];
68
- } else {
69
- const nodeRange = $from.blockRange($to);
70
- if (!nodeRange) {
71
- return [];
72
- }
73
- return getSpanTextRanges($from, $to);
74
- }
75
- }
76
- function getExpectedMarkings(rules, doc, from, to) {
77
- const text = doc.textBetween(from, to, OBJECT_REPLACEMENT_CHARACTER);
78
- const result = [];
79
- for (const rule of rules) {
80
- rule.regex.lastIndex = 0;
81
- const matches = text.matchAll(rule.regex);
82
- const markType = getMarkType(doc.type.schema, rule.type);
83
- const getAttrs = rule.attrs;
84
- for (const match of matches) {
85
- const index = match.index;
86
- if (index == null)
87
- continue;
88
- const attrs = getAttrs ? typeof getAttrs === "function" ? getAttrs(match) : getAttrs : null;
89
- const mark = markType.create(attrs);
90
- result.push([from + index, from + index + match[0].length, mark]);
91
- }
92
- }
93
- return result;
94
- }
95
- function getReceivedMarkings(rules, doc, from, to) {
96
- const result = [];
97
- const schema = doc.type.schema;
98
- const markTypes = rules.map((rule) => getMarkType(schema, rule.type));
99
- let seen = false;
100
- doc.nodesBetween(from, to, (node) => {
101
- if (!node.isTextblock || seen) {
102
- return;
103
- }
104
- seen = true;
105
- node.content.forEach((child, offset) => {
106
- for (const markType of markTypes) {
107
- const mark = child.marks.find((mark2) => mark2.type === markType);
108
- if (mark) {
109
- result.push([from + offset, from + offset + child.nodeSize, mark]);
110
- }
111
- }
112
- });
113
- });
114
- return result;
115
- }
116
- function markingEquals(a, b) {
117
- return a[0] === b[0] && a[1] === b[1] && a[2].eq(b[2]);
118
- }
119
- function markingDiffs(a, b) {
120
- return a.filter((x) => !b.some((y) => markingEquals(x, y)));
121
- }
122
- function applyMarkRules(rules, transactions, oldState, newState) {
123
- if (transactions.length === 0 || transactions.every((tr2) => !tr2.docChanged)) {
124
- return null;
125
- }
126
- const [from, to] = getAffectedRange(transactions, oldState, newState);
127
- const ranges = getCheckRanges(newState.doc, from, to);
128
- const toRemove = [];
129
- const toCreate = [];
130
- for (const [from2, to2] of ranges) {
131
- const expected = getExpectedMarkings(rules, newState.doc, from2, to2);
132
- const received = getReceivedMarkings(rules, newState.doc, from2, to2);
133
- toRemove.push(...markingDiffs(received, expected));
134
- toCreate.push(...markingDiffs(expected, received));
135
- }
136
- if (toCreate.length === 0 && toRemove.length === 0) {
137
- return null;
138
- }
139
- const tr = newState.tr;
140
- for (const [from2, to2, mark] of toRemove) {
141
- tr.removeMark(from2, to2, mark);
142
- }
143
- for (const [from2, to2, mark] of toCreate) {
144
- tr.addMark(from2, to2, mark);
145
- }
146
- return tr;
147
- }
148
- var markRuleFacet = Facet.define({
149
- converter: () => {
150
- let rules = [];
151
- const plugin = new ProseMirrorPlugin({
152
- appendTransaction: (transactions, oldState, newState) => {
153
- return applyMarkRules(rules, transactions, oldState, newState);
154
- }
155
- });
156
- const pluginFunc = () => [plugin];
157
- return {
158
- create: (inputs) => {
159
- rules = inputs;
160
- return pluginFunc;
161
- },
162
- update: (inputs) => {
163
- rules = inputs;
164
- return null;
165
- }
166
- };
167
- },
168
- next: pluginFacet
169
- });
2
+ defineMarkRule
3
+ } from "./chunk-TXF4SPMB.js";
170
4
  export {
171
- defineMarkRule,
172
- getAffectedRange
5
+ defineMarkRule
173
6
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@prosekit/extensions",
3
3
  "type": "module",
4
- "version": "0.0.0-next-20240427133255",
4
+ "version": "0.0.0-next-20240427202431",
5
5
  "private": false,
6
6
  "author": {
7
7
  "name": "ocavue",
@@ -152,8 +152,8 @@
152
152
  "dist"
153
153
  ],
154
154
  "dependencies": {
155
- "@prosekit/core": "0.0.0-next-20240427133255",
156
- "@prosekit/pm": "0.0.0-next-20240427133255",
155
+ "@prosekit/core": "0.0.0-next-20240427202431",
156
+ "@prosekit/pm": "0.0.0-next-20240427202431",
157
157
  "prosemirror-dropcursor": "^1.8.1",
158
158
  "prosemirror-flat-list": "^0.5.0",
159
159
  "prosemirror-highlight": "^0.5.0",