+2
config.maple
+2
config.maple
+39
doc/themes.md
+39
doc/themes.md
···
1
+
# themes
2
+
3
+
beep supports per-user themes, because we like customizability. by default, no
4
+
theme is selected and beep is 100% plain html.
5
+
6
+
themes can be applied to beep by going to your profile and scrolling to your
7
+
settings, then changing the theme url.
8
+
9
+
here is a list of themes that work with beep and themes for beep!
10
+
11
+
## drop-in
12
+
13
+
> note that these themes were **not** made specifically for beep, they just work
14
+
> well out-of-the-box with it!
15
+
16
+
| name | source | css theme url |
17
+
|-------------------|----------------------------------------|---------------------------------------------------------------|
18
+
| sakura | <https://github.com/oxalorg/sakura> | https://cdn.jsdelivr.net/npm/sakura.css/css/sakura.css |
19
+
| water.css (auto) | <https://github.com/kognise/water.css> | https://cdn.jsdelivr.net/npm/water.css@2/out/water.min.css |
20
+
| water.css (dark) | <https://github.com/kognise/water.css> | https://cdn.jsdelivr.net/npm/water.css@2/out/dark.min.css |
21
+
| water.css (light) | <https://github.com/kognise/water.css> | https://cdn.jsdelivr.net/npm/water.css@2/out/light.min.css |
22
+
23
+
> here is a big list of drop-in themes:
24
+
> <https://github.com/dohliam/dropin-minimal-css>
25
+
26
+
## beep-specific
27
+
28
+
| name | source | css theme url |
29
+
|------|--------|---------------|
30
+
| | | |
31
+
32
+
> there is nothing here yet! do you want to be the one to change that?
33
+
34
+
## built-in
35
+
36
+
(todo :D)
37
+
38
+
> beep also features some built-in themes, some of which are based on the themes
39
+
> present in the "it just works" list!
+33
-9
src/api.v
+33
-9
src/api.v
···
133
133
}
134
134
135
135
@['/api/user/set_nickname'; post]
136
-
fn (mut app App) api_user_set_nickname(mut ctx Context) veb.Result {
137
-
mut nickname := ?string(ctx.form['nickname'] or { '' })
138
-
nickname = sanatize(nickname or { '' })
139
-
if (nickname or { '' }) == '' {
140
-
nickname = none
141
-
}
142
-
136
+
fn (mut app App) api_user_set_nickname(mut ctx Context, nickname string) veb.Result {
143
137
user := app.whoami(mut ctx) or {
144
138
ctx.error('you are not logged in!')
145
139
return ctx.redirect('/login')
146
140
}
147
141
142
+
mut sanatized_nickname := ?string(sanatize(nickname).trim_space())
143
+
if sanatized_nickname or { '' } == '' {
144
+
sanatized_nickname = none
145
+
}
146
+
148
147
sql app.db {
149
-
update User set nickname = nickname where id == user.id
148
+
update User set nickname = sanatized_nickname where id == user.id
150
149
} or {
151
150
ctx.error('failed to change nickname')
152
-
eprintln('failed to update nickname for ${user} (${user.nickname} -> ${nickname})')
151
+
eprintln('failed to update nickname for ${user} (${user.nickname} -> ${sanatized_nickname})')
153
152
return ctx.redirect('/me')
154
153
}
155
154
···
177
176
eprintln('insufficient perms to update mute status for ${user} (${user.muted} -> ${muted})')
178
177
return ctx.redirect('/user/${user.username}')
179
178
}
179
+
}
180
+
181
+
@['/api/user/set_theme'; post]
182
+
fn (mut app App) api_user_set_theme(mut ctx Context, url string) veb.Result {
183
+
user := app.whoami(mut ctx) or {
184
+
ctx.error('you are not logged in!')
185
+
return ctx.redirect('/login')
186
+
}
187
+
188
+
mut theme := ?string(none)
189
+
if url.trim_space() != '' {
190
+
theme = sanatize(url).trim_space()
191
+
}
192
+
193
+
println('set theme to: ${theme}')
194
+
195
+
sql app.db {
196
+
update User set theme = theme where id == user.id
197
+
} or {
198
+
ctx.error('failed to change theme')
199
+
eprintln('failed to update theme for ${user} (${user.theme} -> ${theme})')
200
+
return ctx.redirect('/me')
201
+
}
202
+
203
+
return ctx.redirect('/me')
180
204
}
181
205
182
206
////// Posts //////
+10
-1
src/app.v
+10
-1
src/app.v
···
1
1
module main
2
2
3
-
import auth
3
+
import veb
4
4
import db.pg
5
+
import auth
5
6
import entity { User, Post, Like, LikeCache }
6
7
7
8
pub struct App {
9
+
veb.StaticHandler
8
10
pub:
9
11
config Config
10
12
pub mut:
···
76
78
return none
77
79
}
78
80
return posts[0]
81
+
}
82
+
83
+
pub fn (app &App) get_pinned_posts() []Post {
84
+
posts := sql app.db {
85
+
select from Post where pinned == true
86
+
} or { [] }
87
+
return posts
79
88
}
80
89
81
90
pub fn (app &App) whoami(mut ctx Context) ?User {
+3
-1
src/config.v
+3
-1
src/config.v
···
4
4
5
5
pub struct Config {
6
6
pub mut:
7
-
dev_mode bool
7
+
dev_mode bool
8
+
static_path string
8
9
http struct {
9
10
pub mut:
10
11
port int
···
41
42
mut config := Config{}
42
43
43
44
config.dev_mode = loaded.get('dev_mode').to_bool()
45
+
config.static_path = loaded.get('static_path').to_str()
44
46
45
47
loaded_http := loaded.get('http')
46
48
config.http.port = loaded_http.get('port').to_int()
+2
src/entity/post.v
+2
src/entity/post.v
+7
src/entity/user.v
+7
src/entity/user.v
···
15
15
muted bool
16
16
admin bool
17
17
18
+
theme ?string
19
+
18
20
created_at time.Time = time.now()
19
21
}
20
22
···
22
24
pub fn (user User) get_name() string {
23
25
return user.nickname or { user.username }
24
26
}
27
+
28
+
@[inline]
29
+
pub fn (user User) get_theme() string {
30
+
return user.theme or { '' }
31
+
}
+3
-1
src/main.v
+3
-1
src/main.v
···
36
36
auth: auth.new(db)
37
37
}
38
38
39
+
app.mount_static_folder_at(app.config.static_path, '/static')!
40
+
39
41
init_db(db)!
40
42
41
43
if config.dev_mode {
42
-
println('NOTE: YOU ARE IN DEV MODE')
44
+
println('\033[1;31mNOTE: YOU ARE IN DEV MODE\033[0m')
43
45
}
44
46
45
47
veb.run[App, Context](mut app, app.config.http.port)
+2
-1
src/pages.v
+2
-1
src/pages.v
···
5
5
6
6
fn (mut app App) index(mut ctx Context) veb.Result {
7
7
ctx.title = 'beep'
8
+
user := app.whoami(mut ctx) or { User{} }
8
9
recent_posts := app.get_recent_posts()
9
-
user := app.whoami(mut ctx) or { User{} }
10
+
pinned_posts := app.get_pinned_posts()
10
11
return $veb.html()
11
12
}
12
13
src/static/style.css
src/static/style.css
This is a binary file and will not be displayed.
+1
src/templates/assets/style.html
+1
src/templates/assets/style.html
···
1
+
<link rel="stylesheet" href="/static/style.css">
+1
-1
src/templates/components/post.html
+1
-1
src/templates/components/post.html
···
1
-
<div>
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
3
<p>likes: @{app.get_net_likes_for_post(post.id)}</p>
4
4
<p>@post.body</p>
+6
src/templates/components/post_mini.html
+6
src/templates/components/post_mini.html
···
1
+
<div class="post post-mini">
2
+
<p>
3
+
<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>:
4
+
<a href="/post/@post.id">@post.title</a>
5
+
</p>
6
+
</div>
+2
-2
src/templates/components/post_small.html
+2
-2
src/templates/components/post_small.html
···
1
-
<div>
2
-
<p><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</p>
1
+
<div class="post post-small">
2
+
<p><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>: <span class="post-title">@post.title</span></p>
3
3
@if post.body.len > 50
4
4
<p>@{post.body[..50]}...</p>
5
5
@else
+10
-1
src/templates/index.html
+10
-1
src/templates/index.html
···
7
7
@end
8
8
9
9
<div>
10
+
@if pinned_posts.len > 0
11
+
<h2>pinned posts:</h2>
12
+
<div>
13
+
@for post in pinned_posts
14
+
@include 'components/post_small.html'
15
+
@end
16
+
</div>
17
+
@end
18
+
10
19
<h2>recent posts:</h2>
11
20
<div>
12
21
@if recent_posts.len > 0
13
-
@for post in app.get_recent_posts()
22
+
@for post in recent_posts
14
23
@include 'components/post_small.html'
15
24
@end
16
25
@else
+7
-1
src/templates/me.html
+7
-1
src/templates/me.html
···
58
58
value="@{user.nickname or { '' }}"
59
59
required
60
60
>
61
-
<input type="submit" value="update">
61
+
<input type="submit" value="save">
62
62
</form>
63
63
<form action="/api/user/set_nickname" method="post">
64
64
<input type="submit" value="reset nickname">
65
+
</form>
66
+
<br>
67
+
<form action="/api/user/set_theme" method="post">
68
+
<label for="url">theme:</label>
69
+
<input type="url" name="url" id="url" value="@{user.theme or { '' }}">
70
+
<input type="submit" value="save">
65
71
</form>
66
72
</div>
67
73
+4
src/templates/partial/header.html
+4
src/templates/partial/header.html
···
8
8
<meta name="viewport" content="width=device-width, initial-scale=1" />
9
9
<title>@ctx.title</title>
10
10
@include 'assets/style.html'
11
+
@if ctx.is_logged_in() && user.theme != none
12
+
<link rel="stylesheet" href="@user.get_theme()">
13
+
@endif
11
14
</head>
12
15
13
16
<body>
···
39
42
</header>
40
43
41
44
<main>
45
+
<!-- TODO: fix this lol -->
42
46
@if ctx.form_error != ''
43
47
<div>
44
48
<p><strong>error:</strong> ctx.form_error</p>