Changeset 15f1cb5 in Klonkt
- Timestamp:
- 07/30/2026 08:09:51 AM (6 weeks ago)
- Branches:
- main
- Children:
- 2a10445
- Parents:
- 79f00c5
- Files:
-
- 2 edited
-
src/services/ActivityPubService.js (modified) (5 diffs)
-
test/c2s-compose.test.js (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/services/ActivityPubService.js
r79f00c5 r15f1cb5 374 374 // unreliable on Mastodon and its iOS apps; the MP4 plays everywhere. Else the still cover image. 375 375 // Each entry carries the media URL + its alt text (federated as the AS2 attachment `name`, for a11y). 376 if (post.cover_video_url && !noImages) urls.push({ url: abs(post.cover_video_url), name: post.cover_alt || '' });377 else if (post.cover_image_url && !noImages) urls.push({ url: abs(post.cover_image_url), name: post.cover_alt || '' });378 376 // Media a C2S composer attached (shaer-j3uh): federate with their REAL 379 377 // mediaType, because the extension map below knows no audio and would call 380 // an m4a an Image. Images also live inline in the content, so the dedupe 381 // by URL keeps them single. 378 // an m4a an Image. Pushed BEFORE the covers: a C2S video doubles as the 379 // cover video, and the URL-dedupe keeps the FIRST entry, which must be the 380 // one that knows its type and poster. Images also live inline in the 381 // content, so the dedupe keeps those single too. 382 382 try { 383 383 for (const a of JSON.parse(post.c2s_attachments || '[]')) { … … 385 385 } 386 386 } catch { /* malformed never blocks the Note */ } 387 if (post.cover_video_url && !noImages) urls.push({ url: abs(post.cover_video_url), name: post.cover_alt || '' }); 388 else if (post.cover_image_url && !noImages) urls.push({ url: abs(post.cover_image_url), name: post.cover_alt || '' }); 387 389 let body = post.content || ''; 388 390 // Only federate inline images we can actually serve: absolute http(s) URLs, or our own … … 2408 2410 return `<p><video controls playsinline preload="metadata"${poster} src="${a.url}"></video></p>`; 2409 2411 }).join(''); 2412 // The web tile (Robins aanwijzing, 30-7): a video post's first video 2413 // becomes the cover video, so the grid plays it muted-looping as its own 2414 // thumbnail, exactly like an animated cover. Deliberately NO cover image 2415 // next to it: the tile's image branch would win and freeze the tile. A 2416 // photo post gets its first image as cover, instead of the (untitled) 2417 // gradient. 2418 const coverVid = media.find((a) => a.mediaType.startsWith('video/')); 2419 const coverImg = media.find((a) => a.mediaType.startsWith('image/')); 2410 2420 const postId = crypto.randomUUID(); 2411 2421 const slug = 'n-' + postId.slice(0, 8); … … 2418 2428 const vis = c2sVisibility(object); 2419 2429 const fanOnly = (vis === 'friends' || vis === 'direct') ? 1 : 0; 2420 db.prepare(`INSERT INTO posts (id, site_id, slug, author_id, title, content, excerpt, status, type, language, fan_only, ap_visibility, created_at, updated_at, published_at) 2421 VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`) 2422 .run(postId, site.id, slug, user.id, '', html + mediaHtml, '', 'published', 'post', object.language || 'nl', fanOnly, vis, now, now, now); 2430 db.prepare(`INSERT INTO posts (id, site_id, slug, author_id, title, content, excerpt, status, type, language, fan_only, ap_visibility, cover_image_url, cover_video_url, created_at, updated_at, published_at) 2431 VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`) 2432 .run(postId, site.id, slug, user.id, '', html + mediaHtml, '', 'published', 'post', object.language || 'nl', fanOnly, vis, 2433 coverVid ? null : (coverImg ? coverImg.url : null), coverVid ? coverVid.url : null, now, now, now); 2423 2434 if (media.length) { try { db.prepare('UPDATE posts SET c2s_attachments = ? WHERE id = ?').run(JSON.stringify(media), postId); } catch { /* column exists via ensureColumn */ } } 2424 2435 try { db.prepare('UPDATE posts SET content_rendered = ? WHERE id = ?').run(bakePostContent(html + mediaHtml), postId); } catch { /* render fallback covers it */ } … … 2426 2437 try { db.prepare('INSERT INTO posts_fts(content, title, author, post_id) VALUES (?,?,?,?)').run(HtmlSanitizerService.toPlainText(html), '', user.username || '', postId); } catch { /* FTS non-fatal */ } 2427 2438 if (vis !== 'direct') { 2428 deliverCreate(site, { id: postId, slug, title: '', content: html + mediaHtml, published_at: now, created_at: now, fan_only: fanOnly, ap_visibility: vis, c 2s_attachments: media.length ? JSON.stringify(media) : null }).catch(() => { /* best-effort */ });2439 deliverCreate(site, { id: postId, slug, title: '', content: html + mediaHtml, published_at: now, created_at: now, fan_only: fanOnly, ap_visibility: vis, cover_image_url: coverVid ? null : (coverImg ? coverImg.url : null), cover_video_url: coverVid ? coverVid.url : null, c2s_attachments: media.length ? JSON.stringify(media) : null }).catch(() => { /* best-effort */ }); 2429 2440 } 2430 2441 return { status: 201, id: postId, url: `${base}/ap/notes/${postId}` }; -
test/c2s-compose.test.js
r79f00c5 r15f1cb5 85 85 assert.equal(vid.icon && vid.icon.url, 'https://test.example/media/reply-media/film.mp4.poster.jpg', 86 86 'the poster federates as the attachment icon'); 87 // The web tile plays the video as its own thumbnail (Robins aanwijzing): 88 // the first video becomes the cover video, with NO cover image beside it, 89 // because the tile's image branch would win and freeze the tile. 90 assert.equal(post.cover_video_url, '/media/reply-media/film.mp4'); 91 assert.equal(post.cover_image_url, null); 92 assert.equal((note.attachment || []).filter((a) => a.url.endsWith('film.mp4')).length, 1, 93 'cover video + attachment dedupe to the one entry that knows its poster'); 87 94 } finally { 88 95 if (prev === undefined) delete process.env.MEDIA_PATH; else process.env.MEDIA_PATH = prev; … … 115 122 }); 116 123 assert.equal(r.status, 201, 'a picture can be the whole message'); 124 const post = db.prepare('SELECT * FROM posts WHERE id = ?').get(r.id); 125 assert.equal(post.cover_image_url, '/media/reply-media/alleen.jpg', 126 'a photo post fronts its photo instead of the (untitled) gradient'); 117 127 });
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)