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
77 lines
2.5 KiB
JavaScript
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">×</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");
|
|
}
|
|
}
|