feat: add global trash plugin

This commit is contained in:
2026-07-10 08:43:22 +08:00
parent a76500418e
commit c31ee7e0ce
7 changed files with 769 additions and 125 deletions
+13 -109
View File
@@ -361,8 +361,6 @@
var disposed = false;
var fileActions = [];
var contextMenuEntries = [];
var showingTrash = false;
var trashEntries = [];
var historyStack = Array.isArray(savedHistory.stack) && savedHistory.stack.length ? savedHistory.stack.map(cleanPath) : [currentPath];
var historyIndex = Math.max(0, Math.min(Number(savedHistory.index) || 0, historyStack.length - 1));
if (historyStack[historyIndex] !== currentPath) {
@@ -395,14 +393,13 @@
var backBtn = iconButton('back', 'Back', 'back', goBack);
var forwardBtn = iconButton('forward', 'Forward', 'forward', goForward);
var upBtn = iconButton('up', 'Up', 'up', goUp);
var refreshBtn = iconButton('refresh', 'Refresh', 'refresh', function () { if (showingTrash) loadTrashEntries(); else loadEntries(); });
var refreshBtn = iconButton('refresh', 'Refresh', 'refresh', loadEntries);
var newFolderBtn = iconButton('new-folder', 'New folder', 'folderAdd', function () { startCreate('folder'); });
var newMdBtn = iconButton('new-markdown', 'New markdown file', 'markdownAdd', function () { startCreate('markdown'); });
var newTextBtn = iconButton('new-text', 'New text file', 'textAdd', function () { startCreate('text'); });
var openBtn = iconButton('open', 'Open', 'open', function () { openEntry(selectedEntry()); });
var renameBtn = iconButton('rename', 'Rename', 'rename', function () { beginRename(); });
var trashBtn = iconButton('trash', 'Move to trash', 'trash', function () { trashEntry(); });
var trashViewBtn = iconButton('trash-view', 'Trash metadata', 'trashView', function () { loadTrashEntries(); });
var cutBtn = iconButton('cut', 'Cut', 'cut', function () { cutSelection(); });
var copyBtn = iconButton('copy', 'Copy', 'copy', function () { copySelection(); });
var pasteBtn = iconButton('paste', 'Paste', 'paste', function () { pasteEntry(); });
@@ -420,7 +417,7 @@
el('div', { className: 'files-toolbar-group', 'aria-label': 'Navigation' }, [backBtn, forwardBtn, upBtn, refreshBtn]),
el('div', { className: 'files-toolbar-group', 'aria-label': 'Create' }, [newFolderBtn, newMdBtn, newTextBtn]),
el('div', { className: 'files-toolbar-group', 'aria-label': 'Selection actions' }, [openBtn, renameBtn, trashBtn]),
el('div', { className: 'files-toolbar-group', 'aria-label': 'Clipboard and trash' }, [trashViewBtn, cutBtn, copyBtn, pasteBtn]),
el('div', { className: 'files-toolbar-group', 'aria-label': 'Clipboard' }, [cutBtn, copyBtn, pasteBtn]),
el('span', { className: 'files-toolbar-spacer' }),
el('div', { className: 'files-toolbar-group', 'aria-label': 'Filter and sort' }, [filterInput, sortSelect])
].forEach(function (node) { toolbar.appendChild(node); });
@@ -473,16 +470,16 @@
function updateButtons() {
var count = selectedCount();
upBtn.disabled = showingTrash || !currentPath;
newFolderBtn.disabled = showingTrash;
newMdBtn.disabled = showingTrash;
newTextBtn.disabled = showingTrash;
openBtn.disabled = showingTrash || count !== 1;
renameBtn.disabled = showingTrash || count !== 1;
trashBtn.disabled = showingTrash || count === 0;
cutBtn.disabled = showingTrash || count === 0;
copyBtn.disabled = showingTrash || count === 0;
pasteBtn.disabled = showingTrash || !(window.__filesClipboard && window.__filesClipboard.items && window.__filesClipboard.items.length);
upBtn.disabled = !currentPath;
newFolderBtn.disabled = false;
newMdBtn.disabled = false;
newTextBtn.disabled = false;
openBtn.disabled = count !== 1;
renameBtn.disabled = count !== 1;
trashBtn.disabled = count === 0;
cutBtn.disabled = count === 0;
copyBtn.disabled = count === 0;
pasteBtn.disabled = !(window.__filesClipboard && window.__filesClipboard.items && window.__filesClipboard.items.length);
}
function updateHistoryButtons() {
@@ -634,7 +631,6 @@
}
function renderList() {
showingTrash = false;
listContainer.innerHTML = '';
var header = el('div', { className: 'files-header' }, [
el('span', {}, ['Name']),
@@ -701,55 +697,7 @@
updateButtons();
}
function renderTrashList() {
listContainer.innerHTML = '';
breadcrumb.innerHTML = '';
breadcrumb.appendChild(el('span', { className: 'files-breadcrumb-current' }, ['Trash metadata']));
selectedPaths = {};
lastClickedPath = '';
var header = el('div', { className: 'files-header' }, [
el('span', {}, ['Original path']),
el('span', {}, ['Type']),
el('span', {}, ['Deleted']),
el('span', {}, ['Trash path']),
el('span', {}, ['Actions'])
]);
listContainer.appendChild(header);
if (!trashEntries || trashEntries.length === 0) {
listContainer.appendChild(el('div', { className: 'files-empty' }, ['Trash is empty']));
updateButtons();
return;
}
trashEntries.forEach(function (entry) {
listContainer.appendChild(el('div', {
className: 'files-item',
'data-files-trash-id': entry.trashId || ''
}, [
el('span', { className: 'files-item-name', textContent: entry.originalPath || entry.basename || '', title: entry.originalPath || '' }),
el('span', { className: 'files-item-meta' }, [entry.originalType || '']),
el('span', { className: 'files-item-meta hide-narrow' }, [formatDate(entry.deletedAt)]),
el('span', { className: 'files-item-meta hide-narrow', title: entry.trashId || '' }, [entry.trashPath || '']),
el('div', { className: 'files-row-actions' }, [
el('button', {
className: 'files-row-btn',
'data-files-action': 'restore-trash',
'data-files-restore-trash': entry.trashId || '',
title: 'Restore',
'aria-label': 'Restore',
innerHTML: svgIcon(ACTION_ICONS.restore),
onClick: function (event) { event.stopPropagation(); restoreTrashEntry(entry); }
})
])
]));
});
updateButtons();
}
function loadEntries() {
showingTrash = false;
selectedPaths = {};
lastClickedPath = '';
listContainer.innerHTML = '';
@@ -769,36 +717,6 @@
});
}
function loadTrashEntries() {
showingTrash = true;
selectedPaths = {};
lastClickedPath = '';
listContainer.innerHTML = '';
listContainer.appendChild(el('div', { className: 'files-loading' }, ['Loading...']));
if (!api.files || typeof api.files.listTrash !== 'function') {
trashEntries = [];
listContainer.innerHTML = '';
listContainer.appendChild(el('div', { className: 'files-error' }, [
el('div', {}, ['Trash metadata is unavailable'])
]));
updateButtons();
return;
}
api.files.listTrash().then(function (result) {
if (disposed) return;
trashEntries = result || [];
renderTrashList();
}).catch(function (err) {
if (disposed) return;
listContainer.innerHTML = '';
listContainer.appendChild(el('div', { className: 'files-error' }, [
el('div', {}, ['Failed to load trash metadata']),
el('div', { className: 'files-error-msg' }, [(err && err.message) ? err.message : String(err)])
]));
updateButtons();
});
}
function contributionContextMatches(item, entry) {
var context = String(item && item.context || '').toLowerCase();
if (!context || context === '*' || context === 'files' || context === 'vault-entry') return true;
@@ -1029,19 +947,6 @@
}
}
function restoreTrashEntry(entry) {
if (!entry || !entry.trashId) return;
if (!api.files || typeof api.files.restoreTrash !== 'function') {
window.alert('Restore is unavailable');
return;
}
api.files.restoreTrash(entry.trashId, { overwrite: false }).then(function () {
loadEntries();
}).catch(function (err) {
window.alert((err && err.message) ? err.message : String(err));
});
}
filterInput.addEventListener('input', function () { filterText = filterInput.value; renderList(); });
sortSelect.addEventListener('change', function () { sortMode = sortSelect.value; renderList(); });
createConfirm.addEventListener('click', confirmCreate);
@@ -1577,8 +1482,7 @@
if (api.events && typeof api.events.subscribe === 'function') {
api.events.subscribe('file.changed', function (event) {
if (disposed || !isWorkspaceEvent(event)) return;
if (showingTrash) loadTrashEntries();
else loadEntries();
loadEntries();
}).then(function (unsubscribe) {
fileChangedUnsubscribe = unsubscribe;
}).catch(function (err) {