mirror of
https://github.com/Akif9748/akf-forum.git
synced 2024-12-22 07:39:08 +03:00
Added themes api way, and bootstrap theme,useredit
This commit is contained in:
parent
bd722f1651
commit
93bb17981f
42 changed files with 846 additions and 251 deletions
|
@ -1,3 +1,6 @@
|
|||
MONGO_DB_URL = mongodb://localhost:27017/akf-forum
|
||||
SECRET = secret
|
||||
DISCORD_SECRET = yourDiscordSecret
|
||||
DISCORD_SECRET = yourDiscordSecret
|
||||
EMAIL_USER =
|
||||
EMAIL_PASS =
|
||||
EMAIL_SERVICE =
|
20
README.md
20
README.md
|
@ -12,6 +12,9 @@ A Node.js based forum software.
|
|||
Run `node util/reset` to **reset the database** for duplicate key errors, and run `node util/admin` for give admin perms to first member.
|
||||
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.
|
||||
|
||||
### DISCORD AUTH:
|
||||
`"discord_auth": "your_app_id"` in config.json.
|
||||
Add your app secret to `.env` as `DISCORD_SECRET`.
|
||||
|
@ -56,22 +59,17 @@ Akf-forum has got an API for AJAX (fetch), other clients etc. And, you can learn
|
|||
- change category name
|
||||
- _id
|
||||
- add support for transition around gravatar
|
||||
- forum setup page++
|
||||
|
||||
### theme to do:
|
||||
- add bootstrap for navbar
|
||||
- routes/api/routes/users.js check,
|
||||
themes/default/extra/footer.ejs check,
|
||||
themes/default/extra/meta.ejs check
|
||||
- add theme support again, but only works with css folder. Put every css file into one file. (themes/default/css/main.css)
|
||||
- forum setup page rewrite and directly open a router
|
||||
|
||||
### front-end
|
||||
- better usermenu for user profile
|
||||
- add a css file for CodeMirror in threads / send message ok
|
||||
- old contents / titles add to forum interface
|
||||
- categories page is need a update, thread count in category (?)
|
||||
- add ban button to user profile.?
|
||||
- who liked a message for web.
|
||||
- add ban button to user profile
|
||||
- who liked a message
|
||||
- give admin button, not is admin
|
||||
- edit user ++
|
||||
- rewrite main page, list new messages
|
||||
|
||||
## Major Version History
|
||||
- V4: Caching
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"def_theme": {
|
||||
"name": "white",
|
||||
"codename": "bootstrap_black",
|
||||
"language": "en"
|
||||
},
|
||||
"forum_name": "akf",
|
||||
|
@ -18,7 +18,7 @@
|
|||
},
|
||||
"discord_auth": "",
|
||||
"default_thread_state": "OPEN",
|
||||
"email_auth": false,
|
||||
"default_user_state": "ACTIVE",
|
||||
"email_auth": false,
|
||||
"host": "https://akf-forum.glitch.me"
|
||||
}
|
14
index.js
14
index.js
|
@ -6,10 +6,10 @@ const
|
|||
mongoose = require("mongoose"),
|
||||
express = require('express'),
|
||||
fs = require("fs"),
|
||||
{ join } = require("path"),
|
||||
app = express(),
|
||||
{ mw: IP } = require('request-ip'),
|
||||
{ RL } = require('./lib'),
|
||||
{ themes } = require("./config.json"),
|
||||
{ RL, themes } = require('./lib'),
|
||||
SES = require('express-session'),
|
||||
MS = require("connect-mongo"),
|
||||
DB = mongoose.connect(process.env.MONGO_DB_URL)
|
||||
|
@ -32,13 +32,15 @@ app.use(express.static("public"), express.json(), express.urlencoded({ extended:
|
|||
lastSeen: Date.now(), $addToSet: { ips: req.clientIp }
|
||||
}) : null;
|
||||
|
||||
let theme = req.user?.theme || def_theme;
|
||||
|
||||
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: req.user?.theme || def_theme,
|
||||
forum_name,
|
||||
description
|
||||
themes, theme, forum_name, description,
|
||||
getFile: file => join(__dirname, "public", "themes", file),
|
||||
},
|
||||
user: req.user,
|
||||
...options
|
||||
|
|
2
lib.js
2
lib.js
|
@ -2,9 +2,11 @@ const RL = require('express-rate-limit');
|
|||
const nodemailer = require("nodemailer");
|
||||
const config = require("./config.json");
|
||||
const crypto = require("crypto");
|
||||
const { readdirSync } = require('fs');
|
||||
|
||||
require("dotenv").config();
|
||||
module.exports = {
|
||||
themes: readdirSync("./public/themes").filter(f => f !== "common").map(f => require(`./public/themes/${f}`)),
|
||||
threadEnum: ["OPEN", "APPROVAL", "DELETED"],
|
||||
userEnum: ["ACTIVE", "APPROVAL", "DELETED", "BANNED"],
|
||||
RL(windowMs = 60_000, max = 1) {
|
||||
|
|
|
@ -12,7 +12,7 @@ const schema = new mongoose.Schema({
|
|||
about: { type: String, default: "", maxlength: limits.desp },
|
||||
admin: { type: Boolean, default: false },
|
||||
theme: {
|
||||
name: { type: String, default: def_theme.name },
|
||||
codename: { type: String, default: def_theme.codename },
|
||||
language: { type: String, default: def_theme.language }
|
||||
},
|
||||
lastSeen: { type: Date, default: Date.now, select: false },
|
||||
|
|
|
@ -22,7 +22,7 @@ form {
|
|||
}
|
||||
|
||||
/* MODAL */
|
||||
.modal {
|
||||
.forum-modal {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
|
@ -32,7 +32,7 @@ form {
|
|||
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
.forum-modal-content {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
|
@ -44,12 +44,12 @@ form {
|
|||
animation: fadeIn 1s forwards;
|
||||
}
|
||||
|
||||
.modal.no-active {
|
||||
.forum-modal.no-active {
|
||||
display: none;
|
||||
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
.forum-modal-close {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 10px;
|
||||
|
@ -69,7 +69,7 @@ form {
|
|||
}
|
||||
|
||||
@media(max-width:760px) {
|
||||
.modal-content {
|
||||
.forum-modal-content {
|
||||
width: 95%;
|
||||
}
|
||||
}
|
|
@ -80,7 +80,7 @@
|
|||
position: absolute;
|
||||
top: 50px;
|
||||
right: 0;
|
||||
background-color: var(--btn-clr-1);
|
||||
background-color: var(--second);
|
||||
width: 100px;
|
||||
text-align: center;
|
||||
display: none;
|
||||
|
|
10
public/themes/bootstrap_black/bootstrap-night.min.css
vendored
Normal file
10
public/themes/bootstrap_black/bootstrap-night.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
7
public/themes/bootstrap_black/bootstrap.min.js
vendored
Normal file
7
public/themes/bootstrap_black/bootstrap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
41
public/themes/bootstrap_black/extra/footer.ejs
Normal file
41
public/themes/bootstrap_black/extra/footer.ejs
Normal file
|
@ -0,0 +1,41 @@
|
|||
<footer class="text-center text-white fixed-bottom">
|
||||
<% 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>
|
||||
<% } %>
|
||||
|
||||
<div class="text-center p-3">
|
||||
akf-forum bootstrap theme created by <a class="text-white" href="https://akif9748.github.io/">Akif9748</a>,<br>
|
||||
This website is powered by <a class="text-white" href="https://github.com/Akif9748/akf-forum">akf-forum</a>
|
||||
</div>
|
||||
</footer>
|
10
public/themes/bootstrap_black/extra/meta.ejs
Normal file
10
public/themes/bootstrap_black/extra/meta.ejs
Normal file
|
@ -0,0 +1,10 @@
|
|||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title><%= title || dataset.forum_name +"-forum" %></title>
|
||||
<meta name="description" content="<%= dataset.description %>">
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||
<link href="/themes/bootstrap_black/bootstrap-night.min.css" rel="stylesheet">
|
||||
<script src="/themes/bootstrap_black/bootstrap.min.js"></script>
|
||||
<link rel="stylesheet" href="/themes/bootstrap_black/main.css" />
|
||||
</head>
|
72
public/themes/bootstrap_black/extra/navbar.ejs
Normal file
72
public/themes/bootstrap_black/extra/navbar.ejs
Normal file
|
@ -0,0 +1,72 @@
|
|||
<% if (user?.admin){ %>
|
||||
<div class="admin-bar">
|
||||
<a href="/admin" class="admin-bar">Click here to reach admin panel</a>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||
<div class="container-fluid">
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarText" aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<a class="navbar-brand" href="/"><%= dataset.forum_name.toUpperCase() %><span>-FORUM</span></a>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarText">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/threads/create/">Create Thread</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/categories">Categories</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/threads">Threads</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/users">Users</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/search">Search</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/login">Logout</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<% if (user) { %>
|
||||
<a class="navbar-brand" href="<%=user.getLink()%>"> <%= user.name %>
|
||||
<img style="border-radius: 50%;" width="32" src="<%=user.avatar %>">
|
||||
</a>
|
||||
|
||||
<% } else{ %>
|
||||
<a class="navbar-brand" id="login" href="/login">Login</a>
|
||||
<script>
|
||||
document.getElementById("login").href += "?redirect=" + location.pathname;
|
||||
</script>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
const menuItems = document.getElementsByClassName("nav-link");
|
||||
|
||||
for (let i = 0; i < menuItems.length; i++){console.log(menuItems[i].getAttribute("href"))
|
||||
if (window.location.pathname.includes(menuItems[i].getAttribute("href"))) {
|
||||
menuItems[i].classList.add("active");
|
||||
//break;
|
||||
}}
|
||||
</script>
|
||||
|
||||
</nav>
|
82
public/themes/bootstrap_black/extra/usermenu.ejs
Normal file
82
public/themes/bootstrap_black/extra/usermenu.ejs
Normal file
|
@ -0,0 +1,82 @@
|
|||
<% if(user?.admin || user?.id === member.id){ %>
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||
<div class="container-fluid">
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#userMenu" aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="userMenu">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<% if (!member.discordID && discord && user?.id === member.id) { %>
|
||||
<a href="<%=discord%>" class="btn-outline-primary">Discord auth</a>
|
||||
<% } else if(member.discordID && user?.id === member.id) { %>
|
||||
<a class="btn-primary" id="un_discord">Unauth Discord</a>
|
||||
<% } %>
|
||||
<% 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>
|
||||
<% } %>
|
||||
<a class="btn-outline-primary" href="/users/<%=member.id%>/edit">Edit user</a>
|
||||
<a href="/users/<%=member.id%>/avatar" class="btn-outline-primary">Upload avatar</a>
|
||||
</ul>
|
||||
|
||||
<script type="module">
|
||||
import request from "../../js/request.js";
|
||||
|
||||
|
||||
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>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</nav>
|
||||
<% if (user.admin) { %>
|
||||
<select>
|
||||
<option selected>IP LIST</option>
|
||||
<% for(const ip of member.ips) { %>
|
||||
<option><%= ip %></option>
|
||||
<% } %>
|
||||
</select>
|
||||
<% } %>
|
||||
|
||||
<% } %>
|
6
public/themes/bootstrap_black/index.js
Normal file
6
public/themes/bootstrap_black/index.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
module.exports = {
|
||||
name: "Bootstrap black theme",
|
||||
codename: "bootstrap_black",
|
||||
description: "A black theme fueled by bootstrap, created by Akif9748 and overwrited Alair's website's theme.",
|
||||
author: "Akif9748"
|
||||
}
|
273
public/themes/bootstrap_black/main.css
Normal file
273
public/themes/bootstrap_black/main.css
Normal file
|
@ -0,0 +1,273 @@
|
|||
@import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
|
||||
|
||||
|
||||
|
||||
:root {
|
||||
--alair: #736FE9;
|
||||
--main: #736FE9;
|
||||
--btn-clr-1: #e8e8e8;
|
||||
--menu-item: #ffffff;
|
||||
--borders: #d9d9d9;
|
||||
--input-clr: #dcdcdc;
|
||||
--box-shadow: #c3c3c3;
|
||||
--second: #9f9f9f;
|
||||
--anti: #ebebeb;
|
||||
--t-username: rgb(236 236 236);
|
||||
--background-color: #000000;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***********************************
|
||||
|
||||
START OF OLD THEME
|
||||
|
||||
***********************************/
|
||||
|
||||
|
||||
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Poppins;
|
||||
min-height: 400px;
|
||||
margin-bottom: 100px;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: initial;
|
||||
}
|
||||
|
||||
.header {
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
padding: 10px;
|
||||
font-size: 28px;
|
||||
color: var(--main);
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.logo>span {
|
||||
color: var(--second);
|
||||
}
|
||||
|
||||
.buttons {
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
color: #fafafa;
|
||||
background-color: var(--alair);
|
||||
border-color: var(--alair);
|
||||
padding: 10px 20px 10px 20px;
|
||||
border-radius: 4px;
|
||||
font-weight: 700;
|
||||
margin: 10px;
|
||||
cursor: pointer;
|
||||
border: 2px solid var(--main);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
color: var(--alair);
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
border: 2px solid var(--alair);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.btn-danger {
|
||||
color: var(--btn-clr-1);
|
||||
background-color: var(--important);
|
||||
padding: 0px 10px 0px 10px;
|
||||
|
||||
border-radius: 4px;
|
||||
font-weight: 700;
|
||||
margin: 10px;
|
||||
cursor: pointer;
|
||||
border: 2px solid var(--important);
|
||||
}
|
||||
|
||||
|
||||
.btn-outline-primary {
|
||||
|
||||
|
||||
color: var(--alair);
|
||||
padding: 10px 20px 10px 20px;
|
||||
|
||||
border-radius: 4px;
|
||||
font-weight: 700;
|
||||
margin: 10px;
|
||||
cursor: pointer;
|
||||
border: 2px solid var(--borders);
|
||||
}
|
||||
|
||||
.btn-outline-primary:hover {
|
||||
color: var(--btn-clr-1);
|
||||
background-color: var(--alair);
|
||||
border: 2px solid var(--alair);
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background-color: var(--important);
|
||||
border: 2px solid var(--important);
|
||||
}
|
||||
|
||||
|
||||
.menu {
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
padding: 10px;
|
||||
font-weight: 700;
|
||||
background-color: var(--second);
|
||||
color: var(--menu-item);
|
||||
|
||||
border-radius: 5px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin: 0px 10px 0px 0px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.active-menu {
|
||||
background-color: var(--main);
|
||||
}
|
||||
|
||||
.menu-item:hover {
|
||||
background-color: var(--main);
|
||||
}
|
||||
|
||||
.admin-bar {
|
||||
font-size: 15px;
|
||||
color: var(--menu-item);
|
||||
background: var(--main);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div.avatar {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.avatar>img {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.box-username {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: var(--main);
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.header {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.menu {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
margin: 0px 10px 10px 0px;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.btn-outline-primary {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***********************************
|
||||
|
||||
START OF BOOTSTRAP THEME
|
||||
|
||||
***********************************/
|
||||
|
||||
.a:hover {
|
||||
color: var(--alair);
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
color: var(--input-clr);
|
||||
background-color: var(--bs-body-bg);
|
||||
}
|
||||
|
||||
|
||||
footer {
|
||||
background-color: #000000;
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 400px;
|
||||
background-size: cover;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--alair);
|
||||
}
|
||||
|
||||
.alair-trans {
|
||||
padding: 20px;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
.alair-cl {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.alair-cl {
|
||||
width: 90%;
|
||||
}
|
||||
}
|
||||
|
||||
.container-sm {
|
||||
margin-top: 25px;
|
||||
margin-bottom: 125px;
|
||||
}
|
44
public/themes/common/footer.ejs
Normal file
44
public/themes/common/footer.ejs
Normal 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>
|
45
public/themes/common/navbar.ejs
Normal file
45
public/themes/common/navbar.ejs
Normal file
|
@ -0,0 +1,45 @@
|
|||
|
||||
<% 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>
|
72
public/themes/common/usermenu.ejs
Normal file
72
public/themes/common/usermenu.ejs
Normal 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>
|
||||
<% } %>
|
1
public/themes/default_black/extra/footer.ejs
Normal file
1
public/themes/default_black/extra/footer.ejs
Normal file
|
@ -0,0 +1 @@
|
|||
<%- include(dataset.getFile("common/footer")) %>
|
9
public/themes/default_black/extra/meta.ejs
Normal file
9
public/themes/default_black/extra/meta.ejs
Normal file
|
@ -0,0 +1,9 @@
|
|||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title><%= title || dataset.forum_name +"-forum" %></title>
|
||||
<meta name="description" content="<%= dataset.description %>">
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||
<link rel="stylesheet" href="/themes/default_black/main.css" />
|
||||
<link rel="stylesheet" href="/css/common.css" />
|
||||
</head>
|
1
public/themes/default_black/extra/navbar.ejs
Normal file
1
public/themes/default_black/extra/navbar.ejs
Normal file
|
@ -0,0 +1 @@
|
|||
<%- include(dataset.getFile("common/navbar")) %>
|
1
public/themes/default_black/extra/usermenu.ejs
Normal file
1
public/themes/default_black/extra/usermenu.ejs
Normal file
|
@ -0,0 +1 @@
|
|||
<%- include(dataset.getFile("common/usermenu")) %>
|
6
public/themes/default_black/index.js
Normal file
6
public/themes/default_black/index.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
module.exports = {
|
||||
name: "Default Black",
|
||||
codename: "default_black",
|
||||
description: "A black theme over the default white theme. Edited by Akif9748, Created by Tokmak.",
|
||||
author: "Akif9748 & Tokmak"
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
|
||||
/*
|
||||
akf-forum black theme config file
|
||||
*/
|
||||
:root {
|
||||
--main: #ac8fff;
|
||||
--btn-clr-1: #e8e8e8;
|
1
public/themes/default_white/extra/footer.ejs
Normal file
1
public/themes/default_white/extra/footer.ejs
Normal file
|
@ -0,0 +1 @@
|
|||
<%- include(dataset.getFile("common/footer")) %>
|
9
public/themes/default_white/extra/meta.ejs
Normal file
9
public/themes/default_white/extra/meta.ejs
Normal file
|
@ -0,0 +1,9 @@
|
|||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title><%= title || dataset.forum_name +"-forum" %></title>
|
||||
<meta name="description" content="<%= dataset.description %>">
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||
<link rel="stylesheet" href="/themes/default_white/main.css" />
|
||||
<link rel="stylesheet" href="/css/common.css" />
|
||||
</head>
|
1
public/themes/default_white/extra/navbar.ejs
Normal file
1
public/themes/default_white/extra/navbar.ejs
Normal file
|
@ -0,0 +1 @@
|
|||
<%- include(dataset.getFile("common/navbar")) %>
|
1
public/themes/default_white/extra/usermenu.ejs
Normal file
1
public/themes/default_white/extra/usermenu.ejs
Normal file
|
@ -0,0 +1 @@
|
|||
<%- include(dataset.getFile("common/usermenu")) %>
|
6
public/themes/default_white/index.js
Normal file
6
public/themes/default_white/index.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
module.exports = {
|
||||
name: "Default White",
|
||||
codename: "default_white",
|
||||
description: "Default white theme created by Tokmak.",
|
||||
author: "Tokmak"
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
/* akf-forum default theme config file */
|
||||
|
||||
:root {
|
||||
--main: #4d18e6;
|
||||
--btn-clr-1: #e8e8e8;
|
15
routes/api/routes/themes.js
Normal file
15
routes/api/routes/themes.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
const {themes}= require("../../../lib");
|
||||
const { Router } = require("express")
|
||||
|
||||
const app = Router();
|
||||
|
||||
app.get("/", async (req, res) => res.complate(themes));
|
||||
|
||||
app.get("/:codename", async (req, res) => {
|
||||
const theme = themes.find(t => t.codename === req.params.codename);
|
||||
if (!theme) return res.error(404, "Theme not found.");
|
||||
res.complate(theme);
|
||||
});
|
||||
|
||||
|
||||
module.exports = app;
|
|
@ -1,7 +1,7 @@
|
|||
const { UserModel, BanModel } = require("../../../models");
|
||||
const { Router } = require("express");
|
||||
const multer = require("multer");
|
||||
const { themes } = require("../../../config.json")
|
||||
const { themes } = require("../../../lib")
|
||||
const app = Router();
|
||||
|
||||
app.param("id", async (req, res, next, id) => {
|
||||
|
@ -51,7 +51,7 @@ app.patch("/:id", async (req, res) => {
|
|||
if (about.length > desp) return res.error(400, `About must be under ${desp} characters`);
|
||||
member.about = about;
|
||||
}
|
||||
if (theme || themes.some(t => t.name === theme.name).includes(theme))
|
||||
if (theme && themes.some(t => t.codename === theme.codename))
|
||||
member.theme = theme;
|
||||
|
||||
if (typeof admin === "boolean" || ["false", "true"].includes(admin)) member.admin = admin;
|
||||
|
|
|
@ -18,6 +18,7 @@ app.get("/:id/avatar", async (req, res) => {
|
|||
else
|
||||
res.error(404, `We don't have any user with id ${req.params.id}.`);
|
||||
})
|
||||
|
||||
app.get("/:id", async (req, res) => {
|
||||
const user = req.user
|
||||
const { id } = req.params;
|
||||
|
@ -27,10 +28,23 @@ app.get("/:id", async (req, res) => {
|
|||
|
||||
const message = await MessageModel.count({ authorID: id });
|
||||
const thread = await ThreadModel.count({ authorID: id });
|
||||
res.reply("user", { member, counts: { message, thread}, discord: req.app.get("discord_auth") })
|
||||
res.reply("user", { member, counts: { message, thread }, discord: req.app.get("discord_auth") })
|
||||
}
|
||||
else res.error(404, `We don't have any user with id ${id}.`);
|
||||
|
||||
});
|
||||
|
||||
app.get("/:id/edit", async (req, res) => {
|
||||
const user = req.user
|
||||
const { id } = req.params;
|
||||
const member = await UserModel.get(id);
|
||||
if (!member) return res.error(404, `We don't have any user with id ${id}.`);
|
||||
if (user?.admin || user.id === member.id)
|
||||
return res.reply("edit_user", { member });
|
||||
|
||||
res.error(403, "You have not got permission for this.");
|
||||
|
||||
});
|
||||
|
||||
|
||||
module.exports = app;
|
44
views/edit_user.ejs
Normal file
44
views/edit_user.ejs
Normal file
|
@ -0,0 +1,44 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<%- include("extra/meta", {title: member.name }) %>
|
||||
|
||||
|
||||
<body>
|
||||
<%- include("extra/navbar") %>
|
||||
<h1 class="title" style="text-align:center;">
|
||||
Edit <a href="/users/<%= member.id %>"><%= member.name %></a>
|
||||
</h1>
|
||||
|
||||
<form id="form" style="box-shadow:none">
|
||||
<input type="text" name="name" placeholder="<%=member.name%>" class="input">
|
||||
|
||||
<textarea class="input" name="about" rows="4" cols="60" name="content" placeholder="<%=member.about%>"></textarea>
|
||||
<% if (user?.admin){ %>
|
||||
Is Admin? <input id='admin' type='checkbox' value='true' name='admin' <%=member.admin ? "checked": ""%>>
|
||||
<input id='adminHidden' type='hidden' value='false' name='admin'>
|
||||
<% } %>
|
||||
|
||||
<button class="btn-primary" style="width:100%;">Update User!</button>
|
||||
</form>
|
||||
|
||||
<script type="module">
|
||||
import request from "/js/request.js";
|
||||
|
||||
document.getElementById("form").addEventListener("submit", async e => {
|
||||
e.preventDefault();
|
||||
document.getElementById('adminHidden').disabled = document.getElementById("admin").checked;
|
||||
|
||||
const object = {};
|
||||
new FormData(e.target).forEach((value, key) => object[key] = value);
|
||||
|
||||
const res = await request("/api/users/<%=member.id%>", "PATCH", object);
|
||||
if (res) alert(`User is updated!`);
|
||||
location.reload();
|
||||
});
|
||||
</script>
|
||||
<%- include("extra/footer") %>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,33 +1 @@
|
|||
<div class="footer">
|
||||
<% if (user){ %>
|
||||
<select id="theme_select">
|
||||
<% for(const theme of dataset.themes){%>
|
||||
<option value="<%= theme %>"><%= theme %> theme</option>
|
||||
<% } %>
|
||||
</select>
|
||||
<script>
|
||||
const theme_select = document.getElementById("theme_select");
|
||||
theme_select.querySelector(`option[value=<%= user.theme.name %>]`).selected = true;
|
||||
theme_select.addEventListener("change", async e => {
|
||||
const name = e.target.value;
|
||||
await fetch('/api/users/<%= user.id %>', {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({
|
||||
theme: {
|
||||
name
|
||||
}
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
});
|
||||
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>
|
||||
<%- include(dataset.getFile(dataset.theme.codename +"/extra/footer")) %>
|
|
@ -1,9 +1 @@
|
|||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title><%= title || dataset.forum_name +"-forum" %></title>
|
||||
<meta name="description" content="<%= dataset.description %>">
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||
<link rel="stylesheet" href="/css/themes/<%= dataset.theme.name %>.css" />
|
||||
<link rel="stylesheet" href="/css/common.css" />
|
||||
</head>
|
||||
<%- include(dataset.getFile(dataset.theme.codename +"/extra/meta")) %>
|
|
@ -1,45 +1 @@
|
|||
|
||||
<% 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>
|
||||
<%- include(dataset.getFile(dataset.theme.codename +"/extra/navbar")) %>
|
|
@ -9,21 +9,16 @@
|
|||
|
||||
<%- include("extra/navbar") %>
|
||||
|
||||
|
||||
|
||||
<div class="content">
|
||||
<% if (user) { %>
|
||||
<h2 style="color: var(--main);">
|
||||
<div class="box-username">Welcome, <%= user.name %>
|
||||
<div class="avatar"><img src="<%=user.avatar %>"></div>
|
||||
|
||||
</div>
|
||||
You can log out of the site here:
|
||||
|
||||
Main page will be rewritten soon, so stay tuned!
|
||||
</h2>
|
||||
|
||||
<a href="/login" class="btn-outline-primary">LOGOUT</a>
|
||||
<br>
|
||||
|
||||
<% } else { %>
|
||||
<h2 style="color: var(--main);">Welcome, Guest!<br>You can press the button to register:
|
||||
<a href="/register" class="btn-outline-primary">REGISTER</a>
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/2.1.0/showdown.min.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css">
|
||||
<script src="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.js"></script>
|
||||
<link href='https://unpkg.com/boxicons@2.1.2/css/boxicons.min.css' rel='stylesheet'>
|
||||
|
||||
|
||||
<%- include("extra/meta", {title: thread.title }) %>
|
||||
|
||||
|
||||
<body>
|
||||
<%- include("extra/navbar") %>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/2.1.0/showdown.min.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css">
|
||||
<script src="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.js"></script>
|
||||
|
||||
<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/pages.css" />
|
||||
|
@ -142,6 +143,13 @@
|
|||
if (res) location.href = `/threads/<%= thread.id %>?page=${tp-1}`;
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
.fa {
|
||||
color: var(--main);
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<% }%>
|
||||
<div class="pagination">
|
||||
<div class="back">
|
||||
|
|
129
views/user.ejs
129
views/user.ejs
|
@ -13,118 +13,8 @@
|
|||
|
||||
|
||||
<div class="content">
|
||||
<% 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>
|
||||
<link rel="stylesheet" href="/css/modal.css" />
|
||||
|
||||
<a class="btn-outline-primary" id="toogle">Edit user!</a>
|
||||
|
||||
<div class="modal no-active" id="user-edit">
|
||||
<div class="modal-content">
|
||||
<div class="modal-close">
|
||||
<i id="toogle" class="fa-solid fa-square-xmark"></i>
|
||||
</div>
|
||||
<h1 class="title" style="text-align:center;">Edit <a class="see" href="/users/<%= member.id %>"><%= member.name %></a></h1>
|
||||
<div class="content">
|
||||
<form id="form" class="see" style="box-shadow:none">
|
||||
<input type="text" name="name" placeholder="<%=member.name%>" class="input">
|
||||
|
||||
<textarea class="input" name="about" rows="4" cols="60" name="content" placeholder="<%=member.about%>"></textarea>
|
||||
<% if (user?.admin){ %>
|
||||
Is Admin? <input id='admin' type='checkbox' value='true' name='admin' <%=member.admin ? "checked": ""%>>
|
||||
<input id='adminHidden' type='hidden' value='false' name='admin'>
|
||||
<% } %>
|
||||
|
||||
<button class="btn-primary" style="width:100%;">Update User!</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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')
|
||||
|
||||
});
|
||||
form.addEventListener("submit", async e => {
|
||||
e.preventDefault();
|
||||
document.getElementById('adminHidden').disabled = document.getElementById("admin").checked;
|
||||
|
||||
const object = {};
|
||||
new FormData(e.target).forEach((value, key) => object[key] = value);
|
||||
|
||||
const res = await request("/api/users/<%=member.id%>", "PATCH", object);
|
||||
if (res) alert(`User is updated!`);
|
||||
location.reload();
|
||||
});
|
||||
</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>
|
||||
<% } %>
|
||||
|
||||
<%- include(dataset.getFile(dataset.theme.codename +"/extra/usermenu")) %>
|
||||
<div class="box" style="justify-content:center;">
|
||||
<img style="width:150px;height:150px;border-radius:50%;" src="<%=member.avatar %>">
|
||||
</div>
|
||||
|
@ -133,7 +23,7 @@
|
|||
<h2 class="box-value" style="align-self: center;">Admin</h2>
|
||||
<% } %>
|
||||
|
||||
|
||||
<% if (member.about?.length) { %>
|
||||
<div class="box-value" id="about" style="
|
||||
margin: 10px auto;
|
||||
box-shadow: 0 0 5px 0 var(--second);
|
||||
|
@ -152,6 +42,9 @@ color: var(--anti);
|
|||
const about = document.getElementById("about")
|
||||
about.innerHTML = converter.makeHtml(about.innerText);
|
||||
</script>
|
||||
<% } %>
|
||||
|
||||
|
||||
|
||||
<div class="box">
|
||||
<h2 class="box-title">Name:</h2>
|
||||
|
@ -168,12 +61,16 @@ color: var(--anti);
|
|||
</div>
|
||||
<% } %>
|
||||
<div class="box">
|
||||
<a href="/search/messages?authorID=<%= member.id %>" class="box-title">Message:</a>
|
||||
<a class="box-value"><%= counts.message %></a>
|
||||
<h2 class="box-title">Message:</h2>
|
||||
<a class="box-value" href="/search/messages?authorID=<%= member.id %>">
|
||||
<%= counts.message %>
|
||||
</a>
|
||||
</div>
|
||||
<div class="box">
|
||||
<a href="/search/threads?authorID=<%= member.id %>" class="box-title">Thread:</a>
|
||||
<h2 class="box-value"><%= counts.thread %></h2>
|
||||
<h2 class="box-title">Thread:</h2>
|
||||
<a class="box-value" href="/search/threads?authorID=<%= member.id %>">
|
||||
<%= counts.thread %>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<%- include("extra/footer") %>
|
||||
|
|
|
@ -10,15 +10,14 @@
|
|||
<link rel="stylesheet" href="/css/pages.css" />
|
||||
|
||||
<%- include("extra/navbar") %>
|
||||
|
||||
<div class="users">
|
||||
<% users.forEach(user=>{ %>
|
||||
<% users.filter(member=> !member.deleted || user.admin ).forEach(member => { %>
|
||||
<div style="display:flex;justify-content:center;">
|
||||
<div class="user-box">
|
||||
<img src="<%= user.avatar %>" class="user-box-img">
|
||||
<div class="user-box-title"> <a href="<%= user.getLink() %>">
|
||||
<% if (user.deleted) { %> <span style="color: var(--important);">[DELETED]</span><% } %>
|
||||
<%= user.name %></a></div>
|
||||
<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>
|
||||
<% }); %>
|
||||
|
|
Loading…
Reference in a new issue