Files
ideaqueue/static/index.html
T
2026-08-25 14:43:21 +02:00

117 lines
3.7 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>IdeaQueue</title>
<style>
:root { color-scheme: light dark; }
body {
margin: 0; min-height: 100vh;
display: flex; align-items: center; justify-content: center;
font: 16px/1.5 system-ui, sans-serif;
background: Canvas; color: CanvasText;
}
main { width: min(680px, 92vw); padding: 2rem 0; }
h1 { font-size: 1.25rem; margin: 0 0 1rem; }
textarea {
width: 100%; min-height: 40vh; padding: .75rem; box-sizing: border-box;
font: inherit; font-family: ui-monospace, monospace;
border: 1px solid color-mix(in srgb, CanvasText 30%, transparent);
border-radius: 8px; background: Canvas; color: inherit; resize: vertical;
}
.row { display: flex; align-items: center; gap: 1rem; margin-top: .75rem; }
button {
font: inherit; padding: .5rem 1.25rem; border-radius: 8px;
border: 1px solid transparent; background: AccentColor; color: AccentColorText;
cursor: pointer;
}
button:disabled { opacity: .5; cursor: default; }
#status { font-size: .875rem; opacity: .75; }
#status.error { color: #d33; opacity: 1; }
#count { margin-left: auto; font-size: .875rem; opacity: .6; font-variant-numeric: tabular-nums; }
#count.over { color: #d33; opacity: 1; }
</style>
</head>
<body>
<main>
<h1>IdeaQueue</h1>
<form id="form">
<textarea id="text" placeholder="Idee eintippen … (Strg+Enter zum Speichern)" autofocus></textarea>
<div class="row">
<button id="submit" type="submit">Speichern</button>
<span id="status" role="status" aria-live="polite"></span>
<span id="count"></span>
</div>
</form>
</main>
<script>
const KEY = 'ideaqueue.draft';
const MAX = 5000;
const form = document.getElementById('form');
const text = document.getElementById('text');
const submit = document.getElementById('submit');
const status = document.getElementById('status');
const count = document.getElementById('count');
// Entwurf wiederherstellen und bei jeder Änderung sichern.
try { text.value = localStorage.getItem(KEY) || ''; } catch {}
text.addEventListener('input', () => {
try { localStorage.setItem(KEY, text.value); } catch {}
render();
});
// Zeichen zählen wie der Server: Codepoints, nicht UTF-16-Einheiten.
function len(s) { return [...s].length; }
function render() {
const n = len(text.value);
count.textContent = n + ' / ' + MAX;
count.classList.toggle('over', n > MAX);
submit.disabled = n > MAX;
}
function setStatus(msg, isError) {
status.textContent = msg;
status.classList.toggle('error', !!isError);
}
text.addEventListener('keydown', e => {
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') form.requestSubmit();
});
render();
form.addEventListener('submit', async e => {
e.preventDefault();
const body = text.value;
if (!body.trim()) { setStatus('Nichts zu speichern.', true); return; }
if (len(body) > MAX) { setStatus('Zu lang: ' + len(body) + ' von max ' + MAX + ' Zeichen.', true); return; }
submit.disabled = true;
setStatus('Speichere …');
try {
const res = await fetch('api/notes', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: body }),
});
const data = await res.json().catch(() => ({}));
if (!res.ok) throw new Error(data.error || ('HTTP ' + res.status));
// Erst nach bestätigtem Speichern leeren.
text.value = '';
try { localStorage.removeItem(KEY); } catch {}
setStatus('Gespeichert als ' + data.filename);
render();
} catch (err) {
setStatus('Fehlgeschlagen: ' + err.message + ' – Text bleibt erhalten.', true);
} finally {
render();
text.focus();
}
});
</script>
</body>
</html>