/* =========================================================== Trash Plugin - Verstak v2 Frontend Bundle Contract: window.VerstakPluginRegister(id, { components }) =========================================================== */ (function () { 'use strict'; var PLUGIN_ID = 'verstak.trash'; var STYLES = [ '.trash-root{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--vt-color-background,#101020);color:var(--vt-color-text-primary,#f4f7fb);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",system-ui,sans-serif}', '.trash-toolbar{display:flex;align-items:center;gap:.5rem;min-height:2.75rem;padding:.5rem .75rem;border-bottom:1px solid var(--vt-color-border,#202b46);background:var(--vt-color-surface-muted,#111629);flex-shrink:0;flex-wrap:wrap}', '.trash-title{font-size:.88rem;font-weight:650;color:var(--vt-color-text-primary,#f4f7fb)}', '.trash-count{font-size:.74rem;color:var(--vt-color-text-muted,#7f8aa3)}', '.trash-spacer{flex:1}', '.trash-control{min-height:2rem;box-sizing:border-box;border:1px solid var(--vt-color-border-strong,#2c456a);border-radius:var(--vt-radius-sm,4px);background:var(--vt-color-surface,#15152c);color:var(--vt-color-text-primary,#f4f7fb);font:inherit;font-size:.78rem;padding:.35rem .5rem}', '.trash-search{min-width:12rem;flex:1 1 14rem}', '.trash-select{max-width:11rem}', '.trash-btn{min-height:2rem;border:1px solid var(--vt-color-border-strong,#2c456a);border-radius:var(--vt-radius-sm,4px);background:var(--vt-color-surface-hover,#1b2440);color:var(--vt-color-text-secondary,#b7c0d4);font:inherit;font-size:.78rem;padding:.35rem .6rem;cursor:pointer;white-space:nowrap}', '.trash-btn:hover:not(:disabled){border-color:var(--vt-color-accent,#4ecca3);color:var(--vt-color-text-primary,#f4f7fb)}', '.trash-btn:disabled{opacity:.48;cursor:default}', '.trash-btn-danger{border-color:rgba(233,69,96,.52);color:#ffb4bf;background:var(--vt-color-danger-muted,rgba(233,69,96,.14))}', '.trash-status{min-height:1.25rem;padding:.4rem .75rem;border-bottom:1px solid rgba(32,43,70,.7);font-size:.76rem;color:var(--vt-color-text-muted,#7f8aa3);flex-shrink:0}', '.trash-status.error{color:#ffc6ce;background:rgba(233,69,96,.08)}', '.trash-list{flex:1;min-height:0;overflow:auto}', '.trash-header,.trash-row{display:grid;grid-template-columns:minmax(12rem,1.4fr) minmax(7rem,.75fr) minmax(14rem,1.6fr) minmax(8rem,.8fr) minmax(7rem,.65fr) auto;gap:.65rem;align-items:center;padding:.5rem .75rem;border-bottom:1px solid rgba(32,43,70,.72)}', '.trash-header{position:sticky;top:0;z-index:1;background:var(--vt-color-surface-muted,#111629);color:var(--vt-color-text-muted,#7f8aa3);font-size:.68rem;font-weight:650;letter-spacing:.02em;text-transform:uppercase}', '.trash-row:hover{background:var(--vt-color-surface-hover,#1b2440)}', '.trash-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.84rem;font-weight:600}', '.trash-meta{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--vt-color-text-secondary,#b7c0d4);font-size:.76rem}', '.trash-path{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--vt-color-text-muted,#7f8aa3)}', '.trash-type{display:inline-flex;align-items:center;gap:.3rem;font-size:.72rem;color:var(--vt-color-text-secondary,#b7c0d4)}', '.trash-actions{display:flex;justify-content:flex-end;gap:.35rem}', '.trash-empty{height:100%;min-height:12rem;display:flex;align-items:center;justify-content:center;padding:2rem;color:var(--vt-color-text-muted,#7f8aa3);font-size:.88rem;text-align:center}', '.trash-confirm-backdrop{position:absolute;inset:0;z-index:20;display:flex;align-items:center;justify-content:center;padding:1rem;background:rgba(5,7,16,.7)}', '.trash-confirm{width:min(31rem,100%);border:1px solid rgba(233,69,96,.5);border-radius:var(--vt-radius-md,6px);background:var(--vt-color-surface,#15152c);box-shadow:var(--vt-elevation-modal,0 16px 40px rgba(0,0,0,.45));padding:1rem}', '.trash-confirm h3{margin:0;font-size:1rem;font-weight:650}', '.trash-confirm p{margin:.55rem 0;color:var(--vt-color-text-secondary,#b7c0d4);font-size:.82rem;line-height:1.45;overflow-wrap:anywhere}', '.trash-confirm-path{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--vt-color-text-muted,#7f8aa3)}', '.trash-confirm-actions{display:flex;justify-content:flex-end;gap:.5rem;margin-top:1rem}', '@media(max-width:900px){.trash-header{display:none}.trash-row{grid-template-columns:minmax(0,1fr) auto;gap:.45rem .75rem;padding:.7rem .75rem}.trash-row>span:nth-child(2),.trash-row>span:nth-child(3),.trash-row>span:nth-child(4),.trash-row>span:nth-child(5){grid-column:1}.trash-actions{grid-column:2;grid-row:1 / span 5;align-self:center;flex-direction:column}.trash-search{order:5;flex-basis:100%}.trash-select{max-width:none;flex:1 1 8rem}}' ].join('\n'); function injectStyles() { if (document.getElementById('trash-style-injected')) return; var style = document.createElement('style'); style.id = 'trash-style-injected'; style.textContent = STYLES; document.head.appendChild(style); } function el(tag, attrs, children) { var node = document.createElement(tag); attrs = attrs || {}; Object.keys(attrs).forEach(function (key) { var value = attrs[key]; if (value == null) return; if (key === 'className') node.className = value; else if (key === 'textContent') node.textContent = value; else if (key === 'style' && typeof value === 'object') Object.assign(node.style, value); else if (key.slice(0, 2) === 'on') node.addEventListener(key.slice(2).toLowerCase(), value); else if (key === 'value') node.value = value; else if (key === 'disabled') node.disabled = !!value; else node.setAttribute(key, value); }); (Array.isArray(children) ? children : [children]).forEach(function (child) { if (child == null) return; node.appendChild(typeof child === 'string' ? document.createTextNode(child) : child); }); return node; } function text(value) { return String(value == null ? '' : value); } function cleanPath(value) { return text(value).split('/').filter(Boolean).join('/'); } function nameFor(entry) { var path = cleanPath(entry && entry.originalPath); return text(entry && entry.basename).trim() || path.split('/').pop() || 'Untitled item'; } function workspaceFor(entry) { return cleanPath(entry && entry.originalPath).split('/')[0] || 'Vault root'; } function directoryFor(entry) { var path = cleanPath(entry && entry.originalPath); var parts = path.split('/'); parts.pop(); return parts.join('/') || '/'; } function typeLabel(entry) { return entry && entry.originalType === 'folder' ? 'Folder' : 'File'; } function formatSize(value) { var size = Number(value); if (!Number.isFinite(size) || size <= 0) return ''; var units = ['B', 'KB', 'MB', 'GB']; var unit = 0; while (size >= 1024 && unit < units.length - 1) { size /= 1024; unit += 1; } return (unit === 0 ? String(Math.round(size)) : size.toFixed(size >= 10 ? 0 : 1)) + ' ' + units[unit]; } function formatDate(value) { var date = new Date(value); if (Number.isNaN(date.getTime())) return text(value); return new Intl.DateTimeFormat('en-GB', { year: 'numeric', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }).format(date); } function errorText(error) { return error && error.message ? error.message : text(error); } function isConflict(error) { return /conflict:/i.test(errorText(error)); } function sortEntries(entries, mode) { return entries.slice().sort(function (left, right) { if (mode === 'date-asc') return text(left.deletedAt).localeCompare(text(right.deletedAt)) || text(left.trashId).localeCompare(text(right.trashId)); if (mode === 'name-asc') return nameFor(left).localeCompare(nameFor(right), undefined, { sensitivity: 'base' }); if (mode === 'type-asc') return typeLabel(left).localeCompare(typeLabel(right)) || nameFor(left).localeCompare(nameFor(right)); return text(right.deletedAt).localeCompare(text(left.deletedAt)) || text(right.trashId).localeCompare(text(left.trashId)); }); } var TrashView = { mount: function (containerEl, props, api) { injectStyles(); var state = { entries: [], workspace: '', query: '', type: '', sort: 'date-desc', loading: true, busyId: '', confirmingId: '', status: '', statusError: false, disposed: false }; function workspaceOptions() { var values = {}; state.entries.forEach(function (entry) { values[workspaceFor(entry)] = true; }); return Object.keys(values).sort(function (left, right) { return left.localeCompare(right); }); } function visibleEntries() { var query = state.query.trim().toLowerCase(); return sortEntries(state.entries.filter(function (entry) { if (state.workspace && workspaceFor(entry) !== state.workspace) return false; if (state.type && text(entry.originalType) !== state.type) return false; if (!query) return true; return [nameFor(entry), entry.originalPath, directoryFor(entry), workspaceFor(entry)].join(' ').toLowerCase().indexOf(query) !== -1; }), state.sort); } function statusText(entries) { if (state.loading) return 'Loading deleted items...'; if (state.status) return state.status; return entries.length === 1 ? '1 deleted item' : entries.length + ' deleted items'; } function renderConfirmation() { var entry = state.entries.find(function (item) { return item.trashId === state.confirmingId; }); if (!entry) return null; return el('div', { className: 'trash-confirm-backdrop', 'data-trash-confirm': entry.trashId }, [ el('section', { className: 'trash-confirm', role: 'dialog', 'aria-modal': 'true', 'aria-label': 'Delete permanently' }, [ el('h3', {}, ['Delete permanently?']), el('p', {}, ['This cannot be undone.']), el('p', { className: 'trash-confirm-path' }, [entry.originalPath || nameFor(entry)]), el('div', { className: 'trash-confirm-actions' }, [ el('button', { className: 'trash-btn', type: 'button', 'data-trash-confirm-cancel': entry.trashId, onClick: function () { state.confirmingId = ''; render(); } }, ['Cancel']), el('button', { className: 'trash-btn trash-btn-danger', type: 'button', disabled: state.busyId === entry.trashId, 'data-trash-confirm-delete': entry.trashId, onClick: function () { deletePermanently(entry); } }, [state.busyId === entry.trashId ? 'Deleting...' : 'Delete permanently']) ]) ]) ]); } function render() { var rows = visibleEntries(); containerEl.innerHTML = ''; containerEl.className = 'trash-root'; containerEl.setAttribute('data-plugin-id', PLUGIN_ID); var workspaceSelect = el('select', { className: 'trash-control trash-select', value: state.workspace, 'data-trash-filter-workspace': '', onChange: function (event) { state.workspace = event.target.value; render(); } }, [el('option', { value: '' }, ['All workspaces'])]); workspaceOptions().forEach(function (workspace) { workspaceSelect.appendChild(el('option', { value: workspace }, [workspace])); }); var typeSelect = el('select', { className: 'trash-control trash-select', value: state.type, 'data-trash-filter-type': '', onChange: function (event) { state.type = event.target.value; render(); } }, [ el('option', { value: '' }, ['All types']), el('option', { value: 'file' }, ['Files']), el('option', { value: 'folder' }, ['Folders']) ]); var sortSelect = el('select', { className: 'trash-control trash-select', value: state.sort, 'data-trash-sort': '', onChange: function (event) { state.sort = event.target.value; render(); } }, [ el('option', { value: 'date-desc' }, ['Deleted: newest']), el('option', { value: 'date-asc' }, ['Deleted: oldest']), el('option', { value: 'name-asc' }, ['Name']), el('option', { value: 'type-asc' }, ['Type']) ]); containerEl.appendChild(el('div', { className: 'trash-toolbar' }, [ el('span', { className: 'trash-title' }, ['Trash']), el('span', { className: 'trash-count' }, [state.entries.length + ' total']), el('span', { className: 'trash-spacer' }), el('input', { className: 'trash-control trash-search', type: 'search', value: state.query, placeholder: 'Filter name or path', 'data-trash-filter-search': '', onInput: function (event) { state.query = event.target.value; render(); } }), workspaceSelect, typeSelect, sortSelect, el('button', { className: 'trash-btn', type: 'button', onClick: loadEntries }, ['Refresh']) ])); containerEl.appendChild(el('div', { className: 'trash-status' + (state.statusError ? ' error' : ''), 'data-trash-status': '' }, [statusText(rows)])); var list = el('div', { className: 'trash-list', 'data-trash-list': '' }); list.appendChild(el('div', { className: 'trash-header' }, [ el('span', {}, ['Name']), el('span', {}, ['Workspace']), el('span', {}, ['Original path']), el('span', {}, ['Deleted']), el('span', {}, ['Type / size']), el('span', {}, ['Actions']) ])); if (state.loading) { list.appendChild(el('div', { className: 'trash-empty' }, ['Loading deleted items...'])); } else if (!rows.length) { list.appendChild(el('div', { className: 'trash-empty' }, [state.entries.length ? 'No deleted items match the current filters.' : 'Trash is empty.'])); } else { rows.forEach(function (entry) { var size = formatSize(entry.size); list.appendChild(el('div', { className: 'trash-row', 'data-trash-row': entry.trashId, 'data-trash-workspace': workspaceFor(entry) }, [ el('span', { className: 'trash-name', title: nameFor(entry) }, [nameFor(entry)]), el('span', { className: 'trash-meta', title: workspaceFor(entry) }, [workspaceFor(entry)]), el('span', { className: 'trash-meta trash-path', title: entry.originalPath || '' }, [entry.originalPath || directoryFor(entry)]), el('span', { className: 'trash-meta' }, [formatDate(entry.deletedAt)]), el('span', { className: 'trash-type' }, [typeLabel(entry) + (size ? ' - ' + size : '')]), el('div', { className: 'trash-actions' }, [ el('button', { className: 'trash-btn', type: 'button', disabled: state.busyId === entry.trashId, 'data-trash-restore': entry.trashId, onClick: function () { restoreEntry(entry); } }, [state.busyId === entry.trashId ? 'Restoring...' : 'Restore']), el('button', { className: 'trash-btn trash-btn-danger', type: 'button', disabled: state.busyId === entry.trashId, 'data-trash-delete': entry.trashId, onClick: function () { state.confirmingId = entry.trashId; render(); } }, ['Delete permanently']) ]) ])); }); } containerEl.appendChild(list); var confirmation = renderConfirmation(); if (confirmation) containerEl.appendChild(confirmation); } function restoreEntry(entry) { if (!entry || !entry.trashId || !api.files || typeof api.files.restoreTrash !== 'function') return; state.busyId = entry.trashId; state.status = ''; state.statusError = false; render(); api.files.restoreTrash(entry.trashId, { overwrite: false }).then(function () { if (state.disposed) return; state.entries = state.entries.filter(function (item) { return item.trashId !== entry.trashId; }); state.busyId = ''; state.status = 'Restored ' + nameFor(entry) + '.'; render(); }).catch(function (error) { if (state.disposed) return; state.busyId = ''; state.statusError = true; state.status = isConflict(error) ? 'Restore blocked: an item already exists at the original path. Nothing was overwritten.' : 'Restore failed: ' + errorText(error); render(); }); } function deletePermanently(entry) { if (!entry || !entry.trashId || !api.files || typeof api.files.deleteTrash !== 'function') return; state.busyId = entry.trashId; state.status = ''; state.statusError = false; render(); api.files.deleteTrash(entry.trashId).then(function () { if (state.disposed) return; state.entries = state.entries.filter(function (item) { return item.trashId !== entry.trashId; }); state.busyId = ''; state.confirmingId = ''; state.status = 'Permanently deleted ' + nameFor(entry) + '.'; render(); }).catch(function (error) { if (state.disposed) return; state.busyId = ''; state.statusError = true; state.status = 'Permanent delete failed: ' + errorText(error); render(); }); } function loadEntries() { if (!api.files || typeof api.files.listTrash !== 'function') { state.loading = false; state.statusError = true; state.status = 'Trash is unavailable for this plugin configuration.'; render(); return; } state.loading = true; state.status = ''; state.statusError = false; render(); api.files.listTrash().then(function (entries) { if (state.disposed) return; state.entries = Array.isArray(entries) ? entries : []; state.loading = false; render(); }).catch(function (error) { if (state.disposed) return; state.entries = []; state.loading = false; state.statusError = true; state.status = 'Could not load Trash: ' + errorText(error); render(); }); } loadEntries(); containerEl.__trashCleanup = function () { state.disposed = true; containerEl.innerHTML = ''; }; }, unmount: function (containerEl) { if (containerEl && typeof containerEl.__trashCleanup === 'function') containerEl.__trashCleanup(); } }; window.VerstakPluginRegister(PLUGIN_ID, { components: { TrashView: TrashView } }); })();