mirror of
https://github.com/Akif9748/akf-forum.git
synced 2024-12-22 23:59:08 +03:00
thread converted to new theme, page support
This commit is contained in:
parent
c8474694da
commit
9ad7c03162
6 changed files with 274 additions and 167 deletions
10
README.md
10
README.md
|
@ -30,12 +30,14 @@ Akf-forum has got an API for AJAX, other clients etc. And, you can learn about A
|
|||
## Roadmap
|
||||
### TO-DO:
|
||||
- If thread deleted, not show its messages in API.
|
||||
- Thread.ejs fix with new theme
|
||||
- Profile photos will store in database
|
||||
- regex for pfp for now and
|
||||
- admin perm for undelete, thread+message
|
||||
- admin perm for undelete, thread + message
|
||||
- page support for threads
|
||||
|
||||
- message "<b>"
|
||||
- author name of thread
|
||||
- page for threads - users
|
||||
- edit & delete button for thread
|
||||
### Frontend
|
||||
### User
|
||||
| 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 |
|
||||
| Send | 🟢 | HIGH |
|
||||
| Delete | 🟢 | HIGH |
|
||||
| Regex for scripts | 🔴 | HIGH |
|
||||
| Regex for scripts | 🟢 | HIGH |
|
||||
| Undelete | 🟢 | MEDIUM |
|
||||
| React | 🟢 | MEDIUM |
|
||||
| Edit | 🔴 | MEDIUM |
|
||||
|
|
|
@ -1,136 +1,140 @@
|
|||
.title {
|
||||
color: #414141;
|
||||
font-weight: 700;
|
||||
font-size: 36px;
|
||||
color: #414141;
|
||||
font-weight: 700;
|
||||
font-size: 36px;
|
||||
}
|
||||
.date{
|
||||
|
||||
.date {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.thread{
|
||||
.message {
|
||||
max-width: 800px;
|
||||
box-shadow: 0 0 5px 0 #c3c3c3;
|
||||
margin: 10px auto;
|
||||
padding: 20px;
|
||||
display:flex;
|
||||
gap:10px;
|
||||
position:relative;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.thread .left{
|
||||
.message .left {
|
||||
text-align: center;
|
||||
border-right: 2px solid #d9d9d9;
|
||||
border-right: 2px solid #d9d9d9;
|
||||
}
|
||||
|
||||
.thread .left img{
|
||||
.message .left img {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius: 50%;
|
||||
margin-right: 5px;
|
||||
height: 100px;
|
||||
border-radius: 50%;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.thread .left .username{
|
||||
color: #555;
|
||||
.message .left .username {
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.content{
|
||||
.content {
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
.likes{
|
||||
position:absolute;
|
||||
right:20px;
|
||||
bottom:20px;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
gap:10px;
|
||||
.reactions {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
bottom: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.likes div{
|
||||
display:flex;
|
||||
align-items:center;
|
||||
gap:5px;
|
||||
padding:4px;
|
||||
|
||||
.reactions div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 4px;
|
||||
color: #747474;
|
||||
cursor:pointer;
|
||||
transition:color 0.3s ease;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
.likes div:hover{
|
||||
color: #151515;
|
||||
|
||||
.reactions div:hover {
|
||||
color: #151515;
|
||||
|
||||
}
|
||||
|
||||
.likes div i{
|
||||
font-size:22px;
|
||||
.reactions div i {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
|
||||
.pagination{
|
||||
.pagination {
|
||||
box-shadow: 0 0 5px 0 #c3c3c3;
|
||||
margin: 10px auto;
|
||||
padding: 8px;
|
||||
display:flex;
|
||||
justify-content:space-between;
|
||||
align-items:center;
|
||||
max-width:400px;
|
||||
gap:10px;
|
||||
position:relative;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
max-width: 400px;
|
||||
gap: 10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.pagination .back ,
|
||||
.pagination .back,
|
||||
.pagination .after {
|
||||
color:#747474;
|
||||
font-size:26px;
|
||||
cursor:pointer;
|
||||
color: #747474;
|
||||
font-size: 26px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
.pagination .numbers{
|
||||
display:flex;
|
||||
align-items:center;
|
||||
gap:5px;
|
||||
.pagination .numbers {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.pagination .number {
|
||||
color: #747474;
|
||||
font-size: 22px;
|
||||
border: 0 0 5px #747474;
|
||||
padding: 8px;
|
||||
border-radius: 2px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
margin: 8px;
|
||||
color: #747474;
|
||||
font-size: 22px;
|
||||
border: 0 0 5px #747474;
|
||||
padding: 8px;
|
||||
border-radius: 2px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
margin: 8px;
|
||||
}
|
||||
.pagination .number.active{
|
||||
color: #4d18e6;
|
||||
font-weight: 700;
|
||||
|
||||
.pagination .number.active {
|
||||
color: #4d18e6;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
|
||||
.dots{
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
font-size: 22px;
|
||||
top: 10px;
|
||||
color: #747474;
|
||||
cursor:pointer;
|
||||
.dots {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
font-size: 22px;
|
||||
top: 10px;
|
||||
color: #747474;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dots-menu{
|
||||
position: absolute;
|
||||
top: 50px;
|
||||
right: 0;
|
||||
background-color: #e6e6e6;
|
||||
padding: ;
|
||||
width: 100px;
|
||||
text-align: center;
|
||||
display:none;
|
||||
.dots-menu {
|
||||
position: absolute;
|
||||
top: 50px;
|
||||
right: 0;
|
||||
background-color: #e6e6e6;
|
||||
width: 100px;
|
||||
text-align: center;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dots-menu.active{
|
||||
display:block;
|
||||
.dots-menu.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dots-menu a{
|
||||
display:block;
|
||||
margin:8px;
|
||||
cursor:pointer;
|
||||
}
|
||||
.dots-menu a {
|
||||
display: block;
|
||||
margin: 8px;
|
||||
cursor: pointer;
|
||||
}
|
|
@ -45,7 +45,7 @@ window.scrollTo(0, document.body.scrollHeight);
|
|||
/**
|
||||
* Message Sender
|
||||
*/
|
||||
document.getElementById("send").addEventListener("submit", async e => {
|
||||
document.getElementById("send")?.addEventListener("submit", async e => {
|
||||
|
||||
e.preventDefault();
|
||||
const form = e.target;
|
||||
|
|
|
@ -17,18 +17,21 @@ app.get("/create*", (req, res) => res.reply("create_thread"));
|
|||
app.get("/:id", async (req, res) => {
|
||||
|
||||
const { id } = req.params;
|
||||
|
||||
const thread = await ThreadModel.get(id);
|
||||
const page = req.query.page || 0;
|
||||
const thread = await ThreadModel.get(id).skip(page * 10).limit(page * 10 + 10);
|
||||
thread.views++;
|
||||
|
||||
if (thread && (req.user?.admin || !thread.deleted)) {
|
||||
const messages = await Promise.all(thread.messages.map(async id => {
|
||||
const message = await MessageModel.get(id)
|
||||
message.content = message.content.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'").replaceAll("\n", "<br>")
|
||||
message.content = message.content.replaceAll("&", "&")
|
||||
.replaceAll("<", "<").replaceAll(">", ">")
|
||||
.replaceAll("\"", """).replaceAll("'", "'")
|
||||
.replaceAll("\n", "<br>");
|
||||
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
|
||||
res.error(404, "We have not got this thread.");
|
||||
thread.save();
|
||||
|
|
97
views/extra/ot.ejs
Normal file
97
views/extra/ot.ejs
Normal 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>
|
149
views/thread.ejs
149
views/thread.ejs
|
@ -1,104 +1,105 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<%- include("extra/meta", {title: "Thread list!" }) %>
|
||||
<%- include("extra/meta", {title: thread.title }) %>
|
||||
|
||||
|
||||
<body>
|
||||
<%- 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" />
|
||||
<% 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">
|
||||
<div class="title">İlk Forum</div>
|
||||
<div class="date">
|
||||
20/04/2000
|
||||
</div>
|
||||
</div>
|
||||
<div class="thread">
|
||||
<% messages.filter(Boolean).forEach(message=>{ %>
|
||||
|
||||
<div class="message" id="message-<%= message.id %>">
|
||||
<div class="left">
|
||||
<img src="https://renk.gen.tr/images/acik-mavi-renk.jpg"/>
|
||||
<div class="username">Tokmak</div>
|
||||
<div class="date">
|
||||
20/04/2000
|
||||
<img src="<%= message.author.avatar || '/images/guest.png' %>"/>
|
||||
<div class="username"><a href="/users/<%=message.author.id %>"><%=message.author.name %></a></div>
|
||||
<div class="date">
|
||||
<%= new Date(message.time).toLocaleDateString() %>
|
||||
</div>
|
||||
<div class="date">
|
||||
<%= new Date(message.time).toLocaleTimeString() %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
Merhaba ! Bu benim ilk yazım Merhaba ! Bu benim ilk yazım
|
||||
Merhaba ! Bu benim ilk yazım
|
||||
Merhaba ! Bu benim ilk yazım
|
||||
Merhaba ! Bu benim ilk yazım
|
||||
Merhaba ! Bu benim ilk yazım
|
||||
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 class="content"><%- message.content %></div>
|
||||
<% if(user){ %>
|
||||
<% if(user.id === message.author.id || user.admin){ %>
|
||||
|
||||
<div class="dots" onclick="dots('<%=message.id %>')">
|
||||
<i class='bx bx-dots-horizontal-rounded' ></i>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="likes">
|
||||
<div>
|
||||
<i class='bx bx-like'></i> 5
|
||||
</div>
|
||||
<div>
|
||||
<i class='bx bx-dislike'></i> 30
|
||||
<% if (!message.deleted){ %>
|
||||
|
||||
<div class="dots-menu" id="dot-<%=message.id %>">
|
||||
<a onclick="delete_message('<%=message.id %>');">Delete</a>
|
||||
<a onclick="edit_message('<%=message.id %>');">Edit</a>
|
||||
</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 class="pagination">
|
||||
<div class="back">
|
||||
<i class='bx bxs-chevron-left'></i>
|
||||
</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 class="after">
|
||||
<i class='bx bxs-chevron-right'></i>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="pagination">
|
||||
<div class="back">
|
||||
<% if (page > 0){ %>
|
||||
<a href="<%= thread.getLink() %>?page=<%= page-1 %>" class='bx bxs-chevron-left'></a>
|
||||
<% } %>
|
||||
</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 ==== -->
|
||||
|
||||
|
||||
<script>
|
||||
function dots(){
|
||||
var box= document.getElementsByClassName("dots-menu")[0];
|
||||
box.classList.toggle("active")
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<div class="after">
|
||||
<% if (Math.ceil(messages.length/10)-1 > page){ %>
|
||||
<a href="<%= thread.getLink() %>?page=<%= page +1 %>" class='bx bxs-chevron-right'></a>
|
||||
<% } %>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById("message-<%= scroll %>").scrollIntoView();
|
||||
</script>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
</html>
|
||||
</html>
|
Loading…
Reference in a new issue