full support for multiple themes, secure templates

This commit is contained in:
Akif9748 2023-05-25 17:23:31 +03:00
parent 080e1e6ced
commit edf9fc83dc
60 changed files with 403 additions and 257 deletions

View File

@ -13,7 +13,8 @@ Run `node util/reset` to **reset the database** for duplicate key errors, and ru
Edit `config.json` for default theme color (`black` or `white`) of users, and forum name, meta description, character limits, discord auth enabler, global ratelimit.
### How to install theme:
- Copy your theme to `public/themes` folder.
- Copy your theme to `themes` folder.
Additional note for themes: If a theme has not got any .ejs file, it will use default theme's .ejs files. default theme is in themes folder, named as `common`.
### DISCORD AUTH:
`"discord_auth": "your_app_id"` in config.json.
@ -61,6 +62,7 @@ Akf-forum has got an API for AJAX (fetch), other clients etc. And, you can learn
- add support for transition around gravatar
- BETTER SETUP PAGE
- add used open source libraries to README.md
- better folder system
### front-end
- text alling center body
- add a css file for CodeMirror in threads / send message ok

View File

@ -23,6 +23,12 @@ 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("./themes"))
app.use(`/themes/${theme}`, express.static(`./themes/${theme}/public/`));
app.use(express.static("public"), express.json(), express.urlencoded({ extended: true }), IP(),
SES({ secret: process.env.SECRET, store: MS.create({ clientPromise: DB, stringify: false }), resave: false, saveUninitialized: false }),
async (req, res, next) => {
@ -37,15 +43,19 @@ app.use(express.static("public"), express.json(), express.urlencoded({ extended:
if (!themes.some(t => t.codename === theme.codename))
theme = def_theme;
res.reply = (page, options = {}, status = 200) => res.status(status).render(page, {
dataset: {
themes, theme, forum_name, description,
getFile: file => join(__dirname, "public", "themes", file),
},
user: req.user,
...options
});
res.reply = (page, options = {}, status = 200) => {
const road = join(__dirname, "themes", theme.codename, "views", `${page}.ejs`);
const renderpage = fs.existsSync(road) ? road : join(__dirname, "themes", "common", "views", `${page}.ejs`);
return res.status(status).render(renderpage, {
dataset: {
themes, theme, forum_name, description,
getFile: file => join(__dirname, "themes", file),
},
user: req.user,
...options
});
}
res.error = (type, error) => res.reply("error", { type, error }, type);
@ -63,11 +73,9 @@ app.use(express.static("public"), express.json(), express.urlencoded({ extended:
if (discord_auth)
app.set("discord_auth", `https://discord.com/api/oauth2/authorize?client_id=${discord_auth}&redirect_uri=${host}%2Fauth%2Fdiscord&response_type=code&scope=identify`);
if (RLS.enabled) app.use(RL(RLS.windowMs, RLS.max));
for (const file of fs.readdirSync("./routes"))
app.use("/" + file.replace(".js", ""), require(`./routes/${file}`));
app.all("*", (req, res) => res.error(404, "This page does not exist on this forum."));
app.listen(port, () => console.log(`${forum_name}-forum on port:`, port));
app.listen(port, () => console.log(`${forum_name} on port:`, port));

2
lib.js
View File

@ -6,7 +6,7 @@ const { readdirSync } = require('fs');
require("dotenv").config();
module.exports = {
themes: readdirSync("./public/themes").filter(f => f !== "common").map(f => require(`./public/themes/${f}`)),
themes: readdirSync("./themes").filter(f => f !== "common").map(f => require(`./themes/${f}`)),
threadEnum: ["OPEN", "APPROVAL", "DELETED"],
userEnum: ["ACTIVE", "APPROVAL", "DELETED", "BANNED"],
RL(windowMs = 60_000, max = 1) {

View File

@ -1,44 +0,0 @@
<div class="footer">
<% if (user){ %>
<select id="theme_select">
<% for(const theme of dataset.themes){%>
<option value="<%= theme.codename %>"><%= theme.name %></option>
<% } %>
</select>
<script>
const theme_select = document.getElementById("theme_select");
theme_select.querySelector(`option[value=<%= user.theme.codename %>]`).selected = true;
theme_select.addEventListener("change", async e => {
const codename = e.target.value;
await fetch('/api/users/<%= user.id %>', {
method: 'PATCH',
body: JSON.stringify({
theme: {
codename
}
}),
headers: {
"Content-Type": "application/json"
}
});
const theme = await fetch("/api/themes/" + codename).then(res => res.json());
const txt = "Theme changed to:\n" +
"Name: " + theme.name + "\n" +
"Description: " + theme.description + "\n" +
"Author: " + theme.author + "\n";
alert(txt);
location.reload();
});
</script>
<% } %>
<a href="https://github.com/Akif9748/akf-forum" style="color: white;"> This website is powered by
<span style="color: #ffbf00;">akf-forum</span>
</a>
<div>
<span style="color:white">Coders</span> <br>
<div style="text-align:center;">
<a href="https://github.com/Akif9748/" style="color: #ffbf00;">Akif</a><br><a href="#" style="color:#ffbf00;">Tokmak</a>
</div>
</div>
</div>

View File

@ -1,45 +0,0 @@
<% if (user?.admin){ %>
<div class="admin-bar">
<a href="/admin" class="admin-bar">Click here to reach admin panel</a>
</div>
<% } %>
<div class="header">
<a class="logo" href="/"><%= dataset.forum_name.toUpperCase() %> <span>FORUM</span></a>
<div class="buttons">
<% if (user){ %>
<a href="<%=user.getLink() %>" class="btn-outline-primary">
<div class="box-username"><%= user.name %>
<div class="avatar"><img src="<%=user.avatar %>"></div>
</div>
</a>
<a id="logout" href="/login" class="btn-primary">Logout</a>
<% } else { %>
<a id="login" href="/login" class="btn-primary">Login</a>
<a href="/register" class="btn-outline-primary">Register</a>
<script>
document.getElementById("login").href += "?redirect=" + location.pathname;
</script>
<% } %>
</div>
</div>
<div class="menu">
<a href="/threads/create/" class="menu-item">Create Thread</a>
<a href="/categories" class="menu-item">Categories</a>
<a href="/threads" class="menu-item">Threads</a>
<a href="/users" class="menu-item">Users</a>
<a href="/search" class="menu-item">Search</a>
<script>
const menuItems = document.getElementsByClassName("menu-item");
for (let i = 0; i < menuItems.length; i++)
if (window.location.pathname.includes(menuItems[i].getAttribute("href"))) {
menuItems[i].classList.add("active-menu");
break;
}
</script>
</div>

View File

@ -1,72 +0,0 @@
<% if(user?.admin || user?.id === member.id){ %>
<details>
<summary class="btn-outline-primary">User Menu:</summary>
<% if (!member.discordID && discord && user?.id === member.id) { %>
<a href="<%=discord%>" class="btn-outline-primary">DC auth</a>
<% } else if(member.discordID && user?.id === member.id) { %>
<a class="btn-outline-primary" id="un_discord">Unauth DC!</a>
<% } %>
<a href="/users/<%=member.id%>/avatar" class="btn-outline-primary">Upload avatar</a>
<a class="btn-outline-primary" id="toogle">Edit user!</a>
<script type="module">
import request from "/js/request.js";
const form = document.getElementById("form");
document.addEventListener("click", async e => {
if (e.target.id == "delete") {
const response = await request("/api/users/<%= member.id %>", "DELETE");
if (response.state !== "DELETED") return
alert("User is deleted!");
location.reload()
} else if (e.target.id == "undelete") {
const response = await request("/api/users/<%= member.id %>/", "PATCH", {
deleted: false
});
if (response.state == "DELETED") return;
alert("User is undeleted successfully!");
location.reload()
} else if (e.target.id == "un_discord") {
const response = await fetch("/auth/discord/", {
method: "DELETE"
});
alert(await response.text());
location.reload()
} else if (e.target.id.startsWith("last_")) {
let hideLastSeen = e.target.id.replace("last_", "") == "hide" ? true : false;
const response = await request("/api/users/<%= member.id %>/", "PATCH", {
hideLastSeen
});
alert(`Last seen is ${!hideLastSeen?"un":""}hided!`);
location.reload()
} else if (e.target.id == "toogle")
document.getElementById('user-edit').classList.toggle('no-active')
});
</script>
<% if (member.hideLastSeen) {%>
<a id="last_unhide" class="btn-primary">Unhide last seen! </a>
<% } else { %>
<a id="last_hide" class="btn-outline-primary">Hide last seen! </a>
<% } %>
<% if (member.deleted) {%>
<h1>This user has been deleted!</h1>
<a id="undelete" class="btn-primary">Undelete user! </a>
<% } else if (user?.admin){ %>
<a id="delete" class="btn-outline-primary">Delete user! </a>
<% } %>
<% if (user?.admin) {%>
<h2>IP adresses of the user:</h2>
<select>
<% for(const ip of member.ips) { %>
<option><%= ip %></option>
<% } %>
</select>
<% } %>
</details>
<% } %>

View File

@ -1 +0,0 @@
<%- include(dataset.getFile("common/footer")) %>

View File

@ -1 +0,0 @@
<%- include(dataset.getFile("common/navbar")) %>

View File

@ -1 +0,0 @@
<%- include(dataset.getFile("common/usermenu")) %>

View File

@ -1 +0,0 @@
<%- include(dataset.getFile("common/footer")) %>

View File

@ -1 +0,0 @@
<%- include(dataset.getFile("common/navbar")) %>

View File

@ -1 +0,0 @@
<%- include(dataset.getFile("common/usermenu")) %>

View File

@ -1,16 +1,16 @@
const { UserModel, ThreadModel, MessageModel } = require("../models")
const { UserModel, ThreadModel, MessageModel, CategoryModel } = require("../models")
const { Router } = require("express");
const app = Router();
app.get("/", async (req, res) => {
const
mem = process.memoryUsage().heapUsed / Math.pow(2, 20),
const categories = await CategoryModel.count(),
users = await UserModel.count({ deleted: false }),
threads = await ThreadModel.count({ state: "OPEN" }),
messages = await MessageModel.count({ deleted: false });
messages = await MessageModel.count({ deleted: false }),
newestMember = await UserModel.findOne({ deleted: false }, "name").sort({ time: -1 });
res.reply("index", { mem, users, threads, messages });
res.reply("index", { categories, users, threads, messages, newestMember: newestMember.name });
});

View File

@ -0,0 +1,124 @@
<!DOCTYPE html>
<html lang="en">
<%- include("extra/meta", {title: "Welcome to the "+dataset.forum_name+"-forum!" }) %>
<body>
<%- include("extra/navbar") %>
<div class="container my-3">
<nav class="breadcrumb">
<span class="breadcrumb-item active">
<% if (user) { %>
Welcome, <%= user.name %>
<% } else { %>
Welcome, Guest! <a href="/register">You can press to register.</a>
<% } %>
</span>
</nav>
<div class="row">
<div class="col-12 col-xl-9">
<div class="category">
<h2 class="h4 text-white bg-danger mb-0 p-4 rounded-top">Forum category</h2>
<table class="table table-striped table-bordered table-responsive">
<thead class="thead-light">
<tr>
<th scope="col" class="forum-col">Forum</th>
<th scope="col">Topics</th>
<th scope="col">Posts</th>
<th scope="col" class="last-post-col">Last post</th>
</tr>
</thead>
<tbody>
<tr class="thread">
<td>
<h3 class="h5 mb-0"><a href="#0" class="text-uppercase">Forum name</a></h3>
<p class="mb-0">Lorem ipsum dolor sit amet, consectetur adipiscing elit. In laoreet pellentesque lorem sed elementum.</p>
</td>
<td>
<div>5</div>
</td>
<td>
<div>18</div>
</td>
<td>
<h4 class="h6 mb-0 font-weight-bold"><a href="#0">Post name</a></h4>
<div>by <a href="#0">Author name</a></div>
<div>05 Apr 2017, 20:07</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="col-12 col-xl-3">
<aside>
<div class="row">
<div class="col-12 col-sm-6 col-xl-12">
<div class="card mb-3 mb-sm-0 mb-xl-3">
<div class="card-body">
<h2 class="h4 card-title">Members online</h2>
<ul class="list-unstyled mb-0">
<li><a href="/users">You</a></li>
</ul>
</div>
<div class="card-footer">
<dl class="row mb-0">
<dt class="col-8">Total:</dt>
<dd class="col-4 mb-0">-</dd>
</dl>
<dl class="row mb-0">
<dt class="col-8">Members:</dt>
<dd class="col-4 mb-0">-</dd>
</dl>
<dl class="row mb-0">
<dt class="col-8">Guests:</dt>
<dd class="col-4 mb-0">-</dd>
</dl>
</div>
</div>
</div>
<div class="col-12 col-sm-6 col-xl-12">
<div class="card">
<div class="card-body">
<h2 class="h4 card-title">Forum statistics</h2>
<dl class="row mb-0">
<dt class="col-8">Total forums:</dt>
<dd class="col-4 mb-0"><%= categories %></dd>
</dl>
<dl class="row mb-0">
<dt class="col-8">Total threads:</dt>
<dd class="col-4 mb-0"><%= threads %></dd>
</dl>
<dl class="row mb-0">
<dt class="col-8">Total messages:</dt>
<dd class="col-4 mb-0"><%= messages %></dd>
</dl>
<dl class="row mb-0">
<dt class="col-8">Total members:</dt>
<dd class="col-4 mb-0"><%= users %></dd>
</dl>
</div>
<div class="card-footer">
<div>Newest member:</div>
<div><a href="#0"><%= newestMember %></a></div>
</div>
</div>
</div>
</div>
</aside>
</div>
</div>
</div>
<%- include("extra/footer") %>
</body>
</html>

View File

@ -1,11 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<%- include("extra/meta", {title: "Admin Panel!" }) %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/meta"), {title: "Admin Panel!" }) %>
<body style="text-align: center;">
<%- include("extra/navbar") %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/navbar")) %>
<style>
table {
font-family: arial, sans-serif;
@ -83,7 +84,7 @@
}
</script>
</div>
<%- include("extra/footer") %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/footer")) %>
</body>

View File

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html lang="en">
<%- include("extra/meta", {title: "Avatar Upload Panel!" }) %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/meta"), {title: "Avatar Upload Panel!" }) %>
<body style="text-align: center;">
<link rel="stylesheet" href="/libs/cropper/cropper.css">
@ -27,7 +27,8 @@
</style>
<script src="/libs/cropper/cropper.js"></script>
<%- include("extra/navbar") %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/navbar")) %>
<div class="container">
@ -107,7 +108,7 @@
});
</script>
<%- include("extra/footer") %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/footer")) %>
</body>

View File

@ -1,11 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<%- include("extra/meta", {title: "Thread list!" }) %>
<%- 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("extra/navbar") %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/navbar")) %>
<div class="threads">
<% categories.forEach(category=>{ %>
<a href="<%= category.getLink() %>">
@ -48,7 +49,7 @@
</div>
<% } %>
<%- include("extra/footer") %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/footer")) %>
</body>

View File

@ -1,10 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<%- include("extra/meta", {title: "Edit Forum Config!" }) %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/meta"), {title: "Edit Forum Config!" }) %>
<body style="text-align: center;">
<%- include("extra/navbar") %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/navbar")) %>
<h1>Edit forum config</h1>
<textarea rows="30" cols="75"><%= config %></textarea>
<a onclick="send();" class="btn-primary">Edit config</a>
@ -24,7 +25,7 @@
}
</script>
<%- include("extra/footer") %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/footer")) %>
</body>

View File

@ -1,9 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<%- include("extra/meta", {title: "Create Category!" }) %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/meta"), {title: "Create Category!" }) %>
<body>
<%- include("extra/navbar") %>
<%- 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>
@ -41,7 +42,7 @@
});
</script>
<%- include("extra/footer") %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/footer")) %>
</body>

View File

@ -1,11 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<%- include("extra/meta", {title: "Create thread!" }) %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/meta"), {title: "Create thread!" }) %>
<body >
<%- include("extra/navbar") %>
<%- 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>
@ -53,7 +54,7 @@
});
</script>
<%- include("extra/footer") %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/footer")) %>
</body>

View File

@ -1,11 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<%- include("extra/meta", {title: member.name }) %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/meta"), {title: member.name }) %>
<body>
<%- include("extra/navbar") %>
<%- 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>
@ -47,7 +48,7 @@
location.reload();
});
</script>
<%- include("extra/footer") %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/footer")) %>
</body>

View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/meta"), {title: type+" error!" }) %>
<body style="text-align: center;">
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/navbar")) %>
<h1 style="color: var(--main);"><%= type %></h1>
<h2 style="color: var(--second);"><%= error %></h2>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/footer")) %>
</body>
</html>

View File

@ -0,0 +1,44 @@
<div class="footer">
<% if (user){ %>
<select id="theme_select">
<% for(const theme of dataset.themes){%>
<option value="<%= theme.codename %>"><%= theme.name %></option>
<% } %>
</select>
<script>
const theme_select = document.getElementById("theme_select");
theme_select.querySelector(`option[value=<%= user.theme.codename %>]`).selected = true;
theme_select.addEventListener("change", async e => {
const codename = e.target.value;
await fetch('/api/users/<%= user.id %>', {
method: 'PATCH',
body: JSON.stringify({
theme: {
codename
}
}),
headers: {
"Content-Type": "application/json"
}
});
const theme = await fetch("/api/themes/" + codename).then(res => res.json());
const txt = "Theme changed to:\n" +
"Name: " + theme.name + "\n" +
"Description: " + theme.description + "\n" +
"Author: " + theme.author + "\n";
alert(txt);
location.reload();
});
</script>
<% } %>
<a href="https://github.com/Akif9748/akf-forum" style="color: white;"> This website is powered by
<span style="color: #ffbf00;">akf-forum</span>
</a>
<div>
<span style="color:white">Coders</span> <br>
<div style="text-align:center;">
<a href="https://github.com/Akif9748/" style="color: #ffbf00;">Akif</a><br><a href="#" style="color:#ffbf00;">Tokmak</a>
</div>
</div>
</div>

View File

@ -0,0 +1,44 @@
<% if (user?.admin){ %>
<div class="admin-bar">
<a href="/admin" class="admin-bar">Click here to reach admin panel</a>
</div>
<% } %>
<div class="header">
<a class="logo" href="/"><%= dataset.forum_name.toUpperCase() %> <span>FORUM</span></a>
<div class="buttons">
<% if (user){ %>
<a href="<%=user.getLink() %>" class="btn-outline-primary">
<div class="box-username"><%= user.name %>
<div class="avatar"><img src="<%=user.avatar %>"></div>
</div>
</a>
<a id="logout" href="/login" class="btn-primary">Logout</a>
<% } else { %>
<a id="login" href="/login" class="btn-primary">Login</a>
<a href="/register" class="btn-outline-primary">Register</a>
<script>
document.getElementById("login").href += "?redirect=" + location.pathname;
</script>
<% } %>
</div>
</div>
<div class="menu">
<a href="/threads/create/" class="menu-item">Create Thread</a>
<a href="/categories" class="menu-item">Categories</a>
<a href="/threads" class="menu-item">Threads</a>
<a href="/users" class="menu-item">Users</a>
<a href="/search" class="menu-item">Search</a>
<script>
const menuItems = document.getElementsByClassName("menu-item");
for (let i = 0; i < menuItems.length; i++)
if (window.location.pathname.includes(menuItems[i].getAttribute("href"))) {
menuItems[i].classList.add("active-menu");
break;
}
</script>
</div>

View File

@ -0,0 +1,72 @@
<% if(user?.admin || user?.id === member.id){ %>
<details>
<summary class="btn-outline-primary">User Menu:</summary>
<% if (!member.discordID && discord && user?.id === member.id) { %>
<a href="<%=discord%>" class="btn-outline-primary">DC auth</a>
<% } else if(member.discordID && user?.id === member.id) { %>
<a class="btn-outline-primary" id="un_discord">Unauth DC!</a>
<% } %>
<a href="/users/<%=member.id%>/avatar" class="btn-outline-primary">Upload avatar</a>
<a class="btn-outline-primary" id="toogle">Edit user!</a>
<script type="module">
import request from "/js/request.js";
const form = document.getElementById("form");
document.addEventListener("click", async e => {
if (e.target.id == "delete") {
const response = await request("/api/users/<%= member.id %>", "DELETE");
if (response.state !== "DELETED") return
alert("User is deleted!");
location.reload()
} else if (e.target.id == "undelete") {
const response = await request("/api/users/<%= member.id %>/", "PATCH", {
deleted: false
});
if (response.state == "DELETED") return;
alert("User is undeleted successfully!");
location.reload()
} else if (e.target.id == "un_discord") {
const response = await fetch("/auth/discord/", {
method: "DELETE"
});
alert(await response.text());
location.reload()
} else if (e.target.id.startsWith("last_")) {
let hideLastSeen = e.target.id.replace("last_", "") == "hide" ? true : false;
const response = await request("/api/users/<%= member.id %>/", "PATCH", {
hideLastSeen
});
alert(`Last seen is ${!hideLastSeen?"un":""}hided!`);
location.reload()
} else if (e.target.id == "toogle")
document.getElementById('user-edit').classList.toggle('no-active')
});
</script>
<% if (member.hideLastSeen) {%>
<a id="last_unhide" class="btn-primary">Unhide last seen! </a>
<% } else { %>
<a id="last_hide" class="btn-outline-primary">Hide last seen! </a>
<% } %>
<% if (member.deleted) {%>
<h1>This user has been deleted!</h1>
<a id="undelete" class="btn-primary">Undelete user! </a>
<% } else if (user?.admin){ %>
<a id="delete" class="btn-outline-primary">Delete user! </a>
<% } %>
<% if (user?.admin) {%>
<h2>IP adresses of the user:</h2>
<select>
<% for(const ip of member.ips) { %>
<option><%= ip %></option>
<% } %>
</select>
<% } %>
</details>
<% } %>

View File

@ -1,13 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<%- include("extra/meta", {title: "Welcome to the "+dataset.forum_name+"-forum!" }) %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/meta"), {title: "Welcome to the "+dataset.forum_name+"-forum!" }) %>
<body>
<link rel="stylesheet" href="/css/user.css" />
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/navbar")) %>
<%- include("extra/navbar") %>
<div class="usercontent">
<% if (user) { %>
@ -44,12 +43,12 @@
</div>
<div class="userbox">
<h2 class="userbox-title">Memory usage:</h2>
<h2 class="userbox-value"><%= mem.toFixed(2); %> MB</h2>
<h2 class="userbox-title">Category count:</h2>
<h2 class="userbox-value"><%= categories %></h2>
</div>
</div>
<%- include("extra/footer") %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/footer")) %>
</body>

View File

@ -1,11 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<%- include("extra/meta", {title: "Log in!" }) %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/meta"), {title: "Log in!" }) %>
<body style="text-align: center;">
<%- include("extra/navbar") %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/navbar")) %>
<h1 class="title">Login</h1>
@ -20,7 +21,7 @@
<% } %>
<a href="/register" class="btn-outline-primary">Register</a>
<%- include("extra/footer") %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/footer")) %>
</body>

View File

@ -1,11 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<%- include("extra/meta", {title:"Message search!" }) %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/meta"), {title:"Message search!" }) %>
<body style="text-align: center;">
<%- include("extra/navbar") %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/navbar")) %>
<div id="messages">
@ -27,7 +28,7 @@
<% }); %>
</div>
<%- include("extra/footer") %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/footer")) %>
</body>

