Compare commits
2 commits
d6448159fa
...
127163dfa2
| Author | SHA1 | Date | |
|---|---|---|---|
| 127163dfa2 | |||
| 7caf3ecf3f |
14 changed files with 299 additions and 96 deletions
|
|
@ -5,6 +5,13 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.browse-table-wrap {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
|
|
@ -53,7 +60,16 @@
|
|||
border-collapse: collapse;
|
||||
font-size: 0.9rem;
|
||||
background: var(--bg);
|
||||
flex: 1;
|
||||
/* No flex:1 — tables don't reliably distribute extra height across
|
||||
rows the way flex columns do. With few rows we'd get tall rows
|
||||
that shrink as more children are loaded. The wrap div handles
|
||||
scrolling instead. */
|
||||
}
|
||||
|
||||
.browse-table tbody tr {
|
||||
/* Pin rows to a deterministic height so table layout never
|
||||
redistributes vertical space across them. */
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.browse-table thead th {
|
||||
|
|
|
|||
|
|
@ -80,21 +80,36 @@
|
|||
}
|
||||
|
||||
// Tree-row clicks (event delegation on tbody).
|
||||
// Click semantics on a folder row:
|
||||
// - plain click → toggle just this folder
|
||||
// - shift-click → recursive expand/collapse of the whole
|
||||
// subtree (matches common file-explorer
|
||||
// convention; e.g. Finder, VSCode tree,
|
||||
// Windows Explorer)
|
||||
// - alt-click → ALSO recursive (alt is sometimes the
|
||||
// expand-all key on Linux DEs; bind both
|
||||
// so muscle memory works either way)
|
||||
// File rows: let the <a> tag's natural target=_blank do its
|
||||
// job — don't intercept.
|
||||
var tbody = document.getElementById('browseTbody');
|
||||
if (tbody) {
|
||||
tbody.addEventListener('click', function (e) {
|
||||
var row = e.target.closest('tr.tree-row');
|
||||
if (!row) return;
|
||||
var isDir = row.dataset.isdir === 'true';
|
||||
if (!isDir) {
|
||||
// Let the <a> tag's natural target=_blank handle file
|
||||
// clicks. Don't intercept.
|
||||
return;
|
||||
}
|
||||
// Folder: toggle on chevron OR anywhere on the row except
|
||||
// the file link (no link in folder rows).
|
||||
if (!isDir) return;
|
||||
e.preventDefault();
|
||||
tree.toggleFolder(parseInt(row.dataset.id, 10));
|
||||
var id = parseInt(row.dataset.id, 10);
|
||||
if (e.shiftKey || e.altKey) {
|
||||
var node = state.nodes.get(id);
|
||||
if (node && node.expanded) {
|
||||
tree.collapseSubtree(id);
|
||||
} else {
|
||||
tree.expandSubtree(id);
|
||||
}
|
||||
} else {
|
||||
tree.toggleFolder(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -219,34 +219,93 @@
|
|||
}
|
||||
}
|
||||
|
||||
// Load a folder's children (lazy; idempotent re-loads).
|
||||
async function loadChildren(node) {
|
||||
if (node.loaded) return;
|
||||
try {
|
||||
var raw;
|
||||
if (state.source === 'server') {
|
||||
raw = await loader.fetchServerChildren(pathFor(node) + '/');
|
||||
} else if (state.source === 'fs') {
|
||||
raw = await loader.fetchFsChildren(node.handle);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
setChildren(node.id, raw);
|
||||
} catch (e) {
|
||||
window.app.modules.events.statusError(
|
||||
'Failed to load ' + node.name + ': ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle a folder's expanded state. Loads children on first expand.
|
||||
async function toggleFolder(nodeId) {
|
||||
var n = state.nodes.get(nodeId);
|
||||
if (!n || !n.isDir) return;
|
||||
if (!n.expanded && !n.loaded) {
|
||||
try {
|
||||
var raw;
|
||||
if (state.source === 'server') {
|
||||
var childPath = state.currentPath
|
||||
+ n.name + '/'; // server URLs are relative paths
|
||||
// Walk up the parent chain to build the full path.
|
||||
childPath = pathFor(n) + '/';
|
||||
raw = await loader.fetchServerChildren(childPath);
|
||||
} else if (state.source === 'fs') {
|
||||
raw = await loader.fetchFsChildren(n.handle);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
window.app.modules.tree.setChildren(nodeId, raw);
|
||||
} catch (e) {
|
||||
window.app.modules.events.statusError('Failed to load folder: ' + e.message);
|
||||
return;
|
||||
}
|
||||
await loadChildren(n);
|
||||
if (!n.loaded) return; // load failed
|
||||
}
|
||||
n.expanded = !n.expanded;
|
||||
render();
|
||||
}
|
||||
|
||||
// Recursive expand: load + expand all descendants of nodeId. Used
|
||||
// for Shift-click on a folder. Walks breadth-first, fanning out
|
||||
// through children, grand-children, etc. until every reachable
|
||||
// folder is loaded and marked expanded. Status bar shows progress
|
||||
// because deeply-nested trees can take a while.
|
||||
//
|
||||
// Parallelism: kept conservative (per-level fan-out) to avoid
|
||||
// hammering zddc-server with hundreds of concurrent listing
|
||||
// fetches. Browsers also throttle per-origin concurrency, but
|
||||
// queuing politely is friendlier than fighting that.
|
||||
async function expandSubtree(nodeId) {
|
||||
var root = state.nodes.get(nodeId);
|
||||
if (!root || !root.isDir) return;
|
||||
var status = window.app.modules.events.statusInfo;
|
||||
status('Expanding subtree…');
|
||||
var processed = 0;
|
||||
var queue = [root];
|
||||
while (queue.length) {
|
||||
var batch = queue;
|
||||
queue = [];
|
||||
// Load this level's children in parallel (Promise.all).
|
||||
await Promise.all(batch.map(function (n) { return loadChildren(n); }));
|
||||
for (var i = 0; i < batch.length; i++) {
|
||||
var n = batch[i];
|
||||
n.expanded = true;
|
||||
processed++;
|
||||
for (var j = 0; j < n.childIds.length; j++) {
|
||||
var c = state.nodes.get(n.childIds[j]);
|
||||
if (c && c.isDir) queue.push(c);
|
||||
}
|
||||
}
|
||||
// Re-render after each level so the user sees progress
|
||||
// rather than a long pause then a sudden full-tree dump.
|
||||
render();
|
||||
status('Expanding subtree… (' + processed + ' folders loaded)');
|
||||
}
|
||||
status('Expanded ' + processed + ' folder' + (processed === 1 ? '' : 's'));
|
||||
}
|
||||
|
||||
// Recursive collapse: mark this node and every descendant as
|
||||
// collapsed. Doesn't unload — if the user re-expands later, the
|
||||
// children are still in memory and re-render is instant.
|
||||
function collapseSubtree(nodeId) {
|
||||
var root = state.nodes.get(nodeId);
|
||||
if (!root || !root.isDir) return;
|
||||
function walk(n) {
|
||||
n.expanded = false;
|
||||
for (var i = 0; i < n.childIds.length; i++) {
|
||||
var c = state.nodes.get(n.childIds[i]);
|
||||
if (c && c.isDir) walk(c);
|
||||
}
|
||||
}
|
||||
walk(root);
|
||||
render();
|
||||
}
|
||||
|
||||
// Compute the URL/path for a node by walking parents.
|
||||
function pathFor(node) {
|
||||
var parts = [];
|
||||
|
|
@ -269,6 +328,8 @@
|
|||
setChildren: setChildren,
|
||||
render: render,
|
||||
toggleFolder: toggleFolder,
|
||||
expandSubtree: expandSubtree,
|
||||
collapseSubtree: collapseSubtree,
|
||||
setSort: function (key) {
|
||||
if (state.sort.key === key) {
|
||||
state.sort.dir = -state.sort.dir;
|
||||
|
|
|
|||
|
|
@ -43,8 +43,10 @@
|
|||
<li><b>Local</b> — click <i>Select Directory</i> to pick any folder
|
||||
on your computer (Chromium-based browsers).</li>
|
||||
</ul>
|
||||
<p>Once loaded: click folders to expand, click headers to sort, type
|
||||
in the filter to narrow by name. Click any file to open it.</p>
|
||||
<p>Once loaded: click a folder to expand it, <b>shift-click</b>
|
||||
to expand its entire subtree (or collapse it again),
|
||||
click column headers to sort, type in the filter to narrow
|
||||
by name. Click any file to open it.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -55,6 +57,7 @@
|
|||
placeholder="Filter by name (substring)..." />
|
||||
<span class="toolbar__count" id="entryCount"></span>
|
||||
</div>
|
||||
<div class="browse-table-wrap">
|
||||
<table class="browse-table" id="browseTable">
|
||||
<thead>
|
||||
<tr>
|
||||
|
|
@ -67,6 +70,7 @@
|
|||
<tbody id="browseTbody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div id="statusBar" class="status-bar"></div>
|
||||
|
|
|
|||
2
mdedit/dist/mdedit.html
vendored
2
mdedit/dist/mdedit.html
vendored
|
|
@ -1774,7 +1774,7 @@ body.help-open .app-header {
|
|||
</svg>
|
||||
<div class="header-title-group">
|
||||
<span class="app-header__title">ZDDC Markdown</span>
|
||||
<span class="build-timestamp">v0.0.12</span>
|
||||
<span class="build-timestamp">v0.0.13</span>
|
||||
</div>
|
||||
<button id="select-directory" class="btn btn-primary" title="Select a Directory">Select Directory</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2113,7 +2113,7 @@ td[data-field="trackingNumber"] {
|
|||
</svg>
|
||||
<div class="header-title-group">
|
||||
<span class="app-header__title">ZDDC Archive</span>
|
||||
<span class="build-timestamp">v0.0.12</span>
|
||||
<span class="build-timestamp">v0.0.13</span>
|
||||
</div>
|
||||
<button id="addDirectoryBtn" class="btn btn-primary">Add Local Directory</button>
|
||||
<button id="refreshHeaderBtn" class="btn btn-secondary hidden" title="Refresh Data" style="font-size:1.1rem;">⟳</button>
|
||||
|
|
|
|||
|
|
@ -585,6 +585,13 @@ body {
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.browse-table-wrap {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
|
|
@ -633,7 +640,16 @@ body {
|
|||
border-collapse: collapse;
|
||||
font-size: 0.9rem;
|
||||
background: var(--bg);
|
||||
flex: 1;
|
||||
/* No flex:1 — tables don't reliably distribute extra height across
|
||||
rows the way flex columns do. With few rows we'd get tall rows
|
||||
that shrink as more children are loaded. The wrap div handles
|
||||
scrolling instead. */
|
||||
}
|
||||
|
||||
.browse-table tbody tr {
|
||||
/* Pin rows to a deterministic height so table layout never
|
||||
redistributes vertical space across them. */
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.browse-table thead th {
|
||||
|
|
@ -775,7 +791,7 @@ body {
|
|||
</svg>
|
||||
<div class="header-title-group">
|
||||
<span class="app-header__title">ZDDC Browse</span>
|
||||
<span class="build-timestamp">v0.0.12</span>
|
||||
<span class="build-timestamp">v0.0.13</span>
|
||||
</div>
|
||||
<button id="addDirectoryBtn" class="btn btn-primary">Select Directory</button>
|
||||
</div>
|
||||
|
|
@ -796,8 +812,10 @@ body {
|
|||
<li><b>Local</b> — click <i>Select Directory</i> to pick any folder
|
||||
on your computer (Chromium-based browsers).</li>
|
||||
</ul>
|
||||
<p>Once loaded: click folders to expand, click headers to sort, type
|
||||
in the filter to narrow by name. Click any file to open it.</p>
|
||||
<p>Once loaded: click a folder to expand it, <b>shift-click</b>
|
||||
to expand its entire subtree (or collapse it again),
|
||||
click column headers to sort, type in the filter to narrow
|
||||
by name. Click any file to open it.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -808,6 +826,7 @@ body {
|
|||
placeholder="Filter by name (substring)..." />
|
||||
<span class="toolbar__count" id="entryCount"></span>
|
||||
</div>
|
||||
<div class="browse-table-wrap">
|
||||
<table class="browse-table" id="browseTable">
|
||||
<thead>
|
||||
<tr>
|
||||
|
|
@ -820,6 +839,7 @@ body {
|
|||
<tbody id="browseTbody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div id="statusBar" class="status-bar"></div>
|
||||
|
|
@ -1684,34 +1704,93 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
// Load a folder's children (lazy; idempotent re-loads).
|
||||
async function loadChildren(node) {
|
||||
if (node.loaded) return;
|
||||
try {
|
||||
var raw;
|
||||
if (state.source === 'server') {
|
||||
raw = await loader.fetchServerChildren(pathFor(node) + '/');
|
||||
} else if (state.source === 'fs') {
|
||||
raw = await loader.fetchFsChildren(node.handle);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
setChildren(node.id, raw);
|
||||
} catch (e) {
|
||||
window.app.modules.events.statusError(
|
||||
'Failed to load ' + node.name + ': ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle a folder's expanded state. Loads children on first expand.
|
||||
async function toggleFolder(nodeId) {
|
||||
var n = state.nodes.get(nodeId);
|
||||
if (!n || !n.isDir) return;
|
||||
if (!n.expanded && !n.loaded) {
|
||||
try {
|
||||
var raw;
|
||||
if (state.source === 'server') {
|
||||
var childPath = state.currentPath
|
||||
+ n.name + '/'; // server URLs are relative paths
|
||||
// Walk up the parent chain to build the full path.
|
||||
childPath = pathFor(n) + '/';
|
||||
raw = await loader.fetchServerChildren(childPath);
|
||||
} else if (state.source === 'fs') {
|
||||
raw = await loader.fetchFsChildren(n.handle);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
window.app.modules.tree.setChildren(nodeId, raw);
|
||||
} catch (e) {
|
||||
window.app.modules.events.statusError('Failed to load folder: ' + e.message);
|
||||
return;
|
||||
}
|
||||
await loadChildren(n);
|
||||
if (!n.loaded) return; // load failed
|
||||
}
|
||||
n.expanded = !n.expanded;
|
||||
render();
|
||||
}
|
||||
|
||||
// Recursive expand: load + expand all descendants of nodeId. Used
|
||||
// for Shift-click on a folder. Walks breadth-first, fanning out
|
||||
// through children, grand-children, etc. until every reachable
|
||||
// folder is loaded and marked expanded. Status bar shows progress
|
||||
// because deeply-nested trees can take a while.
|
||||
//
|
||||
// Parallelism: kept conservative (per-level fan-out) to avoid
|
||||
// hammering zddc-server with hundreds of concurrent listing
|
||||
// fetches. Browsers also throttle per-origin concurrency, but
|
||||
// queuing politely is friendlier than fighting that.
|
||||
async function expandSubtree(nodeId) {
|
||||
var root = state.nodes.get(nodeId);
|
||||
if (!root || !root.isDir) return;
|
||||
var status = window.app.modules.events.statusInfo;
|
||||
status('Expanding subtree…');
|
||||
var processed = 0;
|
||||
var queue = [root];
|
||||
while (queue.length) {
|
||||
var batch = queue;
|
||||
queue = [];
|
||||
// Load this level's children in parallel (Promise.all).
|
||||
await Promise.all(batch.map(function (n) { return loadChildren(n); }));
|
||||
for (var i = 0; i < batch.length; i++) {
|
||||
var n = batch[i];
|
||||
n.expanded = true;
|
||||
processed++;
|
||||
for (var j = 0; j < n.childIds.length; j++) {
|
||||
var c = state.nodes.get(n.childIds[j]);
|
||||
if (c && c.isDir) queue.push(c);
|
||||
}
|
||||
}
|
||||
// Re-render after each level so the user sees progress
|
||||
// rather than a long pause then a sudden full-tree dump.
|
||||
render();
|
||||
status('Expanding subtree… (' + processed + ' folders loaded)');
|
||||
}
|
||||
status('Expanded ' + processed + ' folder' + (processed === 1 ? '' : 's'));
|
||||
}
|
||||
|
||||
// Recursive collapse: mark this node and every descendant as
|
||||
// collapsed. Doesn't unload — if the user re-expands later, the
|
||||
// children are still in memory and re-render is instant.
|
||||
function collapseSubtree(nodeId) {
|
||||
var root = state.nodes.get(nodeId);
|
||||
if (!root || !root.isDir) return;
|
||||
function walk(n) {
|
||||
n.expanded = false;
|
||||
for (var i = 0; i < n.childIds.length; i++) {
|
||||
var c = state.nodes.get(n.childIds[i]);
|
||||
if (c && c.isDir) walk(c);
|
||||
}
|
||||
}
|
||||
walk(root);
|
||||
render();
|
||||
}
|
||||
|
||||
// Compute the URL/path for a node by walking parents.
|
||||
function pathFor(node) {
|
||||
var parts = [];
|
||||
|
|
@ -1734,6 +1813,8 @@ body {
|
|||
setChildren: setChildren,
|
||||
render: render,
|
||||
toggleFolder: toggleFolder,
|
||||
expandSubtree: expandSubtree,
|
||||
collapseSubtree: collapseSubtree,
|
||||
setSort: function (key) {
|
||||
if (state.sort.key === key) {
|
||||
state.sort.dir = -state.sort.dir;
|
||||
|
|
@ -1834,21 +1915,36 @@ body {
|
|||
}
|
||||
|
||||
// Tree-row clicks (event delegation on tbody).
|
||||
// Click semantics on a folder row:
|
||||
// - plain click → toggle just this folder
|
||||
// - shift-click → recursive expand/collapse of the whole
|
||||
// subtree (matches common file-explorer
|
||||
// convention; e.g. Finder, VSCode tree,
|
||||
// Windows Explorer)
|
||||
// - alt-click → ALSO recursive (alt is sometimes the
|
||||
// expand-all key on Linux DEs; bind both
|
||||
// so muscle memory works either way)
|
||||
// File rows: let the <a> tag's natural target=_blank do its
|
||||
// job — don't intercept.
|
||||
var tbody = document.getElementById('browseTbody');
|
||||
if (tbody) {
|
||||
tbody.addEventListener('click', function (e) {
|
||||
var row = e.target.closest('tr.tree-row');
|
||||
if (!row) return;
|
||||
var isDir = row.dataset.isdir === 'true';
|
||||
if (!isDir) {
|
||||
// Let the <a> tag's natural target=_blank handle file
|
||||
// clicks. Don't intercept.
|
||||
return;
|
||||
}
|
||||
// Folder: toggle on chevron OR anywhere on the row except
|
||||
// the file link (no link in folder rows).
|
||||
if (!isDir) return;
|
||||
e.preventDefault();
|
||||
tree.toggleFolder(parseInt(row.dataset.id, 10));
|
||||
var id = parseInt(row.dataset.id, 10);
|
||||
if (e.shiftKey || e.altKey) {
|
||||
var node = state.nodes.get(id);
|
||||
if (node && node.expanded) {
|
||||
tree.collapseSubtree(id);
|
||||
} else {
|
||||
tree.expandSubtree(id);
|
||||
}
|
||||
} else {
|
||||
tree.toggleFolder(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1376,7 +1376,7 @@ body.help-open .app-header {
|
|||
</svg>
|
||||
<div class="header-title-group">
|
||||
<span class="app-header__title">ZDDC Classifier</span>
|
||||
<span class="build-timestamp">v0.0.12</span>
|
||||
<span class="build-timestamp">v0.0.13</span>
|
||||
</div>
|
||||
<button id="selectDirectoryBtn" class="btn btn-primary">Select Directory</button>
|
||||
<button id="refreshBtn" class="btn btn-secondary hidden" title="Refresh and rescan directory">Refresh</button>
|
||||
|
|
|
|||
|
|
@ -866,7 +866,7 @@ body {
|
|||
</g>
|
||||
</svg>
|
||||
<span class="app-header__title">ZDDC Archive</span>
|
||||
<span class="build-timestamp">v0.0.12</span>
|
||||
<span class="build-timestamp">v0.0.13</span>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<button id="theme-btn" class="btn btn-secondary" title="Theme: auto (follows OS)" aria-label="Theme: auto (follows OS)">◐</button>
|
||||
|
|
|
|||
|
|
@ -1774,7 +1774,7 @@ body.help-open .app-header {
|
|||
</svg>
|
||||
<div class="header-title-group">
|
||||
<span class="app-header__title">ZDDC Markdown</span>
|
||||
<span class="build-timestamp">v0.0.12</span>
|
||||
<span class="build-timestamp">v0.0.13</span>
|
||||
</div>
|
||||
<button id="select-directory" class="btn btn-primary" title="Select a Directory">Select Directory</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2210,7 +2210,7 @@ dialog.modal--narrow {
|
|||
</svg>
|
||||
<div class="header-title-group">
|
||||
<span class="app-header__title">ZDDC Transmittal</span>
|
||||
<span class="build-timestamp">v0.0.12</span>
|
||||
<span class="build-timestamp">v0.0.13</span>
|
||||
</div>
|
||||
<div class="app-header__spacer"></div>
|
||||
<div class="app-header__icons">
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
# Generated by build.sh — do not edit. One <app>=<build label> per line.
|
||||
archive=v0.0.12
|
||||
transmittal=v0.0.12
|
||||
classifier=v0.0.12
|
||||
mdedit=v0.0.12
|
||||
landing=v0.0.12
|
||||
form=v0.0.12
|
||||
browse=v0.0.12
|
||||
archive=v0.0.13
|
||||
transmittal=v0.0.13
|
||||
classifier=v0.0.13
|
||||
mdedit=v0.0.13
|
||||
landing=v0.0.13
|
||||
form=v0.0.13
|
||||
browse=v0.0.13
|
||||
|
|
|
|||
|
|
@ -80,6 +80,13 @@ func ServeDirectory(cfg config.Config, w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
// Vary: Accept is critical — the same URL serves either the JSON
|
||||
// listing or the embedded browse.html depending on the Accept
|
||||
// header. Without Vary, browsers/CDNs cache one response and
|
||||
// serve it for the other Accept value, breaking browse.html's
|
||||
// auto-detect (which fetches the same URL with Accept: JSON).
|
||||
w.Header().Set("Vary", "Accept")
|
||||
|
||||
if strings.Contains(accept, "application/json") {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
|
|
@ -109,6 +116,10 @@ func ServeDirectory(cfg config.Config, w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.Header().Set("X-ZDDC-Source", "embedded:browse")
|
||||
w.Header().Set("Cache-Control", "public, max-age=300, must-revalidate")
|
||||
// no-cache here too — browse.html has session-tied content (the
|
||||
// directory listing it loads via fetch), and we want browser to
|
||||
// always re-validate so deployed-binary updates appear immediately
|
||||
// rather than after a 5-minute cache window.
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
_, _ = w.Write(body)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -722,7 +722,7 @@ body.help-open .app-header {
|
|||
</g>
|
||||
</svg>
|
||||
<span class="app-header__title" id="form-title">ZDDC Form</span>
|
||||
<span class="build-timestamp">v0.0.12</span>
|
||||
<span class="build-timestamp">v0.0.13</span>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<button id="theme-btn" class="btn btn-secondary" title="Theme: auto (follows OS)" aria-label="Theme: auto (follows OS)">◐</button>
|
||||
|
|
|
|||
Loading…
Reference in a new issue