Add incomplete dateFormatString setting

This commit is contained in:
Gimle Larpes 2025-05-30 20:56:05 +02:00
parent 4588168a66
commit 1295c1c9b7
14 changed files with 203 additions and 15 deletions

View file

@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
import dateFormat from 'dateformat';
import { isInSameDay } from '../../../util/common';
function Time({ timestamp, fullTime, hour24Clock }) {
function Time({ timestamp, fullTime, hour24Clock, dateFormatString }) {
const date = new Date(timestamp);
const formattedFullTime = dateFormat(
@ -21,7 +21,7 @@ function Time({ timestamp, fullTime, hour24Clock }) {
const timeFormat = hour24Clock ? 'HH:MM' : 'hh:MM TT';
formattedDate = dateFormat(date, isToday || isYesterday ? timeFormat : 'dd/mm/yyyy');
formattedDate = dateFormat(date, isToday || isYesterday ? timeFormat : dateFormatString);
if (isYesterday) {
formattedDate = `Yesterday, ${formattedDate}`;
}
@ -42,6 +42,7 @@ Time.propTypes = {
timestamp: PropTypes.number.isRequired,
fullTime: PropTypes.bool,
hour24Clock: PropTypes.bool.isRequired,
dateFormatString: PropTypes.string.isRequired,
};
export default Time;

View file

@ -6,10 +6,11 @@ export type TimeProps = {
compact?: boolean;
ts: number;
hour24Clock: boolean;
dateFormatString: string;
};
export const Time = as<'span', TimeProps & ComponentProps<typeof Text>>(
({ compact, hour24Clock, ts, ...props }, ref) => {
({ compact, hour24Clock, dateFormatString, ts, ...props }, ref) => {
const formattedTime = timeHourMinute(ts, hour24Clock);
let time = '';
@ -20,7 +21,7 @@ export const Time = as<'span', TimeProps & ComponentProps<typeof Text>>(
} else if (yesterday(ts)) {
time = `Yesterday ${formattedTime}`;
} else {
time = `${timeDayMonYear(ts)} ${formattedTime}`;
time = `${timeDayMonYear(ts, dateFormatString)} ${formattedTime}`;
}
return (

View file

@ -58,6 +58,7 @@ export function MessageSearch({
const [legacyUsernameColor] = useSetting(settingsAtom, 'legacyUsernameColor');
const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
const [dateFormatString] = useSetting(settingsAtom, 'dateFormatString');
const searchInputRef = useRef<HTMLInputElement>(null);
const scrollTopAnchorRef = useRef<HTMLDivElement>(null);
@ -292,6 +293,7 @@ export function MessageSearch({
onOpen={navigateRoom}
legacyUsernameColor={legacyUsernameColor || mDirects.has(groupRoom.roomId)}
hour24Clock={hour24Clock}
dateFormatString={dateFormatString}
/>
</VirtualTile>
);

View file

@ -58,6 +58,7 @@ type SearchResultGroupProps = {
onOpen: (roomId: string, eventId: string) => void;
legacyUsernameColor?: boolean;
hour24Clock: boolean;
dateFormatString: string;
};
export function SearchResultGroup({
room,
@ -68,6 +69,7 @@ export function SearchResultGroup({
onOpen,
legacyUsernameColor,
hour24Clock,
dateFormatString,
}: SearchResultGroupProps) {
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
@ -277,7 +279,11 @@ export function SearchResultGroup({
</Username>
{tagIconSrc && <PowerIcon size="100" iconSrc={tagIconSrc} />}
</Box>
<Time ts={event.origin_server_ts} hour24Clock={hour24Clock} />
<Time
ts={event.origin_server_ts}
hour24Clock={hour24Clock}
dateFormatString={dateFormatString}
/>
</Box>
<Box shrink="No" gap="200" alignItems="Center">
<Chip

View file

@ -449,6 +449,7 @@ export function RoomTimeline({
const showUrlPreview = room.hasEncryptionStateEvent() ? encUrlPreview : urlPreview;
const [showHiddenEvents] = useSetting(settingsAtom, 'showHiddenEvents');
const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
const [dateFormatString] = useSetting(settingsAtom, 'dateFormatString');
const ignoredUsersList = useIgnoredUsers();
const ignoredUsersSet = useMemo(() => new Set(ignoredUsersList), [ignoredUsersList]);
@ -1070,6 +1071,7 @@ export function RoomTimeline({
accessibleTagColors={accessibleTagColors}
legacyUsernameColor={legacyUsernameColor || direct}
hour24Clock={hour24Clock}
dateFormatString={dateFormatString}
>
{mEvent.isRedacted() ? (
<RedactedContent reason={mEvent.getUnsigned().redacted_because?.content.reason} />
@ -1152,6 +1154,7 @@ export function RoomTimeline({
accessibleTagColors={accessibleTagColors}
legacyUsernameColor={legacyUsernameColor || direct}
hour24Clock={hour24Clock}
dateFormatString={dateFormatString}
>
<EncryptedContent mEvent={mEvent}>
{() => {
@ -1254,6 +1257,7 @@ export function RoomTimeline({
accessibleTagColors={accessibleTagColors}
legacyUsernameColor={legacyUsernameColor || direct}
hour24Clock={hour24Clock}
dateFormatString={dateFormatString}
>
{mEvent.isRedacted() ? (
<RedactedContent reason={mEvent.getUnsigned().redacted_because?.content.reason} />
@ -1286,6 +1290,7 @@ export function RoomTimeline({
ts={mEvent.getTs()}
compact={messageLayout === MessageLayout.Compact}
hour24Clock={hour24Clock}
dateFormatString={dateFormatString}
/>
);
@ -1326,6 +1331,7 @@ export function RoomTimeline({
ts={mEvent.getTs()}
compact={messageLayout === MessageLayout.Compact}
hour24Clock={hour24Clock}
dateFormatString={dateFormatString}
/>
);
@ -1367,6 +1373,7 @@ export function RoomTimeline({
ts={mEvent.getTs()}
compact={messageLayout === MessageLayout.Compact}
hour24Clock={hour24Clock}
dateFormatString={dateFormatString}
/>
);
@ -1408,6 +1415,7 @@ export function RoomTimeline({
ts={mEvent.getTs()}
compact={messageLayout === MessageLayout.Compact}
hour24Clock={hour24Clock}
dateFormatString={dateFormatString}
/>
);
@ -1451,6 +1459,7 @@ export function RoomTimeline({
ts={mEvent.getTs()}
compact={messageLayout === MessageLayout.Compact}
hour24Clock={hour24Clock}
dateFormatString={dateFormatString}
/>
);
@ -1499,6 +1508,7 @@ export function RoomTimeline({
ts={mEvent.getTs()}
compact={messageLayout === MessageLayout.Compact}
hour24Clock={hour24Clock}
dateFormatString={dateFormatString}
/>
);

View file

@ -679,6 +679,7 @@ export type MessageProps = {
accessibleTagColors?: Map<string, string>;
legacyUsernameColor?: boolean;
hour24Clock: boolean;
dateFormatString: string;
};
export const Message = as<'div', MessageProps>(
(
@ -708,6 +709,7 @@ export const Message = as<'div', MessageProps>(
accessibleTagColors,
legacyUsernameColor,
hour24Clock,
dateFormatString,
children,
...props
},
@ -776,6 +778,7 @@ export const Message = as<'div', MessageProps>(
ts={mEvent.getTs()}
compact={messageLayout === MessageLayout.Compact}
hour24Clock={hour24Clock}
dateFormatString={dateFormatString}
/>
</Box>
</Box>

View file

@ -103,6 +103,7 @@ function PinnedMessage({ room, eventId, renderContent, onOpen, canPinEvent }: Pi
const accessibleTagColors = useAccessibleTagColors(theme.kind, powerLevelTags);
const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
const [dateFormatString] = useSetting(settingsAtom, 'dateFormatString');
const [unpinState, unpin] = useAsyncCallback(
useCallback(() => {
@ -207,7 +208,11 @@ function PinnedMessage({ room, eventId, renderContent, onOpen, canPinEvent }: Pi
</Username>
{tagIconSrc && <PowerIcon size="100" iconSrc={tagIconSrc} />}
</Box>
<Time ts={pinnedEvent.getTs()} hour24Clock={hour24Clock} />
<Time
ts={pinnedEvent.getTs()}
hour24Clock={hour24Clock}
dateFormatString={dateFormatString}
/>
</Box>
{renderOptions()}
</Box>

View file

@ -44,6 +44,7 @@ export function DeviceTilePlaceholder() {
function DeviceActiveTime({ ts }: { ts: number }) {
const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
const [dateFormatString] = useSetting(settingsAtom, 'dateFormatString');
return (
<Text className={BreakWord} size="T200">
@ -53,7 +54,8 @@ function DeviceActiveTime({ ts }: { ts: number }) {
<>
{today(ts) && 'Today'}
{yesterday(ts) && 'Yesterday'}
{!today(ts) && !yesterday(ts) && timeDayMonYear(ts)} {timeHourMinute(ts, hour24Clock)}
{!today(ts) && !yesterday(ts) && timeDayMonYear(ts, dateFormatString)}{' '}
{timeHourMinute(ts, hour24Clock)}
</>
</Text>
);

View file

@ -28,7 +28,7 @@ import FocusTrap from 'focus-trap-react';
import { Page, PageContent, PageHeader } from '../../../components/page';
import { SequenceCard } from '../../../components/sequence-card';
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 { KeySymbol } from '../../../utils/key-symbol';
import { isMacOS } from '../../../utils/user-agent';
@ -44,6 +44,7 @@ import {
import { stopPropagation } from '../../../utils/keyboard';
import { useMessageLayoutItems } from '../../../hooks/useMessageLayout';
import { useMessageSpacingItems } from '../../../hooks/useMessageSpacing';
import { useDateFormatItems } from '../../../hooks/useDateFormat';
import { SequenceCardStyle } from '../styles.css';
type ThemeSelectorProps = {
@ -513,6 +514,76 @@ function SelectMessageSpacing() {
);
}
function SelectDateFormat() {
// ADD TEXT INPUT AND RELATED LOGIC-- ADD LIVE PREVIEW
const [menuCords, setMenuCords] = useState<RectCords>();
const [dateFormatString, setDateFormatString] = useSetting(settingsAtom, 'dateFormatString');
const dateFormatItems = useDateFormatItems();
const handleMenu: MouseEventHandler<HTMLButtonElement> = (evt) => {
setMenuCords(evt.currentTarget.getBoundingClientRect());
};
const handleSelect = (format: DateFormat) => {
setDateFormatString(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">
{dateFormatItems.find((i) => i.format === dateFormatString)?.name ?? dateFormatString}
</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={dateFormatString === item.format ? 'Primary' : 'Surface'}
radii="300"
onClick={() => handleSelect(item.format)}
>
<Text size="T300">{item.name}</Text>
</MenuItem>
))}
</Box>
</Menu>
</FocusTrap>
}
/>
</>
);
}
function Messages() {
const [legacyUsernameColor, setLegacyUsernameColor] = useSetting(
settingsAtom,
@ -615,6 +686,9 @@ function Messages() {
after={<Switch variant="Primary" value={hour24Clock} onChange={setHour24Clock} />}
/>
</SequenceCard>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile title="Date Format" after={<SelectDateFormat />} />
</SequenceCard>
</Box>
);
}

View file

@ -0,0 +1,30 @@
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: 'YYYY/MM/DD',
name: 'YYYY/MM/DD',
},
{
format: '',
name: 'Custom',
},
],
[]
);

View file

@ -138,10 +138,18 @@ type InviteCardProps = {
invite: InviteData;
compact?: boolean;
hour24Clock: boolean;
dateFormatString: string;
onNavigate: NavigateHandler;
hideAvatar: boolean;
};
function InviteCard({ invite, compact, hour24Clock, onNavigate, hideAvatar }: InviteCardProps) {
function InviteCard({
invite,
compact,
hour24Clock,
dateFormatString,
onNavigate,
hideAvatar,
}: InviteCardProps) {
const mx = useMatrixClient();
const userId = mx.getSafeUserId();
@ -298,7 +306,13 @@ function InviteCard({ invite, compact, hour24Clock, onNavigate, hideAvatar }: In
</Box>
{invite.inviteTs && (
<Box shrink="No">
<Time size="T200" ts={invite.inviteTs} hour24Clock={hour24Clock} priority="300" />
<Time
size="T200"
ts={invite.inviteTs}
hour24Clock={hour24Clock}
dateFormatString={dateFormatString}
priority="300"
/>
</Box>
)}
</Box>
@ -388,8 +402,15 @@ type KnownInvitesProps = {
handleNavigate: NavigateHandler;
compact: boolean;
hour24Clock: boolean;
dateFormatString: string;
};
function KnownInvites({ invites, handleNavigate, compact, hour24Clock }: KnownInvitesProps) {
function KnownInvites({
invites,
handleNavigate,
compact,
hour24Clock,
dateFormatString,
}: KnownInvitesProps) {
return (
<Box direction="Column" gap="200">
<Text size="H4">Primary</Text>
@ -401,6 +422,7 @@ function KnownInvites({ invites, handleNavigate, compact, hour24Clock }: KnownIn
invite={invite}
compact={compact}
hour24Clock={hour24Clock}
dateFormatString={dateFormatString}
onNavigate={handleNavigate}
hideAvatar={false}
/>
@ -426,8 +448,15 @@ type UnknownInvitesProps = {
handleNavigate: NavigateHandler;
compact: boolean;
hour24Clock: boolean;
dateFormatString: string;
};
function UnknownInvites({ invites, handleNavigate, compact, hour24Clock }: UnknownInvitesProps) {
function UnknownInvites({
invites,
handleNavigate,
compact,
hour24Clock,
dateFormatString,
}: UnknownInvitesProps) {
const mx = useMatrixClient();
const [declineAllStatus, declineAll] = useAsyncCallback(
@ -466,6 +495,7 @@ function UnknownInvites({ invites, handleNavigate, compact, hour24Clock }: Unkno
invite={invite}
compact={compact}
hour24Clock={hour24Clock}
dateFormatString={dateFormatString}
onNavigate={handleNavigate}
hideAvatar
/>
@ -491,8 +521,15 @@ type SpamInvitesProps = {
handleNavigate: NavigateHandler;
compact: boolean;
hour24Clock: boolean;
dateFormatString: string;
};
function SpamInvites({ invites, handleNavigate, compact, hour24Clock }: SpamInvitesProps) {
function SpamInvites({
invites,
handleNavigate,
compact,
hour24Clock,
dateFormatString,
}: SpamInvitesProps) {
const mx = useMatrixClient();
const [showInvites, setShowInvites] = useState(false);
@ -617,6 +654,7 @@ function SpamInvites({ invites, handleNavigate, compact, hour24Clock }: SpamInvi
invite={invite}
compact={compact}
hour24Clock={hour24Clock}
dateFormatString={dateFormatString}
onNavigate={handleNavigate}
hideAvatar
/>
@ -681,6 +719,7 @@ export function Invites() {
const screenSize = useScreenSizeContext();
const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
const [dateFormatString] = useSetting(settingsAtom, 'dateFormatString');
const handleNavigate = (roomId: string, space: boolean) => {
if (space) {
@ -735,6 +774,7 @@ export function Invites() {
invites={knownInvites}
compact={compact}
hour24Clock={hour24Clock}
dateFormatString={dateFormatString}
handleNavigate={handleNavigate}
/>
)}
@ -744,6 +784,7 @@ export function Invites() {
invites={unknownInvites}
compact={compact}
hour24Clock={hour24Clock}
dateFormatString={dateFormatString}
handleNavigate={handleNavigate}
/>
)}
@ -753,6 +794,7 @@ export function Invites() {
invites={spamInvites}
compact={compact}
hour24Clock={hour24Clock}
dateFormatString={dateFormatString}
handleNavigate={handleNavigate}
/>
)}

View file

@ -206,6 +206,7 @@ type RoomNotificationsGroupProps = {
onOpen: (roomId: string, eventId: string) => void;
legacyUsernameColor?: boolean;
hour24Clock: boolean;
dateFormatString: string;
};
function RoomNotificationsGroupComp({
room,
@ -216,6 +217,7 @@ function RoomNotificationsGroupComp({
onOpen,
legacyUsernameColor,
hour24Clock,
dateFormatString,
}: RoomNotificationsGroupProps) {
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
@ -498,7 +500,11 @@ function RoomNotificationsGroupComp({
</Username>
{tagIconSrc && <PowerIcon size="100" iconSrc={tagIconSrc} />}
</Box>
<Time ts={event.origin_server_ts} hour24Clock={hour24Clock} />
<Time
ts={event.origin_server_ts}
hour24Clock={hour24Clock}
dateFormatString={dateFormatString}
/>
</Box>
<Box shrink="No" gap="200" alignItems="Center">
<Chip
@ -552,6 +558,7 @@ export function Notifications() {
const [urlPreview] = useSetting(settingsAtom, 'urlPreview');
const [legacyUsernameColor] = useSetting(settingsAtom, 'legacyUsernameColor');
const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
const [dateFormatString] = useSetting(settingsAtom, 'dateFormatString');
const screenSize = useScreenSizeContext();
const mDirects = useAtomValue(mDirectAtom);
@ -717,6 +724,7 @@ export function Notifications() {
legacyUsernameColor || mDirects.has(groupRoom.roomId)
}
hour24Clock={hour24Clock}
dateFormatString={dateFormatString}
/>
</VirtualTile>
);

View file

@ -1,6 +1,7 @@
import { atom } from 'jotai';
const STORAGE_KEY = 'settings';
export type DateFormat = 'D MMM YYYY' | 'DD/MM/YYYY' | 'YYYY/MM/DD' | '';
export type MessageSpacing = '0' | '100' | '200' | '300' | '400' | '500';
export enum MessageLayout {
Modern = 0,
@ -36,6 +37,7 @@ export interface Settings {
isNotificationSounds: boolean;
hour24Clock: boolean;
dateFormatString: string;
developerTools: boolean;
}
@ -68,6 +70,7 @@ const defaultSettings: Settings = {
isNotificationSounds: true,
hour24Clock: false,
dateFormatString: 'D MMM YYYY',
developerTools: false,
};

View file

@ -12,7 +12,8 @@ export const yesterday = (ts: number): boolean => dayjs(ts).isYesterday();
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');