Compare commits

...

10 Commits

Author SHA1 Message Date
2eddb70d63
add user page markup 2025-05-20 17:05:45 +03:00
3bd474d7fe
use 'me' instead of 'user' consistently 2025-05-20 14:28:23 +03:00
82b25946a0
buttons 2025-05-20 13:49:14 +03:00
a1055b0c43
correct some checks in user view 2025-05-20 13:20:34 +03:00
7cc16047cb
add page titles 2025-05-20 13:12:50 +03:00
8c7ef09567
redirect to topics on root 2025-05-20 13:12:31 +03:00
f1f218fc75
split top nav into its own view 2025-05-20 13:12:05 +03:00
8609c33f00
add thread view 2025-05-20 12:30:41 +03:00
9b689a08e2
add sass 2025-05-20 06:56:14 +03:00
c473d2b1a0
more correct babycode parsing 2025-05-20 06:46:36 +03:00
20 changed files with 692 additions and 96 deletions

2
.gitignore vendored
View File

@ -3,5 +3,5 @@ nginx.conf.compiled
db.*.sqlite
.vscode/
.local/
static/
static/avatars/
secrets.lua

View File

@ -25,8 +25,8 @@ app:include("apps.users", {path = "/user"})
app:include("apps.topics", {path = "/topics"})
app:include("apps.threads", {path = "/threads"})
app:get("/", function()
return "Welcome to Lapis " .. require("lapis.version")
app:get("/", function(self)
return {redirect_to = self:url_for("all_topics")}
end)
return app

View File

@ -9,6 +9,8 @@ local Topics = models.Topics
local Threads = models.Threads
local Posts = models.Posts
local POSTS_PER_PAGE = 10
app:get("thread_create", "/create", function(self)
local user = util.get_logged_in_user(self)
if not user then
@ -20,6 +22,8 @@ app:get("thread_create", "/create", function(self)
return "how did you get here?"
end
self.all_topics = all_topics
self.page_title = "creating thread"
self.me = user
return {render = "threads.create"}
end)
@ -57,7 +61,6 @@ app:post("thread_create", "/create", function(self)
end)
app:get("thread", "/:slug", function(self)
local posts_per_page = 10
local thread = Threads:find({
slug = self.params.slug
})
@ -65,14 +68,26 @@ app:get("thread", "/:slug", function(self)
return {status = 404}
end
self.thread = thread
if self.params.after then
local after_id = tonumber(self.params.after)
local post_position = Posts:count(db.clause({
thread_id = thread.id,
{"id <= ?", after_id},
}))
self.page = math.floor((post_position - 1) / POSTS_PER_PAGE) + 1
else
self.page = tonumber(self.params.page) or 1
end
local post_count = Posts:count(db.clause({
thread_id = thread.id
}))
self.pages = math.ceil(post_count / posts_per_page)
self.page = tonumber(self.params.page) or 1
self.pages = math.ceil(post_count / POSTS_PER_PAGE)
-- self.page = math.max(1, math.min(self.page, self.pages))
local posts = db.query([[
SELECT
posts.id, post_history.content, users.username, avatars.file_path AS avatar_path
posts.id, posts.created_at, post_history.content, post_history.edited_at, users.username, users.status, avatars.file_path AS avatar_path
FROM
posts
JOIN
@ -86,10 +101,13 @@ app:get("thread", "/:slug", function(self)
ORDER BY
posts.created_at ASC
LIMIT ? OFFSET ?
]], thread.id, posts_per_page, (self.page - 1) * posts_per_page)
]], thread.id, POSTS_PER_PAGE, (self.page - 1) * POSTS_PER_PAGE)
self.topic = Topics:find(thread.topic_id)
self.user = util.get_logged_in_user_or_transient(self)
self.me = util.get_logged_in_user_or_transient(self)
self.posts = posts
self.page_title = thread.title
return {render = "threads.thread"}
end)
@ -115,11 +133,15 @@ app:post("thread", "/:slug", function(self)
local post_content = self.params.post_content
local post = util.create_post(thread.id, user.id, post_content)
local post_count = Posts:count(db.clause({
thread_id = thread.id
}))
local last_page = math.ceil(post_count / POSTS_PER_PAGE)
if not post then
return {redirect_to = self:url_for("thread", {slug = thread.slug})}
return {redirect_to = self:url_for("thread", {slug = thread.slug}, {page = last_page}) .. "#latest-post"}
end
return {redirect_to = self:url_for("thread", {slug = thread.slug})}
return {redirect_to = self:url_for("thread", {slug = thread.slug}, {page = last_page}) .. "#latest-post"}
end)
return app

View File

@ -21,7 +21,7 @@ local ThreadCreateError = {
app:get("all_topics", "", function(self)
self.topic_list = db.query("select * from topics limit 25;")
self.user = util.get_logged_in_user(self) or util.TransientUser
self.me = util.get_logged_in_user_or_transient(self)
return {render = "topics.topics"}
end)
@ -31,6 +31,9 @@ app:get("topic_create", "/create", function(self)
return {status = 403}
end
self.page_title = "creating topic"
self.me = user
return {render = "topics.create"}
end)
@ -65,7 +68,7 @@ app:get("topic", "/:slug", function(self)
self.threads_list = db.query("SELECT * FROM threads WHERE topic_id = ? ORDER BY is_stickied DESC, created_at DESC", topic.id)
local user = util.get_logged_in_user_or_transient(self)
print(topic.is_locked, type(topic.is_locked))
self.user = user
self.me = user
self.ThreadCreateError = ThreadCreateError
self.thread_create_error = ThreadCreateError.OK
if user:is_logged_in_guest() then
@ -76,6 +79,8 @@ app:get("topic", "/:slug", function(self)
self.thread_create_error = ThreadCreateError.TOPIC_LOCKED
end
self.page_title = "all threads in " .. topic.name
return {render = "topics.topic"}
end)
@ -91,6 +96,9 @@ app:get("topic_edit", "/:slug/edit", function(self)
return {redirect_to = self:url_for("all_topics")}
end
self.topic = topic
self.me = user
self.page_title = "editing topic " .. topic.name
return {render = "topics.edit"}
end)

View File

@ -71,8 +71,7 @@ app:get("user", "/:username", function(self)
self.session.flash = {}
end
-- local me = validate_session(self.session.session_key) or TransientUser
local me = util.get_logged_in_user(self) or util.TransientUser
local me = util.get_logged_in_user_or_transient(self)
self.user = user
self.me = me
@ -83,6 +82,26 @@ app:get("user", "/:username", function(self)
return {status = 404}
end
end
self.latest_posts = db.query([[
SELECT
posts.id, posts.created_at, post_history.content, post_history.edited_at, threads.title AS thread_title, topics.name as topic_name, threads.slug as thread_slug
FROM
posts
JOIN
post_history ON posts.current_revision_id = post_history.id
JOIN
threads ON posts.thread_id = threads.id
JOIN
topics ON threads.topic_id = topics.id
WHERE
posts.user_id = ?
ORDER BY posts.created_at DESC
LIMIT 10
]], user.id)
self.page_title = user.username .. "'s profile"
return {render = "user.user"}
end)
@ -128,7 +147,9 @@ app:get("user_delete_confirm", "/:username/delete_confirm", function(self)
self.err = self.session.flash.error
self.session.flash = {}
end
self.user = target_user
self.me = target_user
self.page_title = "confirm deletion"
return {render = "user.delete_confirm"}
end)
@ -206,7 +227,9 @@ app:get("user_settings", "/:username/settings", function(self)
self.flash_msg = flash.error
end
end
self.user = target_user
self.me = target_user
self.page_title = "settings"
return {render = "user.settings"}
end)
@ -245,6 +268,9 @@ app:get("user_login", "/login", function(self)
self.err = self.session.flash.error
self.session.flash = {}
end
self.page_title = "log in"
return {render = "user.login"}
end)
@ -287,6 +313,9 @@ app:get("user_signup", "/signup", function(self)
self.err = self.session.flash.error
self.session.flash = {}
end
self.page_title = "sign up"
return {render = "user.signup"}
end)
@ -425,4 +454,4 @@ app:post("guest_user", "/guest_user/:user_id", function(self)
return {redirect_to = self:url_for("user", {username = target_user.username})}
end)
return app
return app

View File

@ -1,15 +1,5 @@
local babycode = {}
local _escape_html = function(text)
return text:gsub("[&<>\"']", {
["&"] = "&amp;",
["<"] = "&lt;",
[">"] = "&gt;",
['"'] = "&quot;",
["'"] = "&#39;"
})
end
---renders babycode to html
---@param s string input babycode
---@param escape_html fun(s: string): string function that escapes html
@ -21,7 +11,8 @@ function babycode.to_html(s, escape_html)
local code_count = 0
local text = s:gsub("%[code%](.-)%[/code%]", function(code)
code_count = code_count + 1
code_blocks[code_count] = code
-- strip leading and trailing newlines, preserve others
code_blocks[code_count] = code:gsub("^%s*(.-)%s*$", "%1")
return "\1CODE:"..code_count.."\1"
end)
@ -48,14 +39,14 @@ function babycode.to_html(s, escape_html)
return url
end)
-- normalize newlines, replace them with <br>
text = text:gsub("\r?\n\r?\n+", "<br>"):gsub("\r?\n", "<br>")
-- replace code block placeholders back with their original contents
text = text:gsub("\1CODE:(%d+)\1", function(n)
return "<pre><code>"..code_blocks[tonumber(n)].."</code></pre>"
end)
-- finally, normalize newlines replace them with <br>
text = text:gsub("\r?\n\r?\n+", "<br>"):gsub("\r?\n", "<br>")
return text
end

View File

@ -28,6 +28,10 @@ function Users_mt:is_default_avatar()
return self.avatar_id == nil
end
function Users_mt:is_logged_in()
return true
end
local ret = {
Users = Users,
Topics = Model:extend("topics"),

223
sass/style.scss Normal file
View File

@ -0,0 +1,223 @@
/* src: */
@use "sass:color";
$accent_color: #c1ceb1;
$dark_bg: color.scale($accent_color, $lightness: -25%, $saturation: -97%);
$dark2: color.scale($accent_color, $lightness: -30%, $saturation: -60%);
$light: color.scale($accent_color, $lightness: 40%, $saturation: -60%);
$main_bg: color.scale($accent_color, $lightness: -10%, $saturation: -40%);
$button_color: color.adjust($accent_color, $hue: 90);
%button-base {
cursor: default;
color: black;
font-size: 0.9rem;
text-decoration: none;
border: 1px solid black;
border-radius: 3px;
padding: 5px 20px;
margin: 10px 0;
}
@mixin button($color) {
@extend %button-base;
background-color: $color;
&:hover {
background-color: color.scale($color, $lightness: 20%);
}
&:active {
background-color: color.scale($color, $lightness: -10%, $saturation: -70%);
}
}
@mixin navbar($color) {
padding: 10px;
display: flex;
justify-content: end;
background-color: $color;
}
body {
font-family: sans-serif;
margin: 20px;
background-color: $main_bg;
}
.big {
font-size: 1.8rem;
}
#topnav {
@include navbar($accent_color);
justify-content: space-between;
align-items: center;
}
#bottomnav {
@include navbar($dark_bg);
}
.darkbg {
padding-bottom: 10px;
padding-left: 10px;
background-color: $dark_bg;
}
.user-actions {
display: flex;
column-gap: 15px;
}
.site-title {
display: inline;
padding-right: 30px;
}
.thread-title {
margin: 0;
}
.post {
display: grid;
grid-template-columns: 200px 1fr;
grid-template-rows: 1fr;
gap: 0;
grid-auto-flow: row;
grid-template-areas:
"usercard post-content-container";
border: 2px outset $dark2;
}
.usercard {
grid-area: usercard;
display: flex;
flex-direction: column;
align-items: center;
padding: 20px 10px;
border: 4px outset $light;
background-color: $dark_bg;
border-right: solid 2px;
}
.post-content-container {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 0.2fr 2.5fr;
gap: 0px 0px;
grid-auto-flow: row;
grid-template-areas:
"post-info"
"post-content";
grid-area: post-content-container;
}
.post-info {
grid-area: post-info;
display: flex;
justify-content: space-between;
padding: 5px 20px;
align-items: center;
border-top: 1px solid black;
border-bottom: 1px solid black;
}
.post-content {
grid-area: post-content;
padding: 5px 20px;
}
.user-posts {
display: grid;
grid-template-columns: 200px 1fr;
grid-template-rows: 1fr;
gap: 0;
grid-auto-flow: row;
grid-template-areas:
"user-page-usercard user-posts-container";
border: 2px outset $dark2;
}
.user-page-usercard {
grid-area: user-page-usercard;
display: flex;
flex-direction: column;
align-items: center;
padding: 20px 10px;
border: 4px outset $light;
background-color: $dark_bg;
border-right: solid 2px;
}
.user-posts-container {
grid-area: user-posts-container;
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 0.2fr 2.5fr;
gap: 0px 0px;
grid-auto-flow: row;
grid-template-areas:
"post-info"
"post-content";
}
.avatar {
width: 90%;
height: 90%;
object-fit: contain;
padding-bottom: 10px;
}
.username-link {
overflow-wrap: anywhere;
}
.user-status {
text-align: center;
}
button, input[type="submit"], .linkbutton {
@include button($button_color);
&.critical {
color: white;
@include button(red);
}
&.warn {
@include button(#fbfb8d);
}
}
// not sure why this one has to be separate, but if it's included in the rule above everything breaks
input[type="file"]::file-selector-button {
@include button($button_color);
}
.pagebutton {
@include button($button_color);
padding: 5px 5px;
margin: 0;
display: inline-block;
min-width: 20px;
text-align: center;
}
.currentpage {
@extend %button-base;
border: none;
padding: 5px 5px;
margin: 0;
display: inline-block;
min-width: 20px;
text-align: center;
}
.modform {
display: inline;
}

216
static/style.css Normal file
View File

@ -0,0 +1,216 @@
/* src: */
.currentpage, .pagebutton, input[type=file]::file-selector-button, button.warn, input[type=submit].warn, .linkbutton.warn, button.critical, input[type=submit].critical, .linkbutton.critical, button, input[type=submit], .linkbutton {
cursor: default;
color: black;
font-size: 0.9rem;
text-decoration: none;
border: 1px solid black;
border-radius: 3px;
padding: 5px 20px;
margin: 10px 0;
}
body {
font-family: sans-serif;
margin: 20px;
background-color: rgb(173.5214173228, 183.6737007874, 161.0262992126);
}
.big {
font-size: 1.8rem;
}
#topnav {
padding: 10px;
display: flex;
justify-content: end;
background-color: #c1ceb1;
justify-content: space-between;
align-items: center;
}
#bottomnav {
padding: 10px;
display: flex;
justify-content: end;
background-color: rgb(143.7039271654, 144.3879625984, 142.8620374016);
}
.darkbg {
padding-bottom: 10px;
padding-left: 10px;
background-color: rgb(143.7039271654, 144.3879625984, 142.8620374016);
}
.user-actions {
display: flex;
column-gap: 15px;
}
.site-title {
display: inline;
padding-right: 30px;
}
.thread-title {
margin: 0;
}
.post {
display: grid;
grid-template-columns: 200px 1fr;
grid-template-rows: 1fr;
gap: 0;
grid-auto-flow: row;
grid-template-areas: "usercard post-content-container";
border: 2px outset rgb(135.1928346457, 145.0974015748, 123.0025984252);
}
.usercard {
grid-area: usercard;
display: flex;
flex-direction: column;
align-items: center;
padding: 20px 10px;
border: 4px outset rgb(217.26, 220.38, 213.42);
background-color: rgb(143.7039271654, 144.3879625984, 142.8620374016);
border-right: solid 2px;
}
.post-content-container {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 0.2fr 2.5fr;
gap: 0px 0px;
grid-auto-flow: row;
grid-template-areas: "post-info" "post-content";
grid-area: post-content-container;
}
.post-info {
grid-area: post-info;
display: flex;
justify-content: space-between;
padding: 5px 20px;
align-items: center;
border-top: 1px solid black;
border-bottom: 1px solid black;
}
.post-content {
grid-area: post-content;
padding: 5px 20px;
}
.user-posts {
display: grid;
grid-template-columns: 200px 1fr;
grid-template-rows: 1fr;
gap: 0;
grid-auto-flow: row;
grid-template-areas: "user-page-usercard user-posts-container";
border: 2px outset rgb(135.1928346457, 145.0974015748, 123.0025984252);
}
.user-page-usercard {
grid-area: user-page-usercard;
display: flex;
flex-direction: column;
align-items: center;
padding: 20px 10px;
border: 4px outset rgb(217.26, 220.38, 213.42);
background-color: rgb(143.7039271654, 144.3879625984, 142.8620374016);
border-right: solid 2px;
}
.user-posts-container {
grid-area: user-posts-container;
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 0.2fr 2.5fr;
gap: 0px 0px;
grid-auto-flow: row;
grid-template-areas: "post-info" "post-content";
}
.avatar {
width: 90%;
height: 90%;
object-fit: contain;
padding-bottom: 10px;
}
.username-link {
overflow-wrap: anywhere;
}
.user-status {
text-align: center;
}
button, input[type=submit], .linkbutton {
background-color: rgb(177, 206, 204.5);
}
button:hover, input[type=submit]:hover, .linkbutton:hover {
background-color: rgb(192.6, 215.8, 214.6);
}
button:active, input[type=submit]:active, .linkbutton:active {
background-color: rgb(166.6881496063, 178.0118503937, 177.4261417323);
}
button.critical, input[type=submit].critical, .linkbutton.critical {
color: white;
background-color: red;
}
button.critical:hover, input[type=submit].critical:hover, .linkbutton.critical:hover {
background-color: #ff3333;
}
button.critical:active, input[type=submit].critical:active, .linkbutton.critical:active {
background-color: rgb(149.175, 80.325, 80.325);
}
button.warn, input[type=submit].warn, .linkbutton.warn {
background-color: #fbfb8d;
}
button.warn:hover, input[type=submit].warn:hover, .linkbutton.warn:hover {
background-color: rgb(251.8, 251.8, 163.8);
}
button.warn:active, input[type=submit].warn:active, .linkbutton.warn:active {
background-color: rgb(198.3813559322, 198.3813559322, 154.4186440678);
}
input[type=file]::file-selector-button {
background-color: rgb(177, 206, 204.5);
}
input[type=file]::file-selector-button:hover {
background-color: rgb(192.6, 215.8, 214.6);
}
input[type=file]::file-selector-button:active {
background-color: rgb(166.6881496063, 178.0118503937, 177.4261417323);
}
.pagebutton {
background-color: rgb(177, 206, 204.5);
padding: 5px 5px;
margin: 0;
display: inline-block;
min-width: 20px;
text-align: center;
}
.pagebutton:hover {
background-color: rgb(192.6, 215.8, 214.6);
}
.pagebutton:active {
background-color: rgb(166.6881496063, 178.0118503937, 177.4261417323);
}
.currentpage {
border: none;
padding: 5px 5px;
margin: 0;
display: inline-block;
min-width: 20px;
text-align: center;
}
.modform {
display: inline;
}

View File

@ -26,6 +26,9 @@ util.TransientUser = {
is_logged_in_guest = function (self)
return false
end,
is_logged_in = function (self)
return false
end,
username = "Deleted User",
}

View File

@ -2,7 +2,13 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Porom</title>
<% if page_title then %>
<title>Porom - <%= page_title %></title>
<% else %>
<title>Porom</title>
<% end %>
<% math.randomseed(os.time()) %>
<link rel="stylesheet" href="<%= "/static/style.css?" .. math.random(1, 100) %>">
</head>
<body>
<% content_for("inner") %>

View File

@ -0,0 +1,27 @@
<% local left_start = math.max(1, current_page - 5) %>
<% local right_end = math.min(page_count, current_page + 5) %>
<div class="pager">
<span>Page:</span>
<% if current_page > 5 then %>
<a href="?page=1" class="pagebutton">1</a>
<% if left_start > 2 then %>
<span class="currentpage">&hellip;</span>
<% end %>
<% end %>
<% for i = left_start, current_page - 1 do%>
<a href="?page=<%= i %>" class="pagebutton"><%= i %></a>
<% end %>
<% if page_count > 0 then %>
<span class="currentpage"><%= current_page %></span>
<% end %>
<% for i = current_page + 1, right_end do %>
<a href="?page=<%= i %>" class="pagebutton"><%= i %></a>
<% end %>
<% if right_end < page_count then %>
<% if right_end < page_count - 1 then %>
<span class="currentpage">&hellip;</span>
<% end %>
<a href="?page=<%= page_count %>" class="pagebutton"><%= page_count %></a>
<% end %>
</div>

13
views/common/topnav.etlua Normal file
View File

@ -0,0 +1,13 @@
<nav id="topnav">
<span>
<h1 class="site-title">Porom</h1>
<a href="<%= url_for("all_topics") %>">All topics</a>
</span>
<span>
<% if me:is_logged_in() then -%>
Welcome, <a href="<%= url_for("user", {username = me.username}) %>"><%= me.username %></a>
<% else -%>
Welcome, guest. Please <a href="<%= url_for("user_signup") %>">sign up</a> or <a href="<%= url_for("user_login") %>">log in</a>
<% end -%>
</span>
</nav>

26
views/threads/post.etlua Normal file
View File

@ -0,0 +1,26 @@
<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">
</a>
<a href="<%= url_for("user", {username = post.username}) %>" class="username-link"><%= post.username %></a>
<% if post.status ~= "" then %>
<em class="user-status"><%= post.status %></em>
<% end %>
</div>
<div class="post-content-container"<%= is_latest and 'id=latest-post' or "" %>>
<div class="post-info">
<div><a href="<%= "#post-" .. post.id %>" title="Permalink"><i>
<% if tonumber(post.edited_at) > tonumber(post.created_at) then -%>
Edited at <%= os.date("%c", post.edited_at) %>
<% else -%>
Posted at <%= os.date("%c", post.created_at) %>
<% end -%>
</i></a></div>
<div><button>Reply</button></div>
</div>
<div class="post-content">
<%- post.content %>
</div>
</div>
</div>

View File

@ -1,27 +1,22 @@
<h1><%= thread.title %></h1>
<p>Posted under <a href="<%= url_for("topic", {slug = topic.slug}) %>"><%= topic.name %></a>
<% for _, post in ipairs(posts) do %>
<div id="post-<%= post.id %>">
<img src="<%= post.avatar_path or "/avatars/default.webp" %>"><br>
<a href="<%= url_for("user", {username = post.username}) %>"><%= post.username %></a>
<div><p><%- post.content %></p></div>
<a href="#post-<%= post.id %>">permalink</a>
</div>
<% end %>
<% render("views.common.topnav") -%>
<main>
<nav class="darkbg">
<h1 class="thread-title"><%= thread.title %></h1>
<span>Posted in <a href="<%= url_for("topic", {slug = topic.slug}) %>"><%= topic.name %></a></span>
</nav>
<% for i, post in ipairs(posts) do %>
<% render("views.threads.post", {post = post, is_latest = i == #posts}) %>
<% end %>
</main>
<% if not user:is_guest() then %>
<nav id="bottomnav">
<% render("views.common.pagination", {page_count = pages, current_page = page}) %>
</nav>
<% if not me:is_guest() then %>
<h1>Respond to "<%= thread.title %>"</h1>
<form method="post">
<textarea id="post_content" name="post_content" placeholder="Response body" required></textarea><br>
<input type="submit" value="Reply">
</form>
<% end %>
<span>
<% for i = 1, math.max(pages, 1) do %>
<% if i == page then %>
<%= tostring(i)%>
<% else %>
<a href="?page=<%= i %>"><%= tostring(i)%></a>
<% end %>
<% end %>
</span>

View File

@ -22,7 +22,7 @@
<p>This topic is locked.</p>
<% end %>
<% if user:is_mod() then %>
<% if me:is_mod() then %>
<br>
<a href="<%= url_for("topic_edit", {slug = topic.slug}) %>">Edit topic</a>
<form method="post" action="<%= url_for("topic_edit", {slug = topic.slug}) %>">

View File

@ -11,6 +11,6 @@
<% end %>
<% end %>
</ul>
<% if user:is_mod() then %>
<% if me:is_mod() then %>
<a href="<%= url_for("topic_create") %>">Create new topic</a>
<% end %>

View File

@ -1,4 +1,4 @@
<h1>Are you sure you want to delete your account, <%= user.username %>?</h1>
<h1>Are you sure you want to delete your account, <%= me.username %>?</h1>
<p>This cannot be undone. This will not delete your posts, only anonymize them.</p>
<p>If you are sure, please type your password below.</p>
@ -6,7 +6,7 @@
<h2><%= err %></h2>
<% end %>
<form method="post" action="<%= url_for("user_delete", {username = user.username}) %>">
<form method="post" action="<%= url_for("user_delete", {username = me.username}) %>">
<input type="password" name="password" id="password" autocomplete="current-password" placeholder="Password" required><br>
<input type="submit" value="Delete my account (NO UNDO)">
<input class="critical" type="submit" value="Delete my account (NO UNDO)">
</form>

View File

@ -2,19 +2,19 @@
<% if flash_msg then %>
<h2><%= flash_msg %></h2>
<% end %>
<form method="post" action="<%= url_for("user_set_avatar", {username = user.username}) %>" enctype="multipart/form-data">
<img src="<%= avatar_url(user) %>"><br>
<input type="file" name="avatar" accept="image/*"><br>
<form method="post" action="<%= url_for("user_set_avatar", {username = me.username}) %>" enctype="multipart/form-data">
<img src="<%= avatar_url(me) %>"><br>
<input id="file" type="file" name="avatar" accept="image/*">
<input type="submit" value="Update avatar">
<% if not user:is_default_avatar() then %>
<input type="submit" value="Clear avatar" formaction="<%= url_for("user_clear_avatar", {username = user.username}) %>">
<% if not me:is_default_avatar() then %>
<input type="submit" value="Clear avatar" formaction="<%= url_for("user_clear_avatar", {username = me.username}) %>">
<% end %>
<br>
</form>
<form method="post" action="">
<label for="status">Status</label>
<input type="text" id="status" name="status" value="<%= user.status %>" maxlength="30"><br>
<input type="submit" value="Save">
<input type="text" id="status" name="status" value="<%= me.status %>" maxlength="30"><br>
<input type="submit" value="Save status">
</form>
<br>
<a href="<%= url_for("user_delete_confirm", {username = user.username}) %>">Delete account</a>
<a class="linkbutton critical" href="<%= url_for("user_delete_confirm", {username = me.username}) %>">Delete account</a>

View File

@ -1,41 +1,74 @@
<% if just_logged_in then %>
<h1>Logged in successfully.</h1>
<% end %>
<img src="<%= avatar_url(user) %>">
<h1><%= user.username %></h1>
<h2><%= PermissionLevelString[user.permission] %></h2>
<% if user:is_guest() and user_is_me then %>
<h2>You are a guest. An Moderator needs to approve your account before you will be able to post.</h2>
<% render("views.common.topnav") -%>
<div class="darkbg">
<h1 class="thread-title">Latest posts by <i><%= user.username %></i></h1>
<div>
User permission: <i><%= PermissionLevelString[user.permission] %></i>
</div>
<% if user_is_me then -%>
<div class="user-actions">
<a class="linkbutton" href="<%= url_for("user_settings", {username = user.username}) %>">Settings</a>
<form method="post" action="<%= url_for("user_logout", {user_id = me.id}) %>">
<input class="warn" type="submit" value="Log out">
</form>
</div>
<% end %>
</div>
<% --[[ duplicating code, maybe i'll refactor the post subview later to work anywhere <clown emoji>]] %>
<% for i, post in ipairs(latest_posts) do %>
<div class="user-posts">
<div class="user-page-usercard">
<img class="avatar" src="<%= avatar_url(user) %>">
<b class="big"><%= user.username %></b>
<em class="user-status"><%= user.status %></em>
</div>
<div class="user-posts-container">
<div class="post-info">
<div><a href="<%= url_for("thread", {slug = post.thread_slug}, {after = post.id}) .. "#post-" .. post.id %>" title="Permalink"><i>
<% if tonumber(post.edited_at) > tonumber(post.created_at) then -%>
Edited in <%= post.thread_title %> at <%= os.date("%c", post.edited_at) %>
<% else -%>
Posted in <%= post.thread_title %> at <%= os.date("%c", post.created_at) %>
<% end -%>
</i></a></div>
</div>
<div class="post-content">
<%- post.content %>
</div>
</div>
</div>
<% end %>
<% if user_is_me then %>
<a href="<%= url_for("user_settings", {username = user.username}) %>">Settings</a>
<form method="post" action="<%= url_for("user_logout", {user_id = me.id}) %>">
<input type="submit" value="Log out">
</form>
<% if user:is_guest() and user_is_me then %>
<h2>You are a guest. A Moderator needs to approve your account before you will be able to post.</h2>
<% end %>
<% if me:is_mod() and not user:is_system() then %>
<h1>Moderator controls</h2>
<% if user:is_guest() then %>
<p>This user is a guest. They signed up on <%= os.date("%c", user.created_at) %>.</p>
<form method="post" action="<%= url_for("confirm_user", {user_id = user.id}) %>">
<input type="submit" value="Confirm user">
</form>
<% else %> <% --[[ user is not guest ]] %>
<p>This user signed up on <%= os.date("%c", user.created_at) %> and was confirmed on <%= os.date("%c", user.confirmed_on) %>.</p>
<% if user.id ~= me.id and user.permission < me.permission then %>
<form method="post" action="<%= url_for("guest_user", {user_id = user.id}) %>">
<input type="submit" value="Demote user to guest (soft ban)">
</form>
<% end %>
<% if me:is_admin() and not user:is_mod() then %>
<form method="post" action="<%= url_for("mod_user", {user_id = user.id}) %>">
<input type="submit" value="Promote user to moderator">
<div class="darkbg">
<h1>Moderator controls</h2>
<% if user:is_guest() then %>
<p>This user is a guest. They signed up on <%= os.date("%c", user.created_at) %>.</p>
<form class="modform" method="post" action="<%= url_for("confirm_user", {user_id = user.id}) %>">
<input type="submit" value="Confirm user">
</form>
<% elseif me:is_admin() then %>
<form method="post" action="<%= url_for("demod_user", {user_id = user.id}) %>">
<input type="submit" value="Demote user to regular user">
<% else %> <% --[[ user is not guest ]] %>
<p>This user signed up on <%= os.date("%c", user.created_at) %> and was confirmed on <%= os.date("%c", user.confirmed_on) %>.</p>
<% if user.permission < me.permission then %>
<form class="modform" method="post" action="<%= url_for("guest_user", {user_id = user.id}) %>">
<input class="warn" type="submit" value="Demote user to guest (soft ban)">
</form>
<% end %>
<% if me:is_admin() and not user:is_mod() then %>
<form class="modform" method="post" action="<%= url_for("mod_user", {user_id = user.id}) %>">
<input class="warn" type="submit" value="Promote user to moderator">
</form>
<% elseif user:is_mod() and user.permission < me.permission then %>
<form class="modform" method="post" action="<%= url_for("demod_user", {user_id = user.id}) %>">
<input class="critical" type="submit" value="Demote user to regular user">
</form>
<% end %>
<% end %>
<% end %>
</div>
<% end %>