diff --git a/client/index.html b/client/index.html
index 843e738b..cdce541f 100644
--- a/client/index.html
+++ b/client/index.html
@@ -264,8 +264,8 @@
diff --git a/defaults/config.js b/defaults/config.js
index 7090760b..a2e3f136 100644
--- a/defaults/config.js
+++ b/defaults/config.js
@@ -51,11 +51,12 @@ module.exports = {
//
// Set the default theme.
+ // Find out how to add new themes at https://thelounge.github.io/docs/packages/themes
//
// @type string
- // @default "themes/example.css"
+ // @default "example"
//
- theme: "themes/example.css",
+ theme: "example",
//
// Prefetch URLs
diff --git a/src/helper.js b/src/helper.js
index 288dcfec..73c53262 100644
--- a/src/helper.js
+++ b/src/helper.js
@@ -12,6 +12,8 @@ const colors = require("colors/safe");
var Helper = {
config: null,
expandHome: expandHome,
+ getPackagesPath: getPackagesPath,
+ getPackageModulePath: getPackageModulePath,
getStoragePath: getStoragePath,
getUserConfigPath: getUserConfigPath,
getUserLogsPath: getUserLogsPath,
@@ -96,6 +98,14 @@ function getStoragePath() {
return path.join(this.HOME, "storage");
}
+function getPackagesPath() {
+ return path.join(this.HOME, "packages", "node_modules");
+}
+
+function getPackageModulePath(packageName) {
+ return path.join(Helper.getPackagesPath(), packageName);
+}
+
function ip2hex(address) {
// no ipv6 support
if (!net.isIPv4(address)) {
diff --git a/src/plugins/themes.js b/src/plugins/themes.js
new file mode 100644
index 00000000..8c90cdd9
--- /dev/null
+++ b/src/plugins/themes.js
@@ -0,0 +1,85 @@
+"use strict";
+
+const fs = require("fs");
+const Helper = require("../helper");
+const colors = require("colors/safe");
+const path = require("path");
+const _ = require("lodash");
+const themes = new Map();
+
+module.exports = {
+ getAll: getAll,
+ getFilename: getFilename
+};
+
+fs.readdir("client/themes/", (err, builtInThemes) => {
+ if (err) {
+ return;
+ }
+ builtInThemes
+ .filter((theme) => theme.endsWith(".css"))
+ .map(makeLocalThemeObject)
+ .forEach((theme) => themes.set(theme.name, theme));
+});
+
+fs.readdir(Helper.getPackagesPath(), (err, packages) => {
+ if (err) {
+ return;
+ }
+ packages
+ .map(makePackageThemeObject)
+ .forEach((theme) => {
+ if (theme) {
+ themes.set(theme.name, theme);
+ }
+ });
+});
+
+function getAll() {
+ return _.sortBy(Array.from(themes.values()), "displayName");
+}
+
+function getFilename(module) {
+ if (themes.has(module)) {
+ return themes.get(module).filename;
+ }
+}
+
+function makeLocalThemeObject(css) {
+ const themeName = css.slice(0, -4);
+ return {
+ displayName: themeName.charAt(0).toUpperCase() + themeName.slice(1),
+ filename: path.join(__dirname, "..", "client", "themes", `${themeName}.css`),
+ name: themeName
+ };
+}
+
+function getModuleInfo(packageName) {
+ let module;
+ try {
+ module = require(Helper.getPackageModulePath(packageName));
+ } catch (e) {
+ log.warn(`Specified theme ${colors.yellow(packageName)} is not installed in packages directory`);
+ return;
+ }
+ if (!module.lounge) {
+ log.warn(`Specified theme ${colors.yellow(packageName)} doesn't have required information.`);
+ return;
+ }
+ return module.lounge;
+}
+
+function makePackageThemeObject(moduleName) {
+ const module = getModuleInfo(moduleName);
+ if (!module || module.type !== "theme") {
+ return;
+ }
+ const modulePath = Helper.getPackageModulePath(moduleName);
+ const displayName = module.name || moduleName;
+ const filename = path.join(modulePath, module.css);
+ return {
+ displayName: displayName,
+ filename: filename,
+ name: moduleName
+ };
+}
diff --git a/src/server.js b/src/server.js
index 5b708555..aebc6bfb 100644
--- a/src/server.js
+++ b/src/server.js
@@ -14,6 +14,7 @@ var Helper = require("./helper");
var colors = require("colors/safe");
const net = require("net");
const Identification = require("./identification");
+const themes = require("./plugins/themes");
// The order defined the priority: the first available plugin is used
// ALways keep local auth in the end, which should always be enabled.
@@ -51,6 +52,15 @@ module.exports = function() {
.set("view engine", "html")
.set("views", path.join(__dirname, "..", "client"));
+ app.get("/themes/:theme.css", (req, res) => {
+ const themeName = req.params.theme;
+ const theme = themes.getFilename(themeName);
+ if (theme === undefined) {
+ return res.status(404).send("Not found");
+ }
+ return res.sendFile(theme);
+ });
+
var config = Helper.config;
var server = null;
@@ -191,16 +201,13 @@ function index(req, res, next) {
pkg,
Helper.config
);
+ if (!data.theme.includes(".css")) { // Backwards compatibility for old way of specifying themes in settings
+ data.theme = `themes/${data.theme}.css`;
+ } else {
+ log.warn(`Referring to CSS files in the ${colors.green("theme")} setting of ${colors.green(Helper.CONFIG_PATH)} is ${colors.bold("deprecated")} and will be removed in a future version.`);
+ }
data.gitCommit = Helper.getGitCommit();
- data.themes = fs.readdirSync("client/themes/").filter(function(themeFile) {
- return themeFile.endsWith(".css");
- }).map(function(css) {
- const filename = css.slice(0, -4);
- return {
- name: filename.charAt(0).toUpperCase() + filename.slice(1),
- filename: filename
- };
- });
+ data.themes = themes.getAll();
const policies = [
"default-src *",