Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 26 additions & 17 deletions packages/core/src/extensions/SuggestionMenu/SuggestionMenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import { EditorState, Plugin, PluginKey } from "prosemirror-state";
import { Decoration, DecorationSet, EditorView } from "prosemirror-view";

import { trackPosition } from "../../api/positionMapping.js";
import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
import {
createExtension,
createStore,
} from "../../editor/BlockNoteExtension.js";
import { UiElementPosition } from "../../extensions-shared/UiElementPosition.js";
import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";

const findBlock = findParentNode((node) => node.type.name === "blockContainer");

Expand Down Expand Up @@ -129,7 +129,7 @@ class SuggestionMenuView {
.deleteRange({
from:
this.pluginState.queryStartPos() -
(this.pluginState.deleteTriggerCharacter
(this.pluginState.userDidTypeTriggerCharacter
? this.pluginState.triggerCharacter!.length
: 0),
to: this.editor.transact((tr) => tr.selection.from),
Expand All @@ -141,15 +141,17 @@ class SuggestionMenuView {
type SuggestionPluginState =
| {
triggerCharacter: string;
deleteTriggerCharacter: boolean;
userDidTypeTriggerCharacter: boolean;
queryStartPos: () => number;
query: string;
decorationId: string;
ignoreQueryLength?: boolean;
}
| undefined;

const suggestionMenuPluginKey = new PluginKey("SuggestionMenuPlugin");
const suggestionMenuPluginKey = new PluginKey<SuggestionPluginState>(
"SuggestionMenuPlugin",
);

/**
* A ProseMirror plugin for suggestions, designed to make '/'-commands possible as well as mentions.
Expand Down Expand Up @@ -187,8 +189,8 @@ export const SuggestionMenu = createExtension(({ editor }) => {
},
openSuggestionMenu: (
triggerCharacter: string,
pluginState?: {
deleteTriggerCharacter?: boolean;
opts?: {
insertTriggerCharacter?: boolean;
ignoreQueryLength?: boolean;
},
) => {
Expand All @@ -199,13 +201,13 @@ export const SuggestionMenu = createExtension(({ editor }) => {
editor.focus();

editor.transact((tr) => {
if (pluginState?.deleteTriggerCharacter) {
if (opts?.insertTriggerCharacter) {
tr.insertText(triggerCharacter);
}
tr.scrollIntoView().setMeta(suggestionMenuPluginKey, {
triggerCharacter: triggerCharacter,
deleteTriggerCharacter: pluginState?.deleteTriggerCharacter || false,
ignoreQueryLength: pluginState?.ignoreQueryLength || false,
userDidTypeTriggerCharacter: opts?.insertTriggerCharacter || false,
ignoreQueryLength: opts?.ignoreQueryLength || false,
});
});
},
Expand Down Expand Up @@ -247,7 +249,7 @@ export const SuggestionMenu = createExtension(({ editor }) => {
// or null if it should be hidden.
const suggestionPluginTransactionMeta: {
triggerCharacter: string;
deleteTriggerCharacter?: boolean;
userDidTypeTriggerCharacter?: boolean;
ignoreQueryLength?: boolean;
} | null = transaction.getMeta(suggestionMenuPluginKey);

Expand All @@ -259,22 +261,29 @@ export const SuggestionMenu = createExtension(({ editor }) => {
// Close the previous menu if it exists
view?.closeMenu();
}

const userDidTypeTriggerCharacter =
suggestionPluginTransactionMeta.userDidTypeTriggerCharacter !==
false;

const trackedPosition = trackPosition(
editor,
newState.selection.from -
// Need to account for the trigger char that was inserted, so we offset the position by the length of the trigger character.
suggestionPluginTransactionMeta.triggerCharacter.length,
(userDidTypeTriggerCharacter
? suggestionPluginTransactionMeta.triggerCharacter.length
: 0),
);
return {
triggerCharacter:
suggestionPluginTransactionMeta.triggerCharacter,
deleteTriggerCharacter:
suggestionPluginTransactionMeta.deleteTriggerCharacter !==
false,
userDidTypeTriggerCharacter,
// When reading the queryStartPos, we offset the result by the length of the trigger character, to make it easy on the caller
queryStartPos: () =>
trackedPosition() +
suggestionPluginTransactionMeta.triggerCharacter.length,
(userDidTypeTriggerCharacter
? suggestionPluginTransactionMeta.triggerCharacter.length
: 0),
query: "",
decorationId: `id_${Math.floor(Math.random() * 0xffffffff)}`,
ignoreQueryLength:
Expand Down Expand Up @@ -358,9 +367,9 @@ export const SuggestionMenu = createExtension(({ editor }) => {
return null;
}

// If the menu was opened programmatically by another extension, it may not use a trigger character. In this
// If the menu was opened programmatically by another extension, it may not use an actual trigger character in the editor. In this
// case, the decoration is set on the whole block instead, as the decoration range would otherwise be empty.
if (!suggestionPluginState.deleteTriggerCharacter) {
if (!suggestionPluginState.userDidTypeTriggerCharacter) {
const blockNode = findBlock(state.selection);
if (blockNode) {
return DecorationSet.create(state.doc, [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ export function getDefaultSlashMenuItems<
items.push({
onItemClick: () => {
editor.getExtension(SuggestionMenu)?.openSuggestionMenu(":", {
deleteTriggerCharacter: true,
insertTriggerCharacter: true,
ignoreQueryLength: true,
});
},
Expand Down
Loading