Portierung aus dem Lokal-Fork (AegisSight-Monitor-Local, Commit 9613610). Vier Angleichungen an das klassische Dashboard. 1. "Klassische Ansicht" ist jetzt derselbe Knopf wie "Studio-Ansicht" im Dashboard, mit Symbol, an erster Stelle im rechten Kopfzeilen-Block. 2. Oben rechts fehlten Barrierefreiheit, Konto-Menü und Abmelden. Der A11yManager liegt jetzt in js/a11y.js und wird von beiden Oberflächen eingebunden, denn das Studio lädt app.js nicht. Konto-Menü (Organisation, Lizenz, Credits, Über KI-Inhalte) und Abmelden nachgezogen, der Theme-Schalter ist derselbe Schiebeschalter wie im Dashboard. 3. Die Fall-Liste hat jetzt die Umschaltung "Alle" / "Eigene" in der Knopf-Optik der klassischen Seitenleiste, der Reiter-Zähler zieht mit. 4. Der Hinweis "Wähle links einen Fall aus" endet vor der Bausteine-Spalte statt am Fensterrand, im gestapelten Layout nimmt er die volle Breite. Das info@-Gating des Studios bleibt unverändert bestehen. Versionsmarker der angefassten Dateien auf 20260725a angehoben. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
163 Zeilen
6.3 KiB
JavaScript
163 Zeilen
6.3 KiB
JavaScript
/**
|
|
* Barrierefreiheits-Manager: Panel mit 4 Schaltern (Kontrast, Focus, Schrift, Animationen).
|
|
*
|
|
* Liegt seit 2026-07-21 in einer eigenen Datei, weil ihn beide Oberflaechen brauchen,
|
|
* das klassische Dashboard (.header-right) und die Studio-Ansicht (.studio-top-right).
|
|
* Vorher steckte er in app.js und fehlte im Studio deshalb komplett.
|
|
*/
|
|
const A11yManager = {
|
|
_key: 'osint_a11y',
|
|
_isOpen: false,
|
|
_settings: { contrast: false, focus: false, fontsize: false, motion: false },
|
|
|
|
init() {
|
|
// Einstellungen aus localStorage laden
|
|
try {
|
|
const saved = JSON.parse(localStorage.getItem(this._key) || '{}');
|
|
Object.keys(this._settings).forEach(k => {
|
|
if (typeof saved[k] === 'boolean') this._settings[k] = saved[k];
|
|
});
|
|
} catch (e) { /* Ungültige Daten ignorieren */ }
|
|
|
|
// Button + Panel dynamisch in die Kopfzeile einfügen (vor Theme-Toggle).
|
|
// Beide Oberflaechen benennen ihren rechten Kopfzeilen-Block anders.
|
|
const headerRight = document.querySelector('.header-right, .studio-top-right');
|
|
const themeToggle = document.getElementById('theme-toggle');
|
|
if (!headerRight) return;
|
|
if (document.getElementById('a11y-btn')) return; // schon vorhanden
|
|
|
|
const container = document.createElement('div');
|
|
container.className = 'a11y-center';
|
|
container.innerHTML = `
|
|
<button class="a11y-btn" id="a11y-btn" title="Barrierefreiheit"
|
|
aria-label="Barrierefreiheit" aria-expanded="false" aria-haspopup="true">
|
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
<circle cx="12" cy="4" r="2"/>
|
|
<path d="M12 8c-3.3 0-6 .5-6 .5v2s2.7-.5 5-.5v3l-3 7h2.5l2.5-5.5 2.5 5.5h2.5l-3-7v-3c2.3 0 5 .5 5 .5v-2S15.3 8 12 8z"/>
|
|
</svg>
|
|
</button>
|
|
<div class="a11y-panel" id="a11y-panel" role="group" aria-label="Barrierefreiheits-Einstellungen" style="display:none;">
|
|
<div class="a11y-panel-title">Barrierefreiheit</div>
|
|
<label class="a11y-option">
|
|
<input type="checkbox" id="a11y-contrast">
|
|
<span class="toggle-switch"></span>
|
|
<span>Hoher Kontrast</span>
|
|
</label>
|
|
<label class="a11y-option">
|
|
<input type="checkbox" id="a11y-focus">
|
|
<span class="toggle-switch"></span>
|
|
<span>Verstärkte Focus-Anzeige</span>
|
|
</label>
|
|
<label class="a11y-option">
|
|
<input type="checkbox" id="a11y-fontsize">
|
|
<span class="toggle-switch"></span>
|
|
<span>Größere Schrift</span>
|
|
</label>
|
|
<label class="a11y-option">
|
|
<input type="checkbox" id="a11y-motion">
|
|
<span class="toggle-switch"></span>
|
|
<span>Animationen aus</span>
|
|
</label>
|
|
</div>
|
|
`;
|
|
|
|
if (themeToggle && themeToggle.parentNode === headerRight) {
|
|
headerRight.insertBefore(container, themeToggle);
|
|
} else {
|
|
headerRight.prepend(container);
|
|
}
|
|
|
|
// Toggle-Event-Listener
|
|
['contrast', 'focus', 'fontsize', 'motion'].forEach(key => {
|
|
document.getElementById('a11y-' + key).addEventListener('change', () => this.toggle(key));
|
|
});
|
|
|
|
// Button öffnet/schließt Panel
|
|
document.getElementById('a11y-btn').addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
this._isOpen ? this._closePanel() : this._openPanel();
|
|
});
|
|
|
|
// Klick außerhalb schließt Panel
|
|
document.addEventListener('click', (e) => {
|
|
if (this._isOpen && !container.contains(e.target)) {
|
|
this._closePanel();
|
|
}
|
|
});
|
|
|
|
// Keyboard: Esc schließt, Pfeiltasten navigieren
|
|
container.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Escape' && this._isOpen) {
|
|
e.stopPropagation();
|
|
this._closePanel();
|
|
return;
|
|
}
|
|
if (!this._isOpen) return;
|
|
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
|
e.preventDefault();
|
|
const options = Array.from(document.querySelectorAll('.a11y-option input[type="checkbox"]'));
|
|
const idx = options.indexOf(document.activeElement);
|
|
let next;
|
|
if (e.key === 'ArrowDown') {
|
|
next = idx < options.length - 1 ? idx + 1 : 0;
|
|
} else {
|
|
next = idx > 0 ? idx - 1 : options.length - 1;
|
|
}
|
|
options[next].focus();
|
|
}
|
|
});
|
|
|
|
// Einstellungen anwenden + Checkboxen synchronisieren
|
|
this._apply();
|
|
this._syncUI();
|
|
},
|
|
|
|
toggle(key) {
|
|
this._settings[key] = !this._settings[key];
|
|
this._apply();
|
|
this._syncUI();
|
|
this._save();
|
|
},
|
|
|
|
_apply() {
|
|
const root = document.documentElement;
|
|
Object.keys(this._settings).forEach(k => {
|
|
if (this._settings[k]) {
|
|
root.setAttribute('data-a11y-' + k, 'true');
|
|
} else {
|
|
root.removeAttribute('data-a11y-' + k);
|
|
}
|
|
});
|
|
},
|
|
|
|
_syncUI() {
|
|
Object.keys(this._settings).forEach(k => {
|
|
const cb = document.getElementById('a11y-' + k);
|
|
if (cb) cb.checked = this._settings[k];
|
|
});
|
|
},
|
|
|
|
_save() {
|
|
localStorage.setItem(this._key, JSON.stringify(this._settings));
|
|
},
|
|
|
|
_openPanel() {
|
|
this._isOpen = true;
|
|
document.getElementById('a11y-panel').style.display = '';
|
|
document.getElementById('a11y-btn').setAttribute('aria-expanded', 'true');
|
|
// Fokus auf erste Option setzen
|
|
requestAnimationFrame(() => {
|
|
const first = document.querySelector('.a11y-option input[type="checkbox"]');
|
|
if (first) first.focus();
|
|
});
|
|
},
|
|
|
|
_closePanel() {
|
|
this._isOpen = false;
|
|
document.getElementById('a11y-panel').style.display = 'none';
|
|
const btn = document.getElementById('a11y-btn');
|
|
btn.setAttribute('aria-expanded', 'false');
|
|
btn.focus();
|
|
}
|
|
};
|