@kerebron/editor 0.4.31 → 0.5.0
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/README.md +3 -5
- package/esm/CoreEditor.d.ts +25 -7
- package/esm/CoreEditor.d.ts.map +1 -1
- package/esm/CoreEditor.js +93 -33
- package/esm/CoreEditor.js.map +1 -1
- package/esm/ExtensionManager.d.ts +6 -9
- package/esm/ExtensionManager.d.ts.map +1 -1
- package/esm/ExtensionManager.js +44 -50
- package/esm/ExtensionManager.js.map +1 -1
- package/esm/Node.d.ts +1 -1
- package/esm/Node.d.ts.map +1 -1
- package/esm/commands/CommandManager.d.ts +1 -2
- package/esm/commands/CommandManager.d.ts.map +1 -1
- package/esm/commands/CommandManager.js +2 -4
- package/esm/commands/CommandManager.js.map +1 -1
- package/esm/commands/baseCommandFactories.d.ts.map +1 -1
- package/esm/commands/baseCommandFactories.js +5 -3
- package/esm/commands/baseCommandFactories.js.map +1 -1
- package/esm/plugins/input-rules/InputRulesPlugin.d.ts +5 -4
- package/esm/plugins/input-rules/InputRulesPlugin.d.ts.map +1 -1
- package/esm/plugins/input-rules/InputRulesPlugin.js +88 -17
- package/esm/plugins/input-rules/InputRulesPlugin.js.map +1 -1
- package/esm/plugins/input-rules/rulebuilders.d.ts.map +1 -1
- package/esm/plugins/input-rules/rulebuilders.js +16 -9
- package/esm/plugins/input-rules/rulebuilders.js.map +1 -1
- package/esm/types.d.ts +4 -12
- package/esm/types.d.ts.map +1 -1
- package/esm/utilities/createNodeFromContent.js +1 -1
- package/esm/utilities/createNodeFromContent.js.map +1 -1
- package/package.json +1 -1
- package/src/CoreEditor.ts +144 -42
- package/src/ExtensionManager.ts +57 -60
- package/src/Node.ts +1 -1
- package/src/commands/CommandManager.ts +2 -5
- package/src/commands/baseCommandFactories.ts +6 -3
- package/src/plugins/input-rules/InputRulesPlugin.ts +126 -15
- package/src/plugins/input-rules/rulebuilders.ts +27 -10
- package/src/types.ts +4 -12
- package/src/utilities/createNodeFromContent.ts +2 -2
|
@@ -25,6 +25,7 @@ type PluginState = {
|
|
|
25
25
|
export class InputRule {
|
|
26
26
|
/// @internal
|
|
27
27
|
handler: (
|
|
28
|
+
tr: Transaction,
|
|
28
29
|
state: EditorState,
|
|
29
30
|
match: RegExpMatchArray,
|
|
30
31
|
start: number,
|
|
@@ -52,10 +53,11 @@ export class InputRule {
|
|
|
52
53
|
/// rule's effect, or null to indicate the input was not handled.
|
|
53
54
|
constructor(
|
|
54
55
|
/// @internal
|
|
55
|
-
readonly
|
|
56
|
+
readonly regex: RegExp,
|
|
56
57
|
handler:
|
|
57
58
|
| string
|
|
58
59
|
| ((
|
|
60
|
+
tr: Transaction,
|
|
59
61
|
state: EditorState,
|
|
60
62
|
match: RegExpMatchArray,
|
|
61
63
|
start: number,
|
|
@@ -72,7 +74,7 @@ export class InputRule {
|
|
|
72
74
|
inCode?: boolean | 'only';
|
|
73
75
|
} = {},
|
|
74
76
|
) {
|
|
75
|
-
this.
|
|
77
|
+
this.regex = regex;
|
|
76
78
|
this.handler = typeof handler == 'string'
|
|
77
79
|
? stringHandler(handler)
|
|
78
80
|
: handler;
|
|
@@ -83,11 +85,16 @@ export class InputRule {
|
|
|
83
85
|
|
|
84
86
|
function stringHandler(string: string) {
|
|
85
87
|
return function (
|
|
88
|
+
tr: Transaction,
|
|
86
89
|
state: EditorState,
|
|
87
90
|
match: RegExpMatchArray,
|
|
88
91
|
start: number,
|
|
89
92
|
end: number,
|
|
90
93
|
) {
|
|
94
|
+
if (!tr) {
|
|
95
|
+
tr = state.tr;
|
|
96
|
+
}
|
|
97
|
+
|
|
91
98
|
let insert = string;
|
|
92
99
|
if (match[1]) {
|
|
93
100
|
const offset = match[0].lastIndexOf(match[1]);
|
|
@@ -99,7 +106,7 @@ function stringHandler(string: string) {
|
|
|
99
106
|
start = end;
|
|
100
107
|
}
|
|
101
108
|
}
|
|
102
|
-
return
|
|
109
|
+
return tr.insertText(insert, start, end);
|
|
103
110
|
};
|
|
104
111
|
}
|
|
105
112
|
|
|
@@ -124,7 +131,7 @@ export class InputRulesPlugin extends Plugin<PluginState> {
|
|
|
124
131
|
|
|
125
132
|
props: {
|
|
126
133
|
handleTextInput(view, from, to, text) {
|
|
127
|
-
const cmd =
|
|
134
|
+
const cmd = runInputRulesRange(from, to, text);
|
|
128
135
|
const dispatch = (tr: Transaction) => {
|
|
129
136
|
view.dispatch(tr);
|
|
130
137
|
};
|
|
@@ -136,28 +143,115 @@ export class InputRulesPlugin extends Plugin<PluginState> {
|
|
|
136
143
|
setTimeout(() => {
|
|
137
144
|
const { $cursor } = view.state.selection as TextSelection;
|
|
138
145
|
if ($cursor) {
|
|
139
|
-
const cmd =
|
|
146
|
+
const cmd = runInputRulesRange($cursor.pos, $cursor.pos, '');
|
|
140
147
|
const dispatch = (tr: Transaction) => {
|
|
141
148
|
view.dispatch(tr);
|
|
142
149
|
};
|
|
143
150
|
return cmd(view.state, dispatch, view);
|
|
144
|
-
// run(view, $cursor.pos, $cursor.pos, '',
|
|
151
|
+
// run(view, $cursor.pos, $cursor.pos, '', this);
|
|
145
152
|
}
|
|
146
153
|
});
|
|
147
154
|
},
|
|
148
155
|
},
|
|
149
156
|
},
|
|
150
157
|
|
|
158
|
+
rules,
|
|
151
159
|
isInputRules: true,
|
|
152
160
|
});
|
|
153
161
|
}
|
|
154
162
|
}
|
|
155
163
|
|
|
156
|
-
export const
|
|
164
|
+
export const runInputRulesTexts: CommandFactory = () => {
|
|
165
|
+
const cmd: Command = (
|
|
166
|
+
state: EditorState,
|
|
167
|
+
dispatch?: (tr: Transaction) => void,
|
|
168
|
+
view?: EditorView,
|
|
169
|
+
) => {
|
|
170
|
+
const plugins = state.plugins;
|
|
171
|
+
const plugin: InputRulesPlugin | undefined = plugins.find((plugin) =>
|
|
172
|
+
(plugin.spec as any).isInputRules
|
|
173
|
+
);
|
|
174
|
+
if (!plugin) {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const rules: readonly InputRule[] = plugin.spec.rules;
|
|
179
|
+
|
|
180
|
+
if (view?.composing) return false;
|
|
181
|
+
|
|
182
|
+
let doc = state.doc;
|
|
183
|
+
let tr = state.tr;
|
|
184
|
+
|
|
185
|
+
for (let i = 0; i < rules.length; i++) {
|
|
186
|
+
const rule = rules[i];
|
|
187
|
+
|
|
188
|
+
const textNodePositions: { pos: number; node: ProseMirrorNode }[] = [];
|
|
189
|
+
doc.descendants((node, pos) => {
|
|
190
|
+
if (node.isText) {
|
|
191
|
+
textNodePositions.push({ pos, node });
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
if (textNodePositions.length === 0) {
|
|
196
|
+
return false; // Nothing to do
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Process from the end of the document to the start to avoid position invalidation
|
|
200
|
+
for (let i = textNodePositions.length - 1; i >= 0; i--) {
|
|
201
|
+
const { pos, node } = textNodePositions[i];
|
|
202
|
+
if (!node.isText || !node.text) continue;
|
|
203
|
+
|
|
204
|
+
let text = node.text;
|
|
205
|
+
|
|
206
|
+
// throw new Error('aaaaaaaaaaa');
|
|
207
|
+
|
|
208
|
+
if (node.type.spec.code) {
|
|
209
|
+
if (!rule.inCode) continue;
|
|
210
|
+
} else if (rule.inCode === 'only') {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
const match = rule.regex.exec(text);
|
|
214
|
+
if (!match) {
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const from = pos;
|
|
219
|
+
const to = pos + node.nodeSize;
|
|
220
|
+
|
|
221
|
+
let subTr = rule.handler(
|
|
222
|
+
tr,
|
|
223
|
+
state,
|
|
224
|
+
match,
|
|
225
|
+
from - (match[0].length - text.length),
|
|
226
|
+
to,
|
|
227
|
+
);
|
|
228
|
+
if (!subTr) continue;
|
|
229
|
+
|
|
230
|
+
tr = subTr;
|
|
231
|
+
doc = tr.doc;
|
|
232
|
+
|
|
233
|
+
if (rule.undoable) {
|
|
234
|
+
tr.setMeta(plugin, { transform: tr, from, to, text });
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (dispatch && tr.docChanged) {
|
|
240
|
+
dispatch(tr);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return true;
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
cmd.displayName = 'runInputRulesTexts';
|
|
247
|
+
|
|
248
|
+
return cmd;
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
export const runInputRulesRange: CommandFactory = (
|
|
157
252
|
from: number,
|
|
158
253
|
to: number,
|
|
159
254
|
text: string,
|
|
160
|
-
rules: readonly InputRule[],
|
|
161
255
|
) => {
|
|
162
256
|
const cmd: Command = (
|
|
163
257
|
state: EditorState,
|
|
@@ -172,6 +266,8 @@ export const runInputRules: CommandFactory = (
|
|
|
172
266
|
return false;
|
|
173
267
|
}
|
|
174
268
|
|
|
269
|
+
const rules: readonly InputRule[] = plugin.spec.rules;
|
|
270
|
+
|
|
175
271
|
if (view?.composing) return false;
|
|
176
272
|
const $from = state.doc.resolve(from);
|
|
177
273
|
const textBefore = $from.parent.textBetween(
|
|
@@ -180,6 +276,7 @@ export const runInputRules: CommandFactory = (
|
|
|
180
276
|
null,
|
|
181
277
|
'\ufffc',
|
|
182
278
|
) + text;
|
|
279
|
+
let tr = state.tr;
|
|
183
280
|
for (let i = 0; i < rules.length; i++) {
|
|
184
281
|
const rule = rules[i];
|
|
185
282
|
if ($from.parent.type.spec.code) {
|
|
@@ -187,19 +284,33 @@ export const runInputRules: CommandFactory = (
|
|
|
187
284
|
} else if (rule.inCode === 'only') {
|
|
188
285
|
continue;
|
|
189
286
|
}
|
|
190
|
-
const match = rule.
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
287
|
+
const match = rule.regex.exec(textBefore);
|
|
288
|
+
if (!match) {
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const subTr = rule.handler(
|
|
293
|
+
tr,
|
|
294
|
+
state,
|
|
295
|
+
match,
|
|
296
|
+
from - (match[0].length - text.length),
|
|
297
|
+
to,
|
|
298
|
+
);
|
|
299
|
+
if (!subTr) continue;
|
|
300
|
+
|
|
301
|
+
tr = subTr;
|
|
194
302
|
if (rule.undoable) {
|
|
195
303
|
tr.setMeta(plugin, { transform: tr, from, to, text });
|
|
196
304
|
}
|
|
197
|
-
view?.dispatch(tr);
|
|
198
|
-
return true;
|
|
199
305
|
}
|
|
306
|
+
|
|
307
|
+
if (dispatch) {
|
|
308
|
+
dispatch(tr);
|
|
309
|
+
}
|
|
310
|
+
|
|
200
311
|
return false;
|
|
201
312
|
};
|
|
202
|
-
cmd.displayName = '
|
|
313
|
+
cmd.displayName = 'runInputRulesRange';
|
|
203
314
|
|
|
204
315
|
return cmd;
|
|
205
316
|
};
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
import { canJoin, findWrapping } from 'prosemirror-transform';
|
|
2
|
-
import {
|
|
1
|
+
import { canJoin, findWrapping, replaceStep } from 'prosemirror-transform';
|
|
2
|
+
import {
|
|
3
|
+
Attrs,
|
|
4
|
+
Fragment,
|
|
5
|
+
Node,
|
|
6
|
+
NodeType,
|
|
7
|
+
ResolvedPos,
|
|
8
|
+
Slice,
|
|
9
|
+
} from 'prosemirror-model';
|
|
3
10
|
|
|
4
11
|
import { InputRule } from './InputRulesPlugin.js';
|
|
5
12
|
|
|
@@ -24,9 +31,12 @@ export function wrappingInputRule(
|
|
|
24
31
|
getAttrs: Attrs | null | ((matches: RegExpMatchArray) => Attrs | null) = null,
|
|
25
32
|
joinPredicate?: (match: RegExpMatchArray, node: Node) => boolean,
|
|
26
33
|
) {
|
|
27
|
-
return new InputRule(regexp, (state, match, start, end) => {
|
|
34
|
+
return new InputRule(regexp, (tr, state, match, start, end) => {
|
|
35
|
+
if (!tr) {
|
|
36
|
+
tr = state.tr;
|
|
37
|
+
}
|
|
28
38
|
const attrs = getAttrs instanceof Function ? getAttrs(match) : getAttrs;
|
|
29
|
-
|
|
39
|
+
tr = tr.delete(start, end);
|
|
30
40
|
const $start = tr.doc.resolve(start);
|
|
31
41
|
const range = $start.blockRange();
|
|
32
42
|
const wrapping = range && findWrapping(range, nodeType, attrs);
|
|
@@ -54,7 +64,10 @@ export function textblockTypeInputRule(
|
|
|
54
64
|
nodeType: NodeType,
|
|
55
65
|
getAttrs: Attrs | null | ((match: RegExpMatchArray) => Attrs | null) = null,
|
|
56
66
|
) {
|
|
57
|
-
return new InputRule(regexp, (state, match, start, end) => {
|
|
67
|
+
return new InputRule(regexp, (tr, state, match, start, end) => {
|
|
68
|
+
if (!tr) {
|
|
69
|
+
tr = state.tr;
|
|
70
|
+
}
|
|
58
71
|
const $start = state.doc.resolve(start);
|
|
59
72
|
const attrs = getAttrs instanceof Function ? getAttrs(match) : getAttrs;
|
|
60
73
|
if (
|
|
@@ -64,7 +77,7 @@ export function textblockTypeInputRule(
|
|
|
64
77
|
nodeType,
|
|
65
78
|
)
|
|
66
79
|
) return null;
|
|
67
|
-
return
|
|
80
|
+
return tr
|
|
68
81
|
.delete(start, end)
|
|
69
82
|
.setBlockType(start, start, nodeType, attrs);
|
|
70
83
|
});
|
|
@@ -75,14 +88,18 @@ export function replaceInlineNode(
|
|
|
75
88
|
nodeType: NodeType,
|
|
76
89
|
getAttrs: Attrs | null | ((matches: RegExpMatchArray) => Attrs | null) = null,
|
|
77
90
|
) {
|
|
78
|
-
return new InputRule(regexp, (state, match, start, end) => {
|
|
91
|
+
return new InputRule(regexp, (tr, state, match, start, end) => {
|
|
92
|
+
if (!tr) {
|
|
93
|
+
tr = state.tr;
|
|
94
|
+
}
|
|
95
|
+
|
|
79
96
|
const attrs = getAttrs instanceof Function ? getAttrs(match) : getAttrs;
|
|
97
|
+
|
|
80
98
|
const node = nodeType.createAndFill(attrs);
|
|
81
|
-
const slice = new Slice(Fragment.from(node), 0, 0);
|
|
82
99
|
|
|
83
|
-
const tr = state.tr;
|
|
84
100
|
const from = tr.mapping.map(start);
|
|
85
101
|
const to = tr.mapping.map(end);
|
|
86
|
-
|
|
102
|
+
|
|
103
|
+
return tr.replaceWith(from, to, Fragment.from(node));
|
|
87
104
|
});
|
|
88
105
|
}
|
package/src/types.ts
CHANGED
|
@@ -3,6 +3,10 @@ import type { Extension } from './Extension.js';
|
|
|
3
3
|
import type { Mark } from './Mark.js';
|
|
4
4
|
import type { Node } from './Node.js';
|
|
5
5
|
|
|
6
|
+
export interface EditorKit {
|
|
7
|
+
getExtensions(): AnyExtensionOrReq[];
|
|
8
|
+
}
|
|
9
|
+
|
|
6
10
|
export type AnyExtension = Extension | Node | Mark;
|
|
7
11
|
export type AnyExtensionOrReq = AnyExtension | {
|
|
8
12
|
requires: Array<AnyExtensionOrReq | string>;
|
|
@@ -10,18 +14,6 @@ export type AnyExtensionOrReq = AnyExtension | {
|
|
|
10
14
|
|
|
11
15
|
export type Content = JSONContent | JSONContent[] | null;
|
|
12
16
|
|
|
13
|
-
export interface EditorConfig {
|
|
14
|
-
element: HTMLElement;
|
|
15
|
-
content: Content;
|
|
16
|
-
parseOptions: ParseOptions;
|
|
17
|
-
extensions: AnyExtensionOrReq[];
|
|
18
|
-
cdnUrl?: string;
|
|
19
|
-
uri?: string;
|
|
20
|
-
languageID?: string;
|
|
21
|
-
topNode?: string;
|
|
22
|
-
readOnly?: boolean;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
17
|
export type JSONContent = {
|
|
26
18
|
type?: string;
|
|
27
19
|
attrs?: Record<string, any>;
|
|
@@ -19,9 +19,9 @@ export function createNodeFromObject(
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
return node;
|
|
22
|
-
} catch (error) {
|
|
22
|
+
} catch (error: any) {
|
|
23
23
|
if (options?.errorOnInvalidContent) {
|
|
24
|
-
throw new Error('Invalid JSON content', {
|
|
24
|
+
throw new Error('Invalid JSON content: ' + error.message, {
|
|
25
25
|
cause: error as Error,
|
|
26
26
|
});
|
|
27
27
|
}
|