Compare commits
3 Commits
68d109f428
...
22526c953e
Author | SHA1 | Date | |
---|---|---|---|
22526c953e | |||
bd1ba6c087 | |||
1e23959e52 |
9
app.lua
9
app.lua
@ -1,5 +1,6 @@
|
|||||||
local lapis = require("lapis")
|
local lapis = require("lapis")
|
||||||
local date = require("date")
|
local date = require("date")
|
||||||
|
local models = require("models")
|
||||||
local app = lapis.Application()
|
local app = lapis.Application()
|
||||||
local constants = require("constants")
|
local constants = require("constants")
|
||||||
local babycode = require("lib.babycode")
|
local babycode = require("lib.babycode")
|
||||||
@ -47,6 +48,14 @@ local function inject_methods(req)
|
|||||||
req.babycode_to_html = function (_, bb)
|
req.babycode_to_html = function (_, bb)
|
||||||
return babycode.to_html(bb, html_escape)
|
return babycode.to_html(bb, html_escape)
|
||||||
end
|
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)
|
util.pop_infobox(req)
|
||||||
end
|
end
|
||||||
|
@ -9,6 +9,7 @@ local models = require("models")
|
|||||||
local Topics = models.Topics
|
local Topics = models.Topics
|
||||||
local Threads = models.Threads
|
local Threads = models.Threads
|
||||||
local Posts = models.Posts
|
local Posts = models.Posts
|
||||||
|
local Subscriptions = models.Subscriptions
|
||||||
|
|
||||||
local POSTS_PER_PAGE = 10
|
local POSTS_PER_PAGE = 10
|
||||||
|
|
||||||
@ -97,7 +98,18 @@ app:get("thread", "/:slug", function(self)
|
|||||||
self.other_topics = db.query("SELECT topics.id, topics.name FROM topics")
|
self.other_topics = db.query("SELECT topics.id, topics.name FROM topics")
|
||||||
self.me = util.get_logged_in_user_or_transient(self)
|
self.me = util.get_logged_in_user_or_transient(self)
|
||||||
self.posts = posts
|
self.posts = posts
|
||||||
|
|
||||||
|
if self.me:is_logged_in() then
|
||||||
|
self.is_subscribed = false
|
||||||
|
local subscription = Subscriptions:find({user_id = self.me.id, thread_id = thread.id})
|
||||||
|
if subscription then
|
||||||
|
self.is_subscribed = true
|
||||||
|
if posts[#posts].created_at > subscription.last_seen then
|
||||||
|
subscription:update({last_seen = os.time()})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
self.page_title = thread.title
|
self.page_title = thread.title
|
||||||
|
|
||||||
return {render = "threads.thread"}
|
return {render = "threads.thread"}
|
||||||
@ -133,6 +145,10 @@ app:post("thread", "/:slug", function(self)
|
|||||||
return {redirect_to = self:url_for("thread", {slug = thread.slug}, {page = last_page}) .. "#latest-post"}
|
return {redirect_to = self:url_for("thread", {slug = thread.slug}, {page = last_page}) .. "#latest-post"}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if self.params.subscribe == "on" then
|
||||||
|
Subscriptions:create({user_id = user.id, thread_id = thread.id, last_seen = os.time()})
|
||||||
|
end
|
||||||
|
|
||||||
return {redirect_to = self:url_for("thread", {slug = thread.slug}, {page = last_page}) .. "#latest-post"}
|
return {redirect_to = self:url_for("thread", {slug = thread.slug}, {page = last_page}) .. "#latest-post"}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@ -206,4 +222,50 @@ app:post("thread_move", "/:slug/move", function(self)
|
|||||||
return {redirect_to = self:url_for("thread", {slug = self.params.slug})}
|
return {redirect_to = self:url_for("thread", {slug = self.params.slug})}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
app:post("thread_subscribe", "/:slug/subscribe", function(self)
|
||||||
|
local user = util.get_logged_in_user(self)
|
||||||
|
if not user then
|
||||||
|
return {status = 403}
|
||||||
|
end
|
||||||
|
local thread = Threads:find({slug = self.params.slug})
|
||||||
|
if not thread then
|
||||||
|
return {status = 404}
|
||||||
|
end
|
||||||
|
local subscription = Subscriptions:find({user_id = user.id, thread_id = thread.id})
|
||||||
|
if self.params.subscribe == "subscribe" then
|
||||||
|
local now = os.time()
|
||||||
|
if subscription then
|
||||||
|
subscription:delete()
|
||||||
|
end
|
||||||
|
Subscriptions:create({user_id = user.id, thread_id = thread.id, last_seen = now})
|
||||||
|
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()
|
||||||
|
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}
|
||||||
|
end)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
@ -14,6 +14,7 @@ local models = require("models")
|
|||||||
local Users = models.Users
|
local Users = models.Users
|
||||||
local Sessions = models.Sessions
|
local Sessions = models.Sessions
|
||||||
local Avatars = models.Avatars
|
local Avatars = models.Avatars
|
||||||
|
local Subscriptions = models.Subscriptions
|
||||||
|
|
||||||
local function authenticate_user(user, password)
|
local function authenticate_user(user, password)
|
||||||
return auth.verify(password, user.password_hash)
|
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 status = self.params.status:sub(1, 100)
|
||||||
local original_sig = self.params.signature or ""
|
local original_sig = self.params.signature or ""
|
||||||
local rendered_sig = babycode.to_html(original_sig, html_escape)
|
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({
|
target_user:update({
|
||||||
status = status,
|
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})}
|
return {redirect_to = self:url_for("user_settings", {username = self.params.username})}
|
||||||
end)
|
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)
|
app:get("user_login", "/login", function(self)
|
||||||
if self.session.session_key then
|
if self.session.session_key then
|
||||||
local user = util.get_logged_in_user(self)
|
local user = util.get_logged_in_user(self)
|
||||||
|
@ -634,3 +634,52 @@ ul, ol {
|
|||||||
max-width: 15px;
|
max-width: 15px;
|
||||||
max-height: 15px;
|
max-height: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.accordion {
|
||||||
|
border-top-right-radius: 3px;
|
||||||
|
border-top-left-radius: 3px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid black;
|
||||||
|
margin: 10px 5px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion.hidden {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: rgb(159.0271653543, 162.0727712915, 172.9728346457);
|
||||||
|
padding: 0 10px;
|
||||||
|
gap: 10px;
|
||||||
|
border-bottom: 1px solid black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-toggle {
|
||||||
|
padding: 0;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
min-width: 36px;
|
||||||
|
min-height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-title {
|
||||||
|
margin-right: auto;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-content {
|
||||||
|
padding: 0 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-content.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inbox-container {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
@ -22,9 +22,27 @@ function activateSelfDeactivateSibs(button) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
// tabs
|
||||||
document.querySelectorAll(".tab-button").forEach(button => {
|
document.querySelectorAll(".tab-button").forEach(button => {
|
||||||
button.addEventListener("click", () => {
|
button.addEventListener("click", () => {
|
||||||
activateSelfDeactivateSibs(button);
|
activateSelfDeactivateSibs(button);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// accordions
|
||||||
|
const accordions = document.querySelectorAll(".accordion");
|
||||||
|
accordions.forEach(accordion => {
|
||||||
|
const header = accordion.querySelector(".accordion-header");
|
||||||
|
const toggleButton = header.querySelector(".accordion-toggle");
|
||||||
|
const content = accordion.querySelector(".accordion-content");
|
||||||
|
|
||||||
|
const toggle = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
accordion.classList.toggle("hidden");
|
||||||
|
content.classList.toggle("hidden");
|
||||||
|
toggleButton.textContent = content.classList.contains("hidden") ? "►" : "▼"
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleButton.addEventListener("click", toggle);
|
||||||
|
});
|
||||||
});
|
});
|
@ -102,4 +102,15 @@ return {
|
|||||||
|
|
||||||
db.query("CREATE INDEX idx_rate_limit_user_method ON api_rate_limits (user_id, method)")
|
db.query("CREATE INDEX idx_rate_limit_user_method ON api_rate_limits (user_id, method)")
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
[13] = function ()
|
||||||
|
schema.create_table("subscriptions", {
|
||||||
|
{"id", types.integer{primary_key = true}},
|
||||||
|
{"user_id", "INTEGER REFERENCES users(id) ON DELETE CASCADE"},
|
||||||
|
{"thread_id", "INTEGER REFERENCES threads(id) ON DELETE CASCADE"},
|
||||||
|
{"last_seen", "INTEGER DEFAULT (unixepoch(CURRENT_TIMESTAMP)) NOT NULL"},
|
||||||
|
})
|
||||||
|
|
||||||
|
db.query("CREATE INDEX idx_subscription_user_thread ON subscriptions (user_id, thread_id)")
|
||||||
|
end,
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,7 @@ local ret = {
|
|||||||
PostHistory = Model:extend("post_history"),
|
PostHistory = Model:extend("post_history"),
|
||||||
Sessions = Model:extend("sessions"),
|
Sessions = Model:extend("sessions"),
|
||||||
Avatars = Model:extend("avatars"),
|
Avatars = Model:extend("avatars"),
|
||||||
|
Subscriptions = Model:extend("subscriptions"),
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
@ -46,6 +46,7 @@ $lighter: color.scale($accent_color, $lightness: 60%, $saturation: -60%);
|
|||||||
$main_bg: color.scale($accent_color, $lightness: -10%, $saturation: -40%);
|
$main_bg: color.scale($accent_color, $lightness: -10%, $saturation: -40%);
|
||||||
$button_color: color.adjust($accent_color, $hue: 90);
|
$button_color: color.adjust($accent_color, $hue: 90);
|
||||||
$button_color2: color.adjust($accent_color, $hue: 180);
|
$button_color2: color.adjust($accent_color, $hue: 180);
|
||||||
|
$accordion_color: color.adjust($accent_color, $hue: 140, $lightness: -10%, $saturation: -15%);
|
||||||
|
|
||||||
%button-base {
|
%button-base {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
@ -643,3 +644,52 @@ ul, ol {
|
|||||||
max-width: 15px;
|
max-width: 15px;
|
||||||
max-height: 15px;
|
max-height: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.accordion {
|
||||||
|
border-top-right-radius: 3px;
|
||||||
|
border-top-left-radius: 3px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid black;
|
||||||
|
margin: 10px 5px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion.hidden {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: $accordion_color;
|
||||||
|
padding: 0 10px;
|
||||||
|
gap: 10px;
|
||||||
|
border-bottom: 1px solid black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-toggle {
|
||||||
|
padding: 0;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
min-width: 36px;
|
||||||
|
min-height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-title {
|
||||||
|
margin-right: auto;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-content {
|
||||||
|
padding: 0 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-content.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inbox-container {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
7
util.lua
7
util.lua
@ -105,13 +105,16 @@ function util.split_sentences(sentences, max_sentences)
|
|||||||
end
|
end
|
||||||
|
|
||||||
---@return string
|
---@return string
|
||||||
function util.get_post_url(req, post_id)
|
function util.get_post_url(req, post_id, hash)
|
||||||
|
hash = hash ~= false
|
||||||
local post = Posts:find({id = post_id})
|
local post = Posts:find({id = post_id})
|
||||||
if not post then return "" end
|
if not post then return "" end
|
||||||
local thread = Threads:find({id = post.thread_id})
|
local thread = Threads:find({id = post.thread_id})
|
||||||
if not thread then return "" end
|
if not thread then return "" end
|
||||||
|
|
||||||
return req:url_for("thread", {slug = thread.slug}, {after = post_id}) .. "#post-" .. post_id
|
local url = req:url_for("thread", {slug = thread.slug}, {after = post_id})
|
||||||
|
if not hash then return url end
|
||||||
|
return url .. "#post-" .. post_id
|
||||||
end
|
end
|
||||||
|
|
||||||
function util.infobox_message(msg)
|
function util.infobox_message(msg)
|
||||||
|
@ -18,6 +18,6 @@
|
|||||||
</footer>
|
</footer>
|
||||||
<script src="/static/js/copy-code.js"></script>
|
<script src="/static/js/copy-code.js"></script>
|
||||||
<script src="/static/js/date-fmt.js"></script>
|
<script src="/static/js/date-fmt.js"></script>
|
||||||
<script src="/static/js/tabs.js"></script>
|
<script src="/static/js/ui.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -6,6 +6,12 @@
|
|||||||
%>
|
%>
|
||||||
<form class="post-edit-form" method="post" action="<%= url or "" %>">
|
<form class="post-edit-form" method="post" action="<%= url or "" %>">
|
||||||
<% render ("views.common.babycode-editor-component", {ta_name = ta_name, prefill = prefill}) %>
|
<% 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" <%= session.subscribe_by_default and "checked" or "" %>>
|
||||||
|
<label for="subscribe">Subscribe to thread</label>
|
||||||
|
</span>
|
||||||
|
<% end %>
|
||||||
<span>
|
<span>
|
||||||
<input type=submit value="<%= save_button_text %>">
|
<input type=submit value="<%= save_button_text %>">
|
||||||
<% if cancel_url then %>
|
<% if cancel_url then %>
|
||||||
|
@ -9,6 +9,8 @@
|
|||||||
Welcome, <a href="<%= url_for("user", {username = me.username}) %>"><%= me.username %></a>
|
Welcome, <a href="<%= url_for("user", {username = me.username}) %>"><%= me.username %></a>
|
||||||
•
|
•
|
||||||
<a href="<%= url_for("user_settings", {username = me.username}) %>">Settings</a>
|
<a href="<%= url_for("user_settings", {username = me.username}) %>">Settings</a>
|
||||||
|
•
|
||||||
|
<a href="<%= url_for("user_inbox", {username = me.username}) %>">Inbox</a>
|
||||||
<% if me:is_mod() then %>
|
<% if me:is_mod() then %>
|
||||||
•
|
•
|
||||||
<a href="<%= url_for("user_list") %>">User list</a>
|
<a href="<%= url_for("user_list") %>">User list</a>
|
||||||
|
@ -18,7 +18,8 @@
|
|||||||
<div class="post-content-container"<%= is_latest and 'id=latest-post' or "" %>>
|
<div class="post-content-container"<%= is_latest and 'id=latest-post' or "" %>>
|
||||||
<div class="post-info">
|
<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>
|
<a href="<%= post_url %>" title="Permalink"><i>
|
||||||
<% if tonumber(post.edited_at) > tonumber(post.created_at) then -%>
|
<% if tonumber(post.edited_at) > tonumber(post.created_at) then -%>
|
||||||
@ -55,7 +56,7 @@
|
|||||||
<button value="<%= reply_text %>" class="reply-button">Quote</button>
|
<button value="<%= reply_text %>" class="reply-button">Quote</button>
|
||||||
<% end %>
|
<% 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
|
if show_delete then
|
||||||
%>
|
%>
|
||||||
<button class="critical post-delete-button" value="<%= post.id %>">Delete</button>
|
<button class="critical post-delete-button" value="<%= post.id %>">Delete</button>
|
||||||
|
@ -14,29 +14,36 @@
|
|||||||
<% if is_stickied then %> • <i>stickied, so it's probably important</i>
|
<% if is_stickied then %> • <i>stickied, so it's probably important</i>
|
||||||
<% end %>
|
<% end %>
|
||||||
</span>
|
</span>
|
||||||
<% if can_lock then %>
|
|
||||||
<div>
|
<div>
|
||||||
<form class="modform" action="<%= url_for("thread_lock", {slug = thread.slug}) %>" method="post">
|
<% if me:is_logged_in() then %>
|
||||||
<input type=hidden value="<%= not is_locked %>" name="target_op">
|
<form class="modform" action="<%= url_for("thread_subscribe", {slug = thread.slug}) %>" method="post">
|
||||||
<input class="warn" type="submit" value="<%= is_locked and "Unlock thread" or "Lock thread" %>">
|
<input type="hidden" name="last_visible_post" value=<%= posts[#posts].id %>>
|
||||||
</form>
|
<input type="hidden" name="subscribe" value=<%= is_subscribed and "unsubscribe" or "subscribe" %>>
|
||||||
<% if me:is_mod() then %>
|
<input type="submit" value="<%= is_subscribed and "Unsubscribe" or "Subscribe" %>">
|
||||||
<form class="modform" action="<%= url_for("thread_sticky", {slug = thread.slug}) %>" method="post">
|
|
||||||
<input type=hidden value="<%= not is_stickied %>" name="target_op">
|
|
||||||
<input class="warn" type="submit" value="<%= is_stickied and "Unsticky thread" or "Sticky thread" %>">
|
|
||||||
</form>
|
|
||||||
<form class="modform" action="<%= url_for("thread_move", {slug = thread.slug}) %>" method="post">
|
|
||||||
<label for="new_topic_id">Move to topic:</label>
|
|
||||||
<select style="width:200px;" id="new_topic_id" name="new_topic_id" autocomplete="off">
|
|
||||||
<% for _, topic in ipairs(other_topics) do %>
|
|
||||||
<option value="<%= topic.id %>" <%- thread.topic_id == topic.id and "selected disabled" or "" %>><%= topic.name %></option>
|
|
||||||
<% end %>
|
|
||||||
</select>
|
|
||||||
<input class="warn" type="submit" value="Move thread">
|
|
||||||
</form>
|
</form>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% if can_lock then %>
|
||||||
|
<form class="modform" action="<%= url_for("thread_lock", {slug = thread.slug}) %>" method="post">
|
||||||
|
<input type=hidden value="<%= not is_locked %>" name="target_op">
|
||||||
|
<input class="warn" type="submit" value="<%= is_locked and "Unlock thread" or "Lock thread" %>">
|
||||||
|
</form>
|
||||||
|
<% if me:is_mod() then %>
|
||||||
|
<form class="modform" action="<%= url_for("thread_sticky", {slug = thread.slug}) %>" method="post">
|
||||||
|
<input type=hidden value="<%= not is_stickied %>" name="target_op">
|
||||||
|
<input class="warn" type="submit" value="<%= is_stickied and "Unsticky thread" or "Sticky thread" %>">
|
||||||
|
</form>
|
||||||
|
<form class="modform" action="<%= url_for("thread_move", {slug = thread.slug}) %>" method="post">
|
||||||
|
<label for="new_topic_id">Move to topic:</label>
|
||||||
|
<select style="width:200px;" id="new_topic_id" name="new_topic_id" autocomplete="off">
|
||||||
|
<% for _, topic in ipairs(other_topics) do %>
|
||||||
|
<option value="<%= topic.id %>" <%- thread.topic_id == topic.id and "selected disabled" or "" %>><%= topic.name %></option>
|
||||||
|
<% end %>
|
||||||
|
</select>
|
||||||
|
<input class="warn" type="submit" value="Move thread">
|
||||||
|
</form>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
|
||||||
</nav>
|
</nav>
|
||||||
<% for i, post in ipairs(posts) do %>
|
<% for i, post in ipairs(posts) do %>
|
||||||
<% render("views.threads.post", {post = post, render_sig = true, is_latest = i == #posts}) %>
|
<% render("views.threads.post", {post = post, render_sig = true, is_latest = i == #posts}) %>
|
||||||
|
35
views/user/inbox.etlua
Normal file
35
views/user/inbox.etlua
Normal 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>
|
@ -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">
|
<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>
|
<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}) %>
|
<% 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">
|
<input type="submit" value="Save settings">
|
||||||
</form>
|
</form>
|
||||||
<form method="post" action="<%= url_for("user_change_password", {username = me.username}) %>">
|
<form method="post" action="<%= url_for("user_change_password", {username = me.username}) %>">
|
||||||
|
Loading…
Reference in New Issue
Block a user