Stand 16.10.2026. Enthält das Marketing-Dashboard, die Daten in daten/, die Produktionsvorgaben, Reels und das Webinar mit Skript, Sprechzettel und Präsentation (HTML, PowerPoint, PDF). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
123 lines
6.1 KiB
JavaScript
123 lines
6.1 KiB
JavaScript
// Webinar: Stand, Unterlagen, Ablauf und alle Slides auf einen Blick.
|
|
//
|
|
// Stand, Unterlagen und Ablauf stehen in daten/webinar.json. Die Slides kommen
|
|
// direkt aus webinar/slides: Reihenfolge und Teile aus quelle/deck.json, die
|
|
// Titel aus den Slide-Dateien, die Bilder aus export/ (npm run export).
|
|
|
|
import { S } from "../app.js";
|
|
import { esc, langDatum, dokLink, pille } from "../hilfen.js";
|
|
|
|
const SLIDES = "/webinar/slides";
|
|
const url = (pfad) => "/" + pfad.split("/").map(encodeURIComponent).join("/");
|
|
|
|
// Reihenfolge, Teile und Titel der Slides. Fehlt etwas, bleibt der Block leer
|
|
// statt die ganze Seite zu stören.
|
|
async function deckLesen() {
|
|
try {
|
|
const deck = await fetch(`${SLIDES}/quelle/deck.json`, { cache: "no-store" }).then((r) => r.json());
|
|
const leser = new DOMParser();
|
|
const titel = await Promise.all(deck.order.map(async (id) => {
|
|
const html = await fetch(`${SLIDES}/quelle/slides/${id}.html`, { cache: "no-store" }).then((r) => r.text()).catch(() => "");
|
|
const doc = leser.parseFromString(html, "text/html");
|
|
doc.querySelectorAll("aside").forEach((a) => a.remove());
|
|
return (doc.querySelector("h1, h2")?.textContent || id).replace(/\s+/g, " ").trim();
|
|
}));
|
|
const starts = Object.values(deck.sections || {}).map((s) => [s.start, s.description]);
|
|
const folien = deck.order.map((id, i) => ({ id, nr: i + 1, titel: titel[i] }));
|
|
return { folien, teile: starts };
|
|
} catch {
|
|
return { folien: [], teile: [] };
|
|
}
|
|
}
|
|
|
|
function unterlage(u) {
|
|
const knoepfe = [];
|
|
if (u.art === "ansehen") knoepfe.push(`<a class="knopf" href="${url(u.pfad)}" target="_blank" rel="noopener">Ansehen</a>`);
|
|
if (u.art === "laden") knoepfe.push(`<a class="knopf" href="${url(u.pfad)}" download>Herunterladen</a>`);
|
|
if (u.art === "extern") knoepfe.push(`<a class="knopf" href="${esc(u.pfad)}" target="_blank" rel="noopener">Öffnen</a>`);
|
|
if (u.art === "dok") knoepfe.push(`<a class="knopf" href="${dokLink(u.pfad)}">Lesen</a>`);
|
|
if (u.pdf) knoepfe.push(`<a class="knopf" href="${url(u.pdf)}" target="_blank" rel="noopener">PDF</a>`);
|
|
return `<div class="zeile unterlage">
|
|
<span><b>${esc(u.name)}</b><span class="klein" style="display:block">${esc(u.text)}</span></span>
|
|
<span class="knoepfe">${knoepfe.join("")}</span>
|
|
</div>`;
|
|
}
|
|
|
|
function folienBlock(deck) {
|
|
if (!deck.folien.length) return `<div class="leer">Die Slides ließen sich nicht lesen.</div>`;
|
|
const gruppen = [];
|
|
for (const f of deck.folien) {
|
|
const teil = deck.teile.find(([start]) => start === f.id);
|
|
if (teil || !gruppen.length) gruppen.push({ name: teil ? teil[1] : "", folien: [] });
|
|
gruppen.at(-1).folien.push(f);
|
|
}
|
|
return gruppen.map((g) => `
|
|
${g.name ? `<div class="folien-teil">${esc(g.name)}</div>` : ""}
|
|
<div class="folien">${g.folien.map((f) => `
|
|
<a class="folie-vorschau" href="${SLIDES}/Praesentation.html#f${f.nr}" target="_blank" rel="noopener" title="${esc(f.titel)}">
|
|
<img src="${SLIDES}/export/${String(f.nr).padStart(2, "0")}.png" alt="" loading="lazy">
|
|
<span><span class="nr">${f.nr}</span>${esc(f.titel)}</span>
|
|
</a>`).join("")}
|
|
</div>`).join("");
|
|
}
|
|
|
|
export async function zeichnen(main) {
|
|
const w = S.webinar;
|
|
if (!w) {
|
|
main.innerHTML = `<section class="block warnung"><h2>Keine Webinar-Daten</h2><p>daten/webinar.json fehlt.</p></section>`;
|
|
return;
|
|
}
|
|
const deck = await deckLesen();
|
|
const nrVon = (id) => deck.folien.find((f) => f.id === id)?.nr;
|
|
const ablauf = w.ablauf.map((a, i) => {
|
|
const von = nrVon(a.start);
|
|
const bis = i + 1 < w.ablauf.length ? (nrVon(w.ablauf[i + 1].start) || 0) - 1 : deck.folien.length;
|
|
return { ...a, slides: von && bis ? (von === bis ? `${von}` : `${von} bis ${bis}`) : "" };
|
|
});
|
|
const fertig = w.schritte.filter((s) => s.status === "erledigt").length;
|
|
|
|
main.innerHTML = `
|
|
<header class="seitenkopf">
|
|
<div><div class="ueber">${esc(w.plattform)}, ${w.termin ? esc(langDatum(w.termin)) : "Termin offen"}, ${esc(w.dauer)}</div>
|
|
<h1>Webinar</h1>
|
|
<div class="unter"><b style="color:var(--tinte)">${esc(w.titel)}</b> ${esc(w.untertitel)}</div>
|
|
<div class="thema">Mit <b>${esc(w.sprecher)}</b>. ${esc(w.stand_text)}</div></div>
|
|
<a class="knopf voll" href="${SLIDES}/Praesentation.html" target="_blank" rel="noopener">Präsentation ansehen</a>
|
|
</header>
|
|
|
|
<div class="raster r-2-1">
|
|
<section class="block"><h2>Unterlagen</h2>
|
|
<div class="zeilen">${w.unterlagen.map(unterlage).join("")}</div>
|
|
</section>
|
|
<section class="block"><h2>Stand <span class="neben">${fertig} von ${w.schritte.length} erledigt</span></h2>
|
|
<div class="zeilen">${w.schritte.map((s) => `<div class="zeile" style="grid-template-columns:96px 1fr">
|
|
${pille(s.status)}
|
|
<span><b>${esc(s.name)}</b>${s.notiz ? `<span class="klein" style="display:block">${esc(s.notiz)}</span>` : ""}</span>
|
|
</div>`).join("")}</div>
|
|
</section>
|
|
</div>
|
|
|
|
<section class="block"><h2>Ablauf <span class="neben">${esc(w.dauer)}</span></h2>
|
|
<div class="tab"><table class="daten">
|
|
<thead><tr><th>Minute</th><th>Teil</th><th>Wer</th><th>Knackpunkt</th><th>Slides</th></tr></thead>
|
|
<tbody>${ablauf.map((a) => `<tr>
|
|
<td class="datum">${esc(a.minuten)}</td>
|
|
<td><b>${esc(a.teil)}</b></td>
|
|
<td style="white-space:nowrap">${esc(a.wer)}</td>
|
|
<td>${esc(a.knackpunkt)}</td>
|
|
<td style="white-space:nowrap">${esc(a.slides)}</td>
|
|
</tr>`).join("")}</tbody>
|
|
</table></div>
|
|
</section>
|
|
|
|
<section class="block"><h2>Slides <span class="neben">${deck.folien.length} Slides, zum Öffnen anklicken</span></h2>
|
|
${folienBlock(deck)}
|
|
</section>
|
|
|
|
<section class="block"><h2>Noch offen</h2>
|
|
<div class="zeilen">${w.offen.map((o) => `<div class="zeile" style="grid-template-columns:1fr">${esc(o)}</div>`).join("")}</div>
|
|
</section>
|
|
|
|
<p class="klein">Stand ${esc(w.stand.split("-").reverse().join("."))}. Neu bauen: im Ordner <code>webinar/slides</code> <code>npm run export</code>, das Skript-PDF mit <code>node skript-pdf.mjs HiveGrid-Webinar-Skript.pdf --quelle Skript-kurz.md --fuer-noah</code> im Ordner <code>webinar</code>.</p>`;
|
|
}
|