feat: add download + copy file content to context menus and editor
FlowDeck CI / lint (push) Successful in 45s
FlowDeck CI / test (push) Successful in 4m8s
FlowDeck CI / docker (push) Successful in 38s

- Add /api/pages/{id}/download and /api/pages/{id}/file-content endpoints
- Add Download + Copy content to shared context menu (_ctx_menu.html)
- Wire handlers in local-workspace and library context menus
- Add Download + Copy content to editor '...' menu for file pages
- Multi-block selection copy/cut/paste with Ctrl+C/X/V shortcuts
- Align page title with blocks start (CSS pseudo-element offset)
This commit is contained in:
2026-09-12 12:53:17 -04:00
parent 3bb17eb984
commit 7a6c66a609
9 changed files with 297 additions and 9 deletions
+30
View File
@@ -1025,6 +1025,8 @@ window._wsInitData = (function() {
setIcon: function (icon) { self.setItemIcon(node, icon); },
duplicate: function (n) { self.duplicateItem(n); },
link: function (n) { self.copyItemLink(n); },
download: function (n) { self.downloadItem(n); },
copyContent: function (n) { self.copyItemContent(n); },
move: function (n) { self._ctxMove(n); },
fav: function (n) { self.toggleFavorite(n); },
delete: function (n) { self.deleteWithUndo(n); },
@@ -1035,6 +1037,34 @@ window._wsInitData = (function() {
store.openMenu(ev, node, 'workspace', handlers);
},
downloadItem(node) {
if (!node || node.is_folder) return;
var url = '/api/pages/' + node.id + '/download';
var a = document.createElement('a');
a.href = url;
a.style.display = 'none';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
if (window.showToast) window.showToast('Download started', 'success');
},
async copyItemContent(node) {
if (!node || node.is_folder) return;
try {
var r = await fetch('/api/pages/' + node.id + '/file-content');
var d = await r.json();
if (d.ok && d.content !== undefined) {
await navigator.clipboard.writeText(d.content);
if (window.showToast) window.showToast('Content copied to clipboard', 'success');
} else {
if (window.showToast) window.showToast('Not a text file', 'error');
}
} catch (e) {
if (window.showToast) window.showToast('Copy failed', 'error');
}
},
_ctxAvailTags(node) {
return (this.workspaceTags || []).filter(function(t) {
return !((node && node.tags) || []).some(function(nt) { return nt.id === t.id; });