View File

@ -1,12 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<%- include("extra/meta", {title: "Register!" }) %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/meta"), {title: "Register!" }) %>
<body style="text-align: center;">
<%- include("extra/navbar") %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/navbar")) %>
<h1 class="title">Register</h1>
@ -24,7 +25,7 @@
<a href="/login" class="btn-outline-primary">Login</a>
<%- include("extra/footer") %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/footer")) %>
</body>

View File

@ -1,11 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<%- include("extra/meta", {title: "Search page!" }) %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/meta"), {title: "Search page!" }) %>
<body style="text-align: center;">
<%- include("extra/navbar") %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/navbar")) %>
<h1 class="title">Search</h1>
@ -27,7 +28,7 @@
<input type="submit" value="Search" class="btn-primary" />
</form>
</div>
<%- include("extra/footer") %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/footer")) %>
</body>

View File

@ -1,11 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<%- include("extra/meta", {title: "Setup the Akf-forum!" }) %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/meta"), {title: "Setup the Akf-forum!" }) %>
<body style="text-align: center;">
<%- include("extra/navbar") %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/navbar")) %>
<h1 style="color: var(--main);">Setup</h1>
<h2 style="color: var(--second);">There is default settings for akf-forum, you not need to edit them, but you can! And, the first registered user will be admin.</h2>

