mirror of
https://github.com/outline/outline.git
synced 2026-06-13 11:25:03 +03:00
fix: Prevent block menu trigger when marked (#12515)
* Prevent block menu trigger when marked * PR feedback
This commit is contained in:
@@ -17,6 +17,7 @@ export default class BlockMenuExtension extends Suggestion {
|
||||
allowSpaces: false,
|
||||
requireSearchTerm: false,
|
||||
enabledInCode: false,
|
||||
enabledInMarks: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -87,7 +88,8 @@ export default class BlockMenuExtension extends Suggestion {
|
||||
condition: ({ node, $start, state }) =>
|
||||
$start.depth === 1 &&
|
||||
state.selection.$from.pos === $start.pos + node.content.size &&
|
||||
node.textContent === "/",
|
||||
node.textContent === "/" &&
|
||||
node.firstChild?.marks.length === 0,
|
||||
text: ` ${t("Keep typing to filter")}…`,
|
||||
},
|
||||
]),
|
||||
|
||||
@@ -4,7 +4,10 @@ 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 {
|
||||
isTriggerMarked,
|
||||
SuggestionsMenuPlugin,
|
||||
} from "@shared/editor/plugins/SuggestionsMenuPlugin";
|
||||
import { isInCode } from "@shared/editor/queries/isInCode";
|
||||
|
||||
/**
|
||||
@@ -14,6 +17,12 @@ import { isInCode } from "@shared/editor/queries/isInCode";
|
||||
export type SuggestionOptions = {
|
||||
/** Whether the suggestion menu is allowed to open inside code blocks or inline code. */
|
||||
enabledInCode: boolean;
|
||||
/**
|
||||
* Whether the suggestion menu may open when the trigger character carries a
|
||||
* mark (e.g. bold, italic, link). Defaults to true – disable for menus where
|
||||
* the trigger is only meaningful as plain text, such as the block menu.
|
||||
*/
|
||||
enabledInMarks?: boolean;
|
||||
/** Character (or list of characters) that opens the suggestion menu. */
|
||||
trigger: string | string[];
|
||||
/** Whether spaces are allowed inside the search term. */
|
||||
@@ -45,7 +54,18 @@ export default class Suggestion<
|
||||
}
|
||||
|
||||
get plugins(): Plugin[] {
|
||||
return [new SuggestionsMenuPlugin(this.state, this.openRegex)];
|
||||
return [
|
||||
new SuggestionsMenuPlugin(
|
||||
this.state,
|
||||
this.openRegex,
|
||||
this.enabledInMarks
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/** Whether the menu may open when the trigger character carries a mark. */
|
||||
protected get enabledInMarks(): boolean {
|
||||
return this.options.enabledInMarks ?? true;
|
||||
}
|
||||
|
||||
keys() {
|
||||
@@ -62,21 +82,29 @@ export default class Suggestion<
|
||||
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;
|
||||
action(
|
||||
(
|
||||
state: EditorState,
|
||||
match: RegExpMatchArray,
|
||||
_start: number,
|
||||
end: number
|
||||
) => {
|
||||
const { parent } = state.selection.$from;
|
||||
if (
|
||||
match &&
|
||||
(parent.type.name === "paragraph" ||
|
||||
parent.type.name === "heading") &&
|
||||
(!isInCode(state) || this.options.enabledInCode) &&
|
||||
(this.enabledInMarks || !isTriggerMarked(state, end, match))
|
||||
) {
|
||||
if (match[0].length <= 2) {
|
||||
this.state.open = true;
|
||||
}
|
||||
this.state.query = match[1];
|
||||
}
|
||||
this.state.query = match[1];
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
})
|
||||
)
|
||||
),
|
||||
];
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { action } from "mobx";
|
||||
import type { EditorState } from "prosemirror-state";
|
||||
import { Plugin } from "prosemirror-state";
|
||||
import type { EditorView } from "prosemirror-view";
|
||||
import { getMarksBetween } from "@shared/editor/queries/getMarksBetween";
|
||||
|
||||
const MAX_MATCH = 500;
|
||||
|
||||
@@ -10,8 +11,35 @@ type ExtensionState = {
|
||||
query: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine whether the trigger character of a suggestion match carries any
|
||||
* marks (e.g. bold, code, link).
|
||||
*
|
||||
* @param state The editor state.
|
||||
* @param cursorPos The document position of the cursor (end of the match).
|
||||
* @param match The regex match where group 1 is the search term.
|
||||
* @returns True if the trigger character has one or more marks applied.
|
||||
*/
|
||||
export function isTriggerMarked(
|
||||
state: EditorState,
|
||||
cursorPos: number,
|
||||
match: RegExpMatchArray
|
||||
): boolean {
|
||||
const queryLength = match[1]?.length ?? 0;
|
||||
const triggerEnd = cursorPos - queryLength;
|
||||
const triggerStart = triggerEnd - 1;
|
||||
if (triggerStart < 0) {
|
||||
return false;
|
||||
}
|
||||
return getMarksBetween(triggerStart, triggerEnd, state).length > 0;
|
||||
}
|
||||
|
||||
export class SuggestionsMenuPlugin extends Plugin {
|
||||
constructor(extensionState: ExtensionState, openRegex: RegExp) {
|
||||
constructor(
|
||||
extensionState: ExtensionState,
|
||||
openRegex: RegExp,
|
||||
enabledInMarks: boolean
|
||||
) {
|
||||
super({
|
||||
props: {
|
||||
handleDOMEvents: {
|
||||
@@ -34,7 +62,10 @@ export class SuggestionsMenuPlugin extends Plugin {
|
||||
);
|
||||
const match = openRegex.exec(textBefore);
|
||||
action(() => {
|
||||
if (match) {
|
||||
if (
|
||||
match &&
|
||||
(enabledInMarks || !isTriggerMarked(state, fromPos, match))
|
||||
) {
|
||||
if (match[0].length <= 2) {
|
||||
extensionState.open = true;
|
||||
}
|
||||
@@ -59,8 +90,11 @@ export class SuggestionsMenuPlugin extends Plugin {
|
||||
fromPos,
|
||||
fromPos,
|
||||
openRegex,
|
||||
action((_, match) => {
|
||||
if (match) {
|
||||
action((state, match) => {
|
||||
if (
|
||||
match &&
|
||||
(enabledInMarks || !isTriggerMarked(state, fromPos, match))
|
||||
) {
|
||||
extensionState.query = match[1];
|
||||
} else {
|
||||
extensionState.open = false;
|
||||
@@ -89,8 +123,11 @@ export class SuggestionsMenuPlugin extends Plugin {
|
||||
fromPos,
|
||||
fromPos,
|
||||
openRegex,
|
||||
action((_, match) => {
|
||||
if (match) {
|
||||
action((state, match) => {
|
||||
if (
|
||||
match &&
|
||||
(enabledInMarks || !isTriggerMarked(state, fromPos, match))
|
||||
) {
|
||||
if (match[0].length <= 2) {
|
||||
extensionState.open = true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user