add thread view
This commit is contained in:
		@@ -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
 | 
			
		||||
@@ -57,7 +59,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
 | 
			
		||||
  })
 | 
			
		||||
@@ -68,11 +69,11 @@ app:get("thread", "/:slug", function(self)
 | 
			
		||||
  local post_count = Posts:count(db.clause({
 | 
			
		||||
    thread_id = thread.id
 | 
			
		||||
  }))
 | 
			
		||||
  self.pages = math.ceil(post_count / posts_per_page)
 | 
			
		||||
  self.pages = math.ceil(post_count / POSTS_PER_PAGE)
 | 
			
		||||
  self.page = tonumber(self.params.page) or 1
 | 
			
		||||
  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,7 +87,7 @@ 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.posts = posts
 | 
			
		||||
@@ -115,11 +116,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
 | 
			
		||||
 
 | 
			
		||||
@@ -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"),
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
/* src: */
 | 
			
		||||
 | 
			
		||||
@use "sass:color";
 | 
			
		||||
 | 
			
		||||
$accent_color: #c1ceb1;
 | 
			
		||||
@@ -8,6 +10,38 @@ $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;
 | 
			
		||||
@@ -16,10 +50,11 @@ body {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#topnav {
 | 
			
		||||
  padding: 10px;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: end;
 | 
			
		||||
  background-color: $accent_color;
 | 
			
		||||
  @include navbar($accent_color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#bottomnav {
 | 
			
		||||
  @include navbar($dark_bg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#threadnav {
 | 
			
		||||
@@ -71,6 +106,9 @@ body {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
  padding: 5px 20px;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  border-top: 1px solid black;
 | 
			
		||||
  border-bottom: 1px solid black;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.post-content {
 | 
			
		||||
@@ -92,3 +130,31 @@ body {
 | 
			
		||||
.user-status {
 | 
			
		||||
  text-align: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
button, input[type="submit"], .linkbutton {
 | 
			
		||||
  @include button($button_color);
 | 
			
		||||
  
 | 
			
		||||
  &.critical {
 | 
			
		||||
    color: white;
 | 
			
		||||
    @include button(red);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.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;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,14 @@
 | 
			
		||||
/* src: */
 | 
			
		||||
.currentpage, .pagebutton, button.critical, input[type=submit].critical, .linkbutton.critical, button, input[type=submit], .linkbutton {
 | 
			
		||||
  cursor: default;
 | 
			
		||||
  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 20px 0px 20px;
 | 
			
		||||
@@ -11,6 +22,13 @@ body {
 | 
			
		||||
  background-color: #c1ceb1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#bottomnav {
 | 
			
		||||
  padding: 10px;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: end;
 | 
			
		||||
  background-color: rgb(143.7039271654, 144.3879625984, 142.8620374016);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#threadnav {
 | 
			
		||||
  padding-bottom: 10px;
 | 
			
		||||
  padding-left: 10px;
 | 
			
		||||
@@ -57,6 +75,9 @@ body {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
  padding: 5px 20px;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  border-top: 1px solid black;
 | 
			
		||||
  border-bottom: 1px solid black;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.post-content {
 | 
			
		||||
@@ -79,4 +100,46 @@ body {
 | 
			
		||||
  text-align: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*# sourceMappingURL=style.css.map */
 | 
			
		||||
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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.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;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
{"version":3,"sourceRoot":"","sources":["../sass/style.scss"],"names":[],"mappings":"AAWA;EACE;EACA;EACA,kBALQ;;;AAQV;EACE;EACA;EACA;EACA,kBAnBa;;;AAsBf;EACE;EACA;EACA,kBAvBQ;;;AA0BV;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA,qBACE;EACF;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA,kBAhDQ;EAiDR;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA,qBACE;EAEF;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE","file":"style.css"}
 | 
			
		||||
							
								
								
									
										3
									
								
								util.lua
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								util.lua
									
									
									
									
									
								
							@@ -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",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@
 | 
			
		||||
<head>
 | 
			
		||||
  <meta charset="UTF-8">
 | 
			
		||||
  <title>Porom</title>
 | 
			
		||||
  <link rel="stylesheet" href="/static/style.css">
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
  <% content_for("inner") %>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										27
									
								
								views/common/pagination.etlua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								views/common/pagination.etlua
									
									
									
									
									
										Normal 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">…</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">…</span>
 | 
			
		||||
    <% end %>
 | 
			
		||||
    <a href="?page=<%= page_count %>" class="pagebutton"><%= page_count %></a>
 | 
			
		||||
  <% end %>
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										26
									
								
								views/threads/post.etlua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								views/threads/post.etlua
									
									
									
									
									
										Normal 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>
 | 
			
		||||
@@ -1,13 +1,26 @@
 | 
			
		||||
<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 %>
 | 
			
		||||
<nav id="topnav">
 | 
			
		||||
  <span>
 | 
			
		||||
    <% if user:is_logged_in() then -%>
 | 
			
		||||
      Welcome, <a href="<%= url_for("user", {username = user.username}) %>"><%= user.username %></a>
 | 
			
		||||
    <% else -%>
 | 
			
		||||
      Welcome, guest. Please <a href="<%= url_for("user_login") %>">log in</a>
 | 
			
		||||
    <% end -%>
 | 
			
		||||
  </span>
 | 
			
		||||
</nav>
 | 
			
		||||
 | 
			
		||||
<main>
 | 
			
		||||
  <nav id="threadnav">
 | 
			
		||||
    <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>
 | 
			
		||||
 | 
			
		||||
<nav id="bottomnav">
 | 
			
		||||
  <% render("views.common.pagination", {page_count = pages, current_page = page}) %>
 | 
			
		||||
</nav>
 | 
			
		||||
 | 
			
		||||
<% if not user:is_guest() then %>
 | 
			
		||||
<h1>Respond to "<%= thread.title %>"</h1>
 | 
			
		||||
@@ -16,12 +29,3 @@
 | 
			
		||||
  <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>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user