@prosekit/extensions 0.0.3 → 0.0.5
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/prosekit-extensions-autocomplete.d.ts +31 -0
- package/dist/prosekit-extensions-autocomplete.js +178 -0
- package/dist/prosekit-extensions-heading.d.ts +2 -1
- package/dist/prosekit-extensions-heading.js +19 -2
- package/dist/prosekit-extensions-placeholder.js +1 -0
- package/dist/prosekit-extensions-suggestion.js +2 -0
- package/package.json +10 -2
@@ -0,0 +1,31 @@
|
|
1
|
+
import { Extension } from '@prosekit/core';
|
2
|
+
import { EditorState, Transaction } from '@prosekit/pm/state';
|
3
|
+
|
4
|
+
type MatchHandler = (options: {
|
5
|
+
state: EditorState;
|
6
|
+
match: RegExpExecArray;
|
7
|
+
from: number;
|
8
|
+
to: number;
|
9
|
+
ignoreMatch: () => void;
|
10
|
+
deleteMatch: () => void;
|
11
|
+
}) => Transaction | null | void;
|
12
|
+
declare class AutocompleteRule {
|
13
|
+
readonly regex: RegExp;
|
14
|
+
readonly onMatch: MatchHandler;
|
15
|
+
readonly onLeave?: VoidFunction;
|
16
|
+
readonly canMatch: (options: {
|
17
|
+
state: EditorState;
|
18
|
+
}) => boolean;
|
19
|
+
constructor(options: {
|
20
|
+
regex: RegExp;
|
21
|
+
onEnter: MatchHandler;
|
22
|
+
onLeave?: VoidFunction;
|
23
|
+
canMatch?: (options: {
|
24
|
+
state: EditorState;
|
25
|
+
}) => boolean;
|
26
|
+
});
|
27
|
+
}
|
28
|
+
|
29
|
+
declare function addAutocomplete(rule: AutocompleteRule): Extension;
|
30
|
+
|
31
|
+
export { AutocompleteRule, MatchHandler, addAutocomplete };
|
@@ -0,0 +1,178 @@
|
|
1
|
+
// src/autocomplete/index.ts
|
2
|
+
import {
|
3
|
+
Facet,
|
4
|
+
pluginFacet
|
5
|
+
} from "@prosekit/core";
|
6
|
+
|
7
|
+
// src/autocomplete/plugin.ts
|
8
|
+
import { Plugin } from "@prosekit/pm/state";
|
9
|
+
import { Decoration, DecorationSet } from "@prosekit/pm/view";
|
10
|
+
|
11
|
+
// src/autocomplete/helpers.ts
|
12
|
+
import "@prosekit/pm/model";
|
13
|
+
import { PluginKey } from "@prosekit/pm/state";
|
14
|
+
function defaultCanMatch({ state }) {
|
15
|
+
return state.selection.empty && !isInsideCode(state.selection.$from);
|
16
|
+
}
|
17
|
+
function isInsideCode($pos) {
|
18
|
+
for (let d = $pos.depth; d > 0; d--) {
|
19
|
+
if ($pos.node(d).type.spec.code) {
|
20
|
+
return true;
|
21
|
+
}
|
22
|
+
}
|
23
|
+
return $pos.marks().some((mark) => mark.type.name === "code");
|
24
|
+
}
|
25
|
+
var OBJECT_REPLACEMENT = "\uFFFC";
|
26
|
+
function getPluginState(state) {
|
27
|
+
return pluginKey.getState(state);
|
28
|
+
}
|
29
|
+
function getTrMeta(tr) {
|
30
|
+
return tr.getMeta(pluginKey);
|
31
|
+
}
|
32
|
+
function setTrMeta(tr, meta) {
|
33
|
+
return tr.setMeta(pluginKey, meta);
|
34
|
+
}
|
35
|
+
var pluginKey = new PluginKey(
|
36
|
+
"prosemirror-prediction"
|
37
|
+
);
|
38
|
+
|
39
|
+
// src/autocomplete/plugin.ts
|
40
|
+
function createAutocompletePlugin({
|
41
|
+
rules
|
42
|
+
}) {
|
43
|
+
return new Plugin({
|
44
|
+
key: pluginKey,
|
45
|
+
state: {
|
46
|
+
init: () => {
|
47
|
+
return { active: false, ignore: null, matching: null };
|
48
|
+
},
|
49
|
+
apply: (tr, prevValue, oldState, newState) => {
|
50
|
+
var _a;
|
51
|
+
const meta = getTrMeta(tr);
|
52
|
+
if (!tr.docChanged && oldState.selection.eq(newState.selection) && !meta) {
|
53
|
+
return prevValue;
|
54
|
+
}
|
55
|
+
if (meta) {
|
56
|
+
return meta;
|
57
|
+
}
|
58
|
+
const nextValue = calcPluginState(newState, rules);
|
59
|
+
if (nextValue.active && prevValue.ignore != null && ((_a = nextValue.matching) == null ? void 0 : _a.from) === prevValue.ignore) {
|
60
|
+
return prevValue;
|
61
|
+
}
|
62
|
+
return nextValue;
|
63
|
+
}
|
64
|
+
},
|
65
|
+
view: () => ({
|
66
|
+
update: (view, prevState) => {
|
67
|
+
var _a, _b, _c;
|
68
|
+
const prevValue = getPluginState(prevState);
|
69
|
+
const currValue = getPluginState(view.state);
|
70
|
+
if ((prevValue == null ? void 0 : prevValue.active) && prevValue.matching && prevValue.matching.rule !== ((_a = currValue == null ? void 0 : currValue.matching) == null ? void 0 : _a.rule)) {
|
71
|
+
(_c = (_b = prevValue.matching.rule).onLeave) == null ? void 0 : _c.call(_b);
|
72
|
+
}
|
73
|
+
if ((currValue == null ? void 0 : currValue.active) && currValue.matching && currValue.matching.from !== currValue.ignore) {
|
74
|
+
const { from, to, match, rule } = currValue.matching;
|
75
|
+
const textContent = view.state.doc.textBetween(
|
76
|
+
from,
|
77
|
+
to,
|
78
|
+
OBJECT_REPLACEMENT
|
79
|
+
);
|
80
|
+
const deleteMatch = () => {
|
81
|
+
if (view.state.doc.textBetween(from, to, OBJECT_REPLACEMENT) === textContent) {
|
82
|
+
view.dispatch(view.state.tr.delete(from, to));
|
83
|
+
}
|
84
|
+
};
|
85
|
+
const ignoreMatch = () => {
|
86
|
+
view.dispatch(
|
87
|
+
setTrMeta(view.state.tr, {
|
88
|
+
active: false,
|
89
|
+
ignore: from,
|
90
|
+
matching: null
|
91
|
+
})
|
92
|
+
);
|
93
|
+
};
|
94
|
+
rule.onMatch({
|
95
|
+
state: view.state,
|
96
|
+
match,
|
97
|
+
from,
|
98
|
+
to,
|
99
|
+
deleteMatch,
|
100
|
+
ignoreMatch
|
101
|
+
});
|
102
|
+
}
|
103
|
+
}
|
104
|
+
}),
|
105
|
+
props: {
|
106
|
+
decorations: (state) => {
|
107
|
+
const pluginState = getPluginState(state);
|
108
|
+
if ((pluginState == null ? void 0 : pluginState.active) && pluginState.matching) {
|
109
|
+
const { from, to } = pluginState.matching;
|
110
|
+
const deco = Decoration.inline(from, to, {
|
111
|
+
class: "prosemirror-prediction-match"
|
112
|
+
});
|
113
|
+
return DecorationSet.create(state.doc, [deco]);
|
114
|
+
}
|
115
|
+
return null;
|
116
|
+
}
|
117
|
+
}
|
118
|
+
});
|
119
|
+
}
|
120
|
+
var MAX_MATCH = 200;
|
121
|
+
function calcPluginState(state, rules) {
|
122
|
+
const $pos = state.selection.$from;
|
123
|
+
const parentOffset = $pos.parentOffset;
|
124
|
+
const textBefore = $pos.parent.textBetween(
|
125
|
+
Math.max(0, parentOffset - MAX_MATCH),
|
126
|
+
parentOffset,
|
127
|
+
null,
|
128
|
+
OBJECT_REPLACEMENT
|
129
|
+
);
|
130
|
+
for (const rule of rules) {
|
131
|
+
if (!rule.canMatch({ state })) {
|
132
|
+
continue;
|
133
|
+
}
|
134
|
+
rule.regex.lastIndex = 0;
|
135
|
+
const match = rule.regex.exec(textBefore);
|
136
|
+
if (!match) {
|
137
|
+
continue;
|
138
|
+
}
|
139
|
+
const from = $pos.pos - textBefore.length + match.index;
|
140
|
+
return {
|
141
|
+
active: true,
|
142
|
+
ignore: null,
|
143
|
+
matching: {
|
144
|
+
rule,
|
145
|
+
match,
|
146
|
+
from,
|
147
|
+
to: $pos.pos
|
148
|
+
}
|
149
|
+
};
|
150
|
+
}
|
151
|
+
return { active: false, ignore: null, matching: null };
|
152
|
+
}
|
153
|
+
|
154
|
+
// src/autocomplete/rule.ts
|
155
|
+
var AutocompleteRule = class {
|
156
|
+
constructor(options) {
|
157
|
+
var _a;
|
158
|
+
this.regex = options.regex;
|
159
|
+
this.onMatch = options.onEnter;
|
160
|
+
this.onLeave = options.onLeave;
|
161
|
+
this.canMatch = (_a = options.canMatch) != null ? _a : defaultCanMatch;
|
162
|
+
}
|
163
|
+
};
|
164
|
+
|
165
|
+
// src/autocomplete/index.ts
|
166
|
+
function addAutocomplete(rule) {
|
167
|
+
return autocompleteFacet.extension([rule]);
|
168
|
+
}
|
169
|
+
var autocompleteFacet = Facet.define({
|
170
|
+
combine: (rules) => {
|
171
|
+
return () => [createAutocompletePlugin({ rules })];
|
172
|
+
},
|
173
|
+
next: pluginFacet
|
174
|
+
});
|
175
|
+
export {
|
176
|
+
AutocompleteRule,
|
177
|
+
addAutocomplete
|
178
|
+
};
|
@@ -6,10 +6,11 @@ interface HeadingAttrs {
|
|
6
6
|
declare function addHeadingSpec(): _prosekit_core.Extension<{
|
7
7
|
NODES: "heading";
|
8
8
|
}>;
|
9
|
+
declare function addHeadingKeymap(): _prosekit_core.Extension<_prosekit_core.ExtensionTyping<string, string, _prosekit_core.CommandArgs>>;
|
9
10
|
declare function addHeadingInputRule(): _prosekit_core.Extension<_prosekit_core.ExtensionTyping<string, string, _prosekit_core.CommandArgs>>;
|
10
11
|
/** @public */
|
11
12
|
declare function addHeading(): _prosekit_core.Extension<{
|
12
13
|
NODES: "heading";
|
13
14
|
}>;
|
14
15
|
|
15
|
-
export { HeadingAttrs, addHeading, addHeadingInputRule, addHeadingSpec };
|
16
|
+
export { HeadingAttrs, addHeading, addHeadingInputRule, addHeadingKeymap, addHeadingSpec };
|
@@ -1,9 +1,11 @@
|
|
1
1
|
// src/heading/index.ts
|
2
2
|
import {
|
3
3
|
addInputRule,
|
4
|
+
addKeymap,
|
4
5
|
addNodeSpec,
|
5
6
|
defineExtension,
|
6
|
-
getNodeType
|
7
|
+
getNodeType,
|
8
|
+
toggleNode
|
7
9
|
} from "@prosekit/core";
|
8
10
|
import { textblockTypeInputRule } from "@prosekit/pm/inputrules";
|
9
11
|
function addHeadingSpec() {
|
@@ -28,6 +30,16 @@ function addHeadingSpec() {
|
|
28
30
|
}
|
29
31
|
});
|
30
32
|
}
|
33
|
+
function addHeadingKeymap() {
|
34
|
+
return addKeymap({
|
35
|
+
"mod-1": toggleNode({ type: "heading", attrs: { level: 1 } }),
|
36
|
+
"mod-2": toggleNode({ type: "heading", attrs: { level: 2 } }),
|
37
|
+
"mod-3": toggleNode({ type: "heading", attrs: { level: 3 } }),
|
38
|
+
"mod-4": toggleNode({ type: "heading", attrs: { level: 4 } }),
|
39
|
+
"mod-5": toggleNode({ type: "heading", attrs: { level: 5 } }),
|
40
|
+
"mod-6": toggleNode({ type: "heading", attrs: { level: 6 } })
|
41
|
+
});
|
42
|
+
}
|
31
43
|
function addHeadingInputRule() {
|
32
44
|
return addInputRule(({ schema }) => {
|
33
45
|
const nodeSpec = getNodeType(schema, "heading");
|
@@ -44,10 +56,15 @@ function addHeadingInputRule() {
|
|
44
56
|
});
|
45
57
|
}
|
46
58
|
function addHeading() {
|
47
|
-
return defineExtension([
|
59
|
+
return defineExtension([
|
60
|
+
addHeadingSpec(),
|
61
|
+
addHeadingInputRule(),
|
62
|
+
addHeadingKeymap()
|
63
|
+
]);
|
48
64
|
}
|
49
65
|
export {
|
50
66
|
addHeading,
|
51
67
|
addHeadingInputRule,
|
68
|
+
addHeadingKeymap,
|
52
69
|
addHeadingSpec
|
53
70
|
};
|
@@ -7,6 +7,8 @@ import { Plugin, PluginKey } from "@prosekit/pm/state";
|
|
7
7
|
import { Decoration, DecorationSet } from "@prosekit/pm/view";
|
8
8
|
|
9
9
|
// src/suggestion/is-valid.ts
|
10
|
+
import "@prosekit/pm/model";
|
11
|
+
import "@prosekit/pm/state";
|
10
12
|
function defaultIsValid({ state }) {
|
11
13
|
return state.selection.empty && !isInsideCode(state.selection.$from);
|
12
14
|
}
|
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "@prosekit/extensions",
|
3
3
|
"type": "module",
|
4
|
-
"version": "0.0.
|
4
|
+
"version": "0.0.5",
|
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
|
+
"./autocomplete": {
|
34
|
+
"types": "./dist/prosekit-extensions-autocomplete.d.ts",
|
35
|
+
"import": "./dist/prosekit-extensions-autocomplete.js",
|
36
|
+
"default": "./dist/prosekit-extensions-autocomplete.js"
|
37
|
+
},
|
33
38
|
"./blockquote": {
|
34
39
|
"types": "./dist/prosekit-extensions-blockquote.d.ts",
|
35
40
|
"import": "./dist/prosekit-extensions-blockquote.js",
|
@@ -81,7 +86,7 @@
|
|
81
86
|
"dist"
|
82
87
|
],
|
83
88
|
"dependencies": {
|
84
|
-
"@prosekit/core": "^0.0.
|
89
|
+
"@prosekit/core": "^0.0.5",
|
85
90
|
"@prosekit/pm": "^0.0.3",
|
86
91
|
"prosemirror-flat-list": "^0.3.15"
|
87
92
|
},
|
@@ -101,6 +106,9 @@
|
|
101
106
|
".": [
|
102
107
|
"./dist/prosekit-extensions.d.ts"
|
103
108
|
],
|
109
|
+
"autocomplete": [
|
110
|
+
"./dist/prosekit-extensions-autocomplete.d.ts"
|
111
|
+
],
|
104
112
|
"blockquote": [
|
105
113
|
"./dist/prosekit-extensions-blockquote.d.ts"
|
106
114
|
],
|