From 0e1c986f9096610768175d1a0ceea44205095c32 Mon Sep 17 00:00:00 2001 From: marc Date: Sat, 29 Aug 2026 23:48:04 +0200 Subject: [PATCH] Reihenfolge mit Pfeilen, Zifferntasten entfernt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sortiert wird im Programm-Editor mit Pfeilen in der Kopfzeile jeder Zeile — das Array ist die Reihenfolge, also gilt sie ueberall zugleich: Streifen, Schublade, Kacheln, Schreibtischsymbole, Startmenue. Neue Apps haengen sich hinten an, statt eine Taste zu verlangen. Die Zifferntasten sind ganz weg (Marcs Entscheidung): ein Pflichtfeld je App, das beim Sortieren nur die Frage aufgeworfen haette, ob es mitwandert. Damit faellt das Feld 'key' aus dem Datenmodell, aus der Kachel, aus der Hilfe und aus der Statuszeile. Alte Eintraege verlieren es beim naechsten Speichern still. Kein A-Z-Knopf — erstmal zu viel, kommt zurueck wenn er fehlt. --- IDEAS.md | 14 +++++++---- README.md | 7 ++++-- internal/store/store.go | 5 ---- web/css/style.css | 26 +++++++++++++-------- web/index.html | 2 +- web/js/os.js | 51 +++++++++++++++++++++++++++++++++-------- 6 files changed, 73 insertions(+), 32 deletions(-) diff --git a/IDEAS.md b/IDEAS.md index 292ec2d..322702c 100644 --- a/IDEAS.md +++ b/IDEAS.md @@ -117,9 +117,15 @@ Zeigerereignis-Variante lohnt erst, wenn sie mehrfach gebraucht wird, und genau das kommt: „Widgets verschieben" braucht sie zwingend. Wird sie dort gebaut, kann die App-Liste sie später erben. -Dazu ein Knopf **A–Z**: einmal alphabetisch sortieren, danach ist es wieder -Handarbeit. Kein dauerhafter Modus, keine Einstellung, keine zwei -Sortierzustände im Kopf. +**Kein A–Z-Knopf** (Marc, 29.08.2026): erstmal zu viel. Bei einer Handvoll Apps +sortiert man einmal von Hand und rührt es nie wieder an — ein zweiter Weg +dorthin ist Zierat. Kommt zurück, wenn er fehlt. + +**Die Zifferntasten sind bei der Gelegenheit ganz weggefallen.** Sie waren ein +Pflichtfeld je App, das man beim Anlegen ausfüllen musste, und sie hätten beim +Sortieren dieselbe Frage aufgeworfen: mitwandern oder stehenbleiben? Beides +falsch. Neue Apps hängen sich jetzt einfach hinten an, die Reihenfolge macht man +mit den Pfeilen. Beim Bauen zu beachten: @@ -128,8 +134,6 @@ Beim Bauen zu beachten: - Beim Neuzeichnen müssen offene Zeilen offen bleiben. - Gespeichert wird wie bisher die ganze Liste auf einmal: Man kann probieren, ohne dass sofort alles umspringt. -- **Die Zifferntasten wandern nicht mit.** Die stehen je App fest; sonst startet - `2` morgen etwas anderes als heute. #### Und eine eigene Reihenfolge je Nutzer? — später, aber der Weg bleibt offen diff --git a/README.md b/README.md index 40dd6b2..539c33d 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,10 @@ fragt über die Domaingrenze und bleibt dort leer. | Name, Untertitel | Beschriftung auf Kachel und Symbol | | Adresse | wohin der Start springt | | Symbol | cloud · note · box · chip · folder · gear · globe · disk | -| Taste | Zifferntaste im Terminal-Modus | + +**Die Reihenfolge machst du mit den Pfeilen** ▲▼ in der Kopfzeile jeder Zeile — sie gilt +überall gleich: Streifen, Schublade, Kacheln, Schreibtischsymbole, Startmenü. Neue Apps +hängen sich hinten an. Gespeichert wird die Liste als Ganzes; der Server prüft sie noch einmal und antwortet mit dem, was er wirklich behalten hat. Eine Adresse ohne `https://` kommt als Fehlermeldung @@ -178,7 +181,7 @@ und in jeder App, ohne dass eine App neu gebaut wird. Umschalten mit **`M`**, über *Einstellungen → Betriebsart* oder im Startmenü. Gleich bleiben in beiden: Menüleiste, Palette, Einstellungen, App-Liste. -**Tasten:** `1` `2` App starten · `M` Betriebsart · `S` Einstellungen · `?` Hilfe · +**Tasten:** `M` Betriebsart · `S` Einstellungen · `?` Hilfe · `Esc` schließt Startmenü, oberstes Fenster oder Dialog — in dieser Reihenfolge. ## Wetter diff --git a/internal/store/store.go b/internal/store/store.go index 15c98b2..6e393b6 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -29,7 +29,6 @@ type App struct { Subtitle string `json:"subtitle,omitempty"` URL string `json:"url"` Glyph string `json:"glyph,omitempty"` - Key string `json:"key,omitempty"` } type User struct { @@ -120,7 +119,6 @@ func (s *Store) SetApps(apps []App) error { a.Subtitle = strings.TrimSpace(a.Subtitle) a.URL = strings.TrimSpace(a.URL) a.Glyph = strings.TrimSpace(a.Glyph) - a.Key = strings.TrimSpace(a.Key) if !idPattern.MatchString(a.ID) { return fmt.Errorf("App %d: ID muss klein geschrieben sein und mit Buchstabe oder Ziffer beginnen", i+1) @@ -140,9 +138,6 @@ func (s *Store) SetApps(apps []App) error { if a.Glyph != "" && !knownGlyphs[a.Glyph] { a.Glyph = "" } - if len([]rune(a.Key)) > 1 { - return fmt.Errorf("App %q: Taste ist ein einzelnes Zeichen", a.ID) - } for _, f := range []string{a.Name, a.Subtitle, a.URL} { if len(f) > maxFieldLen { return fmt.Errorf("App %q: ein Feld ist länger als %d Zeichen", a.ID, maxFieldLen) diff --git a/web/css/style.css b/web/css/style.css index e62cd49..bc0f08b 100644 --- a/web/css/style.css +++ b/web/css/style.css @@ -372,13 +372,6 @@ body { } .tile:active { transform: none; } -.tile-key { - position: absolute; - top: 8px; - right: 10px; - font-size: 11px; - color: var(--p-dim); -} .tile-icon { width: 46px; @@ -464,8 +457,6 @@ body { .tile-name { grid-area: name; } .tile-sub { grid-area: unter; } .tile-state { grid-area: zustand; margin: 0; white-space: nowrap; } - /* Die Zifferntaste hilft nur mit Tastatur. */ - .tile-key { display: none; } } /* ── Modal ─────────────────────────────────────────────────────────────── */ @@ -954,6 +945,23 @@ body { .approw .ae-url { margin-left: auto; color: var(--p-dim); font-size: 11px; max-width: 40%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .approw .ae-body { padding: 10px; display: flex; flex-direction: column; gap: 8px; } +/* Sortieren, ohne die Zeile aufzumachen: Die Pfeile sitzen in der Kopfzeile. + Die Adresse rückt dafür ein Stück nach links (kein margin-left:auto mehr an + ihr allein, sondern hier). */ +.approw .ae-move { margin-left: 8px; display: flex; gap: 2px; flex: none; } +.ae-arrow { + background: none; + border: 1px solid var(--p-dim); + color: var(--p-mid); + font: inherit; + font-size: 10px; + line-height: 1; + padding: 4px 6px; + cursor: pointer; +} +.ae-arrow:hover:not(:disabled) { background: var(--p); color: var(--bg); border-color: var(--p); text-shadow: none; } +.ae-arrow:disabled { opacity: 0.3; cursor: default; } + .ae-field { display: grid; grid-template-columns: 92px 1fr; gap: 8px; align-items: center; } .ae-field > span { font-size: 11px; color: var(--p-dim); letter-spacing: 0.1em; } .ae-field input, .ae-field select { diff --git a/web/index.html b/web/index.html index 0accdc7..eb9027e 100644 --- a/web/index.html +++ b/web/index.html @@ -92,7 +92,7 @@ diff --git a/web/js/os.js b/web/js/os.js index 27179a2..06b9207 100644 --- a/web/js/os.js +++ b/web/js/os.js @@ -277,7 +277,6 @@ function renderTiles() { a.target = state.settings.openIn === "tab" ? "_blank" : "_self"; a.rel = "noopener"; a.innerHTML = ` - ${app.key ? `[${app.key}]` : ""} ${glyphSvg(app.glyph)} ${escapeHtml(app.name)} ${escapeHtml(app.subtitle || "")} @@ -707,11 +706,17 @@ function openApps() { const GLYPHS_LIST = Object.keys(GLYPHS).filter((g) => !WETTER_GLYPHEN.includes(g)); const rowHtml = (a, i) => ` -
+
${glyphSvg(a.glyph)} ${escapeHtml(a.name || a.id || "neue app")} ${escapeHtml(a.url || "")} + + + +
-
@@ -752,6 +755,35 @@ function openApps() { render(root); }) ); + + // Merken, was offen ist — das Umsortieren zeichnet die Liste neu, und ohne + // das klappte dabei alles zu, was man gerade bearbeitet. + list.querySelectorAll(".approw").forEach((d, i) => { + d.addEventListener("toggle", () => { + if (draft[i]) draft[i].__offen = d.open; + }); + }); + + // Die Pfeile sitzen im : Ohne preventDefault klappt jeder + // Sortierklick die Zeile auf oder zu. + const tausche = (i, j) => { + if (j < 0 || j >= draft.length) return; + [draft[i], draft[j]] = [draft[j], draft[i]]; + render(root); + // Zurück auf denselben Knopf, damit man mehrmals hintereinander drücken + // kann, ohne die Maus nachzuführen. + const wieder = list.querySelector(`[data-${j < i ? "up" : "down"}="${j}"]`); + if (wieder && !wieder.disabled) wieder.focus(); + }; + + list.querySelectorAll("[data-up], [data-down]").forEach((b) => + b.addEventListener("click", (e) => { + e.preventDefault(); + e.stopPropagation(); + const i = Number(b.dataset.up ?? b.dataset.down); + tausche(i, "up" in b.dataset ? i - 1 : i + 1); + }) + ); }; openPanel( @@ -768,7 +800,8 @@ function openApps() { render(root); root.querySelector("#btnAdd").addEventListener("click", () => { - draft.push({ id: "", name: "", url: "", glyph: "box", key: String(draft.length + 1), __neu: true }); + // Immer ans Ende — sortiert wird mit den Pfeilen, nicht beim Anlegen. + draft.push({ id: "", name: "", url: "", glyph: "box", __offen: true }); render(root); }); @@ -777,9 +810,10 @@ function openApps() { msg.dataset.kind = ""; msg.textContent = "speichere …"; try { - const clean = draft.map(({ __neu, ...rest }) => rest); + const clean = draft.map(({ __offen, ...rest }) => rest); state.cfg.apps = (await api("/api/apps", { method: "PUT", body: JSON.stringify(clean) })).apps; - draft = state.cfg.apps.map((a) => ({ ...a })); + const offen = new Set(draft.filter((a) => a.__offen).map((a) => a.id)); + draft = state.cfg.apps.map((a) => ({ ...a, __offen: offen.has(a.id) })); msg.textContent = `${state.cfg.apps.length} gespeichert.`; render(root); renderMenubarApps(); @@ -807,7 +841,6 @@ function openAbout() { desktop Symbole und Fenster. Die HoneyBar oben ist in beiden dieselbe — und dieselbe steckt per honeybar/honeybar.js auch in Aether und InkNote.

- ${state.cfg.apps.map((a) => `
${a.key || "·"}
${escapeHtml(a.name)} starten
`).join("")}
M
Betriebsart wechseln
S
Einstellungen
?
diese Hilfe
@@ -1137,8 +1170,6 @@ async function main() { } if (typing) return; - const app = state.cfg.apps.find((a) => a.key === e.key); - if (app) return launch(app); if (e.key === "m" || e.key === "M") setMode(state.settings.mode === "desktop" ? "terminal" : "desktop"); if (e.key === "s" || e.key === "S") openSettings(); if (e.key === "?") openAbout();