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>
162 lines
8.8 KiB
JavaScript
162 lines
8.8 KiB
JavaScript
// shared/icons.js — minimal outline SVG sprite for ZDDC tools.
|
||
//
|
||
// Vendored from Lucide (https://lucide.dev, ISC). Only the 16
|
||
// file-type glyphs the browse tree maps to are bundled; total weight
|
||
// is ~4.5 KB of SVG path data. Each symbol viewBox is 0 0 24 24 with
|
||
// no stroke/fill attributes — those are applied at the call site via
|
||
// CSS so the icons inherit `currentColor` and tint with the theme.
|
||
//
|
||
// API:
|
||
// window.zddc.icons.inject() // mount sprite into <body> once
|
||
// window.zddc.icons.html('icon-foo') // → '<svg viewBox="0 0 24 24"><use href="#icon-foo"/></svg>'
|
||
// window.zddc.icons.ID // string set of valid symbol ids
|
||
//
|
||
// Callers concat html() output into innerHTML the same way they
|
||
// previously concat'd emoji glyphs. The injected sprite is hidden
|
||
// (`display:none` on the outer <svg>) so it costs zero layout.
|
||
//
|
||
// Why a sprite (rather than per-row inline paths): a hundred tree
|
||
// rows × 300 bytes of duplicated path data is 30 KB of churn on
|
||
// every re-render. With <use>, each row carries only a ~60-byte
|
||
// reference. The sprite is parsed once.
|
||
(function () {
|
||
'use strict';
|
||
|
||
if (!window.zddc) window.zddc = {};
|
||
if (window.zddc.icons) return;
|
||
|
||
// ── Sprite (Lucide outline glyphs, viewBox 24×24) ──────────────────────
|
||
// Concatenated from upstream lucide-static@1.16.0 SVGs; class/style
|
||
// attributes stripped. Order matches the icons-mapped block below
|
||
// so a diff against Lucide's source stays readable.
|
||
var SYMBOLS = ''
|
||
+ '<symbol id="icon-folder" viewBox="0 0 24 24">'
|
||
+ '<path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/>'
|
||
+ '</symbol>'
|
||
+ '<symbol id="icon-folder-archive" viewBox="0 0 24 24">'
|
||
+ '<circle cx="15" cy="19" r="2"/>'
|
||
+ '<path d="M20.9 19.8A2 2 0 0 0 22 18V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2h5.1"/>'
|
||
+ '<path d="M15 11v-1"/>'
|
||
+ '<path d="M15 17v-2"/>'
|
||
+ '</symbol>'
|
||
+ '<symbol id="icon-file" viewBox="0 0 24 24">'
|
||
+ '<path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"/>'
|
||
+ '<path d="M14 2v5a1 1 0 0 0 1 1h5"/>'
|
||
+ '</symbol>'
|
||
+ '<symbol id="icon-file-text" viewBox="0 0 24 24">'
|
||
+ '<path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"/>'
|
||
+ '<path d="M14 2v5a1 1 0 0 0 1 1h5"/>'
|
||
+ '<path d="M10 9H8"/><path d="M16 13H8"/><path d="M16 17H8"/>'
|
||
+ '</symbol>'
|
||
+ '<symbol id="icon-file-image" viewBox="0 0 24 24">'
|
||
+ '<path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"/>'
|
||
+ '<path d="M14 2v5a1 1 0 0 0 1 1h5"/>'
|
||
+ '<circle cx="10" cy="12" r="2"/>'
|
||
+ '<path d="m20 17-1.296-1.296a2.41 2.41 0 0 0-3.408 0L9 22"/>'
|
||
+ '</symbol>'
|
||
+ '<symbol id="icon-file-video" viewBox="0 0 24 24">'
|
||
+ '<path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"/>'
|
||
+ '<path d="M14 2v5a1 1 0 0 0 1 1h5"/>'
|
||
+ '<path d="M15.033 13.44a.647.647 0 0 1 0 1.12l-4.065 2.352a.645.645 0 0 1-.968-.56v-4.704a.645.645 0 0 1 .967-.56z"/>'
|
||
+ '</symbol>'
|
||
+ '<symbol id="icon-file-audio" viewBox="0 0 24 24">'
|
||
+ '<path d="M4 6.835V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.706.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2h-.343"/>'
|
||
+ '<path d="M14 2v5a1 1 0 0 0 1 1h5"/>'
|
||
+ '<path d="M2 19a2 2 0 0 1 4 0v1a2 2 0 0 1-4 0v-4a6 6 0 0 1 12 0v4a2 2 0 0 1-4 0v-1a2 2 0 0 1 4 0"/>'
|
||
+ '</symbol>'
|
||
+ '<symbol id="icon-file-archive" viewBox="0 0 24 24">'
|
||
+ '<path d="M13.659 22H18a2 2 0 0 0 2-2V8a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 14 2H6a2 2 0 0 0-2 2v11.5"/>'
|
||
+ '<path d="M14 2v5a1 1 0 0 0 1 1h5"/>'
|
||
+ '<path d="M8 12v-1"/><path d="M8 18v-2"/><path d="M8 7V6"/>'
|
||
+ '<circle cx="8" cy="20" r="2"/>'
|
||
+ '</symbol>'
|
||
+ '<symbol id="icon-file-spreadsheet" viewBox="0 0 24 24">'
|
||
+ '<path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"/>'
|
||
+ '<path d="M14 2v5a1 1 0 0 0 1 1h5"/>'
|
||
+ '<path d="M8 13h2"/><path d="M14 13h2"/><path d="M8 17h2"/><path d="M14 17h2"/>'
|
||
+ '</symbol>'
|
||
+ '<symbol id="icon-file-code" viewBox="0 0 24 24">'
|
||
+ '<path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"/>'
|
||
+ '<path d="M14 2v5a1 1 0 0 0 1 1h5"/>'
|
||
+ '<path d="M10 12.5 8 15l2 2.5"/>'
|
||
+ '<path d="m14 12.5 2 2.5-2 2.5"/>'
|
||
+ '</symbol>'
|
||
+ '<symbol id="icon-file-cog" viewBox="0 0 24 24">'
|
||
+ '<path d="M15 8a1 1 0 0 1-1-1V2a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8z"/>'
|
||
+ '<path d="M20 8v12a2 2 0 0 1-2 2h-4.182"/>'
|
||
+ '<path d="m3.305 19.53.923-.382"/>'
|
||
+ '<path d="M4 10.592V4a2 2 0 0 1 2-2h8"/>'
|
||
+ '<path d="m4.228 16.852-.924-.383"/>'
|
||
+ '<path d="m5.852 15.228-.383-.923"/>'
|
||
+ '<path d="m5.852 20.772-.383.924"/>'
|
||
+ '<path d="m8.148 15.228.383-.923"/>'
|
||
+ '<path d="m8.53 21.696-.382-.924"/>'
|
||
+ '<path d="m9.773 16.852.922-.383"/>'
|
||
+ '<path d="m9.773 19.148.922.383"/>'
|
||
+ '<circle cx="7" cy="18" r="3"/>'
|
||
+ '</symbol>'
|
||
+ '<symbol id="icon-file-pen" viewBox="0 0 24 24">'
|
||
+ '<path d="M12.659 22H18a2 2 0 0 0 2-2V8a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 14 2H6a2 2 0 0 0-2 2v9.34"/>'
|
||
+ '<path d="M14 2v5a1 1 0 0 0 1 1h5"/>'
|
||
+ '<path d="M10.378 12.622a1 1 0 0 1 3 3.003L8.36 20.637a2 2 0 0 1-.854.506l-2.867.837a.5.5 0 0 1-.62-.62l.836-2.869a2 2 0 0 1 .506-.853z"/>'
|
||
+ '</symbol>'
|
||
+ '<symbol id="icon-book-marked" viewBox="0 0 24 24">'
|
||
+ '<path d="M10 2v8l3-3 3 3V2"/>'
|
||
+ '<path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20"/>'
|
||
+ '</symbol>'
|
||
+ '<symbol id="icon-presentation" viewBox="0 0 24 24">'
|
||
+ '<path d="M2 3h20"/>'
|
||
+ '<path d="M21 3v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V3"/>'
|
||
+ '<path d="m7 21 5-5 5 5"/>'
|
||
+ '</symbol>'
|
||
+ '<symbol id="icon-ruler" viewBox="0 0 24 24">'
|
||
+ '<path d="M21.3 15.3a2.4 2.4 0 0 1 0 3.4l-2.6 2.6a2.4 2.4 0 0 1-3.4 0L2.7 8.7a2.41 2.41 0 0 1 0-3.4l2.6-2.6a2.41 2.41 0 0 1 3.4 0Z"/>'
|
||
+ '<path d="m14.5 12.5 2-2"/>'
|
||
+ '<path d="m11.5 9.5 2-2"/>'
|
||
+ '<path d="m8.5 6.5 2-2"/>'
|
||
+ '<path d="m17.5 15.5 2-2"/>'
|
||
+ '</symbol>'
|
||
+ '<symbol id="icon-globe" viewBox="0 0 24 24">'
|
||
+ '<circle cx="12" cy="12" r="10"/>'
|
||
+ '<path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/>'
|
||
+ '<path d="M2 12h20"/>'
|
||
+ '</symbol>'
|
||
// Lightweight outline chevron — used by the tree as the
|
||
// expand/collapse affordance. The single glyph rotates 90°
|
||
// via CSS to indicate the expanded state, so we only ship
|
||
// one path instead of two.
|
||
+ '<symbol id="icon-chevron-right" viewBox="0 0 24 24">'
|
||
+ '<path d="m9 18 6-6-6-6"/>'
|
||
+ '</symbol>';
|
||
|
||
var injected = false;
|
||
|
||
function inject() {
|
||
if (injected) return;
|
||
// insertAdjacentHTML on body parses the SVG namespace correctly
|
||
// across all modern browsers (innerHTML on a <div> wrapper has
|
||
// historically tripped over <symbol> in some engines).
|
||
var sprite = '<svg xmlns="http://www.w3.org/2000/svg" '
|
||
+ 'aria-hidden="true" style="position:absolute;width:0;height:0;'
|
||
+ 'overflow:hidden" focusable="false">'
|
||
+ SYMBOLS
|
||
+ '</svg>';
|
||
if (document.body) {
|
||
document.body.insertAdjacentHTML('afterbegin', sprite);
|
||
injected = true;
|
||
} else {
|
||
document.addEventListener('DOMContentLoaded', inject, { once: true });
|
||
}
|
||
}
|
||
|
||
// Produces the per-row markup callers concat into innerHTML.
|
||
// Bundles the size + stroke defaults inline so the SVG renders
|
||
// correctly even before the page CSS runs (e.g. mid-paint).
|
||
function html(symbolId) {
|
||
return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" '
|
||
+ 'stroke-width="2" stroke-linecap="round" stroke-linejoin="round" '
|
||
+ 'aria-hidden="true"><use href="#' + symbolId + '"/></svg>';
|
||
}
|
||
|
||
window.zddc.icons = { inject: inject, html: html };
|
||
})();
|