[WIKI-547] fix: update find suggestion logic for emoji extension (#7411)

* fix: update find suggestion logic

* refactor: remove logs

* refactor : make logic simpler

* feat: check for one char to show suggestion

* refactor : import types from extension

* refactor: add early return

* refactor : put custom suggestion in helper

* fix : char

* fix: types
This commit is contained in:
Vipin Chaudhary
2025-07-17 13:07:12 +05:30
committed by GitHub
parent ec0ef98c1b
commit 6bb79df0eb
4 changed files with 84 additions and 1 deletions

View File

@@ -17,6 +17,7 @@ export interface EmojiListProps {
items: EmojiItem[];
command: (item: { name: string }) => void;
editor: Editor;
query: string;
}
export interface EmojiListRef {
@@ -43,7 +44,7 @@ const updatePosition = (editor: Editor, element: HTMLElement) => {
};
export const EmojiList = forwardRef<EmojiListRef, EmojiListProps>((props, ref) => {
const { items, command, editor } = props;
const { items, command, editor, query } = props;
const [selectedIndex, setSelectedIndex] = useState<number>(0);
const [isVisible, setIsVisible] = useState(false);
const containerRef = useRef<HTMLDivElement>(null);
@@ -141,6 +142,10 @@ export const EmojiList = forwardRef<EmojiListRef, EmojiListProps>((props, ref) =
[handleKeyDown]
);
if (query.length <= 0) {
return null;
}
return (
<div
ref={containerRef}

View File

@@ -15,6 +15,8 @@ import { Plugin, PluginKey, Transaction } from "@tiptap/pm/state";
import Suggestion, { SuggestionOptions } from "@tiptap/suggestion";
import emojiRegex from "emoji-regex";
import { isEmojiSupported } from "is-emoji-supported";
// helpers
import { customFindSuggestionMatch } from "@/helpers/find-suggestion-match";
declare module "@tiptap/core" {
interface Commands<ReturnType> {
@@ -343,6 +345,7 @@ export const Emoji = Node.create<EmojiOptions, EmojiStorage>({
return [
Suggestion({
editor: this.editor,
findSuggestionMatch: customFindSuggestionMatch,
...this.options.suggestion,
}),

View File

@@ -64,6 +64,7 @@ const emojiSuggestion: EmojiOptions["suggestion"] = {
items: props.items,
command: props.command,
editor: props.editor,
query: props.query,
},
editor: props.editor,
});
@@ -81,6 +82,7 @@ const emojiSuggestion: EmojiOptions["suggestion"] = {
items: props.items,
command: props.command,
editor: props.editor,
query: props.query,
});
},

View File

@@ -0,0 +1,73 @@
import { escapeForRegEx } from "@tiptap/core";
import { Trigger, SuggestionMatch } from "@tiptap/suggestion";
export function customFindSuggestionMatch(config: Trigger): SuggestionMatch | null {
const { char, allowSpaces: allowSpacesOption, allowToIncludeChar, allowedPrefixes, startOfLine, $position } = config;
const allowSpaces = allowSpacesOption && !allowToIncludeChar;
const escapedChar = escapeForRegEx(char);
const suffix = new RegExp(`\\s${escapedChar}$`);
const prefix = startOfLine ? "^" : "";
const finalEscapedChar = allowToIncludeChar ? "" : escapedChar;
const regexp = allowSpaces
? new RegExp(`${prefix}${escapedChar}.*?(?=\\s${finalEscapedChar}|$)`, "gm")
: new RegExp(`${prefix}(?:^)?${escapedChar}[^\\s${finalEscapedChar}]*`, "gm");
// Instead of just looking at nodeBefore.text, we need to extract text from the current paragraph
// to properly handle text with decorators like bold, italic, etc.
const currentParagraph = $position.parent;
if (!currentParagraph.isTextblock) {
return null;
}
// Get the start position of the current paragraph
const paragraphStart = $position.start();
// Extract text content using textBetween which handles text across different nodes/marks
const text = $position.doc.textBetween(paragraphStart, $position.pos, "\0", "\0");
if (!text) {
return null;
}
const textFrom = paragraphStart;
const match = Array.from(text.matchAll(regexp)).pop();
if (!match || match.input === undefined || match.index === undefined) {
return null;
}
// JavaScript doesn't have lookbehinds. This hacks a check that first character
// is a space or the start of the line
const matchPrefix = match.input.slice(Math.max(0, match.index - 1), match.index);
const matchPrefixIsAllowed = new RegExp(`^[${allowedPrefixes?.join("")}]?$`).test(matchPrefix);
if (allowedPrefixes && allowedPrefixes.length > 0 && !matchPrefixIsAllowed) {
return null;
}
// The absolute position of the match in the document
const from = textFrom + match.index;
let to = from + match[0].length;
// Edge case handling; if spaces are allowed and we're directly in between
// two triggers
if (allowSpaces && suffix.test(text.slice(to - 1, to + 1))) {
match[0] += " ";
to += 1;
}
// If the $position is located within the matched substring, return that range
if (from < $position.pos && to >= $position.pos) {
return {
range: {
from,
to,
},
query: match[0].slice(char.length),
text: match[0],
};
}
return null;
}