import { pageRoute } from "../routes.js"; /* Page-alias administration view. */ class AliasAdmin { /** * goal : Create a controller for retained page aliases. * pre : api and translate are application services; reloadPages refreshes * the public page and alias catalogue after an administrative edit. * post : The controller is ready to load and mutate aliases. * result : A directly usable alias administration controller. */ constructor(api, translate, reloadPages) { this.api = api; this.translate = translate; this.reloadPages = reloadPages; this.list = document.getElementById("alias-list"); this.status = document.getElementById("alias-admin-status"); } /** * goal : Load retained aliases and render their references and actions. * pre : The current session has administrator permission. * post : The alias list represents the current server state. * result : A promise that resolves when all alias rows have been rendered. */ async load() { this.status.textContent = ""; const result = await this.api("/api/admin/aliases"); const aliases = result.aliases || []; this.list.replaceChildren(); if (aliases.length === 0) { const empty = document.createElement("p"); empty.className = "muted"; empty.textContent = this.translate("no-page-aliases", "No page aliases."); this.list.append(empty); return; } for (const alias of aliases) this.list.append(this.#renderAlias(alias)); } /** Build one alias row, including cleanup and deletion actions. */ #renderAlias(alias) { const row = document.createElement("article"); row.className = "alias-row"; const address = document.createElement("div"); address.className = "alias-address"; const target = document.createElement("a"); target.href = pageRoute(alias.targetSlug); target.textContent = alias.targetSlug; address.append( document.createTextNode( `${this.translate("old-address", "Old address")}: ${alias.slug} · ` + `${this.translate("current-address", "Current address")}: `), target ); row.append(address); const references = alias.references || []; const currentSummary = document.createElement("div"); currentSummary.className = "alias-summary"; currentSummary.textContent = `${this.translate("current-references", "Current references")}: ${references.length}`; row.append(currentSummary); if (references.length > 0) { const referenceList = document.createElement("ul"); referenceList.className = "alias-references"; for (const reference of references) { const item = document.createElement("li"); const link = document.createElement("a"); link.href = pageRoute(reference.slug); link.textContent = reference.title; item.append(link); referenceList.append(item); } row.append(referenceList); } const historicalCount = Number(alias.historicalReferenceCount || 0); const historicalSummary = document.createElement("div"); historicalSummary.className = "alias-summary muted"; historicalSummary.textContent = `${this.translate("historical-references", "Historical references")}: ${historicalCount}`; row.append(historicalSummary); const historicalReferences = alias.historicalReferences || []; if (historicalReferences.length > 0) { const historyList = document.createElement("ul"); historyList.className = "alias-references alias-history"; for (const reference of historicalReferences) { const item = document.createElement("li"); item.textContent = `${reference.title} · ` + `${this.translate("version", "Version")} ${reference.version}`; historyList.append(item); } row.append(historyList); } const actions = document.createElement("div"); actions.className = "alias-actions"; actions.append( this.#cleanupButton(alias, references), this.#deleteButton(alias, references, historicalCount) ); row.append(actions); return row; } /** Build the action that replaces current references to an old address. */ #cleanupButton(alias, references) { const button = document.createElement("button"); button.type = "button"; button.textContent = this.translate("cleanup-alias", "Clean up references"); button.disabled = references.length === 0; button.addEventListener("click", () => { this.#cleanup(alias).catch((error) => console.error(error)); }); return button; } /** Build the action that removes an unused alias. */ #deleteButton(alias, references, historicalCount) { const button = document.createElement("button"); button.type = "button"; button.textContent = this.translate("delete-alias", "Delete alias"); button.disabled = references.length > 0; button.addEventListener("click", () => { this.#delete(alias, historicalCount).catch((error) => console.error(error)); }); return button; } /** Confirm, perform and report reference cleanup for one alias. */ async #cleanup(alias) { const question = this.translate( "cleanup-alias-confirm", "Replace all current references to this old address with the current address? Historical versions will not be changed."); if (!window.confirm(question)) return; const result = await this.api(`/api/admin/aliases/${alias.id}/cleanup`, { method: "POST" }); await this.reloadPages(); await this.load(); this.status.textContent = `${this.translate("cleanup-complete", "Cleanup complete")}: ${result.changedPages || 0}`; } /** Confirm and remove one alias that has no current references. */ async #delete(alias, historicalCount) { const warning = historicalCount > 0 ? this.translate( "delete-alias-history-confirm", "Delete this alias? Historical page versions still contain this old address and those links may stop resolving.") : this.translate("delete-alias-confirm", "Delete this alias?"); if (!window.confirm(warning)) return; await this.api(`/api/admin/aliases/${alias.id}`, { method: "DELETE" }); await this.reloadPages(); await this.load(); } } export { AliasAdmin };