+2
-1
src/mixins/head.pug
+2
-1
src/mixins/head.pug
···
4
meta(charset='UTF-8')
5
title #{`${title} · lurker `}
6
link(rel="stylesheet", href="/styles.css")
7
+
if theme
8
+
link(rel="stylesheet", href=`/${theme}.css`)
9
link(rel="preconnect" href="https://rsms.me/")
10
link(rel="stylesheet" href="https://rsms.me/inter/inter.css")
11
script(src="https://cdn.dashjs.org/latest/dash.all.min.js")
+36
src/public/theme.css
+36
src/public/theme.css
···
···
1
+
/*
2
+
Uncomment and modify the values in this file to change the theme of the app.
3
+
4
+
:root {
5
+
--bg-color: white;
6
+
--bg-color-muted: #eee;
7
+
--text-color: black;
8
+
--text-color-muted: #999;
9
+
--blockquote-color: green;
10
+
--sticky-color: #dcfeda;
11
+
--gilded: darkorange;
12
+
--link-color: #29bc9b;
13
+
--link-visited-color: #999;
14
+
--accent: var(--link-color);
15
+
--error-text-color: red;
16
+
--border-radius-card: 0.5vmin;
17
+
--border-radius-media: 0.5vmin;
18
+
--border-radius-preview: 0.3vmin;
19
+
}
20
+
21
+
@media (prefers-color-scheme: dark) {
22
+
:root {
23
+
--bg-color: black;
24
+
--bg-color-muted: #333;
25
+
--text-color: white;
26
+
--text-color-muted: #999;
27
+
--blockquote-color: lightgreen;
28
+
--sticky-color: #014413;
29
+
--gilded: gold;
30
+
--link-color: #79ffe1;
31
+
--link-visited-color: #999;
32
+
--accent: var(--link-color);
33
+
--error-text-color: lightcoral;
34
+
}
35
+
}
36
+
*/
+42
-12
src/routes/index.js
+42
-12
src/routes/index.js
···
6
const { db } = require("../db");
7
const { authenticateToken, authenticateAdmin } = require("../auth");
8
const { validateInviteToken } = require("../invite");
9
-
const url = require("url");
10
11
const router = express.Router();
12
const G = new geddit.Geddit();
13
14
// GET /
15
router.get("/", authenticateToken, async (req, res) => {
16
const subs = db
17
.query("SELECT * FROM subscriptions WHERE user_id = $id")
18
.all({ id: req.user.id });
19
20
-
const qs = req.query ? ('?' + new URLSearchParams(req.query).toString()) : '';
21
22
if (subs.length === 0) {
23
res.redirect(`/r/all${qs}`);
···
53
54
const [posts, about] = await Promise.all([postsReq, aboutReq]);
55
56
-
if (query.view == 'card' && posts && posts.posts) {
57
posts.posts.forEach(unescape_selftext);
58
}
59
···
66
user: req.user,
67
isSubbed,
68
currentUrl: req.url,
69
});
70
});
71
···
82
user: req.user,
83
from: req.query.from,
84
query: req.query,
85
});
86
});
87
···
103
comments,
104
parent_id,
105
user: req.user,
106
});
107
},
108
);
···
115
)
116
.all({ id: req.user.id });
117
118
-
res.render("subs", { subs, user: req.user, query: req.query });
119
});
120
121
// GET /search
122
router.get("/search", authenticateToken, async (req, res) => {
123
-
res.render("search", { user: req.user, query: req.query });
124
});
125
126
// GET /sub-search
127
router.get("/sub-search", authenticateToken, async (req, res) => {
128
if (!req.query || !req.query.q) {
129
-
res.render("sub-search", { user: req.user });
130
} else {
131
const { items, after } = await G.searchSubreddits(req.query.q);
132
const subs = db
···
145
user: req.user,
146
original_query: req.query.q,
147
query: req.query,
148
});
149
}
150
});
···
152
// GET /post-search
153
router.get("/post-search", authenticateToken, async (req, res) => {
154
if (!req.query || !req.query.q) {
155
-
res.render("post-search", { user: req.user });
156
} else {
157
const { items, after } = await G.searchSubmissions(req.query.q);
158
const message =
···
160
? "no results found"
161
: `showing ${items.length} results`;
162
163
-
if (req.query.view == 'card' && items) {
164
items.forEach(unescape_selftext);
165
}
166
-
167
res.render("post-search", {
168
items,
169
after,
···
172
original_query: req.query.q,
173
currentUrl: req.url,
174
query: req.query,
175
});
176
}
177
});
···
194
usedAt: Date.parse(inv.usedAt),
195
}));
196
}
197
-
res.render("dashboard", { invites, isAdmin, user: req.user, query: req.query });
198
});
199
200
router.get("/create-invite", authenticateAdmin, async (req, res) => {
···
233
const kind = ["jpg", "jpeg", "png", "gif", "webp"].includes(ext)
234
? "img"
235
: "video";
236
-
res.render("media", { kind, url });
237
});
238
239
router.get("/register", validateInviteToken, async (req, res) => {
240
-
res.render("register", { isDisabled: false, token: req.query.token });
241
});
242
243
router.post("/register", validateInviteToken, async (req, res) => {
···
253
if (user) {
254
return res.render("register", {
255
message: `user by the name "${username}" exists, choose a different username`,
256
});
257
}
258
259
if (password !== confirm_password) {
260
return res.render("register", {
261
message: "passwords do not match, try again",
262
});
263
}
264
···
294
} catch (err) {
295
return res.render("register", {
296
message: "error registering user, try again later",
297
});
298
}
299
});
···
6
const { db } = require("../db");
7
const { authenticateToken, authenticateAdmin } = require("../auth");
8
const { validateInviteToken } = require("../invite");
9
10
const router = express.Router();
11
const G = new geddit.Geddit();
12
13
+
const commonRenderOptions = {
14
+
theme: process.env.LURKER_THEME,
15
+
};
16
+
17
// GET /
18
router.get("/", authenticateToken, async (req, res) => {
19
const subs = db
20
.query("SELECT * FROM subscriptions WHERE user_id = $id")
21
.all({ id: req.user.id });
22
23
+
const qs = req.query ? "?" + new URLSearchParams(req.query).toString() : "";
24
25
if (subs.length === 0) {
26
res.redirect(`/r/all${qs}`);
···
56
57
const [posts, about] = await Promise.all([postsReq, aboutReq]);
58
59
+
if (query.view == "card" && posts && posts.posts) {
60
posts.posts.forEach(unescape_selftext);
61
}
62
···
69
user: req.user,
70
isSubbed,
71
currentUrl: req.url,
72
+
...commonRenderOptions,
73
});
74
});
75
···
86
user: req.user,
87
from: req.query.from,
88
query: req.query,
89
+
...commonRenderOptions,
90
});
91
});
92
···
108
comments,
109
parent_id,
110
user: req.user,
111
+
...commonRenderOptions,
112
});
113
},
114
);
···
121
)
122
.all({ id: req.user.id });
123
124
+
res.render("subs", {
125
+
subs,
126
+
user: req.user,
127
+
query: req.query,
128
+
...commonRenderOptions,
129
+
});
130
});
131
132
// GET /search
133
router.get("/search", authenticateToken, async (req, res) => {
134
+
res.render("search", {
135
+
user: req.user,
136
+
query: req.query,
137
+
...commonRenderOptions,
138
+
});
139
});
140
141
// GET /sub-search
142
router.get("/sub-search", authenticateToken, async (req, res) => {
143
if (!req.query || !req.query.q) {
144
+
res.render("sub-search", { user: req.user, ...commonRenderOptions });
145
} else {
146
const { items, after } = await G.searchSubreddits(req.query.q);
147
const subs = db
···
160
user: req.user,
161
original_query: req.query.q,
162
query: req.query,
163
+
...commonRenderOptions,
164
});
165
}
166
});
···
168
// GET /post-search
169
router.get("/post-search", authenticateToken, async (req, res) => {
170
if (!req.query || !req.query.q) {
171
+
res.render("post-search", { user: req.user, ...commonRenderOptions });
172
} else {
173
const { items, after } = await G.searchSubmissions(req.query.q);
174
const message =
···
176
? "no results found"
177
: `showing ${items.length} results`;
178
179
+
if (req.query.view == "card" && items) {
180
items.forEach(unescape_selftext);
181
}
182
+
183
res.render("post-search", {
184
items,
185
after,
···
188
original_query: req.query.q,
189
currentUrl: req.url,
190
query: req.query,
191
+
...commonRenderOptions,
192
});
193
}
194
});
···
211
usedAt: Date.parse(inv.usedAt),
212
}));
213
}
214
+
res.render("dashboard", {
215
+
invites,
216
+
isAdmin,
217
+
user: req.user,
218
+
query: req.query,
219
+
...commonRenderOptions,
220
+
});
221
});
222
223
router.get("/create-invite", authenticateAdmin, async (req, res) => {
···
256
const kind = ["jpg", "jpeg", "png", "gif", "webp"].includes(ext)
257
? "img"
258
: "video";
259
+
res.render("media", { kind, url, ...commonRenderOptions });
260
});
261
262
router.get("/register", validateInviteToken, async (req, res) => {
263
+
res.render("register", {
264
+
isDisabled: false,
265
+
token: req.query.token,
266
+
...commonRenderOptions,
267
+
});
268
});
269
270
router.post("/register", validateInviteToken, async (req, res) => {
···
280
if (user) {
281
return res.render("register", {
282
message: `user by the name "${username}" exists, choose a different username`,
283
+
...commonRenderOptions,
284
});
285
}
286
287
if (password !== confirm_password) {
288
return res.render("register", {
289
message: "passwords do not match, try again",
290
+
...commonRenderOptions,
291
});
292
}
293
···
323
} catch (err) {
324
return res.render("register", {
325
message: "error registering user, try again later",
326
+
...commonRenderOptions,
327
});
328
}
329
});