From 70ff2d9b886db035d6680b39929018c976c59652 Mon Sep 17 00:00:00 2001
From: Peter Jaszkowiak
Date: Sat, 9 Jun 2018 16:26:28 -0600
Subject: [PATCH] Support scoped theme packages
---
.eslintignore | 1 +
src/cli/manage.js | 7 ++-
src/cli/reset.js | 7 ++-
src/controllers/admin/themes.js | 22 ++++----
src/meta/dependencies.js | 4 +-
src/meta/templates.js | 4 +-
src/meta/themes.js | 69 +++++++++++++++++++++----
src/plugins.js | 6 ++-
src/plugins/load.js | 5 +-
src/views/admin/partials/theme_list.tpl | 2 +-
10 files changed, 98 insertions(+), 29 deletions(-)
diff --git a/.eslintignore b/.eslintignore
index 11b456699f..ca092ce2ea 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -18,3 +18,4 @@ logs/
/build
.eslintrc
test/files
+*.min.js
diff --git a/src/cli/manage.js b/src/cli/manage.js
index 37d2d9bc26..9d360494ec 100644
--- a/src/cli/manage.js
+++ b/src/cli/manage.js
@@ -34,8 +34,11 @@ function buildTargets() {
);
}
+var themeNamePattern = /^(@.*?\/)?nodebb-theme-.*$/;
+var pluginNamePattern = /^(@.*?\/)?nodebb-(theme|plugin|widget|rewards)-.*$/;
+
function activate(plugin) {
- if (plugin.startsWith('nodebb-theme-')) {
+ if (themeNamePattern.test(plugin)) {
reset.reset({
theme: plugin,
}, function (err) {
@@ -50,7 +53,7 @@ function activate(plugin) {
db.init(next);
},
function (next) {
- if (!plugin.startsWith('nodebb-')) {
+ if (!pluginNamePattern.test(plugin)) {
// Allow omission of `nodebb-plugin-`
plugin = 'nodebb-plugin-' + plugin;
}
diff --git a/src/cli/reset.js b/src/cli/reset.js
index bb0d110478..44d78df961 100644
--- a/src/cli/reset.js
+++ b/src/cli/reset.js
@@ -14,6 +14,9 @@ var widgets = require('../widgets');
var dirname = require('./paths').baseDir;
+var themeNamePattern = /^(@.*?\/)?nodebb-theme-.*$/;
+var pluginNamePattern = /^(@.*?\/)?nodebb-(theme|plugin|widget|rewards)-.*$/;
+
exports.reset = function (options, callback) {
var map = {
theme: function (next) {
@@ -21,7 +24,7 @@ exports.reset = function (options, callback) {
if (themeId === true) {
resetThemes(next);
} else {
- if (!themeId.startsWith('nodebb-theme-')) {
+ if (!themeNamePattern.test(themeId)) {
// Allow omission of `nodebb-theme-`
themeId = 'nodebb-theme-' + themeId;
}
@@ -34,7 +37,7 @@ exports.reset = function (options, callback) {
if (pluginId === true) {
resetPlugins(next);
} else {
- if (!pluginId.startsWith('nodebb-plugin-')) {
+ if (!pluginNamePattern.test(pluginId)) {
// Allow omission of `nodebb-plugin-`
pluginId = 'nodebb-plugin-' + pluginId;
}
diff --git a/src/controllers/admin/themes.js b/src/controllers/admin/themes.js
index 717d4bf0dc..719cb8ecff 100644
--- a/src/controllers/admin/themes.js
+++ b/src/controllers/admin/themes.js
@@ -16,22 +16,26 @@ themesController.get = function (req, res, next) {
var screenshotPath;
async.waterfall([
function (next) {
- file.exists(themeConfigPath, next);
- },
- function (exists, next) {
- if (!exists) {
- return next(Error('invalid-data'));
- }
+ fs.readFile(themeConfigPath, 'utf8', function (err, config) {
+ if (err) {
+ if (err.code === 'ENOENT') {
+ return next(Error('invalid-data'));
+ }
- fs.readFile(themeConfigPath, 'utf8', next);
+ return next(err);
+ }
+
+ return next(null, config);
+ });
},
function (themeConfig, next) {
try {
themeConfig = JSON.parse(themeConfig);
- next(null, themeConfig.screenshot ? path.join(themeDir, themeConfig.screenshot) : defaultScreenshotPath);
} catch (e) {
- next(e);
+ return next(e);
}
+
+ next(null, themeConfig.screenshot ? path.join(themeDir, themeConfig.screenshot) : defaultScreenshotPath);
},
function (_screenshotPath, next) {
screenshotPath = _screenshotPath;
diff --git a/src/meta/dependencies.js b/src/meta/dependencies.js
index db403732ec..095b48e19d 100644
--- a/src/meta/dependencies.js
+++ b/src/meta/dependencies.js
@@ -34,13 +34,15 @@ Dependencies.check = function (callback) {
});
};
+var pluginNamePattern = /^(@.*?\/)?nodebb-(theme|plugin|widget|rewards)-.*$/;
+
Dependencies.checkModule = function (moduleName, callback) {
fs.readFile(path.join(__dirname, '../../node_modules/', moduleName, 'package.json'), {
encoding: 'utf-8',
}, function (err, pkgData) {
if (err) {
// If a bundled plugin/theme is not present, skip the dep check (#3384)
- if (err.code === 'ENOENT' && (moduleName === 'nodebb-rewards-essentials' || moduleName.startsWith('nodebb-plugin') || moduleName.startsWith('nodebb-theme'))) {
+ if (err.code === 'ENOENT' && pluginNamePattern.test(moduleName)) {
winston.warn('[meta/dependencies] Bundled plugin ' + moduleName + ' not found, skipping dependency check.');
return callback(null, true);
}
diff --git a/src/meta/templates.js b/src/meta/templates.js
index b2d14801ae..1ccf14c8e3 100644
--- a/src/meta/templates.js
+++ b/src/meta/templates.js
@@ -45,9 +45,11 @@ function processImports(paths, templatePath, source, callback) {
}
Templates.processImports = processImports;
+var themeNamePattern = /^(@.*?\/)?nodebb-theme-.*$/;
+
function getTemplateDirs(activePlugins, callback) {
var pluginTemplates = activePlugins.map(function (id) {
- if (id.startsWith('nodebb-theme-')) {
+ if (themeNamePattern.test(id)) {
return nconf.get('theme_templates_path');
}
if (!plugins.pluginsData[id]) {
diff --git a/src/meta/themes.js b/src/meta/themes.js
index e6eeb7011d..0f22993c3f 100644
--- a/src/meta/themes.js
+++ b/src/meta/themes.js
@@ -14,6 +14,8 @@ var events = require('../events');
var Themes = module.exports;
+var themeNamePattern = /^(@.*?\/)?nodebb-theme-.*$/;
+
Themes.get = function (callback) {
var themePath = nconf.get('themes_path');
if (typeof themePath !== 'string') {
@@ -24,9 +26,13 @@ Themes.get = function (callback) {
function (next) {
fs.readdir(themePath, next);
},
- function (files, next) {
- async.filter(files, function (file, next) {
- fs.stat(path.join(themePath, file), function (err, fileStat) {
+ function (dirs, next) {
+ async.map(dirs.filter(function (dir) {
+ return themeNamePattern.test(dir) || dir.startsWith('@');
+ }), function (dir, next) {
+ var dirpath = path.join(themePath, dir);
+
+ fs.stat(dirpath, function (err, stat) {
if (err) {
if (err.code === 'ENOENT') {
return next(null, false);
@@ -34,11 +40,54 @@ Themes.get = function (callback) {
return next(err);
}
- next(null, (fileStat.isDirectory() && file.slice(0, 13) === 'nodebb-theme-'));
+ if (!stat.isDirectory()) {
+ return next(null, null);
+ }
+
+ if (!dir.startsWith('@')) {
+ return next(null, dir);
+ }
+
+ fs.readdir(dirpath, function (err, themes) {
+ if (err) {
+ return next(err);
+ }
+
+ async.filter(themes.filter(function (theme) {
+ return themeNamePattern.test(theme);
+ }), function (theme, next) {
+ fs.stat(path.join(dirpath, theme), function (err, stat) {
+ if (err) {
+ if (err.code === 'ENOENT') {
+ return next(null, false);
+ }
+ return next(err);
+ }
+
+ next(null, stat.isDirectory());
+ });
+ }, function (err, themes) {
+ if (err) {
+ return next(err);
+ }
+
+ next(null, themes.map(function (theme) {
+ return dir + '/' + theme;
+ }));
+ });
+ });
});
}, next);
},
function (themes, next) {
+ themes = themes.reduce(function (prev, theme) {
+ if (!theme) {
+ return prev;
+ }
+
+ return prev.concat(theme);
+ }, []);
+
async.map(themes, function (theme, next) {
var config = path.join(themePath, theme, 'theme.json');
@@ -55,9 +104,9 @@ Themes.get = function (callback) {
// Minor adjustments for API output
configObj.type = 'local';
if (configObj.screenshot) {
- configObj.screenshot_url = nconf.get('relative_path') + '/css/previews/' + configObj.id;
+ configObj.screenshot_url = 'css/previews/' + encodeURIComponent(configObj.id);
} else {
- configObj.screenshot_url = nconf.get('relative_path') + '/assets/images/themes/default.png';
+ configObj.screenshot_url = 'assets/images/themes/default.png';
}
next(null, configObj);
} catch (err) {
@@ -150,14 +199,14 @@ Themes.setupPaths = function (callback) {
function (data, next) {
var themeId = data.currentThemeId || 'nodebb-theme-persona';
- var themeObj = data.themesData.filter(function (themeObj) {
- return themeObj.id === themeId;
- })[0];
-
if (process.env.NODE_ENV === 'development') {
winston.info('[themes] Using theme ' + themeId);
}
+ var themeObj = data.themesData.find(function (themeObj) {
+ return themeObj.id === themeId;
+ });
+
if (!themeObj) {
return callback(new Error('[[error:theme-not-found]]'));
}
diff --git a/src/plugins.js b/src/plugins.js
index a1193125e8..a4d28944fe 100644
--- a/src/plugins.js
+++ b/src/plugins.js
@@ -138,6 +138,8 @@ Plugins.reloadRoutes = function (callback) {
});
};
+var themeNamePattern = /^(@.*?\/)?nodebb-theme-.*$/;
+
// DEPRECATED: remove in v1.8.0
Plugins.getTemplates = function (callback) {
var templates = {};
@@ -151,7 +153,7 @@ Plugins.getTemplates = function (callback) {
}
async.eachSeries(plugins, function (plugin, next) {
- if (plugin.templates || plugin.id.startsWith('nodebb-theme-')) {
+ if (plugin.templates || themeNamePattern.test(plugin.id)) {
winston.verbose('[plugins] Loading templates (' + plugin.id + ')');
var templatesPath = path.join(__dirname, '../node_modules', plugin.id, plugin.templates || 'templates');
file.walk(templatesPath, function (err, pluginTemplates) {
@@ -261,7 +263,7 @@ Plugins.normalise = function (apiReturn, callback) {
pluginMap[plugin.id].description = plugin.description;
pluginMap[plugin.id].url = pluginMap[plugin.id].url || plugin.url;
pluginMap[plugin.id].installed = true;
- pluginMap[plugin.id].isTheme = !!plugin.id.match('nodebb-theme-');
+ pluginMap[plugin.id].isTheme = themeNamePattern.test(plugin.id);
pluginMap[plugin.id].error = plugin.error || false;
pluginMap[plugin.id].active = plugin.active;
pluginMap[plugin.id].version = plugin.version;
diff --git a/src/plugins/load.js b/src/plugins/load.js
index 8745a833a0..1f36b4eb55 100644
--- a/src/plugins/load.js
+++ b/src/plugins/load.js
@@ -122,13 +122,16 @@ module.exports = function (Plugins) {
], callback);
};
+ var themeNamePattern = /(@.*?\/)?nodebb-theme-.*$/;
+
Plugins.loadPlugin = function (pluginPath, callback) {
Plugins.data.loadPluginInfo(pluginPath, function (err, pluginData) {
if (err) {
if (err.message === '[[error:parse-error]]') {
return callback();
}
- return callback(pluginPath.match('nodebb-theme') ? null : err);
+
+ return callback(themeNamePattern.test(pluginPath) ? null : err);
}
checkVersion(pluginData);
diff --git a/src/views/admin/partials/theme_list.tpl b/src/views/admin/partials/theme_list.tpl
index 26a226b803..aede3d71d0 100644
--- a/src/views/admin/partials/theme_list.tpl
+++ b/src/views/admin/partials/theme_list.tpl
@@ -1,7 +1,7 @@