AUDIT (Auftrag: vollstaendiger Durchgang, Fehler direkt beheben)
Ausgangslage waren 40 Pruefungen mit 1616 Einzelpunkten, alle gruen.
Acht neue Pruefungen kamen dazu; sie haben gefunden, was die alten nicht
sehen konnten.
Der schwerste Fund: Die Zugangsschranke verglich req.path EXAKT gegen
eine Liste. Express raeumt Punkt-Segmente selbst weg, mehrfache
Schraegstriche aber nicht. Damit kam //workspace/start.html OHNE
Anmeldung mit HTTP 200, und ein Creator bekam ueber
/workspace//personen.html die Verwaltungsseite. Die DATEN waren nie
betroffen (nachgemessen: 404 bzw. 401). Behoben durch Normalisierung
UND eine Umkehr der Logik -- jetzt ist jede .html geschuetzt ausser der
Anmeldeseite, statt nur die in der Liste. Eine vergessene neue Seite
steht damit nicht mehr versehentlich offen.
Weiter behoben:
* Kaputter/abgebrochener Rumpf ergab 500 in HTML statt 400 in JSON --
die Oberflaeche ruft ueberall a.json() und lief in einen zweiten
Fehler; der Knopf hing ohne Meldung.
* POST /zustand/sichern war der einzige von 60 schreibenden Wegen
ohne Herkunftspruefung.
* workspace-sicherung.js gab interne Pfade in Fehlermeldungen nach
aussen; alle 23 anderen Module antworten neutral.
* admin_notiz war als einziges von 14 Feldern ohne <label>.
* Der aktive Filter hatte keinen sichtbaren Fokus (CSS-Spezifitaet
0,3,0 schlug 0,2,0) -- genau der Knopf, auf dem man steht.
* h1 -> h3 ohne Zwischenstufe auf zwei Seiten.
* HSTS ging auch ueber http mit (RFC 6797, 7.2 verbietet das).
* upgrade-insecure-requests galt auch auf 127.0.0.1 -- dadurch war
WebKit/Safari ueberhaupt nicht pruefbar, also der Browser, den
jedes iPhone benutzt.
* pruef-grosscheck las readdirSync(".") und pruefte aus server/
gestartet NULL oeffentliche Seiten -- meldete aber "ok".
Neue Pruefungen: struktur, schranke, haerte, alle-wege,
barrierefrei-workspace, breiten, tempo-workspace, browser.
Jede mit Gegenprobe und mit der geprueften Anzahl in der Bedingung.
Vier davon sind beim Bauen durch die eigene Gegenprobe aufgeflogen und
haetten sonst dauerhaft gruen gemeldet, ohne etwas zu messen.
APP-SYMBOLE (Wunsch: alle Apps der Domain, jede anders, ausser
safeaddress)
Zehn Apps, zehn Stile, zehn in OKLCH gerechnete Farben. Zusammen haelt
sie dasselbe Logo, dieselbe Eckenrundung und eine gemeinsame gedeckte
Farbreihe. Beim Bauen wird gemessen, ob sich das Logo vom Grund abhebt
(19 bis 58 Helligkeitsstufen).
Dabei aufgefallen: Das Kundenportal hatte kein eigenes Manifest und
trug Namen und Symbol der Webdesign-Seite. Der Workspace hatte gar
keins und war als App nicht installierbar. Beide haben jetzt eins.
Geaendert wurde AUSSCHLIESSLICH das Symbol. Ein Zwischenstand hatte
auch die Themenfarben gesetzt; das war mehr als bestellt und wurde
zurueckgenommen.
Werkzeuge: tools/logo-freistellen.mjs, tools/app-symbole.mjs,
tools/app-symbole-einbinden.mjs -- alles im Browser gerechnet, kein
Bildprogramm, keine neue Abhaengigkeit.
Gitea und Nextcloud sind bereits live und nachgeprueft.
Co-Authored-By: Claude Opus 5 <[email protected]>
263 lines
11 KiB
JavaScript
263 lines
11 KiB
JavaScript
/* ===================================================================
|
|
Bindet die gebauten App-Symbole überall ein.
|
|
|
|
Getrennt von tools/app-symbole.mjs, weil es zwei verschiedene Dinge
|
|
sind: dort werden Bilder ERZEUGT, hier werden Dateien VERÄNDERT. Wer
|
|
nur den Stil nachjustieren will, soll das tun können, ohne dass dabei
|
|
vierzig HTML-Dateien angefasst werden.
|
|
|
|
WAS DIESES WERKZEUG ANFASST:
|
|
* die Manifeste (vorhandene ändern, fehlende anlegen)
|
|
* in jeder HTML-Seite: das Reiter-Symbol (favicon), das
|
|
Startbildschirm-Symbol (apple-touch-icon) und der Verweis aufs
|
|
richtige Manifest
|
|
|
|
MEHRFACH AUFRUFBAR. Jede Änderung prüft erst, ob sie schon da ist.
|
|
Ein zweiter Lauf ändert deshalb nichts mehr -- wichtig, weil dieses
|
|
Werkzeug nach jeder Stiländerung erneut laufen soll.
|
|
|
|
NICHT ANGEFASST werden Name, Beschreibung, Farben und Startadresse
|
|
der vorhandenen Manifeste. Die sind gewachsen und gehören nicht zu
|
|
dieser Aufgabe.
|
|
|
|
Aufrufen mit: node tools/app-symbole-einbinden.mjs
|
|
=================================================================== */
|
|
|
|
import { readFileSync, writeFileSync, existsSync, readdirSync, mkdirSync, copyFileSync } from "node:fs";
|
|
import { join, dirname } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { APPS, FARBEN, STIL_JE_APP, STILE } from "./app-symbole.mjs";
|
|
|
|
const WURZEL = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
const SYM = join(WURZEL, "assets", "img", "app-symbole");
|
|
|
|
if (!existsSync(SYM) || readdirSync(SYM).length < 60) {
|
|
console.error("Die Symbole fehlen. Erst node tools/app-symbole.mjs bauen laufen lassen.");
|
|
process.exit(1);
|
|
}
|
|
|
|
const farbeVon = Object.fromEntries(FARBEN.map((f) => [f.schluessel, f]));
|
|
let geaendert = 0;
|
|
const berichte = [];
|
|
|
|
/* ---------------------------------------------------------------------
|
|
1. Die Manifeste
|
|
------------------------------------------------------------------ */
|
|
|
|
/** Der Symbolblock, wie ihn ein Manifest braucht. */
|
|
const symbolBlock = (schluessel, pfadVorn) => [
|
|
{ src: `${pfadVorn}${schluessel}-192.png`, sizes: "192x192", type: "image/png", purpose: "any" },
|
|
{ src: `${pfadVorn}${schluessel}-512.png`, sizes: "512x512", type: "image/png", purpose: "any" },
|
|
{ src: `${pfadVorn}${schluessel}-192-maskable.png`, sizes: "192x192", type: "image/png", purpose: "maskable" },
|
|
{ src: `${pfadVorn}${schluessel}-512-maskable.png`, sizes: "512x512", type: "image/png", purpose: "maskable" },
|
|
];
|
|
|
|
const PFAD_VORN = "/assets/img/app-symbole/";
|
|
|
|
/* Vorhandene Manifeste: nur die Symbole und die Themenfarbe tauschen. */
|
|
const MANIFESTE_VORHANDEN = [
|
|
["manifest.json", "universe"],
|
|
["manifest-verwaltung.json", "verwaltung"],
|
|
["webdesign/app.webmanifest", "webdesign"],
|
|
["webdesign/verwaltung.webmanifest", "wdverwalt"],
|
|
];
|
|
|
|
for (const [datei, schluessel] of MANIFESTE_VORHANDEN) {
|
|
const pfad = join(WURZEL, datei);
|
|
if (!existsSync(pfad)) { berichte.push(` ? ${datei} gibt es nicht — übersprungen`); continue; }
|
|
const inhalt = JSON.parse(readFileSync(pfad, "utf8"));
|
|
const neu = symbolBlock(schluessel, PFAD_VORN);
|
|
const f = farbeVon[schluessel];
|
|
|
|
/* NUR DIE SYMBOLE. Themenfarbe und Hintergrundfarbe bleiben, wie sie
|
|
sind.
|
|
|
|
Ein Zwischenstand vom 05.09.2026 hatte sie mitgesetzt -- der
|
|
Gedanke war, dass die Statusleiste der installierten App zur
|
|
Symbolfarbe passen sollte. Filipe dazu: *"wieso cloud ich will doch
|
|
nur dass die symbole anders aussehen wenn ich sie installiere mehr
|
|
nicht."* Das war eindeutig und richtig: Bestellt waren die Symbole.
|
|
Alles andere anzufassen ist Eigenmacht, auch wenn es gut gemeint
|
|
ist und gut aussieht.
|
|
|
|
Die Farben wurden deshalb auf ihre alten Werte zurückgesetzt. Wer
|
|
sie später doch angleichen will, macht das als eigene Entscheidung
|
|
-- nicht als Nebenwirkung eines Symboltauschs. */
|
|
const vorher = JSON.stringify(inhalt);
|
|
inhalt.icons = neu;
|
|
if (JSON.stringify(inhalt) === vorher) {
|
|
berichte.push(` = ${datei} war schon richtig`);
|
|
continue;
|
|
}
|
|
writeFileSync(pfad, JSON.stringify(inhalt, null, 2) + "\n");
|
|
geaendert++;
|
|
berichte.push(` ✓ ${datei} → ${schluessel}`);
|
|
}
|
|
|
|
/* Fehlende Manifeste: Kundenportal und Workspace hatten keins.
|
|
Das Portal lief bisher unter dem Manifest der Webdesign-Seite -- damit
|
|
trug die Verknüpfung deren Namen und deren Symbol. Der Workspace hatte
|
|
überhaupt keins; er war als App gar nicht installierbar. */
|
|
const MANIFESTE_NEU = [
|
|
{
|
|
datei: "webdesign/portal.webmanifest", schluessel: "portal",
|
|
seite: "webdesign/portal.html",
|
|
inhalt: {
|
|
name: "Kundenportal — Dogfather Webdesign",
|
|
short_name: "Kundenportal",
|
|
description: "Der eigene Bereich für Kundinnen und Kunden: Projekte, Anfragen, "
|
|
+ "Zahlungen und Postfach an einem Ort.",
|
|
start_url: "/webdesign/portal.html",
|
|
id: "/webdesign/portal.html",
|
|
scope: "/webdesign/",
|
|
display: "standalone", orientation: "any", lang: "de", dir: "ltr",
|
|
},
|
|
},
|
|
{
|
|
datei: "workspace/app.webmanifest", schluessel: "workspace",
|
|
seite: "workspace/start.html",
|
|
inhalt: {
|
|
name: "Creator Workspace — Dogfather Universe",
|
|
short_name: "Workspace",
|
|
description: "Betreuung an einem Ort: Aufgaben, Termine, Calls, Bereiche, "
|
|
+ "Dateien und Reports. Nur mit persönlichem Zugangscode.",
|
|
start_url: "/workspace/start.html",
|
|
id: "/workspace/start.html",
|
|
scope: "/workspace/",
|
|
display: "standalone", orientation: "any", lang: "de", dir: "ltr",
|
|
},
|
|
},
|
|
];
|
|
|
|
for (const { datei, schluessel, inhalt, seite } of MANIFESTE_NEU) {
|
|
const pfad = join(WURZEL, datei);
|
|
|
|
/* Diese beiden Manifeste sind neu, es gibt also keinen alten Wert,
|
|
auf den man zurueckfallen koennte. Genommen wird deshalb die Farbe,
|
|
die die SEITE schon hat -- nicht die Farbe des neuen Symbols. Auch
|
|
hier gilt: geaendert wird nur das Symbol; ein neues Manifest darf
|
|
nicht durch die Hintertuer ein neues Aussehen mitbringen. */
|
|
let seitenFarbe = "#05070b";
|
|
try {
|
|
const html = readFileSync(join(WURZEL, seite), "utf8");
|
|
const m = html.match(/<meta[^>]*name="theme-color"[^>]*content="([^"]*)"/i);
|
|
if (m) seitenFarbe = m[1];
|
|
} catch { /* Seite nicht lesbar -- dann der dunkle Vorgabewert */ }
|
|
|
|
const fertig = {
|
|
...inhalt,
|
|
background_color: seitenFarbe,
|
|
theme_color: seitenFarbe,
|
|
icons: symbolBlock(schluessel, PFAD_VORN),
|
|
};
|
|
const alt = existsSync(pfad) ? readFileSync(pfad, "utf8") : "";
|
|
const neu = JSON.stringify(fertig, null, 2) + "\n";
|
|
if (alt === neu) { berichte.push(` = ${datei} war schon richtig`); continue; }
|
|
mkdirSync(dirname(pfad), { recursive: true });
|
|
writeFileSync(pfad, neu);
|
|
geaendert++;
|
|
berichte.push(` ${alt ? "✓" : "+"} ${datei} → ${schluessel}${alt ? "" : " (neu angelegt)"}`);
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------
|
|
2. Die HTML-Seiten
|
|
------------------------------------------------------------------ */
|
|
|
|
/** Welche Seite gehört zu welcher App? Die genaueste Regel gewinnt,
|
|
* deshalb steht das Besondere vor dem Allgemeinen. */
|
|
const ZUORDNUNG = [
|
|
{ treffer: (p) => p === "webdesign/portal.html", app: "portal", manifest: "/webdesign/portal.webmanifest" },
|
|
{ treffer: (p) => p === "webdesign/verwaltung.html", app: "wdverwalt", manifest: "/webdesign/verwaltung.webmanifest" },
|
|
{ treffer: (p) => p.startsWith("webdesign/"), app: "webdesign", manifest: "/webdesign/app.webmanifest" },
|
|
{ treffer: (p) => p.startsWith("workspace/"), app: "workspace", manifest: "/workspace/app.webmanifest" },
|
|
{ treffer: (p) => p === "verwaltung.html", app: "verwaltung", manifest: "/manifest-verwaltung.json" },
|
|
{ treffer: (p) => !p.includes("/"), app: "universe", manifest: "/manifest.json" },
|
|
];
|
|
|
|
function htmlSeiten() {
|
|
const raus = [];
|
|
const gehen = (ordner, vorn = "") => {
|
|
for (const n of readdirSync(ordner, { withFileTypes: true })) {
|
|
if (n.name === "node_modules" || n.name.startsWith(".")) continue;
|
|
const p = join(ordner, n.name);
|
|
const rel = vorn ? `${vorn}/${n.name}` : n.name;
|
|
if (n.isDirectory()) {
|
|
if (["webdesign", "workspace"].includes(rel)) gehen(p, rel);
|
|
continue;
|
|
}
|
|
if (n.name.endsWith(".html")) raus.push(rel);
|
|
}
|
|
};
|
|
gehen(WURZEL);
|
|
return raus;
|
|
}
|
|
|
|
for (const rel of htmlSeiten()) {
|
|
const regel = ZUORDNUNG.find((z) => z.treffer(rel));
|
|
if (!regel) continue;
|
|
const pfad = join(WURZEL, rel);
|
|
let text = readFileSync(pfad, "utf8");
|
|
const vorher = text;
|
|
const s = regel.app;
|
|
|
|
/* Reiter-Symbol. Ersetzt jeden vorhandenen icon-Verweis; gibt es
|
|
keinen, kommt er hinter <head>. */
|
|
const favicon = `<link rel="icon" type="image/png" href="${PFAD_VORN}${s}-32.png" />`;
|
|
if (/<link[^>]*rel="icon"[^>]*>/i.test(text)) {
|
|
text = text.replace(/<link[^>]*rel="icon"[^>]*>/i, favicon);
|
|
} else {
|
|
text = text.replace(/<head>/i, `<head>\n${favicon}`);
|
|
}
|
|
|
|
/* Startbildschirm-Symbol. */
|
|
const apple = `<link rel="apple-touch-icon" href="${PFAD_VORN}${s}-180.png" />`;
|
|
if (/<link[^>]*rel="apple-touch-icon"[^>]*>/i.test(text)) {
|
|
text = text.replace(/<link[^>]*rel="apple-touch-icon"[^>]*>/i, apple);
|
|
} else {
|
|
text = text.replace(favicon, `${favicon}\n${apple}`);
|
|
}
|
|
|
|
/* Manifest -- nur ersetzen, nicht neu erfinden: Seiten ohne Manifest
|
|
sollen auch keins bekommen (etwa Diagnoseseiten). */
|
|
if (/<link[^>]*rel="manifest"[^>]*>/i.test(text)) {
|
|
text = text.replace(/<link[^>]*rel="manifest"[^>]*href="[^"]*"/i,
|
|
`<link rel="manifest" href="${regel.manifest}"`);
|
|
}
|
|
|
|
/* Die Themenfarbe der Seite bleibt unangetastet -- siehe die
|
|
Begründung oben bei den Manifesten. Bestellt waren die Symbole. */
|
|
|
|
if (text !== vorher) { writeFileSync(pfad, text); geaendert++; }
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------
|
|
3. Die zwei Apps in eigenen Ordnern
|
|
------------------------------------------------------------------ */
|
|
const AUSWAERTS = [
|
|
{ name: "Dogi's ZockerAnstalt", schluessel: "zocker",
|
|
ziel: "C:/Users/qciga/Documents/Obelix/Spielehalle/public/icons" },
|
|
{ name: "DogFather Analyse", schluessel: "analyse",
|
|
ziel: "C:/Users/qciga/Documents/Obelix/Analyse/logos" },
|
|
];
|
|
|
|
for (const { name, schluessel, ziel } of AUSWAERTS) {
|
|
if (!existsSync(ziel)) { berichte.push(` ? ${name}: ${ziel} gibt es nicht`); continue; }
|
|
for (const g of ["192", "512", "192-maskable", "512-maskable", "180", "32"]) {
|
|
copyFileSync(join(SYM, `${schluessel}-${g}.png`), join(ziel, `dogfather-${schluessel}-${g}.png`));
|
|
}
|
|
berichte.push(` ✓ ${name}: 6 Symbole nach ${ziel.split("/").slice(-3).join("/")} kopiert`);
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------
|
|
Bericht
|
|
------------------------------------------------------------------ */
|
|
console.log("Manifeste und Ordner:");
|
|
for (const z of berichte) console.log(z);
|
|
console.log(`\nGeänderte Dateien insgesamt: ${geaendert}`);
|
|
console.log("\nZuordnung:");
|
|
for (const app of APPS) {
|
|
const f = farbeVon[app.schluessel];
|
|
console.log(` ${app.kurz.padEnd(12)} ${STILE[STIL_JE_APP[app.schluessel]].name.padEnd(18)}`
|
|
+ ` ${f.mittel} ${app.wo}`);
|
|
}
|