diff --git a/browse/js/preview-markdown.js b/browse/js/preview-markdown.js index f4abafa..a9b20cf 100644 --- a/browse/js/preview-markdown.js +++ b/browse/js/preview-markdown.js @@ -315,9 +315,12 @@ } var isZipMemberNode = util.isZipMemberNode; + var isEditableZipMember = util.isEditableZipMember; function canSave(node) { - if (isZipMemberNode(node)) return false; + // A .zddc.zip bundle member is saveable iff editable (elevated admin) — + // the server's ServeZipWrite is the gate; other zip members read-only. + if (isZipMemberNode(node)) return isEditableZipMember(node); // Server-computed authority gate. The listing's verbs string // tells us whether a PUT to this entry would be allowed — // false here means the file API would 403, so we mount in @@ -486,7 +489,7 @@ var sourceEl = document.createElement('span'); sourceEl.className = 'md-shell__source'; if (isZipMemberNode(node)) { - sourceEl.textContent = 'read-only (zip)'; + sourceEl.textContent = isEditableZipMember(node) ? 'config bundle' : 'read-only (zip)'; } else if (node.handle) { sourceEl.textContent = 'local'; } else if (node.url) { diff --git a/browse/js/preview-yaml.js b/browse/js/preview-yaml.js index 22f3903..47aad2e 100644 --- a/browse/js/preview-yaml.js +++ b/browse/js/preview-yaml.js @@ -53,9 +53,13 @@ } var isZipMemberNode = util.isZipMemberNode; + var isEditableZipMember = util.isEditableZipMember; function canSave(node) { - if (isZipMemberNode(node)) return false; + // A .zddc.zip bundle member is saveable iff editable (elevated admin); + // the server's ServeZipWrite is the real gate. Other zip members are + // read-only. + if (isZipMemberNode(node)) return isEditableZipMember(node); // Virtual .zddc placeholders are designed to be saved — a PUT // materializes the file from the synthetic body and the next // listing serves a real entry. Every other virtual node (per- @@ -444,7 +448,7 @@ var sourceEl = document.createElement('span'); sourceEl.className = 'md-shell__source'; - if (isZipMemberNode(node)) sourceEl.textContent = 'read-only (zip)'; + if (isZipMemberNode(node)) sourceEl.textContent = isEditableZipMember(node) ? 'config bundle' : 'read-only (zip)'; else if (node.handle) sourceEl.textContent = 'local'; else if (node.url) sourceEl.textContent = 'server'; diff --git a/browse/js/util.js b/browse/js/util.js index 4b2dbdd..526004b 100644 --- a/browse/js/util.js +++ b/browse/js/util.js @@ -90,6 +90,17 @@ return false; } + // isEditableZipMember reports whether node is a member of the .zddc.zip + // config bundle AND the session is elevated — the one case where the server + // accepts a write into a zip (ServeZipWrite, admin-gated). Every other zip + // member (content archives, or the bundle when not elevated) stays + // read-only. The server is the real gate; this just drives editor UX. + function isEditableZipMember(node) { + if (!node || !node.url || window.app.state.source !== 'server') return false; + if (!/\.zddc\.zip\//i.test(node.url)) return false; + return !!(window.zddc && window.zddc.elevation && window.zddc.elevation.isElevated()); + } + // Thrown by saveFile when the server rejects a write with 412 // Precondition Failed — the file changed under us since we loaded it. // Callers branch on `.status === 412` to open the conflict UI instead @@ -193,6 +204,7 @@ fetchAccessEmails: fetchAccessEmails, fmtSize: fmtSize, isZipMemberNode: isZipMemberNode, + isEditableZipMember: isEditableZipMember, saveFile: saveFile, saveCopy: saveCopy, ConflictError: ConflictError