summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorkj_sh6042026-04-15 23:20:02 -0400
committerkj_sh6042026-04-15 23:20:43 -0400
commit105687f53e052e476c4380bc4abe47ef6d2360b8 (patch)
treeb6c806c32205ab3136bef34b3f47821d8147c170 /src
parent32fb60ed8f96d462edce26710c13d4c032f5ea47 (diff)
refactor: allow tab presses in editor
Diffstat (limited to 'src')
-rw-r--r--src/index.html38
1 files changed, 35 insertions, 3 deletions
diff --git a/src/index.html b/src/index.html
index f87ca69..a6f3ae4 100644
--- a/src/index.html
+++ b/src/index.html
@@ -7,7 +7,7 @@
7 <title>sent-web</title> 7 <title>sent-web</title>
8 <link rel="icon" type="image/svg+xml" href="/favicon.svg"> 8 <link rel="icon" type="image/svg+xml" href="/favicon.svg">
9 <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/kj-sh604/noir.css@latest/out/noir.min.css"> 9 <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/kj-sh604/noir.css@latest/out/noir.min.css">
10 <style> :root { --sent-fg: #000000; --sent-bg: #ffffff; --sent-font: 'Noto Color Emoji', 'DejaVu Sans', sans-serif; } body { max-width: 960px; margin: 0 auto; padding: 1rem; } .subtitle { opacity: 0.6; font-size: 0.9em; margin-top: -0.8em; } /* ── controls ── */ #controls { display: flex; flex-wrap: wrap; gap: 1rem; align-items: center; margin-bottom: 1rem; padding: 0.75rem; border: 1px solid currentColor; border-radius: 4px; } #controls label { display: flex; align-items: center; gap: 0.4rem; font-size: 0.9rem; } #controls input[type="color"] { width: 2rem; height: 2rem; padding: 0; border: 1px solid currentColor; cursor: pointer; background: none; } #controls select { max-width: 200px; } .upload-area { display: flex; align-items: center; gap: 0.5rem; } #upload-input { display: none; } #upload-status { font-size: 0.85rem; opacity: 0.7; } /* ── editor ── */ #input { width: 100%; min-height: 420px; font-family: monospace; font-size: 0.95rem; resize: vertical; tab-size: 4; } .btn-row { display: flex; gap: 0.5rem; margin-top: 0.5rem; } .hint { font-size: 0.8rem; opacity: 0.5; margin-top: 0.25rem; } /* ── presentation overlay ── */ #presentation { position: fixed; inset: 0; z-index: 9999; display: none; align-items: center; justify-content: flex-start; background: var(--sent-bg); color: var(--sent-fg); cursor: none; overflow: hidden; padding-left: 7.5%; } #presentation.active { display: flex; } #slide-content { text-align: left; white-space: break-spaces; tab-size: 8; font-family: var(--sent-font); } #slide-content img { display: block; max-width: 85vw; max-height: 85vh; object-fit: contain; } </style> 10 <style> :root { --sent-fg: #000000; --sent-bg: #ffffff; --sent-font: 'Noto Color Emoji', 'DejaVu Sans', sans-serif; } body { max-width: 960px; margin: 0 auto; padding: 1rem; } .subtitle { opacity: 0.6; font-size: 0.9em; margin-top: -0.8em; } /* ── controls ── */ #controls { display: flex; flex-wrap: wrap; gap: 1rem; align-items: center; margin-bottom: 1rem; padding: 0.75rem; border: 1px solid currentColor; border-radius: 4px; } #controls label { display: flex; align-items: center; gap: 0.4rem; font-size: 0.9rem; } #controls input[type="color"] { width: 2rem; height: 2rem; padding: 0; border: 1px solid currentColor; cursor: pointer; background: none; } #controls select { max-width: 200px; } .upload-area { display: flex; align-items: center; gap: 0.5rem; } #upload-input { display: none; } #upload-status { font-size: 0.85rem; opacity: 0.7; } /* ── editor ── */ #input { width: 100%; min-height: 420px; font-family: monospace; font-size: 0.95rem; resize: vertical; tab-size: 4; } .btn-row { display: flex; gap: 0.5rem; margin-top: 0.5rem; } .hint { font-size: 0.8rem; opacity: 0.5; margin-top: 0.25rem; } /* ── presentation overlay ── */ #presentation { position: fixed; inset: 0; z-index: 9999; display: none; align-items: center; justify-content: flex-start; background: var(--sent-bg); color: var(--sent-fg); cursor: none; overflow: hidden; padding-left: 7.5%; } #presentation.active { display: flex; } #slide-content { text-align: left; white-space: break-spaces; tab-size: 4; font-family: var(--sent-font); } #slide-content img { display: block; max-width: 85vw; max-height: 85vh; object-fit: contain; } </style>
11 <script> 11 <script>
12 /* 12 /*
13 @licstart The following is the entire license notice for the 13 @licstart The following is the entire license notice for the
@@ -255,9 +255,11 @@ questions?</textarea>
255 document.getElementById('font-select').addEventListener('change', () => this.onFontChange()); 255 document.getElementById('font-select').addEventListener('change', () => this.onFontChange());
256 document.getElementById('upload-input').addEventListener('change', e => this.handleUpload(e)); 256 document.getElementById('upload-input').addEventListener('change', e => this.handleUpload(e));
257 257
258 document.getElementById('input').addEventListener('input', () => { 258 const input = document.getElementById('input');
259 localStorage.setItem('sent-web-content', document.getElementById('input').value); 259 input.addEventListener('input', () => {
260 localStorage.setItem('sent-web-content', input.value);
260 }); 261 });
262 input.addEventListener('keydown', e => this.handleEditorKeydown(e));
261 263
262 document.addEventListener('keydown', e => this.handleKeydown(e)); 264 document.addEventListener('keydown', e => this.handleKeydown(e));
263 265
@@ -297,6 +299,36 @@ questions?</textarea>
297 if (this.presenting) this.renderSlide(); 299 if (this.presenting) this.renderSlide();
298 }, 300 },
299 301
302 handleEditorKeydown(e) {
303 if (e.key !== 'Tab' || e.ctrlKey || e.metaKey || e.altKey)
304 return;
305
306 e.preventDefault();
307
308 const ta = e.target;
309 if (!ta || typeof ta.selectionStart !== 'number' || typeof ta.selectionEnd !== 'number')
310 return;
311
312 const indent = ' ';
313 const start = ta.selectionStart;
314 const end = ta.selectionEnd;
315
316 if (start === end) {
317 ta.setRangeText(indent, start, end, 'end');
318 } else {
319 const selected = ta.value.slice(start, end);
320 const indented = selected
321 .split('\n')
322 .map(line => indent + line)
323 .join('\n');
324 ta.setRangeText(indented, start, end, 'select');
325 ta.selectionStart = start;
326 ta.selectionEnd = start + indented.length;
327 }
328
329 localStorage.setItem('sent-web-content', ta.value);
330 },
331
300 normalizeSentNewlines(text) { 332 normalizeSentNewlines(text) {
301 return text.replace(/\r\n?/g, '\n'); 333 return text.replace(/\r\n?/g, '\n');
302 }, 334 },