mirror of
https://github.com/versia-pub/frontend.git
synced 2025-12-06 16:38:20 +01:00
85 lines
2.8 KiB
Vue
85 lines
2.8 KiB
Vue
<template>
|
|
<div class="relative">
|
|
<textarea v-bind="$attrs" ref="textarea" v-model="content"
|
|
class="resize-none min-h-48 prose prose-invert w-full p-0 !ring-none !border-none !outline-none placeholder:text-zinc-500 bg-transparent appearance-none focus:!border-none focus:!outline-none disabled:cursor-not-allowed"
|
|
aria-label="Compose your message" :autofocus="true"></textarea>
|
|
<div v-if="maxCharacters"
|
|
:class="['absolute bottom-0 right-0 p-2 text-gray-300 font-semibold text-xs', remainingCharacters < 0 && 'text-red-500']"
|
|
aria-live="polite">
|
|
{{ remainingCharacters }}
|
|
</div>
|
|
<ComposerEmojiSuggestbox :textarea="textarea" v-if="!!currentlyBeingTypedEmoji"
|
|
:currently-typing-emoji="currentlyBeingTypedEmoji" @autocomplete="autocompleteEmoji" />
|
|
<ComposerMentionSuggestbox :textarea="textarea" v-if="!!currentlyBeingTypedMention"
|
|
:currently-typing-mention="currentlyBeingTypedMention" @autocomplete="autocompleteMention" />
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import { char, createRegExp, exactly } from "magic-regexp";
|
|
|
|
defineOptions({
|
|
inheritAttrs: false,
|
|
});
|
|
const props = defineProps<{
|
|
maxCharacters?: number;
|
|
modelContent: string;
|
|
}>();
|
|
|
|
const emit = defineEmits<{
|
|
"update:modelContent": [value: string];
|
|
}>();
|
|
|
|
const textarea = ref<HTMLTextAreaElement | undefined>(undefined);
|
|
const { input: content } = useTextareaAutosize({
|
|
element: textarea,
|
|
input: props.modelContent,
|
|
});
|
|
|
|
watch(
|
|
() => props.modelContent,
|
|
(value) => {
|
|
content.value = value;
|
|
},
|
|
);
|
|
|
|
watch(content, (newValue) => {
|
|
emit("update:modelContent", newValue);
|
|
});
|
|
|
|
const remainingCharacters = computed(
|
|
() =>
|
|
(props.maxCharacters ?? Number.POSITIVE_INFINITY) -
|
|
(content.value?.length ?? 0),
|
|
);
|
|
const currentlyBeingTypedEmoji = computed(() => {
|
|
const match = content.value?.match(partiallyTypedEmojiValidator);
|
|
return match ? match.at(-1)?.replace(":", "") ?? "" : null;
|
|
});
|
|
const currentlyBeingTypedMention = computed(() => {
|
|
const match = content.value?.match(partiallyTypedMentionValidator);
|
|
return match ? match.at(-1)?.replace("@", "") ?? "" : null;
|
|
});
|
|
|
|
const autocompleteEmoji = (emoji: string) => {
|
|
// Replace the end of the string with the emoji
|
|
content.value = content.value?.replace(
|
|
createRegExp(
|
|
exactly(":"),
|
|
exactly(currentlyBeingTypedEmoji.value ?? "").notBefore(char),
|
|
),
|
|
`:${emoji}:`,
|
|
);
|
|
};
|
|
|
|
const autocompleteMention = (mention: string) => {
|
|
// Replace the end of the string with the mention
|
|
content.value = content.value?.replace(
|
|
createRegExp(
|
|
exactly("@"),
|
|
exactly(currentlyBeingTypedMention.value ?? "").notBefore(char),
|
|
),
|
|
`@${mention} `,
|
|
);
|
|
};
|
|
</script> |