Files: sturdier Seafile pass-through (timeouts, retries, stop on disconnect), in-place sign-in, saved folder lists, upload name clashes, undo and recycle bin, library search, zip downloads, share link options, text viewer and editor
build-container / build (push) Successful in 20m19s

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-22 09:16:40 -04:00
1 parent 004209ed4f
commit cedd6845ce
11 files changed
+399 -64

No files matched your search

+4 -14
View File
@@ -1,6 +1,6 @@
'use strict';
// ---- File actions
const PREVIEW = { img: 'jpg jpeg png gif webp svg bmp heic', pdf: 'pdf', video: 'mp4 mov m4v webm', audio: 'mp3 m4a wav flac ogg aac' };
const PREVIEW = { img: 'jpg jpeg png gif webp svg bmp heic avif', pdf: 'pdf', video: 'mp4 mov m4v webm', audio: 'mp3 m4a wav flac ogg aac opus', text: 'txt md markdown json csv tsv log xml yml yaml ini conf cfg toml js ts css html htm py sh sql env gitignore' };
const previewKind = n => Object.keys(PREVIEW).find(k => PREVIEW[k].split(' ').includes(ext(n)));
const FORM = { 'Content-Type': 'application/x-www-form-urlencoded' };
const encP = p => p.split('/').map(encodeURIComponent).join('/');
@@ -21,6 +21,7 @@ function openFile(repo, path, name, dir) {
async function previewFile(repo, path, name) {
addRecent(repo, path, name);
if (previewKind(name) === 'text') return textPreview(repo, path, name).catch(e => toast(e.message));
try {
const url = (await fileUrl(repo, path)) + '?hubinline=1', kind = previewKind(name);
const media = kind === 'img' ? `<img src="${url}" alt="${esc(name)}" style="max-width:100%;max-height:70dvh;border-radius:12px;display:block;margin:8px auto">`
@@ -32,19 +33,7 @@ async function previewFile(repo, path, name) {
} catch (e) { toast(e.message); }
}
async function shareLink(repo, path, isDir) {
const p = isDir ? path.replace(/\/?$/, '/') : path;
try {
let link;
try { link = (await sf.req('/api/v2.1/share-links/', { method: 'POST', headers: FORM, body: new URLSearchParams({ repo_id: repo, path: p }) })).link; }
catch { link = (await sf.req(`/api/v2.1/share-links/?repo_id=${encodeURIComponent(repo)}&path=${encodeURIComponent(p)}`))[0]?.link; }
if (!link) throw new Error('Could not create a share link');
const el = openSheet('Share link', p.split('/').filter(Boolean).pop() || '', `<input type="text" readonly id="sl" value="${esc(link)}" onfocus="this.select()">
<div class="bar"><button class="btn" id="cp1" style="flex:1;justify-content:center">${icon('copy')}Copy</button>${navigator.share ? '<button class="btn ghost" id="sh1" style="flex:1;justify-content:center">Share…</button>' : ''}</div>`);
el.querySelector('#cp1').onclick = async () => { try { await navigator.clipboard.writeText(link); toast('Link copied'); closeSheet(); } catch { el.querySelector('#sl').select(); toast('Press copy on the selected link'); } };
const sh = el.querySelector('#sh1'); if (sh) sh.onclick = () => navigator.share({ url: link }).catch(() => {});
} catch (e) { toast(e.message); }
}
const shareLink = (repo, path, isDir) => shareSheet(repo, path, isDir);
function fileSheet(repo, path, name, dir) {
const enc = encodeURIComponent, acts = [], ro = libPerm[repo] === 'r', it = { name, type: 'file' };
@@ -66,6 +55,7 @@ function dirSheet(repo, path, name, dir) {
actionSheet(name, 'Folder', [
{ label: 'Open', icon: 'files', run: () => { location.hash = '#/files/' + encodeURIComponent(repo) + path.split('/').filter(Boolean).map(s => '/' + encodeURIComponent(s)).join(''); } },
{ label: 'Share link', icon: 'copy', run: () => shareLink(repo, path, true) },
{ label: 'Download as zip', icon: 'dl', run: () => zipDownload(repo, dir, [name]) },
...(ro ? [] : [{ label: 'Rename', icon: 'edit', run: () => renameItem(repo, path, name, dir, true) }, { label: 'Move to…', icon: 'files', run: () => moveSheet('move', repo, dir, [it]) }]),
{ label: 'Copy to…', icon: 'copy', run: () => moveSheet('copy', repo, dir, [it]) },
...(ro ? [] : [{ label: 'Delete', icon: 'trash', danger: true, run: () => deleteItems(repo, dir, [it]) }]),