All checks were successful
Notify chart dev on beta cut / notify-chart-dev (push) Successful in 5s
A new HTML tool — browse — that lists the contents of any directory.
Designed for ZDDC archives but no ZDDC-specific filtering; just a
straight folder browser with expand/collapse, sort, and name filter.
Modes (auto-detected at page load):
- Online: when served by zddc-server at a folder URL, queries
the same URL with Accept: application/json to load the listing
and renders it. Auto-served as the default at any directory
under ZDDC_ROOT without an index.html (replacing the previous
minimal-HTML stub from directory.go).
- Local: 'Select Directory' button uses FileSystemAccessAPI to
pick any folder on disk; works in Chromium-based browsers.
Features (Phase 1 — what's in this commit):
- Tree view with lazy-loaded folders (children fetched on first
expand).
- Sort by name / size / extension / date (column header click).
- Filter by name substring (toolbar input).
- File click opens in a new tab — for server-backed pages,
routes through zddc-server's normal handler so .archive
redirects + apps cascade overrides + ACL all apply.
Phase 2 deferred:
- ZIP files inline expansion (treat archive entries as virtual
children).
- File preview popup (reuse shared/preview-lib.js).
- Extension multi-select filter.
Wiring:
- browse/ added to top-level ./build's per-tool list, embed
block, versions.txt, and the lockstep release commit + tag set.
All seven tools (archive, transmittal, classifier, mdedit,
landing, form, browse) advance together on stable cuts.
- shared/build-lib.sh: browse added to ZDDC_RELEASE_TOOLS and
verify_channel_links's per-tool loop.
- zddc/internal/apps/embed.go: //go:embed browse.html +
EmbeddedBytes("browse") case.
- zddc/internal/apps/availability.go: browse available at every
directory (same as archive).
- zddc/internal/apps/handler.go: MatchAppHTML routes
/<dir>/browse.html → 'browse'.
- zddc/internal/handler/directory.go: when a directory request
arrives with Accept: text/html and no index.html exists,
serve the embedded browse.html bytes (with a JSON-fallback
if the embedded slot is empty during bootstrap).
130 lines
4.6 KiB
JavaScript
130 lines
4.6 KiB
JavaScript
// loader.js — fetches directory entries for either source mode.
|
|
//
|
|
// Server mode: GET <urlPath> with Accept: application/json. zddc-server
|
|
// (and Caddy's built-in browse, which we mirror) returns an array of
|
|
// FileInfo {name, size, url, mod_time, mode, is_dir, is_symlink}.
|
|
//
|
|
// FS-API mode: enumerate a FileSystemDirectoryHandle's children. No
|
|
// network involved; works on local folders the user picked.
|
|
(function () {
|
|
'use strict';
|
|
|
|
var state = window.app.state;
|
|
|
|
function splitExt(name) {
|
|
var i = name.lastIndexOf('.');
|
|
if (i <= 0 || i === name.length - 1) return '';
|
|
return name.substring(i + 1).toLowerCase();
|
|
}
|
|
|
|
// Build a raw entry from the server's FileInfo shape.
|
|
function fromServerEntry(e) {
|
|
// Server returns directory names with a trailing "/". Strip
|
|
// it for display; the is_dir flag is the canonical signal.
|
|
var displayName = e.is_dir ? e.name.replace(/\/$/, '') : e.name;
|
|
return {
|
|
name: displayName,
|
|
isDir: e.is_dir,
|
|
size: e.size || 0,
|
|
modTime: e.mod_time ? new Date(e.mod_time) : null,
|
|
ext: e.is_dir ? '' : splitExt(displayName),
|
|
url: e.url || null,
|
|
// FS-API specific (null in server mode):
|
|
handle: null
|
|
};
|
|
}
|
|
|
|
// Build a raw entry from a FileSystemHandle.
|
|
async function fromHandle(handle) {
|
|
var name = handle.name;
|
|
var isDir = handle.kind === 'directory';
|
|
var size = 0;
|
|
var modTime = null;
|
|
if (!isDir) {
|
|
try {
|
|
var f = await handle.getFile();
|
|
size = f.size;
|
|
modTime = new Date(f.lastModified);
|
|
} catch (_e) {
|
|
// permission lost; leave size/modTime defaults
|
|
}
|
|
}
|
|
return {
|
|
name: name,
|
|
isDir: isDir,
|
|
size: size,
|
|
modTime: modTime,
|
|
ext: isDir ? '' : splitExt(name),
|
|
url: null,
|
|
handle: handle
|
|
};
|
|
}
|
|
|
|
// Fetch children of a directory in server mode.
|
|
// path must end with '/' so the request hits the directory route.
|
|
async function fetchServerChildren(path) {
|
|
if (!path.endsWith('/')) path += '/';
|
|
var resp = await fetch(path, {
|
|
headers: { 'Accept': 'application/json' },
|
|
credentials: 'same-origin'
|
|
});
|
|
if (!resp.ok) {
|
|
throw new Error('HTTP ' + resp.status + ' fetching ' + path);
|
|
}
|
|
var data = await resp.json();
|
|
if (!Array.isArray(data)) {
|
|
throw new Error('Unexpected response shape from ' + path);
|
|
}
|
|
return data.map(fromServerEntry);
|
|
}
|
|
|
|
// Enumerate a FileSystemDirectoryHandle's immediate children.
|
|
async function fetchFsChildren(dirHandle) {
|
|
var entries = [];
|
|
for await (var [_name, handle] of dirHandle.entries()) {
|
|
entries.push(await fromHandle(handle));
|
|
}
|
|
return entries;
|
|
}
|
|
|
|
// Probe whether THIS page is being served by zddc-server (or any
|
|
// server that responds to JSON listing requests). If so, switch to
|
|
// server mode automatically and load the current directory.
|
|
async function autoDetectServerMode() {
|
|
// Only attempt when running over http(s) and the location's
|
|
// path looks like a directory. Probing on file:// is pointless.
|
|
if (location.protocol !== 'http:' && location.protocol !== 'https:') {
|
|
return false;
|
|
}
|
|
// Strip any /<tool>.html from the path to get the directory.
|
|
var path = location.pathname;
|
|
// If the URL points at the browse.html itself, the directory
|
|
// is the parent. If it's a directory ending in '/', use it.
|
|
var dirPath;
|
|
if (path.endsWith('/')) {
|
|
dirPath = path;
|
|
} else {
|
|
// e.g. '/some/dir/browse.html' → '/some/dir/'
|
|
var slash = path.lastIndexOf('/');
|
|
dirPath = slash >= 0 ? path.substring(0, slash + 1) : '/';
|
|
}
|
|
|
|
try {
|
|
var entries = await fetchServerChildren(dirPath);
|
|
state.source = 'server';
|
|
state.currentPath = dirPath;
|
|
return { entries: entries, path: dirPath };
|
|
} catch (_e) {
|
|
// Not a server-backed page (e.g. opened via file://).
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Public API
|
|
window.app.modules.loader = {
|
|
fetchServerChildren: fetchServerChildren,
|
|
fetchFsChildren: fetchFsChildren,
|
|
autoDetectServerMode: autoDetectServerMode,
|
|
splitExt: splitExt
|
|
};
|
|
})();
|