mirror of
https://github.com/versia-pub/frontend.git
synced 2025-12-06 16:38:20 +01:00
91 lines
3.1 KiB
Vue
91 lines
3.1 KiB
Vue
<template>
|
|
<ComposerSuggestbox class="max-h-40 overflow-auto !w-auto !flex-row">
|
|
<div v-for="(emoji, index) in topEmojis" :key="emoji.shortcode" @click="emit('autocomplete', emoji.shortcode)"
|
|
:ref="el => { if (el) emojiRefs[index] = el as Element }" :title="emoji.shortcode"
|
|
:class="['flex', 'justify-center', 'shrink-0', 'items-center', 'p-2', 'size-12', 'hover:bg-dark-900/70', { 'bg-pink-500': index === selectedEmojiIndex }]">
|
|
<img :src="emoji.url" class="w-full h-full object-contain"
|
|
:alt="`Emoji with shortcode ${emoji.shortcode}`" />
|
|
</div>
|
|
</ComposerSuggestbox>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import type { LysandClient } from "@lysand-org/client";
|
|
import { distance } from "fastest-levenshtein";
|
|
import type { CustomEmoji } from "~/composables/Identities";
|
|
const props = defineProps<{
|
|
currentlyTypingEmoji: string | null;
|
|
}>();
|
|
|
|
const emojiRefs = ref<Element[]>([]);
|
|
const { Tab, ArrowRight, ArrowLeft, Enter } = useMagicKeys({
|
|
passive: false,
|
|
onEventFired(e) {
|
|
if (
|
|
["Tab", "Enter", "ArrowRight", "ArrowLeft"].includes(e.key) &&
|
|
topEmojis.value !== null
|
|
)
|
|
e.preventDefault();
|
|
},
|
|
});
|
|
const identity = useCurrentIdentity();
|
|
const topEmojis = ref<CustomEmoji[] | null>(null);
|
|
const selectedEmojiIndex = ref<number | null>(null);
|
|
|
|
watchEffect(() => {
|
|
if (!identity.value) return;
|
|
|
|
if (props.currentlyTypingEmoji !== null)
|
|
topEmojis.value = identity.value.emojis
|
|
.map((emoji) => ({
|
|
...emoji,
|
|
distance: distance(
|
|
props.currentlyTypingEmoji as string,
|
|
emoji.shortcode,
|
|
),
|
|
}))
|
|
.sort((a, b) => a.distance - b.distance)
|
|
.slice(0, 20);
|
|
else topEmojis.value = null;
|
|
|
|
if (ArrowRight.value && topEmojis.value !== null) {
|
|
selectedEmojiIndex.value = (selectedEmojiIndex.value ?? -1) + 1;
|
|
if (selectedEmojiIndex.value >= topEmojis.value.length) {
|
|
selectedEmojiIndex.value = 0;
|
|
}
|
|
emojiRefs.value[selectedEmojiIndex.value]?.scrollIntoView({
|
|
behavior: "smooth",
|
|
block: "nearest",
|
|
});
|
|
}
|
|
|
|
if (ArrowLeft.value && topEmojis.value !== null) {
|
|
selectedEmojiIndex.value =
|
|
(selectedEmojiIndex.value ?? topEmojis.value.length) - 1;
|
|
if (selectedEmojiIndex.value < 0) {
|
|
selectedEmojiIndex.value = topEmojis.value.length - 1;
|
|
}
|
|
emojiRefs.value[selectedEmojiIndex.value]?.scrollIntoView({
|
|
behavior: "smooth",
|
|
block: "nearest",
|
|
});
|
|
}
|
|
|
|
if ((Tab.value || Enter.value) && topEmojis.value !== null) {
|
|
const emoji = topEmojis.value[selectedEmojiIndex.value ?? 0];
|
|
if (emoji) emit("autocomplete", emoji.shortcode);
|
|
}
|
|
});
|
|
|
|
// When currentlyTypingEmoji changes, reset the selected emoji index
|
|
watch(
|
|
() => props.currentlyTypingEmoji,
|
|
() => {
|
|
selectedEmojiIndex.value = null;
|
|
},
|
|
);
|
|
|
|
const emit = defineEmits<{
|
|
autocomplete: [emoji: string];
|
|
}>();
|
|
</script> |