thread converted to new theme, page support

This commit is contained in:
Akif9748 2022-08-29 00:58:34 +03:00
parent c8474694da
commit 9ad7c03162
6 changed files with 274 additions and 167 deletions

View file

@ -30,12 +30,14 @@ Akf-forum has got an API for AJAX, other clients etc. And, you can learn about A
## Roadmap ## Roadmap
### TO-DO: ### TO-DO:
- If thread deleted, not show its messages in API. - If thread deleted, not show its messages in API.
- Thread.ejs fix with new theme
- Profile photos will store in database - Profile photos will store in database
- regex for pfp for now and - regex for pfp for now and
- admin perm for undelete, thread+message - admin perm for undelete, thread + message
- page support for threads - page support for threads
- message "<b>"
- author name of thread
- page for threads - users
- edit & delete button for thread
### Frontend ### Frontend
### User ### User
| To do | Is done? | Priority | | To do | Is done? | Priority |
@ -58,7 +60,7 @@ Akf-forum has got an API for AJAX, other clients etc. And, you can learn about A
| Ratelimit | 🟢 | HIGH | | Ratelimit | 🟢 | HIGH |
| Send | 🟢 | HIGH | | Send | 🟢 | HIGH |
| Delete | 🟢 | HIGH | | Delete | 🟢 | HIGH |
| Regex for scripts | 🔴 | HIGH | | Regex for scripts | 🟢 | HIGH |
| Undelete | 🟢 | MEDIUM | | Undelete | 🟢 | MEDIUM |
| React | 🟢 | MEDIUM | | React | 🟢 | MEDIUM |
| Edit | 🔴 | MEDIUM | | Edit | 🔴 | MEDIUM |

View file

@ -1,136 +1,140 @@
.title { .title {
color: #414141; color: #414141;
font-weight: 700; font-weight: 700;
font-size: 36px; font-size: 36px;
} }
.date{
.date {
color: gray; color: gray;
} }
.thread{ .message {
max-width: 800px; max-width: 800px;
box-shadow: 0 0 5px 0 #c3c3c3; box-shadow: 0 0 5px 0 #c3c3c3;
margin: 10px auto; margin: 10px auto;
padding: 20px; padding: 20px;
display:flex; display: flex;
gap:10px; gap: 10px;
position:relative; position: relative;
} }
.thread .left{ .message .left {
text-align: center; text-align: center;
border-right: 2px solid #d9d9d9; border-right: 2px solid #d9d9d9;
} }
.thread .left img{ .message .left img {
width: 100px; width: 100px;
height: 100px; height: 100px;
border-radius: 50%; border-radius: 50%;
margin-right: 5px; margin-right: 5px;
} }
.thread .left .username{ .message .left .username {
color: #555; color: #555;
} }
.content{ .content {
width: 70%; width: 70%;
} }
.likes{ .reactions {
position:absolute; position: absolute;
right:20px; right: 20px;
bottom:20px; bottom: 20px;
display:flex; display: flex;
align-items:center; align-items: center;
gap:10px; gap: 10px;
} }
.likes div{
display:flex; .reactions div {
align-items:center; display: flex;
gap:5px; align-items: center;
padding:4px; gap: 5px;
padding: 4px;
color: #747474; color: #747474;
cursor:pointer; cursor: pointer;
transition:color 0.3s ease; transition: color 0.3s ease;
} }
.likes div:hover{
color: #151515; .reactions div:hover {
color: #151515;
} }
.likes div i{ .reactions div i {
font-size:22px; font-size: 22px;
} }
.pagination{ .pagination {
box-shadow: 0 0 5px 0 #c3c3c3; box-shadow: 0 0 5px 0 #c3c3c3;
margin: 10px auto; margin: 10px auto;
padding: 8px; padding: 8px;
display:flex; display: flex;
justify-content:space-between; justify-content: space-between;
align-items:center; align-items: center;
max-width:400px; max-width: 400px;
gap:10px; gap: 10px;
position:relative; position: relative;
} }
.pagination .back , .pagination .back,
.pagination .after { .pagination .after {
color:#747474; color: #747474;
font-size:26px; font-size: 26px;
cursor:pointer; cursor: pointer;
} }
.pagination .numbers{ .pagination .numbers {
display:flex; display: flex;
align-items:center; align-items: center;
gap:5px; gap: 5px;
} }
.pagination .number { .pagination .number {
color: #747474; color: #747474;
font-size: 22px; font-size: 22px;
border: 0 0 5px #747474; border: 0 0 5px #747474;
padding: 8px; padding: 8px;
border-radius: 2px; border-radius: 2px;
font-weight: 600; font-weight: 600;
cursor: pointer; cursor: pointer;
margin: 8px; margin: 8px;
} }
.pagination .number.active{
color: #4d18e6; .pagination .number.active {
font-weight: 700; color: #4d18e6;
font-weight: 700;
} }
.dots{ .dots {
position: absolute; position: absolute;
right: 20px; right: 20px;
font-size: 22px; font-size: 22px;
top: 10px; top: 10px;
color: #747474; color: #747474;
cursor:pointer; cursor: pointer;
} }
.dots-menu{ .dots-menu {
position: absolute; position: absolute;
top: 50px; top: 50px;
right: 0; right: 0;
background-color: #e6e6e6; background-color: #e6e6e6;
padding: ; width: 100px;
width: 100px; text-align: center;
text-align: center; display: none;
display:none;
} }
.dots-menu.active{ .dots-menu.active {
display:block; display: block;
} }
.dots-menu a{ .dots-menu a {
display:block; display: block;
margin:8px; margin: 8px;
cursor:pointer; cursor: pointer;
} }

