selfhostable, read-only reddit client

Feat: Card View Initial Commit

+7 -5
src/mixins/header.pug
··· 1 1 mixin header(user) 2 + - var viewQuery = 'view=' + (query && query.view ? query.view : 'compact') 3 + - var sortQuery = 'sort=' + (query ? (query.sort ? query.sort + (query.t ? '&t=' + query.t : '') : 'hot') : 'hot') 2 4 div.header 3 5 div.header-item 4 - a(href=`/`) home 6 + a(href=`/?${sortQuery}&${viewQuery}`) home 5 7 div.header-item 6 - a(href=`/r/all`) all 8 + a(href=`/r/all?${sortQuery}&${viewQuery}`) all 7 9 div.header-item 8 - a(href=`/search`) search 10 + a(href=`/search?${sortQuery}&${viewQuery}`) search 9 11 div.header-item 10 - a(href=`/subs`) subs 12 + a(href=`/subs?${sortQuery}&${viewQuery}`) subs 11 13 if user 12 14 div.header-item 13 - a(href='/dashboard') #{user.username} 15 + a(href=`/dashboard?${sortQuery}&${viewQuery}`) #{user.username} 14 16 |  15 17 a(href='/logout') (logout) 16 18 else
+65 -40
src/mixins/post.pug
··· 2 2 include postUtils 3 3 mixin post(p, currentUrl) 4 4 - var from = encodeURIComponent(currentUrl) 5 + - var viewQuery = query && query.view ? query.view : 'compact' 6 + - var sortQuery = query && query.sort ? query.sort + (query.t ? '&t=' + query.t : '') : 'hot' 5 7 article.post 6 - div.post-container 7 - div.post-text 8 - div.title-container 9 - a(href=`/comments/${p.id}?from=${from}`) 8 + div.post-container(class=`${query.view}`) 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}`) 10 12 != p.title 11 13 span.domain (#{p.domain}) 12 14 div.info-container ··· 18 20 |  ·  19 21 | #{timeDifference(Date.now(), p.created * 1000)} 20 22 |  ·  21 - a(href=`/r/${p.subreddit}`) r/#{p.subreddit} 23 + a(href=`/r/${p.subreddit}?sort=${sortQuery}&view=${viewQuery}`) r/#{p.subreddit} 22 24 |  ·  23 - a(href=`/comments/${p.id}?from=${from}`) #{fmtnum (p.num_comments)} ↩ 24 - div.media-preview 25 + a(href=`/comments/${p.id}?from=${from}&sort=${sortQuery}&view=${viewQuery}`) #{fmtnum (p.num_comments)} ↩ 26 + if (query.view == "card" && !isPostGallery(p) && !isPostImage(p) && !isPostVideo(p) && p.selftext_html) 27 + div.self-text-overflow(class='card') 28 + if query.view == "card" && (p.spoiler || p.over_18) 29 + div.spoiler(id=`spoiler_${p.id}`, onclick=`javascript:document.getElementById('spoiler_${p.id}').style.display = 'none';`) 30 + h2 31 + != p.over_18 ? 'nsfw' : 'spoiler' 32 + div.self-text(class='card') 33 + != convertInlineImageLinks(p.selftext_html) 34 + div.media-preview(class=`${query.view}`) 35 + if query.view == "card" && (p.spoiler || p.over_18) && (isPostGallery(p) || isPostImage(p) || isPostVideo(p)) 36 + div.spoiler(id=`spoiler_${p.id}`, onclick=`javascript:document.getElementById('spoiler_${p.id}').style.display = 'none';`) 37 + h2 38 + != p.over_18 ? 'nsfw' : 'spoiler' 25 39 if isPostGallery(p) 26 40 - var item = postGalleryItems(p)[0] 27 41 img(src=item.url onclick=`toggleDetails('${p.id}')`) 28 42 else if isPostImage(p) 29 - - var url = postThumbnail(p) 43 + - var url = query.view == "card" ? p.url : postThumbnail(p) 30 44 img(src=url onclick=`toggleDetails('${p.id}')`) 31 45 else if isPostVideo(p) 32 - - var url = p.secure_media.reddit_video.scrubber_media_url 33 - video(src=url data-dashjs-player width='100px' height='100px' onclick=`toggleDetails('${p.id}')`) 46 + - var decodedVideos = decodePostVideoUrls(p) 47 + if query.view == "card" 48 + video(controls="" muted="" data-dashjs-player="" preload="metadata" poster=decodedVideos[4]) 49 + // HLS 50 + source(src=decodedVideos[0]) 51 + // Dash 52 + source(src=decodedVideos[1]) 53 + // Fallback 54 + source(src=decodedVideos[2]) 55 + else 56 + video(autoplay="" muted="" data-dashjs-player="" onclick=`toggleDetails('${p.id}')` width="100px" height="100px") 57 + // Scrubber 58 + source(src=decodedVideos[3]) 34 59 else if isPostLink(p) 35 60 a(href=p.url) 61 + if (query.view == 'card') 62 + | #{p.domain} 36 63 | ↗ 37 64 38 - if isPostGallery(p) 39 - details(id=`${p.id}`) 40 - summary.expand-post expand gallery 41 - div.gallery 42 - each item in postGalleryItems(p) 43 - div.gallery-item 44 - div.gallery-item-idx 45 - | #{`${item.idx}/${item.total}`} 46 - a(href=`/media/${item.url}`) 47 - img(src=item.url loading="lazy") 48 - button(onclick=`toggleDetails('${p.id}')`) close 49 - else if isPostImage(p) 50 - details(id=`${p.id}`) 51 - summary.expand-post expand image 52 - a(href=`/media/${p.url}`) 53 - img(src=p.url loading="lazy").post-media 54 - button(onclick=`toggleDetails('${p.id}')`) close 55 - else if isPostVideo(p) 56 - details(id=`${p.id}`) 57 - summary.expand-post expand video 58 - - var url = p.secure_media.reddit_video.dash_url 59 - video(src=url controls data-dashjs-player loading="lazy").post-media 60 - button(onclick=`toggleDetails('${p.id}')`) close 61 - else if isPostLink(p) 62 - details(id=`${p.id}`) 63 - summary.expand-post expand link 64 - a(href=`${p.url}`) 65 - | #{p.url} 66 - br 67 - button(onclick=`toggleDetails('${p.id}')`) close 65 + if (isPostGallery(p) || isPostImage(p) || isPostVideo(p)) 66 + details(id=`${p.id}`,class=`${query.view}`) 67 + summary.expand-post expand media 68 + div.image-viewer(class=`${query.view}`) 69 + if isPostGallery(p) 70 + div.gallery(class=`${query.view}`) 71 + each item in postGalleryItems(p) 72 + div.gallery-item(class=`${query.view}`) 73 + div.gallery-item-idx(class=`${query.view}`) 74 + | #{`${item.idx}/${item.total}`} 75 + a(href=`/media/${item.url}`) 76 + img(src=item.url loading="lazy") 77 + else if isPostImage(p) 78 + a(href=`/media/${p.url}`) 79 + img(src=p.url loading="lazy").post-media 80 + else if isPostVideo(p) 81 + video(controls="" muted="" data-dashjs-player="" preload="metadata" playsinline="" poster=decodedVideos[4] objectfit="contain" loading="lazy").post-media 82 + //HLS 83 + source(src=decodedVideos[0]) 84 + // Dash 85 + source(src=decodedVideos[1]) 86 + // Fallback 87 + source(src=decodedVideos[2]) 88 + button(onclick=`toggleDetails('${p.id}')`,class=`${query.view}`) 89 + if (query.view == 'card') 90 + | ╳ 91 + else 92 + | close
+36 -6
src/mixins/postUtils.pug
··· 10 10 } 11 11 - 12 12 function postThumbnail(p) { 13 - if (p.thumbnail == "image" || p.thumbnail == "") { 14 - return p.url; 15 - } else if (p.over_18) { 16 - return "/nsfw.svg"; 13 + if (p.over_18) { 14 + return "/nsfw.svg"; 17 15 } else if (p.thumbnail == "spoiler") { 18 - return "/spoiler.svg"; 16 + return "/spoiler.svg"; 17 + } else if (p.thumbnail == "image" || p.thumbnail == "") { 18 + return p.url; 19 19 } else { 20 - return p.thumbnail; 20 + return p.thumbnail; 21 21 } 22 22 } 23 23 - ··· 51 51 return null; 52 52 } 53 53 } 54 + - 55 + function convertInlineImageLinks(html) { 56 + // Find all anchors that href to https://preview.redd.it 57 + const expression = /<a href="https:\/\/preview\.redd\.it.*?">(.*?)<\/a>/g; 58 + const matches = html.matchAll(expression); 59 + var result = html; 60 + matches.forEach((match) => { 61 + // Replace each occurrence with an actual img tag 62 + result = result.replace(match[0], '<a href="' + match[1] + '"><img src="' + match[1] + '"></a>'); 63 + }) 64 + 65 + return result; 66 + } 67 + - 68 + function decodePostVideoUrls(p) { 69 + // Video URLs have querystring separators that are HTML-encoded, so replace them. 70 + const expression = /&amp;/g; 71 + 72 + var hls_url = p.secure_media && p.secure_media.reddit_video && p.secure_media.reddit_video.hls_url ? p.secure_media.reddit_video.hls_url.replace(expression, '&') : ''; 73 + 74 + var dash_url = p.secure_media && p.secure_media.reddit_video && p.secure_media.reddit_video.dash_url ? p.secure_media.reddit_video.dash_url.replace(expression, '&') : ''; 75 + 76 + var fallback_url = p.secure_media && p.secure_media.reddit_video && p.secure_media.reddit_video.fallback_url ? p.secure_media.reddit_video.fallback_url.replace(expression, '&') : ''; 77 + 78 + var scrubber_url = p.secure_media && p.secure_media.reddit_video && p.secure_media.reddit_video.scrubber_media_url ? p.secure_media.reddit_video.scrubber_media_url.replace(expression, '&') : ''; 79 + 80 + var poster_url = p.preview && p.preview.images ? p.preview.images[0].source.url.replace(expression, '&') : ''; 81 + 82 + return [hls_url, dash_url, fallback_url, scrubber_url, poster_url]; 83 + }
+179
src/public/styles.css
··· 43 43 color: var(--text-color); 44 44 } 45 45 46 + body:has(details.card[open]) { 47 + overflow: hidden; 48 + } 49 + 46 50 main { 47 51 display: flex; 48 52 flex-direction: column; ··· 127 131 font-size: 0.9rem; 128 132 } 129 133 134 + .post-container.card { 135 + border: 1px solid var(--bg-color-muted); 136 + border-radius: 16px; 137 + display: block; 138 + } 139 + 140 + .post-text.card { 141 + padding: 0.9rem; 142 + padding-top: 0.3rem; 143 + } 144 + 145 + .self-text-overflow.card { 146 + /* For spoiler positioning */ 147 + position: relative; 148 + padding-top: 0.3rem; 149 + max-height: 10vh; 150 + overflow: hidden; 151 + overflow-wrap: break-word; 152 + display: block; 153 + } 154 + 155 + .self-text.card { 156 + overflow: hidden; 157 + display: -webkit-box; 158 + /* Safari on iOS <= 17 */ 159 + -webkit-box-orient: vertical; 160 + -webkit-line-clamp: 3; 161 + line-clamp: 3; 162 + text-overflow: ellipsis; 163 + } 164 + 165 + .media-preview.card { 166 + position: relative; 167 + padding: 0.3rem; 168 + padding-bottom: 0.3rem; 169 + } 170 + 171 + .media-preview.card > img { 172 + cursor: pointer; 173 + } 174 + 175 + .image-viewer.card { 176 + /* Safari on iOS <= 17 */ 177 + -webkit-backdrop-filter: blur(2rem); 178 + backdrop-filter: blur(2rem); 179 + position: fixed; 180 + inset: 0; 181 + box-sizing: border-box; 182 + display: flex; 183 + height: 100%; 184 + width: 100%; 185 + justify-content: center; 186 + align-items: center; 187 + z-index: 100; 188 + } 189 + 190 + .image-viewer.card > button { 191 + position: absolute; 192 + top: 0; 193 + right: 0; 194 + margin: 1rem; 195 + padding: initial; 196 + height: 3rem; 197 + width: 3rem; 198 + font-size: 2rem; 199 + border-radius: 100%; 200 + cursor: pointer; 201 + } 202 + 203 + .image-viewer.card > a > img { 204 + max-width: 100vw; 205 + max-height: 100vh; 206 + width: auto; 207 + height: auto; 208 + } 209 + 210 + .gallery.card { 211 + align-items: center; 212 + } 213 + 214 + .gallery-item.card > a > img { 215 + max-width: 95vw; 216 + max-height: 95vh; 217 + width: auto; 218 + height: auto; 219 + } 220 + 221 + .spoiler { 222 + background-color: rbga(var(--bg-color-muted), 0.2); 223 + /* Safari on iOS <= 17 */ 224 + -webkit-backdrop-filter: blur(3rem); 225 + backdrop-filter: blur(3rem); 226 + border-radius: 4px; 227 + 228 + position: absolute; 229 + top: 0; 230 + left: 0; 231 + 232 + box-sizing: border-box; 233 + display: flex; 234 + height: 100%; 235 + width: 100%; 236 + 237 + justify-content: center; 238 + align-items: center; 239 + 240 + cursor: pointer; 241 + } 242 + 243 + .gallery-item-idx.card, 244 + .spoiler > h2 { 245 + text-shadow: 0.1rem 0.1rem 1rem var(--bg-color-muted); 246 + } 247 + 130 248 .comments-container { 131 249 font-size: 0.9rem; 132 250 } ··· 171 289 height: 4rem; 172 290 } 173 291 292 + .media-preview.card img, 293 + .media-preview.card video { 294 + border-radius: 16px; 295 + 296 + max-height: 40vh; 297 + max-width: 100%; 298 + 299 + display: block; 300 + width: initial; 301 + height: initial; 302 + margin-left: auto; 303 + margin-right: auto; 304 + margin-bottom: 1rem; 305 + 306 + object-fit: fill; 307 + } 308 + 309 + .media-preview.card a { 310 + font-size: 1.5rem; 311 + margin: 1rem; 312 + padding: initial; 313 + } 314 + 174 315 .media-preview a { 175 316 font-size: 2rem; 176 317 text-decoration: none; ··· 225 366 width: 5rem; 226 367 height: 5rem; 227 368 } 369 + .media-preview.card img, 370 + .media-preview.card video 371 + { 372 + max-height: 50vh; 373 + } 374 + .media-preview.card a { 375 + font-size: 1rem; 376 + margin: 0.7rem; 377 + padding: initial; 378 + } 379 + .self-text.card { 380 + -webkit-line-clamp: 4; 381 + line-clamp: 4; 382 + } 228 383 .post-author { 229 384 display: inline 230 385 } ··· 252 407 width: 5rem; 253 408 height: 5rem; 254 409 } 410 + .media-preview.card img, 411 + .media-preview.card video 412 + { 413 + max-height: 30vh; 414 + } 255 415 .media-preview a { 256 416 font-size: 2rem; 257 417 padding: 2rem; 418 + } 419 + .media-preview.card a { 420 + font-size: 1rem; 421 + margin: 0.5rem; 422 + padding: initial; 423 + } 424 + .self-text.card { 425 + -webkit-line-clamp: 6; 426 + line-clamp: 6; 258 427 } 259 428 .post-author { 260 429 display: inline ··· 276 445 .post, .comments-container, .hero, .header, .footer { 277 446 flex: 1 1 40%; 278 447 width: 40%; 448 + } 449 + .media-preview.card img, 450 + .media-preview.card video 451 + { 452 + max-height: 20vh; 279 453 } 280 454 .sort-opts { 281 455 grid-template-columns: repeat(9, 1fr); ··· 343 517 .title-container > a { 344 518 color: var(--text-color); 345 519 text-decoration: none; 520 + } 521 + 522 + .title-container.card > a { 523 + font-size: 1.125rem; 524 + font-weight: bold; 346 525 } 347 526 348 527 .title-container > a:hover {
+35 -8
src/routes/index.js
··· 16 16 const subs = db 17 17 .query("SELECT * FROM subscriptions WHERE user_id = $id") 18 18 .all({ id: req.user.id }); 19 + 20 + const qs = req.query ? ('?' + new URLSearchParams(req.query).toString()) : ''; 21 + 19 22 if (subs.length === 0) { 20 - res.redirect("/r/all"); 23 + res.redirect(`/r/all${qs}`); 21 24 } else { 22 25 const p = subs.map((s) => s.subreddit).join("+"); 23 - res.redirect(`/r/${p}`); 26 + res.redirect(`/r/${p}${qs}`); 24 27 } 25 28 }); 26 29 ··· 31 34 const query = req.query ? req.query : {}; 32 35 if (!query.sort) { 33 36 query.sort = "hot"; 37 + } 38 + if (!query.view) { 39 + query.view = "compact"; 34 40 } 35 41 36 42 let isSubbed = false; ··· 47 53 48 54 const [posts, about] = await Promise.all([postsReq, aboutReq]); 49 55 56 + if (query.view == 'card' && posts && posts.posts) { 57 + posts.posts.forEach(unescape_selftext); 58 + } 59 + 50 60 res.render("index", { 51 61 subreddit, 52 62 posts, ··· 71 81 data: unescape_submission(response), 72 82 user: req.user, 73 83 from: req.query.from, 84 + query: req.query, 74 85 }); 75 86 }); 76 87 ··· 104 115 ) 105 116 .all({ id: req.user.id }); 106 117 107 - res.render("subs", { subs, user: req.user }); 118 + res.render("subs", { subs, user: req.user, query: req.query }); 108 119 }); 109 120 110 121 // GET /search 111 122 router.get("/search", authenticateToken, async (req, res) => { 112 - res.render("search", { user: req.user }); 123 + res.render("search", { user: req.user, query: req.query }); 113 124 }); 114 125 115 126 // GET /sub-search ··· 133 144 message, 134 145 user: req.user, 135 146 original_query: req.query.q, 147 + query: req.query, 136 148 }); 137 149 } 138 150 }); ··· 147 159 items.length === 0 148 160 ? "no results found" 149 161 : `showing ${items.length} results`; 162 + 163 + if (req.query.view == 'card' && items) { 164 + items.forEach(unescape_selftext); 165 + } 166 + 150 167 res.render("post-search", { 151 168 items, 152 169 after, ··· 154 171 user: req.user, 155 172 original_query: req.query.q, 156 173 currentUrl: req.url, 174 + query: req.query, 157 175 }); 158 176 } 159 177 }); ··· 176 194 usedAt: Date.parse(inv.usedAt), 177 195 })); 178 196 } 179 - res.render("dashboard", { invites, isAdmin, user: req.user }); 197 + res.render("dashboard", { invites, isAdmin, user: req.user, query: req.query }); 180 198 }); 181 199 182 200 router.get("/create-invite", authenticateAdmin, async (req, res) => { ··· 359 377 const post = response.submission.data; 360 378 const comments = response.comments; 361 379 380 + unescape_selftext(post); 381 + comments.forEach(unescape_comment); 382 + 383 + return { post, comments }; 384 + } 385 + 386 + function unescape_selftext(post) { 387 + // If called after getSubmissions 388 + if (post.data && post.data.selftext_html) { 389 + post.data.selftext_html = he.decode(post.data.selftext_html); 390 + } 391 + // If called after getSubmissionComments 362 392 if (post.selftext_html) { 363 393 post.selftext_html = he.decode(post.selftext_html); 364 394 } 365 - comments.forEach(unescape_comment); 366 - 367 - return { post, comments }; 368 395 } 369 396 370 397 function unescape_comment(comment) {
+4 -2
src/views/comments.pug
··· 6 6 7 7 - var post = data.post 8 8 - var comments = data.comments 9 + - var viewQuery = 'view=' + (query && query.view ? query.view : 'compact') 10 + - var sortQuery = 'sort=' + (query && query.sort ? query.sort + (query.t ? '&t=' + query.t : '') : 'hot') 9 11 doctype html 10 12 html 11 13 +head(post.title) ··· 27 29 | &nbsp;&nbsp; 28 30 | · 29 31 | &nbsp;&nbsp; 30 - a(href=`/r/${post.subreddit}`) r/#{post.subreddit} 32 + a(href=`/r/${post.subreddit}?${sortQuery}&${viewQuery}`) r/#{post.subreddit} 31 33 32 34 div.info-container 33 35 - var domain = (new URL(post.url)).hostname ··· 65 67 66 68 if post.selftext_html 67 69 div.self-text 68 - != post.selftext_html 70 + != convertInlineImageLinks(post.selftext_html) 69 71 70 72 hr 71 73
+20 -13
src/views/index.pug
··· 2 2 include ../mixins/header 3 3 include ../mixins/head 4 4 include ../utils 5 + - var viewQuery = query && query.view ? query.view : 'compact' 6 + - var sortQuery = query && query.sort ? query.sort + (query.t ? '&t=' + query.t : '') : 'hot' 5 7 doctype html 6 8 html 7 9 +head("home") ··· 14 16 div.sub-title 15 17 h1 16 18 if isMulti 17 - a(href=`/`) lurker 19 + a(href=`/?${sortQuery}&${viewQuery}`) lurker 18 20 else 19 - a(href=`/r/${subreddit}`) 21 + a(href=`/r/${subreddit}?${sortQuery}&${viewQuery}`) 20 22 | r/#{subreddit} 21 23 if !isMulti 22 24 div#button-container ··· 32 34 a(href="https://donate.stripe.com/dR62bTaZH1295Da4gg") oppiliappan 33 35 |, author of lurker 34 36 hr 35 - details 36 - summary.sorting sorting by #{query.sort + (query.t?' '+query.t:'')} 37 + details.sort-details 38 + summary.sorting sorting by #{query.sort + (query.t?' '+query.t:'')}, #{viewQuery} view 37 39 div.sort-opts 38 40 div 39 - a(href=`/r/${subreddit}?sort=hot`) hot 41 + a(href=`/r/${subreddit}?sort=hot&view=${viewQuery}`) hot 40 42 div 41 - a(href=`/r/${subreddit}?sort=new`) new 43 + a(href=`/r/${subreddit}?sort=new&view=${viewQuery}`) new 44 + div 45 + a(href=`/r/${subreddit}?sort=rising&view=${viewQuery}`) rising 46 + div 47 + a(href=`/r/${subreddit}?sort=top&view=${viewQuery}`) top 42 48 div 43 - a(href=`/r/${subreddit}?sort=rising`) rising 49 + a(href=`/r/${subreddit}?sort=top&t=day&view=${viewQuery}`) top day 44 50 div 45 - a(href=`/r/${subreddit}?sort=top`) top 51 + a(href=`/r/${subreddit}?sort=top&t=week&view=${viewQuery}`) top week 46 52 div 47 - a(href=`/r/${subreddit}?sort=top&t=day`) top day 53 + a(href=`/r/${subreddit}?sort=top&t=month&view=${viewQuery}`) top month 48 54 div 49 - a(href=`/r/${subreddit}?sort=top&t=week`) top week 55 + a(href=`/r/${subreddit}?sort=top&t=year&view=${viewQuery}`) top year 50 56 div 51 - a(href=`/r/${subreddit}?sort=top&t=month`) top month 57 + a(href=`/r/${subreddit}?sort=top&t=all&view=${viewQuery}`) top all 58 + div.sort-opts 52 59 div 53 - a(href=`/r/${subreddit}?sort=top&t=year`) top year 60 + a(href=`/r/${subreddit}?sort=${sortQuery}&view=compact`) compact 54 61 div 55 - a(href=`/r/${subreddit}?sort=top&t=all`) top all 62 + a(href=`/r/${subreddit}?sort=${sortQuery}&view=card`) card 56 63 57 64 if posts 58 65 each child in posts.posts
+4
src/views/post-search.pug
··· 2 2 include ../mixins/header 3 3 include ../mixins/head 4 4 5 + - var viewQuery = query && query.view ? query.view : 'compact' 6 + - var sortQuery = query && query.sort ? query.sort + (query.t ? '&t=' + query.t : '') : 'hot' 5 7 doctype html 6 8 html 7 9 +head("search posts") ··· 14 16 form(action="/post-search" method="get").search-bar 15 17 - var prefill = original_query ?? ""; 16 18 input(type="text" name="q" placeholder="type in a search term..." value=prefill required).search-input 19 + input(type="hidden" name="sort" value=sortQuery) 20 + input(type="hidden" name="view" value=viewQuery) 17 21 button(type="submit").search-button go 18 22 if message 19 23 div.search-message
+6
src/views/search.pug
··· 1 1 include ../mixins/header 2 2 include ../mixins/head 3 3 4 + - var viewQuery = query && query.view ? query.view : 'compact' 5 + - var sortQuery = query && query.sort ? query.sort + (query.t ? '&t=' + query.t : '') : 'hot' 4 6 doctype html 5 7 html 6 8 +head("search subreddits") ··· 14 16 form(action="/sub-search" method="get").search-bar 15 17 - var prefill = original_query ?? ""; 16 18 input(type="text" name="q" placeholder="type in a search term..." value=prefill required).search-input 19 + input(type="hidden" name="sort" value=sortQuery) 20 + input(type="hidden" name="view" value=viewQuery) 17 21 button(type="submit").search-button go 18 22 19 23 hr ··· 23 27 form(action="/post-search" method="get").search-bar 24 28 - var prefill = original_query ?? ""; 25 29 input(type="text" name="q" placeholder="type in a search term..." value=prefill required).search-input 30 + input(type="hidden" name="sort" value=sortQuery) 31 + input(type="hidden" name="view" value=viewQuery) 26 32 button(type="submit").search-button go 27 33 p 28 34 | you can narrow search results using filters:
+5 -1
src/views/sub-search.pug
··· 1 1 include ../mixins/header 2 2 include ../mixins/head 3 3 4 + - var viewQuery = (query && query.view) ? query.view : 'compact' 5 + - var sortQuery = (query && query.sort) ? query.sort + (query.t ? '&t=' + query.t : '') : 'hot' 4 6 doctype html 5 7 html 6 8 +head("search subreddits") ··· 13 15 form(action="/sub-search" method="get").search-bar 14 16 - var prefill = original_query ?? ""; 15 17 input(type="text" name="q" placeholder="type in a search term..." value=prefill required).search-input 18 + input(type="hidden" name="sort" value=sortQuery) 19 + input(type="hidden" name="view" value=viewQuery) 16 20 button(type="submit").search-button go 17 21 if message 18 22 div.search-message ··· 25 29 - var isSubbed = subs.includes(subreddit) 26 30 div.sub-title 27 31 h3 28 - a(href=`/r/${subreddit}`) 32 + a(href=`/r/${subreddit}?sort=${sortQuery}&view=${viewQuery}`) 29 33 | r/#{subreddit} 30 34 div#button-container 31 35 if isSubbed
+3 -1
src/views/subs.pug
··· 1 1 include ../mixins/header 2 2 include ../mixins/head 3 3 4 + - var viewQuery = query && query.view ? query.view : 'compact' 5 + - var sortQuery = query && query.sort ? query.sort + (query.t ? '&t=' + query.t : '') : 'hot' 4 6 doctype html 5 7 html 6 8 +head("subscriptions") ··· 16 18 - var isSubbed = true 17 19 div.sub-title 18 20 h4 19 - a(href=`/r/${subreddit}`) 21 + a(href=`/r/${subreddit}?sort=${sortQuery}&view=${viewQuery}`) 20 22 | r/#{subreddit} 21 23 div#button-container 22 24 if isSubbed