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 `