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