import { api, escapeHtml } from "../core.js"; import { widgetAnmelden, widgetsZeichnen } from "./rahmen.js"; /* ── Der Zettel ──────────────────────────────────────────────────────────── * * Ein Textfeld, mehr nicht. **InkNote gibt es schon** — der Zettel darf keine * zweite Notiz-App werden: kein Titel, keine Liste, keine Ordner, keine Suche. * Genau ein Text, den man im Vorbeigehen benutzt: Einkaufszettel, eine Nummer, * ein Gedanke. Alles, was länger als einen Tag lebt, gehört nach InkNote. * * Zwei Ansichten: gelesen wird gesetzt, geschrieben wird roh. Der Umweg über * einen Knopf ist Absicht — ein Feld, das immer im Bearbeitungsmodus steht, * frisst jeden Klick, den man eigentlich dem Häkchen zugedacht hatte. * * Häkchen lassen sich **ohne** Bearbeiten setzen. Das ist der häufigste Griff * an einer Liste, und dafür erst in den Text zu springen wäre albern. */ const LEER = "Noch nichts notiert."; const VERZUG = 800; // ms bis zum Speichern beim Tippen let text = ""; let bearbeiten = false; let stand = ""; // "speichert …" | "gespeichert" | Fehlertext let uhr = null; widgetAnmelden({ id: "notiz", titel: "zettel", // Der Zettel hält ein Textfeld: Sein Inhalt darf nicht bei jedem Neuzeichnen // ersetzt werden, sonst verliert man beim Tippen Text und Schreibmarke. stabil: true, async laden() { const d = await api("/api/note"); text = (d && d.text) || ""; }, inhalt() { if (bearbeiten) { return `
${escapeHtml(stand)}
`; } return `
${text.trim() ? markdown(text) : `

${LEER}

`}
${escapeHtml(stand)}
`; }, montiert(el) { const feld = el.querySelector(".zettel-feld"); if (feld) { feld.focus(); // Schreibmarke ans Ende, nicht an den Anfang: Man schreibt weiter, man // fängt selten neu an. feld.setSelectionRange(feld.value.length, feld.value.length); feld.addEventListener("input", () => { text = feld.value; spaeterSpeichern(); }); // Beim Verlassen sofort, nicht erst nach dem Verzug — sonst ginge der // letzte Satz verloren, wenn man den Tab zumacht. feld.addEventListener("blur", jetztSpeichern); el.querySelector(".zettel-fertig").addEventListener("click", () => { bearbeiten = false; jetztSpeichern(); neu(); }); return; } const auf = el.querySelector(".zettel-auf"); if (auf) { auf.addEventListener("click", () => { bearbeiten = true; neu(); }); } // Häkchen im gesetzten Text — sie ändern die Quelle, nicht nur die Anzeige. el.querySelectorAll("[data-hak]").forEach((box) => box.addEventListener("change", () => { text = hakenUmlegen(text, Number(box.dataset.hak)); neu(); jetztSpeichern(); }) ); }, }); /** Neu zeichnen — der Rahmen lässt stabile Widgets sonst in Ruhe. */ function neu() { const koerper = document.querySelectorAll('[data-widget="notiz"]'); koerper.forEach((el) => delete el.dataset.zustand); widgetsZeichnen(); } function spaeterSpeichern() { stand = ""; clearTimeout(uhr); uhr = setTimeout(jetztSpeichern, VERZUG); } /** * Speichert, wenn sich etwas geändert hat. * * Zwei offene Geräte überschreiben einander; der letzte Stand gewinnt. Für * einen Zettel ist das vertretbar — man sollte es nur wissen und nicht später * „Datenverlust" nennen. */ let gespeichert = null; async function jetztSpeichern() { clearTimeout(uhr); if (text === gespeichert) return; const wollte = text; standSetzen("speichert …"); try { await api("/api/note", { method: "PUT", body: JSON.stringify({ text: wollte }) }); gespeichert = wollte; standSetzen("gespeichert"); } catch (e) { standSetzen(e.message || "nicht gespeichert"); } } function standSetzen(s) { stand = s; document.querySelectorAll('[data-widget="notiz"] .zettel-stand').forEach((el) => { el.textContent = s; }); } /* ── Das kleine Markdown ─────────────────────────────────────────────────── * * Absichtlich von Hand und absichtlich klein: Überschriften, Häkchen, Striche, * Aufzählungen, fett, kursiv, Code. Keine Bibliothek — HoneyOS hat keinen * Bauschritt, und für sechs Regeln lohnt kein Nachladen. * * **Zuerst wird escapt, dann gesetzt.** Der Text ist zwar der eigene, aber ein * Zettel, der `