@prosekit/extensions 0.1.6 → 0.2.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.
@@ -1,16 +1,22 @@
1
+ import { Attrs } from '@prosekit/pm/model';
2
+ import type { BundledTheme } from 'shikiji';
1
3
  import { CommandArgs } from '@prosekit/core';
4
+ import type { ContentMatch } from '@prosekit/pm/model';
2
5
  import { DedentListOptions } from 'prosemirror-flat-list';
3
6
  import { EditorState } from '@prosekit/pm/state';
4
7
  import { Extension } from '@prosekit/core';
5
8
  import { ExtensionTyping } from '@prosekit/core';
6
- import type { HLJSApi } from 'highlight.js';
7
9
  import { IndentListOptions } from 'prosemirror-flat-list';
10
+ import { InputRule } from '@prosekit/pm/inputrules';
8
11
  import { ListAttributes } from 'prosemirror-flat-list';
9
12
  import { NodeRange } from 'prosemirror-model';
13
+ import { NodeType } from '@prosekit/pm/model';
14
+ import { NodeType as NodeType_2 } from 'prosemirror-model';
10
15
  import { Options } from 'tsup';
11
16
  import { Parser } from 'prosemirror-highlight';
12
17
  import { Plugin as Plugin_2 } from '@prosekit/pm/state';
13
18
  import { PluginKey } from '@prosekit/pm/state';
19
+ import { ProseMirrorNode } from '@prosekit/pm/model';
14
20
  import { ToggleCollapsedOptions } from 'prosemirror-flat-list';
15
21
  import { Transaction } from '@prosekit/pm/state';
16
22
  import { UnwrapListOptions } from 'prosemirror-flat-list';
@@ -49,8 +55,6 @@ export declare function createAutocompletePlugin({ getRules, }: {
49
55
  getRules: () => AutocompleteRule[];
50
56
  }): Plugin_2;
51
57
 
52
- export declare function createPredictionPlugin(options: SuggestionOptions): Plugin_2;
53
-
54
58
  export declare const default_alias: Options | Options[] | ((overrideOptions: Options) => Options | Options[] | Promise<Options | Options[]>);
55
59
 
56
60
  export declare const default_alias_1: {
@@ -59,11 +63,9 @@ export declare const default_alias_1: {
59
63
  };
60
64
  };
61
65
 
62
- export declare function defaultCanMatch({ state }: {
63
- state: EditorState;
64
- }): boolean;
66
+ export declare function defaultBlockAt(match: ContentMatch): NodeType_2 | null;
65
67
 
