teach/public/js/settings.js
Ramon 526e4e5d52
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 56s
feat: CMS-sectie Inhoud voor de systeemmanager (v0.3.84-beta)
- Nieuwe sectie "Inhoud" in Instellingen (groep Systeem, alleen systeemmanager, doorzoekbaar via het sectieregister)
- Welkomstwoord (NL/EN) verschijnt op het inlogscherm, direct aanpasbaar zonder release
- Melding voor alle gebruikers: aan/uit, niveau informatie/belangrijk, per taal; verschijnt als wegklikbare banner onder de menubalk en elke wijziging krijgt een nieuw id zodat een eerder weggeklikte melding opnieuw verschijnt
- Widget-uitleg: per widget een eigen beschrijving die de standaardtekst in de widgetbibliotheek vervangt (leeg = standaard, placeholder toont de standaardtekst)
- Snelkoppelingen naar de bestaande kanalen voor systeembrede inhoud: gedeelde bibliotheek (borden & ankerdiagrammen, map 🌐 Systeem) en de afbeeldingencatalogus
- settings.js kreeg een generiek haakje (showSettingsDom) zodat modules met een eigen paneel zich in het sectieregister kunnen voegen
- Geverifieerd in headless Chromium (15 checks): banner + dismiss + nieuw-id-gedrag, welkomstwoord, bewerken/opslaan, galerij-override, sectie onzichtbaar voor andere rollen

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149FgUQvuwxKKEdvQGmNngF
2026-07-16 19:33:49 +02:00

173 lines
7.6 KiB
JavaScript

