Reshape browse from "tree-as-table with popup preview" into a unified
file-experience tool with three layered behaviors:
Phase A — Two-pane shell
Phase B — Markdown plugin (Toast UI inline)
Phase C — Grid mode (classifier workflow)
Phase D — Deprecation banners on standalone classifier + mdedit
= Phase A: two-pane shell + lightweight preview plugins =
Browse's table view becomes a tree-pane on the left + preview-pane on
the right with a draggable resizer. Click a folder → expand inline.
Click a file → render in the right pane. The previous popup window
becomes an explicit "⤴ Pop out" button in the right-pane header for
users with a second monitor.
Preview rendering reuses shared/preview-lib.js (PDF iframe, image
<img>, TIFF, ZIP listing, text <pre>). Unknown types show a download
link. browse/js/preview.js refactored into renderInline (default) +
renderInPopup (Pop out button); both share the same plugin
dispatch logic.
Filter rows were already removed earlier this session. Sort columns
likewise — the tree is alphabetical by default; the underlying
setSort API still exists for future re-introduction.
= Phase B: markdown plugin =
New browse/js/preview-markdown.js: when a .md or .markdown file is
clicked, the right pane mounts a Toast UI editor (initial-value =
file contents) with a small toolbar containing Save + dirty indicator
+ status text. Save sends PUT through the file API for server-mode
files; non-server sources are read-only for now (deferred to a
follow-up that wires zddc-source.js writes too). Ctrl+S / Cmd+S
inside the editor saves.
Toast UI Editor (~700 KB JS + ~160 KB CSS) was previously bundled
only in mdedit/vendor/. Moved to shared/vendor/ so browse and mdedit
both pull from one location.
= Phase C: grid mode =
View-mode toggle [Browse | Grid] in the toolbar. Grid mode loads the
classifier tool as an iframe scoped to the current directory (server
mode at working/staging/incoming locations) — classifier's full
bulk-rename workflow without leaving browse. v1 implementation; a
future iteration could bundle classifier's modules directly into
browse for tighter integration. Hostile cases (file:// origin, paths
outside working/staging/incoming) show a friendly explanation
instead of a blank iframe.
new browse/js/grid.js handles the activation logic.
= Phase D: deprecation banners =
mdedit and classifier standalones gain a "this tool is being absorbed
into Browse" advisory banner. Both standalones remain fully
functional and continue to ship — they're useful for offline single-
file editing and air-gapped environments. The banner just points
users toward the unified browse experience.
= Files =
+ browse/js/preview-markdown.js (markdown plugin)
+ browse/js/grid.js (grid-mode plugin)
M browse/template.html (two-pane layout, view toggle, banners)
M browse/css/tree.css (two-pane CSS, replaces table styles)
M browse/js/init.js (state additions: selectedId, viewMode)
M browse/js/tree.js (rowHtml: <tr>+<td> → <div>)
M browse/js/preview.js (renderInline / renderInPopup split)
M browse/js/events.js (toggle wiring, resizer, click handlers
adapted from <table> to <div>)
M browse/build.sh (Toast UI vendor + new modules)
R mdedit/vendor/toastui-* → shared/vendor/ (one bundle, two tools)
M mdedit/build.sh (paths)
M mdedit/template.html (deprecation banner)
M classifier/template.html (deprecation banner)
M tests/browse.spec.js (selectors updated for new layout +
new "click file → preview" test)
Bundle sizes after this commit:
browse: ~1020 KB (was ~290 KB; added Toast UI ~700 KB)
classifier: ~1470 KB (unchanged from prior baseline)
mdedit: ~2140 KB (unchanged; vendor location moved but not added)
What's deferred:
- TOC + front-matter pane in browse's markdown plugin (mdedit has
these; browse v1 uses just the editor).
- FS-API writes from browse's markdown plugin (server PUT works).
- Classifier modules bundled directly into browse (v1 uses iframe).
- Sort UI in the new tree (model still supports it; no widget yet).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
104 lines
4.4 KiB
JavaScript
104 lines
4.4 KiB
JavaScript
// grid.js — "Grid mode" plugin for browse. Activated by the
|
|
// view-mode toggle in the toolbar. Loads the standalone classifier
|
|
// tool as an iframe scoped to the current directory; the user gets
|
|
// classifier's full bulk-rename workflow without leaving browse.
|
|
//
|
|
// This is a v1 — a future iteration could bundle classifier's
|
|
// modules directly into browse for tighter integration (shared
|
|
// state, no iframe chrome). For now the iframe is a clean separation
|
|
// that preserves classifier's full feature set.
|
|
//
|
|
// Iframe src resolution:
|
|
// - server mode: <currentDirURL>/classifier.html. classifier is
|
|
// auto-served at any working/staging/incoming subtree per
|
|
// zddc-server's apps/availability.go. Outside those locations the
|
|
// iframe will 404 — we surface a friendly message instead of an
|
|
// opaque blank page.
|
|
// - file:// or unknown: show a "switch to server mode for grid"
|
|
// hint. classifier needs FS-API access; embedding it via file://
|
|
// iframe is blocked by browser security.
|
|
(function () {
|
|
'use strict';
|
|
|
|
var state = window.app.state;
|
|
var mounted = false;
|
|
|
|
function escapeHtml(s) {
|
|
return String(s).replace(/&/g, '&').replace(/</g, '<')
|
|
.replace(/>/g, '>').replace(/"/g, '"');
|
|
}
|
|
|
|
function classifierAvailableHere() {
|
|
// classifier auto-serves under any path containing a segment
|
|
// named working / staging / incoming (case-insensitive).
|
|
// browse mode-toggle should reflect that.
|
|
var path = (window.location && window.location.pathname) || '';
|
|
return /\/(working|staging|incoming)(\/|$)/i.test(path);
|
|
}
|
|
|
|
function activate() {
|
|
var host = document.getElementById('gridView');
|
|
if (!host) return;
|
|
|
|
if (mounted) return;
|
|
|
|
host.innerHTML = '';
|
|
|
|
if (state.source !== 'server') {
|
|
host.innerHTML =
|
|
'<div class="grid-empty">'
|
|
+ '<h3 style="margin-bottom:0.5rem">Grid mode</h3>'
|
|
+ '<p>The classifier (bulk ZDDC rename) workflow runs as an embedded'
|
|
+ ' iframe and requires the page be served by zddc-server.</p>'
|
|
+ '<p>If you opened this file directly (file://), open the standalone'
|
|
+ ' <code>classifier.html</code> tool instead — it provides the same'
|
|
+ ' workflow against a local folder you pick from the file system.</p>'
|
|
+ '</div>';
|
|
return;
|
|
}
|
|
|
|
if (!classifierAvailableHere()) {
|
|
host.innerHTML =
|
|
'<div class="grid-empty">'
|
|
+ '<h3 style="margin-bottom:0.5rem">Grid mode</h3>'
|
|
+ '<p>The classifier (bulk ZDDC rename) workflow auto-serves at'
|
|
+ ' <code>working/</code>, <code>staging/</code>, and'
|
|
+ ' <code>incoming/</code> URLs. The current page'
|
|
+ ' (<code>' + escapeHtml(window.location.pathname) + '</code>) isn\'t'
|
|
+ ' inside any of those, so classifier isn\'t available here.</p>'
|
|
+ '<p>Navigate browse into a working/ or staging/ folder, then'
|
|
+ ' switch to Grid.</p>'
|
|
+ '</div>';
|
|
return;
|
|
}
|
|
|
|
// Compute the iframe src: current page's directory + classifier.html.
|
|
var pathname = window.location.pathname || '/';
|
|
if (!pathname.endsWith('/')) {
|
|
// Strip trailing /<file>.html or similar — keep up to the last "/".
|
|
var lastSlash = pathname.lastIndexOf('/');
|
|
pathname = lastSlash >= 0 ? pathname.substring(0, lastSlash + 1) : '/';
|
|
}
|
|
var src = pathname + 'classifier.html';
|
|
|
|
host.innerHTML = '';
|
|
var frame = document.createElement('iframe');
|
|
frame.src = src;
|
|
frame.title = 'ZDDC Classifier (Grid mode)';
|
|
frame.style.cssText = 'width:100%;height:100%;border:0;display:block;'
|
|
+ 'background:var(--bg);';
|
|
host.appendChild(frame);
|
|
mounted = true;
|
|
}
|
|
|
|
window.app.modules.grid = {
|
|
activate: activate,
|
|
// Hook the toggle button visibility / hint to the activation
|
|
// predicate so users at non-classifier paths see the button
|
|
// in a disabled state with explanation. Callers run this
|
|
// after the initial directory is loaded.
|
|
availableHere: function () {
|
|
return state.source === 'server' && classifierAvailableHere();
|
|
}
|
|
};
|
|
})();
|