feat(browse): turn DOCX/HTML/PDF buttons into anchor links
Right-click → "Copy Link Address" / "Open in New Tab" / "Save Link As" now work natively, so users can share the conversion URL or pick their own download path. The buttons are styled <a href> elements instead of <button>, with the `download` attribute set to the expected filename (foo.docx etc.) so a plain click still downloads. Click handler simplifies a lot: on clean buffer, the handler returns immediately and the browser handles the navigation. On dirty buffer, the handler intercepts, auto-saves, then re-fires the click — which re-enters the handler with dirty=false and falls through to the native navigation. No more JS fetch + blob + objectURL plumbing for the common path. Side effect: if the server returns 422 or 503, the browser shows the response body in the target tab. That's less polished than the previous toast, but it's also a more direct view of what the server actually said. The toast path stays in shared/zddc-source.js's downloadConverted helper for tools that prefer the JS-driven flow.
This commit is contained in:
parent
52a6f139bb
commit
9245017798
1 changed files with 54 additions and 43 deletions
|
|
@ -436,25 +436,33 @@
|
|||
sourceEl.textContent = 'server';
|
||||
}
|
||||
|
||||
// Download-as-{docx,html,pdf} buttons. Server-mode + .md only:
|
||||
// the server endpoint runs pandoc/chromium in a container and
|
||||
// returns the converted bytes. Click handlers wire up below
|
||||
// (after save() is defined) because they auto-save first when
|
||||
// the buffer is dirty.
|
||||
// Download-as-{docx,html,pdf} affordances. Server-mode + .md
|
||||
// only: the server endpoint runs pandoc/chromium in a
|
||||
// container and returns the converted bytes.
|
||||
//
|
||||
// These are real <a> elements with href + download attributes,
|
||||
// styled like buttons. That means right-click → "Copy link
|
||||
// address" / "Open in new tab" / "Save link as" all work
|
||||
// natively — users can share the conversion URL or download
|
||||
// through their preferred path. Click is intercepted only
|
||||
// when the buffer is dirty (auto-save first, then re-fire
|
||||
// the click so the browser fetches the saved bytes).
|
||||
var serverModeMd = window.app && window.app.state &&
|
||||
window.app.state.source === 'server' &&
|
||||
node.url && /\.md$/i.test(node.name);
|
||||
var convertBtns = [];
|
||||
if (serverModeMd && window.zddc && window.zddc.source &&
|
||||
typeof window.zddc.source.downloadConverted === 'function') {
|
||||
if (serverModeMd) {
|
||||
['docx', 'html', 'pdf'].forEach(function (fmt) {
|
||||
var btn = document.createElement('button');
|
||||
btn.className = 'btn btn-sm btn-secondary md-shell__download';
|
||||
btn.type = 'button';
|
||||
btn.textContent = fmt.toUpperCase();
|
||||
btn.title = 'Download as ' + fmt.toUpperCase();
|
||||
btn.dataset.fmt = fmt;
|
||||
convertBtns.push(btn);
|
||||
var a = document.createElement('a');
|
||||
a.className = 'btn btn-sm btn-secondary md-shell__download';
|
||||
a.href = node.url + '?convert=' + encodeURIComponent(fmt);
|
||||
a.download = node.name.replace(/\.md$/i, '') + '.' + fmt;
|
||||
a.textContent = fmt.toUpperCase();
|
||||
a.title = 'Download as ' + fmt.toUpperCase()
|
||||
+ ' (right-click to copy link or open in new tab)';
|
||||
a.dataset.fmt = fmt;
|
||||
a.rel = 'noopener';
|
||||
convertBtns.push(a);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -653,39 +661,42 @@
|
|||
}
|
||||
});
|
||||
|
||||
// Download-as-* click handlers. Auto-save when the buffer is
|
||||
// dirty so the converted file reflects what's on screen. If
|
||||
// the save fails the existing toast/status surfaces it; we
|
||||
// bail without firing the conversion.
|
||||
convertBtns.forEach(function (btn) {
|
||||
btn.addEventListener('click', async function () {
|
||||
var fmt = btn.dataset.fmt;
|
||||
if (currentInstance.dirty) {
|
||||
if (!writable) {
|
||||
if (window.zddc && window.zddc.toast) {
|
||||
window.zddc.toast(
|
||||
'This source is read-only — save a copy elsewhere first.',
|
||||
'error');
|
||||
}
|
||||
return;
|
||||
}
|
||||
btn.disabled = true;
|
||||
try { await save(); } finally { btn.disabled = false; }
|
||||
if (currentInstance.dirty) return; // save failed
|
||||
// Download-as-* click handlers. The anchors are real <a href>
|
||||
// links so right-click / middle-click / Copy Link Address all
|
||||
// work natively. The JS handler only steps in when the buffer
|
||||
// is dirty — auto-save first, then re-fire the click so the
|
||||
// browser fetches the just-saved bytes. After the click is
|
||||
// re-fired, currentInstance.dirty is false so the handler
|
||||
// is a no-op on the second pass and the native navigation
|
||||
// proceeds.
|
||||
convertBtns.forEach(function (a) {
|
||||
a.addEventListener('click', async function (e) {
|
||||
var fmt = a.dataset.fmt;
|
||||
if (!currentInstance.dirty) {
|
||||
// Clean — let the browser handle the click. The
|
||||
// server's response (DOCX/HTML/PDF bytes, 422,
|
||||
// 503, etc.) lands in whatever target the user
|
||||
// picked (current tab, new tab, save-as).
|
||||
return;
|
||||
}
|
||||
btn.disabled = true;
|
||||
try {
|
||||
statusEl.textContent = 'Converting to ' + fmt.toUpperCase() + '…';
|
||||
await window.zddc.source.downloadConverted(node.url, node.name, fmt);
|
||||
statusEl.textContent = 'Downloaded ' + fmt.toUpperCase();
|
||||
} catch (e) {
|
||||
statusEl.textContent = (e && e.message) || String(e);
|
||||
// Dirty: intercept, save, retry.
|
||||
e.preventDefault();
|
||||
if (!writable) {
|
||||
if (window.zddc && window.zddc.toast) {
|
||||
window.zddc.toast((e && e.message) || String(e), 'error');
|
||||
window.zddc.toast(
|
||||
'This source is read-only — save a copy elsewhere first.',
|
||||
'error');
|
||||
}
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
return;
|
||||
}
|
||||
statusEl.textContent = 'Saving before download…';
|
||||
try { await save(); } catch (_) { /* save() surfaces its own error */ }
|
||||
if (currentInstance.dirty) return; // save failed; toast already shown
|
||||
statusEl.textContent = 'Downloading ' + fmt.toUpperCase() + '…';
|
||||
// Re-trigger the click. dirty=false now so the handler
|
||||
// exits early on the second pass and the browser
|
||||
// processes the native navigation.
|
||||
a.click();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue