-1
.gitignore
-1
.gitignore
+14
-1
src/api.v
+14
-1
src/api.v
···
275
275
return ctx.redirect('/me')
276
276
}
277
277
278
+
@['/api/user/get_name']
279
+
fn (mut app App) api_user_get_name(mut ctx Context, username string) veb.Result {
280
+
user := app.get_user_by_name(username) or {
281
+
return ctx.server_error('no such user')
282
+
}
283
+
return ctx.text(user.get_name())
284
+
}
285
+
278
286
////// Posts //////
279
287
280
288
@['/api/post/new_post'; post]
···
325
333
return ctx.redirect('/login')
326
334
}
327
335
328
-
if user.admin || app.config.dev_mode {
336
+
post := app.get_post_by_id(id) or {
337
+
ctx.error('post does not exist')
338
+
return ctx.redirect('/')
339
+
}
340
+
341
+
if user.admin || user.id == post.author_id {
329
342
sql app.db {
330
343
delete from Post where id == id
331
344
delete from Like where post_id == id
+3
-2
src/pages.v
+3
-2
src/pages.v
···
30
30
return ctx.redirect('/login')
31
31
}
32
32
ctx.title = '${app.config.instance.name} - ${user.get_name()}'
33
-
return $veb.html()
33
+
return ctx.redirect('/user/${user.username}')
34
34
}
35
35
36
36
fn (mut app App) admin(mut ctx Context) veb.Result {
···
41
41
42
42
@['/user/:username']
43
43
fn (mut app App) user(mut ctx Context, username string) veb.Result {
44
-
user := app.get_user_by_name(username) or {
44
+
user := app.whoami(mut ctx) or { User{} }
45
+
viewing := app.get_user_by_name(username) or {
45
46
ctx.error('user not found')
46
47
return ctx.redirect('/')
47
48
}
+24
src/static/js/post.js
+24
src/static/js/post.js
···
1
+
const like = async id => {
2
+
await fetch('/api/post/like?id=' + id, {
3
+
method: 'GET'
4
+
})
5
+
window.location.reload()
6
+
}
7
+
8
+
const dislike = async id => {
9
+
await fetch('/api/post/dislike?id=' + id, {
10
+
method: 'GET'
11
+
})
12
+
window.location.reload()
13
+
}
14
+
15
+
const render_post_body = async (id, mention_pattern) => {
16
+
const element = document.getElementById(`post-${id}`)
17
+
var body = element.innerText
18
+
const matches = body.matchAll(new RegExp(mention_pattern, 'g'))
19
+
for (const match of matches) {
20
+
(await fetch('/api/user/get_name?username=' + match[0].substring(2, match[0].length - 1))).text().then(s => {
21
+
element.innerHTML = element.innerHTML.replace(match[0], '<a href="/user/' + match[0].substring(2, match[0].length - 1) + '">' + s + '</a>')
22
+
})
23
+
}
24
+
}
+2
-2
src/static/themes/hackerman.css
+2
-2
src/static/themes/hackerman.css
-62
src/templates/components/post.html
-62
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
-
<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()
21
-
}
22
-
</script>
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)
45
-
<hr>
46
-
<h4>admin powers:</h4>
47
-
<form action="/api/post/delete" method="post">
48
-
<input
49
-
type="number"
50
-
name="id"
51
-
id="id"
52
-
placeholder="post id"
53
-
value="@post.id"
54
-
required
55
-
readonly
56
-
hidden
57
-
aria-hidden
58
-
>
59
-
<input type="submit" value="delete">
60
-
</form>
61
-
@end
62
-
</div>
-42
src/templates/components/user/admin.html
-42
src/templates/components/user/admin.html
···
1
-
@if ctx.is_logged_in() && user.admin
2
-
<div>
3
-
<h2>admin powers:</h2>
4
-
<form action="/api/user/set_muted" method="post">
5
-
<input
6
-
type="text"
7
-
name="id"
8
-
id="id"
9
-
value="@user.id"
10
-
required
11
-
readonly
12
-
hidden
13
-
aria-hidden
14
-
>
15
-
@if !user.muted
16
-
<input
17
-
type="checkbox"
18
-
name="muted"
19
-
id="muted"
20
-
value="true"
21
-
checked
22
-
readonly
23
-
hidden
24
-
aria-hidden
25
-
>
26
-
<input type="submit" value="mute">
27
-
@else
28
-
<input
29
-
type="checkbox"
30
-
name="muted"
31
-
id="muted"
32
-
value="false"
33
-
checked
34
-
readonly
35
-
hidden
36
-
aria-hidden
37
-
>
38
-
<input type="submit" value="unmute">
39
-
@end
40
-
</form>
41
-
</div>
42
-
@end
-6
src/templates/components/user/bio.html
-6
src/templates/components/user/bio.html
-10
src/templates/components/user/info.html
-10
src/templates/components/user/info.html
···
1
-
<div>
2
-
<h2>user info:</h2>
3
-
<p>id: @user.id</p>
4
-
<p>username: @user.username</p>
5
-
<p>display name: @user.get_name()</p>
6
-
@if app.logged_in_as(mut ctx, user.id)
7
-
<p><a href="/api/user/logout">log out</a></p>
8
-
<p><a href="/api/user/full_logout">log out of all devices</a></p>
9
-
@end
10
-
</div>
-20
src/templates/components/user/name.html
-20
src/templates/components/user/name.html
···
1
-
<h1>
2
-
@{user.get_name()}
3
-
(@@@user.username)
4
-
5
-
@if user.pronouns != ''
6
-
(@user.pronouns)
7
-
@end
8
-
9
-
@if user.muted && user.admin
10
-
[muted admin, somehow]
11
-
@else if user.muted
12
-
[muted]
13
-
@else if user.admin
14
-
[admin]
15
-
@end
16
-
</h1>
17
-
18
-
@if app.logged_in_as(mut ctx, user.id)
19
-
<p>this is you!</p>
20
-
@end
-28
src/templates/components/user/new_post.html
-28
src/templates/components/user/new_post.html
···
1
-
<div>
2
-
<form action="/api/post/new_post" method="post">
3
-
<h2>new post:</h2>
4
-
<input
5
-
type="text"
6
-
name="title"
7
-
id="title"
8
-
minlength="@app.config.post.title_min_len"
9
-
maxlength="@app.config.post.title_max_len"
10
-
pattern="@app.config.post.title_pattern"
11
-
placeholder="title"
12
-
required
13
-
>
14
-
<br>
15
-
<textarea
16
-
name="body"
17
-
id="body"
18
-
minlength="@app.config.post.body_min_len"
19
-
maxlength="@app.config.post.body_max_len"
20
-
rows="10"
21
-
cols="30"
22
-
placeholder="body"
23
-
required
24
-
></textarea>
25
-
<br>
26
-
<input type="submit" value="post!">
27
-
</form>
28
-
</div>
-6
src/templates/components/user/posts.html
-6
src/templates/components/user/posts.html
-57
src/templates/components/user/settings.html
-57
src/templates/components/user/settings.html
···
1
-
<div>
2
-
<h2>settings:</h2>
3
-
<form action="/api/user/set_bio" method="post">
4
-
<label for="bio">bio:</label>
5
-
<br>
6
-
<textarea
7
-
name="bio"
8
-
id="bio"
9
-
cols="30"
10
-
rows="10"
11
-
minlength="@app.config.user.bio_min_len"
12
-
maxlength="@app.config.user.bio_max_len"
13
-
required
14
-
>@user.bio</textarea>
15
-
<input type="submit" value="save">
16
-
</form>
17
-
<form action="/api/user/set_pronouns" method="post">
18
-
<label for="pronouns">pronouns:</label>
19
-
<input
20
-
type="text"
21
-
name="pronouns"
22
-
id="pronouns"
23
-
minlength="@app.config.user.pronouns_min_len"
24
-
maxlength="@app.config.user.pronouns_max_len"
25
-
pattern="@app.config.user.pronouns_pattern"
26
-
value="@user.pronouns"
27
-
required
28
-
>
29
-
<br>
30
-
<input type="submit" value="save">
31
-
</form>
32
-
<form action="/api/user/set_nickname" method="post">
33
-
<label for="nickname">nickname:</label>
34
-
<input
35
-
type="text"
36
-
name="nickname"
37
-
id="nickname"
38
-
pattern="@app.config.user.nickname_pattern"
39
-
minlength="@app.config.user.nickname_min_len"
40
-
maxlength="@app.config.user.nickname_max_len"
41
-
value="@{user.nickname or { '' }}"
42
-
required
43
-
>
44
-
<input type="submit" value="save">
45
-
</form>
46
-
<form action="/api/user/set_nickname" method="post">
47
-
<input type="submit" value="reset nickname">
48
-
</form>
49
-
@if app.config.user.allow_changing_theme
50
-
<br>
51
-
<form action="/api/user/set_theme" method="post">
52
-
<label for="url">theme:</label>
53
-
<input type="url" name="url" id="url" value="@{user.theme or { '' }}">
54
-
<input type="submit" value="save">
55
-
</form>
56
-
@end
57
-
</div>
-16
src/templates/me.html
-16
src/templates/me.html
···
1
-
@include 'partial/header.html'
2
-
3
-
@if ctx.is_logged_in()
4
-
5
-
@include 'components/user/name.html'
6
-
@include 'components/user/bio.html'
7
-
@include 'components/user/new_post.html'
8
-
@include 'components/user/posts.html'
9
-
@include 'components/user/info.html'
10
-
@include 'components/user/settings.html
11
-
12
-
@else
13
-
<p>uh oh, you are not logged in! you can log in <a href="/login">here</a></p>
14
-
@end
15
-
16
-
@include 'partial/footer.html'
+57
-2
src/templates/post.html
+57
-2
src/templates/post.html
···
1
1
@include 'partial/header.html'
2
-
<br>
3
-
@include 'components/post.html'
2
+
3
+
<script src="/static/js/post.js"></script>
4
+
5
+
<div class="post post-full">
6
+
<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>
7
+
<pre id="post-@{post.id}">@post.body</pre>
8
+
<p><em>likes: @{app.get_net_likes_for_post(post.id)}</em></p>
9
+
<p><em>posted at: @post.posted_at</em></p>
10
+
11
+
@if ctx.is_logged_in()
12
+
<br>
13
+
<div>
14
+
<button onclick="like(@post.id)">
15
+
@if app.does_user_like_post(user.id, post.id)
16
+
liked :D
17
+
@else
18
+
like
19
+
@end
20
+
</button>
21
+
<button onclick="dislike(@post.id)">
22
+
@if app.does_user_dislike_post(user.id, post.id)
23
+
disliked D:
24
+
@else
25
+
dislike
26
+
@end
27
+
</button>
28
+
</div>
29
+
@end
30
+
31
+
@if (ctx.is_logged_in() && user.admin) || (ctx.is_logged_in() && post.author_id == user.id)
32
+
<hr>
33
+
@if user.admin
34
+
<h4>admin powers:</h4>
35
+
@else if post.author_id == user.id
36
+
<h4>manage post:</h4>
37
+
@end
38
+
<form action="/api/post/delete" method="post">
39
+
<input
40
+
type="number"
41
+
name="id"
42
+
id="id"
43
+
placeholder="post id"
44
+
value="@post.id"
45
+
required
46
+
readonly
47
+
hidden
48
+
aria-hidden
49
+
>
50
+
<input type="submit" value="delete">
51
+
</form>
52
+
@end
53
+
</div>
54
+
55
+
<script type="module">
56
+
await render_post_body(@{post.id}, '@@\\(@{app.config.user.username_pattern}\\)')
57
+
</script>
58
+
4
59
@include 'partial/footer.html'
+177
-5
src/templates/user.html
+177
-5
src/templates/user.html
···
1
1
@include 'partial/header.html'
2
2
3
-
@include 'components/user/name.html'
4
-
@include 'components/user/bio.html'
5
-
@include 'components/user/posts.html'
6
-
@include 'components/user/info.html'
7
-
@include 'components/user/admin.html'
3
+
<h1>
4
+
@{viewing.get_name()}
5
+
(@@@viewing.username)
6
+
7
+
@if viewing.pronouns != ''
8
+
(@viewing.pronouns)
9
+
@end
10
+
11
+
@if viewing.muted && viewing.admin
12
+
[muted admin, somehow]
13
+
@else if viewing.muted
14
+
[muted]
15
+
@else if viewing.admin
16
+
[admin]
17
+
@end
18
+
</h1>
19
+
20
+
@if app.logged_in_as(mut ctx, viewing.id)
21
+
<p>this is you!</p>
22
+
23
+
<div>
24
+
<form action="/api/post/new_post" method="post">
25
+
<h2>new post:</h2>
26
+
<input
27
+
type="text"
28
+
name="title"
29
+
id="title"
30
+
minlength="@app.config.post.title_min_len"
31
+
maxlength="@app.config.post.title_max_len"
32
+
pattern="@app.config.post.title_pattern"
33
+
placeholder="title"
34
+
required
35
+
>
36
+
<br>
37
+
<textarea
38
+
name="body"
39
+
id="body"
40
+
minlength="@app.config.post.body_min_len"
41
+
maxlength="@app.config.post.body_max_len"
42
+
rows="10"
43
+
cols="30"
44
+
placeholder="body"
45
+
required
46
+
></textarea>
47
+
<br>
48
+
<input type="submit" value="post!">
49
+
</form>
50
+
</div>
51
+
@end
52
+
53
+
@if viewing.bio != ''
54
+
<div>
55
+
<h2>bio:</h2>
56
+
<p>@viewing.bio</p>
57
+
</div>
58
+
@end
59
+
60
+
<div>
61
+
<h2>posts:</h2>
62
+
@for post in app.get_posts_from_user(viewing.id)
63
+
@include 'components/post_small.html'
64
+
@end
65
+
</div>
66
+
67
+
<div>
68
+
<h2>user info:</h2>
69
+
<p>id: @viewing.id</p>
70
+
<p>username: @viewing.username</p>
71
+
<p>display name: @viewing.get_name()</p>
72
+
@if app.logged_in_as(mut ctx, viewing.id)
73
+
<p><a href="/api/user/logout">log out</a></p>
74
+
<p><a href="/api/user/full_logout">log out of all devices</a></p>
75
+
@end
76
+
</div>
77
+
78
+
@if app.logged_in_as(mut ctx, viewing.id)
79
+
<div>
80
+
<h2>user settings:</h2>
81
+
<form action="/api/user/set_bio" method="post">
82
+
<label for="bio">bio:</label>
83
+
<br>
84
+
<textarea
85
+
name="bio"
86
+
id="bio"
87
+
cols="30"
88
+
rows="10"
89
+
minlength="@app.config.user.bio_min_len"
90
+
maxlength="@app.config.user.bio_max_len"
91
+
required
92
+
>@user.bio</textarea>
93
+
<input type="submit" value="save">
94
+
</form>
95
+
<form action="/api/user/set_pronouns" method="post">
96
+
<label for="pronouns">pronouns:</label>
97
+
<input
98
+
type="text"
99
+
name="pronouns"
100
+
id="pronouns"
101
+
minlength="@app.config.user.pronouns_min_len"
102
+
maxlength="@app.config.user.pronouns_max_len"
103
+
pattern="@app.config.user.pronouns_pattern"
104
+
value="@user.pronouns"
105
+
required
106
+
>
107
+
<br>
108
+
<input type="submit" value="save">
109
+
</form>
110
+
<form action="/api/user/set_nickname" method="post">
111
+
<label for="nickname">nickname:</label>
112
+
<input
113
+
type="text"
114
+
name="nickname"
115
+
id="nickname"
116
+
pattern="@app.config.user.nickname_pattern"
117
+
minlength="@app.config.user.nickname_min_len"
118
+
maxlength="@app.config.user.nickname_max_len"
119
+
value="@{user.nickname or { '' }}"
120
+
required
121
+
>
122
+
<input type="submit" value="save">
123
+
</form>
124
+
<form action="/api/user/set_nickname" method="post">
125
+
<input type="submit" value="reset nickname">
126
+
</form>
127
+
@if app.config.user.allow_changing_theme
128
+
<br>
129
+
<form action="/api/user/set_theme" method="post">
130
+
<label for="url">theme:</label>
131
+
<input type="url" name="url" id="url" value="@{user.theme or { '' }}">
132
+
<input type="submit" value="save">
133
+
</form>
134
+
@end
135
+
</div>
136
+
@end
137
+
138
+
@if ctx.is_logged_in() && user.admin
139
+
<div>
140
+
<h2>admin powers:</h2>
141
+
<form action="/api/user/set_muted" method="post">
142
+
<input
143
+
type="text"
144
+
name="id"
145
+
id="id"
146
+
value="@user.id"
147
+
required
148
+
readonly
149
+
hidden
150
+
aria-hidden
151
+
>
152
+
@if !user.muted
153
+
<input
154
+
type="checkbox"
155
+
name="muted"
156
+
id="muted"
157
+
value="true"
158
+
checked
159
+
readonly
160
+
hidden
161
+
aria-hidden
162
+
>
163
+
<input type="submit" value="mute">
164
+
@else
165
+
<input
166
+
type="checkbox"
167
+
name="muted"
168
+
id="muted"
169
+
value="false"
170
+
checked
171
+
readonly
172
+
hidden
173
+
aria-hidden
174
+
>
175
+
<input type="submit" value="unmute">
176
+
@end
177
+
</form>
178
+
</div>
179
+
@end
8
180
9
181
@include 'partial/footer.html'