Commit graph

5 commits

Author SHA1 Message Date
3e8737b7c9 feat(browse): preview .docx/.xlsx + fix markdown-editor horizontal overflow
DOCX/XLSX preview: add renderDocx (docx-preview) and renderXlsx (SheetJS)
to shared/preview-lib.js — the natural home alongside renderTiff/
renderZipListing, reusable by every tool. browse dispatches Office files
to them in both the inline pane and the pop-out window via the existing
preview.isOffice() check, and browse/build.sh now bundles the
docx-preview + xlsx vendors. Renderers degrade to a friendly message if a
tool doesn't bundle the vendor.

Overflow fix: .md-shell used `grid-template-columns: 280px 1fr`. A bare
`1fr` is `minmax(auto, 1fr)`, whose `auto` floor is the editor's
min-content width (Toast UI's toolbar) — so the content track refused to
shrink and the whole shell overflowed #previewBody as the window narrowed
instead of the editor reflowing smaller. Switch both tracks to
minmax(0, …) in the CSS and in the three JS spots that rewrite the
columns on sidebar-drag, and give .md-shell__sidebar min-width: 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 08:55:14 -05:00
94b2e29448 feat(browse): SPA overhaul — context menu, YAML editor, icons, hovercard, deep links, autofilter
Major upgrade to the browse tool's UX, plus a few shared modules other
tools can adopt.

User-facing:
- Right-click context menu on tree rows AND empty pane space. Traditional
  file-manager grouping (Open / Download / New / Rename-Delete / Copy /
  Tree ops / View). Items stay visible but disabled when not applicable
  so muscle memory carries. Generic shared/context-menu.js framework
  supports normal items, toggles, submenus, separators, danger styling.
- YAML editor for .yaml / .yml / .zddc files (CodeMirror 5 vendored at
  shared/vendor/codemirror-yaml.min.*). js-yaml lint on every change
  for parse errors. For .zddc cascade files, an additional schema-aware
  lint pass flags unknown keys, bad enum values, and wrong types.
- Per-row drag-drop upload using webkitGetAsEntry (folder uploads work
  recursively). Per-row drop indicator; doc-level overlay still fires
  for blank-space drops at drop_target scopes.
- New folder / New markdown file context-menu items (server mode).
  Rename + Delete with native confirm() dialog. File-API helpers
  removeNode / renameNode use the existing PUT/POST/DELETE endpoints.
- Hover info card with the row's full metadata (ZDDC fields + filesystem
  info + path/URL). Interactive — mouse into it, drag-select text,
  Ctrl/Cmd-C or right-click → Copy. 200ms grace before dismiss.
- Autofilter input at the top of the tree pane. Same grammar as
  archive's column filters (zddc.filter.parse / matches). Filters
  files; folders without matches collapse out. Non-matching folders
  force-open visually when descendants match, without mutating the
  user's actual expand state.
- Two-line ZDDC label: title-first, tracking/rev/status as monospace
  meta below. Icon column anchors to the title line. Chevron is a
  Lucide outline `chevron-right` SVG, rotated 90° on `.expanded`.
- File-type Lucide icon sprite (shared/icons.js — 16 outline glyphs,
  ~5 KB). PDF / Word / Spreadsheet / Slides / Image / Video / Audio /
  CAD / Web / Config / Code / Archive get distinct icons; folders
  tinted with --primary.
- Header wraps gracefully at narrow viewports (shared/base.css
  flex-wrap + title min-width:0 ellipsis). Body becomes flex column
  in browse so a wrapping header doesn't break #appMain height.
- Markdown editor opens in WYSIWYG mode by default. YAML front-matter
  + TOC sidebar reworked: flexbox layout (single visible resizer
  between FM and TOC), both bodies overflow:auto for X+Y scrollbars.
- `?file=<path>` deep links open browse pre-positioned at a specific
  file. Multi-segment paths walk into subdirectories on the way.
  Auto-flips Show hidden when a segment is dot/underscore-prefixed.
- Refresh + show-hidden toggle preserve expansion / selection /
  preview pinning. Path-keyed snapshot survives a re-fetched listing.
- "Add Local Directory" → "Use Local Directory" across the four tools
  that have it (browse, archive, classifier, +transmittal comment).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 12:12:42 -05:00
