feat(settings): drop user-facing time/date format toggle and derive everything from system locale via Intl.DateTimeFormat
This commit is contained in:
parent
a1ff5db724
commit
e547c466a8
19 changed files with 81 additions and 613 deletions
|
|
@ -148,7 +148,7 @@ These are vojo additions on top of stock Cinny — they crossed many recent stab
|
||||||
|
|
||||||
**Jotai** atoms in `src/app/state/`:
|
**Jotai** atoms in `src/app/state/`:
|
||||||
|
|
||||||
- `settings.ts` — User preferences (`themeId`, `useSystemTheme`, `monochromeMode`, `hideMembershipEvents`, `hideNickAvatarEvents`, …). Persisted to `localStorage['settings']`. The `MessageLayout` enum + `messageSpacing` + `legacyUsernameColor` fields were dropped in P3c; the new `dawn-p3c-cleanup` migration in `getSettings()` strips the orphan keys from existing users' persisted JSON on first load.
|
- `settings.ts` — User preferences (`themeId`, `useSystemTheme`, `monochromeMode`, `hideMembershipEvents`, `hideNickAvatarEvents`, …). Persisted to `localStorage['settings']`. The `MessageLayout` enum + `messageSpacing` + `legacyUsernameColor` fields were dropped in P3c; the new `dawn-p3c-cleanup` migration in `getSettings()` strips the orphan keys from existing users' persisted JSON on first load. The user-facing `hour24Clock` / `dateFormatString` fields were removed too — both now derive from the runtime locale via `Intl.DateTimeFormat` in `utils/time.ts` (24-hour locales → `HH:mm` + `DD/MM/YYYY`; AM/PM locales → `hh:mm A` + `MM/DD/YYYY`). The `system-time-format-cleanup` migration synchronously deletes those keys on first load. **Known platform limitation**: Android's manual «Use 24-hour format» toggle in Date & Time settings is invisible to JS — `Intl` reads only CLDR locale conventions. Russian-locale users with AM/PM toggle get 24-hour format on both web and Capacitor; only a native bridge to `android.text.format.DateFormat.is24HourFormat(context)` would respect that toggle.
|
||||||
- `sessions.ts` — Active session
|
- `sessions.ts` — Active session
|
||||||
- `upload.ts` — Upload progress (in-memory)
|
- `upload.ts` — Upload progress (in-memory)
|
||||||
- `room/` — `roomInputDrafts` (in-memory), `roomToParents`, `roomToUnread`
|
- `room/` — `roomInputDrafts` (in-memory), `roomToParents`, `roomToUnread`
|
||||||
|
|
|
||||||
|
|
@ -99,27 +99,6 @@
|
||||||
"monochrome_mode": "Monochrome Mode",
|
"monochrome_mode": "Monochrome Mode",
|
||||||
"twitter_emoji": "Twitter Emoji",
|
"twitter_emoji": "Twitter Emoji",
|
||||||
"page_zoom": "Page Zoom",
|
"page_zoom": "Page Zoom",
|
||||||
"date_time": "Date & Time",
|
|
||||||
"hour_24": "24-Hour Time Format",
|
|
||||||
"date_format": "Date Format",
|
|
||||||
"custom": "Custom",
|
|
||||||
"formatting": "Formatting",
|
|
||||||
"year": "Year",
|
|
||||||
"two_digit_year": "Two-digit year",
|
|
||||||
"four_digit_year": "Four-digit year",
|
|
||||||
"month": "Month",
|
|
||||||
"the_month": "The month",
|
|
||||||
"two_digit_month": "Two-digit month",
|
|
||||||
"short_month_name": "Short month name",
|
|
||||||
"full_month_name": "Full month name",
|
|
||||||
"day_of_month": "Day of the Month",
|
|
||||||
"day_of_month_val": "Day of the month",
|
|
||||||
"two_digit_day": "Two-digit day of the month",
|
|
||||||
"day_of_week": "Day of the Week",
|
|
||||||
"day_of_week_sunday": "Day of the week (Sunday = 0)",
|
|
||||||
"two_letter_day": "Two-letter day name",
|
|
||||||
"short_day_name": "Short day name",
|
|
||||||
"full_day_name": "Full day name",
|
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"editor": "Editor",
|
"editor": "Editor",
|
||||||
"enter_newline": "ENTER for Newline",
|
"enter_newline": "ENTER for Newline",
|
||||||
|
|
|
||||||
|
|
@ -99,27 +99,6 @@
|
||||||
"monochrome_mode": "Монохромный режим",
|
"monochrome_mode": "Монохромный режим",
|
||||||
"twitter_emoji": "Эмодзи Twitter",
|
"twitter_emoji": "Эмодзи Twitter",
|
||||||
"page_zoom": "Масштаб страницы",
|
"page_zoom": "Масштаб страницы",
|
||||||
"date_time": "Дата и время",
|
|
||||||
"hour_24": "24-часовой формат",
|
|
||||||
"date_format": "Формат даты",
|
|
||||||
"custom": "Пользовательский",
|
|
||||||
"formatting": "Форматирование",
|
|
||||||
"year": "Год",
|
|
||||||
"two_digit_year": "Двузначный год",
|
|
||||||
"four_digit_year": "Четырёхзначный год",
|
|
||||||
"month": "Месяц",
|
|
||||||
"the_month": "Месяц",
|
|
||||||
"two_digit_month": "Двузначный месяц",
|
|
||||||
"short_month_name": "Краткое название месяца",
|
|
||||||
"full_month_name": "Полное название месяца",
|
|
||||||
"day_of_month": "День месяца",
|
|
||||||
"day_of_month_val": "День месяца",
|
|
||||||
"two_digit_day": "Двузначный день месяца",
|
|
||||||
"day_of_week": "День недели",
|
|
||||||
"day_of_week_sunday": "День недели (воскресенье = 0)",
|
|
||||||
"two_letter_day": "Двухбуквенное название дня",
|
|
||||||
"short_day_name": "Краткое название дня",
|
|
||||||
"full_day_name": "Полное название дня",
|
|
||||||
"save": "Сохранить",
|
"save": "Сохранить",
|
||||||
"editor": "Редактор",
|
"editor": "Редактор",
|
||||||
"enter_newline": "ENTER для новой строки",
|
"enter_newline": "ENTER для новой строки",
|
||||||
|
|
|
||||||
|
|
@ -6,26 +6,17 @@ import { timeDayMonYear, timeHourMinute, today, yesterday } from '../../utils/ti
|
||||||
export type TimeProps = {
|
export type TimeProps = {
|
||||||
compact?: boolean;
|
compact?: boolean;
|
||||||
ts: number;
|
ts: number;
|
||||||
hour24Clock: boolean;
|
|
||||||
dateFormatString: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders a formatted timestamp, supporting compact and full display modes.
|
* Renders a formatted timestamp using the system locale's hour and date format.
|
||||||
*
|
*
|
||||||
* Displays the time in hour:minute format if the message is from today, yesterday, or if `compact` is true.
|
* Today/yesterday/`compact` show time only; older messages show date + time.
|
||||||
* For older messages, it shows the date and time.
|
|
||||||
*
|
|
||||||
* @param {number} ts - The timestamp to display.
|
|
||||||
* @param {boolean} [compact=false] - If true, always show only the time.
|
|
||||||
* @param {boolean} hour24Clock - Whether to use 24-hour time format.
|
|
||||||
* @param {string} dateFormatString - Format string for the date part.
|
|
||||||
* @returns {React.ReactElement} A <Text as="time"> element with the formatted date/time.
|
|
||||||
*/
|
*/
|
||||||
export const Time = as<'span', TimeProps & ComponentProps<typeof Text>>(
|
export const Time = as<'span', TimeProps & ComponentProps<typeof Text>>(
|
||||||
({ compact, hour24Clock, dateFormatString, ts, ...props }, ref) => {
|
({ compact, ts, ...props }, ref) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const formattedTime = timeHourMinute(ts, hour24Clock);
|
const formattedTime = timeHourMinute(ts);
|
||||||
|
|
||||||
let time = '';
|
let time = '';
|
||||||
if (compact) {
|
if (compact) {
|
||||||
|
|
@ -35,7 +26,7 @@ export const Time = as<'span', TimeProps & ComponentProps<typeof Text>>(
|
||||||
} else if (yesterday(ts)) {
|
} else if (yesterday(ts)) {
|
||||||
time = `${t('Room.yesterday')} ${formattedTime}`;
|
time = `${t('Room.yesterday')} ${formattedTime}`;
|
||||||
} else {
|
} else {
|
||||||
time = `${timeDayMonYear(ts, dateFormatString)} ${formattedTime}`;
|
time = `${timeDayMonYear(ts)} ${formattedTime}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,6 @@ import { nameInitials } from '../../utils/common';
|
||||||
import { useRoomAvatar, useRoomName, useRoomTopic } from '../../hooks/useRoomMeta';
|
import { useRoomAvatar, useRoomName, useRoomTopic } from '../../hooks/useRoomMeta';
|
||||||
import { useIsOneOnOne } from '../../hooks/useRoom';
|
import { useIsOneOnOne } from '../../hooks/useRoom';
|
||||||
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||||
import { useSetting } from '../../state/hooks/settings';
|
|
||||||
import { settingsAtom } from '../../state/settings';
|
|
||||||
import { InviteUserPrompt } from '../invite-user-prompt';
|
import { InviteUserPrompt } from '../invite-user-prompt';
|
||||||
|
|
||||||
export type RoomIntroProps = {
|
export type RoomIntroProps = {
|
||||||
|
|
@ -51,8 +49,6 @@ export const RoomIntro = as<'div', RoomIntroProps>(({ room, ...props }, ref) =>
|
||||||
useCallback(async (roomId: string) => mx.joinRoom(roomId), [mx])
|
useCallback(async (roomId: string) => mx.joinRoom(roomId), [mx])
|
||||||
);
|
);
|
||||||
|
|
||||||
const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box direction="Column" grow="Yes" gap="500" {...props} ref={ref}>
|
<Box direction="Column" grow="Yes" gap="500" {...props} ref={ref}>
|
||||||
<Box>
|
<Box>
|
||||||
|
|
@ -80,7 +76,7 @@ export const RoomIntro = as<'div', RoomIntroProps>(({ room, ...props }, ref) =>
|
||||||
values={{
|
values={{
|
||||||
creator: creatorName,
|
creator: creatorName,
|
||||||
date: timeDayMonthYear(ts),
|
date: timeDayMonthYear(ts),
|
||||||
time: timeHourMinute(ts, hour24Clock),
|
time: timeHourMinute(ts),
|
||||||
}}
|
}}
|
||||||
components={{ bold: <b /> }}
|
components={{ bold: <b /> }}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,14 @@ import { Menu, Box, Text, Chip } from 'folds';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import * as css from './styles.css';
|
import * as css from './styles.css';
|
||||||
import { PickerColumn } from './PickerColumn';
|
import { PickerColumn } from './PickerColumn';
|
||||||
import { hour12to24, hour24to12, hoursToMs, inSameDay, minutesToMs } from '../../utils/time';
|
import {
|
||||||
import { useSetting } from '../../state/hooks/settings';
|
SYSTEM_HOUR_24,
|
||||||
import { settingsAtom } from '../../state/settings';
|
hour12to24,
|
||||||
|
hour24to12,
|
||||||
|
hoursToMs,
|
||||||
|
inSameDay,
|
||||||
|
minutesToMs,
|
||||||
|
} from '../../utils/time';
|
||||||
|
|
||||||
type TimePickerProps = {
|
type TimePickerProps = {
|
||||||
min: number;
|
min: number;
|
||||||
|
|
@ -15,11 +20,9 @@ type TimePickerProps = {
|
||||||
};
|
};
|
||||||
export const TimePicker = forwardRef<HTMLDivElement, TimePickerProps>(
|
export const TimePicker = forwardRef<HTMLDivElement, TimePickerProps>(
|
||||||
({ min, max, value, onChange }, ref) => {
|
({ min, max, value, onChange }, ref) => {
|
||||||
const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
|
|
||||||
|
|
||||||
const hour24 = dayjs(value).hour();
|
const hour24 = dayjs(value).hour();
|
||||||
|
|
||||||
const selectedHour = hour24Clock ? hour24 : hour24to12(hour24);
|
const selectedHour = SYSTEM_HOUR_24 ? hour24 : hour24to12(hour24);
|
||||||
const selectedMinute = dayjs(value).minute();
|
const selectedMinute = dayjs(value).minute();
|
||||||
const selectedPM = hour24 >= 12;
|
const selectedPM = hour24 >= 12;
|
||||||
|
|
||||||
|
|
@ -28,7 +31,7 @@ export const TimePicker = forwardRef<HTMLDivElement, TimePickerProps>(
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleHour = (hour: number) => {
|
const handleHour = (hour: number) => {
|
||||||
const seconds = hoursToMs(hour24Clock ? hour : hour12to24(hour, selectedPM));
|
const seconds = hoursToMs(SYSTEM_HOUR_24 ? hour : hour12to24(hour, selectedPM));
|
||||||
const lastSeconds = hoursToMs(hour24);
|
const lastSeconds = hoursToMs(hour24);
|
||||||
const newValue = value + (seconds - lastSeconds);
|
const newValue = value + (seconds - lastSeconds);
|
||||||
handleSubmit(newValue);
|
handleSubmit(newValue);
|
||||||
|
|
@ -63,7 +66,7 @@ export const TimePicker = forwardRef<HTMLDivElement, TimePickerProps>(
|
||||||
<Menu className={css.PickerMenu} ref={ref}>
|
<Menu className={css.PickerMenu} ref={ref}>
|
||||||
<Box direction="Row" gap="200" className={css.PickerContainer}>
|
<Box direction="Row" gap="200" className={css.PickerContainer}>
|
||||||
<PickerColumn title="Hour">
|
<PickerColumn title="Hour">
|
||||||
{hour24Clock
|
{SYSTEM_HOUR_24
|
||||||
? Array.from(Array(24).keys()).map((hour) => (
|
? Array.from(Array(24).keys()).map((hour) => (
|
||||||
<Chip
|
<Chip
|
||||||
key={hour}
|
key={hour}
|
||||||
|
|
@ -120,7 +123,7 @@ export const TimePicker = forwardRef<HTMLDivElement, TimePickerProps>(
|
||||||
</Chip>
|
</Chip>
|
||||||
))}
|
))}
|
||||||
</PickerColumn>
|
</PickerColumn>
|
||||||
{!hour24Clock && (
|
{!SYSTEM_HOUR_24 && (
|
||||||
<PickerColumn title="Period">
|
<PickerColumn title="Period">
|
||||||
<Chip
|
<Chip
|
||||||
size="500"
|
size="500"
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,6 @@ import { SettingTile } from '../setting-tile';
|
||||||
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
|
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
|
||||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||||
import { BreakWord } from '../../styles/Text.css';
|
import { BreakWord } from '../../styles/Text.css';
|
||||||
import { useSetting } from '../../state/hooks/settings';
|
|
||||||
import { settingsAtom } from '../../state/settings';
|
|
||||||
import { timeDayMonYear, timeHourMinute } from '../../utils/time';
|
import { timeDayMonYear, timeHourMinute } from '../../utils/time';
|
||||||
|
|
||||||
type UserKickAlertProps = {
|
type UserKickAlertProps = {
|
||||||
|
|
@ -16,11 +14,8 @@ type UserKickAlertProps = {
|
||||||
ts?: number;
|
ts?: number;
|
||||||
};
|
};
|
||||||
export function UserKickAlert({ reason, kickedBy, ts }: UserKickAlertProps) {
|
export function UserKickAlert({ reason, kickedBy, ts }: UserKickAlertProps) {
|
||||||
const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
|
const time = ts ? timeHourMinute(ts) : undefined;
|
||||||
const [dateFormatString] = useSetting(settingsAtom, 'dateFormatString');
|
const date = ts ? timeDayMonYear(ts) : undefined;
|
||||||
|
|
||||||
const time = ts ? timeHourMinute(ts, hour24Clock) : undefined;
|
|
||||||
const date = ts ? timeDayMonYear(ts, dateFormatString) : undefined;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CutoutCard style={{ padding: config.space.S200 }} variant="Critical">
|
<CutoutCard style={{ padding: config.space.S200 }} variant="Critical">
|
||||||
|
|
@ -66,11 +61,8 @@ type UserBanAlertProps = {
|
||||||
export function UserBanAlert({ userId, reason, canUnban, bannedBy, ts }: UserBanAlertProps) {
|
export function UserBanAlert({ userId, reason, canUnban, bannedBy, ts }: UserBanAlertProps) {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
const room = useRoom();
|
const room = useRoom();
|
||||||
const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
|
const time = ts ? timeHourMinute(ts) : undefined;
|
||||||
const [dateFormatString] = useSetting(settingsAtom, 'dateFormatString');
|
const date = ts ? timeDayMonYear(ts) : undefined;
|
||||||
|
|
||||||
const time = ts ? timeHourMinute(ts, hour24Clock) : undefined;
|
|
||||||
const date = ts ? timeDayMonYear(ts, dateFormatString) : undefined;
|
|
||||||
|
|
||||||
const [unbanState, unban] = useAsyncCallback<undefined, Error, []>(
|
const [unbanState, unban] = useAsyncCallback<undefined, Error, []>(
|
||||||
useCallback(async () => {
|
useCallback(async () => {
|
||||||
|
|
@ -141,11 +133,8 @@ type UserInviteAlertProps = {
|
||||||
export function UserInviteAlert({ userId, reason, canKick, invitedBy, ts }: UserInviteAlertProps) {
|
export function UserInviteAlert({ userId, reason, canKick, invitedBy, ts }: UserInviteAlertProps) {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
const room = useRoom();
|
const room = useRoom();
|
||||||
const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
|
const time = ts ? timeHourMinute(ts) : undefined;
|
||||||
const [dateFormatString] = useSetting(settingsAtom, 'dateFormatString');
|
const date = ts ? timeDayMonYear(ts) : undefined;
|
||||||
|
|
||||||
const time = ts ? timeHourMinute(ts, hour24Clock) : undefined;
|
|
||||||
const date = ts ? timeDayMonYear(ts, dateFormatString) : undefined;
|
|
||||||
|
|
||||||
const [kickState, kick] = useAsyncCallback<undefined, Error, []>(
|
const [kickState, kick] = useAsyncCallback<undefined, Error, []>(
|
||||||
useCallback(async () => {
|
useCallback(async () => {
|
||||||
|
|
|
||||||
|
|
@ -60,9 +60,6 @@ export function MessageSearch({
|
||||||
const [mediaAutoLoad] = useSetting(settingsAtom, 'mediaAutoLoad');
|
const [mediaAutoLoad] = useSetting(settingsAtom, 'mediaAutoLoad');
|
||||||
const [urlPreview] = useSetting(settingsAtom, 'urlPreview');
|
const [urlPreview] = useSetting(settingsAtom, 'urlPreview');
|
||||||
|
|
||||||
const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
|
|
||||||
const [dateFormatString] = useSetting(settingsAtom, 'dateFormatString');
|
|
||||||
|
|
||||||
const searchInputRef = useRef<HTMLInputElement>(null);
|
const searchInputRef = useRef<HTMLInputElement>(null);
|
||||||
const scrollTopAnchorRef = useRef<HTMLDivElement>(null);
|
const scrollTopAnchorRef = useRef<HTMLDivElement>(null);
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
|
@ -299,8 +296,6 @@ export function MessageSearch({
|
||||||
urlPreview={urlPreview}
|
urlPreview={urlPreview}
|
||||||
onOpen={navigateRoom}
|
onOpen={navigateRoom}
|
||||||
legacyUsernameColor={isOneOnOneRoom(groupRoom)}
|
legacyUsernameColor={isOneOnOneRoom(groupRoom)}
|
||||||
hour24Clock={hour24Clock}
|
|
||||||
dateFormatString={dateFormatString}
|
|
||||||
/>
|
/>
|
||||||
</VirtualTile>
|
</VirtualTile>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -61,8 +61,6 @@ type SearchResultGroupProps = {
|
||||||
urlPreview?: boolean;
|
urlPreview?: boolean;
|
||||||
onOpen: (roomId: string, eventId: string) => void;
|
onOpen: (roomId: string, eventId: string) => void;
|
||||||
legacyUsernameColor?: boolean;
|
legacyUsernameColor?: boolean;
|
||||||
hour24Clock: boolean;
|
|
||||||
dateFormatString: string;
|
|
||||||
};
|
};
|
||||||
export function SearchResultGroup({
|
export function SearchResultGroup({
|
||||||
room,
|
room,
|
||||||
|
|
@ -72,8 +70,6 @@ export function SearchResultGroup({
|
||||||
urlPreview,
|
urlPreview,
|
||||||
onOpen,
|
onOpen,
|
||||||
legacyUsernameColor,
|
legacyUsernameColor,
|
||||||
hour24Clock,
|
|
||||||
dateFormatString,
|
|
||||||
}: SearchResultGroupProps) {
|
}: SearchResultGroupProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
|
@ -292,11 +288,7 @@ export function SearchResultGroup({
|
||||||
</Username>
|
</Username>
|
||||||
{tagIconSrc && <PowerIcon size="100" iconSrc={tagIconSrc} />}
|
{tagIconSrc && <PowerIcon size="100" iconSrc={tagIconSrc} />}
|
||||||
</Box>
|
</Box>
|
||||||
<Time
|
<Time ts={event.origin_server_ts} />
|
||||||
ts={event.origin_server_ts}
|
|
||||||
hour24Clock={hour24Clock}
|
|
||||||
dateFormatString={dateFormatString}
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Box shrink="No" gap="200" alignItems="Center">
|
<Box shrink="No" gap="200" alignItems="Center">
|
||||||
<Chip
|
<Chip
|
||||||
|
|
|
||||||
|
|
@ -260,7 +260,7 @@ const deriveRoomPreview = (room: Room): RoomPreview => {
|
||||||
return { ts: room.getLastActiveTimestamp() || undefined, text: '' };
|
return { ts: room.getLastActiveTimestamp() || undefined, text: '' };
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatRowTime = (ts: number, hour24Clock: boolean): string => {
|
const formatRowTime = (ts: number): string => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const diff = now - ts;
|
const diff = now - ts;
|
||||||
const day = 24 * 60 * 60 * 1000;
|
const day = 24 * 60 * 60 * 1000;
|
||||||
|
|
@ -270,7 +270,7 @@ const formatRowTime = (ts: number, hour24Clock: boolean): string => {
|
||||||
date.getFullYear() === today.getFullYear() &&
|
date.getFullYear() === today.getFullYear() &&
|
||||||
date.getMonth() === today.getMonth() &&
|
date.getMonth() === today.getMonth() &&
|
||||||
date.getDate() === today.getDate();
|
date.getDate() === today.getDate();
|
||||||
if (sameDay) return timeHourMinute(ts, hour24Clock);
|
if (sameDay) return timeHourMinute(ts);
|
||||||
if (diff < 7 * day) {
|
if (diff < 7 * day) {
|
||||||
return date.toLocaleDateString(undefined, { weekday: 'short' });
|
return date.toLocaleDateString(undefined, { weekday: 'short' });
|
||||||
}
|
}
|
||||||
|
|
@ -287,7 +287,6 @@ export function DmStreamRow({ room, selected, notificationMode, linkPath }: DmSt
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
const useAuthentication = useMediaAuthentication();
|
const useAuthentication = useMediaAuthentication();
|
||||||
const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
|
|
||||||
const [hover, setHover] = useState(false);
|
const [hover, setHover] = useState(false);
|
||||||
const { hoverProps } = useHover({ onHoverChange: setHover });
|
const { hoverProps } = useHover({ onHoverChange: setHover });
|
||||||
const { focusWithinProps } = useFocusWithin({ onFocusWithinChange: setHover });
|
const { focusWithinProps } = useFocusWithin({ onFocusWithinChange: setHover });
|
||||||
|
|
@ -302,7 +301,7 @@ export function DmStreamRow({ room, selected, notificationMode, linkPath }: DmSt
|
||||||
const preview = deriveRoomPreview(room);
|
const preview = deriveRoomPreview(room);
|
||||||
const previewText = preview.text;
|
const previewText = preview.text;
|
||||||
const previewTs = preview.ts;
|
const previewTs = preview.ts;
|
||||||
const timeLabel = previewTs ? formatRowTime(previewTs, hour24Clock) : '';
|
const timeLabel = previewTs ? formatRowTime(previewTs) : '';
|
||||||
|
|
||||||
const handleContextMenu: MouseEventHandler<HTMLElement> = (evt) => {
|
const handleContextMenu: MouseEventHandler<HTMLElement> = (evt) => {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
|
|
|
||||||
|
|
@ -435,9 +435,6 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||||
const [showHiddenEvents] = useSetting(settingsAtom, 'showHiddenEvents');
|
const [showHiddenEvents] = useSetting(settingsAtom, 'showHiddenEvents');
|
||||||
const [showDeveloperTools] = useSetting(settingsAtom, 'developerTools');
|
const [showDeveloperTools] = useSetting(settingsAtom, 'developerTools');
|
||||||
|
|
||||||
const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
|
|
||||||
const [dateFormatString] = useSetting(settingsAtom, 'dateFormatString');
|
|
||||||
|
|
||||||
const ignoredUsersList = useIgnoredUsers();
|
const ignoredUsersList = useIgnoredUsers();
|
||||||
const ignoredUsersSet = useMemo(() => new Set(ignoredUsersList), [ignoredUsersList]);
|
const ignoredUsersSet = useMemo(() => new Set(ignoredUsersList), [ignoredUsersList]);
|
||||||
|
|
||||||
|
|
@ -1133,8 +1130,6 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||||
memberPowerTag={getMemberPowerTag(senderId)}
|
memberPowerTag={getMemberPowerTag(senderId)}
|
||||||
accessibleTagColors={accessiblePowerTagColors}
|
accessibleTagColors={accessiblePowerTagColors}
|
||||||
legacyUsernameColor={isOneOnOne}
|
legacyUsernameColor={isOneOnOne}
|
||||||
hour24Clock={hour24Clock}
|
|
||||||
dateFormatString={dateFormatString}
|
|
||||||
streamRailStart={streamRailStart}
|
streamRailStart={streamRailStart}
|
||||||
streamRailEnd={streamRailEnd}
|
streamRailEnd={streamRailEnd}
|
||||||
>
|
>
|
||||||
|
|
@ -1235,8 +1230,6 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||||
memberPowerTag={getMemberPowerTag(mEvent.getSender() ?? '')}
|
memberPowerTag={getMemberPowerTag(mEvent.getSender() ?? '')}
|
||||||
accessibleTagColors={accessiblePowerTagColors}
|
accessibleTagColors={accessiblePowerTagColors}
|
||||||
legacyUsernameColor={isOneOnOne}
|
legacyUsernameColor={isOneOnOne}
|
||||||
hour24Clock={hour24Clock}
|
|
||||||
dateFormatString={dateFormatString}
|
|
||||||
streamRailStart={streamRailStart}
|
streamRailStart={streamRailStart}
|
||||||
streamRailEnd={streamRailEnd}
|
streamRailEnd={streamRailEnd}
|
||||||
>
|
>
|
||||||
|
|
@ -1349,8 +1342,6 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||||
memberPowerTag={getMemberPowerTag(mEvent.getSender() ?? '')}
|
memberPowerTag={getMemberPowerTag(mEvent.getSender() ?? '')}
|
||||||
accessibleTagColors={accessiblePowerTagColors}
|
accessibleTagColors={accessiblePowerTagColors}
|
||||||
legacyUsernameColor={isOneOnOne}
|
legacyUsernameColor={isOneOnOne}
|
||||||
hour24Clock={hour24Clock}
|
|
||||||
dateFormatString={dateFormatString}
|
|
||||||
streamRailStart={streamRailStart}
|
streamRailStart={streamRailStart}
|
||||||
streamRailEnd={streamRailEnd}
|
streamRailEnd={streamRailEnd}
|
||||||
>
|
>
|
||||||
|
|
@ -1397,8 +1388,6 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||||
<Time
|
<Time
|
||||||
ts={mEvent.getTs()}
|
ts={mEvent.getTs()}
|
||||||
compact
|
compact
|
||||||
hour24Clock={hour24Clock}
|
|
||||||
dateFormatString={dateFormatString}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -1447,8 +1436,6 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||||
<Time
|
<Time
|
||||||
ts={mEvent.getTs()}
|
ts={mEvent.getTs()}
|
||||||
compact
|
compact
|
||||||
hour24Clock={hour24Clock}
|
|
||||||
dateFormatString={dateFormatString}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -1498,8 +1485,6 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||||
<Time
|
<Time
|
||||||
ts={mEvent.getTs()}
|
ts={mEvent.getTs()}
|
||||||
compact
|
compact
|
||||||
hour24Clock={hour24Clock}
|
|
||||||
dateFormatString={dateFormatString}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -1549,8 +1534,6 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||||
<Time
|
<Time
|
||||||
ts={mEvent.getTs()}
|
ts={mEvent.getTs()}
|
||||||
compact
|
compact
|
||||||
hour24Clock={hour24Clock}
|
|
||||||
dateFormatString={dateFormatString}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -1608,8 +1591,6 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||||
<Time
|
<Time
|
||||||
ts={mEvent.getTs()}
|
ts={mEvent.getTs()}
|
||||||
compact
|
compact
|
||||||
hour24Clock={hour24Clock}
|
|
||||||
dateFormatString={dateFormatString}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -1656,8 +1637,6 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||||
<Time
|
<Time
|
||||||
ts={mEvent.getTs()}
|
ts={mEvent.getTs()}
|
||||||
compact
|
compact
|
||||||
hour24Clock={hour24Clock}
|
|
||||||
dateFormatString={dateFormatString}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -1706,8 +1685,6 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||||
<Time
|
<Time
|
||||||
ts={mEvent.getTs()}
|
ts={mEvent.getTs()}
|
||||||
compact
|
compact
|
||||||
hour24Clock={hour24Clock}
|
|
||||||
dateFormatString={dateFormatString}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,6 @@ import { useRoom } from '../../../hooks/useRoom';
|
||||||
import { StateEvent } from '../../../../types/matrix/room';
|
import { StateEvent } from '../../../../types/matrix/room';
|
||||||
import { getToday, getYesterday, timeDayMonthYear, timeHourMinute } from '../../../utils/time';
|
import { getToday, getYesterday, timeDayMonthYear, timeHourMinute } from '../../../utils/time';
|
||||||
import { DatePicker, TimePicker } from '../../../components/time-date';
|
import { DatePicker, TimePicker } from '../../../components/time-date';
|
||||||
import { useSetting } from '../../../state/hooks/settings';
|
|
||||||
import { settingsAtom } from '../../../state/settings';
|
|
||||||
|
|
||||||
type JumpToTimeProps = {
|
type JumpToTimeProps = {
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
|
|
@ -49,8 +47,6 @@ export function JumpToTime({ onCancel, onSubmit }: JumpToTimeProps) {
|
||||||
const createTs = useMemo(() => createStateEvent?.getTs() ?? 0, [createStateEvent]);
|
const createTs = useMemo(() => createStateEvent?.getTs() ?? 0, [createStateEvent]);
|
||||||
const [ts, setTs] = useState(() => Date.now());
|
const [ts, setTs] = useState(() => Date.now());
|
||||||
|
|
||||||
const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
|
|
||||||
|
|
||||||
const [timePickerCords, setTimePickerCords] = useState<RectCords>();
|
const [timePickerCords, setTimePickerCords] = useState<RectCords>();
|
||||||
const [datePickerCords, setDatePickerCords] = useState<RectCords>();
|
const [datePickerCords, setDatePickerCords] = useState<RectCords>();
|
||||||
|
|
||||||
|
|
@ -131,7 +127,7 @@ export function JumpToTime({ onCancel, onSubmit }: JumpToTimeProps) {
|
||||||
after={<Icon size="50" src={Icons.ChevronBottom} />}
|
after={<Icon size="50" src={Icons.ChevronBottom} />}
|
||||||
onClick={handleTimePicker}
|
onClick={handleTimePicker}
|
||||||
>
|
>
|
||||||
<Text size="B300">{timeHourMinute(ts, hour24Clock)}</Text>
|
<Text size="B300">{timeHourMinute(ts)}</Text>
|
||||||
</Chip>
|
</Chip>
|
||||||
<PopOut
|
<PopOut
|
||||||
anchor={timePickerCords}
|
anchor={timePickerCords}
|
||||||
|
|
|
||||||
|
|
@ -668,8 +668,6 @@ export type MessageProps = {
|
||||||
memberPowerTag?: MemberPowerTag;
|
memberPowerTag?: MemberPowerTag;
|
||||||
accessibleTagColors?: Map<string, string>;
|
accessibleTagColors?: Map<string, string>;
|
||||||
legacyUsernameColor?: boolean;
|
legacyUsernameColor?: boolean;
|
||||||
hour24Clock: boolean;
|
|
||||||
dateFormatString: string;
|
|
||||||
streamRailStart?: boolean;
|
streamRailStart?: boolean;
|
||||||
streamRailEnd?: boolean;
|
streamRailEnd?: boolean;
|
||||||
};
|
};
|
||||||
|
|
@ -699,8 +697,6 @@ export const Message = as<'div', MessageProps>(
|
||||||
memberPowerTag,
|
memberPowerTag,
|
||||||
accessibleTagColors,
|
accessibleTagColors,
|
||||||
legacyUsernameColor,
|
legacyUsernameColor,
|
||||||
hour24Clock,
|
|
||||||
dateFormatString,
|
|
||||||
streamRailStart,
|
streamRailStart,
|
||||||
streamRailEnd,
|
streamRailEnd,
|
||||||
children,
|
children,
|
||||||
|
|
@ -1047,14 +1043,7 @@ export const Message = as<'div', MessageProps>(
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<StreamLayout
|
<StreamLayout
|
||||||
time={
|
time={<Time ts={mEvent.getTs()} compact />}
|
||||||
<Time
|
|
||||||
ts={mEvent.getTs()}
|
|
||||||
compact
|
|
||||||
hour24Clock={hour24Clock}
|
|
||||||
dateFormatString={dateFormatString}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
dotColor={dot.color}
|
dotColor={dot.color}
|
||||||
dotOpacity={dot.opacity}
|
dotOpacity={dot.opacity}
|
||||||
isOwn={isOwnMessage}
|
isOwn={isOwnMessage}
|
||||||
|
|
|
||||||
|
|
@ -97,8 +97,6 @@ type PinnedMessageProps = {
|
||||||
getMemberPowerTag: GetMemberPowerTag;
|
getMemberPowerTag: GetMemberPowerTag;
|
||||||
accessibleTagColors: Map<string, string>;
|
accessibleTagColors: Map<string, string>;
|
||||||
legacyUsernameColor: boolean;
|
legacyUsernameColor: boolean;
|
||||||
hour24Clock: boolean;
|
|
||||||
dateFormatString: string;
|
|
||||||
};
|
};
|
||||||
function PinnedMessage({
|
function PinnedMessage({
|
||||||
room,
|
room,
|
||||||
|
|
@ -109,8 +107,6 @@ function PinnedMessage({
|
||||||
getMemberPowerTag,
|
getMemberPowerTag,
|
||||||
accessibleTagColors,
|
accessibleTagColors,
|
||||||
legacyUsernameColor,
|
legacyUsernameColor,
|
||||||
hour24Clock,
|
|
||||||
dateFormatString,
|
|
||||||
}: PinnedMessageProps) {
|
}: PinnedMessageProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const pinnedEvent = useRoomEvent(room, eventId);
|
const pinnedEvent = useRoomEvent(room, eventId);
|
||||||
|
|
@ -221,11 +217,7 @@ function PinnedMessage({
|
||||||
</Username>
|
</Username>
|
||||||
{tagIconSrc && <PowerIcon size="100" iconSrc={tagIconSrc} />}
|
{tagIconSrc && <PowerIcon size="100" iconSrc={tagIconSrc} />}
|
||||||
</Box>
|
</Box>
|
||||||
<Time
|
<Time ts={pinnedEvent.getTs()} />
|
||||||
ts={pinnedEvent.getTs()}
|
|
||||||
hour24Clock={hour24Clock}
|
|
||||||
dateFormatString={dateFormatString}
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
{renderOptions()}
|
{renderOptions()}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
@ -279,9 +271,6 @@ export const RoomPinMenu = forwardRef<HTMLDivElement, RoomPinMenuProps>(
|
||||||
|
|
||||||
const isOneOnOne = useIsOneOnOne();
|
const isOneOnOne = useIsOneOnOne();
|
||||||
|
|
||||||
const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
|
|
||||||
const [dateFormatString] = useSetting(settingsAtom, 'dateFormatString');
|
|
||||||
|
|
||||||
const { navigateRoom } = useRoomNavigate();
|
const { navigateRoom } = useRoomNavigate();
|
||||||
const scrollRef = useRef<HTMLDivElement>(null);
|
const scrollRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
|
@ -499,8 +488,6 @@ export const RoomPinMenu = forwardRef<HTMLDivElement, RoomPinMenuProps>(
|
||||||
getMemberPowerTag={getMemberPowerTag}
|
getMemberPowerTag={getMemberPowerTag}
|
||||||
accessibleTagColors={accessibleTagColors}
|
accessibleTagColors={accessibleTagColors}
|
||||||
legacyUsernameColor={isOneOnOne}
|
legacyUsernameColor={isOneOnOne}
|
||||||
hour24Clock={hour24Clock}
|
|
||||||
dateFormatString={dateFormatString}
|
|
||||||
/>
|
/>
|
||||||
</SequenceCard>
|
</SequenceCard>
|
||||||
</VirtualTile>
|
</VirtualTile>
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,6 @@ import { SequenceCard } from '../../../components/sequence-card';
|
||||||
import { SequenceCardStyle } from '../styles.css';
|
import { SequenceCardStyle } from '../styles.css';
|
||||||
import { LogoutDialog } from '../../../components/LogoutDialog';
|
import { LogoutDialog } from '../../../components/LogoutDialog';
|
||||||
import { stopPropagation } from '../../../utils/keyboard';
|
import { stopPropagation } from '../../../utils/keyboard';
|
||||||
import { useSetting } from '../../../state/hooks/settings';
|
|
||||||
import { settingsAtom } from '../../../state/settings';
|
|
||||||
|
|
||||||
export function DeviceTilePlaceholder() {
|
export function DeviceTilePlaceholder() {
|
||||||
return (
|
return (
|
||||||
|
|
@ -45,8 +43,6 @@ export function DeviceTilePlaceholder() {
|
||||||
|
|
||||||
function DeviceActiveTime({ ts }: { ts: number }) {
|
function DeviceActiveTime({ ts }: { ts: number }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
|
|
||||||
const [dateFormatString] = useSetting(settingsAtom, 'dateFormatString');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Text className={BreakWord} size="T200">
|
<Text className={BreakWord} size="T200">
|
||||||
|
|
@ -56,8 +52,8 @@ function DeviceActiveTime({ ts }: { ts: number }) {
|
||||||
<>
|
<>
|
||||||
{today(ts) && t('Settings.today')}
|
{today(ts) && t('Settings.today')}
|
||||||
{yesterday(ts) && t('Settings.yesterday')}
|
{yesterday(ts) && t('Settings.yesterday')}
|
||||||
{!today(ts) && !yesterday(ts) && timeDayMonYear(ts, dateFormatString)}{' '}
|
{!today(ts) && !yesterday(ts) && timeDayMonYear(ts)}{' '}
|
||||||
{timeHourMinute(ts, hour24Clock)}
|
{timeHourMinute(ts)}
|
||||||
</>
|
</>
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,10 @@
|
||||||
import React, {
|
import React, { ChangeEventHandler, KeyboardEventHandler, useState } from 'react';
|
||||||
ChangeEventHandler,
|
|
||||||
FormEventHandler,
|
|
||||||
KeyboardEventHandler,
|
|
||||||
MouseEventHandler,
|
|
||||||
useEffect,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
import dayjs from 'dayjs';
|
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
|
||||||
config,
|
|
||||||
Header,
|
|
||||||
Icon,
|
Icon,
|
||||||
IconButton,
|
IconButton,
|
||||||
Icons,
|
Icons,
|
||||||
Input,
|
Input,
|
||||||
Menu,
|
|
||||||
MenuItem,
|
|
||||||
PopOut,
|
|
||||||
RectCords,
|
|
||||||
Scroll,
|
Scroll,
|
||||||
Switch,
|
Switch,
|
||||||
Text,
|
Text,
|
||||||
|
|
@ -27,16 +12,13 @@ import {
|
||||||
} from 'folds';
|
} from 'folds';
|
||||||
import { isKeyHotkey } from 'is-hotkey';
|
import { isKeyHotkey } from 'is-hotkey';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import FocusTrap from 'focus-trap-react';
|
|
||||||
import { Page, PageContent, PageHeader } from '../../../components/page';
|
import { Page, PageContent, PageHeader } from '../../../components/page';
|
||||||
import { SequenceCard } from '../../../components/sequence-card';
|
import { SequenceCard } from '../../../components/sequence-card';
|
||||||
import { useSetting } from '../../../state/hooks/settings';
|
import { useSetting } from '../../../state/hooks/settings';
|
||||||
import { DateFormat, settingsAtom } from '../../../state/settings';
|
import { settingsAtom } from '../../../state/settings';
|
||||||
import { SettingTile } from '../../../components/setting-tile';
|
import { SettingTile } from '../../../components/setting-tile';
|
||||||
import { KeySymbol } from '../../../utils/key-symbol';
|
import { KeySymbol } from '../../../utils/key-symbol';
|
||||||
import { isMacOS } from '../../../utils/user-agent';
|
import { isMacOS } from '../../../utils/user-agent';
|
||||||
import { stopPropagation } from '../../../utils/keyboard';
|
|
||||||
import { useDateFormatItems } from '../../../hooks/useDateFormat';
|
|
||||||
import { SequenceCardStyle } from '../styles.css';
|
import { SequenceCardStyle } from '../styles.css';
|
||||||
|
|
||||||
function ThemeSelect() {
|
function ThemeSelect() {
|
||||||
|
|
@ -128,375 +110,6 @@ function Appearance() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
type DateHintProps = {
|
|
||||||
hasChanges: boolean;
|
|
||||||
handleReset: () => void;
|
|
||||||
};
|
|
||||||
function DateHint({ hasChanges, handleReset }: DateHintProps) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [anchor, setAnchor] = useState<RectCords>();
|
|
||||||
const categoryPadding = { padding: config.space.S200, paddingTop: 0 };
|
|
||||||
|
|
||||||
const handleOpenMenu: MouseEventHandler<HTMLElement> = (evt) => {
|
|
||||||
setAnchor(evt.currentTarget.getBoundingClientRect());
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<PopOut
|
|
||||||
anchor={anchor}
|
|
||||||
position="Top"
|
|
||||||
align="End"
|
|
||||||
content={
|
|
||||||
<FocusTrap
|
|
||||||
focusTrapOptions={{
|
|
||||||
initialFocus: false,
|
|
||||||
onDeactivate: () => setAnchor(undefined),
|
|
||||||
clickOutsideDeactivates: true,
|
|
||||||
escapeDeactivates: stopPropagation,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Menu style={{ maxHeight: '85vh', overflowY: 'auto' }}>
|
|
||||||
<Header size="300" style={{ padding: `0 ${config.space.S200}` }}>
|
|
||||||
<Text size="L400">{t('Settings.formatting')}</Text>
|
|
||||||
</Header>
|
|
||||||
|
|
||||||
<Box direction="Column">
|
|
||||||
<Box style={categoryPadding} direction="Column">
|
|
||||||
<Header size="300">
|
|
||||||
<Text size="L400">{t('Settings.year')}</Text>
|
|
||||||
</Header>
|
|
||||||
<Box direction="Column" tabIndex={0} gap="100">
|
|
||||||
<Text size="T300">
|
|
||||||
YY
|
|
||||||
<Text as="span" size="Inherit" priority="300">
|
|
||||||
{': '}
|
|
||||||
{t('Settings.two_digit_year')}
|
|
||||||
</Text>{' '}
|
|
||||||
</Text>
|
|
||||||
<Text size="T300">
|
|
||||||
YYYY
|
|
||||||
<Text as="span" size="Inherit" priority="300">
|
|
||||||
{': '}
|
|
||||||
{t('Settings.four_digit_year')}
|
|
||||||
</Text>
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box style={categoryPadding} direction="Column">
|
|
||||||
<Header size="300">
|
|
||||||
<Text size="L400">{t('Settings.month')}</Text>
|
|
||||||
</Header>
|
|
||||||
<Box direction="Column" tabIndex={0} gap="100">
|
|
||||||
<Text size="T300">
|
|
||||||
M
|
|
||||||
<Text as="span" size="Inherit" priority="300">
|
|
||||||
{': '}
|
|
||||||
{t('Settings.the_month')}
|
|
||||||
</Text>
|
|
||||||
</Text>
|
|
||||||
<Text size="T300">
|
|
||||||
MM
|
|
||||||
<Text as="span" size="Inherit" priority="300">
|
|
||||||
{': '}
|
|
||||||
{t('Settings.two_digit_month')}
|
|
||||||
</Text>{' '}
|
|
||||||
</Text>
|
|
||||||
<Text size="T300">
|
|
||||||
MMM
|
|
||||||
<Text as="span" size="Inherit" priority="300">
|
|
||||||
{': '}
|
|
||||||
{t('Settings.short_month_name')}
|
|
||||||
</Text>
|
|
||||||
</Text>
|
|
||||||
<Text size="T300">
|
|
||||||
MMMM
|
|
||||||
<Text as="span" size="Inherit" priority="300">
|
|
||||||
{': '}
|
|
||||||
{t('Settings.full_month_name')}
|
|
||||||
</Text>
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box style={categoryPadding} direction="Column">
|
|
||||||
<Header size="300">
|
|
||||||
<Text size="L400">{t('Settings.day_of_month')}</Text>
|
|
||||||
</Header>
|
|
||||||
<Box direction="Column" tabIndex={0} gap="100">
|
|
||||||
<Text size="T300">
|
|
||||||
D
|
|
||||||
<Text as="span" size="Inherit" priority="300">
|
|
||||||
{': '}
|
|
||||||
{t('Settings.day_of_month_val')}
|
|
||||||
</Text>
|
|
||||||
</Text>
|
|
||||||
<Text size="T300">
|
|
||||||
DD
|
|
||||||
<Text as="span" size="Inherit" priority="300">
|
|
||||||
{': '}
|
|
||||||
{t('Settings.two_digit_day')}
|
|
||||||
</Text>
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
<Box style={categoryPadding} direction="Column">
|
|
||||||
<Header size="300">
|
|
||||||
<Text size="L400">{t('Settings.day_of_week')}</Text>
|
|
||||||
</Header>
|
|
||||||
<Box direction="Column" tabIndex={0} gap="100">
|
|
||||||
<Text size="T300">
|
|
||||||
d
|
|
||||||
<Text as="span" size="Inherit" priority="300">
|
|
||||||
{': '}
|
|
||||||
{t('Settings.day_of_week_sunday')}
|
|
||||||
</Text>
|
|
||||||
</Text>
|
|
||||||
<Text size="T300">
|
|
||||||
dd
|
|
||||||
<Text as="span" size="Inherit" priority="300">
|
|
||||||
{': '}
|
|
||||||
{t('Settings.two_letter_day')}
|
|
||||||
</Text>
|
|
||||||
</Text>
|
|
||||||
<Text size="T300">
|
|
||||||
ddd
|
|
||||||
<Text as="span" size="Inherit" priority="300">
|
|
||||||
{': '}
|
|
||||||
{t('Settings.short_day_name')}
|
|
||||||
</Text>
|
|
||||||
</Text>
|
|
||||||
<Text size="T300">
|
|
||||||
dddd
|
|
||||||
<Text as="span" size="Inherit" priority="300">
|
|
||||||
{': '}
|
|
||||||
{t('Settings.full_day_name')}
|
|
||||||
</Text>
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Menu>
|
|
||||||
</FocusTrap>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{hasChanges ? (
|
|
||||||
<IconButton
|
|
||||||
tabIndex={-1}
|
|
||||||
onClick={handleReset}
|
|
||||||
type="reset"
|
|
||||||
variant="Secondary"
|
|
||||||
size="300"
|
|
||||||
radii="300"
|
|
||||||
>
|
|
||||||
<Icon src={Icons.Cross} size="100" />
|
|
||||||
</IconButton>
|
|
||||||
) : (
|
|
||||||
<IconButton
|
|
||||||
tabIndex={-1}
|
|
||||||
onClick={handleOpenMenu}
|
|
||||||
type="button"
|
|
||||||
variant="Secondary"
|
|
||||||
size="300"
|
|
||||||
radii="300"
|
|
||||||
aria-pressed={!!anchor}
|
|
||||||
>
|
|
||||||
<Icon style={{ opacity: config.opacity.P300 }} size="100" src={Icons.Info} />
|
|
||||||
</IconButton>
|
|
||||||
)}
|
|
||||||
</PopOut>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
type CustomDateFormatProps = {
|
|
||||||
value: string;
|
|
||||||
onChange: (format: string) => void;
|
|
||||||
};
|
|
||||||
function CustomDateFormat({ value, onChange }: CustomDateFormatProps) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [dateFormatCustom, setDateFormatCustom] = useState(value);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setDateFormatCustom(value);
|
|
||||||
}, [value]);
|
|
||||||
|
|
||||||
const handleChange: ChangeEventHandler<HTMLInputElement> = (evt) => {
|
|
||||||
const format = evt.currentTarget.value;
|
|
||||||
setDateFormatCustom(format);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleReset = () => {
|
|
||||||
setDateFormatCustom(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit: FormEventHandler<HTMLFormElement> = (evt) => {
|
|
||||||
evt.preventDefault();
|
|
||||||
|
|
||||||
const target = evt.target as HTMLFormElement | undefined;
|
|
||||||
const customDateFormatInput = target?.customDateFormatInput as HTMLInputElement | undefined;
|
|
||||||
const format = customDateFormatInput?.value;
|
|
||||||
if (!format) return;
|
|
||||||
|
|
||||||
onChange(format);
|
|
||||||
};
|
|
||||||
|
|
||||||
const hasChanges = dateFormatCustom !== value;
|
|
||||||
return (
|
|
||||||
<SettingTile>
|
|
||||||
<Box as="form" onSubmit={handleSubmit} gap="200">
|
|
||||||
<Box grow="Yes" direction="Column">
|
|
||||||
<Input
|
|
||||||
required
|
|
||||||
name="customDateFormatInput"
|
|
||||||
value={dateFormatCustom}
|
|
||||||
onChange={handleChange}
|
|
||||||
maxLength={16}
|
|
||||||
autoComplete="off"
|
|
||||||
variant="Secondary"
|
|
||||||
radii="300"
|
|
||||||
style={{ paddingRight: config.space.S200 }}
|
|
||||||
after={<DateHint hasChanges={hasChanges} handleReset={handleReset} />}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Button
|
|
||||||
size="400"
|
|
||||||
variant={hasChanges ? 'Success' : 'Secondary'}
|
|
||||||
fill={hasChanges ? 'Solid' : 'Soft'}
|
|
||||||
outlined
|
|
||||||
radii="300"
|
|
||||||
disabled={!hasChanges}
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
<Text size="B400">{t('Settings.save')}</Text>
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</SettingTile>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
type PresetDateFormatProps = {
|
|
||||||
value: string;
|
|
||||||
onChange: (format: string) => void;
|
|
||||||
};
|
|
||||||
function PresetDateFormat({ value, onChange }: PresetDateFormatProps) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [menuCords, setMenuCords] = useState<RectCords>();
|
|
||||||
const dateFormatItems = useDateFormatItems();
|
|
||||||
|
|
||||||
const getDisplayDate = (format: string): string =>
|
|
||||||
format !== '' ? dayjs().format(format) : t('Settings.custom');
|
|
||||||
|
|
||||||
const handleMenu: MouseEventHandler<HTMLButtonElement> = (evt) => {
|
|
||||||
setMenuCords(evt.currentTarget.getBoundingClientRect());
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSelect = (format: DateFormat) => {
|
|
||||||
onChange(format);
|
|
||||||
setMenuCords(undefined);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Button
|
|
||||||
size="300"
|
|
||||||
variant="Secondary"
|
|
||||||
outlined
|
|
||||||
fill="Soft"
|
|
||||||
radii="300"
|
|
||||||
after={<Icon size="300" src={Icons.ChevronBottom} />}
|
|
||||||
onClick={handleMenu}
|
|
||||||
>
|
|
||||||
<Text size="T300">
|
|
||||||
{getDisplayDate(dateFormatItems.find((i) => i.format === value)?.format ?? value)}
|
|
||||||
</Text>
|
|
||||||
</Button>
|
|
||||||
<PopOut
|
|
||||||
anchor={menuCords}
|
|
||||||
offset={5}
|
|
||||||
position="Bottom"
|
|
||||||
align="End"
|
|
||||||
content={
|
|
||||||
<FocusTrap
|
|
||||||
focusTrapOptions={{
|
|
||||||
initialFocus: false,
|
|
||||||
onDeactivate: () => setMenuCords(undefined),
|
|
||||||
clickOutsideDeactivates: true,
|
|
||||||
isKeyForward: (evt: KeyboardEvent) =>
|
|
||||||
evt.key === 'ArrowDown' || evt.key === 'ArrowRight',
|
|
||||||
isKeyBackward: (evt: KeyboardEvent) =>
|
|
||||||
evt.key === 'ArrowUp' || evt.key === 'ArrowLeft',
|
|
||||||
escapeDeactivates: stopPropagation,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Menu>
|
|
||||||
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
|
|
||||||
{dateFormatItems.map((item) => (
|
|
||||||
<MenuItem
|
|
||||||
key={item.format}
|
|
||||||
size="300"
|
|
||||||
variant={value === item.format ? 'Primary' : 'Surface'}
|
|
||||||
radii="300"
|
|
||||||
onClick={() => handleSelect(item.format)}
|
|
||||||
>
|
|
||||||
<Text size="T300">{getDisplayDate(item.format)}</Text>
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
</Menu>
|
|
||||||
</FocusTrap>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function SelectDateFormat() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [dateFormatString, setDateFormatString] = useSetting(settingsAtom, 'dateFormatString');
|
|
||||||
const [selectedDateFormat, setSelectedDateFormat] = useState(dateFormatString);
|
|
||||||
const customDateFormat = selectedDateFormat === '';
|
|
||||||
|
|
||||||
const handlePresetChange = (format: string) => {
|
|
||||||
setSelectedDateFormat(format);
|
|
||||||
if (format !== '') {
|
|
||||||
setDateFormatString(format);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<SettingTile
|
|
||||||
title={t('Settings.date_format')}
|
|
||||||
description={customDateFormat ? dayjs().format(dateFormatString) : ''}
|
|
||||||
after={<PresetDateFormat value={selectedDateFormat} onChange={handlePresetChange} />}
|
|
||||||
/>
|
|
||||||
{customDateFormat && (
|
|
||||||
<CustomDateFormat value={dateFormatString} onChange={setDateFormatString} />
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function DateAndTime() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [hour24Clock, setHour24Clock] = useSetting(settingsAtom, 'hour24Clock');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box direction="Column" gap="100">
|
|
||||||
<Text size="L400">{t('Settings.date_time')}</Text>
|
|
||||||
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
|
|
||||||
<SettingTile
|
|
||||||
title={t('Settings.hour_24')}
|
|
||||||
after={<Switch variant="Primary" value={hour24Clock} onChange={setHour24Clock} />}
|
|
||||||
/>
|
|
||||||
</SequenceCard>
|
|
||||||
|
|
||||||
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
|
|
||||||
<SelectDateFormat />
|
|
||||||
</SequenceCard>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Editor() {
|
function Editor() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [enterForNewline, setEnterForNewline] = useSetting(settingsAtom, 'enterForNewline');
|
const [enterForNewline, setEnterForNewline] = useSetting(settingsAtom, 'enterForNewline');
|
||||||
|
|
@ -636,7 +249,6 @@ export function General({ requestClose }: GeneralProps) {
|
||||||
<PageContent>
|
<PageContent>
|
||||||
<Box direction="Column" gap="700">
|
<Box direction="Column" gap="700">
|
||||||
<Appearance />
|
<Appearance />
|
||||||
<DateAndTime />
|
|
||||||
<Editor />
|
<Editor />
|
||||||
<Messages />
|
<Messages />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { DateFormat } from '../state/settings';
|
|
||||||
|
|
||||||
export type DateFormatItem = {
|
|
||||||
name: string;
|
|
||||||
format: DateFormat;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useDateFormatItems = (): DateFormatItem[] =>
|
|
||||||
useMemo(
|
|
||||||
() => [
|
|
||||||
{
|
|
||||||
format: 'D MMM YYYY',
|
|
||||||
name: 'D MMM YYYY',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
format: 'DD/MM/YYYY',
|
|
||||||
name: 'DD/MM/YYYY',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
format: 'MM/DD/YYYY',
|
|
||||||
name: 'MM/DD/YYYY',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
format: 'YYYY/MM/DD',
|
|
||||||
name: 'YYYY/MM/DD',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
format: 'YYYY-MM-DD',
|
|
||||||
name: 'YYYY-MM-DD',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
format: '',
|
|
||||||
name: 'Custom',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
@ -1,13 +1,6 @@
|
||||||
import { atom } from 'jotai';
|
import { atom } from 'jotai';
|
||||||
|
|
||||||
const STORAGE_KEY = 'settings';
|
const STORAGE_KEY = 'settings';
|
||||||
export type DateFormat =
|
|
||||||
| 'D MMM YYYY'
|
|
||||||
| 'DD/MM/YYYY'
|
|
||||||
| 'MM/DD/YYYY'
|
|
||||||
| 'YYYY/MM/DD'
|
|
||||||
| 'YYYY-MM-DD'
|
|
||||||
| '';
|
|
||||||
|
|
||||||
export interface Settings {
|
export interface Settings {
|
||||||
themeId?: string;
|
themeId?: string;
|
||||||
|
|
@ -32,9 +25,6 @@ export interface Settings {
|
||||||
isNotificationSounds: boolean;
|
isNotificationSounds: boolean;
|
||||||
inviteSpamFilter: boolean;
|
inviteSpamFilter: boolean;
|
||||||
|
|
||||||
hour24Clock: boolean;
|
|
||||||
dateFormatString: string;
|
|
||||||
|
|
||||||
developerTools: boolean;
|
developerTools: boolean;
|
||||||
|
|
||||||
migrationsApplied?: Record<string, boolean>;
|
migrationsApplied?: Record<string, boolean>;
|
||||||
|
|
@ -42,6 +32,7 @@ export interface Settings {
|
||||||
|
|
||||||
const DAWN_MIGRATION_KEY = 'dawn-redesign-v1';
|
const DAWN_MIGRATION_KEY = 'dawn-redesign-v1';
|
||||||
const P3C_CLEANUP_KEY = 'dawn-p3c-cleanup';
|
const P3C_CLEANUP_KEY = 'dawn-p3c-cleanup';
|
||||||
|
const SYSTEM_TIME_FORMAT_CLEANUP_KEY = 'system-time-format-cleanup';
|
||||||
|
|
||||||
const defaultSettings: Settings = {
|
const defaultSettings: Settings = {
|
||||||
themeId: undefined,
|
themeId: undefined,
|
||||||
|
|
@ -66,9 +57,6 @@ const defaultSettings: Settings = {
|
||||||
isNotificationSounds: true,
|
isNotificationSounds: true,
|
||||||
inviteSpamFilter: true,
|
inviteSpamFilter: true,
|
||||||
|
|
||||||
hour24Clock: false,
|
|
||||||
dateFormatString: 'D MMM YYYY',
|
|
||||||
|
|
||||||
developerTools: false,
|
developerTools: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -121,6 +109,21 @@ export const getSettings = (): Settings => {
|
||||||
setSettings(merged);
|
setSettings(merged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// System time-format migration: drop the user-facing `hour24Clock` /
|
||||||
|
// `dateFormatString` fields — both now derived from the runtime locale via
|
||||||
|
// `Intl.DateTimeFormat` (see `utils/time.ts`). One-shot strip of the orphan
|
||||||
|
// keys from existing users' persisted JSON.
|
||||||
|
if (!merged.migrationsApplied?.[SYSTEM_TIME_FORMAT_CLEANUP_KEY]) {
|
||||||
|
const orphan = merged as unknown as Record<string, unknown>;
|
||||||
|
delete orphan.hour24Clock;
|
||||||
|
delete orphan.dateFormatString;
|
||||||
|
merged.migrationsApplied = {
|
||||||
|
...(merged.migrationsApplied ?? {}),
|
||||||
|
[SYSTEM_TIME_FORMAT_CLEANUP_KEY]: true,
|
||||||
|
};
|
||||||
|
setSettings(merged);
|
||||||
|
}
|
||||||
|
|
||||||
return merged;
|
return merged;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,36 @@ import isYesterday from 'dayjs/plugin/isYesterday';
|
||||||
dayjs.extend(isToday);
|
dayjs.extend(isToday);
|
||||||
dayjs.extend(isYesterday);
|
dayjs.extend(isYesterday);
|
||||||
|
|
||||||
|
// Detect once at module load — the runtime locale doesn't change without a
|
||||||
|
// page reload. AM/PM systems set `hour12: true`; 24-hour systems leave it
|
||||||
|
// false/undefined. Older WebView builds may omit `hour12` entirely, so we
|
||||||
|
// fall back to the modern `hourCycle` field (h11/h12 = 12-hour, h23/h24 =
|
||||||
|
// 24-hour) before defaulting to 24-hour.
|
||||||
|
const detectSystemHour24 = (): boolean => {
|
||||||
|
try {
|
||||||
|
const opts = new Intl.DateTimeFormat(undefined, { hour: 'numeric' }).resolvedOptions();
|
||||||
|
if (opts.hour12 === true) return false;
|
||||||
|
if (opts.hour12 === false) return true;
|
||||||
|
const cycle = (opts as { hourCycle?: string }).hourCycle;
|
||||||
|
if (cycle === 'h11' || cycle === 'h12') return false;
|
||||||
|
if (cycle === 'h23' || cycle === 'h24') return true;
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SYSTEM_HOUR_24: boolean = detectSystemHour24();
|
||||||
|
|
||||||
|
// 24-hour systems get the European day-month-year layout; 12-hour systems get
|
||||||
|
// the American month-day-year layout.
|
||||||
|
const SYSTEM_DAY_MON_YEAR_FORMAT = SYSTEM_HOUR_24 ? 'DD/MM/YYYY' : 'MM/DD/YYYY';
|
||||||
|
|
||||||
export const today = (ts: number): boolean => dayjs(ts).isToday();
|
export const today = (ts: number): boolean => dayjs(ts).isToday();
|
||||||
|
|
||||||
export const yesterday = (ts: number): boolean => dayjs(ts).isYesterday();
|
export const yesterday = (ts: number): boolean => dayjs(ts).isYesterday();
|
||||||
|
|
||||||
export const timeHour = (ts: number, hour24Clock: boolean): string =>
|
export const timeHour = (ts: number): string => dayjs(ts).format(SYSTEM_HOUR_24 ? 'HH' : 'hh');
|
||||||
dayjs(ts).format(hour24Clock ? 'HH' : 'hh');
|
|
||||||
export const timeMinute = (ts: number): string => dayjs(ts).format('mm');
|
export const timeMinute = (ts: number): string => dayjs(ts).format('mm');
|
||||||
export const timeAmPm = (ts: number): string => dayjs(ts).format('A');
|
export const timeAmPm = (ts: number): string => dayjs(ts).format('A');
|
||||||
export const timeDay = (ts: number): string => dayjs(ts).format('D');
|
export const timeDay = (ts: number): string => dayjs(ts).format('D');
|
||||||
|
|
@ -18,11 +42,10 @@ export const timeMon = (ts: number): string => dayjs(ts).format('MMM');
|
||||||
export const timeMonth = (ts: number): string => dayjs(ts).format('MMMM');
|
export const timeMonth = (ts: number): string => dayjs(ts).format('MMMM');
|
||||||
export const timeYear = (ts: number): string => dayjs(ts).format('YYYY');
|
export const timeYear = (ts: number): string => dayjs(ts).format('YYYY');
|
||||||
|
|
||||||
export const timeHourMinute = (ts: number, hour24Clock: boolean): string =>
|
export const timeHourMinute = (ts: number): string =>
|
||||||
dayjs(ts).format(hour24Clock ? 'HH:mm' : 'hh:mm A');
|
dayjs(ts).format(SYSTEM_HOUR_24 ? 'HH:mm' : 'hh:mm A');
|
||||||
|
|
||||||
export const timeDayMonYear = (ts: number, dateFormatString: string): string =>
|
export const timeDayMonYear = (ts: number): string => dayjs(ts).format(SYSTEM_DAY_MON_YEAR_FORMAT);
|
||||||
dayjs(ts).format(dateFormatString);
|
|
||||||
|
|
||||||
export const timeDayMonthYear = (ts: number): string => dayjs(ts).format('D MMMM YYYY');
|
export const timeDayMonthYear = (ts: number): string => dayjs(ts).format('D MMMM YYYY');
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue