You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nodebb/src/admin/search.js

205 lines
5.1 KiB
JavaScript

'use strict';
var fs = require('fs');
var path = require('path');
var async = require('async');
var sanitizeHTML = require('sanitize-html');
var nconf = require('nconf');
var utils = require('../../public/src/utils');
var Translator = require('../../public/src/modules/translator').Translator;
function filterDirectories(directories) {
return directories.map(function (dir) {
// get the relative path
return dir.replace(/^.*(admin.*?).tpl$/, '$1');
}).filter(function (dir) {
// exclude partials
// only include subpaths
// exclude category.tpl, group.tpl, category-analytics.tpl
return !dir.includes('/partials/') &&
/\/.*\//.test(dir) &&
!/manage\/(category|group|category\-analytics)$/.test(dir);
});
}
function getAdminNamespaces(callback) {
utils.walk(path.resolve(nconf.get('views_dir'), 'admin'), function (err, directories) {
if (err) {
return callback(err);
}
callback(null, filterDirectories(directories));
});
}
function sanitize(html) {
// reduce the template to just meaningful text
// remove all tags and strip out scripts, etc completely
return sanitizeHTML(html, {
allowedTags: [],
allowedAttributes: [],
});
}
function simplify(translations) {
return translations
// remove all mustaches
.replace(/(?:\{{1,2}[^\}]*?\}{1,2})/g, '')
// collapse whitespace
.replace(/(?:[ \t]*[\n\r]+[ \t]*)+/g, '\n')
.replace(/[\t ]+/g, ' ');
}
function nsToTitle(namespace) {
return namespace.replace('admin/', '').split('/').map(function (str) {
return str[0].toUpperCase() + str.slice(1);
}).join(' > ').replace(/[^a-zA-Z> ]/g, ' ');
}
var fallbackCacheInProgress = {};
var fallbackCache = {};
function initFallback(namespace, callback) {
fs.readFile(path.resolve(nconf.get('views_dir'), namespace + '.tpl'), function (err, file) {
if (err) {
return callback(err);
}
var template = file.toString();
var title = nsToTitle(namespace);
var translations = sanitize(template);
translations = Translator.removePatterns(translations);
translations = simplify(translations);
translations += '\n' + title;
callback(null, {
namespace: namespace,
translations: translations,
title: title,
});
});
}
function fallback(namespace, callback) {
if (fallbackCache[namespace]) {
return callback(null, fallbackCache[namespace]);
}
if (fallbackCacheInProgress[namespace]) {
return fallbackCacheInProgress[namespace].push(callback);
}
fallbackCacheInProgress[namespace] = [function (err, params) {
if (err) {
return callback(err);
}
callback(null, params);
}];
initFallback(namespace, function (err, params) {
fallbackCacheInProgress[namespace].forEach(function (fn) {
fn(err, params);
});
fallbackCacheInProgress[namespace] = null;
fallbackCache[namespace] = params;
});
}
function initDict(language, callback) {
var translator = Translator.create(language);
getAdminNamespaces(function (err, namespaces) {
if (err) {
return callback(err);
}
async.map(namespaces, function (namespace, cb) {
async.waterfall([
function (next) {
translator.getTranslation(namespace).then(function (translations) {
next(null, translations);
}, next);
},
function (translations, next) {
if (!translations || !Object.keys(translations).length) {
return next(Error('No translations for ' + language + '/' + namespace));
}
// join all translations into one string separated by newlines
var str = Object.keys(translations).map(function (key) {
return translations[key];
}).join('\n');
str = sanitize(str);
var title = namespace;
if (/admin\/general\/dashboard$/.test(title)) {
title = '[[admin/menu:general/dashboard]]';
} else {
title = title.match(/admin\/(.+?)\/(.+?)$/);
title = '[[admin/menu:section-' +
(title[1] === 'development' ? 'advanced' : title[1]) +
']]' + (title[2] ? (' > [[admin/menu:' +
title[1] + '/' + title[2] + ']]') : '');
}
translator.translate(title).then(function (title) {
next(null, {
namespace: namespace,
translations: str + '\n' + title,
title: title,
});
}).catch(err);
},
], function (err, params) {
if (err) {
return fallback(namespace, function (err, params) {
if (err) {
return cb(null, {
namespace: namespace,
translations: '',
});
}
cb(null, params);
});
}
cb(null, params);
});
}, callback);
});
}
var cacheInProgress = {};
var cache = {};
function getDictionary(language, callback) {
if (cache[language]) {
return callback(null, cache[language]);
}
if (cacheInProgress[language]) {
return cacheInProgress[language].push(callback);
}
cacheInProgress[language] = [function (err, params) {
if (err) {
return callback(err);
}
callback(null, params);
}];
initDict(language, function (err, params) {
cacheInProgress[language].forEach(function (fn) {
fn(err, params);
});
cacheInProgress[language] = null;
cache[language] = params;
});
}
module.exports.getDictionary = getDictionary;
module.exports.filterDirectories = filterDirectories;
module.exports.simplify = simplify;
module.exports.sanitize = sanitize;