From e85e35cf0cefc8cdac6ded36d25621a83d88e8b4 Mon Sep 17 00:00:00 2001
From: barisusakli <barisusakli@gmail.com>
Date: Sun, 5 Mar 2017 00:46:11 +0300
Subject: [PATCH] closes #5497

---
 public/src/client/account/settings.js | 31 +++++++++++++++++++++++----
 src/controllers/api.js                |  2 +-
 src/meta/themes.js                    |  7 ++++--
 src/middleware/header.js              | 21 ++++++++++++++----
 src/user/settings.js                  |  2 +-
 test/socket.io.js                     | 11 +++++++---
 6 files changed, 59 insertions(+), 15 deletions(-)

diff --git a/public/src/client/account/settings.js b/public/src/client/account/settings.js
index 537baab5de..eba41faf9c 100644
--- a/public/src/client/account/settings.js
+++ b/public/src/client/account/settings.js
@@ -4,6 +4,12 @@
 define('forum/account/settings', ['forum/account/header', 'components', 'sounds'], function (header, components, sounds) {
 	var	AccountSettings = {};
 
+	$(window).on('action:ajaxify.start', function () {
+		if (ajaxify.data.template.name === 'account/settings' && $('#bootswatchSkin').val() !== config.bootswatchSkin) {
+			changePageSkin(config.bootswatchSkin);
+		}
+	});
+
 	AccountSettings.init = function () {
 		header.init();
 
@@ -24,10 +30,7 @@ define('forum/account/settings', ['forum/account/header', 'components', 'sounds'
 		});
 
 		$('#bootswatchSkin').on('change', function () {
-			var css = $('#bootswatchCSS');
-			var val = $(this).val() === 'default' ? config['theme:src'] : '//maxcdn.bootstrapcdn.com/bootswatch/latest/' + $(this).val() + '/bootstrap.min.css';
-
-			css.attr('href', val);
+			changePageSkin($(this).val());
 		});
 
 		$('[data-property="homePageRoute"]').on('change', toggleCustomRoute);
@@ -44,6 +47,26 @@ define('forum/account/settings', ['forum/account/header', 'components', 'sounds'
 		components.get('user/sessions').find('.timeago').timeago();
 	};
 
+	function changePageSkin(skinName) {
+		var css = $('#bootswatchCSS');
+		if (skinName === 'default') {
+			css.remove();
+		} else {
+			var cssSource = '//maxcdn.bootstrapcdn.com/bootswatch/latest/' + skinName + '/bootstrap.min.css';
+			if (css.length) {
+				css.attr('href', cssSource);
+			} else {
+				css = $('<link id="bootswatchCSS" href="' + cssSource + '" rel="stylesheet" media="screen">');
+				$('head').append(css);
+			}
+		}
+
+		var currentSkinClassName = $('body').attr('class').split(/\s+/).filter(function (className) {
+			return className.startsWith('skin-');
+		});
+		$('body').removeClass(currentSkinClassName.join(' ')).addClass('skin-' + skinName);
+	}
+
 	function loadSettings() {
 		var settings = {};
 
diff --git a/src/controllers/api.js b/src/controllers/api.js
index ab52da69cd..cfa78a6908 100644
--- a/src/controllers/api.js
+++ b/src/controllers/api.js
@@ -61,7 +61,7 @@ apiController.getConfig = function (req, res, next) {
 	config.categoryTopicSort = meta.config.categoryTopicSort || 'newest_to_oldest';
 	config.csrf_token = req.csrfToken();
 	config.searchEnabled = plugins.hasListeners('filter:search.query');
-	config.bootswatchSkin = 'default';
+	config.bootswatchSkin = meta.config.bootswatchSkin || 'default';
 
 	var timeagoCutoff = meta.config.timeagoCutoff === undefined ? 30 : meta.config.timeagoCutoff;
 	config.timeagoCutoff = timeagoCutoff !== '' ? Math.max(0, parseInt(timeagoCutoff, 10)) : timeagoCutoff;
diff --git a/src/meta/themes.js b/src/meta/themes.js
index e950847421..8853c9a086 100644
--- a/src/meta/themes.js
+++ b/src/meta/themes.js
@@ -104,7 +104,7 @@ module.exports = function (Meta) {
 					themeData['theme:templates'] = config.templates ? config.templates : '';
 					themeData['theme:src'] = '';
 
-					db.setObject('config', themeData, next);
+					Meta.configs.setMultiple(themeData, next);
 
 					// Re-set the themes path (for when NodeBB is reloaded)
 					Meta.themes.setPath(config);
@@ -115,7 +115,10 @@ module.exports = function (Meta) {
 			break;
 
 		case 'bootswatch':
-			Meta.configs.set('theme:src', data.src, callback);
+			Meta.configs.setMultiple({
+				'theme:src': data.src,
+				bootswatchSkin: data.id.toLowerCase(),
+			}, callback);
 			break;
 		}
 	};
diff --git a/src/middleware/header.js b/src/middleware/header.js
index accbbf2fe6..44a541f4bf 100644
--- a/src/middleware/header.js
+++ b/src/middleware/header.js
@@ -42,7 +42,6 @@ module.exports = function (middleware) {
 	middleware.renderHeader = function (req, res, data, callback) {
 		var registrationType = meta.config.registrationType || 'normal';
 		var templateValues = {
-			bootswatchCSS: meta.config['theme:src'],
 			title: meta.config.title || '',
 			description: meta.config.description || '',
 			'cache-buster': meta.config['cache-buster'] || '',
@@ -117,9 +116,7 @@ module.exports = function (middleware) {
 				results.user['email:confirmed'] = parseInt(results.user['email:confirmed'], 10) === 1;
 				results.user.isEmailConfirmSent = !!results.isEmailConfirmSent;
 
-				if (res.locals.config && parseInt(meta.config.disableCustomUserSkins, 10) !== 1 && res.locals.config.bootswatchSkin !== 'default') {
-					templateValues.bootswatchCSS = '//maxcdn.bootstrapcdn.com/bootswatch/latest/' + res.locals.config.bootswatchSkin + '/bootstrap.min.css';
-				}
+				setBootswatchCSS(templateValues, res.locals.config);
 
 				templateValues.browserTitle = controllers.helpers.buildTitle(data.title);
 				templateValues.navigation = results.navigation;
@@ -191,5 +188,21 @@ module.exports = function (middleware) {
 
 		return title;
 	}
+
+	function setBootswatchCSS(obj, config) {
+		if (config && config.bootswatchSkin !== 'default') {
+			var skinToUse = '';
+
+			if (parseInt(meta.config.disableCustomUserSkins, 10) !== 1) {
+				skinToUse = config.bootswatchSkin;
+			} else if (meta.config.bootswatchSkin !== 'default') {
+				skinToUse = meta.config.bootswatchSkin;
+			}
+
+			if (skinToUse) {
+				obj.bootswatchCSS = '//maxcdn.bootstrapcdn.com/bootswatch/latest/' + skinToUse + '/bootstrap.min.css';
+			}
+		}
+	}
 };
 
diff --git a/src/user/settings.js b/src/user/settings.js
index 870dd2e59c..bef20e0087 100644
--- a/src/user/settings.js
+++ b/src/user/settings.js
@@ -74,7 +74,7 @@ module.exports = function (User) {
 			settings.restrictChat = parseInt(getSetting(settings, 'restrictChat', 0), 10) === 1;
 			settings.topicSearchEnabled = parseInt(getSetting(settings, 'topicSearchEnabled', 0), 10) === 1;
 			settings.delayImageLoading = parseInt(getSetting(settings, 'delayImageLoading', 1), 10) === 1;
-			settings.bootswatchSkin = settings.bootswatchSkin || 'default';
+			settings.bootswatchSkin = settings.bootswatchSkin || meta.config.bootswatchSkin || 'default';
 			settings.scrollToMyPost = parseInt(getSetting(settings, 'scrollToMyPost', 1), 10) === 1;
 
 			callback(null, settings);
diff --git a/test/socket.io.js b/test/socket.io.js
index 095c3b5440..497f0ed922 100644
--- a/test/socket.io.js
+++ b/test/socket.io.js
@@ -427,11 +427,16 @@ describe('socket.io', function () {
 	});
 
 	it('should set theme to bootswatch', function (done) {
-		socketAdmin.themes.set({ uid: adminUid }, { type: 'bootswatch', src: 'darkly' }, function (err) {
+		socketAdmin.themes.set({ uid: adminUid }, {
+			type: 'bootswatch',
+			src: '//maxcdn.bootstrapcdn.com/bootswatch/latest/darkly/bootstrap.min.css',
+			id: 'darkly',
+		}, function (err) {
 			assert.ifError(err);
-			meta.configs.get('theme:src', function (err, id) {
+			meta.configs.getFields(['theme:src', 'bootswatchSkin'], function (err, fields) {
 				assert.ifError(err);
-				assert.equal(id, 'darkly');
+				assert.equal(fields['theme:src'], '//maxcdn.bootstrapcdn.com/bootswatch/latest/darkly/bootstrap.min.css');
+				assert.equal(fields.bootswatchSkin, 'darkly');
 				done();
 			});
 		});