monitor/html/js/office-modal.js
fds2610-bot d72c945a61 Codeberg-Projekte des Sync ueber ein Modal am PO konfigurierbar machen
Klick auf den PO oeffnet ein Modal, in dem sich die Projekte (Owner/Repo +
Farbe), aus denen der Codeberg-Sync liest, hinzufuegen, bearbeiten und
loeschen lassen - Icon-Schaltflaechen mit Tooltip statt Textbuttons. Die
Projektliste war bisher eine Umgebungsvariable (CODEBERG_REPOS, Issue #26)
und wandert dafuer in eine neue Tabelle repo_configs (Migration
003_repo_configs.sql, geseedet mit den bisherigen drei Default-Projekten);
CodebergSyncService liest sie ueber RepoConfigRepository bei jedem Lauf
frisch ein, damit eine Aenderung im laufenden --watch-Prozess ohne Neustart
wirkt. Neue Endpunkte GET/POST/PATCH/DELETE /api/v1/repo-configs, Schreiben
hinter X-Admin-Key wie die Agenten-Registrierung; das Frontend fragt den Key
bei der ersten Aenderung per Prompt ab und merkt ihn sich im lokalen
Speicher. office-modal.js bekommt dafuer einen Oeffnen-Token
(isCurrentModal()), damit ein verspaetet aufloesender Request nach dem
Oeffnen eines anderen Modals nicht dessen Inhalt ueberschreibt.

Schliesst #27
2026-07-30 17:53:39 +02:00

77 lines
2.5 KiB
JavaScript

// Generisches Modal fuer Board-/Monitor-Detailansichten (Visualization.md 4.5: "Modals sind
// reines HTML/CSS/JS ohne fremde UI-Bibliothek, per <dialog>-Element ... umgesetzt"). Dieses
// Modul kuemmert sich nur um Erzeugen/Oeffnen/Schliessen des <dialog>-Elements; welchen Inhalt
// es zeigt, entscheidet der Aufrufer (office-render.js) per renderBody-Callback.
let dialogEl = null;
// Erhoeht sich bei jedem openModal()-Aufruf (fuer JEDEN Modal-Typ) - alle Modals teilen sich
// dasselbe <dialog>-Element (ensureDialog() erzeugt es nur einmal). Ein interaktives Modal mit
// eigenem Server-Rundtrip (z.B. das Projekte-Modal, Issue #27) kann per isCurrentModal() vor
// einem verspaeteten Repaint pruefen, ob der Nutzer inzwischen ein anderes Modal geoeffnet hat -
// sonst wuerde eine spaet aufloesende Promise dessen Inhalt ueberschreiben.
let openToken = 0;
function ensureDialog() {
if (dialogEl) {
return dialogEl;
}
dialogEl = document.createElement("dialog");
dialogEl.className = "office-modal";
dialogEl.innerHTML = `
<form method="dialog" class="office-modal-form">
<header class="office-modal-header">
<h2 class="office-modal-title"></h2>
<button type="submit" class="office-modal-close" aria-label="Schliessen">&times;</button>
</header>
<div class="office-modal-body"></div>
</form>
`;
document.body.appendChild(dialogEl);
return dialogEl;
}
/**
* Oeffnet das Modal mit einem Titel und laesst den Aufrufer den Body fuellen.
*
* @param {{title: string}} content
* @param {(body: HTMLElement, content: object) => void} renderBody
* @returns {number} Token fuer isCurrentModal() - siehe openToken oben.
*/
export function openModal(content, renderBody) {
openToken += 1;
const token = openToken;
const dialog = ensureDialog();
dialog.querySelector(".office-modal-title").textContent = content.title;
const body = dialog.querySelector(".office-modal-body");
body.replaceChildren();
renderBody(body, content);
if (typeof dialog.showModal === "function") {
dialog.showModal();
} else {
// Fallback fuer Umgebungen ohne <dialog>-Unterstuetzung (z.B. sehr alte Browser).
dialog.setAttribute("open", "");
}
return token;
}
/** Siehe openToken/openModal() - true, solange kein anderer openModal()-Aufruf dazwischenkam. */
export function isCurrentModal(token) {
return token === openToken;
}
export function closeModal() {
if (dialogEl === null) {
return;
}
if (typeof dialogEl.close === "function") {
dialogEl.close();
} else {
dialogEl.removeAttribute("open");
}
}