Changeset 1907a18 in Klonkt


Ignore:
Timestamp:
06/15/2026 01:48:56 AM (3 months ago)
Author:
roboburr <roboburr@…>
Branches:
main
Children:
d352b63
Parents:
851239c
Message:

feat: embed media via editor button ([[embed:url]] shortcode)

Embed YouTube/Spotify/SoundCloud/Vimeo/Apple Music/Bandcamp with a toolbar
button in the post editor: paste a URL -> [[embed:url]] chip -> server renders
an iframe (AudioEmbedService.embedMediaShortcodes, in the existing embed pipeline).
Standalone URL lines also embed via autoembed(). CSP already allows the domains.

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

Location:
src
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • src/routes/posts.js

    r851239c r1907a18  
    398398  if (site.enable_audio_player !== 0) {
    399399    html = AudioEmbedService.autoembed(html);
     400    html = AudioEmbedService.embedMediaShortcodes(html);
    400401    html = AudioEmbedService.embedExternalLinkShortcodes(html);
    401402
  • src/services/AudioEmbedService.js

    r851239c r1907a18  
    187187      }
    188188    );
     189  }
     190
     191  /**
     192   * Replace [[embed:<url>]] shortcodes met de platform-iframe (YouTube, Spotify,
     193   * SoundCloud, Apple Music, Bandcamp, Vimeo). De editor-knop voegt deze
     194   * shortcode in; losse URL-regels embedden ook automatisch via autoembed().
     195   * Niet-ondersteunde/ongeldige URLs krijgen een nette inline-melding.
     196   */
     197  static embedMediaShortcodes(html) {
     198    if (!html) return html;
     199    return html.replace(/\[\[embed:([^\]]+)\]\]/gi, (match, rawUrl) => {
     200      const url = rawUrl.trim().replace(/&amp;/g, '&');
     201      const detected = this.detectProvider(url);
     202      if (!detected) {
     203        return `<div class="post-embed-missing"><em>Embed: niet-ondersteunde of ongeldige URL.</em></div>`;
     204      }
     205      return this.generateIframe(detected.provider, detected) || match;
     206    });
    189207  }
    190208
  • src/views/pages/post-edit.ejs

    r851239c r1907a18  
    115115            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="15" y2="18"/><polygon points="3 5 3 13 9 9"/></svg>
    116116          </button>
     117          <button type="button" id="insert-embed-btn" title="Embed (YouTube, Spotify, SoundCloud, Vimeo…)" aria-label="Media embedden">
     118            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="4" width="20" height="16" rx="2"/><polygon points="10 9 15.5 12 10 15"/></svg>
     119          </button>
    117120          <span class="pe-toolbar-spacer"></span>
    118121          <button type="button" data-cmd="removeFormat" title="Wis opmaak" aria-label="Wis opmaak">
     
    930933  // HTML attribute; since chipify only walks text nodes (never attribute
    931934  // values) that's already safe.
    932   const SC_RE = /\[\[(track|album|playlist):([^\]]+)\]\]/g;
     935  const SC_RE = /\[\[(track|album|playlist|embed):([^\]]+)\]\]/g;
    933936
    934937  const SC_ICONS = {
     
    936939    album:    '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="3"/></svg>',
    937940    playlist: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="15" y2="18"/><polygon points="3 5 3 13 9 9"/></svg>',
     941    embed:    '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="16" rx="2"/><polygon points="10 9 15.5 12 10 15"/></svg>',
    938942  };
    939943
     
    946950    if (kind === 'album')    return 'Album: ' + value;
    947951    if (kind === 'playlist') return 'Playlist: ' + value;
     952    if (kind === 'embed') {
     953      const clean = String(value || '').replace(/^https?:\/\/(www\.)?/, '');
     954      return '▶ ' + (clean.length > 36 ? clean.slice(0, 34) + '…' : clean);
     955    }
    948956    return value;
    949957  }
     
    11571165    }
    11581166    updateCharCount();
     1167  }
     1168
     1169  // ── Embed insert: plak een platform-URL -> [[embed:url]]-chip die server-side
     1170  //    een iframe wordt (YouTube/Spotify/SoundCloud/Vimeo/Apple Music/Bandcamp).
     1171  const embedBtn = document.getElementById('insert-embed-btn');
     1172  if (embedBtn) {
     1173    embedBtn.addEventListener('click', () => {
     1174      const raw = window.prompt('Plak een media-URL om in te sluiten\n(YouTube, Spotify, SoundCloud, Vimeo, Apple Music, Bandcamp):');
     1175      if (!raw) return;
     1176      const url = raw.trim();
     1177      if (!/^https?:\/\//i.test(url)) { alert('Geef een volledige URL (https://…).'); return; }
     1178      insertChip('embed', url);
     1179    });
    11591180  }
    11601181
Note: See TracChangeset for help on using the changeset viewer.