FEATURE: Optionally show local time for user in card (#9527)

This adds a site setting (default off) to optionally show a user's local time and timezone in their user card. For example, I live in Brisbane, and if at 3:30PM my time I were to open a user who lives in California's card I would see 22:30 (PST).
This commit is contained in:
Martin Brennan 2020-04-28 10:13:59 +10:00 committed by GitHub
parent a93ef2926d
commit bb4e965a66
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 148 additions and 10 deletions

View File

@ -38,7 +38,12 @@ export default Component.extend(CardContentsBase, CanCheckEmails, CleansUp, {
showMoreBadges: gt("moreBadgesCount", 0),
showDelete: and("viewingAdmin", "showName", "user.canBeDeleted"),
linkWebsite: not("user.isBasic"),
hasLocationOrWebsite: or("user.location", "user.website_name"),
@discourseComputed("user")
hasLocaleOrWebsite(user) {
return user.location || user.website_name || this.userTimezone;
},
isSuspendedOrHasBio: or("user.suspend_reason", "user.bio_excerpt"),
showCheckEmail: and("user.staged", "canCheckEmails"),
@ -52,6 +57,8 @@ export default Component.extend(CardContentsBase, CanCheckEmails, CleansUp, {
"siteSettings.allow_featured_topic_on_user_profiles"
),
showUserLocalTime: setting("display_local_time_in_user_card"),
@discourseComputed("user.staff")
staff: isStaff => (isStaff ? "staff" : ""),
@ -63,6 +70,17 @@ export default Component.extend(CardContentsBase, CanCheckEmails, CleansUp, {
return prioritizeNameInUx(name, this.siteSettings);
},
@discourseComputed("user")
userTimezone(user) {
return user.resolvedTimezone();
},
@discourseComputed()
formattedUserLocalTime() {
const timezone = this.userTimezone;
return moment.tz(timezone).format(I18n.t("dates.time_with_zone"));
},
@discourseComputed("username")
usernameClass: username => (username ? `user-card-${username}` : ""),

View File

@ -597,6 +597,11 @@ const User = RestModel.extend({
json.user.card_badge = Badge.create(json.user.card_badge);
}
if (!json.user._timezone) {
json.user._timezone = json.user.timezone;
delete json.user.timezone;
}
user.setProperties(json.user);
return user;
});

View File

@ -149,15 +149,9 @@
</div>
{{/if}}
{{#if this.hasLocationOrWebsite}}
{{#if this.hasLocaleOrWebsite}}
<div class="card-row">
<div class="location-and-website">
{{#if this.user.location}}
<span class="location">
{{d-icon "map-marker-alt"}}
<span>{{this.user.location}}</span>
</span>
{{/if}}
{{#if this.user.website_name}}
<span class="website-name">
{{d-icon "globe"}}
@ -170,6 +164,18 @@
{{/if}}
</span>
{{/if}}
{{#if this.user.location}}
<span class="location">
{{d-icon "map-marker-alt"}}
<span>{{this.user.location}}</span>
</span>
{{/if}}
{{#if showUserLocalTime}}
<span class="local-time">
{{d-icon "far-clock"}}
<span>{{this.formattedUserLocalTime}}</span>
</span>
{{/if}}
{{plugin-outlet name="user-card-location-and-website" args=(hash user=this.user)}}
</div>
</div>

View File

@ -212,7 +212,9 @@ $avatar_margin: -50px; // negative margin makes avatars extend above cards
@include ellipsis;
color: $primary;
}
.location {
.location,
.local-time,
.website-name {
margin-right: 0.5em;
}
.website-name a {

View File

@ -60,7 +60,8 @@ class UserCardSerializer < BasicUserSerializer
:primary_group_flair_url,
:primary_group_flair_bg_color,
:primary_group_flair_color,
:featured_topic
:featured_topic,
:timezone
untrusted_attributes :bio_excerpt,
:website,
@ -194,6 +195,14 @@ class UserCardSerializer < BasicUserSerializer
object.user_profile.featured_topic
end
def include_timezone?
SiteSetting.display_local_time_in_user_card?
end
def timezone
object.user_option.timezone
end
def card_background_upload_url
object.card_background_upload&.url
end

View File

@ -31,6 +31,7 @@ ar:
millions: "{{number}} مليون"
dates:
time: "h:mm a"
time_with_zone: "h:mm a (z)"
time_short_day: "ddd, HH:mm"
timeline_date: "MMM YYYY"
long_no_year_no_time: "D MMM"

View File

@ -25,6 +25,7 @@ be:
millions: "{{number}}M"
dates:
time: "HH:mm"
time_with_zone: "HH:mm (z)"
long_no_year_no_time: "D MMM"
long_with_year_no_time: "D МММ, YYYY"
long_date_with_year_without_time: "D MMM YYYY"

View File

@ -27,6 +27,7 @@ bg:
millions: "{{number}}M "
dates:
time: "HH:mm"
time_with_zone: "HH:mm (z)"
time_short_day: "ddd, HH:mm"
timeline_date: "MMM YYYY"
long_no_year_no_time: "D MMM"

View File

@ -28,6 +28,7 @@ bs_BA:
millions: "{{number}} miliona"
dates:
time: "HH: mm"
time_with_zone: "HH: mm (z)"
timeline_date: "MMM YYYY"
long_no_year_no_time: "MMM D"
full_no_year_no_time: "MMMM Do"

View File

@ -27,6 +27,7 @@ ca:
millions: "{{number}}M"
dates:
time: "h:mm a"
time_with_zone: "h:mm (z)"
timeline_date: "MMM YYYY"
long_no_year_no_time: "MMM D"
full_no_year_no_time: "MMMM Do"

View File

@ -29,6 +29,7 @@ cs:
millions: "{{number}}M"
dates:
time: "H:mm"
time_with_zone: "H:mm (z)"
timeline_date: "MMM YYYY"
long_no_year_no_time: "D. MMMM"
full_no_year_no_time: "D. MMMM"

View File

@ -27,6 +27,7 @@ da:
millions: "{{number}}M"
dates:
time: "HH:mm"
time_with_zone: "HH:mm (z)"
timeline_date: "MMM YYYY"
long_no_year_no_time: "D. MMM"
full_no_year_no_time: "D. MMMM"

View File

@ -27,6 +27,7 @@ de:
millions: "{{number}}M"
dates:
time: "HH:mm"
time_with_zone: "HH:mm (z)"
time_short_day: "ddd, HH:mm"
timeline_date: "MMM YYYY"
long_no_year_no_time: "D. MMM"

View File

@ -27,6 +27,7 @@ el:
millions: "{{number}}εκατ."
dates:
time: "ΗΗ:mm"
time_with_zone: "HH:mm (z)"
timeline_date: "MMM YYYY"
long_no_year_no_time: "DD MMM"
full_no_year_no_time: "Do MMMM"

View File

@ -40,6 +40,8 @@ en:
# Use Moment.js format string: https://momentjs.com/docs/#/displaying/format/
time: "HH:mm"
# Use Moment.js format string: https://momentjs.com/docs/#/displaying/format/
time_with_zone: "HH:mm (z)"
# Use Moment.js format string: https://momentjs.com/docs/#/displaying/format/
time_short_day: "ddd, HH:mm"
# Use Moment.js format string: https://momentjs.com/docs/#/displaying/format/
timeline_date: "MMM YYYY"

View File

@ -4,6 +4,8 @@ en_US:
# Use Moment.js format string: https://momentjs.com/docs/#/displaying/format/
time: "h:mm a"
# Use Moment.js format string: https://momentjs.com/docs/#/displaying/format/
time_with_zone: "hh:mm a (z)"
# Use Moment.js format string: https://momentjs.com/docs/#/displaying/format/
time_short_day: "ddd, h:mm a"
# Use Moment.js format string: https://momentjs.com/docs/#/displaying/format/
timeline_date: "MMM YYYY"

View File

@ -27,6 +27,7 @@ es:
millions: "{{number}}M"
dates:
time: "HH:mm"
time_with_zone: "HH:mm (z)"
time_short_day: "ddd, HH:mm"
timeline_date: "MMM YYYY"
long_no_year: "D MMM, HH:mm"

View File

@ -27,6 +27,7 @@ et:
millions: "{{number}}M"
dates:
time: "hh:mm"
time_with_zone: "hh:mm (z)"
timeline_date: "MMM YYYY"
long_no_year_no_time: "D. MMMM"
full_no_year_no_time: "Do MMMM"

View File

@ -27,6 +27,7 @@ fa_IR:
millions: "{{number}} میلیون"
dates:
time: "HH:mm"
time_with_zone: "HH:mm (z)"
time_short_day: "ddd, HH:mm"
timeline_date: "MMM YYYY"
long_no_year_no_time: "MMM D"

View File

@ -27,6 +27,7 @@ fi:
millions: "{{number}}M"
dates:
time: "H:mm"
time_with_zone: "H:mm (z)"
time_short_day: "ddd HH:mm"
timeline_date: "MMM YYYY"
long_no_year_no_time: "D. MMMM[ta]"

View File

@ -27,6 +27,7 @@ fr:
millions: "{{number}}M"
dates:
time: "HH:mm"
time_with_zone: "HH:mm (z)"
time_short_day: "ddd [à] HH:mm"
timeline_date: "MMM YYYY"
long_no_year_no_time: "D MMM"

View File

@ -27,6 +27,7 @@ gl:
millions: "{{number}}M"
dates:
time: "h:mm a"
time_with_zone: "h:mm a (z)"
timeline_date: "MMM YYYY"
long_no_year_no_time: "D MMM"
full_no_year_no_time: "D MMMM"

View File

@ -29,6 +29,7 @@ he:
millions: "{{number}} מיליון"
dates:
time: "h:mm a"
time_with_zone: "h:mm a (z)"
time_short_day: "ddd, HH:mm"
timeline_date: "MMM YYYY"
long_no_year: "D MMM, HH:mm"

View File

@ -27,6 +27,7 @@ hu:
millions: "{{number}} millió"
dates:
time: "h:mm a"
time_with_zone: "h:mm a (z)"
timeline_date: "YYYY MMMM"
long_no_year_no_time: "MMM D"
full_no_year_no_time: "MMM DD"

View File

@ -27,6 +27,7 @@ hy:
millions: "{{number}}մլն"
dates:
time: "h:mm"
time_with_zone: "h:mm (z)"
timeline_date: "MMM YYYY"
long_no_year_no_time: "MMM D"
full_no_year_no_time: "MMMM Do"

View File

@ -26,6 +26,7 @@ id:
millions: "{{number}}M"
dates:
time: "h:mm a"
time_with_zone: "h:mm a (z)"
time_short_day: "ddd, HH: mm"
timeline_date: "MMM YYYY"
long_no_year_no_time: "MMM D"

View File

@ -27,6 +27,7 @@ it:
millions: "{{number}}M"
dates:
time: "h:mm a"
time_with_zone: "h:mm a (z)"
time_short_day: "ddd, HH:mm"
timeline_date: "MMM YYYY"
long_no_year: "D MMM, HH:mm"

View File

@ -26,6 +26,7 @@ ja:
millions: "{{number}}M"
dates:
time: "H:mm"
time_with_zone: "H:mm (z)"
timeline_date: "YYYY年M月"
long_no_year_no_time: "M月D日"
full_no_year_no_time: "M月D日"

View File

@ -26,6 +26,7 @@ ko:
millions: "{{number}}백만"
dates:
time: "a h:mm"
time_with_zone: "a h:mm (z)"
timeline_date: "YYYY MMM"
long_no_year_no_time: "MMM D"
full_no_year_no_time: "MMMM Do"

View File

@ -29,6 +29,7 @@ lt:
millions: "{{number}}M"
dates:
time: "H:mm"
time_with_zone: "H:mm (z)"
timeline_date: "MMM YYYY"
long_no_year_no_time: "MMMM D"
full_no_year_no_time: "MMMM Do"

View File

@ -28,6 +28,7 @@ lv:
millions: "{{number}} milj."
dates:
time: "hh:mm"
time_with_zone: "hh:mm (z)"
timeline_date: "MMM YYYY"
long_no_year_no_time: "MMM D"
full_no_year_no_time: "MMMM Do"

View File

@ -27,6 +27,7 @@ nb_NO:
millions: "{{number}}M"
dates:
time: "LT"
time_with_zone: "LT (z)"
timeline_date: "MMM YYYY"
long_no_year_no_time: "D MMM"
full_no_year_no_time: "D. MMMM"

View File

@ -27,6 +27,7 @@ nl:
millions: "{{number}}M"
dates:
time: "HH:mm"
time_with_zone: "HH:mm (z)"
time_short_day: "ddd, HH:mm"
timeline_date: "MMM YYYY"
long_no_year: "D MMM, HH:mm"

View File

@ -29,6 +29,7 @@ pl_PL:
millions: "{{number}}M"
dates:
time: "H:mm"
time_with_zone: "H:mm (z)"
time_short_day: "ddd, GG: mm"
timeline_date: "MMM YYYY"
long_no_year: "D MMM, GG: mm"

View File

@ -27,6 +27,7 @@ pt:
millions: "{{number}}M"
dates:
time: "hh:mm"
time_with_zone: "hh:mm (z)"
timeline_date: "MMM YYYY"
long_no_year_no_time: "DD MMM"
full_no_year_no_time: "Do MMMM"

View File

@ -27,6 +27,7 @@ pt_BR:
millions: "{{number}}M"
dates:
time: "HH:mm"
time_with_zone: "HH:mm (z)"
time_short_day: "ddd, HH:mm"
timeline_date: "MMM YYYY"
long_no_year_no_time: "D MMM"

View File

@ -28,6 +28,7 @@ ro:
millions: "{{number}}M"
dates:
time: "HH:mm"
time_with_zone: "HH:mm (z)"
timeline_date: "MMM YYYY"
long_no_year_no_time: "DD MMM"
full_no_year_no_time: "Do MMMM "

View File

@ -29,6 +29,7 @@ ru:
millions: "{{number}} млн."
dates:
time: "HH:mm"
time_with_zone: "HH:mm (z)"
time_short_day: "ddd, HH:mm"
timeline_date: "MMM YYYY"
long_no_year: "D MMM, HH:mm"

View File

@ -29,6 +29,7 @@ sk:
millions: "{{number}}mil"
dates:
time: "h:mm a"
time_with_zone: "h:mm a (z)"
timeline_date: "MMM YYYY"
long_no_year_no_time: "MMM D"
full_no_year_no_time: "MMMM Do"

View File

@ -29,6 +29,7 @@ sl:
millions: "{{number}}M"
dates:
time: "H:mm"
time_with_zone: "H:mm (z)"
timeline_date: "MMM YYYY"
long_no_year_no_time: "D MMM"
full_no_year_no_time: "D. MMMM"

View File

@ -27,6 +27,7 @@ sq:
millions: "{{number}}M"
dates:
time: "h:mm a"
time_with_zone: "h:mm (z)"
timeline_date: "MMM YYYY"
long_no_year_no_time: "MMM D"
full_no_year_no_time: "MMMM Do"

View File

@ -28,6 +28,7 @@ sr:
millions: "{{number}}M"
dates:
time: "HH:mm"
time_with_zone: "HH:mm (z)"
timeline_date: "MMM YYYY"
long_no_year_no_time: "D MMM"
full_no_year_no_time: "MMMM Do"

View File

@ -27,6 +27,7 @@ sv:
millions: "{{number}}M"
dates:
time: "h:mm a"
time_with_zone: "h:mm a (z)"
time_short_day: "ddd, HH:mm"
timeline_date: "MMM YYYY"
long_no_year: "D MMM, HH:mm"

View File

@ -27,6 +27,7 @@ sw:
millions: "{{number}}M"
dates:
time: "h:mm a"
time_with_zone: "h:mm a (z)"
timeline_date: "MMM YYYY"
long_no_year_no_time: "MMM D"
full_no_year_no_time: "MMMM Do"

View File

@ -24,6 +24,7 @@ te:
tb: టీబీ
dates:
time: "h:mm a"
time_with_zone: "h:mm a (z)"
long_no_year_no_time: "MMM D"
long_with_year: "MMM D, YYYY h:mm a"
long_with_year_no_time: "MMM D, YYYY"

View File

@ -26,6 +26,7 @@ th:
millions: "{{number}}ล้าน"
dates:
time: "h:mm a"
time_with_zone: "h:mm a (z)"
timeline_date: "MMM YYYY"
long_no_year_no_time: "MMM D"
full_no_year_no_time: "MMMM Do"

View File

@ -27,6 +27,7 @@ tr_TR:
millions: "{{number}}M"
dates:
time: "h:mm a"
time_with_zone: "h:mm a (z)"
time_short_day: "ggg, SS:dd"
timeline_date: "MMM YYYY"
long_no_year_no_time: "D MMM"

View File

@ -29,6 +29,7 @@ uk:
millions: "{{number}} млн."
dates:
time: "HH:mm"
time_with_zone: "HH:mm (z)"
time_short_day: "ddd, HH:mm"
timeline_date: "MMM YYYY"
long_no_year_no_time: "D MMM"

View File

@ -27,6 +27,7 @@ ur:
millions: "{{number}}M"
dates:
time: "h:mm a"
time_with_zone: "h:mm a (z)"
timeline_date: "MMM YYYY"
long_no_year_no_time: "MMM D"
full_no_year_no_time: "MMMM Do"

View File

@ -26,6 +26,7 @@ vi:
millions: "{{number}}M"
dates:
time: "HH:mm"
time_with_zone: "HH:mm (z)"
time_short_day: "ddd, HH:mm"
timeline_date: "MMM YYYY"
long_no_year_no_time: "D MMM"

View File

@ -26,6 +26,7 @@ zh_CN:
millions: "{{number}}M"
dates:
time: "HH:mm"
time_with_zone: "HH:mm (z)"
time_short_day: "ddd, HH:mm"
timeline_date: "YYYY[年]M[月]"
long_no_year: "M[月]D[日] HH:mm"

View File

@ -26,6 +26,7 @@ zh_TW:
millions: "{{number}} 百萬"
dates:
time: "h:mm a"
time_with_zone: "h:mm a (z)"
timeline_date: "YYYY年 M月"
long_no_year_no_time: "M月 D日"
full_no_year_no_time: "M月 D日"

View File

@ -1407,6 +1407,7 @@ en:
watched_word_regexp_error: "The regular expression for %{action} watched words is invalid. Please check your <a href='%{base_path}/admin/logs/watched_words'>Watched Word settings</a>, or disable the 'watched words regular expressions' site setting."
site_settings:
display_local_time_in_user_card: "Display the local time based on a user's timezone when their user card is opened."
censored_words: "Words that will be automatically replaced with &#9632;&#9632;&#9632;&#9632;"
delete_old_hidden_posts: "Auto-delete any hidden posts that stay hidden for more than 30 days."
default_locale: "The default language of this Discourse instance. You can replace the text of system generated categories and topics at <a href='%{base_path}/admin/customize/site_texts' target='_blank'>Customize / Text</a>."

View File

@ -124,6 +124,9 @@ branding:
default: ""
basic:
display_local_time_in_user_card:
client: true
default: false
allow_user_locale:
client: true
default: false

View File

@ -1,6 +1,46 @@
import { acceptance } from "helpers/qunit-helpers";
import DiscourseURL from "discourse/lib/url";
import pretender from "helpers/create-pretender";
import userFixtures from "fixtures/user_fixtures";
import User from "discourse/models/user";
acceptance("User Card - Show Local Time", {
loggedIn: true,
settings: { display_local_time_in_user_card: true }
});
QUnit.test("user card local time", async assert => {
User.current().changeTimezone("Australia/Brisbane");
let cardResponse = _.clone(userFixtures["/u/eviltrout/card.json"]);
cardResponse.user.timezone = "Australia/Perth";
pretender.get("/u/eviltrout/card.json", () => [
200,
{ "Content-Type": "application/json" },
cardResponse
]);
await visit("/t/internationalization-localization/280");
assert.ok(invisible(".user-card"), "user card is invisible by default");
await click("a[data-user-card=eviltrout]:first");
let expectedTime =
moment
.tz("Australia/Brisbane")
.add(-2, "hours")
.format("hh:mm a") + " (AWST)";
assert.ok(visible(".user-card"), "card should appear");
assert.equal(
find(".user-card .local-time")
.text()
.trim(),
expectedTime,
"user card contains the user's local time"
);
});
acceptance("User Card", { loggedIn: true });
QUnit.test("user card", async assert => {
@ -34,6 +74,11 @@ QUnit.test("user card", async assert => {
"user card contains the data"
);
assert.ok(
!visible(".user-card .local-time"),
"local time with zone does not show by default"
);
await click(".card-content .compose-pm button");
assert.ok(
invisible(".user-card"),