View File

@ -1,11 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<%- include("extra/meta", {title: thread.title }) %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/meta"), {title: thread.title }) %>
<body>
<%- include("extra/navbar") %>
<%- 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">
@ -159,7 +160,7 @@
</div>
</div>
<%- include("extra/footer") %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/footer")) %>
</body>

View File

@ -1,11 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<%- include("extra/meta", {title: "Thread list!" }) %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/meta"), {title: "Thread list!" }) %>
<body style="color: var(--anti); text-align: center;">
<link href='https://unpkg.com/boxicons@2.1.2/css/boxicons.min.css' rel='stylesheet'>
<%- include("extra/navbar") %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/navbar")) %>
<h1><%= title || "Threads" %></h1>
<h2><%= desp %></h2>
<div class="threads">
@ -54,7 +55,7 @@
</div>
<% } %>
<%- include("extra/footer") %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/footer")) %>
</body>

View File

@ -1,11 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<%- include("extra/meta", {title: member.name }) %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/meta"), {title: member.name }) %>
<body>
<%- include("extra/navbar") %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/navbar")) %>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css" integrity="sha512-xh6O/CkQoPOWDdYTDqeRdPCVd1SpvCA9XXcUnZS2FmJNp1coAFzvtCN9BmamE+4aHK8yyUHUSCcJHgXloTyT2A==" crossorigin="anonymous" referrerpolicy="no-referrer" />
@ -72,7 +73,7 @@ color: var(--anti);
</a>
</div>
</div>
<%- include("extra/footer") %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/footer")) %>
</body>

