Changeset 834bcc3 in Klonkt for src/views/pages/post-edit.ejs
- Timestamp:
- 06/23/2026 06:14:27 PM (3 months ago)
- Branches:
- main
- Children:
- d774679
- Parents:
- bb42dfb
- File:
-
- 1 edited
-
src/views/pages/post-edit.ejs (modified) (44 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/views/pages/post-edit.ejs
rbb42dfb r834bcc3 9 9 // and styling changed. 10 10 const _hasCover = !!post.cover_image_url; 11 // In hub -modus moeten save/create/cancel de /user/<slug>/-prefix dragen, anders12 // valt de request terug op de primaire site (siteUrlBase leeg) en rendert de13 // post na opslaan headerless (bareChrome). In solo is _base leeg.11 // In hub mode, save/create/cancel must carry the /user/<slug>/ prefix, otherwise 12 // the request falls back to the primary site (siteUrlBase empty) and the post 13 // renders headerless (bareChrome) after saving. In solo mode _base is empty. 14 14 const _base = (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : ''; 15 15 %> … … 138 138 data-placeholder="<%= t('pedit.editor_placeholder') %>"></div> 139 139 140 <%# Sticky "t ik om te bewerken"-hint (alleen zichtbaar op touch + niet-fullscreen141 via CSS). Pu ur visueel (pointer-events:none); de tik op het veld opentfullscreen. %>140 <%# Sticky "tap to edit" hint (only visible on touch + non-fullscreen 141 via CSS). Purely visual (pointer-events:none); tapping the field opens fullscreen. %> 142 142 <div class="pe-edit-hint" aria-hidden="true">✎ <%= t('pedit.tap_to_edit') %></div> 143 143 … … 225 225 if (!cb || !box) return; 226 226 var inp = document.getElementById('pe-publish-at'); 227 var SITE_TZ = '<%= timezone || '' %>'; // ingestelde site-tijdzone; leeg = browser-lokaal227 var SITE_TZ = '<%= timezone || '' %>'; // configured site timezone; empty = browser local 228 228 var pad = function (n) { return String(n).padStart(2, '0'); }; 229 // Offset (ms) tussen een tijdzone en UTC op een bepaaldmoment.229 // Offset (ms) between a timezone and UTC at a given moment. 230 230 function tzOffset(date, tz) { 231 231 var f = new Intl.DateTimeFormat('en-US', { timeZone: tz, hour12: false, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }); … … 233 233 return Date.UTC(+p.year, +p.month - 1, +p.day, +p.hour, +p.minute, +p.second) - date.getTime(); 234 234 } 235 // datetime-local "wa ndtijd" (in de site-zone) → UTC Date.235 // datetime-local "wall time" (in the site zone) → UTC Date. 236 236 function wallToUtc(wall) { 237 237 if (!SITE_TZ) return new Date(wall); … … 239 239 return new Date(guess - tzOffset(new Date(guess), SITE_TZ)); 240 240 } 241 // UTC-ISO → "YYYY-MM-DDTHH:MM" wa ndtijd in de site-zone.241 // UTC-ISO → "YYYY-MM-DDTHH:MM" wall time in the site zone. 242 242 function utcToWall(iso) { 243 243 var d = new Date(iso); if (isNaN(d)) return ''; … … 247 247 return p.year + '-' + p.month + '-' + p.day + 'T' + p.hour + ':' + p.minute; 248 248 } 249 // Prefill: opgeslagen UTC → wandtijd in de site-zone.249 // Prefill: stored UTC → wall time in the site zone. 250 250 if (inp && inp.dataset.iso) inp.value = utcToWall(inp.dataset.iso); 251 // " Ingepland voor" in leesbare tijd van de site-zone.251 // "Scheduled for" in human-readable time in the site zone. 252 252 var when = document.getElementById('pe-sched-when'); 253 253 if (when && when.dataset.iso) { … … 257 257 function sync() { box.style.display = cb.checked ? '' : 'none'; if (inp) inp.disabled = !cb.checked; } 258 258 cb.addEventListener('change', sync); sync(); 259 // Bij opslaan: wandtijd in de site-zone → UTC-ISO via een hidden veld.259 // On save: wall time in the site zone → UTC-ISO via a hidden field. 260 260 var form = cb.closest('form'); 261 261 if (form) { … … 466 466 width: 32px; height: 32px; 467 467 padding: 0; 468 touch-action: manipulation; /* snappy taps o p mobiel, geen dubbeltik-zoom */468 touch-action: manipulation; /* snappy taps on mobile, no double-tap zoom */ 469 469 -webkit-user-select: none; user-select: none; 470 470 background: transparent; … … 478 478 -webkit-tap-highlight-color: transparent; 479 479 } 480 /* Hover alleen op echte hover-apparaten — op touch blijft :hover anders "plakken"481 na een tik, waardoor de actief/inactief-staat onleesbaar wordt. */480 /* Hover only on real hover-capable devices — on touch :hover otherwise "sticks" 481 after a tap, making the active/inactive state unreadable. */ 482 482 @media (hover: hover) { 483 483 .pe-toolbar button:hover { … … 487 487 } 488 488 .pe-toolbar button:active { transform: scale(.94); } 489 /* Acti eve opmaak = onmiskenbaar gevuld met de accentkleur. Zo is op mobiel490 meteen duidelijk dat bv. vet AAN staat (tik nogmaals = uit). */489 /* Active formatting = unmistakably filled with the accent colour. On mobile 490 it's immediately clear when e.g. bold is ON (tap again = off). */ 491 491 .pe-toolbar button.is-active { 492 492 background: var(--accent); … … 505 505 .pe-toolbar-spacer { flex: 1; } 506 506 507 /* ─── Volledig-scherm schrijfmodus─── */507 /* ─── Full-screen writing mode ─── */ 508 508 .fs-icon-compress { display: none; } 509 509 .pe-editor-frame.pe-fs .fs-icon-expand { display: none; } … … 514 514 height: 100dvh; 515 515 background: var(--paper); 516 overflow: hidden; /* alléén het tekstveld scrollt, niet het frame zelf */517 } 518 /* In fullscreen vult het schrijfveld de resterende ruimte en scrollt het zelf519 (max-height: none over schrijft de mobiele box-begrenzing hieronder). */516 overflow: hidden; /* only the text field scrolls, not the frame itself */ 517 } 518 /* In fullscreen the writing field fills the remaining space and scrolls itself 519 (max-height: none overrides the mobile box limit below). */ 520 520 .pe-editor-frame.pe-fs .pe-editor { 521 521 flex: 1 1 auto; min-height: 0; height: auto; max-height: none; 522 522 } 523 /* NB: bewust GEEN overflow:hidden op html/body in fullscreen — dat kon blijven524 hangen (bv. fullscreen verlaten via navigatie) en blokkeerde dan het scrollen van525 de hele pagina. Het fullscreen-frame (position:fixed, inset:0) dekt de pagina al526 a f, en op touch zijn de scrollbalken verborgen → geen dubbele balk nodig. */527 528 /* " Klaar"-knop: alleen in fullscreen zichtbaar (links in de toolbar), duidelijke529 accent-pill i .p.v. een vierkant icoontje. */530 /* Verberg-regel specifieker dan ".pe-toolbar button" (anders wint die en zie je531 de Klaar-knop ook inline als kleine knop). Alleen in fullscreen tonen → groot. */523 /* NB: deliberately NO overflow:hidden on html/body in fullscreen — that could get 524 stuck (e.g. leaving fullscreen via navigation) and would block scrolling on the 525 whole page. The fullscreen frame (position:fixed, inset:0) already covers the page, 526 and on touch scrollbars are hidden → no double bar needed. */ 527 528 /* "Done" button: only visible in fullscreen (left side of toolbar), clear 529 accent-pill instead of a square icon. */ 530 /* Hide rule more specific than ".pe-toolbar button" (otherwise that wins and the 531 Done button also shows inline as a small button). Only shown in fullscreen → large. */ 532 532 .pe-toolbar button.pe-fs-done { display: none; } 533 533 .pe-editor-frame.pe-fs .pe-fs-done { … … 556 556 } 557 557 .pe-editor:focus { outline: none; } 558 /* Inline (n iet-fullscreen) groeit het schrijfveld gewoon mee met de inhoud — geen559 intern e scroll-box (scroll-binnen-scroll is verwarrend). Op mobiel/tablet gaat560 typen sowieso fullscreen (zie JS), waar het veld de pagina vult en als enige561 scroll t. */558 /* Inline (non-fullscreen) the writing field simply grows with the content — no 559 internal scroll-box (scroll-within-scroll is confusing). On mobile/tablet typing 560 always goes fullscreen (see JS), where the field fills the page and is the sole 561 scroller. */ 562 562 .pe-editor-frame:not(.pe-fs) .pe-editor { overflow: visible; } 563 563 564 /* Touch: het inline content-veld is geen tekstveld maar een tap-vlak→ fullscreen565 bewerken. Een "✎ Tik om te bewerken"-pill plakt sticky onderaan de container,566 zodat de hint altijd in beeld is zonder midden in de tekst te staan. */564 /* Touch: the inline content field is not a text field but a tap target → fullscreen 565 editing. A "✎ Tap to edit" pill sticks to the bottom of the container, 566 so the hint is always visible without sitting in the middle of the text. */ 567 567 .pe-editor.pe-tap-to-edit { cursor: pointer; } 568 568 .pe-edit-hint { display: none; } … … 571 571 display: block; 572 572 position: sticky; 573 bottom: 4.5rem; /* boven de sticky Opslaan/Annuleren-balk*/573 bottom: 4.5rem; /* above the sticky Save/Cancel bar */ 574 574 width: max-content; 575 575 max-width: calc(100% - 2rem); … … 580 580 pointer-events: none; text-align: center; white-space: nowrap; 581 581 } 582 /* N iet relevant op touch (slepen/selecteren hoort bij inline-bewerken opdesktop). */582 /* Not relevant on touch (drag/select belongs to inline editing on desktop). */ 583 583 .pe-content-hint { display: none; } 584 /* Inline (n iet-fullscreen) op touch: simpel houden — je bewerkt hier toch niet,585 dus geen opmaak-toolbar en geen losse rand. Alleen de content-preview + de586 "t ik om te bewerken"-pill. De toolbar verschijnt pas in fullscreen. */584 /* Inline (non-fullscreen) on touch: keep it simple — you don't edit here anyway, 585 so no formatting toolbar and no border. Just the content preview + the 586 "tap to edit" pill. The toolbar only appears in fullscreen. */ 587 587 .pe-editor-frame:not(.pe-fs) .pe-toolbar { display: none; } 588 588 .pe-editor-frame:not(.pe-fs) { border-top: 0; } … … 955 955 } 956 956 957 /* Vastpinnen: checkbox + ▲▼-stepper met beschrijving (i.p.v. een ruw rang-getal). */957 /* Pin: checkbox + ▲▼-stepper with description (instead of a raw rank number). */ 958 958 .pe-pin { display: flex; flex-direction: column; gap: 0.45rem; } 959 959 .pe-pin-pos { display: flex; align-items: center; gap: 0.55rem; padding-left: 1.65rem; } … … 978 978 gap: 0.5rem; 979 979 padding: 0.85rem 1rem; 980 margin: 0.5rem -1rem 0; /* extend to viewport edges on tightcontainer */980 margin: 0.5rem -1rem 0; /* extend to viewport edges on a narrow container */ 981 981 background: color-mix(in srgb, var(--paper) 94%, transparent); 982 982 -webkit-backdrop-filter: blur(8px); … … 987 987 .pe-actions-spacer { flex: 1; } 988 988 989 /* De editor is een focus-scherm: verberg de mobiele site-tabbalk (Home/Zoek/…)990 zodat er niet twee balken onderaan stapelen (Opslaan-balk + tabbalk). De991 Opslaan/Annuleren-balk wordt dan de enige onderbalk → gewoon opbottom:0,992 geen tab-offset meer nodig. Op desktop is de tabbalk toch al verborgen. */989 /* The editor is a focus screen: hide the mobile site tab bar (Home/Search/…) 990 so two bars don't stack at the bottom (Save bar + tab bar). The 991 Save/Cancel bar then becomes the only bottom bar → plain bottom:0, 992 no tab offset needed. On desktop the tab bar is already hidden. */ 993 993 body:has(.post-edit-page) .bottom-tab { display: none; } 994 /* Audio speler (indien aan het spelen) niet meer 56px optillen voor de nu-verborgen995 tab balk, anders zou die zweven boven de actiebalk. */994 /* Audio player (if playing) no longer lifted 56px for the now-hidden 995 tab bar, otherwise it would float above the action bar. */ 996 996 body:has(.post-edit-page) .audio-player { bottom: 0; } 997 997 998 /* ── Afbeeldings-editor (draaien / bijsnijden / spiegelen) ── */998 /* ── Image editor (rotate / crop / mirror) ── */ 999 999 .imed-backdrop { 1000 1000 position: fixed; inset: 0; z-index: 9999; … … 1015 1015 } 1016 1016 .imed-stage img { display: block; max-width: 100%; } 1017 /* Cropper z'n container moet de volle stage-hoogte vullen — anders klapt 'ie dicht1018 in een flex-kolom zonder expliciete hoogte → leeg bewerk-venster (geen afbeelding). */1017 /* Cropper's container must fill the full stage height — otherwise it collapses 1018 in a flex column without an explicit height → empty edit window (no image). */ 1019 1019 .imed-stage .cropper-container { width: 100% !important; height: 100% !important; } 1020 1020 .imed-tools { … … 1052 1052 } 1053 1053 1054 // ── Afbeeldings-editor (draaien / bijsnijden / spiegelen) ──────────1055 // Lazy-load Cropper.js (lo kaal gevendord) pas bij het eerste gebruik.1054 // ── Image editor (rotate / crop / mirror) ────────── 1055 // Lazy-load Cropper.js (locally vendored) on first use. 1056 1056 let _cropperReady = null; 1057 1057 function ensureCropper() { … … 1073 1073 } 1074 1074 1075 // Open t de editor voor een gekozen bestand; resolved met een bewerktFile,1076 // o f null als de gebruiker annuleert. Animatie-GIF's worden niet door de1077 // canvas -editor gehaald (zouden statisch worden) — die uploaden direct.1075 // Opens the editor for a chosen file; resolves with an edited File, 1076 // or null if the user cancels. Animated GIFs are not sent through the 1077 // canvas editor (they would become static) — those upload directly. 1078 1078 async function openImageEditor(file) { 1079 1079 if (!file || !file.type || !file.type.startsWith('image/')) return file; 1080 if (file.type === 'image/gif') return file; // behoud animatie1081 try { await ensureCropper(); } catch (_) { return file; } // editor onbeschikbaar → direct uploaden1080 if (file.type === 'image/gif') return file; // preserve animation 1081 try { await ensureCropper(); } catch (_) { return file; } // editor unavailable → upload directly 1082 1082 1083 1083 return new Promise((resolve) => { … … 1118 1118 cropper = new Cropper(img, { viewMode: 1, autoCropArea: 1, background: false, responsive: true }); 1119 1119 }; 1120 img.onerror = () => { cleanup(); resolve(file); }; // kon niet laden → upload het origineel1120 img.onerror = () => { cleanup(); resolve(file); }; // could not load → upload the original 1121 1121 img.src = url; 1122 1122 … … 1179 1179 const edited = await openImageEditor(coverField.files[0]); 1180 1180 coverField.value = ''; 1181 if (!edited) return; // geannuleerd1181 if (!edited) return; // cancelled 1182 1182 coverStatus.classList.remove('is-error'); 1183 1183 coverStatus.textContent = '<%= t('pedit.js_uploading') %>'; … … 1224 1224 if (!editor) return; 1225 1225 1226 // Titel alleen automatisch focussen op desktop (muis/trackpad). Op touch zou1227 // dit meteen het toetsenbord openen bij het openen van de editor — niet gewenst.1226 // Auto-focus the title only on desktop (mouse/trackpad). On touch this would 1227 // immediately open the keyboard when the editor opens — not desired. 1228 1228 try { 1229 1229 const titleInput = form && form.querySelector('input[name="title"]'); … … 1329 1329 1330 1330 // ── Toolbar wiring 1331 // Houd de scrollpositie vast rond een edit-commando. execCommand/insert scrollt1332 // standaard de caret in beeld → het beeld "verspringt" bij het aanklikken van een1333 // opmaakknop. We leggen ALLE scrollbare voorouders (editor, frame, #pcms-main, …)1334 // + de pagina vast en zetten ze terug — sync én over een paar frames, wantChrome1335 // s crollt soms pas een frame later. De gebruiker scrollt zo zelf.1331 // Lock the scroll position around an edit command. execCommand/insert scrolls 1332 // the caret into view by default → the view "jumps" when clicking a formatting 1333 // button. We lock ALL scrollable ancestors (editor, frame, #pcms-main, …) 1334 // + the page and restore them — sync and over a few frames, because Chrome 1335 // sometimes scrolls a frame later. The user scrolls themselves. 1336 1336 function scrollableAncestors(el) { 1337 1337 const list = []; … … 1345 1345 } 1346 1346 function keepScroll(fn) { 1347 // In fullscreen staat de pagina vast (body overflow:hidden) en mag het veld1348 // gewoon naar de caret scrollen — geen pagina-sprong mogelijk, dus niets fixen.1347 // In fullscreen the page is locked (body overflow:hidden) and the field may 1348 // scroll to the caret freely — no page jump possible, so nothing to fix. 1349 1349 const frame = document.querySelector('.pe-editor-frame'); 1350 1350 if (frame && frame.classList.contains('pe-fs')) { fn(); return; } … … 1389 1389 execCmd('createLink', url); 1390 1390 } 1391 // De huidige selectie zit in een <blockquote> binnen de editor? Geef 'm terug.1391 // Is the current selection inside a <blockquote> within the editor? Return it. 1392 1392 function blockquoteAncestor() { 1393 1393 const sel = window.getSelection(); … … 1400 1400 return null; 1401 1401 } 1402 // Echte toggle: execCommand('formatBlock','blockquote') zet 'm wél AAN maar1403 // krijgt 'm nooit meer UIT (browser-quirk). Staat de caret al in eenquote →1404 // pak de wrapper uit; anders pas blockquote toe.1402 // Real toggle: execCommand('formatBlock','blockquote') does turn it ON but 1403 // can never turn it OFF (browser quirk). If the caret is already in a quote → 1404 // unwrap it; otherwise apply blockquote. 1405 1405 function toggleBlockquote() { 1406 1406 keepScroll(function () { … … 1409 1409 if (bq) { 1410 1410 const parent = bq.parentNode; 1411 // Inhoud uit de quote halen, op z'n plek, en de lege wrapper verwijderen.1411 // Extract content from the quote in place, then remove the empty wrapper. 1412 1412 const ref = bq; 1413 1413 let firstMoved = null; … … 1418 1418 } 1419 1419 parent.removeChild(bq); 1420 // Caret terugzetten in de uitgepakte inhoud.1420 // Restore the caret inside the unwrapped content. 1421 1421 if (firstMoved) { 1422 1422 const sel = window.getSelection(); … … 1436 1436 1437 1437 if (toolbar) { 1438 // CRUCIA AL (mobiel + desktop): voorkom dat een toolbar-knop de focus/selectie1439 // uit het editor-veld steelt. Zonder dit raakt de selectie kwijt bij het tikken1440 // → execCommand werkt op een lege selectie (vet kan niet meer UIT) én de browser1441 // scrollt de caret opnieuw in beeld (de "sprong naar beneden"). preventDefault op1442 // mousedown houdt de focus in de editor; de click blijft gewoon vuren.1438 // CRUCIAL (mobile + desktop): prevent a toolbar button from stealing focus/selection 1439 // from the editor field. Without this the selection is lost on tap 1440 // → execCommand operates on an empty selection (bold can no longer be toggled OFF) 1441 // and the browser scrolls the caret back into view (the "jump down"). preventDefault 1442 // on mousedown keeps focus in the editor; the click still fires normally. 1443 1443 toolbar.addEventListener('mousedown', (e) => { 1444 1444 if (e.target.closest('button')) e.preventDefault(); … … 1457 1457 } 1458 1458 1459 // ── Volledig-scherm schrijfmodus: het schrijfveld vult de hele pagina.1459 // ── Full-screen writing mode: the writing field fills the whole page. 1460 1460 const fsBtn = document.getElementById('pe-fullscreen-btn'); 1461 1461 const editorFrame = document.querySelector('.pe-editor-frame'); 1462 1462 const isTouch = !!(window.matchMedia && window.matchMedia('(pointer: coarse)').matches); 1463 1463 1464 // O p mobiel duwt het toetsenbord de zichtbare (visual) viewport omhoog terwijl1465 // een position:fixed-frame aan de LAYOUT-viewport blijft hangen → de toolbar1466 // s chuift uit beeld. Houd het fullscreen-frame daarom gelijk aan de visual1467 // viewport (top + h oogte), zodat de toolbar altijd bovenaan zichtbaar blijft.1464 // On mobile the keyboard pushes the visible (visual) viewport up while 1465 // a position:fixed frame stays pinned to the LAYOUT viewport → the toolbar 1466 // slides out of view. Keep the fullscreen frame aligned to the visual 1467 // viewport (top + height) so the toolbar stays visible at the top. 1468 1468 function syncFsViewport() { 1469 1469 if (!editorFrame || !editorFrame.classList.contains('pe-fs')) return; … … 1499 1499 } 1500 1500 } 1501 // O p touch is het veld inline NIET bewerkbaar; alleen in fullscreen wel.1501 // On touch the field is NOT editable inline; only in fullscreen. 1502 1502 if (isTouch) editor.setAttribute('contenteditable', on ? 'true' : 'false'); 1503 1503 if (on) { … … 1505 1505 } else { 1506 1506 if (isTouch) editor.blur(); 1507 // Bij sluiten: naar de TOP van de content scrollen i.p.v. ergens onderaan1508 // (footer) te blijven hangen.1507 // On close: scroll to the TOP of the content instead of staying 1508 // somewhere at the bottom (footer). 1509 1509 requestAnimationFrame(function () { 1510 1510 try { editorFrame.scrollIntoView({ block: 'start' }); } catch (_) {} … … 1512 1512 } 1513 1513 } 1514 // De fullscreen schrijf-"pagina": openen pusht een history-state zodat de browser-1515 // back -knop (en de Klaar-knop) 'm sluit en je terug bent in het formulier — voelt1516 // als een aparte pagina, maar álle formuliervelden blijven intact (zelfde DOM).1514 // The fullscreen writing "page": opening pushes a history state so the browser 1515 // back button (and the Done button) closes it and returns you to the form — feels 1516 // like a separate page, but all form fields remain intact (same DOM). 1517 1517 function openFs() { 1518 1518 if (isFs()) return; … … 1522 1522 function closeFs() { 1523 1523 if (!isFs()) return; 1524 if (history.state && history.state.peFs) history.back(); // → popstate sluit af1524 if (history.state && history.state.peFs) history.back(); // → popstate closes it 1525 1525 else applyFs(false); 1526 1526 } … … 1534 1534 }); 1535 1535 1536 // O p mobiel/tablet (touch): het content-veld is INLINE NIET bewerkbaar — het is1537 // dan geen tekstveld. Eén tik → fullscreen, waar het wél bewerkbaar wordt1538 // (toggleFullscreen zet contenteditable aan/uit). Zo kun je nooit inline typen.1536 // On mobile/tablet (touch): the content field is NOT editable inline — it is 1537 // not a text field there. One tap → fullscreen, where it becomes editable 1538 // (toggleFullscreen toggles contenteditable). This prevents inline typing. 1539 1539 if (isTouch) { 1540 1540 editor.setAttribute('contenteditable', 'false'); … … 1555 1555 try { btn.classList.toggle('is-active', document.queryCommandState(cmd)); } catch(_) {} 1556 1556 } 1557 // Quote -knop: actief als de caret in een <blockquote> staat (toggle-feedback).1557 // Quote button: active when the caret is inside a <blockquote> (toggle feedback). 1558 1558 const bqBtn = toolbar.querySelector('button[data-cmd="formatBlock"][data-arg="blockquote"]'); 1559 1559 if (bqBtn) bqBtn.classList.toggle('is-active', !!blockquoteAncestor()); … … 1588 1588 async function uploadAndInsertImage(file) { 1589 1589 const edited = await openImageEditor(file); 1590 if (!edited) return; // geannuleerd1590 if (!edited) return; // cancelled 1591 1591 contentStatus.classList.remove('is-error'); 1592 1592 contentStatus.textContent = '<%= t('pedit.js_uploading') %>'; … … 1654 1654 } 1655 1655 1656 // ── Embed insert: p lak een platform-URL -> [[embed:url]]-chip die server-side1657 // een iframe wordt(YouTube/Spotify/SoundCloud/Vimeo/Apple Music/Bandcamp).1656 // ── Embed insert: paste a platform URL -> [[embed:url]]-chip that becomes 1657 // an iframe server-side (YouTube/Spotify/SoundCloud/Vimeo/Apple Music/Bandcamp). 1658 1658 const embedBtn = document.getElementById('insert-embed-btn'); 1659 1659 if (embedBtn) { … … 1674 1674 const tpEmpty = document.getElementById('tp-empty'); 1675 1675 const tpSearch = document.getElementById('tp-search'); 1676 let tpCache = null; // cached track slist (fetched once per page load)1676 let tpCache = null; // cached track list (fetched once per page load) 1677 1677 let tpLastFocus = null; // element to restore focus to on close 1678 1678 … … 1870 1870 <script> 1871 1871 (function () { 1872 // Vastpinnen: checkbox toggelt het verborgen rang-veld (0 = niet gepind),1873 // ▲▼ verschuift de plek, met een leesbare beschrijving i.p.v. een ruw getal.1872 // Pin: checkbox toggles the hidden rank field (0 = not pinned), 1873 // ▲▼ shifts the position, with a readable description instead of a raw number. 1874 1874 var toggle = document.getElementById('pin-toggle'); 1875 1875 var rank = document.getElementById('pin-rank'); 1876 1876 var pos = document.getElementById('pin-pos'); 1877 1877 var label = document.getElementById('pin-label'); 1878 var up = document.getElementById('pin-up'); // h oger = lager getal (richting 1/bovenaan)1878 var up = document.getElementById('pin-up'); // higher = lower number (towards 1/top) 1879 1879 var down = document.getElementById('pin-down'); 1880 1880 if (!toggle || !rank || !pos) return; … … 1900 1900 1901 1901 (function () { 1902 // De Opslaan/Annuleren-balk (position: sticky; bottom:0) net boven twee mogelijke1903 // obsta kels houden door een dynamische bottom-offset te zetten = het grootste van:1904 // 1) de hoogte van het toetsenbord-deel dat de layout-viewport NIET dekt1905 // (o p iOS verschuift de visual viewport; op Android verkleint de layout-1906 // viewport doorinteractive-widget=resizes-content → offset ≈ 0);1907 // 2) de hoogte van de spelende audiospeler (fixed, z-index 1000).1908 // We blijven bij sticky (geen fixed/top-gegoochel → geen balk midden in beeld).1902 // Keep the Save/Cancel bar (position: sticky; bottom:0) just above two possible 1903 // obstacles by setting a dynamic bottom offset = the greater of: 1904 // 1) the height of the keyboard area NOT covered by the layout viewport 1905 // (on iOS the visual viewport shifts; on Android the layout viewport shrinks 1906 // due to interactive-widget=resizes-content → offset ≈ 0); 1907 // 2) the height of the playing audio player (fixed, z-index 1000). 1908 // We stick with sticky (no fixed/top tricks → no bar floating in the middle). 1909 1909 var bar = document.querySelector('.pe-actions'); 1910 1910 if (!bar) return; … … 1922 1922 window.addEventListener('resize', position); 1923 1923 if (vv) { vv.addEventListener('resize', position); vv.addEventListener('scroll', position); } 1924 // has-audio-player wisselt via een body-class → daarop reageren.1924 // has-audio-player is toggled via a body class → observe it. 1925 1925 try { new MutationObserver(position).observe(document.body, { attributes: true, attributeFilter: ['class'] }); } catch (_) {} 1926 1926 })();
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)