All checks were successful
Notify chart dev on beta cut / notify-chart-dev (push) Successful in 5s
8042 lines
432 KiB
HTML
8042 lines
432 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 Classifier</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);
|
||
}
|
||
|
||
/* Classifier-specific base overrides
|
||
Reset, tokens, buttons, and font are provided by shared/base.css */
|
||
|
||
#app {
|
||
width: 100vw;
|
||
height: 100vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
/* Utility */
|
||
.text-muted { color: var(--text-muted); }
|
||
.text-success { color: var(--success); }
|
||
.text-warning { color: var(--warning); }
|
||
.text-danger { color: var(--danger); }
|
||
|
||
/* Checkbox label */
|
||
.checkbox-label {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
cursor: pointer;
|
||
user-select: none;
|
||
font-size: 0.875rem;
|
||
}
|
||
|
||
.checkbox-label input[type="checkbox"] {
|
||
cursor: pointer;
|
||
}
|
||
|
||
/* ── Toast notifications (classifier-only) ───────────────────────────────── */
|
||
/* shared/base.css intentionally omits toast CSS; only classifier uses toasts. */
|
||
.toast {
|
||
position: fixed;
|
||
bottom: 2rem;
|
||
right: 2rem;
|
||
background: var(--bg);
|
||
color: var(--text);
|
||
padding: 0.875rem 1.25rem;
|
||
border-radius: var(--radius);
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||
z-index: 9000;
|
||
max-width: 400px;
|
||
font-size: 0.875rem;
|
||
animation: zddc-toast-in 0.3s ease-out;
|
||
}
|
||
|
||
.toast-success { border-left: 4px solid var(--success); }
|
||
.toast-error { border-left: 4px solid var(--danger); }
|
||
.toast-info { border-left: 4px solid var(--info); }
|
||
.toast-warning { border-left: 4px solid var(--warning); }
|
||
|
||
.toast-fade {
|
||
animation: zddc-toast-out 0.3s ease-out forwards;
|
||
}
|
||
|
||
@keyframes zddc-toast-in {
|
||
from { transform: translateX(100%); opacity: 0; }
|
||
to { transform: translateX(0); opacity: 1; }
|
||
}
|
||
|
||
@keyframes zddc-toast-out {
|
||
from { transform: translateX(0); opacity: 1; }
|
||
to { transform: translateX(100%); opacity: 0; }
|
||
}
|
||
|
||
/* Classifier layout — tokens from shared/base.css */
|
||
|
||
/* 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;
|
||
}
|
||
|
||
.empty-state-content h2 {
|
||
color: var(--text);
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.empty-state-content p {
|
||
margin-bottom: 1rem;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
.empty-state-content .note {
|
||
font-size: 0.85rem;
|
||
font-style: italic;
|
||
}
|
||
|
||
.welcome-list {
|
||
text-align: left;
|
||
margin: 0.5rem auto;
|
||
max-width: 400px;
|
||
}
|
||
|
||
.empty-state.drag-over {
|
||
background: var(--primary-light);
|
||
outline: 2px dashed var(--primary);
|
||
outline-offset: -4px;
|
||
}
|
||
|
||
/* Browser Warning */
|
||
.browser-warning {
|
||
background-color: rgba(217, 119, 6, 0.08);
|
||
border: 2px solid var(--warning);
|
||
border-radius: var(--radius);
|
||
padding: 1.5rem;
|
||
margin: 1.5rem 0;
|
||
text-align: left;
|
||
}
|
||
|
||
.browser-warning h3 {
|
||
color: var(--warning);
|
||
margin-top: 0;
|
||
}
|
||
|
||
.browser-warning ul {
|
||
margin: 0.5rem 0;
|
||
padding-left: 1.5rem;
|
||
}
|
||
|
||
/* Main App */
|
||
.main-app {
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100vh;
|
||
background-color: var(--bg);
|
||
position: relative;
|
||
}
|
||
|
||
/* Header — shared/base.css provides .app-header base */
|
||
.app-header {
|
||
padding: 0.5rem 1rem;
|
||
}
|
||
|
||
.header-left,
|
||
.header-right {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.header-divider {
|
||
color: var(--border);
|
||
margin: 0 0.25rem;
|
||
}
|
||
|
||
/* Main Content */
|
||
.main-content {
|
||
display: flex;
|
||
flex: 1;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* Folder Tree Pane */
|
||
.folder-tree-pane {
|
||
width: 300px;
|
||
min-width: 150px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
background-color: var(--bg-secondary);
|
||
border-right: 1px solid var(--border);
|
||
flex-shrink: 0;
|
||
position: relative;
|
||
transition: width 0.2s ease, min-width 0.2s ease;
|
||
}
|
||
|
||
.folder-tree-pane.collapsed {
|
||
width: 40px !important;
|
||
min-width: 40px !important;
|
||
max-width: 40px !important;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.folder-tree-pane.collapsed .pane-header-controls,
|
||
.folder-tree-pane.collapsed .folder-tree,
|
||
.folder-tree-pane.collapsed .pane-header h3 {
|
||
display: none;
|
||
}
|
||
|
||
.folder-tree-pane.collapsed .pane-header {
|
||
padding: 0.5rem;
|
||
justify-content: center;
|
||
}
|
||
|
||
.folder-tree-pane.collapsed .pane-header-title {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.pane-header-title {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.collapse-tree-btn {
|
||
padding: 0.25rem 0.5rem;
|
||
font-size: 0.8rem;
|
||
}
|
||
|
||
/* Resize Handle */
|
||
.resize-handle {
|
||
position: absolute;
|
||
right: 0;
|
||
top: 0;
|
||
bottom: 0;
|
||
width: 5px;
|
||
cursor: col-resize;
|
||
background-color: transparent;
|
||
z-index: 10;
|
||
}
|
||
|
||
.resize-handle:hover {
|
||
background-color: var(--primary);
|
||
}
|
||
|
||
.pane-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 0.75rem 1rem;
|
||
background-color: var(--bg);
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
|
||
.pane-header-left,
|
||
.pane-header-right {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.75rem;
|
||
}
|
||
|
||
.pane-header h3 {
|
||
font-size: 1rem;
|
||
font-weight: 600;
|
||
margin: 0;
|
||
}
|
||
|
||
.pane-header-controls {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.5rem;
|
||
align-items: flex-end;
|
||
}
|
||
|
||
.folder-stats,
|
||
.file-stats {
|
||
display: flex;
|
||
gap: 1rem;
|
||
font-size: 12px;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
.folder-tree {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: 0.5rem;
|
||
}
|
||
|
||
/* Folder Item */
|
||
.folder-item {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 0.5rem;
|
||
cursor: pointer;
|
||
border-radius: var(--radius);
|
||
user-select: none;
|
||
transition: background-color 0.15s;
|
||
}
|
||
|
||
.folder-item:hover {
|
||
background-color: var(--bg-hover);
|
||
}
|
||
|
||
.folder-item.selected {
|
||
background-color: var(--bg-selected);
|
||
font-weight: 500;
|
||
}
|
||
|
||
.folder-item.folder-hover-highlight {
|
||
background-color: rgba(217, 119, 6, 0.12);
|
||
border-left: 3px solid var(--warning);
|
||
transition: background-color 0.2s, border-left 0.2s;
|
||
}
|
||
|
||
.folder-item.has-unsaved {
|
||
border-left: 3px solid var(--warning);
|
||
}
|
||
|
||
.folder-toggle {
|
||
width: 20px;
|
||
height: 20px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
font-size: 12px;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
.folder-icon {
|
||
margin-right: 0.5rem;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
.folder-name {
|
||
flex: 1;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.folder-count {
|
||
font-size: 11px;
|
||
color: var(--text-muted);
|
||
margin-left: 0.5rem;
|
||
}
|
||
|
||
.folder-children {
|
||
margin-left: 1.5rem;
|
||
}
|
||
|
||
/* Spreadsheet Pane */
|
||
.spreadsheet-pane {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.spreadsheet-container {
|
||
flex: 1;
|
||
overflow: auto;
|
||
background-color: var(--bg);
|
||
}
|
||
|
||
/* ZIP Extract Button in Tree */
|
||
.zip-extract-btn {
|
||
margin-left: auto;
|
||
padding: 0.15rem 0.4rem;
|
||
font-size: 0.7rem;
|
||
opacity: 0;
|
||
transition: opacity 0.15s;
|
||
}
|
||
|
||
.folder-item:hover .zip-extract-btn {
|
||
opacity: 1;
|
||
}
|
||
|
||
.zip-extract-btn:disabled {
|
||
opacity: 0.5;
|
||
cursor: wait;
|
||
}
|
||
|
||
/* ZIP Extract All Button */
|
||
.zip-extract-all-btn {
|
||
margin-left: auto;
|
||
padding: 0.15rem 0.4rem;
|
||
font-size: 0.7rem;
|
||
opacity: 0;
|
||
transition: opacity 0.15s;
|
||
}
|
||
|
||
.folder-item:hover .zip-extract-all-btn {
|
||
opacity: 1;
|
||
}
|
||
|
||
.zip-extract-all-btn:disabled {
|
||
opacity: 0.5;
|
||
cursor: wait;
|
||
}
|
||
|
||
/**
|
||
* Spreadsheet Styles
|
||
* Table, cells, editing, and row states
|
||
*/
|
||
|
||
.spreadsheet {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
font-size: 0.875rem;
|
||
background-color: var(--bg);
|
||
}
|
||
|
||
/* Selected cells */
|
||
.selected-cell {
|
||
background-color: rgba(0, 123, 255, 0.2) !important;
|
||
outline: 1px solid var(--primary);
|
||
}
|
||
|
||
/* Auto-populated cells (gray text to indicate matches filename) */
|
||
.cell-editable.auto-populated {
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
/* Changed fields (blue text to indicate value differs from original filename) */
|
||
.cell-editable.field-changed {
|
||
color: var(--primary);
|
||
font-weight: 500;
|
||
}
|
||
|
||
.spreadsheet thead {
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 10;
|
||
background-color: var(--bg-secondary);
|
||
}
|
||
|
||
.spreadsheet th {
|
||
padding: 0.75rem 0.5rem;
|
||
text-align: left;
|
||
font-weight: 600;
|
||
border-bottom: 2px solid var(--border);
|
||
background-color: var(--bg-secondary);
|
||
position: relative;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.spreadsheet th:hover:not(.col-row-num) {
|
||
background-color: var(--border);
|
||
}
|
||
|
||
/* Sort indicator */
|
||
.sort-indicator {
|
||
display: inline-block;
|
||
font-size: 0.75rem;
|
||
color: var(--primary);
|
||
margin-left: 0.25rem;
|
||
margin-right: 0.25rem;
|
||
font-weight: bold;
|
||
vertical-align: middle;
|
||
}
|
||
|
||
.spreadsheet td {
|
||
padding: 0.5rem;
|
||
border-bottom: 1px solid var(--border);
|
||
vertical-align: top;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
/* Column resizer */
|
||
.column-resizer {
|
||
position: absolute;
|
||
top: 0;
|
||
right: 0;
|
||
width: 5px;
|
||
height: 100%;
|
||
cursor: col-resize;
|
||
user-select: none;
|
||
z-index: 1;
|
||
}
|
||
|
||
.column-resizer:hover {
|
||
background-color: var(--primary);
|
||
}
|
||
|
||
/* Column Widths */
|
||
.col-row-num {
|
||
width: 50px;
|
||
text-align: center;
|
||
background-color: var(--bg-secondary);
|
||
font-weight: 600;
|
||
color: var(--text-muted);
|
||
user-select: none;
|
||
-webkit-user-select: none;
|
||
}
|
||
|
||
.col-original {
|
||
min-width: 250px;
|
||
}
|
||
|
||
.col-extension {
|
||
width: 60px;
|
||
text-align: center;
|
||
}
|
||
|
||
.col-new {
|
||
min-width: 250px;
|
||
}
|
||
|
||
.col-trackingNumber {
|
||
min-width: 200px;
|
||
width: 200px;
|
||
}
|
||
|
||
.col-revision {
|
||
width: 80px;
|
||
}
|
||
|
||
.col-status {
|
||
width: 100px;
|
||
}
|
||
|
||
.col-title {
|
||
min-width: 200px;
|
||
}
|
||
|
||
.col-sha256 {
|
||
min-width: 150px;
|
||
font-family: var(--font-mono);
|
||
font-size: 11px;
|
||
}
|
||
|
||
.col-actions {
|
||
width: 100px;
|
||
text-align: center;
|
||
}
|
||
|
||
/* Row States */
|
||
.spreadsheet tbody tr {
|
||
transition: background-color 0.15s;
|
||
}
|
||
|
||
.spreadsheet tbody tr:hover {
|
||
background-color: var(--bg-hover);
|
||
}
|
||
|
||
.spreadsheet tbody tr.modified {
|
||
border-left: 3px solid var(--warning);
|
||
}
|
||
|
||
.spreadsheet tbody tr.error {
|
||
border-left: 3px solid var(--danger);
|
||
background-color: rgba(220, 53, 69, 0.08);
|
||
}
|
||
|
||
.spreadsheet tbody tr.saving {
|
||
opacity: 0.6;
|
||
pointer-events: none;
|
||
}
|
||
|
||
/* Cell Content */
|
||
.cell-content {
|
||
display: block;
|
||
width: 100%;
|
||
}
|
||
|
||
.cell-link {
|
||
color: var(--primary);
|
||
text-decoration: none;
|
||
}
|
||
|
||
.cell-link:hover {
|
||
text-decoration: underline;
|
||
}
|
||
|
||
.cell-extension {
|
||
font-family: var(--font-mono);
|
||
font-size: 12px;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
.cell-computed {
|
||
font-style: italic;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
/* Editable Cells */
|
||
.cell-editable {
|
||
cursor: text;
|
||
position: relative;
|
||
}
|
||
|
||
.cell-editable:hover {
|
||
background-color: var(--bg-hover);
|
||
}
|
||
|
||
.cell-content[contenteditable="true"] {
|
||
outline: 2px solid var(--primary);
|
||
outline-offset: 0;
|
||
background-color: var(--bg);
|
||
min-height: 1.5em;
|
||
}
|
||
|
||
.cell-content[contenteditable="true"]:focus {
|
||
outline: 2px solid var(--primary);
|
||
outline-offset: 0;
|
||
}
|
||
|
||
.cell-content.editing {
|
||
white-space: pre-wrap;
|
||
word-break: break-word;
|
||
}
|
||
|
||
/* Computed cells */
|
||
.cell-editable.computed {
|
||
font-style: italic;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
.cell-editable.computed:hover {
|
||
font-style: normal;
|
||
color: var(--text);
|
||
}
|
||
|
||
/* Validation states */
|
||
.validation-error {
|
||
background-color: rgba(220, 53, 69, 0.1);
|
||
border-left: 3px solid var(--danger);
|
||
}
|
||
|
||
.validation-warning {
|
||
background-color: rgba(255, 193, 7, 0.1);
|
||
border-left: 3px solid #ffc107;
|
||
}
|
||
|
||
/* Inline Actions */
|
||
.inline-actions {
|
||
position: absolute;
|
||
right: 0.25rem;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
display: flex;
|
||
gap: 0.25rem;
|
||
background-color: var(--bg);
|
||
padding: 0.125rem;
|
||
border-radius: 3px;
|
||
}
|
||
|
||
.cell-editable {
|
||
position: relative;
|
||
padding-right: 3.5rem; /* Space for buttons */
|
||
}
|
||
|
||
.btn-inline {
|
||
border: none;
|
||
background: rgba(255, 255, 255, 0.9);
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
padding: 0.25rem 0.5rem;
|
||
border-radius: 3px;
|
||
transition: all 0.15s;
|
||
font-weight: bold;
|
||
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.btn-save {
|
||
color: var(--success);
|
||
}
|
||
|
||
.btn-save:hover {
|
||
background-color: var(--success);
|
||
color: white;
|
||
}
|
||
|
||
.btn-cancel {
|
||
color: var(--danger);
|
||
}
|
||
|
||
.btn-cancel:hover {
|
||
background-color: var(--danger);
|
||
color: white;
|
||
}
|
||
|
||
/* Formula Preview */
|
||
.formula-preview {
|
||
position: absolute;
|
||
bottom: 100%;
|
||
left: 0;
|
||
background-color: var(--bg-secondary);
|
||
border: 1px solid var(--border);
|
||
border-radius: 4px;
|
||
padding: 0.5rem;
|
||
font-size: 12px;
|
||
min-width: 200px;
|
||
z-index: 100;
|
||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.formula-preview-label {
|
||
font-weight: 600;
|
||
color: var(--text-muted);
|
||
margin-bottom: 0.25rem;
|
||
}
|
||
|
||
.formula-preview-value {
|
||
color: var(--text);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.formula-preview-value.valid {
|
||
color: var(--success);
|
||
}
|
||
|
||
.formula-preview-value.invalid {
|
||
color: var(--danger);
|
||
}
|
||
|
||
.preview-check {
|
||
color: var(--success);
|
||
font-size: 16px;
|
||
cursor: pointer;
|
||
padding: 0.25rem;
|
||
border-radius: 3px;
|
||
transition: background-color 0.15s;
|
||
}
|
||
|
||
.preview-check:hover {
|
||
background-color: rgba(40, 167, 69, 0.1);
|
||
}
|
||
|
||
.preview-error {
|
||
color: var(--danger);
|
||
font-size: 16px;
|
||
}
|
||
|
||
.formula-preview-errors {
|
||
margin-top: 0.25rem;
|
||
font-size: 11px;
|
||
color: var(--danger);
|
||
white-space: normal;
|
||
}
|
||
|
||
/* Validation States */
|
||
.cell-warning {
|
||
background-color: rgba(255, 193, 7, 0.08);
|
||
border-left: 3px solid var(--warning);
|
||
}
|
||
|
||
.cell-error {
|
||
background-color: rgba(220, 53, 69, 0.08);
|
||
border-left: 3px solid var(--danger);
|
||
}
|
||
|
||
.validation-icon {
|
||
display: inline-block;
|
||
margin-left: 0.25rem;
|
||
cursor: help;
|
||
}
|
||
|
||
/* SHA256 Column */
|
||
.sha256-hash {
|
||
font-family: var(--font-mono);
|
||
font-size: 11px;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
.sha256-calculating {
|
||
font-style: italic;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
/* Action Buttons */
|
||
.row-actions {
|
||
display: flex;
|
||
gap: 0.25rem;
|
||
justify-content: center;
|
||
}
|
||
|
||
.btn-icon {
|
||
width: 28px;
|
||
height: 28px;
|
||
padding: 0;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border: 1px solid var(--border);
|
||
border-radius: 4px;
|
||
background-color: var(--bg);
|
||
cursor: pointer;
|
||
transition: all 0.15s;
|
||
}
|
||
|
||
.btn-icon:hover:not(:disabled) {
|
||
background-color: var(--bg-hover);
|
||
transform: scale(1.1);
|
||
}
|
||
|
||
.btn-icon:disabled {
|
||
opacity: 0.3;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.btn-save {
|
||
color: var(--success);
|
||
}
|
||
|
||
.btn-cancel {
|
||
color: var(--danger);
|
||
}
|
||
|
||
/* Empty State */
|
||
.empty-state {
|
||
padding: 3rem;
|
||
text-align: center;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
.empty-state h3 {
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
|
||
/* Spreadsheet Empty State */
|
||
.spreadsheet-empty {
|
||
text-align: center;
|
||
color: var(--text-muted);
|
||
padding: 1.5rem;
|
||
}
|
||
|
||
/* Selection Highlight */
|
||
.cell-selected {
|
||
outline: 2px solid var(--primary);
|
||
outline-offset: -2px;
|
||
z-index: 1;
|
||
}
|
||
|
||
/* Scrollbar Styling */
|
||
.spreadsheet-container::-webkit-scrollbar {
|
||
width: 12px;
|
||
height: 12px;
|
||
}
|
||
|
||
.spreadsheet-container::-webkit-scrollbar-track {
|
||
background-color: var(--bg-secondary);
|
||
}
|
||
|
||
.spreadsheet-container::-webkit-scrollbar-thumb {
|
||
background-color: var(--border-dark);
|
||
border-radius: 6px;
|
||
}
|
||
|
||
.spreadsheet-container::-webkit-scrollbar-thumb:hover {
|
||
background-color: var(--text-muted);
|
||
}
|
||
|
||
/* Preview button active state */
|
||
#togglePreviewBtn.preview-active {
|
||
background-color: var(--primary);
|
||
color: white;
|
||
border-color: var(--primary);
|
||
}
|
||
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="app">
|
||
<!-- Main Application -->
|
||
<div id="mainApp" class="main-app">
|
||
<!-- 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 Classifier</span>
|
||
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.17-beta · 2026-05-07 · glacier-ivory-arch</span></span>
|
||
</div>
|
||
<button id="addDirectoryBtn" class="btn btn-primary">Add Local Directory</button>
|
||
<button id="refreshHeaderBtn" class="btn btn-secondary hidden" title="Refresh and rescan directory" aria-label="Refresh" 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 Content -->
|
||
<div class="main-content">
|
||
<!-- Folder Tree -->
|
||
<aside class="folder-tree-pane" id="folderTreePane">
|
||
<div class="pane-header">
|
||
<div class="pane-header-title">
|
||
<button class="btn btn-sm collapse-tree-btn" id="collapseTreeBtn" title="Collapse folder tree">◀</button>
|
||
<h3>Folder Tree</h3>
|
||
</div>
|
||
<div class="pane-header-controls">
|
||
<label class="checkbox-label" title="Auto-scroll folder tree when hovering files">
|
||
<input type="checkbox" id="autoScrollCheckbox" checked>
|
||
Auto-scroll
|
||
</label>
|
||
<label class="checkbox-label">
|
||
<input type="checkbox" id="hideCompliantCheckbox">
|
||
Hide Compliant
|
||
</label>
|
||
<span id="selectedFoldersCount" class="folder-count">0 folders selected</span>
|
||
</div>
|
||
</div>
|
||
<div id="folderTree" class="folder-tree">
|
||
<!-- Dynamically populated -->
|
||
</div>
|
||
<div class="resize-handle" id="treeResizeHandle"></div>
|
||
</aside>
|
||
|
||
<!-- Spreadsheet Table -->
|
||
<main class="spreadsheet-pane">
|
||
<div class="pane-header">
|
||
<div class="pane-header-left">
|
||
<h3>Files</h3>
|
||
<div class="file-stats">
|
||
<span id="totalFiles">0 files</span>
|
||
<span id="modifiedFiles">0 modified</span>
|
||
<span id="errorFiles" class="hidden">0 errors</span>
|
||
</div>
|
||
</div>
|
||
<div class="pane-header-right">
|
||
<button id="saveAllBtn" class="btn btn-success btn-sm" disabled>Save All</button>
|
||
<button id="cancelAllBtn" class="btn btn-secondary btn-sm" disabled>Cancel All</button>
|
||
<span class="header-divider">|</span>
|
||
<label class="checkbox-label">
|
||
<input type="checkbox" id="sha256Checkbox">
|
||
SHA256
|
||
</label>
|
||
<button id="exportHashesBtn" class="btn btn-secondary btn-sm" disabled title="Export SHA256 hashes in sha256sum format">💾 Export Hashes</button>
|
||
<span class="header-divider">|</span>
|
||
<button id="togglePreviewBtn" class="btn btn-secondary btn-sm" title="Toggle file preview panel">👁 Preview</button>
|
||
</div>
|
||
</div>
|
||
<div class="spreadsheet-container">
|
||
<table id="spreadsheet" class="spreadsheet">
|
||
<thead>
|
||
<tr>
|
||
<th class="col-row-num">#</th>
|
||
<th class="col-original">Original Filename
|
||
<input type="text" class="column-filter" data-filter-field="original" placeholder="filter…" spellcheck="false" aria-label="Filter by original filename">
|
||
</th>
|
||
<th class="col-extension">Ext
|
||
<input type="text" class="column-filter" data-filter-field="extension" placeholder="filter…" spellcheck="false" aria-label="Filter by extension">
|
||
</th>
|
||
<th class="col-new">New Filename
|
||
<input type="text" class="column-filter" data-filter-field="newFilename" placeholder="filter…" spellcheck="false" aria-label="Filter by new filename">
|
||
</th>
|
||
<th class="col-trackingNumber">Tracking
|
||
<input type="text" class="column-filter" data-filter-field="trackingNumber" placeholder="filter…" spellcheck="false" aria-label="Filter by tracking number">
|
||
</th>
|
||
<th class="col-revision">Rev
|
||
<input type="text" class="column-filter" data-filter-field="revision" placeholder="filter…" spellcheck="false" aria-label="Filter by revision">
|
||
</th>
|
||
<th class="col-status">Status
|
||
<input type="text" class="column-filter" data-filter-field="status" placeholder="filter…" spellcheck="false" aria-label="Filter by status">
|
||
</th>
|
||
<th class="col-title">Title
|
||
<input type="text" class="column-filter" data-filter-field="title" placeholder="filter…" spellcheck="false" aria-label="Filter by title">
|
||
</th>
|
||
<th class="col-sha256 hidden" id="sha256Column">SHA256
|
||
<input type="text" class="column-filter" data-filter-field="sha256" placeholder="filter…" spellcheck="false" aria-label="Filter by SHA256">
|
||
</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="spreadsheetBody">
|
||
<!-- Dynamically populated -->
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</main>
|
||
|
||
</div>
|
||
|
||
<!-- Empty State — shown until a directory is selected -->
|
||
<div id="welcomeScreen" class="empty-state">
|
||
<div class="empty-state-content">
|
||
<h2>ZDDC Classifier</h2>
|
||
<p>Rename a folder of files to ZDDC format using a spreadsheet interface.</p>
|
||
<p>Open a directory, fill in tracking number, revision, status, and title for each file, then save — the files are renamed on disk.</p>
|
||
|
||
<!-- Browser Compatibility Warning -->
|
||
<div id="browserWarning" class="browser-warning hidden">
|
||
<h3>⚠️ Browser Not Supported</h3>
|
||
<p>This application requires the File System Access API, available only in Chromium-based browsers (Chrome, Edge, Brave, Opera).</p>
|
||
</div>
|
||
|
||
<ul class="welcome-list">
|
||
<li>Files already named to ZDDC format are parsed automatically</li>
|
||
<li>Edit cells directly, or copy columns to and from Excel</li>
|
||
<li>Real-time validation highlights non-compliant names</li>
|
||
<li>Rename one file or all modified files at once</li>
|
||
</ul>
|
||
|
||
<p>Click <strong>Add Local Directory</strong> to begin.</p>
|
||
|
||
<p class="note">This application works entirely in your browser. No data is transmitted to any server.</p>
|
||
</div>
|
||
</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 Classifier</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 Classifier?</h3>
|
||
<p>The Classifier is a spreadsheet-based tool for renaming files to ZDDC naming conventions. It reads a folder of files and presents them in an editable grid where you can set tracking number, revision, status, and title — then saves the renamed files back to disk.</p>
|
||
|
||
<h3>Getting Started</h3>
|
||
<ol>
|
||
<li>Click <strong>Add Local Directory</strong> to open a folder containing files to rename.</li>
|
||
<li>The folder tree on the left shows all sub-folders. Click a folder to load its files.</li>
|
||
<li>Edit cells in the spreadsheet to set the new filename components.</li>
|
||
<li>Click <strong>Save All</strong> (or save individual rows) to rename the files on disk.</li>
|
||
</ol>
|
||
|
||
<h3>Folder Tree</h3>
|
||
<dl>
|
||
<dt>Multi-select</dt>
|
||
<dd>Hold <kbd>Ctrl</kbd> and click to select multiple folders. Hold <kbd>Shift</kbd> to select a range. Files from all selected folders are shown together.</dd>
|
||
<dt>Hide Compliant</dt>
|
||
<dd>Hides folders where all files already have valid ZDDC names, letting you focus on work remaining.</dd>
|
||
<dt>Auto-scroll</dt>
|
||
<dd>When enabled, the folder tree scrolls to highlight the folder containing the row you are editing.</dd>
|
||
</dl>
|
||
|
||
<h3>Spreadsheet Editing</h3>
|
||
<dl>
|
||
<dt>Direct cell editing</dt>
|
||
<dd>Click any cell in the New Filename, Tracking, Rev, Status, or Title columns to edit it. Press <kbd>Enter</kbd> to confirm, <kbd>Escape</kbd> to cancel.</dd>
|
||
<dt>RC References</dt>
|
||
<dd>Type a formula like <code>=R[-1]C</code> to copy the value from the cell one row above in the same column — similar to Excel relative references.</dd>
|
||
<dt>Regex capture groups</dt>
|
||
<dd>Type a formula like <code>=RE(RC[-3], "(\w+)-(\d+)", "$1")</code> to extract a pattern from another cell using a regular expression.</dd>
|
||
<dt>Validation</dt>
|
||
<dd>Cells are validated automatically. Invalid values are highlighted in red. The New Filename column shows the composed result.</dd>
|
||
<dt>Column Filters</dt>
|
||
<dd>Each column header has a filter input. Supported syntax:</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>!^~</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>
|
||
|
||
<h3>Saving Files</h3>
|
||
<dl>
|
||
<dt>Save All</dt>
|
||
<dd>Renames all modified files in one operation. Confirms before proceeding.</dd>
|
||
<dt>Cancel All</dt>
|
||
<dd>Reverts all unsaved edits back to the original filenames.</dd>
|
||
<dt>SHA256</dt>
|
||
<dd>Enable to compute a cryptographic hash of each file. Use <strong>Export Hashes</strong> to save a <code>sha256sum</code>-compatible file.</dd>
|
||
</dl>
|
||
|
||
<h3>ZDDC Filename Format</h3>
|
||
<p>The required format is:</p>
|
||
<p><code>TRACKINGNUMBER_REVISION (STATUS) - Title.ext</code></p>
|
||
<p>Example: <code>123456-EL-SPC-2623_A (IFR) - Electrical Specification.pdf</code></p>
|
||
<p>Valid statuses: IFA, IFB, IFC, IFD, IFI, IFP, IFR, IFU, REC, RSA, RSB, RSC, RSD, RSI, ---</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);
|
||
|
||
// shared/zddc-source.js — source abstraction for tools that handle
|
||
// directory trees (classifier, mdedit, transmittal, browse, archive).
|
||
//
|
||
// Two backends:
|
||
//
|
||
// 1. Local — wraps a real FileSystemDirectoryHandle from the
|
||
// File System Access API. Reads + writes go through the
|
||
// FS Access API directly.
|
||
//
|
||
// 2. HTTP — talks to zddc-server's directory listing JSON
|
||
// (Accept: application/json) for reads and the file API
|
||
// (PUT/DELETE/POST X-ZDDC-Op) for writes. Implements a
|
||
// polyfill of the FS Access API surface area the tools
|
||
// use (kind, name, values(), getFileHandle, getDirectoryHandle,
|
||
// removeEntry, getFile, createWritable, queryPermission /
|
||
// requestPermission) so existing code works unchanged.
|
||
//
|
||
// The polyfill makes auto-load possible: when zddc-server serves
|
||
// a tool at /<dir>/<tool>.html, the tool detects HTTP mode at
|
||
// startup, builds an HttpDirectoryHandle for the tool's containing
|
||
// directory, and hands it to the existing openDirectory(handle)
|
||
// flow without ever showing the file picker.
|
||
//
|
||
// Renames inside a tool today are typically done as
|
||
// "write new + remove old". With HTTP-backed handles this becomes
|
||
// PUT + DELETE — non-atomic. Tools that prefer the atomic server
|
||
// MOVE should call window.zddc.source.moveFile(srcUrl, dstUrl)
|
||
// directly instead of going through the polyfill.
|
||
(function () {
|
||
'use strict';
|
||
|
||
if (!window.zddc) window.zddc = {};
|
||
var FA = window.FileSystemDirectoryHandle || null;
|
||
|
||
// -----------------------------------------------------------------
|
||
// HTTP file API helpers
|
||
// -----------------------------------------------------------------
|
||
|
||
function joinUrl(base, name, isDir) {
|
||
if (!base.endsWith('/')) base = base + '/';
|
||
return base + encodeURIComponent(name) + (isDir ? '/' : '');
|
||
}
|
||
|
||
// Server returns directory entries with a trailing "/" on names.
|
||
// Strip it for the FS Access API name surface.
|
||
function stripSlash(name) {
|
||
return name.endsWith('/') ? name.slice(0, -1) : name;
|
||
}
|
||
|
||
async function httpListing(url) {
|
||
var resp = await fetch(url, {
|
||
headers: { 'Accept': 'application/json' },
|
||
credentials: 'same-origin'
|
||
});
|
||
if (!resp.ok) {
|
||
var err = new Error('listing ' + url + ': HTTP ' + resp.status);
|
||
err.status = resp.status;
|
||
throw err;
|
||
}
|
||
var data = await resp.json();
|
||
if (!Array.isArray(data)) {
|
||
throw new Error('listing ' + url + ': non-array body');
|
||
}
|
||
return data;
|
||
}
|
||
|
||
async function httpExists(url) {
|
||
try {
|
||
var r = await fetch(url, { method: 'HEAD', credentials: 'same-origin' });
|
||
return r.ok;
|
||
} catch (_) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// -----------------------------------------------------------------
|
||
// HttpFileHandle — FileSystemFileHandle polyfill
|
||
// -----------------------------------------------------------------
|
||
|
||
function makeFile(blob, name, modTime) {
|
||
return new File([blob], name, {
|
||
type: blob.type,
|
||
lastModified: modTime ? modTime.getTime() : Date.now()
|
||
});
|
||
}
|
||
|
||
function HttpFileHandle(url, name, size, modTime) {
|
||
this.kind = 'file';
|
||
this.name = name;
|
||
this._url = url;
|
||
this._size = size || 0;
|
||
this._modTime = modTime || null;
|
||
this._etag = null;
|
||
}
|
||
HttpFileHandle.prototype.getFile = async function () {
|
||
var resp = await fetch(this._url, { credentials: 'same-origin' });
|
||
if (!resp.ok) {
|
||
throw new Error('GET ' + this._url + ': ' + resp.status);
|
||
}
|
||
var etag = resp.headers.get('ETag');
|
||
if (etag) this._etag = etag.replace(/"/g, '');
|
||
var lm = resp.headers.get('Last-Modified');
|
||
var modTime = lm ? new Date(lm) : this._modTime;
|
||
var blob = await resp.blob();
|
||
return makeFile(blob, this.name, modTime);
|
||
};
|
||
HttpFileHandle.prototype.createWritable = async function () {
|
||
var chunks = [];
|
||
var handle = this;
|
||
return {
|
||
async write(data) {
|
||
if (data == null) return;
|
||
if (typeof data === 'object' && data && 'type' in data && data.type === 'write') {
|
||
chunks.push(data.data);
|
||
return;
|
||
}
|
||
if (typeof data === 'object' && data && 'type' in data) {
|
||
// seek/truncate not supported by HTTP backend
|
||
throw new Error('HttpFileHandle write op not supported: ' + data.type);
|
||
}
|
||
chunks.push(data);
|
||
},
|
||
async close() {
|
||
var blob = new Blob(chunks);
|
||
var resp = await fetch(handle._url, {
|
||
method: 'PUT',
|
||
body: blob,
|
||
credentials: 'same-origin'
|
||
});
|
||
if (!resp.ok) {
|
||
var body = '';
|
||
try { body = await resp.text(); } catch (_) { /* ignore */ }
|
||
throw new Error('PUT ' + handle._url + ': ' + resp.status + ' ' + body);
|
||
}
|
||
var et = resp.headers.get('ETag');
|
||
if (et) handle._etag = et.replace(/"/g, '');
|
||
handle._size = blob.size;
|
||
},
|
||
async abort() { chunks = []; }
|
||
};
|
||
};
|
||
HttpFileHandle.prototype.queryPermission = async function () { return 'granted'; };
|
||
HttpFileHandle.prototype.requestPermission = async function () { return 'granted'; };
|
||
HttpFileHandle.prototype.isHttp = true;
|
||
HttpFileHandle.prototype.url = function () { return this._url; };
|
||
|
||
// -----------------------------------------------------------------
|
||
// HttpDirectoryHandle — FileSystemDirectoryHandle polyfill
|
||
// -----------------------------------------------------------------
|
||
|
||
function HttpDirectoryHandle(url, name) {
|
||
this.kind = 'directory';
|
||
if (!url.endsWith('/')) url = url + '/';
|
||
this._url = url;
|
||
this.name = name || guessNameFromUrl(url);
|
||
}
|
||
function guessNameFromUrl(url) {
|
||
var u = url.replace(/\/+$/, '');
|
||
var slash = u.lastIndexOf('/');
|
||
return slash >= 0 ? decodeURIComponent(u.substring(slash + 1)) : u;
|
||
}
|
||
HttpDirectoryHandle.prototype.values = function () {
|
||
var url = this._url;
|
||
return (async function* () {
|
||
var entries;
|
||
try {
|
||
entries = await httpListing(url);
|
||
} catch (e) {
|
||
return;
|
||
}
|
||
for (var i = 0; i < entries.length; i++) {
|
||
var e = entries[i];
|
||
var rawName = stripSlash(e.name);
|
||
var childUrl = joinUrl(url, rawName, e.is_dir);
|
||
if (e.is_dir) {
|
||
yield new HttpDirectoryHandle(childUrl, rawName);
|
||
} else {
|
||
var modTime = e.mod_time ? new Date(e.mod_time) : null;
|
||
yield new HttpFileHandle(childUrl, rawName, e.size || 0, modTime);
|
||
}
|
||
}
|
||
})();
|
||
};
|
||
HttpDirectoryHandle.prototype.entries = function () {
|
||
var iter = this.values();
|
||
return (async function* () {
|
||
for (;;) {
|
||
var step = await iter.next();
|
||
if (step.done) return;
|
||
yield [step.value.name, step.value];
|
||
}
|
||
})();
|
||
};
|
||
HttpDirectoryHandle.prototype.keys = function () {
|
||
var iter = this.values();
|
||
return (async function* () {
|
||
for (;;) {
|
||
var step = await iter.next();
|
||
if (step.done) return;
|
||
yield step.value.name;
|
||
}
|
||
})();
|
||
};
|
||
HttpDirectoryHandle.prototype.getFileHandle = async function (name, opts) {
|
||
opts = opts || {};
|
||
var url = joinUrl(this._url, name, false);
|
||
var exists = await httpExists(url);
|
||
if (!exists && !opts.create) {
|
||
var err = new Error('NotFoundError: ' + name);
|
||
err.name = 'NotFoundError';
|
||
throw err;
|
||
}
|
||
return new HttpFileHandle(url, name, 0, null);
|
||
};
|
||
HttpDirectoryHandle.prototype.getDirectoryHandle = async function (name, opts) {
|
||
opts = opts || {};
|
||
var url = joinUrl(this._url, name, true);
|
||
if (opts.create) {
|
||
var resp = await fetch(url, {
|
||
method: 'POST',
|
||
headers: { 'X-ZDDC-Op': 'mkdir' },
|
||
credentials: 'same-origin'
|
||
});
|
||
if (!resp.ok && resp.status !== 200 && resp.status !== 201) {
|
||
throw new Error('mkdir ' + url + ': ' + resp.status);
|
||
}
|
||
}
|
||
return new HttpDirectoryHandle(url, name);
|
||
};
|
||
HttpDirectoryHandle.prototype.removeEntry = async function (name, opts) {
|
||
opts = opts || {};
|
||
// Probe listing to discover whether name is a file or directory.
|
||
var entries;
|
||
try {
|
||
entries = await httpListing(this._url);
|
||
} catch (e) {
|
||
throw new Error('removeEntry probe failed: ' + e.message);
|
||
}
|
||
var match = null;
|
||
for (var i = 0; i < entries.length; i++) {
|
||
if (stripSlash(entries[i].name) === name) {
|
||
match = entries[i];
|
||
break;
|
||
}
|
||
}
|
||
if (!match) {
|
||
var err = new Error('NotFoundError: ' + name);
|
||
err.name = 'NotFoundError';
|
||
throw err;
|
||
}
|
||
if (match.is_dir && !opts.recursive) {
|
||
// Server doesn't expose a recursive-delete endpoint yet,
|
||
// and FS Access API requires recursive=true to remove a
|
||
// non-empty directory anyway. Reject explicitly so the
|
||
// caller doesn't silently leave a stale tree behind.
|
||
var derr = new Error('Removing directories over HTTP is not supported');
|
||
derr.name = 'InvalidStateError';
|
||
throw derr;
|
||
}
|
||
var url = joinUrl(this._url, name, match.is_dir);
|
||
var resp = await fetch(url, { method: 'DELETE', credentials: 'same-origin' });
|
||
if (!resp.ok && resp.status !== 204) {
|
||
throw new Error('DELETE ' + url + ': ' + resp.status);
|
||
}
|
||
};
|
||
HttpDirectoryHandle.prototype.queryPermission = async function () { return 'granted'; };
|
||
HttpDirectoryHandle.prototype.requestPermission = async function () { return 'granted'; };
|
||
HttpDirectoryHandle.prototype.isHttp = true;
|
||
HttpDirectoryHandle.prototype.url = function () { return this._url; };
|
||
|
||
// -----------------------------------------------------------------
|
||
// Top-level helpers
|
||
// -----------------------------------------------------------------
|
||
|
||
// Strip a trailing tool .html (e.g. classifier.html) from a path
|
||
// to land on the "directory the tool was opened in".
|
||
function pathToDir(pathname) {
|
||
if (!pathname) return '/';
|
||
if (pathname.endsWith('/')) return pathname;
|
||
var slash = pathname.lastIndexOf('/');
|
||
return slash >= 0 ? pathname.substring(0, slash + 1) : '/';
|
||
}
|
||
|
||
// Probe the server-mode root for the current page. Returns:
|
||
//
|
||
// { handle: HttpDirectoryHandle, status: 200 } — server reachable, listing returned
|
||
// { handle: null, status: 403 } — server reachable but listing forbidden
|
||
// { handle: null, status: 0 } — not http(s), or server unreachable / non-JSON
|
||
//
|
||
// Tools that auto-load on startup distinguish 403 (show "no
|
||
// permission to list this directory" message) from 0 (fall back
|
||
// to local-mode welcome screen).
|
||
//
|
||
// Tool init pattern:
|
||
// if (location.protocol !== 'file:') {
|
||
// const r = await zddc.source.detectServerRoot();
|
||
// if (r.handle) await openDirectory(r.handle);
|
||
// else if (r.status === 403) showNoPermissionMessage();
|
||
// else showWelcome();
|
||
// } else { showWelcome(); }
|
||
async function detectServerRoot() {
|
||
if (typeof location === 'undefined') {
|
||
return { handle: null, status: 0 };
|
||
}
|
||
if (location.protocol !== 'http:' && location.protocol !== 'https:') {
|
||
return { handle: null, status: 0 };
|
||
}
|
||
var dirPath = pathToDir(location.pathname);
|
||
var url = location.origin + dirPath;
|
||
try {
|
||
await httpListing(url);
|
||
} catch (e) {
|
||
if (e && e.status === 403) {
|
||
return { handle: null, status: 403 };
|
||
}
|
||
return { handle: null, status: 0 };
|
||
}
|
||
return {
|
||
handle: new HttpDirectoryHandle(url, guessNameFromUrl(url)),
|
||
status: 200,
|
||
};
|
||
}
|
||
|
||
// Atomic file move. Path arguments are absolute URL paths
|
||
// (starting with /). Honors the file API's POST /op=move
|
||
// contract. Returns the new ETag.
|
||
async function moveFile(srcUrlPath, dstUrlPath, opts) {
|
||
opts = opts || {};
|
||
var headers = {
|
||
'X-ZDDC-Op': 'move',
|
||
'X-ZDDC-Destination': dstUrlPath
|
||
};
|
||
if (opts.ifMatch) headers['If-Match'] = opts.ifMatch;
|
||
var resp = await fetch(srcUrlPath, {
|
||
method: 'POST',
|
||
headers: headers,
|
||
credentials: 'same-origin'
|
||
});
|
||
if (!resp.ok) {
|
||
var body = '';
|
||
try { body = await resp.text(); } catch (_) { /* ignore */ }
|
||
throw new Error('move ' + srcUrlPath + ' → ' + dstUrlPath + ': ' + resp.status + ' ' + body);
|
||
}
|
||
var et = resp.headers.get('ETag');
|
||
return et ? et.replace(/"/g, '') : null;
|
||
}
|
||
|
||
// Detect at construction time whether a directory handle is the
|
||
// HTTP polyfill or a real FS Access API handle. Useful for tools
|
||
// that want to take the optimized path (e.g. atomic moveFile)
|
||
// when in HTTP mode rather than the FS-API copy+remove fallback.
|
||
function isHttpHandle(handle) {
|
||
return !!(handle && handle.isHttp === true);
|
||
}
|
||
|
||
window.zddc.source = {
|
||
HttpDirectoryHandle: HttpDirectoryHandle,
|
||
HttpFileHandle: HttpFileHandle,
|
||
detectServerRoot: detectServerRoot,
|
||
moveFile: moveFile,
|
||
isHttpHandle: isHttpHandle,
|
||
// Lower-level helpers exposed for tools that want to call the
|
||
// server directly without going through the polyfill.
|
||
httpListing: httpListing,
|
||
joinUrl: joinUrl,
|
||
stripSlash: stripSlash
|
||
};
|
||
})();
|
||
|
||
/**
|
||
* 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 Classifier - Main Application
|
||
* Spreadsheet-based file renaming with Excel-like formulas
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
// Global application state
|
||
window.app = {
|
||
// File System
|
||
rootHandle: null,
|
||
|
||
// Data
|
||
folderTree: [],
|
||
selectedFolders: new Set(), // Multi-select support
|
||
lastSelectedFolderPath: null,
|
||
hideCompliant: false,
|
||
calculateSha256: false,
|
||
|
||
// DOM elements (populated on init)
|
||
dom: {},
|
||
|
||
// Modules (populated by other files)
|
||
modules: {}
|
||
};
|
||
|
||
/**
|
||
* Initialize the application
|
||
*/
|
||
function init() {
|
||
// Cache DOM elements + wire events first so the welcome screen
|
||
// (and the HTTP-mode auto-load below) can use them.
|
||
cacheDOMElements();
|
||
setupEventListeners();
|
||
|
||
// Browser-compatibility branch:
|
||
// HTTP mode (served by zddc-server) — works everywhere; the
|
||
// HTTP polyfill stands in for the FS Access API. Auto-load
|
||
// the directory the page lives in.
|
||
// Local mode (file://) — requires FS Access API for write
|
||
// access to the user-picked folder. Show the warning if
|
||
// the API is missing.
|
||
if (location.protocol === 'http:' || location.protocol === 'https:') {
|
||
// Don't disable the picker button — even in HTTP mode the
|
||
// user might want to add a local folder. But the auto-load
|
||
// below means the welcome screen usually never shows.
|
||
(async function () {
|
||
try {
|
||
var probe = await window.zddc.source.detectServerRoot();
|
||
if (probe.handle) {
|
||
await openDirectory(probe.handle);
|
||
return;
|
||
}
|
||
if (probe.status === 403) {
|
||
showHttpForbiddenMessage();
|
||
return;
|
||
}
|
||
} catch (err) {
|
||
console.warn('classifier: server-mode auto-load failed:', err);
|
||
}
|
||
// Server-mode probe inconclusive — fall through to welcome.
|
||
if (!checkBrowserCompatibility()) {
|
||
showBrowserWarning();
|
||
return;
|
||
}
|
||
showWelcomeScreen();
|
||
})();
|
||
return;
|
||
}
|
||
|
||
if (!checkBrowserCompatibility()) {
|
||
showBrowserWarning();
|
||
return;
|
||
}
|
||
showWelcomeScreen();
|
||
}
|
||
|
||
/**
|
||
* Check if browser supports File System Access API. Used in local
|
||
* (file://) mode only — HTTP mode runs through the HTTP polyfill,
|
||
* which has no browser dependency beyond fetch.
|
||
*/
|
||
function checkBrowserCompatibility() {
|
||
return 'showDirectoryPicker' in window;
|
||
}
|
||
|
||
/**
|
||
* Show a clear "no permission to list" message for HTTP-mode users
|
||
* who land on a path their ACL doesn't allow them to list. Distinct
|
||
* from the welcome screen so the user understands why the file tree
|
||
* is empty rather than wondering if they need to pick a folder.
|
||
*/
|
||
function showHttpForbiddenMessage() {
|
||
var screen = document.getElementById('welcomeScreen');
|
||
if (!screen) return;
|
||
screen.classList.remove('hidden');
|
||
var msg = document.createElement('div');
|
||
msg.className = 'classifier-forbidden-message';
|
||
msg.style.cssText = 'padding: 1.5rem; max-width: 36rem; margin: 0 auto; text-align: center;';
|
||
msg.innerHTML =
|
||
'<h2 style="margin-bottom: 0.75rem;">No permission to list this directory</h2>' +
|
||
'<p>Your account does not have read access to this folder. ' +
|
||
'You may still be able to upload files if your role allows it; ' +
|
||
'contact the document controller if you believe this is wrong.</p>';
|
||
screen.appendChild(msg);
|
||
var addBtn = document.getElementById('addDirectoryBtn');
|
||
if (addBtn) addBtn.disabled = true;
|
||
}
|
||
|
||
/**
|
||
* Show browser compatibility warning
|
||
*/
|
||
function showBrowserWarning() {
|
||
const warning = document.getElementById('browserWarning');
|
||
const selectBtn = document.getElementById('addDirectoryBtn');
|
||
if (warning) {
|
||
warning.classList.remove('hidden');
|
||
}
|
||
if (selectBtn) {
|
||
selectBtn.disabled = true;
|
||
selectBtn.textContent = 'Browser Not Supported';
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Cache DOM element references
|
||
*/
|
||
function cacheDOMElements() {
|
||
app.dom = {
|
||
// Screens
|
||
welcomeScreen: document.getElementById('welcomeScreen'),
|
||
mainApp: document.getElementById('mainApp'),
|
||
|
||
// Header buttons
|
||
addDirectoryBtn: document.getElementById('addDirectoryBtn'),
|
||
refreshHeaderBtn: document.getElementById('refreshHeaderBtn'),
|
||
saveAllBtn: document.getElementById('saveAllBtn'),
|
||
cancelAllBtn: document.getElementById('cancelAllBtn'),
|
||
exportHashesBtn: document.getElementById('exportHashesBtn'),
|
||
sha256Checkbox: document.getElementById('sha256Checkbox'),
|
||
hideCompliantCheckbox: document.getElementById('hideCompliantCheckbox'),
|
||
|
||
// Folder tree
|
||
folderTree: document.getElementById('folderTree'),
|
||
folderTreePane: document.getElementById('folderTreePane'),
|
||
collapseTreeBtn: document.getElementById('collapseTreeBtn'),
|
||
autoScrollCheckbox: document.getElementById('autoScrollCheckbox'),
|
||
selectedFoldersCount: document.getElementById('selectedFoldersCount'),
|
||
|
||
// Spreadsheet
|
||
spreadsheet: document.getElementById('spreadsheet'),
|
||
spreadsheetBody: document.getElementById('spreadsheetBody'),
|
||
sha256Column: document.getElementById('sha256Column'),
|
||
|
||
// Stats
|
||
totalFiles: document.getElementById('totalFiles'),
|
||
modifiedFiles: document.getElementById('modifiedFiles'),
|
||
errorFiles: document.getElementById('errorFiles'),
|
||
|
||
// Preview
|
||
togglePreviewBtn: document.getElementById('togglePreviewBtn')
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Set up event listeners
|
||
*/
|
||
function setupEventListeners() {
|
||
// Directory selection
|
||
app.dom.addDirectoryBtn.addEventListener('click', handleSelectDirectory);
|
||
app.dom.refreshHeaderBtn.addEventListener('click', handleRefresh);
|
||
|
||
// Drag and drop on welcome screen
|
||
setupWelcomeDragDrop();
|
||
|
||
// Bulk actions
|
||
app.dom.saveAllBtn.addEventListener('click', handleSaveAll);
|
||
app.dom.cancelAllBtn.addEventListener('click', handleCancelAll);
|
||
|
||
// Export hashes
|
||
app.dom.exportHashesBtn.addEventListener('click', handleExportHashes);
|
||
|
||
// SHA256 toggle
|
||
app.dom.sha256Checkbox.addEventListener('change', handleSha256Toggle);
|
||
|
||
// Hide compliant toggle
|
||
app.dom.hideCompliantCheckbox.addEventListener('change', handleHideCompliantToggle);
|
||
|
||
// Collapse tree button
|
||
app.dom.collapseTreeBtn.addEventListener('click', handleCollapseTree);
|
||
|
||
// Keyboard shortcuts
|
||
document.addEventListener('keydown', handleKeyDown);
|
||
|
||
// Resize handle
|
||
setupResizeHandle();
|
||
}
|
||
|
||
/**
|
||
* Handle collapse/expand folder tree pane
|
||
*/
|
||
function handleCollapseTree() {
|
||
const pane = app.dom.folderTreePane;
|
||
const btn = app.dom.collapseTreeBtn;
|
||
|
||
pane.classList.toggle('collapsed');
|
||
|
||
if (pane.classList.contains('collapsed')) {
|
||
// Clear any inline width from resize handle
|
||
pane.style.width = '';
|
||
btn.textContent = '▶';
|
||
btn.title = 'Expand folder tree';
|
||
} else {
|
||
btn.textContent = '◀';
|
||
btn.title = 'Collapse folder tree';
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Set up folder tree resize handle
|
||
*/
|
||
function setupResizeHandle() {
|
||
const handle = document.getElementById('treeResizeHandle');
|
||
const pane = document.getElementById('folderTreePane');
|
||
|
||
if (!handle || !pane) return;
|
||
|
||
let isResizing = false;
|
||
let startX = 0;
|
||
let startWidth = 0;
|
||
|
||
handle.addEventListener('mousedown', (e) => {
|
||
isResizing = true;
|
||
startX = e.clientX;
|
||
startWidth = pane.offsetWidth;
|
||
document.body.style.cursor = 'col-resize';
|
||
e.preventDefault();
|
||
});
|
||
|
||
document.addEventListener('mousemove', (e) => {
|
||
if (!isResizing) return;
|
||
|
||
const delta = e.clientX - startX;
|
||
const newWidth = startWidth + delta;
|
||
|
||
// Respect min width only
|
||
if (newWidth >= 150) {
|
||
pane.style.width = newWidth + 'px';
|
||
}
|
||
});
|
||
|
||
document.addEventListener('mouseup', () => {
|
||
if (isResizing) {
|
||
isResizing = false;
|
||
document.body.style.cursor = '';
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Set up drag-and-drop on the welcome screen
|
||
*/
|
||
function setupWelcomeDragDrop() {
|
||
const screen = app.dom.welcomeScreen;
|
||
if (!screen) return;
|
||
|
||
['dragenter', 'dragover'].forEach(evt => {
|
||
screen.addEventListener(evt, (e) => {
|
||
e.preventDefault();
|
||
screen.classList.add('drag-over');
|
||
});
|
||
});
|
||
|
||
['dragleave', 'drop'].forEach(evt => {
|
||
screen.addEventListener(evt, (e) => {
|
||
e.preventDefault();
|
||
screen.classList.remove('drag-over');
|
||
});
|
||
});
|
||
|
||
screen.addEventListener('drop', async (e) => {
|
||
const item = e.dataTransfer.items && e.dataTransfer.items[0];
|
||
if (!item) return;
|
||
|
||
const handle = await item.getAsFileSystemHandle();
|
||
if (!handle || handle.kind !== 'directory') {
|
||
alert('Please drop a folder, not a file.');
|
||
return;
|
||
}
|
||
|
||
await openDirectory(handle);
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Handle directory selection via button click
|
||
*/
|
||
async function handleSelectDirectory() {
|
||
try {
|
||
const dirHandle = await window.showDirectoryPicker();
|
||
await openDirectory(dirHandle);
|
||
} catch (err) {
|
||
if (err.name !== 'AbortError') {
|
||
console.error('Error selecting directory:', err);
|
||
alert('Error selecting directory: ' + err.message);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Open a directory handle and initialize the application
|
||
*/
|
||
async function openDirectory(dirHandle) {
|
||
app.rootHandle = dirHandle;
|
||
|
||
// Hide welcome screen and show main UI
|
||
hideWelcomeScreen();
|
||
showMainUI();
|
||
|
||
// Initialize modules BEFORE scanning (so they're ready for store updates)
|
||
app.modules.spreadsheet.init(); // Subscribe to store
|
||
app.modules.selection.init();
|
||
app.modules.preview.init(); // After selection so it can listen for rowfocused
|
||
app.modules.resize.init();
|
||
app.modules.filter.init();
|
||
app.modules.sort.init();
|
||
app.modules.tree.setupKeyboardShortcuts();
|
||
|
||
// Now scan directory (this will trigger store updates and renders)
|
||
await app.modules.scanner.scanDirectory(dirHandle);
|
||
|
||
// Show refresh button now that a directory is loaded
|
||
if (app.dom.refreshHeaderBtn) { app.dom.refreshHeaderBtn.classList.remove('hidden'); }
|
||
}
|
||
|
||
/**
|
||
* Handle Refresh button - rescan current directory
|
||
*/
|
||
async function handleRefresh() {
|
||
if (!app.rootHandle) {
|
||
alert('No directory selected');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// Clear current data
|
||
app.folderTree = [];
|
||
app.selectedFolders.clear();
|
||
app.lastSelectedFolderPath = null;
|
||
|
||
// Reset store
|
||
app.modules.store.reset();
|
||
|
||
// Rescan directory (modules already initialized, just rescan)
|
||
await app.modules.scanner.scanDirectory(app.rootHandle);
|
||
|
||
} catch (err) {
|
||
console.error('Error refreshing directory:', err);
|
||
alert('Error refreshing directory: ' + err.message);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Handle Save All button
|
||
*/
|
||
async function handleSaveAll() {
|
||
if (!confirm('Save all modified files?')) return;
|
||
|
||
try {
|
||
app.dom.saveAllBtn.disabled = true;
|
||
await app.modules.spreadsheet.saveAllFiles();
|
||
} catch (err) {
|
||
console.error('Error saving files:', err);
|
||
alert('Error saving files: ' + err.message);
|
||
} finally {
|
||
app.dom.saveAllBtn.disabled = false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Handle Cancel All button
|
||
*/
|
||
function handleCancelAll() {
|
||
if (!confirm('Cancel all changes?')) return;
|
||
app.modules.spreadsheet.cancelAllChanges();
|
||
}
|
||
|
||
/**
|
||
* Handle Export Hashes button
|
||
*/
|
||
function handleExportHashes() {
|
||
app.modules.excel.exportHashes();
|
||
}
|
||
|
||
/**
|
||
* Handle SHA256 checkbox toggle
|
||
*/
|
||
function handleSha256Toggle() {
|
||
app.calculateSha256 = app.dom.sha256Checkbox.checked;
|
||
|
||
// Show/hide SHA256 column
|
||
if (app.calculateSha256) {
|
||
app.dom.sha256Column.classList.remove('hidden');
|
||
} else {
|
||
app.dom.sha256Column.classList.add('hidden');
|
||
}
|
||
|
||
// Re-render table
|
||
app.modules.spreadsheet.render();
|
||
}
|
||
|
||
/**
|
||
* Handle Hide Compliant checkbox toggle
|
||
*/
|
||
function handleHideCompliantToggle() {
|
||
app.hideCompliant = app.dom.hideCompliantCheckbox.checked;
|
||
app.modules.store.setHideCompliant(app.hideCompliant);
|
||
}
|
||
|
||
/**
|
||
* Handle keyboard shortcuts
|
||
*/
|
||
function handleKeyDown(e) {
|
||
// Ctrl+S - Save All
|
||
if (e.ctrlKey && e.key === 's') {
|
||
e.preventDefault();
|
||
if (!app.dom.saveAllBtn.disabled) {
|
||
handleSaveAll();
|
||
}
|
||
}
|
||
|
||
// Escape - Cancel editing
|
||
if (e.key === 'Escape') {
|
||
app.modules.spreadsheet.cancelEditing();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Show welcome screen (empty-state overlay)
|
||
*/
|
||
function showWelcomeScreen() {
|
||
if (app.dom.welcomeScreen) {
|
||
app.dom.welcomeScreen.classList.remove('hidden');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Hide welcome screen (empty-state overlay)
|
||
*/
|
||
function hideWelcomeScreen() {
|
||
if (app.dom.welcomeScreen) {
|
||
app.dom.welcomeScreen.classList.add('hidden');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Show main UI (no-op: main UI is always rendered)
|
||
*/
|
||
function showMainUI() {
|
||
// Main app is always visible; only the empty-state overlay is toggled
|
||
}
|
||
|
||
/**
|
||
* Update stats display
|
||
*/
|
||
function updateStats() {
|
||
const files = app.modules.store.getDisplayFiles();
|
||
const totalFiles = files.length;
|
||
const modifiedFiles = files.filter(f => f.isDirty).length;
|
||
const errorFiles = files.filter(f => f.error).length;
|
||
|
||
app.dom.totalFiles.textContent = `${totalFiles} file${totalFiles !== 1 ? 's' : ''}`;
|
||
app.dom.modifiedFiles.textContent = `${modifiedFiles} modified`;
|
||
|
||
if (errorFiles > 0) {
|
||
app.dom.errorFiles.textContent = `${errorFiles} error${errorFiles !== 1 ? 's' : ''}`;
|
||
app.dom.errorFiles.classList.remove('hidden');
|
||
} else {
|
||
app.dom.errorFiles.classList.add('hidden');
|
||
}
|
||
|
||
// Enable/disable bulk action buttons
|
||
app.dom.saveAllBtn.disabled = modifiedFiles === 0;
|
||
app.dom.cancelAllBtn.disabled = modifiedFiles === 0;
|
||
|
||
// Enable/disable export hashes button
|
||
app.dom.exportHashesBtn.disabled = totalFiles === 0 || !app.calculateSha256;
|
||
}
|
||
|
||
// Export functions for use by other modules
|
||
app.modules.app = {
|
||
updateStats
|
||
};
|
||
|
||
// Initialize when DOM is ready
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', init);
|
||
} else {
|
||
init();
|
||
}
|
||
})();
|
||
|
||
/**
|
||
* Classifier utilities — thin convenience layer over window.zddc.
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
/**
|
||
* Compute new filename from file fields.
|
||
* ZDDC format: trackingNumber_revision (status) - title.ext
|
||
* Falls back to original filename if any required ZDDC field is missing.
|
||
*/
|
||
function computeNewFilename(file) {
|
||
if (file.manualFilename) {
|
||
return file.manualFilename;
|
||
}
|
||
|
||
const formatted = zddc.formatFilename({
|
||
trackingNumber: file.trackingNumber || '',
|
||
revision: file.revision || '',
|
||
status: file.status || '',
|
||
title: file.title || '',
|
||
extension: file.extension || '',
|
||
});
|
||
|
||
return formatted || zddc.joinExtension(file.originalFilename, file.extension);
|
||
}
|
||
|
||
/**
|
||
* Get column value from file object.
|
||
*/
|
||
function getColumnValue(file, columnName) {
|
||
switch (columnName) {
|
||
case 'original': return file.originalFilename || '';
|
||
case 'extension': return file.extension || '';
|
||
case 'new':
|
||
case 'newFilename': return file.manualFilename || computeNewFilename(file);
|
||
case 'trackingNumber': return file.trackingNumber || '';
|
||
case 'revision': return file.revision || '';
|
||
case 'status': return file.status || '';
|
||
case 'title': return file.title || '';
|
||
case 'sha256': return file.sha256 || '';
|
||
default: return '';
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Get MIME type from file extension (no leading dot).
|
||
*/
|
||
const MIME_TYPES = {
|
||
pdf: 'application/pdf',
|
||
jpg: 'image/jpeg',
|
||
jpeg: 'image/jpeg',
|
||
png: 'image/png',
|
||
gif: 'image/gif',
|
||
svg: 'image/svg+xml',
|
||
webp: 'image/webp',
|
||
bmp: 'image/bmp',
|
||
ico: 'image/x-icon',
|
||
txt: 'text/plain',
|
||
md: 'text/markdown',
|
||
json: 'application/json',
|
||
xml: 'application/xml',
|
||
csv: 'text/csv',
|
||
html: 'text/html',
|
||
css: 'text/css',
|
||
js: 'text/javascript',
|
||
};
|
||
|
||
function getMimeType(extension) {
|
||
return MIME_TYPES[(extension || '').toLowerCase()] || 'application/octet-stream';
|
||
}
|
||
|
||
window.app.modules.utils = {
|
||
computeNewFilename,
|
||
getColumnValue,
|
||
getMimeType,
|
||
};
|
||
})();
|
||
|
||
(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 };
|
||
})();
|
||
|
||
/**
|
||
* Store Module
|
||
* Single source of truth for all application state
|
||
* Manages files, folders, sorting, filtering
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
// State
|
||
const state = {
|
||
// Directory structure
|
||
rootHandle: null,
|
||
folderTree: [],
|
||
selectedFolders: new Set(),
|
||
|
||
// Files
|
||
allFiles: [], // All files from selected folders
|
||
displayFiles: [], // After sorting and filtering
|
||
|
||
// Sort state
|
||
sortColumns: [], // [{column: 'original', direction: 'asc'}]
|
||
|
||
// Filter state
|
||
filters: {}, // {columnName: AST (from zddc.filter.parse)}
|
||
|
||
// UI state
|
||
hideCompliant: false,
|
||
calculateSha256: false
|
||
};
|
||
|
||
// Listeners for state changes
|
||
const listeners = {
|
||
'files': [],
|
||
'folders': [],
|
||
'sort': [],
|
||
'filter': []
|
||
};
|
||
|
||
/**
|
||
* Set sort columns
|
||
*/
|
||
function on(event, callback) {
|
||
if (listeners[event]) {
|
||
listeners[event].push(callback);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Notify listeners of state change
|
||
*/
|
||
function notify(event) {
|
||
if (listeners[event]) {
|
||
listeners[event].forEach(cb => cb());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Set root directory handle
|
||
*/
|
||
function setRootHandle(handle) {
|
||
state.rootHandle = handle;
|
||
}
|
||
|
||
/**
|
||
* Set folder tree
|
||
*/
|
||
function setFolderTree(tree) {
|
||
state.folderTree = tree;
|
||
notify('folders');
|
||
}
|
||
|
||
/**
|
||
* Select/deselect folder
|
||
*/
|
||
function toggleFolder(folderPath) {
|
||
if (state.selectedFolders.has(folderPath)) {
|
||
state.selectedFolders.delete(folderPath);
|
||
} else {
|
||
state.selectedFolders.add(folderPath);
|
||
}
|
||
loadFilesFromSelectedFolders();
|
||
}
|
||
|
||
/**
|
||
* Select multiple folders
|
||
*/
|
||
function setSelectedFolders(folderPaths) {
|
||
state.selectedFolders.clear();
|
||
folderPaths.forEach(path => state.selectedFolders.add(path));
|
||
loadFilesFromSelectedFolders();
|
||
}
|
||
|
||
/**
|
||
* Load files from selected folders
|
||
*/
|
||
function loadFilesFromSelectedFolders() {
|
||
state.allFiles = [];
|
||
|
||
if (state.selectedFolders.size === 0) {
|
||
updateDisplayFiles();
|
||
return;
|
||
}
|
||
|
||
// Collect files from selected folders
|
||
for (const folderPath of state.selectedFolders) {
|
||
const folder = findFolderByPath(folderPath);
|
||
if (folder && folder.files) {
|
||
const files = folder.files.filter(f => !f.isDirectory);
|
||
state.allFiles.push(...files);
|
||
}
|
||
}
|
||
|
||
// Apply default sort if no sort set
|
||
if (state.sortColumns.length === 0) {
|
||
state.sortColumns = [{ column: 'original', direction: 'asc' }];
|
||
}
|
||
|
||
updateDisplayFiles();
|
||
}
|
||
|
||
/**
|
||
* Find folder by path in tree
|
||
*/
|
||
function findFolderByPath(path) {
|
||
function search(folders) {
|
||
for (const folder of folders) {
|
||
if (folder.path === path) return folder;
|
||
if (folder.children) {
|
||
const found = search(folder.children);
|
||
if (found) return found;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
return search(state.folderTree);
|
||
}
|
||
|
||
/**
|
||
* Update display files (apply sort, filter, hide compliant)
|
||
*/
|
||
function updateDisplayFiles() {
|
||
let files = [...state.allFiles];
|
||
|
||
// Apply filters
|
||
files = applyFilters(files);
|
||
|
||
// Apply hide compliant
|
||
if (state.hideCompliant) {
|
||
files = files.filter(file => {
|
||
const newFilename = computeNewFilename(file);
|
||
const validation = validateFilename(newFilename);
|
||
return !validation.isValid;
|
||
});
|
||
}
|
||
|
||
// Apply sort
|
||
files = applySort(files);
|
||
|
||
state.displayFiles = files;
|
||
notify('files');
|
||
}
|
||
|
||
/**
|
||
* Apply filters to files using zddc.filter ASTs
|
||
*/
|
||
function applyFilters(files) {
|
||
if (Object.keys(state.filters).length === 0) {
|
||
return files;
|
||
}
|
||
|
||
return files.filter(file => {
|
||
for (const [columnName, ast] of Object.entries(state.filters)) {
|
||
const value = getColumnValue(file, columnName);
|
||
if (!window.zddc.filter.matches(value, ast)) {
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Apply sort to files
|
||
*/
|
||
function applySort(files) {
|
||
if (state.sortColumns.length === 0) {
|
||
return files;
|
||
}
|
||
|
||
return files.sort((a, b) => {
|
||
for (const sort of state.sortColumns) {
|
||
const result = compareValues(a, b, sort.column, sort.direction);
|
||
if (result !== 0) return result;
|
||
}
|
||
return 0;
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Compare two values for sorting
|
||
*/
|
||
function compareValues(a, b, columnName, direction) {
|
||
let aVal = getColumnValue(a, columnName);
|
||
let bVal = getColumnValue(b, columnName);
|
||
|
||
const comparison = String(aVal).localeCompare(String(bVal), undefined, {
|
||
numeric: true,
|
||
sensitivity: 'base'
|
||
});
|
||
|
||
return direction === 'asc' ? comparison : -comparison;
|
||
}
|
||
|
||
/**
|
||
* Get column value from file (delegates to utils)
|
||
*/
|
||
function getColumnValue(file, columnName) {
|
||
return window.app.modules.utils.getColumnValue(file, columnName);
|
||
}
|
||
|
||
function computeNewFilename(file) {
|
||
return window.app.modules.utils.computeNewFilename(file);
|
||
}
|
||
|
||
/**
|
||
* Validate filename
|
||
*/
|
||
function validateFilename(filename) {
|
||
// Use existing validator module
|
||
if (window.app.modules.validator) {
|
||
return window.app.modules.validator.validateFilename(filename);
|
||
}
|
||
return { isValid: true, errors: [], warnings: [] };
|
||
}
|
||
|
||
/**
|
||
* Match filter text against value
|
||
*/
|
||
function matchesFilter(value, filterText) {
|
||
// Simple contains for now - can enhance later
|
||
return String(value).toLowerCase().includes(filterText.toLowerCase());
|
||
}
|
||
|
||
/**
|
||
* Set sort columns
|
||
*/
|
||
function setSortColumns(columns) {
|
||
state.sortColumns = columns;
|
||
updateDisplayFiles();
|
||
}
|
||
|
||
/**
|
||
* Toggle sort on column
|
||
*/
|
||
function toggleSort(columnName, multiSort) {
|
||
if (!multiSort) {
|
||
state.sortColumns = [];
|
||
}
|
||
|
||
const existingIndex = state.sortColumns.findIndex(s => s.column === columnName);
|
||
|
||
if (existingIndex >= 0) {
|
||
const current = state.sortColumns[existingIndex];
|
||
if (current.direction === 'asc') {
|
||
current.direction = 'desc';
|
||
} else {
|
||
state.sortColumns.splice(existingIndex, 1);
|
||
}
|
||
} else {
|
||
state.sortColumns.push({ column: columnName, direction: 'asc' });
|
||
}
|
||
|
||
updateDisplayFiles();
|
||
notify('sort');
|
||
}
|
||
|
||
/**
|
||
* Set filter for column. ast is the pre-parsed zddc.filter AST.
|
||
*/
|
||
function setFilter(columnName, filterText, ast) {
|
||
if (filterText && ast && ast.length > 0) {
|
||
state.filters[columnName] = ast;
|
||
} else {
|
||
delete state.filters[columnName];
|
||
}
|
||
updateDisplayFiles();
|
||
}
|
||
|
||
/**
|
||
* Replace all filters at once. filtersObj is {columnName: rawString}.
|
||
* Parses each value. Pass {} to clear all filters.
|
||
*/
|
||
function setAllFilters(filtersObj) {
|
||
state.filters = {};
|
||
for (const [columnName, raw] of Object.entries(filtersObj)) {
|
||
if (raw) {
|
||
const ast = window.zddc.filter.parse(raw);
|
||
if (ast && ast.length > 0) {
|
||
state.filters[columnName] = ast;
|
||
}
|
||
}
|
||
}
|
||
updateDisplayFiles();
|
||
}
|
||
|
||
/**
|
||
* Set hide compliant flag
|
||
*/
|
||
function setHideCompliant(hide) {
|
||
state.hideCompliant = hide;
|
||
updateDisplayFiles();
|
||
}
|
||
|
||
/**
|
||
* Update file data
|
||
*/
|
||
function updateFile(index, updates) {
|
||
const file = state.displayFiles[index];
|
||
if (!file) return;
|
||
|
||
// Apply updates
|
||
Object.assign(file, updates);
|
||
|
||
// Mark as dirty unless explicitly set to false
|
||
if (updates.isDirty !== false) {
|
||
file.isDirty = true;
|
||
}
|
||
|
||
// Notify listeners (will trigger re-render)
|
||
notify('files');
|
||
}
|
||
|
||
/**
|
||
* Update file field (for editing)
|
||
*/
|
||
function updateFileField(index, fieldName, value) {
|
||
const file = state.displayFiles[index];
|
||
if (!file) return;
|
||
|
||
file[fieldName] = value;
|
||
file.autoPopulated = false; // Clear auto-populated flag
|
||
|
||
// Re-evaluate dirty: if every field still matches the parsed original,
|
||
// and there is no manual filename override, the file is clean again.
|
||
file.isDirty = _isFileDirty(file);
|
||
|
||
// Notify listeners
|
||
notify('files');
|
||
}
|
||
|
||
/**
|
||
* A file is dirty if its computed filename differs from the original,
|
||
* or if it has a manual filename override.
|
||
*/
|
||
function _isFileDirty(file) {
|
||
if (file.manualFilename) return true;
|
||
const computed = zddc.formatFilename({
|
||
trackingNumber: file.trackingNumber || '',
|
||
revision: file.revision || '',
|
||
status: file.status || '',
|
||
title: file.title || '',
|
||
extension: file.extension || '',
|
||
});
|
||
const original = zddc.joinExtension(file.originalFilename, file.extension);
|
||
// If formatFilename returns '' (missing fields) fall back to original — not dirty
|
||
return computed !== '' && computed !== original;
|
||
}
|
||
|
||
/**
|
||
* Get display files (what should be shown in table)
|
||
*/
|
||
function getDisplayFiles() {
|
||
return state.displayFiles;
|
||
}
|
||
|
||
/**
|
||
* Get all files (unfiltered)
|
||
*/
|
||
function getAllFiles() {
|
||
return state.allFiles;
|
||
}
|
||
|
||
/**
|
||
* Get sort columns
|
||
*/
|
||
function getSortColumns() {
|
||
return state.sortColumns;
|
||
}
|
||
|
||
/**
|
||
* Get selected folder count
|
||
*/
|
||
function getSelectedFolderCount() {
|
||
return state.selectedFolders.size;
|
||
}
|
||
|
||
/**
|
||
* Get state (read-only)
|
||
*/
|
||
function getState() {
|
||
return {
|
||
rootHandle: state.rootHandle,
|
||
folderTree: state.folderTree,
|
||
selectedFolders: Array.from(state.selectedFolders),
|
||
allFiles: state.allFiles,
|
||
displayFiles: state.displayFiles,
|
||
sortColumns: state.sortColumns,
|
||
filters: state.filters,
|
||
hideCompliant: state.hideCompliant
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Reset all state
|
||
*/
|
||
function reset() {
|
||
state.rootHandle = null;
|
||
state.folderTree = [];
|
||
state.selectedFolders.clear();
|
||
state.allFiles = [];
|
||
state.displayFiles = [];
|
||
state.sortColumns = [];
|
||
state.filters = {};
|
||
state.hideCompliant = false;
|
||
|
||
notify('files');
|
||
notify('folders');
|
||
}
|
||
|
||
// Export
|
||
window.app.modules.store = {
|
||
on,
|
||
notify,
|
||
setRootHandle,
|
||
setFolderTree,
|
||
toggleFolder,
|
||
setSelectedFolders,
|
||
toggleSort,
|
||
setFilter,
|
||
setAllFilters,
|
||
setHideCompliant,
|
||
updateFile,
|
||
updateFileField,
|
||
getDisplayFiles,
|
||
getAllFiles,
|
||
getSortColumns,
|
||
getSelectedFolderCount,
|
||
getState,
|
||
reset
|
||
};
|
||
})();
|
||
|
||
/**
|
||
* ZDDC Validation Module
|
||
* Validates file names against ZDDC conventions using the shared zddc library.
|
||
*/
|
||
|
||
(function() {
|
||
'use strict';
|
||
|
||
/**
|
||
* Validate a filename and return a detailed result.
|
||
* Delegates ZDDC pattern checking to the shared zddc.parseFilename() library.
|
||
*/
|
||
function validateFilename(filename) {
|
||
const errors = [];
|
||
const warnings = [];
|
||
|
||
if (!filename) {
|
||
errors.push('Filename is empty.');
|
||
return { isValid: false, warnings, errors };
|
||
}
|
||
|
||
const parsed = zddc.parseFilename(filename);
|
||
if (!parsed || !parsed.valid) {
|
||
errors.push('Filename does not match ZDDC format: trackingNumber_revision (status) - title.ext');
|
||
} else if (!zddc.isValidStatus(parsed.status)) {
|
||
errors.push('Invalid status code "' + parsed.status + '". Valid codes: ' + zddc.STATUSES.join(', '));
|
||
}
|
||
|
||
if (filename.length > 255) {
|
||
warnings.push('Filename is very long (>255 characters)');
|
||
}
|
||
|
||
const invalidChars = /[<>:"|?*]/;
|
||
if (invalidChars.test(filename)) {
|
||
errors.push('Filename contains invalid characters: < > : " | ? *');
|
||
}
|
||
|
||
return {
|
||
isValid: errors.length === 0,
|
||
warnings,
|
||
errors
|
||
};
|
||
}
|
||
|
||
window.app.modules.validator = {
|
||
validateFilename
|
||
};
|
||
})();
|
||
|
||
/**
|
||
* Directory Scanner Module
|
||
* Scans directories and collects files
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
// Store ZIP data for later access
|
||
const zipCache = new Map(); // path -> { zip: JSZip, fileHandle: FileSystemFileHandle }
|
||
|
||
/**
|
||
* Scan directory and build folder tree with files
|
||
*/
|
||
async function scanDirectory(dirHandle, preserveState = false) {
|
||
|
||
|
||
// Save current state if preserving
|
||
let savedExpanded = new Set();
|
||
let savedSelected = new Set();
|
||
if (preserveState) {
|
||
savedExpanded = getExpandedPaths(window.app.folderTree);
|
||
savedSelected = new Set(window.app.selectedFolders);
|
||
}
|
||
|
||
// Clear ZIP cache
|
||
zipCache.clear();
|
||
|
||
// Map to store files by folder handle (or ZIP path for virtual folders)
|
||
const foldersMap = new Map();
|
||
|
||
// Recursively scan
|
||
await scanFolder(dirHandle, foldersMap, dirHandle.name);
|
||
|
||
// Build tree structure
|
||
window.app.folderTree = window.app.modules.tree.buildTree(dirHandle, foldersMap);
|
||
|
||
// Set in store
|
||
window.app.modules.store.setFolderTree(window.app.folderTree);
|
||
|
||
if (preserveState) {
|
||
// Restore expanded state
|
||
restoreExpandedPaths(window.app.folderTree, savedExpanded);
|
||
// Restore selection
|
||
window.app.selectedFolders = savedSelected;
|
||
// Render without changing selection
|
||
window.app.modules.tree.render();
|
||
window.app.modules.store.setSelectedFolders(savedSelected);
|
||
} else {
|
||
// Render tree
|
||
window.app.modules.tree.render();
|
||
// Auto-expand and select all folders
|
||
window.app.modules.tree.expandAll();
|
||
window.app.modules.tree.selectAll();
|
||
}
|
||
|
||
|
||
}
|
||
|
||
/**
|
||
* Get all expanded folder paths from tree
|
||
*/
|
||
function getExpandedPaths(folders, paths = new Set()) {
|
||
for (const folder of folders) {
|
||
if (folder.expanded) {
|
||
paths.add(folder.path);
|
||
}
|
||
if (folder.children) {
|
||
getExpandedPaths(folder.children, paths);
|
||
}
|
||
}
|
||
return paths;
|
||
}
|
||
|
||
/**
|
||
* Restore expanded state to tree
|
||
*/
|
||
function restoreExpandedPaths(folders, expandedPaths) {
|
||
for (const folder of folders) {
|
||
folder.expanded = expandedPaths.has(folder.path);
|
||
if (folder.children) {
|
||
restoreExpandedPaths(folder.children, expandedPaths);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Recursively scan a folder
|
||
*/
|
||
async function scanFolder(dirHandle, foldersMap, currentPath) {
|
||
const items = [];
|
||
|
||
try {
|
||
for await (const entry of dirHandle.values()) {
|
||
if (entry.kind === 'file') {
|
||
// Create file object
|
||
const file = await createFileObject(entry, dirHandle);
|
||
if (file) {
|
||
items.push(file);
|
||
|
||
// Check if it's a ZIP file - scan its contents
|
||
if (file.extension === 'zip' && typeof JSZip !== 'undefined') {
|
||
await scanZipFile(file, foldersMap, currentPath, items);
|
||
}
|
||
}
|
||
} else if (entry.kind === 'directory') {
|
||
// Add directory reference
|
||
items.push({
|
||
handle: entry,
|
||
isDirectory: true
|
||
});
|
||
|
||
// Recursively scan subdirectory
|
||
const childPath = currentPath + '/' + entry.name;
|
||
await scanFolder(entry, foldersMap, childPath);
|
||
}
|
||
}
|
||
} catch (err) {
|
||
console.error('Error scanning folder:', dirHandle.name, err);
|
||
}
|
||
|
||
// Store files for this folder
|
||
foldersMap.set(dirHandle, items);
|
||
}
|
||
|
||
/**
|
||
* Scan a ZIP file and add its contents as virtual folders
|
||
*/
|
||
async function scanZipFile(zipFileObj, foldersMap, parentPath, parentItems) {
|
||
try {
|
||
const fileObj = await zipFileObj.handle.getFile();
|
||
const arrayBuffer = await fileObj.arrayBuffer();
|
||
const zip = await JSZip.loadAsync(arrayBuffer);
|
||
|
||
const zipPath = parentPath + '/' + zddc.joinExtension(zipFileObj.originalFilename, zipFileObj.extension);
|
||
|
||
// Cache the ZIP for later extraction
|
||
zipCache.set(zipPath, {
|
||
zip: zip,
|
||
fileHandle: zipFileObj.handle,
|
||
folderHandle: zipFileObj.folderHandle
|
||
});
|
||
|
||
// Mark the file as a ZIP container
|
||
zipFileObj.isZipContainer = true;
|
||
zipFileObj.zipPath = zipPath;
|
||
|
||
// Build virtual folder structure from ZIP contents
|
||
const virtualFolders = new Map(); // path -> { files: [], subdirs: Set }
|
||
virtualFolders.set(zipPath, { files: [], subdirs: new Set() });
|
||
|
||
zip.forEach((relativePath, zipEntry) => {
|
||
if (zipEntry.dir) {
|
||
// It's a directory
|
||
const dirPath = zipPath + '/' + relativePath.replace(/\/$/, '');
|
||
if (!virtualFolders.has(dirPath)) {
|
||
virtualFolders.set(dirPath, { files: [], subdirs: new Set() });
|
||
}
|
||
// Add to parent's subdirs
|
||
const parentDir = dirPath.substring(0, dirPath.lastIndexOf('/'));
|
||
if (virtualFolders.has(parentDir)) {
|
||
virtualFolders.get(parentDir).subdirs.add(dirPath);
|
||
}
|
||
} else {
|
||
// It's a file
|
||
const fileName = relativePath.split('/').pop();
|
||
const fileDir = relativePath.includes('/')
|
||
? zipPath + '/' + relativePath.substring(0, relativePath.lastIndexOf('/'))
|
||
: zipPath;
|
||
|
||
// Ensure parent directories exist
|
||
ensureVirtualPath(virtualFolders, zipPath, fileDir);
|
||
|
||
// Create virtual file object
|
||
const split = zddc.splitExtension(fileName);
|
||
|
||
const virtualFile = {
|
||
originalFilename: split.name,
|
||
extension: split.extension,
|
||
size: zipEntry._data ? zipEntry._data.uncompressedSize : 0,
|
||
lastModified: zipEntry.date ? zipEntry.date.getTime() : Date.now(),
|
||
|
||
// Virtual file markers
|
||
isVirtual: true,
|
||
zipPath: zipPath,
|
||
zipEntryPath: relativePath,
|
||
|
||
// Editable fields
|
||
trackingNumber: '',
|
||
revision: '',
|
||
status: '',
|
||
title: '',
|
||
|
||
// State
|
||
isDirty: false,
|
||
error: false,
|
||
errorMessage: '',
|
||
validation: null,
|
||
sha256: null
|
||
};
|
||
|
||
virtualFolders.get(fileDir).files.push(virtualFile);
|
||
}
|
||
});
|
||
|
||
// Convert virtual folders to format compatible with tree builder
|
||
// Create a virtual handle for the ZIP root
|
||
const zipVirtualHandle = {
|
||
name: zddc.joinExtension(zipFileObj.originalFilename, zipFileObj.extension),
|
||
kind: 'directory',
|
||
isZipRoot: true,
|
||
zipPath: zipPath
|
||
};
|
||
|
||
// Store virtual folder data
|
||
buildVirtualFolderMap(virtualFolders, zipPath, foldersMap, zipVirtualHandle);
|
||
|
||
// Add ZIP as a virtual directory in parent
|
||
parentItems.push({
|
||
handle: zipVirtualHandle,
|
||
isDirectory: true,
|
||
isZipRoot: true
|
||
});
|
||
|
||
} catch (err) {
|
||
console.error('Error scanning ZIP file:', zipFileObj.originalFilename, err);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Ensure all parent directories exist in virtual folder map
|
||
*/
|
||
function ensureVirtualPath(virtualFolders, zipPath, targetPath) {
|
||
if (virtualFolders.has(targetPath)) return;
|
||
|
||
const parts = targetPath.substring(zipPath.length + 1).split('/').filter(p => p);
|
||
let currentPath = zipPath;
|
||
|
||
for (const part of parts) {
|
||
const parentPath = currentPath;
|
||
currentPath = currentPath + '/' + part;
|
||
|
||
if (!virtualFolders.has(currentPath)) {
|
||
virtualFolders.set(currentPath, { files: [], subdirs: new Set() });
|
||
}
|
||
|
||
if (virtualFolders.has(parentPath)) {
|
||
virtualFolders.get(parentPath).subdirs.add(currentPath);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Build virtual folder entries for the foldersMap
|
||
* Uses path strings as keys for virtual folders to avoid object reference issues
|
||
*/
|
||
function buildVirtualFolderMap(virtualFolders, zipPath, foldersMap, zipVirtualHandle) {
|
||
const rootData = virtualFolders.get(zipPath);
|
||
if (!rootData) return;
|
||
|
||
// Create items array for ZIP root
|
||
const rootItems = [...rootData.files];
|
||
|
||
// Add subdirectories
|
||
for (const subdirPath of rootData.subdirs) {
|
||
const subdirName = subdirPath.split('/').pop();
|
||
const subdirHandle = {
|
||
name: subdirName,
|
||
kind: 'directory',
|
||
isVirtualDir: true,
|
||
virtualPath: subdirPath,
|
||
zipPath: zipPath
|
||
};
|
||
rootItems.push({
|
||
handle: subdirHandle,
|
||
isDirectory: true,
|
||
isVirtualDir: true
|
||
});
|
||
|
||
// Recursively add subdir contents
|
||
buildVirtualSubfolder(virtualFolders, subdirPath, foldersMap, zipPath);
|
||
}
|
||
|
||
// Store with both the handle object AND the path string as keys
|
||
// This ensures lookup works regardless of which reference is used
|
||
foldersMap.set(zipVirtualHandle, rootItems);
|
||
foldersMap.set(zipPath, rootItems); // Path-based key for tree building
|
||
}
|
||
|
||
/**
|
||
* Recursively build virtual subfolder entries
|
||
*/
|
||
function buildVirtualSubfolder(virtualFolders, folderPath, foldersMap, zipPath) {
|
||
const folderData = virtualFolders.get(folderPath);
|
||
if (!folderData) return;
|
||
|
||
const folderName = folderPath.split('/').pop();
|
||
const folderHandle = {
|
||
name: folderName,
|
||
kind: 'directory',
|
||
isVirtualDir: true,
|
||
virtualPath: folderPath,
|
||
zipPath: zipPath
|
||
};
|
||
|
||
const items = [...folderData.files];
|
||
|
||
// Store with path string key for tree building lookup
|
||
foldersMap.set(folderPath, items);
|
||
|
||
// Add subdirectories
|
||
for (const subdirPath of folderData.subdirs) {
|
||
const subdirName = subdirPath.split('/').pop();
|
||
const subdirHandle = {
|
||
name: subdirName,
|
||
kind: 'directory',
|
||
isVirtualDir: true,
|
||
virtualPath: subdirPath,
|
||
zipPath: zipPath
|
||
};
|
||
items.push({
|
||
handle: subdirHandle,
|
||
isDirectory: true,
|
||
isVirtualDir: true
|
||
});
|
||
|
||
// Recursively add subdir contents
|
||
buildVirtualSubfolder(virtualFolders, subdirPath, foldersMap, zipPath);
|
||
}
|
||
|
||
foldersMap.set(folderHandle, items);
|
||
}
|
||
|
||
/**
|
||
* Get cached ZIP data
|
||
*/
|
||
function getZipCache(zipPath) {
|
||
return zipCache.get(zipPath);
|
||
}
|
||
|
||
/**
|
||
* Extract a ZIP file to its parent directory
|
||
*/
|
||
async function extractZip(zipPath) {
|
||
const cached = zipCache.get(zipPath);
|
||
if (!cached) {
|
||
throw new Error('ZIP not found in cache');
|
||
}
|
||
|
||
const { zip, folderHandle } = cached;
|
||
|
||
// Get the ZIP filename without extension for the extract folder name
|
||
const zipName = zipPath.split('/').pop();
|
||
const extractFolderName = zipName.replace(/\.zip$/i, '');
|
||
|
||
// Create extraction folder
|
||
const extractFolder = await folderHandle.getDirectoryHandle(extractFolderName, { create: true });
|
||
|
||
// Extract all files
|
||
const entries = [];
|
||
zip.forEach((relativePath, zipEntry) => {
|
||
if (!zipEntry.dir) {
|
||
entries.push({ path: relativePath, entry: zipEntry });
|
||
}
|
||
});
|
||
|
||
for (const { path, entry } of entries) {
|
||
try {
|
||
// Create subdirectories if needed
|
||
const parts = path.split('/');
|
||
const fileName = parts.pop();
|
||
|
||
let currentDir = extractFolder;
|
||
for (const part of parts) {
|
||
if (part) {
|
||
currentDir = await currentDir.getDirectoryHandle(part, { create: true });
|
||
}
|
||
}
|
||
|
||
// Write file
|
||
const content = await entry.async('arraybuffer');
|
||
const fileHandle = await currentDir.getFileHandle(fileName, { create: true });
|
||
const writable = await fileHandle.createWritable();
|
||
await writable.write(content);
|
||
await writable.close();
|
||
} catch (err) {
|
||
console.error('Error extracting file:', path, err);
|
||
}
|
||
}
|
||
|
||
return extractFolderName;
|
||
}
|
||
|
||
/**
|
||
* Create file object with metadata
|
||
*/
|
||
async function createFileObject(fileHandle, folderHandle) {
|
||
try {
|
||
const file = await fileHandle.getFile();
|
||
const split = zddc.splitExtension(file.name);
|
||
|
||
return {
|
||
handle: fileHandle,
|
||
folderHandle: folderHandle,
|
||
originalFilename: split.name,
|
||
extension: split.extension,
|
||
size: file.size,
|
||
lastModified: file.lastModified,
|
||
|
||
// Editable fields
|
||
trackingNumber: '',
|
||
revision: '',
|
||
status: '',
|
||
title: '',
|
||
|
||
// State
|
||
isDirty: false,
|
||
error: false,
|
||
errorMessage: '',
|
||
validation: null,
|
||
sha256: null
|
||
// folderPath will be added later in buildTree
|
||
};
|
||
} catch (err) {
|
||
console.error('Error reading file:', fileHandle.name, err);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// Export module
|
||
window.app.modules.scanner = {
|
||
scanDirectory,
|
||
getZipCache,
|
||
extractZip
|
||
};
|
||
})();
|
||
|
||
|
||
/**
|
||
* Folder Tree Module
|
||
* Handles folder tree rendering and multi-select
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
/**
|
||
* Render the folder tree
|
||
*/
|
||
function render() {
|
||
const container = window.app.dom.folderTree;
|
||
container.innerHTML = '';
|
||
|
||
if (window.app.folderTree.length === 0) {
|
||
container.innerHTML = '<div class="empty-state">No folders found</div>';
|
||
return;
|
||
}
|
||
|
||
window.app.folderTree.forEach(folder => {
|
||
const element = createFolderElement(folder);
|
||
container.appendChild(element);
|
||
});
|
||
|
||
updateSelectedCount();
|
||
}
|
||
|
||
/**
|
||
* Create a folder element
|
||
*/
|
||
function createFolderElement(folder, level = 0) {
|
||
const div = document.createElement('div');
|
||
|
||
const item = document.createElement('div');
|
||
item.className = 'folder-item';
|
||
item.dataset.path = folder.path;
|
||
item.style.paddingLeft = `${level * 1.5}rem`;
|
||
|
||
// Check if selected
|
||
if (window.app.selectedFolders.has(folder.path)) {
|
||
item.classList.add('selected');
|
||
}
|
||
|
||
// Toggle button (if has children)
|
||
const toggle = document.createElement('span');
|
||
toggle.className = 'folder-toggle';
|
||
if (folder.children && folder.children.length > 0) {
|
||
toggle.textContent = folder.expanded ? '▼' : '▶';
|
||
toggle.addEventListener('click', (e) => {
|
||
e.stopPropagation();
|
||
const recursive = e.ctrlKey || e.metaKey;
|
||
toggleFolder(folder, recursive);
|
||
});
|
||
} else {
|
||
toggle.textContent = ' ';
|
||
}
|
||
item.appendChild(toggle);
|
||
|
||
// Folder icon (different for ZIP files)
|
||
const icon = document.createElement('span');
|
||
icon.className = 'folder-icon';
|
||
if (folder.isZipRoot) {
|
||
icon.innerHTML = '📦'; // 📦
|
||
} else if (folder.isVirtualDir) {
|
||
icon.innerHTML = '📂'; // 📂
|
||
} else {
|
||
icon.innerHTML = '📁'; // 📁
|
||
}
|
||
item.appendChild(icon);
|
||
|
||
// Folder name
|
||
const name = document.createElement('span');
|
||
name.className = 'folder-name';
|
||
name.textContent = folder.name;
|
||
item.appendChild(name);
|
||
|
||
// File count
|
||
const count = document.createElement('span');
|
||
count.className = 'folder-count';
|
||
count.textContent = `(${folder.fileCount || 0})`;
|
||
item.appendChild(count);
|
||
|
||
// Extract button for ZIP roots
|
||
if (folder.isZipRoot) {
|
||
const extractBtn = document.createElement('button');
|
||
extractBtn.className = 'btn btn-sm zip-extract-btn';
|
||
extractBtn.textContent = '📤 Extract';
|
||
extractBtn.title = 'Extract ZIP contents to folder';
|
||
extractBtn.addEventListener('click', async (e) => {
|
||
e.stopPropagation();
|
||
await handleExtractZip(folder);
|
||
});
|
||
item.appendChild(extractBtn);
|
||
}
|
||
|
||
// Extract All button for folders with ZIP descendants (but not ZIP roots themselves)
|
||
if (!folder.isZipRoot && !folder.isVirtualDir) {
|
||
const zipCount = countZipDescendants(folder);
|
||
if (zipCount > 0) {
|
||
const extractAllBtn = document.createElement('button');
|
||
extractAllBtn.className = 'btn btn-sm zip-extract-all-btn';
|
||
extractAllBtn.textContent = `📤 Extract All (${zipCount})`;
|
||
extractAllBtn.title = `Extract all ${zipCount} ZIP file(s) in this folder`;
|
||
extractAllBtn.addEventListener('click', async (e) => {
|
||
e.stopPropagation();
|
||
await handleExtractAllZips(folder);
|
||
});
|
||
item.appendChild(extractAllBtn);
|
||
}
|
||
}
|
||
|
||
// Click handler for selection
|
||
item.addEventListener('click', (e) => {
|
||
handleFolderClick(folder, e);
|
||
});
|
||
|
||
div.appendChild(item);
|
||
|
||
// Children (if expanded)
|
||
if (folder.expanded && folder.children && folder.children.length > 0) {
|
||
const childrenDiv = document.createElement('div');
|
||
childrenDiv.className = 'folder-children';
|
||
folder.children.forEach(child => {
|
||
const childElement = createFolderElement(child, level + 1);
|
||
childrenDiv.appendChild(childElement);
|
||
});
|
||
div.appendChild(childrenDiv);
|
||
}
|
||
|
||
return div;
|
||
}
|
||
|
||
/**
|
||
* Handle folder click with multi-select support
|
||
*/
|
||
function handleFolderClick(folder, event) {
|
||
if (event.ctrlKey || event.metaKey) {
|
||
// Ctrl+Click: Toggle selection
|
||
if (window.app.selectedFolders.has(folder.path)) {
|
||
window.app.selectedFolders.delete(folder.path);
|
||
} else {
|
||
window.app.selectedFolders.add(folder.path);
|
||
}
|
||
} else if (event.shiftKey) {
|
||
// Shift+Click: Range selection
|
||
const visibleFolders = getVisibleFolders();
|
||
const currentIndex = visibleFolders.findIndex(f => f.path === folder.path);
|
||
|
||
if (currentIndex >= 0 && window.app.lastSelectedFolderPath) {
|
||
const lastIndex = visibleFolders.findIndex(f => f.path === window.app.lastSelectedFolderPath);
|
||
|
||
if (lastIndex >= 0) {
|
||
const start = Math.min(currentIndex, lastIndex);
|
||
const end = Math.max(currentIndex, lastIndex);
|
||
|
||
// Select range
|
||
for (let i = start; i <= end; i++) {
|
||
window.app.selectedFolders.add(visibleFolders[i].path);
|
||
}
|
||
}
|
||
} else {
|
||
window.app.selectedFolders.add(folder.path);
|
||
}
|
||
} else {
|
||
// Normal click: Single selection
|
||
window.app.selectedFolders.clear();
|
||
window.app.selectedFolders.add(folder.path);
|
||
}
|
||
|
||
// Remember last selected for shift-click
|
||
window.app.lastSelectedFolderPath = folder.path;
|
||
|
||
// Re-render tree
|
||
render();
|
||
|
||
// Load files from selected folders
|
||
loadFilesFromSelectedFolders();
|
||
}
|
||
|
||
/**
|
||
* Handle ZIP extraction
|
||
*/
|
||
async function handleExtractZip(folder) {
|
||
if (!folder.isZipRoot || !folder.zipPath) return;
|
||
|
||
try {
|
||
const confirmed = confirm(`Extract "${folder.name}" to a new folder?\n\nThis will create a folder named "${folder.name.replace(/\.zip$/i, '')}" with the ZIP contents.`);
|
||
if (!confirmed) return;
|
||
|
||
// Show extracting state
|
||
const btn = document.querySelector(`.folder-item[data-path="${folder.path}"] .zip-extract-btn`);
|
||
if (btn) {
|
||
btn.textContent = '⏳ Extracting...';
|
||
btn.disabled = true;
|
||
}
|
||
|
||
await window.app.modules.scanner.extractZip(folder.zipPath);
|
||
|
||
// Auto-refresh preserving tree state
|
||
await window.app.modules.scanner.scanDirectory(window.app.rootHandle, true);
|
||
} catch (err) {
|
||
console.error('Error extracting ZIP:', err);
|
||
alert('Error extracting ZIP: ' + err.message);
|
||
|
||
// Reset button
|
||
const btn = document.querySelector(`.folder-item[data-path="${folder.path}"] .zip-extract-btn`);
|
||
if (btn) {
|
||
btn.textContent = '📤 Extract';
|
||
btn.disabled = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Count ZIP descendants in a folder
|
||
*/
|
||
function countZipDescendants(folder) {
|
||
let count = 0;
|
||
if (folder.children) {
|
||
for (const child of folder.children) {
|
||
if (child.isZipRoot) {
|
||
count++;
|
||
}
|
||
count += countZipDescendants(child);
|
||
}
|
||
}
|
||
return count;
|
||
}
|
||
|
||
/**
|
||
* Get all ZIP folders as flat list
|
||
*/
|
||
function getZipDescendants(folder, zips = []) {
|
||
if (folder.children) {
|
||
for (const child of folder.children) {
|
||
if (child.isZipRoot) {
|
||
zips.push(child);
|
||
}
|
||
getZipDescendants(child, zips);
|
||
}
|
||
}
|
||
return zips;
|
||
}
|
||
|
||
/**
|
||
* Handle extracting all ZIPs in a folder
|
||
*/
|
||
async function handleExtractAllZips(folder) {
|
||
const zips = getZipDescendants(folder);
|
||
if (zips.length === 0) return;
|
||
|
||
const confirmed = confirm(`Extract ${zips.length} ZIP file(s)?\n\nThis will create folders for each ZIP with their contents.`);
|
||
if (!confirmed) return;
|
||
|
||
try {
|
||
// Show extracting state on button
|
||
const btn = document.querySelector(`.folder-item[data-path="${folder.path}"] .zip-extract-all-btn`);
|
||
if (btn) {
|
||
btn.textContent = '⏳ Extracting...';
|
||
btn.disabled = true;
|
||
}
|
||
|
||
// Extract all ZIPs
|
||
for (const zip of zips) {
|
||
if (zip.zipPath) {
|
||
await window.app.modules.scanner.extractZip(zip.zipPath);
|
||
}
|
||
}
|
||
|
||
// Auto-refresh preserving tree state
|
||
await window.app.modules.scanner.scanDirectory(window.app.rootHandle, true);
|
||
} catch (err) {
|
||
console.error('Error extracting ZIPs:', err);
|
||
alert('Error extracting ZIPs: ' + err.message);
|
||
|
||
// Reset button
|
||
const btn = document.querySelector(`.folder-item[data-path="${folder.path}"] .zip-extract-all-btn`);
|
||
if (btn) {
|
||
btn.textContent = `📤 Extract All (${zips.length})`;
|
||
btn.disabled = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Toggle folder expansion
|
||
*/
|
||
function toggleFolder(folder, recursive = false) {
|
||
folder.expanded = !folder.expanded;
|
||
|
||
if (recursive && folder.children) {
|
||
// Recursively expand/collapse all children
|
||
const newState = folder.expanded;
|
||
function setAllExpanded(f) {
|
||
f.expanded = newState;
|
||
if (f.children) {
|
||
f.children.forEach(setAllExpanded);
|
||
}
|
||
}
|
||
folder.children.forEach(setAllExpanded);
|
||
}
|
||
|
||
render();
|
||
}
|
||
|
||
/**
|
||
* Load files from all selected folders
|
||
*/
|
||
async function loadFilesFromSelectedFolders() {
|
||
// Use store to manage files
|
||
window.app.modules.store.setSelectedFolders(Array.from(window.app.selectedFolders));
|
||
}
|
||
|
||
/**
|
||
* Find folder by path in tree
|
||
*/
|
||
function findFolderByPath(path) {
|
||
function search(folders) {
|
||
for (const folder of folders) {
|
||
if (folder.path === path) {
|
||
return folder;
|
||
}
|
||
if (folder.children) {
|
||
const found = search(folder.children);
|
||
if (found) return found;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
return search(window.app.folderTree);
|
||
}
|
||
|
||
/**
|
||
* Update selected folders count
|
||
*/
|
||
function updateSelectedCount() {
|
||
const count = window.app.selectedFolders.size;
|
||
window.app.dom.selectedFoldersCount.textContent =
|
||
`${count} folder${count !== 1 ? 's' : ''} selected`;
|
||
}
|
||
|
||
/**
|
||
* Build folder tree from scanned data
|
||
*/
|
||
function buildTree(rootHandle, foldersMap) {
|
||
const tree = [];
|
||
|
||
// Convert flat map to tree structure
|
||
function buildNode(handle, path) {
|
||
// For virtual folders, look up by path string; for real folders, use handle
|
||
let files;
|
||
if (handle.isVirtualDir || handle.isZipRoot) {
|
||
files = foldersMap.get(handle.virtualPath || handle.zipPath) || [];
|
||
} else {
|
||
files = foldersMap.get(handle) || [];
|
||
}
|
||
|
||
// Add folderPath to each file for folder highlighting (filter out null files)
|
||
files.filter(file => file !== null).forEach(file => {
|
||
if (!file.isDirectory) {
|
||
file.folderPath = path;
|
||
}
|
||
});
|
||
|
||
// Filter out null files for the node
|
||
const validFiles = files.filter(f => f !== null);
|
||
|
||
const node = {
|
||
name: handle.name,
|
||
path: path,
|
||
handle: handle,
|
||
files: validFiles,
|
||
fileCount: validFiles.length,
|
||
children: [],
|
||
expanded: false
|
||
};
|
||
|
||
// Mark ZIP-related nodes
|
||
if (handle.isZipRoot) {
|
||
node.isZipRoot = true;
|
||
node.zipPath = handle.zipPath;
|
||
}
|
||
if (handle.isVirtualDir) {
|
||
node.isVirtualDir = true;
|
||
node.zipPath = handle.zipPath;
|
||
}
|
||
|
||
return node;
|
||
}
|
||
|
||
// Recursively build tree
|
||
function addChildren(node) {
|
||
// Get subdirectories (filter out null files first)
|
||
// For virtual folders, look up by path string
|
||
let files;
|
||
if (node.handle.isVirtualDir || node.handle.isZipRoot) {
|
||
files = foldersMap.get(node.handle.virtualPath || node.handle.zipPath) || [];
|
||
} else {
|
||
files = foldersMap.get(node.handle) || [];
|
||
}
|
||
const validFiles = files.filter(f => f !== null);
|
||
const subdirs = validFiles.filter(f => f.isDirectory);
|
||
|
||
subdirs.forEach(subdir => {
|
||
const childPath = node.path + '/' + subdir.handle.name;
|
||
const childNode = buildNode(subdir.handle, childPath);
|
||
addChildren(childNode);
|
||
node.children.push(childNode);
|
||
});
|
||
|
||
// Update file count to exclude directories and null files
|
||
node.files = validFiles.filter(f => !f.isDirectory);
|
||
node.fileCount = node.files.length;
|
||
}
|
||
|
||
// Build root
|
||
const root = buildNode(rootHandle, rootHandle.name);
|
||
addChildren(root);
|
||
|
||
// Expand root by default
|
||
root.expanded = true;
|
||
|
||
tree.push(root);
|
||
return tree;
|
||
}
|
||
|
||
/**
|
||
* Get all currently visible folders (expanded tree)
|
||
*/
|
||
function getVisibleFolders() {
|
||
const visible = [];
|
||
|
||
function traverse(folders) {
|
||
for (const folder of folders) {
|
||
visible.push(folder);
|
||
if (folder.expanded && folder.children) {
|
||
traverse(folder.children);
|
||
}
|
||
}
|
||
}
|
||
|
||
traverse(window.app.folderTree);
|
||
return visible;
|
||
}
|
||
|
||
/**
|
||
* Select all visible folders
|
||
*/
|
||
function selectAllVisible() {
|
||
const visible = getVisibleFolders();
|
||
window.app.selectedFolders.clear();
|
||
visible.forEach(f => window.app.selectedFolders.add(f.path));
|
||
render();
|
||
loadFilesFromSelectedFolders();
|
||
}
|
||
|
||
/**
|
||
* Expand all folders in tree
|
||
*/
|
||
function expandAll() {
|
||
function setAllExpanded(folder) {
|
||
folder.expanded = true;
|
||
if (folder.children) {
|
||
folder.children.forEach(setAllExpanded);
|
||
}
|
||
}
|
||
|
||
window.app.folderTree.forEach(setAllExpanded);
|
||
render();
|
||
}
|
||
|
||
/**
|
||
* Select all folders in tree
|
||
*/
|
||
function selectAll() {
|
||
function collectAllPaths(folders, paths = []) {
|
||
folders.forEach(folder => {
|
||
paths.push(folder.path);
|
||
if (folder.children) {
|
||
collectAllPaths(folder.children, paths);
|
||
}
|
||
});
|
||
return paths;
|
||
}
|
||
|
||
const allPaths = collectAllPaths(window.app.folderTree);
|
||
allPaths.forEach(path => window.app.selectedFolders.add(path));
|
||
|
||
render();
|
||
loadFilesFromSelectedFolders();
|
||
}
|
||
|
||
/**
|
||
* Set up keyboard shortcuts for folder tree
|
||
*/
|
||
function setupKeyboardShortcuts() {
|
||
const container = window.app.dom.folderTree;
|
||
|
||
container.addEventListener('keydown', (e) => {
|
||
// Ctrl+A: Select all visible
|
||
if ((e.ctrlKey || e.metaKey) && e.key === 'a') {
|
||
e.preventDefault();
|
||
selectAllVisible();
|
||
}
|
||
});
|
||
|
||
// Make container focusable
|
||
container.tabIndex = 0;
|
||
}
|
||
|
||
// Export module
|
||
window.app.modules.tree = {
|
||
render,
|
||
buildTree,
|
||
loadFilesFromSelectedFolders,
|
||
setupKeyboardShortcuts,
|
||
expandAll,
|
||
selectAll
|
||
};
|
||
})();
|
||
|
||
/**
|
||
* Spreadsheet Module
|
||
* Handles table rendering, cell editing, and file operations
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
let editingCell = null;
|
||
let editingInput = null;
|
||
|
||
/**
|
||
* Render spreadsheet from store
|
||
*/
|
||
function render() {
|
||
const tbody = window.app.dom.spreadsheetBody;
|
||
tbody.innerHTML = '';
|
||
|
||
// Get files from store (already filtered and sorted)
|
||
const files = window.app.modules.store.getDisplayFiles();
|
||
|
||
// Render rows
|
||
if (files.length === 0) {
|
||
const message = window.app.modules.store.getDisplayFiles().length === 0
|
||
? '<h3>No files to display</h3><p>Select one or more folders from the tree to view files</p>'
|
||
: '<h3>No files match filters</h3><p>Adjust or clear filters to see files</p>';
|
||
|
||
tbody.innerHTML = `
|
||
<tr>
|
||
<td colspan="10" class="spreadsheet-empty">
|
||
${message}
|
||
</td>
|
||
</tr>
|
||
`;
|
||
return;
|
||
}
|
||
|
||
files.forEach((file, index) => {
|
||
const row = createRow(file, index);
|
||
tbody.appendChild(row);
|
||
});
|
||
|
||
// Update UI
|
||
window.app.modules.app.updateStats();
|
||
updateSortIndicators();
|
||
|
||
// Calculate SHA256 if enabled
|
||
if (window.app.calculateSha256) {
|
||
calculateSha256ForAll();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Update sort indicators
|
||
*/
|
||
function updateSortIndicators() {
|
||
const sortColumns = window.app.modules.store.getSortColumns();
|
||
const headers = window.app.dom.spreadsheet.querySelectorAll('thead th');
|
||
|
||
headers.forEach(th => {
|
||
const indicator = th.querySelector('.sort-indicator');
|
||
if (!indicator) return;
|
||
|
||
const columnName = th.className.replace('col-', '');
|
||
const sortIndex = sortColumns.findIndex(s => s.column === columnName);
|
||
|
||
if (sortIndex >= 0) {
|
||
const sort = sortColumns[sortIndex];
|
||
const arrow = sort.direction === 'asc' ? '▲' : '▼';
|
||
const priority = sortColumns.length > 1 ? (sortIndex + 1) : '';
|
||
indicator.textContent = ` ${arrow}${priority}`;
|
||
indicator.style.display = 'inline';
|
||
} else {
|
||
indicator.textContent = '';
|
||
indicator.style.display = 'none';
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Create a table row for a file
|
||
*/
|
||
function createRow(file, index) {
|
||
const row = document.createElement('tr');
|
||
row.dataset.index = index;
|
||
row.dataset.folderPath = file.folderPath; // Store folder path for highlighting
|
||
|
||
// Add state classes
|
||
if (file.isDirty) row.classList.add('modified');
|
||
if (file.error) row.classList.add('error');
|
||
|
||
// Highlight folder on hover
|
||
row.addEventListener('mouseenter', () => {
|
||
highlightFolder(file.folderPath);
|
||
});
|
||
|
||
row.addEventListener('mouseleave', () => {
|
||
clearFolderHighlight();
|
||
});
|
||
|
||
// Row number
|
||
row.appendChild(createCell('row-num', index + 1, false));
|
||
|
||
// Original filename — plain text (selectable/copyable, no link)
|
||
const originalCell = createCell('original', file.originalFilename, false);
|
||
row.appendChild(originalCell);
|
||
|
||
// Extension — hyperlink to open the file
|
||
const extCell = createCell('extension', '', false);
|
||
const extLink = document.createElement('a');
|
||
extLink.className = 'cell-link';
|
||
extLink.textContent = file.extension;
|
||
extLink.title = 'Click to open file';
|
||
extLink.style.cursor = 'pointer';
|
||
extLink.addEventListener('click', async (e) => {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
openFile(file);
|
||
});
|
||
extCell.appendChild(extLink);
|
||
row.appendChild(extCell);
|
||
|
||
// Parse original filename to get ZDDC components (always)
|
||
// Pass full filename (name + extension) so the regex can match the .ext suffix
|
||
const parsed = zddc.parseFilename(zddc.joinExtension(file.originalFilename, file.extension)) || {};
|
||
|
||
// Fill any empty fields from parsed filename (per-field, not all-or-nothing)
|
||
// Must happen before computeNewFilename so all fields are available
|
||
if (!file.trackingNumber) file.trackingNumber = parsed.trackingNumber || '';
|
||
if (!file.revision) file.revision = parsed.revision || '';
|
||
if (!file.status) file.status = parsed.status || '';
|
||
if (!file.title) file.title = parsed.title || '';
|
||
|
||
// New filename: show only if it would actually change the file
|
||
const computedFilename = file.manualFilename || computeNewFilename(file, index);
|
||
const originalFullName = zddc.joinExtension(file.originalFilename, file.extension);
|
||
const wouldChange = computedFilename !== originalFullName;
|
||
const newFilenameDisplay = wouldChange ? computedFilename : '';
|
||
const newFilenameCell = createEditableCell('newFilename', newFilenameDisplay, index);
|
||
// Use computedFilename (not newFilenameDisplay) for validation
|
||
const newFilename = computedFilename;
|
||
if (!file.manualFilename) {
|
||
newFilenameCell.classList.add('computed');
|
||
}
|
||
|
||
// Validate and show errors
|
||
const validation = window.app.modules.validator.validateFilename(newFilename);
|
||
if (!validation.isValid) {
|
||
newFilenameCell.classList.add('validation-error');
|
||
newFilenameCell.title = validation.errors.join('; ');
|
||
} else if (validation.warnings.length > 0) {
|
||
newFilenameCell.classList.add('validation-warning');
|
||
newFilenameCell.title = validation.warnings.join('; ');
|
||
}
|
||
|
||
// Only show action buttons if row is dirty
|
||
if (file.isDirty) {
|
||
const actions = document.createElement('span');
|
||
actions.className = 'inline-actions';
|
||
|
||
const saveBtn = document.createElement('button');
|
||
saveBtn.className = 'btn-inline btn-save';
|
||
saveBtn.textContent = '✓';
|
||
saveBtn.title = 'Save';
|
||
saveBtn.addEventListener('click', (e) => {
|
||
e.stopPropagation();
|
||
saveFile(index);
|
||
});
|
||
|
||
const cancelBtn = document.createElement('button');
|
||
cancelBtn.className = 'btn-inline btn-cancel';
|
||
cancelBtn.textContent = '✗';
|
||
cancelBtn.title = 'Clear all fields';
|
||
cancelBtn.addEventListener('click', (e) => {
|
||
e.stopPropagation();
|
||
cancelFile(index);
|
||
});
|
||
|
||
actions.appendChild(saveBtn);
|
||
actions.appendChild(cancelBtn);
|
||
newFilenameCell.appendChild(actions);
|
||
}
|
||
|
||
row.appendChild(newFilenameCell);
|
||
|
||
// For each field: use file value (already populated above) for display
|
||
const displayTracking = file.trackingNumber || '';
|
||
const displayRevision = file.revision || '';
|
||
const displayStatus = file.status || '';
|
||
const displayTitle = file.title || '';
|
||
|
||
const trackingCell = createEditableCell('trackingNumber', displayTracking, index);
|
||
const revisionCell = createEditableCell('revision', displayRevision, index);
|
||
const statusCell = createEditableCell('status', displayStatus, index);
|
||
const titleCell = createEditableCell('title', displayTitle, index);
|
||
|
||
// Gray = field value matches what the original filename parses to (no change)
|
||
// Blue = field value differs from the parsed original (would produce a different filename)
|
||
if (displayTracking === (parsed.trackingNumber || '')) trackingCell.classList.add('auto-populated');
|
||
else trackingCell.classList.add('field-changed');
|
||
if (displayRevision === (parsed.revision || '')) revisionCell.classList.add('auto-populated');
|
||
else revisionCell.classList.add('field-changed');
|
||
if (displayStatus === (parsed.status || '')) statusCell.classList.add('auto-populated');
|
||
else statusCell.classList.add('field-changed');
|
||
if (displayTitle === (parsed.title || '')) titleCell.classList.add('auto-populated');
|
||
else titleCell.classList.add('field-changed');
|
||
|
||
row.appendChild(trackingCell);
|
||
row.appendChild(revisionCell);
|
||
row.appendChild(statusCell);
|
||
row.appendChild(titleCell);
|
||
|
||
// SHA256 (if enabled)
|
||
if (window.app.calculateSha256) {
|
||
const sha256Cell = createCell('sha256', file.sha256 || 'calculating...', false);
|
||
if (!file.sha256) {
|
||
sha256Cell.classList.add('sha256-calculating');
|
||
}
|
||
row.appendChild(sha256Cell);
|
||
}
|
||
|
||
return row;
|
||
}
|
||
|
||
/**
|
||
* Create a table cell
|
||
*/
|
||
function createCell(className, content, editable = false) {
|
||
const td = document.createElement('td');
|
||
td.className = `col-${className}`;
|
||
td.textContent = content;
|
||
return td;
|
||
}
|
||
|
||
/**
|
||
* Create an editable cell
|
||
*/
|
||
function createEditableCell(columnName, value, rowIndex) {
|
||
const td = document.createElement('td');
|
||
td.className = `col-${columnName} cell-editable`;
|
||
td.textContent = value;
|
||
|
||
// Double-click to edit
|
||
td.addEventListener('dblclick', (e) => {
|
||
e.stopPropagation();
|
||
startEditing(td, columnName, rowIndex);
|
||
});
|
||
|
||
return td;
|
||
}
|
||
|
||
|
||
/**
|
||
* Start editing a cell
|
||
*/
|
||
function startEditing(cell, columnName, rowIndex) {
|
||
// Cancel any existing edit
|
||
if (editingCell) {
|
||
cancelEditing();
|
||
}
|
||
|
||
const files = window.app.modules.store.getDisplayFiles();
|
||
const file = files[rowIndex];
|
||
if (!file) return;
|
||
|
||
const currentValue = file[columnName] || '';
|
||
|
||
// Clear any cell selection
|
||
if (window.app.modules.selection) {
|
||
window.app.modules.selection.clearSelection();
|
||
}
|
||
|
||
// Store references
|
||
editingCell = { cell, columnName, rowIndex, originalValue: currentValue };
|
||
|
||
// Save original content and make cell contenteditable
|
||
cell.dataset.originalContent = cell.innerHTML;
|
||
cell.contentEditable = 'true';
|
||
cell.classList.add('editing');
|
||
editingInput = cell;
|
||
|
||
// Set content (text only for editing)
|
||
cell.textContent = currentValue;
|
||
|
||
// Focus and select all
|
||
cell.focus();
|
||
|
||
// Select all text — guard against cell being detached from document
|
||
// (can happen if a re-render fires between dblclick and this point)
|
||
if (document.contains(cell)) {
|
||
const range = document.createRange();
|
||
range.selectNodeContents(cell);
|
||
const selection = window.getSelection();
|
||
selection.removeAllRanges();
|
||
selection.addRange(range);
|
||
}
|
||
|
||
// Event listeners
|
||
cell.addEventListener('blur', handleBlur, { once: true });
|
||
cell.addEventListener('keydown', handleKeyDown);
|
||
}
|
||
|
||
/**
|
||
* Handle blur event
|
||
*/
|
||
function handleBlur() {
|
||
// Small delay to allow click events to fire first
|
||
setTimeout(() => finishEditing(), 100);
|
||
}
|
||
|
||
/**
|
||
* Handle keydown in contenteditable
|
||
*/
|
||
function handleKeyDown(e) {
|
||
if (e.key === 'Enter') {
|
||
// Enter exits edit mode
|
||
e.preventDefault();
|
||
finishEditing();
|
||
} else if (e.key === 'Escape') {
|
||
// Escape undoes and exits edit mode
|
||
e.preventDefault();
|
||
cancelEditing();
|
||
} else if (e.key === 'Tab') {
|
||
// Tab/Shift+Tab moves to next/prev cell
|
||
e.preventDefault();
|
||
const { rowIndex, columnName } = editingCell || {};
|
||
const shiftKey = e.shiftKey;
|
||
finishEditingQuiet(); // Don't trigger store update
|
||
if (rowIndex !== undefined) {
|
||
if (shiftKey) {
|
||
moveToPreviousCell(rowIndex, columnName);
|
||
} else {
|
||
moveToNextCell(rowIndex, columnName);
|
||
}
|
||
}
|
||
} else if (e.key === 'ArrowUp') {
|
||
e.preventDefault();
|
||
const { rowIndex, columnName } = editingCell || {};
|
||
finishEditingQuiet();
|
||
if (rowIndex !== undefined) {
|
||
moveUpRow(rowIndex, columnName);
|
||
}
|
||
} else if (e.key === 'ArrowDown') {
|
||
e.preventDefault();
|
||
const { rowIndex, columnName } = editingCell || {};
|
||
finishEditingQuiet();
|
||
if (rowIndex !== undefined) {
|
||
moveDownRow(rowIndex, columnName);
|
||
}
|
||
} else if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
|
||
// Allow normal cursor movement within cell
|
||
e.stopPropagation();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Finish editing and save value
|
||
*/
|
||
function finishEditing() {
|
||
if (!editingCell || !editingInput) return;
|
||
|
||
const { cell, columnName, rowIndex } = editingCell;
|
||
const newValue = editingInput.textContent.trim();
|
||
const files = window.app.modules.store.getDisplayFiles();
|
||
const file = files[rowIndex];
|
||
if (!file) return;
|
||
|
||
const oldValue = file[columnName] || '';
|
||
|
||
// Remove contenteditable
|
||
editingInput.contentEditable = 'false';
|
||
editingInput.classList.remove('editing');
|
||
editingInput.removeEventListener('keydown', handleKeyDown);
|
||
|
||
// Update file data if changed
|
||
if (newValue !== oldValue) {
|
||
// Special handling for newFilename column
|
||
if (columnName === 'newFilename') {
|
||
if (newValue) {
|
||
window.app.modules.store.updateFileField(rowIndex, 'manualFilename', newValue);
|
||
} else {
|
||
window.app.modules.store.updateFile(rowIndex, { manualFilename: null });
|
||
}
|
||
} else {
|
||
window.app.modules.store.updateFileField(rowIndex, columnName, newValue);
|
||
}
|
||
|
||
const updatedFile = window.app.modules.store.getDisplayFiles()[rowIndex];
|
||
validateFile(updatedFile);
|
||
}
|
||
|
||
editingCell = null;
|
||
editingInput = null;
|
||
}
|
||
|
||
/**
|
||
* Finish editing without triggering store update (for Tab/Arrow navigation)
|
||
*/
|
||
function finishEditingQuiet() {
|
||
if (!editingCell || !editingInput) return;
|
||
|
||
const { columnName, rowIndex } = editingCell;
|
||
const newValue = editingInput.textContent.trim();
|
||
const files = window.app.modules.store.getDisplayFiles();
|
||
const file = files[rowIndex];
|
||
|
||
// Remove contenteditable
|
||
editingInput.contentEditable = 'false';
|
||
editingInput.classList.remove('editing');
|
||
editingInput.removeEventListener('keydown', handleKeyDown);
|
||
|
||
// Update file object directly (no store notification)
|
||
if (file) {
|
||
if (columnName === 'newFilename') {
|
||
file.manualFilename = newValue || null;
|
||
} else {
|
||
file[columnName] = newValue;
|
||
}
|
||
file.isDirty = true;
|
||
}
|
||
|
||
editingCell = null;
|
||
editingInput = null;
|
||
}
|
||
|
||
/**
|
||
* Cancel editing without saving
|
||
*/
|
||
function cancelEditing() {
|
||
if (!editingCell) return;
|
||
|
||
const { rowIndex } = editingCell;
|
||
const files = window.app.modules.store.getDisplayFiles();
|
||
const file = files[rowIndex];
|
||
if (!file) return;
|
||
|
||
// Clear editing state
|
||
editingCell = null;
|
||
editingInput = null;
|
||
|
||
// Re-render the row
|
||
const row = window.app.dom.spreadsheetBody.querySelector(`tr[data-index="${rowIndex}"]`);
|
||
if (row) {
|
||
const newRow = createRow(file, rowIndex);
|
||
row.replaceWith(newRow);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Move to next editable cell
|
||
*/
|
||
function moveToNextCell(rowIndex, currentColumn) {
|
||
const columns = ['newFilename', 'trackingNumber', 'revision', 'status', 'title'];
|
||
const currentIndex = columns.indexOf(currentColumn);
|
||
|
||
if (currentIndex < columns.length - 1) {
|
||
// Next column in same row
|
||
const nextColumn = columns[currentIndex + 1];
|
||
const row = window.app.dom.spreadsheetBody.querySelector(`tr[data-index="${rowIndex}"]`);
|
||
const nextCell = row.querySelector(`.col-${nextColumn}`);
|
||
if (nextCell) {
|
||
startEditing(nextCell, nextColumn, rowIndex);
|
||
}
|
||
} else if (rowIndex < window.app.modules.store.getDisplayFiles().length - 1) {
|
||
// First column of next row
|
||
const nextColumn = columns[0];
|
||
const nextRow = window.app.dom.spreadsheetBody.querySelector(`tr[data-index="${rowIndex + 1}"]`);
|
||
const nextCell = nextRow.querySelector(`.col-${nextColumn}`);
|
||
if (nextCell) {
|
||
startEditing(nextCell, nextColumn, rowIndex + 1);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Move to previous editable cell
|
||
*/
|
||
function moveToPreviousCell(rowIndex, currentColumn) {
|
||
const columns = ['newFilename', 'trackingNumber', 'revision', 'status', 'title'];
|
||
const currentIndex = columns.indexOf(currentColumn);
|
||
|
||
if (currentIndex > 0) {
|
||
// Previous column in same row
|
||
const prevColumn = columns[currentIndex - 1];
|
||
const row = window.app.dom.spreadsheetBody.querySelector(`tr[data-index="${rowIndex}"]`);
|
||
const prevCell = row.querySelector(`.col-${prevColumn}`);
|
||
if (prevCell) {
|
||
startEditing(prevCell, prevColumn, rowIndex);
|
||
}
|
||
} else if (rowIndex > 0) {
|
||
// Last column of previous row
|
||
const prevColumn = columns[columns.length - 1];
|
||
const prevRow = window.app.dom.spreadsheetBody.querySelector(`tr[data-index="${rowIndex - 1}"]`);
|
||
const prevCell = prevRow.querySelector(`.col-${prevColumn}`);
|
||
if (prevCell) {
|
||
startEditing(prevCell, prevColumn, rowIndex - 1);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Move up one row, same column
|
||
*/
|
||
function moveUpRow(rowIndex, currentColumn) {
|
||
if (rowIndex > 0) {
|
||
const prevRow = window.app.dom.spreadsheetBody.querySelector(`tr[data-index="${rowIndex - 1}"]`);
|
||
const cell = prevRow.querySelector(`.col-${currentColumn}`);
|
||
if (cell) {
|
||
startEditing(cell, currentColumn, rowIndex - 1);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Move down one row, same column
|
||
*/
|
||
function moveDownRow(rowIndex, currentColumn) {
|
||
if (rowIndex < window.app.modules.store.getDisplayFiles().length - 1) {
|
||
const nextRow = window.app.dom.spreadsheetBody.querySelector(`tr[data-index="${rowIndex + 1}"]`);
|
||
const cell = nextRow.querySelector(`.col-${currentColumn}`);
|
||
if (cell) {
|
||
startEditing(cell, currentColumn, rowIndex + 1);
|
||
}
|
||
}
|
||
}
|
||
|
||
function computeNewFilename(file) {
|
||
return window.app.modules.utils.computeNewFilename(file);
|
||
}
|
||
|
||
/**
|
||
* Validate a file
|
||
*/
|
||
function validateFile(file) {
|
||
const newFilename = computeNewFilename(file, 0);
|
||
const validation = window.app.modules.validator.validateFilename(newFilename);
|
||
|
||
file.validation = validation;
|
||
file.error = !validation.isValid;
|
||
}
|
||
|
||
/**
|
||
* Open file in new tab
|
||
*/
|
||
async function openFile(file) {
|
||
try {
|
||
let blob;
|
||
if (file.isVirtual) {
|
||
// Virtual file from ZIP - get from cache
|
||
const cached = window.app.modules.scanner.getZipCache(file.zipPath);
|
||
if (!cached) throw new Error('ZIP not found in cache');
|
||
const zipEntry = cached.zip.file(file.zipEntryPath);
|
||
if (!zipEntry) throw new Error('File not found in ZIP');
|
||
const arrayBuffer = await zipEntry.async('arraybuffer');
|
||
const mimeType = getMimeType(file.extension);
|
||
blob = new Blob([arrayBuffer], { type: mimeType });
|
||
} else {
|
||
blob = await file.handle.getFile();
|
||
}
|
||
const url = URL.createObjectURL(blob);
|
||
window.open(url, '_blank');
|
||
|
||
// Clean up URL after a delay
|
||
setTimeout(() => URL.revokeObjectURL(url), 60000);
|
||
} catch (err) {
|
||
console.error('Error opening file:', err);
|
||
alert('Cannot open file: ' + err.message);
|
||
}
|
||
}
|
||
|
||
function getMimeType(extension) {
|
||
return window.app.modules.utils.getMimeType(extension);
|
||
}
|
||
|
||
/**
|
||
* Save a single file
|
||
*/
|
||
async function saveFile(index, skipValidation = false) {
|
||
const files = window.app.modules.store.getDisplayFiles();
|
||
const file = files[index];
|
||
if (!file.isDirty) {
|
||
|
||
return;
|
||
}
|
||
|
||
// Virtual files (from ZIPs) cannot be renamed - must extract first
|
||
if (file.isVirtual) {
|
||
alert('Cannot rename files inside ZIP archives.\nExtract the ZIP first to rename files.');
|
||
return;
|
||
}
|
||
|
||
const row = window.app.dom.spreadsheetBody.querySelector(`tr[data-index="${index}"]`);
|
||
if (!row) {
|
||
console.error(`Row not found for index ${index}`);
|
||
return;
|
||
}
|
||
row.classList.add('saving');
|
||
|
||
try {
|
||
const newFilename = computeNewFilename(file, index);
|
||
const currentFilename = zddc.joinExtension(file.originalFilename, file.extension);
|
||
|
||
// Check if already has correct name
|
||
if (currentFilename === newFilename) {
|
||
|
||
row.classList.remove('saving');
|
||
|
||
// Just clear dirty flag and fields
|
||
file.isDirty = false;
|
||
file.error = false;
|
||
delete file.manualFilename;
|
||
file.trackingNumber = '';
|
||
file.revision = '';
|
||
file.status = '';
|
||
file.title = '';
|
||
|
||
const newRow = createRow(file, index);
|
||
row.replaceWith(newRow);
|
||
window.app.modules.app.updateStats();
|
||
return;
|
||
}
|
||
|
||
// Validate filename
|
||
if (!skipValidation) {
|
||
const validation = window.app.modules.validator.validateFilename(newFilename);
|
||
if (!validation.isValid) {
|
||
const errors = validation.errors.join('\n');
|
||
const confirmed = confirm(
|
||
`⚠️ Warning: Filename is not ZDDC compliant!\n\n` +
|
||
`Errors:\n${errors}\n\n` +
|
||
`Current filename: ${newFilename}\n\n` +
|
||
`Do you want to save it anyway?`
|
||
);
|
||
|
||
if (!confirmed) {
|
||
row.classList.remove('saving');
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Request write permission for the folder
|
||
const folderPermission = await file.folderHandle.queryPermission({ mode: 'readwrite' });
|
||
if (folderPermission !== 'granted') {
|
||
const granted = await file.folderHandle.requestPermission({ mode: 'readwrite' });
|
||
if (granted !== 'granted') {
|
||
throw new Error('Write permission denied');
|
||
}
|
||
}
|
||
|
||
// Rename. HTTP-backed handles (zddc-server) get the atomic
|
||
// POST /op=move path — single round-trip, server-side
|
||
// os.Rename, no risk of half-renamed state. Local FS Access
|
||
// API handles use copy+remove because the API has no native
|
||
// rename verb.
|
||
const oldFilename = zddc.joinExtension(file.originalFilename, file.extension);
|
||
|
||
try {
|
||
if (window.zddc.source.isHttpHandle(file.folderHandle)) {
|
||
const folderUrl = file.folderHandle.url();
|
||
const folderPath = new URL(folderUrl).pathname;
|
||
const srcPath = folderPath + encodeURIComponent(oldFilename);
|
||
const dstPath = folderPath + encodeURIComponent(newFilename);
|
||
await window.zddc.source.moveFile(srcPath, dstPath);
|
||
file.handle = await file.folderHandle.getFileHandle(newFilename);
|
||
} else {
|
||
// Get fresh handle for old file
|
||
const oldHandle = await file.folderHandle.getFileHandle(oldFilename);
|
||
|
||
// Read the file content
|
||
const fileData = await oldHandle.getFile();
|
||
|
||
// Create new file with new name
|
||
const newHandle = await file.folderHandle.getFileHandle(newFilename, { create: true });
|
||
const writable = await newHandle.createWritable();
|
||
await writable.write(fileData);
|
||
await writable.close();
|
||
|
||
// Delete old file
|
||
await file.folderHandle.removeEntry(oldFilename);
|
||
|
||
// Update file handle
|
||
file.handle = newHandle;
|
||
}
|
||
} catch (err) {
|
||
console.error(`Failed to rename file:`, err);
|
||
throw err;
|
||
}
|
||
|
||
// Update file data directly (don't trigger store notification during batch save)
|
||
file.originalFilename = zddc.splitExtension(newFilename).name;
|
||
file.isDirty = false;
|
||
file.error = false;
|
||
file.manualFilename = null;
|
||
file.trackingNumber = '';
|
||
file.revision = '';
|
||
file.status = '';
|
||
file.title = '';
|
||
file.autoPopulated = false;
|
||
|
||
// Update row UI
|
||
row.classList.remove('saving');
|
||
const newRow = createRow(file, index);
|
||
row.replaceWith(newRow);
|
||
|
||
} catch (err) {
|
||
console.error('Error saving file:', err);
|
||
file.error = true;
|
||
file.errorMessage = err.message;
|
||
row.classList.remove('saving');
|
||
row.classList.add('error');
|
||
// Re-throw so caller can handle
|
||
throw err;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Cancel/Clear all fields for a single file
|
||
*/
|
||
function cancelFile(index) {
|
||
// Clear all fields through store
|
||
window.app.modules.store.updateFile(index, {
|
||
trackingNumber: '',
|
||
revision: '',
|
||
status: '',
|
||
title: '',
|
||
manualFilename: null,
|
||
isDirty: false,
|
||
error: false,
|
||
validation: null,
|
||
autoPopulated: false
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Save all modified files (only ZDDC-compliant ones)
|
||
*/
|
||
async function saveAllFiles() {
|
||
const files = window.app.modules.store.getDisplayFiles();
|
||
const modifiedFiles = files
|
||
.map((file, index) => ({ file, index }))
|
||
.filter(({ file }) => file.isDirty);
|
||
|
||
if (modifiedFiles.length === 0) {
|
||
alert('No modified files to save.');
|
||
return;
|
||
}
|
||
|
||
let successCount = 0;
|
||
let skippedCount = 0;
|
||
let errorCount = 0;
|
||
const errors = [];
|
||
const skipped = [];
|
||
|
||
for (let i = 0; i < modifiedFiles.length; i++) {
|
||
const { file, index } = modifiedFiles[i];
|
||
|
||
try {
|
||
// Add small delay between operations to prevent race conditions
|
||
if (i > 0) {
|
||
await new Promise(resolve => setTimeout(resolve, 200));
|
||
}
|
||
|
||
// Validate before saving
|
||
const newFilename = computeNewFilename(file, index);
|
||
const currentFilename = zddc.joinExtension(file.originalFilename, file.extension);
|
||
|
||
|
||
|
||
const validation = window.app.modules.validator.validateFilename(newFilename);
|
||
|
||
if (!validation.isValid) {
|
||
// Skip non-compliant files in Save All
|
||
skippedCount++;
|
||
skipped.push(`${zddc.joinExtension(file.originalFilename, file.extension)}: ${validation.errors[0]}`);
|
||
|
||
continue;
|
||
}
|
||
|
||
// Check if already has correct name
|
||
if (currentFilename === newFilename) {
|
||
|
||
// Just clear dirty flag
|
||
file.isDirty = false;
|
||
file.error = false;
|
||
delete file.manualFilename;
|
||
file.trackingNumber = '';
|
||
file.revision = '';
|
||
file.status = '';
|
||
file.title = '';
|
||
successCount++;
|
||
continue;
|
||
}
|
||
|
||
// Save with validation already done - ensure properly awaited
|
||
try {
|
||
await saveFile(index, true);
|
||
successCount++;
|
||
|
||
} catch (saveErr) {
|
||
console.error(`Error saving file ${index}:`, saveErr);
|
||
errorCount++;
|
||
errors.push(`${zddc.joinExtension(file.originalFilename, file.extension)}: ${saveErr.message}`);
|
||
|
||
// Add delay after errors to let filesystem stabilize
|
||
await new Promise(resolve => setTimeout(resolve, 300));
|
||
}
|
||
} catch (err) {
|
||
console.error(`Error processing file ${index}:`, err);
|
||
errorCount++;
|
||
errors.push(`${file.originalFilename}${file.extension}: ${err.message}`);
|
||
}
|
||
}
|
||
|
||
// Trigger store notification to update UI after all saves
|
||
window.app.modules.store.notify('files');
|
||
|
||
let message = `Saved ${successCount} compliant file(s).`;
|
||
|
||
if (skippedCount > 0) {
|
||
message += `\n\n⚠️ Skipped ${skippedCount} non-compliant file(s):`;
|
||
message += `\n${skipped.slice(0, 3).join('\n')}`;
|
||
if (skipped.length > 3) {
|
||
message += `\n... and ${skipped.length - 3} more`;
|
||
}
|
||
message += `\n\nUse individual save buttons (✓) to save non-compliant files.`;
|
||
}
|
||
|
||
if (errorCount > 0) {
|
||
message += `\n\n❌ ${errorCount} error(s):`;
|
||
message += `\n${errors.slice(0, 3).join('\n')}`;
|
||
if (errors.length > 3) {
|
||
message += `\n... and ${errors.length - 3} more`;
|
||
}
|
||
}
|
||
|
||
alert(message);
|
||
}
|
||
|
||
/**
|
||
* Cancel all changes
|
||
*/
|
||
function cancelAllChanges() {
|
||
const files = window.app.modules.store.getDisplayFiles();
|
||
files.forEach((file, index) => {
|
||
if (file.isDirty) {
|
||
cancelFile(index);
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Calculate SHA256 for all files
|
||
*/
|
||
async function calculateSha256ForAll() {
|
||
const files = window.app.modules.store.getDisplayFiles();
|
||
for (let i = 0; i < files.length; i++) {
|
||
const file = files[i];
|
||
if (!file.sha256) {
|
||
calculateSha256(file, i);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Calculate SHA256 for a single file
|
||
*/
|
||
async function calculateSha256(file, index) {
|
||
try {
|
||
let hashHex;
|
||
if (file.isVirtual) {
|
||
// Virtual file from ZIP
|
||
const cached = window.app.modules.scanner.getZipCache(file.zipPath);
|
||
if (!cached) throw new Error('ZIP not found in cache');
|
||
const zipEntry = cached.zip.file(file.zipEntryPath);
|
||
if (!zipEntry) throw new Error('File not found in ZIP');
|
||
const buffer = await zipEntry.async('arraybuffer');
|
||
hashHex = await zddc.crypto.sha256Hex(buffer);
|
||
} else {
|
||
const fileObj = await file.handle.getFile();
|
||
hashHex = await zddc.crypto.sha256File(fileObj);
|
||
}
|
||
|
||
file.sha256 = hashHex;
|
||
|
||
// Update cell
|
||
const row = window.app.dom.spreadsheetBody.querySelector(`tr[data-index="${index}"]`);
|
||
if (row) {
|
||
const sha256Cell = row.querySelector('.col-sha256');
|
||
if (sha256Cell) {
|
||
sha256Cell.textContent = hashHex.substring(0, 16) + '...';
|
||
sha256Cell.title = hashHex;
|
||
sha256Cell.classList.remove('sha256-calculating');
|
||
}
|
||
}
|
||
} catch (err) {
|
||
console.error('Error calculating SHA256:', err);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Highlight folder in tree when hovering over file
|
||
*/
|
||
function highlightFolder(folderPath) {
|
||
if (!folderPath) return;
|
||
|
||
// Find folder in tree
|
||
const folderTree = document.getElementById('folderTree');
|
||
if (!folderTree) return;
|
||
|
||
// Find the folder item by data-path attribute
|
||
const folderItem = folderTree.querySelector(`[data-path="${folderPath}"]`);
|
||
if (!folderItem) return;
|
||
|
||
// Add highlight class
|
||
folderItem.classList.add('folder-hover-highlight');
|
||
|
||
// Scroll into view if autoscroll is enabled
|
||
const autoScrollCheckbox = document.getElementById('autoScrollCheckbox');
|
||
if (autoScrollCheckbox && autoScrollCheckbox.checked) {
|
||
folderItem.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Clear folder highlight
|
||
*/
|
||
function clearFolderHighlight() {
|
||
const folderTree = document.getElementById('folderTree');
|
||
if (!folderTree) return;
|
||
|
||
// Remove all highlights
|
||
const highlighted = folderTree.querySelectorAll('.folder-hover-highlight');
|
||
highlighted.forEach(el => el.classList.remove('folder-hover-highlight'));
|
||
}
|
||
|
||
/**
|
||
* Initialize spreadsheet - subscribe to store
|
||
*/
|
||
function init() {
|
||
// Subscribe to store changes (only call this after DOM is ready)
|
||
window.app.modules.store.on('files', render);
|
||
}
|
||
|
||
// Export module
|
||
window.app.modules.spreadsheet = {
|
||
init,
|
||
render,
|
||
computeNewFilename,
|
||
saveAllFiles,
|
||
cancelAllChanges,
|
||
cancelEditing
|
||
};
|
||
})();
|
||
|
||
/**
|
||
* Selection Module
|
||
* Handles Excel-style cell selection and copy/paste
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
let selectionStart = null;
|
||
let selectionEnd = null;
|
||
let isSelecting = false;
|
||
let initialized = false;
|
||
let autoScrollInterval = null;
|
||
let lastMouseY = 0;
|
||
let startMouseX = 0;
|
||
let startMouseY = 0;
|
||
let dragDistance = 0;
|
||
|
||
/**
|
||
* Initialize selection handlers
|
||
*/
|
||
function init() {
|
||
// Only initialize once
|
||
if (initialized) return;
|
||
initialized = true;
|
||
|
||
const table = window.app.dom.spreadsheet;
|
||
|
||
// Make table focusable so clipboard events fire
|
||
if (!table.hasAttribute('tabindex')) {
|
||
table.setAttribute('tabindex', '-1');
|
||
table.style.outline = 'none';
|
||
}
|
||
|
||
// Mouse down on cell - start selection
|
||
table.addEventListener('mousedown', handleMouseDown);
|
||
|
||
// Mouse move - extend selection
|
||
document.addEventListener('mousemove', handleMouseMove);
|
||
|
||
// Mouse up - end selection
|
||
document.addEventListener('mouseup', handleMouseUp);
|
||
|
||
// Selectstart handler - prevent only when dragging (multi-cell selection)
|
||
document.addEventListener('selectstart', (e) => {
|
||
if (isSelecting && dragDistance > 4) {
|
||
e.preventDefault();
|
||
}
|
||
});
|
||
|
||
// Copy/paste handlers
|
||
document.addEventListener('copy', handleCopy);
|
||
document.addEventListener('paste', handlePaste);
|
||
document.addEventListener('cut', handleCut);
|
||
}
|
||
|
||
/**
|
||
* Handle mouse down on cell
|
||
*/
|
||
function handleMouseDown(e) {
|
||
const cell = e.target.closest('td');
|
||
if (!cell) return;
|
||
|
||
// Ignore if clicking action buttons
|
||
if (e.target.closest('.inline-actions')) return;
|
||
|
||
// Ignore if cell is being edited (contenteditable)
|
||
if (cell.isContentEditable || cell.classList.contains('editing')) return;
|
||
|
||
// Don't start selection if double-clicking to edit
|
||
if (e.detail === 2) return;
|
||
|
||
const row = cell.closest('tr');
|
||
if (!row) return;
|
||
|
||
const rowIndex = parseInt(row.dataset.index);
|
||
const colIndex = Array.from(row.children).indexOf(cell);
|
||
|
||
// Shift+Click: extend selection from existing start to clicked cell
|
||
if (e.shiftKey && selectionStart) {
|
||
selectionEnd = { row: rowIndex, col: colIndex };
|
||
updateSelection();
|
||
updateButtonStates();
|
||
e.preventDefault();
|
||
return;
|
||
}
|
||
|
||
// Clear previous selection
|
||
clearSelection();
|
||
|
||
// Start new selection
|
||
selectionStart = { row: rowIndex, col: colIndex };
|
||
selectionEnd = { row: rowIndex, col: colIndex };
|
||
isSelecting = true;
|
||
dragDistance = 0;
|
||
startMouseX = e.clientX;
|
||
startMouseY = e.clientY;
|
||
|
||
// Highlight cell
|
||
updateSelection();
|
||
updateButtonStates();
|
||
|
||
// Focus the table so clipboard events (Ctrl+V) fire
|
||
window.app.dom.spreadsheet.focus();
|
||
|
||
// Only prevent default for shift-click (extending selection)
|
||
// Single click should allow text selection to work normally
|
||
if (e.shiftKey) {
|
||
e.preventDefault();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Handle mouse move during selection
|
||
*/
|
||
function handleMouseMove(e) {
|
||
if (!isSelecting) return;
|
||
|
||
// Store mouse position for auto-scroll
|
||
lastMouseY = e.clientY;
|
||
|
||
// Track drag distance from mousedown
|
||
const dx = e.clientX - startMouseX;
|
||
const dy = e.clientY - startMouseY;
|
||
dragDistance = Math.sqrt(dx*dx + dy*dy);
|
||
|
||
const cell = e.target.closest('td');
|
||
if (!cell) return;
|
||
|
||
const row = cell.closest('tr');
|
||
if (!row) return;
|
||
|
||
const rowIndex = parseInt(row.dataset.index);
|
||
const colIndex = Array.from(row.children).indexOf(cell);
|
||
|
||
if (rowIndex === undefined || colIndex === -1) return;
|
||
|
||
// Update selection end
|
||
selectionEnd = { row: rowIndex, col: colIndex };
|
||
|
||
// Highlight selected cells
|
||
updateSelection();
|
||
|
||
// Start auto-scroll if not already running
|
||
if (!autoScrollInterval) {
|
||
startAutoScroll();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Start continuous auto-scroll
|
||
*/
|
||
function startAutoScroll() {
|
||
const scrollThreshold = 50; // pixels from edge
|
||
const scrollSpeed = 5; // pixels per frame
|
||
|
||
const scroll = () => {
|
||
if (!isSelecting) {
|
||
autoScrollInterval = null;
|
||
return;
|
||
}
|
||
|
||
const viewport = document.querySelector('.spreadsheet-pane');
|
||
if (!viewport) {
|
||
autoScrollInterval = null;
|
||
return;
|
||
}
|
||
|
||
const rect = viewport.getBoundingClientRect();
|
||
|
||
// Determine scroll direction based on last mouse position
|
||
if (lastMouseY > rect.bottom - scrollThreshold) {
|
||
viewport.scrollTop += scrollSpeed; // Scroll down
|
||
} else if (lastMouseY < rect.top + scrollThreshold) {
|
||
viewport.scrollTop -= scrollSpeed; // Scroll up
|
||
}
|
||
|
||
// Continue scrolling
|
||
autoScrollInterval = requestAnimationFrame(scroll);
|
||
};
|
||
|
||
autoScrollInterval = requestAnimationFrame(scroll);
|
||
}
|
||
|
||
/**
|
||
* Handle mouse up - end selection
|
||
*/
|
||
function handleMouseUp(e) {
|
||
isSelecting = false;
|
||
dragDistance = 0;
|
||
|
||
// Stop auto-scrolling
|
||
if (autoScrollInterval) {
|
||
cancelAnimationFrame(autoScrollInterval);
|
||
autoScrollInterval = null;
|
||
}
|
||
|
||
updateButtonStates();
|
||
}
|
||
|
||
/**
|
||
* Update visual selection highlighting
|
||
*/
|
||
function updateSelection() {
|
||
if (!selectionStart || !selectionEnd) return;
|
||
|
||
// Clear all previous highlights
|
||
document.querySelectorAll('.selected-cell').forEach(cell => {
|
||
cell.classList.remove('selected-cell');
|
||
});
|
||
|
||
// Calculate selection bounds
|
||
const minRow = Math.min(selectionStart.row, selectionEnd.row);
|
||
const maxRow = Math.max(selectionStart.row, selectionEnd.row);
|
||
const minCol = Math.min(selectionStart.col, selectionEnd.col);
|
||
const maxCol = Math.max(selectionStart.col, selectionEnd.col);
|
||
|
||
// Highlight selected cells
|
||
const tbody = window.app.dom.spreadsheetBody;
|
||
const rows = tbody.querySelectorAll('tr');
|
||
|
||
for (let r = minRow; r <= maxRow; r++) {
|
||
const row = rows[r];
|
||
if (!row) continue;
|
||
|
||
const cells = row.children;
|
||
for (let c = minCol; c <= maxCol; c++) {
|
||
const cell = cells[c];
|
||
if (cell) {
|
||
cell.classList.add('selected-cell');
|
||
}
|
||
}
|
||
}
|
||
|
||
// Emit rowfocused event for preview pane
|
||
emitRowFocused(minRow);
|
||
}
|
||
|
||
/**
|
||
* Emit row focused event for preview pane
|
||
*/
|
||
function emitRowFocused(rowIndex) {
|
||
const files = window.app.modules.store.getDisplayFiles();
|
||
const file = files[rowIndex];
|
||
|
||
if (file) {
|
||
const event = new CustomEvent('rowfocused', {
|
||
detail: { rowIndex, file }
|
||
});
|
||
document.dispatchEvent(event);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Clear selection
|
||
*/
|
||
function clearSelection() {
|
||
selectionStart = null;
|
||
selectionEnd = null;
|
||
document.querySelectorAll('.selected-cell').forEach(cell => {
|
||
cell.classList.remove('selected-cell');
|
||
});
|
||
updateButtonStates();
|
||
}
|
||
|
||
/**
|
||
* Check if there is an active selection
|
||
*/
|
||
function hasSelection() {
|
||
return selectionStart !== null && selectionEnd !== null;
|
||
}
|
||
|
||
/**
|
||
* Update Copy/Paste button enabled states
|
||
*/
|
||
function updateButtonStates() {
|
||
const copyBtn = document.getElementById('copyBtn');
|
||
const pasteBtn = document.getElementById('pasteBtn');
|
||
const active = hasSelection();
|
||
if (copyBtn) copyBtn.disabled = !active;
|
||
if (pasteBtn) pasteBtn.disabled = !active;
|
||
}
|
||
|
||
/**
|
||
* Get column headers for selected columns
|
||
*/
|
||
function getColumnHeaders(minCol, maxCol) {
|
||
const headerCells = window.app.dom.spreadsheet.querySelectorAll('thead th');
|
||
const headers = [];
|
||
for (let c = minCol; c <= maxCol; c++) {
|
||
const th = headerCells[c];
|
||
if (th) {
|
||
// Get the text content, excluding filter inputs
|
||
const text = th.childNodes[0]?.textContent?.trim() || th.textContent.trim();
|
||
headers.push(text);
|
||
} else {
|
||
headers.push('');
|
||
}
|
||
}
|
||
return headers;
|
||
}
|
||
|
||
/**
|
||
* Check if first row looks like a header row
|
||
*/
|
||
function isHeaderRow(row) {
|
||
const headerPatterns = ['#', 'Original', 'Ext', 'New', 'Tracking', 'Rev', 'Status', 'Title', 'SHA256'];
|
||
return row.some(cell => headerPatterns.includes(cell.trim()));
|
||
}
|
||
|
||
/**
|
||
* Convert 2D array of rows to an HTML table string.
|
||
* Excel prefers text/html over text/plain, so providing a
|
||
* proper <table> ensures cell boundaries are preserved.
|
||
*/
|
||
function rowsToHtml(allRows) {
|
||
const esc = s => s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||
const headerRow = allRows[0];
|
||
const dataRows = allRows.slice(1);
|
||
let html = '<table>';
|
||
html += '<tr>' + headerRow.map(c => '<th>' + esc(c) + '</th>').join('') + '</tr>';
|
||
for (const row of dataRows) {
|
||
html += '<tr>' + row.map(c => '<td>' + esc(c) + '</td>').join('') + '</tr>';
|
||
}
|
||
html += '</table>';
|
||
return html;
|
||
}
|
||
|
||
/**
|
||
* Handle copy event
|
||
*/
|
||
function handleCopy(e) {
|
||
if (!selectionStart || !selectionEnd) return;
|
||
|
||
const minCol = Math.min(selectionStart.col, selectionEnd.col);
|
||
const maxCol = Math.max(selectionStart.col, selectionEnd.col);
|
||
|
||
// Get column headers for selected range
|
||
const headers = getColumnHeaders(minCol, maxCol);
|
||
|
||
const data = getSelectionData();
|
||
if (!data) return;
|
||
|
||
// Prepend header row
|
||
const allRows = [headers, ...data];
|
||
|
||
// Convert to TSV and HTML table
|
||
const tsv = allRows.map(row => row.join('\t')).join('\n');
|
||
|
||
e.clipboardData.setData('text/plain', tsv);
|
||
e.clipboardData.setData('text/html', rowsToHtml(allRows));
|
||
e.preventDefault();
|
||
}
|
||
|
||
/**
|
||
* Handle cut event
|
||
*/
|
||
function handleCut(e) {
|
||
if (!selectionStart || !selectionEnd) return;
|
||
|
||
const minCol = Math.min(selectionStart.col, selectionEnd.col);
|
||
const maxCol = Math.max(selectionStart.col, selectionEnd.col);
|
||
|
||
// Get column headers for selected range
|
||
const headers = getColumnHeaders(minCol, maxCol);
|
||
|
||
const data = getSelectionData();
|
||
if (!data) return;
|
||
|
||
// Prepend header row
|
||
const allRows = [headers, ...data];
|
||
|
||
// Convert to TSV
|
||
const tsv = allRows.map(row => row.join('\t')).join('\n');
|
||
|
||
e.clipboardData.setData('text/plain', tsv);
|
||
e.clipboardData.setData('text/html', rowsToHtml(allRows));
|
||
|
||
// Clear selected cells (only editable ones)
|
||
clearSelectionData();
|
||
|
||
e.preventDefault();
|
||
}
|
||
|
||
/**
|
||
* Handle paste event
|
||
*/
|
||
function handlePaste(e) {
|
||
// Don't intercept paste in input fields
|
||
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.isContentEditable) {
|
||
return;
|
||
}
|
||
|
||
if (!selectionStart) return;
|
||
|
||
const tsv = e.clipboardData.getData('text/plain');
|
||
if (!tsv) return;
|
||
|
||
e.preventDefault();
|
||
executePaste(tsv);
|
||
}
|
||
|
||
/**
|
||
* Execute paste from TSV string into the selected range.
|
||
* - If pasted cols > selected cols, right-align (user likely copied all but only pastes back editable cols).
|
||
* - If pasted data includes the first two columns (#, Original), validate they match.
|
||
* - If pasted row count != selected row count, abort with error.
|
||
*/
|
||
function executePaste(tsv) {
|
||
if (!selectionStart || !selectionEnd) return;
|
||
|
||
// Parse TSV
|
||
let rows = tsv.split('\n').map(row => row.split('\t'));
|
||
|
||
// Filter out empty trailing rows
|
||
while (rows.length > 0 && rows[rows.length - 1].every(cell => !cell.trim())) {
|
||
rows.pop();
|
||
}
|
||
if (rows.length === 0) return;
|
||
|
||
// Check if first row is a header row and skip it
|
||
if (rows.length > 1 && isHeaderRow(rows[0])) {
|
||
rows = rows.slice(1);
|
||
}
|
||
if (rows.length === 0) return;
|
||
|
||
// Selection bounds
|
||
const minRow = Math.min(selectionStart.row, selectionEnd.row);
|
||
const maxRow = Math.max(selectionStart.row, selectionEnd.row);
|
||
const minCol = Math.min(selectionStart.col, selectionEnd.col);
|
||
const maxCol = Math.max(selectionStart.col, selectionEnd.col);
|
||
const selectedRowCount = maxRow - minRow + 1;
|
||
const selectedColCount = maxCol - minCol + 1;
|
||
const pastedColCount = rows[0].length;
|
||
|
||
// Row count validation: must match
|
||
if (rows.length !== selectedRowCount) {
|
||
alert(`Paste aborted: row count mismatch.\n` +
|
||
`Selected ${selectedRowCount} row(s), but clipboard has ${rows.length} row(s).`);
|
||
return;
|
||
}
|
||
|
||
// Determine column offset for right-alignment
|
||
let colOffset = 0;
|
||
if (pastedColCount > selectedColCount) {
|
||
colOffset = pastedColCount - selectedColCount;
|
||
}
|
||
|
||
// Get column names from header
|
||
const headerCells = window.app.dom.spreadsheet.querySelectorAll('thead th');
|
||
const columnNames = Array.from(headerCells).map(th => {
|
||
const match = th.className.match(/col-(\w+)/);
|
||
return match ? match[1] : '';
|
||
});
|
||
|
||
// If pasted data includes first two columns (row-num, original), validate they match
|
||
if (colOffset === 0 && pastedColCount >= selectedColCount) {
|
||
// Check if pasted range starts at col 0 or col 1 (row-num or original)
|
||
const files = window.app.modules.store.getDisplayFiles();
|
||
const startsAtRowNum = (minCol === 0);
|
||
const startsAtOriginal = (minCol === 1);
|
||
|
||
if (startsAtRowNum || startsAtOriginal) {
|
||
for (let r = 0; r < rows.length; r++) {
|
||
const targetRowIndex = minRow + r;
|
||
const file = files[targetRowIndex];
|
||
if (!file) continue;
|
||
|
||
if (startsAtRowNum) {
|
||
// Validate col 0 = row number, col 1 = original filename
|
||
const expectedNum = String(targetRowIndex + 1);
|
||
const pastedNum = rows[r][0]?.trim();
|
||
const pastedOriginal = rows[r][1]?.trim();
|
||
if (pastedNum && pastedNum !== expectedNum) {
|
||
alert(`Paste aborted: row number mismatch at row ${targetRowIndex + 1}.\n` +
|
||
`Expected "${expectedNum}", got "${pastedNum}".\n` +
|
||
`Data may be shuffled.`);
|
||
return;
|
||
}
|
||
if (pastedOriginal && pastedOriginal !== file.originalFilename) {
|
||
alert(`Paste aborted: filename mismatch at row ${targetRowIndex + 1}.\n` +
|
||
`Expected "${file.originalFilename}", got "${pastedOriginal}".\n` +
|
||
`Data may be shuffled.`);
|
||
return;
|
||
}
|
||
} else if (startsAtOriginal) {
|
||
// Validate col 0 of paste = original filename
|
||
const pastedOriginal = rows[r][0]?.trim();
|
||
if (pastedOriginal && pastedOriginal !== file.originalFilename) {
|
||
alert(`Paste aborted: filename mismatch at row ${targetRowIndex + 1}.\n` +
|
||
`Expected "${file.originalFilename}", got "${pastedOriginal}".\n` +
|
||
`Data may be shuffled.`);
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Editable columns
|
||
const editableColumns = ['newFilename', 'trackingNumber', 'revision', 'status', 'title'];
|
||
const files = window.app.modules.store.getDisplayFiles();
|
||
let updatedCount = 0;
|
||
|
||
for (let r = 0; r < rows.length; r++) {
|
||
const targetRowIndex = minRow + r;
|
||
if (targetRowIndex >= files.length) continue;
|
||
const file = files[targetRowIndex];
|
||
if (!file) continue;
|
||
|
||
const rowData = rows[r];
|
||
|
||
for (let c = 0; c < selectedColCount; c++) {
|
||
const pasteIdx = c + colOffset; // right-align: skip leading pasted cols
|
||
if (pasteIdx >= rowData.length) continue;
|
||
|
||
const targetColIndex = minCol + c;
|
||
if (targetColIndex >= columnNames.length) continue;
|
||
|
||
const columnName = columnNames[targetColIndex];
|
||
if (!editableColumns.includes(columnName)) continue;
|
||
|
||
const value = rowData[pasteIdx]?.trim() || '';
|
||
|
||
if (columnName === 'newFilename') {
|
||
if (value) {
|
||
file.manualFilename = value;
|
||
} else {
|
||
delete file.manualFilename;
|
||
}
|
||
} else {
|
||
file[columnName] = value;
|
||
if (file.manualFilename) {
|
||
delete file.manualFilename;
|
||
}
|
||
}
|
||
|
||
file.isDirty = true;
|
||
file.autoPopulated = false;
|
||
updatedCount++;
|
||
}
|
||
}
|
||
|
||
// Re-render and restore selection
|
||
if (updatedCount > 0) {
|
||
window.app.modules.spreadsheet.render();
|
||
// Restore selection highlight
|
||
updateSelection();
|
||
showToast(`Pasted ${updatedCount} cell(s)`, 'success');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Show a brief toast notification
|
||
*/
|
||
function showToast(message, type) {
|
||
if (window.app.modules.excel && window.app.modules.excel.showToast) {
|
||
window.app.modules.excel.showToast(message, type);
|
||
return;
|
||
}
|
||
// Fallback: simple toast
|
||
const toast = document.createElement('div');
|
||
toast.textContent = message;
|
||
toast.style.cssText = 'position:fixed;bottom:20px;right:20px;padding:8px 16px;' +
|
||
'background:' + (type === 'success' ? '#28a745' : '#dc3545') + ';color:#fff;' +
|
||
'border-radius:4px;z-index:9999;font-size:14px;';
|
||
document.body.appendChild(toast);
|
||
setTimeout(() => toast.remove(), 3000);
|
||
}
|
||
|
||
/**
|
||
* Get data from selected cells
|
||
*/
|
||
function getSelectionData() {
|
||
if (!selectionStart || !selectionEnd) return null;
|
||
|
||
const minRow = Math.min(selectionStart.row, selectionEnd.row);
|
||
const maxRow = Math.max(selectionStart.row, selectionEnd.row);
|
||
const minCol = Math.min(selectionStart.col, selectionEnd.col);
|
||
const maxCol = Math.max(selectionStart.col, selectionEnd.col);
|
||
|
||
const data = [];
|
||
const tbody = window.app.dom.spreadsheetBody;
|
||
const rows = tbody.querySelectorAll('tr');
|
||
|
||
for (let r = minRow; r <= maxRow; r++) {
|
||
const row = rows[r];
|
||
if (!row) continue;
|
||
|
||
const rowData = [];
|
||
const cells = row.children;
|
||
|
||
for (let c = minCol; c <= maxCol; c++) {
|
||
const cell = cells[c];
|
||
if (cell) {
|
||
rowData.push(cell.textContent.trim());
|
||
} else {
|
||
rowData.push('');
|
||
}
|
||
}
|
||
|
||
data.push(rowData);
|
||
}
|
||
|
||
return data;
|
||
}
|
||
|
||
/**
|
||
* Clear data from selected cells (only editable ones)
|
||
*/
|
||
function clearSelectionData() {
|
||
if (!selectionStart || !selectionEnd) return;
|
||
|
||
const minRow = Math.min(selectionStart.row, selectionEnd.row);
|
||
const maxRow = Math.max(selectionStart.row, selectionEnd.row);
|
||
const minCol = Math.min(selectionStart.col, selectionEnd.col);
|
||
const maxCol = Math.max(selectionStart.col, selectionEnd.col);
|
||
|
||
const tbody = window.app.dom.spreadsheetBody;
|
||
const rows = tbody.querySelectorAll('tr');
|
||
|
||
// Get column names from header
|
||
const headerCells = window.app.dom.spreadsheet.querySelectorAll('thead th');
|
||
const columnNames = Array.from(headerCells).map(th => {
|
||
const className = th.className.replace('col-', '');
|
||
return className;
|
||
});
|
||
|
||
for (let r = minRow; r <= maxRow; r++) {
|
||
const row = rows[r];
|
||
if (!row) continue;
|
||
|
||
const rowIndex = parseInt(row.dataset.index);
|
||
const files = window.app.modules.store.getDisplayFiles();
|
||
const file = files[rowIndex];
|
||
if (!file) continue;
|
||
|
||
const cells = row.children;
|
||
|
||
for (let c = minCol; c <= maxCol; c++) {
|
||
const cell = cells[c];
|
||
if (!cell || !cell.classList.contains('cell-editable')) continue;
|
||
|
||
const columnName = columnNames[c];
|
||
|
||
// Clear the data
|
||
if (columnName === 'newFilename') {
|
||
delete file.manualFilename;
|
||
} else {
|
||
file[columnName] = '';
|
||
}
|
||
|
||
file.isDirty = true;
|
||
}
|
||
}
|
||
|
||
// Re-render
|
||
window.app.modules.spreadsheet.render();
|
||
}
|
||
|
||
/**
|
||
* Copy selection to clipboard via button click
|
||
*/
|
||
function doCopy() {
|
||
if (!selectionStart || !selectionEnd) return;
|
||
|
||
const minCol = Math.min(selectionStart.col, selectionEnd.col);
|
||
const maxCol = Math.max(selectionStart.col, selectionEnd.col);
|
||
const headers = getColumnHeaders(minCol, maxCol);
|
||
const data = getSelectionData();
|
||
if (!data) return;
|
||
|
||
const allRows = [headers, ...data];
|
||
const tsv = allRows.map(row => row.join('\t')).join('\n');
|
||
const html = rowsToHtml(allRows);
|
||
|
||
// Write both plain text and HTML to clipboard
|
||
const htmlBlob = new Blob([html], { type: 'text/html' });
|
||
const textBlob = new Blob([tsv], { type: 'text/plain' });
|
||
const item = new ClipboardItem({ 'text/html': htmlBlob, 'text/plain': textBlob });
|
||
|
||
navigator.clipboard.write([item]).then(() => {
|
||
showToast(`Copied ${data.length} row(s)`, 'success');
|
||
}).catch(err => {
|
||
// Fallback to plain text if ClipboardItem not supported
|
||
navigator.clipboard.writeText(tsv).then(() => {
|
||
showToast(`Copied ${data.length} row(s)`, 'success');
|
||
}).catch(err2 => {
|
||
console.error('Copy failed:', err2);
|
||
});
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Paste from clipboard via button click
|
||
*/
|
||
function doPaste() {
|
||
if (!selectionStart || !selectionEnd) return;
|
||
|
||
navigator.clipboard.readText().then(tsv => {
|
||
if (tsv) executePaste(tsv);
|
||
}).catch(err => {
|
||
console.error('Paste failed:', err);
|
||
alert('Cannot read clipboard. Use Ctrl+V instead, or grant clipboard permission.');
|
||
});
|
||
}
|
||
|
||
// Export module
|
||
window.app.modules.selection = {
|
||
init,
|
||
clearSelection,
|
||
hasSelection,
|
||
doCopy,
|
||
doPaste
|
||
};
|
||
})();
|
||
|
||
/**
|
||
* Preview Module
|
||
* Opens file preview in a separate popup window
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
let currentBlobUrl = null;
|
||
let currentFile = null;
|
||
let currentRowIndex = null;
|
||
let previewWindow = null;
|
||
|
||
// Use shared extension lists from window.zddc.preview where possible
|
||
const IMAGE_EXTENSIONS = zddc.preview.IMAGE_EXTENSIONS;
|
||
const TIFF_EXTENSIONS = zddc.preview.TIFF_EXTENSIONS;
|
||
const TEXT_EXTENSIONS = zddc.preview.TEXT_EXTENSIONS;
|
||
const PDF_EXTENSIONS = ['pdf'];
|
||
const ZIP_EXTENSIONS = ['zip'];
|
||
|
||
// Lazily load a script from CDN — delegates to shared cache.
|
||
const loadLibrary = zddc.preview.loadLibrary;
|
||
|
||
/**
|
||
* Initialize preview module
|
||
*/
|
||
function init() {
|
||
// Listen for row focused events from selection module
|
||
document.addEventListener('rowfocused', handleRowFocused);
|
||
|
||
// Set up toggle button to open/close preview window
|
||
const toggleBtn = document.getElementById('togglePreviewBtn');
|
||
if (toggleBtn) {
|
||
toggleBtn.addEventListener('click', () => {
|
||
if (previewWindow && !previewWindow.closed) {
|
||
// Close preview window
|
||
previewWindow.close();
|
||
previewWindow = null;
|
||
toggleBtn.classList.remove('preview-active');
|
||
} else if (currentFile) {
|
||
openPreviewWindow(currentFile);
|
||
toggleBtn.classList.add('preview-active');
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Handle row focused event
|
||
*/
|
||
function handleRowFocused(e) {
|
||
const { rowIndex, file } = e.detail;
|
||
|
||
currentRowIndex = rowIndex;
|
||
|
||
if (file && file !== currentFile) {
|
||
currentFile = file;
|
||
|
||
// Update preview window if open
|
||
if (previewWindow && !previewWindow.closed) {
|
||
openPreviewWindow(file);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Open preview in a separate popup window
|
||
*/
|
||
async function openPreviewWindow(file) {
|
||
if (!file) return;
|
||
|
||
currentFile = file;
|
||
|
||
try {
|
||
const blob = await getFileBlob(file);
|
||
|
||
// Clean up previous blob URL
|
||
if (currentBlobUrl) {
|
||
URL.revokeObjectURL(currentBlobUrl);
|
||
}
|
||
currentBlobUrl = URL.createObjectURL(blob);
|
||
|
||
const fileName = zddc.joinExtension(file.originalFilename, file.extension);
|
||
|
||
// Build preview HTML with toolbar
|
||
const previewHtml = `
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<title>${escapeHtml(fileName)} - Preview</title>
|
||
<style>
|
||
* { 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;
|
||
}
|
||
.toolbar {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
padding: 0.5rem 1rem;
|
||
background: #f5f5f5;
|
||
border-bottom: 1px solid #ddd;
|
||
}
|
||
.toolbar h1 {
|
||
flex: 1;
|
||
font-size: 0.95rem;
|
||
font-weight: 500;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.btn {
|
||
padding: 0.4rem 0.8rem;
|
||
font-size: 0.85rem;
|
||
border: 1px solid #ccc;
|
||
border-radius: 4px;
|
||
background: white;
|
||
cursor: pointer;
|
||
}
|
||
.btn:hover { background: #e8e8e8; }
|
||
iframe, img {
|
||
flex: 1;
|
||
width: 100%;
|
||
border: none;
|
||
}
|
||
img {
|
||
object-fit: contain;
|
||
background: #f0f0f0;
|
||
}
|
||
pre {
|
||
flex: 1;
|
||
padding: 1rem;
|
||
overflow: auto;
|
||
background: #fafafa;
|
||
font-family: 'Consolas', 'Monaco', monospace;
|
||
font-size: 0.9rem;
|
||
white-space: pre-wrap;
|
||
word-wrap: break-word;
|
||
}
|
||
.unsupported {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: #666;
|
||
}
|
||
.unsupported .icon { font-size: 3rem; margin-bottom: 1rem; }
|
||
#previewContent {
|
||
flex: 1;
|
||
overflow: auto;
|
||
}
|
||
.loading {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 100%;
|
||
color: #666;
|
||
font-size: 1.1rem;
|
||
}
|
||
.docx-wrapper { padding: 1rem; }
|
||
.xlsx-table { border-collapse: collapse; width: 100%; font-size: 0.85rem; }
|
||
.xlsx-table th, .xlsx-table td {
|
||
border: 1px solid #ddd;
|
||
padding: 0.35rem 0.5rem;
|
||
text-align: left;
|
||
white-space: nowrap;
|
||
}
|
||
.xlsx-table th { background: #f0f0f0; font-weight: 600; position: sticky; top: 0; }
|
||
.xlsx-table tr:nth-child(even) { background: #fafafa; }
|
||
.xlsx-table tr:hover { background: #f0f7ff; }
|
||
.sheet-tabs { display: flex; gap: 0; border-bottom: 1px solid #ddd; background: #f5f5f5; }
|
||
.sheet-tab {
|
||
padding: 0.4rem 1rem;
|
||
cursor: pointer;
|
||
border: 1px solid transparent;
|
||
border-bottom: none;
|
||
font-size: 0.85rem;
|
||
background: transparent;
|
||
}
|
||
.sheet-tab:hover { background: #e8e8e8; }
|
||
.sheet-tab.active {
|
||
background: white;
|
||
border-color: #ddd;
|
||
border-bottom-color: white;
|
||
margin-bottom: -1px;
|
||
font-weight: 500;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="toolbar">
|
||
<h1>${escapeHtml(fileName)}</h1>
|
||
<button class="btn" onclick="downloadFile()">Download</button>
|
||
</div>
|
||
${await getPreviewContent(file, currentBlobUrl)}
|
||
<script>
|
||
var blobUrl = "${currentBlobUrl}";
|
||
var fileName = "${escapeHtml(fileName).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>`;
|
||
|
||
// Reuse existing window if open, otherwise create new one
|
||
if (previewWindow && !previewWindow.closed) {
|
||
previewWindow.document.open();
|
||
previewWindow.document.write(previewHtml);
|
||
previewWindow.document.close();
|
||
previewWindow.focus();
|
||
} else {
|
||
// Calculate window size
|
||
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);
|
||
|
||
previewWindow = window.open('', 'classifierPreview',
|
||
`width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes`);
|
||
|
||
if (!previewWindow) {
|
||
// Popup blocked - fall back to new tab
|
||
window.open(currentBlobUrl, '_blank');
|
||
return;
|
||
}
|
||
|
||
// Poll for window close — beforeunload is unreliable for popup close buttons
|
||
const closePoll = setInterval(() => {
|
||
if (previewWindow && previewWindow.closed) {
|
||
clearInterval(closePoll);
|
||
previewWindow = null;
|
||
const btn = document.getElementById('togglePreviewBtn');
|
||
if (btn) btn.classList.remove('preview-active');
|
||
}
|
||
}, 500);
|
||
|
||
previewWindow.document.write(previewHtml);
|
||
previewWindow.document.close();
|
||
previewWindow.focus();
|
||
}
|
||
|
||
// For types that need decoding, render content after window is ready
|
||
const ext = (file.extension || '').toLowerCase();
|
||
if (ext === 'docx') {
|
||
await renderDocxInWindow(file);
|
||
} else if (ext === 'xlsx' || ext === 'xls') {
|
||
await renderXlsxInWindow(file);
|
||
} else if (TIFF_EXTENSIONS.includes(ext)) {
|
||
await renderTiffInWindow(file);
|
||
} else if (ZIP_EXTENSIONS.includes(ext)) {
|
||
await renderZipInWindow(file);
|
||
}
|
||
} catch (err) {
|
||
console.error('Error opening preview:', err);
|
||
alert(`Error opening preview: ${err.message}`);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Get preview content HTML based on file type
|
||
*/
|
||
async function getPreviewContent(file, blobUrl) {
|
||
const ext = file.extension.toLowerCase();
|
||
const previewType = getPreviewType(ext);
|
||
|
||
switch (previewType) {
|
||
case 'pdf':
|
||
return `<iframe src="${blobUrl}#view=FitV"></iframe>`;
|
||
case 'html':
|
||
// Render the HTML natively (not as literal text). Sandbox
|
||
// flags allow same-origin resource loads + opening links
|
||
// in real new tabs (target=_blank / middle-click), but
|
||
// NOT allow-scripts — archived HTML cannot run JS.
|
||
return `<iframe src="${blobUrl}" sandbox="allow-same-origin allow-popups allow-popups-to-escape-sandbox"></iframe>`;
|
||
case 'image':
|
||
return `<img src="${blobUrl}" alt="${escapeHtml(file.originalFilename)}" />`;
|
||
case 'text':
|
||
const text = await getFileText(file);
|
||
const maxLength = 100000;
|
||
const displayText = text.length > maxLength
|
||
? text.substring(0, maxLength) + '\n\n... (truncated)'
|
||
: text;
|
||
return `<pre>${escapeHtml(displayText)}</pre>`;
|
||
case 'docx':
|
||
case 'xlsx':
|
||
case 'tiff':
|
||
case 'zip':
|
||
return `<div id="previewContent"><div class="loading">Loading preview...</div></div>`;
|
||
default:
|
||
return `
|
||
<div class="unsupported">
|
||
<div class="icon">📄</div>
|
||
<p>Preview not available for ${ext} files</p>
|
||
<p style="margin-top: 0.5rem;">Click Download to view in external application</p>
|
||
</div>
|
||
`;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Get preview type from extension
|
||
*/
|
||
function getPreviewType(ext) {
|
||
// HTML is technically in TEXT_EXTENSIONS (used for editor
|
||
// syntax-highlighting elsewhere) but for previews we want to
|
||
// RENDER it, not show source. Check before the text branch.
|
||
if (ext === 'html' || ext === 'htm') return 'html';
|
||
if (PDF_EXTENSIONS.includes(ext)) return 'pdf';
|
||
if (TIFF_EXTENSIONS.includes(ext)) return 'tiff';
|
||
if (IMAGE_EXTENSIONS.includes(ext)) return 'image';
|
||
if (TEXT_EXTENSIONS.includes(ext)) return 'text';
|
||
if (ext === 'docx') return 'docx';
|
||
if (ext === 'xlsx' || ext === 'xls') return 'xlsx';
|
||
if (ZIP_EXTENSIONS.includes(ext)) return 'zip';
|
||
return 'none';
|
||
}
|
||
|
||
function getMimeType(ext) {
|
||
return window.app.modules.utils.getMimeType(ext);
|
||
}
|
||
|
||
/**
|
||
* Get file content as blob (handles both real and virtual files)
|
||
*/
|
||
async function getFileBlob(file) {
|
||
if (file.isVirtual) {
|
||
// Get from ZIP cache
|
||
const cached = window.app.modules.scanner.getZipCache(file.zipPath);
|
||
if (!cached) throw new Error('ZIP not found in cache');
|
||
|
||
const zipEntry = cached.zip.file(file.zipEntryPath);
|
||
if (!zipEntry) throw new Error('File not found in ZIP');
|
||
|
||
// Get as arraybuffer and create blob with correct MIME type
|
||
const arrayBuffer = await zipEntry.async('arraybuffer');
|
||
const mimeType = getMimeType(file.extension);
|
||
return new Blob([arrayBuffer], { type: mimeType });
|
||
} else {
|
||
// Get from file handle
|
||
if (!file.handle) {
|
||
throw new Error('File handle not available');
|
||
}
|
||
return await file.handle.getFile();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Get file content as text (handles both real and virtual files)
|
||
*/
|
||
async function getFileText(file) {
|
||
if (file.isVirtual) {
|
||
const cached = window.app.modules.scanner.getZipCache(file.zipPath);
|
||
if (!cached) throw new Error('ZIP not found in cache');
|
||
|
||
const zipEntry = cached.zip.file(file.zipEntryPath);
|
||
if (!zipEntry) throw new Error('File not found in ZIP');
|
||
|
||
return await zipEntry.async('string');
|
||
} else {
|
||
if (!file.handle) {
|
||
throw new Error('File handle not available');
|
||
}
|
||
const fileObj = await file.handle.getFile();
|
||
return await fileObj.text();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Render a DOCX file in the preview window using docx-preview library
|
||
*/
|
||
async function renderDocxInWindow(file) {
|
||
const container = previewWindow.document.getElementById('previewContent');
|
||
if (!container) return;
|
||
|
||
try {
|
||
// jszip + docx-preview vendored by build.sh — already in scope.
|
||
const blob = await getFileBlob(file);
|
||
const arrayBuffer = await blob.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 = previewWindow.document.getElementById('previewContent');
|
||
if (!container) return;
|
||
|
||
try {
|
||
await loadLibrary('https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/xlsx.full.min.js');
|
||
|
||
const blob = await getFileBlob(file);
|
||
const arrayBuffer = await blob.arrayBuffer();
|
||
const workbook = XLSX.read(arrayBuffer, { type: 'array' });
|
||
|
||
container.innerHTML = '';
|
||
|
||
if (workbook.SheetNames.length > 1) {
|
||
const tabs = previewWindow.document.createElement('div');
|
||
tabs.className = 'sheet-tabs';
|
||
workbook.SheetNames.forEach((name, i) => {
|
||
const tab = previewWindow.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');
|
||
renderSheetInWindow(workbook, name, tableContainer);
|
||
};
|
||
tabs.appendChild(tab);
|
||
});
|
||
container.appendChild(tabs);
|
||
}
|
||
|
||
const tableContainer = previewWindow.document.createElement('div');
|
||
tableContainer.style.flex = '1';
|
||
tableContainer.style.overflow = 'auto';
|
||
container.appendChild(tableContainer);
|
||
|
||
renderSheetInWindow(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 in the preview window
|
||
*/
|
||
function renderSheetInWindow(workbook, sheetName, container) {
|
||
const sheet = workbook.Sheets[sheetName];
|
||
const html = XLSX.utils.sheet_to_html(sheet, { editable: false });
|
||
container.innerHTML = html;
|
||
const table = container.querySelector('table');
|
||
if (table) table.className = 'xlsx-table';
|
||
}
|
||
|
||
/**
|
||
* Render a TIFF file in the preview window using shared zddc.preview.renderTiff
|
||
*/
|
||
async function renderTiffInWindow(file) {
|
||
const container = previewWindow.document.getElementById('previewContent');
|
||
if (!container) return;
|
||
try {
|
||
const blob = await getFileBlob(file);
|
||
const arrayBuffer = await blob.arrayBuffer();
|
||
await zddc.preview.renderTiff(previewWindow.document, container, arrayBuffer, {
|
||
fileName: zddc.joinExtension(file.originalFilename, file.extension)
|
||
});
|
||
} 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 in the preview window using shared zddc.preview.renderZipListing
|
||
*/
|
||
async function renderZipInWindow(file) {
|
||
const container = previewWindow.document.getElementById('previewContent');
|
||
if (!container) return;
|
||
try {
|
||
const blob = await getFileBlob(file);
|
||
const arrayBuffer = await blob.arrayBuffer();
|
||
await zddc.preview.renderZipListing(previewWindow.document, container, arrayBuffer, {
|
||
fileName: zddc.joinExtension(file.originalFilename, file.extension)
|
||
});
|
||
} catch (err) {
|
||
console.error('Error rendering ZIP listing:', err);
|
||
container.innerHTML = `<div class="loading">Error reading ZIP: ${err.message}</div>`;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Escape HTML for safe display
|
||
*/
|
||
function escapeHtml(text) {
|
||
const div = document.createElement('div');
|
||
div.textContent = text;
|
||
return div.innerHTML;
|
||
}
|
||
|
||
/**
|
||
* Download current file
|
||
*/
|
||
async function downloadFile() {
|
||
if (!currentFile) return;
|
||
|
||
try {
|
||
const blob = await getFileBlob(currentFile);
|
||
const url = URL.createObjectURL(blob);
|
||
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = zddc.joinExtension(currentFile.originalFilename, currentFile.extension);
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
document.body.removeChild(a);
|
||
|
||
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
||
} catch (err) {
|
||
console.error('Error downloading file:', err);
|
||
alert('Error downloading file: ' + err.message);
|
||
}
|
||
}
|
||
|
||
// Export module
|
||
window.app.modules.preview = {
|
||
init
|
||
};
|
||
})();
|
||
|
||
/**
|
||
* Column Resize Module
|
||
* Handles resizable table columns
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
let resizingColumn = null;
|
||
let startX = 0;
|
||
let startWidth = 0;
|
||
|
||
/**
|
||
* Initialize column resizing
|
||
*/
|
||
function init() {
|
||
const table = window.app.dom.spreadsheet;
|
||
const headers = table.querySelectorAll('thead th');
|
||
|
||
headers.forEach(th => {
|
||
// Skip if resize handle already exists
|
||
if (th.querySelector('.column-resizer')) return;
|
||
|
||
// Add resize handle
|
||
const resizer = document.createElement('div');
|
||
resizer.className = 'column-resizer';
|
||
th.appendChild(resizer);
|
||
|
||
// Mouse down on resizer
|
||
resizer.addEventListener('mousedown', (e) => {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
|
||
resizingColumn = th;
|
||
startX = e.pageX;
|
||
startWidth = th.offsetWidth;
|
||
|
||
document.addEventListener('mousemove', handleMouseMove);
|
||
document.addEventListener('mouseup', handleMouseUp);
|
||
});
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Handle mouse move during resize
|
||
*/
|
||
function handleMouseMove(e) {
|
||
if (!resizingColumn) return;
|
||
|
||
const diff = e.pageX - startX;
|
||
const newWidth = Math.max(50, startWidth + diff);
|
||
|
||
resizingColumn.style.width = newWidth + 'px';
|
||
resizingColumn.style.minWidth = newWidth + 'px';
|
||
resizingColumn.style.maxWidth = newWidth + 'px';
|
||
}
|
||
|
||
/**
|
||
* Handle mouse up - end resize
|
||
*/
|
||
function handleMouseUp() {
|
||
resizingColumn = null;
|
||
document.removeEventListener('mousemove', handleMouseMove);
|
||
document.removeEventListener('mouseup', handleMouseUp);
|
||
}
|
||
|
||
// Export module
|
||
window.app.modules.resize = {
|
||
init
|
||
};
|
||
})();
|
||
|
||
/**
|
||
* Filter Module
|
||
* Column filter UI: initialises static inputs in thead, wires events.
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
/**
|
||
* Initialize filtering — wire delegated events on thead.
|
||
* Filter inputs already exist in the static template; no dynamic injection needed.
|
||
*/
|
||
function init() {
|
||
const thead = window.app.dom.spreadsheet
|
||
? window.app.dom.spreadsheet.querySelector('thead')
|
||
: document.querySelector('#spreadsheet thead');
|
||
if (!thead) return;
|
||
|
||
thead.addEventListener('input', (e) => {
|
||
if (e.target.matches('.column-filter[data-filter-field]')) {
|
||
e.stopPropagation();
|
||
const field = e.target.getAttribute('data-filter-field');
|
||
const raw = e.target.value.trim();
|
||
const ast = window.zddc.filter.parse(raw);
|
||
window.app.modules.store.setFilter(field, raw, ast);
|
||
}
|
||
});
|
||
|
||
thead.addEventListener('keydown', (e) => {
|
||
if (!e.target.matches('.column-filter[data-filter-field]')) return;
|
||
if (e.key === 'Escape') {
|
||
e.target.value = '';
|
||
const field = e.target.getAttribute('data-filter-field');
|
||
window.app.modules.store.setFilter(field, '', null);
|
||
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();
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Clear all filters — reset inputs and store.
|
||
*/
|
||
function clearFilters() {
|
||
document.querySelectorAll('.column-filter').forEach(input => {
|
||
input.value = '';
|
||
});
|
||
window.app.modules.store.setAllFilters({});
|
||
}
|
||
|
||
window.app.modules.filter = {
|
||
init,
|
||
clearFilters
|
||
};
|
||
})();
|
||
|
||
/**
|
||
* Sort Module
|
||
* Handles multi-column sorting for the spreadsheet
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
// Sort state: array of {column, direction}
|
||
let sortState = [];
|
||
|
||
/**
|
||
* Initialize sorting
|
||
*/
|
||
function init() {
|
||
const table = window.app.dom.spreadsheet;
|
||
const headers = table.querySelectorAll('thead th');
|
||
|
||
headers.forEach((th, index) => {
|
||
// Skip row number column
|
||
if (th.classList.contains('col-row-num')) return;
|
||
|
||
// Skip if already initialized
|
||
if (th.querySelector('.sort-indicator')) return;
|
||
|
||
// Make header clickable
|
||
th.style.cursor = 'pointer';
|
||
th.style.userSelect = 'none';
|
||
|
||
// Add sort indicator container
|
||
const sortIndicator = document.createElement('span');
|
||
sortIndicator.className = 'sort-indicator';
|
||
|
||
// Insert after the text node (before any br or filter)
|
||
const firstChild = th.firstChild;
|
||
if (firstChild && firstChild.nodeType === Node.TEXT_NODE) {
|
||
// Insert after text node
|
||
firstChild.after(sortIndicator);
|
||
} else {
|
||
// Prepend to header
|
||
th.insertBefore(sortIndicator, firstChild);
|
||
}
|
||
|
||
// Click to sort (only add once)
|
||
const handleClick = (e) => {
|
||
// Don't sort if clicking on resizer or filter input
|
||
if (e.target.classList.contains('column-resizer')) return;
|
||
if (e.target.classList.contains('column-filter')) return;
|
||
|
||
const columnName = th.className.replace('col-', '');
|
||
handleSort(columnName, e.ctrlKey || e.metaKey);
|
||
};
|
||
|
||
th.addEventListener('click', handleClick);
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Apply default sort (called after initial render)
|
||
*/
|
||
function applyDefaultSort() {
|
||
const files = window.app.modules.store.getDisplayFiles();
|
||
if (files.length > 0) {
|
||
sortState = [{ column: 'original', direction: 'asc' }];
|
||
applySorts();
|
||
updateSortIndicators();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Handle sort click
|
||
*/
|
||
function handleSort(columnName, multiSort) {
|
||
// Use store to toggle sort
|
||
window.app.modules.store.toggleSort(columnName, multiSort);
|
||
}
|
||
|
||
/**
|
||
* Apply sort to files array (pure function - doesn't mutate)
|
||
*/
|
||
function applySortToFiles(files) {
|
||
if (sortState.length === 0) {
|
||
return files;
|
||
}
|
||
|
||
return [...files].sort((a, b) => {
|
||
for (const sort of sortState) {
|
||
const result = compareValues(a, b, sort.column, sort.direction);
|
||
if (result !== 0) return result;
|
||
}
|
||
return 0;
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Apply all sorts (legacy - triggers render)
|
||
*/
|
||
function applySorts() {
|
||
window.app.modules.spreadsheet.render();
|
||
}
|
||
|
||
/**
|
||
* Apply default sort
|
||
*/
|
||
function applyDefaultSort() {
|
||
const files = window.app.modules.store.getDisplayFiles();
|
||
if (files.length > 0 && sortState.length === 0) {
|
||
sortState = [{ column: 'original', direction: 'asc' }];
|
||
window.app.modules.spreadsheet.render();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Get current sort state
|
||
*/
|
||
function getSortState() {
|
||
return sortState;
|
||
}
|
||
|
||
/**
|
||
* Update sort indicators in headers
|
||
*/
|
||
function updateIndicators() {
|
||
const table = window.app.dom.spreadsheet;
|
||
const headers = table.querySelectorAll('thead th');
|
||
|
||
headers.forEach(th => {
|
||
const indicator = th.querySelector('.sort-indicator');
|
||
if (!indicator) return;
|
||
|
||
const columnName = th.className.replace('col-', '');
|
||
const sortIndex = sortState.findIndex(s => s.column === columnName);
|
||
|
||
if (sortIndex >= 0) {
|
||
const sort = sortState[sortIndex];
|
||
const arrow = sort.direction === 'asc' ? '▲' : '▼';
|
||
const priority = sortState.length > 1 ? (sortIndex + 1) : '';
|
||
indicator.textContent = ` ${arrow}${priority}`;
|
||
indicator.style.display = 'inline';
|
||
} else {
|
||
indicator.textContent = '';
|
||
indicator.style.display = 'none';
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Compare two values for sorting
|
||
*/
|
||
function compareValues(a, b, columnName, direction) {
|
||
let aVal, bVal;
|
||
|
||
// Get values based on column
|
||
switch (columnName) {
|
||
case 'original':
|
||
aVal = a.originalFilename || '';
|
||
bVal = b.originalFilename || '';
|
||
break;
|
||
case 'extension':
|
||
aVal = a.extension || '';
|
||
bVal = b.extension || '';
|
||
break;
|
||
case 'new':
|
||
case 'newFilename':
|
||
aVal = a.manualFilename || window.app.modules.spreadsheet.computeNewFilename(a, 0);
|
||
bVal = b.manualFilename || window.app.modules.spreadsheet.computeNewFilename(b, 0);
|
||
break;
|
||
case 'trackingNumber':
|
||
aVal = a.trackingNumber || '';
|
||
bVal = b.trackingNumber || '';
|
||
break;
|
||
case 'revision':
|
||
aVal = a.revision || '';
|
||
bVal = b.revision || '';
|
||
break;
|
||
case 'status':
|
||
aVal = a.status || '';
|
||
bVal = b.status || '';
|
||
break;
|
||
case 'title':
|
||
aVal = a.title || '';
|
||
bVal = b.title || '';
|
||
break;
|
||
case 'sha256':
|
||
aVal = a.sha256 || '';
|
||
bVal = b.sha256 || '';
|
||
break;
|
||
default:
|
||
return 0;
|
||
}
|
||
|
||
// Natural sort for strings (handles numbers within strings)
|
||
const comparison = aVal.localeCompare(bVal, undefined, { numeric: true, sensitivity: 'base' });
|
||
|
||
return direction === 'asc' ? comparison : -comparison;
|
||
}
|
||
|
||
/**
|
||
* Clear all sorts
|
||
*/
|
||
function clearSorts() {
|
||
sortState = [];
|
||
applySorts();
|
||
}
|
||
|
||
// Export module
|
||
window.app.modules.sort = {
|
||
init,
|
||
applyDefaultSort,
|
||
applySorts,
|
||
applySortToFiles,
|
||
updateIndicators,
|
||
getSortState,
|
||
clearSorts
|
||
};
|
||
})();
|
||
|
||
/**
|
||
* Excel Integration Module
|
||
* Toast notifications and hash export
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
/**
|
||
* Show toast notification
|
||
*/
|
||
function showToast(message, type = 'info') {
|
||
// Remove existing toast
|
||
const existing = document.querySelector('.toast');
|
||
if (existing) {
|
||
existing.remove();
|
||
}
|
||
|
||
// Create toast
|
||
const toast = document.createElement('div');
|
||
toast.className = `toast toast-${type}`;
|
||
toast.textContent = message;
|
||
document.body.appendChild(toast);
|
||
|
||
// Auto-remove after 5 seconds
|
||
setTimeout(() => {
|
||
toast.classList.add('toast-fade');
|
||
setTimeout(() => toast.remove(), 300);
|
||
}, 5000);
|
||
}
|
||
|
||
/**
|
||
* Export SHA256 hashes in sha256sum format
|
||
*/
|
||
async function exportHashes() {
|
||
const files = window.app.modules.store.getDisplayFiles();
|
||
if (files.length === 0) {
|
||
alert('No files to export');
|
||
return;
|
||
}
|
||
|
||
// Check if SHA256 is enabled
|
||
if (!window.app.calculateSha256) {
|
||
alert('Please enable SHA256 checkbox first and wait for hashes to calculate');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// Build sha256sum format: hash *filepath
|
||
const lines = [];
|
||
|
||
// Get root path
|
||
const rootPath = await getFullPath(window.app.rootHandle);
|
||
|
||
for (const file of files) {
|
||
if (!file.sha256 || file.sha256 === 'calculating...' || file.sha256 === 'error') {
|
||
continue; // Skip files without calculated hash
|
||
}
|
||
|
||
// Get full path from root
|
||
const folderPath = await getFullPath(file.folderHandle);
|
||
const fullPath = `${folderPath}/${zddc.joinExtension(file.originalFilename, file.extension)}`;
|
||
|
||
// Format: hash *filepath (asterisk indicates binary mode)
|
||
lines.push(`${file.sha256} *${fullPath}`);
|
||
}
|
||
|
||
if (lines.length === 0) {
|
||
alert('No SHA256 hashes available. Enable SHA256 and wait for calculation to complete.');
|
||
return;
|
||
}
|
||
|
||
// Create output
|
||
const output = lines.join('\n');
|
||
|
||
// Generate filename with timestamp
|
||
const now = new Date();
|
||
const timestamp = now.toISOString().replace(/[:.]/g, '-').slice(0, -5); // YYYY-MM-DDTHH-MM-SS
|
||
const filename = `sha256sums_${timestamp}.txt`;
|
||
|
||
// Download as file
|
||
const blob = new Blob([output], { type: 'text/plain' });
|
||
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);
|
||
|
||
// Show success message
|
||
showToast(`✓ Downloaded ${lines.length} hash(es) to ${filename}`, 'success');
|
||
|
||
} catch (err) {
|
||
console.error('Error exporting hashes:', err);
|
||
alert('Error exporting hashes: ' + err.message);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Get full path from directory handle (all the way to root)
|
||
*/
|
||
async function getFullPath(dirHandle) {
|
||
const parts = [];
|
||
let current = dirHandle;
|
||
|
||
// Walk up to root - collect ALL parent folders
|
||
while (current) {
|
||
parts.unshift(current.name);
|
||
|
||
try {
|
||
// Try to get parent
|
||
if (typeof current.getParent === 'function') {
|
||
const parent = await current.getParent();
|
||
if (parent && parent !== current) {
|
||
current = parent;
|
||
continue;
|
||
}
|
||
}
|
||
break;
|
||
} catch {
|
||
break;
|
||
}
|
||
}
|
||
|
||
return parts.join('/');
|
||
}
|
||
|
||
// Export module
|
||
window.app.modules.excel = {
|
||
showToast,
|
||
exportHashes
|
||
};
|
||
})();
|
||
|
||
/**
|
||
* 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>
|