[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:
parent
ec0ef98c1b
commit
6bb79df0eb
4 changed files with 84 additions and 1 deletions
|
|
@ -17,6 +17,7 @@ export interface EmojiListProps {
|
||||||
items: EmojiItem[];
|
items: EmojiItem[];
|
||||||
command: (item: { name: string }) => void;
|
command: (item: { name: string }) => void;
|
||||||
editor: Editor;
|
editor: Editor;
|
||||||
|
query: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EmojiListRef {
|
export interface EmojiListRef {
|
||||||
|
|
@ -43,7 +44,7 @@ const updatePosition = (editor: Editor, element: HTMLElement) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const EmojiList = forwardRef<EmojiListRef, EmojiListProps>((props, ref) => {
|
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 [selectedIndex, setSelectedIndex] = useState<number>(0);
|
||||||
const [isVisible, setIsVisible] = useState(false);
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
@ -141,6 +142,10 @@ export const EmojiList = forwardRef<EmojiListRef, EmojiListProps>((props, ref) =
|
||||||
[handleKeyDown]
|
[handleKeyDown]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (query.length <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ import { Plugin, PluginKey, Transaction } from "@tiptap/pm/state";
|
||||||
import Suggestion, { SuggestionOptions } from "@tiptap/suggestion";
|
import Suggestion, { SuggestionOptions } from "@tiptap/suggestion";
|
||||||
import emojiRegex from "emoji-regex";
|
import emojiRegex from "emoji-regex";
|
||||||
import { isEmojiSupported } from "is-emoji-supported";
|
import { isEmojiSupported } from "is-emoji-supported";
|
||||||
|
// helpers
|
||||||
|
import { customFindSuggestionMatch } from "@/helpers/find-suggestion-match";
|
||||||
|
|
||||||
declare module "@tiptap/core" {
|
declare module "@tiptap/core" {
|
||||||
interface Commands<ReturnType> {
|
interface Commands<ReturnType> {
|
||||||
|
|
@ -343,6 +345,7 @@ export const Emoji = Node.create<EmojiOptions, EmojiStorage>({
|
||||||
return [
|
return [
|
||||||
Suggestion({
|
Suggestion({
|
||||||
editor: this.editor,
|
editor: this.editor,
|
||||||
|
findSuggestionMatch: customFindSuggestionMatch,
|
||||||
...this.options.suggestion,
|
...this.options.suggestion,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,7 @@ const emojiSuggestion: EmojiOptions["suggestion"] = {
|
||||||
items: props.items,
|
items: props.items,
|
||||||
command: props.command,
|
command: props.command,
|
||||||
editor: props.editor,
|
editor: props.editor,
|
||||||
|
query: props.query,
|
||||||
},
|
},
|
||||||
editor: props.editor,
|
editor: props.editor,
|
||||||
});
|
});
|
||||||
|
|
@ -81,6 +82,7 @@ const emojiSuggestion: EmojiOptions["suggestion"] = {
|
||||||
items: props.items,
|
items: props.items,
|
||||||
command: props.command,
|
command: props.command,
|
||||||
editor: props.editor,
|
editor: props.editor,
|
||||||
|
query: props.query,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
73
packages/editor/src/core/helpers/find-suggestion-match.ts
Normal file
73
packages/editor/src/core/helpers/find-suggestion-match.ts
Normal 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;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue