Add trash: deleted files go to trash, restore or empty

This commit is contained in:
2026-05-22 21:12:29 +02:00
parent 88eebf6944
commit 1433890a4c
4 changed files with 134 additions and 5 deletions
+44 -3
View File
@@ -13,13 +13,25 @@
<input v-model="searchQuery" placeholder="Search files..." @input="filterFiles" />
</div>
<div class="sidebar-nav">
<button :class="{active: view === 'files'}" @click="view = 'files'">📄 My Files</button>
<button :class="{active: view === 'files'}" @click="view = 'files'">📄 Files</button>
<button :class="{active: view === 'shared'}" @click="view = 'shared'">🤝 Shared</button>
<button :class="{active: view === 'prefs'}" @click="view = 'prefs'"> Preferences</button>
<button v-if="isAdmin" :class="{active: view === 'admin'}" @click="view = 'admin'">👤 Admin</button>
<button :class="{active: view === 'trash'}" @click="view = 'trash'; loadTrash()">🗑 Trash</button>
<button :class="{active: view === 'prefs'}" @click="view = 'prefs'"></button>
<button v-if="isAdmin" :class="{active: view === 'admin'}" @click="view = 'admin'">👤</button>
</div>
<FileTree v-if="view === 'files'" :files="filteredFiles" :selected="currentFile" @select="openFile" @delete="deleteItem" @move="moveFile" />
<FileTree v-if="view === 'shared'" :files="sharedFiles" :selected="currentFile" @select="openFile" @delete="deleteItem" @move="moveFile" />
<div v-if="view === 'trash'" class="trash-view">
<div class="trash-header">
<span>{{ trashItems.length }} item(s)</span>
<button v-if="trashItems.length" @click="emptyTrash" class="empty-trash-btn">Empty Trash</button>
</div>
<div v-for="item in trashItems" :key="item.name" class="trash-item">
<span>{{ item.isDir ? '📁' : '📄' }} {{ item.name }}</span>
<button @click="restoreTrash(item.name)" title="Restore"></button>
</div>
<p v-if="!trashItems.length" class="trash-empty">Trash is empty</p>
</div>
</aside>
<main class="editor-area" v-if="view === 'files' || view === 'shared'">
<div class="toolbar">
@@ -224,6 +236,7 @@ const shareLevel = ref('ro')
const shareMsg = ref('')
const gitDirty = ref(0)
const aiResult = ref('')
const trashItems = ref([])
// Preferences
const prefs = ref({ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, defaultMode: 'split', theme: 'dark' })
@@ -385,6 +398,26 @@ async function loadShared() {
}
}
async function loadTrash() {
try {
trashItems.value = await api('/api/files/trash', {})
} catch (e) {
trashItems.value = []
}
}
async function restoreTrash(name) {
await api('/api/files/trash/restore', { name })
await loadTrash()
await loadFiles()
}
async function emptyTrash() {
if (!confirm('Permanently delete all items in trash?')) return
await api('/api/files/trash/empty', {})
trashItems.value = []
}
// ─── Preferences ─────────────────────────────────────────────────────────────
function savePrefs() {
@@ -1041,6 +1074,14 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
.ai-content code { background: var(--code-bg); padding: 2px 6px; border-radius: 3px; }
.ai-content ul, .ai-content ol { padding-left: 2em; margin: 0 0 12px; }
.trash-view { padding: 8px; flex: 1; overflow-y: auto; }
.trash-header { display: flex; justify-content: space-between; align-items: center; padding: 8px; font-size: 12px; color: var(--text-muted); }
.empty-trash-btn { background: var(--danger); color: white; border: none; padding: 4px 10px; border-radius: 4px; cursor: pointer; font-size: 12px; }
.trash-item { display: flex; justify-content: space-between; align-items: center; padding: 6px 8px; border-radius: 4px; font-size: 13px; }
.trash-item:hover { background: var(--bg-hover); }
.trash-item button { background: none; border: none; cursor: pointer; font-size: 14px; }
.trash-empty { color: var(--text-muted); font-size: 13px; text-align: center; padding: 20px; }
/* ─── Responsive ──────────────────────────────────────────────────────────── */
@media (max-width: 768px) {