ZDDC/zddc/internal/apps/embedded/classifier.html

8314 lines
441 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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;
}
/* The refresh ⟳ glyph renders slightly smaller than ◐ / ? — bump to match. */
#refreshHeaderBtn {
font-size: 1.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);
}
/* shared/toast.css — single-toast notification styles paired with
shared/toast.js. Uses BEM-ish .zddc-toast prefix to avoid collisions
with tool-local .toast classes; the old classifier rules can stay
alongside until this file is concatenated above them in the build. */
.zddc-toast {
position: fixed;
bottom: 2rem;
right: 2rem;
background: var(--bg);
color: var(--text);
padding: 0.875rem 1.25rem;
border-radius: var(--radius);
border: 1px solid var(--border);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 9000;
max-width: 400px;
font-size: 0.875rem;
cursor: pointer;
animation: zddc-toast-in 0.3s ease-out;
}
.zddc-toast--success { border-left: 4px solid var(--success); }
.zddc-toast--error { border-left: 4px solid var(--danger); }
.zddc-toast--info { border-left: 4px solid var(--info); }
.zddc-toast--warning { border-left: 4px solid var(--warning); }
.zddc-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; }
}
/* shared/nav.css — lateral project-stage strip paired with shared/nav.js.
Sits as a sibling immediately under .app-header (mounted by JS).
Rendered only in online mode when a project segment is in the URL. */
.zddc-stage-strip {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.3rem 1rem;
background: var(--bg);
border-bottom: 1px solid var(--border);
font-size: 0.8rem;
line-height: 1.3;
flex-shrink: 0;
overflow-x: auto;
white-space: nowrap;
}
.zddc-stage-strip__project {
color: var(--text);
font-weight: 600;
margin-right: 0.15rem;
}
.zddc-stage-strip__divider,
.zddc-stage-strip__sep {
color: var(--text-muted);
user-select: none;
}
.zddc-stage-strip__divider {
margin-right: 0.35rem;
}
.zddc-stage {
color: var(--text-muted);
text-decoration: none;
padding: 0.1rem 0.25rem;
border-radius: var(--radius);
transition: color 0.15s, background 0.15s;
}
.zddc-stage:hover {
color: var(--text);
background: var(--bg-secondary);
text-decoration: none;
}
.zddc-stage--active {
color: var(--primary);
font-weight: 600;
}
.zddc-stage--active:hover {
color: var(--primary);
}
/* 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 come from shared/toast.css (.zddc-toast); the
classifier-local .toast block was promoted there. */
/* 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-10 · iron-pipe-reef</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">&times;</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="&nbsp;",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="&emsp;",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();
}
}());
// shared/toast.js — non-blocking notification helper available to every
// tool via window.zddc.toast(msg, level, opts). Originated as classifier's
// local showToast (classifier/js/excel.js); promoted here so tools that
// today use alert() or silent console.error can switch to a uniform
// non-blocking surface.
//
// Usage:
// window.zddc.toast('Saved.', 'success');
// window.zddc.toast('Could not load: ' + err.message, 'error');
// window.zddc.toast('Note', 'info', { durationMs: 3000 });
//
// Levels: 'info' (default) | 'success' | 'warning' | 'error'.
// Each tool may also expose app.notify(msg, level) as a thin wrapper —
// see ARCHITECTURE.md for the convention.
(function () {
'use strict';
if (!window.zddc) window.zddc = {};
// Don't overwrite if a tool defined its own first.
if (typeof window.zddc.toast === 'function') return;
var DEFAULT_DURATION_MS = 5000;
var FADE_MS = 300;
function toast(message, level, opts) {
opts = opts || {};
var lvl = (level === 'success' || level === 'error' ||
level === 'warning') ? level : 'info';
// Single-toast policy: dismiss any existing toast immediately
// so the new one is always the most recent. Matches the
// classifier's prior behavior and avoids stack-of-toasts UX.
var existing = document.querySelector('.zddc-toast');
if (existing) existing.remove();
var el = document.createElement('div');
el.className = 'zddc-toast zddc-toast--' + lvl;
// ARIA: errors get assertive (interrupts SR queue), others polite.
el.setAttribute('role', lvl === 'error' ? 'alert' : 'status');
el.setAttribute('aria-live', lvl === 'error' ? 'assertive' : 'polite');
el.textContent = message == null ? '' : String(message);
document.body.appendChild(el);
var dur = typeof opts.durationMs === 'number' ?
opts.durationMs : DEFAULT_DURATION_MS;
var timer = setTimeout(function () {
el.classList.add('zddc-toast--fade');
setTimeout(function () {
if (el.parentNode) el.parentNode.removeChild(el);
}, FADE_MS);
}, dur);
// Click-to-dismiss. Useful for sticky errors the user wants gone.
el.addEventListener('click', function () {
clearTimeout(timer);
if (el.parentNode) el.parentNode.removeChild(el);
});
return el;
}
window.zddc.toast = toast;
})();
// shared/nav.js — lateral navigation strip across the four canonical
// project stages (archive · working · staging · reviewing). Renders
// only when:
// 1. location.protocol is http: or https: (online — file:// has no
// project structure to navigate within), AND
// 2. a project segment can be detected from location.pathname (the
// first path segment, when it isn't a tool HTML file).
//
// The strip is inserted as a sibling of <header class="app-header">
// on DOMContentLoaded — no template changes required. Each tool just
// needs ../shared/nav.{js,css} in its build.sh.
//
// Stage URLs follow the canonical workflow folders documented at
// zddc.varasys.io/reference.html#transmittal-workflow:
// archive → <project>/archive.html (archive tool, project-root mode)
// working → <project>/working/ (directory listing → mdedit auto-serves)
// staging → <project>/staging/ (directory listing → transmittal auto-serves)
// reviewing → <project>/reviewing/ (directory listing)
//
// If a deployment doesn't have one of these folders the link will 404 —
// the strip is convention-driven, not probed. Operators on non-standard
// layouts can override by setting window.zddc.nav.disabled = true before
// DOMContentLoaded.
(function () {
'use strict';
if (!window.zddc) window.zddc = {};
if (window.zddc.nav) return; // already loaded
var STAGES = [
{ key: 'archive', label: 'Archive', target: 'archive.html' },
{ key: 'working', label: 'Working', target: 'working/' },
{ key: 'staging', label: 'Staging', target: 'staging/' },
{ key: 'reviewing', label: 'Reviewing', target: 'reviewing/' },
];
function projectSegment(pathname) {
var parts = pathname.split('/').filter(Boolean);
if (parts.length === 0) return null;
var first = parts[0];
// At deployment root (e.g. /archive.html?projects=A,B or
// /index.html) the first segment is a tool HTML — no single
// project to scope the strip to.
if (first.indexOf('.') !== -1) return null;
return first;
}
function currentStage(pathname) {
var parts = pathname.split('/').filter(Boolean);
if (parts.length < 2) return null;
var second = parts[1];
// <project>/working/... | staging/... | reviewing/... | archive/...
for (var i = 0; i < STAGES.length; i++) {
if (second === STAGES[i].key) return STAGES[i].key;
}
// <project>/archive.html → still the archive stage
if (second === 'archive.html') return 'archive';
return null;
}
function shouldRender() {
if (typeof location === 'undefined') return false;
if (location.protocol !== 'http:' && location.protocol !== 'https:') return false;
if (window.zddc.nav && window.zddc.nav.disabled) return false;
return projectSegment(location.pathname) !== null;
}
function buildStrip(project, active) {
var nav = document.createElement('nav');
nav.className = 'zddc-stage-strip';
nav.setAttribute('aria-label', 'Project stage');
var label = document.createElement('span');
label.className = 'zddc-stage-strip__project';
label.textContent = project;
nav.appendChild(label);
var sep0 = document.createElement('span');
sep0.className = 'zddc-stage-strip__divider';
sep0.setAttribute('aria-hidden', 'true');
sep0.textContent = '/';
nav.appendChild(sep0);
for (var i = 0; i < STAGES.length; i++) {
var s = STAGES[i];
var a = document.createElement('a');
a.className = 'zddc-stage';
a.href = '/' + encodeURIComponent(project) + '/' + s.target;
a.textContent = s.label;
if (s.key === active) {
a.classList.add('zddc-stage--active');
a.setAttribute('aria-current', 'page');
}
nav.appendChild(a);
if (i < STAGES.length - 1) {
var sep = document.createElement('span');
sep.className = 'zddc-stage-strip__sep';
sep.setAttribute('aria-hidden', 'true');
sep.textContent = '·';
nav.appendChild(sep);
}
}
return nav;
}
function mount() {
if (!shouldRender()) return;
var header = document.querySelector('.app-header');
if (!header) return;
// Don't double-mount if a tool's main.js calls us a second time.
if (header.previousElementSibling &&
header.previousElementSibling.classList &&
header.previousElementSibling.classList.contains('zddc-stage-strip')) {
return;
}
var project = projectSegment(location.pathname);
var active = currentStage(location.pathname);
var strip = buildStrip(project, active);
// Mount ABOVE the header — the strip is project-level chrome
// (where in the project), the header is tool-level chrome (which
// tool, theme, help). Reading order matches outer-to-inner scope.
header.parentNode.insertBefore(strip, header);
}
// Expose for tests + opt-out.
window.zddc.nav = {
mount: mount,
// Internals visible for unit tests; do not call from tools.
_projectSegment: projectSegment,
_currentStage: currentStage,
_stages: STAGES,
// Set to true before DOMContentLoaded to suppress mounting on
// deployments where the canonical folder layout doesn't apply.
disabled: false,
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', mount, { once: true });
} else {
mount();
}
})();
/**
* 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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}
// ── 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 = '&#128230;'; // 📦
} else if (folder.isVirtualDir) {
icon.innerHTML = '&#128194;'; // 📂
} else {
icon.innerHTML = '&#128193;'; // 📁
}
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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
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';
/**
* Thin wrapper over the shared toast helper. Keeps the
* window.app.modules.excel.showToast call sites in classifier
* unchanged while delegating to the canonical implementation in
* shared/toast.js (window.zddc.toast).
*/
function showToast(message, type = 'info') {
if (window.zddc && typeof window.zddc.toast === 'function') {
window.zddc.toast(message, type);
} else {
// shared/toast.js missing from the build — log so the
// problem is visible without crashing the caller.
console.warn('[classifier] window.zddc.toast unavailable;', type, message);
}
}
/**
* 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>