Monorepo for Tangled
0

Configure Feed

Select the types of activity you want to include in your feed.

appview/pages: support image galleries in bsky posts in homepage

Signed-off-by: oppiliappan <me@oppi.li>

authored by

oppiliappan and committed by
Tangled
(Jul 7, 2026, 1:53 PM +0300) dc0aa3f2 f2714cce

+47 -19
+47 -19
appview/pages/templates/timeline/home.html
··· 20 20 <meta name="description" content="The next-generation social coding platform. Host repositories on your infrastructure with knots, use stacked pull requests, and run CI with spindles." /> 21 21 <link rel="canonical" href="https://tangled.org" /> 22 22 23 - <!-- hls.js: play bluesky video embeds --> 24 23 <script> 25 24 document.addEventListener('DOMContentLoaded', () => { 26 25 const videos = document.querySelectorAll('video[data-hls]'); ··· 30 29 videos.forEach((video) => { 31 30 const src = video.dataset.hls; 32 31 if (video.canPlayType('application/vnd.apple.mpegurl')) { 33 - // safari can play HLS natively 34 32 video.src = src; 35 33 } else if (window.Hls && Hls.isSupported()) { 36 34 const hls = new Hls(); ··· 40 38 }); 41 39 }; 42 40 43 - // only fetch hls.js when a browser actually needs it 44 41 const needsHls = Array.from(videos).some( 45 42 (v) => !v.canPlayType('application/vnd.apple.mpegurl') 46 43 ); ··· 53 50 script.src = '/static/hls.min.js'; 54 51 script.onload = attach; 55 52 document.head.appendChild(script); 53 + }); 54 + </script> 55 + <script> 56 + document.addEventListener('DOMContentLoaded', () => { 57 + document.querySelectorAll('[data-bsky-carousel]').forEach(el => { 58 + let maxRatio = 0; 59 + el.querySelectorAll('img[data-aspect]').forEach(img => { 60 + const [w, h] = img.dataset.aspect.split(':').map(Number); 61 + if (h > 0) maxRatio = Math.max(maxRatio, w / h); 62 + }); 63 + if (maxRatio > 0) { 64 + el.style.height = Math.min(Math.round(el.clientWidth / maxRatio), 400) + 'px'; 65 + } 66 + }); 56 67 }); 57 68 </script> 58 69 {{ end }} ··· 493 504 <p class="text-gray-900 dark:text-gray-100 text-base leading-relaxed whitespace-pre-wrap">{{ .Text }}</p> 494 505 495 506 {{ if .Embed }} 496 - {{ if .Embed.EmbedImages_View }} 497 - <div class="grid {{ if eq (len .Embed.EmbedImages_View.Images) 1 }}grid-cols-1{{ else if eq (len .Embed.EmbedImages_View.Images) 2 }}grid-cols-2{{ else }}grid-cols-2{{ end }} gap-2"> 498 - {{ range .Embed.EmbedImages_View.Images }} 499 - <img src="{{ .Fullsize }}" alt="{{ .Alt }}" class="rounded w-full h-auto object-cover border border-gray-200 dark:border-gray-700" loading="lazy" /> 507 + {{ if .Embed.Images }} 508 + {{ if eq (len .Embed.Images) 1 }} 509 + {{ with index .Embed.Images 0 }} 510 + <img src="{{ .Fullsize }}" alt="{{ .Alt }}" 511 + class="rounded w-full h-auto object-cover border border-gray-200 dark:border-gray-700" 512 + loading="lazy" /> 500 513 {{ end }} 501 - </div> 502 - {{ else if .Embed.EmbedExternal_View }} 503 - <a href="{{ .Embed.EmbedExternal_View.External.Uri }}" target="_blank" rel="noopener noreferrer" class="hover:no-underline block border border-gray-200 dark:border-gray-700 rounded overflow-hidden hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"> 504 - {{ if .Embed.EmbedExternal_View.External.Thumb }} 505 - <img src="{{ .Embed.EmbedExternal_View.External.Thumb }}" alt="" class="w-full h-48 object-cover" loading="lazy" /> 514 + {{ else }} 515 + {{ $total := len .Embed.Images }} 516 + <div class="flex overflow-x-auto gap-1.5 rounded" 517 + style="scrollbar-width: none; -ms-overflow-style: none;" 518 + data-bsky-carousel> 519 + {{ range $i, $img := .Embed.Images }} 520 + <div class="relative flex-shrink-0 h-full"> 521 + <img src="{{ $img.Fullsize }}" alt="{{ $img.Alt }}" 522 + class="h-full w-auto rounded object-cover border border-gray-200 dark:border-gray-700" 523 + loading="lazy" 524 + {{ if $img.AspectRatio }}data-aspect="{{ $img.AspectRatio.Width }}:{{ $img.AspectRatio.Height }}"{{ end }} /> 525 + <span class="absolute top-2 right-2 bg-black/60 text-white text-xs font-medium px-1.5 py-0.5 rounded pointer-events-none select-none tabular-nums">{{ add $i 1 }}/{{ $total }}</span> 526 + </div> 527 + {{ end }} 528 + </div> 529 + {{ end }} 530 + {{ else if .Embed.External }} 531 + <a href="{{ .Embed.External.Uri }}" target="_blank" rel="noopener noreferrer" class="hover:no-underline block border border-gray-200 dark:border-gray-700 rounded overflow-hidden hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"> 532 + {{ if .Embed.External.Thumb }} 533 + <img src="{{ .Embed.External.Thumb }}" alt="" class="w-full h-48 object-cover" loading="lazy" /> 506 534 {{ end }} 507 535 <div class="p-3"> 508 - <div class="font-medium text-gray-900 dark:text-gray-100 text-sm mb-1">{{ .Embed.EmbedExternal_View.External.Title }}</div> 509 - <div class="text-xs text-gray-600 dark:text-gray-400 line-clamp-2">{{ .Embed.EmbedExternal_View.External.Description }}</div> 510 - <div class="text-xs text-gray-500 dark:text-gray-500 mt-1">{{ .Embed.EmbedExternal_View.External.Uri }}</div> 536 + <div class="font-medium text-gray-900 dark:text-gray-100 text-sm mb-1">{{ .Embed.External.Title }}</div> 537 + <div class="text-xs text-gray-600 dark:text-gray-400 line-clamp-2">{{ .Embed.External.Description }}</div> 538 + <div class="text-xs text-gray-500 dark:text-gray-500 mt-1">{{ .Embed.External.Uri }}</div> 511 539 </div> 512 540 </a> 513 - {{ else if .Embed.EmbedVideo_View }} 541 + {{ else if .Embed.Video }} 514 542 <video 515 543 class="rounded w-full h-auto max-h-[32rem] bg-black border border-gray-200 dark:border-gray-700" 516 544 controls 517 545 preload="none" 518 546 playsinline 519 - {{ with .Embed.EmbedVideo_View.Thumbnail }}poster="{{ . }}"{{ end }} 520 - {{ with .Embed.EmbedVideo_View.Alt }}aria-label="{{ . }}"{{ end }} 521 - data-hls="{{ .Embed.EmbedVideo_View.Playlist }}" 547 + {{ with .Embed.Video.Thumbnail }}poster="{{ . }}"{{ end }} 548 + {{ with .Embed.Video.Alt }}aria-label="{{ . }}"{{ end }} 549 + data-hls="{{ .Embed.Video.Playlist }}" 522 550 ></video> 523 551 {{ end }} 524 552 {{ end }}