Notiz zeigt sich sofort gesetzt, Widgets nach Wunsch des Nutzers

Aus dem Zettel wird die Notiz: Der Text steht immer formatiert da, roh ist nur
die Zeile mit der Schreibmarke. Dazu eine kleine Werkzeugleiste (Aufgabe,
Aufzaehlung, Nummer, Ueberschrift, fett, Linie), Enter fuehrt Listen fort,
Backspace am Zeilenanfang nimmt erst den Vorsatz. Fuer groessere Umbauten
bleibt der Quelltext-Knopf.

Kein contenteditable: Der Quelltext bleibt die Wahrheit und wird nie aus dem
HTML zurueckgerechnet, das der Browser beim Tippen erzeugt.

Reihenfolge und An/Aus der Widgets liegen jetzt je Nutzer auf dem Server, in
einer Einstellung ("benutzer,notiz,-system,wetter,bild"). Der Schalter fuer das
Wetter auf dem Schreibtisch faellt damit weg — es ist ein Widget wie jedes
andere.

Die Einstellungen sind in Abschnitte sortiert (Darstellung, HoneyBar, Widgets,
Wetter, Programme, Konto), Beschriftung links, Schalter rechts.
This commit is contained in:
marc committed 2026-08-31 01:24:50 +02:00
1 parent c0abfbb491
commit 3807ddca24
12 files changed
+806 -220

No files matched your search