2dc2d032a0 feat(archive,browse): treat .zip transmittal folders as folders + shared zip adapter
New shared/zip-source.js: a ZipDirectoryHandle / ZipFileHandle pair
that exposes a JSZip instance behind the File-System-Access surface
(values/entries/keys, getDirectoryHandle/getFileHandle, getFile) —
read-only, with a zip-slip guard. Mirrors shared/zddc-source.js's
HTTP polyfill. Wired into archive's and browse's build.sh (both
already bundle JSZip).

archive: a .zip whose name minus ".zip" parses as a transmittal-folder
name is now scanned as that transmittal folder. Offline, the zip is
opened in the browser (ZipDirectoryHandle) and its members enumerated
exactly like an uncompressed folder's files — table/export/hash paths
are unchanged (they go through file.handle.getFile()). Online, the
scanner recurses into the server's "<…>.zip/" virtual-directory
listing, so members come back as "<…>.zip/<member>" URLs the server
extracts on demand — no whole-zip download.

browse: the offline (file://) zip path is migrated onto the shared
adapter — expanding a .zip now opens it as a ZipDirectoryHandle and
its members become ordinary dir/file nodes handled by the normal
fetchFsChildren path (nested zips fall out by recursion). The bespoke
flat-entry walker (loadZipChildren / setZipDirChildren / zipEntries /
zipParentId / zipPath / _zipSyntheticDir) is gone — one zip
implementation repo-wide. Markdown members inside a zip are flagged
read-only (the ZipFileHandle refuses createWritable; server "<…>.zip/"
URLs 405 on PUT).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 12:29:14 -05:00
7d4d2dc9a2 feat(browse): two-pane shell + markdown plugin + grid mode (Phases A/B/C/D)
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>
2026-05-10 15:46:51 -05:00
424bf8e769 feat(browse): Phase 2 — preview popup, ZIP expansion, ext filter, breadcrumbs
Bundles Phase 2 polish + the user-requested header/breadcrumb work:

- Breadcrumbs replacing the plain currentPath span. Server mode
  renders linkified ancestor segments (each <a> navigates to that
  directory; the browser fetches browse.html, the new instance
  auto-loads the listing). FS-API mode renders the rootHandle name
  as a non-link (no ancestor handles to navigate). Both prefix the
  path with a 🏠 root icon. Trailing slash + bold-current segment
  match common file-explorer conventions.

- Subdued 'Select Directory' button in server mode. Once browse is
  serving a real directory listing, the local-folder switcher is
  available but visually quiet (btn--subtle: transparent, muted
  color). FS-API mode keeps the primary styling (it's how the user
  got there). New btn--subtle CSS class added to browse's tree.css.
  A refresh button (⟳) appears next to it in both modes; clicking
  it re-fetches the current root listing.

- Header consistency: browse now matches archive's header layout
  (refresh + help buttons in addition to theme on the right). Help
  is a placeholder for future help dialog wiring.

- File preview popup. Click a file row → opens a popup window with
  the file rendered. Plain types (PDF, HTML, image) load in
  iframes; TIFF + ZIP listings via shared/preview-lib.js's
  renderTiff / renderZipListing helpers; text via <pre>; unknown
  types → 'click Download' placeholder. Modifier-click (ctrl/cmd/
  shift) and middle-click still open the file in a new tab via the
  underlying <a target=_blank>. Single popup window is reused
  across multiple file clicks (matches archive's UX).

- ZIP inline expansion. .zip files have a chevron and act like
  folders in the tree. First expand fetches the zip bytes
  (server URL or FS handle or parent-zip read), parses with JSZip
  (auto-loaded from CDN), and synthesizes the entry tree. Nested
  directories within the zip lazy-expand on demand by re-walking
  the cached entry list at the right path prefix. Click on a
  zip-entry file opens the preview popup with bytes read from
  JSZip. Recursive expand-all skips zip archives by design — they
  can be very large, and explicit click-to-expand is safer.

- Extension multi-select filter. Toolbar now has a <select
  multiple> populated with extensions present in the current
  view. Filter is OR-of-selected; combined with the name filter
  it's AND-of-both. Folders pass through (so expanding a folder
  whose name doesn't match the ext filter still shows its file
  children that do match).
2026-05-03 20:39:49 -05:00