mirror of
https://github.com/docmost/docmost.git
synced 2026-05-17 23:14:07 +08:00
6046d04375
* feat(editor): show emoji name in suggestion list Replace the fixed-column emoji grid with a vertical list that displays each emoji alongside its :shortcode: name. This makes the picker more discoverable—users can see and learn shortcodes without prior knowledge. Changes: - EmojiList: switch from SimpleGrid/ActionIcon to UnstyledButton list rows showing emoji glyph + monospace 🆔 label - Navigation simplified to ArrowUp/ArrowDown (list has no columns) - Results capped at 8 items for a focused, scannable dropdown - CSS module: rename menuBtn -> menuItem, tighten padding * feat(editor): replace SearchIndex with name/id includes search Port the exact search algorithm from the original extension: - Build a flat index from @emoji-mart/data: { id, name (lowercase), native } - Filter with name.includes(q) || id.includes(q) — predictable, no keyword indirection - Results capped at 5 (same as extension) - Frequently-used emojis (sorted by usage) shown when query is empty - Remove emoji-mart init() / SearchIndex / getEmojiDataFromNative dependencies; index is built lazily and cached in memory - Remove unused GRID_COLUMNS constant * feat(editor): emoji picker with browse and search modes When the query is empty the picker shows a category bar with 8 tabs (people, nature, food…) and a scrollable emoji grid. Typing after ':' switches to a compact list that shows the glyph and :shortcode: side by side, making it easy to discover emoji names while you type. - Category data is loaded lazily from @emoji-mart/data and cached, so opening the picker more than once has no overhead - Grid keyboard nav: arrow keys move by cell/row, Enter picks - List keyboard nav: up/down through results, Enter picks - Mouse hover syncs the keyboard selection index in both modes - incrementEmojiUsage tracks picks so frequently used ones bubble up in future sessions * fix(editor): polish emoji picker copy and loading * feat: add emoji to slash command * Add keyboard support to emoji group navigation --------- Co-authored-by: Philipinho <16838612+Philipinho@users.noreply.github.com>
117 lines
2.7 KiB
TypeScript
117 lines
2.7 KiB
TypeScript
import { ReactRenderer, useEditor } from "@tiptap/react";
|
|
import EmojiList from "./emoji-list";
|
|
import {
|
|
autoUpdate,
|
|
computePosition,
|
|
flip,
|
|
offset,
|
|
shift,
|
|
} from "@floating-ui/dom";
|
|
|
|
const renderEmojiItems = () => {
|
|
let component: ReactRenderer | null = null;
|
|
let popup: HTMLDivElement | null = null;
|
|
let cleanup: (() => void) | null = null;
|
|
let getReferenceClientRect: (() => DOMRect) | null = null;
|
|
|
|
const destroy = () => {
|
|
if (cleanup) {
|
|
cleanup();
|
|
cleanup = null;
|
|
}
|
|
|
|
if (popup) {
|
|
popup.remove();
|
|
popup = null;
|
|
}
|
|
|
|
if (component) {
|
|
component.destroy();
|
|
component = null;
|
|
}
|
|
};
|
|
|
|
return {
|
|
onBeforeStart: (props: {
|
|
editor: ReturnType<typeof useEditor>;
|
|
clientRect: () => DOMRect;
|
|
}) => {
|
|
component = new ReactRenderer(EmojiList, {
|
|
props: { isLoading: true, items: [] },
|
|
editor: props.editor,
|
|
});
|
|
|
|
if (!props.clientRect) {
|
|
return;
|
|
}
|
|
|
|
getReferenceClientRect = props.clientRect;
|
|
popup = document.createElement("div");
|
|
popup.style.zIndex = "9999";
|
|
popup.style.position = "absolute";
|
|
popup.style.top = "0";
|
|
popup.style.left = "0";
|
|
popup.appendChild(component.element);
|
|
document.body.appendChild(popup);
|
|
|
|
const virtualElement = {
|
|
getBoundingClientRect: () => {
|
|
return getReferenceClientRect
|
|
? getReferenceClientRect()
|
|
: new DOMRect(0, 0, 0, 0);
|
|
},
|
|
};
|
|
|
|
cleanup = autoUpdate(virtualElement, popup, () => {
|
|
if (!popup) return;
|
|
|
|
computePosition(virtualElement, popup, {
|
|
placement: "bottom-start",
|
|
middleware: [offset(10), flip(), shift()],
|
|
}).then(({ x, y }) => {
|
|
if (!popup) return;
|
|
|
|
Object.assign(popup.style, {
|
|
transform: `translate(${x}px, ${y}px)`,
|
|
});
|
|
});
|
|
});
|
|
},
|
|
onStart: (props: {
|
|
editor: ReturnType<typeof useEditor>;
|
|
clientRect: () => DOMRect;
|
|
}) => {
|
|
component?.updateProps({ ...props, isLoading: false });
|
|
|
|
if (props.clientRect) {
|
|
getReferenceClientRect = props.clientRect;
|
|
}
|
|
},
|
|
onUpdate: (props: {
|
|
editor: ReturnType<typeof useEditor>;
|
|
clientRect: () => DOMRect;
|
|
}) => {
|
|
component?.updateProps(props);
|
|
|
|
if (props.clientRect) {
|
|
getReferenceClientRect = props.clientRect;
|
|
}
|
|
},
|
|
onKeyDown: (props: { event: KeyboardEvent }) => {
|
|
if (props.event.key === "Escape") {
|
|
destroy();
|
|
|
|
return true;
|
|
}
|
|
|
|
// @ts-ignore
|
|
return component?.ref?.onKeyDown(props);
|
|
},
|
|
onExit: () => {
|
|
destroy();
|
|
},
|
|
};
|
|
};
|
|
|
|
export default renderEmojiItems;
|