diff --git a/.gitignore b/.gitignore index db2f430..c000006 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ *.code-workspace .vscode/settings.json /frontend/public/img/.dev -/prisma/migrations \ No newline at end of file +/prisma/migrations +/frontend/views/themes +!/frontend/views/themes/default diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..82435b1 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +*.ejs \ No newline at end of file diff --git a/backend/core/core.js b/backend/core/core.js index 7d48cfd..03c3bfc 100644 --- a/backend/core/core.js +++ b/backend/core/core.js @@ -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 }; diff --git a/backend/core/internal_api.js b/backend/core/internal_api.js index 3adf840..21b12f3 100644 --- a/backend/core/internal_api.js +++ b/backend/core/internal_api.js @@ -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 }; diff --git a/backend/page_scripts.js b/backend/page_scripts.js index 58943ee..b85d8bb 100644 --- a/backend/page_scripts.js +++ b/backend/page_scripts.js @@ -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"); diff --git a/frontend/views/themes/default/css/settings.css b/frontend/views/themes/default/css/settings.css index 3e651c2..fe0b93c 100644 --- a/frontend/views/themes/default/css/settings.css +++ b/frontend/views/themes/default/css/settings.css @@ -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; diff --git a/frontend/views/themes/default/css/settings.scss b/frontend/views/themes/default/css/settings.scss index 3a0803e..f931841 100644 --- a/frontend/views/themes/default/css/settings.scss +++ b/frontend/views/themes/default/css/settings.scss @@ -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 { diff --git a/frontend/views/themes/default/ejs/admin-settings.ejs b/frontend/views/themes/default/ejs/admin-settings.ejs index 60317b4..454a77e 100644 --- a/frontend/views/themes/default/ejs/admin-settings.ejs +++ b/frontend/views/themes/default/ejs/admin-settings.ejs @@ -1,74 +1,99 @@ -
- <%- include("partials/document-head.ejs") %> - - - - - - <%- include("partials/header.ejs") %> -