View File

@ -1,13 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<%- include("extra/meta", {title: "User list!" }) %>
<%- 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("extra/navbar") %>
<%- 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;">
@ -43,7 +44,7 @@
</div>
<% } %>
<%- include("extra/footer") %>
<%- include(dataset.getFile(dataset.theme.codename +"/views/extra/footer")) %>
</body>

View File

@ -0,0 +1 @@
<%- include(dataset.getFile("common/views/extra/footer")) %>

View File

@ -0,0 +1 @@
<%- include(dataset.getFile("common/views/extra/navbar")) %>

View File

@ -0,0 +1 @@
<%- include(dataset.getFile("common/views/extra/usermenu")) %>

View File

@ -0,0 +1 @@
<%- include(dataset.getFile("common/views/extra/footer")) %>

View File

@ -0,0 +1 @@
<%- include(dataset.getFile("common/views/extra/navbar")) %>

View File

@ -0,0 +1 @@
<%- include(dataset.getFile("common/views/extra/usermenu")) %>

View File

@ -1,16 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<%- include("extra/meta", {title: type+" error!" }) %>
<body style="text-align: center;">
<%- include("extra/navbar") %>
<h1 style="color: var(--main);"><%= type %></h1>
<h2 style="color: var(--second);"><%= error %></h2>
<%- include("extra/footer") %>
</body>
</html>

View File

@ -1 +0,0 @@
<%- include(dataset.getFile(dataset.theme.codename +"/extra/footer")) %>

View File

@ -1 +0,0 @@
<%- include(dataset.getFile(dataset.theme.codename +"/extra/meta")) %>

View File

@ -1 +0,0 @@
<%- include(dataset.getFile(dataset.theme.codename +"/extra/navbar")) %>