Compare commits

...

1 Commits

Author SHA1 Message Date
Tom Moor 355dea0a34 fix: Various bugs in the suggestion menu logic 2025-01-19 18:20:42 -05:00
5 changed files with 63 additions and 62 deletions
+4 -3
View File
@@ -12,9 +12,10 @@ import BlockMenu from "../components/BlockMenu";
export default class BlockMenuExtension extends Suggestion {
get defaultOptions() {
return {
// ported from https://github.com/tc39/proposal-regexp-unicode-property-escapes#unicode-aware-version-of-w
openRegex: /(?:^|\s|\()\/([\p{L}\p{M}\d]+)?$/u,
closeRegex: /(?:^|\s|\()\/(([\p{L}\p{M}\d]*\s+)|(\s+[\p{L}\p{M}\d]+))$/u,
trigger: "/",
allowSpaces: false,
requireSearchTerm: false,
enabledInCode: false,
};
}
+4 -5
View File
@@ -19,11 +19,10 @@ export default class EmojiMenuExtension extends Suggestion {
: false;
return {
openRegex: new RegExp(
`(?:^|\\s|\\():([0-9a-zA-Z_+-]+)${languageIsUsingColon ? "" : "?"}$`
),
closeRegex:
/(?:^|\s|\():(([0-9a-zA-Z_+-]*\s+)|(\s+[0-9a-zA-Z_+-]+)|[^0-9a-zA-Z_+-]+)$/,
trigger: ":",
allowSpaces: false,
requireSearchTerm: languageIsUsingColon,
enabledInCode: false,
};
}
+4 -3
View File
@@ -7,9 +7,10 @@ import MentionMenu from "../components/MentionMenu";
export default class MentionMenuExtension extends Suggestion {
get defaultOptions() {
return {
// ported from https://github.com/tc39/proposal-regexp-unicode-property-escapes#unicode-aware-version-of-w
openRegex: /(?:^|\s|\()@([\p{L}\p{M}\d\s{1}@\.]+)?$/u,
closeRegex: /(?:^|\s|\()@(([\p{L}\p{M}\d]*\s{2})|(\s+[\p{L}\p{M}\d]+))$/u,
trigger: "@",
allowSpaces: true,
requireSearchTerm: false,
enabledInCode: false,
};
}
+38 -33
View File
@@ -1,3 +1,4 @@
import escapeRegExp from "lodash/escapeRegExp";
import { action, observable } from "mobx";
import { InputRule } from "prosemirror-inputrules";
import { NodeType, Schema } from "prosemirror-model";
@@ -6,35 +7,37 @@ import Extension from "@shared/editor/lib/Extension";
import { SuggestionsMenuPlugin } from "@shared/editor/plugins/Suggestions";
import { isInCode } from "@shared/editor/queries/isInCode";
type Options = {
enabledInCode: boolean;
trigger: string;
allowSpaces: boolean;
requireSearchTerm: boolean;
};
export default class Suggestion extends Extension {
state: {
open: boolean;
query: string;
} = observable({
open: false,
query: "",
});
constructor(options: Options) {
super(options);
this.openRegex = new RegExp(
`(?:^|\\s|\\()${escapeRegExp(this.options.trigger)}(${`[\\p{L}\\p{M}\\d${
this.options.allowSpaces ? "\\s{1}" : ""
}\\.]+`})${this.options.requireSearchTerm ? "" : "?"}$`,
"u"
);
}
get plugins(): Plugin[] {
return [new SuggestionsMenuPlugin(this.options, this.state)];
return [
new SuggestionsMenuPlugin(this.options, this.state, this.openRegex),
];
}
keys() {
return {
Backspace: action((state: EditorState) => {
const { $from } = state.selection;
const textBefore = $from.parent.textBetween(
Math.max(0, $from.parentOffset - 500), // 500 = max match
Math.max(0, $from.parentOffset - 1), // 1 = account for deleted character
null,
"\ufffc"
);
if (this.options.openRegex.test(textBefore)) {
return false;
Space: action(() => {
if (this.state.open && !this.options.allowSpaces) {
this.state.open = false;
}
this.state.open = false;
return false;
}),
};
@@ -42,7 +45,7 @@ export default class Suggestion extends Extension {
inputRules = (_options: { type: NodeType; schema: Schema }) => [
new InputRule(
this.options.openRegex,
this.openRegex,
action((state: EditorState, match: RegExpMatchArray) => {
const { parent } = state.selection.$from;
if (
@@ -51,21 +54,23 @@ export default class Suggestion extends Extension {
parent.type.name === "heading") &&
(!isInCode(state) || this.options.enabledInCode)
) {
this.state.open = true;
if (match[0].length <= 2) {
this.state.open = true;
}
this.state.query = match[1];
}
return null;
})
),
new InputRule(
this.options.closeRegex,
action((_: EditorState, match: RegExpMatchArray) => {
if (match) {
this.state.open = false;
this.state.query = "";
}
return null;
})
),
];
protected openRegex: RegExp;
protected state: {
open: boolean;
query: string;
} = observable({
open: false,
query: "",
});
}
+13 -18
View File
@@ -7,7 +7,10 @@ const MAX_MATCH = 500;
type Options = {
openRegex: RegExp;
closeRegex: RegExp;
enabledInCode: true;
enabledInCode: boolean;
trigger: string;
allowSpaces: boolean;
requireSearchTerm: boolean;
};
type ExtensionState = {
@@ -16,7 +19,11 @@ type ExtensionState = {
};
export class SuggestionsMenuPlugin extends Plugin {
constructor(options: Options, extensionState: ExtensionState) {
constructor(
options: Options,
extensionState: ExtensionState,
openRegex: RegExp
) {
super({
props: {
handleKeyDown: (view, event) => {
@@ -32,14 +39,12 @@ export class SuggestionsMenuPlugin extends Plugin {
view,
fromPos,
fromPos,
options.openRegex,
openRegex,
action((_, match) => {
if (match) {
extensionState.open = true;
extensionState.query = match[1];
} else {
extensionState.open = false;
extensionState.query = "";
}
return null;
})
@@ -47,25 +52,15 @@ export class SuggestionsMenuPlugin extends Plugin {
});
}
const { pos } = view.state.selection.$from;
// If the query is active and we're navigating the block menu then
// just ignore the key events in the editor itself until we're done
// If the menu is open then just ignore the key events in the editor
// itself until we're done.
if (
event.key === "Enter" ||
event.key === "ArrowUp" ||
event.key === "ArrowDown" ||
event.key === "Tab"
) {
return this.execute(
view,
pos,
pos,
options.openRegex,
(state, match) =>
// just tell Prosemirror we handled it and not to do anything
match ? true : null
);
return extensionState.open;
}
return false;