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