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
build-container / build (push) Successful in 20m19s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
004209ed4f
commit
cedd6845ce
11 files changed
+399
-64
No files matched your search
+22
-5
@@ -100,11 +100,23 @@ let cfg = { mail: '', podcast: 'https://podcast.necloud.us/', verse: 'https://ve
|
||||
// ---- Seafile client (via same-origin proxy)
|
||||
const sf = {
|
||||
token: () => store.get('sf_token'),
|
||||
async req(path, opts = {}) {
|
||||
const headers = { Authorization: 'Token ' + sf.token(), ...(opts.headers || {}) };
|
||||
const r = await fetch('/sf' + path, { ...opts, headers });
|
||||
/** A call to Seafile through the hub. Reads are retried; a slow or unreachable Seafile gives a clear message; an expired sign-in asks for the
|
||||
* password again in place (sfReauth) and then carries on, so you do not lose where you were. */
|
||||
async req(path, opts = {}, tries = 0) {
|
||||
const { _reauthed, timeout, ...rest } = opts, method = (rest.method || 'GET').toUpperCase(), idem = method === 'GET' || method === 'HEAD';
|
||||
const headers = { Authorization: 'Token ' + sf.token(), ...(rest.headers || {}) };
|
||||
let r;
|
||||
try { r = await fetch('/sf' + path, { ...rest, headers, signal: rest.signal || AbortSignal.timeout(timeout || 45000) }); }
|
||||
catch (e) {
|
||||
if (idem && tries < 2 && e.name !== 'AbortError' && navigator.onLine) { await sleep(600 * (tries + 1)); return sf.req(path, opts, tries + 1); }
|
||||
throw new Error(!navigator.onLine ? 'You are offline' : e.name === 'TimeoutError' ? 'Seafile took too long to answer. Try again in a moment.' : 'Could not reach the server');
|
||||
}
|
||||
if (r.status === 401 && !_reauthed) { await sfReauth(); return sf.req(path, { ...opts, _reauthed: true }); }
|
||||
if (r.status === 401) { store.del('sf_token'); throw new Error('Session expired, sign in again'); }
|
||||
if (!r.ok) throw new Error((await r.text().catch(() => '')).slice(0, 200) || 'Request failed (' + r.status + ')');
|
||||
if (!r.ok) {
|
||||
let m = (await r.text().catch(() => '')).slice(0, 200); try { const j = JSON.parse(m); m = j.error || j.error_msg || j.detail || m; } catch {}
|
||||
throw new Error(m || (r.status === 504 ? 'Seafile took too long to answer' : r.status >= 500 ? 'Seafile had a problem (' + r.status + ')' : 'Request failed (' + r.status + ')'));
|
||||
}
|
||||
const t = await r.text();
|
||||
try { return JSON.parse(t); } catch { return t; }
|
||||
},
|
||||
@@ -121,7 +133,12 @@ const sf = {
|
||||
},
|
||||
};
|
||||
|
||||
const toast = (m) => { const t = $('#toast'); t.textContent = m; t.classList.add('on'); clearTimeout(toast.t); toast.t = setTimeout(() => t.classList.remove('on'), 2600); };
|
||||
/** A short message at the bottom; with `action` ({ label, run }) it has a button (Undo, say) and stays a little longer. */
|
||||
const toast = (m, action) => {
|
||||
const t = $('#toast'); t.textContent = m; t.classList.toggle('act', !!action);
|
||||
if (action) { const b = document.createElement('button'); b.textContent = action.label; b.onclick = () => { t.classList.remove('on'); action.run(); }; t.appendChild(b); }
|
||||
t.classList.add('on'); clearTimeout(toast.t); toast.t = setTimeout(() => t.classList.remove('on'), action ? 9000 : 2600);
|
||||
};
|
||||
const ext = n => (n.split('.').pop() || '').toLowerCase();
|
||||
const EDIT_EXT = new Set(['docx', 'doc', 'odt', 'rtf', 'xlsx', 'xls', 'ods', 'csv', 'pptx', 'ppt', 'odp', 'txt']);
|
||||
const OFFICE_EXT =new Set(['docx', 'doc', 'odt', 'rtf', 'xlsx', 'xls', 'ods', 'csv', 'pptx', 'ppt', 'odp', 'txt', 'pdf']);
|
||||
|
||||
Reference in new issue
Block a user