add an inbox view

This commit is contained in:
Lera Elvoé 2025-06-02 23:05:28 +03:00
parent bd1ba6c087
commit 22526c953e
Signed by: yagich
SSH Key Fingerprint: SHA256:6xjGb6uA7lAVcULa7byPEN//rQ0wPoG+UzYVMfZnbvc
11 changed files with 178 additions and 6 deletions

View File

@ -1,5 +1,6 @@
local lapis = require("lapis")
local date = require("date")
local models = require("models")
local app = lapis.Application()
local constants = require("constants")
local babycode = require("lib.babycode")
@ -47,6 +48,14 @@ local function inject_methods(req)
req.babycode_to_html = function (_, bb)
return babycode.to_html(bb, html_escape)
end
req.get_thread_by_id = function(_, id)
return models.Threads:find({id = id})
end
req.get_post_url = function(_, id)
return util.get_post_url(_, id)
end
util.pop_infobox(req)
end

View File

@ -238,13 +238,31 @@ app:post("thread_subscribe", "/:slug/subscribe", function(self)
subscription:delete()
end
Subscriptions:create({user_id = user.id, thread_id = thread.id, last_seen = now})
return {redirect_to = self:url_for("thread", {slug = thread.slug}, {after = self.params.first_visible_post})}
if self.params.last_visible_post then
return {redirect_to = self:url_for("thread", {slug = thread.slug}, {after = self.params.last_visible_post})}
else
return {redirect_to = self:url_for("user_inbox", {username = user.username})}
end
elseif self.params.subscribe == "unsubscribe" then
if not subscription then
return {status = 404}
end
subscription:delete()
return {redirect_to = self:url_for("thread", {slug = thread.slug}, {after = self.params.first_visible_post})}
if self.params.last_visible_post then
return {redirect_to = self:url_for("thread", {slug = thread.slug}, {after = self.params.last_visible_post})}
else
return {redirect_to = self:url_for("user_inbox", {username = user.username})}
end
elseif self.params.subscribe == "read" then
if not subscription then
return {status = 404}
end
subscription:update({last_seen = os.time()})
if self.params.last_visible_post then
return {redirect_to = self:url_for("thread", {slug = thread.slug}, {after = self.params.last_visible_post})}
else
return {redirect_to = self:url_for("user_inbox", {username = user.username})}
end
end
return {status = 400}

View File

@ -14,6 +14,7 @@ local models = require("models")
local Users = models.Users
local Sessions = models.Sessions
local Avatars = models.Avatars
local Subscriptions = models.Subscriptions
local function authenticate_user(user, password)
return auth.verify(password, user.password_hash)
@ -267,6 +268,11 @@ app:post("user_settings", "/:username/settings", function(self)
local status = self.params.status:sub(1, 100)
local original_sig = self.params.signature or ""
local rendered_sig = babycode.to_html(original_sig, html_escape)
if self.params.subscribe_by_default == "on" then
self.session.subscribe_by_default = true
else
self.session.subscribe_by_default = false
end
target_user:update({
status = status,
@ -277,6 +283,97 @@ app:post("user_settings", "/:username/settings", function(self)
return {redirect_to = self:url_for("user_settings", {username = self.params.username})}
end)
app:get("user_inbox", "/:username/inbox", function(self)
local me = util.get_logged_in_user(self)
if me == nil then
util.inject_err_infobox(self, "You must be logged in to perform this action.")
return {redirect_to = self:url_for("user_login")}
end
local target_user = Users:find({username = self.params.username})
if me.id ~= target_user.id then
return {redirect_to = self:url_for("user_inbox", {username = me.username})}
end
self.me = target_user
self.page_title = "inbox"
self.new_posts = {}
local subscription = Subscriptions:find({user_id = me.id})
if subscription then
local q = [[
WITH thread_metadata AS (
SELECT
posts.thread_id, threads.slug AS thread_slug, threads.title AS thread_title, COUNT(*) AS unread_count, MAX(posts.created_at) AS newest_post_time
FROM
posts
LEFT JOIN
threads ON threads.id = posts.thread_id
LEFT JOIN
subscriptions ON subscriptions.thread_id = posts.thread_id
WHERE subscriptions.user_id = ? AND posts.created_at > subscriptions.last_seen
GROUP BY posts.thread_id
)
SELECT
tm.thread_id, tm.thread_slug, tm.thread_title, tm.unread_count, tm.newest_post_time,
posts.id, posts.created_at, post_history.content, post_history.edited_at, users.username, users.status, avatars.file_path AS avatar_path, posts.thread_id, users.id AS user_id, post_history.original_markup, users.signature_rendered
FROM
thread_metadata tm
JOIN
posts ON posts.thread_id = tm.thread_id
JOIN
post_history ON posts.current_revision_id = post_history.id
JOIN
users ON posts.user_id = users.id
LEFT JOIN
threads ON threads.id = posts.thread_id
LEFT JOIN
avatars ON users.avatar_id = avatars.id
LEFT JOIN
subscriptions ON subscriptions.thread_id = posts.thread_id
WHERE
subscriptions.user_id = ? AND posts.created_at > subscriptions.last_seen
ORDER BY
tm.newest_post_time DESC, posts.created_at ASC]]
local new_posts_raw = db.query(q, me.id, me.id)
local threads = {}
local current_thread_id = nil
local current_thread_group = nil
self.total_unreads_count = 0
for _, row in ipairs(new_posts_raw) do
if row.thread_id ~= current_thread_id then
current_thread_group = {
thread_id = row.thread_id,
thread_title = row.thread_title,
unread_count = row.unread_count,
thread_slug = row.thread_slug,
newest_post_time = row.newest_post_time,
posts = {}
}
self.total_unreads_count = self.total_unreads_count + row.unread_count
table.insert(threads, current_thread_group)
current_thread_id = row.thread_id
end
---@diagnostic disable-next-line: need-check-nil
table.insert(current_thread_group.posts, {
id = row.id,
created_at = row.created_at,
content = row.content,
edited_at = row.edited_at,
username = row.username,
status = row.status,
avatar_path = row.avatar_path,
thread_id = row.thread_id,
user_id = row.user_id,
original_markup = row.original_markup,
signature_rendered = row.signature_rendered,
})
end
self.new_posts = threads
end
return {render = "user.inbox"}
end)
app:get("user_login", "/login", function(self)
if self.session.session_key then
local user = util.get_logged_in_user(self)

View File

@ -679,3 +679,7 @@ ul, ol {
.accordion-content.hidden {
display: none;
}
.inbox-container {
padding: 10px;
}

View File

@ -689,3 +689,7 @@ ul, ol {
.accordion-content.hidden {
display: none;
}
.inbox-container {
padding: 10px;
}

View File

@ -8,7 +8,7 @@
<% render ("views.common.babycode-editor-component", {ta_name = ta_name, prefill = prefill}) %>
<% if not cancel_url then %>
<span>
<input type="checkbox" id="subscribe" name="subscribe" checked>
<input type="checkbox" id="subscribe" name="subscribe" <%= session.subscribe_by_default and "checked" or "" %>>
<label for="subscribe">Subscribe to thread</label>
</span>
<% end %>

View File

@ -9,6 +9,8 @@
Welcome, <a href="<%= url_for("user", {username = me.username}) %>"><%= me.username %></a>
&bullet;
<a href="<%= url_for("user_settings", {username = me.username}) %>">Settings</a>
&bullet;
<a href="<%= url_for("user_inbox", {username = me.username}) %>">Inbox</a>
<% if me:is_mod() then %>
&bullet;
<a href="<%= url_for("user_list") %>">User list</a>

View File

@ -18,7 +18,8 @@
<div class="post-content-container"<%= is_latest and 'id=latest-post' or "" %>>
<div class="post-info">
<%
local post_url = url_for("thread", {slug = thread.slug}, {page = page}) .. "#post-" .. post.id
--local post_url = url_for("thread", {slug = thread.slug}, {page = page}) .. "#post-" .. post.id
local post_url = get_post_url(post.id)
%>
<a href="<%= post_url %>" title="Permalink"><i>
<% if tonumber(post.edited_at) > tonumber(post.created_at) then -%>
@ -55,7 +56,7 @@
<button value="<%= reply_text %>" class="reply-button">Quote</button>
<% end %>
<%
local show_delete = (post.user_id == me.id and not ntob(thread.is_locked)) or me:is_mod()
local show_delete = ((post.user_id == me.id and not ntob(thread.is_locked)) or me:is_mod()) and not no_reply
if show_delete then
%>
<button class="critical post-delete-button" value="<%= post.id %>">Delete</button>

View File

@ -17,7 +17,7 @@
<div>
<% if me:is_logged_in() then %>
<form class="modform" action="<%= url_for("thread_subscribe", {slug = thread.slug}) %>" method="post">
<input type="hidden" name="first_visible_post" value=<%= posts[1].id %>>
<input type="hidden" name="last_visible_post" value=<%= posts[#posts].id %>>
<input type="hidden" name="subscribe" value=<%= is_subscribed and "unsubscribe" or "subscribe" %>>
<input type="submit" value="<%= is_subscribed and "Unsubscribe" or "Subscribe" %>">
</form>

35
views/user/inbox.etlua Normal file
View File

@ -0,0 +1,35 @@
<div class="darkbg">
<h1 class="thread-title">Inbox</h1>
</div>
<div class="inbox-container">
<% if #new_posts == 0 then %>
You have no unread posts.
<% else %>
You have <%= total_unreads_count %> unread post<%= total_unreads_count > 1 and "s" or "" %>:
<% for _, thread in ipairs(new_posts) do %>
<div class="accordion">
<div class="accordion-header">
<button type="button" class="accordion-toggle">▼</button>
<% local latest_post_id = thread.posts[#thread.posts].id %>
<%
local unread_posts_text = " (" .. thread.unread_count .. " unread post" .. (thread.unread_count > 1 and "s" or "")-- .. ")"
%>
<a class="accordion-title" href="<%= url_for("thread", {slug = thread.thread_slug}, {after = latest_post_id}) .. "#post-" .. latest_post_id %>" title="Jump to latest post"><%= thread.thread_title .. unread_posts_text %>, latest at <span class="timestamp" data-utc=<%= thread.newest_post_time %>><%= os.date("%c", thread.newest_post_time) %></span>)</a>
<form action="<%= url_for("thread_subscribe", {slug = thread.thread_slug}) %>" method="post">
<input type="hidden" name="subscribe" value="read">
<input type="submit" value="Mark Thread as Read">
</form>
<form action="<%= url_for("thread_subscribe", {slug = thread.thread_slug}) %>" method="post">
<input type="hidden" name="subscribe" value="unsubscribe">
<input class="warn" type="submit" value="Unsubscribe">
</form>
</div>
<div class="accordion-content">
<% for _, post in ipairs(thread.posts) do %>
<% render("views.threads.post", {post = post, edit = false, is_latest = false, no_reply = true, thread = get_thread_by_id(thread.thread_id)}) %>
<% end %>
</div>
</div>
<% end %>
<% end %>
</div>

View File

@ -24,6 +24,8 @@
<input type="text" id="status" name="status" value="<%= me.status %>" maxlength="70" placeholder="Will be shown under your username. Max 70 characters">
<label for="babycode-content">Signature</label><br>
<% render("views.common.babycode-editor-component", {ta_name = "signature", prefill = me.signature_original_markup, ta_placeholder = "Will be shown under each of your posts", optional = true}) %>
<input autocomplete="off" type="checkbox" id="subscribe_by_default" name="subscribe_by_default" <%= session.subscribe_by_default and "checked" or "" %>>
<label for="subscribe_by_default">Subscribe to thread by default when responding</label><br>
<input type="submit" value="Save settings">
</form>
<form method="post" action="<%= url_for("user_change_password", {username = me.username}) %>">