/* =================================================================== Gemeinsames Verhalten der Kopfleiste: Zurück-Knopf, Abmelden, Suche. Liegt bewusst in einer eigenen Datei. Vorher stand das Abmelden in jeder Seitendatei noch einmal -- fünfmal derselbe Block, fünfmal eine Stelle zum Vergessen, wenn sich etwas ändert. =================================================================== */ (() => { 'use strict'; const START = '/workspace/start.html'; /* Gibt es eine sinnvolle vorherige Seite INNERHALB des Workspace? Blindes history.back() reicht nicht: Wer die Adresse direkt eingibt oder gerade von der Anmeldung kommt, landet damit außerhalb des Workspace oder wieder im Anmeldeformular. Deshalb wird zuerst geprüft, woher der Aufruf kam. */ function hatVorherigeSeite() { if (!document.referrer) return false; let u; try { u = new URL(document.referrer); } catch { return false; } if (u.origin !== location.origin) return false; if (!u.pathname.startsWith('/workspace/')) return false; /* Die Anmeldeseite ist kein sinnvolles Ziel -- wer angemeldet ist, würde dort sofort wieder weitergeleitet. */ if (u.pathname === '/workspace/' || u.pathname.endsWith('/index.html')) return false; /* Gleiche Seite heißt: neu geladen, nicht hergekommen. */ if (u.pathname === location.pathname) return false; return true; } /* Die Startseite hat den Knopf gar nicht erst im Dokument -- dort gibt es kein "davor". Deshalb genügt hier die Prüfung, ob er existiert. */ const knopf = document.getElementById('zurueck'); if (knopf) { const beschriftung = knopf.querySelector('.zurueck-knopf__text'); if (hatVorherigeSeite()) { knopf.hidden = false; knopf.addEventListener('click', () => { /* history.back() statt eines festen Ziels: Es führt wirklich dorthin zurück, wo man war -- samt Bildlaufposition. Nur wenn es keine Historie gibt, wird ersatzweise umgeleitet. */ if (history.length > 1) history.back(); else location.assign(START); }); } else { /* Kein Verlauf, aber wir sind in einem Unterbereich: dann wenigstens ein Weg zur Übersicht. */ knopf.hidden = false; if (beschriftung) beschriftung.textContent = 'Übersicht'; knopf.addEventListener('click', () => location.assign(START)); } } const abmelden = document.getElementById('abmelden'); if (abmelden) { abmelden.addEventListener('click', async () => { abmelden.disabled = true; try { await fetch('/workspace/api/abmelden', { method: 'POST', credentials: 'same-origin', }); } catch { /* Auch ohne Antwort weiterleiten -- das Cookie läuft ohnehin ab. */ } location.assign('/workspace/'); }); } /* ================================================================ Suche über alle Bereiche (Konzept, Phase 4). Baut sich selbst in die Kopfleiste ein, statt in neun HTML-Dateien zu stehen. Eine Suche, die man nur auf einer Extraseite findet, benutzt niemand -- sie muss überall sein, wo man gerade ist. Mit "/" zu öffnen, mit Esc zu schließen. Das Ergebnis wird erst nach einer kurzen Pause geholt, damit nicht jeder Tastendruck eine Abfrage auslöst. ================================================================ */ const kopfRechts = document.querySelector('.kopfleiste__rechts'); if (kopfRechts) aufbauSuche(kopfRechts); function aufbauSuche(wohin) { const el = (tag, klasse, text) => { const k = document.createElement(tag); if (klasse) k.className = klasse; if (text !== undefined) k.textContent = text; return k; }; /* Auslöser in der Kopfleiste */ const knopf = el('button', 'suche-knopf'); knopf.type = 'button'; knopf.title = 'Suchen (Taste /)'; knopf.setAttribute('aria-label', 'Suchen'); const lupe = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); lupe.setAttribute('viewBox', '0 0 24 24'); lupe.setAttribute('class', 'suche-knopf__lupe'); lupe.setAttribute('aria-hidden', 'true'); lupe.innerHTML = ''; knopf.append(lupe, el('span', 'suche-knopf__text', 'Suchen')); wohin.prepend(knopf); /* Overlay */ const schleier = el('div', 'suche-schleier'); schleier.hidden = true; const fenster = el('div', 'suche-fenster'); fenster.setAttribute('role', 'dialog'); fenster.setAttribute('aria-modal', 'true'); fenster.setAttribute('aria-label', 'Suche über alle Bereiche'); const zeile = el('div', 'suche-zeile'); const feld = el('input', 'suche-feld'); feld.type = 'search'; feld.placeholder = 'Suchen – Aufgaben, Gespräche, Dateien, Profile …'; feld.setAttribute('aria-label', 'Suchbegriff'); feld.maxLength = 100; const zu = el('button', 'suche-zu', 'Esc'); zu.type = 'button'; zeile.append(feld, zu); const ergebnis = el('div', 'suche-ergebnis'); ergebnis.setAttribute('aria-live', 'polite'); fenster.append(zeile, ergebnis); schleier.append(fenster); document.body.append(schleier); const zeigeHinweis = (text) => { ergebnis.textContent = ''; ergebnis.append(el('p', 'suche-hinweis', text)); }; let letzte = 0; async function suche() { const q = feld.value.trim(); if (q.length < 2) { zeigeHinweis('Mindestens zwei Zeichen.'); return; } const meine = ++letzte; try { const a = await fetch('/workspace/api/suche?q=' + encodeURIComponent(q), { credentials: 'same-origin' }); if (meine !== letzte) return; // eine neuere Abfrage ist schon unterwegs if (a.status === 401) { location.assign('/workspace/'); return; } if (!a.ok) { zeigeHinweis('Die Suche ist gerade nicht erreichbar.'); return; } zeichne(await a.json()); } catch { if (meine === letzte) zeigeHinweis('Die Suche ist gerade nicht erreichbar.'); } } function zeichne(d) { ergebnis.textContent = ''; if (!d.gesamt) { zeigeHinweis(`Nichts gefunden zu „${d.frage}".`); return; } for (const g of d.gruppen) { const s = el('section', 'suche-gruppe'); s.append(el('h3', 'suche-gruppe__titel', g.titel)); for (const tr of g.treffer) { const a = el('a', 'suche-treffer'); a.href = tr.ziel; a.append(el('span', 'suche-treffer__titel', tr.titel)); if (tr.zusatz) a.append(el('span', 'suche-treffer__zusatz', tr.zusatz)); if (tr.text) a.append(el('span', 'suche-treffer__text', tr.text)); s.append(a); } ergebnis.append(s); } } let warte = null; feld.addEventListener('input', () => { clearTimeout(warte); warte = setTimeout(suche, 220); }); function auf() { schleier.hidden = false; document.body.classList.add('suche-offen'); feld.focus(); feld.select(); if (!feld.value.trim()) zeigeHinweis('Tippen, um alle Bereiche zu durchsuchen.'); } function zuMachen() { schleier.hidden = true; document.body.classList.remove('suche-offen'); knopf.focus(); } knopf.addEventListener('click', auf); zu.addEventListener('click', zuMachen); schleier.addEventListener('mousedown', (e) => { if (e.target === schleier) zuMachen(); }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && !schleier.hidden) { zuMachen(); return; } /* "/" oeffnet die Suche -- aber nur, wenn man nicht ohnehin gerade in ein Feld schreibt. */ if (e.key !== '/' || schleier.hidden === false) return; const z = document.activeElement; if (z && (z.tagName === 'INPUT' || z.tagName === 'TEXTAREA' || z.tagName === 'SELECT' || z.isContentEditable)) return; e.preventDefault(); auf(); }); } })();