feat: show files trash metadata

This commit is contained in:
mirivlad 2026-06-28 17:03:50 +08:00
parent 10bc28cef7
commit 67ac99c24d
2 changed files with 101 additions and 9 deletions

View File

@ -312,6 +312,8 @@
var disposed = false; var disposed = false;
var fileActions = []; var fileActions = [];
var contextMenuEntries = []; var contextMenuEntries = [];
var showingTrash = false;
var trashEntries = [];
var historyStack = Array.isArray(savedHistory.stack) && savedHistory.stack.length ? savedHistory.stack.map(cleanPath) : [currentPath]; 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)); var historyIndex = Math.max(0, Math.min(Number(savedHistory.index) || 0, historyStack.length - 1));
if (historyStack[historyIndex] !== currentPath) { if (historyStack[historyIndex] !== currentPath) {
@ -337,13 +339,14 @@
var backBtn = iconButton('back', 'Back', 'back', goBack); var backBtn = iconButton('back', 'Back', 'back', goBack);
var forwardBtn = iconButton('forward', 'Forward', 'forward', goForward); var forwardBtn = iconButton('forward', 'Forward', 'forward', goForward);
var upBtn = iconButton('up', 'Up', 'up', goUp); var upBtn = iconButton('up', 'Up', 'up', goUp);
var refreshBtn = iconButton('refresh', 'Refresh', 'refresh', loadEntries); var refreshBtn = iconButton('refresh', 'Refresh', 'refresh', function () { if (showingTrash) loadTrashEntries(); else loadEntries(); });
var newFolderBtn = iconButton('new-folder', 'New folder', 'folderAdd', function () { startCreate('folder'); }); var newFolderBtn = iconButton('new-folder', 'New folder', 'folderAdd', function () { startCreate('folder'); });
var newMdBtn = iconButton('new-markdown', 'New markdown file', 'markdownAdd', function () { startCreate('markdown'); }); 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 newTextBtn = iconButton('new-text', 'New text file', 'textAdd', function () { startCreate('text'); });
var openBtn = iconButton('open', 'Open', 'open', function () { openEntry(selectedEntry()); }); var openBtn = iconButton('open', 'Open', 'open', function () { openEntry(selectedEntry()); });
var renameBtn = iconButton('rename', 'Rename', 'rename', function () { beginRename(); }); var renameBtn = iconButton('rename', 'Rename', 'rename', function () { beginRename(); });
var trashBtn = iconButton('trash', 'Move to trash', 'trash', function () { trashEntry(); }); var trashBtn = iconButton('trash', 'Move to trash', 'trash', function () { trashEntry(); });
var trashViewBtn = iconButton('trash-view', 'Trash metadata', 'trash', function () { loadTrashEntries(); });
var cutBtn = iconButton('cut', 'Cut', 'cut', function () { cutSelection(); }); var cutBtn = iconButton('cut', 'Cut', 'cut', function () { cutSelection(); });
var copyBtn = iconButton('copy', 'Copy', 'copy', function () { copySelection(); }); var copyBtn = iconButton('copy', 'Copy', 'copy', function () { copySelection(); });
var pasteBtn = iconButton('paste', 'Paste', 'paste', function () { pasteEntry(); }); var pasteBtn = iconButton('paste', 'Paste', 'paste', function () { pasteEntry(); });
@ -356,7 +359,7 @@
el('option', { value: 'size-desc' }, ['Size']) el('option', { value: 'size-desc' }, ['Size'])
]); ]);
toolbar.appendChild(breadcrumb); toolbar.appendChild(breadcrumb);
[backBtn, forwardBtn, upBtn, refreshBtn, newFolderBtn, newMdBtn, newTextBtn, openBtn, renameBtn, trashBtn, cutBtn, copyBtn, pasteBtn, filterInput, sortSelect].forEach(function (node) { toolbar.appendChild(node); }); [backBtn, forwardBtn, upBtn, refreshBtn, newFolderBtn, newMdBtn, newTextBtn, openBtn, renameBtn, trashBtn, trashViewBtn, cutBtn, copyBtn, pasteBtn, filterInput, sortSelect].forEach(function (node) { toolbar.appendChild(node); });
containerEl.appendChild(toolbar); containerEl.appendChild(toolbar);
var listContainer = el('div', { className: 'files-list', 'data-files-list': '' }); var listContainer = el('div', { className: 'files-list', 'data-files-list': '' });
@ -398,13 +401,16 @@
function updateButtons() { function updateButtons() {
var count = selectedCount(); var count = selectedCount();
upBtn.disabled = !currentPath; upBtn.disabled = showingTrash || !currentPath;
openBtn.disabled = count !== 1; newFolderBtn.disabled = showingTrash;
renameBtn.disabled = count !== 1; newMdBtn.disabled = showingTrash;
trashBtn.disabled = count === 0; newTextBtn.disabled = showingTrash;
cutBtn.disabled = count === 0; openBtn.disabled = showingTrash || count !== 1;
copyBtn.disabled = count === 0; renameBtn.disabled = showingTrash || count !== 1;
pasteBtn.disabled = !(window.__filesClipboard && window.__filesClipboard.items && window.__filesClipboard.items.length); 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);
} }
function updateHistoryButtons() { function updateHistoryButtons() {
@ -509,6 +515,7 @@
} }
function renderList() { function renderList() {
showingTrash = false;
listContainer.innerHTML = ''; listContainer.innerHTML = '';
var header = el('div', { className: 'files-header' }, [ var header = el('div', { className: 'files-header' }, [
el('span', {}, ['Name']), el('span', {}, ['Name']),
@ -571,7 +578,45 @@
updateButtons(); 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 ID']),
el('span', {}, ['Trash path'])
]);
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' }, [entry.trashId || '']),
el('span', { className: 'files-item-meta' }, [entry.trashPath || ''])
]));
});
updateButtons();
}
function loadEntries() { function loadEntries() {
showingTrash = false;
selectedPaths = {}; selectedPaths = {};
lastClickedPath = ''; lastClickedPath = '';
listContainer.innerHTML = ''; listContainer.innerHTML = '';
@ -591,6 +636,36 @@
}); });
} }
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) { function contributionContextMatches(item, entry) {
var context = String(item && item.context || '').toLowerCase(); var context = String(item && item.context || '').toLowerCase();
if (!context || context === '*' || context === 'files' || context === 'vault-entry') return true; if (!context || context === '*' || context === 'files' || context === 'vault-entry') return true;

View File

@ -218,6 +218,14 @@ function makeApi() {
createFolder: async () => undefined, createFolder: async () => undefined,
move: async () => undefined, move: async () => undefined,
trash: async () => undefined, trash: async () => undefined,
listTrash: async () => [{
originalPath: 'Docs/deleted.md',
trashPath: '.verstak/trash/files/mock/deleted.md',
trashId: 'mock-trash',
deletedAt: '2026-06-27T01:02:03Z',
originalType: 'file',
basename: 'deleted.md',
}],
openExternal: async (relativePath) => { externalCalls.push({ action: 'open', path: relativePath }); }, openExternal: async (relativePath) => { externalCalls.push({ action: 'open', path: relativePath }); },
showInFolder: async (relativePath) => { externalCalls.push({ action: 'show', path: relativePath }); }, showInFolder: async (relativePath) => { externalCalls.push({ action: 'show', path: relativePath }); },
}, },
@ -308,6 +316,15 @@ async function flush() {
throw new Error(`expected provider file action call, got ${JSON.stringify(api.contributionCalls)}`); throw new Error(`expected provider file action call, got ${JSON.stringify(api.contributionCalls)}`);
} }
const trashViewButton = walk(container, (node) => node.getAttribute && node.getAttribute('data-files-action') === 'trash-view');
if (!trashViewButton) throw new Error('trash metadata toolbar button not found');
trashViewButton.click();
await flush();
const trashRow = walk(container, (node) => node.getAttribute && node.getAttribute('data-files-trash-id') === 'mock-trash');
if (!trashRow || !trashRow.textContent.includes('Docs/deleted.md')) {
throw new Error(`trash metadata row not rendered: ${container.textContent}`);
}
console.log('files frontend smoke passed'); console.log('files frontend smoke passed');
})().catch((err) => { })().catch((err) => {
console.error(err); console.error(err);