ZDDC/transmittal/js/live-digest.js
ZDDC ea385b5366 Initial commit
ZDDC — Zero Day Document Control. A file-naming convention plus five
single-file HTML tools (archive, transmittal, classifier, mdedit,
landing) and an optional Go HTTP server (zddc-server) with ACL and a
virtual archive index. Self-contained, offline-capable, dependency-free.

See README.md for an overview, AGENTS.md and ARCHITECTURE.md for the
build/release/architecture detail, bootstrap/README.md for the
two-level deployment install pattern, and zddc/README.md for the
HTTP server.
2026-04-27 11:05:47 -05:00

106 lines
3.5 KiB
JavaScript

(function (app) {
'use strict';
const dom = app.dom;
const util = app.util;
const json = app.json;
let debounceTimer = null;
const DEBOUNCE_MS = 300; // 300ms debounce
async function updateLiveDigest() {
var digestDisplay = dom.qs('#digest-display');
if (!digestDisplay) { return; }
// Published — let renderSignaturesList handle the display
var data = json.parse();
var envelope = (data && data.envelope) || {};
if (envelope.digest) { return; }
// Only compute live digest in edit mode
if (app.state && app.state.mode !== 'edit') { return; }
try {
// Sync form-field values to JSON only after the app has fully
// initialised (i.e. loadFromJson has run and app.data.files is
// populated). Calling syncUiToJson before that would overwrite
// the saved-draft JSON with the empty in-memory state.
if (app._initialized && app.modules.files && app.modules.files.syncUiToJson) {
app.modules.files.syncUiToJson();
// Re-read after sync so the digest reflects the updated payload.
data = json.parse();
}
var payload = (data && data.payload) || {};
var payloadStr = util.canonicalStringify(payload);
var digest = await util.hashString(payloadStr);
var now = new Date().toLocaleString();
// Render draft verify-card
digestDisplay.innerHTML = '';
var card = document.createElement('div');
card.className = 'verify-card verify-card--draft';
var status = document.createElement('div');
status.className = 'verify-card__status verify-card__status--draft';
status.textContent = 'DRAFT';
card.appendChild(status);
var detail = document.createElement('div');
detail.className = 'verify-card__detail';
detail.innerHTML = 'Digest (SHA-256): <code>' + digest + '</code>';
card.appendChild(detail);
var time = document.createElement('div');
time.className = 'verify-card__detail';
time.textContent = 'Live \u2014 ' + now;
card.appendChild(time);
digestDisplay.appendChild(card);
} catch (err) {
console.error('[live-digest] Failed to calculate digest:', err);
}
}
function scheduleDigestUpdate() {
if (debounceTimer) {
clearTimeout(debounceTimer);
}
debounceTimer = setTimeout(updateLiveDigest, DEBOUNCE_MS);
}
function bindFormChanges() {
const form = dom.qs('#transmittal-form');
if (!form) {
return;
}
// Listen to all input changes
form.addEventListener('input', scheduleDigestUpdate);
form.addEventListener('change', scheduleDigestUpdate);
}
function subscribeToStateChanges() {
// Subscribe to reactive state changes
if (app.state && app.state.subscribe) {
app.state.subscribe(function(property, newValue, oldValue) {
scheduleDigestUpdate();
});
}
}
app.onDirty(function () {
scheduleDigestUpdate();
});
app.modules.liveDigest = {
update: updateLiveDigest,
schedule: scheduleDigestUpdate
};
app.registerInit(function () {
bindFormChanges();
subscribeToStateChanges();
// Calculate initial digest
updateLiveDigest();
});
})(window.transmittalApp);