chore(embedded): cut v0.0.17-beta
All checks were successful
Notify chart dev on beta cut / notify-chart-dev (push) Successful in 6s
All checks were successful
Notify chart dev on beta cut / notify-chart-dev (push) Successful in 6s
This commit is contained in:
parent
e7f6334daa
commit
320c5d09ab
7 changed files with 299 additions and 86 deletions
|
|
@ -2470,7 +2470,7 @@ td[data-field="trackingNumber"] {
|
||||||
</svg>
|
</svg>
|
||||||
<div class="header-title-group">
|
<div class="header-title-group">
|
||||||
<span class="app-header__title">ZDDC Archive</span>
|
<span class="app-header__title">ZDDC Archive</span>
|
||||||
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.17-beta · 2026-05-12 · candle-mast-pearl</span></span>
|
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.17-beta · 2026-05-13 · plaza-fiddle-panel</span></span>
|
||||||
</div>
|
</div>
|
||||||
<button id="addDirectoryBtn" class="btn btn-primary">Add Local Directory</button>
|
<button id="addDirectoryBtn" class="btn btn-primary">Add Local Directory</button>
|
||||||
<button id="refreshHeaderBtn" class="btn btn-secondary hidden" title="Refresh Data">⟳</button>
|
<button id="refreshHeaderBtn" class="btn btn-secondary hidden" title="Refresh Data">⟳</button>
|
||||||
|
|
@ -5214,7 +5214,7 @@ X.B(E,Y);return E}return J}())
|
||||||
*
|
*
|
||||||
* Renderers operate on any document (parent window or popup window), so the
|
* Renderers operate on any document (parent window or popup window), so the
|
||||||
* same code works for tools whose preview opens in a popup (classifier,
|
* same code works for tools whose preview opens in a popup (classifier,
|
||||||
* archive, transmittal) and tools that render inline (mdedit).
|
* archive, transmittal) and tools that render inline (browse).
|
||||||
*
|
*
|
||||||
* Public API on window.zddc.preview:
|
* Public API on window.zddc.preview:
|
||||||
* loadLibrary(url) → Promise<void>
|
* loadLibrary(url) → Promise<void>
|
||||||
|
|
|
||||||
|
|
@ -1477,6 +1477,16 @@ html, body {
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
|
.md-shell__download {
|
||||||
|
/* Slightly tighter than the Save button so a row of three doesn't
|
||||||
|
crowd the title. The base .btn styles still drive padding/color. */
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
}
|
||||||
|
.md-shell__download[disabled] {
|
||||||
|
opacity: 0.55;
|
||||||
|
cursor: progress;
|
||||||
|
}
|
||||||
|
|
||||||
/* Editor host: a single grid cell with overflow:hidden so Toast UI's
|
/* Editor host: a single grid cell with overflow:hidden so Toast UI's
|
||||||
internal scrollers handle the content. */
|
internal scrollers handle the content. */
|
||||||
|
|
@ -1562,34 +1572,41 @@ html, body {
|
||||||
transition: background-color 0.3s ease;
|
transition: background-color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Front matter list ──────────────────────────────────────────────────── */
|
/* ── Front matter editor ────────────────────────────────────────────────── */
|
||||||
.md-fm__empty {
|
.md-fm__body {
|
||||||
|
/* Body cell owns the textarea; sized by the sidebar's grid row. */
|
||||||
|
padding: 0;
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.md-fm__textarea {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.4rem 0.6rem;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text);
|
||||||
|
font-family: var(--font-mono, ui-monospace, SFMono-Regular, Consolas, monospace);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
line-height: 1.45;
|
||||||
|
resize: none;
|
||||||
|
outline: none;
|
||||||
|
white-space: pre;
|
||||||
|
overflow: auto;
|
||||||
|
tab-size: 2;
|
||||||
|
}
|
||||||
|
.md-fm__textarea::placeholder {
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-size: 0.82rem;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0.5rem 0.75rem;
|
|
||||||
}
|
}
|
||||||
.md-fm__list {
|
.md-fm__textarea:focus {
|
||||||
margin: 0;
|
background: var(--surface-2, rgba(0, 0, 0, 0.025));
|
||||||
padding: 0.3rem 0.75rem;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: minmax(4.5rem, max-content) 1fr;
|
|
||||||
gap: 0.2rem 0.6rem;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
}
|
}
|
||||||
.md-fm__list dt {
|
.md-fm__textarea[readonly] {
|
||||||
font-weight: 600;
|
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
text-transform: lowercase;
|
cursor: not-allowed;
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
.md-fm__list dd {
|
|
||||||
margin: 0;
|
|
||||||
color: var(--text);
|
|
||||||
overflow-wrap: anywhere;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Sort control ────────────────────────────────────────────────────────── */
|
/* ── Sort control ────────────────────────────────────────────────────────── */
|
||||||
|
|
@ -1640,7 +1657,7 @@ html, body {
|
||||||
</svg>
|
</svg>
|
||||||
<div class="header-title-group">
|
<div class="header-title-group">
|
||||||
<span class="app-header__title">ZDDC Browse</span>
|
<span class="app-header__title">ZDDC Browse</span>
|
||||||
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.17-beta · 2026-05-12 · candle-mast-pearl</span></span>
|
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.17-beta · 2026-05-13 · plaza-fiddle-panel</span></span>
|
||||||
</div>
|
</div>
|
||||||
<button id="addDirectoryBtn" class="btn btn-primary">Add Local Directory</button>
|
<button id="addDirectoryBtn" class="btn btn-primary">Add Local Directory</button>
|
||||||
<button id="refreshHeaderBtn" class="btn btn-secondary hidden" title="Refresh listing" aria-label="Refresh listing">⟳</button>
|
<button id="refreshHeaderBtn" class="btn btn-secondary hidden" title="Refresh listing" aria-label="Refresh listing">⟳</button>
|
||||||
|
|
@ -4293,7 +4310,7 @@ var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Arr
|
||||||
*
|
*
|
||||||
* Renderers operate on any document (parent window or popup window), so the
|
* Renderers operate on any document (parent window or popup window), so the
|
||||||
* same code works for tools whose preview opens in a popup (classifier,
|
* same code works for tools whose preview opens in a popup (classifier,
|
||||||
* archive, transmittal) and tools that render inline (mdedit).
|
* archive, transmittal) and tools that render inline (browse).
|
||||||
*
|
*
|
||||||
* Public API on window.zddc.preview:
|
* Public API on window.zddc.preview:
|
||||||
* loadLibrary(url) → Promise<void>
|
* loadLibrary(url) → Promise<void>
|
||||||
|
|
@ -5874,22 +5891,31 @@ var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Arr
|
||||||
//
|
//
|
||||||
// Layout (CSS Grid):
|
// Layout (CSS Grid):
|
||||||
// ┌─────────────────────────────────────────────────────────────────┐
|
// ┌─────────────────────────────────────────────────────────────────┐
|
||||||
// │ toolbar: Save | ● modified | status | source │
|
// │ info: name | dirty | status | source | DOCX HTML PDF | Save │
|
||||||
// ├────────────────────────────────────────┬────────────────────────┤
|
// ├────────────────────────┬────────────────────────────────────────┤
|
||||||
// │ │ Outline │
|
// │ YAML front matter │ │
|
||||||
// │ │ • Heading 1 │
|
// │ ┌──────────────────┐ │ │
|
||||||
// │ Toast UI Editor │ • Subheading │
|
// │ │ title: Foo │ │ Toast UI Editor │
|
||||||
// │ (md / wysiwyg / preview) │ • Heading 2 │
|
// │ │ revision: A │ │ (md / wysiwyg / preview) │
|
||||||
// │ ├────────────────────────┤
|
// │ └──────────────────┘ │ │
|
||||||
// │ │ Front matter │
|
// ├────────────────────────┤ │
|
||||||
// │ │ title: Foo │
|
// │ Outline │ │
|
||||||
// │ │ revision: A │
|
// │ • Heading 1 │ │
|
||||||
// └────────────────────────────────────────┴────────────────────────┘
|
// │ • Subheading │ │
|
||||||
|
// │ • Heading 2 │ │
|
||||||
|
// └────────────────────────┴────────────────────────────────────────┘
|
||||||
// Grid keeps every cell's size definite, which is what Toast UI needs
|
// Grid keeps every cell's size definite, which is what Toast UI needs
|
||||||
// to compute its inner scroll regions correctly. The previous nested-
|
// to compute its inner scroll regions correctly. The previous nested-
|
||||||
// flexbox layout produced indeterminate heights and a fragile TOC
|
// flexbox layout produced indeterminate heights and a fragile TOC
|
||||||
// pane width — grid fixes both.
|
// pane width — grid fixes both.
|
||||||
//
|
//
|
||||||
|
// Front matter is edited in a dedicated <textarea> in the sidebar
|
||||||
|
// (always present — typing into the placeholder grows the envelope on
|
||||||
|
// save). On load the `---\n…\n---\n` envelope is stripped from the
|
||||||
|
// bytes fed to Toast UI; on save the textarea content is re-stitched
|
||||||
|
// on top of the editor body. Keeps YAML out of the rich editor where
|
||||||
|
// users can't reliably edit it.
|
||||||
|
//
|
||||||
// Save (Ctrl+S) writes back via PUT (server mode) or
|
// Save (Ctrl+S) writes back via PUT (server mode) or
|
||||||
// FileSystemWritableFileStream (FS-API). Zip-virtual files are
|
// FileSystemWritableFileStream (FS-API). Zip-virtual files are
|
||||||
// read-only — Save stays disabled. Toast UI is vendored
|
// read-only — Save stays disabled. Toast UI is vendored
|
||||||
|
|
@ -5965,25 +5991,37 @@ var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Arr
|
||||||
return { data: data, body: body };
|
return { data: data, body: body };
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderFrontMatter(fmEl, content) {
|
// Inverse of parseFrontMatter — turn a {key: value | array} object back
|
||||||
if (!fmEl) return;
|
// into newline-separated YAML lines suitable for the textarea. Arrays
|
||||||
var parsed = parseFrontMatter(content);
|
// are quoted to match what the parser will round-trip through. Returns
|
||||||
var keys = Object.keys(parsed.data);
|
// "" when there are no keys (so the textarea shows its placeholder).
|
||||||
if (keys.length === 0) {
|
function stringifyFrontMatter(data) {
|
||||||
fmEl.innerHTML = '<p class="md-fm__empty">No front matter.</p>';
|
if (!data) return '';
|
||||||
return;
|
var keys = Object.keys(data);
|
||||||
}
|
if (keys.length === 0) return '';
|
||||||
var html = '<dl class="md-fm__list">';
|
var out = [];
|
||||||
for (var i = 0; i < keys.length; i++) {
|
for (var i = 0; i < keys.length; i++) {
|
||||||
var k = keys[i];
|
var k = keys[i];
|
||||||
var v = parsed.data[k];
|
var v = data[k];
|
||||||
var displayV = Array.isArray(v)
|
if (Array.isArray(v)) {
|
||||||
? v.map(escapeHtml).join(', ')
|
out.push(k + ': [' + v.map(function (x) {
|
||||||
: escapeHtml(String(v));
|
return '"' + String(x).replace(/"/g, '\\"') + '"';
|
||||||
html += '<dt>' + escapeHtml(k) + '</dt><dd>' + displayV + '</dd>';
|
}).join(', ') + ']');
|
||||||
|
} else {
|
||||||
|
out.push(k + ': ' + String(v));
|
||||||
}
|
}
|
||||||
html += '</dl>';
|
}
|
||||||
fmEl.innerHTML = html;
|
return out.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stitch the textarea's YAML lines and the editor's body back together
|
||||||
|
// into the on-disk envelope. Empty textarea → return body unchanged
|
||||||
|
// (no envelope written). Trailing whitespace in the textarea is
|
||||||
|
// tolerated.
|
||||||
|
function assembleContent(fmText, body) {
|
||||||
|
var fm = (fmText || '').replace(/\s+$/, '');
|
||||||
|
if (!fm) return body || '';
|
||||||
|
return '---\n' + fm + '\n---\n' + (body || '');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── TOC (table of contents) ────────────────────────────────────────────
|
// ── TOC (table of contents) ────────────────────────────────────────────
|
||||||
|
|
@ -6209,6 +6247,13 @@ var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Arr
|
||||||
fmHeader.textContent = 'YAML front matter';
|
fmHeader.textContent = 'YAML front matter';
|
||||||
var fmBody = document.createElement('div');
|
var fmBody = document.createElement('div');
|
||||||
fmBody.className = 'md-side__body md-fm__body';
|
fmBody.className = 'md-side__body md-fm__body';
|
||||||
|
var fmTextarea = document.createElement('textarea');
|
||||||
|
fmTextarea.className = 'md-fm__textarea';
|
||||||
|
fmTextarea.spellcheck = false;
|
||||||
|
fmTextarea.autocapitalize = 'off';
|
||||||
|
fmTextarea.autocomplete = 'off';
|
||||||
|
fmTextarea.placeholder = 'title: Document Title\ndate: 2026-05-13\ntags: [example]';
|
||||||
|
fmBody.appendChild(fmTextarea);
|
||||||
fmSection.appendChild(fmHeader);
|
fmSection.appendChild(fmHeader);
|
||||||
fmSection.appendChild(fmBody);
|
fmSection.appendChild(fmBody);
|
||||||
sidebar.appendChild(fmSection);
|
sidebar.appendChild(fmSection);
|
||||||
|
|
@ -6280,10 +6325,35 @@ var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Arr
|
||||||
sourceEl.textContent = 'server';
|
sourceEl.textContent = 'server';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Download-as-{docx,html,pdf} buttons. Server-mode + .md only:
|
||||||
|
// the server endpoint runs pandoc/chromium in a container and
|
||||||
|
// returns the converted bytes. Click handlers wire up below
|
||||||
|
// (after save() is defined) because they auto-save first when
|
||||||
|
// the buffer is dirty.
|
||||||
|
var serverModeMd = window.app && window.app.state &&
|
||||||
|
window.app.state.source === 'server' &&
|
||||||
|
node.url && /\.md$/i.test(node.name);
|
||||||
|
var convertBtns = [];
|
||||||
|
if (serverModeMd && window.zddc && window.zddc.source &&
|
||||||
|
typeof window.zddc.source.downloadConverted === 'function') {
|
||||||
|
['docx', 'html', 'pdf'].forEach(function (fmt) {
|
||||||
|
var btn = document.createElement('button');
|
||||||
|
btn.className = 'btn btn-sm btn-secondary md-shell__download';
|
||||||
|
btn.type = 'button';
|
||||||
|
btn.textContent = fmt.toUpperCase();
|
||||||
|
btn.title = 'Download as ' + fmt.toUpperCase();
|
||||||
|
btn.dataset.fmt = fmt;
|
||||||
|
convertBtns.push(btn);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
infohdr.appendChild(titleEl);
|
infohdr.appendChild(titleEl);
|
||||||
infohdr.appendChild(dirtyEl);
|
infohdr.appendChild(dirtyEl);
|
||||||
infohdr.appendChild(statusEl);
|
infohdr.appendChild(statusEl);
|
||||||
infohdr.appendChild(sourceEl);
|
infohdr.appendChild(sourceEl);
|
||||||
|
for (var ci = 0; ci < convertBtns.length; ci++) {
|
||||||
|
infohdr.appendChild(convertBtns[ci]);
|
||||||
|
}
|
||||||
infohdr.appendChild(saveBtn);
|
infohdr.appendChild(saveBtn);
|
||||||
content.appendChild(infohdr);
|
content.appendChild(infohdr);
|
||||||
|
|
||||||
|
|
@ -6292,15 +6362,21 @@ var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Arr
|
||||||
editorHost.className = 'md-shell__editor';
|
editorHost.className = 'md-shell__editor';
|
||||||
content.appendChild(editorHost);
|
content.appendChild(editorHost);
|
||||||
|
|
||||||
// Construct the editor. height: 100% works because editorHost
|
// Split the loaded bytes into FM (textarea) + body (editor). The
|
||||||
// is a grid cell with a definite size.
|
// hash that gates dirty-state is taken over the reassembled
|
||||||
var initialHash = await hashContent(text);
|
// bytes so that round-tripping a clean file shows "not dirty"
|
||||||
|
// even if we tweak whitespace in the YAML lines.
|
||||||
|
var initialParsed = parseFrontMatter(text);
|
||||||
|
fmTextarea.value = stringifyFrontMatter(initialParsed.data);
|
||||||
|
var bodyText = initialParsed.body;
|
||||||
|
|
||||||
|
var initialHash = await hashContent(assembleContent(fmTextarea.value, bodyText));
|
||||||
var editor = new window.toastui.Editor({
|
var editor = new window.toastui.Editor({
|
||||||
el: editorHost,
|
el: editorHost,
|
||||||
height: '100%',
|
height: '100%',
|
||||||
initialEditType: 'markdown',
|
initialEditType: 'markdown',
|
||||||
previewStyle: 'vertical',
|
previewStyle: 'vertical',
|
||||||
initialValue: text,
|
initialValue: bodyText,
|
||||||
usageStatistics: false,
|
usageStatistics: false,
|
||||||
toolbarItems: [
|
toolbarItems: [
|
||||||
['heading', 'bold', 'italic', 'strike'],
|
['heading', 'bold', 'italic', 'strike'],
|
||||||
|
|
@ -6318,17 +6394,17 @@ var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Arr
|
||||||
node: node,
|
node: node,
|
||||||
hash: initialHash,
|
hash: initialHash,
|
||||||
tocEl: tocBody,
|
tocEl: tocBody,
|
||||||
fmEl: fmBody
|
fmEl: fmTextarea
|
||||||
};
|
};
|
||||||
|
|
||||||
var writable = canSave(node);
|
var writable = canSave(node);
|
||||||
if (!writable) {
|
if (!writable) {
|
||||||
saveBtn.disabled = true;
|
saveBtn.disabled = true;
|
||||||
saveBtn.title = 'Save not available — read-only source.';
|
saveBtn.title = 'Save not available — read-only source.';
|
||||||
|
fmTextarea.readOnly = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderToc(tocBody, text, editor);
|
renderToc(tocBody, bodyText, editor);
|
||||||
renderFrontMatter(fmBody, text);
|
|
||||||
|
|
||||||
// ── Sidebar/content resizer ─────────────────────────────────────────
|
// ── Sidebar/content resizer ─────────────────────────────────────────
|
||||||
// Sidebar is on the LEFT now. Dragging right grows the
|
// Sidebar is on the LEFT now. Dragging right grows the
|
||||||
|
|
@ -6424,18 +6500,24 @@ var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Arr
|
||||||
}
|
}
|
||||||
|
|
||||||
var onChange = debounce(async function () {
|
var onChange = debounce(async function () {
|
||||||
var current = editor.getMarkdown();
|
var body = editor.getMarkdown();
|
||||||
var h = await hashContent(current);
|
var h = await hashContent(assembleContent(fmTextarea.value, body));
|
||||||
markDirty(h !== currentInstance.hash);
|
markDirty(h !== currentInstance.hash);
|
||||||
renderToc(tocBody, current, editor);
|
renderToc(tocBody, body, editor);
|
||||||
renderFrontMatter(fmBody, current);
|
|
||||||
}, 250);
|
}, 250);
|
||||||
editor.on('change', onChange);
|
editor.on('change', onChange);
|
||||||
|
|
||||||
|
var onFmChange = debounce(async function () {
|
||||||
|
var body = editor.getMarkdown();
|
||||||
|
var h = await hashContent(assembleContent(fmTextarea.value, body));
|
||||||
|
markDirty(h !== currentInstance.hash);
|
||||||
|
}, 250);
|
||||||
|
fmTextarea.addEventListener('input', onFmChange);
|
||||||
|
|
||||||
// ── Save ───────────────────────────────────────────────────────────
|
// ── Save ───────────────────────────────────────────────────────────
|
||||||
async function save() {
|
async function save() {
|
||||||
if (!currentInstance.dirty || !writable) return;
|
if (!currentInstance.dirty || !writable) return;
|
||||||
var content = editor.getMarkdown();
|
var content = assembleContent(fmTextarea.value, editor.getMarkdown());
|
||||||
try {
|
try {
|
||||||
statusEl.textContent = 'Saving…';
|
statusEl.textContent = 'Saving…';
|
||||||
await saveContent(node, content);
|
await saveContent(node, content);
|
||||||
|
|
@ -6459,6 +6541,42 @@ var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Arr
|
||||||
save();
|
save();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Download-as-* click handlers. Auto-save when the buffer is
|
||||||
|
// dirty so the converted file reflects what's on screen. If
|
||||||
|
// the save fails the existing toast/status surfaces it; we
|
||||||
|
// bail without firing the conversion.
|
||||||
|
convertBtns.forEach(function (btn) {
|
||||||
|
btn.addEventListener('click', async function () {
|
||||||
|
var fmt = btn.dataset.fmt;
|
||||||
|
if (currentInstance.dirty) {
|
||||||
|
if (!writable) {
|
||||||
|
if (window.zddc && window.zddc.toast) {
|
||||||
|
window.zddc.toast(
|
||||||
|
'This source is read-only — save a copy elsewhere first.',
|
||||||
|
'error');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
btn.disabled = true;
|
||||||
|
try { await save(); } finally { btn.disabled = false; }
|
||||||
|
if (currentInstance.dirty) return; // save failed
|
||||||
|
}
|
||||||
|
btn.disabled = true;
|
||||||
|
try {
|
||||||
|
statusEl.textContent = 'Converting to ' + fmt.toUpperCase() + '…';
|
||||||
|
await window.zddc.source.downloadConverted(node.url, node.name, fmt);
|
||||||
|
statusEl.textContent = 'Downloaded ' + fmt.toUpperCase();
|
||||||
|
} catch (e) {
|
||||||
|
statusEl.textContent = (e && e.message) || String(e);
|
||||||
|
if (window.zddc && window.zddc.toast) {
|
||||||
|
window.zddc.toast((e && e.message) || String(e), 'error');
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
btn.disabled = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
window.app.modules.markdown = {
|
window.app.modules.markdown = {
|
||||||
|
|
|
||||||
|
|
@ -1681,7 +1681,7 @@ body.help-open .app-header {
|
||||||
</svg>
|
</svg>
|
||||||
<div class="header-title-group">
|
<div class="header-title-group">
|
||||||
<span class="app-header__title">ZDDC Classifier</span>
|
<span class="app-header__title">ZDDC Classifier</span>
|
||||||
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.17-beta · 2026-05-12 · candle-mast-pearl</span></span>
|
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.17-beta · 2026-05-13 · plaza-fiddle-panel</span></span>
|
||||||
</div>
|
</div>
|
||||||
<button id="addDirectoryBtn" class="btn btn-primary">Add Local Directory</button>
|
<button id="addDirectoryBtn" class="btn btn-primary">Add Local Directory</button>
|
||||||
<button id="refreshHeaderBtn" class="btn btn-secondary hidden" title="Refresh and rescan directory" aria-label="Refresh" style="font-size:1.1rem;">⟳</button>
|
<button id="refreshHeaderBtn" class="btn btn-secondary hidden" title="Refresh and rescan directory" aria-label="Refresh" style="font-size:1.1rem;">⟳</button>
|
||||||
|
|
@ -3584,7 +3584,7 @@ X.B(E,Y);return E}return J}())
|
||||||
})(typeof window !== 'undefined' ? window : globalThis);
|
})(typeof window !== 'undefined' ? window : globalThis);
|
||||||
|
|
||||||
// shared/zddc-source.js — source abstraction for tools that handle
|
// shared/zddc-source.js — source abstraction for tools that handle
|
||||||
// directory trees (classifier, mdedit, transmittal, browse, archive).
|
// directory trees (classifier, transmittal, browse, archive).
|
||||||
//
|
//
|
||||||
// Two backends:
|
// Two backends:
|
||||||
//
|
//
|
||||||
|
|
@ -3955,12 +3955,44 @@ X.B(E,Y);return E}return J}())
|
||||||
return !!(handle && handle.isHttp === true);
|
return !!(handle && handle.isHttp === true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// downloadConverted fetches a server-side MD→{docx,html,pdf}
|
||||||
|
// conversion and triggers a browser download with a clean filename.
|
||||||
|
// srcUrl points at the .md source on the server. fmt is one of
|
||||||
|
// "docx" | "html" | "pdf". The server response status maps to a
|
||||||
|
// friendly error message for the caller to surface (toast / status).
|
||||||
|
async function downloadConverted(srcUrl, fileName, fmt) {
|
||||||
|
var resp = await fetch(srcUrl + '?convert=' + encodeURIComponent(fmt),
|
||||||
|
{ credentials: 'same-origin' });
|
||||||
|
if (!resp.ok) {
|
||||||
|
var msg;
|
||||||
|
if (resp.status === 503) msg = 'Conversion service unavailable on this server.';
|
||||||
|
else if (resp.status === 422) msg = 'Conversion failed — the source may be malformed.';
|
||||||
|
else if (resp.status === 504) msg = 'Conversion timed out.';
|
||||||
|
else msg = 'Conversion failed (HTTP ' + resp.status + ').';
|
||||||
|
// Append server-supplied body text if it adds detail.
|
||||||
|
try {
|
||||||
|
var detail = await resp.text();
|
||||||
|
if (detail && detail.length < 400) msg += ' ' + detail.trim();
|
||||||
|
} catch (_) { /* ignore */ }
|
||||||
|
throw new Error(msg);
|
||||||
|
}
|
||||||
|
var blob = await resp.blob();
|
||||||
|
var a = document.createElement('a');
|
||||||
|
a.href = URL.createObjectURL(blob);
|
||||||
|
a.download = fileName.replace(/\.md$/i, '') + '.' + fmt;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
a.remove();
|
||||||
|
setTimeout(function () { URL.revokeObjectURL(a.href); }, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
window.zddc.source = {
|
window.zddc.source = {
|
||||||
HttpDirectoryHandle: HttpDirectoryHandle,
|
HttpDirectoryHandle: HttpDirectoryHandle,
|
||||||
HttpFileHandle: HttpFileHandle,
|
HttpFileHandle: HttpFileHandle,
|
||||||
detectServerRoot: detectServerRoot,
|
detectServerRoot: detectServerRoot,
|
||||||
moveFile: moveFile,
|
moveFile: moveFile,
|
||||||
isHttpHandle: isHttpHandle,
|
isHttpHandle: isHttpHandle,
|
||||||
|
downloadConverted: downloadConverted,
|
||||||
// Lower-level helpers exposed for tools that want to call the
|
// Lower-level helpers exposed for tools that want to call the
|
||||||
// server directly without going through the polyfill.
|
// server directly without going through the polyfill.
|
||||||
httpListing: httpListing,
|
httpListing: httpListing,
|
||||||
|
|
@ -4428,7 +4460,7 @@ X.B(E,Y);return E}return J}())
|
||||||
*
|
*
|
||||||
* Renderers operate on any document (parent window or popup window), so the
|
* Renderers operate on any document (parent window or popup window), so the
|
||||||
* same code works for tools whose preview opens in a popup (classifier,
|
* same code works for tools whose preview opens in a popup (classifier,
|
||||||
* archive, transmittal) and tools that render inline (mdedit).
|
* archive, transmittal) and tools that render inline (browse).
|
||||||
*
|
*
|
||||||
* Public API on window.zddc.preview:
|
* Public API on window.zddc.preview:
|
||||||
* loadLibrary(url) → Promise<void>
|
* loadLibrary(url) → Promise<void>
|
||||||
|
|
|
||||||
|
|
@ -1424,7 +1424,7 @@ body {
|
||||||
</svg>
|
</svg>
|
||||||
<div class="header-title-group">
|
<div class="header-title-group">
|
||||||
<span class="app-header__title">ZDDC</span>
|
<span class="app-header__title">ZDDC</span>
|
||||||
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.17-beta · 2026-05-12 · candle-mast-pearl</span></span>
|
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.17-beta · 2026-05-13 · plaza-fiddle-panel</span></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
|
|
@ -3284,9 +3284,9 @@ body {
|
||||||
|
|
||||||
// Render the project-workspace view: title, four stage links, MDL
|
// Render the project-workspace view: title, four stage links, MDL
|
||||||
// section. Stage hrefs use the no-trailing-slash form so the server
|
// section. Stage hrefs use the no-trailing-slash form so the server
|
||||||
// routes them to each canonical default tool (mdedit for working/,
|
// routes them to each canonical default tool (browse for working/+
|
||||||
// transmittal for staging/, etc.). Browse-all and the archive deep
|
// reviewing/, transmittal for staging/, etc.). Browse-all and the
|
||||||
// link use the slash form to land on the directory listing.
|
// archive deep link use the slash form to land on the directory listing.
|
||||||
async function renderProjectMode() {
|
async function renderProjectMode() {
|
||||||
var project = projectFromPath();
|
var project = projectFromPath();
|
||||||
if (!project) return;
|
if (!project) return;
|
||||||
|
|
|
||||||
|
|
@ -2523,7 +2523,7 @@ dialog.modal--narrow {
|
||||||
</svg>
|
</svg>
|
||||||
<div class="header-title-group">
|
<div class="header-title-group">
|
||||||
<span class="app-header__title">ZDDC Transmittal</span>
|
<span class="app-header__title">ZDDC Transmittal</span>
|
||||||
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.17-beta · 2026-05-12 · candle-mast-pearl</span></span>
|
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.17-beta · 2026-05-13 · plaza-fiddle-panel</span></span>
|
||||||
</div>
|
</div>
|
||||||
<span id="no-js-notice" class="text-gray-400 text-xs italic">JavaScript not available</span>
|
<span id="no-js-notice" class="text-gray-400 text-xs italic">JavaScript not available</span>
|
||||||
<!-- Publish split-button (Transmittal-specific primary action;
|
<!-- Publish split-button (Transmittal-specific primary action;
|
||||||
|
|
@ -4640,7 +4640,7 @@ X.B(E,Y);return E}return J}())
|
||||||
})(typeof window !== 'undefined' ? window : globalThis);
|
})(typeof window !== 'undefined' ? window : globalThis);
|
||||||
|
|
||||||
// shared/zddc-source.js — source abstraction for tools that handle
|
// shared/zddc-source.js — source abstraction for tools that handle
|
||||||
// directory trees (classifier, mdedit, transmittal, browse, archive).
|
// directory trees (classifier, transmittal, browse, archive).
|
||||||
//
|
//
|
||||||
// Two backends:
|
// Two backends:
|
||||||
//
|
//
|
||||||
|
|
@ -5011,12 +5011,44 @@ X.B(E,Y);return E}return J}())
|
||||||
return !!(handle && handle.isHttp === true);
|
return !!(handle && handle.isHttp === true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// downloadConverted fetches a server-side MD→{docx,html,pdf}
|
||||||
|
// conversion and triggers a browser download with a clean filename.
|
||||||
|
// srcUrl points at the .md source on the server. fmt is one of
|
||||||
|
// "docx" | "html" | "pdf". The server response status maps to a
|
||||||
|
// friendly error message for the caller to surface (toast / status).
|
||||||
|
async function downloadConverted(srcUrl, fileName, fmt) {
|
||||||
|
var resp = await fetch(srcUrl + '?convert=' + encodeURIComponent(fmt),
|
||||||
|
{ credentials: 'same-origin' });
|
||||||
|
if (!resp.ok) {
|
||||||
|
var msg;
|
||||||
|
if (resp.status === 503) msg = 'Conversion service unavailable on this server.';
|
||||||
|
else if (resp.status === 422) msg = 'Conversion failed — the source may be malformed.';
|
||||||
|
else if (resp.status === 504) msg = 'Conversion timed out.';
|
||||||
|
else msg = 'Conversion failed (HTTP ' + resp.status + ').';
|
||||||
|
// Append server-supplied body text if it adds detail.
|
||||||
|
try {
|
||||||
|
var detail = await resp.text();
|
||||||
|
if (detail && detail.length < 400) msg += ' ' + detail.trim();
|
||||||
|
} catch (_) { /* ignore */ }
|
||||||
|
throw new Error(msg);
|
||||||
|
}
|
||||||
|
var blob = await resp.blob();
|
||||||
|
var a = document.createElement('a');
|
||||||
|
a.href = URL.createObjectURL(blob);
|
||||||
|
a.download = fileName.replace(/\.md$/i, '') + '.' + fmt;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
a.remove();
|
||||||
|
setTimeout(function () { URL.revokeObjectURL(a.href); }, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
window.zddc.source = {
|
window.zddc.source = {
|
||||||
HttpDirectoryHandle: HttpDirectoryHandle,
|
HttpDirectoryHandle: HttpDirectoryHandle,
|
||||||
HttpFileHandle: HttpFileHandle,
|
HttpFileHandle: HttpFileHandle,
|
||||||
detectServerRoot: detectServerRoot,
|
detectServerRoot: detectServerRoot,
|
||||||
moveFile: moveFile,
|
moveFile: moveFile,
|
||||||
isHttpHandle: isHttpHandle,
|
isHttpHandle: isHttpHandle,
|
||||||
|
downloadConverted: downloadConverted,
|
||||||
// Lower-level helpers exposed for tools that want to call the
|
// Lower-level helpers exposed for tools that want to call the
|
||||||
// server directly without going through the polyfill.
|
// server directly without going through the polyfill.
|
||||||
httpListing: httpListing,
|
httpListing: httpListing,
|
||||||
|
|
@ -5484,7 +5516,7 @@ X.B(E,Y);return E}return J}())
|
||||||
*
|
*
|
||||||
* Renderers operate on any document (parent window or popup window), so the
|
* Renderers operate on any document (parent window or popup window), so the
|
||||||
* same code works for tools whose preview opens in a popup (classifier,
|
* same code works for tools whose preview opens in a popup (classifier,
|
||||||
* archive, transmittal) and tools that render inline (mdedit).
|
* archive, transmittal) and tools that render inline (browse).
|
||||||
*
|
*
|
||||||
* Public API on window.zddc.preview:
|
* Public API on window.zddc.preview:
|
||||||
* loadLibrary(url) → Promise<void>
|
* loadLibrary(url) → Promise<void>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
# Generated by build.sh — do not edit. One <app>=<build label> per line.
|
# Generated by build.sh — do not edit. One <app>=<build label> per line.
|
||||||
archive=v0.0.17-beta · 2026-05-12 · candle-mast-pearl
|
archive=v0.0.17-beta · 2026-05-13 · plaza-fiddle-panel
|
||||||
transmittal=v0.0.17-beta · 2026-05-12 · candle-mast-pearl
|
transmittal=v0.0.17-beta · 2026-05-13 · plaza-fiddle-panel
|
||||||
classifier=v0.0.17-beta · 2026-05-12 · candle-mast-pearl
|
classifier=v0.0.17-beta · 2026-05-13 · plaza-fiddle-panel
|
||||||
mdedit=v0.0.17-beta · 2026-05-12 · candle-mast-pearl
|
landing=v0.0.17-beta · 2026-05-13 · plaza-fiddle-panel
|
||||||
landing=v0.0.17-beta · 2026-05-12 · candle-mast-pearl
|
form=v0.0.17-beta · 2026-05-13 · plaza-fiddle-panel
|
||||||
form=v0.0.17-beta · 2026-05-12 · candle-mast-pearl
|
tables=v0.0.17-beta · 2026-05-13 · plaza-fiddle-panel
|
||||||
tables=v0.0.17-beta · 2026-05-12 · candle-mast-pearl
|
browse=v0.0.17-beta · 2026-05-13 · plaza-fiddle-panel
|
||||||
browse=v0.0.17-beta · 2026-05-12 · candle-mast-pearl
|
|
||||||
|
|
|
||||||
|
|
@ -1300,7 +1300,7 @@ body.help-open .app-header {
|
||||||
</svg>
|
</svg>
|
||||||
<div class="header-title-group">
|
<div class="header-title-group">
|
||||||
<span class="app-header__title" id="table-title">ZDDC Table</span>
|
<span class="app-header__title" id="table-title">ZDDC Table</span>
|
||||||
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.17-beta · 2026-05-12 · candle-mast-pearl</span></span>
|
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.17-beta · 2026-05-13 · plaza-fiddle-panel</span></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
|
|
@ -1845,7 +1845,7 @@ body.help-open .app-header {
|
||||||
}(typeof window !== 'undefined' ? window : this));
|
}(typeof window !== 'undefined' ? window : this));
|
||||||
|
|
||||||
// shared/zddc-source.js — source abstraction for tools that handle
|
// shared/zddc-source.js — source abstraction for tools that handle
|
||||||
// directory trees (classifier, mdedit, transmittal, browse, archive).
|
// directory trees (classifier, transmittal, browse, archive).
|
||||||
//
|
//
|
||||||
// Two backends:
|
// Two backends:
|
||||||
//
|
//
|
||||||
|
|
@ -2216,12 +2216,44 @@ body.help-open .app-header {
|
||||||
return !!(handle && handle.isHttp === true);
|
return !!(handle && handle.isHttp === true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// downloadConverted fetches a server-side MD→{docx,html,pdf}
|
||||||
|
// conversion and triggers a browser download with a clean filename.
|
||||||
|
// srcUrl points at the .md source on the server. fmt is one of
|
||||||
|
// "docx" | "html" | "pdf". The server response status maps to a
|
||||||
|
// friendly error message for the caller to surface (toast / status).
|
||||||
|
async function downloadConverted(srcUrl, fileName, fmt) {
|
||||||
|
var resp = await fetch(srcUrl + '?convert=' + encodeURIComponent(fmt),
|
||||||
|
{ credentials: 'same-origin' });
|
||||||
|
if (!resp.ok) {
|
||||||
|
var msg;
|
||||||
|
if (resp.status === 503) msg = 'Conversion service unavailable on this server.';
|
||||||
|
else if (resp.status === 422) msg = 'Conversion failed — the source may be malformed.';
|
||||||
|
else if (resp.status === 504) msg = 'Conversion timed out.';
|
||||||
|
else msg = 'Conversion failed (HTTP ' + resp.status + ').';
|
||||||
|
// Append server-supplied body text if it adds detail.
|
||||||
|
try {
|
||||||
|
var detail = await resp.text();
|
||||||
|
if (detail && detail.length < 400) msg += ' ' + detail.trim();
|
||||||
|
} catch (_) { /* ignore */ }
|
||||||
|
throw new Error(msg);
|
||||||
|
}
|
||||||
|
var blob = await resp.blob();
|
||||||
|
var a = document.createElement('a');
|
||||||
|
a.href = URL.createObjectURL(blob);
|
||||||
|
a.download = fileName.replace(/\.md$/i, '') + '.' + fmt;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
a.remove();
|
||||||
|
setTimeout(function () { URL.revokeObjectURL(a.href); }, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
window.zddc.source = {
|
window.zddc.source = {
|
||||||
HttpDirectoryHandle: HttpDirectoryHandle,
|
HttpDirectoryHandle: HttpDirectoryHandle,
|
||||||
HttpFileHandle: HttpFileHandle,
|
HttpFileHandle: HttpFileHandle,
|
||||||
detectServerRoot: detectServerRoot,
|
detectServerRoot: detectServerRoot,
|
||||||
moveFile: moveFile,
|
moveFile: moveFile,
|
||||||
isHttpHandle: isHttpHandle,
|
isHttpHandle: isHttpHandle,
|
||||||
|
downloadConverted: downloadConverted,
|
||||||
// Lower-level helpers exposed for tools that want to call the
|
// Lower-level helpers exposed for tools that want to call the
|
||||||
// server directly without going through the polyfill.
|
// server directly without going through the polyfill.
|
||||||
httpListing: httpListing,
|
httpListing: httpListing,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue