Support scoped theme packages

v1.18.x
Peter Jaszkowiak 7 years ago committed by Julian Lam
parent d656c65c9a
commit 70ff2d9b88

@ -18,3 +18,4 @@ logs/
/build /build
.eslintrc .eslintrc
test/files test/files
*.min.js

@ -34,8 +34,11 @@ function buildTargets() {
); );
} }
var themeNamePattern = /^(@.*?\/)?nodebb-theme-.*$/;
var pluginNamePattern = /^(@.*?\/)?nodebb-(theme|plugin|widget|rewards)-.*$/;
function activate(plugin) { function activate(plugin) {
if (plugin.startsWith('nodebb-theme-')) { if (themeNamePattern.test(plugin)) {
reset.reset({ reset.reset({
theme: plugin, theme: plugin,
}, function (err) { }, function (err) {
@ -50,7 +53,7 @@ function activate(plugin) {
db.init(next); db.init(next);
}, },
function (next) { function (next) {
if (!plugin.startsWith('nodebb-')) { if (!pluginNamePattern.test(plugin)) {
// Allow omission of `nodebb-plugin-` // Allow omission of `nodebb-plugin-`
plugin = 'nodebb-plugin-' + plugin; plugin = 'nodebb-plugin-' + plugin;
} }

@ -14,6 +14,9 @@ var widgets = require('../widgets');
var dirname = require('./paths').baseDir; var dirname = require('./paths').baseDir;
var themeNamePattern = /^(@.*?\/)?nodebb-theme-.*$/;
var pluginNamePattern = /^(@.*?\/)?nodebb-(theme|plugin|widget|rewards)-.*$/;
exports.reset = function (options, callback) { exports.reset = function (options, callback) {
var map = { var map = {
theme: function (next) { theme: function (next) {
@ -21,7 +24,7 @@ exports.reset = function (options, callback) {
if (themeId === true) { if (themeId === true) {
resetThemes(next); resetThemes(next);
} else { } else {
if (!themeId.startsWith('nodebb-theme-')) { if (!themeNamePattern.test(themeId)) {
// Allow omission of `nodebb-theme-` // Allow omission of `nodebb-theme-`
themeId = 'nodebb-theme-' + themeId; themeId = 'nodebb-theme-' + themeId;
} }
@ -34,7 +37,7 @@ exports.reset = function (options, callback) {
if (pluginId === true) { if (pluginId === true) {
resetPlugins(next); resetPlugins(next);
} else { } else {
if (!pluginId.startsWith('nodebb-plugin-')) { if (!pluginNamePattern.test(pluginId)) {
// Allow omission of `nodebb-plugin-` // Allow omission of `nodebb-plugin-`
pluginId = 'nodebb-plugin-' + pluginId; pluginId = 'nodebb-plugin-' + pluginId;
} }

@ -16,22 +16,26 @@ themesController.get = function (req, res, next) {
var screenshotPath; var screenshotPath;
async.waterfall([ async.waterfall([
function (next) { function (next) {
file.exists(themeConfigPath, next); fs.readFile(themeConfigPath, 'utf8', function (err, config) {
}, if (err) {
function (exists, next) { if (err.code === 'ENOENT') {
if (!exists) {
return next(Error('invalid-data')); return next(Error('invalid-data'));
} }
fs.readFile(themeConfigPath, 'utf8', next); return next(err);
}
return next(null, config);
});
}, },
function (themeConfig, next) { function (themeConfig, next) {
try { try {
themeConfig = JSON.parse(themeConfig); themeConfig = JSON.parse(themeConfig);
next(null, themeConfig.screenshot ? path.join(themeDir, themeConfig.screenshot) : defaultScreenshotPath);
} catch (e) { } catch (e) {
next(e); return next(e);
} }
next(null, themeConfig.screenshot ? path.join(themeDir, themeConfig.screenshot) : defaultScreenshotPath);
}, },
function (_screenshotPath, next) { function (_screenshotPath, next) {
screenshotPath = _screenshotPath; screenshotPath = _screenshotPath;

@ -34,13 +34,15 @@ Dependencies.check = function (callback) {
}); });
}; };
var pluginNamePattern = /^(@.*?\/)?nodebb-(theme|plugin|widget|rewards)-.*$/;
Dependencies.checkModule = function (moduleName, callback) { Dependencies.checkModule = function (moduleName, callback) {
fs.readFile(path.join(__dirname, '../../node_modules/', moduleName, 'package.json'), { fs.readFile(path.join(__dirname, '../../node_modules/', moduleName, 'package.json'), {
encoding: 'utf-8', encoding: 'utf-8',
}, function (err, pkgData) { }, function (err, pkgData) {
if (err) { if (err) {
// If a bundled plugin/theme is not present, skip the dep check (#3384) // 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.'); winston.warn('[meta/dependencies] Bundled plugin ' + moduleName + ' not found, skipping dependency check.');
return callback(null, true); return callback(null, true);
} }

@ -45,9 +45,11 @@ function processImports(paths, templatePath, source, callback) {
} }
Templates.processImports = processImports; Templates.processImports = processImports;
var themeNamePattern = /^(@.*?\/)?nodebb-theme-.*$/;
function getTemplateDirs(activePlugins, callback) { function getTemplateDirs(activePlugins, callback) {
var pluginTemplates = activePlugins.map(function (id) { var pluginTemplates = activePlugins.map(function (id) {
if (id.startsWith('nodebb-theme-')) { if (themeNamePattern.test(id)) {
return nconf.get('theme_templates_path'); return nconf.get('theme_templates_path');
} }
if (!plugins.pluginsData[id]) { if (!plugins.pluginsData[id]) {

@ -14,6 +14,8 @@ var events = require('../events');
var Themes = module.exports; var Themes = module.exports;
var themeNamePattern = /^(@.*?\/)?nodebb-theme-.*$/;
Themes.get = function (callback) { Themes.get = function (callback) {
var themePath = nconf.get('themes_path'); var themePath = nconf.get('themes_path');
if (typeof themePath !== 'string') { if (typeof themePath !== 'string') {
@ -24,9 +26,13 @@ Themes.get = function (callback) {
function (next) { function (next) {
fs.readdir(themePath, next); fs.readdir(themePath, next);
}, },
function (files, next) { function (dirs, next) {
async.filter(files, function (file, next) { async.map(dirs.filter(function (dir) {
fs.stat(path.join(themePath, file), function (err, fileStat) { 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) {
if (err.code === 'ENOENT') { if (err.code === 'ENOENT') {
return next(null, false); return next(null, false);
@ -34,11 +40,54 @@ Themes.get = function (callback) {
return next(err); 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); }, next);
}, },
function (themes, next) { function (themes, next) {
themes = themes.reduce(function (prev, theme) {
if (!theme) {
return prev;
}
return prev.concat(theme);
}, []);
async.map(themes, function (theme, next) { async.map(themes, function (theme, next) {
var config = path.join(themePath, theme, 'theme.json'); var config = path.join(themePath, theme, 'theme.json');
@ -55,9 +104,9 @@ Themes.get = function (callback) {
// Minor adjustments for API output // Minor adjustments for API output
configObj.type = 'local'; configObj.type = 'local';
if (configObj.screenshot) { if (configObj.screenshot) {
configObj.screenshot_url = nconf.get('relative_path') + '/css/previews/' + configObj.id; configObj.screenshot_url = 'css/previews/' + encodeURIComponent(configObj.id);
} else { } else {
configObj.screenshot_url = nconf.get('relative_path') + '/assets/images/themes/default.png'; configObj.screenshot_url = 'assets/images/themes/default.png';
} }
next(null, configObj); next(null, configObj);
} catch (err) { } catch (err) {
@ -150,14 +199,14 @@ Themes.setupPaths = function (callback) {
function (data, next) { function (data, next) {
var themeId = data.currentThemeId || 'nodebb-theme-persona'; 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') { if (process.env.NODE_ENV === 'development') {
winston.info('[themes] Using theme ' + themeId); winston.info('[themes] Using theme ' + themeId);
} }
var themeObj = data.themesData.find(function (themeObj) {
return themeObj.id === themeId;
});
if (!themeObj) { if (!themeObj) {
return callback(new Error('[[error:theme-not-found]]')); return callback(new Error('[[error:theme-not-found]]'));
} }

@ -138,6 +138,8 @@ Plugins.reloadRoutes = function (callback) {
}); });
}; };
var themeNamePattern = /^(@.*?\/)?nodebb-theme-.*$/;
// DEPRECATED: remove in v1.8.0 // DEPRECATED: remove in v1.8.0
Plugins.getTemplates = function (callback) { Plugins.getTemplates = function (callback) {
var templates = {}; var templates = {};
@ -151,7 +153,7 @@ Plugins.getTemplates = function (callback) {
} }
async.eachSeries(plugins, function (plugin, next) { 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 + ')'); winston.verbose('[plugins] Loading templates (' + plugin.id + ')');
var templatesPath = path.join(__dirname, '../node_modules', plugin.id, plugin.templates || 'templates'); var templatesPath = path.join(__dirname, '../node_modules', plugin.id, plugin.templates || 'templates');
file.walk(templatesPath, function (err, pluginTemplates) { file.walk(templatesPath, function (err, pluginTemplates) {
@ -261,7 +263,7 @@ Plugins.normalise = function (apiReturn, callback) {
pluginMap[plugin.id].description = plugin.description; pluginMap[plugin.id].description = plugin.description;
pluginMap[plugin.id].url = pluginMap[plugin.id].url || plugin.url; pluginMap[plugin.id].url = pluginMap[plugin.id].url || plugin.url;
pluginMap[plugin.id].installed = true; 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].error = plugin.error || false;
pluginMap[plugin.id].active = plugin.active; pluginMap[plugin.id].active = plugin.active;
pluginMap[plugin.id].version = plugin.version; pluginMap[plugin.id].version = plugin.version;

@ -122,13 +122,16 @@ module.exports = function (Plugins) {
], callback); ], callback);
}; };
var themeNamePattern = /(@.*?\/)?nodebb-theme-.*$/;
Plugins.loadPlugin = function (pluginPath, callback) { Plugins.loadPlugin = function (pluginPath, callback) {
Plugins.data.loadPluginInfo(pluginPath, function (err, pluginData) { Plugins.data.loadPluginInfo(pluginPath, function (err, pluginData) {
if (err) { if (err) {
if (err.message === '[[error:parse-error]]') { if (err.message === '[[error:parse-error]]') {
return callback(); return callback();
} }
return callback(pluginPath.match('nodebb-theme') ? null : err);
return callback(themeNamePattern.test(pluginPath) ? null : err);
} }
checkVersion(pluginData); checkVersion(pluginData);

@ -1,7 +1,7 @@
<!-- BEGIN themes --> <!-- BEGIN themes -->
<div class="col-lg-4 col-md-6 col-sm-12 col-xs-12" data-type="{themes.type}" data-theme="{themes.id}"<!-- IF themes.css --> data-css="{themes.css}"<!-- ENDIF themes.css -->> <div class="col-lg-4 col-md-6 col-sm-12 col-xs-12" data-type="{themes.type}" data-theme="{themes.id}"<!-- IF themes.css --> data-css="{themes.css}"<!-- ENDIF themes.css -->>
<div class="theme-card mdl-card mdl-shadow--2dp"> <div class="theme-card mdl-card mdl-shadow--2dp">
<div class="mdl-card__title mdl-card--expand" style="background-image: url('{themes.screenshot_url}');"></div> <div class="mdl-card__title mdl-card--expand" style="background-image: url('{relative_path}/{themes.screenshot_url}');"></div>
<div class="mdl-card__supporting-text"> <div class="mdl-card__supporting-text">
<h2 class="mdl-card__title-text">{themes.name}</h2> <h2 class="mdl-card__title-text">{themes.name}</h2>
<p> <p>

Loading…
Cancel
Save