Reihenfolge mit Pfeilen, Zifferntasten entfernt

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.
This commit is contained in:
marc committed 2026-08-29 23:48:04 +02:00
1 parent cc7a50c621
commit 0e1c986f90
6 files changed
+73 -32

No files matched your search

+9 -5
View File
@@ -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
+5 -2
View File
@@ -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
-5
View File
@@ -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)
+17 -9
View File
@@ -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 {
+1 -1
View File
@@ -92,7 +92,7 @@
</footer>
<footer class="statusbar" id="statusbar">
<span id="hint">[1] [2] app starten &nbsp;·&nbsp; [M] modus &nbsp;·&nbsp; [S] einstellungen &nbsp;·&nbsp; [?] hilfe</span>
<span id="hint">[M] modus &nbsp;·&nbsp; [S] einstellungen &nbsp;·&nbsp; [?] hilfe</span>
<span class="sb-right" id="sbRight"></span>
</footer>
</div>
+41 -10
View File
@@ -277,7 +277,6 @@ function renderTiles() {
a.target = state.settings.openIn === "tab" ? "_blank" : "_self";
a.rel = "noopener";
a.innerHTML = `
${app.key ? `<span class="tile-key">[${app.key}]</span>` : ""}
<span class="tile-icon">${glyphSvg(app.glyph)}</span>
<span class="tile-name">${escapeHtml(app.name)}</span>
<span class="tile-sub">${escapeHtml(app.subtitle || "")}</span>
@@ -707,11 +706,17 @@ function openApps() {
const GLYPHS_LIST = Object.keys(GLYPHS).filter((g) => !WETTER_GLYPHEN.includes(g));
const rowHtml = (a, i) => `
<details class="approw" ${a.__neu ? "open" : ""}>
<details class="approw" ${a.__offen ? "open" : ""}>
<summary>
<span class="ae-glyph">${glyphSvg(a.glyph)}</span>
<span>${escapeHtml(a.name || a.id || "neue app")}</span>
<span class="ae-url">${escapeHtml(a.url || "")}</span>
<span class="ae-move">
<button class="ae-arrow" data-up="${i}" ${i === 0 ? "disabled" : ""}
title="nach oben" aria-label="${escapeAttr(a.name || a.id)} nach oben">&#9650;</button>
<button class="ae-arrow" data-down="${i}" ${i === draft.length - 1 ? "disabled" : ""}
title="nach unten" aria-label="${escapeAttr(a.name || a.id)} nach unten">&#9660;</button>
</span>
</summary>
<div class="ae-body">
<label class="ae-field"><span>ID</span>
@@ -727,8 +732,6 @@ function openApps() {
<select data-i="${i}" data-f="glyph">
${GLYPHS_LIST.map((g) => `<option value="${g}" ${a.glyph === g ? "selected" : ""}>${g}</option>`).join("")}
</select></label>
<label class="ae-field"><span>Taste</span>
<input data-i="${i}" data-f="key" value="${escapeAttr(a.key || "")}" maxlength="1" placeholder="1" /></label>
<div class="ae-actions">
<button class="btn btn-danger" data-del="${i}">löschen</button>
</div>
@@ -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 <summary>: 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() {
<code>desktop</code> Symbole und Fenster. Die HoneyBar oben ist in beiden dieselbe —
und dieselbe steckt per <code>honeybar/honeybar.js</code> auch in Aether und InkNote.</p>
<dl class="keys">
${state.cfg.apps.map((a) => `<dt>${a.key || "·"}</dt><dd>${escapeHtml(a.name)} starten</dd>`).join("")}
<dt>M</dt><dd>Betriebsart wechseln</dd>
<dt>S</dt><dd>Einstellungen</dd>
<dt>?</dt><dd>diese Hilfe</dd>
@@ -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();