@prosekit/extensions 0.0.0-next-20240421132240 → 0.0.0-next-20240427200701

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.
@@ -16,6 +16,7 @@ import { IndentListOptions } from 'prosemirror-flat-list';
16
16
  import { InputRule } from '@prosekit/pm/inputrules';
17
17
  import { ListAttributes } from 'prosemirror-flat-list';
18
18
  import { ListDOMSerializer } from 'prosemirror-flat-list';
19
+ import { MarkType } from '@prosekit/pm/model';
19
20
  import { NodeRange } from 'prosemirror-model';
20
21
  import { NodeType } from '@prosekit/pm/model';
21
22
  import { Options } from 'tsup';
@@ -28,6 +29,8 @@ import { ToggleCollapsedOptions } from 'prosemirror-flat-list';
28
29
  import { Transaction } from '@prosekit/pm/state';
29
30
  import { UnwrapListOptions } from 'prosemirror-flat-list';
30
31
 
32
+ export declare function applyMarkRules(rules: MarkRule[], transactions: readonly Transaction[], oldState: EditorState, newState: EditorState): Transaction | null;
33
+
31
34
  declare class AutocompleteRule {
32
35
  readonly regex: RegExp;
33
36
  readonly onMatch: MatchHandler;
@@ -467,6 +470,12 @@ export declare function defineListSpec(): Extension<{
467
470
  NODES: "list";
468
471
  }>;
469
472
 
473
+ /**
474
+ * A mark rule is something that can automatically apply marks to text if it
475
+ * matches a certain pattern, and remove them if it doesn't match anymore.
476
+ */
477
+ export declare function defineMarkRule(options: MarkRuleOptions): Extension<ExtensionTyping<string, string, CommandArgs>>;
478
+
470
479
  /**
471
480
  * @public
472
481
  */
@@ -758,6 +767,8 @@ export declare type EnterRuleOptions = {
758
767
  */
759
768
  export declare const exitTable: Command;
760
769
 
770
+ export declare function getCheckRanges(transactions: readonly Transaction[], oldState: EditorState, newState: EditorState): Array<[number, number]>;
771
+
761
772
  export { getHighlighter }
762
773
 
763
774
  export declare function getPluginState(state: EditorState): PredictionPluginState | undefined;
@@ -812,6 +823,42 @@ export declare interface LinkAttrs {
812
823
 
813
824
  export { ListDOMSerializer }
814
825
 
826
+ /**
827
+ * @internal
828
+ */
829
+ export declare class MarkRule {
830
+ readonly regex: RegExp;
831
+ readonly type: string | MarkType;
832
+ readonly getAttrs: (match: RegExpMatchArray) => Attrs | null;
833
+ constructor({ regex, type, attrs }: MarkRuleOptions);
834
+ }
835
+
836
+ /**
837
+ * The options for {@link defineMarkRule}.
838
+ *
839
+ * @public
840
+ */
841
+ declare interface MarkRuleOptions {
842
+ /**
843
+ * The regular expression to match against. It must has a `g` flag to match
844
+ * all instances of the mark.
845
+ */
846
+ regex: RegExp;
847
+ /**
848
+ * The mark type to apply to the matched text.
849
+ */
850
+ type: string | MarkType;
851
+ /**
852
+ * Attributes to set on the mark. If a function is provided, it will be called
853
+ * with the matched result from the regular expression.
854
+ *
855
+ * @default null
856
+ */
857
+ attrs?: Attrs | null | ((match: RegExpMatchArray) => Attrs | null);
858
+ }
859
+ export { MarkRuleOptions }
860
+ export { MarkRuleOptions as MarkRuleOptions_alias_1 }
861
+
815
862
  declare type MatchHandler = (options: {
816
863
  state: EditorState;
817
864
  match: RegExpExecArray;
@@ -1,9 +1,9 @@
1
- import {
2
- defineTextBlockInputRule
3
- } from "./chunk-DYFRBXUX.js";
4
1
  import {
5
2
  defineTextBlockEnterRule
6
3
  } from "./chunk-ASTUC4KT.js";
4
+ import {
5
+ defineTextBlockInputRule
6
+ } from "./chunk-DYFRBXUX.js";
7
7
 
8
8
  // src/code-block/code-block.ts
9
9
  import { union } from "@prosekit/core";
@@ -1,9 +1,9 @@
1
- import {
2
- defineInputRule
3
- } from "./chunk-DYFRBXUX.js";
4
1
  import {
5
2
  defineEnterRule
6
3
  } from "./chunk-ASTUC4KT.js";
4
+ import {
5
+ defineInputRule
6
+ } from "./chunk-DYFRBXUX.js";
7
7
 
8
8
  // src/link/index.ts
9
9
  import {
@@ -0,0 +1,2 @@
1
+ export { defineMarkRule } from './_tsup-dts-rollup';
2
+ export { MarkRuleOptions } from './_tsup-dts-rollup';
@@ -0,0 +1,184 @@
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
+ export {
183
+ defineMarkRule
184
+ };
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-20240421132240",
4
+ "version": "0.0.0-next-20240427200701",
5
5
  "private": false,
6
6
  "author": {
7
7
  "name": "ocavue",
@@ -30,6 +30,11 @@
30
30
  "import": "./dist/prosekit-extensions.js",
31
31
  "default": "./dist/prosekit-extensions.js"
32
32
  },
33
+ "./mark-rule": {
34
+ "types": "./dist/prosekit-extensions-mark-rule.d.ts",
35
+ "import": "./dist/prosekit-extensions-mark-rule.js",
36
+ "default": "./dist/prosekit-extensions-mark-rule.js"
37
+ },
33
38
  "./autocomplete": {
34
39
  "types": "./dist/prosekit-extensions-autocomplete.d.ts",
35
40
  "import": "./dist/prosekit-extensions-autocomplete.js",
@@ -147,8 +152,8 @@
147
152
  "dist"
148
153
  ],
149
154
  "dependencies": {
150
- "@prosekit/core": "0.0.0-next-20240421132240",
151
- "@prosekit/pm": "0.0.0-next-20240421132240",
155
+ "@prosekit/core": "0.0.0-next-20240427200701",
156
+ "@prosekit/pm": "0.0.0-next-20240427200701",
152
157
  "prosemirror-dropcursor": "^1.8.1",
153
158
  "prosemirror-flat-list": "^0.5.0",
154
159
  "prosemirror-highlight": "^0.5.0",
@@ -159,7 +164,7 @@
159
164
  "@prosekit/dev": "*",
160
165
  "tsup": "^8.0.2",
161
166
  "typescript": "^5.4.5",
162
- "vitest": "^1.5.0"
167
+ "vitest": "^1.5.2"
163
168
  },
164
169
  "scripts": {
165
170
  "build:tsup": "tsup",
@@ -171,6 +176,9 @@
171
176
  ".": [
172
177
  "./dist/prosekit-extensions.d.ts"
173
178
  ],
179
+ "mark-rule": [
180
+ "./dist/prosekit-extensions-mark-rule.d.ts"
181
+ ],
174
182
  "autocomplete": [
175
183
  "./dist/prosekit-extensions-autocomplete.d.ts"
176
184
  ],