feat(internal-chat): support paste and drag-drop for attachments (#269)

This commit is contained in:
Gabriel Jablonski 2026-04-18 10:48:50 -03:00 committed by GitHub
parent c9b1917886
commit adc0d892e0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 70 additions and 1 deletions

View File

@ -66,6 +66,7 @@
"DELETED": "This message was deleted",
"SEND": "Send",
"UPLOAD_FILE": "Upload File",
"DRAG_DROP": "Drop files here to attach",
"MENTION_USER": "Mention a user",
"MENTION_CONVERSATION": "Mention a conversation",
"CONFIRM_DELETE": "Are you sure you want to delete this message?",

View File

@ -66,6 +66,7 @@
"DELETED": "Esta mensagem foi exclu\u00edda",
"SEND": "Enviar",
"UPLOAD_FILE": "Enviar Arquivo",
"DRAG_DROP": "Solte os arquivos aqui para anexar",
"MENTION_USER": "Mencionar um usu\u00e1rio",
"MENTION_CONVERSATION": "Mencionar uma conversa",
"CONFIRM_DELETE": "Tem certeza que deseja excluir esta mensagem?",

View File

@ -49,8 +49,10 @@ const editorContent = ref(props.initialContent);
const attachedFiles = ref([]);
const isMentionMenuOpen = ref(false);
const isConversationMenuOpen = ref(false);
const isDragging = ref(false);
let draftTimer = null;
let dragCounter = 0;
const canSend = computed(() => {
return (
@ -147,6 +149,53 @@ function removeFile(index) {
attachedFiles.value.splice(index, 1);
}
function addFiles(fileList) {
const files = Array.from(fileList || []).filter(f => f && f.size > 0);
if (!files.length) return;
attachedFiles.value = [...attachedFiles.value, ...files];
}
function handlePaste(event) {
const files = event.clipboardData?.files;
if (!files?.length) return;
event.preventDefault();
addFiles(files);
}
function hasFileDrag(event) {
return event.dataTransfer?.types?.includes('Files');
}
function handleDragEnter(event) {
if (!hasFileDrag(event)) return;
event.preventDefault();
dragCounter += 1;
isDragging.value = true;
}
function handleDragOver(event) {
if (!hasFileDrag(event)) return;
event.preventDefault();
}
function handleDragLeave(event) {
if (!hasFileDrag(event)) return;
event.preventDefault();
dragCounter -= 1;
if (dragCounter <= 0) {
dragCounter = 0;
isDragging.value = false;
}
}
function handleDrop(event) {
if (!hasFileDrag(event)) return;
event.preventDefault();
dragCounter = 0;
isDragging.value = false;
addFiles(event.dataTransfer?.files);
}
function filePreviewUrl(file) {
return URL.createObjectURL(file);
}
@ -185,7 +234,25 @@ defineExpose({ focus, setContent, getContent });
</script>
<template>
<div class="border-t border-n-slate-5 bg-n-solid-2 px-4 py-3">
<div
class="relative border-t border-n-slate-5 bg-n-solid-2 px-4 py-3"
@paste="handlePaste"
@dragenter="handleDragEnter"
@dragover="handleDragOver"
@dragleave="handleDragLeave"
@drop="handleDrop"
>
<transition name="modal-fade">
<div
v-show="isDragging"
class="pointer-events-none absolute inset-0 z-20 m-2 flex flex-col items-center justify-center gap-1 rounded-lg border-2 border-dashed border-n-brand bg-n-solid-2/90 text-n-brand"
>
<Icon icon="i-lucide-upload-cloud" class="size-6" />
<span class="text-sm font-medium">
{{ t('INTERNAL_CHAT.MESSAGE.DRAG_DROP') }}
</span>
</div>
</transition>
<div
v-if="editingMessage"
class="flex items-center justify-between border-b border-n-slate-5 px-3 py-1.5 text-xs text-n-brand"