+1
-1
config.maple
+1
-1
config.maple
+3
-3
doc/todo.md
+3
-3
doc/todo.md
···
4
5
## in-progress
6
7
-
- [/] post:likes/dislikes
8
-
9
## planing
10
11
-
- [ ] site:stylesheet (and a toggle for html-only mode)
12
- [ ] post:mentioning ('tagging') other users in posts
13
- [ ] post:replies
14
- [ ] post:tags ('hashtags')
···
26
- [x] user:nicknames
27
- [x] user:bio/about me
28
- [x] user:listed pronouns
···
4
5
## in-progress
6
7
## planing
8
9
- [ ] post:mentioning ('tagging') other users in posts
10
- [ ] post:replies
11
- [ ] post:tags ('hashtags')
···
23
- [x] user:nicknames
24
- [x] user:bio/about me
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
}
344
}
345
346
-
@['/api/post/toggle_like']
347
-
fn (mut app App) api_post_toggle_like(mut ctx Context, id int) veb.Result {
348
user := app.whoami(mut ctx) or {
349
return ctx.unauthorized('not logged in')
350
}
···
364
}
365
return ctx.ok('unliked post')
366
} else {
367
like := Like{
368
user_id: user.id
369
post_id: post.id
···
380
return ctx.ok('liked post')
381
}
382
}
···
343
}
344
}
345
346
+
@['/api/post/like']
347
+
fn (mut app App) api_post_like(mut ctx Context, id int) veb.Result {
348
user := app.whoami(mut ctx) or {
349
return ctx.unauthorized('not logged in')
350
}
···
364
}
365
return ctx.ok('unliked post')
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
+
376
like := Like{
377
user_id: user.id
378
post_id: post.id
···
389
return ctx.ok('liked post')
390
}
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
return !likes.first().is_like
170
}
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
+
183
pub fn (app &App) get_net_likes_for_post(post_id int) int {
184
// check cache
185
cache := sql app.db {
+12
src/static/style.css
+12
src/static/style.css
···
6
.post p {
7
margin: 0;
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
@else
8
9
<h1>admin dashboard</h1>
10
-
<p>logged in as: @user</p>
11
<div>
12
<h2>user list:</h2>
13
-
<div>
14
@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>
55
@end
56
-
</div>
57
</div>
58
59
@end
···
7
@else
8
9
<h1>admin dashboard</h1>
10
+
<p>logged in as:</p>
11
+
<pre>@user</pre>
12
<div>
13
<h2>user list:</h2>
14
+
<ul>
15
@for u in app.get_users()
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>
21
@end
22
+
</ul>
23
</div>
24
25
@end
+25
-9
src/templates/components/post.html
+25
-9
src/templates/components/post.html
···
1
<div class="post post-full">
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>
4
<p><em>likes: @{app.get_net_likes_for_post(post.id)}</em></p>
5
<p><em>posted at: @post.posted_at</em></p>
6
7
@if ctx.is_logged_in()
8
<script>
9
const like = async (id) => {
10
-
await fetch('/api/post/toggle_like?id=' + id, {
11
method: 'GET'
12
})
13
window.location.reload()
···
16
17
<br>
18
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
@end
27
28
@if app.config.dev_mode || (ctx.is_logged_in() && user.admin) || (ctx.is_logged_in() && post.author_id == user.id)
···
1
<div class="post post-full">
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
+
<pre>@post.body</pre>
4
<p><em>likes: @{app.get_net_likes_for_post(post.id)}</em></p>
5
<p><em>posted at: @post.posted_at</em></p>
6
7
@if ctx.is_logged_in()
8
<script>
9
const like = async (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, {
18
method: 'GET'
19
})
20
window.location.reload()
···
23
24
<br>
25
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>
42
@end
43
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
required
11
readonly
12
hidden
13
+
aria-hidden
14
>
15
@if !user.muted
16
<input
···
21
checked
22
readonly
23
hidden
24
+
aria-hidden
25
>
26
<input type="submit" value="mute">
27
@else
···
33
checked
34
readonly
35
hidden
36
+
aria-hidden
37
>
38
<input type="submit" value="unmute">
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