release: v0.0.13 lockstep
This commit is contained in:
parent
7caf3ecf3f
commit
127163dfa2
9 changed files with 151 additions and 55 deletions
2
mdedit/dist/mdedit.html
vendored
2
mdedit/dist/mdedit.html
vendored
|
|
@ -1774,7 +1774,7 @@ body.help-open .app-header {
|
||||||
</svg>
|
</svg>
|
||||||
<div class="header-title-group">
|
<div class="header-title-group">
|
||||||
<span class="app-header__title">ZDDC Markdown</span>
|
<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>
|
</div>
|
||||||
<button id="select-directory" class="btn btn-primary" title="Select a Directory">Select Directory</button>
|
<button id="select-directory" class="btn btn-primary" title="Select a Directory">Select Directory</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -2113,7 +2113,7 @@ td[data-field="trackingNumber"] {
|
||||||
</svg>
|
</svg>
|
||||||
<div class="header-title-group">
|
<div class="header-title-group">
|
||||||
<span class="app-header__title">ZDDC Archive</span>
|
<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>
|
||||||
<button id="addDirectoryBtn" class="btn btn-primary">Add Local Directory</button>
|
<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>
|
<button id="refreshHeaderBtn" class="btn btn-secondary hidden" title="Refresh Data" style="font-size:1.1rem;">⟳</button>
|
||||||
|
|
|
||||||
|
|
@ -585,6 +585,13 @@ body {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.browse-table-wrap {
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar {
|
.toolbar {
|
||||||
|
|
@ -633,7 +640,16 @@ body {
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
background: var(--bg);
|
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 {
|
.browse-table thead th {
|
||||||
|
|
@ -775,7 +791,7 @@ body {
|
||||||
</svg>
|
</svg>
|
||||||
<div class="header-title-group">
|
<div class="header-title-group">
|
||||||
<span class="app-header__title">ZDDC Browse</span>
|
<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>
|
</div>
|
||||||
<button id="addDirectoryBtn" class="btn btn-primary">Select Directory</button>
|
<button id="addDirectoryBtn" class="btn btn-primary">Select Directory</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -796,8 +812,10 @@ body {
|
||||||
<li><b>Local</b> — click <i>Select Directory</i> to pick any folder
|
<li><b>Local</b> — click <i>Select Directory</i> to pick any folder
|
||||||
on your computer (Chromium-based browsers).</li>
|
on your computer (Chromium-based browsers).</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>Once loaded: click folders to expand, click headers to sort, type
|
<p>Once loaded: click a folder to expand it, <b>shift-click</b>
|
||||||
in the filter to narrow by name. Click any file to open it.</p>
|
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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -808,6 +826,7 @@ body {
|
||||||
placeholder="Filter by name (substring)..." />
|
placeholder="Filter by name (substring)..." />
|
||||||
<span class="toolbar__count" id="entryCount"></span>
|
<span class="toolbar__count" id="entryCount"></span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="browse-table-wrap">
|
||||||
<table class="browse-table" id="browseTable">
|
<table class="browse-table" id="browseTable">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|
@ -820,6 +839,7 @@ body {
|
||||||
<tbody id="browseTbody"></tbody>
|
<tbody id="browseTbody"></tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<div id="statusBar" class="status-bar"></div>
|
<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.
|
// Toggle a folder's expanded state. Loads children on first expand.
|
||||||
async function toggleFolder(nodeId) {
|
async function toggleFolder(nodeId) {
|
||||||
var n = state.nodes.get(nodeId);
|
var n = state.nodes.get(nodeId);
|
||||||
if (!n || !n.isDir) return;
|
if (!n || !n.isDir) return;
|
||||||
if (!n.expanded && !n.loaded) {
|
if (!n.expanded && !n.loaded) {
|
||||||
try {
|
await loadChildren(n);
|
||||||
var raw;
|
if (!n.loaded) return; // load failed
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
n.expanded = !n.expanded;
|
n.expanded = !n.expanded;
|
||||||
render();
|
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.
|
// Compute the URL/path for a node by walking parents.
|
||||||
function pathFor(node) {
|
function pathFor(node) {
|
||||||
var parts = [];
|
var parts = [];
|
||||||
|
|
@ -1734,6 +1813,8 @@ body {
|
||||||
setChildren: setChildren,
|
setChildren: setChildren,
|
||||||
render: render,
|
render: render,
|
||||||
toggleFolder: toggleFolder,
|
toggleFolder: toggleFolder,
|
||||||
|
expandSubtree: expandSubtree,
|
||||||
|
collapseSubtree: collapseSubtree,
|
||||||
setSort: function (key) {
|
setSort: function (key) {
|
||||||
if (state.sort.key === key) {
|
if (state.sort.key === key) {
|
||||||
state.sort.dir = -state.sort.dir;
|
state.sort.dir = -state.sort.dir;
|
||||||
|
|
@ -1834,21 +1915,36 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tree-row clicks (event delegation on tbody).
|
// 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');
|
var tbody = document.getElementById('browseTbody');
|
||||||
if (tbody) {
|
if (tbody) {
|
||||||
tbody.addEventListener('click', function (e) {
|
tbody.addEventListener('click', function (e) {
|
||||||
var row = e.target.closest('tr.tree-row');
|
var row = e.target.closest('tr.tree-row');
|
||||||
if (!row) return;
|
if (!row) return;
|
||||||
var isDir = row.dataset.isdir === 'true';
|
var isDir = row.dataset.isdir === 'true';
|
||||||
if (!isDir) {
|
if (!isDir) return;
|
||||||
// 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).
|
|
||||||
e.preventDefault();
|
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>
|
</svg>
|
||||||
<div class="header-title-group">
|
<div class="header-title-group">
|
||||||
<span class="app-header__title">ZDDC Classifier</span>
|
<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>
|
</div>
|
||||||
<button id="selectDirectoryBtn" class="btn btn-primary">Select Directory</button>
|
<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>
|
<button id="refreshBtn" class="btn btn-secondary hidden" title="Refresh and rescan directory">Refresh</button>
|
||||||
|
|
|
||||||
|
|
@ -866,7 +866,7 @@ body {
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="app-header__title">ZDDC Archive</span>
|
<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>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<button id="theme-btn" class="btn btn-secondary" title="Theme: auto (follows OS)" aria-label="Theme: auto (follows OS)">◐</button>
|
<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>
|
</svg>
|
||||||
<div class="header-title-group">
|
<div class="header-title-group">
|
||||||
<span class="app-header__title">ZDDC Markdown</span>
|
<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>
|
</div>
|
||||||
<button id="select-directory" class="btn btn-primary" title="Select a Directory">Select Directory</button>
|
<button id="select-directory" class="btn btn-primary" title="Select a Directory">Select Directory</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -2210,7 +2210,7 @@ dialog.modal--narrow {
|
||||||
</svg>
|
</svg>
|
||||||
<div class="header-title-group">
|
<div class="header-title-group">
|
||||||
<span class="app-header__title">ZDDC Transmittal</span>
|
<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>
|
||||||
<div class="app-header__spacer"></div>
|
<div class="app-header__spacer"></div>
|
||||||
<div class="app-header__icons">
|
<div class="app-header__icons">
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
# Generated by build.sh — do not edit. One <app>=<build label> per line.
|
# Generated by build.sh — do not edit. One <app>=<build label> per line.
|
||||||
archive=v0.0.12
|
archive=v0.0.13
|
||||||
transmittal=v0.0.12
|
transmittal=v0.0.13
|
||||||
classifier=v0.0.12
|
classifier=v0.0.13
|
||||||
mdedit=v0.0.12
|
mdedit=v0.0.13
|
||||||
landing=v0.0.12
|
landing=v0.0.13
|
||||||
form=v0.0.12
|
form=v0.0.13
|
||||||
browse=v0.0.12
|
browse=v0.0.13
|
||||||
|
|
|
||||||
|
|
@ -722,7 +722,7 @@ body.help-open .app-header {
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="app-header__title" id="form-title">ZDDC Form</span>
|
<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>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<button id="theme-btn" class="btn btn-secondary" title="Theme: auto (follows OS)" aria-label="Theme: auto (follows OS)">◐</button>
|
<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