[WIKI-728] fix: update emoji insertion method #7962

This commit is contained in:
Vipin Chaudhary 2025-11-06 19:46:32 +05:30 committed by GitHub
parent 0a738d419e
commit fd542a85fa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 52 additions and 28 deletions

View file

@ -19,10 +19,11 @@ export type EmojiListRef = {
export type EmojisListDropdownProps = SuggestionProps<EmojiItem, { name: string }> & {
onClose: () => void;
forceOpen?: boolean;
};
export const EmojisListDropdown = forwardRef<EmojiListRef, EmojisListDropdownProps>((props, ref) => {
const { items, command, query, onClose } = props;
const { items, command, query, onClose, forceOpen = false } = props;
// states
const [selectedIndex, setSelectedIndex] = useState<number>(0);
const [isVisible, setIsVisible] = useState(false);
@ -41,7 +42,13 @@ export const EmojisListDropdown = forwardRef<EmojiListRef, EmojisListDropdownPro
const handleKeyDown = useCallback(
(event: KeyboardEvent): boolean => {
if (query.length <= 0) {
// Allow keyboard navigation if we have items to show
if (items.length === 0) {
return false;
}
// Don't handle keyboard if modal shouldn't be visible (query empty without forceOpen)
if (query.length === 0 && !forceOpen) {
return false;
}
@ -62,7 +69,7 @@ export const EmojisListDropdown = forwardRef<EmojiListRef, EmojisListDropdownPro
return false;
},
[query.length, items.length, selectItem, selectedIndex]
[items.length, query.length, forceOpen, selectItem, selectedIndex]
);
// Show animation
@ -101,7 +108,7 @@ export const EmojisListDropdown = forwardRef<EmojiListRef, EmojisListDropdownPro
useOutsideClickDetector(dropdownContainerRef, onClose);
if (query.length <= 0) return null;
if (query.length === 0 && !forceOpen) return null;
return (
<>

View file

@ -11,22 +11,17 @@ import {
removeDuplicates,
} from "@tiptap/core";
import { EmojiStorage, emojis, emojiToShortcode, shortcodeToEmoji } from "@tiptap/extension-emoji";
import { Plugin, PluginKey, Transaction } from "@tiptap/pm/state";
import { Fragment } from "@tiptap/pm/model";
import { Plugin, PluginKey, TextSelection, 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> {
emoji: {
/**
* Add an emoji
*/
setEmoji: (shortcode: string) => ReturnType;
};
}
// Extended storage type to include our custom forceOpen flag
export interface ExtendedEmojiStorage extends EmojiStorage {
forceOpen: boolean;
}
export type EmojiItem = {
@ -114,18 +109,22 @@ export const Emoji = Node.create<EmojiOptions, EmojiStorage>({
editor
.chain()
.focus()
.insertContentAt(range, [
{
type: this.name,
attrs: props,
},
{
type: "text",
text: " ",
},
])
.command(({ tr, state }) => {
tr.setStoredMarks(state.doc.resolve(state.selection.to - 2).marks());
.command(({ tr, state, dispatch }) => {
if (!dispatch) return true;
const { schema } = state;
const emojiNode = schema.nodes[this.name].create(props);
const spaceNode = schema.text(" ");
const fragment = Fragment.from([emojiNode, spaceNode]);
tr.replaceWith(range.from, range.to, fragment);
const newPos = range.from + fragment.size;
tr.setSelection(TextSelection.near(tr.doc.resolve(newPos)));
tr.setStoredMarks(tr.doc.resolve(range.from).marks());
return true;
})
.run();
@ -157,6 +156,7 @@ export const Emoji = Node.create<EmojiOptions, EmojiStorage>({
return {
emojis: this.options.emojis,
isSupported: (emojiItem) => (emojiItem.version ? supportMap[emojiItem.version] : false),
forceOpen: false,
};
},

View file

@ -7,6 +7,7 @@ import { updateFloatingUIFloaterPosition } from "@/helpers/floating-ui";
import { CommandListInstance, DROPDOWN_NAVIGATION_KEYS } from "@/helpers/tippy";
// local imports
import { type EmojiItem, EmojisListDropdown, EmojisListDropdownProps } from "./components/emojis-list";
import type { ExtendedEmojiStorage } from "./emoji";
const DEFAULT_EMOJIS = ["+1", "-1", "smile", "orange_heart", "eyes"];
@ -54,16 +55,21 @@ export const emojiSuggestion: EmojiOptions["suggestion"] = {
component?.destroy();
component = null;
(editor || editorRef)?.commands.removeActiveDropbarExtension(CORE_EXTENSIONS.EMOJI);
const emojiStorage = editor?.storage.emoji as ExtendedEmojiStorage;
emojiStorage.forceOpen = false;
cleanup();
};
return {
onStart: (props) => {
editorRef = props.editor;
const emojiStorage = props.editor.storage.emoji as ExtendedEmojiStorage;
const forceOpen = emojiStorage.forceOpen || false;
component = new ReactRenderer<CommandListInstance, EmojisListDropdownProps>(EmojisListDropdown, {
props: {
...props,
onClose: () => handleClose(props.editor),
forceOpen,
} satisfies EmojisListDropdownProps,
editor: props.editor,
className: "fixed z-[100]",
@ -76,7 +82,9 @@ export const emojiSuggestion: EmojiOptions["suggestion"] = {
onUpdate: (props) => {
if (!component || !component.element) return;
component.updateProps(props);
const emojiStorage = props.editor.storage.emoji as ExtendedEmojiStorage;
const forceOpen = emojiStorage.forceOpen || false;
component.updateProps({ ...props, forceOpen });
if (!props.clientRect) return;
cleanup();
cleanup = updateFloatingUIFloaterPosition(props.editor, component.element as HTMLElement).cleanup;

View file

@ -33,6 +33,7 @@ import {
insertImage,
insertCallout,
setText,
openEmojiPicker,
} from "@/helpers/editor-commands";
// plane editor extensions
import { coreEditorAdditionalSlashCommandOptions } from "@/plane-editor/extensions";
@ -198,7 +199,7 @@ export const getSlashCommandFilteredSections =
searchTerms: ["emoji", "icons", "reaction", "emoticon", "emotags"],
icon: <Smile className="size-3.5" />,
command: ({ editor, range }) => {
editor.chain().focus().insertContentAt(range, "<p>:</p>").run();
openEmojiPicker(editor, range);
},
},
],

View file

@ -5,6 +5,7 @@ import { CORE_EXTENSIONS } from "@/constants/extension";
import { replaceCodeWithText } from "@/extensions/code/utils/replace-code-block-with-text";
import type { InsertImageComponentProps } from "@/extensions/custom-image/types";
// helpers
import { ExtendedEmojiStorage } from "@/extensions/emoji/emoji";
import { findTableAncestor } from "@/helpers/common";
export const setText = (editor: Editor, range?: Range) => {
@ -184,3 +185,10 @@ export const insertCallout = (editor: Editor, range?: Range) => {
if (range) editor.chain().focus().deleteRange(range).insertCallout().run();
else editor.chain().focus().insertCallout().run();
};
export const openEmojiPicker = (editor: Editor, range?: Range) => {
if (range) editor.chain().focus().deleteRange(range).run();
const emojiStorage = editor.storage.emoji as ExtendedEmojiStorage;
emojiStorage.forceOpen = true;
editor.chain().focus().insertContent(":").run();
};