mirror of
https://github.com/outline/outline.git
synced 2026-06-13 11:25:03 +03:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 355dea0a34 |
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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: "",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user