Compare commits
5 Commits
9b42d05174
...
7d761bae2e
Author | SHA1 | Date | |
---|---|---|---|
7d761bae2e | |||
7f10dde1ea | |||
9438d3704b | |||
16127983ab | |||
1cb9262ad7 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -3,5 +3,6 @@ nginx.conf.compiled
|
||||
db.*.sqlite
|
||||
.vscode/
|
||||
.local/
|
||||
static/avatars/
|
||||
static/avatars/*
|
||||
!static/avatars/default.webp
|
||||
secrets.lua
|
||||
|
@ -22,7 +22,27 @@ local ThreadCreateError = {
|
||||
}
|
||||
|
||||
app:get("all_topics", "", function(self)
|
||||
self.topic_list = db.query("select * from topics limit 25;")
|
||||
self.topic_list = db.query([[
|
||||
SELECT
|
||||
topics.name, topics.slug, topics.description, topics.is_locked,
|
||||
users.username AS latest_thread_username,
|
||||
threads.title AS latest_thread_title,
|
||||
threads.slug AS latest_thread_slug,
|
||||
threads.created_at AS latest_thread_created_at
|
||||
FROM
|
||||
topics
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
*,
|
||||
row_number() OVER (PARTITION BY threads.topic_id ORDER BY threads.created_at DESC) as rn
|
||||
FROM
|
||||
threads
|
||||
) threads ON threads.topic_id = topics.id AND threads.rn = 1
|
||||
LEFT JOIN
|
||||
users on users.id = threads.user_id
|
||||
ORDER BY
|
||||
topics.sort_order ASC
|
||||
]])
|
||||
self.me = util.get_logged_in_user_or_transient(self)
|
||||
return {render = "topics.topics"}
|
||||
end)
|
||||
@ -50,10 +70,12 @@ app:post("topic_create", "/create", function(self)
|
||||
local time = os.time()
|
||||
local slug = lapis_util.slugify(topic_name) .. "-" .. time
|
||||
|
||||
local topic_count = Topics:count()
|
||||
local topic = Topics:create({
|
||||
name = topic_name,
|
||||
description = topic_description,
|
||||
slug = slug,
|
||||
sort_order = topic_count + 1,
|
||||
})
|
||||
|
||||
util.inject_infobox(self, "Topic created.")
|
||||
|
@ -150,8 +150,10 @@ app:post("user_clear_avatar", "/:username/clear_avatar", function(self)
|
||||
if me.id ~= target_user.id then
|
||||
return {redirect_to = self:url_for("user", {username = self.params.username})}
|
||||
end
|
||||
local old_avatar_id = target_user.avatar_id
|
||||
util.destroy_avatar(old_avatar_id)
|
||||
target_user:update({
|
||||
avatar_id = db.NULL,
|
||||
avatar_id = 1,
|
||||
})
|
||||
util.inject_infobox(self, "Avatar cleared.")
|
||||
return {redirect_to = self:url_for("user_settings", {username = self.params.username})}
|
||||
|
@ -4,6 +4,17 @@ local constants = require("constants")
|
||||
|
||||
local alphabet = "-_@0123456789abcdefghijklmnopqrstuvwABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
|
||||
local function create_default_avatar()
|
||||
if models.Avatars:count() > 0 then
|
||||
print("default avatar must exist")
|
||||
return
|
||||
end
|
||||
models.Avatars:create({
|
||||
file_path = "/avatars/default.webp",
|
||||
uploaded_at = os.time(),
|
||||
})
|
||||
end
|
||||
|
||||
local function create_admin()
|
||||
local username = "admin"
|
||||
local root_count = models.Users:count("username = ?", username)
|
||||
@ -44,5 +55,6 @@ local function create_deleted_user()
|
||||
})
|
||||
end
|
||||
|
||||
create_default_avatar()
|
||||
create_admin()
|
||||
create_deleted_user()
|
||||
|
@ -52,4 +52,15 @@ return {
|
||||
[6] = function ()
|
||||
schema.drop_column("post_history", "user_id")
|
||||
end,
|
||||
|
||||
[7] = function ()
|
||||
db.query('DROP INDEX "idx_users_avatar"')
|
||||
schema.drop_column("users", "avatar_id")
|
||||
schema.add_column("users", "avatar_id", "REFERENCES avatars(id) DEFAULT 1")
|
||||
end,
|
||||
|
||||
[8] = function ()
|
||||
schema.add_column("topics", "sort_order", types.integer{default = 0})
|
||||
db.query("UPDATE topics SET sort_order = (SELECT COUNT(*) FROM topics t2 WHERE t2.ROWID <= topics.ROWID)")
|
||||
end
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ function Users_mt:is_logged_in_guest()
|
||||
end
|
||||
|
||||
function Users_mt:is_default_avatar()
|
||||
return self.avatar_id == nil
|
||||
return self.avatar_id == 1
|
||||
end
|
||||
|
||||
function Users_mt:is_logged_in()
|
||||
|
@ -35,6 +35,10 @@ $button_color: color.adjust($accent_color, $hue: 90);
|
||||
&:active {
|
||||
background-color: color.scale($color, $lightness: -10%, $saturation: -70%);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: color.scale($color, $lightness: 30%, $saturation: -90%);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin navbar($color) {
|
||||
@ -338,3 +342,27 @@ input[type="text"], input[type="password"], textarea, select {
|
||||
text-overflow: ellipsis;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.topic {
|
||||
display: grid;
|
||||
grid-template-columns: 1.5fr 64px;
|
||||
grid-template-rows: 1fr;
|
||||
gap: 0px 0px;
|
||||
grid-auto-flow: row;
|
||||
grid-template-areas:
|
||||
"topic-info-container topic-locked-container";
|
||||
}
|
||||
|
||||
.topic-info-container {
|
||||
grid-area: topic-info-container;
|
||||
background-color: $accent_color;
|
||||
padding: 5px 20px;
|
||||
border: 1px solid black;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.topic-locked-container {
|
||||
grid-area: topic-locked-container;
|
||||
border: 2px outset $light;
|
||||
}
|
||||
|
BIN
static/avatars/default.webp
Normal file
BIN
static/avatars/default.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.3 KiB |
@ -164,6 +164,9 @@ button:hover, input[type=submit]:hover, .linkbutton:hover {
|
||||
button:active, input[type=submit]:active, .linkbutton:active {
|
||||
background-color: rgb(166.6881496063, 178.0118503937, 177.4261417323);
|
||||
}
|
||||
button:disabled, input[type=submit]:disabled, .linkbutton:disabled {
|
||||
background-color: rgb(209.535, 211.565, 211.46);
|
||||
}
|
||||
button.critical, input[type=submit].critical, .linkbutton.critical {
|
||||
color: white;
|
||||
background-color: red;
|
||||
@ -174,6 +177,9 @@ button.critical:hover, input[type=submit].critical:hover, .linkbutton.critical:h
|
||||
button.critical:active, input[type=submit].critical:active, .linkbutton.critical:active {
|
||||
background-color: rgb(149.175, 80.325, 80.325);
|
||||
}
|
||||
button.critical:disabled, input[type=submit].critical:disabled, .linkbutton.critical:disabled {
|
||||
background-color: rgb(174.675, 156.825, 156.825);
|
||||
}
|
||||
button.warn, input[type=submit].warn, .linkbutton.warn {
|
||||
background-color: #fbfb8d;
|
||||
}
|
||||
@ -183,6 +189,9 @@ button.warn:hover, input[type=submit].warn:hover, .linkbutton.warn:hover {
|
||||
button.warn:active, input[type=submit].warn:active, .linkbutton.warn:active {
|
||||
background-color: rgb(198.3813559322, 198.3813559322, 154.4186440678);
|
||||
}
|
||||
button.warn:disabled, input[type=submit].warn:disabled, .linkbutton.warn:disabled {
|
||||
background-color: rgb(217.55, 217.55, 209.85);
|
||||
}
|
||||
|
||||
input[type=file]::file-selector-button {
|
||||
background-color: rgb(177, 206, 204.5);
|
||||
@ -194,6 +203,9 @@ input[type=file]::file-selector-button:hover {
|
||||
input[type=file]::file-selector-button:active {
|
||||
background-color: rgb(166.6881496063, 178.0118503937, 177.4261417323);
|
||||
}
|
||||
input[type=file]::file-selector-button:disabled {
|
||||
background-color: rgb(209.535, 211.565, 211.46);
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 15px 0;
|
||||
@ -213,6 +225,9 @@ p {
|
||||
.pagebutton:active {
|
||||
background-color: rgb(166.6881496063, 178.0118503937, 177.4261417323);
|
||||
}
|
||||
.pagebutton:disabled {
|
||||
background-color: rgb(209.535, 211.565, 211.46);
|
||||
}
|
||||
|
||||
.currentpage {
|
||||
border: none;
|
||||
@ -326,3 +341,26 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus, select:focus
|
||||
text-overflow: ellipsis;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.topic {
|
||||
display: grid;
|
||||
grid-template-columns: 1.5fr 64px;
|
||||
grid-template-rows: 1fr;
|
||||
gap: 0px 0px;
|
||||
grid-auto-flow: row;
|
||||
grid-template-areas: "topic-info-container topic-locked-container";
|
||||
}
|
||||
|
||||
.topic-info-container {
|
||||
grid-area: topic-info-container;
|
||||
background-color: #c1ceb1;
|
||||
padding: 5px 20px;
|
||||
border: 1px solid black;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.topic-locked-container {
|
||||
grid-area: topic-locked-container;
|
||||
border: 2px outset rgb(217.26, 220.38, 213.42);
|
||||
}
|
||||
|
26
util.lua
26
util.lua
@ -34,9 +34,6 @@ util.TransientUser = {
|
||||
}
|
||||
|
||||
function util.get_user_avatar_url(req, user)
|
||||
if not user.avatar_id then
|
||||
return "/avatars/default.webp"
|
||||
end
|
||||
return Avatars:find(user.avatar_id).file_path
|
||||
end
|
||||
|
||||
@ -72,6 +69,29 @@ function util.validate_and_create_image(input_image, filename)
|
||||
return true
|
||||
end
|
||||
|
||||
function util.destroy_avatar(avatar_id)
|
||||
if avatar_id == 1 then
|
||||
print("won't delete default avatar")
|
||||
return
|
||||
end
|
||||
|
||||
local avatar = Avatars:find(avatar_id)
|
||||
|
||||
if not avatar then
|
||||
return
|
||||
end
|
||||
|
||||
local file_path = "static" .. avatar.file_path
|
||||
local f = io.open(file_path, "r")
|
||||
if not f then
|
||||
print("can't open avatar file")
|
||||
else
|
||||
f:close()
|
||||
os.remove(file_path)
|
||||
avatar:delete()
|
||||
end
|
||||
end
|
||||
|
||||
function util.get_logged_in_user(req)
|
||||
if req.session.session_key == nil then
|
||||
return nil
|
||||
|
@ -1,7 +1,7 @@
|
||||
<div class="post" id="post-<%= post.id %>">
|
||||
<div class="usercard">
|
||||
<a href="<%= url_for("user", {username = post.username}) %>" style="display: contents;">
|
||||
<img src="<%= post.avatar_path or "/avatars/default.webp" %>" class="avatar">
|
||||
<img src="<%= post.avatar_path %>" class="avatar">
|
||||
</a>
|
||||
<a href="<%= url_for("user", {username = post.username}) %>" class="username-link"><%= post.username %></a>
|
||||
<% if post.status ~= "" then %>
|
||||
|
@ -1,6 +1,10 @@
|
||||
<h1>Create topic</h1>
|
||||
<form method="post">
|
||||
<input type="text" name="name" id="name" placeholder="Topic name" required><br>
|
||||
<textarea id="description" name="description" placeholder="Topic description" required></textarea><br>
|
||||
<input type="submit" value="Create topic">
|
||||
</form>
|
||||
<div class="darkbg settings-container">
|
||||
<h1>Create topic</h1>
|
||||
<form method="post">
|
||||
<label for=name>Name</label>
|
||||
<input type="text" name="name" id="name" required><br>
|
||||
<label for=description>Description</label>
|
||||
<textarea id="description" name="description" required rows=5></textarea><br>
|
||||
<input type="submit" value="Create topic">
|
||||
</form>
|
||||
</div>
|
||||
|
@ -1,16 +1,33 @@
|
||||
<h1>Topics</h1>
|
||||
<nav class="darkbg">
|
||||
<h1 class="thread-title">All topics</h1>
|
||||
<% if me:is_mod() then %>
|
||||
<a class="linkbutton" href="<%= url_for("topic_create") %>">Create new topic</a>
|
||||
<% end %>
|
||||
</nav>
|
||||
|
||||
<% if #topic_list == 0 then %>
|
||||
<p>There are no topics.</p>
|
||||
<p>There are no topics.</p>
|
||||
<% else %>
|
||||
<ul>
|
||||
<% for i, v in ipairs(topic_list) do %>
|
||||
<li>
|
||||
<a href=<%= url_for("topic", {slug = v.slug}) %>><%= v.name %></a> - <%= v.description %>
|
||||
</li>
|
||||
<% for _, topic in ipairs(topic_list) do %>
|
||||
<% local is_locked = ntob(topic.is_locked) %>
|
||||
<div class="topic">
|
||||
<div class="topic-info-container">
|
||||
<a href=<%= url_for("topic", {slug = topic.slug}) %>><%= topic.name %></a>
|
||||
<%= topic.description %>
|
||||
<% if topic.latest_thread_username then %>
|
||||
<span>
|
||||
Latest thread: <a href="<%= url_for("thread", {slug = topic.latest_thread_slug}) %>"><%= topic.latest_thread_title %></a> by <a href="<%= url_for("user", {username = topic.latest_thread_username}) %>"><%= topic.latest_thread_username %></a> on <%= os.date("%c", topic.latest_thread_created_at) %>
|
||||
</span>
|
||||
<% else %>
|
||||
<i>No threads yet.</i>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="topic-locked-container contain-svg">
|
||||
<% if is_locked then -%>
|
||||
<% render("svg-icons.lock") %>
|
||||
<i>Locked</i>
|
||||
<% end -%>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% if me:is_mod() then %>
|
||||
<a href="<%= url_for("topic_create") %>">Create new topic</a>
|
||||
<% end %>
|
||||
|
@ -1,3 +1,4 @@
|
||||
<% local disable_avatar = me:is_logged_in_guest() %>
|
||||
<div class="darkbg settings-container">
|
||||
<h1>User settings</h1>
|
||||
<% if infobox then %>
|
||||
@ -7,7 +8,7 @@
|
||||
<img src="<%= avatar_url(me) %>">
|
||||
<input id="file" type="file" name="avatar" accept="image/*" required>
|
||||
<div>
|
||||
<input type="submit" value="Update avatar">
|
||||
<input type="submit" value="Update avatar" <%= disable_avatar and "disabled=disabled" %>>
|
||||
<% if not me:is_default_avatar() then %>
|
||||
<input type="submit" value="Clear avatar" formaction="<%= url_for("user_clear_avatar", {username = me.username}) %>" formnovalidate>
|
||||
<% end %>
|
||||
|
Loading…
Reference in New Issue
Block a user