feat: #7743 meta/themes.js

v1.18.x
Julian Lam 6 years ago
parent a15c50bf62
commit 7dc0eaf070

@ -1,203 +1,140 @@
'use strict'; 'use strict';
var nconf = require('nconf'); const path = require('path');
var winston = require('winston'); const nconf = require('nconf');
var fs = require('fs'); const winston = require('winston');
var path = require('path'); const _ = require('lodash');
var async = require('async');
var file = require('../file'); const util = require('util');
var db = require('../database'); const fs = require('fs');
var Meta = require('../meta'); const fsReaddir = util.promisify(fs.readdir);
var events = require('../events'); const fsStat = util.promisify(fs.stat);
const fsReadfile = util.promisify(fs.readFile);
var Themes = module.exports;
var themeNamePattern = /^(@.*?\/)?nodebb-theme-.*$/; const file = require('../file');
const db = require('../database');
const Meta = require('../meta');
const events = require('../events');
const utils = require('../../public/src/utils');
Themes.get = function (callback) { const Themes = module.exports;
var themePath = nconf.get('themes_path');
if (typeof themePath !== 'string') {
return callback(null, []);
}
async.waterfall([ const themeNamePattern = /^(@.*?\/)?nodebb-theme-.*$/;
function (next) {
fs.readdir(themePath, next);
},
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);
}
return next(err);
}
if (!stat.isDirectory()) { Themes.get = async () => {
return next(null, null); const themePath = nconf.get('themes_path');
if (typeof themePath !== 'string') {
return [];
} }
if (!dir.startsWith('@')) { let themes = await getThemes(themePath);
return next(null, dir); themes = _.flatten(themes).filter(Boolean);
} themes = await Promise.all(themes.map(async (theme) => {
const config = path.join(themePath, theme, 'theme.json');
try {
const file = await fsReadfile(config, 'utf8');
const configObj = JSON.parse(file);
fs.readdir(dirpath, function (err, themes) { // Minor adjustments for API output
if (err) { configObj.type = 'local';
return next(err); if (configObj.screenshot) {
configObj.screenshot_url = nconf.get('relative_path') + '/css/previews/' + encodeURIComponent(configObj.id);
} else {
configObj.screenshot_url = nconf.get('relative_path') + '/assets/images/themes/default.png';
} }
async.filter(themes.filter(function (theme) { return configObj;
return themeNamePattern.test(theme); } catch (err) {
}), function (theme, next) {
fs.stat(path.join(dirpath, theme), function (err, stat) {
if (err) {
if (err.code === 'ENOENT') { if (err.code === 'ENOENT') {
return next(null, false); return false;
}
return next(err);
} }
next(null, stat.isDirectory()); winston.error('[themes] Unable to parse theme.json ' + theme);
}); return false;
}, 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); return themes.filter(Boolean);
}, []); };
async.map(themes, function (theme, next) { async function getThemes(themePath) {
var config = path.join(themePath, theme, 'theme.json'); let dirs = await fsReaddir(themePath);
dirs = dirs.filter(dir => themeNamePattern.test(dir) || dir.startsWith('@'));
return await Promise.all(dirs.map(async (dir) => {
try {
const dirpath = path.join(themePath, dir);
const stat = await fsStat(dirpath);
if (!stat.isDirectory()) {
return false;
}
fs.readFile(config, 'utf8', function (err, file) { if (!dir.startsWith('@')) {
if (err) { return dir;
if (err.code === 'ENOENT') {
return next(null, null);
} }
return next(err);
const themes = await getThemes(path.join(themePath, dir));
return themes.map(theme => path.join(dir, theme));
} catch (err) {
if (err.code === 'ENOENT') {
return false;
} }
try {
var configObj = JSON.parse(file);
// Minor adjustments for API output throw err;
configObj.type = 'local';
if (configObj.screenshot) {
configObj.screenshot_url = nconf.get('relative_path') + '/css/previews/' + encodeURIComponent(configObj.id);
} else {
configObj.screenshot_url = nconf.get('relative_path') + '/assets/images/themes/default.png';
} }
next(null, configObj); }));
} catch (err) {
winston.error('[themes] Unable to parse theme.json ' + theme);
next(null, null);
} }
});
}, next);
},
function (themes, next) {
themes = themes.filter(Boolean);
next(null, themes);
},
], callback);
};
Themes.set = function (data, callback) { Themes.set = async (data) => {
var themeData = { const themeData = {
'theme:type': data.type, 'theme:type': data.type,
'theme:id': data.id, 'theme:id': data.id,
'theme:staticDir': '', 'theme:staticDir': '',
'theme:templates': '', 'theme:templates': '',
'theme:src': '', 'theme:src': '',
}; };
const current = await Meta.configs.get('theme:id');
let config = await fsReadfile(path.join(nconf.get('themes_path'), data.id, 'theme.json'), 'utf8');
config = JSON.parse(config);
switch (data.type) { switch (data.type) {
case 'local': case 'local':
async.waterfall([ await db.sortedSetRemove('plugins:active', current);
async.apply(Meta.configs.get, 'theme:id'), await db.sortedSetAdd('plugins:active', 0, data.id);
function (current, next) {
async.series([ // Re-set the themes path (for when NodeBB is reloaded)
async.apply(db.sortedSetRemove, 'plugins:active', current), Themes.setPath(config);
async.apply(db.sortedSetAdd, 'plugins:active', 0, data.id),
], function (err) {
next(err);
});
},
function (next) {
fs.readFile(path.join(nconf.get('themes_path'), data.id, 'theme.json'), 'utf8', function (err, config) {
if (!err) {
config = JSON.parse(config);
next(null, config);
} else {
next(err);
}
});
},
function (config, next) {
themeData['theme:staticDir'] = config.staticDir ? config.staticDir : ''; themeData['theme:staticDir'] = config.staticDir ? config.staticDir : '';
themeData['theme:templates'] = config.templates ? config.templates : ''; themeData['theme:templates'] = config.templates ? config.templates : '';
themeData['theme:src'] = ''; themeData['theme:src'] = '';
themeData.bootswatchSkin = ''; themeData.bootswatchSkin = '';
Meta.configs.setMultiple(themeData, next); await Meta.configs.setMultiple(themeData);
await events.log({
// Re-set the themes path (for when NodeBB is reloaded)
Themes.setPath(config);
},
function (next) {
events.log({
type: 'theme-set', type: 'theme-set',
uid: parseInt(data.uid, 10) || 0, uid: parseInt(data.uid, 10) || 0,
ip: data.ip || '127.0.0.1', ip: data.ip || '127.0.0.1',
text: data.id, text: data.id,
}, next); });
},
], callback);
Meta.reloadRequired = true; Meta.reloadRequired = true;
break; break;
case 'bootswatch': case 'bootswatch':
Meta.configs.setMultiple({ await Meta.configs.setMultiple({
'theme:src': data.src, 'theme:src': data.src,
bootswatchSkin: data.id.toLowerCase(), bootswatchSkin: data.id.toLowerCase(),
}, callback); });
break; break;
} }
}; };
Themes.setupPaths = function (callback) { Themes.setupPaths = async () => {
async.waterfall([ const data = await utils.promiseParallel({
function (next) { themesData: Themes.get(),
async.parallel({ currentThemeId: Meta.configs.get('theme:id'),
themesData: Themes.get, });
currentThemeId: function (next) {
Meta.configs.get('theme:id', next);
},
}, next);
},
function (data, next) {
var themeId = data.currentThemeId || 'nodebb-theme-persona'; var themeId = data.currentThemeId || 'nodebb-theme-persona';
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === 'development') {
@ -209,13 +146,10 @@ Themes.setupPaths = function (callback) {
}); });
if (!themeObj) { if (!themeObj) {
return callback(new Error('[[error:theme-not-found]]')); throw new Error('[[error:theme-not-found]]');
} }
Themes.setPath(themeObj); Themes.setPath(themeObj);
next();
},
], callback);
}; };
Themes.setPath = function (themeObj) { Themes.setPath = function (themeObj) {

Loading…
Cancel
Save