117 lines
3.7 KiB
HTML
117 lines
3.7 KiB
HTML
<!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>
|