Changeset 2086ea2 in Klonkt for src/assets
- Timestamp:
- 08/12/2026 09:16:09 PM (3 months ago)
- Children:
- 39dab6b
- Parents:
- 5412cbb
- Location:
- src/assets
- Files:
-
- 2 edited
-
css/ward.css (modified) (1 diff)
-
js/ward.js (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/assets/css/ward.css
r5412cbb r2086ea2 166 166 .friend .wie { display: flex; flex-direction: column; } 167 167 #friends-cancel { margin-top: 12px; border: 0; background: none; color: var(--dim); font: inherit; } 168 169 /* ── De composer ──────────────────────────────────────────────────────── */ 170 .feed-head { display: flex; align-items: center; gap: 12px; } 171 .feed-head h2 { margin: 24px 0 10px; } 172 #compose-open { margin-left: auto; border: 0; border-radius: 999px; padding: 8px 16px; background: var(--accent); color: #fff; font: inherit; font-weight: 600; } 173 174 dialog#compose-dialog { 175 border: 0; border-radius: 16px; padding: 16px; max-width: 480px; width: calc(100% - 32px); 176 background: var(--card); color: var(--ink); 177 } 178 dialog#compose-dialog::backdrop { background: rgba(0,0,0,.5); } 179 .compose-head { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; } 180 .compose-head #compose-cancel { border: 0; background: #2a3342; color: var(--ink); border-radius: 10px; padding: 8px 12px; font-size: 1rem; } 181 .compose-head #compose-vis { flex: 1; border-radius: 10px; border: 1px solid #2a3342; background: #131a25; color: var(--ink); padding: 8px; font: inherit; } 182 .compose-head .primary { border: 0; border-radius: 10px; padding: 8px 16px; background: var(--accent); color: #fff; font: inherit; font-weight: 600; } 183 .compose-head .primary:disabled { background: #3a3f4d; color: var(--dim); } 184 #compose-text { width: 100%; border-radius: 12px; border: 1px solid #2a3342; background: #131a25; color: var(--ink); padding: 10px; font: inherit; resize: vertical; } 185 .compose-media { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; } 186 .media-chip { position: relative; display: flex; align-items: center; gap: 6px; background: #131a25; border-radius: 10px; padding: 6px 8px; } 187 .media-chip img { width: 56px; height: 56px; object-fit: cover; border-radius: 8px; } 188 .media-chip .kind { font-size: 1.2rem; } 189 .media-chip .name { font-size: .8rem; color: var(--dim); max-width: 120px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } 190 .media-chip .drop { position: absolute; top: -6px; right: -6px; width: 22px; height: 22px; border: 0; border-radius: 50%; background: #000; color: #fff; font-size: .75rem; line-height: 1; } 191 .compose-tools { display: flex; align-items: center; gap: 8px; margin-top: 12px; } 192 .compose-tools .tool { font-size: 1.4rem; cursor: pointer; padding: 4px 8px; border-radius: 10px; background: #131a25; } 193 .compose-tools .hint { margin-left: auto; } -
src/assets/js/ward.js
r5412cbb r2086ea2 423 423 } 424 424 425 /** De controle kost per guardian een remote fetch, dus hij komt ná het tekenen. *//** De controle kost per guardian een remote fetch, dus hij komt ná het tekenen. */ 425 /** 426 * De composer: schrijven met foto, geluid of video. 427 * 428 * De media gaan eerst apart naar /ward/api/upload (dezelfde bak als de app), 429 * en de post draagt daarna alleen de url mee. Zo blijft de post-payload klein 430 * en kan de server elke upload los valideren en verrijken (poster, waveform). 431 */ 432 function wireComposer() { 433 const dlg = document.getElementById('compose-dialog'); 434 const openBtn = document.getElementById('compose-open'); 435 const text = document.getElementById('compose-text'); 436 const mediaBox = document.getElementById('compose-media'); 437 const status = document.getElementById('compose-status'); 438 const sendBtn = document.getElementById('compose-send'); 439 let media = []; // { url, mediaType, name } 440 let uploading = false; 441 442 function reset() { 443 text.value = ''; 444 media = []; 445 mediaBox.replaceChildren(); 446 status.textContent = ''; 447 updateSend(); 448 } 449 function updateSend() { 450 // Een lege post kan niet: tekst OF media, en nooit tijdens een upload. 451 sendBtn.disabled = (!text.value.trim() && !media.length) || uploading; 452 } 453 function renderMedia() { 454 mediaBox.replaceChildren(); 455 media.forEach((m, i) => { 456 const chip = el('div', 'media-chip'); 457 if (m.mediaType.startsWith('image/')) { 458 const img = el('img'); img.src = m.url; img.alt = m.name || ''; chip.append(img); 459 } else { 460 chip.append(el('span', 'kind', m.mediaType.startsWith('audio/') ? '🎤' : '🎬')); 461 chip.append(el('span', 'name', m.name || '')); 462 } 463 const x = el('button', 'drop', '✕'); x.type = 'button'; 464 x.addEventListener('click', () => { media.splice(i, 1); renderMedia(); updateSend(); }); 465 chip.append(x); 466 mediaBox.append(chip); 467 }); 468 } 469 470 async function upload(file) { 471 if (media.length >= 4) return; 472 // 32 MB is de servergrens; hier alvast vangen scheelt een mislukte upload. 473 if (file.size > 32 * 1024 * 1024) { status.textContent = state.strings.tooBig; return; } 474 uploading = true; status.textContent = state.strings.uploading; updateSend(); 475 try { 476 const fd = new FormData(); 477 fd.append('file', file); 478 const r = await fetch('/ward/api/upload', { method: 'POST', body: fd }); 479 if (r.ok) { media.push(await r.json()); renderMedia(); status.textContent = ''; } 480 else status.textContent = (await r.json().catch(() => ({}))).error === 'media_type' 481 ? 'Dat bestandstype kan niet.' : 'Uploaden lukte niet.'; 482 } catch { status.textContent = 'Uploaden lukte niet.'; } 483 uploading = false; updateSend(); 484 } 485 486 openBtn.addEventListener('click', () => { reset(); dlg.showModal(); }); 487 document.getElementById('compose-cancel').addEventListener('click', () => dlg.close()); 488 text.addEventListener('input', updateSend); 489 490 dlg.querySelectorAll('input[type=file]').forEach((inp) => { 491 inp.addEventListener('change', () => { 492 if (inp.files[0]) upload(inp.files[0]); 493 inp.value = ''; // zelfde bestand opnieuw kunnen kiezen 494 }); 495 }); 496 497 sendBtn.addEventListener('click', async () => { 498 if (sendBtn.disabled) return; 499 sendBtn.disabled = true; 500 try { 501 const r = await fetch('/ward/api/post', { 502 method: 'POST', 503 headers: { 'Content-Type': 'application/json' }, 504 body: JSON.stringify({ 505 text: text.value, 506 media, 507 visibility: document.getElementById('compose-vis').value, 508 }), 509 }); 510 if (r.ok) { dlg.close(); loadFeed(); } 511 else status.textContent = 'Plaatsen lukte niet.'; 512 } catch { status.textContent = 'Plaatsen lukte niet.'; } 513 updateSend(); 514 }); 515 } 516 517 /** De controle kost per guardian een remote fetch, dus hij komt ná het tekenen. */ 426 518 async function loadChecks() { 427 519 if (!state.guardians.length) return; … … 449 541 wireBuoy(); 450 542 wireSky(); 543 wireComposer(); 451 544 loadFeed(); 452 545 renderOffers();
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)