/** * Present and maintain the stored history of one CMap. * * History records come from CmapRepository. Loading a version is delegated to * the workspace because it controls unsaved-change transitions and the editor. */ export class CmapHistoryDialog { constructor(dialog, repository, tr, pageDisplayDate, can, loadVersion, showStatus) { this.dialog = dialog; this.repository = repository; this.tr = tr; this.pageDisplayDate = pageDisplayDate; this.can = can; this.loadVersion = loadVersion; this.showStatus = showStatus; this.list = dialog.querySelector("#cmap-history-list"); dialog.querySelector("#cmap-history-close").addEventListener("click", () => dialog.close()); } async open(conceptMap) { const versions = await this.repository.history(conceptMap); this.list.replaceChildren(); if (!versions.length) this.showEmptyMessage(); for (const version of versions) this.list.append(this.versionRow(conceptMap, version)); this.dialog.showModal(); } showEmptyMessage() { const empty = document.createElement("p"); empty.className = "muted cmap-history-empty"; empty.textContent = this.tr( "no-concept-map-history", "No snapshots or manual saves yet."); this.list.append(empty); } versionRow(conceptMap, version) { const row = document.createElement("div"); row.className = "cmap-history-row"; const label = document.createElement("div"); const heading = document.createElement("strong"); heading.textContent = `${this.tr("version", "Version")} ${version.version} — ${version.title}`; const meta = document.createElement("div"); meta.className = "muted"; meta.textContent = `${this.pageDisplayDate(version.createdAt)} · ${version.author} · ${this.versionSummary(version)}`; label.append(heading, document.createElement("br"), meta); const actions = document.createElement("div"); actions.className = "cmap-history-actions"; actions.append(this.loadButton(conceptMap, version)); if (this.can("editor")) actions.append(this.deleteButton(conceptMap, version, row)); row.append(label, actions); return row; } versionSummary(version) { const knownSummaries = { create: this.tr("concept-map-created-version", "CMap created"), rename: this.tr("concept-map-renamed-version", "CMap renamed") }; let summary = knownSummaries[version.action] || version.summary; if (version.action === "snapshot") { const snapshotLabel = this.tr("snapshot", "Snapshot"); if (version.summary === "Current state when CMap history was enabled") { summary = this.tr("concept-map-initial-version", "Initial available version"); } else { summary = version.summary === snapshotLabel ? snapshotLabel : `${snapshotLabel} — ${version.summary}`; } } if (version.summary === "Automatic save") { summary = this.tr("automatic-save", "Automatic save"); } if (version.summary === "Manual save") summary = this.tr("manual-save", "Manual save"); return summary; } loadButton(conceptMap, version) { const load = document.createElement("button"); load.type = "button"; load.textContent = version.version === conceptMap.currentVersion ? this.tr("current-version", "Current") : this.tr("load-version", "Load version"); load.disabled = version.version === conceptMap.currentVersion; load.addEventListener("click", () => { this.dialog.close(); this.loadVersion(version.version); }); return load; } deleteButton(conceptMap, version, row) { const remove = document.createElement("button"); remove.type = "button"; remove.textContent = this.tr("delete-history-item", "Delete"); remove.addEventListener("click", async () => { const question = this.tr( "delete-concept-map-history-confirm", "Delete CMap history version {version}? The current CMap will not be changed.") .replace("{version}", String(version.version)); if (!window.confirm(question)) return; remove.disabled = true; try { await this.repository.deleteVersion(conceptMap, version.version); row.remove(); if (!this.list.querySelector(".cmap-history-row")) this.showEmptyMessage(); this.showStatus( this.tr("concept-map-history-deleted", "History item deleted"), true); } catch (error) { remove.disabled = false; this.showStatus(error.message); } }); return remove; } }