mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-09-13 22:32:26 +03:00
Add settings to enable 24-hour time format and customizable date format (#2347)
* Add setting to enable 24-hour time format * added hour24Clock to TimeProps * Add incomplete dateFormatString setting * Move 24-hour toggle to Appearance * Add "Date & Time" subheading, cleanup after merge * Add setting for date formatting * Fix minor formatting and naming issues * Document functions * adress most comments * add hint for date formatting * add support for 24hr time to TimePicker * prevent overflow on small displays
This commit is contained in:
parent
67b05eeb09
commit
9183fd66b2
17 changed files with 691 additions and 82 deletions
|
@ -4,10 +4,25 @@ import PropTypes from 'prop-types';
|
||||||
import dateFormat from 'dateformat';
|
import dateFormat from 'dateformat';
|
||||||
import { isInSameDay } from '../../../util/common';
|
import { isInSameDay } from '../../../util/common';
|
||||||
|
|
||||||
function Time({ timestamp, fullTime }) {
|
/**
|
||||||
|
* Renders a formatted timestamp.
|
||||||
|
*
|
||||||
|
* Displays the time in hour:minute format if the message is from today or yesterday, unless `fullTime` is true.
|
||||||
|
* For older messages, it shows the date and time.
|
||||||
|
*
|
||||||
|
* @param {number} timestamp - The timestamp to display.
|
||||||
|
* @param {boolean} [fullTime=false] - If true, always show the full date and time.
|
||||||
|
* @param {boolean} hour24Clock - Whether to use 24-hour time format.
|
||||||
|
* @param {string} dateFormatString - Format string for the date part.
|
||||||
|
* @returns {JSX.Element} A <time> element with the formatted date/time.
|
||||||
|
*/
|
||||||
|
function Time({ timestamp, fullTime, hour24Clock, dateFormatString }) {
|
||||||
const date = new Date(timestamp);
|
const date = new Date(timestamp);
|
||||||
|
|
||||||
const formattedFullTime = dateFormat(date, 'dd mmmm yyyy, hh:MM TT');
|
const formattedFullTime = dateFormat(
|
||||||
|
date,
|
||||||
|
hour24Clock ? 'dd mmmm yyyy, HH:MM' : 'dd mmmm yyyy, hh:MM TT'
|
||||||
|
);
|
||||||
let formattedDate = formattedFullTime;
|
let formattedDate = formattedFullTime;
|
||||||
|
|
||||||
if (!fullTime) {
|
if (!fullTime) {
|
||||||
|
@ -16,17 +31,19 @@ function Time({ timestamp, fullTime }) {
|
||||||
compareDate.setDate(compareDate.getDate() - 1);
|
compareDate.setDate(compareDate.getDate() - 1);
|
||||||
const isYesterday = isInSameDay(date, compareDate);
|
const isYesterday = isInSameDay(date, compareDate);
|
||||||
|
|
||||||
formattedDate = dateFormat(date, isToday || isYesterday ? 'hh:MM TT' : 'dd/mm/yyyy');
|
const timeFormat = hour24Clock ? 'HH:MM' : 'hh:MM TT';
|
||||||
|
|
||||||
|
formattedDate = dateFormat(
|
||||||
|
date,
|
||||||
|
isToday || isYesterday ? timeFormat : dateFormatString.toLowerCase()
|
||||||
|
);
|
||||||
if (isYesterday) {
|
if (isYesterday) {
|
||||||
formattedDate = `Yesterday, ${formattedDate}`;
|
formattedDate = `Yesterday, ${formattedDate}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<time
|
<time dateTime={date.toISOString()} title={formattedFullTime}>
|
||||||
dateTime={date.toISOString()}
|
|
||||||
title={formattedFullTime}
|
|
||||||
>
|
|
||||||
{formattedDate}
|
{formattedDate}
|
||||||
</time>
|
</time>
|
||||||
);
|
);
|
||||||
|
@ -39,6 +56,8 @@ Time.defaultProps = {
|
||||||
Time.propTypes = {
|
Time.propTypes = {
|
||||||
timestamp: PropTypes.number.isRequired,
|
timestamp: PropTypes.number.isRequired,
|
||||||
fullTime: PropTypes.bool,
|
fullTime: PropTypes.bool,
|
||||||
|
hour24Clock: PropTypes.bool.isRequired,
|
||||||
|
dateFormatString: PropTypes.string.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Time;
|
export default Time;
|
||||||
|
|
|
@ -5,19 +5,35 @@ 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.
|
||||||
|
*
|
||||||
|
* Displays the time in hour:minute format if the message is from today, yesterday, or if `compact` is true.
|
||||||
|
* 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, ts, ...props }, ref) => {
|
({ compact, hour24Clock, dateFormatString, ts, ...props }, ref) => {
|
||||||
|
const formattedTime = timeHourMinute(ts, hour24Clock);
|
||||||
|
|
||||||
let time = '';
|
let time = '';
|
||||||
if (compact) {
|
if (compact) {
|
||||||
time = timeHourMinute(ts);
|
time = formattedTime;
|
||||||
} else if (today(ts)) {
|
} else if (today(ts)) {
|
||||||
time = timeHourMinute(ts);
|
time = formattedTime;
|
||||||
} else if (yesterday(ts)) {
|
} else if (yesterday(ts)) {
|
||||||
time = `Yesterday ${timeHourMinute(ts)}`;
|
time = `Yesterday ${formattedTime}`;
|
||||||
} else {
|
} else {
|
||||||
time = `${timeDayMonYear(ts)} ${timeHourMinute(ts)}`;
|
time = `${timeDayMonYear(ts, dateFormatString)} ${formattedTime}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -15,6 +15,8 @@ import { nameInitials } from '../../utils/common';
|
||||||
import { useRoomAvatar, useRoomName, useRoomTopic } from '../../hooks/useRoomMeta';
|
import { useRoomAvatar, useRoomName, useRoomTopic } from '../../hooks/useRoomMeta';
|
||||||
import { mDirectAtom } from '../../state/mDirectList';
|
import { mDirectAtom } from '../../state/mDirectList';
|
||||||
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||||
|
import { useSetting } from '../../state/hooks/settings';
|
||||||
|
import { settingsAtom } from '../../state/settings';
|
||||||
|
|
||||||
export type RoomIntroProps = {
|
export type RoomIntroProps = {
|
||||||
room: Room;
|
room: Room;
|
||||||
|
@ -43,6 +45,8 @@ 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>
|
||||||
|
@ -67,7 +71,7 @@ export const RoomIntro = as<'div', RoomIntroProps>(({ room, ...props }, ref) =>
|
||||||
<Text size="T200" priority="300">
|
<Text size="T200" priority="300">
|
||||||
{'Created by '}
|
{'Created by '}
|
||||||
<b>@{creatorName}</b>
|
<b>@{creatorName}</b>
|
||||||
{` on ${timeDayMonthYear(ts)} ${timeHourMinute(ts)}`}
|
{` on ${timeDayMonthYear(ts)} ${timeHourMinute(ts, hour24Clock)}`}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
@ -4,6 +4,8 @@ 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 { hour12to24, hour24to12, hoursToMs, inSameDay, minutesToMs } from '../../utils/time';
|
||||||
|
import { useSetting } from '../../state/hooks/settings';
|
||||||
|
import { settingsAtom } from '../../state/settings';
|
||||||
|
|
||||||
type TimePickerProps = {
|
type TimePickerProps = {
|
||||||
min: number;
|
min: number;
|
||||||
|
@ -13,9 +15,11 @@ 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 = hour24to12(hour24);
|
const selectedHour = hour24Clock ? hour24 : hour24to12(hour24);
|
||||||
const selectedMinute = dayjs(value).minute();
|
const selectedMinute = dayjs(value).minute();
|
||||||
const selectedPM = hour24 >= 12;
|
const selectedPM = hour24 >= 12;
|
||||||
|
|
||||||
|
@ -24,7 +28,7 @@ export const TimePicker = forwardRef<HTMLDivElement, TimePickerProps>(
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleHour = (hour: number) => {
|
const handleHour = (hour: number) => {
|
||||||
const seconds = hoursToMs(hour12to24(hour, selectedPM));
|
const seconds = hoursToMs(hour24Clock ? 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);
|
||||||
|
@ -59,28 +63,43 @@ 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">
|
||||||
{Array.from(Array(12).keys())
|
{hour24Clock
|
||||||
.map((i) => {
|
? Array.from(Array(24).keys()).map((hour) => (
|
||||||
if (i === 0) return 12;
|
<Chip
|
||||||
return i;
|
key={hour}
|
||||||
})
|
size="500"
|
||||||
.map((hour) => (
|
variant={hour === selectedHour ? 'Primary' : 'Background'}
|
||||||
<Chip
|
fill="None"
|
||||||
key={hour}
|
radii="300"
|
||||||
size="500"
|
aria-selected={hour === selectedHour}
|
||||||
variant={hour === selectedHour ? 'Primary' : 'Background'}
|
onClick={() => handleHour(hour)}
|
||||||
fill="None"
|
disabled={(minDay && hour < minHour24) || (maxDay && hour > maxHour24)}
|
||||||
radii="300"
|
>
|
||||||
aria-selected={hour === selectedHour}
|
<Text size="T300">{hour < 10 ? `0${hour}` : hour}</Text>
|
||||||
onClick={() => handleHour(hour)}
|
</Chip>
|
||||||
disabled={
|
))
|
||||||
(minDay && hour12to24(hour, selectedPM) < minHour24) ||
|
: Array.from(Array(12).keys())
|
||||||
(maxDay && hour12to24(hour, selectedPM) > maxHour24)
|
.map((i) => {
|
||||||
}
|
if (i === 0) return 12;
|
||||||
>
|
return i;
|
||||||
<Text size="T300">{hour < 10 ? `0${hour}` : hour}</Text>
|
})
|
||||||
</Chip>
|
.map((hour) => (
|
||||||
))}
|
<Chip
|
||||||
|
key={hour}
|
||||||
|
size="500"
|
||||||
|
variant={hour === selectedHour ? 'Primary' : 'Background'}
|
||||||
|
fill="None"
|
||||||
|
radii="300"
|
||||||
|
aria-selected={hour === selectedHour}
|
||||||
|
onClick={() => handleHour(hour)}
|
||||||
|
disabled={
|
||||||
|
(minDay && hour12to24(hour, selectedPM) < minHour24) ||
|
||||||
|
(maxDay && hour12to24(hour, selectedPM) > maxHour24)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Text size="T300">{hour < 10 ? `0${hour}` : hour}</Text>
|
||||||
|
</Chip>
|
||||||
|
))}
|
||||||
</PickerColumn>
|
</PickerColumn>
|
||||||
<PickerColumn title="Minutes">
|
<PickerColumn title="Minutes">
|
||||||
{Array.from(Array(60).keys()).map((minute) => (
|
{Array.from(Array(60).keys()).map((minute) => (
|
||||||
|
@ -101,30 +120,32 @@ export const TimePicker = forwardRef<HTMLDivElement, TimePickerProps>(
|
||||||
</Chip>
|
</Chip>
|
||||||
))}
|
))}
|
||||||
</PickerColumn>
|
</PickerColumn>
|
||||||
<PickerColumn title="Period">
|
{!hour24Clock && (
|
||||||
<Chip
|
<PickerColumn title="Period">
|
||||||
size="500"
|
<Chip
|
||||||
variant={!selectedPM ? 'Primary' : 'SurfaceVariant'}
|
size="500"
|
||||||
fill="None"
|
variant={!selectedPM ? 'Primary' : 'SurfaceVariant'}
|
||||||
radii="300"
|
fill="None"
|
||||||
aria-selected={!selectedPM}
|
radii="300"
|
||||||
onClick={() => handlePeriod(false)}
|
aria-selected={!selectedPM}
|
||||||
disabled={minDay && minPM}
|
onClick={() => handlePeriod(false)}
|
||||||
>
|
disabled={minDay && minPM}
|
||||||
<Text size="T300">AM</Text>
|
>
|
||||||
</Chip>
|
<Text size="T300">AM</Text>
|
||||||
<Chip
|
</Chip>
|
||||||
size="500"
|
<Chip
|
||||||
variant={selectedPM ? 'Primary' : 'SurfaceVariant'}
|
size="500"
|
||||||
fill="None"
|
variant={selectedPM ? 'Primary' : 'SurfaceVariant'}
|
||||||
radii="300"
|
fill="None"
|
||||||
aria-selected={selectedPM}
|
radii="300"
|
||||||
onClick={() => handlePeriod(true)}
|
aria-selected={selectedPM}
|
||||||
disabled={maxDay && !maxPM}
|
onClick={() => handlePeriod(true)}
|
||||||
>
|
disabled={maxDay && !maxPM}
|
||||||
<Text size="T300">PM</Text>
|
>
|
||||||
</Chip>
|
<Text size="T300">PM</Text>
|
||||||
</PickerColumn>
|
</Chip>
|
||||||
|
</PickerColumn>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
|
|
|
@ -57,6 +57,9 @@ export function MessageSearch({
|
||||||
const [urlPreview] = useSetting(settingsAtom, 'urlPreview');
|
const [urlPreview] = useSetting(settingsAtom, 'urlPreview');
|
||||||
const [legacyUsernameColor] = useSetting(settingsAtom, 'legacyUsernameColor');
|
const [legacyUsernameColor] = useSetting(settingsAtom, 'legacyUsernameColor');
|
||||||
|
|
||||||
|
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();
|
||||||
|
@ -289,6 +292,8 @@ export function MessageSearch({
|
||||||
urlPreview={urlPreview}
|
urlPreview={urlPreview}
|
||||||
onOpen={navigateRoom}
|
onOpen={navigateRoom}
|
||||||
legacyUsernameColor={legacyUsernameColor || mDirects.has(groupRoom.roomId)}
|
legacyUsernameColor={legacyUsernameColor || mDirects.has(groupRoom.roomId)}
|
||||||
|
hour24Clock={hour24Clock}
|
||||||
|
dateFormatString={dateFormatString}
|
||||||
/>
|
/>
|
||||||
</VirtualTile>
|
</VirtualTile>
|
||||||
);
|
);
|
||||||
|
|
|
@ -57,6 +57,8 @@ 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,
|
||||||
|
@ -66,6 +68,8 @@ export function SearchResultGroup({
|
||||||
urlPreview,
|
urlPreview,
|
||||||
onOpen,
|
onOpen,
|
||||||
legacyUsernameColor,
|
legacyUsernameColor,
|
||||||
|
hour24Clock,
|
||||||
|
dateFormatString,
|
||||||
}: SearchResultGroupProps) {
|
}: SearchResultGroupProps) {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
const useAuthentication = useMediaAuthentication();
|
const useAuthentication = useMediaAuthentication();
|
||||||
|
@ -275,7 +279,11 @@ export function SearchResultGroup({
|
||||||
</Username>
|
</Username>
|
||||||
{tagIconSrc && <PowerIcon size="100" iconSrc={tagIconSrc} />}
|
{tagIconSrc && <PowerIcon size="100" iconSrc={tagIconSrc} />}
|
||||||
</Box>
|
</Box>
|
||||||
<Time ts={event.origin_server_ts} />
|
<Time
|
||||||
|
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
|
||||||
|
|
|
@ -450,6 +450,9 @@ export function RoomTimeline({
|
||||||
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]);
|
||||||
|
|
||||||
|
@ -1072,6 +1075,8 @@ export function RoomTimeline({
|
||||||
powerLevelTag={getPowerLevelTag(senderPowerLevel)}
|
powerLevelTag={getPowerLevelTag(senderPowerLevel)}
|
||||||
accessibleTagColors={accessibleTagColors}
|
accessibleTagColors={accessibleTagColors}
|
||||||
legacyUsernameColor={legacyUsernameColor || direct}
|
legacyUsernameColor={legacyUsernameColor || direct}
|
||||||
|
hour24Clock={hour24Clock}
|
||||||
|
dateFormatString={dateFormatString}
|
||||||
>
|
>
|
||||||
{mEvent.isRedacted() ? (
|
{mEvent.isRedacted() ? (
|
||||||
<RedactedContent reason={mEvent.getUnsigned().redacted_because?.content.reason} />
|
<RedactedContent reason={mEvent.getUnsigned().redacted_because?.content.reason} />
|
||||||
|
@ -1154,6 +1159,8 @@ export function RoomTimeline({
|
||||||
powerLevelTag={getPowerLevelTag(senderPowerLevel)}
|
powerLevelTag={getPowerLevelTag(senderPowerLevel)}
|
||||||
accessibleTagColors={accessibleTagColors}
|
accessibleTagColors={accessibleTagColors}
|
||||||
legacyUsernameColor={legacyUsernameColor || direct}
|
legacyUsernameColor={legacyUsernameColor || direct}
|
||||||
|
hour24Clock={hour24Clock}
|
||||||
|
dateFormatString={dateFormatString}
|
||||||
>
|
>
|
||||||
<EncryptedContent mEvent={mEvent}>
|
<EncryptedContent mEvent={mEvent}>
|
||||||
{() => {
|
{() => {
|
||||||
|
@ -1256,6 +1263,8 @@ export function RoomTimeline({
|
||||||
powerLevelTag={getPowerLevelTag(senderPowerLevel)}
|
powerLevelTag={getPowerLevelTag(senderPowerLevel)}
|
||||||
accessibleTagColors={accessibleTagColors}
|
accessibleTagColors={accessibleTagColors}
|
||||||
legacyUsernameColor={legacyUsernameColor || direct}
|
legacyUsernameColor={legacyUsernameColor || direct}
|
||||||
|
hour24Clock={hour24Clock}
|
||||||
|
dateFormatString={dateFormatString}
|
||||||
>
|
>
|
||||||
{mEvent.isRedacted() ? (
|
{mEvent.isRedacted() ? (
|
||||||
<RedactedContent reason={mEvent.getUnsigned().redacted_because?.content.reason} />
|
<RedactedContent reason={mEvent.getUnsigned().redacted_because?.content.reason} />
|
||||||
|
@ -1284,7 +1293,12 @@ export function RoomTimeline({
|
||||||
const parsed = parseMemberEvent(mEvent);
|
const parsed = parseMemberEvent(mEvent);
|
||||||
|
|
||||||
const timeJSX = (
|
const timeJSX = (
|
||||||
<Time ts={mEvent.getTs()} compact={messageLayout === MessageLayout.Compact} />
|
<Time
|
||||||
|
ts={mEvent.getTs()}
|
||||||
|
compact={messageLayout === MessageLayout.Compact}
|
||||||
|
hour24Clock={hour24Clock}
|
||||||
|
dateFormatString={dateFormatString}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1321,7 +1335,12 @@ export function RoomTimeline({
|
||||||
const senderName = getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId);
|
const senderName = getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId);
|
||||||
|
|
||||||
const timeJSX = (
|
const timeJSX = (
|
||||||
<Time ts={mEvent.getTs()} compact={messageLayout === MessageLayout.Compact} />
|
<Time
|
||||||
|
ts={mEvent.getTs()}
|
||||||
|
compact={messageLayout === MessageLayout.Compact}
|
||||||
|
hour24Clock={hour24Clock}
|
||||||
|
dateFormatString={dateFormatString}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1359,7 +1378,12 @@ export function RoomTimeline({
|
||||||
const senderName = getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId);
|
const senderName = getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId);
|
||||||
|
|
||||||
const timeJSX = (
|
const timeJSX = (
|
||||||
<Time ts={mEvent.getTs()} compact={messageLayout === MessageLayout.Compact} />
|
<Time
|
||||||
|
ts={mEvent.getTs()}
|
||||||
|
compact={messageLayout === MessageLayout.Compact}
|
||||||
|
hour24Clock={hour24Clock}
|
||||||
|
dateFormatString={dateFormatString}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1397,7 +1421,12 @@ export function RoomTimeline({
|
||||||
const senderName = getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId);
|
const senderName = getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId);
|
||||||
|
|
||||||
const timeJSX = (
|
const timeJSX = (
|
||||||
<Time ts={mEvent.getTs()} compact={messageLayout === MessageLayout.Compact} />
|
<Time
|
||||||
|
ts={mEvent.getTs()}
|
||||||
|
compact={messageLayout === MessageLayout.Compact}
|
||||||
|
hour24Clock={hour24Clock}
|
||||||
|
dateFormatString={dateFormatString}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1437,7 +1466,12 @@ export function RoomTimeline({
|
||||||
const senderName = getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId);
|
const senderName = getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId);
|
||||||
|
|
||||||
const timeJSX = (
|
const timeJSX = (
|
||||||
<Time ts={mEvent.getTs()} compact={messageLayout === MessageLayout.Compact} />
|
<Time
|
||||||
|
ts={mEvent.getTs()}
|
||||||
|
compact={messageLayout === MessageLayout.Compact}
|
||||||
|
hour24Clock={hour24Clock}
|
||||||
|
dateFormatString={dateFormatString}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1482,7 +1516,12 @@ export function RoomTimeline({
|
||||||
const senderName = getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId);
|
const senderName = getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId);
|
||||||
|
|
||||||
const timeJSX = (
|
const timeJSX = (
|
||||||
<Time ts={mEvent.getTs()} compact={messageLayout === MessageLayout.Compact} />
|
<Time
|
||||||
|
ts={mEvent.getTs()}
|
||||||
|
compact={messageLayout === MessageLayout.Compact}
|
||||||
|
hour24Clock={hour24Clock}
|
||||||
|
dateFormatString={dateFormatString}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -29,6 +29,8 @@ 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;
|
||||||
|
@ -45,6 +47,8 @@ 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>();
|
||||||
|
|
||||||
|
@ -125,7 +129,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)}</Text>
|
<Text size="B300">{timeHourMinute(ts, hour24Clock)}</Text>
|
||||||
</Chip>
|
</Chip>
|
||||||
<PopOut
|
<PopOut
|
||||||
anchor={timePickerCords}
|
anchor={timePickerCords}
|
||||||
|
|
|
@ -682,6 +682,8 @@ export type MessageProps = {
|
||||||
powerLevelTag?: PowerLevelTag;
|
powerLevelTag?: PowerLevelTag;
|
||||||
accessibleTagColors?: Map<string, string>;
|
accessibleTagColors?: Map<string, string>;
|
||||||
legacyUsernameColor?: boolean;
|
legacyUsernameColor?: boolean;
|
||||||
|
hour24Clock: boolean;
|
||||||
|
dateFormatString: string;
|
||||||
};
|
};
|
||||||
export const Message = as<'div', MessageProps>(
|
export const Message = as<'div', MessageProps>(
|
||||||
(
|
(
|
||||||
|
@ -711,6 +713,8 @@ export const Message = as<'div', MessageProps>(
|
||||||
powerLevelTag,
|
powerLevelTag,
|
||||||
accessibleTagColors,
|
accessibleTagColors,
|
||||||
legacyUsernameColor,
|
legacyUsernameColor,
|
||||||
|
hour24Clock,
|
||||||
|
dateFormatString,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
},
|
},
|
||||||
|
@ -775,7 +779,12 @@ export const Message = as<'div', MessageProps>(
|
||||||
</Text>
|
</Text>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Time ts={mEvent.getTs()} compact={messageLayout === MessageLayout.Compact} />
|
<Time
|
||||||
|
ts={mEvent.getTs()}
|
||||||
|
compact={messageLayout === MessageLayout.Compact}
|
||||||
|
hour24Clock={hour24Clock}
|
||||||
|
dateFormatString={dateFormatString}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
|
@ -102,6 +102,9 @@ function PinnedMessage({ room, eventId, renderContent, onOpen, canPinEvent }: Pi
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const accessibleTagColors = useAccessibleTagColors(theme.kind, powerLevelTags);
|
const accessibleTagColors = useAccessibleTagColors(theme.kind, powerLevelTags);
|
||||||
|
|
||||||
|
const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
|
||||||
|
const [dateFormatString] = useSetting(settingsAtom, 'dateFormatString');
|
||||||
|
|
||||||
const [unpinState, unpin] = useAsyncCallback(
|
const [unpinState, unpin] = useAsyncCallback(
|
||||||
useCallback(() => {
|
useCallback(() => {
|
||||||
const pinEvent = getStateEvent(room, StateEvent.RoomPinnedEvents);
|
const pinEvent = getStateEvent(room, StateEvent.RoomPinnedEvents);
|
||||||
|
@ -205,7 +208,11 @@ function PinnedMessage({ room, eventId, renderContent, onOpen, canPinEvent }: Pi
|
||||||
</Username>
|
</Username>
|
||||||
{tagIconSrc && <PowerIcon size="100" iconSrc={tagIconSrc} />}
|
{tagIconSrc && <PowerIcon size="100" iconSrc={tagIconSrc} />}
|
||||||
</Box>
|
</Box>
|
||||||
<Time ts={pinnedEvent.getTs()} />
|
<Time
|
||||||
|
ts={pinnedEvent.getTs()}
|
||||||
|
hour24Clock={hour24Clock}
|
||||||
|
dateFormatString={dateFormatString}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
{renderOptions()}
|
{renderOptions()}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
@ -27,6 +27,8 @@ 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 (
|
||||||
|
@ -41,6 +43,9 @@ export function DeviceTilePlaceholder() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function DeviceActiveTime({ ts }: { ts: number }) {
|
function DeviceActiveTime({ ts }: { ts: number }) {
|
||||||
|
const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
|
||||||
|
const [dateFormatString] = useSetting(settingsAtom, 'dateFormatString');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Text className={BreakWord} size="T200">
|
<Text className={BreakWord} size="T200">
|
||||||
<Text size="Inherit" as="span" priority="300">
|
<Text size="Inherit" as="span" priority="300">
|
||||||
|
@ -49,7 +54,8 @@ function DeviceActiveTime({ ts }: { ts: number }) {
|
||||||
<>
|
<>
|
||||||
{today(ts) && 'Today'}
|
{today(ts) && 'Today'}
|
||||||
{yesterday(ts) && 'Yesterday'}
|
{yesterday(ts) && 'Yesterday'}
|
||||||
{!today(ts) && !yesterday(ts) && timeDayMonYear(ts)} {timeHourMinute(ts)}
|
{!today(ts) && !yesterday(ts) && timeDayMonYear(ts, dateFormatString)}{' '}
|
||||||
|
{timeHourMinute(ts, hour24Clock)}
|
||||||
</>
|
</>
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,15 +1,19 @@
|
||||||
import React, {
|
import React, {
|
||||||
ChangeEventHandler,
|
ChangeEventHandler,
|
||||||
|
FormEventHandler,
|
||||||
KeyboardEventHandler,
|
KeyboardEventHandler,
|
||||||
MouseEventHandler,
|
MouseEventHandler,
|
||||||
|
useEffect,
|
||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
import {
|
import {
|
||||||
as,
|
as,
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Chip,
|
Chip,
|
||||||
config,
|
config,
|
||||||
|
Header,
|
||||||
Icon,
|
Icon,
|
||||||
IconButton,
|
IconButton,
|
||||||
Icons,
|
Icons,
|
||||||
|
@ -28,7 +32,7 @@ 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 { MessageLayout, MessageSpacing, settingsAtom } from '../../../state/settings';
|
import { DateFormat, MessageLayout, MessageSpacing, 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';
|
||||||
|
@ -44,6 +48,7 @@ import {
|
||||||
import { stopPropagation } from '../../../utils/keyboard';
|
import { stopPropagation } from '../../../utils/keyboard';
|
||||||
import { useMessageLayoutItems } from '../../../hooks/useMessageLayout';
|
import { useMessageLayoutItems } from '../../../hooks/useMessageLayout';
|
||||||
import { useMessageSpacingItems } from '../../../hooks/useMessageSpacing';
|
import { useMessageSpacingItems } from '../../../hooks/useMessageSpacing';
|
||||||
|
import { useDateFormatItems } from '../../../hooks/useDateFormat';
|
||||||
import { SequenceCardStyle } from '../styles.css';
|
import { SequenceCardStyle } from '../styles.css';
|
||||||
|
|
||||||
type ThemeSelectorProps = {
|
type ThemeSelectorProps = {
|
||||||
|
@ -341,6 +346,359 @@ function Appearance() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DateHintProps = {
|
||||||
|
hasChanges: boolean;
|
||||||
|
handleReset: () => void;
|
||||||
|
};
|
||||||
|
function DateHint({ hasChanges, handleReset }: DateHintProps) {
|
||||||
|
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">Formatting</Text>
|
||||||
|
</Header>
|
||||||
|
|
||||||
|
<Box direction="Column">
|
||||||
|
<Box style={categoryPadding} direction="Column">
|
||||||
|
<Header size="300">
|
||||||
|
<Text size="L400">Year</Text>
|
||||||
|
</Header>
|
||||||
|
<Box direction="Column" tabIndex={0} gap="100">
|
||||||
|
<Text size="T300">
|
||||||
|
YY
|
||||||
|
<Text as="span" size="Inherit" priority="300">
|
||||||
|
{': '}
|
||||||
|
Two-digit year
|
||||||
|
</Text>{' '}
|
||||||
|
</Text>
|
||||||
|
<Text size="T300">
|
||||||
|
YYYY
|
||||||
|
<Text as="span" size="Inherit" priority="300">
|
||||||
|
{': '}Four-digit year
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box style={categoryPadding} direction="Column">
|
||||||
|
<Header size="300">
|
||||||
|
<Text size="L400">Month</Text>
|
||||||
|
</Header>
|
||||||
|
<Box direction="Column" tabIndex={0} gap="100">
|
||||||
|
<Text size="T300">
|
||||||
|
M
|
||||||
|
<Text as="span" size="Inherit" priority="300">
|
||||||
|
{': '}The month
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
<Text size="T300">
|
||||||
|
MM
|
||||||
|
<Text as="span" size="Inherit" priority="300">
|
||||||
|
{': '}Two-digit month
|
||||||
|
</Text>{' '}
|
||||||
|
</Text>
|
||||||
|
<Text size="T300">
|
||||||
|
MMM
|
||||||
|
<Text as="span" size="Inherit" priority="300">
|
||||||
|
{': '}Short month name
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
<Text size="T300">
|
||||||
|
MMMM
|
||||||
|
<Text as="span" size="Inherit" priority="300">
|
||||||
|
{': '}Full month name
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box style={categoryPadding} direction="Column">
|
||||||
|
<Header size="300">
|
||||||
|
<Text size="L400">Day of the Month</Text>
|
||||||
|
</Header>
|
||||||
|
<Box direction="Column" tabIndex={0} gap="100">
|
||||||
|
<Text size="T300">
|
||||||
|
D
|
||||||
|
<Text as="span" size="Inherit" priority="300">
|
||||||
|
{': '}Day of the month
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
<Text size="T300">
|
||||||
|
DD
|
||||||
|
<Text as="span" size="Inherit" priority="300">
|
||||||
|
{': '}Two-digit day of the month
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Box style={categoryPadding} direction="Column">
|
||||||
|
<Header size="300">
|
||||||
|
<Text size="L400">Day of the Week</Text>
|
||||||
|
</Header>
|
||||||
|
<Box direction="Column" tabIndex={0} gap="100">
|
||||||
|
<Text size="T300">
|
||||||
|
d
|
||||||
|
<Text as="span" size="Inherit" priority="300">
|
||||||
|
{': '}Day of the week (Sunday = 0)
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
<Text size="T300">
|
||||||
|
dd
|
||||||
|
<Text as="span" size="Inherit" priority="300">
|
||||||
|
{': '}Two-letter day name
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
<Text size="T300">
|
||||||
|
ddd
|
||||||
|
<Text as="span" size="Inherit" priority="300">
|
||||||
|
{': '}Short day name
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
<Text size="T300">
|
||||||
|
dddd
|
||||||
|
<Text as="span" size="Inherit" priority="300">
|
||||||
|
{': '}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 [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">Save</Text>
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</SettingTile>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
type PresetDateFormatProps = {
|
||||||
|
value: string;
|
||||||
|
onChange: (format: string) => void;
|
||||||
|
};
|
||||||
|
function PresetDateFormat({ value, onChange }: PresetDateFormatProps) {
|
||||||
|
const [menuCords, setMenuCords] = useState<RectCords>();
|
||||||
|
const dateFormatItems = useDateFormatItems();
|
||||||
|
|
||||||
|
const getDisplayDate = (format: string): string =>
|
||||||
|
format !== '' ? dayjs().format(format) : '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 [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="Date Format"
|
||||||
|
description={customDateFormat ? dayjs().format(dateFormatString) : ''}
|
||||||
|
after={<PresetDateFormat value={selectedDateFormat} onChange={handlePresetChange} />}
|
||||||
|
/>
|
||||||
|
{customDateFormat && (
|
||||||
|
<CustomDateFormat value={dateFormatString} onChange={setDateFormatString} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DateAndTime() {
|
||||||
|
const [hour24Clock, setHour24Clock] = useSetting(settingsAtom, 'hour24Clock');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box direction="Column" gap="100">
|
||||||
|
<Text size="L400">Date & Time</Text>
|
||||||
|
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
|
||||||
|
<SettingTile
|
||||||
|
title="24-Hour Time Format"
|
||||||
|
after={<Switch variant="Primary" value={hour24Clock} onChange={setHour24Clock} />}
|
||||||
|
/>
|
||||||
|
</SequenceCard>
|
||||||
|
|
||||||
|
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
|
||||||
|
<SelectDateFormat />
|
||||||
|
</SequenceCard>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function Editor() {
|
function Editor() {
|
||||||
const [enterForNewline, setEnterForNewline] = useSetting(settingsAtom, 'enterForNewline');
|
const [enterForNewline, setEnterForNewline] = useSetting(settingsAtom, 'enterForNewline');
|
||||||
const [isMarkdown, setIsMarkdown] = useSetting(settingsAtom, 'isMarkdown');
|
const [isMarkdown, setIsMarkdown] = useSetting(settingsAtom, 'isMarkdown');
|
||||||
|
@ -637,6 +995,7 @@ 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>
|
||||||
|
|
34
src/app/hooks/useDateFormat.ts
Normal file
34
src/app/hooks/useDateFormat.ts
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
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: '',
|
||||||
|
name: 'Custom',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
|
@ -65,6 +65,8 @@ import { testBadWords } from '../../../plugins/bad-words';
|
||||||
import { allRoomsAtom } from '../../../state/room-list/roomList';
|
import { allRoomsAtom } from '../../../state/room-list/roomList';
|
||||||
import { useIgnoredUsers } from '../../../hooks/useIgnoredUsers';
|
import { useIgnoredUsers } from '../../../hooks/useIgnoredUsers';
|
||||||
import { useReportRoomSupported } from '../../../hooks/useReportRoomSupported';
|
import { useReportRoomSupported } from '../../../hooks/useReportRoomSupported';
|
||||||
|
import { useSetting } from '../../../state/hooks/settings';
|
||||||
|
import { settingsAtom } from '../../../state/settings';
|
||||||
|
|
||||||
const COMPACT_CARD_WIDTH = 548;
|
const COMPACT_CARD_WIDTH = 548;
|
||||||
|
|
||||||
|
@ -135,10 +137,19 @@ type NavigateHandler = (roomId: string, space: boolean) => void;
|
||||||
type InviteCardProps = {
|
type InviteCardProps = {
|
||||||
invite: InviteData;
|
invite: InviteData;
|
||||||
compact?: boolean;
|
compact?: boolean;
|
||||||
|
hour24Clock: boolean;
|
||||||
|
dateFormatString: string;
|
||||||
onNavigate: NavigateHandler;
|
onNavigate: NavigateHandler;
|
||||||
hideAvatar: boolean;
|
hideAvatar: boolean;
|
||||||
};
|
};
|
||||||
function InviteCard({ invite, compact, onNavigate, hideAvatar }: InviteCardProps) {
|
function InviteCard({
|
||||||
|
invite,
|
||||||
|
compact,
|
||||||
|
hour24Clock,
|
||||||
|
dateFormatString,
|
||||||
|
onNavigate,
|
||||||
|
hideAvatar,
|
||||||
|
}: InviteCardProps) {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
const userId = mx.getSafeUserId();
|
const userId = mx.getSafeUserId();
|
||||||
|
|
||||||
|
@ -295,7 +306,13 @@ function InviteCard({ invite, compact, onNavigate, hideAvatar }: InviteCardProps
|
||||||
</Box>
|
</Box>
|
||||||
{invite.inviteTs && (
|
{invite.inviteTs && (
|
||||||
<Box shrink="No">
|
<Box shrink="No">
|
||||||
<Time size="T200" ts={invite.inviteTs} priority="300" />
|
<Time
|
||||||
|
size="T200"
|
||||||
|
ts={invite.inviteTs}
|
||||||
|
hour24Clock={hour24Clock}
|
||||||
|
dateFormatString={dateFormatString}
|
||||||
|
priority="300"
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -384,8 +401,16 @@ type KnownInvitesProps = {
|
||||||
invites: InviteData[];
|
invites: InviteData[];
|
||||||
handleNavigate: NavigateHandler;
|
handleNavigate: NavigateHandler;
|
||||||
compact: boolean;
|
compact: boolean;
|
||||||
|
hour24Clock: boolean;
|
||||||
|
dateFormatString: string;
|
||||||
};
|
};
|
||||||
function KnownInvites({ invites, handleNavigate, compact }: KnownInvitesProps) {
|
function KnownInvites({
|
||||||
|
invites,
|
||||||
|
handleNavigate,
|
||||||
|
compact,
|
||||||
|
hour24Clock,
|
||||||
|
dateFormatString,
|
||||||
|
}: KnownInvitesProps) {
|
||||||
return (
|
return (
|
||||||
<Box direction="Column" gap="200">
|
<Box direction="Column" gap="200">
|
||||||
<Text size="H4">Primary</Text>
|
<Text size="H4">Primary</Text>
|
||||||
|
@ -396,6 +421,8 @@ function KnownInvites({ invites, handleNavigate, compact }: KnownInvitesProps) {
|
||||||
key={invite.roomId}
|
key={invite.roomId}
|
||||||
invite={invite}
|
invite={invite}
|
||||||
compact={compact}
|
compact={compact}
|
||||||
|
hour24Clock={hour24Clock}
|
||||||
|
dateFormatString={dateFormatString}
|
||||||
onNavigate={handleNavigate}
|
onNavigate={handleNavigate}
|
||||||
hideAvatar={false}
|
hideAvatar={false}
|
||||||
/>
|
/>
|
||||||
|
@ -420,8 +447,16 @@ type UnknownInvitesProps = {
|
||||||
invites: InviteData[];
|
invites: InviteData[];
|
||||||
handleNavigate: NavigateHandler;
|
handleNavigate: NavigateHandler;
|
||||||
compact: boolean;
|
compact: boolean;
|
||||||
|
hour24Clock: boolean;
|
||||||
|
dateFormatString: string;
|
||||||
};
|
};
|
||||||
function UnknownInvites({ invites, handleNavigate, compact }: UnknownInvitesProps) {
|
function UnknownInvites({
|
||||||
|
invites,
|
||||||
|
handleNavigate,
|
||||||
|
compact,
|
||||||
|
hour24Clock,
|
||||||
|
dateFormatString,
|
||||||
|
}: UnknownInvitesProps) {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
|
||||||
const [declineAllStatus, declineAll] = useAsyncCallback(
|
const [declineAllStatus, declineAll] = useAsyncCallback(
|
||||||
|
@ -459,6 +494,8 @@ function UnknownInvites({ invites, handleNavigate, compact }: UnknownInvitesProp
|
||||||
key={invite.roomId}
|
key={invite.roomId}
|
||||||
invite={invite}
|
invite={invite}
|
||||||
compact={compact}
|
compact={compact}
|
||||||
|
hour24Clock={hour24Clock}
|
||||||
|
dateFormatString={dateFormatString}
|
||||||
onNavigate={handleNavigate}
|
onNavigate={handleNavigate}
|
||||||
hideAvatar
|
hideAvatar
|
||||||
/>
|
/>
|
||||||
|
@ -483,8 +520,16 @@ type SpamInvitesProps = {
|
||||||
invites: InviteData[];
|
invites: InviteData[];
|
||||||
handleNavigate: NavigateHandler;
|
handleNavigate: NavigateHandler;
|
||||||
compact: boolean;
|
compact: boolean;
|
||||||
|
hour24Clock: boolean;
|
||||||
|
dateFormatString: string;
|
||||||
};
|
};
|
||||||
function SpamInvites({ invites, handleNavigate, compact }: SpamInvitesProps) {
|
function SpamInvites({
|
||||||
|
invites,
|
||||||
|
handleNavigate,
|
||||||
|
compact,
|
||||||
|
hour24Clock,
|
||||||
|
dateFormatString,
|
||||||
|
}: SpamInvitesProps) {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
const [showInvites, setShowInvites] = useState(false);
|
const [showInvites, setShowInvites] = useState(false);
|
||||||
|
|
||||||
|
@ -608,6 +653,8 @@ function SpamInvites({ invites, handleNavigate, compact }: SpamInvitesProps) {
|
||||||
key={invite.roomId}
|
key={invite.roomId}
|
||||||
invite={invite}
|
invite={invite}
|
||||||
compact={compact}
|
compact={compact}
|
||||||
|
hour24Clock={hour24Clock}
|
||||||
|
dateFormatString={dateFormatString}
|
||||||
onNavigate={handleNavigate}
|
onNavigate={handleNavigate}
|
||||||
hideAvatar
|
hideAvatar
|
||||||
/>
|
/>
|
||||||
|
@ -671,6 +718,9 @@ export function Invites() {
|
||||||
);
|
);
|
||||||
const screenSize = useScreenSizeContext();
|
const screenSize = useScreenSizeContext();
|
||||||
|
|
||||||
|
const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
|
||||||
|
const [dateFormatString] = useSetting(settingsAtom, 'dateFormatString');
|
||||||
|
|
||||||
const handleNavigate = (roomId: string, space: boolean) => {
|
const handleNavigate = (roomId: string, space: boolean) => {
|
||||||
if (space) {
|
if (space) {
|
||||||
navigateSpace(roomId);
|
navigateSpace(roomId);
|
||||||
|
@ -723,6 +773,8 @@ export function Invites() {
|
||||||
<KnownInvites
|
<KnownInvites
|
||||||
invites={knownInvites}
|
invites={knownInvites}
|
||||||
compact={compact}
|
compact={compact}
|
||||||
|
hour24Clock={hour24Clock}
|
||||||
|
dateFormatString={dateFormatString}
|
||||||
handleNavigate={handleNavigate}
|
handleNavigate={handleNavigate}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -731,6 +783,8 @@ export function Invites() {
|
||||||
<UnknownInvites
|
<UnknownInvites
|
||||||
invites={unknownInvites}
|
invites={unknownInvites}
|
||||||
compact={compact}
|
compact={compact}
|
||||||
|
hour24Clock={hour24Clock}
|
||||||
|
dateFormatString={dateFormatString}
|
||||||
handleNavigate={handleNavigate}
|
handleNavigate={handleNavigate}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -739,6 +793,8 @@ export function Invites() {
|
||||||
<SpamInvites
|
<SpamInvites
|
||||||
invites={spamInvites}
|
invites={spamInvites}
|
||||||
compact={compact}
|
compact={compact}
|
||||||
|
hour24Clock={hour24Clock}
|
||||||
|
dateFormatString={dateFormatString}
|
||||||
handleNavigate={handleNavigate}
|
handleNavigate={handleNavigate}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -205,6 +205,8 @@ type RoomNotificationsGroupProps = {
|
||||||
hideActivity: boolean;
|
hideActivity: boolean;
|
||||||
onOpen: (roomId: string, eventId: string) => void;
|
onOpen: (roomId: string, eventId: string) => void;
|
||||||
legacyUsernameColor?: boolean;
|
legacyUsernameColor?: boolean;
|
||||||
|
hour24Clock: boolean;
|
||||||
|
dateFormatString: string;
|
||||||
};
|
};
|
||||||
function RoomNotificationsGroupComp({
|
function RoomNotificationsGroupComp({
|
||||||
room,
|
room,
|
||||||
|
@ -214,6 +216,8 @@ function RoomNotificationsGroupComp({
|
||||||
hideActivity,
|
hideActivity,
|
||||||
onOpen,
|
onOpen,
|
||||||
legacyUsernameColor,
|
legacyUsernameColor,
|
||||||
|
hour24Clock,
|
||||||
|
dateFormatString,
|
||||||
}: RoomNotificationsGroupProps) {
|
}: RoomNotificationsGroupProps) {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
const useAuthentication = useMediaAuthentication();
|
const useAuthentication = useMediaAuthentication();
|
||||||
|
@ -496,7 +500,11 @@ function RoomNotificationsGroupComp({
|
||||||
</Username>
|
</Username>
|
||||||
{tagIconSrc && <PowerIcon size="100" iconSrc={tagIconSrc} />}
|
{tagIconSrc && <PowerIcon size="100" iconSrc={tagIconSrc} />}
|
||||||
</Box>
|
</Box>
|
||||||
<Time ts={event.origin_server_ts} />
|
<Time
|
||||||
|
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
|
||||||
|
@ -549,6 +557,8 @@ export function Notifications() {
|
||||||
const [mediaAutoLoad] = useSetting(settingsAtom, 'mediaAutoLoad');
|
const [mediaAutoLoad] = useSetting(settingsAtom, 'mediaAutoLoad');
|
||||||
const [urlPreview] = useSetting(settingsAtom, 'urlPreview');
|
const [urlPreview] = useSetting(settingsAtom, 'urlPreview');
|
||||||
const [legacyUsernameColor] = useSetting(settingsAtom, 'legacyUsernameColor');
|
const [legacyUsernameColor] = useSetting(settingsAtom, 'legacyUsernameColor');
|
||||||
|
const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
|
||||||
|
const [dateFormatString] = useSetting(settingsAtom, 'dateFormatString');
|
||||||
const screenSize = useScreenSizeContext();
|
const screenSize = useScreenSizeContext();
|
||||||
const mDirects = useAtomValue(mDirectAtom);
|
const mDirects = useAtomValue(mDirectAtom);
|
||||||
|
|
||||||
|
@ -713,6 +723,8 @@ export function Notifications() {
|
||||||
legacyUsernameColor={
|
legacyUsernameColor={
|
||||||
legacyUsernameColor || mDirects.has(groupRoom.roomId)
|
legacyUsernameColor || mDirects.has(groupRoom.roomId)
|
||||||
}
|
}
|
||||||
|
hour24Clock={hour24Clock}
|
||||||
|
dateFormatString={dateFormatString}
|
||||||
/>
|
/>
|
||||||
</VirtualTile>
|
</VirtualTile>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
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' | '';
|
||||||
export type MessageSpacing = '0' | '100' | '200' | '300' | '400' | '500';
|
export type MessageSpacing = '0' | '100' | '200' | '300' | '400' | '500';
|
||||||
export enum MessageLayout {
|
export enum MessageLayout {
|
||||||
Modern = 0,
|
Modern = 0,
|
||||||
|
@ -35,6 +36,9 @@ export interface Settings {
|
||||||
showNotifications: boolean;
|
showNotifications: boolean;
|
||||||
isNotificationSounds: boolean;
|
isNotificationSounds: boolean;
|
||||||
|
|
||||||
|
hour24Clock: boolean;
|
||||||
|
dateFormatString: string;
|
||||||
|
|
||||||
developerTools: boolean;
|
developerTools: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,6 +69,9 @@ const defaultSettings: Settings = {
|
||||||
showNotifications: true,
|
showNotifications: true,
|
||||||
isNotificationSounds: true,
|
isNotificationSounds: true,
|
||||||
|
|
||||||
|
hour24Clock: false,
|
||||||
|
dateFormatString: 'D MMM YYYY',
|
||||||
|
|
||||||
developerTools: false,
|
developerTools: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,8 @@ 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): string => dayjs(ts).format('hh');
|
export const timeHour = (ts: number, hour24Clock: boolean): string =>
|
||||||
|
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');
|
||||||
|
@ -17,9 +18,11 @@ 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): string => dayjs(ts).format('hh:mm A');
|
export const timeHourMinute = (ts: number, hour24Clock: boolean): string =>
|
||||||
|
dayjs(ts).format(hour24Clock ? 'HH:mm' : 'hh:mm A');
|
||||||
|
|
||||||
export const timeDayMonYear = (ts: number): string => dayjs(ts).format('D MMM YYYY');
|
export const timeDayMonYear = (ts: number, dateFormatString: string): string =>
|
||||||
|
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
Add a link
Reference in a new issue