Mass-select grid and bulk delete for moderators

- Select mode in the filter row (upload.delete permission): tiles
  toggle selection in the capture phase, a floating action bar
  offers select-all and delete with confirmation popover
- New POST /api/uploads/bulk-delete endpoint (validated, deduped,
  max 200 ids) removing rows, files and attachment data
- upload-deleted broadcast on bulk and single delete; other clients
  refresh the grid and drop matching notifications
- Also: skeleton tile grid as loading state, dropzone logic moved
  to the layout-level GlobalDropZone, obsolete ytimg preconnect
  removed

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
2026-10-04 03:06:38 +02:00
co-authored by Devin
parent f57fffb414
commit de0cd6d34f
5 changed files with 188 additions and 61 deletions
+10 -1
View File
@@ -7,7 +7,7 @@ import type { AppNotification } from '@/composables/useNotifications'
export default defineNuxtPlugin(() => {
const { $i18n } = useNuxtApp()
const { user } = useAuth()
const { fetchAll, addLive, reset } = useNotifications()
const { fetchAll, addLive, reset, removeByUploads } = useNotifications()
const { pushLive } = useLiveComments()
const motd = useMotd()
const { applyEvent } = useCommentReactions()
@@ -34,6 +34,15 @@ export default defineNuxtPlugin(() => {
else if (message.type === 'upload-reaction') {
applyUploadEvent(message as UploadReactionEventPayload)
}
else if (message.type === 'upload-deleted') {
const ids = Array.isArray(message.ids)
? message.ids.filter((i: unknown) => typeof i === 'string') as string[]
: []
if (ids.length) {
removeByUploads(ids)
refreshNuxtData(['uploads', 'latest-comments'])
}
}
else if (message.type === 'motd') {
motd.value = typeof message.message === 'string' ? message.message : ''
}