+10
readme.txt
+10
readme.txt
···
1
1
nix build .#readit
2
+
3
+
todo:
4
+
- [ ] support crossposts
5
+
- [ ] fix gallery thumbnails
6
+
- [x] pass query params into templates, add into pagination
7
+
- [ ] subscription manager: reorder, mass add
8
+
- [ ] styles for info-containers
9
+
- [ ] open in reddit/reply in reddit link
10
+
- [ ] placeholder for unresolvable thumbnails
11
+
- [ ] expand/collapse comments
+33
-16
src/mixins/comment.pug
+33
-16
src/mixins/comment.pug
···
1
1
include ../utils
2
+
3
+
mixin infoContainer(data)
4
+
div.comment-info-container
5
+
div.info-item #{fmtnum(data.ups)} ↑
6
+
div.info-item u/#{data.author} #{data.is_submitter ? '(OP)' : ''}
7
+
if data.collapsed_reason_code == "DELETED"
8
+
div.info-item
9
+
a(href=`https://undelete.pullpush.io${data.permalink}`) search on undelete
10
+
div.info-item #{timeDifference(Date.now(), data.created * 1000)}
11
+
12
+
-
13
+
function hasReplies(data) {
14
+
return data.replies && data.replies.data && data.replies.data.children && data.replies.data.children.length > 0;
15
+
}
16
+
2
17
mixin comment(com, isfirst)
3
18
- var data = com.data
4
19
- var kind = com.kind
20
+
- var hasReplyData = hasReplies(data)
21
+
5
22
if kind == "more"
6
-
div(class=`${isfirst?'first':''}`)
23
+
div(class=`${isfirst ? 'first' : ''}`)
7
24
div.more #{data.count} more comments
8
25
else
9
-
div(class=`comment ${isfirst?'first':''}`)
10
-
div.comment-info-container
11
-
div.info-item #{fmtnum(data.ups)} ↑
12
-
div.info-item u/#{data.author} #{data.is_submitter?'(OP)':''}
13
-
if data.collapsed_reason_code == "DELETED"
14
-
div.info-item
15
-
a(href=`https://undelete.pullpush.io${data.permalink}`) search on undelete
16
-
div.info-item #{timeDifference(Date.now(), data.created * 1000)}
17
-
div.comment-body
18
-
!= data.body_html
19
-
div.replies
20
-
if data.replies
21
-
if data.replies.data
22
-
if data.replies.data.children
26
+
div(class=`comment ${isfirst ? 'first' : ''}`)
27
+
if hasReplyData
28
+
details(id=`${data.id}` open="")
29
+
summary.expand-comments
30
+
+infoContainer(data)
31
+
div.comment-body
32
+
!= data.body_html
33
+
34
+
div.replies
23
35
each reply in data.replies.data.children
24
-
+comment(reply,false)
36
+
+comment(reply, false)
37
+
38
+
else
39
+
+infoContainer(data)
40
+
div.comment-body
41
+
!= data.body_html
+4
-4
src/mixins/post.pug
+4
-4
src/mixins/post.pug
···
40
40
if p.gallery_data
41
41
if p.gallery_data.items
42
42
details(id=`${p.id}`)
43
-
summary expand gallery
43
+
summary.expand-post expand gallery
44
44
div.gallery
45
45
- var total = p.gallery_data.items.length
46
46
- var idx = 0
···
58
58
button(onclick=`toggleDetails('${p.id}')`) close
59
59
if p.post_hint == "image" && p.thumbnail && p.thumbnail != "self" && p.thumbnail != "default"
60
60
details(id=`${p.id}`)
61
-
summary expand image
61
+
summary.expand-post expand image
62
62
a(href=`/media/${p.url}`)
63
63
img(src=p.url loading="lazy").post-media
64
64
button(onclick=`toggleDetails('${p.id}')`) close
65
65
else if p.post_hint == "hosted:video"
66
66
details(id=`${p.id}`)
67
-
summary expand video
67
+
summary.expand-post expand video
68
68
- var url = p.secure_media.reddit_video.dash_url
69
69
video(src=url controls data-dashjs-player loading="lazy").post-media
70
70
button(onclick=`toggleDetails('${p.id}')`) close
71
71
else if !p.post_hint || (p.post_hint == "link" && p.thumbnail && p.thumbnail != "self" && p.thumbnail != "default")
72
72
details(id=`${p.id}`)
73
-
summary expand link
73
+
summary.expand-post expand link
74
74
a(href=`${p.url}`)
75
75
| #{p.url}
76
76
br
+70
-15
src/public/styles.css
+70
-15
src/public/styles.css
···
1
-
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
1
+
@import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap');
2
2
3
3
:root {
4
4
/* Light mode colors */
···
17
17
--bg-color-muted: #333;
18
18
--text-color: white;
19
19
--text-color-muted: #999;
20
-
--blockquote-color: green;
20
+
--blockquote-color: lightgreen;
21
21
--link-color: lightblue;
22
22
--link-visited-color: violet;
23
23
}
···
58
58
justify-content: center;
59
59
}
60
60
61
+
.sub-title {
62
+
display: flex;
63
+
}
64
+
65
+
#button-container {
66
+
margin-left: 10px;
67
+
display: flex;
68
+
align-items: center;
69
+
}
70
+
71
+
.sort-opts {
72
+
display: flex;
73
+
flex-direction: row;
74
+
flex-wrap: wrap;
75
+
justify-content: space-between;
76
+
}
77
+
78
+
.sort-opts a {
79
+
margin: 10px;
80
+
}
81
+
61
82
.footer {
62
83
display: flex;
63
84
flex-direction: row;
···
90
111
.info-container,
91
112
.comment-info-container,
92
113
.more,
114
+
summary.expand-comments::before,
93
115
hr {
94
116
color: var(--text-color-muted)
95
117
}
96
118
97
-
.info-container, .comment-info-container, .more {
119
+
.info-container, .more {
98
120
font-size: 0.8rem;
99
121
display: flex;
100
122
}
101
123
124
+
.comment-info-container {
125
+
display: inline-flex;
126
+
align-items: center;
127
+
font-size: 0.8rem;
128
+
}
129
+
102
130
.domain {
103
131
color: var(--text-color-muted);
104
132
font-size: 0.8rem;
···
124
152
display: none
125
153
}
126
154
155
+
.post-media {
156
+
display: block;
157
+
margin: 0 auto;
158
+
max-width: 95%;
159
+
padding: 5px;
160
+
}
161
+
127
162
@media (min-width: 768px) {
128
163
.post, .comments-container, .hero, .header, .footer {
129
164
flex: 1 1 90%;
···
135
170
.comments-container {
136
171
font-size: 1.3rem;
137
172
}
138
-
.info-container, .comment-info-container, .more {
173
+
.info-container, .comment-info-container, .more, summary.expand-comments::before
174
+
{
139
175
font-size: 1rem;
140
176
}
141
177
.domain {
···
150
186
.post-author {
151
187
display: inline
152
188
}
189
+
.post-media {
190
+
max-width: 50%;
191
+
}
153
192
}
154
193
155
194
@media (min-width: 1080px) {
···
163
202
.comments-container {
164
203
font-size: 1.3rem;
165
204
}
166
-
.info-container, .comment-info-container, .more {
205
+
.info-container, .comment-info-container, .more, summary.expand-comments::before {
167
206
font-size: 1rem;
168
207
}
169
208
.domain {
···
177
216
}
178
217
.post-author {
179
218
display: inline
219
+
}
220
+
.post-media {
221
+
max-width: 50%;
180
222
}
181
223
}
182
224
···
229
271
flex-direction: row;
230
272
}
231
273
232
-
.post-media {
233
-
display: block;
234
-
margin: 0 auto;
235
-
max-width: 95%;
236
-
padding: 5px;
237
-
}
238
-
239
-
.title-container, .comment-info-container {
240
-
flex: 1;
274
+
.title-container, .comment-info-container, summary.expand-comments::before {
241
275
margin-top: 10px;
242
276
margin-bottom: 10px;
243
277
}
···
296
330
text-align: left;
297
331
}
298
332
299
-
summary {
333
+
summary.expand-post {
300
334
display: none;
335
+
}
336
+
337
+
summary.expand-comments {
338
+
list-style: none;
339
+
cursor: pointer;
340
+
}
341
+
342
+
summary.expand-comments::before {
343
+
content: "[+] ";
344
+
}
345
+
346
+
details[open] summary.expand-comments::before {
347
+
content: "[-] ";
348
+
}
349
+
350
+
details:not([open]) summary.expand-comments::before {
351
+
content: "[+] ";
352
+
}
353
+
354
+
.comment-body {
355
+
display: block;
301
356
}
302
357
303
358
.footer {
+5
-6
src/routes/index.js
+5
-6
src/routes/index.js
···
11
11
});
12
12
13
13
// GET /r/:id
14
-
router.get('/r/:subreddit/:sort?', async (req, res) => {
14
+
router.get('/r/:subreddit', async (req, res) => {
15
15
var subreddit = req.params.subreddit;
16
-
var query = req.query;
17
-
var sort = req.params.sort ? req.params.sort : 'hot';
18
-
var options = req.query;
16
+
var query = req.query? req.query : {};
17
+
var sort = query.sort? query.sort : 'hot';
19
18
20
-
var postsReq = G.getSubmissions(sort, `${subreddit}`, options);
19
+
var postsReq = G.getSubmissions(sort, `${subreddit}`, query);
21
20
var aboutReq = G.getSubreddit(`${subreddit}`);
22
21
23
22
var [posts, about] = await Promise.all([postsReq, aboutReq]);
24
23
25
-
res.render('index', { subreddit, posts, about });
24
+
res.render('index', { subreddit, posts, about, query });
26
25
});
27
26
28
27
// GET /comments/:id
+7
-1
src/utils.pug
+7
-1
src/utils.pug
···
15
15
if (elapsed < msPerMinute) {
16
16
return Math.round(elapsed/1000) + 's';
17
17
} else if (elapsed < msPerHour) {
18
-
return Math.round(elapsed/msPerMinute) + 'min';
18
+
return Math.round(elapsed/msPerMinute) + 'm';
19
19
} else if (elapsed < msPerDay ) {
20
20
return Math.round(elapsed/msPerHour ) + 'h';
21
21
} else if (elapsed < msPerMonth) {
···
26
26
return Math.round(elapsed/msPerYear ) + 'y';
27
27
}
28
28
}
29
+
-
30
+
function encodeQueryParams(obj) {
31
+
return Object.keys(obj)
32
+
.map(key => encodeURIComponent(key) + '=' + encodeURIComponent(obj[key]))
33
+
.join('&');
34
+
}
+9
src/views/comments.pug
+9
src/views/comments.pug
···
1
1
include ../mixins/comment
2
2
include ../mixins/header
3
+
include ../utils
3
4
4
5
doctype html
5
6
html
···
9
10
title reddit
10
11
link(rel='stylesheet', href='/styles.css')
11
12
script(src="https://cdn.dashjs.org/latest/dash.all.min.js")
13
+
script.
14
+
function toggleDetails(details_id) {
15
+
var detailsElement = document.getElementById(details_id);
16
+
if (detailsElement) {
17
+
detailsElement.open = !detailsElement.open;
18
+
}
19
+
}
20
+
12
21
body
13
22
main#content
14
23
+header()
+23
-13
src/views/index.pug
+23
-13
src/views/index.pug
···
1
1
include ../mixins/post
2
2
include ../mixins/sub
3
3
include ../mixins/header
4
+
include ../utils
4
5
- var subs = []
5
6
doctype html
6
7
html
···
42
43
+header()
43
44
44
45
div.hero
45
-
a(href=`/r/${subreddit}`)
46
-
h1 r/#{subreddit}
46
+
div.sub-title
47
+
a(href=`/r/${subreddit}`)
48
+
h1 r/#{subreddit}
49
+
div#button-container
47
50
if about
48
51
p #{about.public_description}
49
-
div#button-container
50
-
ul
51
-
li
52
-
a(href=`/r/${subreddit}/hot`) hot
53
-
li
54
-
a(href=`/r/${subreddit}/top`) top
55
-
li
56
-
a(href=`/r/${subreddit}/top?t=all`) top all
52
+
details
53
+
summary sort by
54
+
div.sort-opts
55
+
a(href=`/r/${subreddit}?sort=hot`) hot
56
+
a(href=`/r/${subreddit}?sort=new`) new
57
+
a(href=`/r/${subreddit}?sort=rising`) rising
58
+
a(href=`/r/${subreddit}?sort=top`) top
59
+
a(href=`/r/${subreddit}?sort=top&t=day`) top day
60
+
a(href=`/r/${subreddit}?sort=top&t=week`) top week
61
+
a(href=`/r/${subreddit}?sort=top&t=month`) top month
62
+
a(href=`/r/${subreddit}?sort=top&t=year`) top year
63
+
a(href=`/r/${subreddit}?sort=top&t=all`) top all
57
64
58
65
if posts
59
66
each child in posts.posts
60
67
+post(child.data)
61
-
div.footer
62
-
div.footer-item
63
-
a(href=`/r/${subreddit}?after=${posts.after}`) next →
68
+
69
+
if posts.after
70
+
div.footer
71
+
div.footer-item
72
+
- var newQuery = {...query, after: posts.after}
73
+
a(href=`/r/${subreddit}?${encodeQueryParams(newQuery)}`) next →