Changeset ca260d1 in Klonkt for src


Ignore:
Timestamp:
09/30/2026 08:08:44 AM (4 hours ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
76bd53c
Parents:
19e28ce
Message:

Fedi open audio per track, vanuit de mediamanager

Robin, 30-9: "Fedi open audio ook per track aan kunnen zetten in de media
manager."

De vlag was altijd al per track (audio_tracks.fedi_open): hij opent het
bestand en maakt er een echte Audio-bijlage van, met een speler in Mastodon en
consorten. Aanzetten kon alleen per POST, met het vinkje in de editor, dat elke
track in die post opende. Nu staat er in de mediamanager, tab Audio, per track
een wereldbol.

EENRICHTINGS, net als in de editor. Dicht is het een gedimde knop die eerst
vraagt of het zeker is, want terug kan niet; open is het een slotje in de
accentkleur en geen knop meer. Een bestand dat al gefedereerd is staat op andere
servers, en het hier weer afschermen zou schijnveiligheid zijn. Ook de algemene
bijwerk-API van een track neemt fedi_open niet aan; een toets bewaakt dat.

Alleen bij een track met een eigen bestand: een track die alleen een link is,
heeft niets om te openen. Een formulier en geen fetch, dus het werkt zonder
JavaScript, en data-confirm uit shell.ejs stelt de vraag.

EN DE POSTS GAAN OPNIEUW DE DEUR UIT. Het vinkje in de editor werkt zichtbaar
omdat het opslaan van de post een Update stuurt, met de audio erin. Een knop
die alleen de vlag zet, laat elke post die al verstuurd is zonder speler staan
tot iemand hem bewerkt. Dus hier dezelfde Update, voor elke gepubliceerde post
van deze site waar de track in staat: rechtstreeks, via een playlist of via zijn
album. postsEmbeddingTrack in music/index.js zoekt die op; trackHostPosts kiest
per track maar een post, voor de bibliotheek. De bibliotheek zelf hoeft niets,
die wordt opgehaald en een open track staat er meteen in.

Getoetst: openen zet de vlag en stuurt precies de drie posts (geen concept,
geen post zonder de track, geen post van een andere site); nog eens openen doet
niets; een link-track en een track van een andere site blijven dicht; de route;
geen weg terug via de API; de knop alleen waar het kan, met bevestiging; en de
hulpfunctie rechtstreeks. Tegenproef per zaak. Eentje ving eerst niets: het
sitefilter zit dubbel, want openen haalt elke post nog eens op met site_id. De
hulpfunctie is los te gebruiken, dus die wordt nu ook rechtstreeks getoetst.
Volledige suite 1301 groen.

Nagekeken in de gerenderde mediamanager met de echte stylesheet: open in de
accentkleur, dicht gedimd, geen wereldbol bij een link-track, dezelfde maat als
de andere knoppen in de rij.

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

Location:
src
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • src/routes/admin-audio.js

    r19e28ce rca260d1  
    2424import { mediaDir } from '../config/paths.js';
    2525import * as ActivityPubService from '../services/ActivityPubService.js';
     26import { postsEmbeddingTrack } from '../services/music/index.js';
     27import { t as i18nT, resolveLang } from '../services/i18n.js';
    2628
    2729const __dirname = path.dirname(fileURLToPath(import.meta.url));
    … …  
    9698  const rows = db.prepare(`
    9799    SELECT t.id, t.title, t.artist, t.album, t.duration, t.cover_url,
    98            t.position, t.created_at, t.downloadable, m.filename, m.size, m.mime_type
     100           t.position, t.created_at, t.downloadable, t.fedi_open, t.media_id,
     101           m.filename, m.size, m.mime_type
    99102    FROM audio_tracks t
    100103    LEFT JOIN media m ON m.id = t.media_id
    … …  
    290293});
    291294
     295/**
     296 * Een track openbaar op de fediverse zetten, vanuit de mediamanager (Robin,
     297 * 30-9: "Fedi open audio ook per track aan kunnen zetten").
     298 *
     299 * Tot nu toe kon dat alleen per POST, met het vinkje in de editor, dat elke
     300 * track in die post opende. De vlag zelf was altijd al per track
     301 * (audio_tracks.fedi_open): hij opent het BESTAND en maakt er een echte
     302 * Audio-bijlage van. Dit is dezelfde vlag, met een eigen knop.
     303 *
     304 * EENRICHTINGS, net als in de editor. Een bestand dat al gefedereerd is, staat
     305 * op andere servers; het hier weer afschermen zou schijnveiligheid zijn. Er is
     306 * dus geen weg terug, en die bouwen we ook niet.
     307 *
     308 * EN DE POSTS GAAN OPNIEUW DE DEUR UIT. Het vinkje in de editor werkt zichtbaar
     309 * omdat het opslaan van de post een Update stuurt, met de audio erin. Een knop
     310 * die alleen de vlag zet, laat elke post die al verstuurd is zonder speler
     311 * staan tot iemand hem bewerkt. Dus hier dezelfde Update, voor elke
     312 * gepubliceerde post waar de track in staat -- rechtstreeks, via een playlist of
     313 * via zijn album. De bibliotheek (Funkwhale) hoeft niets: die wordt opgehaald,
     314 * en een open track staat er meteen in.
     315 *
     316 * Alleen een track met een eigen BESTAND: een track die alleen een link is heeft
     317 * niets om te openen.
     318 *
     319 * `deliver` is er voor de toetsen; de route gebruikt de echte deliverUpdate.
     320 */
     321export function openTrackOnFediverse(site, trackId, { deliver = (s, p) => ActivityPubService.deliverUpdate(s, p) } = {}) {
     322  const t = db.prepare('SELECT id, media_id, fedi_open FROM audio_tracks WHERE id = ? AND site_id = ?').get(trackId, site.id);
     323  if (!t) return { ok: false, reason: 'not_found' };
     324  if (!t.media_id) return { ok: false, reason: 'no_file' };
     325  if (t.fedi_open) return { ok: true, changed: false, updated: 0 };
     326  db.prepare('UPDATE audio_tracks SET fedi_open = 1 WHERE id = ? AND site_id = ?').run(trackId, site.id);
     327  const postIds = postsEmbeddingTrack(site.id, trackId);
     328  for (const id of postIds) {
     329    const post = db.prepare('SELECT * FROM posts WHERE id = ? AND site_id = ?').get(id, site.id);
     330    if (!post) continue;
     331    // Op de achtergrond: een trage volger houdt de knop niet vast.
     332    Promise.resolve()
     333      .then(() => deliver(site, post))
     334      .catch((e) => console.warn('[admin-audio] Update na openen mislukt:', post.id, e.message));
     335  }
     336  return { ok: true, changed: true, updated: postIds.length };
     337}
     338
     339router.post('/:id/fedi-open', requireGod, (req, res) => {
     340  const site = res.locals.site;
     341  if (!site) return res.status(404).send('Site required');
     342  const lang = resolveLang(req, { userLang: req.session?.user?.lang });
     343  const r = openTrackOnFediverse(site, req.params.id);
     344  if (!r.ok) {
     345    const msg = r.reason === 'no_file' ? i18nT(lang, 'aaud.fedi_no_file') : 'Not found';
     346    return res.redirect('/admin/audio?error=' + encodeURIComponent(msg));
     347  }
     348  res.redirect('/admin/audio?success=' + encodeURIComponent(i18nT(lang, 'aaud.fedi_opened', { n: r.updated })));
     349});
     350
    292351router.post('/:id/delete', requireGod, (req, res) => {
    293352  const site = res.locals.site;
  • src/services/i18n.js

    r19e28ce rca260d1  
    400400    'aaud.delete': 'Verwijderen',
    401401    'aaud.delete_confirm': 'Track verwijderen?',
    402     'aaud.dl_on': 'Download-voor-email staat AAN — klik om uit te zetten',
     402    'aaud.fedi_open_btn': 'Openbaar delen op de fediverse', 'aaud.fedi_open_on': 'Openbaar gedeeld op de fediverse. Dat is permanent.', 'aaud.fedi_open_confirm': 'Deze track openbaar delen op de fediverse? Hij wordt speelbaar in apps en het bestand is te downloaden. Posts met deze track gaan opnieuw naar je volgers. Dit is permanent en niet terug te draaien.', 'aaud.fedi_opened': 'Openbaar gedeeld op de fediverse. Posts opnieuw verstuurd: {n}.', 'aaud.fedi_no_file': 'Deze track heeft geen eigen bestand, alleen een link. Er valt niets te openen.', 'aaud.dl_on': 'Download-voor-email staat AAN — klik om uit te zetten',
    403403    'aaud.dl_off': 'Download-voor-email staat uit — klik om aan te zetten',
    404404    'aaud.fedi_on': 'Op de fediverse gedeeld (speelt overal inline, bestand downloadbaar) — klik om uit te zetten',
    … …  
    14101410    'aaud.delete': 'Delete',
    14111411    'aaud.delete_confirm': 'Delete track?',
    1412     'aaud.dl_on': 'Download-for-email is ON — click to turn off',
     1412    'aaud.fedi_open_btn': 'Share openly on the fediverse', 'aaud.fedi_open_on': 'Shared openly on the fediverse. This is permanent.', 'aaud.fedi_open_confirm': 'Share this track openly on the fediverse? It becomes playable in apps and the file can be downloaded. Posts with this track are sent to your followers again. This is permanent and cannot be undone.', 'aaud.fedi_opened': 'Shared openly on the fediverse. Posts sent again: {n}.', 'aaud.fedi_no_file': 'This track has no file of its own, only a link. There is nothing to open.', 'aaud.dl_on': 'Download-for-email is ON — click to turn off',
    14131413    'aaud.dl_off': 'Download-for-email is off — click to turn on',
    14141414    'aaud.fedi_on': 'Shared on the fediverse (plays inline everywhere, file downloadable) — click to turn off',
    … …  
    24192419    'aaud.delete': 'Löschen',
    24202420    'aaud.delete_confirm': 'Track löschen?',
    2421     'aaud.dl_on': 'Download-für-E-Mail ist AN — zum Ausschalten klicken',
     2421    'aaud.fedi_open_btn': 'Offen im Fediverse teilen', 'aaud.fedi_open_on': 'Offen im Fediverse geteilt. Das ist dauerhaft.', 'aaud.fedi_open_confirm': 'Diesen Track offen im Fediverse teilen? Er wird in Apps abspielbar und die Datei kann heruntergeladen werden. Beiträge mit diesem Track werden erneut an deine Follower gesendet. Das ist dauerhaft und kann nicht rückgängig gemacht werden.', 'aaud.fedi_opened': 'Offen im Fediverse geteilt. Erneut gesendete Beiträge: {n}.', 'aaud.fedi_no_file': 'Dieser Track hat keine eigene Datei, nur einen Link. Es gibt nichts zu öffnen.', 'aaud.dl_on': 'Download-für-E-Mail ist AN — zum Ausschalten klicken',
    24222422    'aaud.dl_off': 'Download-für-E-Mail ist aus — zum Einschalten klicken',
    24232423    'aaud.fedi_on': 'Im Fediverse geteilt (spielt überall inline, Datei herunterladbar) — zum Ausschalten klicken',
  • src/services/music/index.js

    r19e28ce rca260d1  
    134134  for (const r of rijen) if (!uit.has(r.tid)) uit.set(r.tid, { id: r.post_id, slug: r.post_slug });
    135135  return uit;
     136}
     137
     138/**
     139 * Elke GEPUBLICEERDE post waar deze track in staat, op welke manier ook:
     140 * rechtstreeks ([[track:id]]), via een playlist of via zijn albumnaam.
     141 *
     142 * Voor iets anders dan trackHostPosts, dat per track EEN post kiest voor de
     143 * bibliotheek. Opent de mediamanager een track (shaer, 30-9), dan moeten ALLE
     144 * posts met die track opnieuw de deur uit, anders krijgt een volger de speler
     145 * pas bij de volgende bewerking van die post.
     146 *
     147 * Alleen posts van deze site: playlists.id is een globale sleutel, en het
     148 * filter hoort op de post en de track, niet op de playlist (zie de
     149 * tenancy-toets bij setAudioFediOpen).
     150 */
     151export function postsEmbeddingTrack(siteId, trackId) {
     152  const t = db.prepare('SELECT id, album FROM audio_tracks WHERE id = ? AND site_id = ?').get(trackId, siteId);
     153  if (!t) return [];
     154  const rows = db.prepare(`
     155    SELECT p.id FROM posts p
     156     WHERE p.site_id = ? AND p.status = 'published'
     157       AND p.content LIKE '%[[track:' || ? || ']]%'
     158    UNION
     159    SELECT p.id FROM playlist_tracks pt
     160      JOIN posts p ON p.site_id = ? AND p.status = 'published'
     161                  AND p.content LIKE '%[[playlist:' || pt.playlist_id || ']]%'
     162     WHERE pt.track_id = ?
     163    UNION
     164    SELECT p.id FROM posts p
     165     WHERE p.site_id = ? AND p.status = 'published'
     166       AND ? IS NOT NULL AND ? <> ''
     167       AND p.content LIKE '%[[album:' || ? || ']]%'
     168  `).all(siteId, t.id, siteId, t.id, siteId, t.album, t.album, t.album);
     169  return rows.map((r) => r.id);
    136170}
    137171
  • src/views/pages/admin-audio.ejs

    r19e28ce rca260d1  
    9191        var _aaudCopy     = t('aaud.copy_click');
    9292        var _aaudDelConfirm = t('aaud.delete_confirm');
     93        var _aaudFediBtn     = t('aaud.fedi_open_btn');
     94        var _aaudFediOn      = t('aaud.fedi_open_on');
     95        var _aaudFediConfirm = t('aaud.fedi_open_confirm');
    9396      %>
    9497      <ul class="ax-list">
    … …  
    125128                        aria-label="<%= t.downloadable ? _aaudDlOn : _aaudDlOff %>" title="<%= t.downloadable ? _aaudDlOn : _aaudDlOff %>"
    126129                        style="<%= t.downloadable ? 'color:var(--accent,#6b8f71)' : 'opacity:.5' %>">⬇</button>
     130              <% } %>
     131              <%# Openbaar op de fediverse, per track (Robin, 30-9). EENRICHTINGS, net
     132                  als het vinkje in de post-editor: open is een slotje en geen knop meer,
     133                  want een bestand dat al verspreid is haal je niet terug. Alleen bij een
     134                  track met een eigen bestand; een link heeft niets om te openen. Een
     135                  formulier en geen fetch: werkt zonder JS, en data-confirm vraagt het
     136                  eerst (shell.ejs). %>
     137              <% if (t.media_id) { %>
     138                <% if (t.fedi_open) { %>
     139                  <span class="ax-icon-btn ax-fedi-on" role="img" aria-label="<%= _aaudFediOn %>" title="<%= _aaudFediOn %>"><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg></span>
     140                <% } else { %>
     141                  <form action="/admin/audio/<%= t.id %>/fedi-open" method="post" data-confirm="<%= _aaudFediConfirm %>" class="ax-track-fedi">
     142                    <button type="submit" class="ax-icon-btn ax-fedi-off" aria-label="<%= _aaudFediBtn %>" title="<%= _aaudFediBtn %>"><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg></button>
     143                  </form>
     144                <% } %>
    127145              <% } %>
    128146              <form action="/admin/audio/<%= t.id %>/delete" method="post" data-confirm="<%= _aaudDelConfirm %>" class="ax-track-delete">
    … …  
    247265}
    248266.ax-file-name[data-empty] { color: var(--ink-muted, var(--ink-soft)); }
     267/* Openbaar op de fediverse: dicht is gedimd, net als een uitgezette download;
     268   open is de accentkleur en geen knop meer, want terug kan niet. */
     269.ax-track-fedi { display: inline; }
     270.ax-fedi-off { opacity: .5; }
     271.ax-fedi-off:hover, .ax-fedi-off:focus-visible { opacity: 1; }
     272.ax-fedi-on { color: var(--accent, #6b8f71); cursor: default; }
    249273
    250274.ax-form-actions {
Note: See TracChangeset for help on using the changeset viewer.