commit
6b7b51eaf0
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"loading": "Loading Skins...",
|
||||||
|
"homepage": "Homepage",
|
||||||
|
"select-skin": "Select Skin",
|
||||||
|
"current-skin": "Current Skin",
|
||||||
|
"skin-updated": "Skin Updated",
|
||||||
|
"applied-success": "%1 skin was succesfully applied",
|
||||||
|
"revert-success": "Skin reverted to base colours"
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"checking-for-installed": "Checking for installed themes...",
|
||||||
|
"homepage": "Homepage",
|
||||||
|
"select-theme": "Select Theme",
|
||||||
|
"current-theme": "Current Theme",
|
||||||
|
"no-themes": "No installed themes found",
|
||||||
|
"revert-confirm": "Are you sure you wish to restore the default NodeBB theme?",
|
||||||
|
"theme-changed": "Theme Changed",
|
||||||
|
"revert-success": "You have successfully reverted your NodeBB back to it's default theme.",
|
||||||
|
"restart-to-activate": "Please restart your NodeBB to fully activate this theme"
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
#acp-search {
|
||||||
|
.dropdown-menu {
|
||||||
|
max-height: 75vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
> li > a {
|
||||||
|
&.focus {
|
||||||
|
&:extend(.dropdown-menu>li>a:focus);
|
||||||
|
}
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.state-start-typing {
|
||||||
|
.keep-typing, .search-forum, .no-results {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.state-keep-typing {
|
||||||
|
.start-typing, .search-forum, .no-results {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.state-no-results {
|
||||||
|
.keep-typing, .start-typing {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.state-yes-results {
|
||||||
|
.keep-typing, .start-typing, .no-results {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-disabled {
|
||||||
|
.search-forum {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,183 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var fs = require('fs');
|
||||||
|
var path = require('path');
|
||||||
|
var async = require('async');
|
||||||
|
var sanitizeHTML = require('sanitize-html');
|
||||||
|
|
||||||
|
var languages = require('../languages');
|
||||||
|
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) &&
|
||||||
|
!/category|group|category\-analytics$/.test(dir);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAdminNamespaces(callback) {
|
||||||
|
utils.walk(path.resolve(__dirname, '../../public/templates/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(' > ');
|
||||||
|
}
|
||||||
|
|
||||||
|
var fallbackCacheInProgress = {};
|
||||||
|
var fallbackCache = {};
|
||||||
|
|
||||||
|
function initFallback(namespace, callback) {
|
||||||
|
fs.readFile(path.resolve(__dirname, '../../public/templates/', namespace + '.tpl'), function (err, file) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
var template = file.toString();
|
||||||
|
|
||||||
|
var translations = sanitize(template);
|
||||||
|
translations = Translator.removePatterns(translations);
|
||||||
|
translations = simplify(translations);
|
||||||
|
translations += '\n' + nsToTitle(namespace);
|
||||||
|
|
||||||
|
callback(null, {
|
||||||
|
namespace: namespace,
|
||||||
|
translations: translations,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
getAdminNamespaces(function (err, namespaces) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
async.map(namespaces, function (namespace, cb) {
|
||||||
|
async.waterfall([
|
||||||
|
function (next) {
|
||||||
|
languages.get(language, namespace, 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');
|
||||||
|
|
||||||
|
next(null, {
|
||||||
|
namespace: namespace,
|
||||||
|
translations: str,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
], function (err, params) {
|
||||||
|
if (err) {
|
||||||
|
return fallback(namespace, function (err, params) {
|
||||||
|
if (err) {
|
||||||
|
return cb({
|
||||||
|
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;
|
@ -0,0 +1,82 @@
|
|||||||
|
'use strict';
|
||||||
|
/*global require*/
|
||||||
|
|
||||||
|
var assert = require('assert');
|
||||||
|
var search = require('../src/admin/search');
|
||||||
|
|
||||||
|
describe('admin search', function () {
|
||||||
|
describe('filterDirectories', function () {
|
||||||
|
it('should resolve all paths to relative paths', function (done) {
|
||||||
|
assert.deepEqual(search.filterDirectories([
|
||||||
|
'hfjksfd/fdsgagag/admin/gdhgfsdg/sggag.tpl',
|
||||||
|
]), [
|
||||||
|
'admin/gdhgfsdg/sggag',
|
||||||
|
]);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
it('should exclude partials', function (done) {
|
||||||
|
assert.deepEqual(search.filterDirectories([
|
||||||
|
'hfjksfd/fdsgagag/admin/gdhgfsdg/sggag.tpl',
|
||||||
|
'dfahdfsgf/admin/partials/hgkfds/fdhsdfh.tpl',
|
||||||
|
]), [
|
||||||
|
'admin/gdhgfsdg/sggag',
|
||||||
|
]);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
it('should exclude files in the admin directory', function (done) {
|
||||||
|
assert.deepEqual(search.filterDirectories([
|
||||||
|
'hfjksfd/fdsgagag/admin/gdhgfsdg/sggag.tpl',
|
||||||
|
'dfdasg/admin/hjkdfsk.tpl',
|
||||||
|
]), [
|
||||||
|
'admin/gdhgfsdg/sggag',
|
||||||
|
]);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('sanitize', function () {
|
||||||
|
it('should strip out scripts', function (done) {
|
||||||
|
assert.equal(
|
||||||
|
search.sanitize('Pellentesque tristique senectus' +
|
||||||
|
'<script>alert("nope");</script> habitant morbi'),
|
||||||
|
'Pellentesque tristique senectus' +
|
||||||
|
' habitant morbi'
|
||||||
|
);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
it('should remove all tags', function (done) {
|
||||||
|
assert.equal(
|
||||||
|
search.sanitize('<p>Pellentesque <b>habitant morbi</b> tristique senectus' +
|
||||||
|
'Aenean <i>vitae</i> est.Mauris <a href="placerat">eleifend</a> leo.</p>'),
|
||||||
|
'Pellentesque habitant morbi tristique senectus' +
|
||||||
|
'Aenean vitae est.Mauris eleifend leo.'
|
||||||
|
);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('simplify', function () {
|
||||||
|
it('should remove all mustaches', function (done) {
|
||||||
|
assert.equal(
|
||||||
|
search.simplify(
|
||||||
|
'Pellentesque tristique {{senectus}}habitant morbi' +
|
||||||
|
'liquam tincidunt {mauris.eu}risus'
|
||||||
|
),
|
||||||
|
'Pellentesque tristique habitant morbi' +
|
||||||
|
'liquam tincidunt risus'
|
||||||
|
);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
it('should collapse all whitespace', function (done) {
|
||||||
|
assert.equal(
|
||||||
|
search.simplify(
|
||||||
|
'Pellentesque tristique habitant morbi' +
|
||||||
|
' \n\n liquam tincidunt mauris eu risus.'
|
||||||
|
),
|
||||||
|
'Pellentesque tristique habitant morbi' +
|
||||||
|
'\nliquam tincidunt mauris eu risus.'
|
||||||
|
);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue