Bake into the dev binary: - ETag + max-age=0 on embedded HTML (304s on repeat loads) - gzip compression middleware (~75% wire-size reduction) - vendored jszip + docx-preview in archive/transmittal/classifier - tee'd file-based access log via --access-log
8571 lines
465 KiB
HTML
8571 lines
465 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>ZDDC Archive</title>
|
||
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+CiAgPHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiByeD0iMTIiIGZpbGw9IiMxZTNhNWYiLz4KICA8ZyBmaWxsPSIjZmZmIj4KICAgIDxyZWN0IHg9IjE0IiB5PSIxOCIgd2lkdGg9IjM2IiBoZWlnaHQ9IjciLz4KICAgIDxwb2x5Z29uIHBvaW50cz0iNDMsMjUgNTAsMjUgMjEsNDMgMTQsNDMiLz4KICAgIDxyZWN0IHg9IjE0IiB5PSI0MyIgd2lkdGg9IjM2IiBoZWlnaHQ9IjciLz4KICA8L2c+Cjwvc3ZnPgo=">
|
||
<style>
|
||
/* ==========================================================================
|
||
ZDDC Shared Base — single source of truth for tokens and primitives
|
||
Included first by every tool's build.sh via ../shared/base.css
|
||
========================================================================== */
|
||
|
||
/* ── CSS custom properties ────────────────────────────────────────────────── */
|
||
:root {
|
||
/* Brand / accent (matches zddc.varasys.io website --accent) */
|
||
--primary: #2a5a8a;
|
||
--primary-hover: #1d4060;
|
||
--primary-active: #163352;
|
||
--primary-light: #e8f0f7;
|
||
|
||
/* Semantic colours */
|
||
--success: #28a745;
|
||
--warning: #d97706;
|
||
--danger: #dc3545;
|
||
--info: #17a2b8;
|
||
|
||
/* Backgrounds */
|
||
--bg: #ffffff;
|
||
--bg-secondary: #f8f9fa;
|
||
--bg-hover: #f0f4f8;
|
||
--bg-selected: var(--primary-light);
|
||
|
||
/* Text */
|
||
--text: #212529;
|
||
--text-muted: #6c757d;
|
||
--text-light: #ffffff;
|
||
|
||
/* Borders */
|
||
--border: #dee2e6;
|
||
--border-dark: #adb5bd;
|
||
|
||
/* Shape */
|
||
--radius: 4px;
|
||
|
||
/* Typography */
|
||
--font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||
--font-mono: 'SF Mono', 'Fira Code', 'Consolas', 'Courier New', monospace;
|
||
}
|
||
|
||
/* ── Dark mode tokens ─────────────────────────────────────────────────────── */
|
||
/* Applied via: OS preference (auto) or [data-theme="dark"] on <html> */
|
||
/* The [data-theme="light"] selector locks light mode regardless of OS pref. */
|
||
@media (prefers-color-scheme: dark) {
|
||
:root:not([data-theme="light"]) {
|
||
--primary: #4a90c4;
|
||
--primary-hover: #5ba3d9;
|
||
--primary-active: #6ab5e8;
|
||
--primary-light: #1a3550;
|
||
|
||
--bg: #1e1e1e;
|
||
--bg-secondary: #252526;
|
||
--bg-hover: #2d2d30;
|
||
--bg-selected: #1a3550;
|
||
|
||
--text: #d4d4d4;
|
||
--text-muted: #9d9d9d;
|
||
--text-light: #ffffff;
|
||
|
||
--border: #3e3e42;
|
||
--border-dark: #6e6e72;
|
||
}
|
||
}
|
||
|
||
/* Manual dark override — wins over media query */
|
||
[data-theme="dark"] {
|
||
--primary: #4a90c4;
|
||
--primary-hover: #5ba3d9;
|
||
--primary-active: #6ab5e8;
|
||
--primary-light: #1a3550;
|
||
|
||
--bg: #1e1e1e;
|
||
--bg-secondary: #252526;
|
||
--bg-hover: #2d2d30;
|
||
--bg-selected: #1a3550;
|
||
|
||
--text: #d4d4d4;
|
||
--text-muted: #9d9d9d;
|
||
--text-light: #ffffff;
|
||
|
||
--border: #3e3e42;
|
||
--border-dark: #6e6e72;
|
||
}
|
||
|
||
/* ── Reset ────────────────────────────────────────────────────────────────── */
|
||
*, *::before, *::after {
|
||
box-sizing: border-box;
|
||
margin: 0;
|
||
padding: 0;
|
||
}
|
||
|
||
/* ── Base document ────────────────────────────────────────────────────────── */
|
||
html, body {
|
||
height: 100%;
|
||
font-family: var(--font);
|
||
font-size: 16px;
|
||
line-height: 1.5;
|
||
color: var(--text);
|
||
background-color: var(--bg-secondary);
|
||
}
|
||
|
||
/* ── Typography ───────────────────────────────────────────────────────────── */
|
||
h1, h2, h3, h4, h5, h6 {
|
||
font-weight: 600;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
a {
|
||
color: var(--primary);
|
||
text-decoration: none;
|
||
}
|
||
|
||
a:hover {
|
||
text-decoration: underline;
|
||
}
|
||
|
||
/* ── Utility ──────────────────────────────────────────────────────────────── */
|
||
.hidden {
|
||
display: none !important;
|
||
}
|
||
|
||
.truncate {
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
/* ── Scrollbars (webkit) ──────────────────────────────────────────────────── */
|
||
::-webkit-scrollbar {
|
||
width: 7px;
|
||
height: 7px;
|
||
}
|
||
|
||
::-webkit-scrollbar-track {
|
||
background: var(--bg-secondary);
|
||
}
|
||
|
||
::-webkit-scrollbar-thumb {
|
||
background: #c1c1c1;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
::-webkit-scrollbar-thumb:hover {
|
||
background: #a0a0a0;
|
||
}
|
||
|
||
/* ── Button primitive ─────────────────────────────────────────────────────── */
|
||
.btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.25rem;
|
||
padding: 0.4rem 0.85rem;
|
||
font-family: var(--font);
|
||
font-size: 0.875rem;
|
||
font-weight: 500;
|
||
line-height: 1.4;
|
||
text-align: center;
|
||
text-decoration: none;
|
||
white-space: nowrap;
|
||
vertical-align: middle;
|
||
cursor: pointer;
|
||
border: 1px solid transparent;
|
||
border-radius: var(--radius);
|
||
transition: background 0.15s, box-shadow 0.15s, border-color 0.15s, color 0.15s;
|
||
background: var(--bg-secondary);
|
||
color: var(--text);
|
||
}
|
||
|
||
.btn:disabled,
|
||
.btn[disabled] {
|
||
opacity: 0.5;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.btn:not(:disabled):hover {
|
||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
|
||
}
|
||
|
||
.btn:not(:disabled):active {
|
||
box-shadow: none;
|
||
}
|
||
|
||
/* Variants */
|
||
.btn-primary {
|
||
background: var(--primary);
|
||
color: var(--text-light);
|
||
border-color: var(--primary);
|
||
}
|
||
|
||
.btn-primary:not(:disabled):hover {
|
||
background: var(--primary-hover);
|
||
border-color: var(--primary-hover);
|
||
color: var(--text-light);
|
||
}
|
||
|
||
.btn-primary:not(:disabled):active {
|
||
background: var(--primary-active);
|
||
border-color: var(--primary-active);
|
||
}
|
||
|
||
.btn-secondary {
|
||
background: var(--bg);
|
||
color: var(--text);
|
||
border-color: var(--border);
|
||
}
|
||
|
||
.btn-secondary:not(:disabled):hover {
|
||
background: var(--bg-secondary);
|
||
}
|
||
|
||
/* Subdued / de-emphasized variant.
|
||
Used on the "Add Local Directory" button when a tool is operating
|
||
in server (online) mode — the local-dir affordance is still
|
||
available but visually quieter, since the typical user already
|
||
has the directory loaded from the server. */
|
||
.btn.btn--subtle {
|
||
background: transparent;
|
||
color: var(--text-muted);
|
||
border-color: var(--border);
|
||
box-shadow: none;
|
||
font-weight: normal;
|
||
}
|
||
|
||
.btn.btn--subtle:not(:disabled):hover {
|
||
color: var(--text);
|
||
background: var(--bg-secondary);
|
||
}
|
||
|
||
.btn-success {
|
||
background: var(--success);
|
||
color: var(--text-light);
|
||
border-color: var(--success);
|
||
}
|
||
|
||
.btn-danger {
|
||
background: var(--danger);
|
||
color: var(--text-light);
|
||
border-color: var(--danger);
|
||
}
|
||
|
||
/* Sizes */
|
||
.btn-sm {
|
||
padding: 0.25rem 0.5rem;
|
||
font-size: 0.75rem;
|
||
}
|
||
|
||
.btn-lg {
|
||
padding: 0.6rem 1.4rem;
|
||
font-size: 1rem;
|
||
}
|
||
|
||
.btn-link {
|
||
background: transparent;
|
||
border-color: transparent;
|
||
color: var(--primary);
|
||
padding-left: 0;
|
||
padding-right: 0;
|
||
}
|
||
|
||
.btn-link:not(:disabled):hover {
|
||
text-decoration: underline;
|
||
box-shadow: none;
|
||
}
|
||
|
||
/* ── App header chrome ────────────────────────────────────────────────────── */
|
||
.app-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 0.35rem 1rem;
|
||
background: var(--bg-secondary);
|
||
border-bottom: 1px solid var(--border);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* Tool name inside the header */
|
||
.app-header__title {
|
||
font-size: 17px;
|
||
font-weight: 600;
|
||
color: var(--text);
|
||
letter-spacing: 0.01em;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
/* Brand logo — sits left of the title in every tool's app-header.
|
||
Self-contained: the SVG provides its own dark blue rounded background,
|
||
so no extra wrapper styling is needed. */
|
||
.app-header__logo {
|
||
width: 26px;
|
||
height: 26px;
|
||
flex-shrink: 0;
|
||
display: block;
|
||
}
|
||
|
||
/* ── Build timestamp ──────────────────────────────────────────────────────── */
|
||
.build-timestamp {
|
||
font-size: 0.55rem;
|
||
color: var(--text-muted);
|
||
opacity: 0.7;
|
||
font-weight: 300;
|
||
white-space: nowrap;
|
||
padding-top: 0.15rem;
|
||
}
|
||
|
||
/* Title + timestamp stacked vertically on the left side of the header */
|
||
.header-title-group {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0;
|
||
line-height: 1;
|
||
}
|
||
|
||
/* ── Icon buttons (help, theme, refresh) ─────────────────────────────────── */
|
||
/* Square, centered — overrides the asymmetric text-button padding/line-height */
|
||
#help-btn,
|
||
#theme-btn,
|
||
#refreshHeaderBtn {
|
||
width: 2rem;
|
||
height: 2rem;
|
||
padding: 0;
|
||
line-height: 1;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 1rem;
|
||
}
|
||
|
||
/* Toast CSS lives in classifier/css/base.css — only that tool uses toasts. */
|
||
|
||
/* ── Theme and help icon buttons ─────────────────────────────────────────── */
|
||
#theme-btn,
|
||
#help-btn {
|
||
font-size: 1rem;
|
||
}
|
||
|
||
/* ── Help panel (shared slide-out drawer) ─────────────────────────────────── */
|
||
/* Used by all four tools. Toggle open/close via shared/help.js. */
|
||
|
||
.help-panel {
|
||
position: fixed;
|
||
top: 0;
|
||
right: 0;
|
||
width: min(420px, 85vw);
|
||
height: 100vh;
|
||
z-index: 1000;
|
||
background: var(--bg);
|
||
border-left: 1px solid var(--border);
|
||
box-shadow: -2px 0 12px rgba(0, 0, 0, 0.08);
|
||
display: flex;
|
||
flex-direction: column;
|
||
transform: translateX(100%);
|
||
transition: transform 0.25s ease;
|
||
}
|
||
|
||
.help-panel:not([hidden]) {
|
||
transform: translateX(0);
|
||
}
|
||
|
||
.help-panel[hidden] {
|
||
display: flex;
|
||
transform: translateX(100%);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.help-panel__header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 0.75rem 1rem;
|
||
border-bottom: 1px solid var(--border);
|
||
flex-shrink: 0;
|
||
background: var(--bg);
|
||
}
|
||
|
||
.help-panel__title {
|
||
font-size: 1rem;
|
||
font-weight: 700;
|
||
color: var(--text);
|
||
margin: 0;
|
||
}
|
||
|
||
.help-panel__close {
|
||
background: none;
|
||
border: none;
|
||
color: var(--text-muted);
|
||
font-size: 1.35rem;
|
||
cursor: pointer;
|
||
padding: 0.25rem 0.5rem;
|
||
border-radius: var(--radius);
|
||
line-height: 1;
|
||
transition: background 0.15s, color 0.15s;
|
||
}
|
||
|
||
.help-panel__close:hover {
|
||
color: var(--text);
|
||
background: var(--bg-secondary);
|
||
}
|
||
|
||
.help-panel__body {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: 1rem 1rem 2rem;
|
||
font-size: 0.85rem;
|
||
line-height: 1.6;
|
||
color: var(--text);
|
||
}
|
||
|
||
.help-panel__body h3 {
|
||
font-size: 0.95rem;
|
||
font-weight: 700;
|
||
margin: 1.25rem 0 0.35rem;
|
||
color: var(--text);
|
||
border-bottom: 1px solid var(--border);
|
||
padding-bottom: 0.15rem;
|
||
}
|
||
|
||
.help-panel__body h3:first-child {
|
||
margin-top: 0;
|
||
}
|
||
|
||
.help-panel__body h4 {
|
||
font-size: 0.7rem;
|
||
font-weight: 700;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.06em;
|
||
margin: 1.25rem 0 0.3rem;
|
||
padding-left: 0.5rem;
|
||
border-left: 3px solid var(--border-dark);
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
.help-panel__body p {
|
||
margin: 0 0 0.5rem;
|
||
}
|
||
|
||
.help-panel__body ol,
|
||
.help-panel__body ul {
|
||
padding-left: 1.5rem;
|
||
margin: 0.3rem 0 0.5rem;
|
||
}
|
||
|
||
.help-panel__body li {
|
||
margin-bottom: 0.3rem;
|
||
}
|
||
|
||
.help-panel__body dl {
|
||
margin: 0.3rem 0;
|
||
}
|
||
|
||
.help-panel__body dt {
|
||
font-weight: 600;
|
||
color: var(--text);
|
||
}
|
||
|
||
.help-panel__body dd {
|
||
margin: 0 0 0.5rem 1rem;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
.help-panel__body code {
|
||
font-family: var(--font-mono);
|
||
font-size: 0.8em;
|
||
background: var(--bg-secondary);
|
||
padding: 0.1em 0.3em;
|
||
border-radius: 3px;
|
||
}
|
||
|
||
.help-badge {
|
||
font-size: 0.7rem;
|
||
font-weight: 600;
|
||
padding: 0.1rem 0.35rem;
|
||
border-radius: var(--radius);
|
||
vertical-align: middle;
|
||
letter-spacing: 0.02em;
|
||
}
|
||
|
||
.help-badge--draft {
|
||
color: #2563eb;
|
||
background: #eff6ff;
|
||
}
|
||
|
||
.help-badge--published {
|
||
color: #7c3aed;
|
||
background: #f5f3ff;
|
||
}
|
||
|
||
/* Shrink main content when help panel is open */
|
||
body.help-open .app-header {
|
||
margin-right: min(420px, 85vw);
|
||
}
|
||
|
||
/* ── Column filter inputs (shared across archive, classifier, transmittal) ─── */
|
||
.column-filter {
|
||
display: block;
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
margin-top: 0.25rem;
|
||
padding: 0.2rem 0.4rem;
|
||
font-size: 0.8rem;
|
||
font-family: var(--font);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius);
|
||
background: var(--bg);
|
||
color: var(--text);
|
||
transition: border-color 0.15s;
|
||
}
|
||
|
||
.column-filter:focus {
|
||
border-color: var(--primary);
|
||
outline: none;
|
||
box-shadow: 0 0 0 1px rgba(42, 90, 138, 0.35);
|
||
}
|
||
|
||
.column-filter::placeholder {
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
/* Archive-specific base overrides
|
||
Reset, tokens, and font are provided by shared/base.css */
|
||
|
||
#appContainer {
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100vh;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.note {
|
||
font-size: 0.9em;
|
||
color: var(--text-muted);
|
||
font-style: italic;
|
||
}
|
||
|
||
/* Scan spinner */
|
||
.scan-spinner {
|
||
display: inline-block;
|
||
width: 0.85em;
|
||
height: 0.85em;
|
||
border: 2px solid var(--border);
|
||
border-top-color: var(--primary);
|
||
border-radius: 50%;
|
||
animation: spin 0.7s linear infinite;
|
||
vertical-align: middle;
|
||
margin-left: 0.4rem;
|
||
}
|
||
|
||
@keyframes spin {
|
||
to { transform: rotate(360deg); }
|
||
}
|
||
|
||
/* Archive layout — tokens from shared/base.css */
|
||
|
||
/* Header — shared/base.css provides base .app-header; add archive-specific overrides */
|
||
.app-header {
|
||
padding: 0.5rem 1rem;
|
||
}
|
||
|
||
.header-left {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.75rem;
|
||
}
|
||
|
||
.header-right {
|
||
display: flex;
|
||
gap: 0.5rem;
|
||
align-items: center;
|
||
}
|
||
|
||
.preview-toggle-label {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.35rem;
|
||
font-size: 0.875rem;
|
||
cursor: pointer;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
/* Main Container */
|
||
.main-container {
|
||
display: flex;
|
||
flex: 1;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* Navigation Pane */
|
||
.nav-pane {
|
||
width: 300px;
|
||
min-width: 200px;
|
||
background: var(--bg);
|
||
border-right: 1px solid var(--border);
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
height: 100%;
|
||
position: relative;
|
||
}
|
||
|
||
.nav-section {
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding: 1rem;
|
||
border-bottom: 1px solid var(--border);
|
||
overflow: hidden;
|
||
position: relative;
|
||
}
|
||
|
||
/* Grouping section - larger default size */
|
||
.nav-section:first-child {
|
||
flex: 0 0 auto;
|
||
height: 250px;
|
||
min-height: 50px;
|
||
}
|
||
|
||
/* Grouping section when collapsed */
|
||
.nav-section:first-child.collapsed {
|
||
height: auto;
|
||
flex: 0 0 auto;
|
||
}
|
||
|
||
/* Transmittal section takes remaining space */
|
||
.nav-section:last-child {
|
||
flex: 1;
|
||
min-height: 150px;
|
||
border-bottom: none;
|
||
}
|
||
|
||
/* Nav section content wrapper */
|
||
.nav-section-content {
|
||
display: flex;
|
||
flex-direction: column;
|
||
flex: 1;
|
||
overflow: hidden;
|
||
min-height: 0;
|
||
}
|
||
|
||
/* Hide content when collapsed */
|
||
.nav-section.collapsed .nav-section-content {
|
||
display: none;
|
||
}
|
||
|
||
/* Resize handles — persistent 1px divider; grab cursor on hover */
|
||
.resize-handle-horizontal {
|
||
position: absolute;
|
||
right: 0;
|
||
top: 0;
|
||
bottom: 0;
|
||
width: 5px;
|
||
cursor: ew-resize;
|
||
z-index: 10;
|
||
/* Persistent 1px right-edge indicator */
|
||
border-right: 1px solid var(--border-dark);
|
||
}
|
||
|
||
.resize-handle-horizontal:hover,
|
||
.resize-handle-horizontal.resizing {
|
||
background: rgba(42, 90, 138, 0.25);
|
||
cursor: col-resize;
|
||
}
|
||
|
||
.resize-handle-vertical {
|
||
position: absolute;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: -3px;
|
||
height: 6px;
|
||
cursor: ns-resize;
|
||
z-index: 10;
|
||
/* Persistent 1px bottom-edge indicator */
|
||
border-bottom: 1px solid var(--border-dark);
|
||
}
|
||
|
||
.resize-handle-vertical:hover,
|
||
.resize-handle-vertical.resizing {
|
||
background: rgba(42, 90, 138, 0.25);
|
||
cursor: row-resize;
|
||
}
|
||
|
||
.nav-section h3 {
|
||
font-size: 1em;
|
||
text-transform: uppercase;
|
||
color: var(--text-muted);
|
||
margin-bottom: 0.5rem;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.nav-section h3 {
|
||
font-size: 1em;
|
||
text-transform: uppercase;
|
||
color: var(--text-muted);
|
||
margin-bottom: 0;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.folder-list {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
overflow-x: hidden;
|
||
margin-top: 0.5rem;
|
||
min-height: 0;
|
||
}
|
||
|
||
/* Content Area */
|
||
.content-area {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
background: var(--bg-secondary);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.content-header {
|
||
display: flex;
|
||
justify-content: flex-start;
|
||
align-items: center;
|
||
gap: 0.75rem;
|
||
padding: 0.75rem 1rem;
|
||
background: var(--bg);
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
|
||
.content-header .content-actions {
|
||
margin-left: auto;
|
||
}
|
||
|
||
.content-actions {
|
||
display: flex;
|
||
gap: 0.5rem;
|
||
align-items: center;
|
||
}
|
||
|
||
/* Table Container */
|
||
.table-container {
|
||
flex: 1;
|
||
overflow: auto;
|
||
background: var(--bg);
|
||
margin: 1rem;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius);
|
||
}
|
||
|
||
/* Status Bar */
|
||
.status-bar {
|
||
display: flex;
|
||
justify-content: flex-start;
|
||
align-items: center;
|
||
padding: 0.35rem 1rem;
|
||
background: var(--bg);
|
||
border-top: 1px solid var(--border);
|
||
font-size: 0.85em;
|
||
color: var(--text-muted);
|
||
gap: 1rem;
|
||
}
|
||
|
||
/* Empty State — positioned below the app header */
|
||
.empty-state {
|
||
position: absolute;
|
||
top: 50px; /* clear the header */
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: var(--bg);
|
||
z-index: 10;
|
||
}
|
||
|
||
.empty-state-content {
|
||
text-align: center;
|
||
max-width: 500px;
|
||
padding: 2rem;
|
||
}
|
||
|
||
/* Project warning banner */
|
||
.project-warning-banner {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 8px 16px;
|
||
background: #fff3cd;
|
||
border-bottom: 1px solid #ffc107;
|
||
color: #664d03;
|
||
font-size: 0.875rem;
|
||
gap: 12px;
|
||
}
|
||
.project-warning-banner.hidden { display: none; }
|
||
.project-warning-dismiss {
|
||
background: none;
|
||
border: none;
|
||
cursor: pointer;
|
||
color: #664d03;
|
||
font-size: 1rem;
|
||
padding: 0 4px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.empty-state-content h2 {
|
||
color: var(--text);
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.empty-state-content p {
|
||
margin-bottom: 1rem;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
/* Project access warning banner */
|
||
.project-warning-banner {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 8px 16px;
|
||
background: #fff3cd;
|
||
border-bottom: 1px solid #ffc107;
|
||
color: #664d03;
|
||
font-size: 0.875rem;
|
||
gap: 12px;
|
||
}
|
||
.project-warning-banner.hidden { display: none; }
|
||
.project-warning-dismiss {
|
||
background: none;
|
||
border: none;
|
||
cursor: pointer;
|
||
color: #664d03;
|
||
font-size: 1rem;
|
||
padding: 0 4px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* Archive component styles — tokens from shared/base.css */
|
||
|
||
/* Select All checkbox label */
|
||
.select-all-label {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.4rem;
|
||
font-size: 0.8rem;
|
||
color: var(--text-muted);
|
||
cursor: pointer;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.select-all-label input[type="checkbox"] {
|
||
margin: 0;
|
||
cursor: pointer;
|
||
}
|
||
|
||
/* One-line bar variant — sits below the section header */
|
||
.select-all-bar {
|
||
padding: 0.2rem 0;
|
||
margin-bottom: 0.35rem;
|
||
}
|
||
|
||
/* Filter + Select All inline row */
|
||
.filter-select-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.4rem;
|
||
margin-bottom: 0.35rem;
|
||
}
|
||
|
||
.filter-select-row .filter-input {
|
||
flex: 1;
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
/* Inline variant: label to the right of the filter, text above checkbox */
|
||
.select-all-inline {
|
||
flex-shrink: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 0.1rem;
|
||
font-size: 0.7rem;
|
||
line-height: 1.1;
|
||
color: var(--text-muted);
|
||
cursor: pointer;
|
||
white-space: nowrap;
|
||
text-align: center;
|
||
}
|
||
|
||
.select-all-inline input[type="checkbox"] {
|
||
margin: 0;
|
||
cursor: pointer;
|
||
}
|
||
|
||
/* Form Inputs */
|
||
.filter-input,
|
||
.form-input {
|
||
width: 100%;
|
||
padding: 0.5rem;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius);
|
||
font-size: 0.9rem;
|
||
font-family: var(--font);
|
||
background: var(--bg);
|
||
color: var(--text);
|
||
transition: border-color 0.2s;
|
||
}
|
||
|
||
.filter-input:focus,
|
||
.form-input:focus {
|
||
outline: none;
|
||
border-color: var(--primary);
|
||
}
|
||
|
||
.filter-input.filter-active {
|
||
background: rgba(234, 179, 8, 0.18);
|
||
border-color: rgba(234, 179, 8, 0.7);
|
||
}
|
||
|
||
/* Form Groups */
|
||
.form-group {
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.form-group label {
|
||
display: block;
|
||
margin-bottom: 0.25rem;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.form-help {
|
||
display: block;
|
||
margin-top: 0.25rem;
|
||
font-size: 0.85rem;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
/* Checkboxes */
|
||
input[type="checkbox"] {
|
||
margin-right: 0.5rem;
|
||
cursor: pointer;
|
||
}
|
||
|
||
/* Folder Tree Chevrons */
|
||
.folder-chevron {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 1rem;
|
||
height: 1rem;
|
||
font-size: 0.6rem;
|
||
color: var(--text-muted);
|
||
cursor: pointer;
|
||
transition: transform 0.15s ease;
|
||
flex-shrink: 0;
|
||
margin-right: 0.25rem;
|
||
}
|
||
|
||
.folder-chevron:not(.collapsed) {
|
||
transform: rotate(90deg);
|
||
}
|
||
|
||
.folder-chevron:hover {
|
||
color: var(--primary);
|
||
}
|
||
|
||
.folder-chevron-placeholder {
|
||
width: 1rem;
|
||
flex-shrink: 0;
|
||
margin-right: 0.25rem;
|
||
}
|
||
|
||
/* Folder Items */
|
||
.folder-item {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 0.25rem 0.5rem;
|
||
cursor: pointer;
|
||
user-select: none;
|
||
border-radius: 3px;
|
||
border-left: 3px solid transparent;
|
||
}
|
||
|
||
.folder-item:hover {
|
||
background: var(--bg-hover);
|
||
}
|
||
|
||
.folder-item.selected {
|
||
background: var(--bg-selected);
|
||
color: inherit;
|
||
border-left: 3px solid var(--primary);
|
||
padding-left: calc(0.5rem - 3px);
|
||
}
|
||
|
||
.folder-item.selected:hover {
|
||
background: var(--bg-hover);
|
||
}
|
||
|
||
.folder-item-name {
|
||
flex: 1;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
user-select: text;
|
||
cursor: text;
|
||
}
|
||
|
||
/* Transmittal folder formatting */
|
||
.transmittal-folder-content {
|
||
flex: 1;
|
||
overflow: hidden;
|
||
user-select: text;
|
||
cursor: text;
|
||
}
|
||
|
||
[data-folder-type="transmittal"] {
|
||
padding-top: 0.5rem;
|
||
padding-bottom: 0.5rem;
|
||
}
|
||
|
||
.transmittal-first-line {
|
||
font-size: 0.9em;
|
||
font-weight: 500;
|
||
color: var(--text);
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.transmittal-second-line {
|
||
font-size: 0.85em;
|
||
color: var(--text-muted);
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
/* Empty filter message in folder lists */
|
||
.folder-list-empty {
|
||
padding: 0.75rem 0.5rem;
|
||
color: var(--text-muted);
|
||
font-size: 0.85rem;
|
||
font-style: italic;
|
||
text-align: center;
|
||
}
|
||
|
||
/* Focus styles for keyboard navigation */
|
||
.folder-list:focus {
|
||
outline: 2px solid var(--primary);
|
||
outline-offset: -2px;
|
||
}
|
||
|
||
.folder-list:focus .folder-item:focus {
|
||
outline: 1px dotted var(--primary);
|
||
outline-offset: -1px;
|
||
}
|
||
|
||
/* ── Folder type toggle bar ─────────────────────────────────────────────── */
|
||
.folder-type-bar {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 0.3rem;
|
||
padding: 0.3rem 0 0.4rem;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.folder-type-toggle {
|
||
padding: 0.2rem 0.6rem;
|
||
font-size: 0.8rem;
|
||
font-family: var(--font);
|
||
border: 1px solid var(--border);
|
||
border-radius: 999px;
|
||
background: var(--bg);
|
||
color: var(--text-muted);
|
||
cursor: pointer;
|
||
transition: background 0.15s, color 0.15s, border-color 0.15s;
|
||
line-height: 1.4;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.folder-type-toggle:hover {
|
||
background: var(--bg-hover);
|
||
color: var(--text);
|
||
border-color: var(--border-dark);
|
||
}
|
||
|
||
.folder-type-toggle.active {
|
||
background: var(--primary);
|
||
color: var(--text-light);
|
||
border-color: var(--primary);
|
||
}
|
||
|
||
.folder-type-toggle.active:hover {
|
||
background: var(--primary-hover);
|
||
border-color: var(--primary-hover);
|
||
}
|
||
|
||
/* Date Group Headers */
|
||
.date-group-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
padding: 0.5rem;
|
||
background: var(--bg-secondary);
|
||
border-bottom: 1px solid var(--border);
|
||
cursor: pointer;
|
||
user-select: none;
|
||
font-weight: 600;
|
||
color: var(--text);
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 1;
|
||
}
|
||
|
||
.date-group-header:hover {
|
||
background: var(--bg-hover);
|
||
}
|
||
|
||
.date-group-toggle {
|
||
font-size: 0.8em;
|
||
width: 1rem;
|
||
text-align: center;
|
||
}
|
||
|
||
.date-group-date {
|
||
flex: 1;
|
||
}
|
||
|
||
.date-group-count {
|
||
font-size: 0.85em;
|
||
color: var(--text-muted);
|
||
font-weight: normal;
|
||
}
|
||
|
||
/* Nav section header with button */
|
||
.nav-section-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
background: var(--bg-secondary);
|
||
margin: -1rem -1rem 0.75rem -1rem;
|
||
padding: 0.4rem 1rem;
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
|
||
.nav-section-header h3 {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.btn-icon {
|
||
background: none;
|
||
border: none;
|
||
padding: 0.25rem;
|
||
cursor: pointer;
|
||
color: var(--text-muted);
|
||
font-size: 1rem;
|
||
line-height: 1;
|
||
border-radius: 3px;
|
||
transition: background 0.2s;
|
||
}
|
||
|
||
.btn-icon:hover {
|
||
background: var(--bg-hover);
|
||
color: var(--text);
|
||
}
|
||
|
||
/* Modals */
|
||
.modal {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
z-index: 1000;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.modal-backdrop {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
}
|
||
|
||
.modal-content {
|
||
position: relative;
|
||
background: var(--bg);
|
||
border-radius: 8px;
|
||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||
max-width: 90vw;
|
||
max-height: 90vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.modal-large {
|
||
width: 80vw;
|
||
}
|
||
|
||
.modal-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 1.5rem;
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
|
||
.modal-header h2 {
|
||
margin: 0;
|
||
}
|
||
|
||
.modal-close {
|
||
background: none;
|
||
border: none;
|
||
font-size: 1.5rem;
|
||
color: var(--text-muted);
|
||
padding: 0;
|
||
width: 2rem;
|
||
height: 2rem;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.modal-close:hover {
|
||
color: var(--text);
|
||
}
|
||
|
||
.modal-body {
|
||
padding: 1.5rem;
|
||
overflow-y: auto;
|
||
flex: 1;
|
||
}
|
||
|
||
.modal-footer {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 0.5rem;
|
||
padding: 1rem 1.5rem;
|
||
border-top: 1px solid var(--border);
|
||
}
|
||
|
||
/* Preview Table */
|
||
.preview-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
margin-top: 0.5rem;
|
||
}
|
||
|
||
.preview-table th,
|
||
.preview-table td {
|
||
text-align: left;
|
||
padding: 0.5rem;
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
|
||
.preview-table th {
|
||
font-weight: 600;
|
||
background: var(--bg-secondary);
|
||
}
|
||
|
||
/* Drag & Drop */
|
||
.drag-over {
|
||
background: var(--bg-selected) !important;
|
||
border-color: var(--primary) !important;
|
||
}
|
||
|
||
/* Loading Spinner */
|
||
.spinner {
|
||
display: inline-block;
|
||
width: 1rem;
|
||
height: 1rem;
|
||
border: 2px solid var(--border);
|
||
border-top: 2px solid var(--primary);
|
||
border-radius: 50%;
|
||
animation: spin 1s linear infinite;
|
||
}
|
||
|
||
@keyframes spin {
|
||
0% { transform: rotate(0deg); }
|
||
100% { transform: rotate(360deg); }
|
||
}
|
||
|
||
/* Revision Title Styling */
|
||
.titles-container {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.revision-title-base,
|
||
.revision-title-modifier {
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
|
||
.revision-title-base:last-child,
|
||
.revision-title-modifier:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.revision-title-base {
|
||
color: var(--text);
|
||
}
|
||
|
||
.revision-title-modifier {
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
/* Modifier Filter Dropdown */
|
||
.modifier-filter-container {
|
||
position: relative;
|
||
display: inline-block;
|
||
}
|
||
|
||
.modifier-filter-btn {
|
||
min-width: 100px;
|
||
}
|
||
|
||
.modifier-filter-dropdown {
|
||
position: absolute;
|
||
top: 100%;
|
||
left: 0;
|
||
z-index: 1000;
|
||
min-width: 180px;
|
||
background: var(--bg);
|
||
border: 1px solid var(--border-dark);
|
||
border-radius: var(--radius);
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||
margin-top: 4px;
|
||
}
|
||
|
||
.modifier-filter-header {
|
||
padding: 0.5rem 0.75rem;
|
||
border-bottom: 1px solid var(--border);
|
||
background: var(--bg-secondary);
|
||
}
|
||
|
||
.modifier-filter-header label {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.modifier-filter-list {
|
||
max-height: 250px;
|
||
overflow-y: auto;
|
||
padding: 0.25rem 0;
|
||
}
|
||
|
||
.modifier-filter-item {
|
||
padding: 0.4rem 0.75rem;
|
||
}
|
||
|
||
.modifier-filter-item label {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
cursor: pointer;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.modifier-filter-item:hover {
|
||
background: var(--bg-hover);
|
||
}
|
||
|
||
.modifier-base {
|
||
font-weight: 500;
|
||
color: var(--text);
|
||
}
|
||
|
||
.modifier-type {
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
/* Active toggle button state */
|
||
.btn-active {
|
||
background: var(--primary);
|
||
color: var(--text-light);
|
||
border-color: var(--primary);
|
||
}
|
||
|
||
.btn-active:hover {
|
||
background: var(--primary-hover);
|
||
border-color: var(--primary-hover);
|
||
}
|
||
|
||
/* Path Error Row Warning */
|
||
.file-row-path-error {
|
||
background: rgba(217, 119, 6, 0.08) !important;
|
||
}
|
||
|
||
.file-row-path-error:hover {
|
||
background: rgba(217, 119, 6, 0.15) !important;
|
||
}
|
||
|
||
.path-error-indicator {
|
||
color: var(--warning);
|
||
cursor: help;
|
||
margin-right: 0.25rem;
|
||
}
|
||
|
||
.file-link-disabled {
|
||
color: var(--text-muted);
|
||
text-decoration: none;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.file-link-disabled:hover {
|
||
text-decoration: none;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
/* PDF Preview Toggle */
|
||
.preview-toggle-label {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
font-size: 0.9rem;
|
||
color: var(--text);
|
||
cursor: pointer;
|
||
padding: 0.4rem 0.85rem;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius);
|
||
background: var(--bg);
|
||
transition: background 0.15s;
|
||
}
|
||
|
||
.preview-toggle-label:hover {
|
||
background: var(--bg-secondary);
|
||
}
|
||
|
||
.preview-toggle-label input[type="checkbox"] {
|
||
margin: 0;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.preview-toggle-label input[type="checkbox"]:checked + span {
|
||
color: var(--primary);
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* ── Download progress indicator ────────────────────────────────────────── */
|
||
.progress-indicator {
|
||
position: fixed;
|
||
bottom: 20px;
|
||
right: 20px;
|
||
background: var(--bg);
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||
z-index: 1000;
|
||
min-width: 300px;
|
||
}
|
||
|
||
.progress-indicator__message {
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.progress-indicator__track {
|
||
background: var(--bg-secondary);
|
||
height: 20px;
|
||
border-radius: 10px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.progress-indicator__fill {
|
||
background: var(--primary);
|
||
height: 100%;
|
||
transition: width 0.3s;
|
||
}
|
||
|
||
.progress-indicator__label {
|
||
text-align: center;
|
||
margin-top: 5px;
|
||
font-size: 0.9em;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
/* ── Welcome screen list ─────────────────────────────────────────────────── */
|
||
.welcome-list {
|
||
text-align: left;
|
||
margin: 0.5rem auto;
|
||
max-width: 400px;
|
||
}
|
||
|
||
/* ── Windows path tip (inside welcome screen) ────────────────────────────── */
|
||
.windows-tip {
|
||
text-align: left;
|
||
margin: 1rem auto;
|
||
max-width: 500px;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.windows-tip summary {
|
||
cursor: pointer;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
.windows-tip__body {
|
||
margin-top: 0.5rem;
|
||
padding: 0.75rem;
|
||
background: var(--bg);
|
||
border: 1px solid var(--warning);
|
||
border-radius: var(--radius);
|
||
}
|
||
|
||
.windows-tip__body > p:first-child {
|
||
margin: 0 0 0.5rem 0;
|
||
}
|
||
|
||
.windows-tip__body ol {
|
||
margin: 0.5rem 0;
|
||
padding-left: 1.5rem;
|
||
}
|
||
|
||
.windows-tip__code {
|
||
display: block;
|
||
margin: 0.25rem 0;
|
||
padding: 0.25rem 0.5rem;
|
||
background: var(--bg-secondary);
|
||
border-radius: var(--radius);
|
||
font-family: var(--font-mono);
|
||
font-size: 0.85em;
|
||
}
|
||
|
||
.windows-tip__note {
|
||
margin: 0.5rem 0 0 0;
|
||
font-size: 0.85rem;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
/* Outstanding virtual transmittal — pinned at top of transmittal list */
|
||
.outstanding-transmittal {
|
||
border-top: 1px solid var(--border);
|
||
border-bottom: 1px solid var(--border);
|
||
margin-bottom: 0.25rem;
|
||
}
|
||
|
||
.outstanding-label {
|
||
font-style: italic;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
.outstanding-transmittal.selected .outstanding-label {
|
||
color: var(--text);
|
||
}
|
||
|
||
/* Reset Filters Button */
|
||
.btn-icon-only {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 2rem;
|
||
height: 2rem;
|
||
font-size: 1.1rem;
|
||
background: var(--bg);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius);
|
||
cursor: pointer;
|
||
transition: background 0.2s, border-color 0.2s, color 0.2s;
|
||
}
|
||
|
||
.btn-icon-only:hover {
|
||
background: var(--bg-hover);
|
||
border-color: var(--primary);
|
||
}
|
||
|
||
.btn-icon-only:active {
|
||
background: var(--primary);
|
||
border-color: var(--primary);
|
||
color: var(--text-light);
|
||
}
|
||
|
||
/* Toolbar separator */
|
||
.toolbar-separator {
|
||
width: 1px;
|
||
height: 1.5rem;
|
||
background: var(--border);
|
||
margin: 0 0.25rem;
|
||
align-self: center;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* ── Preset dropdown ─────────────────────────────────────────────────────── */
|
||
.preset-section {
|
||
position: relative;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.preset-dropdown {
|
||
position: absolute;
|
||
top: 100%;
|
||
left: 0;
|
||
z-index: 1000;
|
||
min-width: 350px;
|
||
max-height: 400px;
|
||
background: var(--bg);
|
||
border: 1px solid var(--border-dark);
|
||
border-radius: var(--radius);
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||
overflow: hidden;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.preset-section-label {
|
||
font-size: 0.75rem;
|
||
font-weight: 600;
|
||
color: var(--text-muted);
|
||
padding: 0.5rem 0.75rem 0.25rem;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.5px;
|
||
}
|
||
|
||
.preset-list {
|
||
padding: 0.25rem 0;
|
||
max-height: 200px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.preset-item {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 0.4rem 0.75rem;
|
||
cursor: pointer;
|
||
user-select: none;
|
||
}
|
||
|
||
.preset-item:hover {
|
||
background: var(--bg-hover);
|
||
}
|
||
|
||
.preset-item .preset-delete {
|
||
background: none;
|
||
border: none;
|
||
color: var(--text-muted);
|
||
font-size: 1rem;
|
||
padding: 0.25rem 0.5rem;
|
||
cursor: pointer;
|
||
border-radius: 3px;
|
||
line-height: 1;
|
||
}
|
||
|
||
.preset-item .preset-delete:hover {
|
||
background: rgba(255, 0, 0, 0.1);
|
||
color: var(--error);
|
||
}
|
||
|
||
.preset-no-presets {
|
||
padding: 0.75rem 0.75rem;
|
||
color: var(--text-muted);
|
||
font-size: 0.85rem;
|
||
font-style: italic;
|
||
text-align: center;
|
||
}
|
||
|
||
.preset-divider {
|
||
height: 1px;
|
||
background: var(--border);
|
||
margin: 0.5rem 0;
|
||
}
|
||
|
||
.preset-projects-list {
|
||
padding: 0.25rem 0;
|
||
max-height: 200px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.preset-project-item {
|
||
padding: 0.25rem 0.75rem;
|
||
}
|
||
|
||
.preset-project-label {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
cursor: pointer;
|
||
font-size: 0.9rem;
|
||
user-select: none;
|
||
}
|
||
|
||
.preset-project-label input[type="checkbox"] {
|
||
margin: 0;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.preset-footer-actions {
|
||
padding: 0.5rem 0.75rem;
|
||
border-top: 1px solid var(--border);
|
||
background: var(--bg-secondary);
|
||
display: flex;
|
||
gap: 0.5rem;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.preset-footer-naming {
|
||
padding: 0.5rem 0.75rem;
|
||
border-top: 1px solid var(--border);
|
||
background: var(--bg-secondary);
|
||
display: flex;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.preset-name-input {
|
||
flex: 1;
|
||
padding: 0.4rem 0.6rem;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius);
|
||
font-size: 0.9rem;
|
||
font-family: var(--font);
|
||
background: var(--bg);
|
||
color: var(--text);
|
||
}
|
||
|
||
.preset-name-input:focus {
|
||
outline: none;
|
||
border-color: var(--primary);
|
||
}
|
||
|
||
.preset-section-top,
|
||
.preset-section-bottom {
|
||
padding: 0.25rem 0;
|
||
}
|
||
|
||
.preset-section-bottom {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
|
||
|
||
/* Table styles */
|
||
|
||
.files-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
background: var(--bg);
|
||
}
|
||
|
||
/* Table Header */
|
||
.files-table thead {
|
||
position: sticky;
|
||
top: 0;
|
||
background: var(--bg);
|
||
z-index: 10;
|
||
}
|
||
|
||
.files-table th {
|
||
position: relative;
|
||
text-align: left;
|
||
font-weight: 600;
|
||
background: var(--bg-secondary);
|
||
border-bottom: 2px solid var(--border);
|
||
user-select: none;
|
||
}
|
||
|
||
.th-content {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 0.75rem 1rem;
|
||
cursor: default;
|
||
}
|
||
|
||
.sortable .th-content {
|
||
cursor: pointer;
|
||
}
|
||
|
||
.sortable .th-content:hover {
|
||
background: var(--bg-hover);
|
||
}
|
||
|
||
/* Sort Indicators */
|
||
.sort-indicator {
|
||
display: inline-block;
|
||
width: 0.75rem;
|
||
height: 1rem;
|
||
margin-left: 0.5rem;
|
||
position: relative;
|
||
}
|
||
|
||
.sort-indicator::before,
|
||
.sort-indicator::after {
|
||
content: '';
|
||
position: absolute;
|
||
left: 0;
|
||
width: 0;
|
||
height: 0;
|
||
border-style: solid;
|
||
}
|
||
|
||
.sort-indicator::before {
|
||
top: 0;
|
||
border-width: 0 0.375rem 0.375rem 0.375rem;
|
||
border-color: transparent transparent var(--border-dark) transparent;
|
||
}
|
||
|
||
.sort-indicator::after {
|
||
bottom: 0;
|
||
border-width: 0.375rem 0.375rem 0 0.375rem;
|
||
border-color: var(--border-dark) transparent transparent transparent;
|
||
}
|
||
|
||
th[data-sort="asc"] .sort-indicator::before {
|
||
border-bottom-color: var(--text);
|
||
}
|
||
|
||
th[data-sort="desc"] .sort-indicator::after {
|
||
border-top-color: var(--text);
|
||
}
|
||
|
||
/* Resize Handle */
|
||
.resize-handle {
|
||
position: absolute;
|
||
right: 0;
|
||
top: 0;
|
||
bottom: 0;
|
||
width: 4px;
|
||
cursor: col-resize;
|
||
background: transparent;
|
||
}
|
||
|
||
.resize-handle:hover {
|
||
background: var(--primary);
|
||
}
|
||
|
||
/* Table Body */
|
||
.files-table tbody tr {
|
||
transition: background-color 0.1s;
|
||
}
|
||
|
||
.files-table tbody tr.group-last {
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
|
||
.files-table tbody tr:hover {
|
||
background: var(--bg-hover);
|
||
}
|
||
|
||
/* Preview-active highlight: marks the row + specific file (when there are
|
||
multiple files per revision) that the preview popup is currently showing,
|
||
so the user can match what's on screen to its location in the table. */
|
||
.files-table tbody tr.is-previewing {
|
||
background: var(--bg-selected, rgba(42, 90, 138, 0.10));
|
||
box-shadow: inset 3px 0 0 var(--primary);
|
||
}
|
||
.files-table tbody tr.is-previewing:hover {
|
||
background: var(--bg-selected-hover, rgba(42, 90, 138, 0.18));
|
||
}
|
||
.revision-file.is-previewing {
|
||
outline: 1.5px solid var(--primary);
|
||
outline-offset: 2px;
|
||
border-radius: 3px;
|
||
}
|
||
|
||
.files-table td {
|
||
padding: 0.25rem 1rem;
|
||
vertical-align: top;
|
||
}
|
||
|
||
/* Tracking Number Column */
|
||
td[data-field="trackingNumber"],
|
||
th[data-sort="trackingNumber"] {
|
||
white-space: nowrap;
|
||
}
|
||
|
||
td[data-field="trackingNumber"] {
|
||
font-family: 'Consolas', 'Monaco', monospace;
|
||
font-size: 0.9em;
|
||
}
|
||
|
||
/* Revisions Column */
|
||
.revision-group {
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
|
||
.revision-group:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.revision-item {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 0.25rem;
|
||
}
|
||
|
||
.revision-item:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.revision-info {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
margin-right: 0.5rem;
|
||
font-family: 'Consolas', 'Monaco', monospace;
|
||
font-size: 0.9em;
|
||
}
|
||
|
||
.revision-id {
|
||
font-weight: 600;
|
||
margin-right: 0.25rem;
|
||
}
|
||
|
||
.revision-status {
|
||
color: var(--text-muted);
|
||
font-size: 0.85em;
|
||
}
|
||
|
||
.revision-file {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
margin-left: 0.25rem;
|
||
}
|
||
|
||
.file-link,
|
||
.file-link-disabled {
|
||
display: inline-flex;
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
margin-right: 0.25rem;
|
||
line-height: 1.1;
|
||
}
|
||
|
||
.file-link {
|
||
color: var(--primary);
|
||
text-decoration: none;
|
||
}
|
||
|
||
.file-link:hover {
|
||
text-decoration: underline;
|
||
}
|
||
|
||
|
||
.file-ext {
|
||
color: var(--text-muted);
|
||
font-size: 0.85em;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
/* Empty Table State */
|
||
.empty-table {
|
||
text-align: center;
|
||
padding: 3rem;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
/* Column Widths */
|
||
.files-table th:nth-child(1),
|
||
.files-table td:nth-child(1) {
|
||
width: 240px;
|
||
min-width: 220px;
|
||
}
|
||
|
||
.files-table th:nth-child(2),
|
||
.files-table td:nth-child(2) {
|
||
width: 40%;
|
||
min-width: 200px;
|
||
}
|
||
|
||
.files-table th:nth-child(3),
|
||
.files-table td:nth-child(3) {
|
||
width: auto;
|
||
min-width: 300px;
|
||
}
|
||
|
||
/* File size — half the height of the extension badge, left-aligned below it */
|
||
.file-size {
|
||
color: var(--text-muted);
|
||
font-size: 0.5em;
|
||
line-height: 1;
|
||
margin-top: 0.15em;
|
||
}
|
||
|
||
/* Active column filter highlight */
|
||
.column-filter.filter-active {
|
||
background: rgba(234, 179, 8, 0.18);
|
||
border-color: rgba(234, 179, 8, 0.7);
|
||
}
|
||
|
||
/* Print styles */
|
||
|
||
@media print {
|
||
/* Hide UI elements */
|
||
.app-header,
|
||
.nav-pane,
|
||
.content-header,
|
||
.status-bar,
|
||
.modal,
|
||
.btn,
|
||
.filter-input,
|
||
.global-search,
|
||
.column-filter,
|
||
input[type="checkbox"],
|
||
.resize-handle,
|
||
.sort-indicator {
|
||
display: none !important;
|
||
}
|
||
|
||
/* Reset layout */
|
||
body {
|
||
font-size: 10pt;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
#appContainer {
|
||
height: auto;
|
||
}
|
||
|
||
.main-container {
|
||
display: block;
|
||
}
|
||
|
||
.content-area {
|
||
background: white;
|
||
}
|
||
|
||
.table-container {
|
||
margin: 0;
|
||
border: none;
|
||
overflow: visible;
|
||
}
|
||
|
||
/* Table adjustments */
|
||
.files-table {
|
||
font-size: 9pt;
|
||
border: 1px solid #000;
|
||
}
|
||
|
||
.files-table thead {
|
||
position: static;
|
||
}
|
||
|
||
.files-table th {
|
||
background: #f0f0f0;
|
||
border: 1px solid #000;
|
||
padding: 4pt 6pt;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.files-table td {
|
||
border: 1px solid #000;
|
||
padding: 3pt 6pt;
|
||
}
|
||
|
||
.files-table tbody tr:hover {
|
||
background: transparent;
|
||
}
|
||
|
||
/* Show only text content for revisions */
|
||
.revision-item {
|
||
display: inline;
|
||
margin-right: 0.5em;
|
||
}
|
||
|
||
.file-link {
|
||
color: black;
|
||
text-decoration: none;
|
||
}
|
||
|
||
.file-link::after {
|
||
content: " (" attr(href) ")";
|
||
font-size: 8pt;
|
||
color: #666;
|
||
}
|
||
|
||
/* Page breaks */
|
||
.files-table {
|
||
page-break-inside: auto;
|
||
}
|
||
|
||
.files-table tr {
|
||
page-break-inside: avoid;
|
||
page-break-after: auto;
|
||
}
|
||
|
||
/* Header on each page */
|
||
@page {
|
||
size: letter portrait;
|
||
margin: 0.5in;
|
||
}
|
||
|
||
/* Add document title */
|
||
body::before {
|
||
content: "Archive Browser Report";
|
||
display: block;
|
||
font-size: 16pt;
|
||
font-weight: bold;
|
||
margin-bottom: 12pt;
|
||
}
|
||
|
||
/* Add timestamp */
|
||
body::after {
|
||
content: "Generated: " attr(data-print-date);
|
||
display: block;
|
||
margin-top: 12pt;
|
||
font-size: 9pt;
|
||
color: #666;
|
||
text-align: right;
|
||
}
|
||
}
|
||
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="appContainer">
|
||
<!-- Project access warning banner (shown when URL contains inaccessible projects) -->
|
||
<div id="projectWarningBanner" class="project-warning-banner hidden" role="alert">
|
||
<span class="project-warning-text"></span>
|
||
<button class="project-warning-dismiss" onclick="dismissProjectWarning()" aria-label="Dismiss">×</button>
|
||
</div>
|
||
|
||
<!-- Header -->
|
||
<header class="app-header">
|
||
<div class="header-left">
|
||
<svg class="app-header__logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" aria-hidden="true">
|
||
<rect width="64" height="64" rx="12" fill="#1e3a5f"/>
|
||
<g fill="#fff">
|
||
<rect x="14" y="18" width="36" height="7"/>
|
||
<polygon points="43,25 50,25 21,43 14,43"/>
|
||
<rect x="14" y="43" width="36" height="7"/>
|
||
</g>
|
||
</svg>
|
||
<div class="header-title-group">
|
||
<span class="app-header__title">ZDDC Archive</span>
|
||
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.16-beta · 2026-05-04 · 8df0def</span></span>
|
||
</div>
|
||
<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>
|
||
</div>
|
||
<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="help-btn" class="btn btn-secondary" title="Help">?</button>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- Main Container -->
|
||
<div class="main-container">
|
||
<!-- Navigation Pane -->
|
||
<nav id="navigationPane" class="nav-pane">
|
||
<!-- Grouping Folders Section -->
|
||
<div class="nav-section" id="groupingSection">
|
||
<div class="nav-section-header">
|
||
<h3>Parties</h3>
|
||
<div class="preset-section" id="presetSection">
|
||
<button id="presetBtn" class="btn btn-secondary btn-sm" title="Party presets">▾ Presets</button>
|
||
<div id="presetDropdown" class="preset-dropdown hidden"></div>
|
||
</div>
|
||
<button id="toggleGroupingBtn" class="btn-icon" title="Collapse/Expand">
|
||
<span id="toggleGroupingIcon">▼</span>
|
||
</button>
|
||
</div>
|
||
<div id="groupingContent" class="nav-section-content">
|
||
<!-- Global folder type toggle bar -->
|
||
<div id="folderTypeBar" class="folder-type-bar">
|
||
<!-- Dynamically populated by renderFolderTypeBar() -->
|
||
</div>
|
||
<div class="filter-select-row">
|
||
<input type="text"
|
||
id="groupingFilter"
|
||
class="filter-input"
|
||
placeholder="Filter parties...">
|
||
<label class="select-all-label select-all-inline" title="Auto-select all visible parties">
|
||
<span>Select<br>All</span>
|
||
<input type="checkbox" id="selectAllGroupingCheckbox" checked>
|
||
</label>
|
||
</div>
|
||
<div id="groupingFoldersList" class="folder-list">
|
||
<!-- Dynamically populated -->
|
||
</div>
|
||
</div>
|
||
<div class="resize-handle-vertical" data-resize="nav-sections"></div>
|
||
</div>
|
||
|
||
<!-- Transmittal Folders Section -->
|
||
<div class="nav-section" id="transmittalSection">
|
||
<div class="nav-section-header">
|
||
<h3>Transmittal Folders</h3>
|
||
<button id="toggleAllDatesBtn" class="btn-icon" title="Expand/Collapse All">
|
||
<span id="toggleAllDatesIcon">▼</span>
|
||
</button>
|
||
</div>
|
||
<div class="filter-select-row">
|
||
<input type="text"
|
||
id="transmittalFilter"
|
||
class="filter-input"
|
||
placeholder="Filter transmittal folders...">
|
||
<label class="select-all-label select-all-inline" title="Auto-select all visible transmittals">
|
||
<span>Select<br>All</span>
|
||
<input type="checkbox" id="selectAllTransmittalsCheckbox" checked>
|
||
</label>
|
||
</div>
|
||
<div id="transmittalFoldersList" class="folder-list">
|
||
<!-- Dynamically populated -->
|
||
</div>
|
||
</div>
|
||
<div class="resize-handle-horizontal" data-resize="nav-pane"></div>
|
||
</nav>
|
||
|
||
<!-- Content Area -->
|
||
<main class="content-area">
|
||
<!-- Content Header -->
|
||
<div class="content-header">
|
||
<!-- Reset Filters -->
|
||
<button id="resetFiltersBtn" class="btn btn-secondary btn-icon-only" title="Reset all column filters">↺</button>
|
||
|
||
<!-- Preview toggle (default on; users can opt out for direct downloads) -->
|
||
<label class="preview-toggle-label" title="Preview PDF, Word, and Excel files in a popup window instead of downloading">
|
||
<input type="checkbox" id="filePreviewToggle" checked>
|
||
<span>Preview</span>
|
||
</label>
|
||
|
||
<!-- Modifier Filter Dropdown -->
|
||
<div class="modifier-filter-container">
|
||
<button id="modifierFilterBtn" class="btn btn-secondary modifier-filter-btn">
|
||
Modifiers ▼
|
||
</button>
|
||
<div id="modifierFilterDropdown" class="modifier-filter-dropdown hidden">
|
||
<div class="modifier-filter-header">
|
||
<label><input type="checkbox" id="modifierSelectAll" checked> Select All</label>
|
||
</div>
|
||
<div id="modifierFilterList" class="modifier-filter-list">
|
||
<!-- Dynamically populated -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="toolbar-separator"></div>
|
||
|
||
<div class="content-actions">
|
||
<button id="filterSelectedBtn" class="btn btn-secondary">Filter Selected</button>
|
||
<button id="downloadSelectedBtn" class="btn btn-secondary">Download (ZIP)</button>
|
||
<button id="exportCsvBtn" class="btn btn-secondary">Export (CSV)</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Files Table -->
|
||
<div class="table-container">
|
||
<table id="filesTable" class="files-table">
|
||
<thead>
|
||
<tr>
|
||
<th class="sortable resizable" data-field="trackingNumber">
|
||
<div class="th-content">
|
||
<span>Tracking Number</span>
|
||
<span class="sort-indicator"></span>
|
||
</div>
|
||
<input type="text"
|
||
class="column-filter"
|
||
data-filter-field="trackingNumber"
|
||
placeholder="Filter...">
|
||
<div class="resize-handle"></div>
|
||
</th>
|
||
<th class="sortable resizable" data-field="title">
|
||
<div class="th-content">
|
||
<span>Title</span>
|
||
<span class="sort-indicator"></span>
|
||
</div>
|
||
<input type="text"
|
||
class="column-filter"
|
||
data-filter-field="title"
|
||
placeholder="Filter...">
|
||
<div class="resize-handle"></div>
|
||
</th>
|
||
<th class="resizable" data-field="revisions">
|
||
<div class="th-content" style="justify-content: flex-start;">
|
||
<input type="checkbox"
|
||
id="selectAllVisibleCheckbox"
|
||
title="Select/deselect all visible files"
|
||
style="margin-right: 0.5rem;">
|
||
<span>Revisions</span>
|
||
</div>
|
||
<input type="text"
|
||
class="column-filter"
|
||
data-filter-field="revisions"
|
||
placeholder="Filter...">
|
||
<div class="resize-handle"></div>
|
||
</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="filesTableBody">
|
||
<!-- Dynamically populated -->
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- Status Bar -->
|
||
<div class="status-bar">
|
||
<span id="fileCount">0 files</span>
|
||
<span id="selectedCount">0 selected</span>
|
||
<span id="scanStatus"></span><span id="scanSpinner" class="scan-spinner hidden"></span>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
|
||
<!-- Drop Modal -->
|
||
<div id="dropModal" class="modal hidden">
|
||
<div class="modal-backdrop"></div>
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h2>Create Transmittal</h2>
|
||
<button class="modal-close">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="form-group">
|
||
<label>Transmittal Folder Name:</label>
|
||
<input type="text" id="transmittalName" class="form-input">
|
||
<small class="form-help">Format: YYYY-MM-DD_TRACKINGNUMBER (STATUS) - TITLE</small>
|
||
</div>
|
||
<div class="files-preview">
|
||
<h3>Files to Add:</h3>
|
||
<table class="preview-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Original Name</th>
|
||
<th>New Name</th>
|
||
<th>Status</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="filesPreviewBody">
|
||
<!-- Dynamically populated -->
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-secondary modal-cancel">Cancel</button>
|
||
<button class="btn btn-primary modal-confirm">Create Transmittal</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- No Directory Selected Message -->
|
||
<div id="noDirectoryMessage" class="empty-state">
|
||
<div class="empty-state-content">
|
||
<h2>Welcome to ZDDC Archive</h2>
|
||
<p>Click <strong>Add Local Directory</strong> to select an archive folder to browse.</p>
|
||
<p>This browser provides a convenient interface for searching and retrieving files from ZDDC-compliant archives.</p>
|
||
<p><strong>How to navigate:</strong></p>
|
||
<ul class="welcome-list">
|
||
<li>Select a party to see their transmittal folders; toggle folder types (Issued, Received, MDL, Incoming) above the list</li>
|
||
<li>Select transmittal folders to see their files</li>
|
||
<li>Use <kbd>Ctrl+Click</kbd> to select multiple folders</li>
|
||
<li>Use <kbd>Shift+Click</kbd> to select a range</li>
|
||
<li><kbd>Ctrl+Click</kbd> chevrons to recursively expand/collapse</li>
|
||
</ul>
|
||
|
||
<details class="windows-tip">
|
||
<summary><strong>⚠️ Windows Path Length Deficiency</strong></summary>
|
||
<div class="windows-tip__body">
|
||
<p>Microsoft Windows has a legacy 260-character path limit that affects most applications. If you see "files skipped" warnings, use Microsoft's own workaround:</p>
|
||
<ol>
|
||
<li>Open Command Prompt as Administrator</li>
|
||
<li>Map your archive to a short drive letter:<br>
|
||
<code class="windows-tip__code">subst Z: "C:\Your\Long\Path\To\Archive"</code>
|
||
</li>
|
||
<li>Use the <strong>Z:</strong> drive in Archive Browser</li>
|
||
<li>To remove later: <code>subst Z: /d</code></li>
|
||
</ol>
|
||
<p class="windows-tip__note">This limitation dates back to Windows 95. The mapping persists until reboot.</p>
|
||
</div>
|
||
</details>
|
||
|
||
<p class="note">Note: This application works entirely in your browser and does not transmit any data.</p>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<!-- Help Panel -->
|
||
<aside id="help-panel" class="help-panel" hidden aria-labelledby="help-panel-title">
|
||
<div class="help-panel__header">
|
||
<h2 id="help-panel-title" class="help-panel__title">Help — ZDDC Archive</h2>
|
||
<button type="button" class="help-panel__close" id="help-panel-close" aria-label="Close">×</button>
|
||
</div>
|
||
<div class="help-panel__body">
|
||
<h3>What is the Archive Browser?</h3>
|
||
<p>The Archive Browser lets you search and retrieve files from a ZDDC-compliant archive stored on your local file system. Everything runs in your browser — no data is transmitted anywhere.</p>
|
||
|
||
<h3>Getting Started</h3>
|
||
<ol>
|
||
<li>When opened from a web server, the archive loads automatically from that server.</li>
|
||
<li>Click <strong>Add Local Directory</strong> to open a local archive folder — works in both offline and online modes, and local files are merged with any server files already loaded.</li>
|
||
<li>The browser scans for grouping folders and transmittal folders automatically.</li>
|
||
<li>Select folders in the left panel to see their files in the main table.</li>
|
||
</ol>
|
||
|
||
<h3>Navigating Folders</h3>
|
||
<p>The left panel has two sections:</p>
|
||
<dl>
|
||
<dt>Parties</dt>
|
||
<dd>Top-level folders representing other parties. Select one or more to filter which transmittals are shown. Use the folder type buttons above the list to show or hide Issued, Received, MDL, and Incoming folder content.</dd>
|
||
<dt>Transmittal Folders</dt>
|
||
<dd>Grouped by date. Select one or more to filter which files appear in the table.</dd>
|
||
</dl>
|
||
<p><strong>Multi-select:</strong> Hold <kbd>Ctrl</kbd> and click to toggle individual folders. Hold <kbd>Shift</kbd> and click to select a range. <kbd>Ctrl+Click</kbd> a chevron (▶) to recursively expand or collapse all sub-folders.</p>
|
||
|
||
<h3>Searching and Filtering</h3>
|
||
<dl>
|
||
<dt>Column Filters</dt>
|
||
<dd>Type in the filter row under each column header to filter by tracking number, title, or revision/status/extension. Filters support the expression syntax below. Active filters are highlighted in blue; use the ↺ reset button in the toolbar to clear all filters at once.</dd>
|
||
</dl>
|
||
<dl>
|
||
<dt><code>term</code></dt>
|
||
<dd>Contains "term" (case-insensitive)</dd>
|
||
<dt><code>!term</code></dt>
|
||
<dd>Does not contain "term"</dd>
|
||
<dt><code>^term</code></dt>
|
||
<dd>Starts with "term"</dd>
|
||
<dt><code>term$</code></dt>
|
||
<dd>Ends with "term"</dd>
|
||
<dt><code>a b</code></dt>
|
||
<dd>Matches both (AND)</dd>
|
||
<dt><code>a | b</code></dt>
|
||
<dd>Matches either (OR)</dd>
|
||
<dt><code>^IFA | ^IFB</code></dt>
|
||
<dd>Starts with IFA or IFB</dd>
|
||
<dt><code>pdf !draft</code></dt>
|
||
<dd>Contains "pdf" and not "draft"</dd>
|
||
<dt><code>!^~</code></dt>
|
||
<dd>Does not start with ~ (excludes drafts)</dd>
|
||
<dt><code>el.*spc</code></dt>
|
||
<dd>Regex: contains "el" followed by "spc" (use <code>.</code> for any char, <code>.*</code> for any sequence)</dd>
|
||
<dt><code>[ei]fa</code></dt>
|
||
<dd>Regex character class: matches "efa" or "ifa"</dd>
|
||
</dl>
|
||
<dl>
|
||
<dt>Modifiers</dt>
|
||
<dd>Use the Modifiers dropdown to show or hide files by revision modifier type (+B, +C, +N, +Q, or base).</dd>
|
||
</dl>
|
||
|
||
<h3>Downloading Files</h3>
|
||
<dl>
|
||
<dt>Download Selected (ZIP)</dt>
|
||
<dd>Packages all checked files into a ZIP archive for download.</dd>
|
||
<dt>Export Selected (CSV)</dt>
|
||
<dd>Exports the visible file list as a CSV spreadsheet.</dd>
|
||
<dt>File Preview</dt>
|
||
<dd>When enabled, clicking a PDF, Word, or Excel file opens a preview popup instead of downloading it.</dd>
|
||
</dl>
|
||
|
||
<h3>Keyboard Shortcuts</h3>
|
||
<dl>
|
||
<dt><kbd>Ctrl+A</kbd></dt>
|
||
<dd>Select / deselect all visible files in the table.</dd>
|
||
<dt><kbd>F5</kbd></dt>
|
||
<dd>Refresh — rescan the current directory.</dd>
|
||
<dt><kbd>Escape</kbd></dt>
|
||
<dd>Close this help panel (or any open modal).</dd>
|
||
</dl>
|
||
|
||
<h3>Windows Path Length Note</h3>
|
||
<p>Windows limits file paths to 260 characters by default. If files are skipped during scanning, map your archive to a short drive letter using <code>subst Z: "C:\Your\Long\Path"</code> in an Administrator Command Prompt, then open the <strong>Z:</strong> drive in the Archive Browser.</p>
|
||
</div>
|
||
</aside>
|
||
|
||
</div>
|
||
|
||
<script>
|
||
/*!
|
||
|
||
JSZip v3.10.1 - A JavaScript class for generating and reading zip files
|
||
<http://stuartk.com/jszip>
|
||
|
||
(c) 2009-2016 Stuart Knightley <stuart [at] stuartk.com>
|
||
Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip/main/LICENSE.markdown.
|
||
|
||
JSZip uses the library pako released under the MIT license :
|
||
https://github.com/nodeca/pako/blob/main/LICENSE
|
||
*/
|
||
|
||
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).JSZip=e()}}(function(){return function s(a,o,h){function u(r,e){if(!o[r]){if(!a[r]){var t="function"==typeof require&&require;if(!e&&t)return t(r,!0);if(l)return l(r,!0);var n=new Error("Cannot find module '"+r+"'");throw n.code="MODULE_NOT_FOUND",n}var i=o[r]={exports:{}};a[r][0].call(i.exports,function(e){var t=a[r][1][e];return u(t||e)},i,i.exports,s,a,o,h)}return o[r].exports}for(var l="function"==typeof require&&require,e=0;e<h.length;e++)u(h[e]);return u}({1:[function(e,t,r){"use strict";var d=e("./utils"),c=e("./support"),p="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";r.encode=function(e){for(var t,r,n,i,s,a,o,h=[],u=0,l=e.length,f=l,c="string"!==d.getTypeOf(e);u<e.length;)f=l-u,n=c?(t=e[u++],r=u<l?e[u++]:0,u<l?e[u++]:0):(t=e.charCodeAt(u++),r=u<l?e.charCodeAt(u++):0,u<l?e.charCodeAt(u++):0),i=t>>2,s=(3&t)<<4|r>>4,a=1<f?(15&r)<<2|n>>6:64,o=2<f?63&n:64,h.push(p.charAt(i)+p.charAt(s)+p.charAt(a)+p.charAt(o));return h.join("")},r.decode=function(e){var t,r,n,i,s,a,o=0,h=0,u="data:";if(e.substr(0,u.length)===u)throw new Error("Invalid base64 input, it looks like a data url.");var l,f=3*(e=e.replace(/[^A-Za-z0-9+/=]/g,"")).length/4;if(e.charAt(e.length-1)===p.charAt(64)&&f--,e.charAt(e.length-2)===p.charAt(64)&&f--,f%1!=0)throw new Error("Invalid base64 input, bad content length.");for(l=c.uint8array?new Uint8Array(0|f):new Array(0|f);o<e.length;)t=p.indexOf(e.charAt(o++))<<2|(i=p.indexOf(e.charAt(o++)))>>4,r=(15&i)<<4|(s=p.indexOf(e.charAt(o++)))>>2,n=(3&s)<<6|(a=p.indexOf(e.charAt(o++))),l[h++]=t,64!==s&&(l[h++]=r),64!==a&&(l[h++]=n);return l}},{"./support":30,"./utils":32}],2:[function(e,t,r){"use strict";var n=e("./external"),i=e("./stream/DataWorker"),s=e("./stream/Crc32Probe"),a=e("./stream/DataLengthProbe");function o(e,t,r,n,i){this.compressedSize=e,this.uncompressedSize=t,this.crc32=r,this.compression=n,this.compressedContent=i}o.prototype={getContentWorker:function(){var e=new i(n.Promise.resolve(this.compressedContent)).pipe(this.compression.uncompressWorker()).pipe(new a("data_length")),t=this;return e.on("end",function(){if(this.streamInfo.data_length!==t.uncompressedSize)throw new Error("Bug : uncompressed data size mismatch")}),e},getCompressedWorker:function(){return new i(n.Promise.resolve(this.compressedContent)).withStreamInfo("compressedSize",this.compressedSize).withStreamInfo("uncompressedSize",this.uncompressedSize).withStreamInfo("crc32",this.crc32).withStreamInfo("compression",this.compression)}},o.createWorkerFrom=function(e,t,r){return e.pipe(new s).pipe(new a("uncompressedSize")).pipe(t.compressWorker(r)).pipe(new a("compressedSize")).withStreamInfo("compression",t)},t.exports=o},{"./external":6,"./stream/Crc32Probe":25,"./stream/DataLengthProbe":26,"./stream/DataWorker":27}],3:[function(e,t,r){"use strict";var n=e("./stream/GenericWorker");r.STORE={magic:"\0\0",compressWorker:function(){return new n("STORE compression")},uncompressWorker:function(){return new n("STORE decompression")}},r.DEFLATE=e("./flate")},{"./flate":7,"./stream/GenericWorker":28}],4:[function(e,t,r){"use strict";var n=e("./utils");var o=function(){for(var e,t=[],r=0;r<256;r++){e=r;for(var n=0;n<8;n++)e=1&e?3988292384^e>>>1:e>>>1;t[r]=e}return t}();t.exports=function(e,t){return void 0!==e&&e.length?"string"!==n.getTypeOf(e)?function(e,t,r,n){var i=o,s=n+r;e^=-1;for(var a=n;a<s;a++)e=e>>>8^i[255&(e^t[a])];return-1^e}(0|t,e,e.length,0):function(e,t,r,n){var i=o,s=n+r;e^=-1;for(var a=n;a<s;a++)e=e>>>8^i[255&(e^t.charCodeAt(a))];return-1^e}(0|t,e,e.length,0):0}},{"./utils":32}],5:[function(e,t,r){"use strict";r.base64=!1,r.binary=!1,r.dir=!1,r.createFolders=!0,r.date=null,r.compression=null,r.compressionOptions=null,r.comment=null,r.unixPermissions=null,r.dosPermissions=null},{}],6:[function(e,t,r){"use strict";var n=null;n="undefined"!=typeof Promise?Promise:e("lie"),t.exports={Promise:n}},{lie:37}],7:[function(e,t,r){"use strict";var n="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Uint32Array,i=e("pako"),s=e("./utils"),a=e("./stream/GenericWorker"),o=n?"uint8array":"array";function h(e,t){a.call(this,"FlateWorker/"+e),this._pako=null,this._pakoAction=e,this._pakoOptions=t,this.meta={}}r.magic="\b\0",s.inherits(h,a),h.prototype.processChunk=function(e){this.meta=e.meta,null===this._pako&&this._createPako(),this._pako.push(s.transformTo(o,e.data),!1)},h.prototype.flush=function(){a.prototype.flush.call(this),null===this._pako&&this._createPako(),this._pako.push([],!0)},h.prototype.cleanUp=function(){a.prototype.cleanUp.call(this),this._pako=null},h.prototype._createPako=function(){this._pako=new i[this._pakoAction]({raw:!0,level:this._pakoOptions.level||-1});var t=this;this._pako.onData=function(e){t.push({data:e,meta:t.meta})}},r.compressWorker=function(e){return new h("Deflate",e)},r.uncompressWorker=function(){return new h("Inflate",{})}},{"./stream/GenericWorker":28,"./utils":32,pako:38}],8:[function(e,t,r){"use strict";function A(e,t){var r,n="";for(r=0;r<t;r++)n+=String.fromCharCode(255&e),e>>>=8;return n}function n(e,t,r,n,i,s){var a,o,h=e.file,u=e.compression,l=s!==O.utf8encode,f=I.transformTo("string",s(h.name)),c=I.transformTo("string",O.utf8encode(h.name)),d=h.comment,p=I.transformTo("string",s(d)),m=I.transformTo("string",O.utf8encode(d)),_=c.length!==h.name.length,g=m.length!==d.length,b="",v="",y="",w=h.dir,k=h.date,x={crc32:0,compressedSize:0,uncompressedSize:0};t&&!r||(x.crc32=e.crc32,x.compressedSize=e.compressedSize,x.uncompressedSize=e.uncompressedSize);var S=0;t&&(S|=8),l||!_&&!g||(S|=2048);var z=0,C=0;w&&(z|=16),"UNIX"===i?(C=798,z|=function(e,t){var r=e;return e||(r=t?16893:33204),(65535&r)<<16}(h.unixPermissions,w)):(C=20,z|=function(e){return 63&(e||0)}(h.dosPermissions)),a=k.getUTCHours(),a<<=6,a|=k.getUTCMinutes(),a<<=5,a|=k.getUTCSeconds()/2,o=k.getUTCFullYear()-1980,o<<=4,o|=k.getUTCMonth()+1,o<<=5,o|=k.getUTCDate(),_&&(v=A(1,1)+A(B(f),4)+c,b+="up"+A(v.length,2)+v),g&&(y=A(1,1)+A(B(p),4)+m,b+="uc"+A(y.length,2)+y);var E="";return E+="\n\0",E+=A(S,2),E+=u.magic,E+=A(a,2),E+=A(o,2),E+=A(x.crc32,4),E+=A(x.compressedSize,4),E+=A(x.uncompressedSize,4),E+=A(f.length,2),E+=A(b.length,2),{fileRecord:R.LOCAL_FILE_HEADER+E+f+b,dirRecord:R.CENTRAL_FILE_HEADER+A(C,2)+E+A(p.length,2)+"\0\0\0\0"+A(z,4)+A(n,4)+f+b+p}}var I=e("../utils"),i=e("../stream/GenericWorker"),O=e("../utf8"),B=e("../crc32"),R=e("../signature");function s(e,t,r,n){i.call(this,"ZipFileWorker"),this.bytesWritten=0,this.zipComment=t,this.zipPlatform=r,this.encodeFileName=n,this.streamFiles=e,this.accumulate=!1,this.contentBuffer=[],this.dirRecords=[],this.currentSourceOffset=0,this.entriesCount=0,this.currentFile=null,this._sources=[]}I.inherits(s,i),s.prototype.push=function(e){var t=e.meta.percent||0,r=this.entriesCount,n=this._sources.length;this.accumulate?this.contentBuffer.push(e):(this.bytesWritten+=e.data.length,i.prototype.push.call(this,{data:e.data,meta:{currentFile:this.currentFile,percent:r?(t+100*(r-n-1))/r:100}}))},s.prototype.openedSource=function(e){this.currentSourceOffset=this.bytesWritten,this.currentFile=e.file.name;var t=this.streamFiles&&!e.file.dir;if(t){var r=n(e,t,!1,this.currentSourceOffset,this.zipPlatform,this.encodeFileName);this.push({data:r.fileRecord,meta:{percent:0}})}else this.accumulate=!0},s.prototype.closedSource=function(e){this.accumulate=!1;var t=this.streamFiles&&!e.file.dir,r=n(e,t,!0,this.currentSourceOffset,this.zipPlatform,this.encodeFileName);if(this.dirRecords.push(r.dirRecord),t)this.push({data:function(e){return R.DATA_DESCRIPTOR+A(e.crc32,4)+A(e.compressedSize,4)+A(e.uncompressedSize,4)}(e),meta:{percent:100}});else for(this.push({data:r.fileRecord,meta:{percent:0}});this.contentBuffer.length;)this.push(this.contentBuffer.shift());this.currentFile=null},s.prototype.flush=function(){for(var e=this.bytesWritten,t=0;t<this.dirRecords.length;t++)this.push({data:this.dirRecords[t],meta:{percent:100}});var r=this.bytesWritten-e,n=function(e,t,r,n,i){var s=I.transformTo("string",i(n));return R.CENTRAL_DIRECTORY_END+"\0\0\0\0"+A(e,2)+A(e,2)+A(t,4)+A(r,4)+A(s.length,2)+s}(this.dirRecords.length,r,e,this.zipComment,this.encodeFileName);this.push({data:n,meta:{percent:100}})},s.prototype.prepareNextSource=function(){this.previous=this._sources.shift(),this.openedSource(this.previous.streamInfo),this.isPaused?this.previous.pause():this.previous.resume()},s.prototype.registerPrevious=function(e){this._sources.push(e);var t=this;return e.on("data",function(e){t.processChunk(e)}),e.on("end",function(){t.closedSource(t.previous.streamInfo),t._sources.length?t.prepareNextSource():t.end()}),e.on("error",function(e){t.error(e)}),this},s.prototype.resume=function(){return!!i.prototype.resume.call(this)&&(!this.previous&&this._sources.length?(this.prepareNextSource(),!0):this.previous||this._sources.length||this.generatedError?void 0:(this.end(),!0))},s.prototype.error=function(e){var t=this._sources;if(!i.prototype.error.call(this,e))return!1;for(var r=0;r<t.length;r++)try{t[r].error(e)}catch(e){}return!0},s.prototype.lock=function(){i.prototype.lock.call(this);for(var e=this._sources,t=0;t<e.length;t++)e[t].lock()},t.exports=s},{"../crc32":4,"../signature":23,"../stream/GenericWorker":28,"../utf8":31,"../utils":32}],9:[function(e,t,r){"use strict";var u=e("../compressions"),n=e("./ZipFileWorker");r.generateWorker=function(e,a,t){var o=new n(a.streamFiles,t,a.platform,a.encodeFileName),h=0;try{e.forEach(function(e,t){h++;var r=function(e,t){var r=e||t,n=u[r];if(!n)throw new Error(r+" is not a valid compression method !");return n}(t.options.compression,a.compression),n=t.options.compressionOptions||a.compressionOptions||{},i=t.dir,s=t.date;t._compressWorker(r,n).withStreamInfo("file",{name:e,dir:i,date:s,comment:t.comment||"",unixPermissions:t.unixPermissions,dosPermissions:t.dosPermissions}).pipe(o)}),o.entriesCount=h}catch(e){o.error(e)}return o}},{"../compressions":3,"./ZipFileWorker":8}],10:[function(e,t,r){"use strict";function n(){if(!(this instanceof n))return new n;if(arguments.length)throw new Error("The constructor with parameters has been removed in JSZip 3.0, please check the upgrade guide.");this.files=Object.create(null),this.comment=null,this.root="",this.clone=function(){var e=new n;for(var t in this)"function"!=typeof this[t]&&(e[t]=this[t]);return e}}(n.prototype=e("./object")).loadAsync=e("./load"),n.support=e("./support"),n.defaults=e("./defaults"),n.version="3.10.1",n.loadAsync=function(e,t){return(new n).loadAsync(e,t)},n.external=e("./external"),t.exports=n},{"./defaults":5,"./external":6,"./load":11,"./object":15,"./support":30}],11:[function(e,t,r){"use strict";var u=e("./utils"),i=e("./external"),n=e("./utf8"),s=e("./zipEntries"),a=e("./stream/Crc32Probe"),l=e("./nodejsUtils");function f(n){return new i.Promise(function(e,t){var r=n.decompressed.getContentWorker().pipe(new a);r.on("error",function(e){t(e)}).on("end",function(){r.streamInfo.crc32!==n.decompressed.crc32?t(new Error("Corrupted zip : CRC32 mismatch")):e()}).resume()})}t.exports=function(e,o){var h=this;return o=u.extend(o||{},{base64:!1,checkCRC32:!1,optimizedBinaryString:!1,createFolders:!1,decodeFileName:n.utf8decode}),l.isNode&&l.isStream(e)?i.Promise.reject(new Error("JSZip can't accept a stream when loading a zip file.")):u.prepareContent("the loaded zip file",e,!0,o.optimizedBinaryString,o.base64).then(function(e){var t=new s(o);return t.load(e),t}).then(function(e){var t=[i.Promise.resolve(e)],r=e.files;if(o.checkCRC32)for(var n=0;n<r.length;n++)t.push(f(r[n]));return i.Promise.all(t)}).then(function(e){for(var t=e.shift(),r=t.files,n=0;n<r.length;n++){var i=r[n],s=i.fileNameStr,a=u.resolve(i.fileNameStr);h.file(a,i.decompressed,{binary:!0,optimizedBinaryString:!0,date:i.date,dir:i.dir,comment:i.fileCommentStr.length?i.fileCommentStr:null,unixPermissions:i.unixPermissions,dosPermissions:i.dosPermissions,createFolders:o.createFolders}),i.dir||(h.file(a).unsafeOriginalName=s)}return t.zipComment.length&&(h.comment=t.zipComment),h})}},{"./external":6,"./nodejsUtils":14,"./stream/Crc32Probe":25,"./utf8":31,"./utils":32,"./zipEntries":33}],12:[function(e,t,r){"use strict";var n=e("../utils"),i=e("../stream/GenericWorker");function s(e,t){i.call(this,"Nodejs stream input adapter for "+e),this._upstreamEnded=!1,this._bindStream(t)}n.inherits(s,i),s.prototype._bindStream=function(e){var t=this;(this._stream=e).pause(),e.on("data",function(e){t.push({data:e,meta:{percent:0}})}).on("error",function(e){t.isPaused?this.generatedError=e:t.error(e)}).on("end",function(){t.isPaused?t._upstreamEnded=!0:t.end()})},s.prototype.pause=function(){return!!i.prototype.pause.call(this)&&(this._stream.pause(),!0)},s.prototype.resume=function(){return!!i.prototype.resume.call(this)&&(this._upstreamEnded?this.end():this._stream.resume(),!0)},t.exports=s},{"../stream/GenericWorker":28,"../utils":32}],13:[function(e,t,r){"use strict";var i=e("readable-stream").Readable;function n(e,t,r){i.call(this,t),this._helper=e;var n=this;e.on("data",function(e,t){n.push(e)||n._helper.pause(),r&&r(t)}).on("error",function(e){n.emit("error",e)}).on("end",function(){n.push(null)})}e("../utils").inherits(n,i),n.prototype._read=function(){this._helper.resume()},t.exports=n},{"../utils":32,"readable-stream":16}],14:[function(e,t,r){"use strict";t.exports={isNode:"undefined"!=typeof Buffer,newBufferFrom:function(e,t){if(Buffer.from&&Buffer.from!==Uint8Array.from)return Buffer.from(e,t);if("number"==typeof e)throw new Error('The "data" argument must not be a number');return new Buffer(e,t)},allocBuffer:function(e){if(Buffer.alloc)return Buffer.alloc(e);var t=new Buffer(e);return t.fill(0),t},isBuffer:function(e){return Buffer.isBuffer(e)},isStream:function(e){return e&&"function"==typeof e.on&&"function"==typeof e.pause&&"function"==typeof e.resume}}},{}],15:[function(e,t,r){"use strict";function s(e,t,r){var n,i=u.getTypeOf(t),s=u.extend(r||{},f);s.date=s.date||new Date,null!==s.compression&&(s.compression=s.compression.toUpperCase()),"string"==typeof s.unixPermissions&&(s.unixPermissions=parseInt(s.unixPermissions,8)),s.unixPermissions&&16384&s.unixPermissions&&(s.dir=!0),s.dosPermissions&&16&s.dosPermissions&&(s.dir=!0),s.dir&&(e=g(e)),s.createFolders&&(n=_(e))&&b.call(this,n,!0);var a="string"===i&&!1===s.binary&&!1===s.base64;r&&void 0!==r.binary||(s.binary=!a),(t instanceof c&&0===t.uncompressedSize||s.dir||!t||0===t.length)&&(s.base64=!1,s.binary=!0,t="",s.compression="STORE",i="string");var o=null;o=t instanceof c||t instanceof l?t:p.isNode&&p.isStream(t)?new m(e,t):u.prepareContent(e,t,s.binary,s.optimizedBinaryString,s.base64);var h=new d(e,o,s);this.files[e]=h}var i=e("./utf8"),u=e("./utils"),l=e("./stream/GenericWorker"),a=e("./stream/StreamHelper"),f=e("./defaults"),c=e("./compressedObject"),d=e("./zipObject"),o=e("./generate"),p=e("./nodejsUtils"),m=e("./nodejs/NodejsStreamInputAdapter"),_=function(e){"/"===e.slice(-1)&&(e=e.substring(0,e.length-1));var t=e.lastIndexOf("/");return 0<t?e.substring(0,t):""},g=function(e){return"/"!==e.slice(-1)&&(e+="/"),e},b=function(e,t){return t=void 0!==t?t:f.createFolders,e=g(e),this.files[e]||s.call(this,e,null,{dir:!0,createFolders:t}),this.files[e]};function h(e){return"[object RegExp]"===Object.prototype.toString.call(e)}var n={load:function(){throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide.")},forEach:function(e){var t,r,n;for(t in this.files)n=this.files[t],(r=t.slice(this.root.length,t.length))&&t.slice(0,this.root.length)===this.root&&e(r,n)},filter:function(r){var n=[];return this.forEach(function(e,t){r(e,t)&&n.push(t)}),n},file:function(e,t,r){if(1!==arguments.length)return e=this.root+e,s.call(this,e,t,r),this;if(h(e)){var n=e;return this.filter(function(e,t){return!t.dir&&n.test(e)})}var i=this.files[this.root+e];return i&&!i.dir?i:null},folder:function(r){if(!r)return this;if(h(r))return this.filter(function(e,t){return t.dir&&r.test(e)});var e=this.root+r,t=b.call(this,e),n=this.clone();return n.root=t.name,n},remove:function(r){r=this.root+r;var e=this.files[r];if(e||("/"!==r.slice(-1)&&(r+="/"),e=this.files[r]),e&&!e.dir)delete this.files[r];else for(var t=this.filter(function(e,t){return t.name.slice(0,r.length)===r}),n=0;n<t.length;n++)delete this.files[t[n].name];return this},generate:function(){throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide.")},generateInternalStream:function(e){var t,r={};try{if((r=u.extend(e||{},{streamFiles:!1,compression:"STORE",compressionOptions:null,type:"",platform:"DOS",comment:null,mimeType:"application/zip",encodeFileName:i.utf8encode})).type=r.type.toLowerCase(),r.compression=r.compression.toUpperCase(),"binarystring"===r.type&&(r.type="string"),!r.type)throw new Error("No output type specified.");u.checkSupport(r.type),"darwin"!==r.platform&&"freebsd"!==r.platform&&"linux"!==r.platform&&"sunos"!==r.platform||(r.platform="UNIX"),"win32"===r.platform&&(r.platform="DOS");var n=r.comment||this.comment||"";t=o.generateWorker(this,r,n)}catch(e){(t=new l("error")).error(e)}return new a(t,r.type||"string",r.mimeType)},generateAsync:function(e,t){return this.generateInternalStream(e).accumulate(t)},generateNodeStream:function(e,t){return(e=e||{}).type||(e.type="nodebuffer"),this.generateInternalStream(e).toNodejsStream(t)}};t.exports=n},{"./compressedObject":2,"./defaults":5,"./generate":9,"./nodejs/NodejsStreamInputAdapter":12,"./nodejsUtils":14,"./stream/GenericWorker":28,"./stream/StreamHelper":29,"./utf8":31,"./utils":32,"./zipObject":35}],16:[function(e,t,r){"use strict";t.exports=e("stream")},{stream:void 0}],17:[function(e,t,r){"use strict";var n=e("./DataReader");function i(e){n.call(this,e);for(var t=0;t<this.data.length;t++)e[t]=255&e[t]}e("../utils").inherits(i,n),i.prototype.byteAt=function(e){return this.data[this.zero+e]},i.prototype.lastIndexOfSignature=function(e){for(var t=e.charCodeAt(0),r=e.charCodeAt(1),n=e.charCodeAt(2),i=e.charCodeAt(3),s=this.length-4;0<=s;--s)if(this.data[s]===t&&this.data[s+1]===r&&this.data[s+2]===n&&this.data[s+3]===i)return s-this.zero;return-1},i.prototype.readAndCheckSignature=function(e){var t=e.charCodeAt(0),r=e.charCodeAt(1),n=e.charCodeAt(2),i=e.charCodeAt(3),s=this.readData(4);return t===s[0]&&r===s[1]&&n===s[2]&&i===s[3]},i.prototype.readData=function(e){if(this.checkOffset(e),0===e)return[];var t=this.data.slice(this.zero+this.index,this.zero+this.index+e);return this.index+=e,t},t.exports=i},{"../utils":32,"./DataReader":18}],18:[function(e,t,r){"use strict";var n=e("../utils");function i(e){this.data=e,this.length=e.length,this.index=0,this.zero=0}i.prototype={checkOffset:function(e){this.checkIndex(this.index+e)},checkIndex:function(e){if(this.length<this.zero+e||e<0)throw new Error("End of data reached (data length = "+this.length+", asked index = "+e+"). Corrupted zip ?")},setIndex:function(e){this.checkIndex(e),this.index=e},skip:function(e){this.setIndex(this.index+e)},byteAt:function(){},readInt:function(e){var t,r=0;for(this.checkOffset(e),t=this.index+e-1;t>=this.index;t--)r=(r<<8)+this.byteAt(t);return this.index+=e,r},readString:function(e){return n.transformTo("string",this.readData(e))},readData:function(){},lastIndexOfSignature:function(){},readAndCheckSignature:function(){},readDate:function(){var e=this.readInt(4);return new Date(Date.UTC(1980+(e>>25&127),(e>>21&15)-1,e>>16&31,e>>11&31,e>>5&63,(31&e)<<1))}},t.exports=i},{"../utils":32}],19:[function(e,t,r){"use strict";var n=e("./Uint8ArrayReader");function i(e){n.call(this,e)}e("../utils").inherits(i,n),i.prototype.readData=function(e){this.checkOffset(e);var t=this.data.slice(this.zero+this.index,this.zero+this.index+e);return this.index+=e,t},t.exports=i},{"../utils":32,"./Uint8ArrayReader":21}],20:[function(e,t,r){"use strict";var n=e("./DataReader");function i(e){n.call(this,e)}e("../utils").inherits(i,n),i.prototype.byteAt=function(e){return this.data.charCodeAt(this.zero+e)},i.prototype.lastIndexOfSignature=function(e){return this.data.lastIndexOf(e)-this.zero},i.prototype.readAndCheckSignature=function(e){return e===this.readData(4)},i.prototype.readData=function(e){this.checkOffset(e);var t=this.data.slice(this.zero+this.index,this.zero+this.index+e);return this.index+=e,t},t.exports=i},{"../utils":32,"./DataReader":18}],21:[function(e,t,r){"use strict";var n=e("./ArrayReader");function i(e){n.call(this,e)}e("../utils").inherits(i,n),i.prototype.readData=function(e){if(this.checkOffset(e),0===e)return new Uint8Array(0);var t=this.data.subarray(this.zero+this.index,this.zero+this.index+e);return this.index+=e,t},t.exports=i},{"../utils":32,"./ArrayReader":17}],22:[function(e,t,r){"use strict";var n=e("../utils"),i=e("../support"),s=e("./ArrayReader"),a=e("./StringReader"),o=e("./NodeBufferReader"),h=e("./Uint8ArrayReader");t.exports=function(e){var t=n.getTypeOf(e);return n.checkSupport(t),"string"!==t||i.uint8array?"nodebuffer"===t?new o(e):i.uint8array?new h(n.transformTo("uint8array",e)):new s(n.transformTo("array",e)):new a(e)}},{"../support":30,"../utils":32,"./ArrayReader":17,"./NodeBufferReader":19,"./StringReader":20,"./Uint8ArrayReader":21}],23:[function(e,t,r){"use strict";r.LOCAL_FILE_HEADER="PK",r.CENTRAL_FILE_HEADER="PK",r.CENTRAL_DIRECTORY_END="PK",r.ZIP64_CENTRAL_DIRECTORY_LOCATOR="PK",r.ZIP64_CENTRAL_DIRECTORY_END="PK",r.DATA_DESCRIPTOR="PK\b"},{}],24:[function(e,t,r){"use strict";var n=e("./GenericWorker"),i=e("../utils");function s(e){n.call(this,"ConvertWorker to "+e),this.destType=e}i.inherits(s,n),s.prototype.processChunk=function(e){this.push({data:i.transformTo(this.destType,e.data),meta:e.meta})},t.exports=s},{"../utils":32,"./GenericWorker":28}],25:[function(e,t,r){"use strict";var n=e("./GenericWorker"),i=e("../crc32");function s(){n.call(this,"Crc32Probe"),this.withStreamInfo("crc32",0)}e("../utils").inherits(s,n),s.prototype.processChunk=function(e){this.streamInfo.crc32=i(e.data,this.streamInfo.crc32||0),this.push(e)},t.exports=s},{"../crc32":4,"../utils":32,"./GenericWorker":28}],26:[function(e,t,r){"use strict";var n=e("../utils"),i=e("./GenericWorker");function s(e){i.call(this,"DataLengthProbe for "+e),this.propName=e,this.withStreamInfo(e,0)}n.inherits(s,i),s.prototype.processChunk=function(e){if(e){var t=this.streamInfo[this.propName]||0;this.streamInfo[this.propName]=t+e.data.length}i.prototype.processChunk.call(this,e)},t.exports=s},{"../utils":32,"./GenericWorker":28}],27:[function(e,t,r){"use strict";var n=e("../utils"),i=e("./GenericWorker");function s(e){i.call(this,"DataWorker");var t=this;this.dataIsReady=!1,this.index=0,this.max=0,this.data=null,this.type="",this._tickScheduled=!1,e.then(function(e){t.dataIsReady=!0,t.data=e,t.max=e&&e.length||0,t.type=n.getTypeOf(e),t.isPaused||t._tickAndRepeat()},function(e){t.error(e)})}n.inherits(s,i),s.prototype.cleanUp=function(){i.prototype.cleanUp.call(this),this.data=null},s.prototype.resume=function(){return!!i.prototype.resume.call(this)&&(!this._tickScheduled&&this.dataIsReady&&(this._tickScheduled=!0,n.delay(this._tickAndRepeat,[],this)),!0)},s.prototype._tickAndRepeat=function(){this._tickScheduled=!1,this.isPaused||this.isFinished||(this._tick(),this.isFinished||(n.delay(this._tickAndRepeat,[],this),this._tickScheduled=!0))},s.prototype._tick=function(){if(this.isPaused||this.isFinished)return!1;var e=null,t=Math.min(this.max,this.index+16384);if(this.index>=this.max)return this.end();switch(this.type){case"string":e=this.data.substring(this.index,t);break;case"uint8array":e=this.data.subarray(this.index,t);break;case"array":case"nodebuffer":e=this.data.slice(this.index,t)}return this.index=t,this.push({data:e,meta:{percent:this.max?this.index/this.max*100:0}})},t.exports=s},{"../utils":32,"./GenericWorker":28}],28:[function(e,t,r){"use strict";function n(e){this.name=e||"default",this.streamInfo={},this.generatedError=null,this.extraStreamInfo={},this.isPaused=!0,this.isFinished=!1,this.isLocked=!1,this._listeners={data:[],end:[],error:[]},this.previous=null}n.prototype={push:function(e){this.emit("data",e)},end:function(){if(this.isFinished)return!1;this.flush();try{this.emit("end"),this.cleanUp(),this.isFinished=!0}catch(e){this.emit("error",e)}return!0},error:function(e){return!this.isFinished&&(this.isPaused?this.generatedError=e:(this.isFinished=!0,this.emit("error",e),this.previous&&this.previous.error(e),this.cleanUp()),!0)},on:function(e,t){return this._listeners[e].push(t),this},cleanUp:function(){this.streamInfo=this.generatedError=this.extraStreamInfo=null,this._listeners=[]},emit:function(e,t){if(this._listeners[e])for(var r=0;r<this._listeners[e].length;r++)this._listeners[e][r].call(this,t)},pipe:function(e){return e.registerPrevious(this)},registerPrevious:function(e){if(this.isLocked)throw new Error("The stream '"+this+"' has already been used.");this.streamInfo=e.streamInfo,this.mergeStreamInfo(),this.previous=e;var t=this;return e.on("data",function(e){t.processChunk(e)}),e.on("end",function(){t.end()}),e.on("error",function(e){t.error(e)}),this},pause:function(){return!this.isPaused&&!this.isFinished&&(this.isPaused=!0,this.previous&&this.previous.pause(),!0)},resume:function(){if(!this.isPaused||this.isFinished)return!1;var e=this.isPaused=!1;return this.generatedError&&(this.error(this.generatedError),e=!0),this.previous&&this.previous.resume(),!e},flush:function(){},processChunk:function(e){this.push(e)},withStreamInfo:function(e,t){return this.extraStreamInfo[e]=t,this.mergeStreamInfo(),this},mergeStreamInfo:function(){for(var e in this.extraStreamInfo)Object.prototype.hasOwnProperty.call(this.extraStreamInfo,e)&&(this.streamInfo[e]=this.extraStreamInfo[e])},lock:function(){if(this.isLocked)throw new Error("The stream '"+this+"' has already been used.");this.isLocked=!0,this.previous&&this.previous.lock()},toString:function(){var e="Worker "+this.name;return this.previous?this.previous+" -> "+e:e}},t.exports=n},{}],29:[function(e,t,r){"use strict";var h=e("../utils"),i=e("./ConvertWorker"),s=e("./GenericWorker"),u=e("../base64"),n=e("../support"),a=e("../external"),o=null;if(n.nodestream)try{o=e("../nodejs/NodejsStreamOutputAdapter")}catch(e){}function l(e,o){return new a.Promise(function(t,r){var n=[],i=e._internalType,s=e._outputType,a=e._mimeType;e.on("data",function(e,t){n.push(e),o&&o(t)}).on("error",function(e){n=[],r(e)}).on("end",function(){try{var e=function(e,t,r){switch(e){case"blob":return h.newBlob(h.transformTo("arraybuffer",t),r);case"base64":return u.encode(t);default:return h.transformTo(e,t)}}(s,function(e,t){var r,n=0,i=null,s=0;for(r=0;r<t.length;r++)s+=t[r].length;switch(e){case"string":return t.join("");case"array":return Array.prototype.concat.apply([],t);case"uint8array":for(i=new Uint8Array(s),r=0;r<t.length;r++)i.set(t[r],n),n+=t[r].length;return i;case"nodebuffer":return Buffer.concat(t);default:throw new Error("concat : unsupported type '"+e+"'")}}(i,n),a);t(e)}catch(e){r(e)}n=[]}).resume()})}function f(e,t,r){var n=t;switch(t){case"blob":case"arraybuffer":n="uint8array";break;case"base64":n="string"}try{this._internalType=n,this._outputType=t,this._mimeType=r,h.checkSupport(n),this._worker=e.pipe(new i(n)),e.lock()}catch(e){this._worker=new s("error"),this._worker.error(e)}}f.prototype={accumulate:function(e){return l(this,e)},on:function(e,t){var r=this;return"data"===e?this._worker.on(e,function(e){t.call(r,e.data,e.meta)}):this._worker.on(e,function(){h.delay(t,arguments,r)}),this},resume:function(){return h.delay(this._worker.resume,[],this._worker),this},pause:function(){return this._worker.pause(),this},toNodejsStream:function(e){if(h.checkSupport("nodestream"),"nodebuffer"!==this._outputType)throw new Error(this._outputType+" is not supported by this method");return new o(this,{objectMode:"nodebuffer"!==this._outputType},e)}},t.exports=f},{"../base64":1,"../external":6,"../nodejs/NodejsStreamOutputAdapter":13,"../support":30,"../utils":32,"./ConvertWorker":24,"./GenericWorker":28}],30:[function(e,t,r){"use strict";if(r.base64=!0,r.array=!0,r.string=!0,r.arraybuffer="undefined"!=typeof ArrayBuffer&&"undefined"!=typeof Uint8Array,r.nodebuffer="undefined"!=typeof Buffer,r.uint8array="undefined"!=typeof Uint8Array,"undefined"==typeof ArrayBuffer)r.blob=!1;else{var n=new ArrayBuffer(0);try{r.blob=0===new Blob([n],{type:"application/zip"}).size}catch(e){try{var i=new(self.BlobBuilder||self.WebKitBlobBuilder||self.MozBlobBuilder||self.MSBlobBuilder);i.append(n),r.blob=0===i.getBlob("application/zip").size}catch(e){r.blob=!1}}}try{r.nodestream=!!e("readable-stream").Readable}catch(e){r.nodestream=!1}},{"readable-stream":16}],31:[function(e,t,s){"use strict";for(var o=e("./utils"),h=e("./support"),r=e("./nodejsUtils"),n=e("./stream/GenericWorker"),u=new Array(256),i=0;i<256;i++)u[i]=252<=i?6:248<=i?5:240<=i?4:224<=i?3:192<=i?2:1;u[254]=u[254]=1;function a(){n.call(this,"utf-8 decode"),this.leftOver=null}function l(){n.call(this,"utf-8 encode")}s.utf8encode=function(e){return h.nodebuffer?r.newBufferFrom(e,"utf-8"):function(e){var t,r,n,i,s,a=e.length,o=0;for(i=0;i<a;i++)55296==(64512&(r=e.charCodeAt(i)))&&i+1<a&&56320==(64512&(n=e.charCodeAt(i+1)))&&(r=65536+(r-55296<<10)+(n-56320),i++),o+=r<128?1:r<2048?2:r<65536?3:4;for(t=h.uint8array?new Uint8Array(o):new Array(o),i=s=0;s<o;i++)55296==(64512&(r=e.charCodeAt(i)))&&i+1<a&&56320==(64512&(n=e.charCodeAt(i+1)))&&(r=65536+(r-55296<<10)+(n-56320),i++),r<128?t[s++]=r:(r<2048?t[s++]=192|r>>>6:(r<65536?t[s++]=224|r>>>12:(t[s++]=240|r>>>18,t[s++]=128|r>>>12&63),t[s++]=128|r>>>6&63),t[s++]=128|63&r);return t}(e)},s.utf8decode=function(e){return h.nodebuffer?o.transformTo("nodebuffer",e).toString("utf-8"):function(e){var t,r,n,i,s=e.length,a=new Array(2*s);for(t=r=0;t<s;)if((n=e[t++])<128)a[r++]=n;else if(4<(i=u[n]))a[r++]=65533,t+=i-1;else{for(n&=2===i?31:3===i?15:7;1<i&&t<s;)n=n<<6|63&e[t++],i--;1<i?a[r++]=65533:n<65536?a[r++]=n:(n-=65536,a[r++]=55296|n>>10&1023,a[r++]=56320|1023&n)}return a.length!==r&&(a.subarray?a=a.subarray(0,r):a.length=r),o.applyFromCharCode(a)}(e=o.transformTo(h.uint8array?"uint8array":"array",e))},o.inherits(a,n),a.prototype.processChunk=function(e){var t=o.transformTo(h.uint8array?"uint8array":"array",e.data);if(this.leftOver&&this.leftOver.length){if(h.uint8array){var r=t;(t=new Uint8Array(r.length+this.leftOver.length)).set(this.leftOver,0),t.set(r,this.leftOver.length)}else t=this.leftOver.concat(t);this.leftOver=null}var n=function(e,t){var r;for((t=t||e.length)>e.length&&(t=e.length),r=t-1;0<=r&&128==(192&e[r]);)r--;return r<0?t:0===r?t:r+u[e[r]]>t?r:t}(t),i=t;n!==t.length&&(h.uint8array?(i=t.subarray(0,n),this.leftOver=t.subarray(n,t.length)):(i=t.slice(0,n),this.leftOver=t.slice(n,t.length))),this.push({data:s.utf8decode(i),meta:e.meta})},a.prototype.flush=function(){this.leftOver&&this.leftOver.length&&(this.push({data:s.utf8decode(this.leftOver),meta:{}}),this.leftOver=null)},s.Utf8DecodeWorker=a,o.inherits(l,n),l.prototype.processChunk=function(e){this.push({data:s.utf8encode(e.data),meta:e.meta})},s.Utf8EncodeWorker=l},{"./nodejsUtils":14,"./stream/GenericWorker":28,"./support":30,"./utils":32}],32:[function(e,t,a){"use strict";var o=e("./support"),h=e("./base64"),r=e("./nodejsUtils"),u=e("./external");function n(e){return e}function l(e,t){for(var r=0;r<e.length;++r)t[r]=255&e.charCodeAt(r);return t}e("setimmediate"),a.newBlob=function(t,r){a.checkSupport("blob");try{return new Blob([t],{type:r})}catch(e){try{var n=new(self.BlobBuilder||self.WebKitBlobBuilder||self.MozBlobBuilder||self.MSBlobBuilder);return n.append(t),n.getBlob(r)}catch(e){throw new Error("Bug : can't construct the Blob.")}}};var i={stringifyByChunk:function(e,t,r){var n=[],i=0,s=e.length;if(s<=r)return String.fromCharCode.apply(null,e);for(;i<s;)"array"===t||"nodebuffer"===t?n.push(String.fromCharCode.apply(null,e.slice(i,Math.min(i+r,s)))):n.push(String.fromCharCode.apply(null,e.subarray(i,Math.min(i+r,s)))),i+=r;return n.join("")},stringifyByChar:function(e){for(var t="",r=0;r<e.length;r++)t+=String.fromCharCode(e[r]);return t},applyCanBeUsed:{uint8array:function(){try{return o.uint8array&&1===String.fromCharCode.apply(null,new Uint8Array(1)).length}catch(e){return!1}}(),nodebuffer:function(){try{return o.nodebuffer&&1===String.fromCharCode.apply(null,r.allocBuffer(1)).length}catch(e){return!1}}()}};function s(e){var t=65536,r=a.getTypeOf(e),n=!0;if("uint8array"===r?n=i.applyCanBeUsed.uint8array:"nodebuffer"===r&&(n=i.applyCanBeUsed.nodebuffer),n)for(;1<t;)try{return i.stringifyByChunk(e,r,t)}catch(e){t=Math.floor(t/2)}return i.stringifyByChar(e)}function f(e,t){for(var r=0;r<e.length;r++)t[r]=e[r];return t}a.applyFromCharCode=s;var c={};c.string={string:n,array:function(e){return l(e,new Array(e.length))},arraybuffer:function(e){return c.string.uint8array(e).buffer},uint8array:function(e){return l(e,new Uint8Array(e.length))},nodebuffer:function(e){return l(e,r.allocBuffer(e.length))}},c.array={string:s,array:n,arraybuffer:function(e){return new Uint8Array(e).buffer},uint8array:function(e){return new Uint8Array(e)},nodebuffer:function(e){return r.newBufferFrom(e)}},c.arraybuffer={string:function(e){return s(new Uint8Array(e))},array:function(e){return f(new Uint8Array(e),new Array(e.byteLength))},arraybuffer:n,uint8array:function(e){return new Uint8Array(e)},nodebuffer:function(e){return r.newBufferFrom(new Uint8Array(e))}},c.uint8array={string:s,array:function(e){return f(e,new Array(e.length))},arraybuffer:function(e){return e.buffer},uint8array:n,nodebuffer:function(e){return r.newBufferFrom(e)}},c.nodebuffer={string:s,array:function(e){return f(e,new Array(e.length))},arraybuffer:function(e){return c.nodebuffer.uint8array(e).buffer},uint8array:function(e){return f(e,new Uint8Array(e.length))},nodebuffer:n},a.transformTo=function(e,t){if(t=t||"",!e)return t;a.checkSupport(e);var r=a.getTypeOf(t);return c[r][e](t)},a.resolve=function(e){for(var t=e.split("/"),r=[],n=0;n<t.length;n++){var i=t[n];"."===i||""===i&&0!==n&&n!==t.length-1||(".."===i?r.pop():r.push(i))}return r.join("/")},a.getTypeOf=function(e){return"string"==typeof e?"string":"[object Array]"===Object.prototype.toString.call(e)?"array":o.nodebuffer&&r.isBuffer(e)?"nodebuffer":o.uint8array&&e instanceof Uint8Array?"uint8array":o.arraybuffer&&e instanceof ArrayBuffer?"arraybuffer":void 0},a.checkSupport=function(e){if(!o[e.toLowerCase()])throw new Error(e+" is not supported by this platform")},a.MAX_VALUE_16BITS=65535,a.MAX_VALUE_32BITS=-1,a.pretty=function(e){var t,r,n="";for(r=0;r<(e||"").length;r++)n+="\\x"+((t=e.charCodeAt(r))<16?"0":"")+t.toString(16).toUpperCase();return n},a.delay=function(e,t,r){setImmediate(function(){e.apply(r||null,t||[])})},a.inherits=function(e,t){function r(){}r.prototype=t.prototype,e.prototype=new r},a.extend=function(){var e,t,r={};for(e=0;e<arguments.length;e++)for(t in arguments[e])Object.prototype.hasOwnProperty.call(arguments[e],t)&&void 0===r[t]&&(r[t]=arguments[e][t]);return r},a.prepareContent=function(r,e,n,i,s){return u.Promise.resolve(e).then(function(n){return o.blob&&(n instanceof Blob||-1!==["[object File]","[object Blob]"].indexOf(Object.prototype.toString.call(n)))&&"undefined"!=typeof FileReader?new u.Promise(function(t,r){var e=new FileReader;e.onload=function(e){t(e.target.result)},e.onerror=function(e){r(e.target.error)},e.readAsArrayBuffer(n)}):n}).then(function(e){var t=a.getTypeOf(e);return t?("arraybuffer"===t?e=a.transformTo("uint8array",e):"string"===t&&(s?e=h.decode(e):n&&!0!==i&&(e=function(e){return l(e,o.uint8array?new Uint8Array(e.length):new Array(e.length))}(e))),e):u.Promise.reject(new Error("Can't read the data of '"+r+"'. Is it in a supported JavaScript type (String, Blob, ArrayBuffer, etc) ?"))})}},{"./base64":1,"./external":6,"./nodejsUtils":14,"./support":30,setimmediate:54}],33:[function(e,t,r){"use strict";var n=e("./reader/readerFor"),i=e("./utils"),s=e("./signature"),a=e("./zipEntry"),o=e("./support");function h(e){this.files=[],this.loadOptions=e}h.prototype={checkSignature:function(e){if(!this.reader.readAndCheckSignature(e)){this.reader.index-=4;var t=this.reader.readString(4);throw new Error("Corrupted zip or bug: unexpected signature ("+i.pretty(t)+", expected "+i.pretty(e)+")")}},isSignature:function(e,t){var r=this.reader.index;this.reader.setIndex(e);var n=this.reader.readString(4)===t;return this.reader.setIndex(r),n},readBlockEndOfCentral:function(){this.diskNumber=this.reader.readInt(2),this.diskWithCentralDirStart=this.reader.readInt(2),this.centralDirRecordsOnThisDisk=this.reader.readInt(2),this.centralDirRecords=this.reader.readInt(2),this.centralDirSize=this.reader.readInt(4),this.centralDirOffset=this.reader.readInt(4),this.zipCommentLength=this.reader.readInt(2);var e=this.reader.readData(this.zipCommentLength),t=o.uint8array?"uint8array":"array",r=i.transformTo(t,e);this.zipComment=this.loadOptions.decodeFileName(r)},readBlockZip64EndOfCentral:function(){this.zip64EndOfCentralSize=this.reader.readInt(8),this.reader.skip(4),this.diskNumber=this.reader.readInt(4),this.diskWithCentralDirStart=this.reader.readInt(4),this.centralDirRecordsOnThisDisk=this.reader.readInt(8),this.centralDirRecords=this.reader.readInt(8),this.centralDirSize=this.reader.readInt(8),this.centralDirOffset=this.reader.readInt(8),this.zip64ExtensibleData={};for(var e,t,r,n=this.zip64EndOfCentralSize-44;0<n;)e=this.reader.readInt(2),t=this.reader.readInt(4),r=this.reader.readData(t),this.zip64ExtensibleData[e]={id:e,length:t,value:r}},readBlockZip64EndOfCentralLocator:function(){if(this.diskWithZip64CentralDirStart=this.reader.readInt(4),this.relativeOffsetEndOfZip64CentralDir=this.reader.readInt(8),this.disksCount=this.reader.readInt(4),1<this.disksCount)throw new Error("Multi-volumes zip are not supported")},readLocalFiles:function(){var e,t;for(e=0;e<this.files.length;e++)t=this.files[e],this.reader.setIndex(t.localHeaderOffset),this.checkSignature(s.LOCAL_FILE_HEADER),t.readLocalPart(this.reader),t.handleUTF8(),t.processAttributes()},readCentralDir:function(){var e;for(this.reader.setIndex(this.centralDirOffset);this.reader.readAndCheckSignature(s.CENTRAL_FILE_HEADER);)(e=new a({zip64:this.zip64},this.loadOptions)).readCentralPart(this.reader),this.files.push(e);if(this.centralDirRecords!==this.files.length&&0!==this.centralDirRecords&&0===this.files.length)throw new Error("Corrupted zip or bug: expected "+this.centralDirRecords+" records in central dir, got "+this.files.length)},readEndOfCentral:function(){var e=this.reader.lastIndexOfSignature(s.CENTRAL_DIRECTORY_END);if(e<0)throw!this.isSignature(0,s.LOCAL_FILE_HEADER)?new Error("Can't find end of central directory : is this a zip file ? If it is, see https://stuk.github.io/jszip/documentation/howto/read_zip.html"):new Error("Corrupted zip: can't find end of central directory");this.reader.setIndex(e);var t=e;if(this.checkSignature(s.CENTRAL_DIRECTORY_END),this.readBlockEndOfCentral(),this.diskNumber===i.MAX_VALUE_16BITS||this.diskWithCentralDirStart===i.MAX_VALUE_16BITS||this.centralDirRecordsOnThisDisk===i.MAX_VALUE_16BITS||this.centralDirRecords===i.MAX_VALUE_16BITS||this.centralDirSize===i.MAX_VALUE_32BITS||this.centralDirOffset===i.MAX_VALUE_32BITS){if(this.zip64=!0,(e=this.reader.lastIndexOfSignature(s.ZIP64_CENTRAL_DIRECTORY_LOCATOR))<0)throw new Error("Corrupted zip: can't find the ZIP64 end of central directory locator");if(this.reader.setIndex(e),this.checkSignature(s.ZIP64_CENTRAL_DIRECTORY_LOCATOR),this.readBlockZip64EndOfCentralLocator(),!this.isSignature(this.relativeOffsetEndOfZip64CentralDir,s.ZIP64_CENTRAL_DIRECTORY_END)&&(this.relativeOffsetEndOfZip64CentralDir=this.reader.lastIndexOfSignature(s.ZIP64_CENTRAL_DIRECTORY_END),this.relativeOffsetEndOfZip64CentralDir<0))throw new Error("Corrupted zip: can't find the ZIP64 end of central directory");this.reader.setIndex(this.relativeOffsetEndOfZip64CentralDir),this.checkSignature(s.ZIP64_CENTRAL_DIRECTORY_END),this.readBlockZip64EndOfCentral()}var r=this.centralDirOffset+this.centralDirSize;this.zip64&&(r+=20,r+=12+this.zip64EndOfCentralSize);var n=t-r;if(0<n)this.isSignature(t,s.CENTRAL_FILE_HEADER)||(this.reader.zero=n);else if(n<0)throw new Error("Corrupted zip: missing "+Math.abs(n)+" bytes.")},prepareReader:function(e){this.reader=n(e)},load:function(e){this.prepareReader(e),this.readEndOfCentral(),this.readCentralDir(),this.readLocalFiles()}},t.exports=h},{"./reader/readerFor":22,"./signature":23,"./support":30,"./utils":32,"./zipEntry":34}],34:[function(e,t,r){"use strict";var n=e("./reader/readerFor"),s=e("./utils"),i=e("./compressedObject"),a=e("./crc32"),o=e("./utf8"),h=e("./compressions"),u=e("./support");function l(e,t){this.options=e,this.loadOptions=t}l.prototype={isEncrypted:function(){return 1==(1&this.bitFlag)},useUTF8:function(){return 2048==(2048&this.bitFlag)},readLocalPart:function(e){var t,r;if(e.skip(22),this.fileNameLength=e.readInt(2),r=e.readInt(2),this.fileName=e.readData(this.fileNameLength),e.skip(r),-1===this.compressedSize||-1===this.uncompressedSize)throw new Error("Bug or corrupted zip : didn't get enough information from the central directory (compressedSize === -1 || uncompressedSize === -1)");if(null===(t=function(e){for(var t in h)if(Object.prototype.hasOwnProperty.call(h,t)&&h[t].magic===e)return h[t];return null}(this.compressionMethod)))throw new Error("Corrupted zip : compression "+s.pretty(this.compressionMethod)+" unknown (inner file : "+s.transformTo("string",this.fileName)+")");this.decompressed=new i(this.compressedSize,this.uncompressedSize,this.crc32,t,e.readData(this.compressedSize))},readCentralPart:function(e){this.versionMadeBy=e.readInt(2),e.skip(2),this.bitFlag=e.readInt(2),this.compressionMethod=e.readString(2),this.date=e.readDate(),this.crc32=e.readInt(4),this.compressedSize=e.readInt(4),this.uncompressedSize=e.readInt(4);var t=e.readInt(2);if(this.extraFieldsLength=e.readInt(2),this.fileCommentLength=e.readInt(2),this.diskNumberStart=e.readInt(2),this.internalFileAttributes=e.readInt(2),this.externalFileAttributes=e.readInt(4),this.localHeaderOffset=e.readInt(4),this.isEncrypted())throw new Error("Encrypted zip are not supported");e.skip(t),this.readExtraFields(e),this.parseZIP64ExtraField(e),this.fileComment=e.readData(this.fileCommentLength)},processAttributes:function(){this.unixPermissions=null,this.dosPermissions=null;var e=this.versionMadeBy>>8;this.dir=!!(16&this.externalFileAttributes),0==e&&(this.dosPermissions=63&this.externalFileAttributes),3==e&&(this.unixPermissions=this.externalFileAttributes>>16&65535),this.dir||"/"!==this.fileNameStr.slice(-1)||(this.dir=!0)},parseZIP64ExtraField:function(){if(this.extraFields[1]){var e=n(this.extraFields[1].value);this.uncompressedSize===s.MAX_VALUE_32BITS&&(this.uncompressedSize=e.readInt(8)),this.compressedSize===s.MAX_VALUE_32BITS&&(this.compressedSize=e.readInt(8)),this.localHeaderOffset===s.MAX_VALUE_32BITS&&(this.localHeaderOffset=e.readInt(8)),this.diskNumberStart===s.MAX_VALUE_32BITS&&(this.diskNumberStart=e.readInt(4))}},readExtraFields:function(e){var t,r,n,i=e.index+this.extraFieldsLength;for(this.extraFields||(this.extraFields={});e.index+4<i;)t=e.readInt(2),r=e.readInt(2),n=e.readData(r),this.extraFields[t]={id:t,length:r,value:n};e.setIndex(i)},handleUTF8:function(){var e=u.uint8array?"uint8array":"array";if(this.useUTF8())this.fileNameStr=o.utf8decode(this.fileName),this.fileCommentStr=o.utf8decode(this.fileComment);else{var t=this.findExtraFieldUnicodePath();if(null!==t)this.fileNameStr=t;else{var r=s.transformTo(e,this.fileName);this.fileNameStr=this.loadOptions.decodeFileName(r)}var n=this.findExtraFieldUnicodeComment();if(null!==n)this.fileCommentStr=n;else{var i=s.transformTo(e,this.fileComment);this.fileCommentStr=this.loadOptions.decodeFileName(i)}}},findExtraFieldUnicodePath:function(){var e=this.extraFields[28789];if(e){var t=n(e.value);return 1!==t.readInt(1)?null:a(this.fileName)!==t.readInt(4)?null:o.utf8decode(t.readData(e.length-5))}return null},findExtraFieldUnicodeComment:function(){var e=this.extraFields[25461];if(e){var t=n(e.value);return 1!==t.readInt(1)?null:a(this.fileComment)!==t.readInt(4)?null:o.utf8decode(t.readData(e.length-5))}return null}},t.exports=l},{"./compressedObject":2,"./compressions":3,"./crc32":4,"./reader/readerFor":22,"./support":30,"./utf8":31,"./utils":32}],35:[function(e,t,r){"use strict";function n(e,t,r){this.name=e,this.dir=r.dir,this.date=r.date,this.comment=r.comment,this.unixPermissions=r.unixPermissions,this.dosPermissions=r.dosPermissions,this._data=t,this._dataBinary=r.binary,this.options={compression:r.compression,compressionOptions:r.compressionOptions}}var s=e("./stream/StreamHelper"),i=e("./stream/DataWorker"),a=e("./utf8"),o=e("./compressedObject"),h=e("./stream/GenericWorker");n.prototype={internalStream:function(e){var t=null,r="string";try{if(!e)throw new Error("No output type specified.");var n="string"===(r=e.toLowerCase())||"text"===r;"binarystring"!==r&&"text"!==r||(r="string"),t=this._decompressWorker();var i=!this._dataBinary;i&&!n&&(t=t.pipe(new a.Utf8EncodeWorker)),!i&&n&&(t=t.pipe(new a.Utf8DecodeWorker))}catch(e){(t=new h("error")).error(e)}return new s(t,r,"")},async:function(e,t){return this.internalStream(e).accumulate(t)},nodeStream:function(e,t){return this.internalStream(e||"nodebuffer").toNodejsStream(t)},_compressWorker:function(e,t){if(this._data instanceof o&&this._data.compression.magic===e.magic)return this._data.getCompressedWorker();var r=this._decompressWorker();return this._dataBinary||(r=r.pipe(new a.Utf8EncodeWorker)),o.createWorkerFrom(r,e,t)},_decompressWorker:function(){return this._data instanceof o?this._data.getContentWorker():this._data instanceof h?this._data:new i(this._data)}};for(var u=["asText","asBinary","asNodeBuffer","asUint8Array","asArrayBuffer"],l=function(){throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide.")},f=0;f<u.length;f++)n.prototype[u[f]]=l;t.exports=n},{"./compressedObject":2,"./stream/DataWorker":27,"./stream/GenericWorker":28,"./stream/StreamHelper":29,"./utf8":31}],36:[function(e,l,t){(function(t){"use strict";var r,n,e=t.MutationObserver||t.WebKitMutationObserver;if(e){var i=0,s=new e(u),a=t.document.createTextNode("");s.observe(a,{characterData:!0}),r=function(){a.data=i=++i%2}}else if(t.setImmediate||void 0===t.MessageChannel)r="document"in t&&"onreadystatechange"in t.document.createElement("script")?function(){var e=t.document.createElement("script");e.onreadystatechange=function(){u(),e.onreadystatechange=null,e.parentNode.removeChild(e),e=null},t.document.documentElement.appendChild(e)}:function(){setTimeout(u,0)};else{var o=new t.MessageChannel;o.port1.onmessage=u,r=function(){o.port2.postMessage(0)}}var h=[];function u(){var e,t;n=!0;for(var r=h.length;r;){for(t=h,h=[],e=-1;++e<r;)t[e]();r=h.length}n=!1}l.exports=function(e){1!==h.push(e)||n||r()}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],37:[function(e,t,r){"use strict";var i=e("immediate");function u(){}var l={},s=["REJECTED"],a=["FULFILLED"],n=["PENDING"];function o(e){if("function"!=typeof e)throw new TypeError("resolver must be a function");this.state=n,this.queue=[],this.outcome=void 0,e!==u&&d(this,e)}function h(e,t,r){this.promise=e,"function"==typeof t&&(this.onFulfilled=t,this.callFulfilled=this.otherCallFulfilled),"function"==typeof r&&(this.onRejected=r,this.callRejected=this.otherCallRejected)}function f(t,r,n){i(function(){var e;try{e=r(n)}catch(e){return l.reject(t,e)}e===t?l.reject(t,new TypeError("Cannot resolve promise with itself")):l.resolve(t,e)})}function c(e){var t=e&&e.then;if(e&&("object"==typeof e||"function"==typeof e)&&"function"==typeof t)return function(){t.apply(e,arguments)}}function d(t,e){var r=!1;function n(e){r||(r=!0,l.reject(t,e))}function i(e){r||(r=!0,l.resolve(t,e))}var s=p(function(){e(i,n)});"error"===s.status&&n(s.value)}function p(e,t){var r={};try{r.value=e(t),r.status="success"}catch(e){r.status="error",r.value=e}return r}(t.exports=o).prototype.finally=function(t){if("function"!=typeof t)return this;var r=this.constructor;return this.then(function(e){return r.resolve(t()).then(function(){return e})},function(e){return r.resolve(t()).then(function(){throw e})})},o.prototype.catch=function(e){return this.then(null,e)},o.prototype.then=function(e,t){if("function"!=typeof e&&this.state===a||"function"!=typeof t&&this.state===s)return this;var r=new this.constructor(u);this.state!==n?f(r,this.state===a?e:t,this.outcome):this.queue.push(new h(r,e,t));return r},h.prototype.callFulfilled=function(e){l.resolve(this.promise,e)},h.prototype.otherCallFulfilled=function(e){f(this.promise,this.onFulfilled,e)},h.prototype.callRejected=function(e){l.reject(this.promise,e)},h.prototype.otherCallRejected=function(e){f(this.promise,this.onRejected,e)},l.resolve=function(e,t){var r=p(c,t);if("error"===r.status)return l.reject(e,r.value);var n=r.value;if(n)d(e,n);else{e.state=a,e.outcome=t;for(var i=-1,s=e.queue.length;++i<s;)e.queue[i].callFulfilled(t)}return e},l.reject=function(e,t){e.state=s,e.outcome=t;for(var r=-1,n=e.queue.length;++r<n;)e.queue[r].callRejected(t);return e},o.resolve=function(e){if(e instanceof this)return e;return l.resolve(new this(u),e)},o.reject=function(e){var t=new this(u);return l.reject(t,e)},o.all=function(e){var r=this;if("[object Array]"!==Object.prototype.toString.call(e))return this.reject(new TypeError("must be an array"));var n=e.length,i=!1;if(!n)return this.resolve([]);var s=new Array(n),a=0,t=-1,o=new this(u);for(;++t<n;)h(e[t],t);return o;function h(e,t){r.resolve(e).then(function(e){s[t]=e,++a!==n||i||(i=!0,l.resolve(o,s))},function(e){i||(i=!0,l.reject(o,e))})}},o.race=function(e){var t=this;if("[object Array]"!==Object.prototype.toString.call(e))return this.reject(new TypeError("must be an array"));var r=e.length,n=!1;if(!r)return this.resolve([]);var i=-1,s=new this(u);for(;++i<r;)a=e[i],t.resolve(a).then(function(e){n||(n=!0,l.resolve(s,e))},function(e){n||(n=!0,l.reject(s,e))});var a;return s}},{immediate:36}],38:[function(e,t,r){"use strict";var n={};(0,e("./lib/utils/common").assign)(n,e("./lib/deflate"),e("./lib/inflate"),e("./lib/zlib/constants")),t.exports=n},{"./lib/deflate":39,"./lib/inflate":40,"./lib/utils/common":41,"./lib/zlib/constants":44}],39:[function(e,t,r){"use strict";var a=e("./zlib/deflate"),o=e("./utils/common"),h=e("./utils/strings"),i=e("./zlib/messages"),s=e("./zlib/zstream"),u=Object.prototype.toString,l=0,f=-1,c=0,d=8;function p(e){if(!(this instanceof p))return new p(e);this.options=o.assign({level:f,method:d,chunkSize:16384,windowBits:15,memLevel:8,strategy:c,to:""},e||{});var t=this.options;t.raw&&0<t.windowBits?t.windowBits=-t.windowBits:t.gzip&&0<t.windowBits&&t.windowBits<16&&(t.windowBits+=16),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new s,this.strm.avail_out=0;var r=a.deflateInit2(this.strm,t.level,t.method,t.windowBits,t.memLevel,t.strategy);if(r!==l)throw new Error(i[r]);if(t.header&&a.deflateSetHeader(this.strm,t.header),t.dictionary){var n;if(n="string"==typeof t.dictionary?h.string2buf(t.dictionary):"[object ArrayBuffer]"===u.call(t.dictionary)?new Uint8Array(t.dictionary):t.dictionary,(r=a.deflateSetDictionary(this.strm,n))!==l)throw new Error(i[r]);this._dict_set=!0}}function n(e,t){var r=new p(t);if(r.push(e,!0),r.err)throw r.msg||i[r.err];return r.result}p.prototype.push=function(e,t){var r,n,i=this.strm,s=this.options.chunkSize;if(this.ended)return!1;n=t===~~t?t:!0===t?4:0,"string"==typeof e?i.input=h.string2buf(e):"[object ArrayBuffer]"===u.call(e)?i.input=new Uint8Array(e):i.input=e,i.next_in=0,i.avail_in=i.input.length;do{if(0===i.avail_out&&(i.output=new o.Buf8(s),i.next_out=0,i.avail_out=s),1!==(r=a.deflate(i,n))&&r!==l)return this.onEnd(r),!(this.ended=!0);0!==i.avail_out&&(0!==i.avail_in||4!==n&&2!==n)||("string"===this.options.to?this.onData(h.buf2binstring(o.shrinkBuf(i.output,i.next_out))):this.onData(o.shrinkBuf(i.output,i.next_out)))}while((0<i.avail_in||0===i.avail_out)&&1!==r);return 4===n?(r=a.deflateEnd(this.strm),this.onEnd(r),this.ended=!0,r===l):2!==n||(this.onEnd(l),!(i.avail_out=0))},p.prototype.onData=function(e){this.chunks.push(e)},p.prototype.onEnd=function(e){e===l&&("string"===this.options.to?this.result=this.chunks.join(""):this.result=o.flattenChunks(this.chunks)),this.chunks=[],this.err=e,this.msg=this.strm.msg},r.Deflate=p,r.deflate=n,r.deflateRaw=function(e,t){return(t=t||{}).raw=!0,n(e,t)},r.gzip=function(e,t){return(t=t||{}).gzip=!0,n(e,t)}},{"./utils/common":41,"./utils/strings":42,"./zlib/deflate":46,"./zlib/messages":51,"./zlib/zstream":53}],40:[function(e,t,r){"use strict";var c=e("./zlib/inflate"),d=e("./utils/common"),p=e("./utils/strings"),m=e("./zlib/constants"),n=e("./zlib/messages"),i=e("./zlib/zstream"),s=e("./zlib/gzheader"),_=Object.prototype.toString;function a(e){if(!(this instanceof a))return new a(e);this.options=d.assign({chunkSize:16384,windowBits:0,to:""},e||{});var t=this.options;t.raw&&0<=t.windowBits&&t.windowBits<16&&(t.windowBits=-t.windowBits,0===t.windowBits&&(t.windowBits=-15)),!(0<=t.windowBits&&t.windowBits<16)||e&&e.windowBits||(t.windowBits+=32),15<t.windowBits&&t.windowBits<48&&0==(15&t.windowBits)&&(t.windowBits|=15),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new i,this.strm.avail_out=0;var r=c.inflateInit2(this.strm,t.windowBits);if(r!==m.Z_OK)throw new Error(n[r]);this.header=new s,c.inflateGetHeader(this.strm,this.header)}function o(e,t){var r=new a(t);if(r.push(e,!0),r.err)throw r.msg||n[r.err];return r.result}a.prototype.push=function(e,t){var r,n,i,s,a,o,h=this.strm,u=this.options.chunkSize,l=this.options.dictionary,f=!1;if(this.ended)return!1;n=t===~~t?t:!0===t?m.Z_FINISH:m.Z_NO_FLUSH,"string"==typeof e?h.input=p.binstring2buf(e):"[object ArrayBuffer]"===_.call(e)?h.input=new Uint8Array(e):h.input=e,h.next_in=0,h.avail_in=h.input.length;do{if(0===h.avail_out&&(h.output=new d.Buf8(u),h.next_out=0,h.avail_out=u),(r=c.inflate(h,m.Z_NO_FLUSH))===m.Z_NEED_DICT&&l&&(o="string"==typeof l?p.string2buf(l):"[object ArrayBuffer]"===_.call(l)?new Uint8Array(l):l,r=c.inflateSetDictionary(this.strm,o)),r===m.Z_BUF_ERROR&&!0===f&&(r=m.Z_OK,f=!1),r!==m.Z_STREAM_END&&r!==m.Z_OK)return this.onEnd(r),!(this.ended=!0);h.next_out&&(0!==h.avail_out&&r!==m.Z_STREAM_END&&(0!==h.avail_in||n!==m.Z_FINISH&&n!==m.Z_SYNC_FLUSH)||("string"===this.options.to?(i=p.utf8border(h.output,h.next_out),s=h.next_out-i,a=p.buf2string(h.output,i),h.next_out=s,h.avail_out=u-s,s&&d.arraySet(h.output,h.output,i,s,0),this.onData(a)):this.onData(d.shrinkBuf(h.output,h.next_out)))),0===h.avail_in&&0===h.avail_out&&(f=!0)}while((0<h.avail_in||0===h.avail_out)&&r!==m.Z_STREAM_END);return r===m.Z_STREAM_END&&(n=m.Z_FINISH),n===m.Z_FINISH?(r=c.inflateEnd(this.strm),this.onEnd(r),this.ended=!0,r===m.Z_OK):n!==m.Z_SYNC_FLUSH||(this.onEnd(m.Z_OK),!(h.avail_out=0))},a.prototype.onData=function(e){this.chunks.push(e)},a.prototype.onEnd=function(e){e===m.Z_OK&&("string"===this.options.to?this.result=this.chunks.join(""):this.result=d.flattenChunks(this.chunks)),this.chunks=[],this.err=e,this.msg=this.strm.msg},r.Inflate=a,r.inflate=o,r.inflateRaw=function(e,t){return(t=t||{}).raw=!0,o(e,t)},r.ungzip=o},{"./utils/common":41,"./utils/strings":42,"./zlib/constants":44,"./zlib/gzheader":47,"./zlib/inflate":49,"./zlib/messages":51,"./zlib/zstream":53}],41:[function(e,t,r){"use strict";var n="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Int32Array;r.assign=function(e){for(var t=Array.prototype.slice.call(arguments,1);t.length;){var r=t.shift();if(r){if("object"!=typeof r)throw new TypeError(r+"must be non-object");for(var n in r)r.hasOwnProperty(n)&&(e[n]=r[n])}}return e},r.shrinkBuf=function(e,t){return e.length===t?e:e.subarray?e.subarray(0,t):(e.length=t,e)};var i={arraySet:function(e,t,r,n,i){if(t.subarray&&e.subarray)e.set(t.subarray(r,r+n),i);else for(var s=0;s<n;s++)e[i+s]=t[r+s]},flattenChunks:function(e){var t,r,n,i,s,a;for(t=n=0,r=e.length;t<r;t++)n+=e[t].length;for(a=new Uint8Array(n),t=i=0,r=e.length;t<r;t++)s=e[t],a.set(s,i),i+=s.length;return a}},s={arraySet:function(e,t,r,n,i){for(var s=0;s<n;s++)e[i+s]=t[r+s]},flattenChunks:function(e){return[].concat.apply([],e)}};r.setTyped=function(e){e?(r.Buf8=Uint8Array,r.Buf16=Uint16Array,r.Buf32=Int32Array,r.assign(r,i)):(r.Buf8=Array,r.Buf16=Array,r.Buf32=Array,r.assign(r,s))},r.setTyped(n)},{}],42:[function(e,t,r){"use strict";var h=e("./common"),i=!0,s=!0;try{String.fromCharCode.apply(null,[0])}catch(e){i=!1}try{String.fromCharCode.apply(null,new Uint8Array(1))}catch(e){s=!1}for(var u=new h.Buf8(256),n=0;n<256;n++)u[n]=252<=n?6:248<=n?5:240<=n?4:224<=n?3:192<=n?2:1;function l(e,t){if(t<65537&&(e.subarray&&s||!e.subarray&&i))return String.fromCharCode.apply(null,h.shrinkBuf(e,t));for(var r="",n=0;n<t;n++)r+=String.fromCharCode(e[n]);return r}u[254]=u[254]=1,r.string2buf=function(e){var t,r,n,i,s,a=e.length,o=0;for(i=0;i<a;i++)55296==(64512&(r=e.charCodeAt(i)))&&i+1<a&&56320==(64512&(n=e.charCodeAt(i+1)))&&(r=65536+(r-55296<<10)+(n-56320),i++),o+=r<128?1:r<2048?2:r<65536?3:4;for(t=new h.Buf8(o),i=s=0;s<o;i++)55296==(64512&(r=e.charCodeAt(i)))&&i+1<a&&56320==(64512&(n=e.charCodeAt(i+1)))&&(r=65536+(r-55296<<10)+(n-56320),i++),r<128?t[s++]=r:(r<2048?t[s++]=192|r>>>6:(r<65536?t[s++]=224|r>>>12:(t[s++]=240|r>>>18,t[s++]=128|r>>>12&63),t[s++]=128|r>>>6&63),t[s++]=128|63&r);return t},r.buf2binstring=function(e){return l(e,e.length)},r.binstring2buf=function(e){for(var t=new h.Buf8(e.length),r=0,n=t.length;r<n;r++)t[r]=e.charCodeAt(r);return t},r.buf2string=function(e,t){var r,n,i,s,a=t||e.length,o=new Array(2*a);for(r=n=0;r<a;)if((i=e[r++])<128)o[n++]=i;else if(4<(s=u[i]))o[n++]=65533,r+=s-1;else{for(i&=2===s?31:3===s?15:7;1<s&&r<a;)i=i<<6|63&e[r++],s--;1<s?o[n++]=65533:i<65536?o[n++]=i:(i-=65536,o[n++]=55296|i>>10&1023,o[n++]=56320|1023&i)}return l(o,n)},r.utf8border=function(e,t){var r;for((t=t||e.length)>e.length&&(t=e.length),r=t-1;0<=r&&128==(192&e[r]);)r--;return r<0?t:0===r?t:r+u[e[r]]>t?r:t}},{"./common":41}],43:[function(e,t,r){"use strict";t.exports=function(e,t,r,n){for(var i=65535&e|0,s=e>>>16&65535|0,a=0;0!==r;){for(r-=a=2e3<r?2e3:r;s=s+(i=i+t[n++]|0)|0,--a;);i%=65521,s%=65521}return i|s<<16|0}},{}],44:[function(e,t,r){"use strict";t.exports={Z_NO_FLUSH:0,Z_PARTIAL_FLUSH:1,Z_SYNC_FLUSH:2,Z_FULL_FLUSH:3,Z_FINISH:4,Z_BLOCK:5,Z_TREES:6,Z_OK:0,Z_STREAM_END:1,Z_NEED_DICT:2,Z_ERRNO:-1,Z_STREAM_ERROR:-2,Z_DATA_ERROR:-3,Z_BUF_ERROR:-5,Z_NO_COMPRESSION:0,Z_BEST_SPEED:1,Z_BEST_COMPRESSION:9,Z_DEFAULT_COMPRESSION:-1,Z_FILTERED:1,Z_HUFFMAN_ONLY:2,Z_RLE:3,Z_FIXED:4,Z_DEFAULT_STRATEGY:0,Z_BINARY:0,Z_TEXT:1,Z_UNKNOWN:2,Z_DEFLATED:8}},{}],45:[function(e,t,r){"use strict";var o=function(){for(var e,t=[],r=0;r<256;r++){e=r;for(var n=0;n<8;n++)e=1&e?3988292384^e>>>1:e>>>1;t[r]=e}return t}();t.exports=function(e,t,r,n){var i=o,s=n+r;e^=-1;for(var a=n;a<s;a++)e=e>>>8^i[255&(e^t[a])];return-1^e}},{}],46:[function(e,t,r){"use strict";var h,c=e("../utils/common"),u=e("./trees"),d=e("./adler32"),p=e("./crc32"),n=e("./messages"),l=0,f=4,m=0,_=-2,g=-1,b=4,i=2,v=8,y=9,s=286,a=30,o=19,w=2*s+1,k=15,x=3,S=258,z=S+x+1,C=42,E=113,A=1,I=2,O=3,B=4;function R(e,t){return e.msg=n[t],t}function T(e){return(e<<1)-(4<e?9:0)}function D(e){for(var t=e.length;0<=--t;)e[t]=0}function F(e){var t=e.state,r=t.pending;r>e.avail_out&&(r=e.avail_out),0!==r&&(c.arraySet(e.output,t.pending_buf,t.pending_out,r,e.next_out),e.next_out+=r,t.pending_out+=r,e.total_out+=r,e.avail_out-=r,t.pending-=r,0===t.pending&&(t.pending_out=0))}function N(e,t){u._tr_flush_block(e,0<=e.block_start?e.block_start:-1,e.strstart-e.block_start,t),e.block_start=e.strstart,F(e.strm)}function U(e,t){e.pending_buf[e.pending++]=t}function P(e,t){e.pending_buf[e.pending++]=t>>>8&255,e.pending_buf[e.pending++]=255&t}function L(e,t){var r,n,i=e.max_chain_length,s=e.strstart,a=e.prev_length,o=e.nice_match,h=e.strstart>e.w_size-z?e.strstart-(e.w_size-z):0,u=e.window,l=e.w_mask,f=e.prev,c=e.strstart+S,d=u[s+a-1],p=u[s+a];e.prev_length>=e.good_match&&(i>>=2),o>e.lookahead&&(o=e.lookahead);do{if(u[(r=t)+a]===p&&u[r+a-1]===d&&u[r]===u[s]&&u[++r]===u[s+1]){s+=2,r++;do{}while(u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&s<c);if(n=S-(c-s),s=c-S,a<n){if(e.match_start=t,o<=(a=n))break;d=u[s+a-1],p=u[s+a]}}}while((t=f[t&l])>h&&0!=--i);return a<=e.lookahead?a:e.lookahead}function j(e){var t,r,n,i,s,a,o,h,u,l,f=e.w_size;do{if(i=e.window_size-e.lookahead-e.strstart,e.strstart>=f+(f-z)){for(c.arraySet(e.window,e.window,f,f,0),e.match_start-=f,e.strstart-=f,e.block_start-=f,t=r=e.hash_size;n=e.head[--t],e.head[t]=f<=n?n-f:0,--r;);for(t=r=f;n=e.prev[--t],e.prev[t]=f<=n?n-f:0,--r;);i+=f}if(0===e.strm.avail_in)break;if(a=e.strm,o=e.window,h=e.strstart+e.lookahead,u=i,l=void 0,l=a.avail_in,u<l&&(l=u),r=0===l?0:(a.avail_in-=l,c.arraySet(o,a.input,a.next_in,l,h),1===a.state.wrap?a.adler=d(a.adler,o,l,h):2===a.state.wrap&&(a.adler=p(a.adler,o,l,h)),a.next_in+=l,a.total_in+=l,l),e.lookahead+=r,e.lookahead+e.insert>=x)for(s=e.strstart-e.insert,e.ins_h=e.window[s],e.ins_h=(e.ins_h<<e.hash_shift^e.window[s+1])&e.hash_mask;e.insert&&(e.ins_h=(e.ins_h<<e.hash_shift^e.window[s+x-1])&e.hash_mask,e.prev[s&e.w_mask]=e.head[e.ins_h],e.head[e.ins_h]=s,s++,e.insert--,!(e.lookahead+e.insert<x)););}while(e.lookahead<z&&0!==e.strm.avail_in)}function Z(e,t){for(var r,n;;){if(e.lookahead<z){if(j(e),e.lookahead<z&&t===l)return A;if(0===e.lookahead)break}if(r=0,e.lookahead>=x&&(e.ins_h=(e.ins_h<<e.hash_shift^e.window[e.strstart+x-1])&e.hash_mask,r=e.prev[e.strstart&e.w_mask]=e.head[e.ins_h],e.head[e.ins_h]=e.strstart),0!==r&&e.strstart-r<=e.w_size-z&&(e.match_length=L(e,r)),e.match_length>=x)if(n=u._tr_tally(e,e.strstart-e.match_start,e.match_length-x),e.lookahead-=e.match_length,e.match_length<=e.max_lazy_match&&e.lookahead>=x){for(e.match_length--;e.strstart++,e.ins_h=(e.ins_h<<e.hash_shift^e.window[e.strstart+x-1])&e.hash_mask,r=e.prev[e.strstart&e.w_mask]=e.head[e.ins_h],e.head[e.ins_h]=e.strstart,0!=--e.match_length;);e.strstart++}else e.strstart+=e.match_length,e.match_length=0,e.ins_h=e.window[e.strstart],e.ins_h=(e.ins_h<<e.hash_shift^e.window[e.strstart+1])&e.hash_mask;else n=u._tr_tally(e,0,e.window[e.strstart]),e.lookahead--,e.strstart++;if(n&&(N(e,!1),0===e.strm.avail_out))return A}return e.insert=e.strstart<x-1?e.strstart:x-1,t===f?(N(e,!0),0===e.strm.avail_out?O:B):e.last_lit&&(N(e,!1),0===e.strm.avail_out)?A:I}function W(e,t){for(var r,n,i;;){if(e.lookahead<z){if(j(e),e.lookahead<z&&t===l)return A;if(0===e.lookahead)break}if(r=0,e.lookahead>=x&&(e.ins_h=(e.ins_h<<e.hash_shift^e.window[e.strstart+x-1])&e.hash_mask,r=e.prev[e.strstart&e.w_mask]=e.head[e.ins_h],e.head[e.ins_h]=e.strstart),e.prev_length=e.match_length,e.prev_match=e.match_start,e.match_length=x-1,0!==r&&e.prev_length<e.max_lazy_match&&e.strstart-r<=e.w_size-z&&(e.match_length=L(e,r),e.match_length<=5&&(1===e.strategy||e.match_length===x&&4096<e.strstart-e.match_start)&&(e.match_length=x-1)),e.prev_length>=x&&e.match_length<=e.prev_length){for(i=e.strstart+e.lookahead-x,n=u._tr_tally(e,e.strstart-1-e.prev_match,e.prev_length-x),e.lookahead-=e.prev_length-1,e.prev_length-=2;++e.strstart<=i&&(e.ins_h=(e.ins_h<<e.hash_shift^e.window[e.strstart+x-1])&e.hash_mask,r=e.prev[e.strstart&e.w_mask]=e.head[e.ins_h],e.head[e.ins_h]=e.strstart),0!=--e.prev_length;);if(e.match_available=0,e.match_length=x-1,e.strstart++,n&&(N(e,!1),0===e.strm.avail_out))return A}else if(e.match_available){if((n=u._tr_tally(e,0,e.window[e.strstart-1]))&&N(e,!1),e.strstart++,e.lookahead--,0===e.strm.avail_out)return A}else e.match_available=1,e.strstart++,e.lookahead--}return e.match_available&&(n=u._tr_tally(e,0,e.window[e.strstart-1]),e.match_available=0),e.insert=e.strstart<x-1?e.strstart:x-1,t===f?(N(e,!0),0===e.strm.avail_out?O:B):e.last_lit&&(N(e,!1),0===e.strm.avail_out)?A:I}function M(e,t,r,n,i){this.good_length=e,this.max_lazy=t,this.nice_length=r,this.max_chain=n,this.func=i}function H(){this.strm=null,this.status=0,this.pending_buf=null,this.pending_buf_size=0,this.pending_out=0,this.pending=0,this.wrap=0,this.gzhead=null,this.gzindex=0,this.method=v,this.last_flush=-1,this.w_size=0,this.w_bits=0,this.w_mask=0,this.window=null,this.window_size=0,this.prev=null,this.head=null,this.ins_h=0,this.hash_size=0,this.hash_bits=0,this.hash_mask=0,this.hash_shift=0,this.block_start=0,this.match_length=0,this.prev_match=0,this.match_available=0,this.strstart=0,this.match_start=0,this.lookahead=0,this.prev_length=0,this.max_chain_length=0,this.max_lazy_match=0,this.level=0,this.strategy=0,this.good_match=0,this.nice_match=0,this.dyn_ltree=new c.Buf16(2*w),this.dyn_dtree=new c.Buf16(2*(2*a+1)),this.bl_tree=new c.Buf16(2*(2*o+1)),D(this.dyn_ltree),D(this.dyn_dtree),D(this.bl_tree),this.l_desc=null,this.d_desc=null,this.bl_desc=null,this.bl_count=new c.Buf16(k+1),this.heap=new c.Buf16(2*s+1),D(this.heap),this.heap_len=0,this.heap_max=0,this.depth=new c.Buf16(2*s+1),D(this.depth),this.l_buf=0,this.lit_bufsize=0,this.last_lit=0,this.d_buf=0,this.opt_len=0,this.static_len=0,this.matches=0,this.insert=0,this.bi_buf=0,this.bi_valid=0}function G(e){var t;return e&&e.state?(e.total_in=e.total_out=0,e.data_type=i,(t=e.state).pending=0,t.pending_out=0,t.wrap<0&&(t.wrap=-t.wrap),t.status=t.wrap?C:E,e.adler=2===t.wrap?0:1,t.last_flush=l,u._tr_init(t),m):R(e,_)}function K(e){var t=G(e);return t===m&&function(e){e.window_size=2*e.w_size,D(e.head),e.max_lazy_match=h[e.level].max_lazy,e.good_match=h[e.level].good_length,e.nice_match=h[e.level].nice_length,e.max_chain_length=h[e.level].max_chain,e.strstart=0,e.block_start=0,e.lookahead=0,e.insert=0,e.match_length=e.prev_length=x-1,e.match_available=0,e.ins_h=0}(e.state),t}function Y(e,t,r,n,i,s){if(!e)return _;var a=1;if(t===g&&(t=6),n<0?(a=0,n=-n):15<n&&(a=2,n-=16),i<1||y<i||r!==v||n<8||15<n||t<0||9<t||s<0||b<s)return R(e,_);8===n&&(n=9);var o=new H;return(e.state=o).strm=e,o.wrap=a,o.gzhead=null,o.w_bits=n,o.w_size=1<<o.w_bits,o.w_mask=o.w_size-1,o.hash_bits=i+7,o.hash_size=1<<o.hash_bits,o.hash_mask=o.hash_size-1,o.hash_shift=~~((o.hash_bits+x-1)/x),o.window=new c.Buf8(2*o.w_size),o.head=new c.Buf16(o.hash_size),o.prev=new c.Buf16(o.w_size),o.lit_bufsize=1<<i+6,o.pending_buf_size=4*o.lit_bufsize,o.pending_buf=new c.Buf8(o.pending_buf_size),o.d_buf=1*o.lit_bufsize,o.l_buf=3*o.lit_bufsize,o.level=t,o.strategy=s,o.method=r,K(e)}h=[new M(0,0,0,0,function(e,t){var r=65535;for(r>e.pending_buf_size-5&&(r=e.pending_buf_size-5);;){if(e.lookahead<=1){if(j(e),0===e.lookahead&&t===l)return A;if(0===e.lookahead)break}e.strstart+=e.lookahead,e.lookahead=0;var n=e.block_start+r;if((0===e.strstart||e.strstart>=n)&&(e.lookahead=e.strstart-n,e.strstart=n,N(e,!1),0===e.strm.avail_out))return A;if(e.strstart-e.block_start>=e.w_size-z&&(N(e,!1),0===e.strm.avail_out))return A}return e.insert=0,t===f?(N(e,!0),0===e.strm.avail_out?O:B):(e.strstart>e.block_start&&(N(e,!1),e.strm.avail_out),A)}),new M(4,4,8,4,Z),new M(4,5,16,8,Z),new M(4,6,32,32,Z),new M(4,4,16,16,W),new M(8,16,32,32,W),new M(8,16,128,128,W),new M(8,32,128,256,W),new M(32,128,258,1024,W),new M(32,258,258,4096,W)],r.deflateInit=function(e,t){return Y(e,t,v,15,8,0)},r.deflateInit2=Y,r.deflateReset=K,r.deflateResetKeep=G,r.deflateSetHeader=function(e,t){return e&&e.state?2!==e.state.wrap?_:(e.state.gzhead=t,m):_},r.deflate=function(e,t){var r,n,i,s;if(!e||!e.state||5<t||t<0)return e?R(e,_):_;if(n=e.state,!e.output||!e.input&&0!==e.avail_in||666===n.status&&t!==f)return R(e,0===e.avail_out?-5:_);if(n.strm=e,r=n.last_flush,n.last_flush=t,n.status===C)if(2===n.wrap)e.adler=0,U(n,31),U(n,139),U(n,8),n.gzhead?(U(n,(n.gzhead.text?1:0)+(n.gzhead.hcrc?2:0)+(n.gzhead.extra?4:0)+(n.gzhead.name?8:0)+(n.gzhead.comment?16:0)),U(n,255&n.gzhead.time),U(n,n.gzhead.time>>8&255),U(n,n.gzhead.time>>16&255),U(n,n.gzhead.time>>24&255),U(n,9===n.level?2:2<=n.strategy||n.level<2?4:0),U(n,255&n.gzhead.os),n.gzhead.extra&&n.gzhead.extra.length&&(U(n,255&n.gzhead.extra.length),U(n,n.gzhead.extra.length>>8&255)),n.gzhead.hcrc&&(e.adler=p(e.adler,n.pending_buf,n.pending,0)),n.gzindex=0,n.status=69):(U(n,0),U(n,0),U(n,0),U(n,0),U(n,0),U(n,9===n.level?2:2<=n.strategy||n.level<2?4:0),U(n,3),n.status=E);else{var a=v+(n.w_bits-8<<4)<<8;a|=(2<=n.strategy||n.level<2?0:n.level<6?1:6===n.level?2:3)<<6,0!==n.strstart&&(a|=32),a+=31-a%31,n.status=E,P(n,a),0!==n.strstart&&(P(n,e.adler>>>16),P(n,65535&e.adler)),e.adler=1}if(69===n.status)if(n.gzhead.extra){for(i=n.pending;n.gzindex<(65535&n.gzhead.extra.length)&&(n.pending!==n.pending_buf_size||(n.gzhead.hcrc&&n.pending>i&&(e.adler=p(e.adler,n.pending_buf,n.pending-i,i)),F(e),i=n.pending,n.pending!==n.pending_buf_size));)U(n,255&n.gzhead.extra[n.gzindex]),n.gzindex++;n.gzhead.hcrc&&n.pending>i&&(e.adler=p(e.adler,n.pending_buf,n.pending-i,i)),n.gzindex===n.gzhead.extra.length&&(n.gzindex=0,n.status=73)}else n.status=73;if(73===n.status)if(n.gzhead.name){i=n.pending;do{if(n.pending===n.pending_buf_size&&(n.gzhead.hcrc&&n.pending>i&&(e.adler=p(e.adler,n.pending_buf,n.pending-i,i)),F(e),i=n.pending,n.pending===n.pending_buf_size)){s=1;break}s=n.gzindex<n.gzhead.name.length?255&n.gzhead.name.charCodeAt(n.gzindex++):0,U(n,s)}while(0!==s);n.gzhead.hcrc&&n.pending>i&&(e.adler=p(e.adler,n.pending_buf,n.pending-i,i)),0===s&&(n.gzindex=0,n.status=91)}else n.status=91;if(91===n.status)if(n.gzhead.comment){i=n.pending;do{if(n.pending===n.pending_buf_size&&(n.gzhead.hcrc&&n.pending>i&&(e.adler=p(e.adler,n.pending_buf,n.pending-i,i)),F(e),i=n.pending,n.pending===n.pending_buf_size)){s=1;break}s=n.gzindex<n.gzhead.comment.length?255&n.gzhead.comment.charCodeAt(n.gzindex++):0,U(n,s)}while(0!==s);n.gzhead.hcrc&&n.pending>i&&(e.adler=p(e.adler,n.pending_buf,n.pending-i,i)),0===s&&(n.status=103)}else n.status=103;if(103===n.status&&(n.gzhead.hcrc?(n.pending+2>n.pending_buf_size&&F(e),n.pending+2<=n.pending_buf_size&&(U(n,255&e.adler),U(n,e.adler>>8&255),e.adler=0,n.status=E)):n.status=E),0!==n.pending){if(F(e),0===e.avail_out)return n.last_flush=-1,m}else if(0===e.avail_in&&T(t)<=T(r)&&t!==f)return R(e,-5);if(666===n.status&&0!==e.avail_in)return R(e,-5);if(0!==e.avail_in||0!==n.lookahead||t!==l&&666!==n.status){var o=2===n.strategy?function(e,t){for(var r;;){if(0===e.lookahead&&(j(e),0===e.lookahead)){if(t===l)return A;break}if(e.match_length=0,r=u._tr_tally(e,0,e.window[e.strstart]),e.lookahead--,e.strstart++,r&&(N(e,!1),0===e.strm.avail_out))return A}return e.insert=0,t===f?(N(e,!0),0===e.strm.avail_out?O:B):e.last_lit&&(N(e,!1),0===e.strm.avail_out)?A:I}(n,t):3===n.strategy?function(e,t){for(var r,n,i,s,a=e.window;;){if(e.lookahead<=S){if(j(e),e.lookahead<=S&&t===l)return A;if(0===e.lookahead)break}if(e.match_length=0,e.lookahead>=x&&0<e.strstart&&(n=a[i=e.strstart-1])===a[++i]&&n===a[++i]&&n===a[++i]){s=e.strstart+S;do{}while(n===a[++i]&&n===a[++i]&&n===a[++i]&&n===a[++i]&&n===a[++i]&&n===a[++i]&&n===a[++i]&&n===a[++i]&&i<s);e.match_length=S-(s-i),e.match_length>e.lookahead&&(e.match_length=e.lookahead)}if(e.match_length>=x?(r=u._tr_tally(e,1,e.match_length-x),e.lookahead-=e.match_length,e.strstart+=e.match_length,e.match_length=0):(r=u._tr_tally(e,0,e.window[e.strstart]),e.lookahead--,e.strstart++),r&&(N(e,!1),0===e.strm.avail_out))return A}return e.insert=0,t===f?(N(e,!0),0===e.strm.avail_out?O:B):e.last_lit&&(N(e,!1),0===e.strm.avail_out)?A:I}(n,t):h[n.level].func(n,t);if(o!==O&&o!==B||(n.status=666),o===A||o===O)return 0===e.avail_out&&(n.last_flush=-1),m;if(o===I&&(1===t?u._tr_align(n):5!==t&&(u._tr_stored_block(n,0,0,!1),3===t&&(D(n.head),0===n.lookahead&&(n.strstart=0,n.block_start=0,n.insert=0))),F(e),0===e.avail_out))return n.last_flush=-1,m}return t!==f?m:n.wrap<=0?1:(2===n.wrap?(U(n,255&e.adler),U(n,e.adler>>8&255),U(n,e.adler>>16&255),U(n,e.adler>>24&255),U(n,255&e.total_in),U(n,e.total_in>>8&255),U(n,e.total_in>>16&255),U(n,e.total_in>>24&255)):(P(n,e.adler>>>16),P(n,65535&e.adler)),F(e),0<n.wrap&&(n.wrap=-n.wrap),0!==n.pending?m:1)},r.deflateEnd=function(e){var t;return e&&e.state?(t=e.state.status)!==C&&69!==t&&73!==t&&91!==t&&103!==t&&t!==E&&666!==t?R(e,_):(e.state=null,t===E?R(e,-3):m):_},r.deflateSetDictionary=function(e,t){var r,n,i,s,a,o,h,u,l=t.length;if(!e||!e.state)return _;if(2===(s=(r=e.state).wrap)||1===s&&r.status!==C||r.lookahead)return _;for(1===s&&(e.adler=d(e.adler,t,l,0)),r.wrap=0,l>=r.w_size&&(0===s&&(D(r.head),r.strstart=0,r.block_start=0,r.insert=0),u=new c.Buf8(r.w_size),c.arraySet(u,t,l-r.w_size,r.w_size,0),t=u,l=r.w_size),a=e.avail_in,o=e.next_in,h=e.input,e.avail_in=l,e.next_in=0,e.input=t,j(r);r.lookahead>=x;){for(n=r.strstart,i=r.lookahead-(x-1);r.ins_h=(r.ins_h<<r.hash_shift^r.window[n+x-1])&r.hash_mask,r.prev[n&r.w_mask]=r.head[r.ins_h],r.head[r.ins_h]=n,n++,--i;);r.strstart=n,r.lookahead=x-1,j(r)}return r.strstart+=r.lookahead,r.block_start=r.strstart,r.insert=r.lookahead,r.lookahead=0,r.match_length=r.prev_length=x-1,r.match_available=0,e.next_in=o,e.input=h,e.avail_in=a,r.wrap=s,m},r.deflateInfo="pako deflate (from Nodeca project)"},{"../utils/common":41,"./adler32":43,"./crc32":45,"./messages":51,"./trees":52}],47:[function(e,t,r){"use strict";t.exports=function(){this.text=0,this.time=0,this.xflags=0,this.os=0,this.extra=null,this.extra_len=0,this.name="",this.comment="",this.hcrc=0,this.done=!1}},{}],48:[function(e,t,r){"use strict";t.exports=function(e,t){var r,n,i,s,a,o,h,u,l,f,c,d,p,m,_,g,b,v,y,w,k,x,S,z,C;r=e.state,n=e.next_in,z=e.input,i=n+(e.avail_in-5),s=e.next_out,C=e.output,a=s-(t-e.avail_out),o=s+(e.avail_out-257),h=r.dmax,u=r.wsize,l=r.whave,f=r.wnext,c=r.window,d=r.hold,p=r.bits,m=r.lencode,_=r.distcode,g=(1<<r.lenbits)-1,b=(1<<r.distbits)-1;e:do{p<15&&(d+=z[n++]<<p,p+=8,d+=z[n++]<<p,p+=8),v=m[d&g];t:for(;;){if(d>>>=y=v>>>24,p-=y,0===(y=v>>>16&255))C[s++]=65535&v;else{if(!(16&y)){if(0==(64&y)){v=m[(65535&v)+(d&(1<<y)-1)];continue t}if(32&y){r.mode=12;break e}e.msg="invalid literal/length code",r.mode=30;break e}w=65535&v,(y&=15)&&(p<y&&(d+=z[n++]<<p,p+=8),w+=d&(1<<y)-1,d>>>=y,p-=y),p<15&&(d+=z[n++]<<p,p+=8,d+=z[n++]<<p,p+=8),v=_[d&b];r:for(;;){if(d>>>=y=v>>>24,p-=y,!(16&(y=v>>>16&255))){if(0==(64&y)){v=_[(65535&v)+(d&(1<<y)-1)];continue r}e.msg="invalid distance code",r.mode=30;break e}if(k=65535&v,p<(y&=15)&&(d+=z[n++]<<p,(p+=8)<y&&(d+=z[n++]<<p,p+=8)),h<(k+=d&(1<<y)-1)){e.msg="invalid distance too far back",r.mode=30;break e}if(d>>>=y,p-=y,(y=s-a)<k){if(l<(y=k-y)&&r.sane){e.msg="invalid distance too far back",r.mode=30;break e}if(S=c,(x=0)===f){if(x+=u-y,y<w){for(w-=y;C[s++]=c[x++],--y;);x=s-k,S=C}}else if(f<y){if(x+=u+f-y,(y-=f)<w){for(w-=y;C[s++]=c[x++],--y;);if(x=0,f<w){for(w-=y=f;C[s++]=c[x++],--y;);x=s-k,S=C}}}else if(x+=f-y,y<w){for(w-=y;C[s++]=c[x++],--y;);x=s-k,S=C}for(;2<w;)C[s++]=S[x++],C[s++]=S[x++],C[s++]=S[x++],w-=3;w&&(C[s++]=S[x++],1<w&&(C[s++]=S[x++]))}else{for(x=s-k;C[s++]=C[x++],C[s++]=C[x++],C[s++]=C[x++],2<(w-=3););w&&(C[s++]=C[x++],1<w&&(C[s++]=C[x++]))}break}}break}}while(n<i&&s<o);n-=w=p>>3,d&=(1<<(p-=w<<3))-1,e.next_in=n,e.next_out=s,e.avail_in=n<i?i-n+5:5-(n-i),e.avail_out=s<o?o-s+257:257-(s-o),r.hold=d,r.bits=p}},{}],49:[function(e,t,r){"use strict";var I=e("../utils/common"),O=e("./adler32"),B=e("./crc32"),R=e("./inffast"),T=e("./inftrees"),D=1,F=2,N=0,U=-2,P=1,n=852,i=592;function L(e){return(e>>>24&255)+(e>>>8&65280)+((65280&e)<<8)+((255&e)<<24)}function s(){this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new I.Buf16(320),this.work=new I.Buf16(288),this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}function a(e){var t;return e&&e.state?(t=e.state,e.total_in=e.total_out=t.total=0,e.msg="",t.wrap&&(e.adler=1&t.wrap),t.mode=P,t.last=0,t.havedict=0,t.dmax=32768,t.head=null,t.hold=0,t.bits=0,t.lencode=t.lendyn=new I.Buf32(n),t.distcode=t.distdyn=new I.Buf32(i),t.sane=1,t.back=-1,N):U}function o(e){var t;return e&&e.state?((t=e.state).wsize=0,t.whave=0,t.wnext=0,a(e)):U}function h(e,t){var r,n;return e&&e.state?(n=e.state,t<0?(r=0,t=-t):(r=1+(t>>4),t<48&&(t&=15)),t&&(t<8||15<t)?U:(null!==n.window&&n.wbits!==t&&(n.window=null),n.wrap=r,n.wbits=t,o(e))):U}function u(e,t){var r,n;return e?(n=new s,(e.state=n).window=null,(r=h(e,t))!==N&&(e.state=null),r):U}var l,f,c=!0;function j(e){if(c){var t;for(l=new I.Buf32(512),f=new I.Buf32(32),t=0;t<144;)e.lens[t++]=8;for(;t<256;)e.lens[t++]=9;for(;t<280;)e.lens[t++]=7;for(;t<288;)e.lens[t++]=8;for(T(D,e.lens,0,288,l,0,e.work,{bits:9}),t=0;t<32;)e.lens[t++]=5;T(F,e.lens,0,32,f,0,e.work,{bits:5}),c=!1}e.lencode=l,e.lenbits=9,e.distcode=f,e.distbits=5}function Z(e,t,r,n){var i,s=e.state;return null===s.window&&(s.wsize=1<<s.wbits,s.wnext=0,s.whave=0,s.window=new I.Buf8(s.wsize)),n>=s.wsize?(I.arraySet(s.window,t,r-s.wsize,s.wsize,0),s.wnext=0,s.whave=s.wsize):(n<(i=s.wsize-s.wnext)&&(i=n),I.arraySet(s.window,t,r-n,i,s.wnext),(n-=i)?(I.arraySet(s.window,t,r-n,n,0),s.wnext=n,s.whave=s.wsize):(s.wnext+=i,s.wnext===s.wsize&&(s.wnext=0),s.whave<s.wsize&&(s.whave+=i))),0}r.inflateReset=o,r.inflateReset2=h,r.inflateResetKeep=a,r.inflateInit=function(e){return u(e,15)},r.inflateInit2=u,r.inflate=function(e,t){var r,n,i,s,a,o,h,u,l,f,c,d,p,m,_,g,b,v,y,w,k,x,S,z,C=0,E=new I.Buf8(4),A=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15];if(!e||!e.state||!e.output||!e.input&&0!==e.avail_in)return U;12===(r=e.state).mode&&(r.mode=13),a=e.next_out,i=e.output,h=e.avail_out,s=e.next_in,n=e.input,o=e.avail_in,u=r.hold,l=r.bits,f=o,c=h,x=N;e:for(;;)switch(r.mode){case P:if(0===r.wrap){r.mode=13;break}for(;l<16;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}if(2&r.wrap&&35615===u){E[r.check=0]=255&u,E[1]=u>>>8&255,r.check=B(r.check,E,2,0),l=u=0,r.mode=2;break}if(r.flags=0,r.head&&(r.head.done=!1),!(1&r.wrap)||(((255&u)<<8)+(u>>8))%31){e.msg="incorrect header check",r.mode=30;break}if(8!=(15&u)){e.msg="unknown compression method",r.mode=30;break}if(l-=4,k=8+(15&(u>>>=4)),0===r.wbits)r.wbits=k;else if(k>r.wbits){e.msg="invalid window size",r.mode=30;break}r.dmax=1<<k,e.adler=r.check=1,r.mode=512&u?10:12,l=u=0;break;case 2:for(;l<16;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}if(r.flags=u,8!=(255&r.flags)){e.msg="unknown compression method",r.mode=30;break}if(57344&r.flags){e.msg="unknown header flags set",r.mode=30;break}r.head&&(r.head.text=u>>8&1),512&r.flags&&(E[0]=255&u,E[1]=u>>>8&255,r.check=B(r.check,E,2,0)),l=u=0,r.mode=3;case 3:for(;l<32;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}r.head&&(r.head.time=u),512&r.flags&&(E[0]=255&u,E[1]=u>>>8&255,E[2]=u>>>16&255,E[3]=u>>>24&255,r.check=B(r.check,E,4,0)),l=u=0,r.mode=4;case 4:for(;l<16;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}r.head&&(r.head.xflags=255&u,r.head.os=u>>8),512&r.flags&&(E[0]=255&u,E[1]=u>>>8&255,r.check=B(r.check,E,2,0)),l=u=0,r.mode=5;case 5:if(1024&r.flags){for(;l<16;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}r.length=u,r.head&&(r.head.extra_len=u),512&r.flags&&(E[0]=255&u,E[1]=u>>>8&255,r.check=B(r.check,E,2,0)),l=u=0}else r.head&&(r.head.extra=null);r.mode=6;case 6:if(1024&r.flags&&(o<(d=r.length)&&(d=o),d&&(r.head&&(k=r.head.extra_len-r.length,r.head.extra||(r.head.extra=new Array(r.head.extra_len)),I.arraySet(r.head.extra,n,s,d,k)),512&r.flags&&(r.check=B(r.check,n,d,s)),o-=d,s+=d,r.length-=d),r.length))break e;r.length=0,r.mode=7;case 7:if(2048&r.flags){if(0===o)break e;for(d=0;k=n[s+d++],r.head&&k&&r.length<65536&&(r.head.name+=String.fromCharCode(k)),k&&d<o;);if(512&r.flags&&(r.check=B(r.check,n,d,s)),o-=d,s+=d,k)break e}else r.head&&(r.head.name=null);r.length=0,r.mode=8;case 8:if(4096&r.flags){if(0===o)break e;for(d=0;k=n[s+d++],r.head&&k&&r.length<65536&&(r.head.comment+=String.fromCharCode(k)),k&&d<o;);if(512&r.flags&&(r.check=B(r.check,n,d,s)),o-=d,s+=d,k)break e}else r.head&&(r.head.comment=null);r.mode=9;case 9:if(512&r.flags){for(;l<16;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}if(u!==(65535&r.check)){e.msg="header crc mismatch",r.mode=30;break}l=u=0}r.head&&(r.head.hcrc=r.flags>>9&1,r.head.done=!0),e.adler=r.check=0,r.mode=12;break;case 10:for(;l<32;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}e.adler=r.check=L(u),l=u=0,r.mode=11;case 11:if(0===r.havedict)return e.next_out=a,e.avail_out=h,e.next_in=s,e.avail_in=o,r.hold=u,r.bits=l,2;e.adler=r.check=1,r.mode=12;case 12:if(5===t||6===t)break e;case 13:if(r.last){u>>>=7&l,l-=7&l,r.mode=27;break}for(;l<3;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}switch(r.last=1&u,l-=1,3&(u>>>=1)){case 0:r.mode=14;break;case 1:if(j(r),r.mode=20,6!==t)break;u>>>=2,l-=2;break e;case 2:r.mode=17;break;case 3:e.msg="invalid block type",r.mode=30}u>>>=2,l-=2;break;case 14:for(u>>>=7&l,l-=7&l;l<32;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}if((65535&u)!=(u>>>16^65535)){e.msg="invalid stored block lengths",r.mode=30;break}if(r.length=65535&u,l=u=0,r.mode=15,6===t)break e;case 15:r.mode=16;case 16:if(d=r.length){if(o<d&&(d=o),h<d&&(d=h),0===d)break e;I.arraySet(i,n,s,d,a),o-=d,s+=d,h-=d,a+=d,r.length-=d;break}r.mode=12;break;case 17:for(;l<14;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}if(r.nlen=257+(31&u),u>>>=5,l-=5,r.ndist=1+(31&u),u>>>=5,l-=5,r.ncode=4+(15&u),u>>>=4,l-=4,286<r.nlen||30<r.ndist){e.msg="too many length or distance symbols",r.mode=30;break}r.have=0,r.mode=18;case 18:for(;r.have<r.ncode;){for(;l<3;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}r.lens[A[r.have++]]=7&u,u>>>=3,l-=3}for(;r.have<19;)r.lens[A[r.have++]]=0;if(r.lencode=r.lendyn,r.lenbits=7,S={bits:r.lenbits},x=T(0,r.lens,0,19,r.lencode,0,r.work,S),r.lenbits=S.bits,x){e.msg="invalid code lengths set",r.mode=30;break}r.have=0,r.mode=19;case 19:for(;r.have<r.nlen+r.ndist;){for(;g=(C=r.lencode[u&(1<<r.lenbits)-1])>>>16&255,b=65535&C,!((_=C>>>24)<=l);){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}if(b<16)u>>>=_,l-=_,r.lens[r.have++]=b;else{if(16===b){for(z=_+2;l<z;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}if(u>>>=_,l-=_,0===r.have){e.msg="invalid bit length repeat",r.mode=30;break}k=r.lens[r.have-1],d=3+(3&u),u>>>=2,l-=2}else if(17===b){for(z=_+3;l<z;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}l-=_,k=0,d=3+(7&(u>>>=_)),u>>>=3,l-=3}else{for(z=_+7;l<z;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}l-=_,k=0,d=11+(127&(u>>>=_)),u>>>=7,l-=7}if(r.have+d>r.nlen+r.ndist){e.msg="invalid bit length repeat",r.mode=30;break}for(;d--;)r.lens[r.have++]=k}}if(30===r.mode)break;if(0===r.lens[256]){e.msg="invalid code -- missing end-of-block",r.mode=30;break}if(r.lenbits=9,S={bits:r.lenbits},x=T(D,r.lens,0,r.nlen,r.lencode,0,r.work,S),r.lenbits=S.bits,x){e.msg="invalid literal/lengths set",r.mode=30;break}if(r.distbits=6,r.distcode=r.distdyn,S={bits:r.distbits},x=T(F,r.lens,r.nlen,r.ndist,r.distcode,0,r.work,S),r.distbits=S.bits,x){e.msg="invalid distances set",r.mode=30;break}if(r.mode=20,6===t)break e;case 20:r.mode=21;case 21:if(6<=o&&258<=h){e.next_out=a,e.avail_out=h,e.next_in=s,e.avail_in=o,r.hold=u,r.bits=l,R(e,c),a=e.next_out,i=e.output,h=e.avail_out,s=e.next_in,n=e.input,o=e.avail_in,u=r.hold,l=r.bits,12===r.mode&&(r.back=-1);break}for(r.back=0;g=(C=r.lencode[u&(1<<r.lenbits)-1])>>>16&255,b=65535&C,!((_=C>>>24)<=l);){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}if(g&&0==(240&g)){for(v=_,y=g,w=b;g=(C=r.lencode[w+((u&(1<<v+y)-1)>>v)])>>>16&255,b=65535&C,!(v+(_=C>>>24)<=l);){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}u>>>=v,l-=v,r.back+=v}if(u>>>=_,l-=_,r.back+=_,r.length=b,0===g){r.mode=26;break}if(32&g){r.back=-1,r.mode=12;break}if(64&g){e.msg="invalid literal/length code",r.mode=30;break}r.extra=15&g,r.mode=22;case 22:if(r.extra){for(z=r.extra;l<z;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}r.length+=u&(1<<r.extra)-1,u>>>=r.extra,l-=r.extra,r.back+=r.extra}r.was=r.length,r.mode=23;case 23:for(;g=(C=r.distcode[u&(1<<r.distbits)-1])>>>16&255,b=65535&C,!((_=C>>>24)<=l);){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}if(0==(240&g)){for(v=_,y=g,w=b;g=(C=r.distcode[w+((u&(1<<v+y)-1)>>v)])>>>16&255,b=65535&C,!(v+(_=C>>>24)<=l);){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}u>>>=v,l-=v,r.back+=v}if(u>>>=_,l-=_,r.back+=_,64&g){e.msg="invalid distance code",r.mode=30;break}r.offset=b,r.extra=15&g,r.mode=24;case 24:if(r.extra){for(z=r.extra;l<z;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}r.offset+=u&(1<<r.extra)-1,u>>>=r.extra,l-=r.extra,r.back+=r.extra}if(r.offset>r.dmax){e.msg="invalid distance too far back",r.mode=30;break}r.mode=25;case 25:if(0===h)break e;if(d=c-h,r.offset>d){if((d=r.offset-d)>r.whave&&r.sane){e.msg="invalid distance too far back",r.mode=30;break}p=d>r.wnext?(d-=r.wnext,r.wsize-d):r.wnext-d,d>r.length&&(d=r.length),m=r.window}else m=i,p=a-r.offset,d=r.length;for(h<d&&(d=h),h-=d,r.length-=d;i[a++]=m[p++],--d;);0===r.length&&(r.mode=21);break;case 26:if(0===h)break e;i[a++]=r.length,h--,r.mode=21;break;case 27:if(r.wrap){for(;l<32;){if(0===o)break e;o--,u|=n[s++]<<l,l+=8}if(c-=h,e.total_out+=c,r.total+=c,c&&(e.adler=r.check=r.flags?B(r.check,i,c,a-c):O(r.check,i,c,a-c)),c=h,(r.flags?u:L(u))!==r.check){e.msg="incorrect data check",r.mode=30;break}l=u=0}r.mode=28;case 28:if(r.wrap&&r.flags){for(;l<32;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}if(u!==(4294967295&r.total)){e.msg="incorrect length check",r.mode=30;break}l=u=0}r.mode=29;case 29:x=1;break e;case 30:x=-3;break e;case 31:return-4;case 32:default:return U}return e.next_out=a,e.avail_out=h,e.next_in=s,e.avail_in=o,r.hold=u,r.bits=l,(r.wsize||c!==e.avail_out&&r.mode<30&&(r.mode<27||4!==t))&&Z(e,e.output,e.next_out,c-e.avail_out)?(r.mode=31,-4):(f-=e.avail_in,c-=e.avail_out,e.total_in+=f,e.total_out+=c,r.total+=c,r.wrap&&c&&(e.adler=r.check=r.flags?B(r.check,i,c,e.next_out-c):O(r.check,i,c,e.next_out-c)),e.data_type=r.bits+(r.last?64:0)+(12===r.mode?128:0)+(20===r.mode||15===r.mode?256:0),(0==f&&0===c||4===t)&&x===N&&(x=-5),x)},r.inflateEnd=function(e){if(!e||!e.state)return U;var t=e.state;return t.window&&(t.window=null),e.state=null,N},r.inflateGetHeader=function(e,t){var r;return e&&e.state?0==(2&(r=e.state).wrap)?U:((r.head=t).done=!1,N):U},r.inflateSetDictionary=function(e,t){var r,n=t.length;return e&&e.state?0!==(r=e.state).wrap&&11!==r.mode?U:11===r.mode&&O(1,t,n,0)!==r.check?-3:Z(e,t,n,n)?(r.mode=31,-4):(r.havedict=1,N):U},r.inflateInfo="pako inflate (from Nodeca project)"},{"../utils/common":41,"./adler32":43,"./crc32":45,"./inffast":48,"./inftrees":50}],50:[function(e,t,r){"use strict";var D=e("../utils/common"),F=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,0,0],N=[16,16,16,16,16,16,16,16,17,17,17,17,18,18,18,18,19,19,19,19,20,20,20,20,21,21,21,21,16,72,78],U=[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0],P=[16,16,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24,25,25,26,26,27,27,28,28,29,29,64,64];t.exports=function(e,t,r,n,i,s,a,o){var h,u,l,f,c,d,p,m,_,g=o.bits,b=0,v=0,y=0,w=0,k=0,x=0,S=0,z=0,C=0,E=0,A=null,I=0,O=new D.Buf16(16),B=new D.Buf16(16),R=null,T=0;for(b=0;b<=15;b++)O[b]=0;for(v=0;v<n;v++)O[t[r+v]]++;for(k=g,w=15;1<=w&&0===O[w];w--);if(w<k&&(k=w),0===w)return i[s++]=20971520,i[s++]=20971520,o.bits=1,0;for(y=1;y<w&&0===O[y];y++);for(k<y&&(k=y),b=z=1;b<=15;b++)if(z<<=1,(z-=O[b])<0)return-1;if(0<z&&(0===e||1!==w))return-1;for(B[1]=0,b=1;b<15;b++)B[b+1]=B[b]+O[b];for(v=0;v<n;v++)0!==t[r+v]&&(a[B[t[r+v]]++]=v);if(d=0===e?(A=R=a,19):1===e?(A=F,I-=257,R=N,T-=257,256):(A=U,R=P,-1),b=y,c=s,S=v=E=0,l=-1,f=(C=1<<(x=k))-1,1===e&&852<C||2===e&&592<C)return 1;for(;;){for(p=b-S,_=a[v]<d?(m=0,a[v]):a[v]>d?(m=R[T+a[v]],A[I+a[v]]):(m=96,0),h=1<<b-S,y=u=1<<x;i[c+(E>>S)+(u-=h)]=p<<24|m<<16|_|0,0!==u;);for(h=1<<b-1;E&h;)h>>=1;if(0!==h?(E&=h-1,E+=h):E=0,v++,0==--O[b]){if(b===w)break;b=t[r+a[v]]}if(k<b&&(E&f)!==l){for(0===S&&(S=k),c+=y,z=1<<(x=b-S);x+S<w&&!((z-=O[x+S])<=0);)x++,z<<=1;if(C+=1<<x,1===e&&852<C||2===e&&592<C)return 1;i[l=E&f]=k<<24|x<<16|c-s|0}}return 0!==E&&(i[c+E]=b-S<<24|64<<16|0),o.bits=k,0}},{"../utils/common":41}],51:[function(e,t,r){"use strict";t.exports={2:"need dictionary",1:"stream end",0:"","-1":"file error","-2":"stream error","-3":"data error","-4":"insufficient memory","-5":"buffer error","-6":"incompatible version"}},{}],52:[function(e,t,r){"use strict";var i=e("../utils/common"),o=0,h=1;function n(e){for(var t=e.length;0<=--t;)e[t]=0}var s=0,a=29,u=256,l=u+1+a,f=30,c=19,_=2*l+1,g=15,d=16,p=7,m=256,b=16,v=17,y=18,w=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0],k=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13],x=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7],S=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],z=new Array(2*(l+2));n(z);var C=new Array(2*f);n(C);var E=new Array(512);n(E);var A=new Array(256);n(A);var I=new Array(a);n(I);var O,B,R,T=new Array(f);function D(e,t,r,n,i){this.static_tree=e,this.extra_bits=t,this.extra_base=r,this.elems=n,this.max_length=i,this.has_stree=e&&e.length}function F(e,t){this.dyn_tree=e,this.max_code=0,this.stat_desc=t}function N(e){return e<256?E[e]:E[256+(e>>>7)]}function U(e,t){e.pending_buf[e.pending++]=255&t,e.pending_buf[e.pending++]=t>>>8&255}function P(e,t,r){e.bi_valid>d-r?(e.bi_buf|=t<<e.bi_valid&65535,U(e,e.bi_buf),e.bi_buf=t>>d-e.bi_valid,e.bi_valid+=r-d):(e.bi_buf|=t<<e.bi_valid&65535,e.bi_valid+=r)}function L(e,t,r){P(e,r[2*t],r[2*t+1])}function j(e,t){for(var r=0;r|=1&e,e>>>=1,r<<=1,0<--t;);return r>>>1}function Z(e,t,r){var n,i,s=new Array(g+1),a=0;for(n=1;n<=g;n++)s[n]=a=a+r[n-1]<<1;for(i=0;i<=t;i++){var o=e[2*i+1];0!==o&&(e[2*i]=j(s[o]++,o))}}function W(e){var t;for(t=0;t<l;t++)e.dyn_ltree[2*t]=0;for(t=0;t<f;t++)e.dyn_dtree[2*t]=0;for(t=0;t<c;t++)e.bl_tree[2*t]=0;e.dyn_ltree[2*m]=1,e.opt_len=e.static_len=0,e.last_lit=e.matches=0}function M(e){8<e.bi_valid?U(e,e.bi_buf):0<e.bi_valid&&(e.pending_buf[e.pending++]=e.bi_buf),e.bi_buf=0,e.bi_valid=0}function H(e,t,r,n){var i=2*t,s=2*r;return e[i]<e[s]||e[i]===e[s]&&n[t]<=n[r]}function G(e,t,r){for(var n=e.heap[r],i=r<<1;i<=e.heap_len&&(i<e.heap_len&&H(t,e.heap[i+1],e.heap[i],e.depth)&&i++,!H(t,n,e.heap[i],e.depth));)e.heap[r]=e.heap[i],r=i,i<<=1;e.heap[r]=n}function K(e,t,r){var n,i,s,a,o=0;if(0!==e.last_lit)for(;n=e.pending_buf[e.d_buf+2*o]<<8|e.pending_buf[e.d_buf+2*o+1],i=e.pending_buf[e.l_buf+o],o++,0===n?L(e,i,t):(L(e,(s=A[i])+u+1,t),0!==(a=w[s])&&P(e,i-=I[s],a),L(e,s=N(--n),r),0!==(a=k[s])&&P(e,n-=T[s],a)),o<e.last_lit;);L(e,m,t)}function Y(e,t){var r,n,i,s=t.dyn_tree,a=t.stat_desc.static_tree,o=t.stat_desc.has_stree,h=t.stat_desc.elems,u=-1;for(e.heap_len=0,e.heap_max=_,r=0;r<h;r++)0!==s[2*r]?(e.heap[++e.heap_len]=u=r,e.depth[r]=0):s[2*r+1]=0;for(;e.heap_len<2;)s[2*(i=e.heap[++e.heap_len]=u<2?++u:0)]=1,e.depth[i]=0,e.opt_len--,o&&(e.static_len-=a[2*i+1]);for(t.max_code=u,r=e.heap_len>>1;1<=r;r--)G(e,s,r);for(i=h;r=e.heap[1],e.heap[1]=e.heap[e.heap_len--],G(e,s,1),n=e.heap[1],e.heap[--e.heap_max]=r,e.heap[--e.heap_max]=n,s[2*i]=s[2*r]+s[2*n],e.depth[i]=(e.depth[r]>=e.depth[n]?e.depth[r]:e.depth[n])+1,s[2*r+1]=s[2*n+1]=i,e.heap[1]=i++,G(e,s,1),2<=e.heap_len;);e.heap[--e.heap_max]=e.heap[1],function(e,t){var r,n,i,s,a,o,h=t.dyn_tree,u=t.max_code,l=t.stat_desc.static_tree,f=t.stat_desc.has_stree,c=t.stat_desc.extra_bits,d=t.stat_desc.extra_base,p=t.stat_desc.max_length,m=0;for(s=0;s<=g;s++)e.bl_count[s]=0;for(h[2*e.heap[e.heap_max]+1]=0,r=e.heap_max+1;r<_;r++)p<(s=h[2*h[2*(n=e.heap[r])+1]+1]+1)&&(s=p,m++),h[2*n+1]=s,u<n||(e.bl_count[s]++,a=0,d<=n&&(a=c[n-d]),o=h[2*n],e.opt_len+=o*(s+a),f&&(e.static_len+=o*(l[2*n+1]+a)));if(0!==m){do{for(s=p-1;0===e.bl_count[s];)s--;e.bl_count[s]--,e.bl_count[s+1]+=2,e.bl_count[p]--,m-=2}while(0<m);for(s=p;0!==s;s--)for(n=e.bl_count[s];0!==n;)u<(i=e.heap[--r])||(h[2*i+1]!==s&&(e.opt_len+=(s-h[2*i+1])*h[2*i],h[2*i+1]=s),n--)}}(e,t),Z(s,u,e.bl_count)}function X(e,t,r){var n,i,s=-1,a=t[1],o=0,h=7,u=4;for(0===a&&(h=138,u=3),t[2*(r+1)+1]=65535,n=0;n<=r;n++)i=a,a=t[2*(n+1)+1],++o<h&&i===a||(o<u?e.bl_tree[2*i]+=o:0!==i?(i!==s&&e.bl_tree[2*i]++,e.bl_tree[2*b]++):o<=10?e.bl_tree[2*v]++:e.bl_tree[2*y]++,s=i,u=(o=0)===a?(h=138,3):i===a?(h=6,3):(h=7,4))}function V(e,t,r){var n,i,s=-1,a=t[1],o=0,h=7,u=4;for(0===a&&(h=138,u=3),n=0;n<=r;n++)if(i=a,a=t[2*(n+1)+1],!(++o<h&&i===a)){if(o<u)for(;L(e,i,e.bl_tree),0!=--o;);else 0!==i?(i!==s&&(L(e,i,e.bl_tree),o--),L(e,b,e.bl_tree),P(e,o-3,2)):o<=10?(L(e,v,e.bl_tree),P(e,o-3,3)):(L(e,y,e.bl_tree),P(e,o-11,7));s=i,u=(o=0)===a?(h=138,3):i===a?(h=6,3):(h=7,4)}}n(T);var q=!1;function J(e,t,r,n){P(e,(s<<1)+(n?1:0),3),function(e,t,r,n){M(e),n&&(U(e,r),U(e,~r)),i.arraySet(e.pending_buf,e.window,t,r,e.pending),e.pending+=r}(e,t,r,!0)}r._tr_init=function(e){q||(function(){var e,t,r,n,i,s=new Array(g+1);for(n=r=0;n<a-1;n++)for(I[n]=r,e=0;e<1<<w[n];e++)A[r++]=n;for(A[r-1]=n,n=i=0;n<16;n++)for(T[n]=i,e=0;e<1<<k[n];e++)E[i++]=n;for(i>>=7;n<f;n++)for(T[n]=i<<7,e=0;e<1<<k[n]-7;e++)E[256+i++]=n;for(t=0;t<=g;t++)s[t]=0;for(e=0;e<=143;)z[2*e+1]=8,e++,s[8]++;for(;e<=255;)z[2*e+1]=9,e++,s[9]++;for(;e<=279;)z[2*e+1]=7,e++,s[7]++;for(;e<=287;)z[2*e+1]=8,e++,s[8]++;for(Z(z,l+1,s),e=0;e<f;e++)C[2*e+1]=5,C[2*e]=j(e,5);O=new D(z,w,u+1,l,g),B=new D(C,k,0,f,g),R=new D(new Array(0),x,0,c,p)}(),q=!0),e.l_desc=new F(e.dyn_ltree,O),e.d_desc=new F(e.dyn_dtree,B),e.bl_desc=new F(e.bl_tree,R),e.bi_buf=0,e.bi_valid=0,W(e)},r._tr_stored_block=J,r._tr_flush_block=function(e,t,r,n){var i,s,a=0;0<e.level?(2===e.strm.data_type&&(e.strm.data_type=function(e){var t,r=4093624447;for(t=0;t<=31;t++,r>>>=1)if(1&r&&0!==e.dyn_ltree[2*t])return o;if(0!==e.dyn_ltree[18]||0!==e.dyn_ltree[20]||0!==e.dyn_ltree[26])return h;for(t=32;t<u;t++)if(0!==e.dyn_ltree[2*t])return h;return o}(e)),Y(e,e.l_desc),Y(e,e.d_desc),a=function(e){var t;for(X(e,e.dyn_ltree,e.l_desc.max_code),X(e,e.dyn_dtree,e.d_desc.max_code),Y(e,e.bl_desc),t=c-1;3<=t&&0===e.bl_tree[2*S[t]+1];t--);return e.opt_len+=3*(t+1)+5+5+4,t}(e),i=e.opt_len+3+7>>>3,(s=e.static_len+3+7>>>3)<=i&&(i=s)):i=s=r+5,r+4<=i&&-1!==t?J(e,t,r,n):4===e.strategy||s===i?(P(e,2+(n?1:0),3),K(e,z,C)):(P(e,4+(n?1:0),3),function(e,t,r,n){var i;for(P(e,t-257,5),P(e,r-1,5),P(e,n-4,4),i=0;i<n;i++)P(e,e.bl_tree[2*S[i]+1],3);V(e,e.dyn_ltree,t-1),V(e,e.dyn_dtree,r-1)}(e,e.l_desc.max_code+1,e.d_desc.max_code+1,a+1),K(e,e.dyn_ltree,e.dyn_dtree)),W(e),n&&M(e)},r._tr_tally=function(e,t,r){return e.pending_buf[e.d_buf+2*e.last_lit]=t>>>8&255,e.pending_buf[e.d_buf+2*e.last_lit+1]=255&t,e.pending_buf[e.l_buf+e.last_lit]=255&r,e.last_lit++,0===t?e.dyn_ltree[2*r]++:(e.matches++,t--,e.dyn_ltree[2*(A[r]+u+1)]++,e.dyn_dtree[2*N(t)]++),e.last_lit===e.lit_bufsize-1},r._tr_align=function(e){P(e,2,3),L(e,m,z),function(e){16===e.bi_valid?(U(e,e.bi_buf),e.bi_buf=0,e.bi_valid=0):8<=e.bi_valid&&(e.pending_buf[e.pending++]=255&e.bi_buf,e.bi_buf>>=8,e.bi_valid-=8)}(e)}},{"../utils/common":41}],53:[function(e,t,r){"use strict";t.exports=function(){this.input=null,this.next_in=0,this.avail_in=0,this.total_in=0,this.output=null,this.next_out=0,this.avail_out=0,this.total_out=0,this.msg="",this.state=null,this.data_type=2,this.adler=0}},{}],54:[function(e,t,r){(function(e){!function(r,n){"use strict";if(!r.setImmediate){var i,s,t,a,o=1,h={},u=!1,l=r.document,e=Object.getPrototypeOf&&Object.getPrototypeOf(r);e=e&&e.setTimeout?e:r,i="[object process]"==={}.toString.call(r.process)?function(e){process.nextTick(function(){c(e)})}:function(){if(r.postMessage&&!r.importScripts){var e=!0,t=r.onmessage;return r.onmessage=function(){e=!1},r.postMessage("","*"),r.onmessage=t,e}}()?(a="setImmediate$"+Math.random()+"$",r.addEventListener?r.addEventListener("message",d,!1):r.attachEvent("onmessage",d),function(e){r.postMessage(a+e,"*")}):r.MessageChannel?((t=new MessageChannel).port1.onmessage=function(e){c(e.data)},function(e){t.port2.postMessage(e)}):l&&"onreadystatechange"in l.createElement("script")?(s=l.documentElement,function(e){var t=l.createElement("script");t.onreadystatechange=function(){c(e),t.onreadystatechange=null,s.removeChild(t),t=null},s.appendChild(t)}):function(e){setTimeout(c,0,e)},e.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),r=0;r<t.length;r++)t[r]=arguments[r+1];var n={callback:e,args:t};return h[o]=n,i(o),o++},e.clearImmediate=f}function f(e){delete h[e]}function c(e){if(u)setTimeout(c,0,e);else{var t=h[e];if(t){u=!0;try{!function(e){var t=e.callback,r=e.args;switch(r.length){case 0:t();break;case 1:t(r[0]);break;case 2:t(r[0],r[1]);break;case 3:t(r[0],r[1],r[2]);break;default:t.apply(n,r)}}(t)}finally{f(e),u=!1}}}}function d(e){e.source===r&&"string"==typeof e.data&&0===e.data.indexOf(a)&&c(+e.data.slice(a.length))}}("undefined"==typeof self?void 0===e?this:e:self)}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}]},{},[10])(10)});
|
||
/*
|
||
* @license
|
||
* docx-preview <https://github.com/VolodymyrBaydalka/docxjs>
|
||
* Released under Apache License 2.0 <https://github.com/VolodymyrBaydalka/docxjs/blob/master/LICENSE>
|
||
* Copyright Volodymyr Baydalka
|
||
*/
|
||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("jszip")):"function"==typeof define&&define.amd?define(["exports","jszip"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).docx={},e.JSZip)}(this,function(e,t){"use strict";var r;function a(e){return/^[^"'].*\s.*[^"']$/.test(e)?`'${e}'`:e}function s(e){let t=e.lastIndexOf("/")+1;return[0==t?"":e.substring(0,t),0==t?e:e.substring(t)]}function n(e,t){try{const r="http://docx/";return new URL(e,r+t).toString().substring(r.length)}catch{return`${t}${e}`}}function l(e,t){return e.reduce((e,r)=>(e[t(r)]=r,e),{})}function o(e){return e&&"object"==typeof e&&!Array.isArray(e)}function i(e,...t){if(!t.length)return e;const r=t.shift();if(o(e)&&o(r))for(const t in r)if(o(r[t])){i(e[t]??(e[t]={}),r[t])}else e[t]=r[t];return i(e,...t)}function c(e){return Array.isArray(e)?e:[e]}!function(e){e.OfficeDocument="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument",e.FontTable="http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable",e.Image="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",e.Numbering="http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering",e.Styles="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles",e.StylesWithEffects="http://schemas.microsoft.com/office/2007/relationships/stylesWithEffects",e.Theme="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme",e.Settings="http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings",e.WebSettings="http://schemas.openxmlformats.org/officeDocument/2006/relationships/webSettings",e.Hyperlink="http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",e.Footnotes="http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes",e.Endnotes="http://schemas.openxmlformats.org/officeDocument/2006/relationships/endnotes",e.Footer="http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer",e.Header="http://schemas.openxmlformats.org/officeDocument/2006/relationships/header",e.ExtendedProperties="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties",e.CoreProperties="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties",e.CustomProperties="http://schemas.openxmlformats.org/package/2006/relationships/metadata/custom-properties",e.Comments="http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments",e.CommentsExtended="http://schemas.microsoft.com/office/2011/relationships/commentsExtended",e.AltChunk="http://schemas.openxmlformats.org/officeDocument/2006/relationships/aFChunk"}(r||(r={}));const h="http://schemas.openxmlformats.org/wordprocessingml/2006/main",m={mul:.05,unit:"pt"},p={mul:1/12700,unit:"pt"},u={mul:.5,unit:"pt"},d={mul:.125,unit:"pt",min:.25,max:12},f={mul:1,unit:"pt"},g={mul:.02,unit:"%"};function b(e,t=m){if(null==e||/.+(p[xt]|[%])$/.test(e))return e;var r=parseInt(e)*t.mul;return t.min&&t.max&&(r=function(e,t,r){return t>e?t:r<e?r:e}(r,t.min,t.max)),`${r.toFixed(2)}${t.unit}`}function y(e,t,r){if(e.namespaceURI!=h)return!1;switch(e.localName){case"color":t.color=r.attr(e,"val");break;case"sz":t.fontSize=r.lengthAttr(e,"val",u);break;default:return!1}return!0}class k{elements(e,t=null){const r=[];for(let a=0,s=e.childNodes.length;a<s;a++){let s=e.childNodes.item(a);s.nodeType!=Node.ELEMENT_NODE||null!=t&&s.localName!=t||r.push(s)}return r}element(e,t){for(let r=0,a=e.childNodes.length;r<a;r++){let a=e.childNodes.item(r);if(1==a.nodeType&&a.localName==t)return a}return null}elementAttr(e,t,r){var a=this.element(e,t);return a?this.attr(a,r):void 0}attrs(e){return Array.from(e.attributes)}attr(e,t){for(let r=0,a=e.attributes.length;r<a;r++){let a=e.attributes.item(r);if(a.localName==t)return a.value}return null}intAttr(e,t,r=null){var a=this.attr(e,t);return a?parseInt(a):r}hexAttr(e,t,r=null){var a=this.attr(e,t);return a?parseInt(a,16):r}floatAttr(e,t,r=null){var a=this.attr(e,t);return a?parseFloat(a):r}boolAttr(e,t,r=null){return function(e,t=!1){switch(e){case"1":case"on":case"true":return!0;case"0":case"off":case"false":return!1;default:return t}}(this.attr(e,t),r)}lengthAttr(e,t,r=m){return b(this.attr(e,t),r)}}const v=new k;class S{constructor(e,t){this._package=e,this.path=t}async load(){this.rels=await this._package.loadRelationships(this.path);const e=await this._package.load(this.path),t=this._package.parseXmlDocument(e);this._package.options.keepOrigin&&(this._xmlDocument=t),this.parseXml(t.firstElementChild)}save(){var e;this._package.update(this.path,(e=this._xmlDocument,(new XMLSerializer).serializeToString(e)))}parseXml(e){}}const P={embedRegular:"regular",embedBold:"bold",embedItalic:"italic",embedBoldItalic:"boldItalic"};function w(e,t){return t.elements(e).map(e=>function(e,t){let r={name:t.attr(e,"name"),embedFontRefs:[]};for(let a of t.elements(e))switch(a.localName){case"family":r.family=t.attr(a,"val");break;case"altName":r.altName=t.attr(a,"val");break;case"embedRegular":case"embedBold":case"embedItalic":case"embedBoldItalic":r.embedFontRefs.push(C(a,t))}return r}(e,t))}function C(e,t){return{id:t.attr(e,"id"),key:t.attr(e,"fontKey"),type:P[e.localName]}}class x extends S{parseXml(e){this.fonts=w(e,this._package.xmlParser)}}class N{constructor(e,t){this._zip=e,this.options=t,this.xmlParser=new k}get(e){const t=function(e){return e.startsWith("/")?e.substr(1):e}(e);return this._zip.files[t]??this._zip.files[t.replace(/\//g,"\\")]}update(e,t){this._zip.file(e,t)}static async load(e,r){const a=await t.loadAsync(e);return new N(a,r)}save(e="blob"){return this._zip.generateAsync({type:e})}load(e,t="string"){return this.get(e)?.async(t)??Promise.resolve(null)}async loadRelationships(e=null){let t="_rels/.rels";if(null!=e){const[r,a]=s(e);t=`${r}_rels/${a}.rels`}const r=await this.load(t);return r?(a=this.parseXmlDocument(r).firstElementChild,(n=this.xmlParser).elements(a).map(e=>({id:n.attr(e,"Id"),type:n.attr(e,"Type"),target:n.attr(e,"Target"),targetMode:n.attr(e,"TargetMode")}))):null;var a,n}parseXmlDocument(e){return function(e,t=!1){var r;t&&(e=e.replace(/<[?].*[?]>/,"")),e=65279===(r=e).charCodeAt(0)?r.substring(1):r;const a=(new DOMParser).parseFromString(e,"application/xml"),s=(n=a,n.getElementsByTagName("parsererror")[0]?.textContent);var n;if(s)throw new Error(s);return a}(e,this.options.trimXmlDeclaration)}}class M extends S{constructor(e,t,r){super(e,t),this._documentParser=r}parseXml(e){this.body=this._documentParser.parseDocumentFile(e)}}function A(e,t){return{type:t.attr(e,"val"),color:t.attr(e,"color"),size:t.lengthAttr(e,"sz",d),offset:t.lengthAttr(e,"space",f),frame:t.boolAttr(e,"frame"),shadow:t.boolAttr(e,"shadow")}}function E(e,t){var r={};for(let a of t.elements(e))switch(a.localName){case"left":r.left=A(a,t);break;case"top":r.top=A(a,t);break;case"right":r.right=A(a,t);break;case"bottom":r.bottom=A(a,t)}return r}var T,R;function B(e,t=v){var r={};for(let a of t.elements(e))switch(a.localName){case"pgSz":r.pageSize={width:t.lengthAttr(a,"w"),height:t.lengthAttr(a,"h"),orientation:t.attr(a,"orient")};break;case"type":r.type=t.attr(a,"val");break;case"pgMar":r.pageMargins={left:t.lengthAttr(a,"left"),right:t.lengthAttr(a,"right"),top:t.lengthAttr(a,"top"),bottom:t.lengthAttr(a,"bottom"),header:t.lengthAttr(a,"header"),footer:t.lengthAttr(a,"footer"),gutter:t.lengthAttr(a,"gutter")};break;case"cols":r.columns=D(a,t);break;case"headerReference":(r.headerRefs??(r.headerRefs=[])).push(F(a,t));break;case"footerReference":(r.footerRefs??(r.footerRefs=[])).push(F(a,t));break;case"titlePg":r.titlePage=t.boolAttr(a,"val",!0);break;case"pgBorders":r.pageBorders=E(a,t);break;case"pgNumType":r.pageNumber=$(a,t)}return r}function D(e,t){return{numberOfColumns:t.intAttr(e,"num"),space:t.lengthAttr(e,"space"),separator:t.boolAttr(e,"sep"),equalWidth:t.boolAttr(e,"equalWidth",!0),columns:t.elements(e,"col").map(e=>({width:t.lengthAttr(e,"w"),space:t.lengthAttr(e,"space")}))}}function $(e,t){return{chapSep:t.attr(e,"chapSep"),chapStyle:t.attr(e,"chapStyle"),format:t.attr(e,"fmt"),start:t.intAttr(e,"start")}}function F(e,t){return{id:t.attr(e,"id"),type:t.attr(e,"type")}}function L(e,t){let r={};for(let a of t.elements(e))I(a,r,t);return r}function I(e,t,r){return!!y(e,t,r)}function O(e,t){let r={};for(let a of t.elements(e))H(a,r,t);return r}function H(e,t,r){if(e.namespaceURI!=h)return!1;if(y(e,t,r))return!0;switch(e.localName){case"tabs":t.tabs=function(e,t){return t.elements(e,"tab").map(e=>({position:t.lengthAttr(e,"pos"),leader:t.attr(e,"leader"),style:t.attr(e,"val")}))}(e,r);break;case"sectPr":t.sectionProps=B(e,r);break;case"numPr":t.numbering=function(e,t){var r={};for(let a of t.elements(e))switch(a.localName){case"numId":r.id=t.attr(a,"val");break;case"ilvl":r.level=t.intAttr(a,"val")}return r}(e,r);break;case"spacing":return t.lineSpacing=function(e,t){return{before:t.lengthAttr(e,"before"),after:t.lengthAttr(e,"after"),line:t.intAttr(e,"line"),lineRule:t.attr(e,"lineRule")}}(e,r),!1;case"textAlignment":return t.textAlignment=r.attr(e,"val"),!1;case"keepLines":t.keepLines=r.boolAttr(e,"val",!0);break;case"keepNext":t.keepNext=r.boolAttr(e,"val",!0);break;case"pageBreakBefore":t.pageBreakBefore=r.boolAttr(e,"val",!0);break;case"outlineLvl":t.outlineLevel=r.intAttr(e,"val");break;case"pStyle":t.styleName=r.attr(e,"val");break;case"rPr":t.runProps=L(e,r);break;default:return!1}return!0}function _(e,t){let r={id:t.attr(e,"numId"),overrides:[]};for(let a of t.elements(e))switch(a.localName){case"abstractNumId":r.abstractId=t.attr(a,"val");break;case"lvlOverride":r.overrides.push(j(a,t))}return r}function z(e,t){let r={id:t.attr(e,"abstractNumId"),levels:[]};for(let a of t.elements(e))switch(a.localName){case"name":r.name=t.attr(a,"val");break;case"multiLevelType":r.multiLevelType=t.attr(a,"val");break;case"numStyleLink":r.numberingStyleLink=t.attr(a,"val");break;case"styleLink":r.styleLink=t.attr(a,"val");break;case"lvl":r.levels.push(V(a,t))}return r}function V(e,t){let r={level:t.intAttr(e,"ilvl")};for(let a of t.elements(e))switch(a.localName){case"start":r.start=t.attr(a,"val");break;case"lvlRestart":r.restart=t.intAttr(a,"val");break;case"numFmt":r.format=t.attr(a,"val");break;case"lvlText":r.text=t.attr(a,"val");break;case"lvlJc":r.justification=t.attr(a,"val");break;case"lvlPicBulletId":r.bulletPictureId=t.attr(a,"val");break;case"pStyle":r.paragraphStyle=t.attr(a,"val");break;case"pPr":r.paragraphProps=O(a,t);break;case"rPr":r.runProps=L(a,t)}return r}function j(e,t){let r={level:t.intAttr(e,"ilvl")};for(let a of t.elements(e))switch(a.localName){case"startOverride":r.start=t.intAttr(a,"val");break;case"lvl":r.numberingLevel=V(a,t)}return r}function W(e,t){var r=t.element(e,"pict"),a=r&&t.element(r,"shape"),s=a&&t.element(a,"imagedata");return s?{id:t.attr(e,"numPicBulletId"),referenceId:t.attr(s,"id"),style:t.attr(a,"style")}:null}!function(e){e.Continuous="continuous",e.NextPage="nextPage",e.NextColumn="nextColumn",e.EvenPage="evenPage",e.OddPage="oddPage"}(T||(T={}));class X extends S{constructor(e,t,r){super(e,t),this._documentParser=r}parseXml(e){Object.assign(this,function(e,t){let r={numberings:[],abstractNumberings:[],bulletPictures:[]};for(let a of t.elements(e))switch(a.localName){case"num":r.numberings.push(_(a,t));break;case"abstractNum":r.abstractNumberings.push(z(a,t));break;case"numPicBullet":r.bulletPictures.push(W(a,t))}return r}(e,this._package.xmlParser)),this.domNumberings=this._documentParser.parseNumberingFile(e)}}class G extends S{constructor(e,t,r){super(e,t),this._documentParser=r}parseXml(e){this.styles=this._documentParser.parseStylesFile(e)}}!function(e){e.Document="document",e.Paragraph="paragraph",e.Run="run",e.Break="break",e.NoBreakHyphen="noBreakHyphen",e.Table="table",e.Row="row",e.Cell="cell",e.Hyperlink="hyperlink",e.SmartTag="smartTag",e.Drawing="drawing",e.Image="image",e.Text="text",e.Tab="tab",e.Symbol="symbol",e.BookmarkStart="bookmarkStart",e.BookmarkEnd="bookmarkEnd",e.Footer="footer",e.Header="header",e.FootnoteReference="footnoteReference",e.EndnoteReference="endnoteReference",e.Footnote="footnote",e.Endnote="endnote",e.SimpleField="simpleField",e.ComplexField="complexField",e.Instruction="instruction",e.VmlPicture="vmlPicture",e.MmlMath="mmlMath",e.MmlMathParagraph="mmlMathParagraph",e.MmlFraction="mmlFraction",e.MmlFunction="mmlFunction",e.MmlFunctionName="mmlFunctionName",e.MmlNumerator="mmlNumerator",e.MmlDenominator="mmlDenominator",e.MmlRadical="mmlRadical",e.MmlBase="mmlBase",e.MmlDegree="mmlDegree",e.MmlSuperscript="mmlSuperscript",e.MmlSubscript="mmlSubscript",e.MmlPreSubSuper="mmlPreSubSuper",e.MmlSubArgument="mmlSubArgument",e.MmlSuperArgument="mmlSuperArgument",e.MmlNary="mmlNary",e.MmlDelimiter="mmlDelimiter",e.MmlRun="mmlRun",e.MmlEquationArray="mmlEquationArray",e.MmlLimit="mmlLimit",e.MmlLimitLower="mmlLimitLower",e.MmlMatrix="mmlMatrix",e.MmlMatrixRow="mmlMatrixRow",e.MmlBox="mmlBox",e.MmlBar="mmlBar",e.MmlGroupChar="mmlGroupChar",e.VmlElement="vmlElement",e.Inserted="inserted",e.Deleted="deleted",e.DeletedText="deletedText",e.Comment="comment",e.CommentReference="commentReference",e.CommentRangeStart="commentRangeStart",e.CommentRangeEnd="commentRangeEnd",e.AltChunk="altChunk"}(R||(R={}));class U{constructor(){this.children=[],this.cssStyle={}}}class q extends U{constructor(){super(...arguments),this.type=R.Header}}class J extends U{constructor(){super(...arguments),this.type=R.Footer}}class Z extends S{constructor(e,t,r){super(e,t),this._documentParser=r}parseXml(e){this.rootElement=this.createRootElement(),this.rootElement.children=this._documentParser.parseBodyElements(e)}}class K extends Z{createRootElement(){return new q}}class Y extends Z{createRootElement(){return new J}}function Q(e){if(void 0!==e)return parseInt(e)}class ee extends S{parseXml(e){this.props=function(e,t){const r={};for(let a of t.elements(e))switch(a.localName){case"Template":r.template=a.textContent;break;case"Pages":r.pages=Q(a.textContent);break;case"Words":r.words=Q(a.textContent);break;case"Characters":r.characters=Q(a.textContent);break;case"Application":r.application=a.textContent;break;case"Lines":r.lines=Q(a.textContent);break;case"Paragraphs":r.paragraphs=Q(a.textContent);break;case"Company":r.company=a.textContent;break;case"AppVersion":r.appVersion=a.textContent}return r}(e,this._package.xmlParser)}}class te extends S{parseXml(e){this.props=function(e,t){const r={};for(let a of t.elements(e))switch(a.localName){case"title":r.title=a.textContent;break;case"description":r.description=a.textContent;break;case"subject":r.subject=a.textContent;break;case"creator":r.creator=a.textContent;break;case"keywords":r.keywords=a.textContent;break;case"language":r.language=a.textContent;break;case"lastModifiedBy":r.lastModifiedBy=a.textContent;break;case"revision":a.textContent&&(r.revision=parseInt(a.textContent))}return r}(e,this._package.xmlParser)}}class re{}function ae(e,t){var r={name:t.attr(e,"name"),colors:{}};for(let n of t.elements(e)){var a=t.element(n,"srgbClr"),s=t.element(n,"sysClr");a?r.colors[n.localName]=t.attr(a,"val"):s&&(r.colors[n.localName]=t.attr(s,"lastClr"))}return r}function se(e,t){var r={name:t.attr(e,"name")};for(let a of t.elements(e))switch(a.localName){case"majorFont":r.majorFont=ne(a,t);break;case"minorFont":r.minorFont=ne(a,t)}return r}function ne(e,t){return{latinTypeface:t.elementAttr(e,"latin","typeface"),eaTypeface:t.elementAttr(e,"ea","typeface"),csTypeface:t.elementAttr(e,"cs","typeface")}}class le extends S{constructor(e,t){super(e,t)}parseXml(e){this.theme=function(e,t){var r=new re,a=t.element(e,"themeElements");for(let e of t.elements(a))switch(e.localName){case"clrScheme":r.colorScheme=ae(e,t);break;case"fontScheme":r.fontScheme=se(e,t)}return r}(e,this._package.xmlParser)}}class oe{}class ie extends oe{constructor(){super(...arguments),this.type=R.Footnote}}class ce extends oe{constructor(){super(...arguments),this.type=R.Endnote}}class he extends S{constructor(e,t,r){super(e,t),this._documentParser=r}}class me extends he{constructor(e,t,r){super(e,t,r)}parseXml(e){this.notes=this._documentParser.parseNotes(e,"footnote",ie)}}class pe extends he{constructor(e,t,r){super(e,t,r)}parseXml(e){this.notes=this._documentParser.parseNotes(e,"endnote",ce)}}function ue(e,t){var r={defaultNoteIds:[]};for(let a of t.elements(e))switch(a.localName){case"numFmt":r.nummeringFormat=t.attr(a,"val");break;case"footnote":case"endnote":r.defaultNoteIds.push(t.attr(a,"id"))}return r}class de extends S{constructor(e,t){super(e,t)}parseXml(e){this.settings=function(e,t){var r={};for(let a of t.elements(e))switch(a.localName){case"defaultTabStop":r.defaultTabStop=t.lengthAttr(a,"val");break;case"footnotePr":r.footnoteProps=ue(a,t);break;case"endnotePr":r.endnoteProps=ue(a,t);break;case"autoHyphenation":r.autoHyphenation=t.boolAttr(a,"val")}return r}(e,this._package.xmlParser)}}class fe extends S{parseXml(e){this.props=function(e,t){return t.elements(e,"property").map(e=>{const r=e.firstChild;return{formatId:t.attr(e,"fmtid"),name:t.attr(e,"name"),type:r.nodeName,value:r.textContent}})}(e,this._package.xmlParser)}}class ge extends S{constructor(e,t,r){super(e,t),this._documentParser=r}parseXml(e){this.comments=this._documentParser.parseComments(e),this.commentMap=l(this.comments,e=>e.id)}}class be extends S{constructor(e,t){super(e,t),this.comments=[]}parseXml(e){const t=this._package.xmlParser;for(let r of t.elements(e,"commentEx"))this.comments.push({paraId:t.attr(r,"paraId"),paraIdParent:t.attr(r,"paraIdParent"),done:t.boolAttr(r,"done")});this.commentMap=l(this.comments,e=>e.paraId)}}const ye=[{type:r.OfficeDocument,target:"word/document.xml"},{type:r.ExtendedProperties,target:"docProps/app.xml"},{type:r.CoreProperties,target:"docProps/core.xml"},{type:r.CustomProperties,target:"docProps/custom.xml"}];class ke{constructor(){this.parts=[],this.partsMap={}}static async load(e,t,r){var a=new ke;return a._options=r,a._parser=t,a._package=await N.load(e,r),a.rels=await a._package.loadRelationships(),await Promise.all(ye.map(e=>{const t=a.rels.find(t=>t.type===e.type)??e;return a.loadRelationshipPart(t.target,t.type)})),a}save(e="blob"){return this._package.save(e)}async loadRelationshipPart(e,t){if(this.partsMap[e])return this.partsMap[e];if(!this._package.get(e))return null;let a=null;switch(t){case r.OfficeDocument:this.documentPart=a=new M(this._package,e,this._parser);break;case r.FontTable:this.fontTablePart=a=new x(this._package,e);break;case r.Numbering:this.numberingPart=a=new X(this._package,e,this._parser);break;case r.Styles:this.stylesPart=a=new G(this._package,e,this._parser);break;case r.Theme:this.themePart=a=new le(this._package,e);break;case r.Footnotes:this.footnotesPart=a=new me(this._package,e,this._parser);break;case r.Endnotes:this.endnotesPart=a=new pe(this._package,e,this._parser);break;case r.Footer:a=new Y(this._package,e,this._parser);break;case r.Header:a=new K(this._package,e,this._parser);break;case r.CoreProperties:this.corePropsPart=a=new te(this._package,e);break;case r.ExtendedProperties:this.extendedPropsPart=a=new ee(this._package,e);break;case r.CustomProperties:a=new fe(this._package,e);break;case r.Settings:this.settingsPart=a=new de(this._package,e);break;case r.Comments:this.commentsPart=a=new ge(this._package,e,this._parser);break;case r.CommentsExtended:this.commentsExtendedPart=a=new be(this._package,e)}if(null==a)return Promise.resolve(null);if(this.partsMap[e]=a,this.parts.push(a),await a.load(),a.rels?.length>0){const[e]=s(a.path);await Promise.all(a.rels.map(t=>this.loadRelationshipPart(n(t.target,e),t.type)))}return a}async loadDocumentImage(e,t){const r=await this.loadResource(t??this.documentPart,e,"blob");return this.blobToURL(r)}async loadNumberingImage(e){const t=await this.loadResource(this.numberingPart,e,"blob");return this.blobToURL(t)}async loadFont(e,t){const r=await this.loadResource(this.fontTablePart,e,"uint8array");return r?this.blobToURL(new Blob([ve(r,t)])):r}async loadAltChunk(e,t){return await this.loadResource(t??this.documentPart,e,"string")}blobToURL(e){return e?this._options.useBase64URL?function(e){return new Promise((t,r)=>{const a=new FileReader;a.onloadend=()=>t(a.result),a.onerror=()=>r(),a.readAsDataURL(e)})}(e):URL.createObjectURL(e):null}findPartByRelId(e,t=null){var r=(t.rels??this.rels).find(t=>t.id==e);const a=t?s(t.path)[0]:"";return r?this.partsMap[n(r.target,a)]:null}getPathById(e,t){const r=e.rels.find(e=>e.id==t),[a]=s(e.path);return r?n(r.target,a):null}loadResource(e,t,r){const a=this.getPathById(e,t);return a?this._package.load(a,r):Promise.resolve(null)}}function ve(e,t){const r=t.replace(/{|}|-/g,""),a=new Array(16);for(let e=0;e<16;e++)a[16-e-1]=parseInt(r.substring(2*e,2*e+2),16);for(let t=0;t<32;t++)e[t]=e[t]^a[t%16];return e}function Se(e,t){return{type:R.BookmarkStart,id:t.attr(e,"id"),name:t.attr(e,"name"),colFirst:t.intAttr(e,"colFirst"),colLast:t.intAttr(e,"colLast")}}function Pe(e,t){return{type:R.BookmarkEnd,id:t.attr(e,"id")}}class we extends U{constructor(){super(...arguments),this.type=R.VmlElement,this.attrs={}}}function Ce(e,t){var r=new we;switch(e.localName){case"rect":r.tagName="rect",Object.assign(r.attrs,{width:"100%",height:"100%"});break;case"oval":r.tagName="ellipse",Object.assign(r.attrs,{cx:"50%",cy:"50%",rx:"50%",ry:"50%"});break;case"line":r.tagName="line";break;case"shape":r.tagName="g";break;case"textbox":r.tagName="foreignObject",Object.assign(r.attrs,{width:"100%",height:"100%"});break;default:return null}for(const t of v.attrs(e))switch(t.localName){case"style":r.cssStyleText=t.value;break;case"fillcolor":r.attrs.fill=t.value;break;case"from":const[e,a]=Me(t.value);Object.assign(r.attrs,{x1:e,y1:a});break;case"to":const[s,n]=Me(t.value);Object.assign(r.attrs,{x2:s,y2:n})}for(const a of v.elements(e))switch(a.localName){case"stroke":Object.assign(r.attrs,xe(a));break;case"fill":Object.assign(r.attrs,Ne());break;case"imagedata":r.tagName="image",Object.assign(r.attrs,{width:"100%",height:"100%"}),r.imageHref={id:v.attr(a,"id"),title:v.attr(a,"title")};break;case"txbxContent":r.children.push(...t.parseBodyElements(a));break;default:const e=Ce(a,t);e&&r.children.push(e)}return r}function xe(e){return{stroke:v.attr(e,"color"),"stroke-width":v.lengthAttr(e,"weight",p)??"1px"}}function Ne(e){return{}}function Me(e){return e.split(",")}class Ae extends U{constructor(){super(...arguments),this.type=R.Comment}}class Ee extends U{constructor(e){super(),this.id=e,this.type=R.CommentReference}}class Te extends U{constructor(e){super(),this.id=e,this.type=R.CommentRangeStart}}class Re extends U{constructor(e){super(),this.id=e,this.type=R.CommentRangeEnd}}var Be="inherit",De="black",$e="black",Fe="transparent";const Le=[],Ie={oMath:R.MmlMath,oMathPara:R.MmlMathParagraph,f:R.MmlFraction,func:R.MmlFunction,fName:R.MmlFunctionName,num:R.MmlNumerator,den:R.MmlDenominator,rad:R.MmlRadical,deg:R.MmlDegree,e:R.MmlBase,sSup:R.MmlSuperscript,sSub:R.MmlSubscript,sPre:R.MmlPreSubSuper,sup:R.MmlSuperArgument,sub:R.MmlSubArgument,d:R.MmlDelimiter,nary:R.MmlNary,eqArr:R.MmlEquationArray,lim:R.MmlLimit,limLow:R.MmlLimitLower,m:R.MmlMatrix,mr:R.MmlMatrixRow,box:R.MmlBox,bar:R.MmlBar,groupChr:R.MmlGroupChar};class Oe{constructor(e){this.options={ignoreWidth:!1,debug:!1,...e}}parseNotes(e,t,r){var a=[];for(let s of v.elements(e,t)){const e=new r;e.id=v.attr(s,"id"),e.noteType=v.attr(s,"type"),e.children=this.parseBodyElements(s),a.push(e)}return a}parseComments(e){var t=[];for(let r of v.elements(e,"comment")){const e=new Ae;e.id=v.attr(r,"id"),e.author=v.attr(r,"author"),e.initials=v.attr(r,"initials"),e.date=v.attr(r,"date"),e.children=this.parseBodyElements(r),t.push(e)}return t}parseDocumentFile(e){var t=v.element(e,"body"),r=v.element(e,"background"),a=v.element(t,"sectPr");return{type:R.Document,children:this.parseBodyElements(t),props:a?B(a,v):{},cssStyle:r?this.parseBackground(r):{}}}parseBackground(e){var t={},r=_e.colorAttr(e,"color");return r&&(t["background-color"]=r),t}parseBodyElements(e){var t=[];for(const r of v.elements(e))switch(r.localName){case"p":t.push(this.parseParagraph(r));break;case"altChunk":t.push(this.parseAltChunk(r));break;case"tbl":t.push(this.parseTable(r));break;case"sdt":t.push(...this.parseSdt(r,e=>this.parseBodyElements(e)))}return t}parseStylesFile(e){var t=[];for(const r of v.elements(e))switch(r.localName){case"style":t.push(this.parseStyle(r));break;case"docDefaults":t.push(this.parseDefaultStyles(r))}return t}parseDefaultStyles(e){var t={id:null,name:null,target:null,basedOn:null,styles:[]};for(const s of v.elements(e))switch(s.localName){case"rPrDefault":var r=v.element(s,"rPr");r&&t.styles.push({target:"span",values:this.parseDefaultProperties(r,{})});break;case"pPrDefault":var a=v.element(s,"pPr");a&&t.styles.push({target:"p",values:this.parseDefaultProperties(a,{})})}return t}parseStyle(e){var t={id:v.attr(e,"styleId"),isDefault:v.boolAttr(e,"default"),name:null,target:null,basedOn:null,styles:[],linked:null};switch(v.attr(e,"type")){case"paragraph":t.target="p";break;case"table":t.target="table";break;case"character":t.target="span"}for(const r of v.elements(e))switch(r.localName){case"basedOn":t.basedOn=v.attr(r,"val");break;case"name":t.name=v.attr(r,"val");break;case"link":t.linked=v.attr(r,"val");break;case"next":t.next=v.attr(r,"val");break;case"aliases":t.aliases=v.attr(r,"val").split(",");break;case"pPr":t.styles.push({target:"p",values:this.parseDefaultProperties(r,{})}),t.paragraphProps=O(r,v);break;case"rPr":t.styles.push({target:"span",values:this.parseDefaultProperties(r,{})}),t.runProps=L(r,v);break;case"tblPr":case"tcPr":t.styles.push({target:"td",values:this.parseDefaultProperties(r,{})});break;case"tblStylePr":for(let e of this.parseTableStyle(r))t.styles.push(e);break;case"rsid":case"qFormat":case"hidden":case"semiHidden":case"unhideWhenUsed":case"autoRedefine":case"uiPriority":break;default:this.options.debug&&console.warn(`DOCX: Unknown style element: ${r.localName}`)}return t}parseTableStyle(e){var t=[],r="",a="";switch(v.attr(e,"type")){case"firstRow":a=".first-row",r="tr.first-row td";break;case"lastRow":a=".last-row",r="tr.last-row td";break;case"firstCol":a=".first-col",r="td.first-col";break;case"lastCol":a=".last-col",r="td.last-col";break;case"band1Vert":a=":not(.no-vband)",r="td.odd-col";break;case"band2Vert":a=":not(.no-vband)",r="td.even-col";break;case"band1Horz":a=":not(.no-hband)",r="tr.odd-row";break;case"band2Horz":a=":not(.no-hband)",r="tr.even-row";break;default:return[]}for(const s of v.elements(e))switch(s.localName){case"pPr":t.push({target:`${r} p`,mod:a,values:this.parseDefaultProperties(s,{})});break;case"rPr":t.push({target:`${r} span`,mod:a,values:this.parseDefaultProperties(s,{})});break;case"tblPr":case"tcPr":t.push({target:r,mod:a,values:this.parseDefaultProperties(s,{})})}return t}parseNumberingFile(e){var t=[],r={},a=[];for(const l of v.elements(e))switch(l.localName){case"abstractNum":this.parseAbstractNumbering(l,a).forEach(e=>t.push(e));break;case"numPicBullet":a.push(this.parseNumberingPicBullet(l));break;case"num":var s=v.attr(l,"numId"),n=v.elementAttr(l,"abstractNumId","val");r[n]=s}return t.forEach(e=>e.id=r[e.id]),t}parseNumberingPicBullet(e){var t=v.element(e,"pict"),r=t&&v.element(t,"shape"),a=r&&v.element(r,"imagedata");return a?{id:v.intAttr(e,"numPicBulletId"),src:v.attr(a,"id"),style:v.attr(r,"style")}:null}parseAbstractNumbering(e,t){var r=[],a=v.attr(e,"abstractNumId");for(const s of v.elements(e))if("lvl"===s.localName)r.push(this.parseNumberingLevel(a,s,t));return r}parseNumberingLevel(e,t,r){var a={id:e,level:v.intAttr(t,"ilvl"),start:1,pStyleName:void 0,pStyle:{},rStyle:{},suff:"tab"};for(const e of v.elements(t))switch(e.localName){case"start":a.start=v.intAttr(e,"val");break;case"pPr":this.parseDefaultProperties(e,a.pStyle);break;case"rPr":this.parseDefaultProperties(e,a.rStyle);break;case"lvlPicBulletId":var s=v.intAttr(e,"val");a.bullet=r.find(e=>e?.id==s);break;case"lvlText":a.levelText=v.attr(e,"val");break;case"pStyle":a.pStyleName=v.attr(e,"val");break;case"numFmt":a.format=v.attr(e,"val");break;case"suff":a.suff=v.attr(e,"val")}return a}parseSdt(e,t){const r=v.element(e,"sdtContent");return r?t(r):[]}parseInserted(e,t){return{type:R.Inserted,children:t(e)?.children??[]}}parseDeleted(e,t){return{type:R.Deleted,children:t(e)?.children??[]}}parseAltChunk(e){return{type:R.AltChunk,children:[],id:v.attr(e,"id")}}parseParagraph(e){var t={type:R.Paragraph,children:[]};for(let r of v.elements(e))switch(r.localName){case"pPr":this.parseParagraphProperties(r,t);break;case"r":t.children.push(this.parseRun(r,t));break;case"hyperlink":t.children.push(this.parseHyperlink(r,t));break;case"smartTag":t.children.push(this.parseSmartTag(r,t));break;case"bookmarkStart":t.children.push(Se(r,v));break;case"bookmarkEnd":t.children.push(Pe(r,v));break;case"commentRangeStart":t.children.push(new Te(v.attr(r,"id")));break;case"commentRangeEnd":t.children.push(new Re(v.attr(r,"id")));break;case"oMath":case"oMathPara":t.children.push(this.parseMathElement(r));break;case"sdt":t.children.push(...this.parseSdt(r,e=>this.parseParagraph(e).children));break;case"ins":t.children.push(this.parseInserted(r,e=>this.parseParagraph(e)));break;case"del":t.children.push(this.parseDeleted(r,e=>this.parseParagraph(e)))}return t}parseParagraphProperties(e,t){this.parseDefaultProperties(e,t.cssStyle={},null,e=>{if(H(e,t,v))return!0;switch(e.localName){case"pStyle":t.styleName=v.attr(e,"val");break;case"cnfStyle":t.className=ze.classNameOfCnfStyle(e);break;case"framePr":this.parseFrame(e,t);break;case"rPr":break;default:return!1}return!0})}parseFrame(e,t){"drop"==v.attr(e,"dropCap")&&(t.cssStyle.float="left")}parseHyperlink(e,t){var r={type:R.Hyperlink,parent:t,children:[]};r.anchor=v.attr(e,"anchor"),r.id=v.attr(e,"id");for(const t of v.elements(e))if("r"===t.localName)r.children.push(this.parseRun(t,r));return r}parseSmartTag(e,t){var r={type:R.SmartTag,parent:t,children:[]},a=v.attr(e,"uri"),s=v.attr(e,"element");a&&(r.uri=a),s&&(r.element=s);for(const t of v.elements(e))if("r"===t.localName)r.children.push(this.parseRun(t,r));return r}parseRun(e,t){var r={type:R.Run,parent:t,children:[]};for(let t of v.elements(e))switch(t=this.checkAlternateContent(t),t.localName){case"t":r.children.push({type:R.Text,text:t.textContent});break;case"delText":r.children.push({type:R.DeletedText,text:t.textContent});break;case"commentReference":r.children.push(new Ee(v.attr(t,"id")));break;case"fldSimple":r.children.push({type:R.SimpleField,instruction:v.attr(t,"instr"),lock:v.boolAttr(t,"lock",!1),dirty:v.boolAttr(t,"dirty",!1)});break;case"instrText":r.fieldRun=!0,r.children.push({type:R.Instruction,text:t.textContent});break;case"fldChar":r.fieldRun=!0,r.children.push({type:R.ComplexField,charType:v.attr(t,"fldCharType"),lock:v.boolAttr(t,"lock",!1),dirty:v.boolAttr(t,"dirty",!1)});break;case"noBreakHyphen":r.children.push({type:R.NoBreakHyphen});break;case"br":r.children.push({type:R.Break,break:v.attr(t,"type")||"textWrapping"});break;case"lastRenderedPageBreak":r.children.push({type:R.Break,break:"lastRenderedPageBreak"});break;case"sym":r.children.push({type:R.Symbol,font:a(v.attr(t,"font")),char:v.attr(t,"char")});break;case"tab":r.children.push({type:R.Tab});break;case"footnoteReference":r.children.push({type:R.FootnoteReference,id:v.attr(t,"id")});break;case"endnoteReference":r.children.push({type:R.EndnoteReference,id:v.attr(t,"id")});break;case"drawing":let e=this.parseDrawing(t);e&&(r.children=[e]);break;case"pict":r.children.push(this.parseVmlPicture(t));break;case"rPr":this.parseRunProperties(t,r)}return r}parseMathElement(e){const t=`${e.localName}Pr`,r={type:Ie[e.localName],children:[]};for(const s of v.elements(e)){if(Ie[s.localName])r.children.push(this.parseMathElement(s));else if("r"==s.localName){var a=this.parseRun(s);a.type=R.MmlRun,r.children.push(a)}else s.localName==t&&(r.props=this.parseMathProperies(s))}return r}parseMathProperies(e){const t={};for(const r of v.elements(e))switch(r.localName){case"chr":t.char=v.attr(r,"val");break;case"vertJc":t.verticalJustification=v.attr(r,"val");break;case"pos":t.position=v.attr(r,"val");break;case"degHide":t.hideDegree=v.boolAttr(r,"val");break;case"begChr":t.beginChar=v.attr(r,"val");break;case"endChr":t.endChar=v.attr(r,"val")}return t}parseRunProperties(e,t){this.parseDefaultProperties(e,t.cssStyle={},null,e=>{switch(e.localName){case"rStyle":t.styleName=v.attr(e,"val");break;case"vertAlign":t.verticalAlign=ze.valueOfVertAlign(e,!0);break;default:return!1}return!0})}parseVmlPicture(e){const t={type:R.VmlPicture,children:[]};for(const r of v.elements(e)){const e=Ce(r,this);e&&t.children.push(e)}return t}checkAlternateContent(e){if("AlternateContent"!=e.localName)return e;var t=v.element(e,"Choice");if(t){var r=v.attr(t,"Requires"),a=e.lookupNamespaceURI(r);if(Le.includes(a))return t.firstElementChild}return v.element(e,"Fallback")?.firstElementChild}parseDrawing(e){for(var t of v.elements(e))switch(t.localName){case"inline":case"anchor":return this.parseDrawingWrapper(t)}}parseDrawingWrapper(e){var t={type:R.Drawing,children:[],cssStyle:{}},r="anchor"==e.localName;let a=null,s=v.boolAttr(e,"simplePos");v.boolAttr(e,"behindDoc");let n={relative:"page",align:"left",offset:"0"},l={relative:"page",align:"top",offset:"0"};for(var o of v.elements(e))switch(o.localName){case"simplePos":s&&(n.offset=v.lengthAttr(o,"x",p),l.offset=v.lengthAttr(o,"y",p));break;case"extent":t.cssStyle.width=v.lengthAttr(o,"cx",p),t.cssStyle.height=v.lengthAttr(o,"cy",p);break;case"positionH":case"positionV":if(!s){let e="positionH"==o.localName?n:l;var i=v.element(o,"align"),c=v.element(o,"posOffset");e.relative=v.attr(o,"relativeFrom")??e.relative,i&&(e.align=i.textContent),c&&(e.offset=b(c.textContent,p))}break;case"wrapTopAndBottom":a="wrapTopAndBottom";break;case"wrapNone":a="wrapNone";break;case"graphic":var h=this.parseGraphic(o);h&&t.children.push(h)}return"wrapTopAndBottom"==a?(t.cssStyle.display="block",n.align&&(t.cssStyle["text-align"]=n.align,t.cssStyle.width="100%")):"wrapNone"==a?(t.cssStyle.display="block",t.cssStyle.position="relative",t.cssStyle.width="0px",t.cssStyle.height="0px",n.offset&&(t.cssStyle.left=n.offset),l.offset&&(t.cssStyle.top=l.offset)):!r||"left"!=n.align&&"right"!=n.align||(t.cssStyle.float=n.align),t}parseGraphic(e){var t=v.element(e,"graphicData");for(let e of v.elements(t))if("pic"===e.localName)return this.parsePicture(e);return null}parsePicture(e){var t={type:R.Image,src:"",cssStyle:{}},r=v.element(e,"blipFill"),a=v.element(r,"blip"),s=v.element(r,"srcRect");t.src=v.attr(a,"embed"),s&&(t.srcRect=[v.intAttr(s,"l",0)/1e5,v.intAttr(s,"t",0)/1e5,v.intAttr(s,"r",0)/1e5,v.intAttr(s,"b",0)/1e5]);var n=v.element(e,"spPr"),l=v.element(n,"xfrm");if(t.cssStyle.position="relative",l)for(var o of(t.rotation=v.intAttr(l,"rot",0)/6e4,v.elements(l)))switch(o.localName){case"ext":t.cssStyle.width=v.lengthAttr(o,"cx",p),t.cssStyle.height=v.lengthAttr(o,"cy",p);break;case"off":t.cssStyle.left=v.lengthAttr(o,"x",p),t.cssStyle.top=v.lengthAttr(o,"y",p)}return t}parseTable(e){var t={type:R.Table,children:[]};for(const r of v.elements(e))switch(r.localName){case"tr":t.children.push(this.parseTableRow(r));break;case"tblGrid":t.columns=this.parseTableColumns(r);break;case"tblPr":this.parseTableProperties(r,t)}return t}parseTableColumns(e){var t=[];for(const r of v.elements(e))if("gridCol"===r.localName)t.push({width:v.lengthAttr(r,"w")});return t}parseTableProperties(e,t){switch(t.cssStyle={},t.cellStyle={},this.parseDefaultProperties(e,t.cssStyle,t.cellStyle,e=>{switch(e.localName){case"tblStyle":t.styleName=v.attr(e,"val");break;case"tblLook":t.className=ze.classNameOftblLook(e);break;case"tblpPr":this.parseTablePosition(e,t);break;case"tblStyleColBandSize":t.colBandSize=v.intAttr(e,"val");break;case"tblStyleRowBandSize":t.rowBandSize=v.intAttr(e,"val");break;case"hidden":t.cssStyle.display="none";break;default:return!1}return!0}),t.cssStyle["text-align"]){case"center":delete t.cssStyle["text-align"],t.cssStyle["margin-left"]="auto",t.cssStyle["margin-right"]="auto";break;case"right":delete t.cssStyle["text-align"],t.cssStyle["margin-left"]="auto"}}parseTablePosition(e,t){var r=v.lengthAttr(e,"topFromText"),a=v.lengthAttr(e,"bottomFromText"),s=v.lengthAttr(e,"rightFromText"),n=v.lengthAttr(e,"leftFromText");t.cssStyle.float="left",t.cssStyle["margin-bottom"]=ze.addSize(t.cssStyle["margin-bottom"],a),t.cssStyle["margin-left"]=ze.addSize(t.cssStyle["margin-left"],n),t.cssStyle["margin-right"]=ze.addSize(t.cssStyle["margin-right"],s),t.cssStyle["margin-top"]=ze.addSize(t.cssStyle["margin-top"],r)}parseTableRow(e){var t={type:R.Row,children:[]};for(const r of v.elements(e))switch(r.localName){case"tc":t.children.push(this.parseTableCell(r));break;case"trPr":case"tblPrEx":this.parseTableRowProperties(r,t)}return t}parseTableRowProperties(e,t){t.cssStyle=this.parseDefaultProperties(e,{},null,e=>{switch(e.localName){case"cnfStyle":t.className=ze.classNameOfCnfStyle(e);break;case"tblHeader":t.isHeader=v.boolAttr(e,"val");break;case"gridBefore":t.gridBefore=v.intAttr(e,"val");break;case"gridAfter":t.gridAfter=v.intAttr(e,"val");break;default:return!1}return!0})}parseTableCell(e){var t={type:R.Cell,children:[]};for(const r of v.elements(e))switch(r.localName){case"tbl":t.children.push(this.parseTable(r));break;case"p":t.children.push(this.parseParagraph(r));break;case"tcPr":this.parseTableCellProperties(r,t)}return t}parseTableCellProperties(e,t){t.cssStyle=this.parseDefaultProperties(e,{},null,e=>{switch(e.localName){case"gridSpan":t.span=v.intAttr(e,"val",null);break;case"vMerge":t.verticalMerge=v.attr(e,"val")??"continue";break;case"cnfStyle":t.className=ze.classNameOfCnfStyle(e);break;default:return!1}return!0}),this.parseTableCellVerticalText(e,t)}parseTableCellVerticalText(e,t){const r={btLr:{writingMode:"vertical-rl",transform:"rotate(180deg)"},lrTb:{writingMode:"vertical-lr",transform:"none"},tbRl:{writingMode:"vertical-rl",transform:"none"}};for(const a of v.elements(e))if("textDirection"===a.localName){const e=r[v.attr(a,"val")]||{writingMode:"horizontal-tb"};t.cssStyle["writing-mode"]=e.writingMode,t.cssStyle.transform=e.transform}}parseDefaultProperties(e,t=null,r=null,a=null){t=t||{};for(const s of v.elements(e))if(!a?.(s))switch(s.localName){case"jc":t["text-align"]=ze.valueOfJc(s);break;case"textAlignment":t["vertical-align"]=ze.valueOfTextAlignment(s);break;case"color":t.color=_e.colorAttr(s,"val",null,De);break;case"sz":t["font-size"]=t["min-height"]=v.lengthAttr(s,"val",u);break;case"shd":t["background-color"]=_e.colorAttr(s,"fill",null,Be);break;case"highlight":t["background-color"]=_e.colorAttr(s,"val",null,Fe);break;case"vertAlign":break;case"position":t.verticalAlign=v.lengthAttr(s,"val",u);break;case"tcW":if(this.options.ignoreWidth)break;case"tblW":t.width=ze.valueOfSize(s,"w");break;case"trHeight":this.parseTrHeight(s,t);break;case"strike":t["text-decoration"]=v.boolAttr(s,"val",!0)?"line-through":"none";break;case"b":t["font-weight"]=v.boolAttr(s,"val",!0)?"bold":"normal";break;case"i":t["font-style"]=v.boolAttr(s,"val",!0)?"italic":"normal";break;case"caps":t["text-transform"]=v.boolAttr(s,"val",!0)?"uppercase":"none";break;case"smallCaps":t["font-variant"]=v.boolAttr(s,"val",!0)?"small-caps":"none";break;case"u":this.parseUnderline(s,t);break;case"ind":case"tblInd":this.parseIndentation(s,t);break;case"rFonts":this.parseFont(s,t);break;case"tblBorders":this.parseBorderProperties(s,r||t);break;case"tblCellSpacing":t["border-spacing"]=ze.valueOfMargin(s),t["border-collapse"]="separate";break;case"pBdr":this.parseBorderProperties(s,t);break;case"bdr":t.border=ze.valueOfBorder(s);break;case"tcBorders":this.parseBorderProperties(s,t);break;case"vanish":v.boolAttr(s,"val",!0)&&(t.display="none");break;case"kern":case"noWrap":break;case"tblCellMar":case"tcMar":this.parseMarginProperties(s,r||t);break;case"tblLayout":t["table-layout"]=ze.valueOfTblLayout(s);break;case"vAlign":t["vertical-align"]=ze.valueOfTextAlignment(s);break;case"spacing":"pPr"==e.localName&&this.parseSpacing(s,t);break;case"wordWrap":v.boolAttr(s,"val")&&(t["overflow-wrap"]="break-word");break;case"suppressAutoHyphens":t.hyphens=v.boolAttr(s,"val",!0)?"none":"auto";break;case"lang":t.$lang=v.attr(s,"val");break;case"rtl":case"bidi":v.boolAttr(s,"val",!0)&&(t.direction="rtl");break;case"bCs":case"iCs":case"szCs":case"tabs":case"outlineLvl":case"contextualSpacing":case"tblStyleColBandSize":case"tblStyleRowBandSize":case"webHidden":case"pageBreakBefore":case"suppressLineNumbers":case"keepLines":case"keepNext":case"widowControl":case"bidi":case"rtl":case"noProof":break;default:this.options.debug&&console.warn(`DOCX: Unknown document element: ${e.localName}.${s.localName}`)}return t}parseUnderline(e,t){var r=v.attr(e,"val");if(null!=r){switch(r){case"dash":case"dashDotDotHeavy":case"dashDotHeavy":case"dashedHeavy":case"dashLong":case"dashLongHeavy":case"dotDash":case"dotDotDash":t["text-decoration"]="underline dashed";break;case"dotted":case"dottedHeavy":t["text-decoration"]="underline dotted";break;case"double":t["text-decoration"]="underline double";break;case"single":case"thick":case"words":t["text-decoration"]="underline";break;case"wave":case"wavyDouble":case"wavyHeavy":t["text-decoration"]="underline wavy";break;case"none":t["text-decoration"]="none"}var a=_e.colorAttr(e,"color");a&&(t["text-decoration-color"]=a)}}parseFont(e,t){var r=[v.attr(e,"ascii"),ze.themeValue(e,"asciiTheme"),v.attr(e,"eastAsia")].filter(e=>e).map(e=>a(e));r.length>0&&(t["font-family"]=[...new Set(r)].join(", "))}parseIndentation(e,t){var r=v.lengthAttr(e,"firstLine"),a=v.lengthAttr(e,"hanging"),s=v.lengthAttr(e,"left"),n=v.lengthAttr(e,"start"),l=v.lengthAttr(e,"right"),o=v.lengthAttr(e,"end");r&&(t["text-indent"]=r),a&&(t["text-indent"]=`-${a}`),(s||n)&&(t["margin-inline-start"]=s||n),(l||o)&&(t["margin-inline-end"]=l||o)}parseSpacing(e,t){var r=v.lengthAttr(e,"before"),a=v.lengthAttr(e,"after"),s=v.intAttr(e,"line",null),n=v.attr(e,"lineRule");if(r&&(t["margin-top"]=r),a&&(t["margin-bottom"]=a),null!==s)switch(n){case"auto":t["line-height"]=`${(s/240).toFixed(2)}`;break;case"atLeast":t["line-height"]=`calc(100% + ${s/20}pt)`;break;default:t["line-height"]=t["min-height"]=s/20+"pt"}}parseMarginProperties(e,t){for(const r of v.elements(e))switch(r.localName){case"left":t["padding-left"]=ze.valueOfMargin(r);break;case"right":t["padding-right"]=ze.valueOfMargin(r);break;case"top":t["padding-top"]=ze.valueOfMargin(r);break;case"bottom":t["padding-bottom"]=ze.valueOfMargin(r)}}parseTrHeight(e,t){v.attr(e,"hRule"),t.height=v.lengthAttr(e,"val")}parseBorderProperties(e,t){for(const r of v.elements(e))switch(r.localName){case"start":case"left":t["border-left"]=ze.valueOfBorder(r);break;case"end":case"right":t["border-right"]=ze.valueOfBorder(r);break;case"top":t["border-top"]=ze.valueOfBorder(r);break;case"bottom":t["border-bottom"]=ze.valueOfBorder(r)}}}const He=["black","blue","cyan","darkBlue","darkCyan","darkGray","darkGreen","darkMagenta","darkRed","darkYellow","green","lightGray","magenta","none","red","white","yellow"];class _e{static colorAttr(e,t,r=null,a="black"){var s=v.attr(e,t);if(s)return"auto"==s?a:He.includes(s)?s:`#${s}`;var n=v.attr(e,"themeColor");return n?`var(--docx-${n}-color)`:r}}class ze{static themeValue(e,t){var r=v.attr(e,t);return r?`var(--docx-${r}-font)`:null}static valueOfSize(e,t){var r=m;switch(v.attr(e,"type")){case"dxa":break;case"pct":r=g;break;case"auto":return"auto"}return v.lengthAttr(e,t,r)}static valueOfMargin(e){return v.lengthAttr(e,"w")}static valueOfBorder(e){var t=ze.parseBorderType(v.attr(e,"val"));if("none"==t)return"none";var r=_e.colorAttr(e,"color");return`${v.lengthAttr(e,"sz",d)} ${t} ${"auto"==r?$e:r}`}static parseBorderType(e){switch(e){case"single":case"dashDotStroked":case"thick":case"thickThinLargeGap":case"thickThinMediumGap":case"thickThinSmallGap":case"thinThickLargeGap":case"thinThickMediumGap":case"thinThickSmallGap":case"thinThickThinLargeGap":case"thinThickThinMediumGap":case"thinThickThinSmallGap":case"threeDEmboss":case"threeDEngrave":case"wave":return"solid";case"dashed":case"dashSmallGap":return"dashed";case"dotDash":case"dotDotDash":case"dotted":return"dotted";case"double":case"doubleWave":case"triple":return"double";case"inset":return"inset";case"nil":case"none":return"none";case"outset":return"outset"}return"solid"}static valueOfTblLayout(e){return"fixed"==v.attr(e,"val")?"fixed":"auto"}static classNameOfCnfStyle(e){const t=v.attr(e,"val");return["first-row","last-row","first-col","last-col","odd-col","even-col","odd-row","even-row","ne-cell","nw-cell","se-cell","sw-cell"].filter((e,r)=>"1"==t[r]).join(" ")}static valueOfJc(e){var t=v.attr(e,"val");switch(t){case"start":case"left":return"left";case"center":return"center";case"end":case"right":return"right";case"both":return"justify"}return t}static valueOfVertAlign(e,t=!1){var r=v.attr(e,"val");switch(r){case"subscript":return"sub";case"superscript":return t?"sup":"super"}return t?null:r}static valueOfTextAlignment(e){var t=v.attr(e,"val");switch(t){case"auto":case"baseline":return"baseline";case"top":return"top";case"center":return"middle";case"bottom":return"bottom"}return t}static addSize(e,t){return null==e?t:null==t?e:`calc(${e} + ${t})`}static classNameOftblLook(e){const t=v.hexAttr(e,"val",0);let r="";return(v.boolAttr(e,"firstRow")||32&t)&&(r+=" first-row"),(v.boolAttr(e,"lastRow")||64&t)&&(r+=" last-row"),(v.boolAttr(e,"firstColumn")||128&t)&&(r+=" first-col"),(v.boolAttr(e,"lastColumn")||256&t)&&(r+=" last-col"),(v.boolAttr(e,"noHBand")||512&t)&&(r+=" no-hband"),(v.boolAttr(e,"noVBand")||1024&t)&&(r+=" no-vband"),r.trim()}}const Ve={pos:0,leader:"none",style:"left"};function je(e,t,r,a=.75){const s=e.closest("p"),n=e.getBoundingClientRect(),l=s.getBoundingClientRect(),o=getComputedStyle(s),i=t?.length>0?t.map(e=>({pos:We(e.position),leader:e.leader,style:e.style})).sort((e,t)=>e.pos-t.pos):[Ve],c=i[i.length-1],h=l.width*a,m=We(r);let p=c.pos+m;if(p<h)for(;p<h&&i.length<50;p+=m)i.push({...Ve,pos:p});const u=parseFloat(o.marginLeft),d=l.left+u,f=(n.left-d)*a,g=i.find(e=>"clear"!=e.style&&e.pos>f);if(null==g)return;let b=1;if("right"==g.style||"center"==g.style){const t=Array.from(s.querySelectorAll(`.${e.className}`)),r=t.indexOf(e)+1,n=document.createRange();n.setStart(e,1),r<t.length?n.setEndBefore(t[r]):n.setEndAfter(s);const o="center"==g.style?.5:1,i=n.getBoundingClientRect(),c=i.left+o*i.width-(l.left-u);b=g.pos-c*a}else b=g.pos-f;switch(e.innerHTML=" ",e.style.textDecoration="inherit",e.style.wordSpacing=`${b.toFixed(0)}pt`,g.leader){case"dot":case"middleDot":e.style.textDecoration="underline",e.style.textDecorationStyle="dotted";break;case"hyphen":case"heavy":case"underscore":e.style.textDecoration="underline"}}function We(e){return parseFloat(e)}const Xe="http://www.w3.org/2000/svg",Ge="http://www.w3.org/1998/Math/MathML";class Ue{constructor(e){this.htmlDocument=e,this.className="docx",this.styleMap={},this.currentPart=null,this.tableVerticalMerges=[],this.currentVerticalMerge=null,this.tableCellPositions=[],this.currentCellPosition=null,this.footnoteMap={},this.endnoteMap={},this.currentEndnoteIds=[],this.usedHederFooterParts=[],this.currentTabs=[],this.commentMap={},this.tasks=[],this.postRenderTasks=[]}async render(e,t,r=null,a){this.document=e,this.options=a,this.className=a.className,this.rootSelector=a.inWrapper?`.${this.className}-wrapper`:":root",this.styleMap=null,this.tasks=[],this.options.renderComments&&globalThis.Highlight&&(this.commentHighlight=new Highlight),qe(r=r||t),qe(t),r.appendChild(this.createComment("docxjs library predefined styles")),r.appendChild(this.renderDefaultStyle()),e.themePart&&(r.appendChild(this.createComment("docxjs document theme values")),this.renderTheme(e.themePart,r)),null!=e.stylesPart&&(this.styleMap=this.processStyles(e.stylesPart.styles),r.appendChild(this.createComment("docxjs document styles")),r.appendChild(this.renderStyles(e.stylesPart.styles))),e.numberingPart&&(this.prodessNumberings(e.numberingPart.domNumberings),r.appendChild(this.createComment("docxjs document numbering styles")),r.appendChild(this.renderNumbering(e.numberingPart.domNumberings,r))),e.footnotesPart&&(this.footnoteMap=l(e.footnotesPart.notes,e=>e.id)),e.endnotesPart&&(this.endnoteMap=l(e.endnotesPart.notes,e=>e.id)),e.settingsPart&&(this.defaultTabSize=e.settingsPart.settings?.defaultTabStop),!a.ignoreFonts&&e.fontTablePart&&this.renderFontTable(e.fontTablePart,r);var s=this.renderSections(e.documentPart.body);this.options.inWrapper?t.appendChild(this.renderWrapper(s)):Je(t,s),this.commentHighlight&&a.renderComments&&CSS.highlights.set(`${this.className}-comments`,this.commentHighlight),this.postRenderTasks.forEach(e=>e()),await Promise.allSettled(this.tasks),this.refreshTabStops()}renderTheme(e,t){const r={},a=e.theme?.fontScheme;a&&(a.majorFont&&(r["--docx-majorHAnsi-font"]=a.majorFont.latinTypeface),a.minorFont&&(r["--docx-minorHAnsi-font"]=a.minorFont.latinTypeface));const s=e.theme?.colorScheme;if(s)for(let[e,t]of Object.entries(s.colors))r[`--docx-${e}-color`]=`#${t}`;const n=this.styleToString(`.${this.className}`,r);t.appendChild(this.createStyleElement(n))}renderFontTable(e,t){for(let r of e.fonts)for(let e of r.embedFontRefs)this.tasks.push(this.document.loadFont(e.id,e.key).then(s=>{const n={"font-family":a(r.name),src:`url(${s})`};"bold"!=e.type&&"boldItalic"!=e.type||(n["font-weight"]="bold"),"italic"!=e.type&&"boldItalic"!=e.type||(n["font-style"]="italic");const l=this.styleToString("@font-face",n);t.appendChild(this.createComment(`docxjs ${r.name} font`)),t.appendChild(this.createStyleElement(l))}))}processStyleName(e){return e?`${this.className}_${function(e){return e?.replace(/[ .]+/g,"-").replace(/[&]+/g,"and").toLowerCase()}(e)}`:this.className}processStyles(e){const t=l(e.filter(e=>null!=e.id),e=>e.id);for(const a of e.filter(e=>e.basedOn)){var r=t[a.basedOn];if(r){a.paragraphProps=i(a.paragraphProps,r.paragraphProps),a.runProps=i(a.runProps,r.runProps);for(const e of r.styles){const t=a.styles.find(t=>t.target==e.target);t?this.copyStyleProperties(e.values,t.values):a.styles.push({...e,values:{...e.values}})}}else this.options.debug&&console.warn(`Can't find base style ${a.basedOn}`)}for(let t of e)t.cssName=this.processStyleName(t.id);return t}prodessNumberings(e){for(let t of e.filter(e=>e.pStyleName)){const e=this.findStyle(t.pStyleName);e?.paragraphProps?.numbering&&(e.paragraphProps.numbering.level=t.level)}}processElement(e){if(e.children)for(var t of e.children)t.parent=e,t.type==R.Table?this.processTable(t):this.processElement(t)}processTable(e){for(var t of e.children)for(var r of t.children)r.cssStyle=this.copyStyleProperties(e.cellStyle,r.cssStyle,["border-left","border-right","border-top","border-bottom","padding-left","padding-right","padding-top","padding-bottom"]),this.processElement(r)}copyStyleProperties(e,t,r=null){if(!e)return t;for(var a of(null==t&&(t={}),null==r&&(r=Object.getOwnPropertyNames(e)),r))e.hasOwnProperty(a)&&!t.hasOwnProperty(a)&&(t[a]=e[a]);return t}createPageElement(e,t){var r=this.createElement("section",{className:e});return t&&(t.pageMargins&&(r.style.paddingLeft=t.pageMargins.left,r.style.paddingRight=t.pageMargins.right,r.style.paddingTop=t.pageMargins.top,r.style.paddingBottom=t.pageMargins.bottom),t.pageSize&&(this.options.ignoreWidth||(r.style.width=t.pageSize.width),this.options.ignoreHeight||(r.style.minHeight=t.pageSize.height))),r}createSectionContent(e){var t=this.createElement("article");return e.columns&&e.columns.numberOfColumns&&(t.style.columnCount=`${e.columns.numberOfColumns}`,t.style.columnGap=e.columns.space,e.columns.separator&&(t.style.columnRule="1px solid black")),t}renderSections(e){const t=[];this.processElement(e);const r=this.splitBySection(e.children,e.props),a=this.groupByPageBreaks(r);let s=null;for(let r=0,l=a.length;r<l;r++){this.currentFootnoteIds=[];let o=a[r][0].sectProps;const i=this.createPageElement(this.className,o);this.renderStyleValues(e.cssStyle,i),this.options.renderHeaders&&this.renderHeaderFooter(o.headerRefs,o,t.length,s!=o,i);for(const e of a[r]){var n=this.createSectionContent(e.sectProps);this.renderElements(e.elements,n),i.appendChild(n),o=e.sectProps}this.options.renderFootnotes&&this.renderNotes(this.currentFootnoteIds,this.footnoteMap,i),this.options.renderEndnotes&&r==l-1&&this.renderNotes(this.currentEndnoteIds,this.endnoteMap,i),this.options.renderFooters&&this.renderHeaderFooter(o.footerRefs,o,t.length,s!=o,i),t.push(i),s=o}return t}renderHeaderFooter(e,t,r,a,s){if(e){var n=(t.titlePage&&a?e.find(e=>"first"==e.type):null)??(r%2==1?e.find(e=>"even"==e.type):null)??e.find(e=>"default"==e.type),l=n&&this.document.findPartByRelId(n.id,this.document.documentPart);if(l){this.currentPart=l,this.usedHederFooterParts.includes(l.path)||(this.processElement(l.rootElement),this.usedHederFooterParts.push(l.path));const[e]=this.renderElements([l.rootElement],s);t?.pageMargins&&(l.rootElement.type===R.Header?(e.style.marginTop=`calc(${t.pageMargins.header} - ${t.pageMargins.top})`,e.style.minHeight=`calc(${t.pageMargins.top} - ${t.pageMargins.header})`):l.rootElement.type===R.Footer&&(e.style.marginBottom=`calc(${t.pageMargins.footer} - ${t.pageMargins.bottom})`,e.style.minHeight=`calc(${t.pageMargins.bottom} - ${t.pageMargins.footer})`)),this.currentPart=null}}}isPageBreakElement(e){return e.type==R.Break&&("lastRenderedPageBreak"==e.break?!this.options.ignoreLastRenderedPageBreak:"page"==e.break)}isPageBreakSection(e,t){return!!e&&(!!t&&(e.pageSize?.orientation!=t.pageSize?.orientation||e.pageSize?.width!=t.pageSize?.width||e.pageSize?.height!=t.pageSize?.height))}splitBySection(e,t){var r={sectProps:null,elements:[],pageBreak:!1},a=[r];for(let t of e){if(t.type==R.Paragraph){const e=this.findStyle(t.styleName);e?.paragraphProps?.pageBreakBefore&&(r.sectProps=s,r.pageBreak=!0,r={sectProps:null,elements:[],pageBreak:!1},a.push(r))}if(r.elements.push(t),t.type==R.Paragraph){const e=t;var s=e.sectionProps,n=-1,l=-1;if(this.options.breakPages&&e.children&&(n=e.children.findIndex(e=>-1!=(l=e.children?.findIndex(this.isPageBreakElement.bind(this))??-1))),(s||-1!=n)&&(r.sectProps=s,r.pageBreak=-1!=n,r={sectProps:null,elements:[],pageBreak:!1},a.push(r)),-1!=n){let a=e.children[n],s=l<a.children.length-1;if(n<e.children.length-1||s){var o=t.children,i={...t,children:o.slice(n)};if(t.children=o.slice(0,n),r.elements.push(i),s){let e=a.children,r={...a,children:e.slice(0,l)};t.children.push(r),a.children=e.slice(l)}}}}}let c=null;for(let e=a.length-1;e>=0;e--)null==a[e].sectProps?a[e].sectProps=c??t:c=a[e].sectProps;return a}groupByPageBreaks(e){let t,r=[];const a=[r];for(let s of e)r.push(s),(this.options.ignoreLastRenderedPageBreak||s.pageBreak||this.isPageBreakSection(t,s.sectProps))&&a.push(r=[]),t=s.sectProps;return a.filter(e=>e.length>0)}renderWrapper(e){return this.createElement("div",{className:`${this.className}-wrapper`},e)}renderDefaultStyle(){var e=this.className,t=`\n.${e}-wrapper { background: gray; padding: 30px; padding-bottom: 0px; display: flex; flex-flow: column; align-items: center; } \n.${e}-wrapper>section.${e} { background: white; box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); margin-bottom: 30px; }`;this.options.hideWrapperOnPrint&&(t=`@media not print { ${t} }`);var r=`${t}\n.${e} { color: black; hyphens: auto; text-underline-position: from-font; }\nsection.${e} { box-sizing: border-box; display: flex; flex-flow: column nowrap; position: relative; overflow: hidden; }\nsection.${e}>article { margin-bottom: auto; z-index: 1; }\nsection.${e}>footer { z-index: 1; }\n.${e} table { border-collapse: collapse; }\n.${e} table td, .${e} table th { vertical-align: top; }\n.${e} p { margin: 0pt; min-height: 1em; }\n.${e} span { white-space: pre-wrap; overflow-wrap: break-word; }\n.${e} a { color: inherit; text-decoration: inherit; }\n.${e} svg { fill: transparent; }\n`;return this.options.renderComments&&(r+=`\n.${e}-comment-ref { cursor: default; }\n.${e}-comment-popover { display: none; z-index: 1000; padding: 0.5rem; background: white; position: absolute; box-shadow: 0 0 0.25rem rgba(0, 0, 0, 0.25); width: 30ch; }\n.${e}-comment-ref:hover~.${e}-comment-popover { display: block; }\n.${e}-comment-author,.${e}-comment-date { font-size: 0.875rem; color: #888; }\n`),this.createStyleElement(r)}renderNumbering(e,t){var r="",a=[];for(var s of e){var n=`p.${this.numberingClass(s.id,s.level)}`,l="none";if(s.bullet){let e=`--${this.className}-${s.bullet.src}`.toLowerCase();r+=this.styleToString(`${n}:before`,{content:"' '",display:"inline-block",background:`var(${e})`},s.bullet.style),this.tasks.push(this.document.loadNumberingImage(s.bullet.src).then(r=>{var a=`${this.rootSelector} { ${e}: url(${r}) }`;t.appendChild(this.createStyleElement(a))}))}else if(s.levelText){let e=this.numberingCounter(s.id,s.level);const t=e+" "+(s.start-1);s.level>0&&(r+=this.styleToString(`p.${this.numberingClass(s.id,s.level-1)}`,{"counter-set":t})),a.push(t),r+=this.styleToString(`${n}:before`,{content:this.levelTextToContent(s.levelText,s.suff,s.id,this.numFormatToCssValue(s.format)),"counter-increment":e,...s.rStyle})}else l=this.numFormatToCssValue(s.format);r+=this.styleToString(n,{display:"list-item","list-style-position":"inside","list-style-type":l,...s.pStyle})}return a.length>0&&(r+=this.styleToString(this.rootSelector,{"counter-reset":a.join(" ")})),this.createStyleElement(r)}renderStyles(e){var t="";const r=this.styleMap,a=l(e.filter(e=>e.isDefault),e=>e.target);for(const l of e){var s=l.styles;if(l.linked){var n=l.linked&&r[l.linked];n?s=s.concat(n.styles):this.options.debug&&console.warn(`Can't find linked style ${l.linked}`)}for(const e of s){var o=`${l.target??""}.${l.cssName}`;l.target!=e.target&&(o+=` ${e.target}`),a[l.target]==l&&(o=`.${this.className} ${l.target}, `+o),t+=this.styleToString(o,e.values)}}return this.createStyleElement(t)}renderNotes(e,t,r){var a=e.map(e=>t[e]).filter(e=>e);if(a.length>0){var s=this.createElement("ol",null,this.renderElements(a));r.appendChild(s)}}renderElement(e){switch(e.type){case R.Paragraph:return this.renderParagraph(e);case R.BookmarkStart:return this.renderBookmarkStart(e);case R.BookmarkEnd:return null;case R.Run:return this.renderRun(e);case R.Table:return this.renderTable(e);case R.Row:return this.renderTableRow(e);case R.Cell:return this.renderTableCell(e);case R.Hyperlink:return this.renderHyperlink(e);case R.SmartTag:return this.renderSmartTag(e);case R.Drawing:return this.renderDrawing(e);case R.Image:return this.renderImage(e);case R.Text:case R.Text:return this.renderText(e);case R.DeletedText:return this.renderDeletedText(e);case R.Tab:return this.renderTab(e);case R.Symbol:return this.renderSymbol(e);case R.Break:return this.renderBreak(e);case R.Footer:return this.renderContainer(e,"footer");case R.Header:return this.renderContainer(e,"header");case R.Footnote:case R.Endnote:return this.renderContainer(e,"li");case R.FootnoteReference:return this.renderFootnoteReference(e);case R.EndnoteReference:return this.renderEndnoteReference(e);case R.NoBreakHyphen:return this.createElement("wbr");case R.VmlPicture:return this.renderVmlPicture(e);case R.VmlElement:return this.renderVmlElement(e);case R.MmlMath:return this.renderContainerNS(e,Ge,"math",{xmlns:Ge});case R.MmlMathParagraph:return this.renderContainer(e,"span");case R.MmlFraction:return this.renderContainerNS(e,Ge,"mfrac");case R.MmlBase:return this.renderContainerNS(e,Ge,e.parent.type==R.MmlMatrixRow?"mtd":"mrow");case R.MmlNumerator:case R.MmlDenominator:case R.MmlFunction:case R.MmlLimit:case R.MmlBox:return this.renderContainerNS(e,Ge,"mrow");case R.MmlGroupChar:return this.renderMmlGroupChar(e);case R.MmlLimitLower:return this.renderContainerNS(e,Ge,"munder");case R.MmlMatrix:return this.renderContainerNS(e,Ge,"mtable");case R.MmlMatrixRow:return this.renderContainerNS(e,Ge,"mtr");case R.MmlRadical:return this.renderMmlRadical(e);case R.MmlSuperscript:return this.renderContainerNS(e,Ge,"msup");case R.MmlSubscript:return this.renderContainerNS(e,Ge,"msub");case R.MmlDegree:case R.MmlSuperArgument:case R.MmlSubArgument:return this.renderContainerNS(e,Ge,"mn");case R.MmlFunctionName:return this.renderContainerNS(e,Ge,"ms");case R.MmlDelimiter:return this.renderMmlDelimiter(e);case R.MmlRun:return this.renderMmlRun(e);case R.MmlNary:return this.renderMmlNary(e);case R.MmlPreSubSuper:return this.renderMmlPreSubSuper(e);case R.MmlBar:return this.renderMmlBar(e);case R.MmlEquationArray:return this.renderMllList(e);case R.Inserted:return this.renderInserted(e);case R.Deleted:return this.renderDeleted(e);case R.CommentRangeStart:return this.renderCommentRangeStart(e);case R.CommentRangeEnd:return this.renderCommentRangeEnd(e);case R.CommentReference:return this.renderCommentReference(e);case R.AltChunk:return this.renderAltChunk(e)}return null}renderElements(e,t){if(null==e)return null;var r=e.flatMap(e=>this.renderElement(e)).filter(e=>null!=e);return t&&Je(t,r),r}renderContainer(e,t,r){return this.createElement(t,r,this.renderElements(e.children))}renderContainerNS(e,t,r,a){return this.createElementNS(t,r,a,this.renderElements(e.children))}renderParagraph(e){var t=this.renderContainer(e,"p");const r=this.findStyle(e.styleName);e.tabs??(e.tabs=r?.paragraphProps?.tabs),this.renderClass(e,t),this.renderStyleValues(e.cssStyle,t),this.renderCommonProperties(t.style,e);const a=e.numbering??r?.paragraphProps?.numbering;return a&&t.classList.add(this.numberingClass(a.id,a.level)),t}renderRunProperties(e,t){this.renderCommonProperties(e,t)}renderCommonProperties(e,t){null!=t&&(t.color&&(e.color=t.color),t.fontSize&&(e["font-size"]=t.fontSize))}renderHyperlink(e){var t=this.renderContainer(e,"a");this.renderStyleValues(e.cssStyle,t);let r="";if(e.id){const t=this.document.documentPart.rels.find(t=>t.id==e.id&&"External"===t.targetMode);r=t?.target??r}return e.anchor&&(r+=`#${e.anchor}`),t.href=r,t}renderSmartTag(e){return this.renderContainer(e,"span")}renderCommentRangeStart(e){if(!this.options.renderComments)return null;const t=new Range;this.commentHighlight?.add(t);const r=this.createComment(`start of comment #${e.id}`);return this.later(()=>t.setStart(r,0)),this.commentMap[e.id]=t,r}renderCommentRangeEnd(e){if(!this.options.renderComments)return null;const t=this.commentMap[e.id],r=this.createComment(`end of comment #${e.id}`);return this.later(()=>t?.setEnd(r,0)),r}renderCommentReference(e){if(!this.options.renderComments)return null;var t=this.document.commentsPart?.commentMap[e.id];if(!t)return null;const r=new DocumentFragment,a=this.createElement("span",{className:`${this.className}-comment-ref`},["💬"]),s=this.createElement("div",{className:`${this.className}-comment-popover`});return this.renderCommentContent(t,s),r.appendChild(this.createComment(`comment #${t.id} by ${t.author} on ${t.date}`)),r.appendChild(a),r.appendChild(s),r}renderAltChunk(e){if(!this.options.renderAltChunks)return null;var t=this.createElement("iframe");return this.tasks.push(this.document.loadAltChunk(e.id,this.currentPart).then(e=>{t.srcdoc=e})),t}renderCommentContent(e,t){t.appendChild(this.createElement("div",{className:`${this.className}-comment-author`},[e.author])),t.appendChild(this.createElement("div",{className:`${this.className}-comment-date`},[new Date(e.date).toLocaleString()])),this.renderElements(e.children,t)}renderDrawing(e){var t=this.renderContainer(e,"div");return t.style.display="inline-block",t.style.position="relative",t.style.textIndent="0px",this.renderStyleValues(e.cssStyle,t),t}renderImage(e){let t=this.createElement("img"),r=e.cssStyle?.transform;if(this.renderStyleValues(e.cssStyle,t),e.srcRect&&e.srcRect.some(e=>0!=e)){var[a,s,n,l]=e.srcRect;r=`scale(${1/(1-a-n)}, ${1/(1-s-l)})`,t.style["clip-path"]=`rect(${(100*s).toFixed(2)}% ${(100*(1-n)).toFixed(2)}% ${(100*(1-l)).toFixed(2)}% ${(100*a).toFixed(2)}%)`}return e.rotation&&(r=`rotate(${e.rotation}deg) ${r??""}`),t.style.transform=r?.trim(),this.document&&this.tasks.push(this.document.loadDocumentImage(e.src,this.currentPart).then(e=>{t.src=e})),t}renderText(e){return this.htmlDocument.createTextNode(e.text)}renderDeletedText(e){return this.options.renderChanges?this.renderText(e):null}renderBreak(e){return"textWrapping"==e.break?this.createElement("br"):null}renderInserted(e){return this.options.renderChanges?this.renderContainer(e,"ins"):this.renderElements(e.children)}renderDeleted(e){return this.options.renderChanges?this.renderContainer(e,"del"):null}renderSymbol(e){var t=this.createElement("span");return t.style.fontFamily=e.font,t.innerHTML=`&#x${e.char};`,t}renderFootnoteReference(e){var t=this.createElement("sup");return this.currentFootnoteIds.push(e.id),t.textContent=`${this.currentFootnoteIds.length}`,t}renderEndnoteReference(e){var t=this.createElement("sup");return this.currentEndnoteIds.push(e.id),t.textContent=`${this.currentEndnoteIds.length}`,t}renderTab(e){var t=this.createElement("span");if(t.innerHTML=" ",this.options.experimental){t.className=this.tabStopClass();var r=function(e,t){var r=e.parent;for(;null!=r&&r.type!=t;)r=r.parent;return r}(e,R.Paragraph)?.tabs;this.currentTabs.push({stops:r,span:t})}return t}renderBookmarkStart(e){return this.createElement("span",{id:e.name})}renderRun(e){if(e.fieldRun)return null;const t=this.createElement("span");if(e.id&&(t.id=e.id),this.renderClass(e,t),this.renderStyleValues(e.cssStyle,t),e.verticalAlign){const r=this.createElement(e.verticalAlign);this.renderElements(e.children,r),t.appendChild(r)}else this.renderElements(e.children,t);return t}renderTable(e){let t=this.createElement("table");return this.tableCellPositions.push(this.currentCellPosition),this.tableVerticalMerges.push(this.currentVerticalMerge),this.currentVerticalMerge={},this.currentCellPosition={col:0,row:0},e.columns&&t.appendChild(this.renderTableColumns(e.columns)),this.renderClass(e,t),this.renderElements(e.children,t),this.renderStyleValues(e.cssStyle,t),this.currentVerticalMerge=this.tableVerticalMerges.pop(),this.currentCellPosition=this.tableCellPositions.pop(),t}renderTableColumns(e){let t=this.createElement("colgroup");for(let r of e){let e=this.createElement("col");r.width&&(e.style.width=r.width),t.appendChild(e)}return t}renderTableRow(e){let t=this.createElement("tr");return this.currentCellPosition.col=0,e.gridBefore&&t.appendChild(this.renderTableCellPlaceholder(e.gridBefore)),this.renderClass(e,t),this.renderElements(e.children,t),this.renderStyleValues(e.cssStyle,t),e.gridAfter&&t.appendChild(this.renderTableCellPlaceholder(e.gridAfter)),this.currentCellPosition.row++,t}renderTableCellPlaceholder(e){const t=this.createElement("td",{colSpan:e});return t.style.border="none",t}renderTableCell(e){let t=this.renderContainer(e,"td");const r=this.currentCellPosition.col;return e.verticalMerge?"restart"==e.verticalMerge?(this.currentVerticalMerge[r]=t,t.rowSpan=1):this.currentVerticalMerge[r]&&(this.currentVerticalMerge[r].rowSpan+=1,t.style.display="none"):this.currentVerticalMerge[r]=null,this.renderClass(e,t),this.renderStyleValues(e.cssStyle,t),e.span&&(t.colSpan=e.span),this.currentCellPosition.col+=t.colSpan,t}renderVmlPicture(e){return this.renderContainer(e,"div")}renderVmlElement(e){var t=this.createSvgElement("svg");t.setAttribute("style",e.cssStyleText);const r=this.renderVmlChildElement(e);return e.imageHref?.id&&this.tasks.push(this.document?.loadDocumentImage(e.imageHref.id,this.currentPart).then(e=>r.setAttribute("href",e))),t.appendChild(r),requestAnimationFrame(()=>{const e=t.firstElementChild.getBBox();t.setAttribute("width",`${Math.ceil(e.x+e.width)}`),t.setAttribute("height",`${Math.ceil(e.y+e.height)}`)}),t}renderVmlChildElement(e){const t=this.createSvgElement(e.tagName);Object.entries(e.attrs).forEach(([e,r])=>t.setAttribute(e,r));for(let r of e.children)r.type==R.VmlElement?t.appendChild(this.renderVmlChildElement(r)):t.appendChild(...c(this.renderElement(r)));return t}renderMmlRadical(e){const t=e.children.find(e=>e.type==R.MmlBase);if(e.props?.hideDegree)return this.createElementNS(Ge,"msqrt",null,this.renderElements([t]));const r=e.children.find(e=>e.type==R.MmlDegree);return this.createElementNS(Ge,"mroot",null,this.renderElements([t,r]))}renderMmlDelimiter(e){const t=[];return t.push(this.createElementNS(Ge,"mo",null,[e.props.beginChar??"("])),t.push(...this.renderElements(e.children)),t.push(this.createElementNS(Ge,"mo",null,[e.props.endChar??")"])),this.createElementNS(Ge,"mrow",null,t)}renderMmlNary(e){const t=[],r=l(e.children,e=>e.type),a=r[R.MmlSuperArgument],s=r[R.MmlSubArgument],n=a?this.createElementNS(Ge,"mo",null,c(this.renderElement(a))):null,o=s?this.createElementNS(Ge,"mo",null,c(this.renderElement(s))):null,i=this.createElementNS(Ge,"mo",null,[e.props?.char??"∫"]);return n||o?t.push(this.createElementNS(Ge,"munderover",null,[i,o,n])):n?t.push(this.createElementNS(Ge,"mover",null,[i,n])):o?t.push(this.createElementNS(Ge,"munder",null,[i,o])):t.push(i),t.push(...this.renderElements(r[R.MmlBase].children)),this.createElementNS(Ge,"mrow",null,t)}renderMmlPreSubSuper(e){const t=[],r=l(e.children,e=>e.type),a=r[R.MmlSuperArgument],s=r[R.MmlSubArgument],n=a?this.createElementNS(Ge,"mo",null,c(this.renderElement(a))):null,o=s?this.createElementNS(Ge,"mo",null,c(this.renderElement(s))):null,i=this.createElementNS(Ge,"mo",null);return t.push(this.createElementNS(Ge,"msubsup",null,[i,o,n])),t.push(...this.renderElements(r[R.MmlBase].children)),this.createElementNS(Ge,"mrow",null,t)}renderMmlGroupChar(e){const t="bot"===e.props.verticalJustification?"mover":"munder",r=this.renderContainerNS(e,Ge,t);return e.props.char&&r.appendChild(this.createElementNS(Ge,"mo",null,[e.props.char])),r}renderMmlBar(e){const t=this.renderContainerNS(e,Ge,"mrow");switch(e.props.position){case"top":t.style.textDecoration="overline";break;case"bottom":t.style.textDecoration="underline"}return t}renderMmlRun(e){const t=this.createElementNS(Ge,"ms",null,this.renderElements(e.children));return this.renderClass(e,t),this.renderStyleValues(e.cssStyle,t),t}renderMllList(e){const t=this.createElementNS(Ge,"mtable");this.renderClass(e,t),this.renderStyleValues(e.cssStyle,t);for(let r of this.renderElements(e.children))t.appendChild(this.createElementNS(Ge,"mtr",null,[this.createElementNS(Ge,"mtd",null,[r])]));return t}renderStyleValues(e,t){for(let r in e)r.startsWith("$")?t.setAttribute(r.slice(1),e[r]):t.style[r]=e[r]}renderClass(e,t){e.className&&(t.className=e.className),e.styleName&&t.classList.add(this.processStyleName(e.styleName))}findStyle(e){return e&&this.styleMap?.[e]}numberingClass(e,t){return`${this.className}-num-${e}-${t}`}tabStopClass(){return`${this.className}-tab-stop`}styleToString(e,t,r=null){let a=`${e} {\r\n`;for(const e in t)e.startsWith("$")||(a+=` ${e}: ${t[e]};\r\n`);return r&&(a+=r),a+"}\r\n"}numberingCounter(e,t){return`${this.className}-num-${e}-${t}`}levelTextToContent(e,t,r,a){return`"${e.replace(/%\d*/g,e=>{let t=parseInt(e.substring(1),10)-1;return`"counter(${this.numberingCounter(r,t)}, ${a})"`})}${{tab:"\\9",space:"\\a0"}[t]??""}"`}numFormatToCssValue(e){return{none:"none",bullet:"disc",decimal:"decimal",lowerLetter:"lower-alpha",upperLetter:"upper-alpha",lowerRoman:"lower-roman",upperRoman:"upper-roman",decimalZero:"decimal-leading-zero",aiueo:"katakana",aiueoFullWidth:"katakana",chineseCounting:"simp-chinese-informal",chineseCountingThousand:"simp-chinese-informal",chineseLegalSimplified:"simp-chinese-formal",chosung:"hangul-consonant",ideographDigital:"cjk-ideographic",ideographTraditional:"cjk-heavenly-stem",ideographLegalTraditional:"trad-chinese-formal",ideographZodiac:"cjk-earthly-branch",iroha:"katakana-iroha",irohaFullWidth:"katakana-iroha",japaneseCounting:"japanese-informal",japaneseDigitalTenThousand:"cjk-decimal",japaneseLegal:"japanese-formal",thaiNumbers:"thai",koreanCounting:"korean-hangul-formal",koreanDigital:"korean-hangul-formal",koreanDigital2:"korean-hanja-informal",hebrew1:"hebrew",hebrew2:"hebrew",hindiNumbers:"devanagari",ganada:"hangul",taiwaneseCounting:"cjk-ideographic",taiwaneseCountingThousand:"cjk-ideographic",taiwaneseDigital:"cjk-decimal"}[e]??e}refreshTabStops(){this.options.experimental&&setTimeout(()=>{const e=function(e=document.body){const t=document.createElement("div");t.style.width="100pt",e.appendChild(t);const r=100/t.offsetWidth;return e.removeChild(t),r}();for(let t of this.currentTabs)je(t.span,t.stops,this.defaultTabSize,e)},500)}createElementNS(e,t,r,a){var s=e?this.htmlDocument.createElementNS(e,t):this.htmlDocument.createElement(t);return Object.assign(s,r),a&&Je(s,a),s}createElement(e,t,r){return this.createElementNS(void 0,e,t,r)}createSvgElement(e,t,r){return this.createElementNS(Xe,e,t,r)}createStyleElement(e){return this.createElement("style",{innerHTML:e})}createComment(e){return this.htmlDocument.createComment(e)}later(e){this.postRenderTasks.push(e)}}function qe(e){e.innerHTML=""}function Je(e,t){t.forEach(t=>{return e.appendChild("string"==typeof(r=t)||r instanceof String?document.createTextNode(t):t);var r})}const Ze={ignoreHeight:!1,ignoreWidth:!1,ignoreFonts:!1,breakPages:!0,debug:!1,experimental:!1,className:"docx",inWrapper:!0,hideWrapperOnPrint:!1,trimXmlDeclaration:!0,ignoreLastRenderedPageBreak:!0,renderHeaders:!0,renderFooters:!0,renderFootnotes:!0,renderEndnotes:!0,useBase64URL:!1,renderChanges:!1,renderComments:!1,renderAltChunks:!0};function Ke(e,t){const r={...Ze,...t};return ke.load(e,new Oe(r),r)}async function Ye(e,t,r,a){const s={...Ze,...a},n=new Ue(window.document);return await n.render(e,t,r,s)}e.defaultOptions=Ze,e.parseAsync=Ke,e.renderAsync=async function(e,t,r,a){const s=await Ke(e,a);return await Ye(s,t,r,a),s},e.renderDocument=Ye});
|
||
//# sourceMappingURL=docx-preview.min.js.map
|
||
|
||
/**
|
||
* ZDDC — shared naming convention library
|
||
*
|
||
* Canonical implementation of all ZDDC filename, folder name, tracking number,
|
||
* revision, and status logic. Included in every tool's build via shared/zddc.js.
|
||
*
|
||
* Exposed as window.zddc (plain global) so it works with every tool's module
|
||
* pattern (archive globals, classifier IIFE, transmittal IIFE, mdedit globals).
|
||
*
|
||
* Public API
|
||
* ----------
|
||
* zddc.parseFilename(str) → ParsedFile | null
|
||
* zddc.parseFolder(str) → ParsedFolder | null
|
||
* zddc.parseRevision(str) → ParsedRevision
|
||
* zddc.formatFilename(parts) → string
|
||
* zddc.formatFolder(parts) → string
|
||
* zddc.compareRevisions(a, b) → number (-1 | 0 | 1)
|
||
* zddc.isValidStatus(str) → boolean
|
||
* zddc.STATUSES → string[]
|
||
*
|
||
* ParsedFile { trackingNumber, revision, status, title, extension }
|
||
* ParsedFolder { date, trackingNumber, status, title }
|
||
* ParsedRevision { base, modifier, modifierType, modifierNumber, isDraft, full }
|
||
*/
|
||
|
||
(function (root) {
|
||
'use strict';
|
||
|
||
// ── Valid status codes ───────────────────────────────────────────────────
|
||
|
||
/**
|
||
* Complete list of valid ZDDC document status codes.
|
||
* '---' denotes an unknown or not-yet-assigned status.
|
||
*/
|
||
var STATUSES = [
|
||
'---',
|
||
'IFA', 'IFB', 'IFC', 'IFD', 'IFI', 'IFP', 'IFR', 'IFU',
|
||
'REC',
|
||
'RSA', 'RSB', 'RSC', 'RSD', 'RSI',
|
||
];
|
||
|
||
var STATUS_SET = {};
|
||
for (var _i = 0; _i < STATUSES.length; _i++) {
|
||
STATUS_SET[STATUSES[_i]] = true;
|
||
}
|
||
|
||
function isValidStatus(str) {
|
||
return !!STATUS_SET[str];
|
||
}
|
||
|
||
// ── Filename parsing ─────────────────────────────────────────────────────
|
||
|
||
/**
|
||
* Canonical file regex.
|
||
* Matches: TRACKING_REVISION (STATUS) - TITLE.EXT
|
||
*
|
||
* Tracking number: no underscores, no whitespace.
|
||
* Revision: no whitespace, no parentheses.
|
||
* Status: anything inside parentheses (validated separately).
|
||
* Title: everything up to the last dot.
|
||
* Extension: after the last dot (lowercased by parseFilename).
|
||
*/
|
||
var FILE_RE = /^([^_\s]+)_([^\s()_]+)\s*\(([^)]+)\)\s*-\s*(\S.*\S|\S)\.\s*([^\s.]+)$/;
|
||
|
||
/**
|
||
* Parse a ZDDC filename.
|
||
*
|
||
* @param {string} filename
|
||
* @returns {{ trackingNumber: string, revision: string, status: string,
|
||
* title: string, extension: string, valid: boolean } | null}
|
||
* null only if filename is falsy.
|
||
* `valid` is true when all fields matched the ZDDC pattern.
|
||
*/
|
||
function parseFilename(filename) {
|
||
if (!filename) { return null; }
|
||
|
||
var match = filename.match(FILE_RE);
|
||
|
||
if (!match) {
|
||
var lastDot = filename.lastIndexOf('.');
|
||
return {
|
||
trackingNumber: '',
|
||
revision: '',
|
||
status: '',
|
||
title: lastDot > 0 ? filename.substring(0, lastDot) : filename,
|
||
extension: lastDot > 0 ? filename.substring(lastDot + 1).toLowerCase() : '',
|
||
valid: false,
|
||
};
|
||
}
|
||
|
||
return {
|
||
trackingNumber: match[1].trim(),
|
||
revision: match[2].trim(),
|
||
status: match[3].trim(),
|
||
title: match[4].trim(),
|
||
extension: match[5].toLowerCase(),
|
||
valid: true,
|
||
};
|
||
}
|
||
|
||
// ── Folder name parsing ──────────────────────────────────────────────────
|
||
|
||
/**
|
||
* Transmittal folder regex.
|
||
* Matches: YYYY-MM-DD_TRACKING (STATUS) - TITLE
|
||
*/
|
||
var FOLDER_RE = /^(\d{4}-\d{2}-\d{2})_([^_\s(]+)\s*\(([^)]+)\)\s*-\s*(.+)$/;
|
||
|
||
/**
|
||
* Parse a ZDDC transmittal folder name.
|
||
*
|
||
* @param {string} foldername
|
||
* @returns {{ date: string, trackingNumber: string, status: string,
|
||
* title: string, valid: boolean } | null}
|
||
* null only if foldername is falsy.
|
||
*/
|
||
function parseFolder(foldername) {
|
||
if (!foldername) { return null; }
|
||
|
||
var match = foldername.match(FOLDER_RE);
|
||
|
||
if (!match) {
|
||
return {
|
||
date: '',
|
||
trackingNumber: '',
|
||
status: '',
|
||
title: foldername,
|
||
valid: false,
|
||
};
|
||
}
|
||
|
||
return {
|
||
date: match[1],
|
||
trackingNumber: match[2].trim(),
|
||
status: match[3].trim(),
|
||
title: match[4].trim(),
|
||
valid: true,
|
||
};
|
||
}
|
||
|
||
// ── Revision parsing ─────────────────────────────────────────────────────
|
||
|
||
/**
|
||
* Modifier sub-regex: +LETTER DIGITS e.g. +C1, +B2, +N1, +Q1
|
||
* The draft prefix (~) may appear inside the modifier: A+~C1
|
||
*/
|
||
var MODIFIER_RE = /^\+(~?)([A-Za-z])(\d+)$/;
|
||
|
||
/**
|
||
* Parse a ZDDC revision string.
|
||
*
|
||
* Revision grammar:
|
||
* revision = ['~'] base ['+' ['~'] modifier_letter modifier_number]
|
||
* base = letter(s) | digit(s) | date(YYYY-MM-DD)
|
||
* modifier = letter + digits e.g. C1, B2, N1, Q1
|
||
*
|
||
* @param {string} revision
|
||
* @returns {{
|
||
* base: string,
|
||
* modifier: string, full modifier string e.g. '+C1', '' if none
|
||
* modifierType: string, modifier letter e.g. 'C', '' if none
|
||
* modifierNumber: number, modifier number e.g. 1, 0 if none
|
||
* modifierIsDraft: boolean,
|
||
* isDraft: boolean, true if base revision starts with ~
|
||
* full: string, original input
|
||
* }}
|
||
*/
|
||
function parseRevision(revision) {
|
||
var raw = (revision || '').toString();
|
||
|
||
// Split on '+' to separate base from optional modifier
|
||
var plusIdx = raw.indexOf('+');
|
||
var basePart = plusIdx === -1 ? raw : raw.substring(0, plusIdx);
|
||
var modifierPart = plusIdx === -1 ? '' : raw.substring(plusIdx);
|
||
|
||
// Draft flag on the base part
|
||
var isDraft = basePart.startsWith('~');
|
||
var base = isDraft ? basePart.substring(1) : basePart;
|
||
|
||
// Parse modifier
|
||
var modifier = '';
|
||
var modifierType = '';
|
||
var modifierNumber = 0;
|
||
var modifierIsDraft = false;
|
||
|
||
if (modifierPart) {
|
||
var mMatch = modifierPart.match(MODIFIER_RE);
|
||
if (mMatch) {
|
||
modifierIsDraft = mMatch[1] === '~';
|
||
modifierType = mMatch[2].toUpperCase();
|
||
modifierNumber = parseInt(mMatch[3], 10);
|
||
modifier = modifierPart;
|
||
} else {
|
||
// Unrecognised modifier — preserve as-is
|
||
modifier = modifierPart;
|
||
}
|
||
}
|
||
|
||
return {
|
||
base: base,
|
||
modifier: modifier,
|
||
modifierType: modifierType,
|
||
modifierNumber: modifierNumber,
|
||
modifierIsDraft: modifierIsDraft,
|
||
isDraft: isDraft,
|
||
full: raw,
|
||
};
|
||
}
|
||
|
||
// ── Revision comparison ──────────────────────────────────────────────────
|
||
|
||
/**
|
||
* Classify a base revision string into a sort tier:
|
||
* 0 = date (YYYY-MM-DD)
|
||
* 1 = letter(s) A, B, AA …
|
||
* 2 = number(s) 0, 1, 2, 1.5 …
|
||
* 3 = other
|
||
*/
|
||
function _baseTier(base) {
|
||
if (/^\d{4}-\d{2}-\d{2}$/.test(base)) { return 0; }
|
||
if (/^[A-Za-z]+$/.test(base)) { return 1; }
|
||
if (/^\d+(\.\d+)?$/.test(base)) { return 2; }
|
||
return 3;
|
||
}
|
||
|
||
/**
|
||
* Compare two base revision strings.
|
||
* Sort order: dates < letters < numbers < other.
|
||
*/
|
||
function _compareBase(a, b) {
|
||
var ta = _baseTier(a);
|
||
var tb = _baseTier(b);
|
||
if (ta !== tb) { return ta - tb; }
|
||
|
||
if (ta === 0) { return a < b ? -1 : a > b ? 1 : 0; } // date lexicographic = chronological
|
||
if (ta === 1) { return a.toUpperCase() < b.toUpperCase() ? -1 : a.toUpperCase() > b.toUpperCase() ? 1 : 0; }
|
||
if (ta === 2) { return parseFloat(a) - parseFloat(b); }
|
||
return a.localeCompare(b);
|
||
}
|
||
|
||
/**
|
||
* Compare two ZDDC revision strings for sort ordering.
|
||
*
|
||
* Canonical order (ascending = older → newer):
|
||
* ~A < A < A+B1 < A+C1 < A+~C2 < A+C2 < A+N1 < A+Q1
|
||
* < ~B < B < … < 0 < 1 < 2
|
||
*
|
||
* Rules:
|
||
* 1. Compare base revisions first (dates < letters < numbers).
|
||
* 2. For equal bases, draft (isDraft=true) comes before final.
|
||
* 3. For equal base+draft, no-modifier < has-modifier.
|
||
* 4. For equal base+draft+modifier presence:
|
||
* a. modifier draft comes before modifier final (modifierIsDraft).
|
||
* b. Sort modifier by type letter then by number.
|
||
*
|
||
* @param {string} a
|
||
* @param {string} b
|
||
* @returns {number} negative if a < b, 0 if equal, positive if a > b
|
||
*/
|
||
function compareRevisions(a, b) {
|
||
var pa = parseRevision(a);
|
||
var pb = parseRevision(b);
|
||
|
||
// 1. Base revision
|
||
var baseCmp = _compareBase(pa.base, pb.base);
|
||
if (baseCmp !== 0) { return baseCmp; }
|
||
|
||
// 2. Draft before final (for same base)
|
||
if (pa.isDraft !== pb.isDraft) { return pa.isDraft ? -1 : 1; }
|
||
|
||
// 3. No modifier before any modifier
|
||
var aHasMod = pa.modifier !== '';
|
||
var bHasMod = pb.modifier !== '';
|
||
if (aHasMod !== bHasMod) { return aHasMod ? 1 : -1; }
|
||
|
||
if (!aHasMod) { return 0; } // both have no modifier
|
||
|
||
// 4. Compare modifiers: type → number → draft (draft is a tie-breaker only)
|
||
// 4a. Modifier type letter (B < C < N < Q …)
|
||
if (pa.modifierType !== pb.modifierType) {
|
||
return pa.modifierType < pb.modifierType ? -1 : 1;
|
||
}
|
||
|
||
// 4b. Modifier number (1 < 2 …)
|
||
if (pa.modifierNumber !== pb.modifierNumber) {
|
||
return pa.modifierNumber - pb.modifierNumber;
|
||
}
|
||
|
||
// 4c. Draft of a modifier comes before the final modifier (same type+number)
|
||
if (pa.modifierIsDraft !== pb.modifierIsDraft) {
|
||
return pa.modifierIsDraft ? -1 : 1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
// ── Filename / folder formatting ─────────────────────────────────────────
|
||
|
||
/**
|
||
* Build a ZDDC filename from its components.
|
||
*
|
||
* @param {{ trackingNumber: string, revision: string, status: string,
|
||
* title: string, extension: string }} parts
|
||
* @returns {string} e.g. "123456-EL-SPC-2623_A (IFR) - Specification.pdf"
|
||
*/
|
||
function formatFilename(parts) {
|
||
var tn = (parts.trackingNumber || '').trim();
|
||
var rev = (parts.revision || '').trim();
|
||
var st = (parts.status || '').trim();
|
||
var ttl = (parts.title || '').trim();
|
||
var ext = (parts.extension || '').replace(/^\./, '');
|
||
|
||
if (!tn || !rev || !st || !ttl) { return ''; }
|
||
|
||
var name = tn + '_' + rev + ' (' + st + ') - ' + ttl;
|
||
return ext ? name + '.' + ext : name;
|
||
}
|
||
|
||
/**
|
||
* Build a ZDDC transmittal folder name from its components.
|
||
*
|
||
* @param {{ date: string, trackingNumber: string, status: string,
|
||
* title: string }} parts
|
||
* @returns {string} e.g. "2025-10-31_123456-EM-SUB-0001 (IFR) - Title"
|
||
*/
|
||
function formatFolder(parts) {
|
||
var dt = (parts.date || '').trim();
|
||
var tn = (parts.trackingNumber || '').trim();
|
||
var st = (parts.status || '').trim();
|
||
var ttl = (parts.title || '').trim();
|
||
|
||
if (!dt || !tn || !st || !ttl) { return ''; }
|
||
|
||
return dt + '_' + tn + ' (' + st + ') - ' + ttl;
|
||
}
|
||
|
||
// ── Filename / extension splitting ───────────────────────────────────────
|
||
|
||
/**
|
||
* Split a filename into its base name and extension (no leading dot).
|
||
* Treats leading dot ('.gitignore') as no extension.
|
||
*
|
||
* @param {string} filename
|
||
* @returns {{ name: string, extension: string }}
|
||
*/
|
||
function splitExtension(filename) {
|
||
if (!filename) { return { name: '', extension: '' }; }
|
||
var lastDot = filename.lastIndexOf('.');
|
||
if (lastDot <= 0) { return { name: filename, extension: '' }; }
|
||
return {
|
||
name: filename.substring(0, lastDot),
|
||
extension: filename.substring(lastDot + 1).toLowerCase(),
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Join a base name and extension. Tolerant of either form ('pdf' or '.pdf').
|
||
* Returns just the name when extension is empty.
|
||
*/
|
||
function joinExtension(name, extension) {
|
||
var ext = (extension || '').replace(/^\./, '');
|
||
return ext ? name + '.' + ext : name;
|
||
}
|
||
|
||
// ── Public API ───────────────────────────────────────────────────────────
|
||
|
||
root.zddc = {
|
||
STATUSES: STATUSES,
|
||
isValidStatus: isValidStatus,
|
||
parseFilename: parseFilename,
|
||
parseFolder: parseFolder,
|
||
parseRevision: parseRevision,
|
||
formatFilename: formatFilename,
|
||
formatFolder: formatFolder,
|
||
compareRevisions: compareRevisions,
|
||
splitExtension: splitExtension,
|
||
joinExtension: joinExtension,
|
||
};
|
||
|
||
}(typeof window !== 'undefined' ? window : this));
|
||
|
||
/**
|
||
* ZDDC — shared SHA-256 helpers
|
||
*
|
||
* Attaches to window.zddc.crypto. Must load AFTER shared/zddc.js (which creates
|
||
* the window.zddc object).
|
||
*
|
||
* Exports:
|
||
* zddc.crypto.sha256Hex(buffer) → Promise<string> hex digest of ArrayBuffer/Uint8Array
|
||
* zddc.crypto.sha256String(str) → Promise<string> hex digest of UTF-8 encoded string
|
||
* zddc.crypto.sha256File(file, onProgress?) → Promise<string>
|
||
* chunked streaming digest for File/Blob; for files >= 4 MB, streams 2 MB chunks
|
||
* and invokes onProgress(loaded, total) every ~8 MB.
|
||
* zddc.crypto.bytesToHex(buffer) → string (hex of ArrayBuffer/Uint8Array, no digest)
|
||
*
|
||
* Throws if Web Crypto SubtleCrypto is not available.
|
||
*/
|
||
(function (root) {
|
||
'use strict';
|
||
|
||
if (!root.zddc) {
|
||
throw new Error('shared/hash.js: window.zddc must be loaded first');
|
||
}
|
||
|
||
var HASH_CHUNK_SIZE = 2 * 1024 * 1024; // 2 MB
|
||
|
||
function requireSubtle() {
|
||
if (!root.crypto || !root.crypto.subtle || typeof root.crypto.subtle.digest !== 'function') {
|
||
throw new Error('Web Crypto SubtleCrypto is required');
|
||
}
|
||
}
|
||
|
||
function bytesToHex(buffer) {
|
||
return Array.from(new Uint8Array(buffer), function (byte) {
|
||
return byte.toString(16).padStart(2, '0');
|
||
}).join('');
|
||
}
|
||
|
||
async function sha256Hex(buffer) {
|
||
requireSubtle();
|
||
var input = (buffer instanceof Uint8Array) ? buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength) : buffer;
|
||
var hash = await root.crypto.subtle.digest('SHA-256', input);
|
||
return bytesToHex(hash);
|
||
}
|
||
|
||
async function sha256String(str) {
|
||
requireSubtle();
|
||
var bytes = new TextEncoder().encode(str);
|
||
var hash = await root.crypto.subtle.digest('SHA-256', bytes);
|
||
return bytesToHex(hash);
|
||
}
|
||
|
||
async function sha256File(file, onProgress) {
|
||
requireSubtle();
|
||
// Single-shot for small files or environments without ReadableStream
|
||
if (file.size < HASH_CHUNK_SIZE * 2 || typeof file.stream !== 'function') {
|
||
if (onProgress) { onProgress(file.size, file.size); }
|
||
var buf = await file.arrayBuffer();
|
||
var hash = await root.crypto.subtle.digest('SHA-256', buf);
|
||
return bytesToHex(hash);
|
||
}
|
||
// Chunked streaming for large files
|
||
var reader = file.stream().getReader();
|
||
var loaded = 0;
|
||
var chunks = [];
|
||
var yieldCounter = 0;
|
||
while (true) {
|
||
var result = await reader.read();
|
||
if (result.done) { break; }
|
||
chunks.push(result.value);
|
||
loaded += result.value.byteLength;
|
||
yieldCounter++;
|
||
if (onProgress && yieldCounter % 4 === 0) {
|
||
onProgress(loaded, file.size);
|
||
await new Promise(function (r) { setTimeout(r, 0); });
|
||
}
|
||
}
|
||
var total = new Uint8Array(loaded);
|
||
var offset = 0;
|
||
for (var i = 0; i < chunks.length; i++) {
|
||
total.set(chunks[i], offset);
|
||
offset += chunks[i].byteLength;
|
||
}
|
||
var digest = await root.crypto.subtle.digest('SHA-256', total.buffer);
|
||
if (onProgress) { onProgress(file.size, file.size); }
|
||
return bytesToHex(digest);
|
||
}
|
||
|
||
root.zddc.crypto = {
|
||
sha256Hex: sha256Hex,
|
||
sha256String: sha256String,
|
||
sha256File: sha256File,
|
||
bytesToHex: bytesToHex,
|
||
};
|
||
})(typeof window !== 'undefined' ? window : globalThis);
|
||
|
||
/**
|
||
* ZDDC shared theme toggle — light / dark / auto.
|
||
* Persists choice to localStorage under 'zddc-theme'.
|
||
* Works with all four tools regardless of their module pattern.
|
||
* Expects: #theme-btn in the DOM (optional — skips gracefully if absent).
|
||
*
|
||
* Theme cycle: auto → light → dark → auto …
|
||
* 'auto' honours the OS prefers-color-scheme media query (CSS handles it).
|
||
* 'light' sets data-theme="light" on <html> (overrides dark media query).
|
||
* 'dark' sets data-theme="dark" on <html>.
|
||
*/
|
||
(function () {
|
||
'use strict';
|
||
|
||
var STORAGE_KEY = 'zddc-theme';
|
||
var THEMES = ['auto', 'light', 'dark'];
|
||
|
||
var LABELS = {
|
||
auto: '◐',
|
||
light: '☀',
|
||
dark: '☾'
|
||
};
|
||
|
||
var TITLES = {
|
||
auto: 'Theme: auto (follows OS)',
|
||
light: 'Theme: light',
|
||
dark: 'Theme: dark'
|
||
};
|
||
|
||
function load() {
|
||
var stored = localStorage.getItem(STORAGE_KEY);
|
||
return THEMES.indexOf(stored) !== -1 ? stored : 'auto';
|
||
}
|
||
|
||
function apply(theme) {
|
||
if (theme === 'dark') {
|
||
document.documentElement.setAttribute('data-theme', 'dark');
|
||
} else if (theme === 'light') {
|
||
document.documentElement.setAttribute('data-theme', 'light');
|
||
} else {
|
||
document.documentElement.removeAttribute('data-theme');
|
||
}
|
||
}
|
||
|
||
function save(theme) {
|
||
try { localStorage.setItem(STORAGE_KEY, theme); } catch (e) {}
|
||
}
|
||
|
||
function updateButton(btn, theme) {
|
||
btn.textContent = LABELS[theme];
|
||
btn.title = TITLES[theme];
|
||
btn.setAttribute('aria-label', TITLES[theme]);
|
||
}
|
||
|
||
function next(theme) {
|
||
return THEMES[(THEMES.indexOf(theme) + 1) % THEMES.length];
|
||
}
|
||
|
||
function init() {
|
||
var current = load();
|
||
apply(current);
|
||
|
||
var btn = document.getElementById('theme-btn');
|
||
if (!btn) { return; }
|
||
|
||
updateButton(btn, current);
|
||
|
||
btn.addEventListener('click', function () {
|
||
current = next(current);
|
||
apply(current);
|
||
save(current);
|
||
updateButton(btn, current);
|
||
});
|
||
}
|
||
|
||
/* Apply theme immediately (before DOM ready) to avoid flash */
|
||
apply(load());
|
||
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', init);
|
||
} else {
|
||
init();
|
||
}
|
||
}());
|
||
|
||
/**
|
||
* ZDDC — shared preview helpers
|
||
*
|
||
* Cross-tool helpers for previewing file types that need a decoder:
|
||
* - TIFF (UTIF.js) — multi-page, browser-PDF-viewer-style toolbar
|
||
* - ZIP listing (JSZip) — sortable file-list view
|
||
*
|
||
* Renderers operate on any document (parent window or popup window), so the
|
||
* same code works for tools whose preview opens in a popup (classifier,
|
||
* archive, transmittal) and tools that render inline (mdedit).
|
||
*
|
||
* Public API on window.zddc.preview:
|
||
* loadLibrary(url) → Promise<void>
|
||
* renderTiff(doc, container, arrayBuffer, opts) → Promise<void>
|
||
* renderZipListing(doc, container, arrayBuffer, opts) → Promise<void>
|
||
* TIFF_EXTENSIONS, IMAGE_EXTENSIONS, TEXT_EXTENSIONS, OFFICE_EXTENSIONS
|
||
* isTiff(ext), isImage(ext), isText(ext), isZip(ext), isOffice(ext)
|
||
*
|
||
* Each tool keeps its own dispatcher; this lib only owns the heavy renderers.
|
||
*/
|
||
|
||
(function (root) {
|
||
'use strict';
|
||
|
||
var TIFF_EXTENSIONS = ['tif', 'tiff'];
|
||
var IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp', 'bmp', 'ico'];
|
||
var TEXT_EXTENSIONS = [
|
||
'txt', 'md', 'markdown', 'json', 'xml', 'csv', 'tsv', 'log',
|
||
'html', 'htm', 'css', 'js', 'mjs', 'ts', 'tsx', 'jsx',
|
||
'py', 'rb', 'sh', 'bash', 'zsh', 'bat', 'ps1',
|
||
'yaml', 'yml', 'ini', 'cfg', 'conf', 'toml',
|
||
'c', 'cc', 'cpp', 'h', 'hpp', 'go', 'rs', 'java', 'kt',
|
||
'sql', 'env'
|
||
];
|
||
var OFFICE_EXTENSIONS = ['docx', 'xlsx', 'xls'];
|
||
|
||
function lowerExt(ext) { return (ext || '').toLowerCase(); }
|
||
function isTiff(ext) { return TIFF_EXTENSIONS.indexOf(lowerExt(ext)) !== -1; }
|
||
function isImage(ext) { return IMAGE_EXTENSIONS.indexOf(lowerExt(ext)) !== -1; }
|
||
function isText(ext) { return TEXT_EXTENSIONS.indexOf(lowerExt(ext)) !== -1; }
|
||
function isZip(ext) { return lowerExt(ext) === 'zip'; }
|
||
function isOffice(ext) { return OFFICE_EXTENSIONS.indexOf(lowerExt(ext)) !== -1; }
|
||
|
||
// ── CDN library loader (parent window cache) ─────────────────────────────
|
||
|
||
var _libCache = new Map();
|
||
|
||
function loadLibrary(url) {
|
||
if (_libCache.has(url)) return _libCache.get(url);
|
||
var p = new Promise(function (resolve, reject) {
|
||
var s = document.createElement('script');
|
||
s.src = url;
|
||
s.onload = function () { resolve(); };
|
||
s.onerror = function () { reject(new Error('Failed to load: ' + url)); };
|
||
document.head.appendChild(s);
|
||
});
|
||
_libCache.set(url, p);
|
||
return p;
|
||
}
|
||
|
||
// ── Style injection (idempotent per-document) ────────────────────────────
|
||
|
||
function injectStyles(doc, id, css) {
|
||
if (doc.getElementById(id)) return;
|
||
var style = doc.createElement('style');
|
||
style.id = id;
|
||
style.textContent = css;
|
||
doc.head.appendChild(style);
|
||
}
|
||
|
||
// ── Helpers ──────────────────────────────────────────────────────────────
|
||
|
||
function formatSize(bytes) {
|
||
if (bytes == null) return '';
|
||
if (bytes < 1024) return bytes + ' B';
|
||
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
||
if (bytes < 1024 * 1024 * 1024) return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
||
return (bytes / (1024 * 1024 * 1024)).toFixed(2) + ' GB';
|
||
}
|
||
|
||
function formatDate(d) {
|
||
if (!d) return '';
|
||
var pad = function (n) { return n < 10 ? '0' + n : '' + n; };
|
||
return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate())
|
||
+ ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes());
|
||
}
|
||
|
||
function escapeHtml(s) {
|
||
return String(s)
|
||
.replace(/&/g, '&')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
.replace(/"/g, '"');
|
||
}
|
||
|
||
// ── TIFF renderer ────────────────────────────────────────────────────────
|
||
|
||
var TIFF_CSS =
|
||
'.tiff-toolbar{display:flex;align-items:center;gap:.4rem;padding:.4rem .6rem;' +
|
||
'background:#f5f5f5;border-bottom:1px solid #ddd;flex-wrap:wrap;font-size:.85rem;}' +
|
||
'.tiff-toolbar .tiff-btn{padding:.25rem .55rem;border:1px solid #ccc;border-radius:3px;' +
|
||
'background:#fff;cursor:pointer;font-size:.85rem;line-height:1;min-width:1.8rem;}' +
|
||
'.tiff-toolbar .tiff-btn:hover:not(:disabled){background:#e8e8e8;}' +
|
||
'.tiff-toolbar .tiff-btn:disabled{opacity:.4;cursor:default;}' +
|
||
'.tiff-toolbar .tiff-page-info{display:inline-flex;align-items:center;gap:.3rem;}' +
|
||
'.tiff-toolbar .tiff-page-input{width:3.2rem;padding:.2rem .3rem;border:1px solid #ccc;' +
|
||
'border-radius:3px;text-align:center;font-size:.85rem;}' +
|
||
'.tiff-toolbar .tiff-zoom-select{padding:.2rem .3rem;border:1px solid #ccc;border-radius:3px;' +
|
||
'background:#fff;font-size:.85rem;}' +
|
||
'.tiff-toolbar .tiff-spacer{flex:1;}' +
|
||
'.tiff-viewport{flex:1;overflow:auto;background:#525659;display:flex;align-items:flex-start;' +
|
||
'justify-content:center;padding:1rem;}' +
|
||
'.tiff-canvas{background:#fff;box-shadow:0 2px 8px rgba(0,0,0,.4);display:block;' +
|
||
'image-rendering:auto;}' +
|
||
'.tiff-error{flex:1;display:flex;align-items:center;justify-content:center;color:#900;' +
|
||
'padding:2rem;text-align:center;}';
|
||
|
||
function renderTiff(doc, container, arrayBuffer, opts) {
|
||
opts = opts || {};
|
||
injectStyles(doc, 'zddc-tiff-styles', TIFF_CSS);
|
||
|
||
return loadLibrary('https://cdn.jsdelivr.net/npm/utif@3.1.0/UTIF.js').then(function () {
|
||
var ifds;
|
||
try {
|
||
ifds = window.UTIF.decode(arrayBuffer);
|
||
} catch (e) {
|
||
container.innerHTML = '<div class="tiff-error">Failed to parse TIFF: '
|
||
+ escapeHtml(e.message || e) + '</div>';
|
||
return;
|
||
}
|
||
if (!ifds || !ifds.length) {
|
||
container.innerHTML = '<div class="tiff-error">No images found in TIFF.</div>';
|
||
return;
|
||
}
|
||
|
||
// Reset container to a flex column
|
||
container.innerHTML = '';
|
||
container.style.display = 'flex';
|
||
container.style.flexDirection = 'column';
|
||
container.style.minHeight = '0';
|
||
container.style.height = '100%';
|
||
container.style.overflow = 'hidden';
|
||
|
||
// Toolbar
|
||
var toolbar = doc.createElement('div');
|
||
toolbar.className = 'tiff-toolbar';
|
||
|
||
var btnPrev = doc.createElement('button');
|
||
btnPrev.className = 'tiff-btn'; btnPrev.type = 'button';
|
||
btnPrev.title = 'Previous page'; btnPrev.textContent = '◀';
|
||
|
||
var pageInfo = doc.createElement('span');
|
||
pageInfo.className = 'tiff-page-info';
|
||
var pageInput = doc.createElement('input');
|
||
pageInput.type = 'number'; pageInput.min = '1'; pageInput.value = '1';
|
||
pageInput.className = 'tiff-page-input';
|
||
var pageOf = doc.createElement('span');
|
||
pageOf.textContent = ' of ' + ifds.length;
|
||
pageInfo.appendChild(doc.createTextNode('Page '));
|
||
pageInfo.appendChild(pageInput);
|
||
pageInfo.appendChild(pageOf);
|
||
|
||
var btnNext = doc.createElement('button');
|
||
btnNext.className = 'tiff-btn'; btnNext.type = 'button';
|
||
btnNext.title = 'Next page'; btnNext.textContent = '▶';
|
||
|
||
var spacer = doc.createElement('span');
|
||
spacer.className = 'tiff-spacer';
|
||
|
||
var btnZoomOut = doc.createElement('button');
|
||
btnZoomOut.className = 'tiff-btn'; btnZoomOut.type = 'button';
|
||
btnZoomOut.title = 'Zoom out'; btnZoomOut.textContent = '−';
|
||
|
||
var zoomSelect = doc.createElement('select');
|
||
zoomSelect.className = 'tiff-zoom-select';
|
||
var zoomOptions = [
|
||
['fit-width', 'Fit width'],
|
||
['fit-page', 'Fit page'],
|
||
['0.5', '50%'],
|
||
['0.75', '75%'],
|
||
['1', '100%'],
|
||
['1.25', '125%'],
|
||
['1.5', '150%'],
|
||
['2', '200%'],
|
||
['3', '300%'],
|
||
['4', '400%']
|
||
];
|
||
zoomOptions.forEach(function (z) {
|
||
var o = doc.createElement('option');
|
||
o.value = z[0]; o.textContent = z[1];
|
||
zoomSelect.appendChild(o);
|
||
});
|
||
zoomSelect.value = 'fit-width';
|
||
|
||
var btnZoomIn = doc.createElement('button');
|
||
btnZoomIn.className = 'tiff-btn'; btnZoomIn.type = 'button';
|
||
btnZoomIn.title = 'Zoom in'; btnZoomIn.textContent = '+';
|
||
|
||
toolbar.appendChild(btnPrev);
|
||
toolbar.appendChild(pageInfo);
|
||
toolbar.appendChild(btnNext);
|
||
toolbar.appendChild(spacer);
|
||
toolbar.appendChild(btnZoomOut);
|
||
toolbar.appendChild(zoomSelect);
|
||
toolbar.appendChild(btnZoomIn);
|
||
|
||
// Viewport with canvas
|
||
var viewport = doc.createElement('div');
|
||
viewport.className = 'tiff-viewport';
|
||
var canvas = doc.createElement('canvas');
|
||
canvas.className = 'tiff-canvas';
|
||
viewport.appendChild(canvas);
|
||
|
||
container.appendChild(toolbar);
|
||
container.appendChild(viewport);
|
||
|
||
// Render state
|
||
var currentPage = 0;
|
||
var zoom = 1;
|
||
var fitMode = 'width'; // 'width' | 'page' | null
|
||
var decoded = new Array(ifds.length);
|
||
|
||
function decodePage(i) {
|
||
if (decoded[i]) return decoded[i];
|
||
var ifd = ifds[i];
|
||
window.UTIF.decodeImage(arrayBuffer, ifd);
|
||
var rgba = window.UTIF.toRGBA8(ifd);
|
||
decoded[i] = { rgba: rgba, w: ifd.width, h: ifd.height };
|
||
return decoded[i];
|
||
}
|
||
|
||
function applyZoom() {
|
||
var page = decoded[currentPage];
|
||
if (!page) return;
|
||
var availW = viewport.clientWidth - 32; // padding
|
||
var availH = viewport.clientHeight - 32;
|
||
var scale;
|
||
if (fitMode === 'width') {
|
||
scale = availW / page.w;
|
||
} else if (fitMode === 'page') {
|
||
scale = Math.min(availW / page.w, availH / page.h);
|
||
} else {
|
||
scale = zoom;
|
||
}
|
||
if (!isFinite(scale) || scale <= 0) scale = 1;
|
||
canvas.style.width = (page.w * scale) + 'px';
|
||
canvas.style.height = (page.h * scale) + 'px';
|
||
}
|
||
|
||
function renderPage() {
|
||
var page;
|
||
try {
|
||
page = decodePage(currentPage);
|
||
} catch (e) {
|
||
container.innerHTML = '<div class="tiff-error">Failed to decode page '
|
||
+ (currentPage + 1) + ': ' + escapeHtml(e.message || e) + '</div>';
|
||
return;
|
||
}
|
||
canvas.width = page.w;
|
||
canvas.height = page.h;
|
||
var ctx = canvas.getContext('2d');
|
||
var imgData = ctx.createImageData(page.w, page.h);
|
||
imgData.data.set(page.rgba);
|
||
ctx.putImageData(imgData, 0, 0);
|
||
applyZoom();
|
||
pageInput.value = String(currentPage + 1);
|
||
btnPrev.disabled = currentPage <= 0;
|
||
btnNext.disabled = currentPage >= ifds.length - 1;
|
||
}
|
||
|
||
function setZoomFromSelect() {
|
||
var v = zoomSelect.value;
|
||
if (v === 'fit-width') { fitMode = 'width'; }
|
||
else if (v === 'fit-page') { fitMode = 'page'; }
|
||
else { fitMode = null; zoom = parseFloat(v) || 1; }
|
||
applyZoom();
|
||
}
|
||
|
||
function nudgeZoom(factor) {
|
||
if (fitMode) {
|
||
// capture current effective scale before leaving fit mode
|
||
var page = decoded[currentPage];
|
||
if (page) {
|
||
var availW = viewport.clientWidth - 32;
|
||
var availH = viewport.clientHeight - 32;
|
||
zoom = fitMode === 'width'
|
||
? availW / page.w
|
||
: Math.min(availW / page.w, availH / page.h);
|
||
} else {
|
||
zoom = 1;
|
||
}
|
||
fitMode = null;
|
||
}
|
||
zoom = Math.max(0.1, Math.min(8, zoom * factor));
|
||
// Match select option if any are close, else show as percent
|
||
var matched = false;
|
||
for (var i = 0; i < zoomSelect.options.length; i++) {
|
||
var ov = zoomSelect.options[i].value;
|
||
if (ov !== 'fit-width' && ov !== 'fit-page' && Math.abs(parseFloat(ov) - zoom) < 0.001) {
|
||
zoomSelect.value = ov; matched = true; break;
|
||
}
|
||
}
|
||
if (!matched) {
|
||
// Nearest standard step
|
||
var best = '1', bestDiff = Infinity;
|
||
for (var j = 0; j < zoomSelect.options.length; j++) {
|
||
var v2 = zoomSelect.options[j].value;
|
||
if (v2 === 'fit-width' || v2 === 'fit-page') continue;
|
||
var diff = Math.abs(parseFloat(v2) - zoom);
|
||
if (diff < bestDiff) { bestDiff = diff; best = v2; }
|
||
}
|
||
zoom = parseFloat(best);
|
||
zoomSelect.value = best;
|
||
}
|
||
applyZoom();
|
||
}
|
||
|
||
btnPrev.addEventListener('click', function () {
|
||
if (currentPage > 0) { currentPage--; renderPage(); }
|
||
});
|
||
btnNext.addEventListener('click', function () {
|
||
if (currentPage < ifds.length - 1) { currentPage++; renderPage(); }
|
||
});
|
||
pageInput.addEventListener('change', function () {
|
||
var n = parseInt(pageInput.value, 10);
|
||
if (!isNaN(n) && n >= 1 && n <= ifds.length) {
|
||
currentPage = n - 1;
|
||
renderPage();
|
||
} else {
|
||
pageInput.value = String(currentPage + 1);
|
||
}
|
||
});
|
||
zoomSelect.addEventListener('change', setZoomFromSelect);
|
||
btnZoomIn.addEventListener('click', function () { nudgeZoom(1.25); });
|
||
btnZoomOut.addEventListener('click', function () { nudgeZoom(1 / 1.25); });
|
||
|
||
// Keyboard nav (only when toolbar/viewport in focus path)
|
||
container.tabIndex = 0;
|
||
container.addEventListener('keydown', function (e) {
|
||
if (e.target === pageInput) return;
|
||
if (e.key === 'ArrowLeft' || e.key === 'PageUp') {
|
||
if (currentPage > 0) { currentPage--; renderPage(); e.preventDefault(); }
|
||
} else if (e.key === 'ArrowRight' || e.key === 'PageDown' || e.key === ' ') {
|
||
if (currentPage < ifds.length - 1) { currentPage++; renderPage(); e.preventDefault(); }
|
||
}
|
||
});
|
||
|
||
// Re-fit on viewport resize
|
||
if (typeof (doc.defaultView && doc.defaultView.ResizeObserver) === 'function') {
|
||
var ro = new doc.defaultView.ResizeObserver(function () { applyZoom(); });
|
||
ro.observe(viewport);
|
||
} else if (doc.defaultView) {
|
||
doc.defaultView.addEventListener('resize', function () { applyZoom(); });
|
||
}
|
||
|
||
renderPage();
|
||
});
|
||
}
|
||
|
||
// ── ZIP listing renderer ─────────────────────────────────────────────────
|
||
|
||
var ZIP_CSS =
|
||
'.zip-header{padding:.4rem .8rem;background:#f5f5f5;border-bottom:1px solid #ddd;' +
|
||
'font-size:.85rem;color:#444;}' +
|
||
'.zip-table-wrap{flex:1;overflow:auto;}' +
|
||
'.zip-table{width:100%;border-collapse:collapse;font-size:.85rem;font-family:' +
|
||
'-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;}' +
|
||
'.zip-table thead th{position:sticky;top:0;background:#f0f0f0;text-align:left;' +
|
||
'padding:.4rem .6rem;border-bottom:1px solid #ccc;cursor:pointer;user-select:none;' +
|
||
'font-weight:600;}' +
|
||
'.zip-table thead th:hover{background:#e6e6e6;}' +
|
||
'.zip-table thead th.zip-sort-asc::after{content:" ▲";font-size:.7rem;color:#888;}' +
|
||
'.zip-table thead th.zip-sort-desc::after{content:" ▼";font-size:.7rem;color:#888;}' +
|
||
'.zip-table tbody td{padding:.3rem .6rem;border-bottom:1px solid #eee;}' +
|
||
'.zip-table tbody tr:hover{background:#f6faff;}' +
|
||
'.zip-table .zip-folder{color:#888;}' +
|
||
'.zip-table .zip-name{color:#222;}' +
|
||
'.zip-table .zip-size,.zip-table .zip-date{font-variant-numeric:tabular-nums;' +
|
||
'white-space:nowrap;color:#555;}' +
|
||
'.zip-table .zip-col-size,.zip-table .zip-col-date{text-align:right;}' +
|
||
'.zip-empty{padding:2rem;text-align:center;color:#888;}';
|
||
|
||
function renderZipListing(doc, container, arrayBuffer, opts) {
|
||
opts = opts || {};
|
||
injectStyles(doc, 'zddc-zip-styles', ZIP_CSS);
|
||
|
||
return loadLibrary('https://cdn.jsdelivr.net/npm/jszip@3/dist/jszip.min.js').then(function () {
|
||
return window.JSZip.loadAsync(arrayBuffer);
|
||
}).then(function (zip) {
|
||
var entries = [];
|
||
zip.forEach(function (relativePath, zipEntry) {
|
||
if (zipEntry.dir) return;
|
||
var size = (zipEntry._data && zipEntry._data.uncompressedSize) || 0;
|
||
entries.push({
|
||
path: relativePath,
|
||
name: relativePath.split('/').pop(),
|
||
size: size,
|
||
modified: zipEntry.date instanceof Date ? zipEntry.date : null
|
||
});
|
||
});
|
||
|
||
container.innerHTML = '';
|
||
container.style.display = 'flex';
|
||
container.style.flexDirection = 'column';
|
||
container.style.minHeight = '0';
|
||
container.style.height = '100%';
|
||
container.style.overflow = 'hidden';
|
||
|
||
var totalSize = entries.reduce(function (s, e) { return s + e.size; }, 0);
|
||
|
||
var header = doc.createElement('div');
|
||
header.className = 'zip-header';
|
||
header.textContent = entries.length + ' file' + (entries.length === 1 ? '' : 's')
|
||
+ (totalSize ? ' · ' + formatSize(totalSize) + ' uncompressed' : '');
|
||
container.appendChild(header);
|
||
|
||
if (!entries.length) {
|
||
var empty = doc.createElement('div');
|
||
empty.className = 'zip-empty';
|
||
empty.textContent = '(empty archive)';
|
||
container.appendChild(empty);
|
||
return;
|
||
}
|
||
|
||
var wrap = doc.createElement('div');
|
||
wrap.className = 'zip-table-wrap';
|
||
|
||
var table = doc.createElement('table');
|
||
table.className = 'zip-table';
|
||
var thead = doc.createElement('thead');
|
||
var trh = doc.createElement('tr');
|
||
var cols = [
|
||
{ key: 'path', label: 'Name', cls: 'zip-col-name' },
|
||
{ key: 'size', label: 'Size', cls: 'zip-col-size' },
|
||
{ key: 'modified', label: 'Modified', cls: 'zip-col-date' }
|
||
];
|
||
cols.forEach(function (c) {
|
||
var th = doc.createElement('th');
|
||
th.className = c.cls;
|
||
th.dataset.key = c.key;
|
||
th.textContent = c.label;
|
||
trh.appendChild(th);
|
||
});
|
||
thead.appendChild(trh);
|
||
table.appendChild(thead);
|
||
|
||
var tbody = doc.createElement('tbody');
|
||
table.appendChild(tbody);
|
||
|
||
wrap.appendChild(table);
|
||
container.appendChild(wrap);
|
||
|
||
var sortKey = 'path';
|
||
var sortDir = 1;
|
||
|
||
function render() {
|
||
var sorted = entries.slice().sort(function (a, b) {
|
||
var av, bv;
|
||
if (sortKey === 'size') { av = a.size; bv = b.size; }
|
||
else if (sortKey === 'modified') {
|
||
av = a.modified ? a.modified.getTime() : 0;
|
||
bv = b.modified ? b.modified.getTime() : 0;
|
||
} else {
|
||
av = a.path.toLowerCase(); bv = b.path.toLowerCase();
|
||
}
|
||
if (av < bv) return -1 * sortDir;
|
||
if (av > bv) return 1 * sortDir;
|
||
return 0;
|
||
});
|
||
|
||
tbody.innerHTML = '';
|
||
sorted.forEach(function (e) {
|
||
var tr = doc.createElement('tr');
|
||
var td1 = doc.createElement('td');
|
||
var slash = e.path.lastIndexOf('/');
|
||
if (slash >= 0) {
|
||
var folder = doc.createElement('span');
|
||
folder.className = 'zip-folder';
|
||
folder.textContent = e.path.substring(0, slash + 1);
|
||
td1.appendChild(folder);
|
||
}
|
||
var name = doc.createElement('span');
|
||
name.className = 'zip-name';
|
||
name.textContent = e.name;
|
||
td1.appendChild(name);
|
||
|
||
var td2 = doc.createElement('td');
|
||
td2.className = 'zip-size';
|
||
td2.textContent = formatSize(e.size);
|
||
|
||
var td3 = doc.createElement('td');
|
||
td3.className = 'zip-date';
|
||
td3.textContent = formatDate(e.modified);
|
||
|
||
tr.appendChild(td1); tr.appendChild(td2); tr.appendChild(td3);
|
||
tbody.appendChild(tr);
|
||
});
|
||
|
||
// Update sort arrows
|
||
var ths = thead.querySelectorAll('th');
|
||
for (var i = 0; i < ths.length; i++) {
|
||
ths[i].classList.remove('zip-sort-asc', 'zip-sort-desc');
|
||
if (ths[i].dataset.key === sortKey) {
|
||
ths[i].classList.add(sortDir > 0 ? 'zip-sort-asc' : 'zip-sort-desc');
|
||
}
|
||
}
|
||
}
|
||
|
||
thead.querySelectorAll('th').forEach(function (th) {
|
||
th.addEventListener('click', function () {
|
||
var k = th.dataset.key;
|
||
if (sortKey === k) sortDir = -sortDir;
|
||
else { sortKey = k; sortDir = 1; }
|
||
render();
|
||
});
|
||
});
|
||
|
||
render();
|
||
}).catch(function (err) {
|
||
container.innerHTML = '<div class="zip-empty">Failed to read ZIP: '
|
||
+ escapeHtml(err.message || err) + '</div>';
|
||
});
|
||
}
|
||
|
||
// ── Public API ───────────────────────────────────────────────────────────
|
||
|
||
if (!root.zddc) root.zddc = {};
|
||
root.zddc.preview = {
|
||
TIFF_EXTENSIONS: TIFF_EXTENSIONS,
|
||
IMAGE_EXTENSIONS: IMAGE_EXTENSIONS,
|
||
TEXT_EXTENSIONS: TEXT_EXTENSIONS,
|
||
OFFICE_EXTENSIONS: OFFICE_EXTENSIONS,
|
||
isTiff: isTiff,
|
||
isImage: isImage,
|
||
isText: isText,
|
||
isZip: isZip,
|
||
isOffice: isOffice,
|
||
loadLibrary: loadLibrary,
|
||
renderTiff: renderTiff,
|
||
renderZipListing: renderZipListing,
|
||
formatSize: formatSize,
|
||
formatDate: formatDate
|
||
};
|
||
})(typeof window !== 'undefined' ? window : this);
|
||
|
||
/**
|
||
* ZDDC Archive - Initialization
|
||
* Sets up window.app and window.app.modules before other modules run.
|
||
* Must be the first JS file in the build.
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
window.app = {
|
||
sourceMode: null,
|
||
directories: [],
|
||
groupingFolders: [],
|
||
transmittalFolders: [],
|
||
files: [],
|
||
filteredFiles: [],
|
||
selectedFiles: new Set(),
|
||
isScanning: false,
|
||
scanProgress: '',
|
||
|
||
columnFilters: { trackingNumber: '', title: '', revisions: '' },
|
||
columnFilterASTs: { trackingNumber: null, title: null, revisions: null },
|
||
groupingFilter: '',
|
||
transmittalFilter: '',
|
||
enabledFolderTypes: new Set(['issued', 'received']),
|
||
sortField: 'trackingNumber',
|
||
sortDirection: 'asc',
|
||
selectedGroupingFolders: new Set(),
|
||
selectedTransmittalFolders: new Set(),
|
||
collapsedDateGroups: new Set(),
|
||
collapsedGroupingFolders: new Set(),
|
||
selectAllGroupingFolders: true,
|
||
selectAllTransmittals: true,
|
||
availableModifiers: new Set(),
|
||
selectedModifiers: new Set(),
|
||
showSelectedOnly: false,
|
||
projectFilter: new Set(), // URL-derived; controls what gets SCANNED
|
||
visibleProjects: new Set(), // dropdown-derived; controls VISIBILITY of already-scanned data
|
||
availableProjects: [], // populated by autoConnectHttpSource from server's ProjectInfo[]
|
||
isMultiProject: false, // true when ?projects= is set OR server returns ProjectInfo
|
||
FOLDER_TYPE_NAMES: ['issued', 'received', 'mdl', 'incoming'],
|
||
ARCHIVE_STAGE_NAME: 'archive',
|
||
modules: {}
|
||
};
|
||
|
||
})();
|
||
|
||
// Archive grouping/sorting helpers — ZDDC parsing comes from window.zddc directly.
|
||
(function() {
|
||
'use strict';
|
||
|
||
function isTransmittalFolder(name) {
|
||
var parsed = zddc.parseFolder(name);
|
||
return !!(parsed && parsed.valid);
|
||
}
|
||
|
||
function groupFilesByTrackingNumber(files) {
|
||
const groups = {};
|
||
files.forEach(file => {
|
||
if (!file.trackingNumber) return;
|
||
if (!groups[file.trackingNumber]) {
|
||
groups[file.trackingNumber] = { trackingNumber: file.trackingNumber, title: file.title, revisions: {} };
|
||
}
|
||
if (file.title.length > groups[file.trackingNumber].title.length) {
|
||
groups[file.trackingNumber].title = file.title;
|
||
}
|
||
const revKey = `${file.revision}_${file.status}`;
|
||
if (!groups[file.trackingNumber].revisions[revKey]) {
|
||
groups[file.trackingNumber].revisions[revKey] = {
|
||
revision: file.revision, status: file.status, title: file.title,
|
||
hasModifier: file.revision.includes('+'), files: []
|
||
};
|
||
}
|
||
if (file.title.length > groups[file.trackingNumber].revisions[revKey].title.length) {
|
||
groups[file.trackingNumber].revisions[revKey].title = file.title;
|
||
}
|
||
groups[file.trackingNumber].revisions[revKey].files.push(file);
|
||
});
|
||
return groups;
|
||
}
|
||
|
||
function sortGroupedFiles(groups) {
|
||
const field = window.app.sortField || 'trackingNumber';
|
||
const direction = window.app.sortDirection === 'desc' ? -1 : 1;
|
||
const sorted = Object.values(groups).sort((a, b) => {
|
||
let comparison = 0;
|
||
if (field === 'trackingNumber') comparison = a.trackingNumber.localeCompare(b.trackingNumber);
|
||
else if (field === 'title') comparison = a.title.localeCompare(b.title);
|
||
else if (field === 'revisions') {
|
||
const aRevs = Object.keys(a.revisions), bRevs = Object.keys(b.revisions);
|
||
comparison = zddc.compareRevisions(
|
||
aRevs.length > 0 ? aRevs[aRevs.length - 1] : '',
|
||
bRevs.length > 0 ? bRevs[bRevs.length - 1] : ''
|
||
);
|
||
}
|
||
return comparison * direction;
|
||
});
|
||
sorted.forEach(group => {
|
||
const revisions = Object.values(group.revisions);
|
||
revisions.sort((a, b) => zddc.compareRevisions(b.revision, a.revision));
|
||
group.sortedRevisions = revisions;
|
||
});
|
||
return sorted;
|
||
}
|
||
|
||
window.app.modules.parser = {
|
||
isTransmittalFolder,
|
||
groupFilesByTrackingNumber,
|
||
sortGroupedFiles,
|
||
};
|
||
|
||
})();
|
||
|
||
(function() {
|
||
'use strict';
|
||
// Source abstraction — local (File System Access API) and HTTP (Caddy JSON browse)
|
||
//
|
||
// Three scan modes, decided once at the entry point:
|
||
// 1. Multi-project — ?projects=A,B URL param non-empty. The scan root holds project
|
||
// folders; for each in the filter, descend into its `archive/` subfolder (case-
|
||
// insensitive) and scan from there.
|
||
// 2. Project-root — scan root has an `archive/` child (case-insensitive). Descend
|
||
// into it and scan from there. Other stage folders (reviewing/staging/mdl/working)
|
||
// are not entered.
|
||
// 3. In-archive (default) — scan root's children are third-party (grouping) folders.
|
||
// Today's behavior, unchanged.
|
||
//
|
||
// The recursion below the entry point never re-applies the mode check: once we are
|
||
// inside the archive folder for a given project, descent is uniform across modes.
|
||
//
|
||
// Listing skip: at any depth, a directory child whose lowercased name is a member of
|
||
// FOLDER_TYPE_NAMES (issued/received/mdl/incoming) AND not currently in
|
||
// enabledFolderTypes is skipped entirely — we do not even fetch its listing. Toggling
|
||
// a type back on triggers a refresh in app.js.
|
||
|
||
// Shared utility used by both source implementations
|
||
function getDisplayPath(fullPath) {
|
||
if (fullPath.length <= 100) {
|
||
return fullPath;
|
||
}
|
||
const parts = fullPath.split('/');
|
||
if (parts.length > 3) {
|
||
return parts[0] + '/.../' + parts.slice(-2).join('/');
|
||
}
|
||
return '...' + fullPath.substring(fullPath.length - 80);
|
||
}
|
||
|
||
// True if a directory child should be skipped entirely (don't fetch its listing).
|
||
function isHiddenFolderTypeName(rawName) {
|
||
const lower = rawName.toLowerCase();
|
||
return window.app.FOLDER_TYPE_NAMES.includes(lower)
|
||
&& !window.app.enabledFolderTypes.has(lower);
|
||
}
|
||
|
||
// createSource(type, options) returns a source object:
|
||
// source.type — 'local' | 'http'
|
||
// source.canWrite — boolean
|
||
// source.scan(rootIdentifier, callbacks) — Promise; walks tree calling:
|
||
// callbacks.onGroupingFolder(folder) folder: { name, path, displayPath, handle? }
|
||
// callbacks.onTransmittalFolder(folder) folder: { name, path, displayPath, handle?, url? }
|
||
// callbacks.onFile(file) file: full file object (parsed + metadata)
|
||
// callbacks.onProgress(message)
|
||
// source.fetchFile(fileRef) — Promise<ArrayBuffer>
|
||
|
||
function createSource(type, options) {
|
||
if (type === 'local') {
|
||
return createLocalSource();
|
||
} else if (type === 'http') {
|
||
return createHttpSource(options.baseUrl);
|
||
}
|
||
throw new Error('Unknown source type: ' + type);
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Local source — wraps File System Access API
|
||
// ---------------------------------------------------------------------------
|
||
|
||
function createLocalSource() {
|
||
return {
|
||
type: 'local',
|
||
canWrite: true,
|
||
|
||
scan: function(dirHandle, callbacks) {
|
||
return scanLocalRoot(dirHandle, dirHandle.name, callbacks);
|
||
},
|
||
|
||
fetchFile: function(fileRef) {
|
||
return fileRef.handle.getFile().then(function(f) {
|
||
return f.arrayBuffer();
|
||
});
|
||
}
|
||
};
|
||
}
|
||
|
||
async function listLocalEntries(dirHandle, currentPath) {
|
||
const entries = [];
|
||
try {
|
||
for await (const entry of dirHandle.values()) {
|
||
entries.push(entry);
|
||
}
|
||
} catch (err) {
|
||
console.warn('Could not read directory ' + currentPath + ':', err);
|
||
return null;
|
||
}
|
||
return entries;
|
||
}
|
||
|
||
function findArchiveInEntries(entries) {
|
||
const stage = window.app.ARCHIVE_STAGE_NAME;
|
||
for (const entry of entries) {
|
||
if (entry.kind === 'directory' && entry.name.toLowerCase() === stage) {
|
||
return entry;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
async function scanLocalRoot(dirHandle, rootPath, callbacks) {
|
||
callbacks.onProgress('Scanning ' + rootPath + '...');
|
||
|
||
const entries = await listLocalEntries(dirHandle, rootPath);
|
||
if (!entries) return;
|
||
|
||
// Mode 1 — multi-project (?projects= set)
|
||
if (window.app.projectFilter && window.app.projectFilter.size > 0) {
|
||
for (const entry of entries) {
|
||
if (entry.kind !== 'directory') continue;
|
||
if (!window.app.projectFilter.has(entry.name)) continue;
|
||
const projPath = rootPath + '/' + entry.name;
|
||
const projEntries = await listLocalEntries(entry, projPath);
|
||
if (!projEntries) continue;
|
||
const archive = findArchiveInEntries(projEntries);
|
||
if (!archive) continue;
|
||
const archivePath = projPath + '/' + archive.name;
|
||
await scanLocalRecursive(archive, archivePath, 0, callbacks);
|
||
}
|
||
return;
|
||
}
|
||
|
||
// Mode 2 — project-root (scan root has an archive/ child)
|
||
const archive = findArchiveInEntries(entries);
|
||
if (archive) {
|
||
const archivePath = rootPath + '/' + archive.name;
|
||
await scanLocalRecursive(archive, archivePath, 0, callbacks);
|
||
return;
|
||
}
|
||
|
||
// Mode 3 — in-archive (default)
|
||
await processLocalEntries(entries, rootPath, 0, callbacks);
|
||
}
|
||
|
||
async function scanLocalRecursive(dirHandle, currentPath, depth, callbacks) {
|
||
if (currentPath.length > 200) {
|
||
console.warn('Path too long, skipping deeper scan: ' + currentPath);
|
||
return;
|
||
}
|
||
if (depth > 10) {
|
||
console.warn('Directory depth limit reached at: ' + currentPath);
|
||
return;
|
||
}
|
||
|
||
callbacks.onProgress('Scanning ' + currentPath + '...');
|
||
|
||
const entries = await listLocalEntries(dirHandle, currentPath);
|
||
if (!entries) return;
|
||
|
||
await processLocalEntries(entries, currentPath, depth, callbacks);
|
||
}
|
||
|
||
async function processLocalEntries(entries, currentPath, depth, callbacks) {
|
||
for (const entry of entries) {
|
||
if (entry.kind === 'directory') {
|
||
if (isHiddenFolderTypeName(entry.name)) continue;
|
||
|
||
const subPath = currentPath + '/' + entry.name;
|
||
try {
|
||
if (window.app.modules.parser.isTransmittalFolder(entry.name)) {
|
||
const folder = {
|
||
name: entry.name,
|
||
path: subPath,
|
||
displayPath: getDisplayPath(subPath),
|
||
handle: entry
|
||
};
|
||
callbacks.onTransmittalFolder(folder);
|
||
await scanLocalTransmittalFolder(entry, subPath, 0, subPath, callbacks);
|
||
} else {
|
||
const folder = {
|
||
name: entry.name,
|
||
path: subPath,
|
||
displayPath: entry.name,
|
||
handle: entry
|
||
};
|
||
callbacks.onGroupingFolder(folder);
|
||
await scanLocalRecursive(entry, subPath, depth + 1, callbacks);
|
||
}
|
||
} catch (err) {
|
||
console.warn('Could not process directory ' + entry.name + ':', err);
|
||
}
|
||
} else if (entry.kind === 'file') {
|
||
// File directly in a grouping folder — assign to the Outstanding virtual transmittal.
|
||
// actualPath records the real containing folder for grouping-folder-scoped filtering.
|
||
try {
|
||
const file = await entry.getFile();
|
||
const parsed = zddc.parseFilename(file.name) || {};
|
||
const fullPath = currentPath + '/' + file.name;
|
||
const displayPath = fullPath.length > 250
|
||
? '...' + fullPath.substring(fullPath.length - 200)
|
||
: fullPath;
|
||
|
||
callbacks.onFile({
|
||
id: crypto.randomUUID(),
|
||
name: file.name,
|
||
path: fullPath,
|
||
displayPath: displayPath,
|
||
url: null,
|
||
size: file.size,
|
||
modified: file.lastModified,
|
||
handle: entry,
|
||
folderPath: '__outstanding__',
|
||
actualPath: currentPath,
|
||
hasPathError: false,
|
||
...parsed
|
||
});
|
||
} catch (fileErr) {
|
||
const fullPath = currentPath + '/' + entry.name;
|
||
const displayPath = fullPath.length > 250
|
||
? '...' + fullPath.substring(fullPath.length - 200)
|
||
: fullPath;
|
||
const parsed = zddc.parseFilename(entry.name) || {};
|
||
|
||
callbacks.onFile({
|
||
id: crypto.randomUUID(),
|
||
name: entry.name,
|
||
path: fullPath,
|
||
displayPath: displayPath,
|
||
url: null,
|
||
size: null,
|
||
modified: null,
|
||
handle: null,
|
||
folderPath: '__outstanding__',
|
||
actualPath: currentPath,
|
||
hasPathError: true,
|
||
pathErrorMessage: fileErr.message || 'File access error',
|
||
...parsed
|
||
});
|
||
|
||
console.warn('Could not access file ' + entry.name + ' (path error):', fileErr.message);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
async function scanLocalTransmittalFolder(dirHandle, folderPath, depth, transmittalPath, callbacks) {
|
||
if (depth > 10) {
|
||
console.warn('Directory depth limit reached in transmittal folder: ' + folderPath);
|
||
return;
|
||
}
|
||
|
||
try {
|
||
if (folderPath.length > 240) {
|
||
console.warn('Path approaching Windows limit (' + folderPath.length + ' chars): ' + folderPath);
|
||
}
|
||
|
||
for await (const entry of dirHandle.values()) {
|
||
if (entry.kind === 'file') {
|
||
try {
|
||
const file = await entry.getFile();
|
||
const parsed = zddc.parseFilename(file.name) || {};
|
||
const fullPath = folderPath + '/' + file.name;
|
||
const displayPath = fullPath.length > 250
|
||
? '...' + fullPath.substring(fullPath.length - 200)
|
||
: fullPath;
|
||
|
||
callbacks.onFile({
|
||
id: crypto.randomUUID(),
|
||
name: file.name,
|
||
path: fullPath,
|
||
displayPath: displayPath,
|
||
url: null,
|
||
size: file.size,
|
||
modified: file.lastModified,
|
||
handle: entry,
|
||
folderPath: transmittalPath,
|
||
actualPath: folderPath,
|
||
hasPathError: false,
|
||
...parsed
|
||
});
|
||
} catch (fileErr) {
|
||
const fullPath = folderPath + '/' + entry.name;
|
||
const displayPath = fullPath.length > 250
|
||
? '...' + fullPath.substring(fullPath.length - 200)
|
||
: fullPath;
|
||
const parsed = zddc.parseFilename(entry.name) || {};
|
||
|
||
callbacks.onFile({
|
||
id: crypto.randomUUID(),
|
||
name: entry.name,
|
||
path: fullPath,
|
||
displayPath: displayPath,
|
||
url: null,
|
||
size: null,
|
||
modified: null,
|
||
handle: null,
|
||
folderPath: transmittalPath,
|
||
actualPath: folderPath,
|
||
hasPathError: true,
|
||
pathErrorMessage: fileErr.message || 'File access error',
|
||
...parsed
|
||
});
|
||
|
||
console.warn('Could not access file ' + entry.name + ' (path error):', fileErr.message);
|
||
}
|
||
} else if (entry.kind === 'directory') {
|
||
const subPath = folderPath + '/' + entry.name;
|
||
try {
|
||
await scanLocalTransmittalFolder(entry, subPath, depth + 1, transmittalPath, callbacks);
|
||
} catch (err) {
|
||
console.warn('Could not scan subdirectory ' + entry.name + ' in ' + folderPath + ':', err);
|
||
}
|
||
}
|
||
}
|
||
} catch (err) {
|
||
console.error('Error scanning folder ' + folderPath + ':', err);
|
||
}
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// HTTP source — uses Caddy JSON browse (Accept: application/json)
|
||
// ---------------------------------------------------------------------------
|
||
|
||
function createHttpSource(baseUrl) {
|
||
// Normalise: ensure baseUrl ends with /
|
||
const root = baseUrl.endsWith('/') ? baseUrl : baseUrl + '/';
|
||
|
||
return {
|
||
type: 'http',
|
||
canWrite: false,
|
||
|
||
scan: function(rootUrl, callbacks) {
|
||
const scanRoot = (rootUrl && rootUrl !== root) ? rootUrl : root;
|
||
return scanHttpRoot(scanRoot, root, callbacks);
|
||
},
|
||
|
||
fetchFile: function(fileRef) {
|
||
return fetch(fileRef.url).then(function(r) {
|
||
if (!r.ok) throw new Error('HTTP ' + r.status + ' fetching ' + fileRef.url);
|
||
return r.arrayBuffer();
|
||
});
|
||
}
|
||
};
|
||
}
|
||
|
||
async function fetchHttpListing(dirUrl) {
|
||
try {
|
||
const resp = await fetch(dirUrl, {
|
||
headers: { 'Accept': 'application/json' }
|
||
});
|
||
if (!resp.ok) {
|
||
// 403/404 on a sub-path is expected when ACLs deny access or a
|
||
// listed dir doesn't exist — log at info level to avoid alarming
|
||
// users in the console.
|
||
console.info('skip ' + dirUrl + ' (' + resp.status + ')');
|
||
return null;
|
||
}
|
||
const items = await resp.json();
|
||
if (!Array.isArray(items)) {
|
||
// Server returned 200 but the body wasn't a JSON array — most
|
||
// commonly Caddy serving an HTML error page or an index.html
|
||
// when file_browse isn't enabled at that path. Silent skip.
|
||
return null;
|
||
}
|
||
return items;
|
||
} catch (err) {
|
||
// JSON parse failures, network errors, etc. — single concise line.
|
||
console.info('skip ' + dirUrl + ': ' + (err.message || err));
|
||
return null;
|
||
}
|
||
}
|
||
|
||
function rawNameOf(item) {
|
||
return item.name.endsWith('/') ? item.name.slice(0, -1) : item.name;
|
||
}
|
||
|
||
function findArchiveInItems(items) {
|
||
const stage = window.app.ARCHIVE_STAGE_NAME;
|
||
for (const item of items) {
|
||
if (!item.is_dir) continue;
|
||
const name = rawNameOf(item);
|
||
if (name.toLowerCase() === stage) return { item: item, name: name };
|
||
}
|
||
return null;
|
||
}
|
||
|
||
async function scanHttpRoot(scanRootUrl, rootUrl, callbacks) {
|
||
// Mode 1 — multi-project (?projects= set). Skip listing scanRootUrl entirely:
|
||
// the zddc-server returns a ProjectInfo array there (not a Caddy fileInfo
|
||
// listing), so iterating it as if it were a directory listing wouldn't work.
|
||
// Project URLs are deterministic — go straight to each one.
|
||
if (window.app.projectFilter && window.app.projectFilter.size > 0) {
|
||
const tasks = [];
|
||
for (const name of window.app.projectFilter) {
|
||
if (!name || name.startsWith('.')) continue;
|
||
const projUrl = resolveHttpUrl(scanRootUrl, name, true);
|
||
tasks.push((async () => {
|
||
const projItems = await fetchHttpListing(projUrl);
|
||
if (!projItems) return;
|
||
const found = findArchiveInItems(projItems);
|
||
if (!found) return;
|
||
const archiveUrl = resolveHttpUrl(projUrl, found.name, true);
|
||
await scanHttpRecursive(archiveUrl, rootUrl, 0, null, callbacks);
|
||
})());
|
||
}
|
||
await Promise.all(tasks);
|
||
return;
|
||
}
|
||
|
||
const items = await fetchHttpListing(scanRootUrl);
|
||
if (!items) return;
|
||
|
||
// Mode 2 — project-root (scan root has archive/ child)
|
||
const found = findArchiveInItems(items);
|
||
if (found) {
|
||
const archiveUrl = resolveHttpUrl(scanRootUrl, found.name, true);
|
||
await scanHttpRecursive(archiveUrl, rootUrl, 0, null, callbacks);
|
||
return;
|
||
}
|
||
|
||
// Mode 3 — in-archive (default)
|
||
await processHttpItems(items, scanRootUrl, rootUrl, 0, null, callbacks);
|
||
}
|
||
|
||
async function scanHttpRecursive(dirUrl, rootUrl, depth, transmittalPath, callbacks) {
|
||
if (depth > 10) {
|
||
console.warn('HTTP directory depth limit reached at: ' + dirUrl);
|
||
return;
|
||
}
|
||
|
||
const items = await fetchHttpListing(dirUrl);
|
||
if (!items) return;
|
||
|
||
await processHttpItems(items, dirUrl, rootUrl, depth, transmittalPath, callbacks);
|
||
}
|
||
|
||
async function processHttpItems(items, dirUrl, rootUrl, depth, transmittalPath, callbacks) {
|
||
// Collect subdirectory scan promises so siblings run in parallel
|
||
const subdirPromises = [];
|
||
|
||
for (const item of items) {
|
||
// Caddy appends "/" to directory names; strip it to get the bare name for matching
|
||
const rawName = rawNameOf(item);
|
||
|
||
// Skip hidden files
|
||
if (rawName.startsWith('.')) continue;
|
||
|
||
const isDir = item.is_dir === true;
|
||
const itemUrl = resolveHttpUrl(dirUrl, rawName, isDir);
|
||
const logicalPath = urlToLogicalPath(itemUrl, rootUrl);
|
||
|
||
if (isDir) {
|
||
// Skip listings for folder-types that are toggled off — applies at any depth.
|
||
if (transmittalPath === null && isHiddenFolderTypeName(rawName)) continue;
|
||
|
||
if (transmittalPath !== null) {
|
||
// Inside a transmittal folder — recurse into subdirectories
|
||
subdirPromises.push(
|
||
scanHttpRecursive(itemUrl, rootUrl, depth + 1, transmittalPath, callbacks)
|
||
);
|
||
} else if (window.app.modules.parser.isTransmittalFolder(rawName)) {
|
||
const folder = {
|
||
name: rawName,
|
||
path: logicalPath,
|
||
displayPath: getDisplayPath(logicalPath),
|
||
handle: null,
|
||
url: itemUrl
|
||
};
|
||
callbacks.onTransmittalFolder(folder);
|
||
subdirPromises.push(
|
||
scanHttpRecursive(itemUrl, rootUrl, depth + 1, logicalPath, callbacks)
|
||
);
|
||
} else {
|
||
const folder = {
|
||
name: rawName,
|
||
path: logicalPath,
|
||
displayPath: rawName,
|
||
handle: null,
|
||
url: itemUrl
|
||
};
|
||
callbacks.onGroupingFolder(folder);
|
||
subdirPromises.push(
|
||
scanHttpRecursive(itemUrl, rootUrl, depth + 1, null, callbacks)
|
||
);
|
||
}
|
||
} else {
|
||
// It's a file
|
||
if (transmittalPath === null) {
|
||
// File directly in a grouping folder — assign to Outstanding virtual transmittal.
|
||
// actualPath records the real containing folder for grouping-folder-scoped filtering.
|
||
const dirLogicalPath = urlToLogicalPath(dirUrl, rootUrl);
|
||
const parsed = zddc.parseFilename(rawName) || {};
|
||
const modified = item.mod_time ? new Date(item.mod_time).getTime() : null;
|
||
|
||
callbacks.onFile({
|
||
id: crypto.randomUUID(),
|
||
name: rawName,
|
||
path: logicalPath,
|
||
displayPath: logicalPath,
|
||
url: itemUrl,
|
||
size: item.size || null,
|
||
modified: modified,
|
||
handle: null,
|
||
folderPath: '__outstanding__',
|
||
actualPath: dirLogicalPath,
|
||
hasPathError: false,
|
||
...parsed
|
||
});
|
||
} else {
|
||
// Inside a transmittal folder
|
||
const parsed = zddc.parseFilename(rawName) || {};
|
||
// mod_time is an ISO 8601 string from Go's time.Time.UTC()
|
||
const modified = item.mod_time ? new Date(item.mod_time).getTime() : null;
|
||
|
||
callbacks.onFile({
|
||
id: crypto.randomUUID(),
|
||
name: rawName,
|
||
path: logicalPath,
|
||
displayPath: logicalPath,
|
||
url: itemUrl,
|
||
size: item.size || null,
|
||
modified: modified,
|
||
handle: null,
|
||
folderPath: transmittalPath,
|
||
actualPath: logicalPath.substring(0, logicalPath.lastIndexOf('/')),
|
||
hasPathError: false,
|
||
...parsed
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
// Wait for all sibling subdirectory scans to complete in parallel
|
||
if (subdirPromises.length > 0) {
|
||
await Promise.all(subdirPromises);
|
||
}
|
||
}
|
||
|
||
// Build an absolute URL for an item inside a directory listing URL
|
||
function resolveHttpUrl(dirUrl, name, isDir) {
|
||
const base = dirUrl.endsWith('/') ? dirUrl : dirUrl + '/';
|
||
const encoded = encodeURIComponent(name);
|
||
return base + encoded + (isDir ? '/' : '');
|
||
}
|
||
|
||
// Convert an absolute item URL to a logical relative path (for display / filtering)
|
||
function urlToLogicalPath(itemUrl, rootUrl) {
|
||
const root = rootUrl.endsWith('/') ? rootUrl : rootUrl + '/';
|
||
let rel = itemUrl;
|
||
if (rel.startsWith(root)) {
|
||
rel = rel.substring(root.length);
|
||
}
|
||
// Decode percent-encoding for display
|
||
try { rel = decodeURIComponent(rel); } catch (e) { /* leave encoded */ }
|
||
// Strip trailing slash for directories
|
||
if (rel.endsWith('/')) rel = rel.slice(0, -1);
|
||
return rel;
|
||
}
|
||
|
||
window.app.modules.source = {
|
||
getDisplayPath,
|
||
createSource
|
||
};
|
||
|
||
})();
|
||
|
||
(function() {
|
||
'use strict';
|
||
// SHA-256 hashing and cache management
|
||
|
||
const HASH_CACHE_FILENAME = '.hashes.json';
|
||
|
||
// Calculate SHA-256 hash for a file (delegates to shared/hash.js)
|
||
async function calculateFileHash(file) {
|
||
return zddc.crypto.sha256File(file);
|
||
}
|
||
|
||
// Load hash cache for a directory
|
||
async function loadHashCache(dirHandle) {
|
||
try {
|
||
const cacheHandle = await dirHandle.getFileHandle(HASH_CACHE_FILENAME);
|
||
const cacheFile = await cacheHandle.getFile();
|
||
const cacheText = await cacheFile.text();
|
||
return JSON.parse(cacheText);
|
||
} catch (err) {
|
||
// Cache doesn't exist or can't be read
|
||
return {};
|
||
}
|
||
}
|
||
|
||
// Save hash cache for a directory
|
||
async function saveHashCache(dirHandle, cache) {
|
||
try {
|
||
const cacheHandle = await dirHandle.getFileHandle(HASH_CACHE_FILENAME, { create: true });
|
||
const writable = await cacheHandle.createWritable();
|
||
await writable.write(JSON.stringify(cache, null, 2));
|
||
await writable.close();
|
||
return true;
|
||
} catch (err) {
|
||
console.warn('Unable to save hash cache:', err);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// Hash files in a directory with caching
|
||
async function hashDirectoryFiles(dirHandle, files) {
|
||
const cache = await loadHashCache(dirHandle);
|
||
const updatedCache = {};
|
||
const results = {};
|
||
|
||
for (const fileInfo of files) {
|
||
try {
|
||
const file = await fileInfo.handle.getFile();
|
||
const cacheKey = file.name;
|
||
const lastModified = file.lastModified;
|
||
|
||
// Check if we have a cached hash
|
||
if (cache[cacheKey] && cache[cacheKey].lastModified === lastModified) {
|
||
// Use cached hash
|
||
results[fileInfo.id] = cache[cacheKey].hash;
|
||
updatedCache[cacheKey] = cache[cacheKey];
|
||
} else {
|
||
// Calculate new hash
|
||
const hash = await calculateFileHash(file);
|
||
results[fileInfo.id] = hash;
|
||
updatedCache[cacheKey] = {
|
||
hash: hash,
|
||
lastModified: lastModified,
|
||
size: file.size
|
||
};
|
||
}
|
||
} catch (err) {
|
||
console.error(`Error hashing file ${fileInfo.name}:`, err);
|
||
}
|
||
}
|
||
|
||
// Try to save updated cache
|
||
await saveHashCache(dirHandle, updatedCache);
|
||
|
||
return results;
|
||
}
|
||
|
||
// Add hash information to files
|
||
async function addHashesToFiles() {
|
||
if (!window.app.files.length) return;
|
||
|
||
// Hash operations require local file handles — not available in HTTP mode
|
||
if (window.app.sourceMode === 'http') {
|
||
console.log('Hash operations not available in HTTP mode.');
|
||
return;
|
||
}
|
||
|
||
window.app.modules.export.showProgress('Calculating file hashes...', 0, window.app.files.length);
|
||
|
||
try {
|
||
// Group files by directory
|
||
const filesByDir = {};
|
||
window.app.files.forEach(file => {
|
||
const dirPath = file.folderPath;
|
||
if (!filesByDir[dirPath]) {
|
||
filesByDir[dirPath] = {
|
||
handle: null,
|
||
files: []
|
||
};
|
||
}
|
||
filesByDir[dirPath].files.push(file);
|
||
});
|
||
|
||
// Find directory handles
|
||
for (const dirPath in filesByDir) {
|
||
const folder = window.app.transmittalFolders.find(f => f.path === dirPath);
|
||
if (folder) {
|
||
filesByDir[dirPath].handle = folder.handle;
|
||
}
|
||
}
|
||
|
||
// Hash files in each directory
|
||
let processed = 0;
|
||
for (const dirPath in filesByDir) {
|
||
const dirInfo = filesByDir[dirPath];
|
||
if (dirInfo.handle) {
|
||
const hashes = await hashDirectoryFiles(dirInfo.handle, dirInfo.files);
|
||
|
||
// Update file objects with hashes
|
||
dirInfo.files.forEach(file => {
|
||
if (hashes[file.id]) {
|
||
file.hash = hashes[file.id];
|
||
}
|
||
});
|
||
}
|
||
|
||
processed += dirInfo.files.length;
|
||
window.app.modules.export.showProgress('Calculating file hashes...', processed, window.app.files.length);
|
||
}
|
||
|
||
window.app.modules.export.hideProgress();
|
||
|
||
} catch (err) {
|
||
window.app.modules.export.hideProgress();
|
||
console.error('Error calculating hashes:', err);
|
||
}
|
||
}
|
||
|
||
// Verify file integrity
|
||
async function verifyFileIntegrity(fileId) {
|
||
const file = window.app.files.find(f => f.id === fileId);
|
||
if (!file || !file.hash) {
|
||
alert('No hash available for this file.');
|
||
return;
|
||
}
|
||
|
||
if (!file.handle) {
|
||
alert('File integrity verification is not available in HTTP mode.');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const fileData = await file.handle.getFile();
|
||
const currentHash = await calculateFileHash(fileData);
|
||
|
||
if (currentHash === file.hash) {
|
||
alert('File integrity verified. Hash matches.');
|
||
} else {
|
||
alert('WARNING: File has been modified! Hash does not match.');
|
||
}
|
||
} catch (err) {
|
||
alert('Error verifying file: ' + err.message);
|
||
}
|
||
}
|
||
|
||
// Export hash report
|
||
function exportHashReport() {
|
||
const headers = ['File Path', 'SHA-256 Hash', 'Size', 'Last Modified'];
|
||
const rows = [headers];
|
||
|
||
window.app.filteredFiles.forEach(file => {
|
||
if (file.hash) {
|
||
rows.push([
|
||
file.path,
|
||
file.hash,
|
||
window.app.modules.export.formatFileSize(file.size),
|
||
new Date(file.modified).toISOString()
|
||
]);
|
||
}
|
||
});
|
||
|
||
window.app.modules.export.downloadFile(window.app.modules.export.rowsToCSV(rows), 'file-hashes.csv', 'text/csv');
|
||
}
|
||
|
||
window.app.modules.hash = {
|
||
calculateFileHash,
|
||
loadHashCache,
|
||
saveHashCache,
|
||
hashDirectoryFiles,
|
||
addHashesToFiles,
|
||
verifyFileIntegrity,
|
||
exportHashReport
|
||
};
|
||
|
||
})();
|
||
|
||
(function() {
|
||
'use strict';
|
||
// Drag and drop functionality
|
||
|
||
let draggedFiles = [];
|
||
let targetGroupingFolder = null;
|
||
|
||
// Setup drag and drop
|
||
function setupDragAndDrop() {
|
||
// Prevent default drag behaviors
|
||
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
||
document.addEventListener(eventName, preventDefaults, false);
|
||
});
|
||
|
||
// Highlight drop zones
|
||
['dragenter', 'dragover'].forEach(eventName => {
|
||
document.addEventListener(eventName, highlight, false);
|
||
});
|
||
|
||
['dragleave', 'drop'].forEach(eventName => {
|
||
document.addEventListener(eventName, unhighlight, false);
|
||
});
|
||
|
||
// Handle drops on grouping folders (for creating transmittals)
|
||
document.getElementById('groupingFoldersList').addEventListener('drop', handleDrop, false);
|
||
|
||
// Handle drops on the main app area (for adding directories).
|
||
// Note: the root element is id="appContainer" (id="app" was a
|
||
// stale reference that crashed dragDrop init in local mode).
|
||
document.getElementById('appContainer').addEventListener('drop', handleAppDrop, false);
|
||
}
|
||
|
||
// Prevent default behaviors
|
||
function preventDefaults(e) {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
}
|
||
|
||
// Highlight drop zone
|
||
function highlight(e) {
|
||
const folderItem = e.target.closest('.folder-item');
|
||
if (folderItem && folderItem.parentElement.id === 'groupingFoldersList') {
|
||
folderItem.classList.add('drag-over');
|
||
}
|
||
}
|
||
|
||
// Remove highlight
|
||
function unhighlight(e) {
|
||
document.querySelectorAll('.drag-over').forEach(item => {
|
||
item.classList.remove('drag-over');
|
||
});
|
||
}
|
||
|
||
// Handle directory drop on main app area (for adding directories)
|
||
async function handleAppDrop(e) {
|
||
// Check if this is a drop on a grouping folder (handled separately)
|
||
const folderItem = e.target.closest('.folder-item');
|
||
if (folderItem && folderItem.parentElement.id === 'groupingFoldersList') {
|
||
return; // Let handleDrop handle this
|
||
}
|
||
|
||
// Check if dataTransfer has directory items
|
||
const items = e.dataTransfer.items;
|
||
if (!items || items.length === 0) return;
|
||
|
||
// Process each dropped item
|
||
for (let i = 0; i < items.length; i++) {
|
||
const item = items[i];
|
||
|
||
// Check if it's a directory using the File System Access API
|
||
if (item.kind === 'file') {
|
||
const entry = await item.getAsFileSystemHandle();
|
||
|
||
if (entry && entry.kind === 'directory') {
|
||
// Check if already added
|
||
const exists = window.app.directories.some(d => d.name === entry.name);
|
||
if (exists) {
|
||
|
||
continue;
|
||
}
|
||
|
||
// Add to directories
|
||
window.app.directories.push({
|
||
handle: entry,
|
||
name: entry.name,
|
||
path: entry.name
|
||
});
|
||
|
||
// Hide empty state if this is the first directory
|
||
if (window.app.directories.length === 1) {
|
||
window.app.modules.app.hideEmptyState();
|
||
}
|
||
|
||
// Scan the new directory
|
||
await window.app.modules.directory.scanDirectory(entry, entry.name);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Update UI after processing all dropped directories
|
||
if (window.app.directories.length > 0) {
|
||
window.app.modules.app.updateUI();
|
||
}
|
||
}
|
||
|
||
// Handle file drop on grouping folder (for creating transmittals)
|
||
async function handleDrop(e) {
|
||
const folderItem = e.target.closest('.folder-item');
|
||
if (!folderItem) return;
|
||
|
||
const files = Array.from(e.dataTransfer.files);
|
||
if (files.length === 0) return;
|
||
|
||
// Find the grouping folder
|
||
const folderPath = folderItem.getAttribute('data-path');
|
||
const groupingFolder = window.app.groupingFolders.find(f => f.path === folderPath);
|
||
if (!groupingFolder) return;
|
||
|
||
targetGroupingFolder = groupingFolder;
|
||
draggedFiles = files;
|
||
|
||
// Show transmittal creation dialog
|
||
showTransmittalDialog();
|
||
}
|
||
|
||
// Show transmittal creation dialog
|
||
function showTransmittalDialog() {
|
||
const modal = document.getElementById('dropModal');
|
||
|
||
// Generate default transmittal name
|
||
const today = new Date();
|
||
const dateStr = today.toISOString().split('T')[0]; // YYYY-MM-DD
|
||
|
||
// Try to extract tracking number from first file
|
||
let trackingNumber = 'TRACKING';
|
||
let title = 'Transmittal';
|
||
|
||
if (draggedFiles.length > 0) {
|
||
const firstFile = draggedFiles[0];
|
||
const parsed = zddc.parseFilename(firstFile.name) || {};
|
||
if (parsed.trackingNumber) {
|
||
trackingNumber = parsed.trackingNumber;
|
||
}
|
||
if (parsed.title) {
|
||
title = parsed.title;
|
||
}
|
||
}
|
||
|
||
const defaultName = `${dateStr}_${trackingNumber} (IFI) - ${title}`;
|
||
document.getElementById('transmittalName').value = defaultName;
|
||
|
||
// Show file preview
|
||
updateFilePreview();
|
||
|
||
modal.classList.remove('hidden');
|
||
}
|
||
|
||
// Update file preview in dialog
|
||
function updateFilePreview() {
|
||
const tbody = document.getElementById('filesPreviewBody');
|
||
|
||
const rows = draggedFiles.map(file => {
|
||
const parsed = zddc.parseFilename(file.name) || {};
|
||
|
||
// Generate ZDDC-compliant name
|
||
let newName = file.name;
|
||
if (parsed.trackingNumber) {
|
||
newName = `${parsed.trackingNumber}_${parsed.revision || 'A'} (${parsed.status || 'IFI'}) - ${parsed.title}.${parsed.extension}`;
|
||
}
|
||
|
||
const isValid = !!parsed.trackingNumber;
|
||
|
||
return `
|
||
<tr>
|
||
<td>${window.app.modules.app.escapeHtml(file.name)}</td>
|
||
<td>
|
||
<input type="text"
|
||
class="form-input"
|
||
value="${window.app.modules.app.escapeHtml(newName)}"
|
||
data-original="${window.app.modules.app.escapeHtml(file.name)}"
|
||
style="width: 100%;">
|
||
</td>
|
||
<td style="color: ${isValid ? 'green' : 'red'};">
|
||
${isValid ? '✓ Valid' : '✗ Invalid'}
|
||
</td>
|
||
</tr>
|
||
`;
|
||
}).join('');
|
||
|
||
tbody.innerHTML = rows;
|
||
}
|
||
|
||
// Confirm transmittal creation
|
||
async function confirmTransmittal() {
|
||
const transmittalName = document.getElementById('transmittalName').value.trim();
|
||
|
||
// Validate transmittal folder name
|
||
if (!window.app.modules.parser.isTransmittalFolder(transmittalName)) {
|
||
alert('Invalid transmittal folder name. Must follow format: YYYY-MM-DD_TRACKINGNUMBER (STATUS) - TITLE');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// Create transmittal folder
|
||
const transmittalHandle = await targetGroupingFolder.handle.getDirectoryHandle(transmittalName, { create: true });
|
||
|
||
// Get file mappings from preview
|
||
const fileMappings = [];
|
||
const inputs = document.querySelectorAll('#filesPreviewBody input');
|
||
inputs.forEach((input, index) => {
|
||
fileMappings.push({
|
||
originalFile: draggedFiles[index],
|
||
newName: input.value.trim()
|
||
});
|
||
});
|
||
|
||
// Save files with new names
|
||
for (const mapping of fileMappings) {
|
||
const fileHandle = await transmittalHandle.getFileHandle(mapping.newName, { create: true });
|
||
const writable = await fileHandle.createWritable();
|
||
await writable.write(mapping.originalFile);
|
||
await writable.close();
|
||
}
|
||
|
||
// Close modal
|
||
document.getElementById('dropModal').classList.add('hidden');
|
||
|
||
// Refresh to show new files
|
||
await window.app.modules.directory.refreshDirectories();
|
||
|
||
alert(`Transmittal created successfully with ${fileMappings.length} files.`);
|
||
|
||
} catch (err) {
|
||
console.error('Error creating transmittal:', err);
|
||
alert('Error creating transmittal: ' + err.message);
|
||
}
|
||
}
|
||
|
||
// Handle drag and drop on table rows (for metadata copy)
|
||
function setupTableRowDragDrop() {
|
||
document.getElementById('filesTableBody').addEventListener('dragover', (e) => {
|
||
const tr = e.target.closest('tr');
|
||
if (tr) {
|
||
e.preventDefault();
|
||
tr.classList.add('drag-over');
|
||
}
|
||
});
|
||
|
||
document.getElementById('filesTableBody').addEventListener('dragleave', (e) => {
|
||
const tr = e.target.closest('tr');
|
||
if (tr) {
|
||
tr.classList.remove('drag-over');
|
||
}
|
||
});
|
||
|
||
document.getElementById('filesTableBody').addEventListener('drop', async (e) => {
|
||
const tr = e.target.closest('tr');
|
||
if (!tr) return;
|
||
|
||
tr.classList.remove('drag-over');
|
||
|
||
// Get tracking number and title from the row
|
||
const trackingNumber = tr.querySelector('td[data-field="trackingNumber"]').textContent;
|
||
const title = tr.querySelector('td[data-field="title"]').textContent;
|
||
|
||
const files = Array.from(e.dataTransfer.files);
|
||
if (files.length === 0) return;
|
||
|
||
// For table row drops, just copy metadata
|
||
alert(`Would copy metadata:\nTracking Number: ${trackingNumber}\nTitle: ${title}\n\nTo ${files.length} file(s)`);
|
||
});
|
||
}
|
||
|
||
window.app.modules.dragDrop = {
|
||
setupDragAndDrop,
|
||
showTransmittalDialog,
|
||
updateFilePreview,
|
||
confirmTransmittal,
|
||
setupTableRowDragDrop
|
||
};
|
||
|
||
})();
|
||
|
||
(function() {
|
||
'use strict';
|
||
// Directory selection and scanning functionality
|
||
|
||
// Add directory
|
||
async function addDirectory() {
|
||
try {
|
||
const dirHandle = await window.showDirectoryPicker();
|
||
|
||
// Check if already added
|
||
const exists = window.app.directories.some(d => d.name === dirHandle.name);
|
||
if (exists) {
|
||
alert('This directory has already been added.');
|
||
return;
|
||
}
|
||
|
||
// Add to directories
|
||
window.app.directories.push({
|
||
handle: dirHandle,
|
||
name: dirHandle.name,
|
||
path: dirHandle.name // Root path
|
||
});
|
||
|
||
// Hide empty state if this is the first directory
|
||
if (window.app.directories.length === 1) {
|
||
window.app.modules.app.hideEmptyState();
|
||
}
|
||
|
||
// Scan the new directory
|
||
await scanDirectory(dirHandle, dirHandle.name);
|
||
|
||
window.app.modules.app.updateUI();
|
||
} catch (err) {
|
||
if (err.name !== 'AbortError') {
|
||
console.error('Error selecting directory:', err);
|
||
alert('Error selecting directory: ' + err.message);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Scan directory recursively (local mode — delegates to local source in source.js)
|
||
async function scanDirectory(dirHandle, path) {
|
||
window.app.isScanning = true;
|
||
window.app.scanProgress = 'Scanning ' + path + '...';
|
||
window.app.modules.app.updateStatusBar();
|
||
|
||
const source = window.app.modules.source.createSource('local', {});
|
||
|
||
const callbacks = {
|
||
onGroupingFolder: function(folder) {
|
||
window.app.groupingFolders.push(folder);
|
||
},
|
||
onTransmittalFolder: function(folder) {
|
||
window.app.transmittalFolders.push(folder);
|
||
},
|
||
onFile: function(file) {
|
||
window.app.files.push(file);
|
||
},
|
||
onProgress: function(message) {
|
||
window.app.scanProgress = message;
|
||
window.app.modules.app.updateStatusBar();
|
||
}
|
||
};
|
||
|
||
try {
|
||
await source.scan(dirHandle, callbacks);
|
||
|
||
// Only auto-select top-level party folders (shallowest depth).
|
||
// Selection is keyed by party NAME so the same-named third-party folder
|
||
// appearing under multiple projects is selected/deselected as a unit.
|
||
const groupingDepths = window.app.groupingFolders.map(f => f.path.split('/').length);
|
||
const minGroupingDepth = groupingDepths.length > 0 ? Math.min(...groupingDepths) : 1;
|
||
window.app.groupingFolders.forEach(folder => {
|
||
if (folder.path.split('/').length === minGroupingDepth) {
|
||
window.app.selectedGroupingFolders.add(folder.name);
|
||
}
|
||
});
|
||
|
||
window.app.transmittalFolders.forEach(folder => {
|
||
if (!window.app.modules.app.isUnderHiddenFolderType(folder.path)) {
|
||
window.app.selectedTransmittalFolders.add(folder.path);
|
||
}
|
||
});
|
||
|
||
window.app.modules.app.ensureOutstandingTransmittal();
|
||
// Auto-select Outstanding if selectAllTransmittals is active
|
||
if (window.app.selectAllTransmittals) {
|
||
window.app.selectedTransmittalFolders.add('__outstanding__');
|
||
}
|
||
|
||
window.app.modules.app.collectModifiers();
|
||
window.app.modules.app.updateUI();
|
||
window.app.modules.filtering.applyFilters();
|
||
if (window.app.modules.presets) {
|
||
window.app.modules.presets.init();
|
||
}
|
||
} catch (err) {
|
||
console.error('Error scanning directory:', err);
|
||
alert('Error scanning directory: ' + err.message);
|
||
} finally {
|
||
window.app.isScanning = false;
|
||
window.app.scanProgress = '';
|
||
window.app.modules.app.updateStatusBar();
|
||
}
|
||
}
|
||
|
||
// Refresh all directories
|
||
async function refreshDirectories() {
|
||
// Clear existing data
|
||
window.app.groupingFolders = [];
|
||
window.app.transmittalFolders = [];
|
||
window.app.files = [];
|
||
window.app.filteredFiles = [];
|
||
|
||
if (window.app.sourceMode === 'http') {
|
||
// Re-scan all HTTP sources
|
||
const dirs = window.app.directories.slice();
|
||
window.app.directories = [];
|
||
for (const dir of dirs) {
|
||
await window.app.modules.app.addHttpSource(dir.url);
|
||
}
|
||
} else {
|
||
// Re-scan all local directories
|
||
for (const dir of window.app.directories) {
|
||
await scanDirectory(dir.handle, dir.name);
|
||
}
|
||
}
|
||
|
||
window.app.modules.app.updateUI();
|
||
}
|
||
|
||
// Remove directory
|
||
function removeDirectory(dirName) {
|
||
const index = window.app.directories.findIndex(d => d.name === dirName);
|
||
if (index !== -1) {
|
||
window.app.directories.splice(index, 1);
|
||
|
||
// Remove associated folders and files
|
||
window.app.groupingFolders = window.app.groupingFolders.filter(f =>
|
||
!f.path.startsWith(dirName)
|
||
);
|
||
window.app.transmittalFolders = window.app.transmittalFolders.filter(f =>
|
||
!f.path.startsWith(dirName)
|
||
);
|
||
window.app.files = window.app.files.filter(f =>
|
||
!f.path.startsWith(dirName)
|
||
);
|
||
|
||
// Clean up the Outstanding virtual transmittal if no outstanding files remain
|
||
const hasAnyOutstanding = window.app.files.some(f => f.folderPath === '__outstanding__');
|
||
if (!hasAnyOutstanding) {
|
||
window.app.transmittalFolders = window.app.transmittalFolders.filter(f => f.path !== '__outstanding__');
|
||
window.app.selectedTransmittalFolders.delete('__outstanding__');
|
||
}
|
||
|
||
// Show empty state if no directories left
|
||
if (window.app.directories.length === 0) {
|
||
window.app.modules.app.showEmptyState();
|
||
}
|
||
|
||
window.app.modules.app.updateUI();
|
||
}
|
||
}
|
||
|
||
// Request permission for directory
|
||
async function requestPermission(dirHandle) {
|
||
const options = { mode: 'read' };
|
||
|
||
// Check current permission state
|
||
if ((await dirHandle.queryPermission(options)) === 'granted') {
|
||
return true;
|
||
}
|
||
|
||
// Request permission
|
||
if ((await dirHandle.requestPermission(options)) === 'granted') {
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
window.app.modules.directory = {
|
||
addDirectory,
|
||
scanDirectory,
|
||
refreshDirectories,
|
||
removeDirectory,
|
||
requestPermission
|
||
};
|
||
|
||
})();
|
||
|
||
(function() {
|
||
'use strict';
|
||
|
||
// Escape a string for use in a RegExp (literal match)
|
||
function escapeRegex(str) {
|
||
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||
}
|
||
|
||
// Build regex pattern at parse time based on anchors
|
||
function compilePattern(raw, anchorStart, anchorEnd) {
|
||
var src = (anchorStart ? '^' : '') + raw + (anchorEnd ? '$' : '');
|
||
try {
|
||
return new RegExp(src, 'i');
|
||
} catch (e) {
|
||
// Invalid regex — escape and retry (always succeeds)
|
||
var safe = (anchorStart ? '^' : '') + escapeRegex(raw) + (anchorEnd ? '$' : '');
|
||
return new RegExp(safe, 'i');
|
||
}
|
||
}
|
||
|
||
// Parse a single token string into a node
|
||
function parseToken(token) {
|
||
var s = token;
|
||
var negate = false;
|
||
var anchorStart = false;
|
||
var anchorEnd = false;
|
||
|
||
if (s.charAt(0) === '!') {
|
||
negate = true;
|
||
s = s.slice(1);
|
||
}
|
||
if (s.charAt(0) === '^') {
|
||
anchorStart = true;
|
||
s = s.slice(1);
|
||
}
|
||
if (s.length > 0 && s.charAt(s.length - 1) === '$') {
|
||
anchorEnd = true;
|
||
s = s.slice(0, -1);
|
||
}
|
||
|
||
if (s === '') return null;
|
||
|
||
// bare * (possibly after stripping !) → wildcard-all or wildcard-none
|
||
if (s === '*' && !anchorStart && !anchorEnd) {
|
||
return negate ? null : { type: 'wildcard-all' };
|
||
}
|
||
|
||
var re = compilePattern(s, anchorStart, anchorEnd);
|
||
return { type: negate ? 'no-match' : 'match', re: re };
|
||
}
|
||
|
||
// Parse expression string into AST array
|
||
function parse(expression) {
|
||
if (!expression || typeof expression !== 'string') return [];
|
||
var trimmed = expression.trim();
|
||
if (trimmed === '') return [];
|
||
if (trimmed === '*') return [{ type: 'wildcard-all' }];
|
||
|
||
var ast = [];
|
||
var i = 0;
|
||
var len = trimmed.length;
|
||
|
||
while (i < len) {
|
||
var ch = trimmed.charAt(i);
|
||
|
||
if (ch === '(') {
|
||
var depth = 1;
|
||
var j = i + 1;
|
||
while (j < len && depth > 0) {
|
||
if (trimmed.charAt(j) === '(') depth++;
|
||
else if (trimmed.charAt(j) === ')') depth--;
|
||
j++;
|
||
}
|
||
var innerAst = parse(trimmed.slice(i + 1, j - 1));
|
||
if (innerAst.length === 1) {
|
||
ast.push(innerAst[0]);
|
||
} else if (innerAst.length > 1) {
|
||
for (var k = 0; k < innerAst.length; k++) ast.push(innerAst[k]);
|
||
}
|
||
i = j;
|
||
} else if (ch === '|') {
|
||
ast.push({ type: 'pipe' });
|
||
i++;
|
||
} else if (ch === ' ') {
|
||
i++;
|
||
} else {
|
||
var j = i;
|
||
while (j < len) {
|
||
var c = trimmed.charAt(j);
|
||
if (c === ' ' || c === '(' || c === '|' || c === ')') break;
|
||
j++;
|
||
}
|
||
var token = trimmed.slice(i, j);
|
||
if (token.length > 0) {
|
||
var node = parseToken(token);
|
||
if (node !== null) ast.push(node);
|
||
}
|
||
i = j;
|
||
}
|
||
}
|
||
|
||
// Group pipes into OR nodes
|
||
var hasPipe = false;
|
||
var branches = [[]];
|
||
for (var l = 0; l < ast.length; l++) {
|
||
if (ast[l].type === 'pipe') {
|
||
hasPipe = true;
|
||
branches.push([]);
|
||
} else {
|
||
branches[branches.length - 1].push(ast[l]);
|
||
}
|
||
}
|
||
branches = branches.filter(function(b) { return b.length > 0; });
|
||
|
||
if (!hasPipe) {
|
||
return ast.filter(function(n) { return n.type !== 'pipe'; });
|
||
}
|
||
|
||
var orNodes = branches.map(function(branch) {
|
||
if (branch.length === 1) return branch[0];
|
||
return { type: 'and', nodes: branch };
|
||
});
|
||
return [{ type: 'or', nodes: orNodes }];
|
||
}
|
||
|
||
// Check if a single node matches the value
|
||
function nodeMatches(node, value) {
|
||
switch (node.type) {
|
||
case 'wildcard-all': return true;
|
||
case 'match': return node.re.test(value);
|
||
case 'no-match': return !node.re.test(value);
|
||
case 'or':
|
||
for (var i = 0; i < node.nodes.length; i++) {
|
||
if (nodeMatches(node.nodes[i], value)) return true;
|
||
}
|
||
return false;
|
||
case 'and':
|
||
for (var i = 0; i < node.nodes.length; i++) {
|
||
if (!nodeMatches(node.nodes[i], value)) return false;
|
||
}
|
||
return true;
|
||
default: return false;
|
||
}
|
||
}
|
||
|
||
// Evaluate AST against value
|
||
function matches(value, ast) {
|
||
if (!ast || ast.length === 0) return true;
|
||
var v = String(value); // no forced lowercase — regex has 'i' flag
|
||
for (var i = 0; i < ast.length; i++) {
|
||
if (!nodeMatches(ast[i], v)) return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
if (!window.zddc) {
|
||
throw new Error('shared/zddc-filter.js: window.zddc must be loaded first');
|
||
}
|
||
window.zddc.filter = { parse: parse, matches: matches };
|
||
})();
|
||
|
||
// Filtering functionality
|
||
|
||
// Apply all filters
|
||
function applyFilters() {
|
||
// Start with files from selected transmittal folders AND selected grouping folders
|
||
let filtered = window.app.files.filter(file => {
|
||
// Must have at least one grouping folder selected (if grouping folders exist)
|
||
if (window.app.groupingFolders.length > 0 && window.app.selectedGroupingFolders.size === 0) {
|
||
return false;
|
||
}
|
||
|
||
// Must have at least one transmittal folder selected
|
||
if (window.app.selectedTransmittalFolders.size === 0) {
|
||
return false;
|
||
}
|
||
|
||
// Multi-project visibility filter — files under unchecked projects are
|
||
// hidden from view (without re-scanning).
|
||
if (!window.app.modules.app.pathIsInVisibleProject(file.path)) {
|
||
return false;
|
||
}
|
||
|
||
// File must be in a selected transmittal folder
|
||
if (!window.app.selectedTransmittalFolders.has(file.folderPath)) {
|
||
return false;
|
||
}
|
||
|
||
// Outstanding files: actualPath must be under a selected grouping folder that is
|
||
// itself visible (not hidden by folder type toggles).
|
||
if (file.folderPath === '__outstanding__') {
|
||
if (!window.app.modules.app.outstandingFileIsVisible(file)) return false;
|
||
}
|
||
|
||
// If grouping folders exist and are selected, the file's transmittal folder
|
||
// path must contain a path segment matching one of the selected party names.
|
||
// Outstanding files are exempt — their grouping scope is enforced by the
|
||
// actualPath check above.
|
||
if (file.folderPath !== '__outstanding__' && window.app.groupingFolders.length > 0 && window.app.selectedGroupingFolders.size > 0) {
|
||
const inSelectedGrouping = file.folderPath.split('/').some(seg =>
|
||
window.app.selectedGroupingFolders.has(seg)
|
||
);
|
||
if (!inSelectedGrouping) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
});
|
||
|
||
|
||
|
||
// Apply column filters
|
||
filtered = applyColumnFilters(filtered);
|
||
|
||
// Apply modifier filter
|
||
if (window.app.selectedModifiers.size < window.app.availableModifiers.size) {
|
||
filtered = filtered.filter(file => window.app.modules.app.filePassesModifierFilter(file));
|
||
}
|
||
|
||
updateResetFiltersBtn();
|
||
|
||
// Apply selected-only filter
|
||
if (window.app.showSelectedOnly) {
|
||
filtered = filtered.filter(file => window.app.selectedFiles.has(file.id));
|
||
}
|
||
|
||
window.app.filteredFiles = filtered;
|
||
window.app.modules.table.updateFileTable();
|
||
window.app.modules.app.updateStatusBar();
|
||
window.app.modules.table.updateSelectAllVisibleCheckbox();
|
||
}
|
||
|
||
|
||
|
||
// Apply column filters using stored ASTs
|
||
function applyColumnFilters(files) {
|
||
const asts = window.app.columnFilterASTs;
|
||
|
||
if (asts.trackingNumber && asts.trackingNumber.length > 0) {
|
||
files = files.filter(file =>
|
||
zddc.filter.matches(file.trackingNumber || '', asts.trackingNumber)
|
||
);
|
||
}
|
||
|
||
if (asts.title && asts.title.length > 0) {
|
||
files = files.filter(file =>
|
||
zddc.filter.matches(file.title || '', asts.title)
|
||
);
|
||
}
|
||
|
||
if (asts.revisions && asts.revisions.length > 0) {
|
||
files = files.filter(file => {
|
||
const revisionText = [
|
||
file.revision,
|
||
file.status,
|
||
file.extension
|
||
].join(' ');
|
||
return zddc.filter.matches(revisionText, asts.revisions);
|
||
});
|
||
}
|
||
|
||
return files;
|
||
}
|
||
|
||
// Clear all filters
|
||
function clearFilters() {
|
||
window.app.columnFilters = {
|
||
trackingNumber: '',
|
||
title: '',
|
||
revisions: ''
|
||
};
|
||
window.app.columnFilterASTs = {
|
||
trackingNumber: null,
|
||
title: null,
|
||
revisions: null
|
||
};
|
||
window.app.groupingFilter = '';
|
||
window.app.transmittalFilter = '';
|
||
|
||
// Clear UI inputs
|
||
const groupingFilterEl = document.getElementById('groupingFilter');
|
||
groupingFilterEl.value = '';
|
||
groupingFilterEl.classList.remove('filter-active');
|
||
const transmittalFilterEl = document.getElementById('transmittalFilter');
|
||
transmittalFilterEl.value = '';
|
||
transmittalFilterEl.classList.remove('filter-active');
|
||
document.querySelectorAll('.column-filter').forEach(input => {
|
||
input.value = '';
|
||
input.classList.remove('filter-active');
|
||
});
|
||
|
||
window.app.modules.app.toggleAllModifiers(true);
|
||
updateResetFiltersBtn();
|
||
applyFilters();
|
||
window.app.modules.urlState.push();
|
||
}
|
||
|
||
// Update reset filters button visibility
|
||
function updateResetFiltersBtn() {
|
||
// Button is always visible — no show/hide logic needed
|
||
}
|
||
|
||
// Register filtering module
|
||
window.app.modules.filtering = {
|
||
applyFilters,
|
||
applyColumnFilters,
|
||
clearFilters,
|
||
updateResetFiltersBtn
|
||
};
|
||
|
||
// Table management functionality
|
||
|
||
(function() {
|
||
'use strict';
|
||
|
||
|
||
// FileBlobCache, processedLinks, preview state, and utilities
|
||
const fileBlobCache = new Map();
|
||
const processedLinks = new WeakSet();
|
||
let fileLinkHandlersAttached = false;
|
||
let filePreviewWindow = null;
|
||
// All extensions previewable in the popup. Image / tiff / zip / text routed
|
||
// through #previewContent below; pdf gets a direct iframe; docx/xlsx use
|
||
// dedicated lazy-loaded renderers.
|
||
const PREVIEW_EXTENSIONS = [
|
||
'pdf',
|
||
'docx', 'xlsx', 'xls',
|
||
...zddc.preview.IMAGE_EXTENSIONS,
|
||
...zddc.preview.TIFF_EXTENSIONS,
|
||
...zddc.preview.TEXT_EXTENSIONS,
|
||
'zip'
|
||
];
|
||
const loadedLibraries = new Map();
|
||
let resizing = null;
|
||
|
||
// Currently-previewing file (visual highlight in the file table). Survives
|
||
// re-renders via applyPreviewHighlight, which is called at the tail of
|
||
// updateFileTable. Cleared when the preview popup is closed.
|
||
let currentPreviewFileId = null;
|
||
let previewWindowWatcher = null;
|
||
|
||
function setCurrentPreviewFile(fileId) {
|
||
currentPreviewFileId = fileId;
|
||
applyPreviewHighlight();
|
||
}
|
||
|
||
function applyPreviewHighlight() {
|
||
const tbody = document.getElementById('filesTableBody');
|
||
if (!tbody) return;
|
||
// Clear any prior highlight first.
|
||
tbody.querySelectorAll('tr.is-previewing').forEach(el => el.classList.remove('is-previewing'));
|
||
tbody.querySelectorAll('.revision-file.is-previewing').forEach(el => el.classList.remove('is-previewing'));
|
||
if (!currentPreviewFileId) return;
|
||
const checkbox = tbody.querySelector(`input[type="checkbox"][data-file-id="${cssEscape(currentPreviewFileId)}"]`);
|
||
if (!checkbox) return;
|
||
const wrapper = checkbox.closest('.revision-file');
|
||
if (wrapper) wrapper.classList.add('is-previewing');
|
||
const row = checkbox.closest('tr');
|
||
if (row) row.classList.add('is-previewing');
|
||
}
|
||
|
||
function cssEscape(s) {
|
||
if (window.CSS && typeof window.CSS.escape === 'function') return window.CSS.escape(s);
|
||
return String(s).replace(/[^a-zA-Z0-9_-]/g, c => '\\' + c);
|
||
}
|
||
|
||
// Watch the preview popup; clear the highlight when the user closes it so
|
||
// the table doesn't keep advertising a preview that's no longer on screen.
|
||
function watchPreviewWindow() {
|
||
if (previewWindowWatcher) {
|
||
clearInterval(previewWindowWatcher);
|
||
previewWindowWatcher = null;
|
||
}
|
||
if (!filePreviewWindow) return;
|
||
previewWindowWatcher = setInterval(() => {
|
||
if (!filePreviewWindow || filePreviewWindow.closed) {
|
||
clearInterval(previewWindowWatcher);
|
||
previewWindowWatcher = null;
|
||
if (currentPreviewFileId) setCurrentPreviewFile(null);
|
||
}
|
||
}, 500);
|
||
}
|
||
|
||
|
||
/**
|
||
* Get or create a blob URL for a file.
|
||
* - Local files: reads via File System Access API, caches the blob URL.
|
||
* - HTTP files: fetches the remote URL, caches the blob URL.
|
||
* Returns a Promise<string> resolving to a blob: URL.
|
||
*/
|
||
async function getFileBlobUrl(file) {
|
||
if (fileBlobCache.has(file.id)) {
|
||
return fileBlobCache.get(file.id);
|
||
}
|
||
let blob;
|
||
if (file.handle) {
|
||
// Local file via File System Access API
|
||
const f = await file.handle.getFile();
|
||
blob = f;
|
||
} else if (file.url) {
|
||
// HTTP file — fetch and convert to blob
|
||
const resp = await fetch(file.url);
|
||
if (!resp.ok) throw new Error('HTTP ' + resp.status + ' fetching ' + file.url);
|
||
blob = await resp.blob();
|
||
} else {
|
||
throw new Error('File has neither a handle nor a URL');
|
||
}
|
||
const url = URL.createObjectURL(blob);
|
||
fileBlobCache.set(file.id, url);
|
||
return url;
|
||
}
|
||
|
||
/**
|
||
* Clean up blob URLs for files no longer displayed
|
||
*/
|
||
function cleanupUnusedBlobUrls() {
|
||
const displayedFileIds = new Set(window.app.filteredFiles.map(f => f.id));
|
||
for (const [fileId, url] of fileBlobCache.entries()) {
|
||
if (!displayedFileIds.has(fileId)) {
|
||
URL.revokeObjectURL(url);
|
||
fileBlobCache.delete(fileId);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Revoke all blob URLs and clear cache
|
||
*/
|
||
function cleanupAllBlobUrls() {
|
||
for (const url of fileBlobCache.values()) {
|
||
URL.revokeObjectURL(url);
|
||
}
|
||
fileBlobCache.clear();
|
||
}
|
||
|
||
// Update file table
|
||
function updateFileTable() {
|
||
const tbody = document.getElementById('filesTableBody');
|
||
|
||
if (window.app.filteredFiles.length === 0) {
|
||
tbody.innerHTML = `
|
||
<tr>
|
||
<td colspan="3" class="empty-table">
|
||
No files found matching the current filters.
|
||
</td>
|
||
</tr>
|
||
`;
|
||
cleanupUnusedBlobUrls(); // Clean up all blob URLs
|
||
return;
|
||
}
|
||
|
||
// Group and sort files
|
||
const grouped = window.app.modules.parser.groupFilesByTrackingNumber(window.app.filteredFiles);
|
||
const sorted = window.app.modules.parser.sortGroupedFiles(grouped);
|
||
|
||
// Build table rows
|
||
const rows = [];
|
||
sorted.forEach(group => {
|
||
rows.push(createFileGroupRow(group));
|
||
});
|
||
|
||
tbody.innerHTML = rows.join('');
|
||
|
||
// Re-apply the preview highlight after every re-render so a file that
|
||
// was being previewed when filters changed still shows as previewing if
|
||
// it's still in the visible set.
|
||
applyPreviewHighlight();
|
||
|
||
// Clean up blob URLs for files no longer visible
|
||
cleanupUnusedBlobUrls();
|
||
}
|
||
|
||
// Create row for a file group
|
||
function createFileGroupRow(group) {
|
||
// Generate one <tr> per revision; last row gets class group-last for border
|
||
const lastIndex = group.sortedRevisions.length - 1;
|
||
return group.sortedRevisions.map((revision, i) => {
|
||
const titleClass = revision.hasModifier ? 'revision-title-modifier' : 'revision-title-base';
|
||
const titleHtml = `<div class="${titleClass}">${window.app.modules.app.escapeHtml(revision.title)}</div>`;
|
||
const revisionHtml = createRevisionHtml(group.trackingNumber, revision);
|
||
const lastClass = i === lastIndex ? ' group-last' : '';
|
||
|
||
// First row includes trackingNumber cell with rowspan
|
||
if (i === 0) {
|
||
return `
|
||
<tr class="group-row${lastClass}">
|
||
<td data-field="trackingNumber" rowspan="${group.sortedRevisions.length}">${window.app.modules.app.escapeHtml(group.trackingNumber)}</td>
|
||
<td data-field="title">${titleHtml}</td>
|
||
<td data-field="revisions">${revisionHtml}</td>
|
||
</tr>
|
||
`;
|
||
}
|
||
// Subsequent rows omit trackingNumber cell
|
||
return `
|
||
<tr class="group-row${lastClass}">
|
||
<td data-field="title">${titleHtml}</td>
|
||
<td data-field="revisions">${revisionHtml}</td>
|
||
</tr>
|
||
`;
|
||
}).join('');
|
||
}
|
||
|
||
// Create HTML for a revision
|
||
function createRevisionHtml(trackingNumber, revision) {
|
||
const filesHtml = revision.files.map(file =>
|
||
createFileHtml(file)
|
||
).join(' ');
|
||
|
||
return `
|
||
<div class="revision-group">
|
||
<div class="revision-item">
|
||
<span class="revision-info">
|
||
<span class="revision-id">${window.app.modules.app.escapeHtml(revision.revision)}</span>
|
||
<span class="revision-status">(${window.app.modules.app.escapeHtml(revision.status)})</span>
|
||
</span>
|
||
${filesHtml}
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// Create HTML for a file
|
||
function createFileHtml(file) {
|
||
const checked = window.app.selectedFiles.has(file.id) ? 'checked' : '';
|
||
const fullPath = file.path || file.folderPath + '/' + file.name;
|
||
|
||
// Handle files with path errors (Windows 260-char limit)
|
||
if (file.hasPathError) {
|
||
const errorTitle = `⚠️ Cannot access: Microsoft Windows path length limit (260 chars)\n\nPath: ${fullPath}\n\nUse 'subst' to map archive to a drive letter, or shorten folder names.`;
|
||
return `
|
||
<span class="revision-file">
|
||
<input type="checkbox"
|
||
data-file-id="${file.id}"
|
||
${checked}
|
||
onchange="toggleFileSelection('${file.id}')">
|
||
<span class="path-error-indicator" title="${window.app.modules.app.escapeHtml(errorTitle)}">⚠️</span>
|
||
<span class="file-link-disabled"
|
||
title="${window.app.modules.app.escapeHtml(errorTitle)}">
|
||
<span class="file-ext">${window.app.modules.app.escapeHtml(file.extension.toUpperCase())}</span>
|
||
${file.size != null ? `<span class="file-size">${window.app.modules.export.formatFileSize(file.size)}</span>` : ''}
|
||
</span>
|
||
</span>
|
||
`;
|
||
}
|
||
|
||
return `
|
||
<span class="revision-file">
|
||
<input type="checkbox"
|
||
data-file-id="${file.id}"
|
||
${checked}
|
||
onchange="toggleFileSelection('${file.id}')">
|
||
<a href="#"
|
||
class="file-link"
|
||
data-file-id="${file.id}"
|
||
data-file-name="${window.app.modules.app.escapeHtml(file.name)}"
|
||
title="${window.app.modules.app.escapeHtml(fullPath)}">
|
||
<span class="file-ext">${window.app.modules.app.escapeHtml(file.extension.toUpperCase())}</span>
|
||
${file.size != null ? `<span class="file-size">${window.app.modules.export.formatFileSize(file.size)}</span>` : ''}
|
||
</a>
|
||
</span>
|
||
`;
|
||
}
|
||
|
||
// Toggle file selection
|
||
function toggleFileSelection(fileId) {
|
||
if (window.app.selectedFiles.has(fileId)) {
|
||
window.app.selectedFiles.delete(fileId);
|
||
} else {
|
||
window.app.selectedFiles.add(fileId);
|
||
}
|
||
window.app.modules.app.updateStatusBar();
|
||
updateSelectAllVisibleCheckbox();
|
||
}
|
||
|
||
// Toggle selection of all visible files based on checkbox state
|
||
function toggleSelectAllVisible(selectAll) {
|
||
window.app.filteredFiles.forEach(file => {
|
||
if (selectAll) {
|
||
window.app.selectedFiles.add(file.id);
|
||
} else {
|
||
window.app.selectedFiles.delete(file.id);
|
||
}
|
||
});
|
||
|
||
updateFileTable();
|
||
window.app.modules.app.updateStatusBar();
|
||
updateSelectAllVisibleCheckbox();
|
||
}
|
||
|
||
// Update the select all visible checkbox to reflect current state
|
||
function updateSelectAllVisibleCheckbox() {
|
||
const checkbox = document.getElementById('selectAllVisibleCheckbox');
|
||
if (!checkbox) return;
|
||
|
||
const visibleCount = window.app.filteredFiles.length;
|
||
if (visibleCount === 0) {
|
||
checkbox.checked = false;
|
||
checkbox.indeterminate = false;
|
||
return;
|
||
}
|
||
|
||
const selectedVisibleCount = window.app.filteredFiles.filter(f =>
|
||
window.app.selectedFiles.has(f.id)
|
||
).length;
|
||
|
||
if (selectedVisibleCount === 0) {
|
||
checkbox.checked = false;
|
||
checkbox.indeterminate = false;
|
||
} else if (selectedVisibleCount === visibleCount) {
|
||
checkbox.checked = true;
|
||
checkbox.indeterminate = false;
|
||
} else {
|
||
checkbox.checked = false;
|
||
checkbox.indeterminate = true;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Memory-efficient blob URL management
|
||
*
|
||
* fileBlobCache: Maps file IDs to blob URLs for reuse
|
||
* processedLinks: WeakSet tracks DOM elements that already have blob URLs
|
||
* - Automatically garbage collected when DOM elements are removed
|
||
* - Prevents redundant async operations on mouseover
|
||
*/
|
||
|
||
/**
|
||
* Lazily load a script from CDN. Returns a promise that resolves when loaded.
|
||
* Caches the promise so subsequent calls return immediately.
|
||
*/
|
||
function loadLibrary(url) {
|
||
if (loadedLibraries.has(url)) return loadedLibraries.get(url);
|
||
const promise = new Promise((resolve, reject) => {
|
||
const script = document.createElement('script');
|
||
script.src = url;
|
||
script.onload = resolve;
|
||
script.onerror = () => reject(new Error(`Failed to load library: ${url}`));
|
||
document.head.appendChild(script);
|
||
});
|
||
loadedLibraries.set(url, promise);
|
||
return promise;
|
||
}
|
||
|
||
/**
|
||
* Check if file preview mode is enabled
|
||
*/
|
||
function isFilePreviewEnabled() {
|
||
const toggle = document.getElementById('filePreviewToggle');
|
||
return toggle && toggle.checked;
|
||
}
|
||
|
||
/**
|
||
* Show file preview in a separate popup window
|
||
* Supports PDF (iframe), DOCX (docx-preview), XLSX/XLS (SheetJS)
|
||
*/
|
||
async function showFilePreview(file) {
|
||
const ext = file.extension.toLowerCase();
|
||
|
||
try {
|
||
// For HTML preview, prefer the file's real server URL over a
|
||
// blob URL when available (zddc-server-backed archives have
|
||
// file.url set; local FileSystemAccessAPI mode doesn't).
|
||
//
|
||
// Why it matters: HTML files in an archive often link to
|
||
// sibling/parent paths via relative URLs — e.g.
|
||
// ../.archive/<tracking>.html — which zddc-server intercepts
|
||
// and resolves. From a blob: URL the relative resolution
|
||
// produces blob:.../.archive/X.html, which never reaches the
|
||
// server. Loading the iframe from the actual https://zddc.../
|
||
// URL means relative links resolve back to the server and the
|
||
// .archive interception fires as designed.
|
||
//
|
||
// Other types (pdf, images rendered via canvas / iframe etc.)
|
||
// are content-only — they don't depend on relative URLs — so
|
||
// a blob URL is fine.
|
||
const isHtml = ext === 'html' || ext === 'htm';
|
||
const url = (isHtml && file.url)
|
||
? file.url
|
||
: await getFileBlobUrl(file);
|
||
|
||
// Mirror the parent window's theme in the popup
|
||
const parentTheme = document.documentElement.getAttribute('data-theme') || '';
|
||
const themeAttr = parentTheme ? ` data-theme="${parentTheme}"` : '';
|
||
|
||
// Base HTML shell for the preview window
|
||
const previewHtml = `
|
||
<!DOCTYPE html>
|
||
<html${themeAttr}>
|
||
<head>
|
||
<title>${window.app.modules.app.escapeHtml(file.name)} - Preview</title>
|
||
<style>
|
||
:root {
|
||
--bg: #ffffff;
|
||
--bg-secondary: #f5f5f5;
|
||
--bg-hover: #e8e8e8;
|
||
--text: #212529;
|
||
--text-muted: #666666;
|
||
--border: #dddddd;
|
||
--primary: #2a5a8a;
|
||
}
|
||
@media (prefers-color-scheme: dark) {
|
||
:root:not([data-theme="light"]) {
|
||
--bg: #1e1e1e;
|
||
--bg-secondary: #252526;
|
||
--bg-hover: #2d2d30;
|
||
--text: #d4d4d4;
|
||
--text-muted: #9d9d9d;
|
||
--border: #3e3e42;
|
||
--primary: #4a90c4;
|
||
}
|
||
}
|
||
[data-theme="dark"] {
|
||
--bg: #1e1e1e;
|
||
--bg-secondary: #252526;
|
||
--bg-hover: #2d2d30;
|
||
--text: #d4d4d4;
|
||
--text-muted: #9d9d9d;
|
||
--border: #3e3e42;
|
||
--primary: #4a90c4;
|
||
}
|
||
[data-theme="light"] {
|
||
--bg: #ffffff;
|
||
--bg-secondary: #f5f5f5;
|
||
--bg-hover: #e8e8e8;
|
||
--text: #212529;
|
||
--text-muted: #666666;
|
||
--border: #dddddd;
|
||
--primary: #2a5a8a;
|
||
}
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
body {
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100vh;
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||
background: var(--bg);
|
||
color: var(--text);
|
||
}
|
||
.toolbar {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
padding: 0.5rem 1rem;
|
||
background: var(--bg-secondary);
|
||
border-bottom: 1px solid var(--border);
|
||
flex-shrink: 0;
|
||
}
|
||
.toolbar h1 {
|
||
flex: 1;
|
||
font-size: 0.95rem;
|
||
font-weight: 500;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
color: var(--text);
|
||
}
|
||
.btn {
|
||
padding: 0.4rem 0.8rem;
|
||
font-size: 0.85rem;
|
||
border: 1px solid var(--border);
|
||
border-radius: 4px;
|
||
background: var(--bg);
|
||
color: var(--text);
|
||
cursor: pointer;
|
||
}
|
||
.btn:hover { background: var(--bg-hover); }
|
||
iframe {
|
||
flex: 1;
|
||
width: 100%;
|
||
border: none;
|
||
}
|
||
#previewContent {
|
||
flex: 1;
|
||
overflow: auto;
|
||
background: var(--bg);
|
||
}
|
||
.loading {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 100%;
|
||
color: var(--text-muted);
|
||
font-size: 1.1rem;
|
||
}
|
||
/* docx-preview container */
|
||
.docx-wrapper { padding: 1rem; }
|
||
/* Image preview */
|
||
img.preview-image {
|
||
max-width: 100%;
|
||
max-height: 100%;
|
||
display: block;
|
||
margin: auto;
|
||
object-fit: contain;
|
||
}
|
||
/* Text preview */
|
||
pre.preview-text {
|
||
padding: 1rem;
|
||
font-family: 'Consolas', 'Monaco', monospace;
|
||
font-size: 0.85rem;
|
||
white-space: pre-wrap;
|
||
word-wrap: break-word;
|
||
color: var(--text);
|
||
background: var(--bg);
|
||
}
|
||
/* xlsx table styling */
|
||
.xlsx-table { border-collapse: collapse; width: 100%; font-size: 0.85rem; }
|
||
.xlsx-table th, .xlsx-table td {
|
||
border: 1px solid var(--border);
|
||
padding: 0.35rem 0.5rem;
|
||
text-align: left;
|
||
white-space: nowrap;
|
||
color: var(--text);
|
||
}
|
||
.xlsx-table th { background: var(--bg-secondary); font-weight: 600; position: sticky; top: 0; }
|
||
.xlsx-table tr:nth-child(even) { background: var(--bg-secondary); }
|
||
.xlsx-table tr:hover { background: var(--bg-hover); }
|
||
.sheet-tabs { display: flex; gap: 0; border-bottom: 1px solid var(--border); background: var(--bg-secondary); }
|
||
.sheet-tab {
|
||
padding: 0.4rem 1rem;
|
||
cursor: pointer;
|
||
border: 1px solid transparent;
|
||
border-bottom: none;
|
||
font-size: 0.85rem;
|
||
background: transparent;
|
||
color: var(--text);
|
||
}
|
||
.sheet-tab:hover { background: var(--bg-hover); }
|
||
.sheet-tab.active {
|
||
background: var(--bg);
|
||
border-color: var(--border);
|
||
border-bottom-color: var(--bg);
|
||
margin-bottom: -1px;
|
||
font-weight: 500;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="toolbar">
|
||
<h1>${window.app.modules.app.escapeHtml(file.name)}</h1>
|
||
<button class="btn" onclick="downloadFile()">Download</button>
|
||
</div>
|
||
${(ext === 'pdf' || ext === 'html' || ext === 'htm')
|
||
? '<iframe src="' + url + '"' + (ext === 'pdf' ? '' : ' sandbox="allow-same-origin allow-popups allow-popups-to-escape-sandbox"') + '></iframe>'
|
||
: '<div id="previewContent"><div class="loading">Loading preview...</div></div>'}
|
||
<script>
|
||
var blobUrl = "${url}";
|
||
var fileName = "${window.app.modules.app.escapeHtml(file.name).replace(/"/g, '\\"')}";
|
||
|
||
function downloadFile() {
|
||
const a = document.createElement('a');
|
||
a.href = blobUrl;
|
||
a.download = fileName;
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
document.body.removeChild(a);
|
||
}
|
||
<\/script>
|
||
</body>
|
||
</html>`;
|
||
|
||
// Open or reuse the preview window
|
||
if (filePreviewWindow && !filePreviewWindow.closed) {
|
||
filePreviewWindow.document.open();
|
||
filePreviewWindow.document.write(previewHtml);
|
||
filePreviewWindow.document.close();
|
||
filePreviewWindow.focus();
|
||
} else {
|
||
const width = Math.round(screen.width * 0.6);
|
||
const height = Math.round(screen.height * 0.8);
|
||
const left = Math.round((screen.width - width) / 2);
|
||
const top = Math.round((screen.height - height) / 2);
|
||
|
||
filePreviewWindow = window.open('', 'filePreview',
|
||
`width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes`);
|
||
|
||
if (!filePreviewWindow) {
|
||
window.open(url, '_blank');
|
||
return;
|
||
}
|
||
|
||
filePreviewWindow.document.write(previewHtml);
|
||
filePreviewWindow.document.close();
|
||
filePreviewWindow.focus();
|
||
}
|
||
|
||
// For non-PDF / non-HTML types, render content into the
|
||
// preview window. PDF and HTML are already wired up via the
|
||
// <iframe> in the popup's body (see buildPreviewHtml above);
|
||
// for HTML this means the page is RENDERED, not shown as
|
||
// literal source text. The previewBlobUrl carries the right
|
||
// MIME type ('text/html') so the iframe loads natively.
|
||
if (ext === 'pdf' || ext === 'html' || ext === 'htm') {
|
||
// iframe already wired in popup HTML; nothing to do
|
||
} else if (ext === 'docx') {
|
||
await renderDocxInWindow(file);
|
||
} else if (ext === 'xlsx' || ext === 'xls') {
|
||
await renderXlsxInWindow(file);
|
||
} else if (zddc.preview.isTiff(ext)) {
|
||
await renderTiffInWindow(file);
|
||
} else if (zddc.preview.isZip(ext)) {
|
||
await renderZipInWindow(file);
|
||
} else if (zddc.preview.isImage(ext)) {
|
||
renderImageInWindow(file, url);
|
||
} else if (zddc.preview.isText(ext)) {
|
||
await renderTextInWindow(file);
|
||
}
|
||
|
||
} catch (err) {
|
||
console.error('Error loading file preview:', err);
|
||
alert(`Error loading preview: ${err.message}`);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Render a DOCX file in the preview window using docx-preview library
|
||
*/
|
||
async function renderDocxInWindow(file) {
|
||
const container = filePreviewWindow.document.getElementById('previewContent');
|
||
if (!container) return;
|
||
|
||
try {
|
||
// jszip + docx-preview are vendored (concatenated by build.sh
|
||
// ahead of every tool module), so window.JSZip and window.docx
|
||
// are already defined here.
|
||
const arrayBuffer = await (file.handle
|
||
? file.handle.getFile().then(f => f.arrayBuffer())
|
||
: fetch(file.url).then(r => r.arrayBuffer()));
|
||
|
||
container.innerHTML = '';
|
||
await window.docx.renderAsync(arrayBuffer, container);
|
||
} catch (err) {
|
||
console.error('Error rendering DOCX:', err);
|
||
container.innerHTML = `<div class="loading">Error rendering DOCX: ${err.message}<br>Click Download to view in Word.</div>`;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Render an XLSX/XLS file in the preview window using SheetJS
|
||
*/
|
||
async function renderXlsxInWindow(file) {
|
||
const container = filePreviewWindow.document.getElementById('previewContent');
|
||
if (!container) return;
|
||
|
||
try {
|
||
await loadLibrary('https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/xlsx.full.min.js');
|
||
|
||
const arrayBuffer = await (file.handle
|
||
? file.handle.getFile().then(f => f.arrayBuffer())
|
||
: fetch(file.url).then(r => r.arrayBuffer()));
|
||
const workbook = XLSX.read(arrayBuffer, { type: 'array' });
|
||
|
||
container.innerHTML = '';
|
||
|
||
// Build sheet tabs if multiple sheets
|
||
if (workbook.SheetNames.length > 1) {
|
||
const tabs = filePreviewWindow.document.createElement('div');
|
||
tabs.className = 'sheet-tabs';
|
||
workbook.SheetNames.forEach((name, i) => {
|
||
const tab = filePreviewWindow.document.createElement('button');
|
||
tab.className = 'sheet-tab' + (i === 0 ? ' active' : '');
|
||
tab.textContent = name;
|
||
tab.onclick = () => {
|
||
tabs.querySelectorAll('.sheet-tab').forEach(t => t.classList.remove('active'));
|
||
tab.classList.add('active');
|
||
renderSheet(workbook, name, tableContainer);
|
||
};
|
||
tabs.appendChild(tab);
|
||
});
|
||
container.appendChild(tabs);
|
||
}
|
||
|
||
const tableContainer = filePreviewWindow.document.createElement('div');
|
||
tableContainer.style.flex = '1';
|
||
tableContainer.style.overflow = 'auto';
|
||
container.appendChild(tableContainer);
|
||
|
||
renderSheet(workbook, workbook.SheetNames[0], tableContainer);
|
||
} catch (err) {
|
||
console.error('Error rendering XLSX:', err);
|
||
container.innerHTML = `<div class="loading">Error rendering spreadsheet: ${err.message}<br>Click Download to view in Excel.</div>`;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Render a single sheet as an HTML table
|
||
*/
|
||
function renderSheet(workbook, sheetName, container) {
|
||
const sheet = workbook.Sheets[sheetName];
|
||
const html = XLSX.utils.sheet_to_html(sheet, { editable: false });
|
||
container.innerHTML = html;
|
||
// Apply styling to the generated table
|
||
const table = container.querySelector('table');
|
||
if (table) table.className = 'xlsx-table';
|
||
}
|
||
|
||
async function _getFileArrayBuffer(file) {
|
||
if (file.handle) {
|
||
const f = await file.handle.getFile();
|
||
return f.arrayBuffer();
|
||
}
|
||
if (file.url) {
|
||
const r = await fetch(file.url);
|
||
if (!r.ok) throw new Error('HTTP ' + r.status);
|
||
return r.arrayBuffer();
|
||
}
|
||
throw new Error('No file source available');
|
||
}
|
||
|
||
/**
|
||
* Render an image (non-tiff) directly using the popup's <img> element.
|
||
* The browser handles decoding for jpg/jpeg/png/gif/webp/bmp/svg/ico natively.
|
||
*/
|
||
function renderImageInWindow(file, url) {
|
||
const container = filePreviewWindow.document.getElementById('previewContent');
|
||
if (!container) return;
|
||
container.innerHTML = '';
|
||
const img = filePreviewWindow.document.createElement('img');
|
||
img.className = 'preview-image';
|
||
img.src = url;
|
||
img.alt = file.name || '';
|
||
container.appendChild(img);
|
||
}
|
||
|
||
/**
|
||
* Render a TIFF using the shared zddc.preview.renderTiff helper (UTIF.js).
|
||
*/
|
||
async function renderTiffInWindow(file) {
|
||
const container = filePreviewWindow.document.getElementById('previewContent');
|
||
if (!container) return;
|
||
try {
|
||
const arrayBuffer = await _getFileArrayBuffer(file);
|
||
await zddc.preview.renderTiff(filePreviewWindow.document, container, arrayBuffer, {
|
||
fileName: file.name
|
||
});
|
||
} catch (err) {
|
||
console.error('Error rendering TIFF:', err);
|
||
container.innerHTML = `<div class="loading">Error rendering TIFF: ${err.message}<br>Click Download to view in another application.</div>`;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Render a ZIP listing using the shared zddc.preview.renderZipListing helper.
|
||
*/
|
||
async function renderZipInWindow(file) {
|
||
const container = filePreviewWindow.document.getElementById('previewContent');
|
||
if (!container) return;
|
||
try {
|
||
const arrayBuffer = await _getFileArrayBuffer(file);
|
||
await zddc.preview.renderZipListing(filePreviewWindow.document, container, arrayBuffer, {
|
||
fileName: file.name
|
||
});
|
||
} catch (err) {
|
||
console.error('Error rendering ZIP listing:', err);
|
||
container.innerHTML = `<div class="loading">Error reading ZIP: ${err.message}</div>`;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Render a text file as preformatted text. Truncates very large files to
|
||
* keep the popup responsive — users can Download to see the full file.
|
||
*/
|
||
async function renderTextInWindow(file) {
|
||
const container = filePreviewWindow.document.getElementById('previewContent');
|
||
if (!container) return;
|
||
try {
|
||
const fileObj = file.handle ? await file.handle.getFile() : await fetch(file.url).then(r => r.blob());
|
||
let text = await fileObj.text();
|
||
const MAX = 200000;
|
||
if (text.length > MAX) text = text.substring(0, MAX) + '\n\n... (truncated, ' + (text.length - MAX) + ' more chars — Download for full file)';
|
||
container.innerHTML = '';
|
||
const pre = filePreviewWindow.document.createElement('pre');
|
||
pre.className = 'preview-text';
|
||
pre.textContent = text;
|
||
container.appendChild(pre);
|
||
} catch (err) {
|
||
console.error('Error rendering text file:', err);
|
||
container.innerHTML = `<div class="loading">Error reading file: ${err.message}</div>`;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Setup event delegation for file links
|
||
* Left-click: Download file (or preview if PDF and preview mode enabled)
|
||
* Right-click: Allow "Open in new tab" with blob URL
|
||
*/
|
||
function setupFileLinkHandlers() {
|
||
if (fileLinkHandlersAttached) return;
|
||
|
||
const table = document.getElementById('filesTable');
|
||
if (!table) {
|
||
console.warn('Files table not found');
|
||
return;
|
||
}
|
||
|
||
// Handle clicks - download file or show preview
|
||
table.addEventListener('click', async (e) => {
|
||
const link = e.target.closest('.file-link');
|
||
if (!link) return;
|
||
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
|
||
const fileId = link.getAttribute('data-file-id');
|
||
const fileName = link.getAttribute('data-file-name');
|
||
|
||
if (!fileId || !fileName) {
|
||
console.error('Invalid link data');
|
||
return;
|
||
}
|
||
|
||
const file = window.app.files.find(f => f.id === fileId);
|
||
|
||
if (!file) {
|
||
console.error(`File not found: ${fileId}`);
|
||
alert('File not found. Please refresh and try again.');
|
||
return;
|
||
}
|
||
|
||
// Check if file preview is enabled and file type is previewable
|
||
if (isFilePreviewEnabled() && PREVIEW_EXTENSIONS.includes(file.extension.toLowerCase())) {
|
||
await showFilePreview(file);
|
||
setCurrentPreviewFile(file.id);
|
||
watchPreviewWindow();
|
||
return;
|
||
}
|
||
|
||
try {
|
||
if (!file.handle && file.url) {
|
||
// HTTP mode: open the file URL directly in a new tab
|
||
window.open(file.url, '_blank');
|
||
} else {
|
||
// Local mode: create blob URL and trigger download
|
||
const url = await getFileBlobUrl(file);
|
||
const downloadLink = document.createElement('a');
|
||
downloadLink.href = url;
|
||
downloadLink.download = fileName;
|
||
document.body.appendChild(downloadLink);
|
||
downloadLink.click();
|
||
document.body.removeChild(downloadLink);
|
||
}
|
||
} catch (err) {
|
||
console.error('Error opening file:', err);
|
||
alert(`Error opening file: ${err.message}`);
|
||
}
|
||
}, true); // Use capture phase
|
||
|
||
// Handle mouseover - pre-load URL for fast right-click / middle-click
|
||
table.addEventListener('mouseover', async (e) => {
|
||
const link = e.target.closest('.file-link');
|
||
if (!link) return;
|
||
|
||
// Skip if already processed (prevents redundant operations)
|
||
if (processedLinks.has(link)) return;
|
||
|
||
const fileId = link.getAttribute('data-file-id');
|
||
if (!fileId) return;
|
||
|
||
const file = window.app.files.find(f => f.id === fileId);
|
||
if (!file) {
|
||
console.warn(`File not found for pre-load: ${fileId}`);
|
||
return;
|
||
}
|
||
|
||
try {
|
||
if (!file.handle && file.url) {
|
||
// HTTP mode: set href directly — no async needed
|
||
link.href = file.url;
|
||
link.target = '_blank';
|
||
processedLinks.add(link);
|
||
} else {
|
||
// Local mode: pre-load blob URL asynchronously
|
||
const url = await getFileBlobUrl(file);
|
||
link.href = url;
|
||
link.target = '_blank';
|
||
processedLinks.add(link);
|
||
}
|
||
} catch (err) {
|
||
console.error('Error pre-loading file link:', err);
|
||
// Don't mark as processed so it can retry
|
||
}
|
||
}, true); // Use capture phase
|
||
|
||
// Handle context menu - ensure blob URL is set (fallback if mouseover didn't fire)
|
||
table.addEventListener('contextmenu', async (e) => {
|
||
const link = e.target.closest('.file-link');
|
||
if (!link) return;
|
||
|
||
// If already processed, blob URL is set - allow context menu to work
|
||
if (processedLinks.has(link)) return;
|
||
|
||
const fileId = link.getAttribute('data-file-id');
|
||
if (!fileId) return;
|
||
|
||
const file = window.app.files.find(f => f.id === fileId);
|
||
if (!file) {
|
||
console.warn(`File not found for context menu: ${fileId}`);
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// Get blob URL and set it as href synchronously as possible
|
||
const url = await getFileBlobUrl(file);
|
||
link.href = url;
|
||
link.target = '_blank';
|
||
|
||
// Mark as processed
|
||
processedLinks.add(link);
|
||
} catch (err) {
|
||
console.error('Error preparing file for context menu:', err);
|
||
// Don't mark as processed so it can retry
|
||
}
|
||
}, true); // Use capture phase
|
||
|
||
fileLinkHandlersAttached = true;
|
||
}
|
||
|
||
// Get MIME type from extension
|
||
function getMimeType(extension) {
|
||
const ext = extension.toLowerCase();
|
||
const mimeTypes = {
|
||
// Documents
|
||
'pdf': 'application/pdf',
|
||
'doc': 'application/msword',
|
||
'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||
'xls': 'application/vnd.ms-excel',
|
||
'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||
'ppt': 'application/vnd.ms-powerpoint',
|
||
'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||
|
||
// Text
|
||
'txt': 'text/plain',
|
||
'csv': 'text/csv',
|
||
'html': 'text/html',
|
||
'htm': 'text/html',
|
||
'xml': 'text/xml',
|
||
'json': 'application/json',
|
||
|
||
// Code
|
||
'js': 'text/javascript',
|
||
'css': 'text/css',
|
||
'py': 'text/plain',
|
||
'java': 'text/plain',
|
||
'cpp': 'text/plain',
|
||
'c': 'text/plain',
|
||
'h': 'text/plain',
|
||
|
||
// Images
|
||
'jpg': 'image/jpeg',
|
||
'jpeg': 'image/jpeg',
|
||
'png': 'image/png',
|
||
'gif': 'image/gif',
|
||
'bmp': 'image/bmp',
|
||
'svg': 'image/svg+xml',
|
||
'webp': 'image/webp',
|
||
'ico': 'image/x-icon',
|
||
|
||
// Archives
|
||
'zip': 'application/zip',
|
||
'rar': 'application/x-rar-compressed',
|
||
'7z': 'application/x-7z-compressed',
|
||
'tar': 'application/x-tar',
|
||
'gz': 'application/gzip',
|
||
|
||
// CAD
|
||
'dwg': 'application/acad',
|
||
'dxf': 'application/dxf',
|
||
'dwf': 'model/vnd.dwf',
|
||
'dgn': 'application/x-dgn',
|
||
|
||
// Other
|
||
'mp4': 'video/mp4',
|
||
'mp3': 'audio/mpeg',
|
||
'wav': 'audio/wav',
|
||
'avi': 'video/x-msvideo',
|
||
'mov': 'video/quicktime',
|
||
'md': 'text/markdown',
|
||
'log': 'text/plain',
|
||
'ini': 'text/plain',
|
||
'cfg': 'text/plain',
|
||
'conf': 'text/plain',
|
||
'yaml': 'text/yaml',
|
||
'yml': 'text/yaml'
|
||
};
|
||
|
||
return mimeTypes[ext] || 'application/octet-stream';
|
||
}
|
||
|
||
// Sort table
|
||
function sortTable(field) {
|
||
if (window.app.sortField === field) {
|
||
// Toggle direction
|
||
window.app.sortDirection = window.app.sortDirection === 'asc' ? 'desc' : 'asc';
|
||
} else {
|
||
// New field, default to ascending
|
||
window.app.sortField = field;
|
||
window.app.sortDirection = 'asc';
|
||
}
|
||
|
||
updateSortIndicators();
|
||
window.app.modules.filtering.applyFilters(); // Re-apply filters which will trigger table update
|
||
window.app.modules.urlState.push();
|
||
}
|
||
|
||
// Update sort indicators
|
||
function updateSortIndicators() {
|
||
// Remove all sort indicators
|
||
document.querySelectorAll('th[data-sort]').forEach(th => {
|
||
th.removeAttribute('data-sort');
|
||
});
|
||
|
||
// Add current sort indicator
|
||
const th = document.querySelector(`th[data-field="${window.app.sortField}"]`);
|
||
if (th) {
|
||
th.setAttribute('data-sort', window.app.sortDirection);
|
||
}
|
||
}
|
||
|
||
// Column resize functionality
|
||
function initializeColumnResize() {
|
||
const handles = document.querySelectorAll('.resize-handle');
|
||
handles.forEach(handle => {
|
||
handle.addEventListener('mousedown', startResize);
|
||
});
|
||
|
||
document.addEventListener('mousemove', doResize);
|
||
document.addEventListener('mouseup', stopResize);
|
||
}
|
||
|
||
function startResize(e) {
|
||
const th = e.target.parentElement;
|
||
resizing = {
|
||
th: th,
|
||
startX: e.clientX,
|
||
startWidth: th.offsetWidth
|
||
};
|
||
|
||
document.body.style.cursor = 'col-resize';
|
||
document.body.style.userSelect = 'none';
|
||
}
|
||
|
||
function doResize(e) {
|
||
if (! resizing) return;
|
||
|
||
const diff = e.clientX - resizing.startX;
|
||
const newWidth = Math.max(100, resizing.startWidth + diff);
|
||
resizing.th.style.width = newWidth + 'px';
|
||
|
||
// Update corresponding column
|
||
const field = resizing.th.getAttribute('data-field');
|
||
const cells = document.querySelectorAll(`td[data-field="${field}"]`);
|
||
cells.forEach(cell => {
|
||
cell.style.width = newWidth + 'px';
|
||
});
|
||
}
|
||
|
||
function stopResize() {
|
||
if (resizing) {
|
||
document.body.style.cursor = '';
|
||
document.body.style.userSelect = '';
|
||
resizing = null;
|
||
}
|
||
}
|
||
|
||
// Toggle all files (Ctrl+A shortcut handler)
|
||
// wrapper around toggleSelectAllVisible for keyboard shortcuts
|
||
function toggleSelectAll() {
|
||
toggleSelectAllVisible(true);
|
||
}
|
||
|
||
/**
|
||
* Clean up resources when page unloads
|
||
*/
|
||
window.addEventListener('beforeunload', () => {
|
||
cleanupAllBlobUrls();
|
||
});
|
||
|
||
window.app.modules.table = {
|
||
updateFileTable,
|
||
toggleFileSelection,
|
||
toggleSelectAllVisible,
|
||
updateSelectAllVisibleCheckbox,
|
||
setupFileLinkHandlers,
|
||
updateSortIndicators,
|
||
sortTable,
|
||
initializeColumnResize
|
||
};
|
||
})();
|
||
|
||
(function() {
|
||
'use strict';
|
||
// Export functionality
|
||
|
||
// Escape a single value for RFC-4180 CSV
|
||
function csvCell(value) {
|
||
const str = String(value == null ? '' : value);
|
||
if (str.includes(',') || str.includes('"') || str.includes('\n')) {
|
||
return '"' + str.replace(/"/g, '""') + '"';
|
||
}
|
||
return str;
|
||
}
|
||
|
||
// Convert an array of row arrays to a CSV string
|
||
function rowsToCSV(rows) {
|
||
return rows.map(row => row.map(csvCell).join(',')).join('\n');
|
||
}
|
||
|
||
// Export selected files to CSV
|
||
function exportCSV() {
|
||
if (window.app.selectedFiles.size === 0) {
|
||
alert('No files selected for export.');
|
||
return;
|
||
}
|
||
|
||
const headers = ['Tracking Number', 'Title', 'Revision', 'Status', 'Extension', 'Size', 'Size (bytes)', 'Path', 'Modified'];
|
||
const rows = [headers];
|
||
|
||
// Add data rows for selected files only
|
||
window.app.files.forEach(file => {
|
||
if (!window.app.selectedFiles.has(file.id)) return;
|
||
|
||
rows.push([
|
||
file.trackingNumber || '',
|
||
file.title || '',
|
||
file.revision || '',
|
||
file.status || '',
|
||
file.extension || '',
|
||
formatFileSize(file.size),
|
||
file.size != null ? file.size : '',
|
||
file.path,
|
||
file.modified ? new Date(file.modified).toLocaleString() : '—'
|
||
]);
|
||
});
|
||
|
||
downloadFile(rowsToCSV(rows), 'archive-export.csv', 'text/csv');
|
||
}
|
||
|
||
// Download selected files as ZIP
|
||
async function downloadSelected() {
|
||
if (window.app.selectedFiles.size === 0) {
|
||
alert('No files selected for download.');
|
||
return;
|
||
}
|
||
|
||
// JSZip is vendored (concat'd by build.sh), so window.JSZip is
|
||
// already defined. Defensive check in case a future refactor
|
||
// reorders things.
|
||
if (typeof JSZip === 'undefined') {
|
||
alert('JSZip library not bundled — rebuild archive with shared/vendor/jszip.min.js');
|
||
return;
|
||
}
|
||
|
||
const zip = new JSZip();
|
||
const selectedFiles = [];
|
||
|
||
// Get selected file objects
|
||
window.app.files.forEach(file => {
|
||
if (window.app.selectedFiles.has(file.id)) {
|
||
selectedFiles.push(file);
|
||
}
|
||
});
|
||
|
||
// Show progress
|
||
showProgress('Preparing ZIP file...', 0, selectedFiles.length);
|
||
|
||
try {
|
||
// Add files to ZIP
|
||
for (let i = 0; i < selectedFiles.length; i++) {
|
||
const file = selectedFiles[i];
|
||
showProgress(`Adding ${file.name}...`, i + 1, selectedFiles.length);
|
||
|
||
try {
|
||
let arrayBuffer;
|
||
if (file.handle) {
|
||
// Local mode: read via File System Access API
|
||
const fileData = await file.handle.getFile();
|
||
arrayBuffer = await fileData.arrayBuffer();
|
||
} else if (file.url) {
|
||
// HTTP mode: fetch from server
|
||
const resp = await fetch(file.url);
|
||
if (!resp.ok) throw new Error('HTTP ' + resp.status);
|
||
arrayBuffer = await resp.arrayBuffer();
|
||
} else {
|
||
throw new Error('No file handle or URL available');
|
||
}
|
||
|
||
// Create folder structure in ZIP
|
||
const relativePath = file.path.substring(file.path.indexOf('/') + 1); // Remove root directory
|
||
zip.file(relativePath, arrayBuffer);
|
||
} catch (err) {
|
||
console.error(`Error adding file ${file.name}:`, err);
|
||
}
|
||
}
|
||
|
||
showProgress('Generating ZIP...', selectedFiles.length, selectedFiles.length);
|
||
|
||
// Generate ZIP
|
||
const blob = await zip.generateAsync({
|
||
type: 'blob',
|
||
compression: 'DEFLATE',
|
||
compressionOptions: { level: 6 }
|
||
});
|
||
|
||
// Download
|
||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').substring(0, 19);
|
||
downloadBlob(blob, `archive-${timestamp}.zip`);
|
||
|
||
hideProgress();
|
||
|
||
} catch (err) {
|
||
hideProgress();
|
||
console.error('Error creating ZIP:', err);
|
||
alert('Error creating ZIP file: ' + err.message);
|
||
}
|
||
}
|
||
|
||
// Show progress indicator
|
||
function showProgress(message, current, total) {
|
||
let progressDiv = document.getElementById('progressIndicator');
|
||
|
||
if (!progressDiv) {
|
||
progressDiv = document.createElement('div');
|
||
progressDiv.id = 'progressIndicator';
|
||
progressDiv.className = 'progress-indicator';
|
||
document.body.appendChild(progressDiv);
|
||
}
|
||
|
||
const percentage = Math.round((current / total) * 100);
|
||
|
||
progressDiv.innerHTML =
|
||
'<div class="progress-indicator__message">' + window.app.modules.app.escapeHtml(message) + '</div>' +
|
||
'<div class="progress-indicator__track">' +
|
||
'<div class="progress-indicator__fill" style="width:' + percentage + '%"></div>' +
|
||
'</div>' +
|
||
'<div class="progress-indicator__label">' + current + ' / ' + total + '</div>';
|
||
}
|
||
|
||
// Hide progress indicator
|
||
function hideProgress() {
|
||
const progressDiv = document.getElementById('progressIndicator');
|
||
if (progressDiv) {
|
||
progressDiv.remove();
|
||
}
|
||
}
|
||
|
||
// Download file utility
|
||
function downloadFile(content, filename, mimeType) {
|
||
const blob = new Blob([content], { type: mimeType });
|
||
downloadBlob(blob, filename);
|
||
}
|
||
|
||
// Download blob utility
|
||
function downloadBlob(blob, filename) {
|
||
const url = URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = filename;
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
document.body.removeChild(a);
|
||
URL.revokeObjectURL(url);
|
||
}
|
||
|
||
// Format file size
|
||
function formatFileSize(bytes) {
|
||
if (bytes === 0) return '0 B';
|
||
const k = 1024;
|
||
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||
}
|
||
|
||
// Export to HTML report
|
||
function exportHTMLReport() {
|
||
// Group files by tracking number
|
||
const grouped = window.app.modules.parser.groupFilesByTrackingNumber(window.app.filteredFiles);
|
||
const sorted = window.app.modules.parser.sortGroupedFiles(grouped);
|
||
|
||
const html = `
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>Archive Report - ${new Date().toLocaleDateString()}</title>
|
||
<style>
|
||
body { font-family: Arial, sans-serif; margin: 20px; }
|
||
h1 { color: #333; }
|
||
table { border-collapse: collapse; width: 100%; margin-top: 20px; }
|
||
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
|
||
th { background-color: #f2f2f2; font-weight: bold; }
|
||
tr:nth-child(even) { background-color: #f9f9f9; }
|
||
.revision { font-family: monospace; }
|
||
.status { color: #666; font-size: 0.9em; }
|
||
@media print {
|
||
body { margin: 0; }
|
||
h1 { font-size: 18pt; }
|
||
table { font-size: 10pt; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<h1>Archive Report</h1>
|
||
<p>Generated: ${new Date().toLocaleString()}</p>
|
||
<p>Total Files: ${window.app.filteredFiles.length}</p>
|
||
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Tracking Number</th>
|
||
<th>Title</th>
|
||
<th>Revisions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
${sorted.map(group => `
|
||
<tr>
|
||
<td>${window.app.modules.app.escapeHtml(group.trackingNumber)}</td>
|
||
<td>${window.app.modules.app.escapeHtml(group.title)}</td>
|
||
<td>
|
||
${group.sortedRevisions.map(rev => `
|
||
<div>
|
||
<span class="revision">${window.app.modules.app.escapeHtml(rev.revision)}</span>
|
||
<span class="status">(${window.app.modules.app.escapeHtml(rev.status)})</span>
|
||
${rev.files.map(f => f.extension.toUpperCase()).join(', ')}
|
||
</div>
|
||
`).join('')}
|
||
</td>
|
||
</tr>
|
||
`).join('')}
|
||
</tbody>
|
||
</table>
|
||
</body>
|
||
</html>`;
|
||
|
||
downloadFile(html, 'archive-report.html', 'text/html');
|
||
}
|
||
|
||
window.app.modules.export = {
|
||
csvCell,
|
||
rowsToCSV,
|
||
exportCSV,
|
||
downloadSelected,
|
||
showProgress,
|
||
hideProgress,
|
||
downloadFile,
|
||
downloadBlob,
|
||
formatFileSize,
|
||
exportHTMLReport
|
||
};
|
||
|
||
})();
|
||
|
||
(function() {
|
||
'use strict';
|
||
// Project-picker dropdown for the archive browser.
|
||
//
|
||
// In multi-project mode (HTTP source against zddc-server, OR ?projects=
|
||
// present in the URL), this dropdown lets the user toggle which projects
|
||
// are scanned. Toggling a checkbox updates window.app.projectFilter, pushes
|
||
// the new ?projects= state to the URL, and triggers a re-scan.
|
||
//
|
||
// In single-project mode the dropdown is hidden — only one project is ever
|
||
// in scope, so picking is meaningless.
|
||
|
||
let isOpen = false;
|
||
|
||
// The set of project names currently shown in the dropdown.
|
||
function getKnownProjects() {
|
||
if (window.app.availableProjects && window.app.availableProjects.length > 0) {
|
||
return window.app.availableProjects.slice();
|
||
}
|
||
// Fall back to whatever is in the URL filter — useful when the server's
|
||
// ProjectInfo endpoint isn't reachable but ?projects= names the set.
|
||
return Array.from(window.app.projectFilter || []);
|
||
}
|
||
|
||
// Visibility-only filter: change visibleProjects, push URL state, re-render
|
||
// UI. No rescan — already-scanned data stays in memory. URL is updated via
|
||
// history.replaceState (same mechanism as every other UI control).
|
||
function applyVisibility(names) {
|
||
window.app.visibleProjects = new Set(names);
|
||
window.app.modules.urlState.push();
|
||
window.app.modules.app.updateUI();
|
||
window.app.modules.filtering.applyFilters();
|
||
renderDropdown();
|
||
}
|
||
|
||
function escapeHtml(text) {
|
||
var div = document.createElement('div');
|
||
div.textContent = text;
|
||
return div.innerHTML;
|
||
}
|
||
|
||
function renderDropdown() {
|
||
var dropdown = document.getElementById('presetDropdown');
|
||
if (!dropdown) return;
|
||
|
||
var selected = new Set(window.app.visibleProjects || []);
|
||
var known = getKnownProjects().slice().sort();
|
||
|
||
var projectsHtml = known.map(name => {
|
||
var checked = selected.has(name) ? ' checked' : '';
|
||
var n = escapeHtml(name);
|
||
return '<div class="preset-project-item">'
|
||
+ '<label class="preset-project-label">'
|
||
+ '<input type="checkbox" class="preset-checkbox" data-name="' + n + '"' + checked + '>'
|
||
+ ' ' + n
|
||
+ '</label>'
|
||
+ '</div>';
|
||
}).join('');
|
||
if (!projectsHtml) {
|
||
projectsHtml = '<div class="preset-no-presets"><i>No projects available</i></div>';
|
||
}
|
||
|
||
dropdown.innerHTML =
|
||
'<div class="preset-section-bottom">'
|
||
+ '<div class="preset-section-label">Projects:</div>'
|
||
+ '<div class="preset-projects-list">' + projectsHtml + '</div>'
|
||
+ '</div>';
|
||
}
|
||
|
||
function toggleDropdown() {
|
||
var dropdown = document.getElementById('presetDropdown');
|
||
if (isOpen) { closeDropdown(); return; }
|
||
isOpen = true;
|
||
if (dropdown) dropdown.classList.remove('hidden');
|
||
renderDropdown();
|
||
}
|
||
|
||
function closeDropdown() {
|
||
isOpen = false;
|
||
var dropdown = document.getElementById('presetDropdown');
|
||
if (dropdown) dropdown.classList.add('hidden');
|
||
}
|
||
|
||
function setupDropdownDelegation() {
|
||
var dropdown = document.getElementById('presetDropdown');
|
||
if (!dropdown) return;
|
||
|
||
dropdown.addEventListener('click', function(e) {
|
||
e.stopPropagation();
|
||
var checkbox = e.target.closest('.preset-checkbox');
|
||
if (!checkbox) return;
|
||
var projectName = checkbox.getAttribute('data-name');
|
||
if (!projectName) return;
|
||
var sel = new Set(window.app.visibleProjects || []);
|
||
if (checkbox.checked) sel.add(projectName);
|
||
else sel.delete(projectName);
|
||
applyVisibility(Array.from(sel));
|
||
});
|
||
}
|
||
|
||
function setupOutsideClickHandler() {
|
||
document.addEventListener('click', function(e) {
|
||
var section = document.getElementById('presetSection');
|
||
var dropdown = document.getElementById('presetDropdown');
|
||
if (isOpen && section && dropdown && !section.contains(e.target)) {
|
||
closeDropdown();
|
||
}
|
||
});
|
||
}
|
||
|
||
function init() {
|
||
var section = document.getElementById('presetSection');
|
||
if (!section) return;
|
||
|
||
// Hide the dropdown entirely outside multi-project mode.
|
||
if (!window.app.isMultiProject) {
|
||
section.classList.add('hidden');
|
||
return;
|
||
}
|
||
section.classList.remove('hidden');
|
||
|
||
var btn = document.getElementById('presetBtn');
|
||
if (!btn || btn.dataset.presetInit) return;
|
||
btn.dataset.presetInit = '1';
|
||
btn.title = 'Project picker';
|
||
btn.textContent = '▾ Projects';
|
||
|
||
btn.addEventListener('click', function(e) {
|
||
e.stopPropagation();
|
||
toggleDropdown();
|
||
});
|
||
|
||
setupDropdownDelegation();
|
||
setupOutsideClickHandler();
|
||
}
|
||
|
||
window.app.modules.presets = {
|
||
init: init,
|
||
toggleDropdown: toggleDropdown,
|
||
closeDropdown: closeDropdown,
|
||
// No-op kept so existing callers (events.js after grouping-folder click)
|
||
// don't need to null-check; preset dirty state was removed with the
|
||
// saved-presets feature.
|
||
checkDirty: function() {}
|
||
};
|
||
|
||
})();
|
||
|
||
(function() {
|
||
'use strict';
|
||
// URL state sync module for ZDDC Archive
|
||
|
||
// Default values for URL params
|
||
var DEFAULT_SORT_FIELD = 'trackingNumber';
|
||
var DEFAULT_SORT_DIRECTION = 'asc';
|
||
var DEFAULT_ENABLED_TYPES = ['issued', 'received'];
|
||
|
||
// Map URL param names to state paths
|
||
var PARAM_MAP = {
|
||
sort: 'sortField',
|
||
dir: 'sortDirection',
|
||
tn: 'columnFilters.trackingNumber',
|
||
ti: 'columnFilters.title',
|
||
rv: 'columnFilters.revisions',
|
||
types: 'enabledFolderTypes',
|
||
gf: 'groupingFilter',
|
||
tf: 'transmittalFilter',
|
||
projects: 'projectFilter',
|
||
show: 'visibleProjects'
|
||
};
|
||
|
||
// Serialize current state to URL query string
|
||
function serialize() {
|
||
var params = new URLSearchParams();
|
||
|
||
// Sort field
|
||
if (window.app.sortField !== DEFAULT_SORT_FIELD) {
|
||
params.set('sort', window.app.sortField);
|
||
}
|
||
|
||
// Sort direction
|
||
if (window.app.sortDirection !== DEFAULT_SORT_DIRECTION) {
|
||
params.set('dir', window.app.sortDirection);
|
||
}
|
||
|
||
// Column filters
|
||
if (window.app.columnFilters.trackingNumber !== '') {
|
||
params.set('tn', window.app.columnFilters.trackingNumber);
|
||
}
|
||
if (window.app.columnFilters.title !== '') {
|
||
params.set('ti', window.app.columnFilters.title);
|
||
}
|
||
if (window.app.columnFilters.revisions !== '') {
|
||
params.set('rv', window.app.columnFilters.revisions);
|
||
}
|
||
|
||
// Folder types (only if different from default [issued, received])
|
||
var enabledTypes = Array.from(window.app.enabledFolderTypes).sort();
|
||
var defaultTypes = DEFAULT_ENABLED_TYPES.slice().sort();
|
||
if (JSON.stringify(enabledTypes) !== JSON.stringify(defaultTypes)) {
|
||
params.set('types', enabledTypes.join(','));
|
||
}
|
||
|
||
// Grouping filter
|
||
if (window.app.groupingFilter !== '') {
|
||
params.set('gf', window.app.groupingFilter);
|
||
}
|
||
|
||
// Transmittal filter
|
||
if (window.app.transmittalFilter !== '') {
|
||
params.set('tf', window.app.transmittalFilter);
|
||
}
|
||
|
||
// Project filter — always preserved if set (for shareable URLs)
|
||
if (window.app.projectFilter && window.app.projectFilter.size > 0) {
|
||
params.set('projects', Array.from(window.app.projectFilter).join(','));
|
||
}
|
||
|
||
// Visibility filter (project picker). Emit only when it's a strict subset
|
||
// of projectFilter — the common "everything visible" case keeps URLs clean.
|
||
if (window.app.visibleProjects && window.app.projectFilter
|
||
&& window.app.projectFilter.size > 0) {
|
||
var pfSize = window.app.projectFilter.size;
|
||
var vp = Array.from(window.app.visibleProjects).filter(function(n) {
|
||
return window.app.projectFilter.has(n);
|
||
});
|
||
if (vp.length < pfSize) {
|
||
params.set('show', vp.slice().sort().join(','));
|
||
}
|
||
}
|
||
|
||
// Build query string
|
||
var qs = params.toString();
|
||
return qs ? '?' + qs : '';
|
||
}
|
||
|
||
// Push state to URL without triggering popstate
|
||
function push() {
|
||
var result = serialize();
|
||
if (result === location.search) {
|
||
return;
|
||
}
|
||
try {
|
||
history.replaceState(null, '', location.pathname + result);
|
||
} catch (e) {
|
||
// Silently swallow errors (e.g., file:// protocol restrictions)
|
||
}
|
||
}
|
||
|
||
// Restore state from URL query string
|
||
function restore() {
|
||
var params = new URLSearchParams(location.search);
|
||
|
||
// Restore sort field
|
||
if (params.has('sort')) {
|
||
var sortValue = params.get('sort');
|
||
if (sortValue === 'trackingNumber' || sortValue === 'title') {
|
||
window.app.sortField = sortValue;
|
||
}
|
||
}
|
||
|
||
// Restore sort direction
|
||
if (params.has('dir')) {
|
||
var dirValue = params.get('dir');
|
||
if (dirValue === 'asc' || dirValue === 'desc') {
|
||
window.app.sortDirection = dirValue;
|
||
}
|
||
}
|
||
|
||
// Restore column filters with AST parsing
|
||
if (params.has('tn')) {
|
||
var tnValue = params.get('tn');
|
||
window.app.columnFilters.trackingNumber = tnValue;
|
||
window.app.columnFilterASTs.trackingNumber = zddc.filter.parse(tnValue);
|
||
}
|
||
if (params.has('ti')) {
|
||
var tiValue = params.get('ti');
|
||
window.app.columnFilters.title = tiValue;
|
||
window.app.columnFilterASTs.title = zddc.filter.parse(tiValue);
|
||
}
|
||
if (params.has('rv')) {
|
||
var rvValue = params.get('rv');
|
||
window.app.columnFilters.revisions = rvValue;
|
||
window.app.columnFilterASTs.revisions = zddc.filter.parse(rvValue);
|
||
}
|
||
|
||
// Restore folder types
|
||
if (params.has('types')) {
|
||
var typesValue = params.get('types');
|
||
var typeValues = typesValue.split(',').map(function(t) { return t.trim(); });
|
||
// Validate against app.FOLDER_TYPE_NAMES
|
||
var validTypes = typeValues.filter(function(t) {
|
||
return window.app.FOLDER_TYPE_NAMES.indexOf(t) !== -1;
|
||
});
|
||
window.app.enabledFolderTypes = new Set(validTypes);
|
||
}
|
||
|
||
// Restore grouping filter
|
||
if (params.has('gf')) {
|
||
window.app.groupingFilter = params.get('gf');
|
||
}
|
||
|
||
// Restore transmittal filter
|
||
if (params.has('tf')) {
|
||
window.app.transmittalFilter = params.get('tf');
|
||
}
|
||
|
||
// Restore project filter
|
||
if (params.has('projects')) {
|
||
var projValue = params.get('projects');
|
||
var projNames = projValue.split(',').map(function(p) { return p.trim(); }).filter(Boolean);
|
||
window.app.projectFilter = new Set(projNames);
|
||
}
|
||
|
||
// Restore visibility filter. autoConnectHttpSource will intersect against
|
||
// projectFilter / availableProjects after the project list resolves, so
|
||
// dropping bogus names is handled there. We just parse here.
|
||
if (params.has('show')) {
|
||
var showValue = params.get('show');
|
||
var showNames = showValue.split(',').map(function(p) { return p.trim(); }).filter(Boolean);
|
||
window.app.visibleProjects = new Set(showNames);
|
||
}
|
||
|
||
// Update DOM inputs to reflect restored values
|
||
updateFilterInputs();
|
||
}
|
||
|
||
// Update DOM filter inputs to match restored state
|
||
function updateFilterInputs() {
|
||
// Column filter inputs
|
||
document.querySelectorAll('.column-filter[data-filter-field]').forEach(function(input) {
|
||
var field = input.getAttribute('data-filter-field');
|
||
var filterValue = window.app.columnFilters[field] || '';
|
||
input.value = filterValue;
|
||
if (filterValue !== '') {
|
||
input.classList.add('filter-active');
|
||
} else {
|
||
input.classList.remove('filter-active');
|
||
}
|
||
});
|
||
|
||
// Grouping filter
|
||
var groupingFilterEl = document.getElementById('groupingFilter');
|
||
if (groupingFilterEl) {
|
||
groupingFilterEl.value = window.app.groupingFilter;
|
||
if (window.app.groupingFilter !== '') {
|
||
groupingFilterEl.classList.add('filter-active');
|
||
} else {
|
||
groupingFilterEl.classList.remove('filter-active');
|
||
}
|
||
}
|
||
|
||
// Transmittal filter
|
||
var transmittalFilterEl = document.getElementById('transmittalFilter');
|
||
if (transmittalFilterEl) {
|
||
transmittalFilterEl.value = window.app.transmittalFilter;
|
||
if (window.app.transmittalFilter !== '') {
|
||
transmittalFilterEl.classList.add('filter-active');
|
||
} else {
|
||
transmittalFilterEl.classList.remove('filter-active');
|
||
}
|
||
}
|
||
}
|
||
|
||
// Register module
|
||
window.app.modules.urlState = {
|
||
serialize: serialize,
|
||
push: push,
|
||
restore: restore
|
||
};
|
||
|
||
})();
|
||
|
||
(function() {
|
||
'use strict';
|
||
// Event handling
|
||
|
||
// Set up all event listeners
|
||
function setupEventListeners() {
|
||
// Header buttons
|
||
document.getElementById('addDirectoryBtn').addEventListener('click', () => window.app.modules.directory.addDirectory());
|
||
document.getElementById('refreshHeaderBtn').addEventListener('click', () => window.app.modules.directory.refreshDirectories());
|
||
|
||
// Content area buttons
|
||
document.getElementById('filterSelectedBtn').addEventListener('click', () => window.app.modules.app.toggleFilterSelected());
|
||
document.getElementById('downloadSelectedBtn').addEventListener('click', () => window.app.modules.export.downloadSelected());
|
||
document.getElementById('exportCsvBtn').addEventListener('click', () => window.app.modules.export.exportCSV());
|
||
|
||
// Search and filter inputs
|
||
document.getElementById('groupingFilter').addEventListener('input', (e) => {
|
||
window.app.groupingFilter = e.target.value;
|
||
e.target.classList.toggle('filter-active', e.target.value.length > 0);
|
||
window.app.modules.app.updateUI();
|
||
window.app.modules.filtering.applyFilters();
|
||
window.app.modules.urlState.push();
|
||
});
|
||
|
||
document.getElementById('transmittalFilter').addEventListener('input', (e) => {
|
||
window.app.transmittalFilter = e.target.value;
|
||
e.target.classList.toggle('filter-active', e.target.value.length > 0);
|
||
window.app.modules.app.updateUI();
|
||
window.app.modules.filtering.applyFilters(); // Re-filter files when transmittal filter changes
|
||
window.app.modules.urlState.push();
|
||
});
|
||
|
||
// Select All Grouping Folders checkbox
|
||
document.getElementById('selectAllGroupingCheckbox').addEventListener('change', (e) => {
|
||
window.app.selectAllGroupingFolders = e.target.checked;
|
||
window.app.modules.app.renderGroupingFolders();
|
||
window.app.modules.app.renderTransmittalFolders();
|
||
window.app.modules.filtering.applyFilters();
|
||
});
|
||
|
||
// Folder type toggle bar — global click delegation
|
||
document.addEventListener('click', (e) => {
|
||
const btn = e.target.closest('.folder-type-toggle');
|
||
if (btn) {
|
||
const type = btn.getAttribute('data-type');
|
||
if (type) window.app.modules.app.toggleFolderType(type);
|
||
}
|
||
});
|
||
|
||
// Select All Transmittals checkbox
|
||
document.getElementById('selectAllTransmittalsCheckbox').addEventListener('change', (e) => {
|
||
window.app.selectAllTransmittals = e.target.checked;
|
||
window.app.modules.app.renderTransmittalFolders();
|
||
window.app.modules.filtering.applyFilters();
|
||
});
|
||
|
||
// Modifier filter dropdown
|
||
document.getElementById('modifierFilterBtn').addEventListener('click', () => window.app.modules.app.toggleModifierDropdown());
|
||
document.getElementById('modifierSelectAll').addEventListener('change', (e) => {
|
||
window.app.modules.app.toggleAllModifiers(e.target.checked);
|
||
});
|
||
|
||
// Close modifier dropdown when clicking outside
|
||
document.addEventListener('click', (e) => {
|
||
const container = document.querySelector('.modifier-filter-container');
|
||
const dropdown = document.getElementById('modifierFilterDropdown');
|
||
if (container && dropdown && !container.contains(e.target)) {
|
||
dropdown.classList.add('hidden');
|
||
}
|
||
});
|
||
|
||
// Select all visible files checkbox
|
||
document.getElementById('selectAllVisibleCheckbox').addEventListener('change', (e) => {
|
||
e.stopPropagation();
|
||
window.app.modules.table.toggleSelectAllVisible(e.target.checked);
|
||
});
|
||
|
||
// Reset filters button
|
||
document.getElementById('resetFiltersBtn').addEventListener('click', () => window.app.modules.filtering.clearFilters());
|
||
|
||
// Column filters — delegated from thead
|
||
const thead = document.querySelector('thead');
|
||
if (thead) {
|
||
thead.addEventListener('input', (e) => {
|
||
if (e.target.matches('.column-filter[data-filter-field]')) {
|
||
const field = e.target.getAttribute('data-filter-field');
|
||
const raw = e.target.value.trim();
|
||
window.app.columnFilters[field] = raw;
|
||
window.app.columnFilterASTs[field] = zddc.filter.parse(raw);
|
||
|
||
// Add/remove filter-active class based on non-empty value
|
||
if (raw) {
|
||
e.target.classList.add('filter-active');
|
||
} else {
|
||
e.target.classList.remove('filter-active');
|
||
}
|
||
|
||
window.app.modules.filtering.applyFilters();
|
||
window.app.modules.urlState.push();
|
||
}
|
||
});
|
||
thead.addEventListener('keydown', (e) => {
|
||
if (!e.target.matches('.column-filter[data-filter-field]')) return;
|
||
if (e.key === 'Escape') {
|
||
e.target.value = '';
|
||
e.target.classList.remove('filter-active');
|
||
const field = e.target.getAttribute('data-filter-field');
|
||
window.app.columnFilters[field] = '';
|
||
window.app.columnFilterASTs[field] = null;
|
||
window.app.modules.filtering.applyFilters();
|
||
window.app.modules.urlState.push();
|
||
e.preventDefault();
|
||
} else if (e.key === 'Enter') {
|
||
e.preventDefault();
|
||
const inputs = Array.from(thead.querySelectorAll('.column-filter'));
|
||
const idx = inputs.indexOf(e.target);
|
||
if (idx !== -1) {
|
||
inputs[(idx + 1) % inputs.length].focus();
|
||
}
|
||
}
|
||
});
|
||
thead.addEventListener('click', (e) => {
|
||
if (e.target.matches('.column-filter')) {
|
||
e.stopPropagation();
|
||
}
|
||
});
|
||
}
|
||
|
||
// Table sorting
|
||
document.querySelectorAll('.sortable').forEach(th => {
|
||
th.querySelector('.th-content').addEventListener('click', () => {
|
||
const field = th.getAttribute('data-field');
|
||
window.app.modules.table.sortTable(field);
|
||
});
|
||
});
|
||
|
||
// Initialize column resize
|
||
window.app.modules.table.initializeColumnResize();
|
||
|
||
// Modal close buttons
|
||
document.querySelectorAll('.modal-close').forEach(btn => {
|
||
btn.addEventListener('click', closeModal);
|
||
});
|
||
|
||
// Modal backdrop clicks
|
||
document.querySelectorAll('.modal-backdrop').forEach(backdrop => {
|
||
backdrop.addEventListener('click', closeModal);
|
||
});
|
||
|
||
// Drop modal buttons
|
||
const dropModal = document.getElementById('dropModal');
|
||
dropModal.querySelector('.modal-cancel').addEventListener('click', closeModal);
|
||
dropModal.querySelector('.modal-confirm').addEventListener('click', () => window.app.modules.dragDrop.confirmTransmittal());
|
||
|
||
// Drag and drop (local mode only — requires write access)
|
||
if (window.app.sourceMode === 'local') {
|
||
window.app.modules.dragDrop.setupDragAndDrop();
|
||
}
|
||
|
||
// Multi-select for folders
|
||
setupFolderMultiSelect();
|
||
|
||
// Date group toggle handlers
|
||
setupDateGroupToggles();
|
||
|
||
// Grouping section collapse toggle
|
||
setupGroupingToggle();
|
||
|
||
// Resizable panes
|
||
setupResizablePanes();
|
||
|
||
// Keyboard shortcuts
|
||
document.addEventListener('keydown', handleKeyboardShortcuts);
|
||
}
|
||
|
||
|
||
|
||
// Handle grouping filter
|
||
function handleGroupingFilter(e) {
|
||
window.app.groupingFilter = e.target.value;
|
||
window.app.modules.app.renderGroupingFolders();
|
||
// Re-render transmittal folders as they depend on grouping selection
|
||
window.app.modules.app.renderTransmittalFolders();
|
||
// Re-filter files based on updated folder selections
|
||
window.app.modules.filtering.applyFilters();
|
||
}
|
||
|
||
// Handle transmittal filter
|
||
function handleTransmittalFilter(e) {
|
||
window.app.transmittalFilter = e.target.value;
|
||
window.app.modules.app.renderTransmittalFolders();
|
||
// Re-filter files based on updated folder selections
|
||
window.app.modules.filtering.applyFilters();
|
||
}
|
||
|
||
// Close modal
|
||
function closeModal(e) {
|
||
const modal = e.target.closest('.modal');
|
||
if (modal) {
|
||
modal.classList.add('hidden');
|
||
}
|
||
}
|
||
|
||
// Handle keyboard shortcuts
|
||
function handleKeyboardShortcuts(e) {
|
||
// Escape closes modals
|
||
if (e.key === 'Escape') {
|
||
document.querySelectorAll('.modal:not(.hidden)').forEach(modal => {
|
||
modal.classList.add('hidden');
|
||
});
|
||
}
|
||
|
||
// Ctrl+A selects all visible files
|
||
if (e.ctrlKey && e.key === 'a' && e.target.tagName !== 'INPUT') {
|
||
e.preventDefault();
|
||
toggleSelectAll();
|
||
}
|
||
|
||
// F5 refreshes
|
||
if (e.key === 'F5') {
|
||
e.preventDefault();
|
||
window.app.modules.directory.refreshDirectories();
|
||
}
|
||
}
|
||
|
||
// Utility: Debounce function
|
||
function debounce(func, wait) {
|
||
let timeout;
|
||
return function executedFunction(...args) {
|
||
const later = () => {
|
||
clearTimeout(timeout);
|
||
func(...args);
|
||
};
|
||
clearTimeout(timeout);
|
||
timeout = setTimeout(later, wait);
|
||
};
|
||
}
|
||
|
||
// Multi-select handling for folder lists
|
||
function setupFolderMultiSelect() {
|
||
let lastSelectedGroupingIndex = -1;
|
||
let lastSelectedTransmittalIndex = -1;
|
||
|
||
// Handle grouping folders
|
||
const groupingList = document.getElementById('groupingFoldersList');
|
||
groupingList.addEventListener('click', (e) => {
|
||
const result = handleFolderClick(e, window.app.selectedGroupingFolders, lastSelectedGroupingIndex);
|
||
if (result !== undefined) {
|
||
lastSelectedGroupingIndex = result;
|
||
// Turn off "Select All" mode when user manually selects
|
||
if (window.app.selectAllGroupingFolders) {
|
||
window.app.selectAllGroupingFolders = false;
|
||
document.getElementById('selectAllGroupingCheckbox').checked = false;
|
||
}
|
||
// Update selection state first
|
||
window.app.modules.app.updateFolderSelectionState('groupingFoldersList');
|
||
// Then update transmittal folder list based on new selection
|
||
window.app.modules.app.renderTransmittalFolders();
|
||
window.app.modules.filtering.applyFilters(); // Re-filter files
|
||
// Check presets dirty state
|
||
if (window.app.modules.presets) {
|
||
window.app.modules.presets.checkDirty();
|
||
}
|
||
// Reset transmittal index since list may have changed
|
||
lastSelectedTransmittalIndex = -1;
|
||
}
|
||
});
|
||
|
||
// Handle transmittal folders
|
||
const transmittalList = document.getElementById('transmittalFoldersList');
|
||
transmittalList.addEventListener('click', (e) => {
|
||
const result = handleFolderClick(e, window.app.selectedTransmittalFolders, lastSelectedTransmittalIndex);
|
||
if (result !== undefined) {
|
||
lastSelectedTransmittalIndex = result;
|
||
// Turn off "Select All" mode when user manually selects
|
||
if (window.app.selectAllTransmittals) {
|
||
window.app.selectAllTransmittals = false;
|
||
document.getElementById('selectAllTransmittalsCheckbox').checked = false;
|
||
}
|
||
// Update selection state without rebuilding DOM
|
||
window.app.modules.app.updateFolderSelectionState('transmittalFoldersList');
|
||
window.app.modules.filtering.applyFilters(); // Update file display
|
||
}
|
||
});
|
||
|
||
// Handle Ctrl+A for folder lists
|
||
groupingList.addEventListener('keydown', (e) => {
|
||
if (e.ctrlKey && e.key === 'a') {
|
||
e.preventDefault();
|
||
selectAllVisibleFolders('grouping');
|
||
}
|
||
});
|
||
|
||
transmittalList.addEventListener('keydown', (e) => {
|
||
if (e.ctrlKey && e.key === 'a') {
|
||
e.preventDefault();
|
||
selectAllVisibleFolders('transmittal');
|
||
}
|
||
});
|
||
|
||
// Make lists focusable
|
||
groupingList.setAttribute('tabindex', '0');
|
||
transmittalList.setAttribute('tabindex', '0');
|
||
}
|
||
|
||
/**
|
||
* Handle folder click with multi-select support (Shift/Ctrl)
|
||
* @param {Event} e - Click event
|
||
* @param {Set} selectedSet - Set of selected folder paths
|
||
* @param {number} lastIndex - Index of last clicked item
|
||
* @returns {number|undefined} Current index if valid click, undefined otherwise
|
||
*/
|
||
function handleFolderClick(e, selectedSet, lastIndex) {
|
||
const folderItem = e.target.closest('.folder-item');
|
||
if (!folderItem) return undefined;
|
||
|
||
const path = folderItem.getAttribute('data-path');
|
||
if (!path) return undefined;
|
||
|
||
const container = folderItem.parentElement;
|
||
const items = Array.from(container.children);
|
||
const currentIndex = items.indexOf(folderItem);
|
||
|
||
if (e.shiftKey && lastIndex !== -1 && lastIndex < items.length) {
|
||
// Shift+click: select range from last to current
|
||
e.preventDefault();
|
||
const start = Math.min(lastIndex, currentIndex);
|
||
const end = Math.max(lastIndex, currentIndex);
|
||
|
||
if (!e.ctrlKey) {
|
||
selectedSet.clear();
|
||
}
|
||
|
||
for (let i = start; i <= end; i++) {
|
||
const itemPath = items[i]?.getAttribute('data-path');
|
||
if (itemPath) {
|
||
selectedSet.add(itemPath);
|
||
}
|
||
}
|
||
} else if (e.ctrlKey || e.metaKey) {
|
||
// Ctrl+click: toggle individual selection
|
||
e.preventDefault();
|
||
if (selectedSet.has(path)) {
|
||
selectedSet.delete(path);
|
||
} else {
|
||
selectedSet.add(path);
|
||
}
|
||
} else {
|
||
// Regular click: clear and select single item
|
||
selectedSet.clear();
|
||
selectedSet.add(path);
|
||
}
|
||
|
||
return currentIndex;
|
||
}
|
||
|
||
/**
|
||
* Toggle expand/collapse state of a grouping folder
|
||
* @param {string} path - Folder path to toggle
|
||
* @param {boolean} recursive - If true, also toggle all descendants
|
||
*/
|
||
function toggleGroupingFolder(path, recursive) {
|
||
const isCurrentlyCollapsed = window.app.collapsedGroupingFolders.has(path);
|
||
|
||
if (recursive) {
|
||
// Get all descendant folder paths
|
||
const descendants = window.app.groupingFolders
|
||
.filter(f => f.path.startsWith(path + '/'))
|
||
.map(f => f.path);
|
||
|
||
if (isCurrentlyCollapsed) {
|
||
// Expand this folder and all descendants
|
||
window.app.collapsedGroupingFolders.delete(path);
|
||
descendants.forEach(p => window.app.collapsedGroupingFolders.delete(p));
|
||
} else {
|
||
// Collapse this folder and all descendants
|
||
window.app.collapsedGroupingFolders.add(path);
|
||
descendants.forEach(p => window.app.collapsedGroupingFolders.add(p));
|
||
}
|
||
} else {
|
||
// Just toggle this folder
|
||
if (isCurrentlyCollapsed) {
|
||
window.app.collapsedGroupingFolders.delete(path);
|
||
} else {
|
||
window.app.collapsedGroupingFolders.add(path);
|
||
}
|
||
}
|
||
|
||
window.app.modules.app.renderGroupingFolders();
|
||
}
|
||
|
||
// Select all visible folders
|
||
function selectAllVisibleFolders(folderType) {
|
||
const container = folderType === 'grouping' ?
|
||
document.getElementById('groupingFoldersList') :
|
||
document.getElementById('transmittalFoldersList');
|
||
|
||
const selectedSet = folderType === 'grouping' ?
|
||
window.app.selectedGroupingFolders :
|
||
window.app.selectedTransmittalFolders;
|
||
|
||
selectedSet.clear();
|
||
|
||
const items = container.querySelectorAll('.folder-item');
|
||
items.forEach(item => {
|
||
const path = item.getAttribute('data-path');
|
||
if (path) {
|
||
selectedSet.add(path);
|
||
}
|
||
});
|
||
|
||
if (folderType === 'grouping') {
|
||
// Update UI to reflect grouping changes
|
||
window.app.modules.app.updateUI();
|
||
window.app.modules.filtering.applyFilters();
|
||
} else {
|
||
// For transmittal folders, just update selection state
|
||
window.app.modules.app.updateFolderSelectionState('transmittalFoldersList');
|
||
window.app.modules.filtering.applyFilters();
|
||
}
|
||
}
|
||
|
||
// Setup date group toggle handlers
|
||
function setupDateGroupToggles() {
|
||
// Toggle all dates button
|
||
const toggleAllBtn = document.getElementById('toggleAllDatesBtn');
|
||
if (toggleAllBtn) {
|
||
toggleAllBtn.addEventListener('click', toggleAllDateGroups);
|
||
}
|
||
|
||
// Individual date group headers (using event delegation)
|
||
const transmittalList = document.getElementById('transmittalFoldersList');
|
||
transmittalList.addEventListener('click', (e) => {
|
||
const header = e.target.closest('.date-group-header');
|
||
if (header) {
|
||
const date = header.getAttribute('data-date');
|
||
if (date) {
|
||
toggleDateGroup(date);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// Toggle a single date group
|
||
function toggleDateGroup(date) {
|
||
if (window.app.collapsedDateGroups.has(date)) {
|
||
window.app.collapsedDateGroups.delete(date);
|
||
} else {
|
||
window.app.collapsedDateGroups.add(date);
|
||
}
|
||
window.app.modules.app.renderTransmittalFolders();
|
||
updateToggleAllIcon();
|
||
}
|
||
|
||
// Toggle all date groups
|
||
function toggleAllDateGroups() {
|
||
const headers = document.querySelectorAll('.date-group-header');
|
||
const allDates = Array.from(headers).map(h => h.getAttribute('data-date')).filter(Boolean);
|
||
|
||
// If all are collapsed, expand all. Otherwise, collapse all.
|
||
const allCollapsed = allDates.length > 0 && allDates.every(date => window.app.collapsedDateGroups.has(date));
|
||
|
||
if (allCollapsed) {
|
||
// Expand all
|
||
window.app.collapsedDateGroups.clear();
|
||
} else {
|
||
// Collapse all
|
||
allDates.forEach(date => window.app.collapsedDateGroups.add(date));
|
||
}
|
||
|
||
window.app.modules.app.renderTransmittalFolders();
|
||
updateToggleAllIcon();
|
||
}
|
||
|
||
// Update the toggle all icon based on current state
|
||
function updateToggleAllIcon() {
|
||
const icon = document.getElementById('toggleAllDatesIcon');
|
||
if (!icon) return;
|
||
|
||
const headers = document.querySelectorAll('.date-group-header');
|
||
const allDates = Array.from(headers).map(h => h.getAttribute('data-date')).filter(Boolean);
|
||
const allCollapsed = allDates.length > 0 && allDates.every(date => window.app.collapsedDateGroups.has(date));
|
||
|
||
icon.textContent = allCollapsed ? '▶' : '▼';
|
||
}
|
||
|
||
// Setup grouping section collapse toggle
|
||
function setupGroupingToggle() {
|
||
const toggleBtn = document.getElementById('toggleGroupingBtn');
|
||
const groupingSection = document.getElementById('groupingSection');
|
||
const icon = document.getElementById('toggleGroupingIcon');
|
||
|
||
if (toggleBtn && groupingSection && icon) {
|
||
toggleBtn.addEventListener('click', () => {
|
||
groupingSection.classList.toggle('collapsed');
|
||
icon.textContent = groupingSection.classList.contains('collapsed') ? '▶' : '▼';
|
||
});
|
||
}
|
||
}
|
||
|
||
// Setup resizable panes
|
||
function setupResizablePanes() {
|
||
// Resize nav sections (vertical divider between grouping and transmittal)
|
||
const navSectionsHandle = document.querySelector('[data-resize="nav-sections"]');
|
||
if (navSectionsHandle) {
|
||
let isResizing = false;
|
||
let startY = 0;
|
||
let startHeight = 0;
|
||
let groupingSection = null;
|
||
|
||
navSectionsHandle.addEventListener('mousedown', (e) => {
|
||
isResizing = true;
|
||
startY = e.clientY;
|
||
groupingSection = document.getElementById('groupingSection');
|
||
startHeight = groupingSection.offsetHeight;
|
||
navSectionsHandle.classList.add('resizing');
|
||
e.preventDefault();
|
||
});
|
||
|
||
document.addEventListener('mousemove', (e) => {
|
||
if (!isResizing) return;
|
||
|
||
const deltaY = e.clientY - startY;
|
||
const newHeight = startHeight + deltaY;
|
||
|
||
// Set min/max heights
|
||
if (newHeight >= 100 && newHeight <= window.innerHeight - 250) {
|
||
groupingSection.style.flex = 'none';
|
||
groupingSection.style.height = newHeight + 'px';
|
||
}
|
||
});
|
||
|
||
document.addEventListener('mouseup', () => {
|
||
if (isResizing) {
|
||
isResizing = false;
|
||
navSectionsHandle.classList.remove('resizing');
|
||
}
|
||
});
|
||
}
|
||
|
||
// Resize nav pane (horizontal divider between nav and content)
|
||
const navPaneHandle = document.querySelector('[data-resize="nav-pane"]');
|
||
if (navPaneHandle) {
|
||
let isResizing = false;
|
||
let startX = 0;
|
||
let startWidth = 0;
|
||
let navPane = null;
|
||
|
||
navPaneHandle.addEventListener('mousedown', (e) => {
|
||
isResizing = true;
|
||
startX = e.clientX;
|
||
navPane = document.getElementById('navigationPane');
|
||
startWidth = navPane.offsetWidth;
|
||
navPaneHandle.classList.add('resizing');
|
||
e.preventDefault();
|
||
});
|
||
|
||
document.addEventListener('mousemove', (e) => {
|
||
if (!isResizing) return;
|
||
|
||
const deltaX = e.clientX - startX;
|
||
const newWidth = startWidth + deltaX;
|
||
|
||
// Set min/max widths
|
||
if (newWidth >= 200 && newWidth <= window.innerWidth - 400) {
|
||
navPane.style.width = newWidth + 'px';
|
||
}
|
||
});
|
||
|
||
document.addEventListener('mouseup', () => {
|
||
if (isResizing) {
|
||
isResizing = false;
|
||
navPaneHandle.classList.remove('resizing');
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
window.app.modules.events = {
|
||
setupEventListeners,
|
||
handleFolderClick,
|
||
toggleGroupingFolder,
|
||
selectAllVisibleFolders,
|
||
setupDateGroupToggles,
|
||
toggleDateGroup,
|
||
toggleAllDateGroups,
|
||
updateToggleAllIcon,
|
||
setupGroupingToggle,
|
||
setupResizablePanes
|
||
};
|
||
|
||
})();
|
||
|
||
(function() {
|
||
'use strict';
|
||
// window.app is initialized in init.js. Reference shape (read-only docs):
|
||
// directories[], groupingFolders[], transmittalFolders[], files[],
|
||
// filteredFiles[], selectedFiles:Set, sourceMode ('local'|'http'),
|
||
// isScanning, scanProgress,
|
||
// columnFilters {trackingNumber,title,revisions}, columnFilterASTs {...},
|
||
// groupingFilter, transmittalFilter,
|
||
// enabledFolderTypes:Set('issued','received'),
|
||
// sortField ('trackingNumber'), sortDirection ('asc'|'desc'),
|
||
// selectedGroupingFolders:Set, selectedTransmittalFolders:Set,
|
||
// collapsedDateGroups:Set, collapsedGroupingFolders:Set,
|
||
// selectAllGroupingFolders:bool, selectAllTransmittals:bool,
|
||
// availableModifiers:Set, selectedModifiers:Set, showSelectedOnly:bool
|
||
|
||
// Parse search terms from filter string
|
||
function parseSearchTerms(filter) {
|
||
if (!filter || !filter.trim()) return [];
|
||
return filter.trim().toLowerCase().split(/\s+/);
|
||
}
|
||
|
||
// Check if text matches all search terms (AND logic)
|
||
function matchesSearchTerms(text, terms) {
|
||
if (!terms || terms.length === 0) return true;
|
||
return terms.every(term => text.includes(term));
|
||
}
|
||
|
||
// Initialize application
|
||
function initApp() {
|
||
// Detect source mode from protocol
|
||
window.app.sourceMode = (location.protocol === 'file:') ? 'local' : 'http';
|
||
|
||
if (window.app.sourceMode === 'local') {
|
||
// Check File System Access API support (local mode only)
|
||
if (!('showDirectoryPicker' in window)) {
|
||
showUnsupportedBrowserMessage();
|
||
return;
|
||
}
|
||
}
|
||
|
||
// Set up event listeners
|
||
window.app.modules.events.setupEventListeners();
|
||
|
||
// Set up file link handlers (event delegation)
|
||
window.app.modules.table.setupFileLinkHandlers();
|
||
|
||
// Apply source-mode-specific UI adjustments
|
||
applySourceModeUI();
|
||
|
||
// Restore filter/sort state from URL query string
|
||
window.app.modules.urlState.restore();
|
||
|
||
// Initialize UI
|
||
updateUI();
|
||
|
||
// Show initial sort indicator
|
||
window.app.modules.table.updateSortIndicators();
|
||
|
||
if (window.app.sourceMode === 'http') {
|
||
// Auto-connect to the server in HTTP mode
|
||
autoConnectHttpSource();
|
||
} else {
|
||
// Show empty state if no directories (local mode)
|
||
if (window.app.directories.length === 0) {
|
||
showEmptyState();
|
||
}
|
||
}
|
||
}
|
||
|
||
// Apply UI differences based on source mode
|
||
function applySourceModeUI() {
|
||
// "Add Local Directory" button is always visible in both modes —
|
||
// in HTTP mode the user can augment the online archive with local directories.
|
||
}
|
||
|
||
// Auto-connect to the HTTP server
|
||
// Derives the base URL from the current page's location
|
||
async function autoConnectHttpSource() {
|
||
var href = window.location.href;
|
||
// Strip query string and fragment
|
||
href = href.split('?')[0].split('#')[0];
|
||
// Strip the filename to get the directory
|
||
var lastSlash = href.lastIndexOf('/');
|
||
var baseUrl = (lastSlash >= 0) ? href.substring(0, lastSlash + 1) : href + '/';
|
||
|
||
// Multi-project mode is opt-in via ?projects= in the URL.
|
||
// ?projects= absent → not multi-project; scan whatever the URL
|
||
// points to (single-project or in-archive
|
||
// mode). The server's project list, if any,
|
||
// stays out of view.
|
||
// ?projects= empty → multi-project; include every project the
|
||
// server says the user can access.
|
||
// ?projects=A,B → multi-project; include only the listed
|
||
// projects (intersected with server access).
|
||
// The archive never sees projects beyond this scope — the visibility
|
||
// dropdown only narrows what's already in availableProjects.
|
||
var urlParams = new URLSearchParams(location.search);
|
||
var projectsParamPresent = urlParams.has('projects');
|
||
|
||
if (projectsParamPresent) {
|
||
window.app.isMultiProject = true;
|
||
|
||
// Fetch the server's ACL-filtered project list so we can drop any
|
||
// listed names the user doesn't actually have access to (and so
|
||
// the empty-projects= "include everything" mode has a list to use).
|
||
var serverNames = null;
|
||
try {
|
||
var resp = await fetch(baseUrl, { headers: { 'Accept': 'application/json' } });
|
||
if (resp.ok) {
|
||
var serverProjects = await resp.json();
|
||
if (Array.isArray(serverProjects) && serverProjects.length > 0
|
||
&& serverProjects[0] && typeof serverProjects[0].name === 'string') {
|
||
serverNames = new Set(serverProjects.map(function(p) { return p.name; }));
|
||
}
|
||
}
|
||
} catch (e) {
|
||
// Plain Caddy or proxy-stripped — trust the URL list as-is.
|
||
}
|
||
|
||
if (window.app.projectFilter && window.app.projectFilter.size > 0) {
|
||
// Listed names: intersect with server access, warn about misses.
|
||
if (serverNames) {
|
||
var accessible = new Set();
|
||
var missing = [];
|
||
window.app.projectFilter.forEach(function(p) {
|
||
if (serverNames.has(p)) accessible.add(p);
|
||
else missing.push(p);
|
||
});
|
||
window.app.projectFilter = accessible;
|
||
if (missing.length > 0) showProjectWarning(missing);
|
||
}
|
||
window.app.availableProjects = Array.from(window.app.projectFilter).sort();
|
||
} else if (serverNames) {
|
||
// Empty ?projects= — scan everything the user can access.
|
||
window.app.projectFilter = new Set(serverNames);
|
||
window.app.availableProjects = Array.from(serverNames).sort();
|
||
}
|
||
// else: ?projects= empty AND no server list — leave projectFilter
|
||
// empty; source.js will fall through to in-archive mode.
|
||
}
|
||
|
||
// visibleProjects: default to projectFilter (everything visible), or
|
||
// honor an explicit ?show= from the URL (intersected with projectFilter
|
||
// to drop names that aren't in scope). An empty ?show= means "hide
|
||
// everything" — distinct from "no ?show= at all".
|
||
var showInUrl = urlParams.has('show');
|
||
if (showInUrl) {
|
||
var inScope = new Set();
|
||
(window.app.visibleProjects || new Set()).forEach(function(n) {
|
||
if (window.app.projectFilter.has(n)) inScope.add(n);
|
||
});
|
||
window.app.visibleProjects = inScope;
|
||
} else {
|
||
window.app.visibleProjects = new Set(window.app.projectFilter);
|
||
}
|
||
|
||
await addHttpSource(baseUrl);
|
||
}
|
||
|
||
// Add an HTTP source root (analogous to addDirectory() for local mode)
|
||
async function addHttpSource(baseUrl) {
|
||
// Derive a display name from the URL path
|
||
var urlPath = baseUrl.replace(/\/$/, '');
|
||
var rootName = urlPath.substring(urlPath.lastIndexOf('/') + 1) || urlPath;
|
||
|
||
// Check if already added
|
||
var exists = window.app.directories.some(function(d) { return d.url === baseUrl; });
|
||
if (exists) return;
|
||
|
||
window.app.directories.push({
|
||
handle: null,
|
||
name: rootName,
|
||
path: rootName,
|
||
url: baseUrl
|
||
});
|
||
|
||
if (window.app.directories.length === 1) {
|
||
hideEmptyState();
|
||
}
|
||
|
||
await scanHttpSource(baseUrl, rootName);
|
||
updateUI();
|
||
}
|
||
|
||
// Scan an HTTP source root
|
||
async function scanHttpSource(baseUrl, rootName) {
|
||
window.app.isScanning = true;
|
||
window.app.scanProgress = 'Connecting to server...';
|
||
updateStatusBar();
|
||
|
||
var source = window.app.modules.source.createSource('http', { baseUrl: baseUrl });
|
||
|
||
var fileCount = 0;
|
||
var callbacks = {
|
||
onGroupingFolder: function(folder) {
|
||
window.app.groupingFolders.push(folder);
|
||
},
|
||
onTransmittalFolder: function(folder) {
|
||
window.app.transmittalFolders.push(folder);
|
||
},
|
||
onFile: function(file) {
|
||
window.app.files.push(file);
|
||
fileCount++;
|
||
// Throttled progress update — don't update DOM on every file
|
||
if (fileCount % 10 === 0) {
|
||
window.app.scanProgress = 'Scanning\u2026 ' + fileCount + ' files found';
|
||
updateStatusBar();
|
||
}
|
||
},
|
||
onProgress: function() { /* no-op: parallel scan — spinner is enough */ }
|
||
};
|
||
|
||
try {
|
||
await source.scan(baseUrl, callbacks);
|
||
|
||
// Auto-select top-level party folders (shallowest depth). Keyed by
|
||
// party NAME so duplicate third-party folders across projects merge.
|
||
var groupingDepths = window.app.groupingFolders.map(function(f) { return f.path.split('/').length; });
|
||
var minGroupingDepth = groupingDepths.length > 0 ? Math.min.apply(null, groupingDepths) : 1;
|
||
window.app.groupingFolders.forEach(function(folder) {
|
||
if (folder.path.split('/').length === minGroupingDepth) {
|
||
window.app.selectedGroupingFolders.add(folder.name);
|
||
}
|
||
});
|
||
|
||
window.app.transmittalFolders.forEach(function(folder) {
|
||
if (!isUnderHiddenFolderType(folder.path)) {
|
||
window.app.selectedTransmittalFolders.add(folder.path);
|
||
}
|
||
});
|
||
|
||
ensureOutstandingTransmittal();
|
||
// Auto-select Outstanding if selectAllTransmittals is active
|
||
if (window.app.selectAllTransmittals) {
|
||
window.app.selectedTransmittalFolders.add('__outstanding__');
|
||
}
|
||
|
||
collectModifiers();
|
||
updateUI();
|
||
window.app.modules.filtering.applyFilters();
|
||
if (window.app.modules.presets) {
|
||
window.app.modules.presets.init();
|
||
}
|
||
} catch (err) {
|
||
console.error('Error scanning HTTP source:', err);
|
||
showHttpErrorState(err.message);
|
||
} finally {
|
||
window.app.isScanning = false;
|
||
window.app.scanProgress = '';
|
||
updateStatusBar();
|
||
}
|
||
}
|
||
|
||
// Ensure the Outstanding virtual transmittal exists if there are any outstanding files.
|
||
// Called after each scan completes. Idempotent — safe to call multiple times.
|
||
function ensureOutstandingTransmittal() {
|
||
const hasOutstanding = window.app.files.some(f => f.folderPath === '__outstanding__');
|
||
const alreadyExists = window.app.transmittalFolders.some(f => f.path === '__outstanding__');
|
||
if (hasOutstanding && !alreadyExists) {
|
||
window.app.transmittalFolders.push({
|
||
name: 'Outstanding',
|
||
path: '__outstanding__',
|
||
displayPath: 'Outstanding',
|
||
handle: null,
|
||
url: null,
|
||
isVirtual: true
|
||
});
|
||
}
|
||
}
|
||
|
||
// Show error state when HTTP server is unreachable
|
||
function showHttpErrorState(message) {
|
||
var el = document.getElementById('noDirectoryMessage');
|
||
if (!el) return;
|
||
var content = el.querySelector('.empty-state-content');
|
||
if (content) {
|
||
content.innerHTML =
|
||
'<h2>Could not connect to server</h2>' +
|
||
'<p>The archive browser could not retrieve the directory listing from the server.</p>' +
|
||
'<p><strong>Error:</strong> ' + escapeHtml(message || 'Unknown error') + '</p>' +
|
||
'<p>Ensure the server is running, CORS is not blocking the request, and Caddy\'s file browsing is enabled.</p>';
|
||
}
|
||
el.classList.remove('hidden');
|
||
}
|
||
|
||
// Show a warning banner listing projects in the URL filter that the user cannot access
|
||
function showProjectWarning(missingProjects) {
|
||
var el = document.getElementById('projectWarningBanner');
|
||
if (!el || missingProjects.length === 0) return;
|
||
var list = missingProjects.map(function(p) { return escapeHtml(p); }).join(', ');
|
||
el.querySelector('.project-warning-text').innerHTML =
|
||
'This link includes projects you don\'t have access to: <strong>' + list + '</strong>';
|
||
el.classList.remove('hidden');
|
||
}
|
||
|
||
function dismissProjectWarning() {
|
||
var el = document.getElementById('projectWarningBanner');
|
||
if (el) el.classList.add('hidden');
|
||
}
|
||
|
||
// Show unsupported browser message
|
||
function showUnsupportedBrowserMessage() {
|
||
const app = document.getElementById('appContainer');
|
||
app.innerHTML = `
|
||
<div class="empty-state">
|
||
<div class="empty-state-content">
|
||
<h2>Browser Not Supported</h2>
|
||
<p>This application requires a Chromium-based browser (Chrome, Edge, Brave) with File System Access API support.</p>
|
||
<p>Please use one of these browsers to access the Archive Browser.</p>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// Show empty state
|
||
function showEmptyState() {
|
||
document.getElementById('noDirectoryMessage').classList.remove('hidden');
|
||
document.querySelector('.main-container').style.display = 'none';
|
||
// Keep header visible
|
||
document.querySelector('.app-header').style.display = '';
|
||
var refreshBtn = document.getElementById('refreshHeaderBtn');
|
||
if (refreshBtn) { refreshBtn.classList.add('hidden'); }
|
||
}
|
||
|
||
// Hide empty state
|
||
function hideEmptyState() {
|
||
document.getElementById('noDirectoryMessage').classList.add('hidden');
|
||
document.querySelector('.main-container').style.display = '';
|
||
var refreshBtn = document.getElementById('refreshHeaderBtn');
|
||
if (refreshBtn) { refreshBtn.classList.remove('hidden'); }
|
||
}
|
||
|
||
// Update UI based on current state
|
||
function updateUI() {
|
||
renderFolderTypeBar();
|
||
renderFolderLists();
|
||
window.app.modules.table.updateFileTable();
|
||
updateStatusBar();
|
||
}
|
||
|
||
// Render folder lists (rebuilds DOM)
|
||
function renderFolderLists() {
|
||
renderGroupingFolders();
|
||
renderTransmittalFolders();
|
||
}
|
||
|
||
// Check if a folder path is under a hidden folder type
|
||
// Returns true if any path segment is a known folder type that is NOT currently enabled
|
||
function isUnderHiddenFolderType(path) {
|
||
const parts = path.toLowerCase().split('/');
|
||
return parts.some(part =>
|
||
window.app.FOLDER_TYPE_NAMES.includes(part) && !window.app.enabledFolderTypes.has(part)
|
||
);
|
||
}
|
||
|
||
// Get filtered grouping folders (single source of truth for filtering logic)
|
||
function getFilteredGroupingFolders() {
|
||
const filter = window.app.groupingFilter;
|
||
|
||
return window.app.groupingFolders.filter(folder => {
|
||
if (isUnderHiddenFolderType(folder.path)) {
|
||
return false;
|
||
}
|
||
|
||
if (!filter) return true;
|
||
|
||
const terms = parseSearchTerms(filter);
|
||
return matchesSearchTerms(folder.name.toLowerCase(), terms);
|
||
});
|
||
}
|
||
|
||
// Render grouping folders as a flat list of unique party names. Same-named
|
||
// third-party folders across multiple projects collapse to one row.
|
||
// selectedGroupingFolders is a Set of party NAMES (not paths) so toggling
|
||
// affects every project occurrence at once.
|
||
function renderGroupingFolders() {
|
||
const container = document.getElementById('groupingFoldersList');
|
||
|
||
// Get filtered grouping folders (uses shared filtering logic)
|
||
const filteredFolders = getFilteredGroupingFolders();
|
||
|
||
// Only show top-level party folders (the shallowest depth among all grouping folders)
|
||
const allDepths = window.app.groupingFolders.map(f => f.path.split('/').length);
|
||
const minDepth = allDepths.length > 0 ? Math.min(...allDepths) : 1;
|
||
const partyFolders = filteredFolders.filter(f => f.path.split('/').length === minDepth);
|
||
|
||
// Dedupe by name (keep first occurrence per name). In multi-project mode,
|
||
// skip parties whose every occurrence is under a hidden project — if at
|
||
// least one occurrence is in a visible project, the party stays.
|
||
const seen = new Set();
|
||
const uniqueParties = [];
|
||
for (const f of partyFolders) {
|
||
if (seen.has(f.name)) continue;
|
||
if (window.app.isMultiProject) {
|
||
const hasVisible = partyFolders.some(p =>
|
||
p.name === f.name && pathIsInVisibleProject(p.path)
|
||
);
|
||
if (!hasVisible) continue;
|
||
}
|
||
seen.add(f.name);
|
||
uniqueParties.push(f);
|
||
}
|
||
uniqueParties.sort((a, b) => a.name.localeCompare(b.name));
|
||
|
||
const partyNames = new Set(uniqueParties.map(f => f.name));
|
||
|
||
// If "Select All" mode is active, auto-select all visible party names.
|
||
if (window.app.selectAllGroupingFolders) {
|
||
window.app.selectedGroupingFolders.clear();
|
||
uniqueParties.forEach(f => window.app.selectedGroupingFolders.add(f.name));
|
||
} else {
|
||
// Remove selections for names that are no longer visible.
|
||
for (const selectedName of window.app.selectedGroupingFolders) {
|
||
if (!partyNames.has(selectedName)) {
|
||
window.app.selectedGroupingFolders.delete(selectedName);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Sync checkbox state
|
||
const checkbox = document.getElementById('selectAllGroupingCheckbox');
|
||
if (checkbox) checkbox.checked = window.app.selectAllGroupingFolders;
|
||
|
||
if (uniqueParties.length === 0 && window.app.groupingFilter) {
|
||
container.innerHTML = '<div class="folder-list-empty">No parties match your filter</div>';
|
||
updateFolderSelectionState('groupingFoldersList');
|
||
return;
|
||
}
|
||
|
||
container.innerHTML = uniqueParties.map(folder => `
|
||
<div class="folder-item ${window.app.selectedGroupingFolders.has(folder.name) ? 'selected' : ''}"
|
||
data-path="${escapeHtml(folder.name)}"
|
||
data-folder-type="grouping">
|
||
<span class="folder-item-name" title="${escapeHtml(folder.name)}">${escapeHtml(folder.name)}</span>
|
||
</div>
|
||
`).join('');
|
||
|
||
updateFolderSelectionState('groupingFoldersList');
|
||
}
|
||
|
||
// Render the global folder type toggle bar
|
||
function renderFolderTypeBar() {
|
||
const bar = document.getElementById('folderTypeBar');
|
||
if (!bar) return;
|
||
|
||
const FOLDER_TYPE_LABELS = { mdl: 'MDL', incoming: 'Incoming', issued: 'Issued', received: 'Received' };
|
||
bar.innerHTML = window.app.FOLDER_TYPE_NAMES.map(type => {
|
||
const active = window.app.enabledFolderTypes.has(type);
|
||
const label = FOLDER_TYPE_LABELS[type] || (type.charAt(0).toUpperCase() + type.slice(1));
|
||
return `<button class="folder-type-toggle ${active ? 'active' : ''}"
|
||
data-type="${type}"
|
||
title="Toggle ${label} folders">${label}</button>`;
|
||
}).join('');
|
||
}
|
||
|
||
// Toggle a folder type on/off globally.
|
||
// Off->on triggers a refresh because source.js skips listings for disabled folder types
|
||
// entirely (no listing fetched), so newly-enabled types need a rescan to surface their data.
|
||
function toggleFolderType(type) {
|
||
const wasEnabled = window.app.enabledFolderTypes.has(type);
|
||
if (wasEnabled) {
|
||
window.app.enabledFolderTypes.delete(type);
|
||
} else {
|
||
window.app.enabledFolderTypes.add(type);
|
||
}
|
||
renderFolderTypeBar();
|
||
renderGroupingFolders();
|
||
renderTransmittalFolders();
|
||
window.app.modules.filtering.applyFilters();
|
||
window.app.modules.urlState.push();
|
||
|
||
if (!wasEnabled && window.app.directories.length > 0) {
|
||
window.app.modules.directory.refreshDirectories();
|
||
}
|
||
}
|
||
|
||
// In multi-project mode, returns true if the path contains a segment matching
|
||
// a checked project in the picker. Single-project mode always returns true
|
||
// (no project segment to match against).
|
||
function pathIsInVisibleProject(path) {
|
||
if (!window.app.isMultiProject) return true;
|
||
if (!window.app.visibleProjects || window.app.visibleProjects.size === 0) return false;
|
||
return path.split('/').some(seg => window.app.visibleProjects.has(seg));
|
||
}
|
||
|
||
// Returns true if an outstanding file's actualPath has a path segment matching
|
||
// any selected party name and is not under a hidden folder type. Segment-equality
|
||
// (not prefix) so the same party name selected across projects matches all
|
||
// occurrences regardless of project ID prefix.
|
||
function outstandingFileIsVisible(file) {
|
||
const selectedGrouping = window.app.selectedGroupingFolders;
|
||
if (selectedGrouping.size === 0) return false;
|
||
if (isUnderHiddenFolderType(file.actualPath)) return false;
|
||
if (!pathIsInVisibleProject(file.actualPath)) return false;
|
||
return file.actualPath.split('/').some(seg => selectedGrouping.has(seg));
|
||
}
|
||
|
||
// Returns true if any outstanding (non-transmittal) files exist under the currently
|
||
// selected and visible grouping folders.
|
||
function hasVisibleOutstandingFiles() {
|
||
return window.app.files.some(function(f) {
|
||
if (f.folderPath !== '__outstanding__') return false;
|
||
return outstandingFileIsVisible(f);
|
||
});
|
||
}
|
||
|
||
// Returns true if the transmittal folder's path satisfies all three cascade
|
||
// layers: project visibility, party selection (any path segment matches a
|
||
// selected party name), and folder-type enablement (no segment is a
|
||
// folder-type marker that's currently disabled, regardless of where in the
|
||
// path it sits). Segment-equality matching means a party "BM" selected
|
||
// matches every "<...>/BM/<...>" path regardless of the prefix; and the
|
||
// folder-type check covers BOTH the canonical "<party>/Issued/<txn>" layout
|
||
// AND nested layouts like "<party>/<sub>/Issued/<txn>" — a deeper folder-
|
||
// type marker still triggers the cascade.
|
||
function transmittalIsUnderVisibleParty(folder) {
|
||
if (!pathIsInVisibleProject(folder.path)) return false;
|
||
if (isUnderHiddenFolderType(folder.path)) return false;
|
||
return folder.path.split('/').some(seg =>
|
||
window.app.selectedGroupingFolders.has(seg)
|
||
);
|
||
}
|
||
|
||
// Render transmittal folders (rebuilds DOM)
|
||
function renderTransmittalFolders() {
|
||
const container = document.getElementById('transmittalFoldersList');
|
||
const filter = window.app.transmittalFilter;
|
||
|
||
// Filter transmittal folders based on grouping selection and name filter
|
||
const filteredFolders = window.app.transmittalFolders.filter(folder => {
|
||
// Outstanding virtual transmittal: include if there are visible outstanding files
|
||
if (folder.path === '__outstanding__') {
|
||
if (!hasVisibleOutstandingFiles()) return false;
|
||
// Apply name filter to "Outstanding" label too
|
||
if (filter && filter.trim()) {
|
||
const terms = parseSearchTerms(filter.trim());
|
||
if (!matchesSearchTerms('outstanding', terms)) return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
// Check name filter
|
||
let matchesFilter = true;
|
||
if (filter && filter.trim()) {
|
||
const terms = parseSearchTerms(filter.trim());
|
||
const folderText = folder.name.toLowerCase();
|
||
matchesFilter = matchesSearchTerms(folderText, terms);
|
||
}
|
||
|
||
// If no grouping folders exist at all, show all transmittal folders (flat structure)
|
||
if (window.app.groupingFolders.length === 0) {
|
||
return matchesFilter;
|
||
}
|
||
|
||
// If grouping folders exist but none are selected, show nothing
|
||
if (window.app.selectedGroupingFolders.size === 0) {
|
||
return false;
|
||
}
|
||
|
||
// Check party + folder type visibility
|
||
return matchesFilter && transmittalIsUnderVisibleParty(folder);
|
||
});
|
||
|
||
// Sort regular transmittal folders by date (newest first); Outstanding handled separately
|
||
const regularFolders = filteredFolders.filter(f => f.path !== '__outstanding__');
|
||
regularFolders.sort((a, b) => b.name.localeCompare(a.name));
|
||
|
||
const showOutstanding = filteredFolders.some(f => f.path === '__outstanding__');
|
||
|
||
// Build set of visible folder paths (for Select All and deselection logic)
|
||
const filteredPaths = new Set(filteredFolders.map(f => f.path));
|
||
|
||
// If "Select All" mode is active, auto-select all visible transmittal folders
|
||
if (window.app.selectAllTransmittals) {
|
||
window.app.selectedTransmittalFolders.clear();
|
||
filteredFolders.forEach(f => window.app.selectedTransmittalFolders.add(f.path));
|
||
} else {
|
||
// Remove selections for folders that are now filtered out
|
||
for (const selectedPath of window.app.selectedTransmittalFolders) {
|
||
if (!filteredPaths.has(selectedPath)) {
|
||
window.app.selectedTransmittalFolders.delete(selectedPath);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Sync checkbox state
|
||
const checkbox = document.getElementById('selectAllTransmittalsCheckbox');
|
||
if (checkbox) checkbox.checked = window.app.selectAllTransmittals;
|
||
|
||
// Group regular folders by date
|
||
const foldersByDate = new Map();
|
||
regularFolders.forEach(folder => {
|
||
const match = folder.name.match(/^(\d{4}-\d{2}-\d{2})/);
|
||
const date = match ? match[1] : 'Unknown';
|
||
if (!foldersByDate.has(date)) {
|
||
foldersByDate.set(date, []);
|
||
}
|
||
foldersByDate.get(date).push(folder);
|
||
});
|
||
|
||
// Build HTML
|
||
let html = '';
|
||
|
||
// Outstanding virtual transmittal — pinned at top
|
||
if (showOutstanding) {
|
||
const isSelected = window.app.selectedTransmittalFolders.has('__outstanding__');
|
||
html += `
|
||
<div class="folder-item outstanding-transmittal ${isSelected ? 'selected' : ''}"
|
||
data-path="__outstanding__"
|
||
data-folder-type="transmittal"
|
||
title="Files in non-transmittal folders under selected grouping folders">
|
||
<div class="transmittal-folder-content">
|
||
<div class="transmittal-first-line outstanding-label">⋯ Outstanding</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// Regular date-grouped folders
|
||
for (const [date, folders] of foldersByDate) {
|
||
const isCollapsed = window.app.collapsedDateGroups.has(date);
|
||
const folderCount = folders.length;
|
||
|
||
html += `
|
||
<div class="date-group-header" data-date="${escapeHtml(date)}">
|
||
<span class="date-group-toggle">${isCollapsed ? '▶' : '▼'}</span>
|
||
<span class="date-group-date">${escapeHtml(date)}</span>
|
||
<span class="date-group-count">(${folderCount})</span>
|
||
</div>
|
||
`;
|
||
|
||
if (!isCollapsed) {
|
||
for (const folder of folders) {
|
||
const match = folder.name.match(/^\d{4}-\d{2}-\d{2}_([^_\s]+)\s*\(([^)]+)\)\s*-\s*(.+)$/);
|
||
let firstLine = folder.name;
|
||
let secondLine = '';
|
||
|
||
if (match) {
|
||
const [, tracking, status, title] = match;
|
||
firstLine = `${tracking} • ${status}`;
|
||
secondLine = title;
|
||
}
|
||
|
||
html += `
|
||
<div class="folder-item ${window.app.selectedTransmittalFolders.has(folder.path) ? 'selected' : ''}"
|
||
data-path="${escapeHtml(folder.path)}"
|
||
data-folder-type="transmittal"
|
||
title="${escapeHtml(folder.path)}">
|
||
<div class="transmittal-folder-content">
|
||
<div class="transmittal-first-line">${escapeHtml(firstLine)}</div>
|
||
${secondLine ? `<div class="transmittal-second-line">${escapeHtml(secondLine)}</div>` : ''}
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (filteredFolders.length === 0 && window.app.transmittalFilter) {
|
||
container.innerHTML = '<div class="folder-list-empty">No folders match your filter</div>';
|
||
updateFolderSelectionState('transmittalFoldersList');
|
||
window.app.modules.events.updateToggleAllIcon();
|
||
return;
|
||
}
|
||
|
||
container.innerHTML = html;
|
||
|
||
// Ensure selection state is visually reflected after DOM rebuild
|
||
updateFolderSelectionState('transmittalFoldersList');
|
||
|
||
// Update the toggle all icon to reflect current state
|
||
window.app.modules.events.updateToggleAllIcon();
|
||
}
|
||
|
||
|
||
// Update status bar
|
||
function updateStatusBar() {
|
||
const fileCountEl = document.getElementById('fileCount');
|
||
const selectedCountEl = document.getElementById('selectedCount');
|
||
|
||
// Before any directory is loaded, show a hint instead of "0 files"
|
||
if (window.app.directories.length === 0 && !window.app.isScanning) {
|
||
fileCountEl.textContent = 'Select a directory to begin';
|
||
selectedCountEl.textContent = '';
|
||
document.getElementById('scanStatus').textContent = '';
|
||
var spinner2 = document.getElementById('scanSpinner');
|
||
if (spinner2) spinner2.classList.add('hidden');
|
||
document.getElementById('downloadSelectedBtn').disabled = true;
|
||
document.getElementById('exportCsvBtn').disabled = true;
|
||
return;
|
||
}
|
||
|
||
// Count unique tracking numbers
|
||
const trackingNumbers = new Set(window.app.filteredFiles.map(f => f.trackingNumber));
|
||
const trackingCount = trackingNumbers.size;
|
||
const fileCount = window.app.filteredFiles.length;
|
||
|
||
// Count files with path errors
|
||
const pathErrorCount = window.app.filteredFiles.filter(f => f.hasPathError).length;
|
||
|
||
// Format: "X tracking numbers, Y files" + optional path error warning
|
||
let countText = `${trackingCount} tracking number${trackingCount !== 1 ? 's' : ''}, ${fileCount} file${fileCount !== 1 ? 's' : ''}`;
|
||
if (pathErrorCount > 0) {
|
||
countText += ` (⚠️ ${pathErrorCount} inaccessible)`;
|
||
}
|
||
|
||
fileCountEl.textContent = countText;
|
||
selectedCountEl.textContent = `${window.app.selectedFiles.size} selected`;
|
||
document.getElementById('scanStatus').textContent = window.app.scanProgress;
|
||
var spinner = document.getElementById('scanSpinner');
|
||
if (spinner) { spinner.classList.toggle('hidden', !window.app.isScanning); }
|
||
|
||
// Disable action buttons when nothing is selected
|
||
const noneSelected = window.app.selectedFiles.size === 0;
|
||
document.getElementById('downloadSelectedBtn').disabled = noneSelected;
|
||
document.getElementById('exportCsvBtn').disabled = noneSelected;
|
||
}
|
||
|
||
// Escape HTML for safe insertion
|
||
function escapeHtml(text) {
|
||
const div = document.createElement('div');
|
||
div.textContent = text;
|
||
return div.innerHTML;
|
||
}
|
||
|
||
/**
|
||
* Update folder selection visual state without rebuilding DOM
|
||
* This is more efficient than re-rendering when only selection changes
|
||
* @param {string} containerId - 'groupingFoldersList' or 'transmittalFoldersList'
|
||
*/
|
||
function updateFolderSelectionState(containerId) {
|
||
const container = document.getElementById(containerId);
|
||
if (!container) {
|
||
console.warn(`Container not found: ${containerId}`);
|
||
return;
|
||
}
|
||
|
||
const selectedSet = containerId === 'groupingFoldersList' ?
|
||
window.app.selectedGroupingFolders :
|
||
window.app.selectedTransmittalFolders;
|
||
|
||
// Update selected class on existing elements
|
||
container.querySelectorAll('.folder-item').forEach(item => {
|
||
const path = item.getAttribute('data-path');
|
||
if (path) {
|
||
item.classList.toggle('selected', selectedSet.has(path));
|
||
}
|
||
});
|
||
}
|
||
|
||
// Extract modifier type from revision string (e.g., "2+B1" -> "+B", "2" -> "base")
|
||
function getModifierType(revision) {
|
||
if (!revision) return 'base';
|
||
const match = revision.match(/\+([A-Za-z])/);
|
||
return match ? '+' + match[1].toUpperCase() : 'base';
|
||
}
|
||
|
||
// Collect all unique modifiers from files
|
||
function collectModifiers() {
|
||
window.app.availableModifiers.clear();
|
||
|
||
window.app.files.forEach(file => {
|
||
const modType = getModifierType(file.revision);
|
||
window.app.availableModifiers.add(modType);
|
||
});
|
||
|
||
// Default selection: 'base' (un-modified revisions) plus '+C' (comment
|
||
// markups against base). Other modifier types (+B, +D, …) are
|
||
// available in the dropdown but hidden by default — users opt them in
|
||
// via the Modifiers dropdown when they want to see scratch / draft /
|
||
// hold-style markups. Falls back to selecting whatever is available
|
||
// when neither default exists, so the table never goes empty out of
|
||
// the gate.
|
||
const defaults = ['base', '+C'];
|
||
const selected = new Set();
|
||
defaults.forEach(d => {
|
||
if (window.app.availableModifiers.has(d)) selected.add(d);
|
||
});
|
||
if (selected.size === 0) {
|
||
window.app.availableModifiers.forEach(m => selected.add(m));
|
||
}
|
||
window.app.selectedModifiers = selected;
|
||
|
||
// Update the dropdown UI
|
||
renderModifierDropdown();
|
||
}
|
||
|
||
// Render the modifier dropdown options
|
||
function renderModifierDropdown() {
|
||
const list = document.getElementById('modifierFilterList');
|
||
if (!list) return;
|
||
|
||
// Sort modifiers: "base" first, then alphabetically
|
||
const sorted = Array.from(window.app.availableModifiers).sort((a, b) => {
|
||
if (a === 'base') return -1;
|
||
if (b === 'base') return 1;
|
||
return a.localeCompare(b);
|
||
});
|
||
|
||
let html = '';
|
||
sorted.forEach(mod => {
|
||
const checked = window.app.selectedModifiers.has(mod) ? 'checked' : '';
|
||
const label = mod === 'base' ? 'Base (no modifier)' : mod;
|
||
const labelClass = mod === 'base' ? 'modifier-base' : 'modifier-type';
|
||
html += `
|
||
<div class="modifier-filter-item">
|
||
<label>
|
||
<input type="checkbox"
|
||
data-modifier="${mod}"
|
||
${checked}
|
||
onchange="toggleModifierFilter('${mod}')">
|
||
<span class="${labelClass}">${label}</span>
|
||
</label>
|
||
</div>
|
||
`;
|
||
});
|
||
|
||
list.innerHTML = html;
|
||
updateModifierSelectAll();
|
||
updateModifierButtonLabel();
|
||
}
|
||
|
||
// Toggle a specific modifier filter
|
||
function toggleModifierFilter(mod) {
|
||
if (window.app.selectedModifiers.has(mod)) {
|
||
window.app.selectedModifiers.delete(mod);
|
||
} else {
|
||
window.app.selectedModifiers.add(mod);
|
||
}
|
||
updateModifierSelectAll();
|
||
updateModifierButtonLabel();
|
||
window.app.modules.filtering.applyFilters();
|
||
}
|
||
|
||
// Toggle all modifiers
|
||
function toggleAllModifiers(selectAll) {
|
||
if (selectAll) {
|
||
window.app.selectedModifiers = new Set(window.app.availableModifiers);
|
||
} else {
|
||
window.app.selectedModifiers.clear();
|
||
}
|
||
renderModifierDropdown();
|
||
window.app.modules.filtering.applyFilters();
|
||
}
|
||
|
||
// Update the "Select All" checkbox state
|
||
function updateModifierSelectAll() {
|
||
const selectAllCheckbox = document.getElementById('modifierSelectAll');
|
||
if (selectAllCheckbox) {
|
||
selectAllCheckbox.checked = window.app.selectedModifiers.size === window.app.availableModifiers.size;
|
||
selectAllCheckbox.indeterminate = window.app.selectedModifiers.size > 0 &&
|
||
window.app.selectedModifiers.size < window.app.availableModifiers.size;
|
||
}
|
||
}
|
||
|
||
// Update button label to show filter status
|
||
function updateModifierButtonLabel() {
|
||
const btn = document.getElementById('modifierFilterBtn');
|
||
if (!btn) return;
|
||
|
||
const total = window.app.availableModifiers.size;
|
||
const selected = window.app.selectedModifiers.size;
|
||
|
||
if (selected === total) {
|
||
btn.textContent = 'Modifiers ▼';
|
||
} else if (selected === 0) {
|
||
btn.textContent = 'Modifiers (none) ▼';
|
||
} else {
|
||
btn.textContent = `Modifiers (${selected}/${total}) ▼`;
|
||
}
|
||
}
|
||
|
||
// Toggle modifier dropdown visibility
|
||
function toggleModifierDropdown() {
|
||
const dropdown = document.getElementById('modifierFilterDropdown');
|
||
dropdown.classList.toggle('hidden');
|
||
}
|
||
|
||
// Update the Folders icon button state based on active visibility toggles
|
||
function updateFolderVisibilityBtnLabel() {
|
||
// replaced by renderFolderTypeBar()
|
||
}
|
||
|
||
// Check if a file passes the modifier filter
|
||
function filePassesModifierFilter(file) {
|
||
const modType = getModifierType(file.revision);
|
||
return window.app.selectedModifiers.has(modType);
|
||
}
|
||
|
||
// Toggle filter to show only selected files
|
||
function toggleFilterSelected() {
|
||
window.app.showSelectedOnly = !window.app.showSelectedOnly;
|
||
|
||
// Update button visual state and label
|
||
const btn = document.getElementById('filterSelectedBtn');
|
||
if (window.app.showSelectedOnly) {
|
||
btn.classList.add('btn-active');
|
||
btn.textContent = 'Show All';
|
||
} else {
|
||
btn.classList.remove('btn-active');
|
||
btn.textContent = 'Filter Selected';
|
||
}
|
||
|
||
window.app.modules.filtering.applyFilters();
|
||
}
|
||
// Register with module system
|
||
window.app.modules.app = {
|
||
updateUI,
|
||
updateStatusBar,
|
||
escapeHtml,
|
||
updateFolderSelectionState,
|
||
getModifierType,
|
||
collectModifiers,
|
||
renderModifierDropdown,
|
||
toggleModifierFilter,
|
||
toggleAllModifiers,
|
||
updateModifierSelectAll,
|
||
updateModifierButtonLabel,
|
||
toggleModifierDropdown,
|
||
updateFolderVisibilityBtnLabel,
|
||
filePassesModifierFilter,
|
||
toggleFilterSelected,
|
||
isUnderHiddenFolderType,
|
||
ensureOutstandingTransmittal,
|
||
showHttpErrorState,
|
||
showUnsupportedBrowserMessage,
|
||
showProjectWarning,
|
||
dismissProjectWarning,
|
||
showEmptyState,
|
||
hideEmptyState,
|
||
addHttpSource,
|
||
scanHttpSource,
|
||
renderGroupingFolders,
|
||
renderTransmittalFolders,
|
||
renderFolderTypeBar,
|
||
toggleFolderType,
|
||
outstandingFileIsVisible,
|
||
hasVisibleOutstandingFiles,
|
||
transmittalIsUnderVisibleParty,
|
||
pathIsInVisibleProject,
|
||
renderFolderLists,
|
||
getFilteredGroupingFolders,
|
||
showProjectWarning,
|
||
dismissProjectWarning,
|
||
};
|
||
|
||
// Expose key functions on window for inline HTML handlers
|
||
window.initApp = initApp;
|
||
window.toggleFileSelection = function(id) { window.app.modules.table.toggleFileSelection(id); };
|
||
window.sortTable = function(f) { window.app.modules.table.sortTable(f); };
|
||
window.confirmTransmittal = function() { window.app.modules.dragDrop.confirmTransmittal(); };
|
||
window.toggleModifierFilter = toggleModifierFilter;
|
||
window.toggleFilterSelected = toggleFilterSelected;
|
||
window.toggleFolderType = toggleFolderType;
|
||
window.toggleGroupingFolder = function(p, r) { window.app.modules.events.toggleGroupingFolder(p, r); };
|
||
window.toggleDateGroup = function(d) { window.app.modules.events.toggleDateGroup(d); };
|
||
window.toggleAllDateGroups = function() { window.app.modules.events.toggleAllDateGroups(); };
|
||
window.selectAllVisibleFolders = function(t) { window.app.modules.events.selectAllVisibleFolders(t); };
|
||
window.removeDirectory = function(n) { window.app.modules.directory.removeDirectory(n); };
|
||
window.dismissProjectWarning = dismissProjectWarning;
|
||
window.verifyFileIntegrity = function(id) { window.app.modules.hash.verifyFileIntegrity(id); };
|
||
window.showProjectWarning = showProjectWarning;
|
||
window.dismissProjectWarning = dismissProjectWarning;
|
||
|
||
// Initialize on DOM ready
|
||
document.addEventListener('DOMContentLoaded', initApp);
|
||
|
||
})();
|
||
|
||
/**
|
||
* ZDDC shared help panel — open/close logic.
|
||
* Works with all four tools regardless of their module pattern.
|
||
* Expects: #help-btn, #help-panel, #help-panel-close in the DOM.
|
||
*/
|
||
(function () {
|
||
'use strict';
|
||
|
||
function init() {
|
||
var helpBtn = document.getElementById('help-btn');
|
||
var panel = document.getElementById('help-panel');
|
||
var closeBtn = document.getElementById('help-panel-close');
|
||
|
||
if (!helpBtn || !panel) { return; }
|
||
|
||
function isOpen() { return !panel.hidden; }
|
||
|
||
function openPanel() {
|
||
panel.hidden = false;
|
||
document.body.classList.add('help-open');
|
||
}
|
||
|
||
function closePanel() {
|
||
panel.hidden = true;
|
||
document.body.classList.remove('help-open');
|
||
}
|
||
|
||
helpBtn.addEventListener('click', function () {
|
||
if (isOpen()) { closePanel(); } else { openPanel(); }
|
||
});
|
||
|
||
if (closeBtn) {
|
||
closeBtn.addEventListener('click', closePanel);
|
||
}
|
||
|
||
document.addEventListener('keydown', function (e) {
|
||
if (e.key === 'Escape' && isOpen()) { closePanel(); }
|
||
});
|
||
}
|
||
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', init);
|
||
} else {
|
||
init();
|
||
}
|
||
}());
|
||
|
||
</script>
|
||
</body>
|
||
</html>
|