diff --git a/browse/js/download.js b/browse/js/download.js index b489ff7..3ec7e70 100644 --- a/browse/js/download.js +++ b/browse/js/download.js @@ -183,8 +183,35 @@ } } + // Export a file converted to another format. Server-only: builds the + // sibling-extension URL (foo.docx → foo.md) and lets the browser pull it — + // zddc-server recognises the virtual path and converts on the fly, emitting + // Content-Disposition. fmt is a bare extension ("md" | "docx" | "html"). + function exportFile(node, fmt) { + if (!node || node.isDir) { + events().statusError('Not a file: ' + (node && node.name)); + return; + } + if (state.source !== 'server') { + events().statusError('Export to .' + fmt + ' needs a server connection'); + return; + } + var tree = window.app.modules.tree; + var path = tree && tree.pathFor ? tree.pathFor(node) : node.url; + if (!path) { + events().statusError('No path for ' + node.name); + return; + } + var url = path.replace(/\.[^./]+$/, '') + '.' + fmt; + var name = node.name.replace(/\.[^./]+$/, '') + '.' + fmt; + events().statusInfo('Exporting ' + name + '…'); + downloadUrl(name, url); + setTimeout(function () { events().statusClear(); }, 2500); + } + window.app.modules.download = { downloadFile: downloadFile, - downloadFolder: downloadFolder + downloadFolder: downloadFolder, + exportFile: exportFile }; })(); diff --git a/browse/js/menu-model.js b/browse/js/menu-model.js index 56bb50a..73366f1 100644 --- a/browse/js/menu-model.js +++ b/browse/js/menu-model.js @@ -46,6 +46,10 @@ function isServer() { return state.source === 'server'; } function appliesToFolderLike(node) { return !!(node && (node.isDir || node.isZip)); } function appliesToFile(node) { return !!(node && !node.isDir && !node.isZip); } + + // Formats the Export submenu offers for a file (server-side conversion): + // a file of one of these extensions can be exported as the other two. + var EXPORT_FORMATS = ['md', 'docx', 'html']; function cap() { return window.zddc && window.zddc.cap; } function canVerb(node, verb) { @@ -176,6 +180,32 @@ else d.downloadFile(ctx.node); } }, + { + // Export submenu: a folder offers ".zip" (both modes); a md/docx/html + // file offers the OTHER two formats (server-side conversion, so + // server mode only). A zip is already an archive — no Export. + id: 'export', group: 'io', surfaces: ['row'], + label: 'Export', + appliesTo: function (ctx) { + var n = ctx.node; + if (!n || n.virtual) return false; + if (n.isDir) return true; + if (n.isZip) return false; + return isServer() && EXPORT_FORMATS.indexOf((n.ext || '').toLowerCase()) !== -1; + }, + items: function (ctx) { + var n = ctx.node; + var d = window.app.modules.download; + if (!d) return []; + if (n.isDir) { + return [{ label: '.zip', action: function () { d.downloadFolder(n); } }]; + } + var cur = (n.ext || '').toLowerCase(); + return EXPORT_FORMATS.filter(function (f) { return f !== cur; }).map(function (fmt) { + return { label: '.' + fmt, action: function () { d.exportFile(n, fmt); } }; + }); + } + }, // ── create (folder rows + pane; NOT file rows) ── // Create actions are HIDDEN unless the user can create here (the @@ -361,7 +391,7 @@ } function toMenuItem(d, ctx) { - return { + var item = { label: resolve(d.label, ctx), accel: d.accel, danger: d.danger, @@ -370,9 +400,16 @@ disabled: function () { return !resolveBool(d.enabled, ctx, true); }, tooltip: function () { return resolveBool(d.enabled, ctx, true) ? '' : (resolve(d.tooltip, ctx) || ''); - }, - action: function () { if (d.action) d.action(ctx); } + } }; + // A descriptor with `items` becomes a submenu (resolved against the + // captured browse ctx); otherwise it's a normal action row. + if (d.items) { + item.items = function () { return resolve(d.items, ctx); }; + } else { + item.action = function () { if (d.action) d.action(ctx); }; + } + return item; } function project(surface, ctx) {