selfhostable, read-only reddit client
16
fork

Configure Feed

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

Merge pull request #22 from PortableProgrammer/feat-consolidate-card-compact-media

Feat: Consolidate Card and Compact media viewers

authored by

akshay and committed by
GitHub
71280f02 f983c131

+156 -163
+57 -80
src/mixins/post.pug
··· 6 6 - var sortQuery = query && query.sort ? query.sort + (query.t ? '&t=' + query.t : '') : 'hot' 7 7 article(class=`post`) 8 8 div.post-container(class=`${query.view} ${p.stickied?"sticky":""}`) 9 - div.post-text(class=`${query.view}`) 10 - div.title-container(class=`${query.view}`) 11 - a(class=`${query.view}`, href=`/comments/${p.id}?from=${from}&sort=${sortQuery}&view=${viewQuery}`) 12 - != p.title 13 - span.domain (#{p.domain}) 14 - div.info-container 15 - p 16 - | #{fmtnum(p.ups)} ↑ 17 - if p.gilded > 0 9 + div.post-info 10 + div.post-text(class=`${query.view}`) 11 + div.title-container(class=`${query.view}`) 12 + a(class=`${query.view}`, href=`/comments/${p.id}?from=${from}&sort=${sortQuery}&view=${viewQuery}`) 13 + != p.title 14 + span.domain (#{p.domain}) 15 + div.info-container 16 + p 17 + | #{fmtnum(p.ups)} ↑ 18 + if p.gilded > 0 19 + |  ·  20 + span.gilded 21 + | #{p.gilded} ☆ 22 + span.post-author 23 + |  ·  24 + | u/#{p.author} 25 + |  ·  26 + | #{timeDifference(Date.now(), p.created * 1000)} 18 27 |  ·  19 - span.gilded 20 - | #{p.gilded} ☆ 21 - span.post-author 28 + a(href=`/r/${p.subreddit}?sort=${sortQuery}&view=${viewQuery}`) r/#{p.subreddit} 22 29 |  ·  23 - | u/#{p.author} 24 - |  ·  25 - | #{timeDifference(Date.now(), p.created * 1000)} 26 - |  ·  27 - a(href=`/r/${p.subreddit}?sort=${sortQuery}&view=${viewQuery}`) r/#{p.subreddit} 28 - |  ·  29 - a(href=`/comments/${p.id}?from=${from}&sort=${sortQuery}&view=${viewQuery}`) #{fmtnum (p.num_comments)} ↩ 30 - if (query.view == "card" && !isPostGallery(p) && !isPostImage(p) && !isPostVideo(p) && p.selftext_html) 31 - div.self-text-overflow(class='card') 32 - if query.view == "card" && (p.spoiler || p.over_18) 33 - div.spoiler(id=`spoiler_${p.id}`, onclick=`javascript:document.getElementById('spoiler_${p.id}').style.display = 'none';`) 34 - h2 35 - != p.over_18 ? 'nsfw' : 'spoiler' 36 - div.self-text(class='card') 37 - != convertInlineImageLinks(p.selftext_html) 38 - div.media-preview(class=`${query.view}`) 39 - - var onclick = query.view != "card" ? `toggleDetails('${p.id}')` : `` 40 - if query.view == "card" && (p.spoiler || p.over_18) && (isPostGallery(p) || isPostImage(p) || isPostVideo(p)) 41 - div.spoiler(id=`spoiler_${p.id}`, onclick=`javascript:document.getElementById('spoiler_${p.id}').style.display = 'none';`) 42 - h2 43 - != p.over_18 ? 'nsfw' : 'spoiler' 44 - if isPostGallery(p) 45 - - var item = postGalleryItems(p)[0] 46 - if query.view == "card" 47 - div.gallery(class=`${query.view}`) 48 - each item in postGalleryItems(p) 49 - div.gallery-item(class=`${query.view}`) 50 - a(href=`/media/${item.url}`) 51 - img(src=item.url loading="lazy") 52 - div.gallery-item-idx(class=`${query.view}`) 53 - | #{`${item.idx}/${item.total}`} 54 - else 55 - img(src=item.url onclick=onclick) 56 - else if isPostImage(p) 57 - - var url = query.view == "card" ? p.url : postThumbnail(p) 58 - #{query.view == "card" ? "a href=/media/" + url : span} 59 - img(src=url onclick=onclick) 60 - else if isPostVideo(p) 61 - - var decodedVideos = decodePostVideoUrls(p) 62 - if query.view == "card" 63 - video(controls="" muted="" data-dashjs-player="" preload="metadata" poster=decodedVideos[4]) 64 - // HLS 65 - source(src=decodedVideos[0]) 66 - // Dash 67 - source(src=decodedVideos[1]) 68 - // Fallback 69 - source(src=decodedVideos[2]) 70 - else 71 - video(autoplay="" muted="" data-dashjs-player="" onclick=`toggleDetails('${p.id}')` width="100px" height="100px") 72 - // Scrubber 73 - source(src=decodedVideos[3]) 74 - else if isPostLink(p) 75 - a(href=p.url) 76 - if (query.view == 'card') 77 - | #{p.domain} 78 - | ↗ 30 + a(href=`/comments/${p.id}?from=${from}&sort=${sortQuery}&view=${viewQuery}`) #{fmtnum (p.num_comments)} ↩ 31 + if (query.view == "card" && !isPostMedia(p) && p.selftext_html) 32 + div.self-text-overflow.card 33 + if p.spoiler || p.over_18 34 + div.spoiler(id=`spoiler_${p.id}`, onclick=`javascript:document.getElementById('spoiler_${p.id}').style.display = 'none';`) 35 + h2 36 + != p.over_18 ? 'nsfw' : 'spoiler' 37 + div.self-text.card 38 + != convertInlineImageLinks(p.selftext_html) 39 + if query.view != "card" 40 + div.media-preview 41 + - var onclick = `toggleDetails('${p.id}')` 42 + if isPostGallery(p) 43 + - var item = (p.over_18 ? `/nsfw.svg` : p.spoiler ? `/spoiler.svg` : postGalleryItems(p)[0].url) 44 + img(src=item onclick=onclick) 45 + else if isPostImage(p) 46 + - var url = postThumbnail(p) 47 + img(src=url onclick=onclick) 48 + else if isPostVideo(p) 49 + - var decodedVideos = decodePostVideoUrls(p) 50 + video(data-dashjs-player="" playsinline="" autoplay="" muted="" onclick=`toggleDetails('${p.id}')` src=decodedVideos[3] poster=decodedVideos[4] width="100px" height="100px") 51 + else if isPostLink(p) 52 + a(href=p.url) 53 + | ↗ 79 54 80 - if query.view == "compact" && (isPostGallery(p) || isPostImage(p) || isPostVideo(p)) 81 - details(id=`${p.id}`) 55 + details(id=`${p.id}` open=(query.view == "card" && (isPostMedia(p) || isPostLink(p))) class=`${query.view}`) 82 56 summary.expand-post expand media 83 57 div.image-viewer 58 + if query.view == "card" && (p.spoiler || p.over_18) && isPostMedia(p) 59 + div.spoiler(id=`spoiler_${p.id}`, onclick=`javascript:document.getElementById('spoiler_${p.id}').style.display = 'none';`) 60 + h2 61 + != p.over_18 ? 'nsfw' : 'spoiler' 84 62 if isPostGallery(p) 85 63 div.gallery 86 64 each item in postGalleryItems(p) 87 65 div.gallery-item 88 - div.gallery-item-idx 89 - | #{`${item.idx}/${item.total}`} 90 66 a(href=`/media/${item.url}`) 91 67 img(src=item.url loading="lazy") 68 + div.gallery-item-idx 69 + | #{`${item.idx}/${item.total}`} 92 70 else if isPostImage(p) 93 71 a(href=`/media/${p.url}`) 94 - img(src=p.url loading="lazy").post-media 72 + img(src=p.url loading="lazy") 95 73 else if isPostVideo(p) 96 - video(controls="" muted="" data-dashjs-player="" preload="metadata" playsinline="" poster=decodedVideos[4] objectfit="contain" loading="lazy").post-media 97 - //HLS 98 - source(src=decodedVideos[0]) 99 - // Dash 100 - source(src=decodedVideos[1]) 101 - // Fallback 102 - source(src=decodedVideos[2]) 74 + - var decodedVideos = decodePostVideoUrls(p) 75 + video(data-dashjs-player="" playsinline="" controls="" muted="" preload="metadata" src=decodedVideos[1] poster=decodedVideos[4]) 76 + else if isPostLink(p) 77 + a(href=p.url) 78 + | #{p.domain} ↗ 79 + if (query.view == "compact") 103 80 button(onclick=`toggleDetails('${p.id}')`) 104 81 | close
+4
src/mixins/postUtils.pug
··· 1 + - 2 + function isPostMedia(p) { 3 + return isPostImage(p) || isPostGallery(p) || isPostVideo(p); 4 + } 1 5 - 2 6 function isPostGallery(p) { 3 7 return (p.is_gallery && p.is_gallery == true);
+70 -64
src/public/styles.css
··· 11 11 --link-visited-color: #999; 12 12 --accent: var(--link-color); 13 13 --error-text-color: red; 14 + --border-radius-card: 2vmin; 15 + --border-radius-media: 1.5vmin; 16 + --border-radius-preview: 1vmin; 14 17 15 18 font-family: Inter, sans-serif; 16 19 font-feature-settings: 'ss01' 1, 'kern' 1, 'liga' 1, 'cv05' 1, 'dlig' 1, 'ss01' 1, 'ss07' 1, 'ss08' 1; ··· 45 48 overflow-x: hidden; 46 49 background-color: var(--bg-color); 47 50 color: var(--text-color); 48 - } 49 - 50 - body:has(details.card[open]) { 51 - overflow: hidden; 52 51 } 53 52 54 53 body.media-maximized { ··· 169 168 170 169 .post-container.card { 171 170 border: 1px solid var(--bg-color-muted); 172 - border-radius: 8px; 171 + border-radius: var(--border-radius-card); 173 172 display: block; 174 173 } 175 174 176 175 .post-text.card { 177 176 padding: 0.9rem; 178 - padding-top: 0.3rem; 177 + padding-top: 0.5rem; 178 + padding-bottom: 0.5rem; 179 + overflow-wrap: break-word; 180 + max-width: 95%; 179 181 } 180 182 181 183 .self-text-overflow.card { ··· 186 188 overflow: hidden; 187 189 overflow-wrap: break-word; 188 190 display: block; 191 + max-width: 98%; 189 192 } 190 193 191 194 .self-text.card { ··· 198 201 text-overflow: ellipsis; 199 202 } 200 203 201 - .media-preview.card { 204 + .image-viewer { 202 205 position: relative; 203 - padding: 0.3rem; 204 - padding-bottom: 0.3rem; 206 + margin: 0.9rem; 205 207 } 206 208 207 - .media-preview.card > img { 209 + .image-viewer > img { 208 210 cursor: pointer; 209 211 } 210 212 211 - .gallery.card { 212 - align-items: center; 213 - scroll-snap-type: both mandatory; 214 - } 215 - 216 - .gallery-item.card { 217 - max-width: 100%; 218 - width: 100%; 219 - scroll-snap-align: center; 220 - } 221 - 222 - .gallery-item-idx.card { 223 - text-align: center; 224 - } 225 - 226 213 .spoiler { 227 214 background-color: rbga(var(--bg-color-muted), 0.2); 228 215 /* Safari on iOS <= 17 */ 229 216 -webkit-backdrop-filter: blur(3rem); 230 217 backdrop-filter: blur(3rem); 231 - border-radius: 4px; 218 + border-radius: var(--border-radius-preview); 232 219 233 220 position: absolute; 234 221 top: 0; ··· 247 234 z-index: 10; 248 235 } 249 236 250 - .gallery-item-idx.card, 237 + .gallery-item-idx, 251 238 .spoiler > h2 { 252 239 text-shadow: 0.1rem 0.1rem 1rem var(--bg-color-muted); 253 240 } ··· 294 281 object-fit: cover; 295 282 width: 4rem; 296 283 height: 4rem; 284 + border-radius: var(--border-radius-preview); 297 285 } 298 286 299 - .media-preview.card { 300 - padding: unset; 301 - } 287 + .image-viewer img, 288 + .image-viewer video { 289 + border-radius: var(--border-radius-media); 302 290 303 - .media-preview.card img, 304 - .media-preview.card video { 305 - border-radius: 6px; 306 - 307 - max-height: 40vh; 291 + max-height: 50vh; 308 292 max-width: 95%; 309 293 310 294 display: block; ··· 317 301 object-fit: fill; 318 302 } 319 303 320 - .media-preview.card a { 321 - font-size: 1.5rem; 322 - padding: unset; 323 - padding-left: 1rem; 304 + .image-viewer.main-content img, 305 + .image-viewer.main-content video { 306 + max-height: 70vh; 307 + } 308 + 309 + .image-viewer.main-content a { 310 + margin: unset; 324 311 } 325 312 326 - .media-preview.card a:has(img) { 313 + .image-viewer a:has(img) { 327 314 font-size: 0rem; 328 315 padding: unset; 316 + margin: unset; 329 317 } 330 318 331 - .media-preview a { 332 - font-size: 2rem; 319 + .media-preview a, 320 + .image-viewer a { 321 + font-size: 1.5rem; 333 322 text-decoration: none; 334 - padding: 1rem; 323 + padding: unset; 324 + margin: 1rem; 335 325 } 336 326 337 327 .media-maximized { ··· 372 362 } 373 363 374 364 @media (min-width: 768px) { 365 + :root { 366 + --border-radius-card: 1vmin; 367 + --border-radius-media: 1vmin; 368 + --border-radius-preview: 0.5vmin; 369 + } 375 370 .post, .comments-container, .hero, .header, .footer { 376 371 flex: 1 1 90%; 377 372 width: 90%; ··· 382 377 width: 5rem; 383 378 height: 5rem; 384 379 } 385 - .media-preview.card img, 386 - .media-preview.card video 380 + .image-viewer img, 381 + .image-viewer video 387 382 { 388 - max-height: 50vh; 383 + max-height: 45vh; 389 384 } 390 - .media-preview.card a { 385 + .image-viewer a { 391 386 font-size: 1rem; 392 387 margin: 0.7rem; 393 388 padding: initial; 394 389 } 395 - .self-text.card { 396 - -webkit-line-clamp: 4; 397 - line-clamp: 4; 390 + .post-text.card { 391 + max-width: 100%; 392 + } 393 + .self-text-overflow.card { 394 + max-width: 100%; 398 395 } 399 396 .post-author { 400 397 display: inline ··· 414 411 } 415 412 416 413 @media (min-width: 1080px) { 414 + :root { 415 + --border-radius-card: 0.5vmin; 416 + --border-radius-media: 0.5vmin; 417 + --border-radius-preview: 0.3vmin; 418 + } 417 419 .post, .comments-container, .hero, .header, .footer { 418 420 flex: 1 1 60%; 419 421 width: 60%; ··· 424 426 width: 5rem; 425 427 height: 5rem; 426 428 } 427 - .media-preview.card img, 428 - .media-preview.card video 429 + .image-viewer img, 430 + .image-viewer video 429 431 { 430 - max-height: 30vh; 432 + max-height: 35vh; 431 433 } 432 434 .media-preview a { 433 435 font-size: 2rem; 434 436 padding: 2rem; 435 437 } 436 - .media-preview.card a { 438 + .image-viewer a { 437 439 font-size: 1rem; 438 - margin: 0.5rem; 440 + margin: 1rem; 439 441 padding: initial; 440 442 } 441 443 .self-text.card { 442 - -webkit-line-clamp: 6; 443 - line-clamp: 6; 444 + -webkit-line-clamp: 4; 445 + line-clamp: 4; 444 446 } 445 447 .post-author { 446 448 display: inline ··· 464 466 flex: 1 1 40%; 465 467 width: 40%; 466 468 } 467 - .media-preview.card img, 468 - .media-preview.card video 469 + .image-viewer img, 470 + .image-viewer video 469 471 { 470 - max-height: 20vh; 472 + max-height: 30vh; 471 473 } 472 474 .sort-opts, 473 475 .view-opts { ··· 494 496 margin-top: 10px; 495 497 } 496 498 497 - .post-container { 499 + .post-info { 498 500 display: flex; 499 501 flex-direction: row; 500 502 align-items: center; ··· 648 650 overflow-x: auto; 649 651 position: relative; 650 652 padding: 5px; 653 + align-items: center; 654 + scroll-snap-type: both mandatory; 651 655 } 652 656 653 657 .gallery-item { 654 658 flex: 0 0 auto; 655 659 margin-right: 10px; 660 + max-width: 100%; 661 + width: 100%; 662 + scroll-snap-align: center; 656 663 } 657 664 658 - .gallery img { 659 - width: auto; 660 - max-height: 500px; 665 + .gallery-item-idx { 666 + text-align: center; 661 667 } 662 668 663 669 .post-title {
+18 -17
src/views/comments.pug
··· 47 47 h2.post-title 48 48 != post.title 49 49 50 - if isPostGallery(post) 51 - div.gallery 52 - each item in postGalleryItems(post) 53 - div.gallery-item 54 - div.gallery-item-idx 55 - | #{`${item.idx}/${item.total}`} 56 - a(href=`/media/${item.url}`) 57 - img(src=item.url loading="lazy") 58 - else if isPostImage(post) 59 - a(href=`/media/${post.url}`) 60 - img(src=post.url).post-media 61 - else if isPostVideo(post) 62 - - var url = post.secure_media.reddit_video.dash_url 63 - video(controls data-dashjs-player src=`${url}`).post-media 64 - else if isPostLink(post) 65 - a(href=post.url) 66 - | #{post.url} 50 + div.image-viewer.main-content 51 + if isPostGallery(post) 52 + div.gallery 53 + each item in postGalleryItems(post) 54 + div.gallery-item 55 + a(href=`/media/${item.url}`) 56 + img(src=item.url loading="lazy") 57 + div.gallery-item-idx 58 + | #{`${item.idx}/${item.total}`} 59 + else if isPostImage(post) 60 + a(href=`/media/${post.url}`) 61 + img(src=post.url).post-media 62 + else if isPostVideo(post) 63 + - var url = post.secure_media.reddit_video.dash_url 64 + video(controls data-dashjs-player src=`${url}`).post-media 65 + else if isPostLink(post) 66 + a(href=post.url) 67 + | #{post.domain} ↗ 67 68 68 69 if post.selftext_html 69 70 div.self-text
+7 -2
src/views/media.pug
··· 3 3 html 4 4 +head("home") 5 5 script(type='text/javascript'). 6 - function toggleZoom() { 6 + function toggleZoom(event) { 7 + const percentX = event.offsetX / event.target.width; 8 + const percentY = event.offsetY / event.target.height; 7 9 Array.from(document.getElementsByClassName('media-maximized')).forEach(element => element.classList.toggle('zoom')); 10 + const moveClientX = (event.target.width * percentX) + event.target.offsetLeft - (event.view.visualViewport.width / 2) 11 + const moveClientY = (event.target.height * percentY) + event.target.offsetTop - (event.view.visualViewport.height / 2); 12 + event.target.parentElement.scrollTo(moveClientX, moveClientY); 8 13 } 9 14 10 15 body.media-maximized 11 16 div.media-maximized.container 12 17 if kind == 'img' 13 - img(src=url onclick=`toggleZoom()`).media-maximized 18 + img(src=url onclick=`toggleZoom(event)`).media-maximized 14 19 else 15 20 video(src=url controls).media-maximized