Systemmenue im Logo, in beiden Leisten
Das ■ HONEYOS ist jetzt ein Menue statt eines Links: Schreibtisch, Einstellungen, HoneyBar einklappen, Abmelden. In fremden Apps sind es Links auf den Schreibtisch (?einstellungen=1 / ?abmelden=1), weil die HoneyBar dort nicht schreiben darf — Sitzung und CSRF-Pruefwort ueber- leben die Site-Grenze nicht, und ein Abmelden per Aufruf koennte jede Seite ausloesen. Der Abmeldeknopf in der Menueleiste faellt weg (stand doppelt), und Abmelden erscheint nur, wenn HoneyOS ueberhaupt anmeldet — dafuer gibt /api/apps jetzt oidc mit heraus. Nebenbei: restliche 'Leiste' im Text zu 'HoneyBar' vereinheitlicht.
This commit is contained in:
1 parent
52deffa30e
commit
0a5cd94ba0
12 files changed
+244
-51
No files matched your search
@@ -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
|
Standardnutzers, weil ihr über die Site-Grenze niemand sagen kann, wer da
|
||||||
gerade schaut.
|
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
|
**Steht**, in beiden Leisten mit denselben Punkten (siehe [README](README.md)).
|
||||||
Apfel-Menü gehören darunter: Schreibtisch · Einstellungen · Abmelden. Dann sind
|
Der Abmeldeknopf in der Menüleiste des Schreibtischs ist dafür weggefallen — er
|
||||||
die HoneyOS-Sachen aus jeder App heraus erreichbar, ohne sie zu verlassen.
|
stand sonst zweimal da, und die HoneyBar hat ihn nie gehabt.
|
||||||
|
|
||||||
Die Einstellungen sind derselbe Inhalt wie auf dem Schreibtisch — sie liegen im
|
Eine Annahme von damals hat sich nicht gehalten: „ohne sie zu verlassen"
|
||||||
Server, also stimmt beides automatisch überein.
|
**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
|
### 1.3 App-Schublade mit Symbolen
|
||||||
|
|
||||||
|
|||||||
@@ -386,12 +386,41 @@ nur, wessen App selbst eine schickt.
|
|||||||
- **Andere Proxys:** Caddy braucht ein Zusatzmodul (`replace-response`),
|
- **Andere Proxys:** Caddy braucht ein Zusatzmodul (`replace-response`),
|
||||||
Traefik ein Plugin für den Antwortkörper. nginx kann es ohne Erweiterung.
|
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
|
## Wer angemeldet ist
|
||||||
|
|
||||||
Steht auf dem Schreibtisch, als eigenes Widget: Bild, Name und Begrüßung, aus
|
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
|
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
|
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
|
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
|
mitbringen; die HoneyBar fragte damit die App nach dem Anzeigenamen. Beides ist
|
||||||
|
|||||||
@@ -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
|
// SettingsFor liefert das Aussehen für sub — und hilft nach, wenn die Frage
|
||||||
// über eine Site-Grenze kam.
|
// ü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
|
// mit einer Sitzung: der Browser schickt das Cookie dorthin nicht mit. Sie
|
||||||
// landet deshalb beim Standardnutzer. Sind die Einstellungen aber unter einem
|
// 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.
|
// bliebe bernsteinfarben, während der Schreibtisch grün leuchtet.
|
||||||
//
|
//
|
||||||
// Gibt es genau einen Nutzer mit Einstellungen, gilt dessen Aussehen. Bei
|
// 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
|
// 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 {
|
func (s *Store) AppOrigins() map[string]bool {
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
defer s.mu.RUnlock()
|
defer s.mu.RUnlock()
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
|
|
||||||
// Wetter kommt vom Server, nicht aus dem Browser.
|
// 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
|
// 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
|
// 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.
|
// HoneyOS es, sieht der Dienst einen Server, und die Apps bleiben unberührt.
|
||||||
|
|||||||
@@ -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.
|
// stehen darf: Ohne ihn ginge bei jedem Seitenaufruf eine Anfrage nach draußen.
|
||||||
func TestZweiteFrageGehtNichtNachDraussen(t *testing.T) {
|
func TestZweiteFrageGehtNichtNachDraussen(t *testing.T) {
|
||||||
var aufrufe int32
|
var aufrufe int32
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ var webFS embed.FS
|
|||||||
// Version ist die einzige Stelle, an der sie steht. build.sh liest sie hier aus
|
// 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.
|
// 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.
|
// 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 {
|
type Server struct {
|
||||||
cfg config.Config
|
cfg config.Config
|
||||||
@@ -128,7 +128,7 @@ func (s *Server) routes() http.Handler {
|
|||||||
mux.HandleFunc("PUT /api/settings", s.requireAuth(s.handleSetSettings))
|
mux.HandleFunc("PUT /api/settings", s.requireAuth(s.handleSetSettings))
|
||||||
mux.HandleFunc("GET /api/apps", s.appsReadable(s.handleApps))
|
mux.HandleFunc("GET /api/apps", s.appsReadable(s.handleApps))
|
||||||
mux.HandleFunc("PUT /api/apps", s.requireAuth(s.handleSetApps))
|
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.
|
// kommt dort nur mit dem Schlüssel an Daten.
|
||||||
mux.HandleFunc("GET /api/weather", s.appsReadable(s.handleWeather))
|
mux.HandleFunc("GET /api/weather", s.appsReadable(s.handleWeather))
|
||||||
// Die Ortssuche dagegen nur für Angemeldete. Sie reicht einen Suchbegriff an
|
// Die Ortssuche dagegen nur für Angemeldete. Sie reicht einen Suchbegriff an
|
||||||
@@ -180,7 +180,7 @@ func htmlAttr(v string) string {
|
|||||||
|
|
||||||
/* ── Zwischenschichten ───────────────────────────────────────────────────── */
|
/* ── 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
|
// App-Liste ist damit zugleich die Freigabeliste; wer nicht darin steht, kommt
|
||||||
// auch nicht an die Sitzung.
|
// auch nicht an die Sitzung.
|
||||||
func (s *Server) cors(next http.Handler) http.Handler {
|
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 {
|
func noCache(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Cache-Control", "no-cache")
|
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
|
// Apps, die selbst COEP „require-corp" setzen (alles mit WASM oder
|
||||||
// SharedArrayBuffer, etwa BentoPDF), lehnen sonst jede fremde Datei ab,
|
// SharedArrayBuffer, etwa BentoPDF), lehnen sonst jede fremde Datei ab,
|
||||||
// die sich nicht ausdrücklich dazu bekennt. Ohne diese Zeile erscheint
|
// 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")
|
w.Header().Set("Cross-Origin-Resource-Policy", "cross-origin")
|
||||||
next.ServeHTTP(w, r)
|
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
|
// Der Grund ist eine Browserregel, keine Bequemlichkeit: Liegt HoneyOS auf
|
||||||
// einer anderen Site als die Apps, schickt der Browser die Sitzung bei der
|
// 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
|
// 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.
|
// 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 {
|
func (s *Server) appsReadable(next func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
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
|
// 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
|
// einer anderen Site, kommt also nur mit dem Schlüssel an Daten und hat keine
|
||||||
// Sitzung, aus der sich der Rest ergäbe.
|
// Sitzung, aus der sich der Rest ergäbe.
|
||||||
//
|
//
|
||||||
// Welche Einstellungen? Die des Angemeldeten, wenn es einen gibt — sonst die des
|
// 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.
|
// Standardnutzers; für eine Person stimmt es immer.
|
||||||
func (s *Server) handleApps(w http.ResponseWriter, r *http.Request, sub string) {
|
func (s *Server) handleApps(w http.ResponseWriter, r *http.Request, sub string) {
|
||||||
writeJSON(w, http.StatusOK, map[string]any{
|
writeJSON(w, http.StatusOK, map[string]any{
|
||||||
"apps": s.store.Apps(),
|
"apps": s.store.Apps(),
|
||||||
"settings": s.store.SettingsFor(sub),
|
"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.
|
// handleWeather liefert das Wetter für den eingestellten Ort — oder nichts.
|
||||||
//
|
//
|
||||||
// „Nichts" ist hier ein 204 und kein Fehler: Kein Ort eingestellt ist der
|
// „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
|
// nicht rot blinken, sondern das Symbol einfach weglassen. Dasselbe gilt, wenn
|
||||||
// der Dienst nicht erreichbar ist: lieber kein Wetter als eine Zahl von gestern.
|
// 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) {
|
func (s *Server) handleWeather(w http.ResponseWriter, r *http.Request, sub string) {
|
||||||
|
|||||||
+10
-2
@@ -769,6 +769,14 @@ body {
|
|||||||
box-shadow: 0 -8px 30px rgba(0, 0, 0, 0.6);
|
box-shadow: 0 -8px 30px rgba(0, 0, 0, 0.6);
|
||||||
}
|
}
|
||||||
.startmenu[data-open="1"] { display: flex; }
|
.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 {
|
.startmenu .sm-head {
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
@@ -846,7 +854,7 @@ body {
|
|||||||
}
|
}
|
||||||
.login-note b { color: var(--p-mid); font-weight: 400; }
|
.login-note b { color: var(--p-mid); font-weight: 400; }
|
||||||
|
|
||||||
/* Nutzer in der Leiste */
|
/* Nutzer in der HoneyBar */
|
||||||
|
|
||||||
/* ── App-Verwaltung ────────────────────────────────────────────────────── */
|
/* ── App-Verwaltung ────────────────────────────────────────────────────── */
|
||||||
.apped { display: flex; flex-direction: column; gap: 10px; }
|
.apped { display: flex; flex-direction: column; gap: 10px; }
|
||||||
@@ -980,7 +988,7 @@ body {
|
|||||||
.wx-hit:hover { border-color: var(--p-faint); color: var(--p); }
|
.wx-hit:hover { border-color: var(--p-faint); color: var(--p); }
|
||||||
|
|
||||||
/* Wetter in der Menüleiste — dasselbe Maß wie in honeybar.js, damit die
|
/* 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 {
|
.mb-wx {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
+80
-16
@@ -56,6 +56,8 @@
|
|||||||
|
|
||||||
let apps = DEFAULT_APPS;
|
let apps = DEFAULT_APPS;
|
||||||
let settings = null;
|
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.
|
// Was die Seite oben schon hatte, bevor die HoneyBar sie heruntergeschoben hat.
|
||||||
let urspruenglichesPolster = null;
|
let urspruenglichesPolster = null;
|
||||||
|
|
||||||
@@ -149,6 +151,7 @@
|
|||||||
wie ein anderes Logo aus. */
|
wie ein anderes Logo aus. */
|
||||||
.home .hex { font-size: 10px; vertical-align: 2px; }
|
.home .hex { font-size: 10px; vertical-align: 2px; }
|
||||||
.home:hover { border-color: var(--p-dim); }
|
.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
|
/* Bei vielen Apps schiebt man den Streifen seitwärts. Ohne
|
||||||
flex-shrink:0 an den Knöpfen quetscht das Raster sie stattdessen
|
flex-shrink:0 an den Knöpfen quetscht das Raster sie stattdessen
|
||||||
zusammen, bis nur noch Buchstabenreste dastehen — am Handy sofort.
|
zusammen, bis nur noch Buchstabenreste dastehen — am Handy sofort.
|
||||||
@@ -183,6 +186,11 @@
|
|||||||
background: var(--bg); border: 1px solid var(--p-dim); padding: 4px;
|
background: var(--bg); border: 1px solid var(--p-dim); padding: 4px;
|
||||||
display: none; flex-direction: column; gap: 2px;
|
display: none; flex-direction: column; gap: 2px;
|
||||||
box-shadow: 0 10px 30px rgba(0,0,0,.6);
|
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[data-open="1"] { display: flex; }
|
||||||
.menu a, .menu button { text-align: left; padding: 6px 8px; color: var(--p-mid); }
|
.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;
|
padding: 4px 8px; font-size: 10px; letter-spacing: .18em;
|
||||||
text-transform: uppercase; color: var(--p-dim);
|
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; } }
|
@media (max-width: 560px) { .clock { display: none; } }
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="bar">
|
<div class="bar">
|
||||||
<a class="home" id="home" title="HoneyOS"><span class="hex">■</span> HONEY<b>OS</b></a>
|
<button class="home" id="home" title="HoneyOS-Menü" aria-haspopup="true" aria-expanded="false"><span class="hex">■</span> HONEY<b>OS</b></button>
|
||||||
<nav class="apps" id="apps"></nav>
|
<nav class="apps" id="apps"></nav>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<span class="wx" id="wx" hidden></span>
|
<span class="wx" id="wx" hidden></span>
|
||||||
@@ -205,11 +216,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="menu sys" id="sysmenu" role="menu">
|
||||||
|
<div class="head">honeyos</div>
|
||||||
|
<a id="mHome" role="menuitem">Schreibtisch</a>
|
||||||
|
<a id="mSettings" role="menuitem">Einstellungen …</a>
|
||||||
|
<hr />
|
||||||
|
<button id="hide" role="menuitem">HoneyBar einklappen</button>
|
||||||
|
<hr id="mLogoutSep" hidden />
|
||||||
|
<a id="mLogout" role="menuitem" hidden>Abmelden</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="menu" id="menu" role="menu">
|
<div class="menu" id="menu" role="menu">
|
||||||
<div class="head">wechseln zu</div>
|
<div class="head">wechseln zu</div>
|
||||||
<div id="menuApps"></div>
|
<div id="menuApps"></div>
|
||||||
<hr />
|
|
||||||
<button id="hide" role="menuitem">honeybar einklappen</button>
|
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
/* ── Aussehen ─────────────────────────────────────────────────────────────
|
/* ── Aussehen ─────────────────────────────────────────────────────────────
|
||||||
@@ -327,30 +346,46 @@
|
|||||||
clock();
|
clock();
|
||||||
setInterval(clock, 20000);
|
setInterval(clock, 20000);
|
||||||
|
|
||||||
root.getElementById("home").href = homeUrl;
|
systemZiele();
|
||||||
}
|
}
|
||||||
|
|
||||||
function verdrahten() {
|
function verdrahten() {
|
||||||
if (verdrahtet) return;
|
if (verdrahtet) return;
|
||||||
verdrahtet = true;
|
verdrahtet = true;
|
||||||
|
|
||||||
const menu = root.getElementById("menu");
|
// Zwei Menues, gleiche Mechanik: links das System, rechts die Apps.
|
||||||
const btn = root.getElementById("menuBtn");
|
// Eines auf heisst anderes zu — zwei offene Felder in 32 px Hoehe
|
||||||
btn.addEventListener("click", (e) => {
|
// ueberdecken sich gegenseitig.
|
||||||
e.stopPropagation();
|
const paare = [
|
||||||
const open = menu.dataset.open === "1";
|
[root.getElementById("sysmenu"), root.getElementById("home")],
|
||||||
menu.dataset.open = open ? "0" : "1";
|
[root.getElementById("menu"), root.getElementById("menuBtn")],
|
||||||
btn.setAttribute("aria-expanded", String(!open));
|
];
|
||||||
|
|
||||||
|
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
|
// Klick daneben schließt — das Ereignis muss die Grenze des Shadow DOM
|
||||||
// in beide Richtungen überleben, deshalb beide Wurzeln.
|
// in beide Richtungen überleben, deshalb beide Wurzeln.
|
||||||
const close = () => {
|
|
||||||
menu.dataset.open = "0";
|
|
||||||
btn.setAttribute("aria-expanded", "false");
|
|
||||||
};
|
|
||||||
document.addEventListener("click", close);
|
document.addEventListener("click", close);
|
||||||
root.addEventListener("click", (e) => {
|
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", () => {
|
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
|
* 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
|
* 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);
|
apps = [{ id: "honeyos", name: "DESKTOP", url: homeUrl }].concat(data.apps);
|
||||||
}
|
}
|
||||||
settings = data.settings || null;
|
settings = data.settings || null;
|
||||||
|
oidc = !!data.oidc;
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
/* nicht angemeldet, kein CORS, kein Netz → eingebaute Liste;
|
/* nicht angemeldet, kein CORS, kein Netz → eingebaute Liste;
|
||||||
|
|||||||
+3
-2
@@ -30,7 +30,9 @@
|
|||||||
<div class="desktop" id="desktop" hidden>
|
<div class="desktop" id="desktop" hidden>
|
||||||
<!-- Menüleiste: dieselbe Anatomie wie honeybar.js in den Apps -->
|
<!-- Menüleiste: dieselbe Anatomie wie honeybar.js in den Apps -->
|
||||||
<header class="menubar">
|
<header class="menubar">
|
||||||
<button class="mb-logo" id="btnAbout" title="Über HoneyOS">
|
<!-- Das Logo ist das Systemmenü — dasselbe wie in der HoneyBar
|
||||||
|
fremder Apps, damit HoneyOS von überall gleich erreichbar ist. -->
|
||||||
|
<button class="mb-logo" id="btnSystem" title="HoneyOS-Menü" aria-haspopup="true" aria-expanded="false">
|
||||||
<span class="hex">■</span> HONEY<b>OS</b>
|
<span class="hex">■</span> HONEY<b>OS</b>
|
||||||
</button>
|
</button>
|
||||||
<nav class="mb-apps" id="mbApps" aria-label="Anwendungen"></nav>
|
<nav class="mb-apps" id="mbApps" aria-label="Anwendungen"></nav>
|
||||||
@@ -38,7 +40,6 @@
|
|||||||
<span class="mb-wx" id="mbWx" hidden></span>
|
<span class="mb-wx" id="mbWx" hidden></span>
|
||||||
<span class="mb-clock" id="clock">--:--</span>
|
<span class="mb-clock" id="clock">--:--</span>
|
||||||
<button class="mb-btn" id="btnSettings" title="Einstellungen (S)">⚙</button>
|
<button class="mb-btn" id="btnSettings" title="Einstellungen (S)">⚙</button>
|
||||||
<button class="mb-btn" id="btnLogout" title="Abmelden" hidden>⏻</button>
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|||||||
+82
-6
@@ -110,7 +110,6 @@ function runBoot() {
|
|||||||
|
|
||||||
function renderShell() {
|
function renderShell() {
|
||||||
$("desktop").hidden = false;
|
$("desktop").hidden = false;
|
||||||
$("btnLogout").hidden = !state.oidc;
|
|
||||||
renderMenubarApps();
|
renderMenubarApps();
|
||||||
renderMode();
|
renderMode();
|
||||||
tickClock();
|
tickClock();
|
||||||
@@ -792,6 +791,60 @@ function openAbout() {
|
|||||||
/* ── Hilfsmittel ───────────────────────────────────────────────────────── */
|
/* ── 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 =
|
||||||
|
`<div class="sm-head">honeyos</div>
|
||||||
|
<button data-do="about">Über HoneyOS</button>
|
||||||
|
<button data-do="settings">Einstellungen</button>
|
||||||
|
<hr />
|
||||||
|
<button data-do="mode">${terminal ? "▣ Desktop-Modus" : "↩ Terminal-Modus"}</button>` +
|
||||||
|
(state.oidc ? `<hr /><button data-do="logout">⏻ Abmelden</button>` : "");
|
||||||
|
|
||||||
|
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ü ─────────────────────────────────────────────────────────── */
|
/* ── Startmenü ─────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
function buildStartMenu() {
|
function buildStartMenu() {
|
||||||
@@ -805,6 +858,10 @@ function buildStartMenu() {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const open = menu.dataset.open === "1";
|
const open = menu.dataset.open === "1";
|
||||||
if (open) return closeStartMenu();
|
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 = `<div class="sm-head">starten</div>` +
|
menu.innerHTML = `<div class="sm-head">starten</div>` +
|
||||||
state.cfg.apps.map((a) => `<button data-app="${a.id}">${escapeHtml(a.name)}</button>`).join("") +
|
state.cfg.apps.map((a) => `<button data-app="${a.id}">${escapeHtml(a.name)}</button>`).join("") +
|
||||||
@@ -812,8 +869,10 @@ function buildStartMenu() {
|
|||||||
<button data-win="system">System</button>
|
<button data-win="system">System</button>
|
||||||
<button data-win="settings">Einstellungen</button>
|
<button data-win="settings">Einstellungen</button>
|
||||||
<button data-win="about">Über HoneyOS</button>
|
<button data-win="about">Über HoneyOS</button>
|
||||||
<hr /><button data-mode="terminal">↩ Terminal-Modus</button>
|
<hr /><button data-mode="terminal">↩ Terminal-Modus</button>` +
|
||||||
<button data-logout="1">⏻ Abmelden</button>`;
|
// Ohne Anbieter gibt es nichts abzumelden — im Systemmenü steht der
|
||||||
|
// Punkt aus demselben Grund nur bei Anmeldung.
|
||||||
|
(state.oidc ? `<button data-logout="1">⏻ Abmelden</button>` : "");
|
||||||
|
|
||||||
menu.querySelectorAll("[data-app]").forEach((b) =>
|
menu.querySelectorAll("[data-app]").forEach((b) =>
|
||||||
b.addEventListener("click", () => {
|
b.addEventListener("click", () => {
|
||||||
@@ -834,7 +893,7 @@ function buildStartMenu() {
|
|||||||
closeStartMenu();
|
closeStartMenu();
|
||||||
setMode("terminal");
|
setMode("terminal");
|
||||||
});
|
});
|
||||||
menu.querySelector("[data-logout]").addEventListener("click", logout);
|
menu.querySelector("[data-logout]")?.addEventListener("click", logout);
|
||||||
|
|
||||||
menu.dataset.open = "1";
|
menu.dataset.open = "1";
|
||||||
btn.setAttribute("aria-expanded", "true");
|
btn.setAttribute("aria-expanded", "true");
|
||||||
@@ -922,6 +981,13 @@ async function main() {
|
|||||||
loadLocalSettings();
|
loadLocalSettings();
|
||||||
applySettings();
|
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" };
|
let pub = { oidc: false, buttonLabel: "Anmelden" };
|
||||||
try {
|
try {
|
||||||
pub = await api("/api/public");
|
pub = await api("/api/public");
|
||||||
@@ -948,18 +1014,26 @@ async function main() {
|
|||||||
|
|
||||||
renderShell();
|
renderShell();
|
||||||
buildStartMenu();
|
buildStartMenu();
|
||||||
|
buildSystemMenu();
|
||||||
probeApps();
|
probeApps();
|
||||||
setInterval(tickClock, 15000);
|
setInterval(tickClock, 15000);
|
||||||
setInterval(probeApps, 120000);
|
setInterval(probeApps, 120000);
|
||||||
|
|
||||||
$("btnSettings").addEventListener("click", openSettings);
|
$("btnSettings").addEventListener("click", openSettings);
|
||||||
$("btnAbout").addEventListener("click", openAbout);
|
|
||||||
$("btnLogout").addEventListener("click", logout);
|
|
||||||
$("btnClose").addEventListener("click", closeModal);
|
$("btnClose").addEventListener("click", closeModal);
|
||||||
$("modal").addEventListener("click", (e) => {
|
$("modal").addEventListener("click", (e) => {
|
||||||
if (e.target === $("modal")) closeModal();
|
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) => {
|
window.addEventListener("keydown", (e) => {
|
||||||
if (e.metaKey || e.ctrlKey || e.altKey) return;
|
if (e.metaKey || e.ctrlKey || e.altKey) return;
|
||||||
const typing = /^(INPUT|TEXTAREA|SELECT)$/.test(e.target.tagName);
|
const typing = /^(INPUT|TEXTAREA|SELECT)$/.test(e.target.tagName);
|
||||||
@@ -968,6 +1042,8 @@ async function main() {
|
|||||||
// einem gleichzeitig das oberste Fenster weg.
|
// einem gleichzeitig das oberste Fenster weg.
|
||||||
const menu = $("startmenu");
|
const menu = $("startmenu");
|
||||||
if (menu && menu.dataset.open === "1") return closeStartMenu();
|
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();
|
return state.settings.mode === "desktop" ? closeTopWindow() : closeModal();
|
||||||
}
|
}
|
||||||
if (typing) return;
|
if (typing) return;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ function greeting() {
|
|||||||
*
|
*
|
||||||
* Stand früher rechts in der Menüleiste. Dort war es das einzige Stück, das
|
* 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
|
* 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.
|
* Schreibtisch ist ohnehin Platz dafür, und die Begrüßung gehört dazu.
|
||||||
*/
|
*/
|
||||||
function userHtml() {
|
function userHtml() {
|
||||||
|
|||||||
@@ -53,9 +53,9 @@ export function renderWeather() {
|
|||||||
// Und in der HoneyBar. Die Menüleiste hier ist zwar anderer Code als
|
// 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 —
|
// 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.
|
// ein Schalter, der nur woanders wirkt, wäre nicht zu erklären.
|
||||||
const inLeiste = state.settings.weatherBar === "on" && w;
|
const inBar = state.settings.weatherBar === "on" && w;
|
||||||
bar.hidden = !inLeiste;
|
bar.hidden = !inBar;
|
||||||
if (inLeiste) {
|
if (inBar) {
|
||||||
bar.innerHTML = `${glyphSvg(w.glyph)}<span class="mb-wx-temp">${Math.round(w.grad)}°</span>`;
|
bar.innerHTML = `${glyphSvg(w.glyph)}<span class="mb-wx-temp">${Math.round(w.grad)}°</span>`;
|
||||||
bar.title = `${w.ort}: ${w.text}, ${Math.round(w.grad)}°`;
|
bar.title = `${w.ort}: ${w.text}, ${Math.round(w.grad)}°`;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user