diff --git a/IDEAS.md b/IDEAS.md index fd83e08..b7dfd1a 100644 --- a/IDEAS.md +++ b/IDEAS.md @@ -30,14 +30,26 @@ Offen bleibt: Bei mehreren Nutzern trägt die HoneyBar die Farbe des Standardnutzers, weil ihr über die Site-Grenze niemand sagen kann, wer da gerade schaut. -### 1.2 Systemmenü im Logo +### 1.2 Systemmenü im Logo — ✅ gebaut (29.08.2026) -Das `■ HONEYOS` links ist heute nur ein Link zum Schreibtisch. Wie ein -Apfel-Menü gehören darunter: Schreibtisch · Einstellungen · Abmelden. Dann sind -die HoneyOS-Sachen aus jeder App heraus erreichbar, ohne sie zu verlassen. +**Steht**, in beiden Leisten mit denselben Punkten (siehe [README](README.md)). +Der Abmeldeknopf in der Menüleiste des Schreibtischs ist dafür weggefallen — er +stand sonst zweimal da, und die HoneyBar hat ihn nie gehabt. -Die Einstellungen sind derselbe Inhalt wie auf dem Schreibtisch — sie liegen im -Server, also stimmt beides automatisch überein. +Eine Annahme von damals hat sich nicht gehalten: „ohne sie zu verlassen" +**geht nicht**, jedenfalls nicht für Einstellungen und Abmelden. Beides +schreibt, und die HoneyBar darf in einer fremden App nicht schreiben — die +Sitzung kommt über die Site-Grenze nicht mit, und selbst unter einer Domain +liegt das CSRF-Prüfwort in einem Cookie, das nur HoneyOS' eigene Seiten lesen. +Also reicht die HoneyBar die Frage als `?einstellungen=1` / `?abmelden=1` an den +Schreibtisch weiter. Ein Klick, ein Seitenwechsel — dort, wo man angemeldet ist. + +**Was ohne Seitenwechsel ginge, wenn es je stört:** die reinen Aussehensschalter +(Röhre, Leuchten, Scanlines) direkt im Menü. Sie kämen ohne Prüfwort aus, wenn +der Server stattdessen den `Origin` der Anfrage gegen die eingetragenen Apps +hält — dieselbe Liste, die heute schon die CORS-Freigabe steuert. Das ist eine +Sicherheitsentscheidung und keine Kleinigkeit; erst bauen, wenn der Umweg über +den Schreibtisch wirklich nervt. ### 1.3 App-Schublade mit Symbolen diff --git a/README.md b/README.md index 961c66a..e6d72c7 100644 --- a/README.md +++ b/README.md @@ -386,12 +386,41 @@ nur, wessen App selbst eine schickt. - **Andere Proxys:** Caddy braucht ein Zusatzmodul (`replace-response`), Traefik ein Plugin für den Antwortkörper. nginx kann es ohne Erweiterung. +## Das Systemmenü im Logo + +Das `■ HONEYOS` links ist ein Menü — auf dem Schreibtisch wie in jeder App: + +| in der HoneyBar (fremde App) | auf dem Schreibtisch | +|---|---| +| Schreibtisch | Über HoneyOS | +| Einstellungen … | Einstellungen | +| HoneyBar einklappen | Terminal-/Desktop-Modus | +| Abmelden | Abmelden | + +Rechts daneben bleibt der Pfeil mit den Apps. Eines auf heißt anderes zu. + +**In den Apps sind es Links auf den Schreibtisch, keine Handlungen** — und das +ist eine Grenze, keine Bequemlichkeit. Einstellungen und Abmelden schreiben, und +dafür braucht der Server die HoneyOS-Sitzung: Aus einer fremden Site schickt der +Browser sie nicht mit, und selbst unter einer Domain liegt das CSRF-Prüfwort in +einem Cookie, das nur HoneyOS' eigene Seiten lesen dürfen. Ein Abmelden, das ein +einfacher Aufruf erledigt, könnte außerdem jede beliebige Seite auslösen. + +Also stellt die HoneyBar die Frage in der Adresse (`?einstellungen=1`, +`?abmelden=1`), und der Schreibtisch beantwortet sie: Er macht dasselbe +Einstellungsfeld auf, das auch `[S]` öffnet, und meldet mit einem echten POST +ab. Danach nimmt er den Anhang aus der Adresse wieder heraus. + +`Abmelden` steht nur da, wenn HoneyOS überhaupt anmeldet — im offenen Betrieb +oder hinter einem Proxy fehlt der Punkt. Deshalb gibt `/api/apps` neben Liste +und Aussehen auch `oidc` heraus. + ## Wer angemeldet ist Steht auf dem Schreibtisch, als eigenes Widget: Bild, Name und Begrüßung, aus der HoneyOS-Anmeldung. In den Apps steht es **nicht** — die HoneyBar dort trägt genau dasselbe wie die Menüleiste hier, sonst merkt man beim Wechsel, dass es -zwei verschiedene Leisten sind. +zwei verschiedene HoneyBars sind. Früher konnte jede App einen „Wer-bin-ich"-Pfad und einen Token-Schlüssel mitbringen; die HoneyBar fragte damit die App nach dem Anzeigenamen. Beides ist diff --git a/internal/store/store.go b/internal/store/store.go index 362e974..15c98b2 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -213,10 +213,10 @@ func (s *Store) SetSettings(sub string, in map[string]string) error { // SettingsFor liefert das Aussehen für sub — und hilft nach, wenn die Frage // über eine Site-Grenze kam. // -// Die Leiste in einer fremden App weist sich nur mit dem Schlüssel aus, nicht +// Die HoneyBar in einer fremden App weist sich nur mit dem Schlüssel aus, nicht // mit einer Sitzung: der Browser schickt das Cookie dorthin nicht mit. Sie // landet deshalb beim Standardnutzer. Sind die Einstellungen aber unter einem -// Namen aus der Proxy-Kopfzeile gespeichert, wäre der leer — und die Leiste +// Namen aus der Proxy-Kopfzeile gespeichert, wäre der leer — und die HoneyBar // bliebe bernsteinfarben, während der Schreibtisch grün leuchtet. // // Gibt es genau einen Nutzer mit Einstellungen, gilt dessen Aussehen. Bei @@ -246,7 +246,7 @@ func (s *Store) SettingsFor(sub string) map[string]string { } // AppOrigins nennt die Ursprünge der eingetragenen Apps. Nur sie dürfen die -// Leiste mit Anmeldedaten abfragen — die Liste ist damit ihre eigene Freigabe. +// HoneyBar mit Anmeldedaten abfragen — die Liste ist damit ihre eigene Freigabe. func (s *Store) AppOrigins() map[string]bool { s.mu.RLock() defer s.mu.RUnlock() diff --git a/internal/weather/weather.go b/internal/weather/weather.go index ca56eb5..09cfcd2 100644 --- a/internal/weather/weather.go +++ b/internal/weather/weather.go @@ -16,7 +16,7 @@ import ( // Wetter kommt vom Server, nicht aus dem Browser. // -// Die Leiste läuft auf jeder Seite jeder App. Fragte sie selbst beim +// Die HoneyBar läuft auf jeder Seite jeder App. Fragte sie selbst beim // Wetterdienst an, ginge bei jedem Seitenaufruf aus jeder App eine Anfrage samt // IP dorthin — und die CSP jeder App müsste den fremden Ursprung erlauben. Holt // HoneyOS es, sieht der Dienst einen Server, und die Apps bleiben unberührt. diff --git a/internal/weather/weather_test.go b/internal/weather/weather_test.go index ffe3c84..bbf391a 100644 --- a/internal/weather/weather_test.go +++ b/internal/weather/weather_test.go @@ -52,7 +52,7 @@ func TestWetterKommtAn(t *testing.T) { } } -// Der Zwischenspeicher ist der Grund, warum die Leiste auf jeder Seite jeder App +// Der Zwischenspeicher ist der Grund, warum die HoneyBar auf jeder Seite jeder App // stehen darf: Ohne ihn ginge bei jedem Seitenaufruf eine Anfrage nach draußen. func TestZweiteFrageGehtNichtNachDraussen(t *testing.T) { var aufrufe int32 diff --git a/main.go b/main.go index 27b5b18..f1eef9c 100644 --- a/main.go +++ b/main.go @@ -31,7 +31,7 @@ var webFS embed.FS // Version ist die einzige Stelle, an der sie steht. build.sh liest sie hier aus // und vergibt denselben Docker-Tag; die Oberfläche holt sie über /api/public. // Vorher stand sie zusätzlich in os.js und lief mit den Image-Tags auseinander. -const Version = "1.0" +const Version = "1.1" type Server struct { cfg config.Config @@ -128,7 +128,7 @@ func (s *Server) routes() http.Handler { mux.HandleFunc("PUT /api/settings", s.requireAuth(s.handleSetSettings)) mux.HandleFunc("GET /api/apps", s.appsReadable(s.handleApps)) mux.HandleFunc("PUT /api/apps", s.requireAuth(s.handleSetApps)) - // Dieselbe Tür wie die App-Liste: Die Leiste steht in einer fremden App und + // Dieselbe Tür wie die App-Liste: Die HoneyBar steht in einer fremden App und // kommt dort nur mit dem Schlüssel an Daten. mux.HandleFunc("GET /api/weather", s.appsReadable(s.handleWeather)) // Die Ortssuche dagegen nur für Angemeldete. Sie reicht einen Suchbegriff an @@ -180,7 +180,7 @@ func htmlAttr(v string) string { /* ── Zwischenschichten ───────────────────────────────────────────────────── */ -// cors öffnet die Leiste für die eingetragenen Apps — und nur für die. Die +// cors öffnet die HoneyBar für die eingetragenen Apps — und nur für die. Die // App-Liste ist damit zugleich die Freigabeliste; wer nicht darin steht, kommt // auch nicht an die Sitzung. func (s *Server) cors(next http.Handler) http.Handler { @@ -205,11 +205,11 @@ func (s *Server) cors(next http.Handler) http.Handler { func noCache(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Cache-Control", "no-cache") - // Die Leiste wird von fremden Ursprüngen geladen — das ist ihr Zweck. + // Die HoneyBar wird von fremden Ursprüngen geladen — das ist ihr Zweck. // Apps, die selbst COEP „require-corp" setzen (alles mit WASM oder // SharedArrayBuffer, etwa BentoPDF), lehnen sonst jede fremde Datei ab, // die sich nicht ausdrücklich dazu bekennt. Ohne diese Zeile erscheint - // dort keine Leiste, und im Fehlerprotokoll steht nur ein Kürzel. + // dort keine HoneyBar, und im Fehlerprotokoll steht nur ein Kürzel. w.Header().Set("Cross-Origin-Resource-Policy", "cross-origin") next.ServeHTTP(w, r) }) @@ -220,7 +220,7 @@ func noCache(next http.Handler) http.Handler { // Der Grund ist eine Browserregel, keine Bequemlichkeit: Liegt HoneyOS auf // einer anderen Site als die Apps, schickt der Browser die Sitzung bei der // Anfrage aus einer App heraus nicht mit. Ohne einen zweiten Ausweis bekäme die -// Leiste dort nie eine Liste. Der Schlüssel steht im Skript-Tag der App und ist +// HoneyBar dort nie eine Liste. Der Schlüssel steht im Skript-Tag der App und ist // damit nur für den lesbar, der ohnehin an die App herankommt. func (s *Server) appsReadable(next func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { @@ -305,24 +305,27 @@ func (s *Server) handleSetSettings(w http.ResponseWriter, r *http.Request, sub s } // handleApps liefert die Liste **und** das Aussehen. Beides zusammen, weil die -// Leiste in einer fremden App genau eine Anfrage stellen kann: Sie liegt oft auf +// HoneyBar in einer fremden App genau eine Anfrage stellen kann: Sie liegt oft auf // einer anderen Site, kommt also nur mit dem Schlüssel an Daten und hat keine // Sitzung, aus der sich der Rest ergäbe. // // Welche Einstellungen? Die des Angemeldeten, wenn es einen gibt — sonst die des -// Standardnutzers. Bei mehreren Nutzern trägt die Leiste damit die Farbe des +// Standardnutzers. Bei mehreren Nutzern trägt die HoneyBar damit die Farbe des // Standardnutzers; für eine Person stimmt es immer. func (s *Server) handleApps(w http.ResponseWriter, r *http.Request, sub string) { writeJSON(w, http.StatusOK, map[string]any{ "apps": s.store.Apps(), "settings": s.store.SettingsFor(sub), + // Fuer das Systemmenue der HoneyBar: ohne Anbieter gibt es nichts + // abzumelden, dann steht der Punkt dort auch nicht. + "oidc": s.auth.Enabled(), }) } // handleWeather liefert das Wetter für den eingestellten Ort — oder nichts. // // „Nichts" ist hier ein 204 und kein Fehler: Kein Ort eingestellt ist der -// Normalfall, solange niemand einen gewählt hat, und die Leiste soll dafür +// Normalfall, solange niemand einen gewählt hat, und die HoneyBar soll dafür // nicht rot blinken, sondern das Symbol einfach weglassen. Dasselbe gilt, wenn // der Dienst nicht erreichbar ist: lieber kein Wetter als eine Zahl von gestern. func (s *Server) handleWeather(w http.ResponseWriter, r *http.Request, sub string) { diff --git a/web/css/style.css b/web/css/style.css index e9f5f40..1a8c0d8 100644 --- a/web/css/style.css +++ b/web/css/style.css @@ -769,6 +769,14 @@ body { box-shadow: 0 -8px 30px rgba(0, 0, 0, 0.6); } .startmenu[data-open="1"] { display: flex; } +/* Dasselbe Menü, nur andersherum aufgehängt: das Startmenü wächst aus der + Taskleiste nach oben, das Systemmenü aus der Menüleiste nach unten. */ +.sysmenu { + top: calc(32px + env(safe-area-inset-top) + 2px); + bottom: auto; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.6); +} +.mb-logo[aria-expanded="true"] { background: var(--p-faint); } .startmenu .sm-head { padding: 4px 8px; font-size: 10px; @@ -846,7 +854,7 @@ body { } .login-note b { color: var(--p-mid); font-weight: 400; } -/* Nutzer in der Leiste */ +/* Nutzer in der HoneyBar */ /* ── App-Verwaltung ────────────────────────────────────────────────────── */ .apped { display: flex; flex-direction: column; gap: 10px; } @@ -980,7 +988,7 @@ body { .wx-hit:hover { border-color: var(--p-faint); color: var(--p); } /* Wetter in der Menüleiste — dasselbe Maß wie in honeybar.js, damit die - Leiste hier und die in den Apps nicht unterschiedlich aussehen. */ + HoneyBar hier und die in den Apps nicht unterschiedlich aussehen. */ .mb-wx { display: flex; align-items: center; diff --git a/web/honeybar/honeybar.js b/web/honeybar/honeybar.js index 82b4962..ca2617c 100644 --- a/web/honeybar/honeybar.js +++ b/web/honeybar/honeybar.js @@ -56,6 +56,8 @@ let apps = DEFAULT_APPS; let settings = null; + // Ob HoneyOS ueberhaupt anmeldet — ohne Anbieter gaebe es nichts abzumelden. + let oidc = false; // Was die Seite oben schon hatte, bevor die HoneyBar sie heruntergeschoben hat. let urspruenglichesPolster = null; @@ -149,6 +151,7 @@ wie ein anderes Logo aus. */ .home .hex { font-size: 10px; vertical-align: 2px; } .home:hover { border-color: var(--p-dim); } + .home[aria-expanded="true"] { border-color: var(--p-dim); background: var(--p-faint); } /* Bei vielen Apps schiebt man den Streifen seitwärts. Ohne flex-shrink:0 an den Knöpfen quetscht das Raster sie stattdessen zusammen, bis nur noch Buchstabenreste dastehen — am Handy sofort. @@ -183,6 +186,11 @@ background: var(--bg); border: 1px solid var(--p-dim); padding: 4px; display: none; flex-direction: column; gap: 2px; box-shadow: 0 10px 30px rgba(0,0,0,.6); + /* Die Menues haengen neben der HoneyBar, nicht darin — ohne eigene + Schrift erben sie die der fremden App und stehen dann in deren + Fliesstext da. Genau so sah es aus. */ + font: 12px/1.2 ui-monospace, "SF Mono", "DejaVu Sans Mono", Menlo, Consolas, monospace; + color: var(--p); text-shadow: var(--glow); } .menu[data-open="1"] { display: flex; } .menu a, .menu button { text-align: left; padding: 6px 8px; color: var(--p-mid); } @@ -192,11 +200,14 @@ padding: 4px 8px; font-size: 10px; letter-spacing: .18em; text-transform: uppercase; color: var(--p-dim); } + /* Dasselbe Menue, nur an der anderen Seite: das Systemmenue haengt am + Logo links, das App-Menue am Pfeil rechts. */ + .menu.sys { left: 6px; right: auto; } @media (max-width: 560px) { .clock { display: none; } }
- ■ HONEYOS +
@@ -205,11 +216,19 @@
+ + `; /* ── Aussehen ───────────────────────────────────────────────────────────── @@ -327,30 +346,46 @@ clock(); setInterval(clock, 20000); - root.getElementById("home").href = homeUrl; + systemZiele(); } function verdrahten() { if (verdrahtet) return; verdrahtet = true; - const menu = root.getElementById("menu"); - const btn = root.getElementById("menuBtn"); - btn.addEventListener("click", (e) => { - e.stopPropagation(); - const open = menu.dataset.open === "1"; - menu.dataset.open = open ? "0" : "1"; - btn.setAttribute("aria-expanded", String(!open)); + // Zwei Menues, gleiche Mechanik: links das System, rechts die Apps. + // Eines auf heisst anderes zu — zwei offene Felder in 32 px Hoehe + // ueberdecken sich gegenseitig. + const paare = [ + [root.getElementById("sysmenu"), root.getElementById("home")], + [root.getElementById("menu"), root.getElementById("menuBtn")], + ]; + + const close = () => { + paare.forEach(([m, b]) => { + m.dataset.open = "0"; + b.setAttribute("aria-expanded", "false"); + }); + }; + + paare.forEach(([menu, btn]) => { + btn.addEventListener("click", (e) => { + e.stopPropagation(); + const auf = menu.dataset.open === "1"; + close(); + if (!auf) { + menu.dataset.open = "1"; + btn.setAttribute("aria-expanded", "true"); + } + }); }); + // Klick daneben schließt — das Ereignis muss die Grenze des Shadow DOM // in beide Richtungen überleben, deshalb beide Wurzeln. - const close = () => { - menu.dataset.open = "0"; - btn.setAttribute("aria-expanded", "false"); - }; document.addEventListener("click", close); root.addEventListener("click", (e) => { - if (!e.composedPath().includes(menu) && e.target !== btn) close(); + const drin = paare.some(([m, b]) => e.composedPath().includes(m) || e.target === b); + if (!drin) close(); }); root.getElementById("hide").addEventListener("click", () => { @@ -364,6 +399,34 @@ }); } + /** + * Die drei Wege ins System. Alle drei sind Links auf den Schreibtisch, keine + * Aktionen hier — und das ist keine Bequemlichkeit, sondern die Grenze: + * + * Einstellungen und Abmelden **schreiben**. Dafuer braucht der Server die + * HoneyOS-Sitzung, und die schickt der Browser aus einer fremden App nicht + * mit; liegt alles unter einer Domain, kommt sie zwar mit, aber das + * CSRF-Prüfwort steht in einem Cookie der HoneyOS-Adresse, das dieses Skript + * von hier aus nicht lesen darf. Und ein Abmelden, das ein simpler Aufruf + * erledigt, koennte jede fremde Seite ausloesen. + * + * Also: die HoneyBar zeigt den Weg, gehandelt wird dort, wo man angemeldet + * ist. Der Schreibtisch sieht die Frage in der Adresse und macht sie auf. + */ + function systemZiele() { + const ziel = (q) => { + const u = new URL(homeUrl, location.href); + u.search = q; + return u.href; + }; + root.getElementById("mHome").href = new URL(homeUrl, location.href).href; + root.getElementById("mSettings").href = ziel("einstellungen=1"); + const ab = root.getElementById("mLogout"); + ab.href = ziel("abmelden=1"); + ab.hidden = !oidc; + root.getElementById("mLogoutSep").hidden = !oidc; + } + /** * Eingeklappt bleibt ein 3 px hoher Strich am oberen Rand stehen. Er nimmt * praktisch keinen Platz, liegt über dem Inhalt statt ihn zu schieben — und @@ -457,6 +520,7 @@ apps = [{ id: "honeyos", name: "DESKTOP", url: homeUrl }].concat(data.apps); } settings = data.settings || null; + oidc = !!data.oidc; } } catch { /* nicht angemeldet, kein CORS, kein Netz → eingebaute Liste; diff --git a/web/index.html b/web/index.html index ebf9917..0e1bd1d 100644 --- a/web/index.html +++ b/web/index.html @@ -30,7 +30,9 @@ diff --git a/web/js/os.js b/web/js/os.js index 36fe4d1..0ab6f5e 100644 --- a/web/js/os.js +++ b/web/js/os.js @@ -110,7 +110,6 @@ function runBoot() { function renderShell() { $("desktop").hidden = false; - $("btnLogout").hidden = !state.oidc; renderMenubarApps(); renderMode(); tickClock(); @@ -792,6 +791,60 @@ function openAbout() { /* ── Hilfsmittel ───────────────────────────────────────────────────────── */ +/* ── Systemmenü im Logo ──────────────────────────────────────────────────── + * Der Zwilling des Menüs in honeybar.js: gleiche Punkte, gleiche Reihenfolge. + * Hier sind es Handlungen, dort Links hierher — denn nur hier ist man + * angemeldet. Wer aus Aether auf „Einstellungen“ klickt, landet auf genau + * demselben Feld, das dieser Punkt aufmacht. + */ + +function buildSystemMenu() { + const menu = document.createElement("div"); + menu.className = "startmenu sysmenu"; + menu.id = "sysmenu"; + $("desktop").appendChild(menu); + + const btn = $("btnSystem"); + btn.addEventListener("click", (e) => { + e.stopPropagation(); + if (menu.dataset.open === "1") return closeSystemMenu(); + closeStartMenu(); + + const terminal = state.settings.mode === "terminal"; + menu.innerHTML = + `
honeyos
+ + +
+ ` + + (state.oidc ? `
` : ""); + + menu.querySelectorAll("[data-do]").forEach((b) => + b.addEventListener("click", () => { + closeSystemMenu(); + ({ + about: openAbout, + settings: openSettings, + mode: () => setMode(terminal ? "desktop" : "terminal"), + logout, + })[b.dataset.do](); + }) + ); + + menu.dataset.open = "1"; + btn.setAttribute("aria-expanded", "true"); + }); + + document.addEventListener("click", closeSystemMenu); +} + +function closeSystemMenu() { + const menu = $("sysmenu"); + if (!menu) return; + menu.dataset.open = "0"; + $("btnSystem").setAttribute("aria-expanded", "false"); +} + /* ── Startmenü ─────────────────────────────────────────────────────────── */ function buildStartMenu() { @@ -805,6 +858,10 @@ function buildStartMenu() { e.stopPropagation(); const open = menu.dataset.open === "1"; if (open) return closeStartMenu(); + // Beide Menüs offen ginge, sähe aber aus wie ein Fehler: Der Klick auf den + // Startknopf hält sein Ereignis auf, also erreicht es den Schließer des + // Systemmenüs nicht von selbst. + closeSystemMenu(); menu.innerHTML = `
starten
` + state.cfg.apps.map((a) => ``).join("") + @@ -812,8 +869,10 @@ function buildStartMenu() { -
- `; +
` + + // Ohne Anbieter gibt es nichts abzumelden — im Systemmenü steht der + // Punkt aus demselben Grund nur bei Anmeldung. + (state.oidc ? `` : ""); menu.querySelectorAll("[data-app]").forEach((b) => b.addEventListener("click", () => { @@ -834,7 +893,7 @@ function buildStartMenu() { closeStartMenu(); setMode("terminal"); }); - menu.querySelector("[data-logout]").addEventListener("click", logout); + menu.querySelector("[data-logout]")?.addEventListener("click", logout); menu.dataset.open = "1"; btn.setAttribute("aria-expanded", "true"); @@ -922,6 +981,13 @@ async function main() { loadLocalSettings(); applySettings(); + // ?abmelden=1 kommt aus dem Systemmenü der HoneyBar. Es hier zu erledigen + // statt am Server heißt: Die Abmeldung bleibt ein POST von dieser Seite aus, + // und keine fremde Seite kann sie durch ein eingebettetes Bild auslösen. + if (new URLSearchParams(location.search).get("abmelden") === "1") { + return logout(); + } + let pub = { oidc: false, buttonLabel: "Anmelden" }; try { pub = await api("/api/public"); @@ -948,18 +1014,26 @@ async function main() { renderShell(); buildStartMenu(); + buildSystemMenu(); probeApps(); setInterval(tickClock, 15000); setInterval(probeApps, 120000); $("btnSettings").addEventListener("click", openSettings); - $("btnAbout").addEventListener("click", openAbout); - $("btnLogout").addEventListener("click", logout); $("btnClose").addEventListener("click", closeModal); $("modal").addEventListener("click", (e) => { if (e.target === $("modal")) closeModal(); }); + // Was die HoneyBar aus einer fremden App herüberreicht: Sie darf selbst + // nichts schreiben (siehe honeybar.js), also stellt sie die Frage in der + // Adresse und der Schreibtisch beantwortet sie. Danach kommt die Adresse + // weg — ein Neuladen soll die Einstellungen nicht wieder aufreißen. + if (new URLSearchParams(location.search).get("einstellungen") === "1") { + history.replaceState(null, "", location.pathname); + openSettings(); + } + window.addEventListener("keydown", (e) => { if (e.metaKey || e.ctrlKey || e.altKey) return; const typing = /^(INPUT|TEXTAREA|SELECT)$/.test(e.target.tagName); @@ -968,6 +1042,8 @@ async function main() { // einem gleichzeitig das oberste Fenster weg. const menu = $("startmenu"); if (menu && menu.dataset.open === "1") return closeStartMenu(); + const sys = $("sysmenu"); + if (sys && sys.dataset.open === "1") return closeSystemMenu(); return state.settings.mode === "desktop" ? closeTopWindow() : closeModal(); } if (typing) return; diff --git a/web/js/widgets/user.js b/web/js/widgets/user.js index 7d1f9f2..3f3a10a 100644 --- a/web/js/widgets/user.js +++ b/web/js/widgets/user.js @@ -13,7 +13,7 @@ function greeting() { * * Stand früher rechts in der Menüleiste. Dort war es das einzige Stück, das * die HoneyBar in den Apps nicht hat — beide sollen aber gleich aussehen, sonst - * merkt man beim Wechsel, dass es zwei verschiedene Leisten sind. Auf dem + * merkt man beim Wechsel, dass es zwei verschiedene HoneyBars sind. Auf dem * Schreibtisch ist ohnehin Platz dafür, und die Begrüßung gehört dazu. */ function userHtml() { diff --git a/web/js/widgets/weather.js b/web/js/widgets/weather.js index ed2380c..47bd4e3 100644 --- a/web/js/widgets/weather.js +++ b/web/js/widgets/weather.js @@ -53,9 +53,9 @@ export function renderWeather() { // Und in der HoneyBar. Die Menüleiste hier ist zwar anderer Code als // honeybar.js in den Apps, aber für den, der davorsitzt, dieselbe HoneyBar — // ein Schalter, der nur woanders wirkt, wäre nicht zu erklären. - const inLeiste = state.settings.weatherBar === "on" && w; - bar.hidden = !inLeiste; - if (inLeiste) { + const inBar = state.settings.weatherBar === "on" && w; + bar.hidden = !inBar; + if (inBar) { bar.innerHTML = `${glyphSvg(w.glyph)}${Math.round(w.grad)}°`; bar.title = `${w.ort}: ${w.text}, ${Math.round(w.grad)}°`; }