diff --git a/app/assets/javascripts/discourse/controllers/preferences/interface.js.es6 b/app/assets/javascripts/discourse/controllers/preferences/interface.js.es6
index c52e4c5e839..4c5d920fbac 100644
--- a/app/assets/javascripts/discourse/controllers/preferences/interface.js.es6
+++ b/app/assets/javascripts/discourse/controllers/preferences/interface.js.es6
@@ -1,21 +1,30 @@
import PreferencesTabController from "discourse/mixins/preferences-tab-controller";
import { default as computed, observes } from "ember-addons/ember-computed-decorators";
-import { listThemes, previewTheme } from 'discourse/lib/theme-selector';
+import { listThemes, previewTheme, setLocalTheme } from 'discourse/lib/theme-selector';
import { popupAjaxError } from 'discourse/lib/ajax-error';
export default Ember.Controller.extend(PreferencesTabController, {
- saveAttrNames: [
- 'locale',
- 'external_links_in_new_tab',
- 'dynamic_favicon',
- 'enable_quoting',
- 'disable_jump_reply',
- 'automatically_unpin_topics',
- 'theme_key'
- ],
+ @computed("makeThemeDefault")
+ saveAttrNames(makeDefault) {
+ let attrs = [
+ 'locale',
+ 'external_links_in_new_tab',
+ 'dynamic_favicon',
+ 'enable_quoting',
+ 'disable_jump_reply',
+ 'automatically_unpin_topics'
+ ];
+
+ if (makeDefault) {
+ attrs.push('theme_key');
+ }
+
+ return attrs;
+ },
preferencesController: Ember.inject.controller('preferences'),
+ makeThemeDefault: true,
@computed()
availableLocales() {
@@ -40,8 +49,15 @@ export default Ember.Controller.extend(PreferencesTabController, {
actions: {
save() {
this.set('saved', false);
+ const makeThemeDefault = this.get("makeThemeDefault");
+
return this.get('model').save(this.get('saveAttrNames')).then(() => {
this.set('saved', true);
+
+ if (!makeThemeDefault) {
+ setLocalTheme(this.get('model.user_option.theme_key'), this.get('model.user_option.theme_key_seq'));
+ }
+
}).catch(popupAjaxError);
}
}
diff --git a/app/assets/javascripts/discourse/lib/theme-selector.js.es6 b/app/assets/javascripts/discourse/lib/theme-selector.js.es6
index af0f8da3903..463e0189bbb 100644
--- a/app/assets/javascripts/discourse/lib/theme-selector.js.es6
+++ b/app/assets/javascripts/discourse/lib/theme-selector.js.es6
@@ -13,6 +13,14 @@ export function currentThemeKey() {
return themeKey;
}
+export function setLocalTheme(key, themeSeq) {
+ if (key) {
+ $.cookie('theme_key', `${key},${themeSeq}`, {path: '/', expires: 9999});
+ } else {
+ $.cookie('theme_key', null, {path: '/', expires: 1});
+ }
+}
+
export function refreshCSS(node, hash, newHref, options) {
let $orig = $(node);
diff --git a/app/assets/javascripts/discourse/templates/preferences/interface.hbs b/app/assets/javascripts/discourse/templates/preferences/interface.hbs
index b8266e906bf..ba9e1a90d59 100644
--- a/app/assets/javascripts/discourse/templates/preferences/interface.hbs
+++ b/app/assets/javascripts/discourse/templates/preferences/interface.hbs
@@ -4,6 +4,9 @@
{{combo-box content=userSelectableThemes value=model.user_option.theme_key}}
+
+ {{preference-checkbox labelKey="user.theme_default_on_all_devices" checked=makeThemeDefault}}
+
{{/if}}
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 0cd39cd3a57..f1e12606f13 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -267,13 +267,19 @@ class ApplicationController < ActionController::Base
resolve_safe_mode
return if request.env[NO_CUSTOM]
- theme_key = flash[:preview_theme_key] || current_user&.user_option&.theme_key
+ theme_key = flash[:preview_theme_key]
- # TODO 2018: delete this, old cookie cleanup code
- if cookies[:theme_key]
- cookies.delete(:theme_key)
+ user_option = current_user&.user_option
+
+ unless theme_key
+ key, seq = cookies[:theme_key]&.split(",")
+ if key && seq && seq.to_i == user_option&.theme_key_seq
+ theme_key = key
+ end
end
+ theme_key ||= user_option&.theme_key
+
if theme_key && !guardian.allow_theme?(theme_key)
theme_key = nil
end
diff --git a/app/models/user_option.rb b/app/models/user_option.rb
index 62480aca83a..551380335dd 100644
--- a/app/models/user_option.rb
+++ b/app/models/user_option.rb
@@ -158,6 +158,7 @@ end
# include_tl0_in_digests :boolean default(FALSE)
# notification_level_when_replying :integer
# theme_key :string
+# theme_key_seq :integer default(0), not null
#
# Indexes
#
diff --git a/app/serializers/user_option_serializer.rb b/app/serializers/user_option_serializer.rb
index 534913323b6..a6a7caeb236 100644
--- a/app/serializers/user_option_serializer.rb
+++ b/app/serializers/user_option_serializer.rb
@@ -19,7 +19,8 @@ class UserOptionSerializer < ApplicationSerializer
:email_in_reply_to,
:like_notification_frequency,
:include_tl0_in_digests,
- :theme_key
+ :theme_key,
+ :theme_key_seq
def auto_track_topics_after_msecs
diff --git a/app/services/user_updater.rb b/app/services/user_updater.rb
index 30bed45d60a..758c019c2a0 100644
--- a/app/services/user_updater.rb
+++ b/app/services/user_updater.rb
@@ -76,6 +76,11 @@ class UserUpdater
save_options = false
+ # special handling for theme_key cause we need to bump a sequence number
+ if attributes.key?(:theme_key) && user.user_option.theme_key != attributes[:theme_key]
+ user.user_option.theme_key_seq += 1
+ end
+
OPTION_ATTR.each do |attribute|
if attributes.key?(attribute)
save_options = true
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 92c2a981f59..6228db50610 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -581,6 +581,7 @@ en:
first_notification: "Your first notification! Select it to begin."
disable_jump_reply: "Don't jump to my post after I reply"
dynamic_favicon: "Show new / updated topic count on browser icon"
+ theme_default_on_all_devices: "Make this my default theme on all my devices"
external_links_in_new_tab: "Open all external links in a new tab"
enable_quoting: "Enable quote reply for highlighted text"
change: "change"
diff --git a/db/migrate/20170515152725_add_theme_key_seq_to_user_options.rb b/db/migrate/20170515152725_add_theme_key_seq_to_user_options.rb
new file mode 100644
index 00000000000..7bba1cf9c55
--- /dev/null
+++ b/db/migrate/20170515152725_add_theme_key_seq_to_user_options.rb
@@ -0,0 +1,5 @@
+class AddThemeKeySeqToUserOptions < ActiveRecord::Migration
+ def change
+ add_column :user_options, :theme_key_seq, :integer, null: false, default: 0
+ end
+end
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index c59fb9120ca..dc86db91228 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -18,8 +18,15 @@ describe TopicsController do
end
describe "themes" do
+ let :theme do
+ Theme.create!(user_id: -1, name: 'bob', user_selectable: true)
+ end
+
+ let :theme2 do
+ Theme.create!(user_id: -1, name: 'bobbob', user_selectable: true)
+ end
+
it "selects the theme the user has selected" do
- theme = Theme.create!(user_id: -1, name: 'bob', user_selectable: true)
user = log_in
user.user_option.update_columns(theme_key: theme.key)
@@ -31,6 +38,26 @@ describe TopicsController do
get :show, id: 666
expect(controller.theme_key).not_to eq(theme.key)
end
+
+ it "can be overridden with a cookie" do
+ user = log_in
+ user.user_option.update_columns(theme_key: theme.key)
+
+ cookies['theme_key'] = "#{theme2.key},#{user.user_option.theme_key_seq}"
+
+ get :show, id: 666
+ expect(controller.theme_key).to eq(theme2.key)
+
+ end
+
+ it "cookie can fail back to user if out of sync" do
+ user = log_in
+ user.user_option.update_columns(theme_key: theme.key)
+ cookies['theme_key'] = "#{theme2.key},#{user.user_option.theme_key_seq-1}"
+
+ get :show, id: 666
+ expect(controller.theme_key).to eq(theme.key)
+ end
end
it "doesn't store an incoming link when there's no referer" do
diff --git a/spec/services/user_updater_spec.rb b/spec/services/user_updater_spec.rb
index 3305f67842d..0d025c8cc41 100644
--- a/spec/services/user_updater_spec.rb
+++ b/spec/services/user_updater_spec.rb
@@ -66,6 +66,10 @@ describe UserUpdater do
updater = UserUpdater.new(acting_user, user)
date_of_birth = Time.zone.now
+ theme = Theme.create!(user_id: -1, name: "test", user_selectable: true)
+
+ seq = user.user_option.theme_key_seq
+
val = updater.update(bio_raw: 'my new bio',
email_always: 'true',
mailing_list_mode: true,
@@ -74,7 +78,8 @@ describe UserUpdater do
auto_track_topics_after_msecs: 101,
notification_level_when_replying: 3,
email_in_reply_to: false,
- date_of_birth: date_of_birth
+ date_of_birth: date_of_birth,
+ theme_key: theme.key
)
expect(val).to be_truthy
@@ -88,6 +93,8 @@ describe UserUpdater do
expect(user.user_option.auto_track_topics_after_msecs).to eq 101
expect(user.user_option.notification_level_when_replying).to eq 3
expect(user.user_option.email_in_reply_to).to eq false
+ expect(user.user_option.theme_key).to eq theme.key
+ expect(user.user_option.theme_key_seq).to eq(seq+1)
expect(user.date_of_birth).to eq(date_of_birth.to_date)
end