+4
-1
doc/todo.md
+4
-1
doc/todo.md
···
6
6
7
7
## planing
8
8
9
-
- [ ] post:tags ('hashtags')
9
+
> p.s. when initially writing "planing," i made a typo. it should be "planning."
10
+
> however, i will not be fixing it, because it is funny.
11
+
10
12
- [ ] post:images (should have a config.maple toggle to enable/disable)
11
13
- [ ] post:saving (add the post to a list of saved posts that a user can view later)
12
14
- [ ] user:change password
···
29
31
- [x] post:mentioning:who mentioned you (send notifications when a user mentions you)
30
32
- [x] post:editing
31
33
- [x] post:replies
34
+
- [x] post:tags ('hashtags')
32
35
- [ ] ~~site:stylesheet (and a toggle for html-only mode)~~
33
36
- replaced with per-user optional stylesheets
34
37
- [x] site:message of the day (admins can add a welcome message displayed on index.html)
+23
src/api.v
+23
src/api.v
···
586
586
return ctx.redirect('/post/${id}')
587
587
}
588
588
589
+
@['/api/post/pin'; post]
590
+
fn (mut app App) api_post_pin(mut ctx Context, id int) veb.Result {
591
+
user := app.whoami(mut ctx) or {
592
+
ctx.error('not logged in!')
593
+
return ctx.redirect('/login')
594
+
}
595
+
596
+
if user.admin {
597
+
sql app.db {
598
+
update Post set pinned = true where id == id
599
+
} or {
600
+
eprintln('failed to pin post: ${id}')
601
+
ctx.error('failed to pin post')
602
+
return ctx.redirect('/post/${id}')
603
+
}
604
+
return ctx.redirect('/post/${id}')
605
+
} else {
606
+
ctx.error('insufficient permissions!')
607
+
eprintln('insufficient perms to pin post: ${id} (${user.id})')
608
+
return ctx.redirect('/')
609
+
}
610
+
}
611
+
589
612
////// site //////
590
613
591
614
@['/api/site/set_motd'; post]
+7
src/app.v
+7
src/app.v
···
108
108
return posts[0]
109
109
}
110
110
111
+
pub fn (app &App) get_posts_with_tag(tag string, offset int) []Post {
112
+
posts := sql app.db {
113
+
select from Post where body like '%#(${tag})%' order by posted_at desc limit 10 offset offset
114
+
} or { [] }
115
+
return posts
116
+
}
117
+
111
118
pub fn (app &App) get_pinned_posts() []Post {
112
119
posts := sql app.db {
113
120
select from Post where pinned == true
+1
-1
src/config.v
+1
-1
src/config.v
+21
src/pages.v
+21
src/pages.v
···
143
143
replying_to_user := User{}
144
144
return $veb.html('templates/new_post.html')
145
145
}
146
+
147
+
@['/tag/:tag']
148
+
fn (mut app App) tag(mut ctx Context, tag string) veb.Result {
149
+
user := app.whoami(mut ctx) or {
150
+
ctx.error('not logged in')
151
+
return ctx.redirect('/login')
152
+
}
153
+
ctx.title = '${app.config.instance.name} - #${tag}'
154
+
offset := 0
155
+
return $veb.html()
156
+
}
157
+
158
+
@['/tag/:tag/:offset']
159
+
fn (mut app App) tag_with_offset(mut ctx Context, tag string, offset int) veb.Result {
160
+
user := app.whoami(mut ctx) or {
161
+
ctx.error('not logged in')
162
+
return ctx.redirect('/login')
163
+
}
164
+
ctx.title = '${app.config.instance.name} - #${tag}'
165
+
return $veb.html('templates/tag.html')
166
+
}
+12
-3
src/static/js/render_body.js
+12
-3
src/static/js/render_body.js
···
18
18
}
19
19
const link = document.createElement('a')
20
20
link.href = `/user/${match[0].substring(2, match[0].length - 1)}`
21
-
link.innerText = s
21
+
link.innerText = '@' + s
22
22
cache[match[0]] = link.outerHTML
23
23
element.innerHTML = element.innerHTML.replace(match[0], link.outerHTML)
24
24
})
25
25
}
26
-
// hashtag
26
+
// tags
27
27
else if (match[0][0] == '#') {
28
+
// we do not cache tags because they do not need to do
29
+
// any http queries, and most people will not use the
30
+
// same tag multiple times in a single post.
31
+
const link = document.createElement('a')
32
+
const tag = match[0].substring(2, match[0].length - 1)
33
+
link.href = `/tag/${tag}`
34
+
link.innerText = '#' + tag
35
+
cache[match[0]] = link.outerHTML
36
+
element.innerHTML = element.innerHTML.replace(match[0], link.outerHTML)
28
37
}
29
38
// post reference
30
39
else if (match[0][0] == '*') {
···
38
47
}
39
48
const link = document.createElement('a')
40
49
link.href = `/post/${match[0].substring(2, match[0].length - 1)}`
41
-
link.innerText = s
50
+
link.innerText = '*' + s
42
51
cache[match[0]] = link.outerHTML
43
52
element.innerHTML = element.innerHTML.replace(match[0], link.outerHTML)
44
53
})
+15
src/templates/post.html
+15
src/templates/post.html
···
70
70
<input type="submit" value="delete">
71
71
</form>
72
72
73
+
<form action="/api/post/pin" method="post">
74
+
<input
75
+
type="number"
76
+
name="id"
77
+
id="id"
78
+
placeholder="post id"
79
+
value="@post.id"
80
+
required
81
+
readonly
82
+
hidden
83
+
aria-hidden
84
+
>
85
+
<input type="submit" value="pin">
86
+
</form>
87
+
73
88
</div>
74
89
@end
75
90
</div>
+11
src/templates/tag.html
+11
src/templates/tag.html
+14
-14
src/templates/user.html
+14
-14
src/templates/user.html
···
23
23
<div>
24
24
<form action="/api/post/new_post" method="post">
25
25
<h2>new post:</h2>
26
+
26
27
<input
27
28
type="text"
28
29
name="title"
···
31
32
maxlength="@app.config.post.title_max_len"
32
33
pattern="@app.config.post.title_pattern"
33
34
placeholder="title"
34
-
required
35
+
required aria-required
35
36
>
36
37
<br>
38
+
37
39
<textarea
38
40
name="body"
39
41
id="body"
···
42
44
rows="10"
43
45
cols="30"
44
46
placeholder="body"
45
-
required
47
+
required aria-required
46
48
></textarea>
47
49
<br>
50
+
48
51
<input type="submit" value="post!">
49
52
</form>
50
53
</div>
···
84
87
name="id"
85
88
id="id"
86
89
value="@user.id"
87
-
required
88
-
readonly
89
-
hidden
90
-
aria-hidden
90
+
required aria-required
91
+
readonly aria-readonly
92
+
hidden aria-hidden
91
93
>
92
94
@if !user.muted
93
95
<input
···
95
97
name="muted"
96
98
id="muted"
97
99
value="true"
98
-
checked
99
-
readonly
100
-
hidden
101
-
aria-hidden
100
+
checked aria-checked
101
+
readonly aria-readonly
102
+
hidden aria-hidden
102
103
>
103
104
<input type="submit" value="mute">
104
105
@else
···
107
108
name="muted"
108
109
id="muted"
109
110
value="false"
110
-
checked
111
-
readonly
112
-
hidden
113
-
aria-hidden
111
+
checked aria-checked
112
+
readonly aria-readonly
113
+
hidden aria-hidden
114
114
>
115
115
<input type="submit" value="unmute">
116
116
@end