feat: SQLite preview read straight from the file, and sorting of the file table by column, kept per folder

This commit is contained in:
redtea committed 2026-10-14 18:46:32 +03:00
1 parent 1a90af7800
commit c911143eb2
2 files changed
+191 -10

No files matched your search

+48 -4
View File
@@ -45,6 +45,10 @@
.ic { display:inline-block; width:18px; text-align:center; margin-right:6px; }
.sz { color:var(--muted); font-variant-numeric:tabular-nums; }
.act { display:flex; gap:6px; justify-content:flex-end; }
/* Sortable headers: a click sorts by the column, a second one turns it round. */
table.fm th.srt { cursor:pointer; user-select:none; white-space:nowrap; }
table.fm th.srt:hover { color:var(--text); }
table.fm th.srt i { font-style:normal; display:inline-block; width:12px; margin-left:4px; color:var(--accent); font-size:10px; }
table.fm .c-sz { width:120px; }
table.fm .c-mt { width:160px; }
table.fm .c-act { width:160px; }
@@ -208,8 +212,8 @@
<table class="fm">
<thead><tr>
<th style="width:28px"><input type="checkbox" id="all"></th>
<th>Имя</th><th class="sz c-sz">Размер</th>
<th class="sz c-mt">Изменён</th><th class="c-act"></th>
<th class="srt" data-sort="name">Имя<i></i></th><th class="sz c-sz srt" data-sort="size">Размер<i></i></th>
<th class="sz c-mt srt" data-sort="mtime">Изменён<i></i></th><th class="c-act"></th>
</tr></thead>
<tbody id="list"></tbody>
</table>
@@ -326,10 +330,49 @@
$('path').value = cwd; render();
}
// Sorting: by name, size or time, folders always ahead of files. Kept in
// this browser for each folder of each machine.
const SORT_DEF = {by: 'name', dir: 1};
const sortKey = () => 'lpra.sort:' + agentID + ':' + cwd;
function getSort() {
try { const v = JSON.parse(localStorage.getItem(sortKey())); if (v && ['name', 'size', 'mtime'].includes(v.by)) return v; } catch (e) {}
return SORT_DEF;
}
function setSort(v) {
try {
if (v.by === SORT_DEF.by && v.dir === SORT_DEF.dir) localStorage.removeItem(sortKey());
else localStorage.setItem(sortKey(), JSON.stringify(v));
} catch (e) {}
}
const byName = (a, b) => a.name.localeCompare(b.name, undefined, {numeric: true, sensitivity: 'base'}) || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
function sorted() {
const s = getSort();
const key = s.by === 'size' ? e => e.size || 0 : s.by === 'mtime' ? e => e.mtime || 0 : null;
return entries.slice().sort((a, b) => {
if (a.is_dir !== b.is_dir) return a.is_dir ? -1 : 1;
const d = key ? key(a) - key(b) : 0;
return (d || byName(a, b)) * s.dir;
});
}
function showSort() {
const s = getSort();
document.querySelectorAll('table.fm th.srt').forEach(th => {
th.querySelector('i').textContent = th.dataset.sort === s.by ? (s.dir > 0 ? '▲' : '▼') : '';
});
}
document.querySelectorAll('table.fm th.srt').forEach(th => th.onclick = () => {
const s = getSort(), by = th.dataset.sort;
// A new column starts the way it is most often wanted: names A to Z,
// sizes and times biggest and newest first.
setSort(s.by === by ? {by, dir: -s.dir} : {by, dir: by === 'name' ? 1 : -1});
render();
});
function render() {
showSort();
const tb = $('list'); tb.innerHTML = '';
updateSel();
for (const e of entries) {
for (const e of sorted()) {
const tr = document.createElement('tr');
const isZip = !e.is_dir && /\.zip$/i.test(e.name);
const dlHref = '/api/panel/agents/' + encodeURIComponent(agentID) + '/fs/download?path=' + encodeURIComponent(join(cwd, e.name));
@@ -381,7 +424,7 @@
const IMG = /\.(png|jpe?g|gif|webp|bmp|ico|avif|svg)$/i, VID = /\.(mp4|m4v|webm|ogv|mov)$/i, AUD = /\.(mp3|wav|ogg|oga|m4a|aac|flac|opus)$/i;
const HEX_CAP = 64 * 1024 * 1024; // what the hex editor takes in memory
const DOC = /\.(pdf|docx?|odt|xlsx|ods|csv|tsv|html?)$/i;
const DOC = /\.(pdf|docx?|odt|xlsx|ods|csv|tsv|html?|db|sqlite3?|db3)$/i;
const DOC_CAP = 32 * 1024 * 1024; // the biggest document drawn here
// Highlighting by the file's name, then its extension, then its #! line.
@@ -564,6 +607,7 @@
: ext === 'xlsx' ? await DocView.xlsx(buf)
: ext === 'ods' ? await DocView.ods(buf)
: ext === 'doc' ? DocView.doc(buf)
: /^(db|sqlite3?|db3)$/.test(ext) ? DocView.sqlite(buf)
: DocView.csv(new TextDecoder().decode(buf), ext === 'tsv');
} catch (err) {
html = '<p class="dv-note">не удалось показать: ' + esc(err.message) + '</p>';