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.
135 lines
4.4 KiB
JavaScript
135 lines
4.4 KiB
JavaScript
import { api, escapeAttr, escapeHtml } from "../core.js";
|
|
import { widgetAnmelden, widgetsLaden, widgetsZeichnen } from "./rahmen.js";
|
|
|
|
/* ── Das Bild ──────────────────────────────────────────────────────────────
|
|
*
|
|
* Genau **ein** Bild je Nutzer, hochgeladen oder verlinkt. Ein neues ersetzt
|
|
* das alte, ohne Verwaltung und ohne Liste — das ist der ganze Reiz: Es ist
|
|
* das erste Widget, das nichts abfragt, sondern etwas *ist*.
|
|
*
|
|
* Hochgeladen wird zum eigenen Server; verlinkt wird auf eine fremde Adresse.
|
|
* Der Unterschied ist keiner der Bequemlichkeit: Ein Link holt bei jedem
|
|
* Aufbau des Schreibtischs etwas von woanders, ein Upload nie.
|
|
*/
|
|
|
|
const LEER = "Kein Bild — hochladen oder Adresse einfügen.";
|
|
|
|
let art = ""; // "datei" | "link" | ""
|
|
let quelle = "";
|
|
let stand = ""; // Meldung unter dem Bild
|
|
let linkAuf = false;
|
|
|
|
widgetAnmelden({
|
|
id: "bild",
|
|
titel: "bild",
|
|
// Hält ein Dateifeld und ein Eingabefeld: Der Rahmen darf den Inhalt nicht
|
|
// unter den Fingern austauschen.
|
|
stabil: true,
|
|
|
|
async laden() {
|
|
const d = await api("/api/image/info");
|
|
art = (d && d.art) || "";
|
|
quelle = (d && d.url) || "";
|
|
},
|
|
|
|
inhalt() {
|
|
const bild = quelle
|
|
? `<img class="bildw-bild" src="${escapeAttr(quelle)}" alt="" />`
|
|
: `<p class="w-zustand">${LEER}</p>`;
|
|
|
|
return `
|
|
<div class="bildw">${bild}</div>
|
|
${
|
|
linkAuf
|
|
? `<div class="bildw-linkzeile">
|
|
<input class="bildw-url" type="url" spellcheck="false"
|
|
placeholder="https://…" value="${escapeAttr(art === "link" ? quelle : "")}" />
|
|
<button class="btn bildw-uebernehmen">setzen</button>
|
|
</div>`
|
|
: ""
|
|
}
|
|
<div class="w-fuss">
|
|
<span class="w-stand">${escapeHtml(stand)}</span>
|
|
<span class="bildw-knoepfe">
|
|
<input class="bildw-datei" type="file" accept="image/png,image/jpeg,image/webp,image/gif" hidden />
|
|
<button class="btn bildw-waehlen">${quelle ? "ersetzen" : "hochladen"}</button>
|
|
<button class="btn bildw-link">link</button>
|
|
${quelle ? `<button class="btn bildw-weg">entfernen</button>` : ""}
|
|
</span>
|
|
</div>`;
|
|
},
|
|
|
|
montiert(el) {
|
|
const datei = el.querySelector(".bildw-datei");
|
|
el.querySelector(".bildw-waehlen").addEventListener("click", () => datei.click());
|
|
datei.addEventListener("change", () => datei.files[0] && hochladen(datei.files[0]));
|
|
|
|
el.querySelector(".bildw-link").addEventListener("click", () => {
|
|
linkAuf = !linkAuf;
|
|
neu();
|
|
});
|
|
|
|
const url = el.querySelector(".bildw-url");
|
|
if (url) {
|
|
url.focus();
|
|
const setzen = () => linkSetzen(url.value.trim());
|
|
el.querySelector(".bildw-uebernehmen").addEventListener("click", setzen);
|
|
url.addEventListener("keydown", (e) => {
|
|
if (e.key === "Enter") {
|
|
e.preventDefault();
|
|
setzen();
|
|
}
|
|
});
|
|
}
|
|
|
|
const weg = el.querySelector(".bildw-weg");
|
|
if (weg) weg.addEventListener("click", () => linkSetzen(""));
|
|
|
|
// Ein Bild, das nicht kommt, soll es sagen — bei einem Link ist das der
|
|
// Normalfall des Tages, an dem die fremde Seite umzieht.
|
|
const img = el.querySelector(".bildw-bild");
|
|
if (img) {
|
|
img.addEventListener("error", () => {
|
|
melden(art === "link" ? "Adresse liefert kein Bild" : "Bild nicht lesbar");
|
|
});
|
|
}
|
|
},
|
|
});
|
|
|
|
async function hochladen(file) {
|
|
melden("lädt hoch …");
|
|
try {
|
|
await api("/api/image", { method: "POST", body: file, headers: { "Content-Type": file.type || "application/octet-stream" } });
|
|
linkAuf = false;
|
|
await widgetsLaden("bild");
|
|
melden("");
|
|
} catch (e) {
|
|
melden(e.message);
|
|
}
|
|
}
|
|
|
|
async function linkSetzen(adresse) {
|
|
melden(adresse ? "übernehme …" : "entferne …");
|
|
try {
|
|
await api("/api/image", { method: "PUT", body: JSON.stringify({ url: adresse }) });
|
|
linkAuf = false;
|
|
await widgetsLaden("bild");
|
|
melden("");
|
|
} catch (e) {
|
|
melden(e.message);
|
|
}
|
|
}
|
|
|
|
function melden(s) {
|
|
stand = s;
|
|
document.querySelectorAll('[data-widget="bild"] .w-stand').forEach((el) => {
|
|
el.textContent = s;
|
|
});
|
|
}
|
|
|
|
/** Neu zeichnen — stabile Widgets lässt der Rahmen sonst in Ruhe. */
|
|
function neu() {
|
|
document.querySelectorAll('[data-widget="bild"]').forEach((el) => delete el.dataset.zustand);
|
|
widgetsZeichnen();
|
|
}
|