@prosekit/extensions 0.0.0-next-20240427200701 → 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.
@@ -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
|
}>;
|
@@ -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,184 +1,6 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
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
|
-
});
|
1
|
+
import {
|
2
|
+
defineMarkRule
|
3
|
+
} from "./chunk-TXF4SPMB.js";
|
182
4
|
export {
|
183
5
|
defineMarkRule
|
184
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-
|
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-
|
156
|
-
"@prosekit/pm": "0.0.0-next-
|
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",
|