/* teach - CMS van de systeemmanager: site-inhoud die zonder release aanpasbaar is. Drie sleutels (server: site_content, routes in api.js): welcome - welkomstwoord op het inlogscherm, per taal; announcement - melding/banner voor alle gebruikers (aan/uit, info of belangrijk, per taal; elke wijziging krijgt een nieuw id zodat weggeklikte meldingen opnieuw verschijnen); widgetInfo - per widget een eigen uitleg die de standaardtekst in de widgetgalerij vervangt (per taal, leeg = standaard). Ankerdiagrammen/borden deelt de systeemmanager via de bestaande gedeelde bibliotheek (map 🌐 Systeem in de verkenner) en afbeeldingen via de catalogus - de Inhoud-sectie linkt daar rechtstreeks naartoe. */ "use strict"; let CMS = {}; (function(){ const DISMISS_KEY = "teach.dismissedAnnouncement"; const cmsEl = (tag, cls, text)=>{ const e = document.createElement(tag); if(cls) e.className = cls; if(text != null) e.textContent = text; return e; }; const inLang = v => v ? ((LANG==="en" ? v.en : v.nl) || v.nl || v.en || "") : ""; async function loadContent(){ try{ CMS = await api("/content") || {}; }catch(e){ CMS = {}; } renderAnnouncement(); renderWelcome(); document.dispatchEvent(new CustomEvent("cmschange")); } /* ---- banner (update-melding) voor iedereen -------------------------------- */ let banner = null; function renderAnnouncement(){ if(banner){ banner.remove(); banner = null; } const a = CMS.announcement; const text = a && a.active ? inLang(a) : ""; if(!text) return; let dismissed = null; try{ dismissed = localStorage.getItem(DISMISS_KEY); }catch(e){} if(dismissed === String(a.id)) return; banner = cmsEl("div", "cms-banner" + (a.level==="warn" ? " warn" : "")); banner.appendChild(cmsEl("span", null, text)); const x = cmsEl("button", "cms-banner-x", "✕"); x.type = "button"; x.title = T("close"); x.addEventListener("click", ()=>{ try{ localStorage.setItem(DISMISS_KEY, String(a.id)); }catch(e){} banner.remove(); banner = null; }); banner.appendChild(x); document.body.appendChild(banner); } /* ---- welkomstwoord op het inlogscherm -------------------------------------- */ function renderWelcome(){ let box = document.getElementById("cmsWelcome"); const text = inLang(CMS.welcome); if(!text){ if(box) box.remove(); return; } if(!box){ box = cmsEl("div"); box.id = "cmsWelcome"; document.querySelector("#loginModal .login-logo").after(box); } box.textContent = text; } document.addEventListener("langchange", ()=>{ renderAnnouncement(); renderWelcome(); }); /* ---- Inhoud-sectie in Instellingen (alleen systeemmanager) ----------------- */ const host = cmsEl("div", "st-section"); host.dataset.section = "inhoud"; host.style.display = "none"; document.getElementById("stContent").appendChild(host); async function save(key, value, msgEl){ try{ await api("/admin/content/"+key, { method:"PUT", body:value }); CMS[key] = value; msgEl.textContent = T("cmsSaved"); msgEl.classList.add("ok"); renderAnnouncement(); renderWelcome(); document.dispatchEvent(new CustomEvent("cmschange")); }catch(e){ msgEl.textContent = e.message; msgEl.classList.remove("ok"); } } const langArea = (labelKey, value)=>{ const wrap = cmsEl("label", "cms-field"); wrap.appendChild(cmsEl("span", "am-field-lbl", T(labelKey))); const ta = document.createElement("textarea"); ta.className = "am-inp cms-ta"; ta.maxLength = 2000; ta.value = value || ""; ta.addEventListener("keydown", ev=>ev.stopPropagation()); wrap.appendChild(ta); wrap._ta = ta; return wrap; }; function renderPanel(){ host.innerHTML = ""; /* welkomstwoord */ host.appendChild(cmsEl("div", "am-h3", T("cmsWelcomeH"))); const wMsg = cmsEl("div", "formMsg"); const wNl = langArea("cmsNl", CMS.welcome && CMS.welcome.nl); const wEn = langArea("cmsEn", CMS.welcome && CMS.welcome.en); const wSave = cmsEl("button", "am-btn", T("save")); wSave.type = "button"; wSave.addEventListener("click", ()=>save("welcome", { nl: wNl._ta.value.trim(), en: wEn._ta.value.trim() }, wMsg)); const wBlock = cmsEl("div", "am-reset cms-block"); [wNl, wEn, wSave, wMsg].forEach(n=>wBlock.appendChild(n)); host.appendChild(wBlock); /* update-melding */ host.appendChild(cmsEl("div", "am-h3", T("cmsAnnounceH"))); const a = CMS.announcement || {}; const aMsg = cmsEl("div", "formMsg"); const activeLbl = cmsEl("label", "cms-check"); const active = document.createElement("input"); active.type = "checkbox"; active.checked = !!a.active; activeLbl.appendChild(active); activeLbl.appendChild(cmsEl("span", null, " " + T("cmsActive"))); const level = document.createElement("select"); level.className = "am-sel"; level.appendChild(new Option(T("cmsLevelInfo"), "info")); level.appendChild(new Option(T("cmsLevelWarn"), "warn")); level.value = a.level === "warn" ? "warn" : "info"; const aNl = langArea("cmsNl", a.nl); const aEn = langArea("cmsEn", a.en); const aSave = cmsEl("button", "am-btn", T("save")); aSave.type = "button"; aSave.addEventListener("click", ()=>{ /* nieuw id per wijziging: weggeklikte meldingen verschijnen dan opnieuw */ save("announcement", { active: active.checked, level: level.value, nl: aNl._ta.value.trim(), en: aEn._ta.value.trim(), id: Date.now() }, aMsg); }); const aBlock = cmsEl("div", "am-reset cms-block"); const aRow = cmsEl("div", "cms-row"); aRow.appendChild(activeLbl); aRow.appendChild(level); [aRow, aNl, aEn, aSave, aMsg].forEach(n=>aBlock.appendChild(n)); host.appendChild(aBlock); /* widget-uitleg */ host.appendChild(cmsEl("div", "am-h3", T("cmsWidgetH"))); const gBlock = cmsEl("div", "am-reset cms-block"); const gMsg = cmsEl("div", "formMsg"); const pick = document.createElement("select"); pick.className = "am-sel"; pick.appendChild(new Option(T("cmsWidgetPick"), "")); REGISTRY.forEach(def=>pick.appendChild(new Option(T("wg_"+def.id), def.id))); const editor = cmsEl("div"); editor.style.display = "none"; pick.addEventListener("change", ()=>{ editor.innerHTML = ""; gMsg.textContent = ""; if(!pick.value){ editor.style.display = "none"; return; } const id = pick.value; const cur = (CMS.widgetInfo || {})[id] || {}; const gNl = langArea("cmsNl", cur.nl); const gEn = langArea("cmsEn", cur.en); gNl._ta.placeholder = I18N.nl["wg_"+id+"_d"] || ""; gEn._ta.placeholder = I18N.en["wg_"+id+"_d"] || ""; const hint = cmsEl("div", "guestnote", T("cmsWidgetHint")); const gSave = cmsEl("button", "am-btn", T("save")); gSave.type = "button"; gSave.addEventListener("click", ()=>{ const info = { ...(CMS.widgetInfo || {}) }; const nl = gNl._ta.value.trim(), en = gEn._ta.value.trim(); if(nl || en) info[id] = { nl, en }; else delete info[id]; save("widgetInfo", info, gMsg); }); [gNl, gEn, hint, gSave].forEach(n=>editor.appendChild(n)); editor.style.display = ""; }); gBlock.appendChild(pick); gBlock.appendChild(editor); gBlock.appendChild(gMsg); host.appendChild(gBlock); /* systeembrede inhoud: ankerdiagrammen/borden en afbeeldingen */ host.appendChild(cmsEl("div", "am-h3", T("cmsShortcutsH"))); const sBlock = cmsEl("div", "am-reset cms-block"); sBlock.appendChild(cmsEl("div", "guestnote", T("cmsShortcutsHint"))); const shared = cmsEl("button", "tbtn ghost", T("cmsOpenShared")); shared.type = "button"; shared.addEventListener("click", ()=>{ document.getElementById("settingsWrap").classList.remove("open"); document.getElementById("btnFolders").click(); }); const imgs = cmsEl("button", "tbtn ghost", T("cmsOpenImages")); imgs.type = "button"; imgs.addEventListener("click", ()=>{ document.getElementById("settingsWrap").classList.remove("open"); openImageCatalog(); }); const sRow = cmsEl("div", "cms-row"); sRow.appendChild(shared); sRow.appendChild(imgs); sBlock.appendChild(sRow); host.appendChild(sBlock); } registerSettingsSection({ id:"inhoud", icon:"📢", group:"system", order:31, label:()=>T("stInhoud"), sub:()=>T("stInhoudSub"), visible:()=>!!currentUser && can("content.manage"), activate(){ renderPanel(); showSettingsDom("inhoud"); }, keywords:()=>[T("cmsWelcomeH"), T("cmsAnnounceH"), T("cmsWidgetH"), "welkom","welcome","melding","announcement","banner","uitleg","cms","tekst","text"], }); loadContent(); })();