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>
140 lines
8.3 KiB
JavaScript
140 lines
8.3 KiB
JavaScript
// Formate: Übersicht aus der Formatbibliothek, mit Nutzung im Redaktionsplan.
|
|
|
|
import { S, schublade } from "../app.js";
|
|
import { heute, kurzDatum, esc, md, mdInline, ohneMd, codes, dokLink, pille, FUNNEL_NAME } from "../hilfen.js";
|
|
|
|
const QUELLE = "Ressourcen/Formatbibliothek.md";
|
|
let cache = { zeit: null, daten: null };
|
|
|
|
// Liest die Formatbibliothek: die Übersichtstabelle und die Abschnitte je Format.
|
|
export async function formatbibliothek() {
|
|
const zeit = S.stand?.dokumente;
|
|
if (cache.daten && cache.zeit === zeit) return cache.daten;
|
|
const text = await fetch("/" + QUELLE.split("/").map(encodeURIComponent).join("/"), { cache: "no-store" }).then((r) => r.text());
|
|
|
|
const abschnitte = {};
|
|
const teile = text.split(/^## /m);
|
|
const vorspann = teile.shift();
|
|
for (const t of teile) {
|
|
const kopf = t.slice(0, t.indexOf("\n"));
|
|
const rumpf = t.slice(t.indexOf("\n") + 1);
|
|
const m = kopf.match(/^(F\d{1,2}b?)\s/);
|
|
abschnitte[m ? m[1] : kopf.trim()] = { titel: kopf.trim(), rumpf };
|
|
}
|
|
|
|
const uebersicht = [];
|
|
const tabelle = (abschnitte["Übersicht"]?.rumpf || "").split("\n").filter((z) => /^\|\s*F\d/.test(z));
|
|
for (const z of tabelle) {
|
|
const [code, name, herkunft, aufwand, konto, laenge, start] = z.split("|").slice(1, -1).map((x) => x.trim());
|
|
uebersicht.push({ code, name, herkunft, aufwand, konto, laenge, start });
|
|
}
|
|
cache = { zeit, daten: { vorspann, abschnitte, uebersicht } };
|
|
return cache.daten;
|
|
}
|
|
|
|
function nutzung(code) {
|
|
return S.slots.filter((s) => codes(s.format).includes(code) && s.status !== "ausgefallen");
|
|
}
|
|
|
|
// Wie das Format für Noah heißt, welche Funnel-Stufe es hat und wie das Thema darin vorkommt.
|
|
const planFormat = (code) => S.plan.formate.find((f) => f.kuerzel === code) || S.plan.linkedin.find((l) => l.kuerzel === code);
|
|
const noahName = (code) => planFormat(code)?.noah_name || "";
|
|
const funnelZelle = (code) => (planFormat(code)?.funnel ? `<span class="fu fu-${planFormat(code).funnel}">${FUNNEL_NAME[planFormat(code).funnel]}</span>` : "");
|
|
|
|
function imPlan(code, h) {
|
|
const im = nutzung(code);
|
|
const naechst = im.find((s) => s.datum >= h);
|
|
if (!im.length) return `<span class="muted">nicht im Plan</span>`;
|
|
return `${im.length} Mal${naechst ? `<span class="klein" style="display:block;white-space:nowrap">ab ${kurzDatum(naechst.datum)}</span>` : ""}`;
|
|
}
|
|
|
|
export async function zeichnen(main, r) {
|
|
const h = heute();
|
|
const fb = await formatbibliothek();
|
|
const liste = [...fb.uebersicht];
|
|
// CH steht nur im Redaktionsplan, nicht in der Formatbibliothek.
|
|
for (const f of S.plan.formate) {
|
|
if (!liste.some((x) => x.code === f.kuerzel)) {
|
|
liste.push({ code: f.kuerzel, name: f.name, aufwand: f.aufwand === "mittel" ? "2" : f.aufwand === "leicht" ? "1" : "3", konto: "HiveGrid", start: "laut Redaktionsplan" });
|
|
}
|
|
}
|
|
const allgemein = fb.abschnitte["Was für alle Reel-Formate gilt"];
|
|
|
|
const imPlanListe = liste.filter((f) => nutzung(f.code).length);
|
|
const rest = liste.filter((f) => !nutzung(f.code).length);
|
|
const aufwandText = (a) => ({ "1": "leicht", "2": "mittel", "3": "hoch", "1 bis 2": "leicht bis mittel" }[String(a)] || String(a));
|
|
const zeile = (f, mitPlan) => `<tr class="klickbar" data-format="${esc(f.code)}">
|
|
<td style="width:56px" class="code">${esc(f.code)}</td>
|
|
<td><b>${esc(f.name)}</b>${noahName(f.code) ? `<span class="klein" style="display:block">Für Noah: ${esc(noahName(f.code))}</span>` : ""}</td>
|
|
<td>${funnelZelle(f.code)}</td><td>${esc(aufwandText(f.aufwand))}</td><td>${esc(f.konto || "")}</td>
|
|
${mitPlan ? `<td style="white-space:nowrap">${imPlan(f.code, h)}</td>` : `<td>${esc(f.start || "")}</td>`}
|
|
</tr>`;
|
|
|
|
main.innerHTML = `
|
|
<header class="seitenkopf">
|
|
<div><h1>Formate</h1><div class="unter">Ein Format ist eine wiederholbare Bauform. Alle Formate tragen dasselbe Thema.</div>
|
|
<div class="thema">Thema: <b>${esc(S.plan.thema.name)}</b></div></div>
|
|
<a class="knopf" href="${dokLink(QUELLE)}">Formatbibliothek lesen</a>
|
|
</header>
|
|
<section class="block"><h2>Im Plan <span class="neben">${imPlanListe.length} Formate, zum Öffnen anklicken</span></h2>
|
|
<div class="tab"><table class="daten">
|
|
<thead><tr><th></th><th>Format</th><th>Funnel</th><th>Aufwand</th><th>Konto</th><th>Im Plan</th></tr></thead>
|
|
<tbody>${imPlanListe.map((f) => zeile(f, true)).join("")}</tbody></table></div>
|
|
</section>
|
|
<section class="block">
|
|
<details><summary>${rest.length} weitere Formate, noch nicht im Plan</summary>
|
|
<div class="tab" style="margin-top:10px"><table class="daten">
|
|
<thead><tr><th></th><th>Format</th><th>Funnel</th><th>Aufwand</th><th>Konto</th><th>Start</th></tr></thead>
|
|
<tbody>${rest.map((f) => zeile(f, false)).join("")}</tbody></table></div>
|
|
</details>
|
|
</section>
|
|
${S.plan.ideen?.length ? `<section class="block"><h2>Ideen-Pool zum Thema <span class="neben">nicht fest im Plan, für Ausfälle und nach T5</span></h2>
|
|
<div class="tab"><table class="daten">
|
|
<thead><tr><th>Idee</th><th>Funnel</th><th>Aufwand</th><th>Baut auf</th></tr></thead>
|
|
<tbody>${S.plan.ideen.map((i) => `<tr>
|
|
<td><b>${esc(i.name)}</b><span class="klein" style="display:block">${esc(i.text)}</span></td>
|
|
<td><span class="fu fu-${esc(i.funnel)}">${FUNNEL_NAME[i.funnel] || ""}</span></td>
|
|
<td>${esc(i.aufwand)}</td>
|
|
<td class="klein">${esc([i.aus === "neu" ? "neu" : i.aus, i.vorbild].filter(Boolean).join(", "))}</td></tr>`).join("")}</tbody></table></div>
|
|
</section>` : ""}
|
|
<div class="raster r-2">
|
|
<section class="block"><h2>LinkedIn-Postarten <span class="neben">Text und Bild</span></h2>
|
|
${S.plan.linkedin.map((l) => `<div class="zeile" style="grid-template-columns:40px 1fr auto">
|
|
<span class="code">${esc(l.kuerzel)}</span><span><b>${esc(l.name)}</b>
|
|
<details><summary>Was dahinter steckt</summary><span class="klein" style="display:block;margin-top:6px">${mdInline(l.text)}</span></details></span>
|
|
<span class="klein" style="white-space:nowrap">${nutzung(l.kuerzel).length} Mal</span></div>`).join("")}
|
|
</section>
|
|
<section class="block"><h2>Blickwinkel <span class="neben">auf das eine Thema</span></h2>
|
|
${S.plan.saeulen.map((s) => `<div class="zeile" style="grid-template-columns:40px 1fr auto">
|
|
<span class="code">${esc(s.kuerzel)}</span><span><b>${esc(s.name)}</b><span class="klein" style="display:block">${esc(s.text || "")}</span></span>
|
|
<span class="klein">${S.slots.filter((x) => x.saeule === s.kuerzel).length} Slots</span></div>`).join("")}
|
|
</section>
|
|
</div>
|
|
${allgemein ? `<section class="block"><details><summary>${esc(allgemein.titel)}</summary><div class="md" style="margin-top:10px">${md(allgemein.rumpf)}</div></details></section>` : ""}
|
|
<p class="klein">Woher die Formate kommen: <a href="${dokLink("Ressourcen/Reel-Analyse/analysen/2026-10-05-inspo-liste-dejan.md")}">Analyse der Inspo-Reels</a>.</p>
|
|
`;
|
|
|
|
main.querySelectorAll("[data-format]").forEach((tr) => {
|
|
tr.onclick = () => { location.hash = `#/formate/${encodeURIComponent(tr.dataset.format)}`; };
|
|
});
|
|
|
|
const code = r.rest[0];
|
|
if (!code) return;
|
|
const abschnitt = fb.abschnitte[code];
|
|
const eintrag = liste.find((f) => f.code === code);
|
|
const im = nutzung(code);
|
|
schublade(`
|
|
<div class="ueber">Format</div>
|
|
<h2>${esc(abschnitt ? abschnitt.titel : `${code} ${eintrag?.name || ""}`)}</h2>
|
|
<div class="unter">${eintrag ? [`Aufwand ${esc(eintrag.aufwand)}`, eintrag.konto && esc(eintrag.konto), eintrag.laenge && esc(eintrag.laenge)].filter(Boolean).join(", ") + "." : ""}</div>
|
|
<dl class="felder">
|
|
<dt>Im Plan</dt><dd>${im.length ? im.map((s) =>
|
|
`<a href="#/plan/${esc(s.id)}">${kurzDatum(s.datum)}, ${esc(ohneMd(s.format))}</a>${s.status !== "geplant" ? ` ${pille(s.status)}` : ""}`).join("<br>") : `<span class="muted">nicht im festen Plan</span>`}</dd>
|
|
${planFormat(code)?.funnel ? `<dt>Funnel</dt><dd>${funnelZelle(code)}</dd>` : ""}
|
|
${planFormat(code)?.thema ? `<dt>Im Thema</dt><dd>${esc(planFormat(code).thema)}</dd>` : ""}
|
|
${noahName(code) ? `<dt>Für Noah</dt><dd>${esc(noahName(code))}</dd>` : ""}
|
|
</dl>
|
|
<div class="md" style="margin-top:18px">${abschnitt ? md(abschnitt.rumpf) : `<p class="muted">In der Formatbibliothek gibt es dazu keinen eigenen Abschnitt.</p>`}</div>
|
|
`, { zurueck: "#/formate", breit: true });
|
|
}
|