mirror of
https://github.com/element-hq/element-web
synced 2024-11-21 16:55:34 +03:00
Allow user to set timezone (#12775)
* Allow user to set timezone * Update test snapshots --------- Co-authored-by: Florian Duros <florianduros@element.io>
This commit is contained in:
parent
acc7342758
commit
ae15bbe6e0
15 changed files with 256 additions and 9 deletions
|
@ -17,6 +17,11 @@ limitations under the License.
|
|||
|
||||
import { test, expect } from "../../element-web-test";
|
||||
|
||||
test.use({
|
||||
locale: "en-GB",
|
||||
timezoneId: "Europe/London",
|
||||
});
|
||||
|
||||
test.describe("Preferences user settings tab", () => {
|
||||
test.use({
|
||||
displayName: "Bob",
|
||||
|
@ -26,9 +31,9 @@ test.describe("Preferences user settings tab", () => {
|
|||
},
|
||||
});
|
||||
|
||||
test("should be rendered properly", async ({ app, user }) => {
|
||||
test("should be rendered properly", async ({ app, page, user }) => {
|
||||
page.setViewportSize({ width: 1024, height: 3300 });
|
||||
const tab = await app.settings.openUserSettings("Preferences");
|
||||
|
||||
// Assert that the top heading is rendered
|
||||
await expect(tab.getByRole("heading", { name: "Preferences" })).toBeVisible();
|
||||
await expect(tab).toMatchScreenshot("Preferences-user-settings-tab-should-be-rendered-properly-1.png");
|
||||
|
@ -53,4 +58,19 @@ test.describe("Preferences user settings tab", () => {
|
|||
// Assert that the default value is rendered again
|
||||
await expect(languageInput.getByText("English")).toBeVisible();
|
||||
});
|
||||
|
||||
test("should be able to change the timezone", async ({ uut, user }) => {
|
||||
// Check language and region setting dropdown
|
||||
const timezoneInput = uut.locator(".mx_dropdownUserTimezone");
|
||||
const timezoneValue = uut.locator("#mx_dropdownUserTimezone_value");
|
||||
await timezoneInput.scrollIntoViewIfNeeded();
|
||||
// Check the default value
|
||||
await expect(timezoneValue.getByText("Browser default")).toBeVisible();
|
||||
// Click the button to display the dropdown menu
|
||||
await timezoneInput.getByRole("button", { name: "Set timezone" }).click();
|
||||
// Select a different value
|
||||
timezoneInput.getByRole("option", { name: /Africa\/Abidjan/ }).click();
|
||||
// Check the new value
|
||||
await expect(timezoneValue.getByText("Africa/Abidjan")).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 195 KiB |
|
@ -64,4 +64,8 @@ limitations under the License.
|
|||
gap: var(--cpd-space-6x);
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.mx_SettingsSubsection_dropdown {
|
||||
min-width: 360px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ limitations under the License.
|
|||
import { Optional } from "matrix-events-sdk";
|
||||
|
||||
import { _t, getUserLanguage } from "./languageHandler";
|
||||
import { getUserTimezone } from "./TimezoneHandler";
|
||||
|
||||
export const MINUTE_MS = 60000;
|
||||
export const HOUR_MS = MINUTE_MS * 60;
|
||||
|
@ -77,6 +78,7 @@ export function formatDate(date: Date, showTwelveHour = false, locale?: string):
|
|||
weekday: "short",
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
timeZone: getUserTimezone(),
|
||||
}).format(date);
|
||||
} else if (now.getFullYear() === date.getFullYear()) {
|
||||
return new Intl.DateTimeFormat(_locale, {
|
||||
|
@ -86,6 +88,7 @@ export function formatDate(date: Date, showTwelveHour = false, locale?: string):
|
|||
day: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
timeZone: getUserTimezone(),
|
||||
}).format(date);
|
||||
}
|
||||
return formatFullDate(date, showTwelveHour, false, _locale);
|
||||
|
@ -104,6 +107,7 @@ export function formatFullDateNoTime(date: Date, locale?: string): string {
|
|||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
timeZone: getUserTimezone(),
|
||||
}).format(date);
|
||||
}
|
||||
|
||||
|
@ -127,6 +131,7 @@ export function formatFullDate(date: Date, showTwelveHour = false, showSeconds =
|
|||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
second: showSeconds ? "2-digit" : undefined,
|
||||
timeZone: getUserTimezone(),
|
||||
}).format(date);
|
||||
}
|
||||
|
||||
|
@ -160,6 +165,7 @@ export function formatFullTime(date: Date, showTwelveHour = false, locale?: stri
|
|||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
timeZone: getUserTimezone(),
|
||||
}).format(date);
|
||||
}
|
||||
|
||||
|
@ -178,6 +184,7 @@ export function formatTime(date: Date, showTwelveHour = false, locale?: string):
|
|||
...getTwelveHourOptions(showTwelveHour),
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
timeZone: getUserTimezone(),
|
||||
}).format(date);
|
||||
}
|
||||
|
||||
|
@ -285,6 +292,7 @@ export function formatFullDateNoDayNoTime(date: Date, locale?: string): string {
|
|||
year: "numeric",
|
||||
month: "numeric",
|
||||
day: "numeric",
|
||||
timeZone: getUserTimezone(),
|
||||
}).format(date);
|
||||
}
|
||||
|
||||
|
@ -354,6 +362,9 @@ export function formatPreciseDuration(durationMs: number): string {
|
|||
* @returns {string} formattedDate
|
||||
*/
|
||||
export const formatLocalDateShort = (timestamp: number, locale?: string): string =>
|
||||
new Intl.DateTimeFormat(locale ?? getUserLanguage(), { day: "2-digit", month: "2-digit", year: "2-digit" }).format(
|
||||
timestamp,
|
||||
);
|
||||
new Intl.DateTimeFormat(locale ?? getUserLanguage(), {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "2-digit",
|
||||
timeZone: getUserTimezone(),
|
||||
}).format(timestamp);
|
||||
|
|
55
src/TimezoneHandler.ts
Normal file
55
src/TimezoneHandler.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
Copyright 2024 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { SettingLevel } from "./settings/SettingLevel";
|
||||
import SettingsStore from "./settings/SettingsStore";
|
||||
|
||||
export const USER_TIMEZONE_KEY = "userTimezone";
|
||||
|
||||
/**
|
||||
* Returning `undefined` ensure that if unset the browser default will be used in `DateTimeFormat`.
|
||||
* @returns The user specified timezone or `undefined`
|
||||
*/
|
||||
export function getUserTimezone(): string | undefined {
|
||||
const tz = SettingsStore.getValueAt(SettingLevel.DEVICE, USER_TIMEZONE_KEY);
|
||||
return tz || undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set in the settings the given timezone
|
||||
* @timezone
|
||||
*/
|
||||
export function setUserTimezone(timezone: string): Promise<void> {
|
||||
return SettingsStore.setValue(USER_TIMEZONE_KEY, null, SettingLevel.DEVICE, timezone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the available timezones
|
||||
*/
|
||||
export function getAllTimezones(): string[] {
|
||||
return Intl.supportedValuesOf("timeZone");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current timezone in a short human readable way
|
||||
*/
|
||||
export function shortBrowserTimezone(): string {
|
||||
return (
|
||||
new Intl.DateTimeFormat(undefined, { timeZoneName: "short" })
|
||||
.formatToParts(new Date())
|
||||
.find((x) => x.type === "timeZoneName")?.value ?? "GMT"
|
||||
);
|
||||
}
|
|
@ -47,6 +47,7 @@ import { ViewRoomOpts } from "@matrix-org/react-sdk-module-api/lib/lifecycles/Ro
|
|||
|
||||
import shouldHideEvent from "../../shouldHideEvent";
|
||||
import { _t } from "../../languageHandler";
|
||||
import * as TimezoneHandler from "../../TimezoneHandler";
|
||||
import { RoomPermalinkCreator } from "../../utils/permalinks/Permalinks";
|
||||
import ResizeNotifier from "../../utils/ResizeNotifier";
|
||||
import ContentMessages from "../../ContentMessages";
|
||||
|
@ -228,6 +229,7 @@ export interface IRoomState {
|
|||
lowBandwidth: boolean;
|
||||
alwaysShowTimestamps: boolean;
|
||||
showTwelveHourTimestamps: boolean;
|
||||
userTimezone: string | undefined;
|
||||
readMarkerInViewThresholdMs: number;
|
||||
readMarkerOutOfViewThresholdMs: number;
|
||||
showHiddenEvents: boolean;
|
||||
|
@ -455,6 +457,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
lowBandwidth: SettingsStore.getValue("lowBandwidth"),
|
||||
alwaysShowTimestamps: SettingsStore.getValue("alwaysShowTimestamps"),
|
||||
showTwelveHourTimestamps: SettingsStore.getValue("showTwelveHourTimestamps"),
|
||||
userTimezone: TimezoneHandler.getUserTimezone(),
|
||||
readMarkerInViewThresholdMs: SettingsStore.getValue("readMarkerInViewThresholdMs"),
|
||||
readMarkerOutOfViewThresholdMs: SettingsStore.getValue("readMarkerOutOfViewThresholdMs"),
|
||||
showHiddenEvents: SettingsStore.getValue("showHiddenEventsInTimeline"),
|
||||
|
@ -512,6 +515,9 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
SettingsStore.watchSetting("showTwelveHourTimestamps", null, (...[, , , value]) =>
|
||||
this.setState({ showTwelveHourTimestamps: value as boolean }),
|
||||
),
|
||||
SettingsStore.watchSetting(TimezoneHandler.USER_TIMEZONE_KEY, null, (...[, , , value]) =>
|
||||
this.setState({ userTimezone: value as string }),
|
||||
),
|
||||
SettingsStore.watchSetting("readMarkerInViewThresholdMs", null, (...[, , , value]) =>
|
||||
this.setState({ readMarkerInViewThresholdMs: value as number }),
|
||||
),
|
||||
|
|
|
@ -15,12 +15,14 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import React, { ReactElement, useCallback, useEffect, useState } from "react";
|
||||
|
||||
import { NonEmptyArray } from "../../../../../@types/common";
|
||||
import { _t, getCurrentLanguage } from "../../../../../languageHandler";
|
||||
import { UseCase } from "../../../../../settings/enums/UseCase";
|
||||
import SettingsStore from "../../../../../settings/SettingsStore";
|
||||
import Field from "../../../elements/Field";
|
||||
import Dropdown from "../../../elements/Dropdown";
|
||||
import { SettingLevel } from "../../../../../settings/SettingLevel";
|
||||
import SettingsFlag from "../../../elements/SettingsFlag";
|
||||
import AccessibleButton from "../../../elements/AccessibleButton";
|
||||
|
@ -38,12 +40,16 @@ import PlatformPeg from "../../../../../PlatformPeg";
|
|||
import { IS_MAC } from "../../../../../Keyboard";
|
||||
import SpellCheckSettings from "../../SpellCheckSettings";
|
||||
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
|
||||
import * as TimezoneHandler from "../../../../../TimezoneHandler";
|
||||
|
||||
interface IProps {
|
||||
closeSettingsFn(success: boolean): void;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
timezone: string | undefined;
|
||||
timezones: string[];
|
||||
timezoneSearch: string | undefined;
|
||||
autocompleteDelay: string;
|
||||
readMarkerInViewThresholdMs: string;
|
||||
readMarkerOutOfViewThresholdMs: string;
|
||||
|
@ -68,7 +74,7 @@ const LanguageSection: React.FC = () => {
|
|||
);
|
||||
|
||||
return (
|
||||
<div className="mx_SettingsSubsection_contentStretch">
|
||||
<div className="mx_SettingsSubsection_dropdown">
|
||||
{_t("settings|general|application_language")}
|
||||
<LanguageDropdown onOptionChange={onLanguageChange} value={language} />
|
||||
<div className="mx_PreferencesUserSettingsTab_section_hint">
|
||||
|
@ -173,6 +179,9 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
|
|||
super(props);
|
||||
|
||||
this.state = {
|
||||
timezone: TimezoneHandler.getUserTimezone(),
|
||||
timezones: TimezoneHandler.getAllTimezones(),
|
||||
timezoneSearch: undefined,
|
||||
autocompleteDelay: SettingsStore.getValueAt(SettingLevel.DEVICE, "autocompleteDelay").toString(10),
|
||||
readMarkerInViewThresholdMs: SettingsStore.getValueAt(
|
||||
SettingLevel.DEVICE,
|
||||
|
@ -185,6 +194,25 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
|
|||
};
|
||||
}
|
||||
|
||||
private onTimezoneChange = (tz: string): void => {
|
||||
this.setState({ timezone: tz });
|
||||
TimezoneHandler.setUserTimezone(tz);
|
||||
};
|
||||
|
||||
/**
|
||||
* If present filter the time zones matching the search term
|
||||
*/
|
||||
private onTimezoneSearchChange = (search: string): void => {
|
||||
const timezoneSearch = search.toLowerCase();
|
||||
const timezones = timezoneSearch
|
||||
? TimezoneHandler.getAllTimezones().filter((tz) => {
|
||||
return tz.toLowerCase().includes(timezoneSearch);
|
||||
})
|
||||
: TimezoneHandler.getAllTimezones();
|
||||
|
||||
this.setState({ timezones, timezoneSearch });
|
||||
};
|
||||
|
||||
private onAutocompleteDelayChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
||||
this.setState({ autocompleteDelay: e.target.value });
|
||||
SettingsStore.setValue("autocompleteDelay", null, SettingLevel.DEVICE, e.target.value);
|
||||
|
@ -217,6 +245,16 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
|
|||
// Only show the user onboarding setting if the user should see the user onboarding page
|
||||
.filter((it) => it !== "FTUE.userOnboardingButton" || showUserOnboardingPage(useCase));
|
||||
|
||||
const browserTimezoneLabel: string = _t("settings|preferences|default_timezone", {
|
||||
timezone: TimezoneHandler.shortBrowserTimezone(),
|
||||
});
|
||||
|
||||
// Always Preprend the default option
|
||||
const timezones = this.state.timezones.map((tz) => {
|
||||
return <div key={tz}>{tz}</div>;
|
||||
});
|
||||
timezones.unshift(<div key="">{browserTimezoneLabel}</div>);
|
||||
|
||||
return (
|
||||
<SettingsTab data-testid="mx_PreferencesUserSettingsTab">
|
||||
<SettingsSection>
|
||||
|
@ -254,6 +292,23 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
|
|||
</SettingsSubsection>
|
||||
|
||||
<SettingsSubsection heading={_t("settings|preferences|time_heading")}>
|
||||
<div className="mx_SettingsSubsection_dropdown">
|
||||
{_t("settings|preferences|user_timezone")}
|
||||
<Dropdown
|
||||
id="mx_dropdownUserTimezone"
|
||||
className="mx_dropdownUserTimezone"
|
||||
data-testid="mx_dropdownUserTimezone"
|
||||
searchEnabled={true}
|
||||
value={this.state.timezone}
|
||||
label={_t("settings|preferences|user_timezone")}
|
||||
placeholder={browserTimezoneLabel}
|
||||
onOptionChange={this.onTimezoneChange}
|
||||
onSearchChange={this.onTimezoneSearchChange}
|
||||
>
|
||||
{timezones as NonEmptyArray<ReactElement & { key: string }>}
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
{this.renderGroup(PreferencesUserSettingsTab.TIME_SETTINGS)}
|
||||
</SettingsSubsection>
|
||||
|
||||
|
|
|
@ -55,6 +55,7 @@ const RoomContext = createContext<
|
|||
lowBandwidth: false,
|
||||
alwaysShowTimestamps: false,
|
||||
showTwelveHourTimestamps: false,
|
||||
userTimezone: undefined,
|
||||
readMarkerInViewThresholdMs: 3000,
|
||||
readMarkerOutOfViewThresholdMs: 30000,
|
||||
showHiddenEvents: false,
|
||||
|
|
|
@ -2703,6 +2703,7 @@
|
|||
"code_blocks_heading": "Code blocks",
|
||||
"compact_modern": "Use a more compact 'Modern' layout",
|
||||
"composer_heading": "Composer",
|
||||
"default_timezone": "Browser default (%(timezone)s)",
|
||||
"dialog_title": "<strong>Settings:</strong> Preferences",
|
||||
"enable_hardware_acceleration": "Enable hardware acceleration",
|
||||
"enable_tray_icon": "Show tray icon and minimise window to it on close",
|
||||
|
@ -2718,7 +2719,8 @@
|
|||
"show_checklist_shortcuts": "Show shortcut to welcome checklist above the room list",
|
||||
"show_polls_button": "Show polls button",
|
||||
"surround_text": "Surround selected text when typing special characters",
|
||||
"time_heading": "Displaying time"
|
||||
"time_heading": "Displaying time",
|
||||
"user_timezone": "Set timezone"
|
||||
},
|
||||
"prompt_invite": "Prompt before sending invites to potentially invalid matrix IDs",
|
||||
"replace_plain_emoji": "Automatically replace plain text Emoji",
|
||||
|
|
|
@ -649,6 +649,11 @@ export const SETTINGS: { [setting: string]: ISetting } = {
|
|||
displayName: _td("settings|always_show_message_timestamps"),
|
||||
default: false,
|
||||
},
|
||||
"userTimezone": {
|
||||
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS,
|
||||
displayName: _td("settings|preferences|user_timezone"),
|
||||
default: "",
|
||||
},
|
||||
"autoplayGifs": {
|
||||
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
||||
displayName: _td("settings|autoplay_gifs"),
|
||||
|
|
31
test/TimezoneHandler-test.ts
Normal file
31
test/TimezoneHandler-test.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import * as tzh from "../src/TimezoneHandler";
|
||||
|
||||
describe("TimezoneHandler", () => {
|
||||
it("should support setting a user timezone", async () => {
|
||||
const tz = "Europe/Paris";
|
||||
await tzh.setUserTimezone(tz);
|
||||
expect(tzh.getUserTimezone()).toEqual(tz);
|
||||
});
|
||||
it("Return undefined with an empty TZ", async () => {
|
||||
await tzh.setUserTimezone("");
|
||||
expect(tzh.getUserTimezone()).toEqual(undefined);
|
||||
});
|
||||
});
|
|
@ -66,6 +66,7 @@ describe("<SendMessageComposer/>", () => {
|
|||
lowBandwidth: false,
|
||||
alwaysShowTimestamps: false,
|
||||
showTwelveHourTimestamps: false,
|
||||
userTimezone: undefined,
|
||||
readMarkerInViewThresholdMs: 3000,
|
||||
readMarkerOutOfViewThresholdMs: 30000,
|
||||
showHiddenEvents: false,
|
||||
|
|
|
@ -55,6 +55,32 @@ describe("PreferencesUserSettingsTab", () => {
|
|||
expect(reloadStub).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should search and select a user timezone", async () => {
|
||||
renderTab();
|
||||
|
||||
expect(await screen.findByText(/Browser default/)).toBeInTheDocument();
|
||||
const timezoneDropdown = await screen.findByRole("button", { name: "Set timezone" });
|
||||
await userEvent.click(timezoneDropdown);
|
||||
|
||||
// Without filtering `expect(screen.queryByRole("option" ...` take over 1s.
|
||||
await fireEvent.change(screen.getByRole("combobox", { name: "Set timezone" }), {
|
||||
target: { value: "Africa/Abidjan" },
|
||||
});
|
||||
|
||||
expect(screen.queryByRole("option", { name: "Africa/Abidjan" })).toBeInTheDocument();
|
||||
expect(screen.queryByRole("option", { name: "Europe/Paris" })).not.toBeInTheDocument();
|
||||
|
||||
await fireEvent.change(screen.getByRole("combobox", { name: "Set timezone" }), {
|
||||
target: { value: "Europe/Paris" },
|
||||
});
|
||||
|
||||
expect(screen.queryByRole("option", { name: "Africa/Abidjan" })).not.toBeInTheDocument();
|
||||
const option = await screen.getByRole("option", { name: "Europe/Paris" });
|
||||
await userEvent.click(option);
|
||||
|
||||
expect(await screen.findByText("Europe/Paris")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should not show spell check setting if unsupported", async () => {
|
||||
PlatformPeg.get()!.supportsSpellCheckSettings = jest.fn().mockReturnValue(false);
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ exports[`PreferencesUserSettingsTab should render 1`] = `
|
|||
class="mx_SettingsSubsection_content"
|
||||
>
|
||||
<div
|
||||
class="mx_SettingsSubsection_contentStretch"
|
||||
class="mx_SettingsSubsection_dropdown"
|
||||
>
|
||||
Application language
|
||||
<div
|
||||
|
@ -224,6 +224,35 @@ exports[`PreferencesUserSettingsTab should render 1`] = `
|
|||
<div
|
||||
class="mx_SettingsSubsection_content"
|
||||
>
|
||||
<div
|
||||
class="mx_SettingsSubsection_dropdown"
|
||||
>
|
||||
Set timezone
|
||||
<div
|
||||
class="mx_Dropdown mx_dropdownUserTimezone"
|
||||
>
|
||||
<div
|
||||
aria-describedby="mx_dropdownUserTimezone_value"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
aria-label="Set timezone"
|
||||
aria-owns="mx_dropdownUserTimezone_input"
|
||||
class="mx_AccessibleButton mx_Dropdown_input mx_no_textinput"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Dropdown_option"
|
||||
id="mx_dropdownUserTimezone_value"
|
||||
>
|
||||
Browser default (UTC)
|
||||
</div>
|
||||
<span
|
||||
class="mx_Dropdown_arrow"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsFlag"
|
||||
>
|
||||
|
|
|
@ -72,6 +72,7 @@ export function getRoomContext(room: Room, override: Partial<IRoomState>): IRoom
|
|||
layout: Layout.Group,
|
||||
lowBandwidth: false,
|
||||
alwaysShowTimestamps: false,
|
||||
userTimezone: undefined,
|
||||
showTwelveHourTimestamps: false,
|
||||
readMarkerInViewThresholdMs: 3000,
|
||||
readMarkerOutOfViewThresholdMs: 30000,
|
||||
|
|
Loading…
Reference in a new issue