+1
-1
config.maple
+1
-1
config.maple
+3
-3
doc/todo.md
+3
-3
doc/todo.md
···
4
4
5
5
## in-progress
6
6
7
-
- [/] post:likes/dislikes
8
-
9
7
## planing
10
8
11
-
- [ ] site:stylesheet (and a toggle for html-only mode)
12
9
- [ ] post:mentioning ('tagging') other users in posts
13
10
- [ ] post:replies
14
11
- [ ] post:tags ('hashtags')
···
26
23
- [x] user:nicknames
27
24
- [x] user:bio/about me
28
25
- [x] user:listed pronouns
26
+
- [x] post:likes/dislikes
27
+
- [ ] ~~site:stylesheet (and a toggle for html-only mode)~~
28
+
- replaced with per-user optional stylesheets
+58
-2
src/api.v
+58
-2
src/api.v
···
343
343
}
344
344
}
345
345
346
-
@['/api/post/toggle_like']
347
-
fn (mut app App) api_post_toggle_like(mut ctx Context, id int) veb.Result {
346
+
@['/api/post/like']
347
+
fn (mut app App) api_post_like(mut ctx Context, id int) veb.Result {
348
348
user := app.whoami(mut ctx) or {
349
349
return ctx.unauthorized('not logged in')
350
350
}
···
364
364
}
365
365
return ctx.ok('unliked post')
366
366
} else {
367
+
// remove the old dislike, if it exists
368
+
if app.does_user_dislike_post(user.id, post.id) {
369
+
sql app.db {
370
+
delete from Like where user_id == user.id && post_id == post.id
371
+
} or {
372
+
eprintln('user ${user.id} failed to remove dislike on post ${id} when liking it')
373
+
}
374
+
}
375
+
367
376
like := Like{
368
377
user_id: user.id
369
378
post_id: post.id
···
380
389
return ctx.ok('liked post')
381
390
}
382
391
}
392
+
393
+
@['/api/post/dislike']
394
+
fn (mut app App) api_post_dislike(mut ctx Context, id int) veb.Result {
395
+
user := app.whoami(mut ctx) or {
396
+
return ctx.unauthorized('not logged in')
397
+
}
398
+
399
+
post := app.get_post_by_id(id) or {
400
+
return ctx.server_error('post does not exist')
401
+
}
402
+
403
+
if app.does_user_dislike_post(user.id, post.id) {
404
+
sql app.db {
405
+
delete from Like where user_id == user.id && post_id == post.id
406
+
// yeet the old cached like value
407
+
delete from LikeCache where post_id == post.id
408
+
} or {
409
+
eprintln('user ${user.id} failed to unlike post ${id}')
410
+
return ctx.server_error('failed to unlike post')
411
+
}
412
+
return ctx.ok('undisliked post')
413
+
} else {
414
+
// remove the old like, if it exists
415
+
if app.does_user_like_post(user.id, post.id) {
416
+
sql app.db {
417
+
delete from Like where user_id == user.id && post_id == post.id
418
+
} or {
419
+
eprintln('user ${user.id} failed to remove like on post ${id} when disliking it')
420
+
}
421
+
}
422
+
423
+
like := Like{
424
+
user_id: user.id
425
+
post_id: post.id
426
+
is_like: false
427
+
}
428
+
sql app.db {
429
+
insert like into Like
430
+
// yeet the old cached like value
431
+
delete from LikeCache where post_id == post.id
432
+
} or {
433
+
eprintln('user ${user.id} failed to dislike post ${id}')
434
+
return ctx.server_error('failed to dislike post')
435
+
}
436
+
return ctx.ok('disliked post')
437
+
}
438
+
}
+11
src/app.v
+11
src/app.v
···
169
169
return !likes.first().is_like
170
170
}
171
171
172
+
pub fn (app &App) does_user_like_or_dislike_post(user_id int, post_id int) bool {
173
+
likes := sql app.db {
174
+
select from Like where user_id == user_id && post_id == post_id
175
+
} or { [] }
176
+
if likes.len > 1 {
177
+
// something is very wrong lol
178
+
eprintln('a user somehow got two or more likes on the same post (user: ${user_id}, post: ${post_id})')
179
+
}
180
+
return likes.len == 1
181
+
}
182
+
172
183
pub fn (app &App) get_net_likes_for_post(post_id int) int {
173
184
// check cache
174
185
cache := sql app.db {
+12
src/static/style.css
+12
src/static/style.css
···
6
6
.post p {
7
7
margin: 0;
8
8
}
9
+
10
+
pre {
11
+
white-space: pre-wrap;
12
+
}
13
+
14
+
/*
15
+
* some themes make input fields display: block, which overrides my hidden
16
+
* attribute. to resolve that, i will just override the override.
17
+
*/
18
+
input[hidden] {
19
+
display: none !important;
20
+
}
+9
-43
src/templates/admin.html
+9
-43
src/templates/admin.html
···
7
7
@else
8
8
9
9
<h1>admin dashboard</h1>
10
-
<p>logged in as: @user</p>
10
+
<p>logged in as:</p>
11
+
<pre>@user</pre>
11
12
<div>
12
13
<h2>user list:</h2>
13
-
<div>
14
+
<ul>
14
15
@for u in app.get_users()
15
-
<div>
16
-
<a href="/user/@u.username">@u.get_name() (@@@u.username) [@u.id]</a>
17
-
<p>muted=@u.muted, admin=@u.admin</p>
18
-
<p>created_at=@u.created_at</p>
19
-
<form action="/api/user/set_muted" method="post">
20
-
<input
21
-
type="text"
22
-
name="id"
23
-
id="id"
24
-
value="@u.id"
25
-
required
26
-
readonly
27
-
hidden
28
-
>
29
-
@if !user.muted
30
-
<input
31
-
type="checkbox"
32
-
name="muted"
33
-
id="muted"
34
-
value="true"
35
-
checked
36
-
readonly
37
-
hidden
38
-
>
39
-
<input type="submit" value="mute">
40
-
@else
41
-
<input
42
-
type="checkbox"
43
-
name="muted"
44
-
id="muted"
45
-
value="false"
46
-
checked
47
-
readonly
48
-
hidden
49
-
>
50
-
<input type="submit" value="unmute">
51
-
@end
52
-
</form>
53
-
</div>
54
-
<hr>
16
+
<li>
17
+
<a href="/user/@u.username">@u.get_name()</a>
18
+
(@@@u.username) [@u.id]
19
+
{muted=@u.muted, admin=@u.admin, created_at=@u.created_at}
20
+
</li>
55
21
@end
56
-
</div>
22
+
</ul>
57
23
</div>
58
24
59
25
@end
+25
-9
src/templates/components/post.html
+25
-9
src/templates/components/post.html
···
1
1
<div class="post post-full">
2
2
<h2><a href="/user/@{(app.get_user_by_id(post.author_id) or { app.get_unknown_user() }).username}"><strong>@{(app.get_user_by_id(post.author_id) or { app.get_unknown_user() }).get_name()}</strong></a> - @post.title</h2>
3
-
<p>@post.body</p>
3
+
<pre>@post.body</pre>
4
4
<p><em>likes: @{app.get_net_likes_for_post(post.id)}</em></p>
5
5
<p><em>posted at: @post.posted_at</em></p>
6
6
7
7
@if ctx.is_logged_in()
8
8
<script>
9
9
const like = async (id) => {
10
-
await fetch('/api/post/toggle_like?id=' + id, {
10
+
await fetch('/api/post/like?id=' + id, {
11
+
method: 'GET'
12
+
})
13
+
window.location.reload()
14
+
}
15
+
16
+
const dislike = async (id) => {
17
+
await fetch('/api/post/dislike?id=' + id, {
11
18
method: 'GET'
12
19
})
13
20
window.location.reload()
···
16
23
17
24
<br>
18
25
19
-
<button onclick="like(@post.id)">
20
-
@if app.does_user_like_post(user.id, post.id)
21
-
liked!
22
-
@else
23
-
like
24
-
@end
25
-
</button>
26
+
<div>
27
+
<button onclick="like(@post.id)">
28
+
@if app.does_user_like_post(user.id, post.id)
29
+
liked :D
30
+
@else
31
+
like
32
+
@end
33
+
</button>
34
+
<button onclick="dislike(@post.id)">
35
+
@if app.does_user_dislike_post(user.id, post.id)
36
+
disliked D:
37
+
@else
38
+
dislike
39
+
@end
40
+
</button>
41
+
</div>
26
42
@end
27
43
28
44
@if app.config.dev_mode || (ctx.is_logged_in() && user.admin) || (ctx.is_logged_in() && post.author_id == user.id)
+3
src/templates/components/user/admin.html
+3
src/templates/components/user/admin.html
···
10
10
required
11
11
readonly
12
12
hidden
13
+
aria-hidden
13
14
>
14
15
@if !user.muted
15
16
<input
···
20
21
checked
21
22
readonly
22
23
hidden
24
+
aria-hidden
23
25
>
24
26
<input type="submit" value="mute">
25
27
@else
···
31
33
checked
32
34
readonly
33
35
hidden
36
+
aria-hidden
34
37
>
35
38
<input type="submit" value="unmute">
36
39
@end
+1
-1
src/templates/components/user/name.html
+1
-1
src/templates/components/user/name.html
+2
-2
src/templates/partial/header.html
+2
-2
src/templates/partial/header.html
···
24
24
<a href="/">home</a>
25
25
-
26
26
27
-
@if app.config.dev_mode
27
+
@if app.config.dev_mode || (ctx.is_logged_in() && user.admin)
28
28
<a href="/admin">admin</a>
29
29
-
30
30
@end
···
44
44
<!-- TODO: fix this lol -->
45
45
@if ctx.form_error != ''
46
46
<div>
47
-
<p><strong>error:</strong> ctx.form_error</p>
47
+
<p><strong>error:</strong> @ctx.form_error</p>
48
48
</div>
49
49
@end