Compare commits

..

3 Commits

Author SHA1 Message Date
Armored Dragon e39fce5f40
Theme support (#6)
Signed-off-by: Armored Dragon <publicmail@armoreddragon.com>
2024-07-08 13:24:31 -05:00
Armored Dragon 57460c2328
Formatted with eslint.
Signed-off-by: Armored Dragon <publicmail@armoreddragon.com>
2024-07-08 13:21:36 -05:00
Armored Dragon f4bf5c37d9
Added eslint.
Removed old linter.

Signed-off-by: Armored Dragon <publicmail@armoreddragon.com>
2024-07-08 13:17:53 -05:00
13 changed files with 1896 additions and 693 deletions

2
.gitignore vendored
View File

@ -5,3 +5,5 @@
.vscode/settings.json
/frontend/public/img/.dev
/prisma/migrations
/frontend/views/themes
!/frontend/views/themes/default

1
.prettierignore Normal file
View File

@ -0,0 +1 @@
*.ejs

View File

@ -608,6 +608,11 @@ async function postSetting(key, value) {
settings[key] = value;
}
// Refresh the theme if it was a theme change
if (key === "theme") {
// Refresh the theme
}
return { success: true };
} catch (e) {
return { success: false, message: e.message };
@ -627,6 +632,39 @@ async function editSetting({ name, value }) {
return _r(true);
}
async function installTheme(url, { requester_id } = {}) {
// User is admin?
let user = await getUser({ user_id: requester_id });
if (!user.success) return _r(false, "User does not exist.");
user = user.data;
if (user.role !== "ADMIN") return _r(false, "User is not permitted.");
// TODO: Test if git repo has valid manifest.json
const path = require("path");
const { execSync } = require("child_process");
execSync(`git clone ${url}`, {
stdio: [0, 1, 2], // we need this so node will print the command output
cwd: path.resolve(__dirname, "../../frontend/views/themes"), // path to where you want to save the file
});
return _r(true);
}
async function deleteTheme(name, { requester_id } = {}) {
let user = await getUser({ user_id: requester_id });
if (!user.success) return _r(false, "User does not exist.");
user = user.data;
if (user.role !== "ADMIN") return _r(false, "User is not permitted.");
const path = require("path");
const { execSync } = require("child_process");
if (!name) _r(false, "Panic! No theme specified");
execSync(`rm -r ${name}`, {
cwd: path.resolve(__dirname, "../../frontend/views/themes"),
});
return _r(true);
}
function _stripPrivatePost(post) {
if (!post) return;
if (post.owner) delete post.owner.password;
@ -636,4 +674,4 @@ const _r = (s, m) => {
return { success: s, message: m };
};
module.exports = { settings, newUser, getUser, editUser, getPost, newPost, editPost, deletePost, getBiography, updateBiography, uploadMedia, getTags, postSetting, getSetting };
module.exports = { settings, newUser, getUser, editUser, getPost, newPost, editPost, deletePost, getBiography, updateBiography, uploadMedia, getTags, postSetting, getSetting, installTheme, deleteTheme };

View File

@ -66,5 +66,11 @@ async function patchBiography(request, response) {
async function patchUser(request, response) {
return response.json(await core.editUser({ requester_id: request.session.user.id, user_id: request.body.id, user_content: request.body }));
}
async function postTheme(request, response) {
return response.json(await core.installTheme(request.body.url, { requester_id: request.session.user.id }));
}
async function deleteTheme(request, response) {
return response.json(await core.deleteTheme(request.body.name, { requester_id: request.session.user.id }));
}
module.exports = { postRegister, patchBiography, postLogin, postSetting, postImage, deleteImage, deleteBlog, patchBlog, patchUser };
module.exports = { postRegister, patchBiography, postLogin, postSetting, postImage, deleteImage, deleteBlog, patchBlog, patchUser, postTheme, deleteTheme };

View File

@ -1,5 +1,7 @@
const external = require("./core/external_api");
const core = require("./core/core");
const fs = require("fs");
const path = require("path");
function _getThemePage(page_name) {
let manifest = require(`../frontend/views/themes/${core.settings.theme}/manifest.json`);
@ -99,7 +101,17 @@ async function blogEdit(req, res) {
res.render(_getThemePage("postNew"), { ...(await getDefaults(req)), existing_blog: existing_blog });
}
async function admin(request, response) {
response.render(_getThemePage("admin-settings"), { ...(await getDefaults(request)) });
let theme_data = {
installed: [],
current: core.settings.theme,
};
// Get theme list
fs.readdir(path.resolve(__dirname, "../frontend/views/themes"), (err, files) => {
files.forEach((theme) => theme_data.installed.push(theme));
});
response.render(_getThemePage("admin-settings"), { ...(await getDefaults(request)), theme_data: theme_data });
}
async function atom(req, res) {
res.type("application/xml");

4
eslint.config.mjs Normal file
View File

@ -0,0 +1,4 @@
import globals from "globals";
import pluginJs from "@eslint/js";
export default [{ files: ["**/*.js"], languageOptions: { sourceType: "commonjs" } }, { languageOptions: { globals: globals.browser } }, { rules: { "no-unused-vars": "error", "no-undef": "error", indent: ["error", "tab", { tabWidth: 4 }] } }, pluginJs.configs.recommended];

View File

@ -66,6 +66,45 @@
.page .page-center .setting-list .setting:nth-child(even) {
background-color: rgb(250, 250, 250);
}
.page .page-center .theme-list .add-theme-area {
display: flex;
flex-direction: row;
margin-bottom: 1rem;
}
.page .page-center .theme-list .add-theme-area input {
flex-grow: 1;
margin-right: 1rem;
}
.page .page-center .theme-list .add-theme-area button {
width: 5rem;
}
.page .page-center .theme-list .entry {
width: 100%;
height: 32px;
background-color: rgb(240, 240, 240);
padding: 0.1rem;
box-sizing: border-box;
display: flex;
flex-direction: row;
}
.page .page-center .theme-list .entry .title {
margin: auto auto auto 0;
}
.page .page-center .theme-list .entry .value {
margin: 0 0 0 auto;
display: flex;
}
.page .page-center .theme-list .entry .value button {
margin: auto;
font-size: 1rem;
text-align: center;
}
.page .page-center .theme-list .entry:nth-child(even) {
background-color: rgb(250, 250, 250);
}
.page .page-center .theme-list .entry.active {
background-color: #85c8ff;
}
.switch {
position: relative;

View File

@ -73,6 +73,52 @@
background-color: rgb(250, 250, 250);
}
}
.theme-list {
.add-theme-area {
display: flex;
flex-direction: row;
margin-bottom: 1rem;
input {
flex-grow: 1;
margin-right: 1rem;
}
button {
width: 5rem;
}
}
.entry {
width: 100%;
height: 32px;
background-color: rgb(240, 240, 240);
padding: 0.1rem;
box-sizing: border-box;
display: flex;
flex-direction: row;
.title {
margin: auto auto auto 0;
}
.value {
margin: 0 0 0 auto;
display: flex;
button {
margin: auto;
font-size: 1rem;
text-align: center;
}
}
}
.entry:nth-child(even) {
background-color: rgb(250, 250, 250);
}
.entry.active {
background-color: #85c8ff;
}
}
}
.switch {

View File

@ -68,8 +68,33 @@
</div>
</div>
</div>
<div class="page-center">
<div class="header">Themes</div>
<div class="theme-list">
<div class="add-theme-area">
<input id="theme-url" placeholder="Enter a git repository URL" type="text">
<button onclick="addTheme()">Add</button>
</div>
<% for(theme of theme_data.installed) { %>
<!-- -->
<div class="entry <% if(theme == theme_data.current) {%> active <% } %>">
<div class="title"><%= theme %></div>
<div class="value">
<button onclick="setTheme('<%= theme %>')" type="button">Use</button>
</div>
</div>
<!-- -->
<% } %>
</div>
</div>
</div>
<%- include("partials/footer.ejs") %>
</body>
</html>

View File

@ -22,3 +22,28 @@ async function changeValue(setting_name, element) {
if (response.body.success) {
}
}
async function addTheme() {
const url = qs("#theme-url").value;
if (!url || url.length == 0) return false;
const response = await request("/api/theme", "POST", { url: url });
if (response.body.success) {
alert("Added theme.");
}
}
async function setTheme(name) {
const form = {
setting_name: "theme",
value: name,
};
const response = await request("/setting", "POST", form);
if (response.body.success) {
alert("Changed theme.");
}
}

1004
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -19,6 +19,9 @@
"author": "Armored Dragon",
"license": "GPL-3.0",
"devDependencies": {
"@eslint/js": "^9.6.0",
"eslint": "^9.6.0",
"globals": "^15.8.0",
"nodemon": "^3.0.1"
},
"dependencies": {

10
yab.js
View File

@ -15,9 +15,11 @@ app.set("views", path.join(__dirname, "frontend/views"));
app.use(express.json({ limit: "500mb" }));
app.use(express.urlencoded({ extended: false }));
// TODO: Does this persist previous themes? May cause security issues!
const refreshTheme = (theme_name) => app.use(express.static(path.join(__dirname, `frontend/views/themes/${theme_name}`)));
refreshTheme("default");
app.use((req, res, next) => {
let theme = require("./backend/core/core").settings.theme;
let middleware = express.static(path.join(__dirname, `frontend/views/themes/${theme}`));
middleware(req, res, next);
});
app.use(
session({
@ -34,9 +36,11 @@ app.post("/setting", checkAuthenticated, internal.postSetting);
app.post("/api/web/image", checkAuthenticated, internal.postImage);
app.delete("/api/web/post/image", checkAuthenticated, internal.deleteImage);
app.delete("/api/web/post", checkAuthenticated, internal.deleteBlog);
app.delete("/api/theme", checkAuthenticated, internal.deleteTheme);
app.patch("/api/web/post", checkAuthenticated, internal.patchBlog);
app.patch("/api/web/biography", checkAuthenticated, internal.patchBiography);
app.patch("/api/web/user", checkAuthenticated, internal.patchUser);
app.post("/api/theme", checkAuthenticated, internal.postTheme);
// app.delete("/logout", page_scripts.logout);