Generalize USMT Dash and add a containerized demo mode
Remove all environment-specific data and make the dashboard adoptable anywhere, then add a demo that runs the real PowerShell services. - Strip cached device data, internal knowledge-base articles and screenshots, server/site names and the old project notes; replace the knowledge base with generic sample articles and diagrams - Move every environment value into Config/usmt-dash.psd1 - Route all infrastructure access in the worker and Get-Software.ps1 through a provider (Providers/SCCM.ps1), with a config-driven asset inventory view instead of a hard-coded custom class - Add Providers/Demo.ps1: a deterministic simulated fleet whose migrations progress in real time, and Demo/New-DemoData.ps1 to seed it - Server: -Demo makes the API read-only; add get-config and get-logs; serve static files only from web/, Documentation/ and an allowlist of Data/ files with a path-traversal guard; gzip large responses - Mirror both services' console output to Data/Logs via a Write-Host proxy, and add a terminal panel to the dashboard that streams it - Demo sandbox (web/demo/demo.js): visitor changes to tags, articles and images are kept in the visitor's browser and echoed in the terminals; nothing a visitor does reaches the server - Vendor marked, highlight.js and html2pdf.js instead of loading CDNs - Dockerfile and compose file for hosting the demo (Coolify-ready)
This commit is contained in:
commit
1c531bc2ab
56 files changed
+10232
No files matched your search
@@ -0,0 +1,108 @@
|
||||
/* ---------- Terminal dock (docked to the bottom of the page) ---------- */
|
||||
.usmt-term {
|
||||
--usmt-term-h: 340px;
|
||||
position: fixed; left: 0; right: 0; bottom: 0; z-index: 1900;
|
||||
font-family: 'Segoe UI', sans-serif;
|
||||
box-shadow: 0 -4px 16px rgba(0,0,0,0.35);
|
||||
}
|
||||
body { padding-bottom: 40px; }
|
||||
body.usmt-term-open { padding-bottom: calc(var(--usmt-term-h, 340px) + 72px); }
|
||||
|
||||
.usmt-term-resize { height: 5px; cursor: ns-resize; background: transparent; }
|
||||
.usmt-term.collapsed .usmt-term-resize { display: none; }
|
||||
|
||||
.usmt-term-bar {
|
||||
display: flex; align-items: center; gap: 6px; height: 38px; padding: 0 10px;
|
||||
background: #1f2937; color: #d1d5db; border-top: 1px solid #374151; overflow: hidden;
|
||||
}
|
||||
.usmt-term-toggle, .usmt-term-tab, .usmt-term-btn {
|
||||
background: transparent; border: 1px solid transparent; color: #d1d5db; cursor: pointer;
|
||||
font: inherit; font-size: 0.85em; padding: 4px 10px; border-radius: 4px; white-space: nowrap;
|
||||
}
|
||||
.usmt-term-toggle { font-weight: 600; color: #fff; }
|
||||
.usmt-term-toggle:hover, .usmt-term-tab:hover, .usmt-term-btn:hover { background: rgba(255,255,255,0.08); }
|
||||
.usmt-term-chevron { display: inline-block; font-size: 0.75em; transition: transform 0.2s; }
|
||||
.usmt-term:not(.collapsed) .usmt-term-chevron { transform: rotate(180deg); }
|
||||
.usmt-term-tabs { display: flex; gap: 2px; min-width: 0; }
|
||||
.usmt-term-tab { font-family: Consolas, 'Cascadia Mono', 'Courier New', monospace; position: relative; }
|
||||
.usmt-term-tab.active { background: #012456; border-color: #2b4a7a; color: #fff; }
|
||||
.usmt-term-tab.unread::after {
|
||||
content: ''; position: absolute; top: 4px; right: 3px; width: 6px; height: 6px; border-radius: 50%; background: #16c60c;
|
||||
}
|
||||
.usmt-term-live {
|
||||
margin-left: auto; display: inline-flex; align-items: center; gap: 5px;
|
||||
font-size: 0.72em; font-weight: 700; letter-spacing: 1px; color: #86efac; white-space: nowrap;
|
||||
}
|
||||
.usmt-term-live i { width: 7px; height: 7px; border-radius: 50%; background: #16c60c; animation: usmt-pulse 1.6s infinite; }
|
||||
@keyframes usmt-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.25; } }
|
||||
.usmt-term-legend { font-size: 0.75em; color: #9ca3af; white-space: nowrap; cursor: help; }
|
||||
.usmt-term-follow.active { border-color: #4b5563; color: #fff; }
|
||||
|
||||
.usmt-term-window { height: var(--usmt-term-h); display: flex; flex-direction: column; background: #012456; }
|
||||
.usmt-term.collapsed .usmt-term-window { display: none; }
|
||||
.usmt-term-titlebar {
|
||||
height: 26px; flex: 0 0 26px; display: flex; align-items: center; padding: 0 12px;
|
||||
background: #0c0c0c; color: #cccccc; font-size: 0.78em; border-bottom: 1px solid #1b1b1b;
|
||||
}
|
||||
.usmt-term-screens { flex: 1; min-height: 0; position: relative; }
|
||||
.usmt-term-body {
|
||||
display: none; position: absolute; inset: 0; overflow: auto; padding: 8px 12px 14px 18px;
|
||||
font-family: Consolas, 'Cascadia Mono', 'Courier New', monospace; font-size: 13px; line-height: 1.35;
|
||||
color: #cccccc; background: #012456; white-space: pre-wrap; word-break: break-all;
|
||||
}
|
||||
.usmt-term-body.active { display: block; }
|
||||
.usmt-term-line { position: relative; min-height: 1.35em; }
|
||||
.usmt-term-line.sim::before {
|
||||
content: '\25CF'; position: absolute; left: -13px; top: 0; font-size: 9px; line-height: 1.8em; color: #f9f1a5; opacity: 0.6;
|
||||
}
|
||||
|
||||
/* PowerShell ConsoleColor -> Windows Terminal "Campbell Powershell" palette */
|
||||
.c-Default, .c-Gray { color: #cccccc; }
|
||||
.c-Prompt { color: #cccccc; }
|
||||
.c-Black { color: #0c0c0c; }
|
||||
.c-DarkBlue { color: #0037da; }
|
||||
.c-DarkGreen { color: #13a10e; }
|
||||
.c-DarkCyan { color: #3a96dd; }
|
||||
.c-DarkRed { color: #c50f1f; }
|
||||
.c-DarkMagenta { color: #b25fc7; }
|
||||
.c-DarkYellow { color: #c19c00; }
|
||||
.c-DarkGray { color: #8a8a8a; }
|
||||
.c-Blue { color: #3b78ff; }
|
||||
.c-Green { color: #16c60c; }
|
||||
.c-Cyan { color: #61d6d6; }
|
||||
.c-Red { color: #e74856; }
|
||||
.c-Magenta { color: #d65fd0; }
|
||||
.c-Yellow { color: #f9f1a5; }
|
||||
.c-White { color: #f2f2f2; }
|
||||
|
||||
/* ---------- Demo pill + about dialog ---------- */
|
||||
.usmt-demo-pill {
|
||||
background: #f59e0b; color: #1f2937; border: none; border-radius: 999px; cursor: pointer;
|
||||
font-weight: 800; font-size: 0.72em; letter-spacing: 1.5px; padding: 5px 10px; flex-shrink: 0;
|
||||
}
|
||||
.usmt-demo-pill:hover { background: #fbbf24; }
|
||||
|
||||
.usmt-demo-modal {
|
||||
display: none; position: fixed; inset: 0; z-index: 3000; background: rgba(0,0,0,0.6);
|
||||
align-items: center; justify-content: center; padding: 16px;
|
||||
}
|
||||
.usmt-demo-modal.show { display: flex; }
|
||||
.usmt-demo-card {
|
||||
background: var(--bg-card, #fff); color: var(--text-main, #333); max-width: 640px; width: 100%;
|
||||
max-height: calc(100vh - 32px); overflow: auto; border-radius: 10px; padding: 26px 28px;
|
||||
box-shadow: 0 12px 40px rgba(0,0,0,0.45); border-top: 5px solid #f59e0b; line-height: 1.55;
|
||||
}
|
||||
.usmt-demo-card h2 { margin: 0 0 10px; }
|
||||
.usmt-demo-card p { margin: 0 0 12px; font-size: 0.95em; }
|
||||
.usmt-demo-card code { background: rgba(127,127,127,0.18); padding: 1px 5px; border-radius: 3px; font-size: 0.9em; }
|
||||
.usmt-demo-try { color: var(--text-muted, #666); }
|
||||
.usmt-demo-actions { display: flex; gap: 10px; justify-content: flex-end; flex-wrap: wrap; margin-top: 18px; }
|
||||
.usmt-demo-actions button { border: 1px solid var(--border-color, #ddd); background: transparent; color: inherit; padding: 8px 14px; border-radius: 6px; cursor: pointer; font: inherit; font-size: 0.9em; }
|
||||
.usmt-demo-actions .usmt-demo-close { background: #3498db; border-color: #3498db; color: #fff; font-weight: 600; }
|
||||
.usmt-demo-actions .usmt-demo-reset { color: #e74c3c; border-color: #e74c3c; margin-right: auto; }
|
||||
|
||||
@media (max-width: 700px) {
|
||||
.usmt-term-legend, .usmt-term-follow { display: none; }
|
||||
.usmt-term-tab { padding: 4px 6px; font-size: 0.75em; }
|
||||
.usmt-term { --usmt-term-h: 45vh; }
|
||||
}
|
||||
@@ -0,0 +1,971 @@
|
||||
/*
|
||||
* USMT Dash - terminal panel + demo sandbox
|
||||
*
|
||||
* Loaded before the dashboard's own script. It does two things:
|
||||
*
|
||||
* 1. Terminal panel (Live and Demo mode). Streams the real console output of
|
||||
* USMT-Server.ps1 and USMT-Worker.ps1 from the server's /get-logs route.
|
||||
*
|
||||
* 2. Demo sandbox (Demo mode only). The public demo's API is read-only, so every request
|
||||
* that would change something (tracking tags, docs, images, force refresh) is handled
|
||||
* here instead, in the visitor's own browser (IndexedDB). Reads are answered from the
|
||||
* real server merged with the visitor's changes, and the terminals show what the real
|
||||
* services would have printed. Nothing a visitor does is ever sent to the server or
|
||||
* seen by anyone else.
|
||||
*
|
||||
* In Live mode window.fetch is passed straight through untouched.
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const realFetch = window.fetch.bind(window);
|
||||
const configReady = realFetch('/get-config', { cache: 'no-store' })
|
||||
.then(r => (r.ok ? r.json() : null))
|
||||
.catch(() => null)
|
||||
.then(c => c || { Mode: 'Live', Demo: false, Terminals: false });
|
||||
|
||||
// =====================================================================================
|
||||
// Helpers
|
||||
// =====================================================================================
|
||||
const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
const clone = o => JSON.parse(JSON.stringify(o));
|
||||
const jsonResponse = (obj, status = 200) => new Response(JSON.stringify(obj), { status, headers: { 'Content-Type': 'application/json' } });
|
||||
const textResponse = (txt, status = 200, headers = {}) => new Response(txt, { status, headers });
|
||||
const pad2 = n => String(n).padStart(2, '0');
|
||||
|
||||
function psTime(d = new Date()) { // 03:01:43 PM
|
||||
let h = d.getHours(); const ampm = h >= 12 ? 'PM' : 'AM'; h = h % 12 || 12;
|
||||
return `${pad2(h)}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())} ${ampm}`;
|
||||
}
|
||||
function psStamp(d = new Date()) { // 10/06/26 03:01 PM
|
||||
let h = d.getHours(); const ampm = h >= 12 ? 'PM' : 'AM'; h = h % 12 || 12;
|
||||
return `${pad2(d.getMonth() + 1)}/${pad2(d.getDate())}/${String(d.getFullYear()).slice(-2)} ${pad2(h)}:${pad2(d.getMinutes())} ${ampm}`;
|
||||
}
|
||||
|
||||
// Small deterministic hash (FNV-1a + murmur finalizer) for visitor-added devices
|
||||
function hash(str) {
|
||||
let h = 0x811c9dc5;
|
||||
for (const ch of String(str).toUpperCase()) { h ^= ch.charCodeAt(0); h = Math.imul(h, 0x01000193); }
|
||||
h ^= h >>> 16; h = Math.imul(h, 0x85ebca6b); h ^= h >>> 13; h = Math.imul(h, 0xc2b2ae35); h ^= h >>> 16;
|
||||
return h >>> 0;
|
||||
}
|
||||
const rand = (name, salt) => hash(name + '|' + salt) / 4294967296;
|
||||
const ALPHA = '0123456789ABCDEFGHJKLMNPQRSTUVWXYZ';
|
||||
const serial = seed => Array.from({ length: 8 }, (_, i) => ALPHA[Math.floor(rand(seed, 's' + i) * ALPHA.length)]).join('');
|
||||
const hex = (seed, n) => Array.from({ length: n }, (_, i) => Math.floor(rand(seed, 'h' + i) * 256).toString(16).toUpperCase().padStart(2, '0'));
|
||||
|
||||
// =====================================================================================
|
||||
// Terminal panel
|
||||
// =====================================================================================
|
||||
const TERMINALS = [
|
||||
{ id: 'server', title: 'USMT-Server.ps1' },
|
||||
{ id: 'worker', title: 'USMT-Worker.ps1' }
|
||||
];
|
||||
const MAX_LINES = 2500;
|
||||
|
||||
const Term = {
|
||||
el: null, open: false, active: 'worker', demo: false,
|
||||
state: {}, // id -> { lastId, body, hold, held[], unread }
|
||||
timer: null,
|
||||
|
||||
init(demo) {
|
||||
this.demo = demo;
|
||||
TERMINALS.forEach(t => { this.state[t.id] = { lastId: 0, body: null, hold: false, held: [], loaded: false }; });
|
||||
this.build();
|
||||
try { if (localStorage.getItem('usmt_term_open') === '1') this.toggle(true); } catch (e) {}
|
||||
try { const tab = localStorage.getItem('usmt_term_tab'); if (tab && this.state[tab]) this.select(tab); } catch (e) {}
|
||||
this.loop();
|
||||
},
|
||||
|
||||
build() {
|
||||
const dock = document.createElement('div');
|
||||
dock.id = 'usmt-term';
|
||||
dock.className = 'usmt-term collapsed';
|
||||
dock.innerHTML = `
|
||||
<div class="usmt-term-resize" title="Drag to resize"></div>
|
||||
<div class="usmt-term-bar">
|
||||
<button class="usmt-term-toggle" type="button" title="Show / hide terminals">
|
||||
<span class="usmt-term-chevron">▲</span> Terminals
|
||||
</button>
|
||||
<div class="usmt-term-tabs"></div>
|
||||
<span class="usmt-term-live" title="Streaming the real PowerShell console output from the server"><i></i>LIVE</span>
|
||||
${this.demo ? '<span class="usmt-term-legend" title="Lines marked with a dot were produced by your own actions. They are simulated in your browser and never reach the real services.">● your actions (simulated)</span>' : ''}
|
||||
<button class="usmt-term-btn usmt-term-follow active" type="button" title="Auto-scroll">Follow</button>
|
||||
</div>
|
||||
<div class="usmt-term-window">
|
||||
<div class="usmt-term-titlebar"><span class="usmt-term-wtitle"></span></div>
|
||||
<div class="usmt-term-screens"></div>
|
||||
</div>`;
|
||||
document.body.appendChild(dock);
|
||||
this.el = dock;
|
||||
|
||||
const tabs = dock.querySelector('.usmt-term-tabs');
|
||||
const screens = dock.querySelector('.usmt-term-screens');
|
||||
TERMINALS.forEach(t => {
|
||||
const b = document.createElement('button');
|
||||
b.type = 'button'; b.className = 'usmt-term-tab'; b.dataset.id = t.id; b.textContent = t.title;
|
||||
b.onclick = () => { this.select(t.id); if (!this.open) this.toggle(true); };
|
||||
tabs.appendChild(b);
|
||||
const body = document.createElement('div');
|
||||
body.className = 'usmt-term-body'; body.dataset.id = t.id;
|
||||
screens.appendChild(body);
|
||||
this.state[t.id].body = body;
|
||||
});
|
||||
dock.querySelector('.usmt-term-toggle').onclick = () => this.toggle();
|
||||
const follow = dock.querySelector('.usmt-term-follow');
|
||||
follow.onclick = () => { follow.classList.toggle('active'); this.scroll(true); };
|
||||
this.select(this.active);
|
||||
|
||||
// drag-to-resize
|
||||
const handle = dock.querySelector('.usmt-term-resize');
|
||||
handle.addEventListener('mousedown', e => {
|
||||
e.preventDefault();
|
||||
const startY = e.clientY, startH = dock.querySelector('.usmt-term-window').offsetHeight;
|
||||
const move = ev => {
|
||||
const h = Math.max(140, Math.min(window.innerHeight - 140, startH + (startY - ev.clientY)));
|
||||
dock.style.setProperty('--usmt-term-h', h + 'px');
|
||||
};
|
||||
const up = () => { document.removeEventListener('mousemove', move); document.removeEventListener('mouseup', up); };
|
||||
document.addEventListener('mousemove', move); document.addEventListener('mouseup', up);
|
||||
});
|
||||
},
|
||||
|
||||
toggle(force) {
|
||||
this.open = force === undefined ? !this.open : force;
|
||||
this.el.classList.toggle('collapsed', !this.open);
|
||||
document.body.classList.toggle('usmt-term-open', this.open);
|
||||
try { localStorage.setItem('usmt_term_open', this.open ? '1' : '0'); } catch (e) {}
|
||||
if (this.open) { this.poll(this.active); this.scroll(true); }
|
||||
},
|
||||
|
||||
select(id) {
|
||||
this.active = id;
|
||||
this.el.querySelectorAll('.usmt-term-tab').forEach(b => b.classList.toggle('active', b.dataset.id === id));
|
||||
this.el.querySelectorAll('.usmt-term-body').forEach(b => b.classList.toggle('active', b.dataset.id === id));
|
||||
const t = TERMINALS.find(x => x.id === id);
|
||||
this.el.querySelector('.usmt-term-wtitle').textContent = (this.demo ? 'Administrator: PowerShell (demo container) - ' : 'Administrator: PowerShell - ') + t.title;
|
||||
this.el.querySelector(`.usmt-term-tab[data-id="${id}"]`).classList.remove('unread');
|
||||
try { localStorage.setItem('usmt_term_tab', id); } catch (e) {}
|
||||
this.poll(id);
|
||||
this.scroll(true);
|
||||
},
|
||||
|
||||
following() { return this.el.querySelector('.usmt-term-follow').classList.contains('active'); },
|
||||
|
||||
scroll(force) {
|
||||
const body = this.state[this.active].body;
|
||||
if (force || this.following()) body.scrollTop = body.scrollHeight;
|
||||
},
|
||||
|
||||
// segs: [[text, ConsoleColor], ...]
|
||||
render(id, lines, simulated) {
|
||||
const st = this.state[id];
|
||||
const frag = document.createDocumentFragment();
|
||||
for (const ln of lines) {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'usmt-term-line' + (simulated ? ' sim' : '');
|
||||
if (ln.t) row.title = ln.t;
|
||||
const segs = ln.s || [];
|
||||
if (segs.length === 0) row.innerHTML = ' ';
|
||||
for (const [text, color] of segs) {
|
||||
const span = document.createElement('span');
|
||||
span.className = 'c-' + (color || 'Default');
|
||||
span.textContent = text;
|
||||
row.appendChild(span);
|
||||
}
|
||||
frag.appendChild(row);
|
||||
}
|
||||
st.body.appendChild(frag);
|
||||
while (st.body.childNodes.length > MAX_LINES) st.body.removeChild(st.body.firstChild);
|
||||
if (id === this.active && this.open) this.scroll();
|
||||
else if (lines.length) { const tab = this.el.querySelector(`.usmt-term-tab[data-id="${id}"]`); if (tab) tab.classList.add('unread'); }
|
||||
},
|
||||
|
||||
// Simulated output from the visitor's own actions (demo mode)
|
||||
write(id, segs) {
|
||||
if (!this.el) return;
|
||||
this.render(id, [{ s: segs, t: new Date().toLocaleTimeString() }], true);
|
||||
},
|
||||
|
||||
// While a simulated worker cycle is printing, real worker lines are held back so the
|
||||
// two don't interleave mid-cycle; they're flushed when the simulated cycle ends.
|
||||
hold(id, on) {
|
||||
const st = this.state[id];
|
||||
if (!st) return;
|
||||
st.hold = on;
|
||||
if (!on && st.held.length) { this.render(id, st.held, false); st.held = []; }
|
||||
},
|
||||
|
||||
async poll(id) {
|
||||
const st = this.state[id];
|
||||
if (st.busy) return;
|
||||
st.busy = true;
|
||||
try {
|
||||
const r = await realFetch(`/get-logs?source=${id}&after=${st.lastId}&t=${Date.now()}`, { cache: 'no-store' });
|
||||
if (!r.ok) return;
|
||||
const data = await r.json();
|
||||
const lines = (data.lines || []).filter(l => l.i > st.lastId);
|
||||
if (!lines.length) return;
|
||||
st.lastId = lines[lines.length - 1].i;
|
||||
if (!st.loaded) {
|
||||
st.loaded = true;
|
||||
if (lines[0].s && lines[0].s[0] && lines[0].s[0][1] !== 'Prompt') {
|
||||
lines.unshift({ s: [['... (earlier output trimmed) ...', 'DarkGray']] });
|
||||
}
|
||||
}
|
||||
if (st.hold) st.held.push(...lines); else this.render(id, lines, false);
|
||||
} catch (e) {
|
||||
} finally { st.busy = false; }
|
||||
},
|
||||
|
||||
async loop() {
|
||||
let tick = 0;
|
||||
for (;;) {
|
||||
if (this.open) {
|
||||
await this.poll(this.active);
|
||||
if (tick % 3 === 0) for (const t of TERMINALS) if (t.id !== this.active) await this.poll(t.id);
|
||||
}
|
||||
tick++;
|
||||
await sleep(this.open ? 1500 : 4000);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// =====================================================================================
|
||||
// Demo sandbox: per-visitor state in IndexedDB
|
||||
// =====================================================================================
|
||||
const Store = {
|
||||
db: null,
|
||||
state: null,
|
||||
empty() {
|
||||
return {
|
||||
version: 1,
|
||||
tracked: null, // visitor's copy of Tracked-Devices.json (null = untouched)
|
||||
appliedTracked: null, // roster the (simulated) worker last swept
|
||||
rev: 0, // bumps whenever simulated data changes
|
||||
docs: {}, // title -> { content, modified }
|
||||
deletedDocs: [], // server docs the visitor archived
|
||||
images: {}, // name -> { dataUrl, date } (visitor uploads)
|
||||
renamedImages: {}, // new name -> original server name
|
||||
deletedImages: [] // server image names the visitor deleted
|
||||
};
|
||||
},
|
||||
loading: null,
|
||||
load() {
|
||||
if (!this.loading) this.loading = this.open();
|
||||
return this.loading;
|
||||
},
|
||||
async open() {
|
||||
this.state = this.empty();
|
||||
try {
|
||||
this.db = await new Promise((res, rej) => {
|
||||
const req = indexedDB.open('usmt-dash-demo', 1);
|
||||
req.onupgradeneeded = () => req.result.createObjectStore('kv');
|
||||
req.onsuccess = () => res(req.result);
|
||||
req.onerror = () => rej(req.error);
|
||||
});
|
||||
const saved = await new Promise(res => {
|
||||
const tx = this.db.transaction('kv', 'readonly').objectStore('kv').get('state');
|
||||
tx.onsuccess = () => res(tx.result); tx.onerror = () => res(null);
|
||||
});
|
||||
if (saved && saved.version === 1) this.state = Object.assign(this.empty(), saved);
|
||||
} catch (e) { this.db = null; } // private browsing etc. - keep changes in memory only
|
||||
|
||||
// The demo container reseeds its fleet on restart; a roster copied from an older
|
||||
// seed would reference tags that no longer exist, so drop it (docs/images are kept).
|
||||
if (this.state.tracked) {
|
||||
const base = await serverGet('/Tracked-Devices.json', true);
|
||||
if (base && hash(base) !== this.state.baseHash) { this.state.tracked = null; this.state.appliedTracked = null; this.save(); }
|
||||
}
|
||||
return this.state;
|
||||
},
|
||||
save() {
|
||||
if (!this.db) return;
|
||||
try { this.db.transaction('kv', 'readwrite').objectStore('kv').put(this.state, 'state'); } catch (e) {}
|
||||
},
|
||||
async reset() {
|
||||
this.state = this.empty();
|
||||
if (this.db) await new Promise(res => { const tx = this.db.transaction('kv', 'readwrite'); tx.objectStore('kv').delete('state'); tx.oncomplete = res; tx.onerror = res; });
|
||||
},
|
||||
hasChanges() {
|
||||
const s = this.state;
|
||||
return !!(s && (s.tracked || Object.keys(s.docs).length || s.deletedDocs.length || Object.keys(s.images).length || Object.keys(s.renamedImages).length || s.deletedImages.length));
|
||||
}
|
||||
};
|
||||
|
||||
// Short-lived memo of server reads so one dashboard poll doesn't fetch the same file twice
|
||||
const serverMemo = {};
|
||||
async function serverGet(path, asText) {
|
||||
const key = path + (asText ? '#t' : '');
|
||||
const hit = serverMemo[key];
|
||||
if (hit && Date.now() - hit.at < 2500) return hit.promise;
|
||||
const promise = realFetch(path + (path.includes('?') ? '&' : '?') + 't=' + Date.now(), { cache: 'no-store' })
|
||||
.then(r => (r.ok ? (asText ? r.text() : r.json()) : null))
|
||||
.catch(() => null);
|
||||
serverMemo[key] = { at: Date.now(), promise };
|
||||
return promise;
|
||||
}
|
||||
|
||||
// =====================================================================================
|
||||
// Demo sandbox: simulated data for the visitor's roster
|
||||
// =====================================================================================
|
||||
const SCHEME = /^(PRV|PUB)(DT|LT)-[0-9A-Z]{8}$/;
|
||||
|
||||
function parseRoster(tracked) {
|
||||
const tagMap = {}, manualPairs = {}, skipTags = new Set();
|
||||
for (const [tag, entry] of Object.entries(tracked || {})) {
|
||||
const list = Array.isArray(entry) ? entry : (entry.devices || []);
|
||||
if (!Array.isArray(entry) && entry.skipUSMT) skipTags.add(tag);
|
||||
for (const raw of list) {
|
||||
if (!raw || !String(raw).trim()) continue;
|
||||
const d = String(raw).trim().toUpperCase();
|
||||
const m = d.match(/^([^,]+)\s*,\s*([^,]+)$/);
|
||||
const add = n => { (tagMap[n] = tagMap[n] || []).includes(tag) || tagMap[n].push(tag); };
|
||||
if (m) { const s = m[1].trim(), t = m[2].trim(); manualPairs[s] = t; add(s); add(t); }
|
||||
else add(d);
|
||||
}
|
||||
}
|
||||
return { tagMap, manualPairs, skipTags };
|
||||
}
|
||||
|
||||
function blankRow(src, dst, tags) {
|
||||
return {
|
||||
"Source Device": src, "Destination Device": dst, "Tags": tags,
|
||||
"Source Total Space": "N/A", "Source Free Space": "N/A", "USMT Status": "N/A", "Backup Status": "N/A",
|
||||
"Source Online": "N/A", "Destination Online": "N/A",
|
||||
"Source Scanstate Line": "N/A", "Destination Loadstate Line": "N/A",
|
||||
"Source Scanstate File": "N/A", "Destination Loadstate File": "N/A",
|
||||
"Source Scanstate Elapsed": "N/A", "Destination Loadstate Elapsed": "N/A"
|
||||
};
|
||||
}
|
||||
|
||||
// A device the visitor typed that the demo fleet doesn't know: borrow the live status of
|
||||
// a fleet device with the same prefix, so it keeps moving through the stages like the rest.
|
||||
function pickTemplate(name, rows) {
|
||||
const prefix = name.split('-')[0];
|
||||
const pool = rows.filter(r => r["Source Device"].startsWith(prefix + '-') && r["Destination Device"] !== "N/A"
|
||||
&& !["Skipped", "N/A"].includes(r["Backup Status"]) && r["USMT Status"] !== "N/A")
|
||||
.sort((a, b) => a["Source Device"].localeCompare(b["Source Device"]));
|
||||
return pool.length ? pool[hash(name) % pool.length] : null;
|
||||
}
|
||||
const synthDest = name => name.split('-')[0] + '-' + serial(name + '|dest');
|
||||
|
||||
function renameIn(value, from, to) {
|
||||
return typeof value === 'string' ? value.split(from).join(to) : value;
|
||||
}
|
||||
|
||||
function synthAdv(name, templateName, adv) {
|
||||
const base = adv[templateName];
|
||||
if (!base) return null;
|
||||
const a = clone(base);
|
||||
a.MAC = hex(name + 'mac', 6).join(':');
|
||||
if (a.IP && a.IP !== 'N/A') { const p = a.IP.split('.'); a.IP = `${p[0]}.${p[1]}.${10 + Math.floor(rand(name, 'ip3') * 240)}.${10 + Math.floor(rand(name, 'ip4') * 240)}`; }
|
||||
if (a.Disk) a.Disk = a.Disk.replace(/S\/N: [^<]*$/, 'S/N: ' + hex(name + 'sn', 8).join('').replace(/(.{4})/g, '$1_').replace(/_$/, '.'));
|
||||
for (const k of ['LastUser', 'SCCMPrimaryUsers']) if (typeof a[k] === 'string') a[k] = renameIn(a[k], templateName, name).replace(templateName.toLowerCase(), name.toLowerCase());
|
||||
return a;
|
||||
}
|
||||
|
||||
// Builds the Data.json rows the worker would produce for a roster, plus synthetic
|
||||
// advanced/software entries for devices the fleet doesn't know.
|
||||
function buildRows(tracked, serverRows, serverAdv) {
|
||||
const { tagMap, manualPairs, skipTags } = parseRoster(tracked);
|
||||
const bySrc = {};
|
||||
for (const r of serverRows) bySrc[r["Source Device"].toUpperCase()] = r;
|
||||
const manualDests = new Set(Object.values(manualPairs));
|
||||
const out = [], adv = {}, software = {}; // software: name -> template name to copy
|
||||
|
||||
for (const dev of Object.keys(tagMap).sort()) {
|
||||
const tags = tagMap[dev].join(', ');
|
||||
const srow = bySrc[dev];
|
||||
|
||||
if (tagMap[dev].some(t => skipTags.has(t))) {
|
||||
const r = blankRow(dev, "N/A", tags);
|
||||
Object.assign(r, { "USMT Status": "Skipped", "Backup Status": "Skipped", "Source Online": "Skipped", "Destination Online": "-" });
|
||||
out.push(r);
|
||||
if (!serverAdv[dev]) { const t = pickTemplate(dev, serverRows); if (t && SCHEME.test(dev)) { adv[dev] = synthAdv(dev, t["Source Device"], serverAdv); software[dev] = t["Source Device"]; } }
|
||||
continue;
|
||||
}
|
||||
|
||||
if (manualPairs[dev]) {
|
||||
const dst = manualPairs[dev];
|
||||
if (srow && srow["Destination Device"] === dst && srow["USMT Status"] === "N/A") { out.push(Object.assign(clone(srow), { Tags: tags })); continue; }
|
||||
const r = blankRow(dev, dst, tags);
|
||||
for (const [n, col] of [[dev, "Source Online"], [dst, "Destination Online"]]) {
|
||||
const t = SCHEME.test(n) ? pickTemplate(n, serverRows) : null;
|
||||
r[col] = t ? t["Source Online"] : "Not Found";
|
||||
if (t && !serverAdv[n]) { adv[n] = synthAdv(n, t["Source Device"], serverAdv); software[n] = t["Source Device"]; }
|
||||
else if (!t) software[n] = null;
|
||||
}
|
||||
out.push(r);
|
||||
continue;
|
||||
}
|
||||
if (manualDests.has(dev)) continue;
|
||||
|
||||
if (srow && !["Skipped", "N/A"].includes(srow["Backup Status"])) { out.push(Object.assign(clone(srow), { Tags: tags })); continue; }
|
||||
|
||||
const t = SCHEME.test(dev) ? pickTemplate(dev, serverRows) : null;
|
||||
if (!t) {
|
||||
// Not in the fleet and not a valid name: what the worker reports for a typo
|
||||
const r = blankRow(dev, "N/A", tags);
|
||||
Object.assign(r, { "USMT Status": "Not Associated", "Backup Status": "Not Associated", "Source Online": "Not Found", "Destination Online": "-",
|
||||
"Source Scanstate Line": "Unreachable (Not Found)", "Source Scanstate File": "Unreachable" });
|
||||
out.push(r);
|
||||
software[dev] = null; // SCCM has no inventory for it: an empty list, same as Get-Software.ps1
|
||||
continue;
|
||||
}
|
||||
const tSrc = t["Source Device"], tDst = t["Destination Device"], dst = synthDest(dev);
|
||||
const r = clone(t);
|
||||
for (const k of Object.keys(r)) r[k] = renameIn(renameIn(r[k], tSrc, dev), tDst, dst);
|
||||
r.Tags = tags;
|
||||
out.push(r);
|
||||
if (!serverAdv[dev]) adv[dev] = synthAdv(dev, tSrc, serverAdv);
|
||||
if (!serverAdv[dst]) adv[dst] = synthAdv(dst, tDst, serverAdv);
|
||||
software[dev] = tSrc; software[dst] = tDst;
|
||||
}
|
||||
return { rows: out, adv, software };
|
||||
}
|
||||
|
||||
async function serverSnapshot() {
|
||||
const [data, adv] = await Promise.all([serverGet('/Data.json'), serverGet('/Advanced-Cache.json')]);
|
||||
const rows = data && Array.isArray(data.Devices) ? data.Devices : (Array.isArray(data) ? data : []);
|
||||
return { data: data || {}, rows, adv: adv || {} };
|
||||
}
|
||||
|
||||
async function visitorTracked() {
|
||||
const s = Store.state;
|
||||
if (s.tracked) return s.tracked;
|
||||
return (await serverGet('/Tracked-Devices.json')) || {};
|
||||
}
|
||||
|
||||
async function ensureOwnRoster() {
|
||||
const s = Store.state;
|
||||
if (!s.tracked) {
|
||||
const text = (await serverGet('/Tracked-Devices.json', true)) || '{}';
|
||||
const base = JSON.parse(text);
|
||||
s.baseHash = hash(text);
|
||||
s.tracked = clone(base);
|
||||
s.appliedTracked = clone(base);
|
||||
}
|
||||
return s.tracked;
|
||||
}
|
||||
|
||||
// =====================================================================================
|
||||
// Demo sandbox: simulated worker cycle (prints like USMT-Worker.ps1 -v)
|
||||
// =====================================================================================
|
||||
const Sim = { running: false, again: null, worker: null, timer: null };
|
||||
|
||||
function scheduleCycle(kind, delay) {
|
||||
clearTimeout(Sim.timer);
|
||||
Sim.timer = setTimeout(() => runCycle(kind), delay);
|
||||
}
|
||||
|
||||
function pingLine(name, status) {
|
||||
const color = { "Online": "Green", "Timed Out": "DarkYellow" }[status] || "Red";
|
||||
const text = { "Online": "Online", "Timed Out": "Timed Out.", "DNS Issue": "DNS Issue (reverse lookup mismatch).", "Not Found": "Not Found (No IP)." }[status] || "Not Found (No IP).";
|
||||
return [[` > Fast Ping: ${name}... `, 'DarkGray'], [text, color]];
|
||||
}
|
||||
|
||||
function logLines(name, logName, file) {
|
||||
const out = [[[` > Scraping logs for '${logName}'...`, 'DarkGray']]];
|
||||
const folders = ['Windows\\CCM\\Logs\\SMSTSLog', 'Windows\\CCM\\Logs'];
|
||||
for (const f of folders) {
|
||||
const path = `\\\\${name}\\C$\\${f}`;
|
||||
const hit = typeof file === 'string' && file.startsWith(path + '\\');
|
||||
out.push([[` Checking ${path}... `, 'DarkGray'], hit ? ['Found!', 'Green'] : ['Empty/Not there.', 'DarkGray']]);
|
||||
if (hit) break;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function deviceLines(r, isNew, full) {
|
||||
const W = [];
|
||||
const add = (...segs) => W.push(segs);
|
||||
const src = r["Source Device"], dst = r["Destination Device"];
|
||||
if (r["Backup Status"] === "Skipped") {
|
||||
add([' -> [SKIP USMT] Tag marked Skip USMT. Caching Advanced/Software only.', 'DarkMagenta']);
|
||||
if (isNew || full) add([` -> [CACHE MISS / UPDATE] Running AD & SQL lookup for ${src}...`, 'DarkYellow']);
|
||||
return W;
|
||||
}
|
||||
if (r["USMT Status"] === "N/A" && dst !== "N/A") {
|
||||
add([' -> [MANUAL MATCH] Hardware exception detected. Bypassing SCCM...', 'DarkYellow']);
|
||||
W.push(pingLine(src, r["Source Online"]), pingLine(dst, r["Destination Online"]));
|
||||
return W;
|
||||
}
|
||||
add([' -> Querying SCCM for associations...', 'DarkGray']);
|
||||
if (r["Source Online"] === "Cached") {
|
||||
add([' -> SCCM DB Status: Completed (Raw: 2)', 'DarkGray']);
|
||||
add([' -> [CACHE HIT] Fast-tracking row and bypassing network sweeps.', 'Green']);
|
||||
return W;
|
||||
}
|
||||
if (r["USMT Status"] === "Not Associated") {
|
||||
add([' -> No SCCM association found. Sweeping source device anyway...', 'DarkGray']);
|
||||
W.push(pingLine(src, r["Source Online"]));
|
||||
if (r["Source Online"] === "Online") {
|
||||
add([' > Checking C: Drive Storage (SMB Method)... ', 'DarkGray'], [`${(r["Source Free Space"] || '').replace(' GB', 'GB')} Free out of ${(r["Source Total Space"] || '').replace(' GB', 'GB')}`, 'Green']);
|
||||
W.push(...logLines(src, 'scanstateprogress', r["Source Scanstate File"]));
|
||||
}
|
||||
if (isNew || full) add([` -> [CACHE MISS / UPDATE] Running AD & SQL lookup for ${src}...`, 'DarkYellow']);
|
||||
add([` - Status: ${r["Backup Status"]}`, 'Cyan']);
|
||||
return W;
|
||||
}
|
||||
const raw = { "Not Started": 0, "In Progress": 1, "Completed": 2 }[r["USMT Status"]];
|
||||
if (isNew) add([` -> [CACHE MISS] Fetching software for Dest Device ${dst}...`, 'DarkCyan']);
|
||||
add([` -> SCCM DB Status: ${r["USMT Status"]} (Raw: ${raw === undefined ? '?' : raw})`, 'DarkGray']);
|
||||
add([` -> [SWEEP SOURCE] ${src}`, 'DarkCyan']);
|
||||
W.push(pingLine(src, r["Source Online"]));
|
||||
if (r["Source Online"] === "Online") {
|
||||
add([' > Checking C: Drive Storage (SMB Method)... ', 'DarkGray'], [`${(r["Source Free Space"] || '').replace(' GB', 'GB')} Free out of ${(r["Source Total Space"] || '').replace(' GB', 'GB')}`, 'Green']);
|
||||
W.push(...logLines(src, 'scanstateprogress', r["Source Scanstate File"]));
|
||||
}
|
||||
add([` -> [SWEEP DESTINATION] ${dst}`, 'DarkCyan']);
|
||||
W.push(pingLine(dst, r["Destination Online"]));
|
||||
if (r["Destination Online"] === "Online") W.push(...logLines(dst, 'loadstateprogress', r["Destination Loadstate File"]));
|
||||
add([' -> Calculating Master Status...', 'DarkGray']);
|
||||
add([' -> Fetching Advanced SQL/AD Data...', 'DarkGray']);
|
||||
if (isNew || full) {
|
||||
add([` -> [CACHE MISS / UPDATE] Running AD & SQL lookup for ${src}...`, 'DarkYellow']);
|
||||
add([` -> [CACHE MISS / UPDATE] Running AD & SQL lookup for ${dst}...`, 'DarkYellow']);
|
||||
}
|
||||
add([` - Status: ${r["Backup Status"]}`, 'Cyan']);
|
||||
return W;
|
||||
}
|
||||
|
||||
async function runCycle(kind) {
|
||||
if (Sim.running) { Sim.again = kind; return; }
|
||||
Sim.running = true;
|
||||
const full = kind === 'full';
|
||||
const w = segs => Term.write('worker', segs);
|
||||
Term.hold('worker', true);
|
||||
try {
|
||||
const s = Store.state;
|
||||
const tracked = await visitorTracked();
|
||||
const applied = s.appliedTracked || tracked;
|
||||
const snap = await serverSnapshot();
|
||||
const before = new Set(Object.keys(parseRoster(applied).tagMap));
|
||||
const { rows } = buildRows(tracked, snap.rows, snap.adv);
|
||||
const { tagMap } = parseRoster(tracked);
|
||||
const total = Object.keys(tagMap).length;
|
||||
const started = new Date();
|
||||
const state = (status, cur) => { Sim.worker = { Status: status, Current: cur, Total: total }; };
|
||||
|
||||
if (kind === 'force') w([['[!] Force Update triggered by Dashboard!', 'Cyan']]);
|
||||
if (full) w([['[!] Force FULL Refresh triggered by Dashboard!', 'Cyan']]);
|
||||
w([]); w([]);
|
||||
w([[`--- Cycle Start: ${psTime(started)} ---`, 'White']]);
|
||||
state('Preparing...', 0); await sleep(400);
|
||||
w([]);
|
||||
w([['[Checking Software Cache Engine...]', 'Cyan']]);
|
||||
state('Software Check...', 0);
|
||||
if (full) w([['Dashboard Force FULL Refresh. Triggering global software/advanced update.', 'Magenta']]);
|
||||
for (const dev of Object.keys(tagMap).sort()) {
|
||||
if (full || !before.has(dev)) { w([[`Fetching background software for ${dev}...`, 'DarkCyan']]); await sleep(full ? 8 : 60); }
|
||||
}
|
||||
w([['[Software Cache Synchronized]', 'Green']]);
|
||||
w([]);
|
||||
w([[`--- Commencing Data Sweep (${total} Devices) ---`, 'White']]);
|
||||
|
||||
const delay = Math.max(15, Math.min(90, 9000 / Math.max(1, rows.length)));
|
||||
let i = 0;
|
||||
for (const r of rows) {
|
||||
i++;
|
||||
state('Updating Devices...', i);
|
||||
const src = r["Source Device"];
|
||||
w([[`[${i}/${total}] Processing: ${src}`, 'Yellow']]);
|
||||
w([[` -> Tags: [${r.Tags}]`, 'DarkCyan']]);
|
||||
for (const segs of deviceLines(r, !before.has(src), full)) w(segs);
|
||||
await sleep(delay);
|
||||
}
|
||||
|
||||
w([[' -> Pruning and Compressing Software Cache...', 'DarkGray']]);
|
||||
w([[' -> Pruning and Saving Advanced Info Cache...', 'DarkGray']]);
|
||||
w([[' -> Writing payload to Data.json...', 'DarkGray']]);
|
||||
const secs = (new Date() - started) / 1000;
|
||||
w([]);
|
||||
w([[`Cycle Complete in ${secs.toFixed(1)}s.`, 'Gray']]);
|
||||
const next = new Date(Date.now() + 45000);
|
||||
w([[`Next Cycle Scheduled For: ${psStamp(next)}`, 'DarkGray']]);
|
||||
w([]); w([]);
|
||||
|
||||
if (!s.tracked) await ensureOwnRoster();
|
||||
s.appliedTracked = clone(tracked);
|
||||
s.rev++;
|
||||
Store.save();
|
||||
} finally {
|
||||
Sim.worker = null;
|
||||
Sim.running = false;
|
||||
Term.hold('worker', false);
|
||||
}
|
||||
if (Sim.again) { const k = Sim.again; Sim.again = null; scheduleCycle(k, 1500); }
|
||||
}
|
||||
|
||||
// =====================================================================================
|
||||
// Demo sandbox: route handlers
|
||||
// =====================================================================================
|
||||
const srv = (text, color) => Term.write('server', [[text, color]]);
|
||||
const DOC_NAME = /[<>:"/\\|?*]/g;
|
||||
const IMG_TYPES = { png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif', webp: 'image/webp', svg: 'image/svg+xml', bmp: 'image/bmp' };
|
||||
|
||||
async function readBody(init) {
|
||||
try { return JSON.parse((init && init.body) || '{}'); } catch (e) { return {}; }
|
||||
}
|
||||
|
||||
async function serverDocNames() {
|
||||
const list = await serverGet('/get-docs-list');
|
||||
return Array.isArray(list) ? list : (list ? [list] : []);
|
||||
}
|
||||
async function visibleDocNames() {
|
||||
const s = Store.state;
|
||||
const names = new Set((await serverDocNames()).filter(d => !s.deletedDocs.includes(d)));
|
||||
Object.keys(s.docs).forEach(d => names.add(d));
|
||||
return [...names].sort((a, b) => a.localeCompare(b));
|
||||
}
|
||||
async function docContent(title) {
|
||||
const s = Store.state;
|
||||
if (s.docs[title]) return s.docs[title].content;
|
||||
if (s.deletedDocs.includes(title)) return null;
|
||||
const r = await realFetch(`/Documentation/${encodeURIComponent(title)}.md?t=${Date.now()}`);
|
||||
return r.ok ? r.text() : null;
|
||||
}
|
||||
|
||||
async function serverImages() {
|
||||
const list = await serverGet('/list-images');
|
||||
return Array.isArray(list) ? list : (list ? [list] : []);
|
||||
}
|
||||
// name the visitor sees -> what backs it ({ dataUrl } or { server: originalName })
|
||||
async function imageMap() {
|
||||
const s = Store.state;
|
||||
const map = {};
|
||||
const renamedFrom = new Set(Object.values(s.renamedImages));
|
||||
for (const img of await serverImages()) {
|
||||
if (s.deletedImages.includes(img.Name) || renamedFrom.has(img.Name)) continue;
|
||||
map[img.Name] = { server: img.Name, Date: img.Date };
|
||||
}
|
||||
for (const [name, orig] of Object.entries(s.renamedImages)) map[name] = { server: orig, Date: new Date().toISOString() };
|
||||
for (const [name, img] of Object.entries(s.images)) map[name] = { dataUrl: img.dataUrl, Date: img.date };
|
||||
return map;
|
||||
}
|
||||
|
||||
const routes = {
|
||||
// ---- tracking roster ----
|
||||
'GET Tracked-Devices.json': async () => {
|
||||
const s = Store.state;
|
||||
const text = s.tracked ? JSON.stringify(s.tracked, null, 2) : ((await serverGet('/Tracked-Devices.json', true)) || '{}');
|
||||
// Trailing whitespace makes the text differ after a simulated cycle, which is how
|
||||
// the dashboard notices it should reload Data.json.
|
||||
return textResponse(text + ' '.repeat(s.rev), 200, { 'Content-Type': 'application/json' });
|
||||
},
|
||||
'GET get-all-tags': async () => jsonResponse(Object.keys(await visitorTracked())),
|
||||
'GET get-tracked': async url => {
|
||||
const entry = (await visitorTracked())[url.searchParams.get('tech')];
|
||||
if (!entry) return jsonResponse([]);
|
||||
return jsonResponse(Array.isArray(entry) ? entry : (entry.devices || []));
|
||||
},
|
||||
'GET get-tag-meta': async url => {
|
||||
const entry = (await visitorTracked())[url.searchParams.get('tech')];
|
||||
return jsonResponse({ skipUSMT: !!(entry && !Array.isArray(entry) && entry.skipUSMT) });
|
||||
},
|
||||
'POST update-tracked': async (url, init) => {
|
||||
const body = await readBody(init);
|
||||
const tracked = await ensureOwnRoster();
|
||||
const tech = body.tech;
|
||||
const skip = Object.prototype.hasOwnProperty.call(body, 'skipUSMT') ? !!body.skipUSMT : false;
|
||||
if (!body.devices || body.devices.length === 0) delete tracked[tech];
|
||||
else tracked[tech] = { devices: body.devices, skipUSMT: skip };
|
||||
Store.save();
|
||||
srv(`Updated tracking roster for Tech/Tag: ${tech}`, 'Green');
|
||||
scheduleCycle('roster', 2500); // the worker picks the change up on its next sweep
|
||||
return textResponse('', 200);
|
||||
},
|
||||
|
||||
// ---- worker data ----
|
||||
'GET Worker-State.json': async () => {
|
||||
const state = (await serverGet('/Worker-State.json')) || {};
|
||||
if (Sim.worker) return jsonResponse(Object.assign({}, state, Sim.worker));
|
||||
return jsonResponse(state);
|
||||
},
|
||||
'GET Data.json': async () => {
|
||||
const s = Store.state;
|
||||
const snap = await serverSnapshot();
|
||||
if (!s.appliedTracked) return jsonResponse(snap.data);
|
||||
const { rows } = buildRows(s.appliedTracked, snap.rows, snap.adv);
|
||||
return jsonResponse({ LastUpdated: snap.data.LastUpdated || psStamp(), Devices: rows });
|
||||
},
|
||||
'GET Advanced-Cache.json': async () => {
|
||||
const s = Store.state;
|
||||
const snap = await serverSnapshot();
|
||||
if (!s.appliedTracked) return jsonResponse(snap.adv);
|
||||
const { adv } = buildRows(s.appliedTracked, snap.rows, snap.adv);
|
||||
for (const [k, val] of Object.entries(adv)) if (val) snap.adv[k] = val;
|
||||
return jsonResponse(snap.adv);
|
||||
},
|
||||
'GET Software-Cache.json': async () => {
|
||||
const s = Store.state;
|
||||
const cache = (await serverGet('/Software-Cache.json')) || {};
|
||||
if (s.appliedTracked) {
|
||||
const snap = await serverSnapshot();
|
||||
const { software } = buildRows(s.appliedTracked, snap.rows, snap.adv);
|
||||
for (const [name, template] of Object.entries(software)) {
|
||||
if (cache[name]) continue;
|
||||
cache[name] = (template && cache[template]) || { Timestamp: new Date().toISOString(), Data: [] };
|
||||
}
|
||||
}
|
||||
return jsonResponse(cache);
|
||||
},
|
||||
'GET get-software': async url => {
|
||||
const name = (url.searchParams.get('name') || '').toUpperCase();
|
||||
const cache = await (await routes['GET Software-Cache.json']()).json();
|
||||
srv(`Fetching Software for: ${name}`, 'Yellow');
|
||||
const entry = cache[name];
|
||||
return jsonResponse(entry ? (Array.isArray(entry) ? entry : [].concat(entry.Data || [])) : []);
|
||||
},
|
||||
'POST force-update': async () => {
|
||||
srv('Received Force Update Command from Dashboard', 'Cyan');
|
||||
scheduleCycle('force', 800);
|
||||
return textResponse('', 200);
|
||||
},
|
||||
'POST force-full': async () => {
|
||||
srv('Received Force FULL Refresh Command from Dashboard', 'Cyan');
|
||||
scheduleCycle('full', 800);
|
||||
return textResponse('', 200);
|
||||
},
|
||||
|
||||
// ---- knowledge base ----
|
||||
'GET get-docs-list': async () => jsonResponse(await visibleDocNames()),
|
||||
'GET Documentation': async url => {
|
||||
const rel = decodeURIComponent(url.pathname.replace(/^\/Documentation\//, ''));
|
||||
if (rel.startsWith('images/')) return null; // passthrough (handled by the <img> rewriter)
|
||||
if (!rel.endsWith('.md') || rel.includes('/')) return null;
|
||||
const s = Store.state;
|
||||
const title = rel.slice(0, -3);
|
||||
if (s.docs[title]) return textResponse(s.docs[title].content, 200, { 'Content-Type': 'text/markdown', 'Last-Modified': new Date(s.docs[title].modified).toUTCString() });
|
||||
if (s.deletedDocs.includes(title)) return textResponse('', 404);
|
||||
return null;
|
||||
},
|
||||
'POST save-doc': async (url, init) => {
|
||||
const body = await readBody(init);
|
||||
const s = Store.state;
|
||||
const title = String(body.title || '').replace(DOC_NAME, '');
|
||||
const original = String(body.originalTitle || '').replace(DOC_NAME, '');
|
||||
if (!title.trim()) return textResponse('', 400);
|
||||
if (original && original !== title) {
|
||||
delete s.docs[original];
|
||||
if ((await serverDocNames()).includes(original) && !s.deletedDocs.includes(original)) s.deletedDocs.push(original);
|
||||
}
|
||||
s.docs[title] = { content: String(body.content || ''), modified: Date.now() };
|
||||
s.deletedDocs = s.deletedDocs.filter(d => d !== title);
|
||||
Store.save();
|
||||
srv(`Saved documentation: ${title}.md`, 'Green');
|
||||
return textResponse('', 200);
|
||||
},
|
||||
'POST delete-doc': async (url, init) => {
|
||||
const body = await readBody(init);
|
||||
const s = Store.state;
|
||||
const title = String(body.title || '').replace(DOC_NAME, '');
|
||||
if (!title.trim()) return textResponse('', 400);
|
||||
const onServer = (await serverDocNames()).includes(title) && !s.deletedDocs.includes(title);
|
||||
if (!s.docs[title] && !onServer) return textResponse('', 404);
|
||||
delete s.docs[title];
|
||||
if (onServer) s.deletedDocs.push(title);
|
||||
Store.save();
|
||||
srv(`Archived documentation: ${title}.md`, 'Yellow');
|
||||
return textResponse('', 200);
|
||||
},
|
||||
|
||||
// ---- images ----
|
||||
'GET list-images': async () => {
|
||||
const map = await imageMap();
|
||||
return jsonResponse(Object.entries(map).map(([Name, v]) => ({ Name, Date: v.Date })).sort((a, b) => a.Name.localeCompare(b.Name)));
|
||||
},
|
||||
'POST upload-image': async (url, init) => {
|
||||
const body = await readBody(init);
|
||||
const name = String(body.filename || '').replace(DOC_NAME, '');
|
||||
if (!name || !body.data) return textResponse('', 500);
|
||||
const ext = (name.split('.').pop() || '').toLowerCase();
|
||||
Store.state.images[name] = { dataUrl: `data:${IMG_TYPES[ext] || 'application/octet-stream'};base64,${body.data}`, date: new Date().toISOString() };
|
||||
delete Store.state.renamedImages[name];
|
||||
Store.save();
|
||||
ImageRewriter.refresh();
|
||||
srv(`Uploaded Image: ${name}`, 'Cyan');
|
||||
return textResponse('', 200);
|
||||
},
|
||||
'POST rename-image': async (url, init) => {
|
||||
const body = await readBody(init);
|
||||
const s = Store.state;
|
||||
const oldRaw = String(body.oldName || ''), oldName = oldRaw.replace(DOC_NAME, '');
|
||||
const newName = String(body.newName || '').replace(DOC_NAME, '');
|
||||
const map = await imageMap();
|
||||
if (!map[oldName] || !newName) return textResponse('', 404);
|
||||
if (s.images[oldName]) { s.images[newName] = s.images[oldName]; delete s.images[oldName]; }
|
||||
else { const orig = map[oldName].server; delete s.renamedImages[oldName]; s.renamedImages[newName] = orig; }
|
||||
|
||||
// Same markdown crawler as the server: fix links in every article
|
||||
const encOld = encodeURIComponent(oldRaw), encNew = encodeURIComponent(newName);
|
||||
for (const title of await visibleDocNames()) {
|
||||
const content = await docContent(title);
|
||||
if (content == null) continue;
|
||||
let next = content.split('images/' + oldRaw).join('images/' + newName);
|
||||
next = next.split('images/' + encOld).join('images/' + encNew);
|
||||
if (next !== content) {
|
||||
s.docs[title] = { content: next, modified: Date.now() };
|
||||
srv(`Auto-fixed image link in ${title}.md`, 'Magenta');
|
||||
}
|
||||
}
|
||||
Store.save();
|
||||
ImageRewriter.refresh();
|
||||
return textResponse('', 200);
|
||||
},
|
||||
'POST delete-image': async (url, init) => {
|
||||
const body = await readBody(init);
|
||||
const s = Store.state;
|
||||
const name = String(body.filename || '').replace(DOC_NAME, '');
|
||||
const map = await imageMap();
|
||||
if (!map[name]) return textResponse('', 404);
|
||||
if (s.images[name]) delete s.images[name];
|
||||
else if (s.renamedImages[name]) { s.deletedImages.push(s.renamedImages[name]); delete s.renamedImages[name]; }
|
||||
else s.deletedImages.push(name);
|
||||
Store.save();
|
||||
ImageRewriter.refresh();
|
||||
srv(`Deleted Image: ${body.filename}`, 'Yellow');
|
||||
return textResponse('', 200);
|
||||
}
|
||||
};
|
||||
|
||||
function findRoute(url, method) {
|
||||
const path = decodeURIComponent(url.pathname.replace(/^\//, ''));
|
||||
if (routes[`${method} ${path}`]) return routes[`${method} ${path}`];
|
||||
if (method === 'GET' && path.startsWith('Documentation/')) return routes['GET Documentation'];
|
||||
return null;
|
||||
}
|
||||
|
||||
// <img> requests can't be intercepted with fetch, so images the visitor uploaded or
|
||||
// renamed are swapped in as their elements appear.
|
||||
const ImageRewriter = {
|
||||
map: {},
|
||||
async refresh() {
|
||||
this.map = await imageMap();
|
||||
document.querySelectorAll('img').forEach(img => this.fix(img));
|
||||
},
|
||||
fix(img) {
|
||||
const src = img.getAttribute('src') || '';
|
||||
if (img.dataset.usmtFor === src) return; // already rewritten by us
|
||||
const m = src.match(/^\/?Documentation\/images\/([^?#]+)/);
|
||||
if (!m) return;
|
||||
let name; try { name = decodeURIComponent(m[1]); } catch (e) { return; }
|
||||
const entry = this.map[name];
|
||||
let want;
|
||||
if (!entry) want = 'data:,'; // deleted in this sandbox
|
||||
else if (entry.dataUrl) want = entry.dataUrl; // uploaded by this visitor
|
||||
else if (entry.server !== name) want = '/Documentation/images/' + encodeURIComponent(entry.server);
|
||||
if (want) { img.dataset.usmtFor = want; img.setAttribute('src', want); }
|
||||
},
|
||||
start() {
|
||||
this.refresh();
|
||||
new MutationObserver(muts => {
|
||||
for (const m of muts) {
|
||||
if (m.type === 'attributes') this.fix(m.target);
|
||||
else m.addedNodes.forEach(n => {
|
||||
if (n.nodeType !== 1) return;
|
||||
if (n.tagName === 'IMG') this.fix(n); else n.querySelectorAll && n.querySelectorAll('img').forEach(i => this.fix(i));
|
||||
});
|
||||
}
|
||||
}).observe(document.documentElement, { childList: true, subtree: true, attributes: true, attributeFilter: ['src'] });
|
||||
}
|
||||
};
|
||||
|
||||
// =====================================================================================
|
||||
// Demo banner + about dialog
|
||||
// =====================================================================================
|
||||
const Demo = {
|
||||
build() {
|
||||
const controls = document.querySelector('.nav-right-controls');
|
||||
if (controls) {
|
||||
const pill = document.createElement('button');
|
||||
pill.type = 'button'; pill.className = 'usmt-demo-pill'; pill.textContent = 'DEMO';
|
||||
pill.title = 'About this demo';
|
||||
pill.onclick = () => this.about(true);
|
||||
controls.insertBefore(pill, controls.firstChild);
|
||||
}
|
||||
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'usmt-demo-modal';
|
||||
modal.innerHTML = `
|
||||
<div class="usmt-demo-card" role="dialog" aria-labelledby="usmt-demo-title">
|
||||
<h2 id="usmt-demo-title">You're looking at a live demo</h2>
|
||||
<p>This is the real dashboard, served by the real <code>USMT-Server.ps1</code> HttpListener and fed by the real
|
||||
<code>USMT-Worker.ps1</code> polling loop, running in PowerShell 7 in a container. The only difference is the data
|
||||
source: instead of SCCM, Active Directory and SMB, the worker talks to a simulated fleet of 200 devices whose
|
||||
migrations progress in real time.</p>
|
||||
<p><strong>Your changes stay in your browser.</strong> The demo API is read-only. Adding devices, editing articles,
|
||||
uploading images and forcing refreshes are simulated in this browser only, and the terminals show what the
|
||||
services would print. No one else sees your changes, and they're never sent to the server.
|
||||
Run it yourself without <code>-Demo</code> and every action works against the real backend.</p>
|
||||
<p class="usmt-demo-try"><strong>Things to try:</strong> open <em>Terminals</em> at the bottom to watch the worker sweep the
|
||||
fleet · add a device such as <code>PRVDT-7QK4M2XA</code> under <em>Tracked Devices</em> ·
|
||||
click a device for its details · <em>All Devices</em> shows apps missing from replacement machines ·
|
||||
edit an article under <em>Documentation</em>.</p>
|
||||
<div class="usmt-demo-actions">
|
||||
<button type="button" class="usmt-demo-reset">Reset my changes</button>
|
||||
<button type="button" class="usmt-demo-terms">Open terminals</button>
|
||||
<button type="button" class="usmt-demo-close">Explore the dashboard</button>
|
||||
</div>
|
||||
</div>`;
|
||||
document.body.appendChild(modal);
|
||||
modal.addEventListener('click', e => { if (e.target === modal) this.about(false); });
|
||||
modal.querySelector('.usmt-demo-close').onclick = () => this.about(false);
|
||||
modal.querySelector('.usmt-demo-terms').onclick = () => { this.about(false); Term.toggle(true); };
|
||||
modal.querySelector('.usmt-demo-reset').onclick = async () => {
|
||||
await Store.reset();
|
||||
try { ['usmt_workbench_stacks', 'usmt_custom_columns', 'usmt_custom_filters', 'usmt_custom_widths', 'usmt_custom_widths_locked', 'usmt_tech_id'].forEach(k => localStorage.removeItem(k)); } catch (e) {}
|
||||
location.reload();
|
||||
};
|
||||
this.modal = modal;
|
||||
|
||||
let seen = false;
|
||||
try { seen = localStorage.getItem('usmt_demo_seen') === '1'; } catch (e) {}
|
||||
if (!seen) this.about(true);
|
||||
},
|
||||
about(show) {
|
||||
this.modal.classList.toggle('show', show);
|
||||
if (!show) { try { localStorage.setItem('usmt_demo_seen', '1'); } catch (e) {} }
|
||||
}
|
||||
};
|
||||
|
||||
// =====================================================================================
|
||||
// Wire-up
|
||||
// =====================================================================================
|
||||
window.fetch = async function (input, init) {
|
||||
const cfg = await configReady;
|
||||
if (!cfg.Demo) return realFetch(input, init);
|
||||
|
||||
let url;
|
||||
try { url = new URL(typeof input === 'string' ? input : input.url, location.href); } catch (e) { return realFetch(input, init); }
|
||||
if (url.origin !== location.origin) return realFetch(input, init);
|
||||
|
||||
await Store.load();
|
||||
const method = ((init && init.method) || (input && input.method) || 'GET').toUpperCase();
|
||||
const handler = findRoute(url, method);
|
||||
if (handler) {
|
||||
try {
|
||||
const res = await handler(url, init);
|
||||
if (res) return res;
|
||||
} catch (e) {
|
||||
console.error('[demo sandbox]', e);
|
||||
return textResponse('', 500);
|
||||
}
|
||||
}
|
||||
return realFetch(input, init);
|
||||
};
|
||||
|
||||
function onReady(fn) { if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', fn); else fn(); }
|
||||
|
||||
onReady(async () => {
|
||||
const cfg = await configReady;
|
||||
if (cfg.Terminals) Term.init(!!cfg.Demo);
|
||||
if (cfg.Demo) {
|
||||
document.documentElement.classList.add('usmt-demo');
|
||||
await Store.load();
|
||||
Demo.build();
|
||||
ImageRewriter.start();
|
||||
}
|
||||
});
|
||||
})();
|
||||
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 257 KiB |
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 158 KiB |
Executable
+4460
File diff suppressed because it is too large.
Load diff
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2006, Ivan Sagalaev.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+1213
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,39 @@
|
||||
/*! `powershell` grammar compiled for Highlight.js 11.9.0 */
|
||||
(()=>{var e=(()=>{"use strict";return e=>{const n={$pattern:/-?[A-z\.\-]+\b/,
|
||||
keyword:"if else foreach return do while until elseif begin for trap data dynamicparam end break throw param continue finally in switch exit filter try process catch hidden static parameter",
|
||||
built_in:"ac asnp cat cd CFS chdir clc clear clhy cli clp cls clv cnsn compare copy cp cpi cpp curl cvpa dbp del diff dir dnsn ebp echo|0 epal epcsv epsn erase etsn exsn fc fhx fl ft fw gal gbp gc gcb gci gcm gcs gdr gerr ghy gi gin gjb gl gm gmo gp gps gpv group gsn gsnp gsv gtz gu gv gwmi h history icm iex ihy ii ipal ipcsv ipmo ipsn irm ise iwmi iwr kill lp ls man md measure mi mount move mp mv nal ndr ni nmo npssc nsn nv ogv oh popd ps pushd pwd r rbp rcjb rcsn rd rdr ren ri rjb rm rmdir rmo rni rnp rp rsn rsnp rujb rv rvpa rwmi sajb sal saps sasv sbp sc scb select set shcm si sl sleep sls sort sp spjb spps spsv start stz sujb sv swmi tee trcm type wget where wjb write"
|
||||
},s={begin:"`[\\s\\S]",relevance:0},i={className:"variable",variants:[{
|
||||
begin:/\$\B/},{className:"keyword",begin:/\$this/},{begin:/\$[\w\d][\w\d_:]*/}]
|
||||
},a={className:"string",variants:[{begin:/"/,end:/"/},{begin:/@"/,end:/^"@/}],
|
||||
contains:[s,i,{className:"variable",begin:/\$[A-z]/,end:/[^A-z]/}]},t={
|
||||
className:"string",variants:[{begin:/'/,end:/'/},{begin:/@'/,end:/^'@/}]
|
||||
},r=e.inherit(e.COMMENT(null,null),{variants:[{begin:/#/,end:/$/},{begin:/<#/,
|
||||
end:/#>/}],contains:[{className:"doctag",variants:[{
|
||||
begin:/\.(synopsis|description|example|inputs|outputs|notes|link|component|role|functionality)/
|
||||
},{
|
||||
begin:/\.(parameter|forwardhelptargetname|forwardhelpcategory|remotehelprunspace|externalhelp)\s+\S+/
|
||||
}]}]}),c={className:"class",beginKeywords:"class enum",end:/\s*[{]/,
|
||||
excludeEnd:!0,relevance:0,contains:[e.TITLE_MODE]},l={className:"function",
|
||||
begin:/function\s+/,end:/\s*\{|$/,excludeEnd:!0,returnBegin:!0,relevance:0,
|
||||
contains:[{begin:"function",relevance:0,className:"keyword"},{className:"title",
|
||||
begin:/\w[\w\d]*((-)[\w\d]+)*/,relevance:0},{begin:/\(/,end:/\)/,
|
||||
className:"params",relevance:0,contains:[i]}]},o={begin:/using\s/,end:/$/,
|
||||
returnBegin:!0,contains:[a,t,{className:"keyword",
|
||||
begin:/(using|assembly|command|module|namespace|type)/}]},p={
|
||||
className:"function",begin:/\[.*\]\s*[\w]+[ ]??\(/,end:/$/,returnBegin:!0,
|
||||
relevance:0,contains:[{className:"keyword",
|
||||
begin:"(".concat(n.keyword.toString().replace(/\s/g,"|"),")\\b"),endsParent:!0,
|
||||
relevance:0},e.inherit(e.TITLE_MODE,{endsParent:!0})]
|
||||
},g=[p,r,s,e.NUMBER_MODE,a,t,{className:"built_in",variants:[{
|
||||
begin:"(Add|Clear|Close|Copy|Enter|Exit|Find|Format|Get|Hide|Join|Lock|Move|New|Open|Optimize|Pop|Push|Redo|Remove|Rename|Reset|Resize|Search|Select|Set|Show|Skip|Split|Step|Switch|Undo|Unlock|Watch|Backup|Checkpoint|Compare|Compress|Convert|ConvertFrom|ConvertTo|Dismount|Edit|Expand|Export|Group|Import|Initialize|Limit|Merge|Mount|Out|Publish|Restore|Save|Sync|Unpublish|Update|Approve|Assert|Build|Complete|Confirm|Deny|Deploy|Disable|Enable|Install|Invoke|Register|Request|Restart|Resume|Start|Stop|Submit|Suspend|Uninstall|Unregister|Wait|Debug|Measure|Ping|Repair|Resolve|Test|Trace|Connect|Disconnect|Read|Receive|Send|Write|Block|Grant|Protect|Revoke|Unblock|Unprotect|Use|ForEach|Sort|Tee|Where)+(-)[\\w\\d]+"
|
||||
}]},i,{className:"literal",begin:/\$(null|true|false)\b/},{
|
||||
className:"selector-tag",begin:/@\B/,relevance:0}],m={begin:/\[/,end:/\]/,
|
||||
excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[].concat("self",g,{
|
||||
begin:"(string|char|byte|int|long|bool|decimal|single|double|DateTime|xml|array|hashtable|void)",
|
||||
className:"built_in",relevance:0},{className:"type",begin:/[\.\w\d]+/,
|
||||
relevance:0})};return p.contains.unshift(m),{name:"PowerShell",
|
||||
aliases:["pwsh","ps","ps1"],case_insensitive:!0,keywords:n,
|
||||
contains:g.concat(c,l,o,{variants:[{className:"operator",
|
||||
begin:"(-and|-as|-band|-bnot|-bor|-bxor|-casesensitive|-ccontains|-ceq|-cge|-cgt|-cle|-clike|-clt|-cmatch|-cne|-cnotcontains|-cnotlike|-cnotmatch|-contains|-creplace|-csplit|-eq|-exact|-f|-file|-ge|-gt|-icontains|-ieq|-ige|-igt|-ile|-ilike|-ilt|-imatch|-in|-ine|-inotcontains|-inotlike|-inotmatch|-ireplace|-is|-isnot|-isplit|-join|-le|-like|-lt|-match|-ne|-not|-notcontains|-notin|-notlike|-notmatch|-or|-regex|-replace|-shl|-shr|-split|-wildcard|-xor)\\b"
|
||||
},{className:"literal",begin:/(-){1,2}[\w\d-]+/,relevance:0}]},m)}}})()
|
||||
;hljs.registerLanguage("powershell",e)})();
|
||||
@@ -0,0 +1 @@
|
||||
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#abb2bf;background:#282c34}.hljs-comment,.hljs-quote{color:#5c6370;font-style:italic}.hljs-doctag,.hljs-formula,.hljs-keyword{color:#c678dd}.hljs-deletion,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-subst{color:#e06c75}.hljs-literal{color:#56b6c2}.hljs-addition,.hljs-attribute,.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#98c379}.hljs-attr,.hljs-number,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-pseudo,.hljs-template-variable,.hljs-type,.hljs-variable{color:#d19a66}.hljs-bullet,.hljs-link,.hljs-meta,.hljs-selector-id,.hljs-symbol,.hljs-title{color:#61aeee}.hljs-built_in,.hljs-class .hljs-title,.hljs-title.class_{color:#e6c07b}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-link{text-decoration:underline}
|
||||
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2017 Erik Koopmans
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
/*! For license information please see html2pdf.bundle.min.js.LICENSE.txt */
|
||||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("html2pdf",[],t):"object"==typeof exports?exports.html2pdf=t():e.html2pdf=t()}(self,(function(){return function(){var e,t,r={"./node_modules/@babel/runtime-corejs3/core-js-stable/array/from.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/array/from.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/array/is-array.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/array/is-array.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/date/now.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/date/now.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/instance/bind.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/instance/bind.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/instance/concat.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/instance/concat.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/instance/every.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/instance/every.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/instance/fill.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/instance/fill.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/instance/filter.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/instance/filter.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/instance/for-each.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/instance/for-each.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/instance/includes.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/instance/includes.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/instance/index-of.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/instance/index-of.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/instance/map.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/instance/map.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/instance/reduce.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/instance/reduce.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/instance/reverse.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/instance/reverse.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/instance/slice.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/instance/slice.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/instance/some.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/instance/some.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/instance/starts-with.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/instance/starts-with.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/instance/trim.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/instance/trim.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/instance/values.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/instance/values.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/map.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/map/index.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/object/define-properties.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/object/define-properties.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/object/define-property.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/object/define-property.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/object/get-own-property-descriptor.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/object/get-own-property-descriptor.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/object/get-own-property-descriptors.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/object/get-own-property-descriptors.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/object/get-own-property-symbols.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/object/get-own-property-symbols.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/object/keys.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/object/keys.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/parse-float.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/parse-float.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/parse-int.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/parse-int.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/promise.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/promise/index.js")},"./node_modules/@babel/runtime-corejs3/core-js-Line truncated
|
||||
//# sourceMappingURL=html2pdf.bundle.min.js.map
|
||||
Vendored
+44
@@ -0,0 +1,44 @@
|
||||
# License information
|
||||
|
||||
## Contribution License Agreement
|
||||
|
||||
If you contribute code to this project, you are implicitly allowing your code
|
||||
to be distributed under the MIT license. You are also implicitly verifying that
|
||||
all code is your original work. `</legalese>`
|
||||
|
||||
## Marked
|
||||
|
||||
Copyright (c) 2018+, MarkedJS (https://github.com/markedjs/)
|
||||
Copyright (c) 2011-2018, Christopher Jeffrey (https://github.com/chjj/)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
## Markdown
|
||||
|
||||
Copyright © 2004, John Gruber
|
||||
http://daringfireball.net/
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
* Neither the name “Markdown” nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
This software is provided by the copyright holders and contributors “as is” and any express or implied warranties, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose are disclaimed. In no event shall the copyright owner or contributors be liable for any direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this software, even if advised of the possibility of such damage.
|
||||
Vendored
+69
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* marked v15.0.12 - a markdown parser
|
||||
* Copyright (c) 2011-2025, Christopher Jeffrey. (MIT Licensed)
|
||||
* https://github.com/markedjs/marked
|
||||
*/
|
||||
|
||||
/**
|
||||
* DO NOT EDIT THIS FILE
|
||||
* The code in this file is generated from files in ./src/
|
||||
*/
|
||||
(function(g,f){if(typeof exports=="object"&&typeof module<"u"){module.exports=f()}else if("function"==typeof define && define.amd){define("marked",f)}else {g["marked"]=f()}}(typeof globalThis < "u" ? globalThis : typeof self < "u" ? self : this,function(){var exports={};var __exports=exports;var module={exports};
|
||||
"use strict";var H=Object.defineProperty;var be=Object.getOwnPropertyDescriptor;var Te=Object.getOwnPropertyNames;var we=Object.prototype.hasOwnProperty;var ye=(l,e)=>{for(var t in e)H(l,t,{get:e[t],enumerable:!0})},Re=(l,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of Te(e))!we.call(l,s)&&s!==t&&H(l,s,{get:()=>e[s],enumerable:!(n=be(e,s))||n.enumerable});return l};var Se=l=>Re(H({},"__esModule",{value:!0}),l);var kt={};ye(kt,{Hooks:()=>L,Lexer:()=>x,Marked:()=>E,Parser:()=>b,Renderer:()=>$,TextRenderer:()=>_,Tokenizer:()=>S,defaults:()=>w,getDefaults:()=>z,lexer:()=>ht,marked:()=>k,options:()=>it,parse:()=>pt,parseInline:()=>ct,parser:()=>ut,setOptions:()=>ot,use:()=>lt,walkTokens:()=>at});module.exports=Se(kt);function z(){return{async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null}}var w=z();function N(l){w=l}var I={exec:()=>null};function h(l,e=""){let t=typeof l=="string"?l:l.source,n={replace:(s,i)=>{let r=typeof i=="string"?i:i.source;return r=r.replace(m.caret,"$1"),t=t.replace(s,r),n},getRegex:()=>new RegExp(t,e)};return n}var m={codeRemoveIndent:/^(?: {1,4}| {0,3}\t)/gm,outputLinkReplace:/\\([\[\]])/g,indentCodeCompensation:/^(\s+)(?:```)/,beginningSpace:/^\s+/,endingHash:/#$/,startingSpaceChar:/^ /,endingSpaceChar:/ $/,nonSpaceChar:/[^ ]/,newLineCharGlobal:/\n/g,tabCharGlobal:/\t/g,multipleSpaceGlobal:/\s+/g,blankLine:/^[ \t]*$/,doubleBlankLine:/\n[ \t]*\n[ \t]*$/,blockquoteStart:/^ {0,3}>/,blockquoteSetextReplace:/\n {0,3}((?:=+|-+) *)(?=\n|$)/g,blockquoteSetextReplace2:/^ {0,3}>[ \t]?/gm,listReplaceTabs:/^\t+/,listReplaceNesting:/^ {1,4}(?=( {4})*[^ ])/g,listIsTask:/^\[[ xX]\] /,listReplaceTask:/^\[[ xX]\] +/,anyLine:/\n.*\n/,hrefBrackets:/^<(.*)>$/,tableDelimiter:/[:|]/,tableAlignChars:/^\||\| *$/g,tableRowBlankLine:/\n[ \t]*$/,tableAlignRight:/^ *-+: *$/,tableAlignCenter:/^ *:-+: *$/,tableAlignLeft:/^ *:-+ *$/,startATag:/^<a /i,endATag:/^<\/a>/i,startPreScriptTag:/^<(pre|code|kbd|script)(\s|>)/i,endPreScriptTag:/^<\/(pre|code|kbd|script)(\s|>)/i,startAngleBracket:/^</,endAngleBracket:/>$/,pedanticHrefTitle:/^([^'"]*[^\s])\s+(['"])(.*)\2/,unicodeAlphaNumeric:/[\p{L}\p{N}]/u,escapeTest:/[&<>"']/,escapeReplace:/[&<>"']/g,escapeTestNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,escapeReplaceNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g,unescapeTest:/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig,caret:/(^|[^\[])\^/g,percentDecode:/%25/g,findPipe:/\|/g,splitPipe:/ \|/,slashPipe:/\\\|/g,carriageReturn:/\r\n|\r/g,spaceLine:/^ +$/gm,notSpaceStart:/^\S*/,endingNewline:/\n$/,listItemRegex:l=>new RegExp(`^( {0,3}${l})((?:[ ][^\\n]*)?(?:\\n|$))`),nextBulletRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ ][^\\n]*)?(?:\\n|$))`),hrRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`),fencesBeginRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}(?:\`\`\`|~~~)`),headingBeginRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}#`),htmlBeginRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}<(?:[a-z].*>|!--)`,"i")},$e=/^(?:[ \t]*(?:\n|$))+/,_e=/^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/,Le=/^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/,O=/^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/,ze=/^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,F=/(?:[*+-]|\d{1,9}[.)])/,ie=/^(?!bull |blockCode|fences|blockquote|heading|html|table)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html|table))+?)\n {0,3}(=+|-+) *(?:\n+|$)/,oe=h(ie).replace(/bull/g,F).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).replace(/\|table/g,"").getRegex(),Me=h(ie).replace(/bull/g,F).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).replace(/table/g,/ {0,3}\|?(?:[:\- ]*\|)+[\:\- ]*\n/).getRegex(),Q=/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/,Pe=/^[^\n]+/,U=/(?!\s*\])(?:\\.|[^\[\]\\])+/,Ae=h(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label",U).replace("title",/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex(),Ee=h(/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/).replace(/bull/g,F).getRegex(),v="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",K=/<!--(?:-?>|[\s\S]*?(?:-->|$))/,Ce=h("^ {Line truncated
|
||||
]`).replace("lheading",oe).replace("|table","").replace("blockquote"," {0,3}>").replace("|fences","").replace("|list","").replace("|html","").replace("|tag","").getRegex()},qe=/^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,ve=/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,ae=/^( {2,}|\\)\n(?!\s*$)/,De=/^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*_]|\b_|$)|[^ ](?= {2,}\n)))/,D=/[\p{P}\p{S}]/u,W=/[\s\p{P}\p{S}]/u,ce=/[^\s\p{P}\p{S}]/u,Ze=h(/^((?![*_])punctSpace)/,"u").replace(/punctSpace/g,W).getRegex(),pe=/(?!~)[\p{P}\p{S}]/u,Ge=/(?!~)[\s\p{P}\p{S}]/u,He=/(?:[^\s\p{P}\p{S}]|~)/u,Ne=/\[[^[\]]*?\]\((?:\\.|[^\\\(\)]|\((?:\\.|[^\\\(\)])*\))*\)|`[^`]*?`|<[^<>]*?>/g,ue=/^(?:\*+(?:((?!\*)punct)|[^\s*]))|^_+(?:((?!_)punct)|([^\s_]))/,je=h(ue,"u").replace(/punct/g,D).getRegex(),Fe=h(ue,"u").replace(/punct/g,pe).getRegex(),he="^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)punct(\\*+)(?=[\\s]|$)|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)|(?!\\*)punctSpace(\\*+)(?=notPunctSpace)|[\\s](\\*+)(?!\\*)(?=punct)|(?!\\*)punct(\\*+)(?!\\*)(?=punct)|notPunctSpace(\\*+)(?=notPunctSpace)",Qe=h(he,"gu").replace(/notPunctSpace/g,ce).replace(/punctSpace/g,W).replace(/punct/g,D).getRegex(),Ue=h(he,"gu").replace(/notPunctSpace/g,He).replace(/punctSpace/g,Ge).replace(/punct/g,pe).getRegex(),Ke=h("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)punct(_+)(?=[\\s]|$)|notPunctSpace(_+)(?!_)(?=punctSpace|$)|(?!_)punctSpace(_+)(?=notPunctSpace)|[\\s](_+)(?!_)(?=punct)|(?!_)punct(_+)(?!_)(?=punct)","gu").replace(/notPunctSpace/g,ce).replace(/punctSpace/g,W).replace(/punct/g,D).getRegex(),Xe=h(/\\(punct)/,"gu").replace(/punct/g,D).getRegex(),We=h(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme",/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email",/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex(),Je=h(K).replace("(?:-->|$)","-->").getRegex(),Ve=h("^comment|^</[a-zA-Z][\\w:-]*\\s*>|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^<![a-zA-Z]+\\s[\\s\\S]*?>|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>").replace("comment",Je).replace("attribute",/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex(),q=/(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/,Ye=h(/^!?\[(label)\]\(\s*(href)(?:(?:[ \t]*(?:\n[ \t]*)?)(title))?\s*\)/).replace("label",q).replace("href",/<(?:\\.|[^\n<>\\])+>|[^ \t\n\x00-\x1f]*/).replace("title",/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex(),ke=h(/^!?\[(label)\]\[(ref)\]/).replace("label",q).replace("ref",U).getRegex(),ge=h(/^!?\[(ref)\](?:\[\])?/).replace("ref",U).getRegex(),et=h("reflink|nolink(?!\\()","g").replace("reflink",ke).replace("nolink",ge).getRegex(),J={_backpedal:I,anyPunctuation:Xe,autolink:We,blockSkip:Ne,br:ae,code:ve,del:I,emStrongLDelim:je,emStrongRDelimAst:Qe,emStrongRDelimUnd:Ke,escape:qe,link:Ye,nolink:ge,punctuation:Ze,reflink:ke,reflinkSearch:et,tag:Ve,text:De,url:I},tt={...J,link:h(/^!?\[(label)\]\((.*?)\)/).replace("label",q).getRegex(),reflink:h(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label",q).getRegex()},j={...J,emStrongRDelimAst:Ue,emStrongLDelim:Fe,url:h(/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/,"i").replace("email",/[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/).getRegex(),_backpedal:/(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/,del:/^(~~?)(?=[^\s~])((?:\\.|[^\\])*?(?:\\.|[^\s~\\]))\1(?=[^~]|$)/,text:/^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\<!\[`*~_]|\b_|https?:\/\/|ftp:\/\/|www\.|$)|[^ ](?= {2,}\n)|[^a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-](?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)))/},nt={...j,br:h(ae).replace("{2,}","*").getRegex(),text:h(j.text).replace("\\b_","\\b_| {2,}\\n").replace(/\{2,\}/g,"*").getRegex()},B={normal:X,gfm:Oe,pedantic:Be},P={normal:J,gfm:j,breaks:nt,pedantic:tt};var st={"&":"&","<":"<",">":">",'"':""","'":"'"},fe=l=>st[l];function R(l,e){if(e){if(m.escapeTest.test(l))return l.replace(m.escapeReplace,fe)}else if(m.escapeTestNoEncode.test(l))return l.replace(m.escapeReplaceNoEncode,fe);return l}function V(l){try{l=encodeURI(l).replace(m.percentDecode,"%")}catch{return null}return l}function Y(l,e){let t=l.replace(m.findPipe,(i,r,o)=>{let a=!1,c=r;for(;--c>=0&&o[c]==="\\";)a=!a;return a?"|":" |"}),n=t.split(m.splitPipe),s=0;if(n[0].trim()||n.shift(),n.length>0&&!n.at(-1)?.trim()&&n.pop(),e)if(n.length>e)n.splice(e);else for(;n.length<e;)n.push("");for(;s<n.length;s++)n[s]=n[s].trim().replace(m.slashPipe,"|");return n}function A(l,e,t){let n=l.length;if(n===0)return"";let s=0;for(;s<n;){let i=l.charAt(n-s-1);if(i===e&&!t)s++;else if(i!==e&&t)s++;else break}return l.slice(0,n-s)}function de(l,e){if(l.indexOf(e[1])===-1)return-1;let t=0;for(let n=0;n<l.length;n++)if(l[n]==="\\")n++;else if(l[n]===e[0])t++;else if(l[n]===e[1]&&(t--,t<0))return n;return t>0?-2:-1}fLine truncated
|
||||
`).map(i=>{let r=i.match(t.other.beginningSpace);if(r===null)return i;let[o]=r;return o.length>=s.length?i.slice(s.length):i}).join(`
|
||||
`)}var S=class{options;rules;lexer;constructor(e){this.options=e||w}space(e){let t=this.rules.block.newline.exec(e);if(t&&t[0].length>0)return{type:"space",raw:t[0]}}code(e){let t=this.rules.block.code.exec(e);if(t){let n=t[0].replace(this.rules.other.codeRemoveIndent,"");return{type:"code",raw:t[0],codeBlockStyle:"indented",text:this.options.pedantic?n:A(n,`
|
||||
`)}}}fences(e){let t=this.rules.block.fences.exec(e);if(t){let n=t[0],s=rt(n,t[3]||"",this.rules);return{type:"code",raw:n,lang:t[2]?t[2].trim().replace(this.rules.inline.anyPunctuation,"$1"):t[2],text:s}}}heading(e){let t=this.rules.block.heading.exec(e);if(t){let n=t[2].trim();if(this.rules.other.endingHash.test(n)){let s=A(n,"#");(this.options.pedantic||!s||this.rules.other.endingSpaceChar.test(s))&&(n=s.trim())}return{type:"heading",raw:t[0],depth:t[1].length,text:n,tokens:this.lexer.inline(n)}}}hr(e){let t=this.rules.block.hr.exec(e);if(t)return{type:"hr",raw:A(t[0],`
|
||||
`)}}blockquote(e){let t=this.rules.block.blockquote.exec(e);if(t){let n=A(t[0],`
|
||||
`).split(`
|
||||
`),s="",i="",r=[];for(;n.length>0;){let o=!1,a=[],c;for(c=0;c<n.length;c++)if(this.rules.other.blockquoteStart.test(n[c]))a.push(n[c]),o=!0;else if(!o)a.push(n[c]);else break;n=n.slice(c);let p=a.join(`
|
||||
`),u=p.replace(this.rules.other.blockquoteSetextReplace,`
|
||||
$1`).replace(this.rules.other.blockquoteSetextReplace2,"");s=s?`${s}
|
||||
${p}`:p,i=i?`${i}
|
||||
${u}`:u;let d=this.lexer.state.top;if(this.lexer.state.top=!0,this.lexer.blockTokens(u,r,!0),this.lexer.state.top=d,n.length===0)break;let g=r.at(-1);if(g?.type==="code")break;if(g?.type==="blockquote"){let T=g,f=T.raw+`
|
||||
`+n.join(`
|
||||
`),y=this.blockquote(f);r[r.length-1]=y,s=s.substring(0,s.length-T.raw.length)+y.raw,i=i.substring(0,i.length-T.text.length)+y.text;break}else if(g?.type==="list"){let T=g,f=T.raw+`
|
||||
`+n.join(`
|
||||
`),y=this.list(f);r[r.length-1]=y,s=s.substring(0,s.length-g.raw.length)+y.raw,i=i.substring(0,i.length-T.raw.length)+y.raw,n=f.substring(r.at(-1).raw.length).split(`
|
||||
`);continue}}return{type:"blockquote",raw:s,tokens:r,text:i}}}list(e){let t=this.rules.block.list.exec(e);if(t){let n=t[1].trim(),s=n.length>1,i={type:"list",raw:"",ordered:s,start:s?+n.slice(0,-1):"",loose:!1,items:[]};n=s?`\\d{1,9}\\${n.slice(-1)}`:`\\${n}`,this.options.pedantic&&(n=s?n:"[*+-]");let r=this.rules.other.listItemRegex(n),o=!1;for(;e;){let c=!1,p="",u="";if(!(t=r.exec(e))||this.rules.block.hr.test(e))break;p=t[0],e=e.substring(p.length);let d=t[2].split(`
|
||||
`,1)[0].replace(this.rules.other.listReplaceTabs,Z=>" ".repeat(3*Z.length)),g=e.split(`
|
||||
`,1)[0],T=!d.trim(),f=0;if(this.options.pedantic?(f=2,u=d.trimStart()):T?f=t[1].length+1:(f=t[2].search(this.rules.other.nonSpaceChar),f=f>4?1:f,u=d.slice(f),f+=t[1].length),T&&this.rules.other.blankLine.test(g)&&(p+=g+`
|
||||
`,e=e.substring(g.length+1),c=!0),!c){let Z=this.rules.other.nextBulletRegex(f),te=this.rules.other.hrRegex(f),ne=this.rules.other.fencesBeginRegex(f),se=this.rules.other.headingBeginRegex(f),xe=this.rules.other.htmlBeginRegex(f);for(;e;){let G=e.split(`
|
||||
`,1)[0],C;if(g=G,this.options.pedantic?(g=g.replace(this.rules.other.listReplaceNesting," "),C=g):C=g.replace(this.rules.other.tabCharGlobal," "),ne.test(g)||se.test(g)||xe.test(g)||Z.test(g)||te.test(g))break;if(C.search(this.rules.other.nonSpaceChar)>=f||!g.trim())u+=`
|
||||
`+C.slice(f);else{if(T||d.replace(this.rules.other.tabCharGlobal," ").search(this.rules.other.nonSpaceChar)>=4||ne.test(d)||se.test(d)||te.test(d))break;u+=`
|
||||
`+g}!T&&!g.trim()&&(T=!0),p+=G+`
|
||||
`,e=e.substring(G.length+1),d=C.slice(f)}}i.loose||(o?i.loose=!0:this.rules.other.doubleBlankLine.test(p)&&(o=!0));let y=null,ee;this.options.gfm&&(y=this.rules.other.listIsTask.exec(u),y&&(ee=y[0]!=="[ ] ",u=u.replace(this.rules.other.listReplaceTask,""))),i.items.push({type:"list_item",raw:p,task:!!y,checked:ee,loose:!1,text:u,tokens:[]}),i.raw+=p}let a=i.items.at(-1);if(a)a.raw=a.raw.trimEnd(),a.text=a.text.trimEnd();else return;i.raw=i.raw.trimEnd();for(let c=0;c<i.items.length;c++)if(this.lexer.state.top=!1,i.items[c].tokens=this.lexer.blockTokens(i.items[c].text,[]),!i.loose){let p=i.items[c].tokens.filter(d=>d.type==="space"),u=p.length>0&&p.some(d=>this.rules.other.anyLine.test(d.raw));i.loose=u}if(i.loose)for(let c=0;c<i.items.length;c++)i.items[c].loose=!0;return i}}html(e){let t=this.rules.block.html.exec(e);if(t)return{type:"html",block:!0,raw:t[0],pre:t[1]==="pre"||t[1]==="script"||t[1]==="style",text:t[0]}}def(e){let t=this.rules.block.def.exec(e);if(t){let n=t[1].toLowerCase().replace(this.rules.other.multipleSpaceGlobal," "),s=t[2]?t[2].replace(this.rules.other.hrefBrackets,"$1").replace(this.rules.inline.anyPunctuation,"$1"):"",i=t[3]?t[3].substring(1,t[3].length-1).replace(this.rules.inline.anyPunctuation,"$1"):t[3];return{type:"def",tag:n,raw:t[0],href:s,title:i}}}table(e){let t=this.rules.block.table.exec(e);if(!t||!this.rules.other.tableDelimiter.test(t[2]))return;let n=Y(t[1]),s=t[2].replace(this.rules.other.tableAlignChars,"").split("|"),i=t[3]?.trim()?t[3].replace(this.rules.other.tableRowBlankLine,"").split(`
|
||||
`):[],r={type:"table",raw:t[0],header:[],align:[],rows:[]};if(n.length===s.length){for(let o of s)this.rules.other.tableAlignRight.test(o)?r.align.push("right"):this.rules.other.tableAlignCenter.test(o)?r.align.push("center"):this.rules.other.tableAlignLeft.test(o)?r.align.push("left"):r.align.push(null);for(let o=0;o<n.length;o++)r.header.push({text:n[o],tokens:this.lexer.inline(n[o]),header:!0,align:r.align[o]});for(let o of i)r.rows.push(Y(o,r.header.length).map((a,c)=>({text:a,tokens:this.lexer.inline(a),header:!1,align:r.align[c]})));return r}}lheading(e){let t=this.rules.block.lheading.exec(e);if(t)return{type:"heading",raw:t[0],depth:t[2].charAt(0)==="="?1:2,text:t[1],tokens:this.lexer.inline(t[1])}}paragraph(e){let t=this.rules.block.paragraph.exec(e);if(t){let n=t[1].charAt(t[1].length-1)===`
|
||||
`?t[1].slice(0,-1):t[1];return{type:"paragraph",raw:t[0],text:n,tokens:this.lexer.inline(n)}}}text(e){let t=this.rules.block.text.exec(e);if(t)return{type:"text",raw:t[0],text:t[0],tokens:this.lexer.inline(t[0])}}escape(e){let t=this.rules.inline.escape.exec(e);if(t)return{type:"escape",raw:t[0],text:t[1]}}tag(e){let t=this.rules.inline.tag.exec(e);if(t)return!this.lexer.state.inLink&&this.rules.other.startATag.test(t[0])?this.lexer.state.inLink=!0:this.lexer.state.inLink&&this.rules.other.endATag.test(t[0])&&(this.lexer.state.inLink=!1),!this.lexer.state.inRawBlock&&this.rules.other.startPreScriptTag.test(t[0])?this.lexer.state.inRawBlock=!0:this.lexer.state.inRawBlock&&this.rules.other.endPreScriptTag.test(t[0])&&(this.lexer.state.inRawBlock=!1),{type:"html",raw:t[0],inLink:this.lexer.state.inLink,inRawBlock:this.lexer.state.inRawBlock,block:!1,text:t[0]}}link(e){let t=this.rules.inline.link.exec(e);if(t){let n=t[2].trim();if(!this.options.pedantic&&this.rules.other.startAngleBracket.test(n)){if(!this.rules.other.endAngleBracket.test(n))return;let r=A(n.slice(0,-1),"\\");if((n.length-r.length)%2===0)return}else{let r=de(t[2],"()");if(r===-2)return;if(r>-1){let a=(t[0].indexOf("!")===0?5:4)+t[1].length+r;t[2]=t[2].substring(0,r),t[0]=t[0].substring(0,a).trim(),t[3]=""}}let s=t[2],i="";if(this.options.pedantic){let r=this.rules.other.pedanticHrefTitle.exec(s);r&&(s=r[1],i=r[3])}else i=t[3]?t[3].slice(1,-1):"";return s=s.trim(),this.rules.other.startAngleBracket.test(s)&&(this.options.pedantic&&!this.rules.other.endAngleBracket.test(n)?s=s.slice(1):s=s.slice(1,-1)),me(t,{href:s&&s.replace(this.rules.inline.anyPunctuation,"$1"),title:i&&i.replace(this.rules.inline.anyPunctuation,"$1")},t[0],this.lexer,this.rules)}}reflink(e,t){let n;if((n=this.rules.inline.reflink.exec(e))||(n=this.rules.inline.nolink.exec(e))){let s=(n[2]||n[1]).replace(this.rules.other.multipleSpaceGlobal," "),i=t[s.toLowerCase()];if(!i){let r=n[0].charAt(0);return{type:"text",raw:r,text:r}}return me(n,i,n[0],this.lexer,this.rules)}}emStrong(e,t,n=""){let s=this.rules.inline.emStrongLDelim.exec(e);if(!s||s[3]&&n.match(this.rules.other.unicodeAlphaNumeric))return;if(!(s[1]||s[2]||"")||!n||this.rules.inline.punctuation.exec(n)){let r=[...s[0]].length-1,o,a,c=r,p=0,u=s[0][0]==="*"?this.rules.inline.emStrongRDelimAst:this.rules.inline.emStrongRDelimUnd;for(u.lastIndex=0,t=t.slice(-1*e.length+r);(s=u.exec(t))!=null;){if(o=s[1]||s[2]||s[3]||s[4]||s[5]||s[6],!o)continue;if(a=[...o].length,s[3]||s[4]){c+=a;continue}else if((s[5]||s[6])&&r%3&&!((r+a)%3)){p+=a;continue}if(c-=a,c>0)continue;a=Math.min(a,a+c+p);let d=[...s[0]][0].length,g=e.slice(0,r+s.index+d+a);if(Math.min(r,a)%2){let f=g.slice(1,-1);return{type:"em",raw:g,text:f,tokens:this.lexer.inlineTokens(f)}}let T=g.slice(2,-2);return{type:"strong",raw:g,text:T,tokens:this.lexer.inlineTokens(T)}}}}codespan(e){let t=this.rules.inline.code.exec(e);if(t){let n=t[2].replace(this.rules.other.newLineCharGlobal," "),s=this.rules.other.nonSpaceChar.test(n),i=this.rules.other.startingSpaceChar.test(n)&&this.rules.other.endingSpaceChar.test(n);return s&&i&&(n=n.substring(1,n.length-1)),{type:"codespan",raw:t[0],text:n}}}br(e){let t=this.rules.inline.br.exec(e);if(t)return{type:"br",raw:t[0]}}del(e){let t=this.rules.inline.del.exec(e);if(t)return{type:"del",raw:t[0],text:t[2],tokens:this.lexer.inlineTokens(t[2])}}autolink(e){let t=this.rules.inline.autolink.exec(e);if(t){let n,s;return t[2]==="@"?(n=t[1],s="mailto:"+n):(n=t[1],s=n),{type:"link",raw:t[0],text:n,href:s,tokens:[{type:"text",raw:n,text:n}]}}}url(e){let t;if(t=this.rules.inline.url.exec(e)){let n,s;if(t[2]==="@")n=t[0],s="mailto:"+n;else{let i;do i=t[0],t[0]=this.rules.inline._backpedal.exec(t[0])?.[0]??"";while(i!==t[0]);n=t[0],t[1]==="www."?s="http://"+t[0]:s=t[0]}return{type:"link",raw:t[0],text:n,href:s,tokens:[{type:"text",raw:n,text:n}]}}}inlineText(e){let t=this.rules.inline.text.exec(e);if(t){let n=this.lexer.state.inRawBlock;return{type:"text",raw:t[0],text:t[0],escaped:n}}}};var x=class l{tokens;options;state;tokenizer;inlineQueue;constructor(e){this.tokens=[],this.tokens.links=Object.create(null),this.options=e||w,this.options.tokenizer=this.options.tokenizer||new S,this.tokenizer=this.options.tokenizer,this.tokenizer.options=this.options,this.tokenizer.lexer=this,this.inlineQueue=[],this.state={inLink:!1,inRawBlock:!1,top:!0};let t={other:m,block:B.normal,inline:P.normal};this.options.pedantic?(t.block=B.pedantic,t.inline=P.pedantic):this.options.gfm&&(t.block=B.gfm,this.options.breaks?t.inline=P.breaks:t.inline=P.gfm),this.tokenizer.rules=t}static get rules(){return{block:B,inline:P}}static lex(e,t){return new l(t).lex(e)}static lexInline(e,t){return new l(t).inlineTokens(e)}lex(e){e=e.replace(m.carriageReturn,`
|
||||
`),this.blockTokens(e,this.tokens);for(let t=0;t<this.inlineQueue.length;t++){let n=this.inlineQueue[t];this.inlineTokens(n.src,n.tokens)}return this.inlineQueue=[],this.tokens}blockTokens(e,t=[],n=!1){for(this.options.pedantic&&(e=e.replace(m.tabCharGlobal," ").replace(m.spaceLine,""));e;){let s;if(this.options.extensions?.block?.some(r=>(s=r.call({lexer:this},e,t))?(e=e.substring(s.raw.length),t.push(s),!0):!1))continue;if(s=this.tokenizer.space(e)){e=e.substring(s.raw.length);let r=t.at(-1);s.raw.length===1&&r!==void 0?r.raw+=`
|
||||
`:t.push(s);continue}if(s=this.tokenizer.code(e)){e=e.substring(s.raw.length);let r=t.at(-1);r?.type==="paragraph"||r?.type==="text"?(r.raw+=`
|
||||
`+s.raw,r.text+=`
|
||||
`+s.text,this.inlineQueue.at(-1).src=r.text):t.push(s);continue}if(s=this.tokenizer.fences(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.heading(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.hr(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.blockquote(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.list(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.html(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.def(e)){e=e.substring(s.raw.length);let r=t.at(-1);r?.type==="paragraph"||r?.type==="text"?(r.raw+=`
|
||||
`+s.raw,r.text+=`
|
||||
`+s.raw,this.inlineQueue.at(-1).src=r.text):this.tokens.links[s.tag]||(this.tokens.links[s.tag]={href:s.href,title:s.title});continue}if(s=this.tokenizer.table(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.lheading(e)){e=e.substring(s.raw.length),t.push(s);continue}let i=e;if(this.options.extensions?.startBlock){let r=1/0,o=e.slice(1),a;this.options.extensions.startBlock.forEach(c=>{a=c.call({lexer:this},o),typeof a=="number"&&a>=0&&(r=Math.min(r,a))}),r<1/0&&r>=0&&(i=e.substring(0,r+1))}if(this.state.top&&(s=this.tokenizer.paragraph(i))){let r=t.at(-1);n&&r?.type==="paragraph"?(r.raw+=`
|
||||
`+s.raw,r.text+=`
|
||||
`+s.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=r.text):t.push(s),n=i.length!==e.length,e=e.substring(s.raw.length);continue}if(s=this.tokenizer.text(e)){e=e.substring(s.raw.length);let r=t.at(-1);r?.type==="text"?(r.raw+=`
|
||||
`+s.raw,r.text+=`
|
||||
`+s.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=r.text):t.push(s);continue}if(e){let r="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(r);break}else throw new Error(r)}}return this.state.top=!0,t}inline(e,t=[]){return this.inlineQueue.push({src:e,tokens:t}),t}inlineTokens(e,t=[]){let n=e,s=null;if(this.tokens.links){let o=Object.keys(this.tokens.links);if(o.length>0)for(;(s=this.tokenizer.rules.inline.reflinkSearch.exec(n))!=null;)o.includes(s[0].slice(s[0].lastIndexOf("[")+1,-1))&&(n=n.slice(0,s.index)+"["+"a".repeat(s[0].length-2)+"]"+n.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex))}for(;(s=this.tokenizer.rules.inline.anyPunctuation.exec(n))!=null;)n=n.slice(0,s.index)+"++"+n.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex);for(;(s=this.tokenizer.rules.inline.blockSkip.exec(n))!=null;)n=n.slice(0,s.index)+"["+"a".repeat(s[0].length-2)+"]"+n.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);let i=!1,r="";for(;e;){i||(r=""),i=!1;let o;if(this.options.extensions?.inline?.some(c=>(o=c.call({lexer:this},e,t))?(e=e.substring(o.raw.length),t.push(o),!0):!1))continue;if(o=this.tokenizer.escape(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.tag(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.link(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.reflink(e,this.tokens.links)){e=e.substring(o.raw.length);let c=t.at(-1);o.type==="text"&&c?.type==="text"?(c.raw+=o.raw,c.text+=o.text):t.push(o);continue}if(o=this.tokenizer.emStrong(e,n,r)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.codespan(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.br(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.del(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.autolink(e)){e=e.substring(o.raw.length),t.push(o);continue}if(!this.state.inLink&&(o=this.tokenizer.url(e))){e=e.substring(o.raw.length),t.push(o);continue}let a=e;if(this.options.extensions?.startInline){let c=1/0,p=e.slice(1),u;this.options.extensions.startInline.forEach(d=>{u=d.call({lexer:this},p),typeof u=="number"&&u>=0&&(c=Math.min(c,u))}),c<1/0&&c>=0&&(a=e.substring(0,c+1))}if(o=this.tokenizer.inlineText(a)){e=e.substring(o.raw.length),o.raw.slice(-1)!=="_"&&(r=o.raw.slice(-1)),i=!0;let c=t.at(-1);c?.type==="text"?(c.raw+=o.raw,c.text+=o.text):t.push(o);continue}if(e){let c="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(c);break}else throw new Error(c)}}return t}};var $=class{options;parser;constructor(e){this.options=e||w}space(e){return""}code({text:e,lang:t,escaped:n}){let s=(t||"").match(m.notSpaceStart)?.[0],i=e.replace(m.endingNewline,"")+`
|
||||
`;return s?'<pre><code class="language-'+R(s)+'">'+(n?i:R(i,!0))+`</code></pre>
|
||||
`:"<pre><code>"+(n?i:R(i,!0))+`</code></pre>
|
||||
`}blockquote({tokens:e}){return`<blockquote>
|
||||
${this.parser.parse(e)}</blockquote>
|
||||
`}html({text:e}){return e}heading({tokens:e,depth:t}){return`<h${t}>${this.parser.parseInline(e)}</h${t}>
|
||||
`}hr(e){return`<hr>
|
||||
`}list(e){let t=e.ordered,n=e.start,s="";for(let o=0;o<e.items.length;o++){let a=e.items[o];s+=this.listitem(a)}let i=t?"ol":"ul",r=t&&n!==1?' start="'+n+'"':"";return"<"+i+r+`>
|
||||
`+s+"</"+i+`>
|
||||
`}listitem(e){let t="";if(e.task){let n=this.checkbox({checked:!!e.checked});e.loose?e.tokens[0]?.type==="paragraph"?(e.tokens[0].text=n+" "+e.tokens[0].text,e.tokens[0].tokens&&e.tokens[0].tokens.length>0&&e.tokens[0].tokens[0].type==="text"&&(e.tokens[0].tokens[0].text=n+" "+R(e.tokens[0].tokens[0].text),e.tokens[0].tokens[0].escaped=!0)):e.tokens.unshift({type:"text",raw:n+" ",text:n+" ",escaped:!0}):t+=n+" "}return t+=this.parser.parse(e.tokens,!!e.loose),`<li>${t}</li>
|
||||
`}checkbox({checked:e}){return"<input "+(e?'checked="" ':"")+'disabled="" type="checkbox">'}paragraph({tokens:e}){return`<p>${this.parser.parseInline(e)}</p>
|
||||
`}table(e){let t="",n="";for(let i=0;i<e.header.length;i++)n+=this.tablecell(e.header[i]);t+=this.tablerow({text:n});let s="";for(let i=0;i<e.rows.length;i++){let r=e.rows[i];n="";for(let o=0;o<r.length;o++)n+=this.tablecell(r[o]);s+=this.tablerow({text:n})}return s&&(s=`<tbody>${s}</tbody>`),`<table>
|
||||
<thead>
|
||||
`+t+`</thead>
|
||||
`+s+`</table>
|
||||
`}tablerow({text:e}){return`<tr>
|
||||
${e}</tr>
|
||||
`}tablecell(e){let t=this.parser.parseInline(e.tokens),n=e.header?"th":"td";return(e.align?`<${n} align="${e.align}">`:`<${n}>`)+t+`</${n}>
|
||||
`}strong({tokens:e}){return`<strong>${this.parser.parseInline(e)}</strong>`}em({tokens:e}){return`<em>${this.parser.parseInline(e)}</em>`}codespan({text:e}){return`<code>${R(e,!0)}</code>`}br(e){return"<br>"}del({tokens:e}){return`<del>${this.parser.parseInline(e)}</del>`}link({href:e,title:t,tokens:n}){let s=this.parser.parseInline(n),i=V(e);if(i===null)return s;e=i;let r='<a href="'+e+'"';return t&&(r+=' title="'+R(t)+'"'),r+=">"+s+"</a>",r}image({href:e,title:t,text:n,tokens:s}){s&&(n=this.parser.parseInline(s,this.parser.textRenderer));let i=V(e);if(i===null)return R(n);e=i;let r=`<img src="${e}" alt="${n}"`;return t&&(r+=` title="${R(t)}"`),r+=">",r}text(e){return"tokens"in e&&e.tokens?this.parser.parseInline(e.tokens):"escaped"in e&&e.escaped?e.text:R(e.text)}};var _=class{strong({text:e}){return e}em({text:e}){return e}codespan({text:e}){return e}del({text:e}){return e}html({text:e}){return e}text({text:e}){return e}link({text:e}){return""+e}image({text:e}){return""+e}br(){return""}};var b=class l{options;renderer;textRenderer;constructor(e){this.options=e||w,this.options.renderer=this.options.renderer||new $,this.renderer=this.options.renderer,this.renderer.options=this.options,this.renderer.parser=this,this.textRenderer=new _}static parse(e,t){return new l(t).parse(e)}static parseInline(e,t){return new l(t).parseInline(e)}parse(e,t=!0){let n="";for(let s=0;s<e.length;s++){let i=e[s];if(this.options.extensions?.renderers?.[i.type]){let o=i,a=this.options.extensions.renderers[o.type].call({parser:this},o);if(a!==!1||!["space","hr","heading","code","table","blockquote","list","html","paragraph","text"].includes(o.type)){n+=a||"";continue}}let r=i;switch(r.type){case"space":{n+=this.renderer.space(r);continue}case"hr":{n+=this.renderer.hr(r);continue}case"heading":{n+=this.renderer.heading(r);continue}case"code":{n+=this.renderer.code(r);continue}case"table":{n+=this.renderer.table(r);continue}case"blockquote":{n+=this.renderer.blockquote(r);continue}case"list":{n+=this.renderer.list(r);continue}case"html":{n+=this.renderer.html(r);continue}case"paragraph":{n+=this.renderer.paragraph(r);continue}case"text":{let o=r,a=this.renderer.text(o);for(;s+1<e.length&&e[s+1].type==="text";)o=e[++s],a+=`
|
||||
`+this.renderer.text(o);t?n+=this.renderer.paragraph({type:"paragraph",raw:a,text:a,tokens:[{type:"text",raw:a,text:a,escaped:!0}]}):n+=a;continue}default:{let o='Token with "'+r.type+'" type was not found.';if(this.options.silent)return console.error(o),"";throw new Error(o)}}}return n}parseInline(e,t=this.renderer){let n="";for(let s=0;s<e.length;s++){let i=e[s];if(this.options.extensions?.renderers?.[i.type]){let o=this.options.extensions.renderers[i.type].call({parser:this},i);if(o!==!1||!["escape","html","link","image","strong","em","codespan","br","del","text"].includes(i.type)){n+=o||"";continue}}let r=i;switch(r.type){case"escape":{n+=t.text(r);break}case"html":{n+=t.html(r);break}case"link":{n+=t.link(r);break}case"image":{n+=t.image(r);break}case"strong":{n+=t.strong(r);break}case"em":{n+=t.em(r);break}case"codespan":{n+=t.codespan(r);break}case"br":{n+=t.br(r);break}case"del":{n+=t.del(r);break}case"text":{n+=t.text(r);break}default:{let o='Token with "'+r.type+'" type was not found.';if(this.options.silent)return console.error(o),"";throw new Error(o)}}}return n}};var L=class{options;block;constructor(e){this.options=e||w}static passThroughHooks=new Set(["preprocess","postprocess","processAllTokens"]);preprocess(e){return e}postprocess(e){return e}processAllTokens(e){return e}provideLexer(){return this.block?x.lex:x.lexInline}provideParser(){return this.block?b.parse:b.parseInline}};var E=class{defaults=z();options=this.setOptions;parse=this.parseMarkdown(!0);parseInline=this.parseMarkdown(!1);Parser=b;Renderer=$;TextRenderer=_;Lexer=x;Tokenizer=S;Hooks=L;constructor(...e){this.use(...e)}walkTokens(e,t){let n=[];for(let s of e)switch(n=n.concat(t.call(this,s)),s.type){case"table":{let i=s;for(let r of i.header)n=n.concat(this.walkTokens(r.tokens,t));for(let r of i.rows)for(let o of r)n=n.concat(this.walkTokens(o.tokens,t));break}case"list":{let i=s;n=n.concat(this.walkTokens(i.items,t));break}default:{let i=s;this.defaults.extensions?.childTokens?.[i.type]?this.defaults.extensions.childTokens[i.type].forEach(r=>{let o=i[r].flat(1/0);n=n.concat(this.walkTokens(o,t))}):i.tokens&&(n=n.concat(this.walkTokens(i.tokens,t)))}}return n}use(...e){let t=this.defaults.extensions||{renderers:{},childTokens:{}};return e.forEach(n=>{let s={...n};if(s.async=this.defaults.async||s.async||!1,n.extensions&&(n.extensions.forEach(i=>{if(!i.name)throw new Error("extension name required");if("renderer"in i){let r=t.renderers[i.name];r?t.renderers[i.name]=function(...o){let a=i.renderer.apply(this,o);return a===!1&&(a=r.apply(this,o)),a}:t.renderers[i.name]=i.renderer}if("tokenizer"in i){if(!i.level||i.level!=="block"&&i.level!=="inline")throw new Error("extension level must be 'block' or 'inline'");let r=t[i.level];r?r.unshift(i.tokenizer):t[i.level]=[i.tokenizer],i.start&&(i.level==="block"?t.startBlock?t.startBlock.push(i.start):t.startBlock=[i.start]:i.level==="inline"&&(t.startInline?t.startInline.push(i.start):t.startInline=[i.start]))}"childTokens"in i&&i.childTokens&&(t.childTokens[i.name]=i.childTokens)}),s.extensions=t),n.renderer){let i=this.defaults.renderer||new $(this.defaults);for(let r in n.renderer){if(!(r in i))throw new Error(`renderer '${r}' does not exist`);if(["options","parser"].includes(r))continue;let o=r,a=n.renderer[o],c=i[o];i[o]=(...p)=>{let u=a.apply(i,p);return u===!1&&(u=c.apply(i,p)),u||""}}s.renderer=i}if(n.tokenizer){let i=this.defaults.tokenizer||new S(this.defaults);for(let r in n.tokenizer){if(!(r in i))throw new Error(`tokenizer '${r}' does not exist`);if(["options","rules","lexer"].includes(r))continue;let o=r,a=n.tokenizer[o],c=i[o];i[o]=(...p)=>{let u=a.apply(i,p);return u===!1&&(u=c.apply(i,p)),u}}s.tokenizer=i}if(n.hooks){let i=this.defaults.hooks||new L;for(let r in n.hooks){if(!(r in i))throw new Error(`hook '${r}' does not exist`);if(["options","block"].includes(r))continue;let o=r,a=n.hooks[o],c=i[o];L.passThroughHooks.has(r)?i[o]=p=>{if(this.defaults.async)return Promise.resolve(a.call(i,p)).then(d=>c.call(i,d));let u=a.call(i,p);return c.call(i,u)}:i[o]=(...p)=>{let u=a.apply(i,p);return u===!1&&(u=c.apply(i,p)),u}}s.hooks=i}if(n.walkTokens){let i=this.defaults.walkTokens,r=n.walkTokens;s.walkTokens=function(o){let a=[];return a.push(r.call(this,o)),i&&(a=a.concat(i.call(this,o))),a}}this.defaults={...this.defaults,...s}}),this}setOptions(e){return this.defaults={...this.defaults,...e},this}lexer(e,t){return x.lex(e,t??this.defaults)}parser(e,t){return b.parse(e,t??this.defaults)}parseMarkdown(e){return(n,s)=>{let i={...s},r={...this.defaults,...i},o=this.onError(!!r.silent,!!r.async);if(this.defaults.async===!0&&i.async===!1)return o(new Error("marked(): The async option was set to true by an extension. Remove async: false from the parse options object to return a Promise."));if(typeof n>"u"||n===null)return o(new Error("marked(): input parameter is undefined or null"));if(typeof n!="string")return o(new Error("marked(): input parameter is of type "+Object.prototype.
Line truncated
|
||||
Please report this to https://github.com/markedjs/marked.`,e){let s="<p>An error occurred:</p><pre>"+R(n.message+"",!0)+"</pre>";return t?Promise.resolve(s):s}if(t)return Promise.reject(n);throw n}}};var M=new E;function k(l,e){return M.parse(l,e)}k.options=k.setOptions=function(l){return M.setOptions(l),k.defaults=M.defaults,N(k.defaults),k};k.getDefaults=z;k.defaults=w;k.use=function(...l){return M.use(...l),k.defaults=M.defaults,N(k.defaults),k};k.walkTokens=function(l,e){return M.walkTokens(l,e)};k.parseInline=M.parseInline;k.Parser=b;k.parser=b.parse;k.Renderer=$;k.TextRenderer=_;k.Lexer=x;k.lexer=x.lex;k.Tokenizer=S;k.Hooks=L;k.parse=k;var it=k.options,ot=k.setOptions,lt=k.use,at=k.walkTokens,ct=k.parseInline,pt=k,ut=b.parse,ht=x.lex;
|
||||
|
||||
if(__exports != exports)module.exports = exports;return module.exports}));
|
||||
Reference in new issue
Block a user