/* teach - instellingen-schil: één sectieregister als enige bron van waarheid.
Elke sectie meldt zich aan met zichtbaarheid per rol, een activate() en
zoekgegevens; de schil bouwt daar de navigatie (gegroepeerd: Persoonlijk /
School / Systeem), de sectiekop en de zoekfunctie uit op. Zo bepaalt de rol
van de ingelogde gebruiker vanzelf wat er te zien is: een gast alleen
Weergave, een leerling/ouder Account + Weergave, staf ook de beheerdelen
(admin.js registreert die zelf - de schil kent het beheer niet van binnen).
De zoekbalk zoekt door sectienamen, instelling-trefwoorden én data
(gebruikers/klassen/scholen via searchItems van de secties). */
"use strict";
const ST_SECTIONS = [];
function registerSettingsSection(sec){ ST_SECTIONS.push(sec); }
(function(){
const wrap = document.getElementById("settingsWrap");
const nav = document.getElementById("stNav");
const content = document.getElementById("stContent");
const secHead = document.getElementById("stSecHead");
const searchInp = document.getElementById("stSearch");
const resultsEl = document.getElementById("stSearchResults");
let activeSection = null;
const el = (tag, cls, text)=>{
const e = document.createElement(tag);
if(cls) e.className = cls;
if(text != null) e.textContent = text;
return e;
};
const visibleSections = ()=>
ST_SECTIONS.filter(s=>s.visible()).sort((a,b)=>(a.order||99)-(b.order||99));
/* ---- ingebouwde secties: Account en Weergave ------------------------------ */
registerSettingsSection({
id:"account", icon:"👤", group:"personal", order:1,
label:()=>T("stAccount"), sub:()=>T("stAccountSub"),
visible:()=>!!currentUser,
activate(){ showDomSection("account"); loadBackups(); },
keywords:()=>[T("stPassword"), T("stBackups"), T("stSession"), T("logout"),
"wachtwoord","password","reservekopie","backup","herstel","restore",
"uitloggen","logout","sessie","session","naam","name"],
});
registerSettingsSection({
id:"view", icon:"🎨", group:"personal", order:2,
label:()=>T("stView"), sub:()=>T("stViewSub"),
visible:()=>true,
activate(){ showDomSection("view"); },
keywords:()=>[T("vwTheme"), T("vwContrast"), T("vwLang"), T("vwTbar"), T("vwMotion"), T("vwGlass"),
"thema","theme","donker","dark","licht","light","contrast","taal","language",
"menubalk","toolbar","animaties","animations","glas","glass"],
});
/* de drie DOM-secties (account/view/admin-host); beheer-secties delen de
admin-host, admin.js wisselt daarbinnen zelf van paneel */
function showDomSection(id){
resultsEl.style.display = "none";
content.querySelectorAll(".st-section").forEach(s=>{
s.style.display = s.dataset.section===id ? "" : "none";
});
}
window.showAdminHost = ()=>showDomSection("admin");
/* modules met een eigen paneel (bv. cms.js) tonen hun sectie-div hiermee */
window.showSettingsDom = showDomSection;
const GROUPS = [["personal","stGroupPersonal"], ["school","stGroupSchool"], ["system","stGroupSystem"]];
function buildNav(){
nav.innerHTML = "";
const secs = visibleSections();
GROUPS.forEach(([g, key])=>{
const inGroup = secs.filter(s=>s.group===g);
if(!inGroup.length) return;
/* groepskoppen pas als er echt iets te groeperen valt (staf); een
gast/leerling met 1-2 secties krijgt een kale, rustige lijst */
if(secs.length > 3) nav.appendChild(el("div","st-nav-group", T(key)));
inGroup.forEach(s=>{
const b = el("button","st-nav-btn");
b.type = "button";
b.dataset.section = s.id;
b.appendChild(el("span","st-nav-ico", s.icon));
b.appendChild(el("span","st-nav-lbl", s.label()));
b.addEventListener("click", ()=>goSection(s.id));
nav.appendChild(b);
});
});
}
async function goSection(id, payload){
const s = ST_SECTIONS.find(x=>x.id===id && x.visible());
if(!s) return;
activeSection = id;
searchInp.value = "";
nav.querySelectorAll(".st-nav-btn").forEach(b=>b.classList.toggle("active", b.dataset.section===id));
secHead.innerHTML = "";
secHead.appendChild(el("div","st-sec-title", s.icon + " " + s.label()));
if(s.sub) secHead.appendChild(el("div","st-sec-sub", s.sub()));
content.scrollTop = 0;
await s.activate(payload);
}
window.goSettingsSection = goSection;
/* ---- zoeken ---------------------------------------------------------------
drie lagen per zichtbare sectie: (1) sectienaam, (2) trefwoorden van de
instellingen erin, (3) live data (searchItems: gebruikers/klassen/scholen).
Resultaten vervangen de inhoud; een klik navigeert (en filtert waar kan). */
function doSearch(){
const q = searchInp.value.trim().toLowerCase();
if(!q){
if(activeSection) goSection(activeSection);
return;
}
nav.querySelectorAll(".st-nav-btn").forEach(b=>b.classList.remove("active"));
secHead.innerHTML = "";
resultsEl.innerHTML = "";
content.querySelectorAll(".st-section").forEach(s=>{ s.style.display = "none"; });
let total = 0;
visibleSections().forEach(s=>{
const hits = [];
if(s.label().toLowerCase().includes(q)){
hits.push({ icon:s.icon, label:s.label(), sub:T("stOpenSection"), go:()=>goSection(s.id) });
}else{
const kw = (s.keywords ? s.keywords() : []).find(k=>String(k).toLowerCase().includes(q));
if(kw) hits.push({ icon:s.icon, label:String(kw), sub:s.label(), go:()=>goSection(s.id) });
}
if(s.searchItems) hits.push(...s.searchItems(q));
if(!hits.length) return;
total += hits.length;
resultsEl.appendChild(el("div","st-nav-group", s.label()));
hits.slice(0, 8).forEach(hit=>{
const row = el("button","st-result");
row.type = "button";
row.appendChild(el("span","st-nav-ico", hit.icon || s.icon));
row.appendChild(el("span","st-result-lbl", hit.label));
if(hit.sub) row.appendChild(el("span","st-result-sub", hit.sub));
row.addEventListener("click", ()=>hit.go());
resultsEl.appendChild(row);
});
});
if(!total) resultsEl.appendChild(el("div","guestnote", T("stNoResults")));
resultsEl.style.display = "";
}
searchInp.addEventListener("input", doSearch);
searchInp.addEventListener("keydown", e=>{
e.stopPropagation();
if(e.key==="Escape"){ searchInp.value = ""; doSearch(); }
if(e.key==="Enter"){ const first = resultsEl.querySelector(".st-result"); if(first) first.click(); }
});
/* ---- openen ---------------------------------------------------------------- */
window.openSettings = function(section){
const msg = document.getElementById("settingsMsg");
msg.textContent = ""; msg.classList.remove("ok");
searchInp.value = "";
searchInp.placeholder = T("stSearchPh");
buildNav();
goSection(section || (currentUser ? "account" : "view"));
/* beheerdata alvast op de achtergrond laden zodat zoeken op namen van
gebruikers/klassen/scholen meteen wat oplevert (admin.js bewaakt zelf
dat er maar één keer geladen wordt) */
if(currentUser && can("admin.access") && window.adminPrefetch) window.adminPrefetch();
wrap.classList.add("open");
};
document.addEventListener("langchange", ()=>{
searchInp.placeholder = T("stSearchPh");
if(wrap.classList.contains("open")){ buildNav(); if(activeSection) goSection(activeSection); }
});
document.addEventListener("userchange", ()=>{
if(wrap.classList.contains("open")){
buildNav();
const secs = visibleSections();
if(!secs.some(s=>s.id===activeSection)) goSection(secs[0] ? secs[0].id : "view");
else goSection(activeSection);
}
});
})();