initial
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
<!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>
|
||||
Reference in New Issue
Block a user