first commit
This commit is contained in:
commit
73b678e688
5 changed files with 608 additions and 0 deletions
48
README.md
Normal file
48
README.md
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
# Timezones
|
||||||
|
|
||||||
|
A Vencord plugin that shows the local time of users in profiles and message headers. Supports persistent timezones via a centralized database.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Shows user timezones in message headers and profile modals.
|
||||||
|
- Allows setting local or database-based timezones.
|
||||||
|
- 24h or 12h format toggle.
|
||||||
|
- Easily manage your own timezone with database sync.
|
||||||
|
- Tooltip with detailed datetime format.
|
||||||
|
|
||||||
|
## Settings
|
||||||
|
|
||||||
|
| Setting | Type | Description | Default |
|
||||||
|
|---------------------------|-----------|-----------------------------------------------------------------------------|-----------|
|
||||||
|
| Show Own Timezone | Boolean | Show your own timezone in message headers and profiles | `true` |
|
||||||
|
| 24h Time | Boolean | Display time in 24-hour format | `false` |
|
||||||
|
| Show Time in Messages | Boolean | Show local time next to messages | `true` |
|
||||||
|
| Show Time in Profiles | Boolean | Show local time in user profiles | `true` |
|
||||||
|
| Use Database | Boolean | Enable pulling timezones from the online database | `true` |
|
||||||
|
| Prefer Database Over Local| Boolean | Prefer database timezones over locally stored ones | `true` |
|
||||||
|
| Set Database Timezone | Component | Opens a modal to set your timezone in the database | |
|
||||||
|
| Reset Database Timezone | Component | Clears your stored database timezone | |
|
||||||
|
|
||||||
|
## Toolbox Actions
|
||||||
|
|
||||||
|
- **Set Database Timezone**: Opens the authorization modal to set your current timezone.
|
||||||
|
- **Refresh Database Timezones**: Refetches all stored timezones from the server.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
For installation instructions, see [Vencord Docs](https://docs.vencord.dev/installing/custom-plugins/).
|
||||||
|
|
||||||
|
## Infos
|
||||||
|
|
||||||
|
- Timezones are saved per-user.
|
||||||
|
- Database-based timezones are synced from [timezone.creations.works](https://timezone.creations.works).
|
||||||
|
- The plugin prompts the user to store their timezone on the first use if database mode is enabled.
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
- [daveyy](https://daveyy.net)
|
||||||
|
- [creations](https://creations.works)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html)
|
106
TimezoneModal.tsx
Normal file
106
TimezoneModal.tsx
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as DataStore from "@api/DataStore";
|
||||||
|
import { classNameFactory } from "@api/Styles";
|
||||||
|
import { Margins } from "@utils/margins";
|
||||||
|
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot } from "@utils/modal";
|
||||||
|
import { Button, Forms, SearchableSelect, useEffect, useMemo, useState } from "@webpack/common";
|
||||||
|
|
||||||
|
import { DATASTORE_KEY, settings, timezones } from ".";
|
||||||
|
import { getTimezone, setTimezone, setUserDatabaseTimezone } from "./database";
|
||||||
|
|
||||||
|
export async function setUserTimezone(userId: string, timezone: string | null) {
|
||||||
|
timezones[userId] = timezone;
|
||||||
|
await DataStore.set(DATASTORE_KEY, timezones);
|
||||||
|
}
|
||||||
|
|
||||||
|
const cl = classNameFactory("vc-timezone-");
|
||||||
|
|
||||||
|
export function SetTimezoneModal({ userId, modalProps, database }: { userId: string, modalProps: ModalProps; database?: boolean; }) {
|
||||||
|
const [currentValue, setCurrentValue] = useState<string | null>(timezones[userId] ?? null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const localTimezone = timezones[userId];
|
||||||
|
const shouldUseDatabase =
|
||||||
|
settings.store.useDatabase &&
|
||||||
|
(settings.store.preferDatabaseOverLocal || !localTimezone);
|
||||||
|
|
||||||
|
const value = shouldUseDatabase
|
||||||
|
? getTimezone(userId) ?? localTimezone
|
||||||
|
: localTimezone;
|
||||||
|
|
||||||
|
setCurrentValue(value ?? Intl.DateTimeFormat().resolvedOptions().timeZone);
|
||||||
|
}, [userId, settings.store.useDatabase, settings.store.preferDatabaseOverLocal]);
|
||||||
|
|
||||||
|
const options = useMemo(() => {
|
||||||
|
return Intl.supportedValuesOf("timeZone").map(timezone => {
|
||||||
|
const offset = new Intl.DateTimeFormat(undefined, { timeZone: timezone, timeZoneName: "short" })
|
||||||
|
.formatToParts(new Date())
|
||||||
|
.find(part => part.type === "timeZoneName")!.value;
|
||||||
|
|
||||||
|
return { label: `${timezone} (${offset})`, value: timezone };
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalRoot {...modalProps}>
|
||||||
|
<ModalHeader className={cl("modal-header")}>
|
||||||
|
<Forms.FormTitle tag="h2">
|
||||||
|
Timezones
|
||||||
|
</Forms.FormTitle>
|
||||||
|
<ModalCloseButton onClick={modalProps.onClose} />
|
||||||
|
</ModalHeader>
|
||||||
|
|
||||||
|
<ModalContent className={cl("modal-content")}>
|
||||||
|
<section className={Margins.bottom16}>
|
||||||
|
<Forms.FormTitle tag="h3">
|
||||||
|
Select Timezone
|
||||||
|
</Forms.FormTitle>
|
||||||
|
|
||||||
|
<SearchableSelect
|
||||||
|
options={options}
|
||||||
|
value={options.find(o => o.value === currentValue)}
|
||||||
|
placeholder={"Select a Timezone"}
|
||||||
|
maxVisibleItems={5}
|
||||||
|
closeOnSelect={true}
|
||||||
|
onChange={v => setCurrentValue(v)}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
</ModalContent>
|
||||||
|
|
||||||
|
<ModalFooter className={cl("modal-footer")}>
|
||||||
|
{!database && (
|
||||||
|
<Button
|
||||||
|
color={Button.Colors.RED}
|
||||||
|
onClick={async () => {
|
||||||
|
await setUserTimezone(userId, null);
|
||||||
|
modalProps.onClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Delete Timezone
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
color={Button.Colors.BRAND}
|
||||||
|
disabled={currentValue === null}
|
||||||
|
onClick={async () => {
|
||||||
|
if (database) {
|
||||||
|
await setUserDatabaseTimezone(userId, currentValue);
|
||||||
|
await setTimezone(currentValue!);
|
||||||
|
} else {
|
||||||
|
await setUserTimezone(userId, currentValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
modalProps.onClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalRoot>
|
||||||
|
);
|
||||||
|
}
|
110
database.tsx
Normal file
110
database.tsx
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2025 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { openModal } from "@utils/index";
|
||||||
|
import { OAuth2AuthorizeModal, showToast, Toasts } from "@webpack/common";
|
||||||
|
|
||||||
|
const databaseTimezones: Record<string, { value: string | null; }> = {};
|
||||||
|
|
||||||
|
const DOMAIN = "https://timezone.creations.works";
|
||||||
|
const REDIRECT_URI = `${DOMAIN}/auth/discord/callback`;
|
||||||
|
const CLIENT_ID = "1377021506810417173";
|
||||||
|
|
||||||
|
export async function setUserDatabaseTimezone(userId: string, timezone: string | null) {
|
||||||
|
databaseTimezones[userId] = { value: timezone };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTimezone(userId: string): string | null {
|
||||||
|
return databaseTimezones[userId]?.value ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadDatabaseTimezones(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${DOMAIN}/list`, {
|
||||||
|
headers: { Accept: "application/json" }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
const json = await res.json();
|
||||||
|
for (const id in json) {
|
||||||
|
databaseTimezones[id] = {
|
||||||
|
value: json[id]?.timezone ?? null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to fetch timezones list:", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setTimezone(timezone: string): Promise<boolean> {
|
||||||
|
const res = await fetch(`${DOMAIN}/set?timezone=${encodeURIComponent(timezone)}`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json"
|
||||||
|
},
|
||||||
|
credentials: "include"
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteTimezone(): Promise<boolean> {
|
||||||
|
const res = await fetch(`${DOMAIN}/delete`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json"
|
||||||
|
},
|
||||||
|
credentials: "include"
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function authModal(callback?: () => void) {
|
||||||
|
openModal(modalProps => (
|
||||||
|
<OAuth2AuthorizeModal
|
||||||
|
{...modalProps}
|
||||||
|
clientId={CLIENT_ID}
|
||||||
|
redirectUri={REDIRECT_URI}
|
||||||
|
responseType="code"
|
||||||
|
scopes={["identify"]}
|
||||||
|
permissions={0n}
|
||||||
|
cancelCompletesFlow={false}
|
||||||
|
callback={async (res: any) => {
|
||||||
|
if (!res || !res.location) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = new URL(res.location);
|
||||||
|
|
||||||
|
const r = await fetch(url, {
|
||||||
|
credentials: "include",
|
||||||
|
headers: { Accept: "application/json" }
|
||||||
|
});
|
||||||
|
|
||||||
|
const json = await r.json();
|
||||||
|
if (!r.ok) {
|
||||||
|
showToast(json.message ?? "Authorization failed", Toasts.Type.FAILURE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showToast("Authorization successful!", Toasts.Type.SUCCESS);
|
||||||
|
callback?.();
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error during authorization:", e);
|
||||||
|
showToast("Unexpected error during authorization", Toasts.Type.FAILURE);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
}
|
303
index.tsx
Normal file
303
index.tsx
Normal file
|
@ -0,0 +1,303 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2025 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import "./styles.css";
|
||||||
|
|
||||||
|
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||||
|
import * as DataStore from "@api/DataStore";
|
||||||
|
import { definePluginSettings } from "@api/Settings";
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import { openModal } from "@utils/modal";
|
||||||
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
import { findByPropsLazy } from "@webpack";
|
||||||
|
import { Button, Menu, showToast, Toasts, Tooltip, useEffect, UserStore, useState } from "@webpack/common";
|
||||||
|
import { Message, User } from "discord-types/general";
|
||||||
|
|
||||||
|
import { authModal, deleteTimezone, getTimezone, loadDatabaseTimezones, setUserDatabaseTimezone } from "./database";
|
||||||
|
import { SetTimezoneModal } from "./TimezoneModal";
|
||||||
|
|
||||||
|
export let timezones: Record<string, string | null> = {};
|
||||||
|
export const DATASTORE_KEY = "vencord-timezones";
|
||||||
|
|
||||||
|
const classes = findByPropsLazy("timestamp", "compact", "contentOnly");
|
||||||
|
const locale = findByPropsLazy("getLocale");
|
||||||
|
|
||||||
|
export const settings = definePluginSettings({
|
||||||
|
"Show Own Timezone": {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Show your own timezone in profiles and message headers",
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
|
||||||
|
"24h Time": {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Show time in 24h format",
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
|
||||||
|
showMessageHeaderTime: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Show time in message headers",
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
|
||||||
|
showProfileTime: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Show time in profiles",
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
|
||||||
|
useDatabase: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Enable database for getting user timezones",
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
|
||||||
|
preferDatabaseOverLocal: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Prefer database over local storage for timezones",
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
|
||||||
|
setDatabaseTimezone: {
|
||||||
|
description: "Set your timezone on the database",
|
||||||
|
type: OptionType.COMPONENT,
|
||||||
|
component: () => (
|
||||||
|
<Button onClick={() => {
|
||||||
|
authModal(async () => {
|
||||||
|
openModal(modalProps => <SetTimezoneModal userId={UserStore.getCurrentUser().id} modalProps={modalProps} database={true} />);
|
||||||
|
});
|
||||||
|
}}>
|
||||||
|
Set Timezone on Database
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
resetDatabaseTimezone: {
|
||||||
|
description: "Reset your timezone on the database",
|
||||||
|
type: OptionType.COMPONENT,
|
||||||
|
component: () => (
|
||||||
|
<Button
|
||||||
|
color={Button.Colors.RED}
|
||||||
|
onClick={() => {
|
||||||
|
authModal(async () => {
|
||||||
|
await setUserDatabaseTimezone(UserStore.getCurrentUser().id, null);
|
||||||
|
await deleteTimezone();
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Reset Database Timezones
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
askedTimezone: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Whether the user has been asked to set their timezone",
|
||||||
|
hidden: true,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function getTime(timezone: string, timestamp: string | number, props: Intl.DateTimeFormatOptions = {}) {
|
||||||
|
const date = new Date(timestamp);
|
||||||
|
const formatter = new Intl.DateTimeFormat(locale.getLocale() ?? "en-US", {
|
||||||
|
hour12: !settings.store["24h Time"],
|
||||||
|
timeZone: timezone,
|
||||||
|
...props
|
||||||
|
});
|
||||||
|
return formatter.format(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
userId: string;
|
||||||
|
timestamp?: string;
|
||||||
|
type: "message" | "profile";
|
||||||
|
}
|
||||||
|
|
||||||
|
const TimestampComponent = ErrorBoundary.wrap(({ userId, timestamp, type }: Props) => {
|
||||||
|
const [currentTime, setCurrentTime] = useState(timestamp || Date.now());
|
||||||
|
const [timezone, setTimezone] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const localTimezone = timezones[userId];
|
||||||
|
const shouldUseDatabase =
|
||||||
|
settings.store.useDatabase &&
|
||||||
|
(settings.store.preferDatabaseOverLocal || !localTimezone);
|
||||||
|
|
||||||
|
if (shouldUseDatabase) {
|
||||||
|
setTimezone(getTimezone(userId) ?? localTimezone);
|
||||||
|
} else {
|
||||||
|
setTimezone(localTimezone);
|
||||||
|
}
|
||||||
|
}, [userId, settings.store.useDatabase, settings.store.preferDatabaseOverLocal]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (type !== "profile") return;
|
||||||
|
|
||||||
|
setCurrentTime(Date.now());
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const delay = (60 - now.getSeconds()) * 1000 + 1000 - now.getMilliseconds();
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setCurrentTime(Date.now());
|
||||||
|
}, delay);
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [type, currentTime]);
|
||||||
|
|
||||||
|
if (!timezone) return null;
|
||||||
|
|
||||||
|
const shortTime = getTime(timezone, currentTime, { hour: "numeric", minute: "numeric" });
|
||||||
|
const longTime = getTime(timezone, currentTime, {
|
||||||
|
weekday: "long",
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
hour: "numeric",
|
||||||
|
minute: "numeric"
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
position="top"
|
||||||
|
// @ts-ignore
|
||||||
|
delay={750}
|
||||||
|
allowOverflow={false}
|
||||||
|
spacing={8}
|
||||||
|
hideOnClick={true}
|
||||||
|
tooltipClassName="timezone-tooltip"
|
||||||
|
text={longTime}
|
||||||
|
>
|
||||||
|
{toolTipProps => (
|
||||||
|
<span
|
||||||
|
{...toolTipProps}
|
||||||
|
className={type === "message" ? `timezone-message-item ${classes.timestamp}` : "timezone-profile-item"}
|
||||||
|
>
|
||||||
|
{type === "message" ? `(${shortTime})` : shortTime}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}, { noop: true });
|
||||||
|
|
||||||
|
const userContextMenuPatch: NavContextMenuPatchCallback = (children, { user }: { user: User; }) => {
|
||||||
|
if (user?.id == null) return;
|
||||||
|
|
||||||
|
const setTimezoneItem = (
|
||||||
|
<Menu.MenuItem
|
||||||
|
label="Set Local Timezone"
|
||||||
|
id="set-timezone"
|
||||||
|
action={() => openModal(modalProps => <SetTimezoneModal userId={user.id} modalProps={modalProps} />)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
children.push(<Menu.MenuSeparator />, setTimezoneItem);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "Timezones",
|
||||||
|
authors: [Devs.Aria, {
|
||||||
|
name: "creations",
|
||||||
|
id: 209830981060788225n,
|
||||||
|
}],
|
||||||
|
description: "Shows the local time of users in profiles and message headers",
|
||||||
|
contextMenus: {
|
||||||
|
"user-context": userContextMenuPatch
|
||||||
|
},
|
||||||
|
|
||||||
|
patches: [
|
||||||
|
// stolen from ViewIcons
|
||||||
|
{
|
||||||
|
find: 'backgroundColor:"COMPLETE"',
|
||||||
|
replacement: {
|
||||||
|
match: /(?<=backgroundImage.+?)children:\[/,
|
||||||
|
replace: "$&$self.renderProfileTimezone(arguments[0]),"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: '"Message Username"',
|
||||||
|
replacement: {
|
||||||
|
// thanks https://github.com/Syncxv/vc-timezones/pull/4
|
||||||
|
match: /(?<=isVisibleOnlyOnHover.+?)id:.{1,11},timestamp.{1,50}}\),/,
|
||||||
|
replace: "$&,$self.renderMessageTimezone(arguments[0]),"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
toolboxActions: {
|
||||||
|
"Set Database Timezone": () => {
|
||||||
|
authModal(async () => {
|
||||||
|
openModal(modalProps => <SetTimezoneModal userId={UserStore.getCurrentUser().id} modalProps={modalProps} database={true} />);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
"Refresh Database Timezones": async () => {
|
||||||
|
try {
|
||||||
|
const good = await loadDatabaseTimezones();
|
||||||
|
|
||||||
|
if (good) {
|
||||||
|
showToast("Timezones refreshed successfully!", Toasts.Type.SUCCESS);
|
||||||
|
} else {
|
||||||
|
showToast("Timezones Failed to refresh!", Toasts.Type.FAILURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error("Failed to refresh timezone:", error);
|
||||||
|
showToast("Failed to refresh timezones.", Toasts.Type.FAILURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async start() {
|
||||||
|
timezones = await DataStore.get<Record<string, string>>(DATASTORE_KEY) || {};
|
||||||
|
|
||||||
|
if (settings.store.useDatabase) {
|
||||||
|
await loadDatabaseTimezones();
|
||||||
|
|
||||||
|
if (!settings.store.askedTimezone) {
|
||||||
|
showToast(
|
||||||
|
"",
|
||||||
|
Toasts.Type.MESSAGE,
|
||||||
|
{
|
||||||
|
duration: 10000,
|
||||||
|
component: (
|
||||||
|
<Button
|
||||||
|
color={Button.Colors.GREEN}
|
||||||
|
onClick={() => {
|
||||||
|
authModal(async () => {
|
||||||
|
openModal(modalProps => <SetTimezoneModal userId={UserStore.getCurrentUser().id} modalProps={modalProps} database={true} />);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Want to save your timezone to the database? Click here to set it.
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
position: Toasts.Position.BOTTOM
|
||||||
|
}
|
||||||
|
);
|
||||||
|
settings.store.askedTimezone = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
settings,
|
||||||
|
getTime,
|
||||||
|
|
||||||
|
renderProfileTimezone: (props?: { user?: User; }) => {
|
||||||
|
if (!settings.store.showProfileTime || !props?.user?.id) return null;
|
||||||
|
if (props.user.id === UserStore.getCurrentUser().id && !settings.store["Show Own Timezone"]) return null;
|
||||||
|
|
||||||
|
return <TimestampComponent userId={props.user.id} type="profile" />;
|
||||||
|
},
|
||||||
|
|
||||||
|
renderMessageTimezone: (props?: { message?: Message; }) => {
|
||||||
|
if (!settings.store.showMessageHeaderTime || !props?.message) return null;
|
||||||
|
if (props.message.author.id === UserStore.getCurrentUser().id && !settings.store["Show Own Timezone"]) return null;
|
||||||
|
|
||||||
|
return <TimestampComponent userId={props.message.author.id} timestamp={props.message.timestamp.toISOString()} type="message" />;
|
||||||
|
}
|
||||||
|
});
|
41
styles.css
Normal file
41
styles.css
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
.timezone-profile-item {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin: 28px 16px 4px;
|
||||||
|
background: var(--profile-body-background-color, var(--background-primary));
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
[class*="topSection"] .timezone-profile-item {
|
||||||
|
margin: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timezone-message-item {
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-timezone-modal-header {
|
||||||
|
place-content: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-timezone-modal-header h1 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-timezone-modal-content {
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-timezone-modal-footer {
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timezone-tooltip {
|
||||||
|
max-width: none!important;
|
||||||
|
white-space: nowrap
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue