mirror of
https://github.com/outline/outline.git
synced 2026-06-13 11:25:03 +03:00
adbffc0734
* chore: clear mechanical lint warnings Drops 44 oxlint warnings (559 → 515) by fixing easy mechanical rules across the codebase: no-useless-escape, no-duplicate-type-constituents, no-redundant-type-constituents, no-unused-expressions, no-meaningless-void-operator, require-array-sort-compare, await-thenable. * chore: drop callback parameter from useCallback deps The `open` argument is a parameter of the callback, not a closed-over variable, so it doesn't belong in the deps array. * chore: promote cleared lint rules to errors Promotes the rules cleared in this PR from warn to error so future violations fail the lint: - no-unused-expressions - typescript/await-thenable - typescript/no-duplicate-type-constituents - typescript/no-meaningless-void-operator - typescript/require-array-sort-compare Removes the override that suppressed no-useless-escape on source files (the global rule is already error) and fixes the 21 escape violations that this exposed in regex character classes and template literals. * chore: address PR review feedback - usePinnedDocuments: simplify UrlId to plain string instead of the intersection trick. - PlantUML embed: move - to end of character class so it's a literal hyphen rather than a range operator. - checkboxes: type token params as Token | undefined to match the actual call sites that pass tokens[index - 2] etc.
88 lines
2.4 KiB
TypeScript
88 lines
2.4 KiB
TypeScript
import escapeRegExp from "lodash/escapeRegExp";
|
||
import { action, observable } from "mobx";
|
||
import { InputRule } from "prosemirror-inputrules";
|
||
import type { NodeType, Schema } from "prosemirror-model";
|
||
import type { EditorState, Plugin } from "prosemirror-state";
|
||
import Extension from "@shared/editor/lib/Extension";
|
||
import { SuggestionsMenuPlugin } from "@shared/editor/plugins/SuggestionsMenuPlugin";
|
||
import { isInCode } from "@shared/editor/queries/isInCode";
|
||
|
||
type Options = {
|
||
enabledInCode: boolean;
|
||
trigger: string | string[];
|
||
allowSpaces: boolean;
|
||
requireSearchTerm: boolean;
|
||
};
|
||
|
||
export default class Suggestion extends Extension {
|
||
constructor(options: Options) {
|
||
super(options);
|
||
|
||
const triggers = Array.isArray(this.options.trigger)
|
||
? this.options.trigger
|
||
: [this.options.trigger];
|
||
const triggerPattern =
|
||
triggers.length === 1
|
||
? escapeRegExp(triggers[0])
|
||
: `(?:${triggers.map(escapeRegExp).join("|")})`;
|
||
|
||
this.openRegex = new RegExp(
|
||
`(?:^|\\s|\\(|[\\p{Script=Han}\\p{Script=Hiragana}\\p{Script=Katakana}\\p{Script=Hangul}])${triggerPattern}(${`[\\p{L}/\\p{M}\\d${
|
||
this.options.allowSpaces ? "\\s{1}" : ""
|
||
}\\.\\-–_]+`})${this.options.requireSearchTerm ? "" : "?"}$`,
|
||
"u"
|
||
);
|
||
}
|
||
|
||
get plugins(): Plugin[] {
|
||
return [new SuggestionsMenuPlugin(this.state, this.openRegex)];
|
||
}
|
||
|
||
keys() {
|
||
return {
|
||
Space: action(() => {
|
||
if (this.state.open && !this.options.allowSpaces) {
|
||
this.state.open = false;
|
||
}
|
||
return false;
|
||
}),
|
||
};
|
||
}
|
||
|
||
inputRules = (_options: { type: NodeType; schema: Schema }) => [
|
||
new InputRule(
|
||
this.openRegex,
|
||
action((state: EditorState, match: RegExpMatchArray) => {
|
||
const { parent } = state.selection.$from;
|
||
if (
|
||
match &&
|
||
(parent.type.name === "paragraph" ||
|
||
parent.type.name === "heading") &&
|
||
(!isInCode(state) || this.options.enabledInCode)
|
||
) {
|
||
if (match[0].length <= 2) {
|
||
this.state.open = true;
|
||
}
|
||
this.state.query = match[1];
|
||
}
|
||
return null;
|
||
})
|
||
),
|
||
];
|
||
|
||
protected openRegex: RegExp;
|
||
|
||
protected state: {
|
||
open: boolean;
|
||
query: string;
|
||
} = observable({
|
||
open: false,
|
||
query: "",
|
||
});
|
||
|
||
/** Whether the suggestion menu is currently open. */
|
||
get isOpen(): boolean {
|
||
return this.state.open;
|
||
}
|
||
}
|