2024-06-19 08:16:28 +02:00
|
|
|
<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"
|
2024-07-22 01:23:29 +02:00
|
|
|
:class="['absolute bottom-0 right-0 p-2 text-gray-300 font-semibold text-xs', remainingCharacters < 0 && 'text-red-500']"
|
2024-06-19 08:16:28 +02:00
|
|
|
aria-live="polite">
|
|
|
|
|
{{ remainingCharacters }}
|
|
|
|
|
</div>
|
2024-06-21 04:09:09 +02:00
|
|
|
<EmojiSuggestbox :textarea="textarea" v-if="!!currentlyBeingTypedEmoji"
|
2024-06-19 08:16:28 +02:00
|
|
|
:currently-typing-emoji="currentlyBeingTypedEmoji" @autocomplete="autocompleteEmoji" />
|
2024-06-21 04:09:09 +02:00
|
|
|
<MentionSuggestbox :textarea="textarea" v-if="!!currentlyBeingTypedMention"
|
2024-06-19 08:16:28 +02:00
|
|
|
:currently-typing-mention="currentlyBeingTypedMention" @autocomplete="autocompleteMention" />
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script lang="ts" setup>
|
|
|
|
|
import { char, createRegExp, exactly } from "magic-regexp";
|
2024-11-30 19:15:23 +01:00
|
|
|
import EmojiSuggestbox from "../composer-old/emoji-suggestbox.vue";
|
|
|
|
|
import MentionSuggestbox from "../composer-old/mention-suggestbox.vue";
|
2024-06-19 08:16:28 +02:00
|
|
|
|
|
|
|
|
defineOptions({
|
|
|
|
|
inheritAttrs: false,
|
|
|
|
|
});
|
|
|
|
|
const props = defineProps<{
|
|
|
|
|
maxCharacters?: number;
|
|
|
|
|
}>();
|
|
|
|
|
|
2024-11-04 21:20:19 +01:00
|
|
|
const modelContent = defineModel<string>("modelContent", {
|
|
|
|
|
required: true,
|
|
|
|
|
});
|
2024-06-19 08:16:28 +02:00
|
|
|
|
|
|
|
|
const textarea = ref<HTMLTextAreaElement | undefined>(undefined);
|
|
|
|
|
const { input: content } = useTextareaAutosize({
|
|
|
|
|
element: textarea,
|
2024-11-04 21:20:19 +01:00
|
|
|
input: modelContent,
|
2024-06-19 08:16:28 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const remainingCharacters = computed(
|
|
|
|
|
() =>
|
|
|
|
|
(props.maxCharacters ?? Number.POSITIVE_INFINITY) -
|
|
|
|
|
(content.value?.length ?? 0),
|
|
|
|
|
);
|
|
|
|
|
const currentlyBeingTypedEmoji = computed(() => {
|
|
|
|
|
const match = content.value?.match(partiallyTypedEmojiValidator);
|
2024-09-14 13:18:49 +02:00
|
|
|
return match ? (match.at(-1)?.replace(":", "") ?? "") : null;
|
2024-06-19 08:16:28 +02:00
|
|
|
});
|
|
|
|
|
const currentlyBeingTypedMention = computed(() => {
|
|
|
|
|
const match = content.value?.match(partiallyTypedMentionValidator);
|
2024-09-14 13:18:49 +02:00
|
|
|
return match ? (match.at(-1)?.replace("@", "") ?? "") : null;
|
2024-06-19 08:16:28 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
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>
|