View file

@ -45,7 +45,7 @@ window.scrollTo(0, document.body.scrollHeight);
/** /**
* Message Sender * Message Sender
*/ */
document.getElementById("send").addEventListener("submit", async e => { document.getElementById("send")?.addEventListener("submit", async e => {
e.preventDefault(); e.preventDefault();
const form = e.target; const form = e.target;

View file

@ -17,18 +17,21 @@ app.get("/create*", (req, res) => res.reply("create_thread"));
app.get("/:id", async (req, res) => { app.get("/:id", async (req, res) => {
const { id } = req.params; const { id } = req.params;
const page = req.query.page || 0;
const thread = await ThreadModel.get(id); const thread = await ThreadModel.get(id).skip(page * 10).limit(page * 10 + 10);
thread.views++; thread.views++;
if (thread && (req.user?.admin || !thread.deleted)) { if (thread && (req.user?.admin || !thread.deleted)) {
const messages = await Promise.all(thread.messages.map(async id => { const messages = await Promise.all(thread.messages.map(async id => {
const message = await MessageModel.get(id) const message = await MessageModel.get(id)
message.content = message.content.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;").replaceAll("'", "&#39;").replaceAll("\n", "<br>") message.content = message.content.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;").replaceAll(">", "&gt;")
.replaceAll("\"", "&quot;").replaceAll("'", "&#39;")
.replaceAll("\n", "<br>");
return req.user?.admin || !message?.deleted ? message.toObject({ virtuals: true }) : null; return req.user?.admin || !message?.deleted ? message.toObject({ virtuals: true }) : null;
})); }));
res.reply("thread", { thread, messages, scroll: req.query.scroll || false }); res.reply("thread", { page,thread, messages, scroll: req.query.scroll || thread.messages[0]});
} else } else
res.error(404, "We have not got this thread."); res.error(404, "We have not got this thread.");
thread.save(); thread.save();

97
views/extra/ot.ejs Normal file
View file

@ -0,0 +1,97 @@
<!DOCTYPE html>
<html lang="en">
<%- include("extra/meta", {title: "Thread list!" }) %>
<body style="text-align: center;">
<%- include("extra/navbar") %>
<link rel="stylesheet" href="/css/thread.css" />
<% if (user){ %>
<script type="module" src="/js/thread.js"></script>
<% }%>
<h1 style="font-size: 35px;color: #4d18e6;" ><%= thread.title %></h1>
<h3 >View count: <%= thread.views %></h1>
<h2 style="display:inline;">By <a href="<%='/users/' + thread.author.id %>"> <%= thread.author.name %></a>
<img class="circle" src="<%=thread.author.avatar %>">
</h2>
<% if (user && !thread.deleted){ %>
<a onclick="delete_thread('<%= thread.id %>' )" value=style="display:inline;" >DELETE</a>
<a onclick="edit_thread('<%= thread.id %>')" style="display:inline;" >EDIT</a>
<% } else if (thread.deleted) { %>
<h3 style="display:inline;">This thread has been deleted</h3>
<a onclick="undelete_thread('<%= thread.id %>')" style="display:inline;" >UNDELETE</a>
<% }; %>
<hr>
<div id="messages" value="<%= thread.id %>">
<% messages.filter(Boolean).forEach(message=>{ %>
<div class="message" id="message-<%= message.id %>">
<h3 style="float:right;"><%= new Date(message.time).toLocaleString() %> </h3>
<h2>
<img class="circle" src="<%= message.author.avatar %>">
<a href="/users/<%=message.author.id %>"><%=message.author.name %></a>:
</h2>
<p><%= message.content%></p><br>
<div id="message-delete-<%=message.id %>">
<% if (!message.deleted){ %>
<a onclick="delete_message('<%=message.id %>');">DELETE</a>
<a onclick="edit_message('<%=message.id %>');">EDIT</a>
<% }else{ %>
<h3 style="display:inline;">This message has been deleted</h3>
<a onclick="undelete_message('<%=message.id %>');">UNDELETE</a>
<% } %>
</div>
<div style="float: right;">
<h3 id="count<%=message.id %>" style="display:inline;"><%=message.reactCount %></h3>
<a onclick="react('<%=message.id %>', 'like');">+🔼</a>
<a onclick="react('<%=message.id %>', 'dislike');">-🔽</a>
</div>
</div>
<% }); %>
</div>
<hr>
<form id="send">
<textarea rows="4" cols="133" name="content"></textarea>
<input name="threadID" type="hidden" value="<%= thread.id %>"></input>
<br>
<% if (user){ %>
<button type="submit">Send!</button>
<%} else {%>
<button disabled>Login for send</button>
<% }%>
</form>
<script>
document.getElementById("message-<%= scroll %>").scrollIntoView();
</script>
<!-- BURAYA Bİ İLERİ BİR GERİ SAYFA BUTONU GELMEZ Mİ BE-->
</body>
</html>

View file

@ -1,104 +1,105 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<%- include("extra/meta", {title: "Thread list!" }) %> <%- include("extra/meta", {title: thread.title }) %>
<body> <body>
<%- include("extra/navbar") %> <%- include("extra/navbar") %>
<link href='https://unpkg.com/boxicons@2.1.2/css/boxicons.min.css' rel='stylesheet'> <link href='https://unpkg.com/boxicons@2.1.2/css/boxicons.min.css' rel='stylesheet'>
<link rel="stylesheet" href="/css/thread.css" /> <link rel="stylesheet" href="/css/thread.css" />
<% if (user){ %> <% if (user){ %>
<script type="module" src="/js/thread.js"></script> <script type="module" src="/js/thread.js"></script>
<% }%> <% }; %>
<div style="text-align:center;padding:8px">
<div class="title"><%= thread.title %></div>
<div class="date">
<%= new Date(thread.time).toLocaleString() %> • Views: <%= thread.views %>
</div>
</div>
<div id="messages" value="<%= thread.id %>">
<div style="text-align:center;padding:8px"> <% messages.filter(Boolean).forEach(message=>{ %>
<div class="title">İlk Forum</div>
<div class="date"> <div class="message" id="message-<%= message.id %>">
20/04/2000
</div>
</div>
<div class="thread">
<div class="left"> <div class="left">
<img src="https://renk.gen.tr/images/acik-mavi-renk.jpg"/> <img src="<%= message.author.avatar || '/images/guest.png' %>"/>
<div class="username">Tokmak</div> <div class="username"><a href="/users/<%=message.author.id %>"><%=message.author.name %></a></div>
<div class="date"> <div class="date">
20/04/2000 <%= new Date(message.time).toLocaleDateString() %>
</div>
<div class="date">
<%= new Date(message.time).toLocaleTimeString() %>
</div> </div>
</div> </div>
<div class="content"> <div class="content"><%- message.content %></div>
Merhaba ! Bu benim ilk yazım Merhaba ! Bu benim ilk yazım <% if(user){ %>
Merhaba ! Bu benim ilk yazım <% if(user.id === message.author.id || user.admin){ %>
Merhaba ! Bu benim ilk yazım
Merhaba ! Bu benim ilk yazım <div class="dots" onclick="dots('<%=message.id %>')">
Merhaba ! Bu benim ilk yazım <i class='bx bx-dots-horizontal-rounded' ></i>
Merhaba ! Bu benim ilk yazım
Merhaba ! Bu benim ilk yazım
</div>
<div class="dots" onclick="dots()">
<i class='bx bx-dots-horizontal-rounded' ></i>
</div>
<div class="dots-menu">
<a>Delete</a>
<a>Edit</a>
</div> </div>
<% if (!message.deleted){ %>
<div class="likes">
<div> <div class="dots-menu" id="dot-<%=message.id %>">
<i class='bx bx-like'></i> 5 <a onclick="delete_message('<%=message.id %>');">Delete</a>
</div> <a onclick="edit_message('<%=message.id %>');">Edit</a>
<div>
<i class='bx bx-dislike'></i> 30
</div> </div>
<% }else if (user.admin){ %>
<div class="dots-menu" id="dot-<%=message.id %>">
<a onclick="undelete_message('<%=message.id %>');">UNDELETE</a>
</div>
<% } %>
<% } %>
<div class="reactions">
<div>
<i class='bx bx-like'></i> <%=message.react.like.length %>
</div>
<div>
<i class='bx bx-dislike'></i> <%=message.react.dislike.length %>
</div> </div>
</div> </div>
<% }; %>
<div class="pagination">
<div class="back">
<i class='bx bxs-chevron-left'></i>
</div> </div>
<div class="numbers">
<a class="number active" href="/deneme">1</a>
<a class="number" href="/deneme">2</a>
<a class="number" href="/deneme">3</a>
<a class="number" href="/deneme">4</a>
<a class="number" href="/deneme">5</a>
<a class="number" href="/deneme">6</a>
<% }); %>
</div> </div>
<div class="after"> <div class="pagination">
<i class='bx bxs-chevron-right'></i> <div class="back">
</div> <% if (page > 0){ %>
<a href="<%= thread.getLink() %>?page=<%= page-1 %>" class='bx bxs-chevron-left'></a>
</div> <% } %>
</div>
<div class="numbers">
<% for(let i=0;i< Math.ceil(messages.length/10);i++){ %>
<a class="number <%= i==page?'active':'' %>" href="<%= thread.getLink() %>?page=<%= i %>"><%= i %></a>
<% } %>
</div>
<!-- ==== Dots Event ==== --> <div class="after">
<% if (Math.ceil(messages.length/10)-1 > page){ %>
<a href="<%= thread.getLink() %>?page=<%= page +1 %>" class='bx bxs-chevron-right'></a>
<script> <% } %>
function dots(){ </div>
var box= document.getElementsByClassName("dots-menu")[0];
box.classList.toggle("active")
}
</script>
<script> </div>
document.getElementById("message-<%= scroll %>").scrollIntoView();
</script> <script>
document.getElementById("message-<%= scroll %>").scrollIntoView();
function dots(id) {
document.getElementById('dot-'+id).classList.toggle('active')
}
</script>
<!-- BURAYA Bİ İLERİ BİR GERİ SAYFA BUTONU GELMEZ Mİ BE-->
</body> </body>
</html> </html>