+111 -62
View File
@@ -4,7 +4,9 @@ import {
SS_BOOTED, state,
} from "./core.js";
import { GLYPHS, glyphSvg } from "./glyphs.js";
import { widgetsLaden, widgetsOrdnen, widgetsStarten, widgetsZeichnen } from "./widgets/rahmen.js";
import {
widgetPlan, widgetPlanSetzen, widgetsLaden, widgetsOrdnen, widgetsStarten, widgetsZeichnen,
} from "./widgets/rahmen.js";
import "./widgets/user.js";
import { statsHtml } from "./widgets/system.js";
import { loadWeather, renderWeatherBar } from "./widgets/weather.js";
@@ -551,75 +553,66 @@ function closeModal() {
function openSettings() {
const s = state.settings;
/* Ein Feld je Zeile, Beschriftung links, Schalter rechts — und die Zeilen in
* Abschnitten. Vorher stand alles untereinander in einer einzigen Spalte,
* mit Abstandshaltern zwischen den Schaltern; bei zwölf Einstellungen sieht
* das aus wie eine Liste, in der man suchen muss. */
const seg = (name, opts, cur) =>
`<div class="seg" data-seg="${name}">` +
opts.map(([v, l]) => `<button type="button" data-v="${v}" aria-pressed="${cur === v}">${l}</button>`).join("") +
`</div>`;
const zeile = (label, rechts) => `<div class="reihe"><span class="r-label">${label}</span>${rechts}</div>`;
const schalter = (name, label, anLabel = "an") =>
zeile(label, seg(name, [["on", anLabel], ["off", "aus"]], s[name]));
const sektion = (titel, inhalt) => `<section class="sek"><h4 class="sek-titel">${titel}</h4>${inhalt}</section>`;
openPanel(
"settings",
"einstellungen",
`
<div class="field">
<span class="label">Betriebsart</span>
${seg("mode", [["terminal", "terminal"], ["desktop", "desktop"]], s.mode)}
</div>
<div class="field">
<span class="label">Röhre</span>
${seg("phosphor", [["amber", "amber"], ["green", "grün"], ["ice", "eis"]], s.phosphor)}
</div>
<div class="field">
<span class="label">Bildröhren-Effekte</span>
${seg("scanlines", [["on", "scanlines an"], ["off", "aus"]], s.scanlines)}
<div style="height:6px"></div>
${seg("flicker", [["on", "flackern an"], ["off", "aus"]], s.flicker)}
<div style="height:6px"></div>
${seg("glow", [["on", "glühen an"], ["off", "aus"]], s.glow)}
</div>
<div class="field">
<span class="label">HoneyBar</span>
${seg("barGlyphs", [["on", "symbole an"], ["off", "nur namen"]], s.barGlyphs)}
<p class="ae-hint" style="margin-left:0">gilt für den App-Streifen hier und in jeder App</p>
</div>
<div class="field">
<span class="label">Apps öffnen</span>
${seg("openIn", [["tab", "neuer tab"], ["same", "gleiches fenster"]], s.openIn)}
</div>
<div class="field">
<span class="label">Wetter</span>
${seg("weatherDesk", [["on", "auf dem schreibtisch"], ["off", "aus"]], s.weatherDesk)}
<div style="height:6px"></div>
${seg("weatherBar", [["on", "in der honeybar"], ["off", "aus"]], s.weatherBar)}
<div class="wx-city-row">
<input id="wxQuery" type="text" placeholder="Stadt suchen …" autocomplete="off" />
<button class="btn" id="btnWxSearch">suchen</button>
</div>
<div id="wxResults" class="wx-results"></div>
<p class="ae-hint" id="wxCurrent" style="margin-left:0"></p>
</div>
<div class="field">
<span class="label">Programme</span>
<button class="btn" id="btnApps">${state.cfg.apps.length} eingetragen — verwalten …</button>
</div>
<p class="login-note" style="margin-top:4px">
Diese Einstellungen liegen auf dem Server, nicht im Browser — auf jedem
Gerät, an dem du angemeldet bist, sieht HoneyOS gleich aus.
</p>` +
// Abmelden steht hier und nur hier: Es ist das Einzige, was man aus einer
// fremden App heraus nicht anklicken können soll (siehe honeybar.js), und
// von HoneyOS aus findet man es dort, wo alles andere auch steht.
(state.oidc
? `<div class="field field-out">
<button class="btn btn-out" id="btnLogout">⏻ Abmelden</button>
</div>`
: ""),
sektion(
"Darstellung",
zeile("Betriebsart", seg("mode", [["terminal", "terminal"], ["desktop", "desktop"]], s.mode)) +
zeile("Röhre", seg("phosphor", [["amber", "amber"], ["green", "grün"], ["ice", "eis"]], s.phosphor)) +
schalter("scanlines", "Scanlines") +
schalter("flicker", "Flackern") +
schalter("glow", "Glühen")
) +
sektion(
"HoneyBar",
zeile("Symbole im App-Streifen", seg("barGlyphs", [["on", "an"], ["off", "nur namen"]], s.barGlyphs)) +
zeile("Apps öffnen", seg("openIn", [["tab", "neuer tab"], ["same", "gleiches fenster"]], s.openIn)) +
schalter("weatherBar", "Wetter in der Leiste") +
`<p class="sek-hinweis">Gilt auch für die HoneyBar in deinen Apps — es ist dieselbe Leiste.</p>`
) +
sektion(
"Widgets",
`<ul class="wl" id="wlListe"></ul>
<p class="sek-hinweis">Reihenfolge und An/Aus liegen bei deinem Konto, nicht am Gerät.</p>`
) +
sektion(
"Wetter",
`<div class="wx-city-row">
<input id="wxQuery" type="text" placeholder="Stadt suchen …" autocomplete="off" />
<button class="btn" id="btnWxSearch">suchen</button>
</div>
<div id="wxResults" class="wx-results"></div>
<p class="sek-hinweis" id="wxCurrent"></p>`
) +
sektion(
"Programme",
zeile("Eingetragene Apps", `<button class="btn" id="btnApps">${state.cfg.apps.length} — verwalten …</button>`)
) +
sektion(
"Konto",
`<p class="sek-hinweis">Diese Einstellungen liegen auf dem Server, nicht im Browser — auf jedem
Gerät, an dem du angemeldet bist, sieht HoneyOS gleich aus.</p>` +
// Abmelden steht hier und nur hier: Es ist das Einzige, was man aus
// einer fremden App heraus nicht anklicken können soll (siehe
// honeybar.js), und von HoneyOS aus findet man es dort, wo alles
// andere auch steht.
(state.oidc ? `<button class="btn btn-out" id="btnLogout">⏻ Abmelden</button>` : "")
),
(root) => {
root.querySelectorAll(".seg").forEach((segEl) => {
segEl.addEventListener("click", (e) => {
@@ -644,6 +637,8 @@ function openSettings() {
});
});
widgetListeZeichnen(root.querySelector("#wlListe"));
const zeigeOrt = () => {
root.querySelector("#wxCurrent").textContent = state.settings.weatherCity
? `eingestellt: ${state.settings.weatherCity}`
@@ -698,6 +693,60 @@ function openSettings() {
);
}
/* ── Die Widget-Liste in den Einstellungen ─────────────────────────────────
*
* Dieselbe Bedienung wie bei den Apps: Pfeile statt Ziehen. Ziehen wäre hübsch,
* aber am Handy ist es fummelig, und die Liste ist kurz.
*
* Der Rahmen kennt die Widgets, nicht diese Datei — hier wird nur gezeigt, was
* er hergibt, und zurückgegeben, was der Nutzer daraus gemacht hat.
*/
function widgetListeZeichnen(ul) {
if (!ul) return;
const plan = widgetPlan();
ul.innerHTML = plan
.map(
(p, i) => `
<li class="wl-zeile${p.an ? "" : " wl-aus"}">
<button type="button" class="wl-an" data-an="${i}" aria-pressed="${p.an}">${p.an ? "an" : "aus"}</button>
<span class="wl-name">${escapeHtml(p.titel)}${
p.wo && p.wo.length === 1 ? `<em>nur ${p.wo[0]}</em>` : ""
}</span>
<span class="ae-move">
<button type="button" class="ae-arrow" data-hoch="${i}" ${i === 0 ? "disabled" : ""}>&#9650;</button>
<button type="button" class="ae-arrow" data-runter="${i}" ${
i === plan.length - 1 ? "disabled" : ""
}>&#9660;</button>
</span>
</li>`
)
.join("");
const uebernehmen = () => {
widgetPlanSetzen(plan);
widgetListeZeichnen(ul);
};
ul.querySelectorAll("[data-an]").forEach((b) =>
b.addEventListener("click", () => {
const p = plan[Number(b.dataset.an)];
p.an = !p.an;
uebernehmen();
})
);
const tausche = (i, j) => {
[plan[i], plan[j]] = [plan[j], plan[i]];
uebernehmen();
};
ul.querySelectorAll("[data-hoch]").forEach((b) =>
b.addEventListener("click", () => tausche(Number(b.dataset.hoch), Number(b.dataset.hoch) - 1))
);
ul.querySelectorAll("[data-runter]").forEach((b) =>
b.addEventListener("click", () => tausche(Number(b.dataset.runter), Number(b.dataset.runter) + 1))
);
}
/* ── App-Verwaltung ────────────────────────────────────────────────────────
* Bearbeitet wird eine Kopie der Liste; erst „speichern" schickt sie als
* Ganzes zum Server. Der prüft sie noch einmal und antwortet mit dem, was er