66
- export declare function defaultIsValid({ state }: {
68
+ export declare function defaultCanMatch({ state }: {
67
69
  state: EditorState;
68
70
  }): boolean;
69
71
 
@@ -117,22 +119,13 @@ toggleCode: [];
117
119
  *
118
120
  * - {@link defineCodeBlockSpec}
119
121
  * - {@link defineCodeBlockInputRule}
122
+ * - {@link defineCodeBlockEnterRule}
123
+ * - {@link defineCodeBlockKeymap}
120
124
  * - {@link defineCodeBlockCommands}.
121
125
  *
122
126
  * @public
123
127
  */
124
- export declare function defineCodeBlock(options?: {
125
- /**
126
- * @deprecated Use `defineCodeBlockHighlight` function instead.
127
- */
128
- hljs?: HLJSApi;
129
- /**
130
- * A parser for the `prosemirror-highlight` package to use for syntax highlighting.
131
- *
132
- * @deprecated Use the standalone `defineCodeBlockHighlight` function instead.
133
- */
134
- parser?: HighlightParser;
135
- }): Extension< {
128
+ export declare function defineCodeBlock(): Extension< {
136
129
  NODES: "codeBlock";
137
130
  COMMAND_ARGS: {
138
131
  setCodeBlockLanguage: [language: string];
@@ -152,6 +145,15 @@ setCodeBlockLanguage: [language: string];
152
145
  export { defineCodeBlockCommands }
153
146
  export { defineCodeBlockCommands as defineCodeBlockCommands_alias_1 }
154
147
 
148
+ /**
149
+ * Adds enter rules for `codeBlock` nodes.
150
+ *
151
+ * @public
152
+ */
153
+ declare function defineCodeBlockEnterRule(): Extension<ExtensionTyping<string, string, CommandArgs>>;
154
+ export { defineCodeBlockEnterRule }
155
+ export { defineCodeBlockEnterRule as defineCodeBlockEnterRule_alias_1 }
156
+
155
157
  /**
156
158
  * Adds syntax highlighting to code blocks. This function requires a `Parser`
157
159
  * instance from the `prosemirror-highlight` package. See the
@@ -166,13 +168,6 @@ declare function defineCodeBlockHighlight({ parser, }: {
166
168
  export { defineCodeBlockHighlight }
167
169
  export { defineCodeBlockHighlight as defineCodeBlockHighlight_alias_1 }
168
170
 
169
- /**
170
- * @deprecated
171
- */
172
- export declare function defineCodeBlockHighlightDeprecated(options: {
173
- hljs?: HLJSApi;
174
- }): Extension<ExtensionTyping<string, string, CommandArgs>>;
175
-
176
171
  /**
177
172
  * Adds input rules for `codeBlock` nodes.
178
173
  *
@@ -182,6 +177,27 @@ declare function defineCodeBlockInputRule(): Extension<ExtensionTyping<string, s
182
177
  export { defineCodeBlockInputRule }
183
178
  export { defineCodeBlockInputRule as defineCodeBlockInputRule_alias_1 }
184
179
 
180
+ /**
181
+ * Defines the keymap for code blocks.
182
+ */
183
+ export declare function defineCodeBlockKeymap(): Extension<ExtensionTyping<string, string, CommandArgs>>;
184
+
185
+ /**
186
+ * Adds syntax highlighting to code blocks using the [shikiji](https://github.com/antfu/shikiji) package.
187
+ *
188
+ * @public
189
+ */
190
+ declare function defineCodeBlockShikiji(options?: {
191
+ /**
192
+ * The shikiji theme to use.
193
+ *
194
+ * @default 'github-light'
195
+ */
196
+ theme?: BundledTheme;
197
+ }): Extension;
198
+ export { defineCodeBlockShikiji }
199
+ export { defineCodeBlockShikiji as defineCodeBlockShikiji_alias_1 }
200
+
185
201
  /**
186
202
  * Defines the `codeBlock` node spec.
187
203
  *
@@ -206,6 +222,15 @@ export declare function defineCodeSpec(): Extension< {
206
222
  MARKS: "code";
207
223
  }>;
208
224
 
225
+ /**
226
+ * Defines an enter rule. An enter rule applies when the text directly in front of
227
+ * the cursor matches `regex` and user presses Enter. The `regex` should end
228
+ * with `$`.
229
+ *
230
+ * @public
231
+ */
232
+ export declare function defineEnterRule({ regex, handler, }: EnterRuleOptions): Extension;
233
+
209
234
  /**
210
235
  * @public
211
236
  */
@@ -254,6 +279,15 @@ export declare function defineImageSpec(): Extension< {
254
279
  NODES: "image";
255
280
  }>;
256
281
 
282
+ /**
283
+ * Defines an input rule extension.
284
+ *
285
+ * @param rule - The ProseMirror input rule to add.
286
+ *
287
+ * @public
288
+ */
289
+ export declare function defineInputRule(rule: InputRule): Extension;
290
+
257
291
  /**
258
292
  * @public
259
293
  */
@@ -303,36 +337,36 @@ MARKS: "link";
303
337
  /**
304
338
  * @public
305
339
  */
306
- export declare function defineList(): Extension< {
307
- NODES: "list";
308
- COMMAND_ARGS: {
309
- dedentList: [options?: DedentListOptions | undefined];
310
- indentList: [options?: IndentListOptions | undefined];
311
- moveList: [direction: "up" | "down"];
312
- splitList: [];
313
- toggleCollapsed: [(ToggleCollapsedOptions | undefined)?];
314
- toggleList: [attrs: ListAttributes];
315
- unwrapList: [options?: UnwrapListOptions | undefined];
316
- wrapInList: [getAttrs: ListAttributes | ((range: NodeRange) => ListAttributes | null)];
317
- insertList: [attrs?: ListAttributes | undefined];
318
- };
340
+ export declare function defineList(): Extension<{
341
+ NODES: "list";
342
+ COMMAND_ARGS: {
343
+ dedentList: [options?: DedentListOptions | undefined];
344
+ indentList: [options?: IndentListOptions | undefined];
345
+ moveList: [direction: "up" | "down"];
346
+ splitList: [];
347
+ toggleCollapsed: [(ToggleCollapsedOptions | undefined)?];
348
+ toggleList: [attrs: ListAttributes];
349
+ unwrapList: [options?: UnwrapListOptions | undefined];
350
+ wrapInList: [getAttrs: ListAttributes | ((range: NodeRange) => ListAttributes | null)];
351
+ insertList: [attrs?: ListAttributes | undefined];
352
+ };
319
353
  }>;
320
354
 
321
- export declare function defineListCommands(): Extension< {
322
- COMMAND_ARGS: {
323
- dedentList: [options?: DedentListOptions | undefined];
324
- indentList: [options?: IndentListOptions | undefined];
325
- moveList: [direction: "up" | "down"];
326
- splitList: [];
327
- toggleCollapsed: [(ToggleCollapsedOptions | undefined)?];
328
- toggleList: [attrs: ListAttributes];
329
- unwrapList: [options?: UnwrapListOptions | undefined];
330
- wrapInList: [getAttrs: ListAttributes | ((range: NodeRange) => ListAttributes | null)];
331
- insertList: [attrs?: ListAttributes | undefined];
332
- };
355
+ export declare function defineListCommands(): Extension<{
356
+ COMMAND_ARGS: {
357
+ dedentList: [options?: DedentListOptions | undefined];
358
+ indentList: [options?: IndentListOptions | undefined];
359
+ moveList: [direction: "up" | "down"];
360
+ splitList: [];
361
+ toggleCollapsed: [(ToggleCollapsedOptions | undefined)?];
362
+ toggleList: [attrs: ListAttributes];
363
+ unwrapList: [options?: UnwrapListOptions | undefined];
364
+ wrapInList: [getAttrs: ListAttributes | ((range: NodeRange) => ListAttributes | null)];
365
+ insertList: [attrs?: ListAttributes | undefined];
366
+ };
333
367
  }>;
334
368
 
335
- export declare function defineListInputRules(): Extension<ExtensionTyping<string, string, CommandArgs>>;
369
+ export declare function defineListInputRules(): Extension;
336
370
 
337
371
  /**
338
372
  * Returns a extension that adds key bindings for list.
@@ -343,8 +377,8 @@ export declare function defineListKeymap(): Extension<ExtensionTyping<string, st
343
377
 
344
378
  export declare function defineListPlugins(): Extension<ExtensionTyping<string, string, CommandArgs>>;
345
379
 
346
- export declare function defineListSpec(): Extension< {
347
- NODES: "list";
380
+ export declare function defineListSpec(): Extension<{
381
+ NODES: "list";
348
382
  }>;
349
383
 
350
384
  /**
@@ -404,9 +438,35 @@ MARKS: "strike";
404
438
  }>;
405
439
 
406
440
  /**
407
- * @deprecated Use `defineAutocomplete` instead.
441
+ * Defines an enter rule that replaces the matched text with a block node.
442
+ *
443
+ * See also {@link defineEnterRule}.
444
+ *
445
+ * @public
408
446
  */
409
- export declare function defineSuggestion(options: SuggestionOptions): Extension<ExtensionTyping<string, string, CommandArgs>>;
447
+ export declare function defineTextBlockEnterRule({ regex, type, attrs, }: TextBlockEnterRuleOptions): Extension;
448
+
449
+ /**
450
+ * Defines an input rule that changes the type of a textblock when the matched
451
+ * text is typed into it.
452
+ *
453
+ * See also [textblockTypeInputRule](https://prosemirror.net/docs/ref/#inputrules.textblockTypeInputRule)
454
+ */
455
+ export declare function defineTextBlockInputRule({ regex, type, attrs, }: {
456
+ /**
457
+ * The regular expression to match against. You'll usually want to start it
458
+ * with `^` to that it is only matched at the start of a textblock.
459
+ */
460
+ regex: RegExp;
461
+ /**
462
+ * The node type to replace the matched text with.
463
+ */
464
+ type: string | NodeType;
465
+ /**
466
+ * Attributes to set on the node.
467
+ */
468
+ attrs?: Attrs | null | ((match: RegExpMatchArray) => Attrs | null);
469
+ }): Extension;
410
470
 
411
471
  /**
412
472
  * @public
@@ -430,6 +490,63 @@ export declare function defineUnderlineSpec(): Extension< {
430
490
  MARKS: "underline";
431
491
  }>;
432
492
 
493
+ /**
494
+ * Defines an input rule for automatically wrapping a textblock when a given
495
+ * string is typed.
496
+ *
497
+ * See also [wrappingInputRule](https://prosemirror.net/docs/ref/#inputrules.wrappingInputRule)
498
+ */
499
+ export declare function defineWrappingInputRule({ regex, type, attrs, join, }: {
500
+ /**
501
+ * The regular expression to match against. You'll usually want to start it
502
+ * with `^` to that it is only matched at the start of a textblock.
503
+ */
504
+ regex: RegExp;
505
+ /**
506
+ * The type of node to wrap in.
507
+ */
508
+ type: string | NodeType;
509
+ /**
510
+ * Attributes to set on the node.
511
+ */
512
+ attrs?: Attrs | null | ((match: RegExpMatchArray) => Attrs | null);
513
+ /**
514
+ * By default, if there's a node with the same type above the newly wrapped
515
+ * node, the rule will try to
516
+ * [join](https://prosemirror.net/docs/ref/#transform.Transform.join) those
517
+ * two nodes. You can pass a join predicate, which takes a regular expression
518
+ * match and the node before the wrapped node, and can return a boolean to
519
+ * indicate whether a join should happen.
520
+ */
521
+ join?: (match: RegExpMatchArray, node: ProseMirrorNode) => boolean;
522
+ }): Extension;
523
+
524
+ /**
525
+ * @public
526
+ */
527
+ export declare type EnterRuleHandler = (options: {
528
+ state: EditorState;
529
+ from: number;
530
+ to: number;
531
+ match: RegExpExecArray;
532
+ }) => Transaction | null;
533
+
534
+ /**
535
+ * Options for {@link createEnterRule}.
536
+ *
537
+ * @public
538
+ */
539
+ export declare type EnterRuleOptions = {
540
+ /**
541
+ * The regular expression to match against. It should end with `$`.
542
+ */
543
+ regex: RegExp;
544
+ /**
545
+ * A handler function to be called when an enter rule is triggered.
546
+ */
547
+ handler: EnterRuleHandler;
548
+ };
549
+
433
550
  export declare function getPluginState(state: EditorState): PredictionPluginState | undefined;
434
551
 
435
552
  export declare function getTrMeta(tr: Transaction): PredictionPluginState;
@@ -469,25 +586,15 @@ declare type MatchHandler = (options: {
469
586
  export { MatchHandler }
470
587
  export { MatchHandler as MatchHandler_alias_1 }
471
588
 
472
- /**
473
- * @returns Return a Transaction object if you want to append a transaction to current state (using )
474
- */
475
- declare type MatchHandler_2 = (options: {
476
- rule: PredictionRule;
477
- match: RegExpMatchArray;
478
- matchAfter: RegExpMatchArray | null;
479
- state: EditorState;
480
- dismiss: VoidFunction;
481
- deleteMatch: VoidFunction;
482
- }) => void;
483
-
484
589
  export declare interface MentionAttrs {
485
590
  id: string;
486
591
  kind: string;
487
592
  value: string;
488
593
  }
489
594
 
490
- export declare const OBJECT_REPLACEMENT = "\uFFFC";
595
+ export declare const NO_BREAK_SPACE = "\u00A0";
596
+
597
+ export declare const OBJECT_REPLACEMENT_CHARACTER = "\uFFFC";
491
598
 
492
599
  export declare interface PlaceholderOptions {
493
600
  /**
@@ -517,31 +624,26 @@ export declare interface PredictionPluginState {
517
624
  } | null;
518
625
  }
519
626
 
520
- declare interface PredictionRule {
521
- match: RegExp;
522
- matchAfter?: RegExp;
523
- }
524
- export { PredictionRule }
525
- export { PredictionRule as PredictionRule_alias_1 }
526
-
527
627
  export declare function setTrMeta(tr: Transaction, meta: PredictionPluginState): Transaction;
528
628
 
529
- declare interface SuggestionOptions {
530
- rules: PredictionRule[];
531
- onMatch: MatchHandler_2;
532
- onDeactivate: VoidFunction;
629
+ /**
630
+ * Options for {@link createTextBlockEnterRule}.
631
+ *
632
+ * @public
633
+ */
634
+ export declare type TextBlockEnterRuleOptions = {
533
635
  /**
534
- * You can pass this function if you want to skip the matching in some cases.
535
- * By default, the plugin will only run the matching if the current selection
536
- * is empty, and the selection is not inside a
537
- * [code](https://prosemirror.net/docs/ref/#model.NodeSpec.code) node nor
538
- * inside a mark with the name as `code`.
636
+ * The regular expression to match against. It should end with `$`.
539
637
  */
540
- isValid?: (options: {
541
- state: EditorState;
542
- }) => boolean;
543
- }
544
- export { SuggestionOptions }
545
- export { SuggestionOptions as SuggestionOptions_alias_1 }
638
+ regex: RegExp;
639
+ /**
640
+ * The node type to replace the matched text with.
641
+ */
642
+ type: string | NodeType;
643
+ /**
644
+ * Attributes to set on the node.
645
+ */
646
+ attrs?: Attrs | null | ((match: RegExpMatchArray) => Attrs | null);
647
+ };
546
648
 
547
649
  export { }
@@ -0,0 +1,56 @@
1
+ // src/input-rule/index.ts
2
+ import {
3
+ Facet,
4
+ getNodeType,
5
+ pluginFacet
6
+ } from "@prosekit/core";
7
+ import {
8
+ inputRules,
9
+ textblockTypeInputRule,
10
+ wrappingInputRule
11
+ } from "@prosekit/pm/inputrules";
12
+ import "@prosekit/pm/model";
13
+ import "@prosekit/pm/state";
14
+ function defineInputRule(rule) {
15
+ return inputRuleFacet.extension([() => rule]);
16
+ }
17
+ function defineTextBlockInputRule({
18
+ regex,
19
+ type,
20
+ attrs
21
+ }) {
22
+ return inputRuleFacet.extension([
23
+ ({ schema }) => {
24
+ const nodeType = getNodeType(schema, type);
25
+ return textblockTypeInputRule(regex, nodeType, attrs);
26
+ }
27
+ ]);
28
+ }
29
+ function defineWrappingInputRule({
30
+ regex,
31
+ type,
32
+ attrs,
33
+ join
34
+ }) {
35
+ return inputRuleFacet.extension([
36
+ ({ schema }) => {
37
+ const nodeType = getNodeType(schema, type);
38
+ return wrappingInputRule(regex, nodeType, attrs, join);
39
+ }
40
+ ]);
41
+ }
42
+ var inputRuleFacet = Facet.define({
43
+ convert: (inputs) => {
44
+ return (context) => {
45
+ const rules = inputs.flatMap((callback) => callback(context));
46
+ return [inputRules({ rules })];
47
+ };
48
+ },
49
+ next: pluginFacet
50
+ });
51
+
52
+ export {
53
+ defineInputRule,
54
+ defineTextBlockInputRule,
55
+ defineWrappingInputRule
56
+ };
@@ -0,0 +1,6 @@
1
+ // src/utils/unicode.ts
2
+ var OBJECT_REPLACEMENT_CHARACTER = "\uFFFC";
3
+
4
+ export {
5
+ OBJECT_REPLACEMENT_CHARACTER
6
+ };
@@ -1,3 +1,7 @@
1
+ import {
2
+ OBJECT_REPLACEMENT_CHARACTER
3
+ } from "./chunk-HQZORKGY.js";
4
+
1
5
  // src/autocomplete/index.ts
2
6
  import {
3
7
  Facet,
@@ -22,7 +26,6 @@ function isInsideCode($pos) {
22
26
  }
23
27
  return $pos.marks().some((mark) => mark.type.name === "code");
24
28
  }
25
- var OBJECT_REPLACEMENT = "\uFFFC";
26
29
  function getPluginState(state) {
27
30
  return pluginKey.getState(state);
28
31
  }
@@ -75,10 +78,14 @@ function createAutocompletePlugin({
75
78
  const textContent = view.state.doc.textBetween(
76
79
  from,
77
80
  to,
78
- OBJECT_REPLACEMENT
81
+ OBJECT_REPLACEMENT_CHARACTER
79
82
  );
80
83
  const deleteMatch = () => {
81
- if (view.state.doc.textBetween(from, to, OBJECT_REPLACEMENT) === textContent) {
84
+ if (view.state.doc.textBetween(
85
+ from,
86
+ to,
87
+ OBJECT_REPLACEMENT_CHARACTER
88
+ ) === textContent) {
82
89
  view.dispatch(view.state.tr.delete(from, to));
83
90
  }
84
91
  };
@@ -125,7 +132,7 @@ function calcPluginState(state, rules) {
125
132
  Math.max(0, parentOffset - MAX_MATCH),
126
133
  parentOffset,
127
134
  null,
128
- OBJECT_REPLACEMENT
135
+ OBJECT_REPLACEMENT_CHARACTER
129
136
  );
130
137
  for (const rule of rules) {
131
138
  if (!rule.canMatch({ state })) {
@@ -1,7 +1,9 @@
1
1
  export { defineCodeBlock } from './_tsup-dts-rollup';
2
2
  export { defineCodeBlockCommands_alias_1 as defineCodeBlockCommands } from './_tsup-dts-rollup';
3
+ export { defineCodeBlockEnterRule_alias_1 as defineCodeBlockEnterRule } from './_tsup-dts-rollup';
3
4
  export { defineCodeBlockHighlight_alias_1 as defineCodeBlockHighlight } from './_tsup-dts-rollup';
4
5
  export { defineCodeBlockInputRule_alias_1 as defineCodeBlockInputRule } from './_tsup-dts-rollup';
6
+ export { defineCodeBlockShikiji_alias_1 as defineCodeBlockShikiji } from './_tsup-dts-rollup';
5
7
  export { defineCodeBlockSpec_alias_1 as defineCodeBlockSpec } from './_tsup-dts-rollup';
6
8
  export { CodeBlockAttrs_alias_1 as CodeBlockAttrs } from './_tsup-dts-rollup';
7
9
  export { HighlightParser_alias_1 as HighlightParser } from './_tsup-dts-rollup';
@@ -1,3 +1,10 @@
1
+ import {
2
+ OBJECT_REPLACEMENT_CHARACTER
3
+ } from "./chunk-HQZORKGY.js";
4
+ import {
5
+ defineTextBlockInputRule
6
+ } from "./chunk-DYFRBXUX.js";
7
+
1
8
  // src/code-block/index.ts
2
9
  import { union } from "@prosekit/core";
3
10
 
@@ -30,59 +37,216 @@ function defineCodeBlockHighlight({
30
37
  );
31
38
  }
32
39
 
33
- // src/code-block/code-block-highlight-deprecated.ts
34
- import { definePlugin as definePlugin2 } from "@prosekit/core";
35
- import { PluginKey, ProseMirrorPlugin } from "@prosekit/pm/state";
36
- import { DecorationSet } from "@prosekit/pm/view";
37
- import { getHighlightDecorations } from "prosemirror-highlightjs";
38
- function defineCodeBlockHighlightDeprecated(options) {
39
- const hljs = options.hljs;
40
- const plugin = new ProseMirrorPlugin({
41
- key,
42
- state: {
43
- init(_config, state) {
44
- const decorations = hljs ? getHighlightDecorations(
45
- state.doc,
46
- hljs,
47
- blockTypes,
48
- languageExtractor
49
- ) : [];
50
- return DecorationSet.create(state.doc, decorations);
51
- },
52
- apply(tr, set) {
53
- if (!tr.docChanged) {
54
- return set.map(tr.mapping, tr.doc);
55
- }
56
- const decorations = hljs ? getHighlightDecorations(tr.doc, hljs, blockTypes, languageExtractor) : [];
57
- return DecorationSet.create(tr.doc, decorations);
58
- }
59
- },
60
- props: {
61
- decorations(state) {
62
- return key.getState(state);
40
+ // src/enter-rule/index.ts
41
+ import {
42
+ Facet,
43
+ getNodeType,
44
+ isTextSelection,
45
+ keymapFacet
46
+ } from "@prosekit/core";
47
+ function defineEnterRule({
48
+ regex,
49
+ handler
50
+ }) {
51
+ const rule = new EnterRule(regex, handler);
52
+ return inputRuleFacet.extension([rule]);
53
+ }
54
+ function defineTextBlockEnterRule({
55
+ regex,
56
+ type,
57
+ attrs
58
+ }) {
59
+ return defineEnterRule({
60
+ regex,
61
+ handler: ({ state, from, to, match }) => {
62
+ const nodeType = getNodeType(state.schema, type);
63
+ const $start = state.doc.resolve(from);
64
+ if (!$start.node(-1).canReplaceWith($start.index(-1), $start.indexAfter(-1), nodeType)) {
65
+ return null;
63
66
  }
67
+ const nodeAttrs = attrs && typeof attrs === "function" ? attrs(match) : attrs;
68
+ return state.tr.delete(from, to).setBlockType(from, from, nodeType, nodeAttrs);
64
69
  }
65
70
  });
66
- return definePlugin2(plugin);
67
71
  }
68
- var key = new PluginKey("prosekit-code-block-highlight");
69
- var blockTypes = ["codeBlock"];
70
- function languageExtractor(node) {
71
- return node.attrs.language || "javascript";
72
+ var EnterRule = class {
73
+ constructor(regex, handler) {
74
+ this.regex = regex;
75
+ this.handler = handler;
76
+ }
77
+ };
78
+ var inputRuleFacet = Facet.define({
79
+ convert: (inputs) => {
80
+ return {
81
+ Enter: (state, dispatch, view) => {
82
+ if (!view)
83
+ return false;
84
+ return execRules(view, inputs, dispatch);
85
+ }
86
+ };
87
+ },
88
+ next: keymapFacet
89
+ });
90
+ function execRules(view, rules, dispatch) {
91
+ if (view.composing)
92
+ return false;
93
+ const state = view.state;
94
+ const selection = state.selection;
95
+ if (!isTextSelection(selection))
96
+ return false;
97
+ const $cursor = selection.$cursor;
98
+ if (!$cursor || $cursor.parent.type.spec.code)
99
+ return false;
100
+ const textBefore = $cursor.parent.textBetween(
101
+ Math.max(0, $cursor.parentOffset - MAX_MATCH),
102
+ $cursor.parentOffset,
103
+ null,
104
+ OBJECT_REPLACEMENT_CHARACTER
105
+ );
106
+ for (const rule of rules) {
107
+ rule.regex.lastIndex = 0;
108
+ const match = rule.regex.exec(textBefore);
109
+ const tr = match && rule.handler({
110
+ state,
111
+ from: $cursor.pos - match[0].length,
112
+ to: $cursor.pos,
113
+ match
114
+ });
115
+ if (!tr)
116
+ continue;
117
+ dispatch == null ? void 0 : dispatch(tr);
118
+ return true;
119
+ }
120
+ return false;
72
121
  }
122
+ var MAX_MATCH = 200;
73
123
 
74
124
  // src/code-block/code-block-input-rule.ts
75
- import { defineInputRule, getNodeType } from "@prosekit/core";
76
- import { textblockTypeInputRule } from "@prosekit/pm/inputrules";
77
125
  function defineCodeBlockInputRule() {
78
- return defineInputRule(({ schema }) => {
79
- const nodeType = getNodeType(schema, "codeBlock");
80
- const getAttrs = (match) => {
81
- return { language: match[1] || "" };
82
- };
83
- return textblockTypeInputRule(/^```(\S*)\s$/, nodeType, getAttrs);
126
+ return defineTextBlockInputRule({
127
+ regex: /^```(\S*)\s$/,
128
+ type: "codeBlock",
129
+ attrs: getAttrs
130
+ });
131
+ }
132
+ function defineCodeBlockEnterRule() {
133
+ return defineTextBlockEnterRule({
134
+ regex: /^```(\S*)$/,
135
+ type: "codeBlock",
136
+ attrs: getAttrs
84
137
  });
85
138
  }
139
+ function getAttrs(match) {
140
+ return { language: match[1] || "" };
141
+ }
142
+
143
+ // src/code-block/code-block-keymap.ts
144
+ import { defineKeymap } from "@prosekit/core";
145
+ import { TextSelection } from "@prosekit/pm/state";
146
+
147
+ // src/utils/default-block-at.ts
148
+ function defaultBlockAt(match) {
149
+ for (let i = 0; i < match.edgeCount; i++) {
150
+ const { type } = match.edge(i);
151
+ if (type.isTextblock && !type.hasRequiredAttrs())
152
+ return type;
153
+ }
154
+ return null;
155
+ }
156
+
157
+ // src/code-block/code-block-keymap.ts
158
+ function defineCodeBlockKeymap() {
159
+ return defineKeymap({
160
+ Enter: existCodeBlock
161
+ });
162
+ }
163
+ var existCodeBlock = (state, dispatch) => {
164
+ if (!state.selection.empty) {
165
+ return false;
166
+ }
167
+ const { $head } = state.selection;
168
+ const parent = $head.parent;
169
+ if (parent.isTextblock && parent.type.spec.code && $head.parentOffset === parent.content.size && parent.textContent.endsWith("\n\n")) {
170
+ const grandParent = $head.node(-1);
171
+ const insertIndex = $head.indexAfter(-1);
172
+ const type = defaultBlockAt(grandParent.contentMatchAt(insertIndex));
173
+ if (!type || !grandParent.canReplaceWith(insertIndex, insertIndex, type)) {
174
+ return false;
175
+ }
176
+ if (dispatch) {
177
+ const { tr } = state;
178
+ tr.delete($head.pos - 2, $head.pos);
179
+ const pos = tr.selection.$head.after();
180
+ const node = type.createAndFill();
181
+ if (node) {
182
+ tr.replaceWith(pos, pos, node);
183
+ tr.setSelection(TextSelection.near(tr.doc.resolve(pos), 1));
184
+ dispatch(tr.scrollIntoView());
185
+ }
186
+ }
187
+ return true;
188
+ }
189
+ return false;
190
+ };
191
+
192
+ // src/code-block/code-block-shikiji.ts
193
+ import { createParser } from "prosemirror-highlight/shikiji";
194
+ function createHighlighterLoader() {
195
+ let shikijiImport;
196
+ let highlighter;
197
+ const languages = /* @__PURE__ */ new Set();
198
+ const themes = /* @__PURE__ */ new Set();
199
+ return function highlighterLoader(lang, theme) {
200
+ if (!shikijiImport) {
201
+ shikijiImport = import("shikiji").then(async ({ getHighlighter }) => {
202
+ const fallbackLang = "md";
203
+ highlighter = await getHighlighter({
204
+ langs: [fallbackLang],
205
+ themes: []
206
+ });
207
+ });
208
+ return { promise: shikijiImport };
209
+ }
210
+ if (!highlighter) {
211
+ return { promise: shikijiImport };
212
+ }
213
+ if (!languages.has(lang)) {
214
+ const promise = highlighter.loadLanguage(lang).then(() => {
215
+ languages.add(lang);
216
+ }).catch(() => {
217
+ });
218
+ return { promise };
219
+ }
220
+ if (!themes.has(theme)) {
221
+ const promise = highlighter.loadTheme(theme).then(() => {
222
+ themes.add(theme);
223
+ }).catch(() => {
224
+ });
225
+ return { promise };
226
+ }
227
+ return { highlighter };
228
+ };
229
+ }
230
+ function createLazyParser(theme) {
231
+ let parser;
232
+ const highlighterLoader = createHighlighterLoader();
233
+ return function lazyParser(options) {
234
+ const language = options.language || "";
235
+ const { highlighter, promise } = highlighterLoader(language, theme);
236
+ if (!highlighter) {
237
+ return promise || [];
238
+ }
239
+ if (!parser) {
240
+ parser = createParser(highlighter);
241
+ }
242
+ return parser(options);
243
+ };
244
+ }
245
+ function defineCodeBlockShikiji(options) {
246
+ const theme = (options == null ? void 0 : options.theme) || "github-light";
247
+ const parser = createLazyParser(theme);
248
+ return defineCodeBlockHighlight({ parser });
249
+ }
86
250
 
87
251
  // src/code-block/code-block-spec.ts
88
252
  import { defineNodeSpec } from "@prosekit/core";
@@ -106,37 +270,27 @@ function defineCodeBlockSpec() {
106
270
  ],
107
271
  toDOM(node) {
108
272
  const attrs = node.attrs;
109
- return [
110
- "pre",
111
- // TODO: remove class 'hljs'
112
- { "data-language": attrs.language, class: "hljs" },
113
- ["code", 0]
114
- ];
273
+ return ["pre", { "data-language": attrs.language }, ["code", 0]];
115
274
  }
116
275
  });
117
276
  }
118
277
 
119
278
  // src/code-block/index.ts
120
- function defineCodeBlock(options) {
121
- const extensions = [
279
+ function defineCodeBlock() {
280
+ return union([
122
281
  defineCodeBlockSpec(),
123
282
  defineCodeBlockInputRule(),
283
+ defineCodeBlockEnterRule(),
284
+ defineCodeBlockKeymap(),
124
285
  defineCodeBlockCommands()
125
- ];
126
- const parser = options == null ? void 0 : options.parser;
127
- if (parser) {
128
- extensions.push(defineCodeBlockHighlight({ parser }));
129
- }
130
- const hljs = options == null ? void 0 : options.hljs;
131
- if (hljs) {
132
- extensions.push(defineCodeBlockHighlightDeprecated({ hljs }));
133
- }
134
- return union(extensions);
286
+ ]);
135
287
  }
136
288
  export {
137
289
  defineCodeBlock,
138
290
  defineCodeBlockCommands,
291
+ defineCodeBlockEnterRule,
139
292
  defineCodeBlockHighlight,
140
293
  defineCodeBlockInputRule,
294
+ defineCodeBlockShikiji,
141
295
  defineCodeBlockSpec
142
296
  };
@@ -1,16 +1,17 @@
1
+ import {
2
+ defineTextBlockInputRule
3
+ } from "./chunk-DYFRBXUX.js";
4
+
1
5
  // src/heading/index.ts
2
6
  import {
3
7
  defineCommands,
4
- defineInputRule,
5
8
  defineKeymap,
6
9
  defineNodeSpec,
7
- getNodeType,
8
10
  insertNode,
9
11
  setBlockType,
10
12
  toggleNode,
11
13
  union
12
14
  } from "@prosekit/core";
13
- import { textblockTypeInputRule } from "@prosekit/pm/inputrules";
14
15
  function defineHeadingSpec() {
15
16
  return defineNodeSpec({
16
17
  name: "heading",
@@ -42,13 +43,14 @@ function defineHeadingKeymap() {
42
43
  });
43
44
  }
44
45
  function defineHeadingInputRule() {
45
- return defineInputRule(({ schema }) => {
46
- const nodeSpec = getNodeType(schema, "heading");
47
- return textblockTypeInputRule(/^(#{1,6})\s/, nodeSpec, (match) => {
46
+ return defineTextBlockInputRule({
47
+ regex: /^(#{1,6})\s$/,
48
+ type: "heading",
49
+ attrs: (match) => {
48
50
  var _a, _b;
49
51
  const level = (_b = (_a = match[1]) == null ? void 0 : _a.length) != null ? _b : 1;
50
52
  return { level };
51
- });
53
+ }
52
54
  });
53
55
  }
54
56
  function defineHeadingCommands() {
@@ -0,0 +1,3 @@
1
+ export { defineInputRule } from './_tsup-dts-rollup';
2
+ export { defineTextBlockInputRule } from './_tsup-dts-rollup';
3
+ export { defineWrappingInputRule } from './_tsup-dts-rollup';
@@ -0,0 +1,10 @@
1
+ import {
2
+ defineInputRule,
3
+ defineTextBlockInputRule,
4
+ defineWrappingInputRule
5
+ } from "./chunk-DYFRBXUX.js";
6
+ export {
7
+ defineInputRule,
8
+ defineTextBlockInputRule,
9
+ defineWrappingInputRule
10
+ };
@@ -1,8 +1,11 @@
1
+ import {
2
+ defineInputRule
3
+ } from "./chunk-DYFRBXUX.js";
4
+
1
5
  // src/list/index.ts
2
6
  import {
3
7
  Priority,
4
8
  defineCommands,
5
- defineInputRule,
6
9
  defineKeymap,
7
10
  defineNodeSpec,
8
11
  definePlugin,
@@ -34,7 +37,7 @@ function defineListKeymap() {
34
37
  return defineKeymap(listKeymap);
35
38
  }
36
39
  function defineListInputRules() {
37
- return defineInputRule(() => listInputRules);
40
+ return union(listInputRules.map(defineInputRule));
38
41
  }
39
42
  function defineListCommands() {
40
43
  return defineCommands({
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@prosekit/extensions",
3
3
  "type": "module",
4
- "version": "0.1.6",
4
+ "version": "0.2.1",
5
5
  "private": false,
6
6
  "author": {
7
7
  "name": "ocavue",
@@ -65,6 +65,11 @@
65
65
  "import": "./dist/prosekit-extensions-image.js",
66
66
  "default": "./dist/prosekit-extensions-image.js"
67
67
  },
68
+ "./input-rule": {
69
+ "types": "./dist/prosekit-extensions-input-rule.d.ts",
70
+ "import": "./dist/prosekit-extensions-input-rule.js",
71
+ "default": "./dist/prosekit-extensions-input-rule.js"
72
+ },
68
73
  "./italic": {
69
74
  "types": "./dist/prosekit-extensions-italic.d.ts",
70
75
  "import": "./dist/prosekit-extensions-italic.js",
@@ -106,11 +111,6 @@
106
111
  "import": "./dist/prosekit-extensions-strike.js",
107
112
  "default": "./dist/prosekit-extensions-strike.js"
108
113
  },
109
- "./suggestion": {
110
- "types": "./dist/prosekit-extensions-suggestion.d.ts",
111
- "import": "./dist/prosekit-extensions-suggestion.js",
112
- "default": "./dist/prosekit-extensions-suggestion.js"
113
- },
114
114
  "./underline": {
115
115
  "types": "./dist/prosekit-extensions-underline.d.ts",
116
116
  "import": "./dist/prosekit-extensions-underline.js",
@@ -121,18 +121,25 @@
121
121
  "dist"
122
122
  ],
123
123
  "dependencies": {
124
- "@prosekit/core": "^0.1.8",
124
+ "@prosekit/core": "^0.2.0",
125
125
  "@prosekit/pm": "^0.1.1",
126
- "highlight.js": "^11.9.0",
127
126
  "prosemirror-flat-list": "^0.4.5",
128
- "prosemirror-highlight": "^0.3.2",
129
- "prosemirror-highlightjs": "^0.9.1"
127
+ "prosemirror-highlight": "^0.4.0"
128
+ },
129
+ "peerDependencies": {
130
+ "shikiji": ">= 0.9.0"
131
+ },
132
+ "peerDependenciesMeta": {
133
+ "shikiji": {
134
+ "optional": true
135
+ }
130
136
  },
131
137
  "devDependencies": {
132
138
  "@prosekit/dev": "*",
139
+ "shikiji": "^0.9.16",
133
140
  "tsup": "^8.0.1",
134
141
  "typescript": "^5.3.3",
135
- "vitest": "^1.0.4"
142
+ "vitest": "^1.1.1"
136
143
  },
137
144
  "scripts": {
138
145
  "build:tsup": "tsup",
@@ -165,6 +172,9 @@
165
172
  "image": [
166
173
  "./dist/prosekit-extensions-image.d.ts"
167
174
  ],
175
+ "input-rule": [
176
+ "./dist/prosekit-extensions-input-rule.d.ts"
177
+ ],
168
178
  "italic": [
169
179
  "./dist/prosekit-extensions-italic.d.ts"
170
180
  ],
@@ -186,9 +196,6 @@
186
196
  "strike": [
187
197
  "./dist/prosekit-extensions-strike.d.ts"
188
198
  ],
189
- "suggestion": [
190
- "./dist/prosekit-extensions-suggestion.d.ts"
191
- ],
192
199
  "underline": [
193
200
  "./dist/prosekit-extensions-underline.d.ts"
194
201
  ]
@@ -1,3 +0,0 @@
1
- export { defineSuggestion } from './_tsup-dts-rollup';
2
- export { PredictionRule } from './_tsup-dts-rollup';
3
- export { SuggestionOptions } from './_tsup-dts-rollup';
@@ -1,161 +0,0 @@
1
- // src/suggestion/index.ts
2
- import { definePlugin } from "@prosekit/core";
3
-
4
- // src/suggestion/plugin.ts
5
- import { ProseKitError } from "@prosekit/core";
6
- import { Plugin, PluginKey } from "@prosekit/pm/state";
7
- import { Decoration, DecorationSet } from "@prosekit/pm/view";
8
-
9
- // src/suggestion/is-valid.ts
10
- import "@prosekit/pm/model";
11
- import "@prosekit/pm/state";
12
- function defaultIsValid({ state }) {
13
- return state.selection.empty && !isInsideCode(state.selection.$from);
14
- }
15
- function isInsideCode($pos) {
16
- for (let d = $pos.depth; d > 0; d--) {
17
- if ($pos.node(d).type.spec.code) {
18
- return true;
19
- }
20
- }
21
- return $pos.marks().some((mark) => mark.type.name === "code");
22
- }
23
-
24
- // src/suggestion/plugin.ts
25
- var pluginKey = new PluginKey("prosemirror-prediction");
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
- function createPredictionPlugin(options) {
36
- if (options.rules.length === 0) {
37
- throw new ProseKitError(
38
- "You can't create a prediction plugin without rules"
39
- );
40
- }
41
- const { onMatch, onDeactivate, isValid = defaultIsValid } = options;
42
- return new Plugin({
43
- key: pluginKey,
44
- state: {
45
- init: () => {
46
- return { active: false, ignore: null, matching: null };
47
- },
48
- apply: (tr, prevValue, oldState, newState) => {
49
- var _a;
50
- const meta = getTrMeta(tr);
51
- if (!tr.docChanged && oldState.selection.eq(newState.selection) && !meta) {
52
- return prevValue;
53
- }
54
- if (meta) {
55
- return meta;
56
- }
57
- if (!isValid({ state: newState })) {
58
- return { active: false, ignore: null, matching: null };
59
- }
60
- const nextValue = calcPluginState(newState, options.rules);
61
- if (nextValue.active && prevValue.ignore != null && ((_a = nextValue.matching) == null ? void 0 : _a.from) === prevValue.ignore) {
62
- return prevValue;
63
- }
64
- return nextValue;
65
- }
66
- },
67
- view: () => ({
68
- update: (view, prevState) => {
69
- const prevPluginState = getPluginState(prevState);
70
- const currPluginState = getPluginState(view.state);
71
- if ((currPluginState == null ? void 0 : currPluginState.active) && currPluginState.matching && currPluginState.matching.from !== currPluginState.ignore) {
72
- const { from, to } = currPluginState.matching;
73
- const dismiss = () => {
74
- view.dispatch(
75
- setTrMeta(view.state.tr, {
76
- active: false,
77
- ignore: from,
78
- matching: null
79
- })
80
- );
81
- };
82
- const textContent = view.state.doc.textBetween(from, to, "\uFFFC");
83
- const deleteMatch = () => {
84
- if (view.state.doc.textBetween(from, to, "\uFFFC") === textContent) {
85
- view.dispatch(view.state.tr.delete(from, to));
86
- }
87
- };
88
- onMatch({
89
- rule: currPluginState.matching.rule,
90
- match: currPluginState.matching.match,
91
- matchAfter: currPluginState.matching.matchAfter,
92
- state: view.state,
93
- dismiss,
94
- deleteMatch
95
- });
96
- } else if (prevPluginState == null ? void 0 : prevPluginState.active) {
97
- onDeactivate();
98
- }
99
- }
100
- }),
101
- props: {
102
- decorations: (state) => {
103
- const pluginState = getPluginState(state);
104
- if ((pluginState == null ? void 0 : pluginState.active) && pluginState.matching) {
105
- const { from, to } = pluginState.matching;
106
- const deco = Decoration.inline(from, to, {
107
- class: "prosemirror-prediction-match"
108
- });
109
- return DecorationSet.create(state.doc, [deco]);
110
- }
111
- return null;
112
- }
113
- }
114
- });
115
- }
116
- function calcPluginState(state, rules) {
117
- const { $anchor } = state.selection;
118
- const matchAfter = rules.some((rule) => rule.matchAfter);
119
- const parentOffset = $anchor.parentOffset;
120
- const textBefore = $anchor.parent.textBetween(
121
- Math.max(0, parentOffset - MAX_MATCH),
122
- parentOffset,
123
- null,
124
- "\uFFFC"
125
- );
126
- const textAfter = matchAfter ? $anchor.parent.textBetween(
127
- parentOffset,
128
- Math.min(parentOffset + MAX_MATCH, $anchor.parent.content.size),
129
- null
130
- ) : "";
131
- for (const rule of rules) {
132
- const match = textBefore.match(rule.match);
133
- const matchAfter2 = rule.matchAfter ? textAfter.match(rule.matchAfter) : null;
134
- if ((match == null ? void 0 : match.index) != null) {
135
- const from = $anchor.pos - textBefore.length + match.index;
136
- const to = $anchor.pos + (matchAfter2 ? matchAfter2[0].length : 0);
137
- return {
138
- active: true,
139
- ignore: null,
140
- matching: {
141
- rule,
142
- from,
143
- to,
144
- match,
145
- matchAfter: matchAfter2
146
- }
147
- };
148
- }
149
- }
150
- return { active: false };
151
- }
152
- var MAX_MATCH = 200;
153
-
154
- // src/suggestion/index.ts
155
- function defineSuggestion(options) {
156
- const plugin = createPredictionPlugin(options);
157
- return definePlugin(plugin);
158
- }
159
- export {
160
- defineSuggestion
161
- };