appview-less bluesky client
24
fork

Configure Feed

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

at main 77 lines 3.0 kB view raw
1import type { PostWithUri } from './at/fetch'; 2 3const LINE_HEIGHT = 20; // approx line height in px 4const CHARS_PER_LINE = 66; // approx chars per line 5const BASE_HEIGHT = 100; // header + footer + padding 6const IMAGE_HEIGHT = 300; // constrained height for single images 7const IMAGE_GRID_HEIGHT = 200; // height for grid of images 8const VIDEO_HEIGHT = 300; // default video height 9const LINK_CARD_HEIGHT = 100; // external link card 10const QUOTE_FALLBACK_HEIGHT = 100; // fallback for quote 11 12// idk this is dumb idk i hate virtual lists and shit bleh 13export const estimatePostHeight = (post: PostWithUri | undefined | null, depth = 0): number => { 14 if (!post) return 150; // default fallback if post is missing 15 if (depth > 1) return 0; // prevent infinite recursion 16 17 const record = post.record; 18 let height = BASE_HEIGHT; 19 20 // 1. text height 21 if (record.text) { 22 const lines = Math.ceil(record.text.length / CHARS_PER_LINE) || 1; 23 // add a bit more for newlines if present 24 const newlines = (record.text.match(/\n/g) || []).length; 25 height += (Math.max(lines, newlines + 1) * LINE_HEIGHT); 26 } 27 28 // 2. embeds 29 if (record.embed) { 30 const embed = record.embed; 31 32 if (embed.$type === 'app.bsky.embed.images') { 33 // images 34 if (embed.images.length === 1) { 35 const aspect = embed.images[0].aspectRatio; 36 if (aspect) { 37 // w / h = a => h = w / a 38 // assuming max width of ~500px in feed 39 const calculatedHeight = 500 / (aspect.width / aspect.height); 40 height += Math.min(calculatedHeight, 500); // clamp max height 41 } else { 42 height += IMAGE_HEIGHT; 43 } 44 } else { 45 height += IMAGE_GRID_HEIGHT; 46 } 47 } else if (embed.$type === 'app.bsky.embed.video') { 48 // video 49 const aspect = embed.aspectRatio; 50 if (aspect) { 51 const calculatedHeight = 500 / (aspect.width / aspect.height); 52 height += Math.min(calculatedHeight, 500); 53 } else { 54 height += VIDEO_HEIGHT; 55 } 56 } else if (embed.$type === 'app.bsky.embed.record') { 57 // quote post 58 height += QUOTE_FALLBACK_HEIGHT; 59 60 } else if (embed.$type === 'app.bsky.embed.recordWithMedia') { 61 // recordWithMedia 62 // media part 63 const media = embed.media; 64 if (media.$type === 'app.bsky.embed.images') { 65 height += (media.images.length === 1 ? IMAGE_HEIGHT : IMAGE_GRID_HEIGHT); 66 } else if (media.$type === 'app.bsky.embed.video') { 67 height += VIDEO_HEIGHT; 68 } 69 // quote part 70 height += QUOTE_FALLBACK_HEIGHT; 71 } else if (embed.$type === 'app.bsky.embed.external') { 72 height += LINK_CARD_HEIGHT; 73 } 74 } 75 76 return Math.round(height); 77};