Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions public/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,59 @@ const fileSidebar = document.getElementById('file-sidebar');
const fileSidebarToggle = document.getElementById('file-sidebar-toggle');
const fileSidebarClose = document.getElementById('file-sidebar-close');
const fileSidebarUp = document.getElementById('file-sidebar-up');
const fileSidebarSort = document.getElementById('file-sidebar-sort');
const fileSidebarSearch = document.getElementById('file-sidebar-search');
const fileList = document.getElementById('file-list');
const fileSidebarPath = document.getElementById('file-sidebar-path');
const fileBrowser = new FileBrowser(fileList, fileSidebarPath, messageInput, (filePath) => {
const name = filePath.split(/[/\\]/).pop() || filePath;
const ext = name.split('.').pop()?.toLowerCase() || '';
pendingFilePaths.push({ path: filePath, name, ext });
renderAttachmentPreviews();
}, fileSidebarSearch);

const fileSortDropdown = document.getElementById('file-sort-dropdown');
const fileSortMenu = document.getElementById('file-sort-menu');
const SORT_LABELS = { name: 'Name', date: 'Date', size: 'Size' };

function updateSortMenuActive() {
fileSortMenu.querySelectorAll('.model-dropdown-item').forEach(el => {
el.classList.toggle('active', el.dataset.sort === fileBrowser.sortBy);
});
}

function closeSortMenu() {
fileSortMenu.classList.add('hidden');
fileSortDropdown.classList.remove('open');
}

fileSidebarSort.title = `Sort: ${SORT_LABELS[fileBrowser.sortBy]}`;
fileSidebarSort.addEventListener('click', (e) => {
e.stopPropagation();
const isOpen = !fileSortMenu.classList.contains('hidden');
if (isOpen) {
closeSortMenu();
} else {
updateSortMenuActive();
fileSortMenu.classList.remove('hidden');
fileSortDropdown.classList.add('open');
}
});

fileSortMenu.querySelectorAll('.model-dropdown-item').forEach(el => {
el.addEventListener('click', () => {
fileBrowser.setSortBy(el.dataset.sort);
fileSidebarSort.title = `Sort: ${SORT_LABELS[el.dataset.sort]}`;
closeSortMenu();
});
});

document.addEventListener('click', (e) => {
if (!fileSortDropdown.contains(e.target)) closeSortMenu();
});

fileSidebarSearch.addEventListener('input', () => {
fileBrowser.setSearchQuery(fileSidebarSearch.value);
});

fileSidebarToggle.addEventListener('click', () => {
Expand Down
46 changes: 43 additions & 3 deletions public/file-browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,29 @@ function formatSize(bytes) {
return `${(bytes / (1024 * 1024)).toFixed(1)}M`;
}

const SORT_MODES = ['name', 'date', 'size'];

export class FileBrowser {
constructor(container, pathEl, messageInput, onFileInserted = null) {
constructor(container, pathEl, messageInput, onFileInserted = null, searchInput = null) {
this.container = container;
this.pathEl = pathEl;
this.messageInput = messageInput;
this.onFileInserted = onFileInserted;
this.searchInput = searchInput;
this.currentPath = null;
this.items = [];
this.searchQuery = '';
this.sortBy = SORT_MODES.includes(localStorage.getItem('tau-file-sort'))
? localStorage.getItem('tau-file-sort')
: 'name';

this.setupDropTarget();
}

async load(dirPath) {
this.container.innerHTML = '<div class="file-loading">Loading…</div>';
this.searchQuery = '';
if (this.searchInput) this.searchInput.value = '';

try {
const url = dirPath
Expand All @@ -66,12 +76,41 @@ export class FileBrowser {
this.currentPath = data.path;
this.pathEl.textContent = data.path;
this.pathEl.title = data.path;
this.render(data.items);
this.items = data.items;
this.render(this.getVisibleItems());
} catch (err) {
this.container.innerHTML = '<div class="file-loading">Failed to load</div>';
}
}

getVisibleItems() {
const query = this.searchQuery.trim().toLowerCase();
const filtered = query
? this.items.filter(item => item.name.toLowerCase().includes(query))
: this.items;

const items = [...filtered];
items.sort((a, b) => {
if (a.isDirectory !== b.isDirectory) return a.isDirectory ? -1 : 1;
if (this.sortBy === 'date') return b.mtime - a.mtime;
if (this.sortBy === 'size') return (b.size || 0) - (a.size || 0);
return a.name.localeCompare(b.name);
});
return items;
}

setSearchQuery(query) {
this.searchQuery = query;
this.render(this.getVisibleItems());
}

setSortBy(key) {
if (!SORT_MODES.includes(key)) return;
this.sortBy = key;
localStorage.setItem('tau-file-sort', key);
if (this.items.length) this.render(this.getVisibleItems());
}

getParentPath() {
if (!this.currentPath) return null;
const sep = this.currentPath.includes('\\') ? '\\' : '/';
Expand All @@ -86,7 +125,8 @@ export class FileBrowser {
this.container.innerHTML = '';

if (items.length === 0) {
this.container.innerHTML = '<div class="file-loading">Empty directory</div>';
const message = this.searchQuery ? 'No matches' : 'Empty directory';
this.container.innerHTML = `<div class="file-loading">${message}</div>`;
return;
}

Expand Down
19 changes: 16 additions & 3 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,12 @@
<span class="status-indicator" id="status-indicator"></span>
<span class="status-text" id="status-text">Connecting...</span>
</div>
<button class="icon-btn" id="file-sidebar-toggle" title="Files" aria-label="Toggle file browser">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/></svg>
</button>
<button class="settings-btn" id="settings-btn" title="Settings" aria-label="Settings">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></svg>
</button>
<button class="icon-btn" id="file-sidebar-toggle" title="Files" aria-label="Toggle file browser">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/></svg>
</button>
</div>
</div>

Expand Down Expand Up @@ -157,8 +157,21 @@
<button class="icon-btn" id="file-sidebar-finder" title="Open in file manager">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2"/></svg>
</button>
<div class="file-sort-dropdown" id="file-sort-dropdown">
<button class="icon-btn" id="file-sidebar-sort" title="Sort: Name">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 5h10"/><path d="M11 9h7"/><path d="M11 13h4"/><path d="m3 17 3 3 3-3"/><path d="M6 18V4"/></svg>
</button>
<div class="model-dropdown-menu file-sort-menu hidden" id="file-sort-menu">
<div class="model-dropdown-item" data-sort="name">Name</div>
<div class="model-dropdown-item" data-sort="date">Date</div>
<div class="model-dropdown-item" data-sort="size">Size</div>
</div>
</div>
<button class="icon-btn" id="file-sidebar-close" title="Close" aria-label="Close file browser"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></button>
</div>
<div class="file-sidebar-search-row">
<input type="text" id="file-sidebar-search" class="sidebar-search-input" placeholder="Search files..." autocomplete="off" />
</div>
<div class="file-sidebar-path" id="file-sidebar-path" title=""></div>
<div class="file-list" id="file-list"></div>
</div>
Expand Down
15 changes: 15 additions & 0 deletions public/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -3018,6 +3018,21 @@ body.hide-thinking .thinking-block { display: none !important; }
letter-spacing: 0.04em;
}

.file-sort-dropdown {
position: relative;
}

.file-sort-menu {
left: auto;
right: 0;
min-width: 110px;
}

.file-sidebar-search-row {
display: flex;
padding: 0 12px 8px;
}

.file-sidebar-path {
padding: 6px 12px;
font-size: 10px;
Expand Down