Changeset 834bcc3 in Klonkt for src/views/pages/post-edit.ejs


Ignore:
Timestamp:
06/23/2026 06:14:27 PM (3 months ago)
Author:
Robin Genis <roboburr@…>
Branches:
main
Children:
d774679
Parents:
bb42dfb
Message:

i18n: translate Dutch code comments to English across src/

Comments in routes/services/views/config/middleware/assets translated to
English for the public repo. A few dev-facing throw/console message strings
were Englished too. No user-facing UI strings or i18n dictionary values changed
(src/services/i18n.js untouched). Logic unchanged.

Co-Authored-By: Claude <noreply@…>

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/views/pages/post-edit.ejs

    rbb42dfb r834bcc3  
    99// and styling changed.
    1010const _hasCover = !!post.cover_image_url;
    11 // In hub-modus moeten save/create/cancel de /user/<slug>/-prefix dragen, anders
    12 // valt de request terug op de primaire site (siteUrlBase leeg) en rendert de
    13 // 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.
    1414const _base = (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '';
    1515%>
     
    138138             data-placeholder="<%= t('pedit.editor_placeholder') %>"></div>
    139139
    140         <%# Sticky "tik om te bewerken"-hint (alleen zichtbaar op touch + niet-fullscreen
    141             via CSS). Puur visueel (pointer-events:none); de tik op het veld opent fullscreen. %>
     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. %>
    142142        <div class="pe-edit-hint" aria-hidden="true">✎ <%= t('pedit.tap_to_edit') %></div>
    143143
     
    225225              if (!cb || !box) return;
    226226              var inp = document.getElementById('pe-publish-at');
    227               var SITE_TZ = '<%= timezone || '' %>'; // ingestelde site-tijdzone; leeg = browser-lokaal
     227              var SITE_TZ = '<%= timezone || '' %>'; // configured site timezone; empty = browser local
    228228              var pad = function (n) { return String(n).padStart(2, '0'); };
    229               // Offset (ms) tussen een tijdzone en UTC op een bepaald moment.
     229              // Offset (ms) between a timezone and UTC at a given moment.
    230230              function tzOffset(date, tz) {
    231231                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' });
     
    233233                return Date.UTC(+p.year, +p.month - 1, +p.day, +p.hour, +p.minute, +p.second) - date.getTime();
    234234              }
    235               // datetime-local "wandtijd" (in de site-zone) → UTC Date.
     235              // datetime-local "wall time" (in the site zone) → UTC Date.
    236236              function wallToUtc(wall) {
    237237                if (!SITE_TZ) return new Date(wall);
     
    239239                return new Date(guess - tzOffset(new Date(guess), SITE_TZ));
    240240              }
    241               // UTC-ISO → "YYYY-MM-DDTHH:MM" wandtijd in de site-zone.
     241              // UTC-ISO → "YYYY-MM-DDTHH:MM" wall time in the site zone.
    242242              function utcToWall(iso) {
    243243                var d = new Date(iso); if (isNaN(d)) return '';
     
    247247                return p.year + '-' + p.month + '-' + p.day + 'T' + p.hour + ':' + p.minute;
    248248              }
    249               // Prefill: opgeslagen UTC → wandtijd in de site-zone.
     249              // Prefill: stored UTC → wall time in the site zone.
    250250              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.
    252252              var when = document.getElementById('pe-sched-when');
    253253              if (when && when.dataset.iso) {
     
    257257              function sync() { box.style.display = cb.checked ? '' : 'none'; if (inp) inp.disabled = !cb.checked; }
    258258              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.
    260260              var form = cb.closest('form');
    261261              if (form) {
     
    466466  width: 32px; height: 32px;
    467467  padding: 0;
    468   touch-action: manipulation; /* snappy taps op mobiel, geen dubbeltik-zoom */
     468  touch-action: manipulation; /* snappy taps on mobile, no double-tap zoom */
    469469  -webkit-user-select: none; user-select: none;
    470470  background: transparent;
     
    478478  -webkit-tap-highlight-color: transparent;
    479479}
    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. */
    482482@media (hover: hover) {
    483483  .pe-toolbar button:hover {
     
    487487}
    488488.pe-toolbar button:active { transform: scale(.94); }
    489 /* Actieve opmaak = onmiskenbaar gevuld met de accentkleur. Zo is op mobiel
    490    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). */
    491491.pe-toolbar button.is-active {
    492492  background: var(--accent);
     
    505505.pe-toolbar-spacer { flex: 1; }
    506506
    507 /* ─── Volledig-scherm schrijfmodus ─── */
     507/* ─── Full-screen writing mode ─── */
    508508.fs-icon-compress { display: none; }
    509509.pe-editor-frame.pe-fs .fs-icon-expand { display: none; }
     
    514514  height: 100dvh;
    515515  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 zelf
    519    (max-height: none overschrijft 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). */
    520520.pe-editor-frame.pe-fs .pe-editor {
    521521  flex: 1 1 auto; min-height: 0; height: auto; max-height: none;
    522522}
    523 /* NB: bewust GEEN overflow:hidden op html/body in fullscreen — dat kon blijven
    524    hangen (bv. fullscreen verlaten via navigatie) en blokkeerde dan het scrollen van
    525    de hele pagina. Het fullscreen-frame (position:fixed, inset:0) dekt de pagina al
    526    af, en op touch zijn de scrollbalken verborgen → geen dubbele balk nodig. */
    527 
    528 /* "Klaar"-knop: alleen in fullscreen zichtbaar (links in de toolbar), duidelijke
    529    accent-pill i.p.v. een vierkant icoontje. */
    530 /* Verberg-regel specifieker dan ".pe-toolbar button" (anders wint die en zie je
    531    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. */
    532532.pe-toolbar button.pe-fs-done { display: none; }
    533533.pe-editor-frame.pe-fs .pe-fs-done {
     
    556556}
    557557.pe-editor:focus { outline: none; }
    558 /* Inline (niet-fullscreen) groeit het schrijfveld gewoon mee met de inhoud — geen
    559    interne scroll-box (scroll-binnen-scroll is verwarrend). Op mobiel/tablet gaat
    560    typen sowieso fullscreen (zie JS), waar het veld de pagina vult en als enige
    561    scrollt. */
     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. */
    562562.pe-editor-frame:not(.pe-fs) .pe-editor { overflow: visible; }
    563563
    564 /* Touch: het inline content-veld is geen tekstveld maar een tap-vlak → fullscreen
    565    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. */
    567567.pe-editor.pe-tap-to-edit { cursor: pointer; }
    568568.pe-edit-hint { display: none; }
     
    571571    display: block;
    572572    position: sticky;
    573     bottom: 4.5rem;   /* boven de sticky Opslaan/Annuleren-balk */
     573    bottom: 4.5rem;   /* above the sticky Save/Cancel bar */
    574574    width: max-content;
    575575    max-width: calc(100% - 2rem);
     
    580580    pointer-events: none; text-align: center; white-space: nowrap;
    581581  }
    582   /* Niet relevant op touch (slepen/selecteren hoort bij inline-bewerken op desktop). */
     582  /* Not relevant on touch (drag/select belongs to inline editing on desktop). */
    583583  .pe-content-hint { display: none; }
    584   /* Inline (niet-fullscreen) op touch: simpel houden — je bewerkt hier toch niet,
    585      dus geen opmaak-toolbar en geen losse rand. Alleen de content-preview + de
    586      "tik 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. */
    587587  .pe-editor-frame:not(.pe-fs) .pe-toolbar { display: none; }
    588588  .pe-editor-frame:not(.pe-fs) { border-top: 0; }
     
    955955}
    956956
    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). */
    958958.pe-pin { display: flex; flex-direction: column; gap: 0.45rem; }
    959959.pe-pin-pos { display: flex; align-items: center; gap: 0.55rem; padding-left: 1.65rem; }
     
    978978  gap: 0.5rem;
    979979  padding: 0.85rem 1rem;
    980   margin: 0.5rem -1rem 0;   /* extend to viewport edges on tight container */
     980  margin: 0.5rem -1rem 0;   /* extend to viewport edges on a narrow container */
    981981  background: color-mix(in srgb, var(--paper) 94%, transparent);
    982982  -webkit-backdrop-filter: blur(8px);
     
    987987.pe-actions-spacer { flex: 1; }
    988988
    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). De
    991    Opslaan/Annuleren-balk wordt dan de enige onderbalk → gewoon op bottom: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. */
    993993body:has(.post-edit-page) .bottom-tab { display: none; }
    994 /* Audiospeler (indien aan het spelen) niet meer 56px optillen voor de nu-verborgen
    995    tabbalk, 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. */
    996996body:has(.post-edit-page) .audio-player { bottom: 0; }
    997997
    998 /* ── Afbeeldings-editor (draaien / bijsnijden / spiegelen) ── */
     998/* ── Image editor (rotate / crop / mirror) ── */
    999999.imed-backdrop {
    10001000  position: fixed; inset: 0; z-index: 9999;
     
    10151015}
    10161016.imed-stage img { display: block; max-width: 100%; }
    1017 /* Cropper z'n container moet de volle stage-hoogte vullen — anders klapt 'ie dicht
    1018    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). */
    10191019.imed-stage .cropper-container { width: 100% !important; height: 100% !important; }
    10201020.imed-tools {
     
    10521052  }
    10531053
    1054   // ── Afbeeldings-editor (draaien / bijsnijden / spiegelen) ──────────
    1055   // Lazy-load Cropper.js (lokaal gevendord) pas bij het eerste gebruik.
     1054  // ── Image editor (rotate / crop / mirror) ──────────
     1055  // Lazy-load Cropper.js (locally vendored) on first use.
    10561056  let _cropperReady = null;
    10571057  function ensureCropper() {
     
    10731073  }
    10741074
    1075   // Opent de editor voor een gekozen bestand; resolved met een bewerkt File,
    1076   // of null als de gebruiker annuleert. Animatie-GIF's worden niet door de
    1077   // 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.
    10781078  async function openImageEditor(file) {
    10791079    if (!file || !file.type || !file.type.startsWith('image/')) return file;
    1080     if (file.type === 'image/gif') return file; // behoud animatie
    1081     try { await ensureCropper(); } catch (_) { return file; } // editor onbeschikbaar → direct uploaden
     1080    if (file.type === 'image/gif') return file; // preserve animation
     1081    try { await ensureCropper(); } catch (_) { return file; } // editor unavailable → upload directly
    10821082
    10831083    return new Promise((resolve) => {
     
    11181118        cropper = new Cropper(img, { viewMode: 1, autoCropArea: 1, background: false, responsive: true });
    11191119      };
    1120       img.onerror = () => { cleanup(); resolve(file); }; // kon niet laden → upload het origineel
     1120      img.onerror = () => { cleanup(); resolve(file); }; // could not load → upload the original
    11211121      img.src = url;
    11221122
     
    11791179      const edited = await openImageEditor(coverField.files[0]);
    11801180      coverField.value = '';
    1181       if (!edited) return; // geannuleerd
     1181      if (!edited) return; // cancelled
    11821182      coverStatus.classList.remove('is-error');
    11831183      coverStatus.textContent = '<%= t('pedit.js_uploading') %>';
     
    12241224  if (!editor) return;
    12251225
    1226   // Titel alleen automatisch focussen op desktop (muis/trackpad). Op touch zou
    1227   // 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.
    12281228  try {
    12291229    const titleInput = form && form.querySelector('input[name="title"]');
     
    13291329
    13301330  // ── Toolbar wiring
    1331   // Houd de scrollpositie vast rond een edit-commando. execCommand/insert scrollt
    1332   // standaard de caret in beeld → het beeld "verspringt" bij het aanklikken van een
    1333   // opmaakknop. We leggen ALLE scrollbare voorouders (editor, frame, #pcms-main, …)
    1334   // + de pagina vast en zetten ze terug — sync én over een paar frames, want Chrome
    1335   // scrollt 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.
    13361336  function scrollableAncestors(el) {
    13371337    const list = [];
     
    13451345  }
    13461346  function keepScroll(fn) {
    1347     // In fullscreen staat de pagina vast (body overflow:hidden) en mag het veld
    1348     // 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.
    13491349    const frame = document.querySelector('.pe-editor-frame');
    13501350    if (frame && frame.classList.contains('pe-fs')) { fn(); return; }
     
    13891389    execCmd('createLink', url);
    13901390  }
    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.
    13921392  function blockquoteAncestor() {
    13931393    const sel = window.getSelection();
     
    14001400    return null;
    14011401  }
    1402   // Echte toggle: execCommand('formatBlock','blockquote') zet 'm wél AAN maar
    1403   // krijgt 'm nooit meer UIT (browser-quirk). Staat de caret al in een quote →
    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.
    14051405  function toggleBlockquote() {
    14061406    keepScroll(function () {
     
    14091409      if (bq) {
    14101410        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.
    14121412        const ref = bq;
    14131413        let firstMoved = null;
     
    14181418        }
    14191419        parent.removeChild(bq);
    1420         // Caret terugzetten in de uitgepakte inhoud.
     1420        // Restore the caret inside the unwrapped content.
    14211421        if (firstMoved) {
    14221422          const sel = window.getSelection();
     
    14361436
    14371437  if (toolbar) {
    1438     // CRUCIAAL (mobiel + desktop): voorkom dat een toolbar-knop de focus/selectie
    1439     // uit het editor-veld steelt. Zonder dit raakt de selectie kwijt bij het tikken
    1440     // → execCommand werkt op een lege selectie (vet kan niet meer UIT) én de browser
    1441     // scrollt de caret opnieuw in beeld (de "sprong naar beneden"). preventDefault op
    1442     // 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.
    14431443    toolbar.addEventListener('mousedown', (e) => {
    14441444      if (e.target.closest('button')) e.preventDefault();
     
    14571457  }
    14581458
    1459   // ── Volledig-scherm schrijfmodus: het schrijfveld vult de hele pagina.
     1459  // ── Full-screen writing mode: the writing field fills the whole page.
    14601460  const fsBtn = document.getElementById('pe-fullscreen-btn');
    14611461  const editorFrame = document.querySelector('.pe-editor-frame');
    14621462  const isTouch = !!(window.matchMedia && window.matchMedia('(pointer: coarse)').matches);
    14631463
    1464   // Op mobiel duwt het toetsenbord de zichtbare (visual) viewport omhoog terwijl
    1465   // een position:fixed-frame aan de LAYOUT-viewport blijft hangen → de toolbar
    1466   // schuift uit beeld. Houd het fullscreen-frame daarom gelijk aan de visual
    1467   // viewport (top + hoogte), 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.
    14681468  function syncFsViewport() {
    14691469    if (!editorFrame || !editorFrame.classList.contains('pe-fs')) return;
     
    14991499      }
    15001500    }
    1501     // Op touch is het veld inline NIET bewerkbaar; alleen in fullscreen wel.
     1501    // On touch the field is NOT editable inline; only in fullscreen.
    15021502    if (isTouch) editor.setAttribute('contenteditable', on ? 'true' : 'false');
    15031503    if (on) {
     
    15051505    } else {
    15061506      if (isTouch) editor.blur();
    1507       // Bij sluiten: naar de TOP van de content scrollen i.p.v. ergens onderaan
    1508       // (footer) te blijven hangen.
     1507      // On close: scroll to the TOP of the content instead of staying
     1508      // somewhere at the bottom (footer).
    15091509      requestAnimationFrame(function () {
    15101510        try { editorFrame.scrollIntoView({ block: 'start' }); } catch (_) {}
     
    15121512    }
    15131513  }
    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 — voelt
    1516   // 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).
    15171517  function openFs() {
    15181518    if (isFs()) return;
     
    15221522  function closeFs() {
    15231523    if (!isFs()) return;
    1524     if (history.state && history.state.peFs) history.back(); // → popstate sluit af
     1524    if (history.state && history.state.peFs) history.back(); // → popstate closes it
    15251525    else applyFs(false);
    15261526  }
     
    15341534  });
    15351535
    1536   // Op mobiel/tablet (touch): het content-veld is INLINE NIET bewerkbaar — het is
    1537   // dan geen tekstveld. Eén tik → fullscreen, waar het wél bewerkbaar wordt
    1538   // (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.
    15391539  if (isTouch) {
    15401540    editor.setAttribute('contenteditable', 'false');
     
    15551555      try { btn.classList.toggle('is-active', document.queryCommandState(cmd)); } catch(_) {}
    15561556    }
    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).
    15581558    const bqBtn = toolbar.querySelector('button[data-cmd="formatBlock"][data-arg="blockquote"]');
    15591559    if (bqBtn) bqBtn.classList.toggle('is-active', !!blockquoteAncestor());
     
    15881588  async function uploadAndInsertImage(file) {
    15891589    const edited = await openImageEditor(file);
    1590     if (!edited) return; // geannuleerd
     1590    if (!edited) return; // cancelled
    15911591    contentStatus.classList.remove('is-error');
    15921592    contentStatus.textContent = '<%= t('pedit.js_uploading') %>';
     
    16541654  }
    16551655
    1656   // ── Embed insert: plak een platform-URL -> [[embed:url]]-chip die server-side
    1657   //    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).
    16581658  const embedBtn = document.getElementById('insert-embed-btn');
    16591659  if (embedBtn) {
     
    16741674    const tpEmpty  = document.getElementById('tp-empty');
    16751675    const tpSearch = document.getElementById('tp-search');
    1676     let tpCache = null;       // cached tracks list (fetched once per page load)
     1676    let tpCache = null;       // cached track list (fetched once per page load)
    16771677    let tpLastFocus = null;   // element to restore focus to on close
    16781678
     
    18701870<script>
    18711871(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.
    18741874  var toggle = document.getElementById('pin-toggle');
    18751875  var rank   = document.getElementById('pin-rank');
    18761876  var pos    = document.getElementById('pin-pos');
    18771877  var label  = document.getElementById('pin-label');
    1878   var up     = document.getElementById('pin-up');    // hoger = lager getal (richting 1/bovenaan)
     1878  var up     = document.getElementById('pin-up');    // higher = lower number (towards 1/top)
    18791879  var down   = document.getElementById('pin-down');
    18801880  if (!toggle || !rank || !pos) return;
     
    19001900
    19011901(function () {
    1902   // De Opslaan/Annuleren-balk (position: sticky; bottom:0) net boven twee mogelijke
    1903   // obstakels houden door een dynamische bottom-offset te zetten = het grootste van:
    1904   //  1) de hoogte van het toetsenbord-deel dat de layout-viewport NIET dekt
    1905   //     (op iOS verschuift de visual viewport; op Android verkleint de layout-
    1906   //     viewport door interactive-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).
    19091909  var bar = document.querySelector('.pe-actions');
    19101910  if (!bar) return;
     
    19221922  window.addEventListener('resize', position);
    19231923  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.
    19251925  try { new MutationObserver(position).observe(document.body, { attributes: true, attributeFilter: ['class'] }); } catch (_) {}
    19261926})();
Note: See TracChangeset for help on using the changeset viewer.