diff --git a/app/assets/javascripts/discourse/controllers/bookmark.js b/app/assets/javascripts/discourse/controllers/bookmark.js index ecafa0dd23d..18a6112a254 100644 --- a/app/assets/javascripts/discourse/controllers/bookmark.js +++ b/app/assets/javascripts/discourse/controllers/bookmark.js @@ -45,7 +45,7 @@ export default Controller.extend(ModalFunctionality, { customReminderTime: null, lastCustomReminderDate: null, lastCustomReminderTime: null, - userTimezone: this.currentUser.timezone + userTimezone: this.currentUser.resolvedTimezone() }); this.loadLastUsedCustomReminderDatetime(); diff --git a/app/assets/javascripts/discourse/controllers/preferences/profile.js b/app/assets/javascripts/discourse/controllers/preferences/profile.js index 57d8759ac84..71992e99fdd 100644 --- a/app/assets/javascripts/discourse/controllers/preferences/profile.js +++ b/app/assets/javascripts/discourse/controllers/preferences/profile.js @@ -73,6 +73,10 @@ export default Controller.extend({ ); }, + useCurrentTimezone() { + this.model.set("user_option.timezone", moment.tz.guess()); + }, + save() { this.set("saved", false); @@ -95,7 +99,7 @@ export default Controller.extend({ // update the timezone in memory so we can use the new // one if we change routes without reloading the user if (this.currentUser.id === this.model.id) { - this.currentUser.timezone = this.model.user_option.timezone; + this.currentUser.changeTimezone(this.model.user_option.timezone); } cookAsync(model.get("bio_raw")) diff --git a/app/assets/javascripts/discourse/models/bookmark.js b/app/assets/javascripts/discourse/models/bookmark.js index 0473c108118..bb88e7da526 100644 --- a/app/assets/javascripts/discourse/models/bookmark.js +++ b/app/assets/javascripts/discourse/models/bookmark.js @@ -113,7 +113,7 @@ const Bookmark = RestModel.extend({ formattedReminder(bookmarkReminderAt) { const currentUser = PreloadStore.get("currentUser"); return moment - .tz(bookmarkReminderAt, currentUser.timezone || moment.tz.guess()) + .tz(bookmarkReminderAt, currentUser.resolvedTimezone()) .format(I18n.t("dates.long_with_year")); }, diff --git a/app/assets/javascripts/discourse/models/user.js b/app/assets/javascripts/discourse/models/user.js index 421376609a3..7b106d9206f 100644 --- a/app/assets/javascripts/discourse/models/user.js +++ b/app/assets/javascripts/discourse/models/user.js @@ -840,10 +840,40 @@ const User = RestModel.extend({ !secondFactorEnabled && (enforce === "all" || (enforce === "staff" && staff)) ); + }, + + resolvedTimezone() { + if (this._timezone) { + return this._timezone; + } + + this.changeTimezone(moment.tz.guess()); + ajax(userPath(this.username + ".json"), { + type: "PUT", + dataType: "json", + data: { timezone: this._timezone } + }); + + return this._timezone; + }, + + changeTimezone(tz) { + this._timezone = tz; } }); User.reopenClass(Singleton, { + munge(json) { + // timezone should not be directly accessed, use + // resolvedTimezone() and changeTimezone(tz) + if (!json._timezone) { + json._timezone = json.timezone; + delete json.timezone; + } + + return json; + }, + // Find a `User` for a given username. findByUsername(username, options) { const user = User.create({ username: username }); @@ -852,7 +882,7 @@ User.reopenClass(Singleton, { // TODO: Use app.register and junk Singleton createCurrent() { - const userJson = PreloadStore.get("currentUser"); + let userJson = PreloadStore.get("currentUser"); if (userJson && userJson.primary_group_id) { const primaryGroup = userJson.groups.find( @@ -864,6 +894,7 @@ User.reopenClass(Singleton, { } if (userJson) { + userJson = User.munge(userJson); const store = Discourse.__container__.lookup("service:store"); return store.createRecord("user", userJson); } diff --git a/app/assets/javascripts/discourse/routes/preferences-profile.js b/app/assets/javascripts/discourse/routes/preferences-profile.js index 02eb4df866c..fb9a2ff5275 100644 --- a/app/assets/javascripts/discourse/routes/preferences-profile.js +++ b/app/assets/javascripts/discourse/routes/preferences-profile.js @@ -1,13 +1,8 @@ import RestrictedUserRoute from "discourse/routes/restricted-user"; -import { set } from "@ember/object"; export default RestrictedUserRoute.extend({ showFooter: true, setupController(controller, model) { - if (!model.user_option.timezone) { - set(model, "user_option.timezone", moment.tz.guess()); - } - controller.set("model", model); } }); diff --git a/app/assets/javascripts/discourse/templates/preferences/profile.hbs b/app/assets/javascripts/discourse/templates/preferences/profile.hbs index f07c8a9a38b..fded99a5fd6 100644 --- a/app/assets/javascripts/discourse/templates/preferences/profile.hbs +++ b/app/assets/javascripts/discourse/templates/preferences/profile.hbs @@ -14,6 +14,7 @@ onChange=(action (mut model.user_option.timezone)) class="input-xxlarge" }} + {{d-button icon="globe" label="user.use_current_timezone" action=(action "useCurrentTimezone") }}
diff --git a/app/assets/javascripts/discourse/widgets/post-menu.js b/app/assets/javascripts/discourse/widgets/post-menu.js index 95c3477ffd6..c21d60f6989 100644 --- a/app/assets/javascripts/discourse/widgets/post-menu.js +++ b/app/assets/javascripts/discourse/widgets/post-menu.js @@ -315,7 +315,7 @@ registerButton("bookmarkWithReminder", (attrs, state, siteSettings) => { if (attrs.bookmarkReminderAt) { let reminderAtDate = moment(attrs.bookmarkReminderAt).tz( - Discourse.currentUser.timezone + Discourse.currentUser.resolvedTimezone() ); title = "bookmarks.created_with_reminder"; titleOptions = { diff --git a/app/assets/stylesheets/common/base/user.scss b/app/assets/stylesheets/common/base/user.scss index ae62bc5fdf0..c06f4fc6559 100644 --- a/app/assets/stylesheets/common/base/user.scss +++ b/app/assets/stylesheets/common/base/user.scss @@ -724,3 +724,7 @@ } } } + +.timezone-input { + margin-bottom: 0.5em; +} diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 4382448fea5..9f72e5b3d86 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -846,6 +846,7 @@ en: clear: title: "Clear" warning: "Are you sure you want to clear your featured topic?" + use_current_timezone: "Use Current Timezone" profile_hidden: "This user's public profile is hidden." expand_profile: "Expand" collapse_profile: "Collapse" diff --git a/test/javascripts/controllers/bookmark-test.js.es6 b/test/javascripts/controllers/bookmark-test.js.es6 index 978b7cd19f0..c517ad2a89b 100644 --- a/test/javascripts/controllers/bookmark-test.js.es6 +++ b/test/javascripts/controllers/bookmark-test.js.es6 @@ -1,10 +1,11 @@ -import { currentUser } from "helpers/qunit-helpers"; +import { logIn } from "helpers/qunit-helpers"; +import User from "discourse/models/user"; let BookmarkController; moduleFor("controller:bookmark", { beforeEach() { - Discourse.currentUser = currentUser(); - BookmarkController = this.subject({ currentUser: Discourse.currentUser }); + logIn(); + BookmarkController = this.subject({ currentUser: User.current() }); }, afterEach() { @@ -119,7 +120,7 @@ QUnit.test( function(assert) { let dt = moment.tz( "2019-12-11T11:37:16", - BookmarkController.currentUser.timezone + BookmarkController.currentUser.resolvedTimezone() ); assert.equal( @@ -181,14 +182,23 @@ QUnit.test( } ); -QUnit.test( - "userHasTimezoneSet updates true/false based on whether the current user timezone is set globally", - function(assert) { - Discourse.currentUser.timezone = null; - BookmarkController.onShow(); - assert.equal(BookmarkController.userHasTimezoneSet, false); - Discourse.currentUser.timezone = "Australia/Brisbane"; - BookmarkController.onShow(); - assert.equal(BookmarkController.userHasTimezoneSet, true); - } -); +QUnit.test("user timezone updates when the modal is shown", function(assert) { + User.current().changeTimezone(null); + let stub = sandbox.stub(moment.tz, "guess").returns("Europe/Moscow"); + BookmarkController.onShow(); + assert.equal(BookmarkController.userHasTimezoneSet, true); + assert.equal( + BookmarkController.userTimezone, + "Europe/Moscow", + "the user does not have their timezone set and a timezone is guessed" + ); + User.current().changeTimezone("Australia/Brisbane"); + BookmarkController.onShow(); + assert.equal(BookmarkController.userHasTimezoneSet, true); + assert.equal( + BookmarkController.userTimezone, + "Australia/Brisbane", + "the user does their timezone set" + ); + stub.restore(); +}); diff --git a/test/javascripts/models/user-test.js.es6 b/test/javascripts/models/user-test.js.es6 index 20ef656cf2b..4675043adf1 100644 --- a/test/javascripts/models/user-test.js.es6 +++ b/test/javascripts/models/user-test.js.es6 @@ -1,5 +1,7 @@ import User from "discourse/models/user"; import Group from "discourse/models/group"; +import * as ajaxlib from "discourse/lib/ajax"; +import pretender from "helpers/create-pretender"; QUnit.module("model:user"); @@ -66,3 +68,39 @@ QUnit.test("canMangeGroup", assert => { "a group owner should be able to manage the group" ); }); + +QUnit.test("resolvedTimezone", assert => { + const tz = "Australia/Brisbane"; + let user = User.create({ timezone: tz, username: "chuck" }); + let stub = sandbox.stub(moment.tz, "guess").returns("America/Chicago"); + + pretender.put("/u/chuck.json", () => { + return [200, { "Content-Type": "application/json" }, {}]; + }); + + let spy = sandbox.spy(ajaxlib, "ajax"); + assert.equal( + user.resolvedTimezone(), + tz, + "if the user already has a timezone return it" + ); + assert.ok( + spy.notCalled, + "if the user already has a timezone do not call AJAX update" + ); + user = User.create({ username: "chuck" }); + assert.equal( + user.resolvedTimezone(), + "America/Chicago", + "if the user has no timezone guess it with moment" + ); + assert.ok( + spy.calledWith("/u/chuck.json", { + type: "PUT", + dataType: "json", + data: { timezone: "America/Chicago" } + }), + "if the user has no timezone save it with an AJAX update" + ); + stub.restore(); +});