feat(filetransfer): allow file deletion and add user delete config option (#683)

* feat(filetransfer): allow file deletion and add user delete config option

* fix(client): make user_delete required boolean in payload

* lint.

* feat(filetransfer): add confirmation dialog for file deletion.

* feat(filetransfer): add delete file confirmation messages in multiple languages

* lint.

---------

Co-authored-by: Miroslav Šedivý <sedivy.miro@gmail.com>
This commit is contained in:
Abdellatif Anaflous
2026-08-07 17:12:02 +01:00
committed by GitHub
parent 30d8e1c595
commit d6a4200b36
26 changed files with 205 additions and 10 deletions
+37
View File
@@ -10,6 +10,12 @@
<p class="file-name" :title="item.name">{{ item.name }}</p>
<p class="file-size">{{ fileSize(item.size) }}</p>
<i v-if="item.type !== 'dir' && canDownload" class="fas fa-download download" @click="download(item)" />
<i
v-if="item.type !== 'dir' && canDelete"
class="fas fa-trash delete"
:title="$t('files.delete')"
@click="deleteFile(item)"
/>
</div>
</div>
<div class="transfer-area">
@@ -181,10 +187,15 @@
.refresh:hover,
.download:hover,
.delete:hover,
.remove-transfer:hover {
cursor: pointer;
}
.delete {
margin-left: 0.5em;
}
.transfer-area {
margin-top: auto;
}
@@ -286,6 +297,10 @@
return this.$accessor.user.admin || this.$accessor.files.userDownload
}
get canDelete() {
return this.$accessor.user.admin || this.$accessor.files.userDelete
}
get canUpload() {
return this.$accessor.user.admin || this.$accessor.files.userUpload
}
@@ -450,6 +465,28 @@
this.$accessor.files.removeTransfer(transfer)
}
async deleteFile(item: FileListItem) {
const { isConfirmed } = await this.$swal({
title: this.$t('files.delete_title', { name: item.name }) as string,
text: this.$t('files.delete_confirm', { name: item.name }) as string,
icon: 'warning',
showCancelButton: true,
confirmButtonText: this.$t('context.confirm.button_yes') as string,
cancelButtonText: this.$t('context.confirm.button_cancel') as string,
})
if (!isConfirmed) {
return
}
const url =
'/file?pwd=' + encodeURIComponent(this.$accessor.password) + '&filename=' + encodeURIComponent(item.name)
try {
await this.$http.delete(url)
this.$accessor.files.refresh()
} catch (error: any) {
this.$log.error(error)
}
}
fileIcon(file: FileListItem) {
let className = 'file-icon fas '
// if is directory
+3
View File
@@ -124,4 +124,7 @@ export const files = {
downloads: 'Herunterladen',
uploads: 'Hochladen',
upload_here: 'Klicken oder ziehen Sie Dateien zum Hochladen hierher',
delete: 'Löschen',
delete_title: '"{name}" löschen?',
delete_confirm: 'Möchten Sie diese Datei wirklich entfernen?',
}
+3
View File
@@ -127,4 +127,7 @@ export const files = {
downloads: 'Downloads',
uploads: 'Uploads',
upload_here: 'Click or drag files here to upload',
delete: 'Delete',
delete_title: 'Delete "{name}"?',
delete_confirm: 'Do you really want to remove this file?',
}
+3
View File
@@ -124,4 +124,7 @@ export const files = {
downloads: 'Descargas',
uploads: 'Subidas',
upload_here: 'Haz clic o arrastra archivos aquí para subirlos',
delete: 'Eliminar',
delete_title: '¿Eliminar "{name}"?',
delete_confirm: '¿Realmente deseas eliminar este archivo?',
}
+3
View File
@@ -124,4 +124,7 @@ export const files = {
downloads: 'Lataukset',
uploads: 'Lataa',
upload_here: 'Klikkaa tai vedä tiedostoja tähän ladataksesi',
delete: 'Poista',
delete_title: 'Poistetaanko "{name}"?',
delete_confirm: 'Haluatko varmasti poistaa tämän tiedoston?',
}
+6 -3
View File
@@ -2,7 +2,7 @@ export const logout = 'Se déconnecter'
export const unsupported = 'ce navigateur ne prend pas en charge WebRTC'
export const admin_loggedin = "Vous êtes connecté en tant qu'admin"
export const you = 'Vous'
export const somebody = 'Quelqu\'un' // TODO: vérifier la traduction
export const somebody = "Quelqu'un" // TODO: vérifier la traduction
export const send_a_message = 'Envoyer un message'
export const side = {
@@ -18,7 +18,7 @@ export const connect = {
password: 'Mot de passe',
connect: 'Connexion',
error: 'Erreur de connexion',
empty_displayname: 'Le nom d\'affichage ne peut pas être vide.', // TODO: vérifier la traduction
empty_displayname: "Le nom d'affichage ne peut pas être vide.", // TODO: vérifier la traduction
}
export const context = {
@@ -51,7 +51,7 @@ export const controls = {
lock: 'Vérouiller le contrôle',
unlock: 'Débloquer le contrôle',
has: 'Vous avez le contrôle', // TODO: vérifier la traduction
hasnot: 'Vous n\'avez pas le contrôle', // TODO: vérifier la traduction
hasnot: "Vous n'avez pas le contrôle", // TODO: vérifier la traduction
}
export const locks = {
@@ -124,4 +124,7 @@ export const files = {
downloads: 'Téléchargements',
uploads: 'Envois',
upload_here: 'Cliquez ou faites glisser les fichiers ici pour les envoyer',
delete: 'Supprimer',
delete_title: 'Supprimer "{name}" ?',
delete_confirm: 'Voulez-vous vraiment supprimer ce fichier ?',
}
+3
View File
@@ -124,4 +124,7 @@ export const files = {
downloads: 'Unduhan',
uploads: 'Unggahan',
upload_here: 'Klik atau seret berkas ke sini untuk mengunggah',
delete: 'Hapus',
delete_title: 'Hapus "{name}"?',
delete_confirm: 'Apakah Anda benar-benar ingin menghapus berkas ini?',
}
+3
View File
@@ -124,4 +124,7 @@ export const files = {
downloads: 'ダウンロード',
uploads: 'アップロード',
upload_here: 'アップロードするにはここをクリックするかファイルをドラッグしてください',
delete: '削除',
delete_title: '"{name}" を削除しますか?',
delete_confirm: '本当にこのファイルを削除しますか?',
}
+3
View File
@@ -124,4 +124,7 @@ export const files = {
downloads: '다운로드',
uploads: '업로드',
upload_here: '업로드할 파일을 여기로 클릭하거나 드래그하세요',
delete: '삭제',
delete_title: '"{name}" 을(를) 삭제하시겠습니까?',
delete_confirm: '정말로 이 파일을 삭제하시겠습니까?',
}
+3
View File
@@ -124,4 +124,7 @@ export const files = {
downloads: 'Nedlastinger',
uploads: 'Opplastinger',
upload_here: 'Klikk eller dra filer hit for å laste opp',
delete: 'Slett',
delete_title: 'Slette "{name}"?',
delete_confirm: 'Er du sikker på at du vil slette denne filen?',
}
+3
View File
@@ -124,4 +124,7 @@ export const files = {
downloads: 'Pobrane pliki',
uploads: 'Wysyłane pliki',
upload_here: 'Kliknij lub przeciągnij pliki tutaj, aby przesłać',
delete: 'Usuń',
delete_title: 'Usunąć "{name}"?',
delete_confirm: 'Czy na pewno chcesz usunąć ten plik?',
}
+3
View File
@@ -124,4 +124,7 @@ export const files = {
downloads: 'Загрузки',
uploads: 'Загрузить',
upload_here: 'Нажмите или перетащите сюда файлы для загрузки',
delete: 'Удалить',
delete_title: 'Удалить "{name}"?',
delete_confirm: 'Вы действительно хотите удалить этот файл?',
}
+3
View File
@@ -130,4 +130,7 @@ export const files = {
downloads: 'Stiahnutia',
uploads: 'Nahrávanie',
upload_here: 'Kliknutím alebo pretiahnutím súborov sem ich môžete nahrať',
delete: 'Odstrániť',
delete_title: 'Odstrániť "{name}"?',
delete_confirm: 'Naozaj chcete odstrániť tento súbor?',
}
+3
View File
@@ -124,4 +124,7 @@ export const files = {
downloads: 'Nedladdningar',
uploads: 'Ladda upp',
upload_here: 'Klicka eller dra filer hit för att ladda upp dem',
delete: 'Ta bort',
delete_title: 'Ta bort "{name}"?',
delete_confirm: 'Vill du verkligen ta bort den här filen?',
}
+9 -6
View File
@@ -72,12 +72,12 @@ export const locks = {
notif_unlocked: '房间已解锁',
},
file_transfer: {
lock: '锁定文件传输(对用户)',
unlock: '解锁文件传输(对用户)',
locked: '文件传输已锁定(对用户)',
unlocked: '文件传输已解锁(对用户)',
notif_locked: '已锁定文件传输',
notif_unlocked: '已解锁文件传输',
lock: '锁定文件传输(对用户)',
unlock: '解锁文件传输(对用户)',
locked: '文件传输已锁定(对用户)',
unlocked: '文件传输已解锁(对用户)',
notif_locked: '已锁定文件传输',
notif_unlocked: '已解锁文件传输',
},
}
@@ -124,4 +124,7 @@ export const files = {
downloads: '下载',
uploads: '上传',
upload_here: '点击或拖动文件到此处上传',
delete: '删除',
delete_title: '删除"{name}"?',
delete_confirm: '确定要删除此文件吗?',
}
+3
View File
@@ -124,4 +124,7 @@ export const files = {
downloads: '下載',
uploads: '上傳',
upload_here: '點選或拖曳檔案至此上傳',
delete: '刪除',
delete_title: '刪除「{name}」?',
delete_confirm: '確定要刪除此檔案嗎?',
}
+8 -1
View File
@@ -361,11 +361,18 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
/////////////////////////////
// File Transfer Events
/////////////////////////////
protected [EVENT.FILETRANSFER.LIST]({ cwd, user_download, user_upload, files }: FileTransferListPayload) {
protected [EVENT.FILETRANSFER.LIST]({
cwd,
user_download,
user_upload,
user_delete,
files,
}: FileTransferListPayload) {
this.$accessor.files.setCwd(cwd)
this.$accessor.files.setFileList(files)
this.$accessor.files.setUserDownload(user_download)
this.$accessor.files.setUserUpload(user_upload)
this.$accessor.files.setUserDelete(user_delete)
}
/////////////////////////////
+1
View File
@@ -206,6 +206,7 @@ export interface FileTransferListPayload {
cwd: string
user_download: boolean
user_upload: boolean
user_delete: boolean
files: FileListItem[]
}
+9
View File
@@ -9,6 +9,7 @@ export const state = () => ({
transfers: [] as FileTransfer[],
userDownload: false,
userUpload: false,
userDelete: false,
})
export const getters = getterTree(state, {
@@ -32,6 +33,10 @@ export const mutations = mutationTree(state, {
state.userUpload = val
},
_setUserDelete(state, val: boolean) {
state.userDelete = val
},
_addTransfer(state, transfer: FileTransfer) {
state.transfers = [...state.transfers, transfer]
},
@@ -60,6 +65,10 @@ export const actions = actionTree(
accessor.files._setUserUpload(val)
},
setUserDelete(store, val: boolean) {
accessor.files._setUserDelete(val)
},
addTransfer(store, transfer: FileTransfer) {
if (transfer.status !== 'pending') {
return