@prosekit/extensions 0.8.0 → 0.9.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/dist/commit/style.css +1 -1
- package/dist/commit/style.js +0 -0
- package/dist/enter-rule-RdhEA900.js +96 -0
- package/dist/gap-cursor/style.css +6 -3
- package/dist/gap-cursor/style.js +0 -0
- package/dist/input-rule-Gji4N7Oe.js +93 -0
- package/dist/list/style.css +7 -7
- package/dist/list/style.js +0 -0
- package/dist/loro/style.css +13 -9
- package/dist/loro/style.js +0 -0
- package/dist/mark-rule-wEOcDt6i.js +160 -0
- package/dist/placeholder/style.css +3 -3
- package/dist/placeholder/style.js +0 -0
- package/dist/prosekit-extensions-autocomplete.d.ts +33 -3
- package/dist/prosekit-extensions-autocomplete.js +126 -174
- package/dist/prosekit-extensions-blockquote.d.ts +48 -8
- package/dist/prosekit-extensions-blockquote.js +64 -78
- package/dist/prosekit-extensions-bold.d.ts +54 -8
- package/dist/prosekit-extensions-bold.js +61 -73
- package/dist/prosekit-extensions-code-block.d.ts +159 -20
- package/dist/prosekit-extensions-code-block.js +201 -184
- package/dist/prosekit-extensions-code.d.ts +54 -8
- package/dist/prosekit-extensions-code.js +44 -56
- package/dist/prosekit-extensions-commit.d.ts +52 -4
- package/dist/prosekit-extensions-commit.js +140 -183
- package/dist/prosekit-extensions-doc.d.ts +19 -2
- package/dist/prosekit-extensions-doc.js +14 -12
- package/dist/prosekit-extensions-drop-cursor.d.ts +35 -3
- package/dist/prosekit-extensions-drop-cursor.js +14 -8
- package/dist/prosekit-extensions-enter-rule.d.ts +105 -5
- package/dist/prosekit-extensions-enter-rule.js +3 -8
- package/dist/prosekit-extensions-file.d.ts +129 -8
- package/dist/prosekit-extensions-file.js +124 -132
- package/dist/prosekit-extensions-gap-cursor.d.ts +26 -2
- package/dist/prosekit-extensions-gap-cursor.js +21 -9
- package/dist/prosekit-extensions-hard-break.d.ts +53 -0
- package/dist/prosekit-extensions-hard-break.js +58 -0
- package/dist/prosekit-extensions-heading.d.ts +63 -9
- package/dist/prosekit-extensions-heading.js +121 -95
- package/dist/prosekit-extensions-horizontal-rule.d.ts +38 -8
- package/dist/prosekit-extensions-horizontal-rule.js +53 -71
- package/dist/prosekit-extensions-image.d.ts +50 -7
- package/dist/prosekit-extensions-image.js +71 -62
- package/dist/prosekit-extensions-input-rule.d.ts +129 -6
- package/dist/prosekit-extensions-input-rule.js +3 -14
- package/dist/prosekit-extensions-italic.d.ts +54 -8
- package/dist/prosekit-extensions-italic.js +51 -63
- package/dist/prosekit-extensions-link.d.ts +62 -10
- package/dist/prosekit-extensions-link.js +95 -100
- package/dist/prosekit-extensions-list.d.ts +104 -17
- package/dist/prosekit-extensions-list.js +115 -158
- package/dist/prosekit-extensions-loro.d.ts +69 -11
- package/dist/prosekit-extensions-loro.js +49 -77
- package/dist/prosekit-extensions-mark-rule.d.ts +37 -2
- package/dist/prosekit-extensions-mark-rule.js +3 -6
- package/dist/prosekit-extensions-mention.d.ts +39 -4
- package/dist/prosekit-extensions-mention.js +52 -50
- package/dist/prosekit-extensions-mod-click-prevention.d.ts +17 -2
- package/dist/prosekit-extensions-mod-click-prevention.js +20 -16
- package/dist/prosekit-extensions-paragraph.d.ts +60 -7
- package/dist/prosekit-extensions-paragraph.js +46 -45
- package/dist/prosekit-extensions-placeholder.d.ts +33 -2
- package/dist/prosekit-extensions-placeholder.js +39 -56
- package/dist/prosekit-extensions-readonly.d.ts +10 -1
- package/dist/prosekit-extensions-readonly.js +13 -14
- package/dist/prosekit-extensions-search.d.ts +74 -3
- package/dist/prosekit-extensions-search.js +48 -47
- package/dist/prosekit-extensions-strike.d.ts +47 -8
- package/dist/prosekit-extensions-strike.js +44 -49
- package/dist/prosekit-extensions-table.d.ts +231 -26
- package/dist/prosekit-extensions-table.js +3 -34
- package/dist/prosekit-extensions-text-align.d.ts +71 -8
- package/dist/prosekit-extensions-text-align.js +63 -44
- package/dist/prosekit-extensions-text.d.ts +19 -2
- package/dist/prosekit-extensions-text.js +13 -11
- package/dist/prosekit-extensions-underline.d.ts +43 -7
- package/dist/prosekit-extensions-underline.js +33 -37
- package/dist/prosekit-extensions-virtual-selection.d.ts +21 -2
- package/dist/prosekit-extensions-virtual-selection.js +49 -52
- package/dist/prosekit-extensions-yjs.d.ts +90 -14
- package/dist/prosekit-extensions-yjs.js +88 -131
- package/dist/prosekit-extensions.d.ts +1 -1
- package/dist/search/style.css +4 -3
- package/dist/search/style.js +0 -0
- package/dist/shiki-highlighter-chunk-CZGvZlhf.d.ts +18 -0
- package/dist/shiki-highlighter-chunk.d.ts +2 -0
- package/dist/shiki-highlighter-chunk.js +32 -39
- package/dist/table/style.css +10 -13
- package/dist/table/style.js +0 -0
- package/dist/table-DnVliJ6E.js +287 -0
- package/dist/virtual-selection/style.css +2 -2
- package/dist/virtual-selection/style.js +0 -0
- package/dist/yjs/style.css +9 -8
- package/dist/yjs/style.js +0 -0
- package/package.json +81 -54
- package/dist/_tsup-dts-rollup.d.ts +0 -2459
- package/dist/chunk-6UYLCVBX.js +0 -185
- package/dist/chunk-D54VSLLS.js +0 -105
- package/dist/chunk-I2UMHK3L.js +0 -99
- package/dist/chunk-QVFEYPQ6.js +0 -306
package/dist/commit/style.css
CHANGED
File without changes
|
@@ -0,0 +1,96 @@
|
|
1
|
+
import { OBJECT_REPLACEMENT_CHARACTER, defineFacet, defineFacetPayload, getNodeType, isTextSelection, maybeRun, pluginFacet } from "@prosekit/core";
|
2
|
+
import { PluginKey, ProseMirrorPlugin } from "@prosekit/pm/state";
|
3
|
+
import { keydownHandler } from "@prosekit/pm/keymap";
|
4
|
+
|
5
|
+
//#region src/enter-rule/index.ts
|
6
|
+
/**
|
7
|
+
* Defines an enter rule. An enter rule applies when the text directly in front of
|
8
|
+
* the cursor matches `regex` and user presses Enter. The `regex` should end
|
9
|
+
* with `$`.
|
10
|
+
*
|
11
|
+
* @param options
|
12
|
+
*
|
13
|
+
* @public
|
14
|
+
*/
|
15
|
+
function defineEnterRule({ regex, handler, stop = false }) {
|
16
|
+
const rule = new EnterRule(regex, handler, stop);
|
17
|
+
return defineFacetPayload(enterRule, [rule]);
|
18
|
+
}
|
19
|
+
/**
|
20
|
+
* Defines an enter rule that replaces the matched text with a block node.
|
21
|
+
*
|
22
|
+
* See also {@link defineEnterRule}.
|
23
|
+
*
|
24
|
+
* @param options
|
25
|
+
*
|
26
|
+
* @public
|
27
|
+
*/
|
28
|
+
function defineTextBlockEnterRule({ regex, type, attrs, stop = true }) {
|
29
|
+
return defineEnterRule({
|
30
|
+
regex,
|
31
|
+
handler: ({ state, from, to, match }) => {
|
32
|
+
const nodeType = getNodeType(state.schema, type);
|
33
|
+
const $start = state.doc.resolve(from);
|
34
|
+
if (!$start.node(-1).canReplaceWith($start.index(-1), $start.indexAfter(-1), nodeType)) return null;
|
35
|
+
const nodeAttrs = maybeRun(attrs, match);
|
36
|
+
return state.tr.delete(from, to).setBlockType(from, from, nodeType, nodeAttrs);
|
37
|
+
},
|
38
|
+
stop
|
39
|
+
});
|
40
|
+
}
|
41
|
+
/**
|
42
|
+
* @internal
|
43
|
+
*/
|
44
|
+
var EnterRule = class {
|
45
|
+
constructor(regex, handler, stop) {
|
46
|
+
this.regex = regex;
|
47
|
+
this.handler = handler;
|
48
|
+
this.stop = stop;
|
49
|
+
}
|
50
|
+
};
|
51
|
+
const enterRule = defineFacet({
|
52
|
+
reduce: () => {
|
53
|
+
let rules = [];
|
54
|
+
const command = (state, dispatch, view) => {
|
55
|
+
if (!view) return false;
|
56
|
+
return execRules(view, rules, dispatch);
|
57
|
+
};
|
58
|
+
const handler = keydownHandler({ Enter: command });
|
59
|
+
const plugin = new ProseMirrorPlugin({
|
60
|
+
key: new PluginKey("prosekit-enter-rule"),
|
61
|
+
props: { handleKeyDown: handler }
|
62
|
+
});
|
63
|
+
return function reducer(inputs) {
|
64
|
+
rules = inputs;
|
65
|
+
return plugin;
|
66
|
+
};
|
67
|
+
},
|
68
|
+
parent: pluginFacet
|
69
|
+
});
|
70
|
+
function execRules(view, rules, dispatch) {
|
71
|
+
if (view.composing) return false;
|
72
|
+
const state = view.state;
|
73
|
+
const selection = state.selection;
|
74
|
+
if (!isTextSelection(selection)) return false;
|
75
|
+
const $cursor = selection.$cursor;
|
76
|
+
if (!$cursor || $cursor.parent.type.spec.code) return false;
|
77
|
+
const textBefore = $cursor.parent.textBetween(Math.max(0, $cursor.parentOffset - MAX_MATCH), $cursor.parentOffset, null, OBJECT_REPLACEMENT_CHARACTER);
|
78
|
+
for (const rule of rules) {
|
79
|
+
rule.regex.lastIndex = 0;
|
80
|
+
const match = rule.regex.exec(textBefore);
|
81
|
+
const tr = match && rule.handler({
|
82
|
+
state,
|
83
|
+
from: $cursor.pos - match[0].length,
|
84
|
+
to: $cursor.pos,
|
85
|
+
match
|
86
|
+
});
|
87
|
+
if (!tr) continue;
|
88
|
+
dispatch?.(tr);
|
89
|
+
if (rule.stop) return true;
|
90
|
+
}
|
91
|
+
return false;
|
92
|
+
}
|
93
|
+
const MAX_MATCH = 200;
|
94
|
+
|
95
|
+
//#endregion
|
96
|
+
export { defineEnterRule, defineTextBlockEnterRule };
|
@@ -1,23 +1,26 @@
|
|
1
|
-
/* src/gap-cursor/style.css */
|
2
1
|
.ProseMirror-gapcursor {
|
3
2
|
display: none;
|
4
|
-
pointer-events: none;
|
5
3
|
position: relative;
|
4
|
+
pointer-events: none;
|
6
5
|
}
|
6
|
+
|
7
7
|
.ProseMirror-gapcursor:after {
|
8
|
-
content: "";
|
9
8
|
display: block;
|
10
9
|
position: absolute;
|
11
10
|
top: -2px;
|
12
11
|
width: 20px;
|
13
12
|
border-top: 1px solid currentColor;
|
13
|
+
content: "";
|
14
14
|
animation: ProseMirror-cursor-blink 1.1s steps(2, start) infinite;
|
15
15
|
}
|
16
|
+
|
16
17
|
@keyframes ProseMirror-cursor-blink {
|
17
18
|
to {
|
18
19
|
visibility: hidden;
|
19
20
|
}
|
20
21
|
}
|
22
|
+
|
21
23
|
.ProseMirror-focused .ProseMirror-gapcursor {
|
22
24
|
display: block;
|
23
25
|
}
|
26
|
+
|
File without changes
|
@@ -0,0 +1,93 @@
|
|
1
|
+
import { defineFacet, defineFacetPayload, getMarkType, getNodeType, isMarkAbsent, maybeRun, pluginFacet } from "@prosekit/core";
|
2
|
+
import { InputRule, inputRules, textblockTypeInputRule, wrappingInputRule } from "@prosekit/pm/inputrules";
|
3
|
+
|
4
|
+
//#region src/input-rule/index.ts
|
5
|
+
/**
|
6
|
+
* Defines an input rule extension.
|
7
|
+
*
|
8
|
+
* @param rule - The ProseMirror input rule to add.
|
9
|
+
*
|
10
|
+
* @public
|
11
|
+
*/
|
12
|
+
function defineInputRule(rule) {
|
13
|
+
return defineInputRuleFacetPayload(() => rule);
|
14
|
+
}
|
15
|
+
/**
|
16
|
+
* @internal
|
17
|
+
*/
|
18
|
+
function createMarkInputRule({ regex, type, attrs = null, inCodeMark = false }) {
|
19
|
+
const rule = new InputRule(regex, (state, match, start, end) => {
|
20
|
+
const { tr, schema } = state;
|
21
|
+
const [fullText, markText] = match;
|
22
|
+
if (!markText) return null;
|
23
|
+
const markStart = start + fullText.indexOf(markText);
|
24
|
+
const markEnd = markStart + markText.length;
|
25
|
+
if (!(start <= markStart && markStart < markEnd && markEnd <= end)) return null;
|
26
|
+
const markType = getMarkType(schema, type);
|
27
|
+
const mark = markType.create(maybeRun(attrs, match));
|
28
|
+
if (!isMarkAbsent(tr.doc, markStart, markEnd, markType, attrs)) return null;
|
29
|
+
const initialStoredMarks = tr.storedMarks ?? [];
|
30
|
+
tr.addMark(markStart, markEnd, mark);
|
31
|
+
if (markEnd < end) tr.delete(markEnd, end);
|
32
|
+
if (start < markStart) tr.delete(start, markStart);
|
33
|
+
tr.setStoredMarks(initialStoredMarks);
|
34
|
+
return tr;
|
35
|
+
}, { inCodeMark });
|
36
|
+
return rule;
|
37
|
+
}
|
38
|
+
/**
|
39
|
+
* Defines an input rule for automatically adding inline marks when a given
|
40
|
+
* pattern is typed.
|
41
|
+
*
|
42
|
+
* @public
|
43
|
+
*/
|
44
|
+
function defineMarkInputRule(options) {
|
45
|
+
return defineInputRule(createMarkInputRule(options));
|
46
|
+
}
|
47
|
+
/**
|
48
|
+
* Defines an input rule that changes the type of a textblock when the matched
|
49
|
+
* text is typed into it.
|
50
|
+
*
|
51
|
+
* See also [textblockTypeInputRule](https://prosemirror.net/docs/ref/#inputrules.textblockTypeInputRule)
|
52
|
+
*
|
53
|
+
* @param options
|
54
|
+
*
|
55
|
+
* @public
|
56
|
+
*/
|
57
|
+
function defineTextBlockInputRule({ regex, type, attrs }) {
|
58
|
+
return defineInputRuleFacetPayload(({ schema }) => {
|
59
|
+
const nodeType = getNodeType(schema, type);
|
60
|
+
return textblockTypeInputRule(regex, nodeType, attrs);
|
61
|
+
});
|
62
|
+
}
|
63
|
+
/**
|
64
|
+
* Defines an input rule for automatically wrapping a textblock when a given
|
65
|
+
* string is typed.
|
66
|
+
*
|
67
|
+
* See also [wrappingInputRule](https://prosemirror.net/docs/ref/#inputrules.wrappingInputRule)
|
68
|
+
*
|
69
|
+
* @param options
|
70
|
+
*
|
71
|
+
* @public
|
72
|
+
*/
|
73
|
+
function defineWrappingInputRule({ regex, type, attrs, join }) {
|
74
|
+
return defineInputRuleFacetPayload(({ schema }) => {
|
75
|
+
const nodeType = getNodeType(schema, type);
|
76
|
+
return wrappingInputRule(regex, nodeType, attrs, join);
|
77
|
+
});
|
78
|
+
}
|
79
|
+
function defineInputRuleFacetPayload(input) {
|
80
|
+
return defineFacetPayload(inputRuleFacet, [input]);
|
81
|
+
}
|
82
|
+
const inputRuleFacet = defineFacet({
|
83
|
+
reducer: (inputs) => {
|
84
|
+
return (context) => {
|
85
|
+
const rules = inputs.flatMap((callback) => callback(context));
|
86
|
+
return [inputRules({ rules })];
|
87
|
+
};
|
88
|
+
},
|
89
|
+
parent: pluginFacet
|
90
|
+
});
|
91
|
+
|
92
|
+
//#endregion
|
93
|
+
export { createMarkInputRule, defineInputRule, defineMarkInputRule, defineTextBlockInputRule, defineWrappingInputRule };
|
package/dist/list/style.css
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
/*
|
1
|
+
/* src/style.css */
|
2
2
|
.prosemirror-flat-list {
|
3
3
|
padding: 0;
|
4
4
|
margin-top: 0;
|
@@ -25,9 +25,6 @@
|
|
25
25
|
.prosemirror-flat-list[data-list-kind=bullet] {
|
26
26
|
list-style: disc;
|
27
27
|
}
|
28
|
-
.prosemirror-flat-list[data-list-kind=ordered] {
|
29
|
-
counter-increment: prosemirror-flat-list-counter;
|
30
|
-
}
|
31
28
|
.prosemirror-flat-list[data-list-kind=ordered] > * {
|
32
29
|
contain: style;
|
33
30
|
}
|
@@ -37,6 +34,9 @@
|
|
37
34
|
font-variant-numeric: tabular-nums;
|
38
35
|
content: counter(prosemirror-flat-list-counter, decimal) ". ";
|
39
36
|
}
|
37
|
+
.prosemirror-flat-list[data-list-kind=ordered] {
|
38
|
+
counter-increment: prosemirror-flat-list-counter;
|
39
|
+
}
|
40
40
|
.prosemirror-flat-list[data-list-kind=ordered]:first-child,
|
41
41
|
:not(.prosemirror-flat-list[data-list-kind=ordered]) + .prosemirror-flat-list[data-list-kind=ordered] {
|
42
42
|
counter-reset: prosemirror-flat-list-counter;
|
@@ -58,8 +58,8 @@
|
|
58
58
|
width: 1.5em;
|
59
59
|
width: 1lh;
|
60
60
|
}
|
61
|
-
.prosemirror-flat-list[data-list-kind=task] > .list-marker,
|
62
|
-
.prosemirror-flat-list[data-list-kind=task] > .list-marker * {
|
61
|
+
:is(.prosemirror-flat-list[data-list-kind=task] > .list-marker),
|
62
|
+
:is(.prosemirror-flat-list[data-list-kind=task] > .list-marker) * {
|
63
63
|
cursor: pointer;
|
64
64
|
}
|
65
65
|
.prosemirror-flat-list[data-list-kind=toggle] > .list-marker {
|
@@ -86,4 +86,4 @@
|
|
86
86
|
display: none;
|
87
87
|
}
|
88
88
|
|
89
|
-
|
89
|
+
|
File without changes
|
package/dist/loro/style.css
CHANGED
@@ -1,30 +1,34 @@
|
|
1
|
-
/* src/loro/style.css */
|
2
1
|
.ProseMirror-loro-cursor:first-child {
|
3
2
|
margin-top: 16px;
|
4
3
|
}
|
4
|
+
|
5
|
+
/* This gives the remote user caret. The colors are automatically overwritten */
|
5
6
|
.ProseMirror-loro-cursor {
|
6
7
|
position: relative;
|
7
|
-
margin-left: -1px;
|
8
8
|
margin-right: -1px;
|
9
|
-
|
9
|
+
margin-left: -1px;
|
10
10
|
border-right: 1px solid black;
|
11
|
+
border-left: 1px solid black;
|
11
12
|
border-color: orange;
|
12
13
|
word-break: normal;
|
13
14
|
pointer-events: none;
|
14
15
|
}
|
16
|
+
|
17
|
+
/* This renders the username above the caret */
|
15
18
|
.ProseMirror-loro-cursor > div {
|
16
19
|
position: absolute;
|
17
20
|
top: -1.05em;
|
18
21
|
left: -1px;
|
19
|
-
|
22
|
+
padding-right: 2px;
|
23
|
+
padding-left: 2px;
|
20
24
|
background-color: rgb(250, 129, 0);
|
21
|
-
|
25
|
+
color: white;
|
22
26
|
font-style: normal;
|
23
27
|
font-weight: normal;
|
28
|
+
font-size: 13px;
|
24
29
|
line-height: normal;
|
25
|
-
|
26
|
-
color: white;
|
27
|
-
padding-left: 2px;
|
28
|
-
padding-right: 2px;
|
30
|
+
font-family: serif;
|
29
31
|
white-space: nowrap;
|
32
|
+
user-select: none;
|
30
33
|
}
|
34
|
+
|
File without changes
|
@@ -0,0 +1,160 @@
|
|
1
|
+
import { OBJECT_REPLACEMENT_CHARACTER, defineFacet, defineFacetPayload, getMarkType, maybeRun, pluginFacet } from "@prosekit/core";
|
2
|
+
import { PluginKey, ProseMirrorPlugin } from "@prosekit/pm/state";
|
3
|
+
|
4
|
+
//#region src/mark-rule/range.ts
|
5
|
+
function getSpanTextRanges($from, $to) {
|
6
|
+
const nodeRange = $from.blockRange($to);
|
7
|
+
if (!nodeRange) return [];
|
8
|
+
const stack = [];
|
9
|
+
let start = nodeRange.start;
|
10
|
+
for (let i = nodeRange.startIndex; i < nodeRange.endIndex; i++) {
|
11
|
+
const child = nodeRange.parent.child(i);
|
12
|
+
stack.push([start, child]);
|
13
|
+
start += child.nodeSize;
|
14
|
+
}
|
15
|
+
const ranges = [];
|
16
|
+
while (stack.length > 0) {
|
17
|
+
const [start$1, node] = stack.pop();
|
18
|
+
if (node.type.spec.code) continue;
|
19
|
+
if (node.type.isTextblock) {
|
20
|
+
ranges.push([start$1 + 1, start$1 + 1 + node.content.size]);
|
21
|
+
continue;
|
22
|
+
}
|
23
|
+
node.forEach((child, offset) => {
|
24
|
+
stack.push([start$1 + offset + 1, child]);
|
25
|
+
});
|
26
|
+
}
|
27
|
+
return ranges;
|
28
|
+
}
|
29
|
+
function getInlineTextRange($from, $to) {
|
30
|
+
return [$from.start(), $to.end()];
|
31
|
+
}
|
32
|
+
function getTextRanges(doc, from, to) {
|
33
|
+
const $from = doc.resolve(from);
|
34
|
+
const $to = doc.resolve(to);
|
35
|
+
if ($from.sameParent($to) && $from.parent.isTextblock) return [getInlineTextRange($from, $to)];
|
36
|
+
else {
|
37
|
+
const nodeRange = $from.blockRange($to);
|
38
|
+
if (!nodeRange) return [];
|
39
|
+
return getSpanTextRanges($from, $to);
|
40
|
+
}
|
41
|
+
}
|
42
|
+
function getMapRange(transactions, oldState, newState) {
|
43
|
+
let lo = oldState.selection.from;
|
44
|
+
let hi = oldState.selection.to;
|
45
|
+
for (const tr of transactions) for (const map of tr.mapping.maps) {
|
46
|
+
lo = map.map(lo);
|
47
|
+
hi = map.map(hi);
|
48
|
+
map.forEach((_oldStart, _oldEnd, newStart, newEnd) => {
|
49
|
+
lo = Math.min(lo, hi, newStart);
|
50
|
+
hi = Math.max(lo, hi, newEnd);
|
51
|
+
});
|
52
|
+
}
|
53
|
+
lo = Math.min(lo, hi, newState.selection.from);
|
54
|
+
hi = Math.min(lo, hi, newState.selection.to);
|
55
|
+
return [lo, hi];
|
56
|
+
}
|
57
|
+
function getCheckRanges(transactions, oldState, newState) {
|
58
|
+
const [from, to] = getMapRange(transactions, oldState, newState);
|
59
|
+
return getTextRanges(newState.doc, from, to);
|
60
|
+
}
|
61
|
+
|
62
|
+
//#endregion
|
63
|
+
//#region src/mark-rule/apply.ts
|
64
|
+
function getExpectedMarkings(rules, doc, from, to) {
|
65
|
+
const text = doc.textBetween(from, to, null, OBJECT_REPLACEMENT_CHARACTER);
|
66
|
+
const ranges = [];
|
67
|
+
for (const rule of rules) {
|
68
|
+
rule.regex.lastIndex = 0;
|
69
|
+
const matches = text.matchAll(rule.regex);
|
70
|
+
const markType = getMarkType(doc.type.schema, rule.type);
|
71
|
+
for (const match of matches) {
|
72
|
+
const index = match.index;
|
73
|
+
if (index == null) continue;
|
74
|
+
const attrs = maybeRun(rule.attrs, match);
|
75
|
+
const mark = markType.create(attrs);
|
76
|
+
ranges.push([
|
77
|
+
from + index,
|
78
|
+
from + index + match[0].length,
|
79
|
+
mark
|
80
|
+
]);
|
81
|
+
}
|
82
|
+
}
|
83
|
+
ranges.sort((a, b) => a[0] - b[0] || b[1] - a[1]);
|
84
|
+
const result = [];
|
85
|
+
let freeIndex = 0;
|
86
|
+
for (const range of ranges) if (range[0] >= freeIndex) {
|
87
|
+
result.push(range);
|
88
|
+
freeIndex = range[1];
|
89
|
+
}
|
90
|
+
return result;
|
91
|
+
}
|
92
|
+
function getReceivedMarkings(rules, doc, from, to) {
|
93
|
+
const result = [];
|
94
|
+
const schema = doc.type.schema;
|
95
|
+
const markTypes = rules.map((rule) => getMarkType(schema, rule.type));
|
96
|
+
doc.nodesBetween(from, to, (node, pos) => {
|
97
|
+
if (!node.isInline) return;
|
98
|
+
for (const markType of markTypes) {
|
99
|
+
const mark = node.marks.find((mark$1) => mark$1.type === markType);
|
100
|
+
if (mark) result.push([
|
101
|
+
pos,
|
102
|
+
pos + node.nodeSize,
|
103
|
+
mark
|
104
|
+
]);
|
105
|
+
}
|
106
|
+
});
|
107
|
+
return result;
|
108
|
+
}
|
109
|
+
function markRangeEquals(a, b) {
|
110
|
+
return a[0] === b[0] && a[1] === b[1] && a[2].eq(b[2]);
|
111
|
+
}
|
112
|
+
function markRangeDiffs(a, b) {
|
113
|
+
return a.filter((x) => !b.some((y) => markRangeEquals(x, y)));
|
114
|
+
}
|
115
|
+
function applyMarkRules(rules, transactions, oldState, newState) {
|
116
|
+
if (transactions.length === 0 || transactions.every((tr$1) => !tr$1.docChanged)) return null;
|
117
|
+
const ranges = getCheckRanges(transactions, oldState, newState);
|
118
|
+
const toRemove = [];
|
119
|
+
const toCreate = [];
|
120
|
+
for (const [from, to] of ranges) {
|
121
|
+
const expected = getExpectedMarkings(rules, newState.doc, from, to);
|
122
|
+
const received = getReceivedMarkings(rules, newState.doc, from, to);
|
123
|
+
toRemove.push(...markRangeDiffs(received, expected));
|
124
|
+
toCreate.push(...markRangeDiffs(expected, received));
|
125
|
+
}
|
126
|
+
if (toCreate.length === 0 && toRemove.length === 0) return null;
|
127
|
+
const tr = newState.tr;
|
128
|
+
for (const [from, to, mark] of toRemove) tr.removeMark(from, to, mark);
|
129
|
+
for (const [from, to, mark] of toCreate) tr.addMark(from, to, mark);
|
130
|
+
return tr;
|
131
|
+
}
|
132
|
+
|
133
|
+
//#endregion
|
134
|
+
//#region src/mark-rule/mark-rule.ts
|
135
|
+
/**
|
136
|
+
* A mark rule is something that can automatically apply marks to text if it
|
137
|
+
* matches a certain pattern, and remove them if it doesn't match anymore.
|
138
|
+
*/
|
139
|
+
function defineMarkRule(options) {
|
140
|
+
return defineFacetPayload(markRuleFacet, [options]);
|
141
|
+
}
|
142
|
+
const markRuleFacet = defineFacet({
|
143
|
+
reduce: () => {
|
144
|
+
let rules = [];
|
145
|
+
const plugin = new ProseMirrorPlugin({
|
146
|
+
key: new PluginKey("prosekit-mark-rule"),
|
147
|
+
appendTransaction: (transactions, oldState, newState) => {
|
148
|
+
return applyMarkRules(rules, transactions, oldState, newState);
|
149
|
+
}
|
150
|
+
});
|
151
|
+
return function reducer(input) {
|
152
|
+
rules = input;
|
153
|
+
return plugin;
|
154
|
+
};
|
155
|
+
},
|
156
|
+
parent: pluginFacet
|
157
|
+
});
|
158
|
+
|
159
|
+
//#endregion
|
160
|
+
export { defineMarkRule };
|
File without changes
|
@@ -1,3 +1,33 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
import { Extension } from "@prosekit/core";
|
2
|
+
import { EditorState } from "@prosekit/pm/state";
|
3
|
+
|
4
|
+
//#region src/autocomplete/autocomplete-rule.d.ts
|
5
|
+
type MatchHandler = (options: {
|
6
|
+
state: EditorState;
|
7
|
+
match: RegExpExecArray;
|
8
|
+
from: number;
|
9
|
+
to: number;
|
10
|
+
ignoreMatch: () => void;
|
11
|
+
deleteMatch: () => void;
|
12
|
+
}) => void;
|
13
|
+
declare class AutocompleteRule {
|
14
|
+
readonly regex: RegExp;
|
15
|
+
readonly onMatch: MatchHandler;
|
16
|
+
readonly onLeave?: VoidFunction;
|
17
|
+
readonly canMatch: (options: {
|
18
|
+
state: EditorState;
|
19
|
+
}) => boolean;
|
20
|
+
constructor(options: {
|
21
|
+
regex: RegExp;
|
22
|
+
onEnter: MatchHandler;
|
23
|
+
onLeave?: VoidFunction;
|
24
|
+
canMatch?: (options: {
|
25
|
+
state: EditorState;
|
26
|
+
}) => boolean;
|
27
|
+
});
|
28
|
+
}
|
29
|
+
//#endregion
|
30
|
+
//#region src/autocomplete/autocomplete.d.ts
|
31
|
+
declare function defineAutocomplete(rule: AutocompleteRule): Extension;
|
32
|
+
//#endregion
|
33
|
+
export { AutocompleteRule, MatchHandler, defineAutocomplete };
|