From 9ee1d589ebc78117103099f6c185241de0e01415 Mon Sep 17 00:00:00 2001 From: Akif9748 <akif9748@gmail.com> Date: Thu, 25 May 2023 19:28:05 +0300 Subject: [PATCH] Better pagination and rewiretten threads etc. --- README.md | 3 + src/index.js | 4 +- src/themes/bootstrap_black/public/main.css | 45 ----- src/themes/bootstrap_black/views/admin.ejs | 124 +++++++++++++ .../bootstrap_black/views/categories.ejs | 74 ++++++++ .../bootstrap_black/views/create_category.ejs | 59 ++++++ .../bootstrap_black/views/create_thread.ejs | 69 +++++++ src/themes/bootstrap_black/views/thread.ejs | 168 ++++++++++++++++++ src/themes/bootstrap_black/views/threads.ejs | 83 +++++++++ src/themes/bootstrap_black/views/users.ejs | 51 ++++++ 10 files changed, 633 insertions(+), 47 deletions(-) create mode 100644 src/themes/bootstrap_black/views/admin.ejs create mode 100644 src/themes/bootstrap_black/views/categories.ejs create mode 100644 src/themes/bootstrap_black/views/create_category.ejs create mode 100644 src/themes/bootstrap_black/views/create_thread.ejs create mode 100644 src/themes/bootstrap_black/views/thread.ejs create mode 100644 src/themes/bootstrap_black/views/threads.ejs create mode 100644 src/themes/bootstrap_black/views/users.ejs diff --git a/README.md b/README.md index f6208a7..0391f07 100644 --- a/README.md +++ b/README.md @@ -58,8 +58,11 @@ Akf-forum has got an API for AJAX (fetch), other clients etc. And, you can learn - add used open source libraries to README.md - send public to common/public - user.ejs for per theme +- categori search title like thread search ### front-end +- working reset button +- better pagination - text alling center body - add a css file for CodeMirror in threads / send message ok - old contents / titles add to forum interface diff --git a/src/index.js b/src/index.js index 2df22dd..c2124a2 100644 --- a/src/index.js +++ b/src/index.js @@ -23,8 +23,6 @@ app.ips = []; app.set("view engine", "ejs"); app.set("limits", limits); -if (RLS.enabled) app.use(RL(RLS.windowMs, RLS.max)); - for (const theme of fs.readdirSync(join(__dirname, "themes"))) app.use(`/themes/${theme}`, express.static(join(__dirname, "themes", theme, "public"))); @@ -70,6 +68,8 @@ app.use(express.static(join(__dirname, "public")), express.json(), express.urlen } ); +if (RLS.enabled) app.use(RL(RLS.windowMs, RLS.max)); + if (discord_auth) app.set("DISCORD_AUTH_URL", `https://discord.com/api/oauth2/authorize?client_id=${process.env.DISCORD_ID}&redirect_uri=${host}%2Fauth%2Fdiscord&response_type=code&scope=identify`); diff --git a/src/themes/bootstrap_black/public/main.css b/src/themes/bootstrap_black/public/main.css index b98806f..77443b7 100644 --- a/src/themes/bootstrap_black/public/main.css +++ b/src/themes/bootstrap_black/public/main.css @@ -293,51 +293,6 @@ a { } -/*********************************** -PAGINATION -***********************************/ -.pagination { - box-shadow: 0 0 5px 0 var(--box-shadow); - margin: 10px auto; - padding: 8px; - display: flex; - justify-content: space-between; - align-items: center; - max-width: 400px; - gap: 10px; - position: relative; -} - -.pagination .back, -.pagination .after { - color: var(--second); - font-size: 26px; - cursor: pointer; -} - - -.pagination .numbers { - display: flex; - align-items: center; - gap: 5px; -} - -.pagination .number { - color: var(--second); - font-size: 22px; - border: 0 0 5px var(--second); - padding: 8px; - border-radius: 2px; - font-weight: 600; - cursor: pointer; - margin: 8px; -} - -.pagination .number.active { - color: var(--main); - font-weight: 700; -} - /************************************ Threads diff --git a/src/themes/bootstrap_black/views/admin.ejs b/src/themes/bootstrap_black/views/admin.ejs new file mode 100644 index 0000000..62aa5ea --- /dev/null +++ b/src/themes/bootstrap_black/views/admin.ejs @@ -0,0 +1,124 @@ +<!DOCTYPE html> +<html lang="en"> + +<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/meta"), {title: "Admin Panel!" }) %> + + +<body style="text-align: center;"> + <%- include(dataset.getFile(dataset.theme.codename +"/views/extra/navbar")) %> + + <style> + table { + font-family: arial, sans-serif; + border-collapse: collapse; + width: 100%; + } + + td, + th { + border: 1px solid #dddddd; + text-align: left; + padding: 8px; + color: var(--anti); + } + + tr:nth-child(even) { + background-color: #dddddd; + } + </style> + + <h2>Welcome to the admin panel of the forum, <%= user.name %>!</h1> + <div> + + + + <!-- Modal --> + <div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true"> + <div class="modal-dialog modal-lg" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <h5 class="modal-title" id="exampleModalLabel">Banned users</h5> + <button type="button" class="close" data-dismiss="modal" aria-label="Close" onclick="document.getElementById('exampleModal').style.display = 'none';"> + <span aria-hidden="true">×</span> + </button> + </div> + <div class="modal-body"> + <table> + <tr> + <th>IP</th> + <th>Reason</th> + <th>AuthorID</th> + </tr> + <% for (const ban of bans) { %> + <tr> + <td><%=ban.ip%></td> + <td><%=ban.reason%></td> + <td><%=ban.authorID%></td> + </tr> + <% } %> + </table> + </div> + <div class="modal-footer"> + <a class="btn-primary" onclick="ban();">IP BAN</a> + <a class="btn-outline-primary" onclick="unban();">REMOVE IP BAN</a> + </div> + </div> + </div> + </div> + + + + <button onclick="window.location.href = '/categories/create';" class="btn-primary">Create Category</button> + <button onclick="window.location.href = '/admin/config';" class="btn-primary">Edit config</button> + + <button onclick="document.getElementById('exampleModal').style.display = 'block';" class="btn-primary" type="button" data-bs-toggle="collapse" data-bs-target="#exampleModal" aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation"> + Banned users + </button> + <div> + <table> + <tr> + <th>Admin list</th> + </tr> + <% for (const admin of admins) { %> + <tr> + <td><a style="color: var(--anti);" href="<%= admin.getLink() %>"><%= admin.name %></a></td> + </tr> + <% } %> + </table> + <ul> + + + </ul> + </div> + + <script type="module"> + import request from "../../js/request.js"; + + window.unban = async function() { + const ip = prompt("Enter ip to unban"); + if (!ip) return; + const response = await request("/api/bans/" + ip, "DELETE"); + if (response) + alert("IP unbanned!"); + else + alert("IP is not unbanned!"); + location.reload(); + } + window.ban = async function() { + const ip = prompt("Enter ip to ban"); + if (!ip) return; + + const response = await request("/api/bans/" + ip); + if (response) + alert("IP banned!"); + else + alert("IP is not banned!"); + location.reload(); + } + </script> + </div> + <%- include(dataset.getFile(dataset.theme.codename +"/views/extra/footer")) %> + +</body> + +</html> \ No newline at end of file diff --git a/src/themes/bootstrap_black/views/categories.ejs b/src/themes/bootstrap_black/views/categories.ejs new file mode 100644 index 0000000..8086e65 --- /dev/null +++ b/src/themes/bootstrap_black/views/categories.ejs @@ -0,0 +1,74 @@ +<!DOCTYPE html> +<html lang="en"> +<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/meta"), {title: "Category list!" }) %> + +<body> + <link href='https://unpkg.com/boxicons@2.1.2/css/boxicons.min.css' rel='stylesheet'> + + <%- include(dataset.getFile(dataset.theme.codename +"/views/extra/navbar")) %> + + <div class="container my-3"> + <div class="row"> + <div class="col-12"> + <h2 class="h4 text-white bg-info mb-0 p-4 rounded-top"><%= "Categories" %></h2> + + <table class="table table-striped table-bordered table-responsive-lg"> + <thead class="thead-light"> + <tr> + <th scope="col" class="topic-col">Topic</th> + <th scope="col">Description</th> + <% if (user?.admin){ %> <th scope="col" class="last-post-col">Action</th> <% } %> + </tr> + </thead> + <tbody> + <% categories.forEach(category=>{ %> + <tr> + <td> + <h3 class="h6"> + <a href="<%= category.getLink() %>"><%= category.name %></a> + </h3> + </td> + <td> + <div><%= category.desp %></div> + </td> + <% if (user?.admin){ %> + <td> + <a class="btn-danger" onclick="fetch('/api/categories/<%= category.id %>/',{method:'DELETE'})"><i class="bx bx-trash bx-sm"></i></a> + </td> + <% } %> + </tr> + <% }); %> + + </tbody> + </table> + </div> + </div> + + + <% if(typeof page === "number"){ %> + <div class="mb-3 clearfix"> + <nav aria-label="Navigate post pages" class="float-lg-right"> + <ul class="pagination pagination-sm mb-lg-0"> + <% if (page > 0){ %> + <li class="page-item"><a href="/categories?page=<%= page-1 %>" class="page-link">Back</a></li> + <% } %> + <% for(let i=0; i < pages; i++){ %> + <li class="page-item <%= i==page?'active':'' %>"><a href="/categories?page=<%= i %>" class="page-link"><%= i+1 %> + <% if (i==page){ %> + <span class="sr-only">(current)</span> + <% } %> + </a></li> + <% } %> + <% if (pages-1 > page) { %> + <li class="page-item"><a href="/categories?page=<%= page+1 %>" class="page-link">Next</a></li> + <% } %> + </ul> + </nav> + </div> + + <% } %> + <%- include(dataset.getFile(dataset.theme.codename +"/views/extra/footer")) %> + +</body> + +</html> \ No newline at end of file diff --git a/src/themes/bootstrap_black/views/create_category.ejs b/src/themes/bootstrap_black/views/create_category.ejs new file mode 100644 index 0000000..12aae29 --- /dev/null +++ b/src/themes/bootstrap_black/views/create_category.ejs @@ -0,0 +1,59 @@ +<!DOCTYPE html> +<html lang="en"> +<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/meta"), {title: "Create Category!" }) %> + +<body> + <%- include(dataset.getFile(dataset.theme.codename +"/views/extra/navbar")) %> + + <link rel="stylesheet" href="/libs/simplemde/simplemde.min.css"> + <script src="/libs/simplemde/simplemde.min.js"></script> + + <div class="container my-3"> + <div class="col-12"> + <h2 class="h4 text-white bg-info mb-3 p-4 rounded">Create new category</h2> + <form class="mb-3"> + <div class="form-group"> + <label for="topic">Name</label> + <input type="text" class="form-control" id="title" placeholder="Give your category a name" required> + </div> + <div class="form-group"> + <label for="comment">Description</label> + <textarea id="textarea"></textarea> + </div> + <button type="submit" class="btn btn-primary">Create</button> + <button type="reset" class="btn btn-danger">Reset</button> + </form> + </div> + </div> + </div> + + + + <script src="/js/editor.js"></script> + + + <script type="module"> + const simplemde = editor("category-create"); + + import request from "../../js/request.js"; + + document.addEventListener("submit", async e => { + e.preventDefault(); + const response = await request("/api/categories/", "POST", { + name: document.getElementById("title").value, + desp: simplemde.value() + }); + + simplemde.clearAutosavedValue(); + + if (response) + window.location.href = "/categories/" + response.id; + + + }); + </script> + <%- include(dataset.getFile(dataset.theme.codename +"/views/extra/footer")) %> + +</body> + +</html> \ No newline at end of file diff --git a/src/themes/bootstrap_black/views/create_thread.ejs b/src/themes/bootstrap_black/views/create_thread.ejs new file mode 100644 index 0000000..9d9db3a --- /dev/null +++ b/src/themes/bootstrap_black/views/create_thread.ejs @@ -0,0 +1,69 @@ +<!DOCTYPE html> +<html lang="en"> + +<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/meta"), {title: "Create thread!" }) %> + +<body> + + <%- include(dataset.getFile(dataset.theme.codename +"/views/extra/navbar")) %> + + <link rel="stylesheet" href="/libs/simplemde/simplemde.min.css"> + <script src="/libs/simplemde/simplemde.min.js"></script> + + + <div class="container my-3"> + <div class="col-12"> + <h2 class="h4 text-white bg-info mb-3 p-4 rounded">Create new thread</h2> + <form class="mb-3"> + <div class="form-group"> + <label for="topic">Title</label> + <input type="text" class="form-control" id="title" placeholder="Give your thread a title." required> + </div> + <div class="form-group"> + <label for="comment">Comment:</label> + <textarea id="textarea"></textarea> + </div> + <div class="form-group"> + <label class="form-check-label"> + Category: + </label> + + <select id="category" class="input"> + <% for (const category of categories) { %> + <option value="<%= category.id %>"><%= category.name %></option> + <% } %> + </select> + </div> + <button type="submit" class="btn btn-primary">Create</button> + <button type="reset" class="btn btn-danger">Reset</button> + </form> + </div> + </div> + </div> + + <script src="/js/editor.js"></script> + + + <script type="module"> + const simplemde = editor("thread-create"); + + import request from "../../js/request.js"; + + document.addEventListener("submit", async e => { + e.preventDefault(); + const response = await request("/api/threads/", "POST", { + title: document.getElementById("title").value, + content: simplemde.value(), + category: document.getElementById("category").value + }); + if (response) { + simplemde.clearAutosavedValue(); + window.location.href = "/threads/" + response.id; + } + }); + </script> + <%- include(dataset.getFile(dataset.theme.codename +"/views/extra/footer")) %> + +</body> + +</html> \ No newline at end of file diff --git a/src/themes/bootstrap_black/views/thread.ejs b/src/themes/bootstrap_black/views/thread.ejs new file mode 100644 index 0000000..415c89e --- /dev/null +++ b/src/themes/bootstrap_black/views/thread.ejs @@ -0,0 +1,168 @@ +<!DOCTYPE html> +<html lang="en"> + +<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/meta"), {title: thread.title }) %> + + +<body> + <%- include(dataset.getFile(dataset.theme.codename +"/views/extra/navbar")) %> + + <script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/2.1.0/showdown.min.js"></script> + <link href='https://unpkg.com/boxicons@2.1.2/css/boxicons.min.css' rel='stylesheet'> + <link rel="stylesheet" href="/libs/simplemde/simplemde.min.css"> + <script src="/libs/simplemde/simplemde.min.js"></script> + + <div style="text-align:center;padding:8px"> + <a href="/categories/<%= thread.categoryID %>" class="title" id="title"><%= thread.title %></a> + <div class="date"> + <%= new Date(thread.time).toLocaleString() %> • Views: <%= thread.views %> + </div> + <div class="date"> + <a style="color: var(--anti);" href="/users/<%= thread.author.id %>"><%= thread.author.name %></a> <%= "• "+(thread.edited ? "Edited" : "Not edited")%> + </div> + + </div> + + <div style="text-align:center;padding:8px"> + <% if (user && (user.id === thread.authorID || user.admin ) && !thread.deleted){ %> + <a onclick="delete_thread('<%= thread.id %>')" class="btn-outline-primary">DELETE</a> + <a onclick="edit_thread('<%= thread.id %>')" class="btn-outline-primary">EDIT</a> + <% } else if (thread.deleted) { %> + <h4 class="title" style="display:inline; font-size: 20px;">This thread has been deleted</h3> + <a onclick="undelete_thread('<%= thread.id %>')" class="btn-primary">UNDELETE</a> + <% }; %> + </div> + + <div id="messages"> + + <% messages.filter(Boolean).forEach(message=>{ %> + + <div class="message" id="message-<%= message.id %>"> + <div class="left"> + <img src="<%= message.author.avatar %>" /> + <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"><%= message.content %></div> + <% if(user){ %> + <% if(user.id === message.authorID || user.admin){ %> + + <div class="dots" modal="#modal-<%=message.id %>"> + <% if (message.deleted){ %> + <i class='bx bx-trash bx-sm' id="deleted" style="color: var(--important);"></i> + <% } %> + <% if (message.edited){ %> + <i class='bx bx-pencil bx-sm' id="edited" style="color: GREEN;"></i> + <% } %> + <i class='bx bx-dots-horizontal-rounded'></i> + </div> + + <div class="dots-menu" id="modal-<%=message.id %>"> + <% if (!message.deleted){ %> + <a onclick="delete_message('<%=message.id %>');">Delete</a> + <a onclick="edit_message('<%=message.id %>');">Edit</a> + <% }else { %> + <a onclick="undelete_message('<%=message.id %>');">Undelete</a> + <% } %> + </div> + <% } %> + + <div class="reactions"> + <div <% if (message.react.like.includes(user?.id)) { %> style="color: var(--main)" <% } %>> + <i onclick='react("<%= message.id %>","like");' class='bx bx-like'></i> + <div id="like"><%=message.react.like.length %></div> + </div> + <div <% if (message.react.dislike.includes(user?.id)) { %> style="color: var(--main)" <% } %>> + <i onclick='react("<%= message.id %>","dislike");' class='bx bx-dislike'></i> + <div id="dislike"><%=message.react.dislike.length %></div> + </div> + </div> + <% }; %> + + </div> + + <% }); %> + </div> + + <script> + const converter = new showdown.Converter(); + for (const message of document.querySelectorAll(".message")) { + const content = message.querySelector(".content"); + content.innerHTML = converter.makeHtml(content.rawText = content.innerHTML); + } + </script> + <script src="/js/modal.js"></script> + <% if (user){ %> + <script type="module" src="/js/thread.js"></script> + + <div class="message" id="send-div"> + + <form id="send" style="width:100%"> + <textarea rows="4" id="textarea"></textarea> + <input name="page" type="hidden" value="<%= page %>"></input> + <button class="btn-primary">Send!</button> + </form> + + </div> + + <script src="/js/editor.js"></script> + + <script type="module"> + const simplemde = editor("thread-<%= thread.id %>"); + + import request from "../../js/request.js"; + + document.getElementById("send").addEventListener("submit", async e => { + + e.preventDefault(); + const res = await request("/api/messages", "POST", { + threadID: "<%= thread.id %>", + content: simplemde.value() + }) + simplemde.clearAutosavedValue(); + let tp = Number("<%= thread.pages %>") + let tm = Number("<%= thread.count %>") + if (tp * 10 === tm) tp++; + if (res) location.href = `/threads/<%= thread.id %>?page=${tp-1}`; + }); + </script> + <style> + .fa { + color: var(--main); + } + </style> + + + <% }%> + + <div class="mb-3 clearfix"> + <nav aria-label="Navigate post pages" class="float-lg-right"> + <ul class="pagination pagination-sm mb-lg-0"> + <% if (page > 0){ %> + <li class="page-item"><a href="/<%= thread.getLink() %>?page=<%= page-1 %>" class="page-link">Back</a></li> + <% } %> + <% for(let i=0; i < thread.pages; i++){ %> + <li class="page-item <%= i==page?'active':'' %>"><a href="/<%= thread.getLink() %>?page=<%= i %>" class="page-link"><%= i+1 %> + <% if (i==page){ %> + <span class="sr-only">(current)</span> + <% } %> + </a></li> + <% } %> + <% if (thread.pages-1 > page) { %> + <li class="page-item"><a href="/<%= thread.getLink() %>?page=<%= page+1 %>" class="page-link">Next</a></li> + <% } %> + </ul> + </nav> + </div> + + <%- include(dataset.getFile(dataset.theme.codename +"/views/extra/footer")) %> + +</body> + +</html> \ No newline at end of file diff --git a/src/themes/bootstrap_black/views/threads.ejs b/src/themes/bootstrap_black/views/threads.ejs new file mode 100644 index 0000000..b2b0f5e --- /dev/null +++ b/src/themes/bootstrap_black/views/threads.ejs @@ -0,0 +1,83 @@ +<!DOCTYPE html> +<html lang="en"> +<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/meta"), {title: "Thread list!" }) %> + +<body style="text-align: center;"> + <link href='https://unpkg.com/boxicons@2.1.2/css/boxicons.min.css' rel='stylesheet'> + + <%- include(dataset.getFile(dataset.theme.codename +"/views/extra/navbar")) %> + + + <div class="container my-3"> + <div class="row"> + <div class="col-12"> + <h2 class="h4 text-white bg-info mb-0 p-4 rounded-top"><%= title || "Threads" %></h2> + <h3 class="h6 text-white bg-info mb-0 p-4 rounded-top"><%= desp %></h2> + + <table class="table table-striped table-bordered table-responsive-lg"> + <thead class="thead-light"> + <tr> + <th scope="col" class="topic-col">Topic</th> + <th scope="col" class="created-col">Created</th> + <th scope="col">Statistics</th> + <% if (user?.admin){ %> <th scope="col" class="last-post-col">Action</th> <% } %> + </tr> + </thead> + <tbody> + + <% threads.forEach(thread=>{ %> + <tr> + <td> + <h3 class="h6"> + <% if (thread.deleted) { %> <span class="badge badge-primary">[DELETED]</span><% } %> + <a href="<%= thread.getLink() %>"><%= thread.title %></a> + </h3> + </td> + <td> + <div> </div> + <div class="avatar">by <a href="<%= thread.getLink() %>"><%= thread.author.name %></a><img src="<%=thread.author.avatar %>"></div> + <div><%= new Date(thread.time).toLocaleString() %></div> + </td> + <td> + <div><%= thread.messages.length %> messages</div> + <div><%= thread.views %> views</div> + </td> + <% if (user?.admin){ %> + <td> + <% if (!thread.deleted){ %> + <a class="btn-danger" onclick="fetch('/api/threads/<%= thread.id %>/',{method:'DELETE'})"><i class="bx bx-trash bx-sm"></i></a> + <% } %> + </td> + <% } %> + </tr> + <% }); %> + </tbody> + </table> + </div> + </div> + <div class="mb-3 clearfix"> + <nav aria-label="Navigate post pages" class="float-lg-right"> + <ul class="pagination pagination-sm mb-lg-0"> + <% if (page > 0){ %> + <li class="page-item"><a href="/threads?page=<%= page-1 %>" class="page-link">Back</a></li> + <% } %> + <% for(let i=0; i < pages; i++){ %> + <li class="page-item <%= i==page?'active':'' %>"><a href="/threads?page=<%= i %>" class="page-link"><%= i+1 %> + <% if (i==page){ %> + <span class="sr-only">(current)</span> + <% } %> + </a></li> + <% } %> + <% if (pages-1 > page) { %> + <li class="page-item"><a href="/threads?page=<%= page+1 %>" class="page-link">Next</a></li> + <% } %> + </ul> + </nav> + </div> + <a href="/threads/create" class="btn btn-lg btn-primary">New Thread</a> + </div> + <%- include(dataset.getFile(dataset.theme.codename +"/views/extra/footer")) %> + +</body> + +</html> \ No newline at end of file diff --git a/src/themes/bootstrap_black/views/users.ejs b/src/themes/bootstrap_black/views/users.ejs new file mode 100644 index 0000000..16661e7 --- /dev/null +++ b/src/themes/bootstrap_black/views/users.ejs @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html lang="en"> + +<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/meta"), {title: "User list!" }) %> + + +<body> + <link href='https://unpkg.com/boxicons@2.1.2/css/boxicons.min.css' rel='stylesheet'> + + <%- include(dataset.getFile(dataset.theme.codename +"/views/extra/navbar")) %> + + <div class="users"> + <% users.filter(member=> !member.deleted || user.admin ).forEach(member => { %> + <div style="display:flex;justify-content:center;"> + <div class="user-box"> + <img src="<%= member.avatar %>" class="user-box-img"> + <div class="user-box-title"> <a href="<%= member.getLink() %>"> + <% if (member.deleted) { %> <span style="color: var(--important);">[DELETED]</span><% } %> + <%= member.name %></a></div> + </div> + </div> + <% }); %> + + </div> + <% if(typeof page === "number"){ %> + <div class="mb-3 clearfix"> + <nav aria-label="Navigate post pages" class="float-lg-right"> + <ul class="pagination pagination-sm mb-lg-0"> + <% if (page > 0){ %> + <li class="page-item"><a href="/categories?page=<%= page-1 %>" class="page-link">Back</a></li> + <% } %> + <% for(let i=0; i < pages; i++){ %> + <li class="page-item <%= i==page?'active':'' %>"><a href="/categories?page=<%= i %>" class="page-link"><%= i+1 %> + <% if (i==page){ %> + <span class="sr-only">(current)</span> + <% } %> + </a></li> + <% } %> + <% if (pages-1 > page) { %> + <li class="page-item"><a href="/categories?page=<%= page+1 %>" class="page-link">Next</a></li> + <% } %> + </ul> + </nav> + </div> + + <% } %> + <%- include(dataset.getFile(dataset.theme.codename +"/views/extra/footer")) %> + +</body> + +</html> \ No newline at end of file