From a431dc030587f47760e005d30576176d0e1f1163 Mon Sep 17 00:00:00 2001 From: Peter Jaszkowiak
' +
+ results +
+ '
' +
+ '' +
+ '...' + description + '...
Pellentesque habitant morbi tristique senectus' + + 'Aenean vitae est.Mauris eleifend leo.
'), + '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(); + }); + }); +}); \ No newline at end of file diff --git a/test/translator.js b/test/translator.js index 9df035cfde..91fbf8f696 100644 --- a/test/translator.js +++ b/test/translator.js @@ -233,3 +233,15 @@ describe('Translator modules', function () { done(); }); }); + +describe('Translator static methods', function () { + describe('.removePatterns', function () { + it('should remove translator patterns from text', function (done) { + assert.strictEqual( + Translator.removePatterns('Lorem ipsum dolor [[sit:amet]], consectetur adipiscing elit. [[sed:vitae, [[semper:dolor]]]] lorem'), + 'Lorem ipsum dolor , consectetur adipiscing elit. lorem' + ); + done(); + }); + }); +}); From 240e958fb1a6a7e7a7baa1507bf786752608d256 Mon Sep 17 00:00:00 2001 From: Peter Jaszkowiak' +
@@ -68,7 +68,7 @@ define(function () {
$('#acp-search').parents('form').on('submit', function (ev) {
var firstResult = menu.find('li:first-child > a').attr('href');
- var href = firstResult ? firstResult : RELATIVE_PATH + '/search/' + input.val();
+ var href = firstResult ? firstResult : config.relative_path + '/search/' + input.val();
ajaxify.go(href.replace(/^\//, ''));
@@ -102,7 +102,7 @@ define(function () {
menu.find('.search-forum')
.not('.divider')
.find('a')
- .attr('href', RELATIVE_PATH + '/search/' + value)
+ .attr('href', config.relative_path + '/search/' + value)
.find('strong')
.html(value);
} else {
From fd4d53e42cd7d40c8276b4acbc5153a114361ea4 Mon Sep 17 00:00:00 2001
From: Peter Jaszkowiak
Date: Mon, 28 Nov 2016 13:05:22 -0700
Subject: [PATCH 05/11] Remove unnecesary admin search indexing
---
src/meta/templates.js | 23 ++---------------------
1 file changed, 2 insertions(+), 21 deletions(-)
diff --git a/src/meta/templates.js b/src/meta/templates.js
index 4e7f934624..fd5c1f71e1 100644
--- a/src/meta/templates.js
+++ b/src/meta/templates.js
@@ -12,7 +12,6 @@ var plugins = require('../plugins');
var utils = require('../../public/src/utils');
var Templates = {};
-var searchIndex = {};
Templates.compile = function (callback) {
callback = callback || function () {};
@@ -131,10 +130,6 @@ function compile(callback) {
}
}
- if (relativePath.match(/^\/admin\/[\s\S]*?/)) {
- addIndex(relativePath, file);
- }
-
mkdirp.sync(path.join(viewsPath, relativePath.split('/').slice(0, -1).join('/')));
fs.writeFile(path.join(viewsPath, relativePath), file, next);
}, function (err) {
@@ -143,25 +138,11 @@ function compile(callback) {
return callback(err);
}
- compileIndex(viewsPath, function (err) {
- if (err) {
- return callback(err);
- }
- winston.verbose('[meta/templates] Successfully compiled templates.');
+ winston.verbose('[meta/templates] Successfully compiled templates.');
- callback();
- });
+ callback();
});
});
}
-
-function addIndex(path, file) {
- searchIndex[path] = file;
-}
-
-function compileIndex(viewsPath, callback) {
- fs.writeFile(path.join(viewsPath, '/indexed.json'), JSON.stringify(searchIndex), callback);
-}
-
module.exports = Templates;
\ No newline at end of file
From 77e58f31c5e8dc005e808dcf1101adc66a697b95 Mon Sep 17 00:00:00 2001
From: Peter Jaszkowiak
Date: Mon, 28 Nov 2016 18:16:13 -0700
Subject: [PATCH 06/11] Fixes, passes tests
---
.../{en_GB => en-GB}/admin/appearance/themes.json | 0
src/admin/search.js | 10 +++++-----
src/languages.js | 2 +-
src/socket.io/admin.js | 2 +-
4 files changed, 7 insertions(+), 7 deletions(-)
rename public/language/{en_GB => en-GB}/admin/appearance/themes.json (100%)
diff --git a/public/language/en_GB/admin/appearance/themes.json b/public/language/en-GB/admin/appearance/themes.json
similarity index 100%
rename from public/language/en_GB/admin/appearance/themes.json
rename to public/language/en-GB/admin/appearance/themes.json
diff --git a/src/admin/search.js b/src/admin/search.js
index 76f127a4d8..940c202cf4 100644
--- a/src/admin/search.js
+++ b/src/admin/search.js
@@ -6,7 +6,7 @@ var sanitizeHTML = require('sanitize-html');
var languages = require('../languages');
var utils = require('../../public/src/utils');
-var Translator = require('../../public/src/modules/translator');
+var Translator = require('../../public/src/modules/translator').Translator;
function walk(directory) {
return new Promise(function (resolve, reject) {
@@ -32,9 +32,9 @@ function readFile(path) {
});
}
-function loadLanguage(language, filename) {
+function loadLanguage(language, namespace) {
return new Promise(function (resolve, reject) {
- languages.get(language, filename + '.json', function (err, data) {
+ languages.get(language, namespace, function (err, data) {
if (err || !data || !Object.keys(data).length) {
reject(err);
} else {
@@ -56,7 +56,7 @@ function filterDirectories(directories) {
}
function getAdminNamespaces() {
- return walk(path.resolve('./public/templates/admin'))
+ return walk(path.resolve(__dirname, '../../public/templates/admin'))
.then(filterDirectories);
}
@@ -81,7 +81,7 @@ function simplify(translations) {
var fallbackCache = {};
function initFallback(namespace) {
- return readFile(path.resolve('./public/templates/', namespace + '.tpl'))
+ return readFile(path.resolve(__dirname, '../../public/templates/', namespace + '.tpl'))
.then(function (template) {
var translations = sanitize(template);
translations = simplify(translations);
diff --git a/src/languages.js b/src/languages.js
index 86563628b2..f3b9aa5743 100644
--- a/src/languages.js
+++ b/src/languages.js
@@ -75,7 +75,7 @@ Languages.list = function (callback) {
fs.readFile(configPath, function (err, stream) {
if (err) {
- next();
+ return next(err);
}
languages.push(JSON.parse(stream.toString()));
next();
diff --git a/src/socket.io/admin.js b/src/socket.io/admin.js
index 9817099ef5..00d90067f7 100644
--- a/src/socket.io/admin.js
+++ b/src/socket.io/admin.js
@@ -283,7 +283,7 @@ SocketAdmin.getSearchDict = function (socket, data, callback) {
if (err) {
return callback(err);
}
- var lang = settings.userLang || meta.config.defaultLang || 'en_GB';
+ var lang = settings.userLang || meta.config.defaultLang || 'en-GB';
getAdminSearchDict(lang)
.then(function (results) {
callback(null, results);
From 9fd64549a3dc5e2ef32862a5e77a94a60bc3c778 Mon Sep 17 00:00:00 2001
From: Peter Jaszkowiak
Date: Mon, 5 Dec 2016 17:55:04 -0700
Subject: [PATCH 07/11] Use async instead of Promises
---
src/admin/search.js | 200 ++++++++++++++++++++++++-----------------
src/socket.io/admin.js | 7 +-
test/search-admin.js | 4 +-
3 files changed, 123 insertions(+), 88 deletions(-)
diff --git a/src/admin/search.js b/src/admin/search.js
index 940c202cf4..8f567071bf 100644
--- a/src/admin/search.js
+++ b/src/admin/search.js
@@ -2,48 +2,13 @@
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 walk(directory) {
- return new Promise(function (resolve, reject) {
- utils.walk(directory, function (err, data) {
- if (err) {
- reject(err);
- } else {
- resolve(data);
- }
- });
- });
-}
-
-function readFile(path) {
- return new Promise(function (resolve, reject) {
- fs.readFile(path, function (err, data) {
- if (err) {
- reject(err);
- } else {
- resolve(data.toString());
- }
- });
- });
-}
-
-function loadLanguage(language, namespace) {
- return new Promise(function (resolve, reject) {
- languages.get(language, namespace, function (err, data) {
- if (err || !data || !Object.keys(data).length) {
- reject(err);
- } else {
- resolve(data);
- }
- });
- });
-}
-
function filterDirectories(directories) {
return directories.map(function (dir) {
// get the relative path
@@ -51,13 +16,21 @@ function filterDirectories(directories) {
}).filter(function (dir) {
// exclude partials
// only include subpaths
- return !dir.includes('/partials/') && /\/.*\//.test(dir);
+ // exclude category.tpl, group.tpl, category-analytics.tpl
+ return !dir.includes('/partials/') &&
+ /\/.*\//.test(dir) &&
+ !/category|group|category\-analytics$/.test(dir);
});
}
-function getAdminNamespaces() {
- return walk(path.resolve(__dirname, '../../public/templates/admin'))
- .then(filterDirectories);
+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) {
@@ -78,68 +51,133 @@ function simplify(translations) {
.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) {
- return readFile(path.resolve(__dirname, '../../public/templates/', namespace + '.tpl'))
- .then(function (template) {
- var translations = sanitize(template);
- translations = simplify(translations);
- translations = Translator.removePatterns(translations);
-
- return {
- namespace: namespace,
- translations: translations,
- };
+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) {
- // use cache if exists, else make it
- fallbackCache[namespace] = fallbackCache[namespace] || initFallback(namespace);
- return fallbackCache[namespace];
+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) {
- return getAdminNamespaces().then(function (namespaces) {
- return Promise.all(namespaces.map(function (namespace) {
- return loadLanguage(language, namespace)
- .then(function (translations) {
+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');
- return {
+ next(null, {
namespace: namespace,
translations: str,
- };
- })
- // TODO: Use translator to get title for admin route?
- .catch(function () {
- // no translations for this route, fallback to template
- return fallback(namespace);
- })
- .catch(function () {
- // no fallback, just return blank
- return {
- namespace: namespace,
- translations: '',
- };
- });
- }));
+ });
+ }
+ ], 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 getDict(language) {
- // use cache if exists, else make it
- cache[language] = cache[language] || initDict(language);
- return cache[language];
+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.getDict = getDict;
+module.exports.getDictionary = getDictionary;
module.exports.filterDirectories = filterDirectories;
module.exports.simplify = simplify;
module.exports.sanitize = sanitize;
diff --git a/src/socket.io/admin.js b/src/socket.io/admin.js
index 00d90067f7..141d567f11 100644
--- a/src/socket.io/admin.js
+++ b/src/socket.io/admin.js
@@ -15,7 +15,7 @@ var emailer = require('../emailer');
var db = require('../database');
var analytics = require('../analytics');
var index = require('./index');
-var getAdminSearchDict = require('../admin/search').getDict;
+var getAdminSearchDict = require('../admin/search').getDictionary;
var SocketAdmin = {
user: require('./admin/user'),
@@ -284,10 +284,7 @@ SocketAdmin.getSearchDict = function (socket, data, callback) {
return callback(err);
}
var lang = settings.userLang || meta.config.defaultLang || 'en-GB';
- getAdminSearchDict(lang)
- .then(function (results) {
- callback(null, results);
- }, callback);
+ getAdminSearchDict(lang, callback);
});
};
diff --git a/test/search-admin.js b/test/search-admin.js
index 5351018057..216d26d35f 100644
--- a/test/search-admin.js
+++ b/test/search-admin.js
@@ -2,7 +2,7 @@
/*global require*/
var assert = require('assert');
-var search = require('../src/admin/search.js');
+var search = require('../src/admin/search');
describe('admin search', function () {
describe('filterDirectories', function () {
@@ -60,7 +60,7 @@ describe('admin search', function () {
assert.equal(
search.simplify(
'Pellentesque tristique {{senectus}}habitant morbi' +
- 'liquam tincidunt {{mauris.eu}}risus'
+ 'liquam tincidunt {mauris.eu}risus'
),
'Pellentesque tristique habitant morbi' +
'liquam tincidunt risus'
From 5843e8dd77f761e2495fb914c78586f5e58acdc8 Mon Sep 17 00:00:00 2001
From: Peter Jaszkowiak
Date: Mon, 5 Dec 2016 17:55:42 -0700
Subject: [PATCH 08/11] Fix Translator to work with namespace paths
---
public/src/modules/translator.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/public/src/modules/translator.js b/public/src/modules/translator.js
index d0d1b90251..a876dba44c 100644
--- a/public/src/modules/translator.js
+++ b/public/src/modules/translator.js
@@ -3,7 +3,7 @@
(function (factory) {
'use strict';
function loadClient(language, namespace) {
- return Promise.resolve(jQuery.getJSON(config.relative_path + '/api/language/' + language + '/' + namespace));
+ return Promise.resolve(jQuery.getJSON(config.relative_path + '/api/language/' + language + '/' + encodeURIComponent(namespace)));
}
if (typeof define === 'function' && define.amd) {
// AMD. Register as a named module
From f1cfed50a1f750bd4871d7263615e0a85ee5a9c3 Mon Sep 17 00:00:00 2001
From: Peter Jaszkowiak
Date: Mon, 5 Dec 2016 18:31:58 -0700
Subject: [PATCH 09/11] Translate skins and themes fully
---
.../en-GB/admin/appearance/skins.json | 9 ++
.../en-GB/admin/appearance/themes.json | 9 +-
public/src/admin/appearance/skins.js | 62 +++++++------
public/src/admin/appearance/themes.js | 90 ++++++++++---------
src/views/admin/appearance/skins.tpl | 2 +-
src/views/admin/partials/theme_list.tpl | 2 +-
6 files changed, 102 insertions(+), 72 deletions(-)
create mode 100644 public/language/en-GB/admin/appearance/skins.json
diff --git a/public/language/en-GB/admin/appearance/skins.json b/public/language/en-GB/admin/appearance/skins.json
new file mode 100644
index 0000000000..4db6fbdd8a
--- /dev/null
+++ b/public/language/en-GB/admin/appearance/skins.json
@@ -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"
+}
\ No newline at end of file
diff --git a/public/language/en-GB/admin/appearance/themes.json b/public/language/en-GB/admin/appearance/themes.json
index 3f1b10cf7f..3148a01337 100644
--- a/public/language/en-GB/admin/appearance/themes.json
+++ b/public/language/en-GB/admin/appearance/themes.json
@@ -1,6 +1,11 @@
{
"checking-for-installed": "Checking for installed themes...",
"homepage": "Homepage",
- "select-skin": "Select Skin",
- "select-theme": "Select Theme"
+ "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"
}
\ No newline at end of file
diff --git a/public/src/admin/appearance/skins.js b/public/src/admin/appearance/skins.js
index 7583dc952d..35ad5a2289 100644
--- a/public/src/admin/appearance/skins.js
+++ b/public/src/admin/appearance/skins.js
@@ -1,7 +1,7 @@
"use strict";
/* global define, app, socket, templates */
-define('admin/appearance/skins', function () {
+define('admin/appearance/skins', ['translator'], function (translator) {
var Skins = {};
Skins.init = function () {
@@ -40,8 +40,8 @@ define('admin/appearance/skins', function () {
app.alert({
alert_id: 'admin:theme',
type: 'info',
- title: 'Skin Updated',
- message: themeId ? (themeId + ' skin was successfully applied') : 'Skin reverted to base colours',
+ title: '[[admin/appearance/skins:skin-updated]]',
+ message: themeId ? ('[[admin/appearance/skins:applied-success, ' + themeId + ']]') : '[[admin/appearance/skins:revert-success]]',
timeout: 5000
});
});
@@ -67,40 +67,48 @@ define('admin/appearance/skins', function () {
}),
showRevert: true
}, function (html) {
- themeContainer.html(html);
+ translator.translate(html, function (html) {
+ themeContainer.html(html);
- if (config['theme:src']) {
- var skin = config['theme:src']
+ if (config['theme:src']) {
+ var skin = config['theme:src']
.match(/latest\/(\S+)\/bootstrap.min.css/)[1]
.replace(/(^|\s)([a-z])/g , function (m,p1,p2) {return p1 + p2.toUpperCase();});
- highlightSelectedTheme(skin);
- }
+ highlightSelectedTheme(skin);
+ }
+ });
});
};
function highlightSelectedTheme(themeId) {
- $('[data-theme]')
- .removeClass('selected')
- .find('[data-action="use"]').each(function () {
- if ($(this).parents('[data-theme]').attr('data-theme')) {
- $(this)
- .html('Select Skin')
- .removeClass('btn-success')
- .addClass('btn-primary');
- }
- });
+ translator.translate('[[admin/appearance/skins:select-skin]] || [[admin/appearance/skins:current-skin]]', function (text) {
+ text = text.split(' || ');
+ var select = text[0];
+ var current = text[1];
+
+ $('[data-theme]')
+ .removeClass('selected')
+ .find('[data-action="use"]').each(function () {
+ if ($(this).parents('[data-theme]').attr('data-theme')) {
+ $(this)
+ .html(select)
+ .removeClass('btn-success')
+ .addClass('btn-primary');
+ }
+ });
- if (!themeId) {
- return;
- }
+ if (!themeId) {
+ return;
+ }
- $('[data-theme="' + themeId + '"]')
- .addClass('selected')
- .find('[data-action="use"]')
- .html('Current Skin')
- .removeClass('btn-primary')
- .addClass('btn-success');
+ $('[data-theme="' + themeId + '"]')
+ .addClass('selected')
+ .find('[data-action="use"]')
+ .html(current)
+ .removeClass('btn-primary')
+ .addClass('btn-success');
+ });
}
return Skins;
diff --git a/public/src/admin/appearance/themes.js b/public/src/admin/appearance/themes.js
index 835dcda654..0c71baa4c4 100644
--- a/public/src/admin/appearance/themes.js
+++ b/public/src/admin/appearance/themes.js
@@ -1,7 +1,7 @@
"use strict";
/* global define, app, socket, bootbox, templates, config */
-define('admin/appearance/themes', function () {
+define('admin/appearance/themes', ['translator'], function (translator) {
var Themes = {};
Themes.init = function () {
@@ -28,8 +28,8 @@ define('admin/appearance/themes', function () {
app.alert({
alert_id: 'admin:theme',
type: 'info',
- title: 'Theme Changed',
- message: 'Please restart your NodeBB to fully activate this theme',
+ title: '[[admin/appearance/themes:theme-changed]]',
+ message: '[[admin/appearance/themes:restart-to-activate]]',
timeout: 5000,
clickfn: function () {
socket.emit('admin.restart');
@@ -38,27 +38,29 @@ define('admin/appearance/themes', function () {
});
}
});
-
- $('#revert_theme').on('click', function () {
- bootbox.confirm('Are you sure you wish to restore the default NodeBB theme?', function (confirm) {
- if (confirm) {
- socket.emit('admin.themes.set', {
- type: 'local',
- id: 'nodebb-theme-persona'
- }, function (err) {
- if (err) {
- return app.alertError(err.message);
- }
- highlightSelectedTheme('nodebb-theme-persona');
- app.alert({
- alert_id: 'admin:theme',
- type: 'success',
- title: 'Theme Changed',
- message: 'You have successfully reverted your NodeBB back to it\'s default theme.',
- timeout: 3500
+
+ translator.translate('[[admin/appearance/themes:revert-confirm]]', function (revert) {
+ $('#revert_theme').on('click', function () {
+ bootbox.confirm(revert, function (confirm) {
+ if (confirm) {
+ socket.emit('admin.themes.set', {
+ type: 'local',
+ id: 'nodebb-theme-persona'
+ }, function (err) {
+ if (err) {
+ return app.alertError(err.message);
+ }
+ highlightSelectedTheme('nodebb-theme-persona');
+ app.alert({
+ alert_id: 'admin:theme',
+ type: 'success',
+ title: '[[admin/appearance/themes:theme-changed]]',
+ message: '[[admin/appearance/themes:revert-success]]',
+ timeout: 3500
+ });
});
- });
- }
+ }
+ });
});
});
@@ -70,17 +72,17 @@ define('admin/appearance/themes', function () {
var instListEl = $('#installed_themes');
if (!themes.length) {
- instListEl.append($('').addClass('no-themes').html('No installed themes found'));
+ translator.translate('[[admin/appearance/themes:no-themes]]', function (text) {
+ instListEl.append($(' ').addClass('no-themes').html(text));
+ });
return;
} else {
templates.parse('admin/partials/theme_list', {
themes: themes
}, function (html) {
- require(['translator'], function (translator) {
- translator.translate(html, function (html) {
- instListEl.html(html);
- highlightSelectedTheme(config['theme:id']);
- });
+ translator.translate(html, function (html) {
+ instListEl.html(html);
+ highlightSelectedTheme(config['theme:id']);
});
});
}
@@ -88,19 +90,25 @@ define('admin/appearance/themes', function () {
};
function highlightSelectedTheme(themeId) {
- $('[data-theme]')
- .removeClass('selected')
- .find('[data-action="use"]')
- .html('Select Theme')
- .removeClass('btn-success')
- .addClass('btn-primary');
+ translator.translate('[[admin/appearance/themes:select-theme]] || [[admin/appearance/themes:current-theme]]', function (text) {
+ text = text.split(' || ');
+ var select = text[0];
+ var current = text[1];
- $('[data-theme="' + themeId + '"]')
- .addClass('selected')
- .find('[data-action="use"]')
- .html('Current Theme')
- .removeClass('btn-primary')
- .addClass('btn-success');
+ $('[data-theme]')
+ .removeClass('selected')
+ .find('[data-action="use"]')
+ .html(select)
+ .removeClass('btn-success')
+ .addClass('btn-primary');
+
+ $('[data-theme="' + themeId + '"]')
+ .addClass('selected')
+ .find('[data-action="use"]')
+ .html(current)
+ .removeClass('btn-primary')
+ .addClass('btn-success');
+ });
}
return Themes;
diff --git a/src/views/admin/appearance/skins.tpl b/src/views/admin/appearance/skins.tpl
index 0c1b543a47..c5d1355f08 100644
--- a/src/views/admin/appearance/skins.tpl
+++ b/src/views/admin/appearance/skins.tpl
@@ -1,6 +1,6 @@
- Loading Skins
+ [[admin/appearance/skins:loading]]
diff --git a/src/views/admin/partials/theme_list.tpl b/src/views/admin/partials/theme_list.tpl
index 066f43cdd8..26a226b803 100644
--- a/src/views/admin/partials/theme_list.tpl
+++ b/src/views/admin/partials/theme_list.tpl
@@ -16,7 +16,7 @@
From 8ca98625b9eb4d993fbdfb5d0b7c6c935a4f8727 Mon Sep 17 00:00:00 2001
From: Peter Jaszkowiak
Date: Mon, 5 Dec 2016 18:32:17 -0700
Subject: [PATCH 10/11] Key through search results
---
public/less/admin/modules/search.less | 9 ++++++
public/src/admin/modules/search.js | 44 ++++++++++++++++++++++++---
2 files changed, 49 insertions(+), 4 deletions(-)
diff --git a/public/less/admin/modules/search.less b/public/less/admin/modules/search.less
index 381b940646..d2286005bf 100644
--- a/public/less/admin/modules/search.less
+++ b/public/less/admin/modules/search.less
@@ -2,6 +2,15 @@
.dropdown-menu {
max-height: 75vh;
overflow-y: auto;
+
+ > li > a {
+ &.focus {
+ &:extend(.dropdown-menu>li>a:focus);
+ }
+ &:focus {
+ outline: none;
+ }
+ }
}
.state-start-typing {
diff --git a/public/src/admin/modules/search.js b/public/src/admin/modules/search.js
index 5eb2f336a2..d4ac16f1d8 100644
--- a/public/src/admin/modules/search.js
+++ b/public/src/admin/modules/search.js
@@ -1,7 +1,7 @@
"use strict";
/* globals socket, app, define, ajaxify, config */
-define(function () {
+define(['mousetrap'], function (mousetrap) {
var search = {};
function nsToTitle(namespace) {
@@ -67,8 +67,11 @@ define(function () {
});
$('#acp-search').parents('form').on('submit', function (ev) {
- var firstResult = menu.find('li:first-child > a').attr('href');
- var href = firstResult ? firstResult : config.relative_path + '/search/' + input.val();
+ var selected = menu.find('li.result > a.focus').attr('href');
+ if (!selected.length) {
+ selected = menu.find('li.result > a').first().attr('href');
+ }
+ var href = selected ? selected : config.relative_path + '/search/' + input.val();
ajaxify.go(href.replace(/^\//, ''));
@@ -81,8 +84,41 @@ define(function () {
return false;
});
+ mousetrap(input[0]).bind(['up', 'down'], function (ev, key) {
+ var next;
+ if (key === 'up') {
+ next = menu.find('li.result > a.focus').removeClass('focus').parent().prev('.result').children();
+ if (!next.length) {
+ next = menu.find('li.result > a').last();
+ }
+ next.addClass('focus');
+ if (menu[0].getBoundingClientRect().top > next[0].getBoundingClientRect().top) {
+ next[0].scrollIntoView(true);
+ }
+ } else if (key === 'down') {
+ next = menu.find('li.result > a.focus').removeClass('focus').parent().next('.result').children();
+ if (!next.length) {
+ next = menu.find('li.result > a').first();
+ }
+ next.addClass('focus');
+ if (menu[0].getBoundingClientRect().bottom < next[0].getBoundingClientRect().bottom) {
+ next[0].scrollIntoView(false);
+ }
+ }
+
+ ev.preventDefault();
+ });
+
+ var prevValue;
+
input.on('keyup focus', function () {
var value = input.val().toLowerCase();
+
+ if (value === prevValue) {
+ return;
+ }
+ prevValue = value;
+
menu.children('.result').remove();
var len = value.length;
@@ -106,7 +142,7 @@ define(function () {
.find('strong')
.html(value);
} else {
- menu.removeClass('state-no-results');
+ menu.removeClass('state-no-results state-yes-results');
}
});
}
From e2ea3cb21e6635bb2435371ed8d41868946eb49e Mon Sep 17 00:00:00 2001
From: Peter Jaszkowiak
Date: Tue, 6 Dec 2016 15:13:34 -0700
Subject: [PATCH 11/11] Fix linting error
---
public/src/admin/modules/search.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/public/src/admin/modules/search.js b/public/src/admin/modules/search.js
index d4ac16f1d8..4f46898b8e 100644
--- a/public/src/admin/modules/search.js
+++ b/public/src/admin/modules/search.js
@@ -1,7 +1,7 @@
"use strict";
/* globals socket, app, define, ajaxify, config */
-define(['mousetrap'], function (mousetrap) {
+define('admin/modules/search', ['mousetrap'], function (mousetrap) {
var search = {};
function nsToTitle(namespace) {