mirror of
				https://github.com/cinnyapp/cinny.git
				synced 2025-11-04 06:20:28 +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 { 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 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;
 | 
			
		||||
 | 
			
		||||
  if (!fullTime) {
 | 
			
		||||
| 
						 | 
				
			
			@ -16,17 +31,19 @@ function Time({ timestamp, fullTime }) {
 | 
			
		|||
    compareDate.setDate(compareDate.getDate() - 1);
 | 
			
		||||
    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) {
 | 
			
		||||
      formattedDate = `Yesterday, ${formattedDate}`;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <time
 | 
			
		||||
      dateTime={date.toISOString()}
 | 
			
		||||
      title={formattedFullTime}
 | 
			
		||||
    >
 | 
			
		||||
    <time dateTime={date.toISOString()} title={formattedFullTime}>
 | 
			
		||||
      {formattedDate}
 | 
			
		||||
    </time>
 | 
			
		||||
  );
 | 
			
		||||
| 
						 | 
				
			
			@ -39,6 +56,8 @@ Time.defaultProps = {
 | 
			
		|||
Time.propTypes = {
 | 
			
		||||
  timestamp: PropTypes.number.isRequired,
 | 
			
		||||
  fullTime: PropTypes.bool,
 | 
			
		||||
  hour24Clock: PropTypes.bool.isRequired,
 | 
			
		||||
  dateFormatString: PropTypes.string.isRequired,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default Time;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,19 +5,35 @@ import { timeDayMonYear, timeHourMinute, today, yesterday } from '../../utils/ti
 | 
			
		|||
export type TimeProps = {
 | 
			
		||||
  compact?: boolean;
 | 
			
		||||
  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>>(
 | 
			
		||||
  ({ compact, ts, ...props }, ref) => {
 | 
			
		||||
  ({ compact, hour24Clock, dateFormatString, ts, ...props }, ref) => {
 | 
			
		||||
    const formattedTime = timeHourMinute(ts, hour24Clock);
 | 
			
		||||
 | 
			
		||||
    let time = '';
 | 
			
		||||
    if (compact) {
 | 
			
		||||
      time = timeHourMinute(ts);
 | 
			
		||||
      time = formattedTime;
 | 
			
		||||
    } else if (today(ts)) {
 | 
			
		||||
      time = timeHourMinute(ts);
 | 
			
		||||
      time = formattedTime;
 | 
			
		||||
    } else if (yesterday(ts)) {
 | 
			
		||||
      time = `Yesterday ${timeHourMinute(ts)}`;
 | 
			
		||||
      time = `Yesterday ${formattedTime}`;
 | 
			
		||||
    } else {
 | 
			
		||||
      time = `${timeDayMonYear(ts)} ${timeHourMinute(ts)}`;
 | 
			
		||||
      time = `${timeDayMonYear(ts, dateFormatString)} ${formattedTime}`;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,6 +15,8 @@ import { nameInitials } from '../../utils/common';
 | 
			
		|||
import { useRoomAvatar, useRoomName, useRoomTopic } from '../../hooks/useRoomMeta';
 | 
			
		||||
import { mDirectAtom } from '../../state/mDirectList';
 | 
			
		||||
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
 | 
			
		||||
import { useSetting } from '../../state/hooks/settings';
 | 
			
		||||
import { settingsAtom } from '../../state/settings';
 | 
			
		||||
 | 
			
		||||
export type RoomIntroProps = {
 | 
			
		||||
  room: Room;
 | 
			
		||||
| 
						 | 
				
			
			@ -43,6 +45,8 @@ export const RoomIntro = as<'div', RoomIntroProps>(({ room, ...props }, ref) =>
 | 
			
		|||
    useCallback(async (roomId: string) => mx.joinRoom(roomId), [mx])
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Box direction="Column" grow="Yes" gap="500" {...props} ref={ref}>
 | 
			
		||||
      <Box>
 | 
			
		||||
| 
						 | 
				
			
			@ -67,7 +71,7 @@ export const RoomIntro = as<'div', RoomIntroProps>(({ room, ...props }, ref) =>
 | 
			
		|||
            <Text size="T200" priority="300">
 | 
			
		||||
              {'Created by '}
 | 
			
		||||
              <b>@{creatorName}</b>
 | 
			
		||||
              {` on ${timeDayMonthYear(ts)} ${timeHourMinute(ts)}`}
 | 
			
		||||
              {` on ${timeDayMonthYear(ts)} ${timeHourMinute(ts, hour24Clock)}`}
 | 
			
		||||
            </Text>
 | 
			
		||||
          )}
 | 
			
		||||
        </Box>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,8 @@ import dayjs from 'dayjs';
 | 
			
		|||
import * as css from './styles.css';
 | 
			
		||||
import { PickerColumn } from './PickerColumn';
 | 
			
		||||
import { hour12to24, hour24to12, hoursToMs, inSameDay, minutesToMs } from '../../utils/time';
 | 
			
		||||
import { useSetting } from '../../state/hooks/settings';
 | 
			
		||||
import { settingsAtom } from '../../state/settings';
 | 
			
		||||
 | 
			
		||||
type TimePickerProps = {
 | 
			
		||||
  min: number;
 | 
			
		||||
| 
						 | 
				
			
			@ -13,9 +15,11 @@ type TimePickerProps = {
 | 
			
		|||
};
 | 
			
		||||
export const TimePicker = forwardRef<HTMLDivElement, TimePickerProps>(
 | 
			
		||||
  ({ min, max, value, onChange }, ref) => {
 | 
			
		||||
    const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
 | 
			
		||||
 | 
			
		||||
    const hour24 = dayjs(value).hour();
 | 
			
		||||
 | 
			
		||||
    const selectedHour = hour24to12(hour24);
 | 
			
		||||
    const selectedHour = hour24Clock ? hour24 : hour24to12(hour24);
 | 
			
		||||
    const selectedMinute = dayjs(value).minute();
 | 
			
		||||
    const selectedPM = hour24 >= 12;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -24,7 +28,7 @@ export const TimePicker = forwardRef<HTMLDivElement, TimePickerProps>(
 | 
			
		|||
    };
 | 
			
		||||
 | 
			
		||||
    const handleHour = (hour: number) => {
 | 
			
		||||
      const seconds = hoursToMs(hour12to24(hour, selectedPM));
 | 
			
		||||
      const seconds = hoursToMs(hour24Clock ? hour : hour12to24(hour, selectedPM));
 | 
			
		||||
      const lastSeconds = hoursToMs(hour24);
 | 
			
		||||
      const newValue = value + (seconds - lastSeconds);
 | 
			
		||||
      handleSubmit(newValue);
 | 
			
		||||
| 
						 | 
				
			
			@ -59,28 +63,43 @@ export const TimePicker = forwardRef<HTMLDivElement, TimePickerProps>(
 | 
			
		|||
      <Menu className={css.PickerMenu} ref={ref}>
 | 
			
		||||
        <Box direction="Row" gap="200" className={css.PickerContainer}>
 | 
			
		||||
          <PickerColumn title="Hour">
 | 
			
		||||
            {Array.from(Array(12).keys())
 | 
			
		||||
              .map((i) => {
 | 
			
		||||
                if (i === 0) return 12;
 | 
			
		||||
                return i;
 | 
			
		||||
              })
 | 
			
		||||
              .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>
 | 
			
		||||
              ))}
 | 
			
		||||
            {hour24Clock
 | 
			
		||||
              ? Array.from(Array(24).keys()).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 && hour < minHour24) || (maxDay && hour > maxHour24)}
 | 
			
		||||
                  >
 | 
			
		||||
                    <Text size="T300">{hour < 10 ? `0${hour}` : hour}</Text>
 | 
			
		||||
                  </Chip>
 | 
			
		||||
                ))
 | 
			
		||||
              : Array.from(Array(12).keys())
 | 
			
		||||
                  .map((i) => {
 | 
			
		||||
                    if (i === 0) return 12;
 | 
			
		||||
                    return i;
 | 
			
		||||
                  })
 | 
			
		||||
                  .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 title="Minutes">
 | 
			
		||||
            {Array.from(Array(60).keys()).map((minute) => (
 | 
			
		||||
| 
						 | 
				
			
			@ -101,30 +120,32 @@ export const TimePicker = forwardRef<HTMLDivElement, TimePickerProps>(
 | 
			
		|||
              </Chip>
 | 
			
		||||
            ))}
 | 
			
		||||
          </PickerColumn>
 | 
			
		||||
          <PickerColumn title="Period">
 | 
			
		||||
            <Chip
 | 
			
		||||
              size="500"
 | 
			
		||||
              variant={!selectedPM ? 'Primary' : 'SurfaceVariant'}
 | 
			
		||||
              fill="None"
 | 
			
		||||
              radii="300"
 | 
			
		||||
              aria-selected={!selectedPM}
 | 
			
		||||
              onClick={() => handlePeriod(false)}
 | 
			
		||||
              disabled={minDay && minPM}
 | 
			
		||||
            >
 | 
			
		||||
              <Text size="T300">AM</Text>
 | 
			
		||||
            </Chip>
 | 
			
		||||
            <Chip
 | 
			
		||||
              size="500"
 | 
			
		||||
              variant={selectedPM ? 'Primary' : 'SurfaceVariant'}
 | 
			
		||||
              fill="None"
 | 
			
		||||
              radii="300"
 | 
			
		||||
              aria-selected={selectedPM}
 | 
			
		||||
              onClick={() => handlePeriod(true)}
 | 
			
		||||
              disabled={maxDay && !maxPM}
 | 
			
		||||
            >
 | 
			
		||||
              <Text size="T300">PM</Text>
 | 
			
		||||
            </Chip>
 | 
			
		||||
          </PickerColumn>
 | 
			
		||||
          {!hour24Clock && (
 | 
			
		||||
            <PickerColumn title="Period">
 | 
			
		||||
              <Chip
 | 
			
		||||
                size="500"
 | 
			
		||||
                variant={!selectedPM ? 'Primary' : 'SurfaceVariant'}
 | 
			
		||||
                fill="None"
 | 
			
		||||
                radii="300"
 | 
			
		||||
                aria-selected={!selectedPM}
 | 
			
		||||
                onClick={() => handlePeriod(false)}
 | 
			
		||||
                disabled={minDay && minPM}
 | 
			
		||||
              >
 | 
			
		||||
                <Text size="T300">AM</Text>
 | 
			
		||||
              </Chip>
 | 
			
		||||
              <Chip
 | 
			
		||||
                size="500"
 | 
			
		||||
                variant={selectedPM ? 'Primary' : 'SurfaceVariant'}
 | 
			
		||||
                fill="None"
 | 
			
		||||
                radii="300"
 | 
			
		||||
                aria-selected={selectedPM}
 | 
			
		||||
                onClick={() => handlePeriod(true)}
 | 
			
		||||
                disabled={maxDay && !maxPM}
 | 
			
		||||
              >
 | 
			
		||||
                <Text size="T300">PM</Text>
 | 
			
		||||
              </Chip>
 | 
			
		||||
            </PickerColumn>
 | 
			
		||||
          )}
 | 
			
		||||
        </Box>
 | 
			
		||||
      </Menu>
 | 
			
		||||
    );
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -57,6 +57,9 @@ export function MessageSearch({
 | 
			
		|||
  const [urlPreview] = useSetting(settingsAtom, 'urlPreview');
 | 
			
		||||
  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);
 | 
			
		||||
  const [searchParams, setSearchParams] = useSearchParams();
 | 
			
		||||
| 
						 | 
				
			
			@ -289,6 +292,8 @@ export function MessageSearch({
 | 
			
		|||
                    urlPreview={urlPreview}
 | 
			
		||||
                    onOpen={navigateRoom}
 | 
			
		||||
                    legacyUsernameColor={legacyUsernameColor || mDirects.has(groupRoom.roomId)}
 | 
			
		||||
                    hour24Clock={hour24Clock}
 | 
			
		||||
                    dateFormatString={dateFormatString}
 | 
			
		||||
                  />
 | 
			
		||||
                </VirtualTile>
 | 
			
		||||
              );
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -57,6 +57,8 @@ type SearchResultGroupProps = {
 | 
			
		|||
  urlPreview?: boolean;
 | 
			
		||||
  onOpen: (roomId: string, eventId: string) => void;
 | 
			
		||||
  legacyUsernameColor?: boolean;
 | 
			
		||||
  hour24Clock: boolean;
 | 
			
		||||
  dateFormatString: string;
 | 
			
		||||
};
 | 
			
		||||
export function SearchResultGroup({
 | 
			
		||||
  room,
 | 
			
		||||
| 
						 | 
				
			
			@ -66,6 +68,8 @@ export function SearchResultGroup({
 | 
			
		|||
  urlPreview,
 | 
			
		||||
  onOpen,
 | 
			
		||||
  legacyUsernameColor,
 | 
			
		||||
  hour24Clock,
 | 
			
		||||
  dateFormatString,
 | 
			
		||||
}: SearchResultGroupProps) {
 | 
			
		||||
  const mx = useMatrixClient();
 | 
			
		||||
  const useAuthentication = useMediaAuthentication();
 | 
			
		||||
| 
						 | 
				
			
			@ -275,7 +279,11 @@ export function SearchResultGroup({
 | 
			
		|||
                      </Username>
 | 
			
		||||
                      {tagIconSrc && <PowerIcon size="100" iconSrc={tagIconSrc} />}
 | 
			
		||||
                    </Box>
 | 
			
		||||
                    <Time ts={event.origin_server_ts} />
 | 
			
		||||
                    <Time
 | 
			
		||||
                      ts={event.origin_server_ts}
 | 
			
		||||
                      hour24Clock={hour24Clock}
 | 
			
		||||
                      dateFormatString={dateFormatString}
 | 
			
		||||
                    />
 | 
			
		||||
                  </Box>
 | 
			
		||||
                  <Box shrink="No" gap="200" alignItems="Center">
 | 
			
		||||
                    <Chip
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -450,6 +450,9 @@ export function RoomTimeline({
 | 
			
		|||
  const [showHiddenEvents] = useSetting(settingsAtom, 'showHiddenEvents');
 | 
			
		||||
  const [showDeveloperTools] = useSetting(settingsAtom, 'developerTools');
 | 
			
		||||
 | 
			
		||||
  const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
 | 
			
		||||
  const [dateFormatString] = useSetting(settingsAtom, 'dateFormatString');
 | 
			
		||||
 | 
			
		||||
  const ignoredUsersList = useIgnoredUsers();
 | 
			
		||||
  const ignoredUsersSet = useMemo(() => new Set(ignoredUsersList), [ignoredUsersList]);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1072,6 +1075,8 @@ export function RoomTimeline({
 | 
			
		|||
            powerLevelTag={getPowerLevelTag(senderPowerLevel)}
 | 
			
		||||
            accessibleTagColors={accessibleTagColors}
 | 
			
		||||
            legacyUsernameColor={legacyUsernameColor || direct}
 | 
			
		||||
            hour24Clock={hour24Clock}
 | 
			
		||||
            dateFormatString={dateFormatString}
 | 
			
		||||
          >
 | 
			
		||||
            {mEvent.isRedacted() ? (
 | 
			
		||||
              <RedactedContent reason={mEvent.getUnsigned().redacted_because?.content.reason} />
 | 
			
		||||
| 
						 | 
				
			
			@ -1154,6 +1159,8 @@ export function RoomTimeline({
 | 
			
		|||
            powerLevelTag={getPowerLevelTag(senderPowerLevel)}
 | 
			
		||||
            accessibleTagColors={accessibleTagColors}
 | 
			
		||||
            legacyUsernameColor={legacyUsernameColor || direct}
 | 
			
		||||
            hour24Clock={hour24Clock}
 | 
			
		||||
            dateFormatString={dateFormatString}
 | 
			
		||||
          >
 | 
			
		||||
            <EncryptedContent mEvent={mEvent}>
 | 
			
		||||
              {() => {
 | 
			
		||||
| 
						 | 
				
			
			@ -1256,6 +1263,8 @@ export function RoomTimeline({
 | 
			
		|||
            powerLevelTag={getPowerLevelTag(senderPowerLevel)}
 | 
			
		||||
            accessibleTagColors={accessibleTagColors}
 | 
			
		||||
            legacyUsernameColor={legacyUsernameColor || direct}
 | 
			
		||||
            hour24Clock={hour24Clock}
 | 
			
		||||
            dateFormatString={dateFormatString}
 | 
			
		||||
          >
 | 
			
		||||
            {mEvent.isRedacted() ? (
 | 
			
		||||
              <RedactedContent reason={mEvent.getUnsigned().redacted_because?.content.reason} />
 | 
			
		||||
| 
						 | 
				
			
			@ -1284,7 +1293,12 @@ export function RoomTimeline({
 | 
			
		|||
        const parsed = parseMemberEvent(mEvent);
 | 
			
		||||
 | 
			
		||||
        const timeJSX = (
 | 
			
		||||
          <Time ts={mEvent.getTs()} compact={messageLayout === MessageLayout.Compact} />
 | 
			
		||||
          <Time
 | 
			
		||||
            ts={mEvent.getTs()}
 | 
			
		||||
            compact={messageLayout === MessageLayout.Compact}
 | 
			
		||||
            hour24Clock={hour24Clock}
 | 
			
		||||
            dateFormatString={dateFormatString}
 | 
			
		||||
          />
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        return (
 | 
			
		||||
| 
						 | 
				
			
			@ -1321,7 +1335,12 @@ export function RoomTimeline({
 | 
			
		|||
        const senderName = getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId);
 | 
			
		||||
 | 
			
		||||
        const timeJSX = (
 | 
			
		||||
          <Time ts={mEvent.getTs()} compact={messageLayout === MessageLayout.Compact} />
 | 
			
		||||
          <Time
 | 
			
		||||
            ts={mEvent.getTs()}
 | 
			
		||||
            compact={messageLayout === MessageLayout.Compact}
 | 
			
		||||
            hour24Clock={hour24Clock}
 | 
			
		||||
            dateFormatString={dateFormatString}
 | 
			
		||||
          />
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        return (
 | 
			
		||||
| 
						 | 
				
			
			@ -1359,7 +1378,12 @@ export function RoomTimeline({
 | 
			
		|||
        const senderName = getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId);
 | 
			
		||||
 | 
			
		||||
        const timeJSX = (
 | 
			
		||||
          <Time ts={mEvent.getTs()} compact={messageLayout === MessageLayout.Compact} />
 | 
			
		||||
          <Time
 | 
			
		||||
            ts={mEvent.getTs()}
 | 
			
		||||
            compact={messageLayout === MessageLayout.Compact}
 | 
			
		||||
            hour24Clock={hour24Clock}
 | 
			
		||||
            dateFormatString={dateFormatString}
 | 
			
		||||
          />
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        return (
 | 
			
		||||
| 
						 | 
				
			
			@ -1397,7 +1421,12 @@ export function RoomTimeline({
 | 
			
		|||
        const senderName = getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId);
 | 
			
		||||
 | 
			
		||||
        const timeJSX = (
 | 
			
		||||
          <Time ts={mEvent.getTs()} compact={messageLayout === MessageLayout.Compact} />
 | 
			
		||||
          <Time
 | 
			
		||||
            ts={mEvent.getTs()}
 | 
			
		||||
            compact={messageLayout === MessageLayout.Compact}
 | 
			
		||||
            hour24Clock={hour24Clock}
 | 
			
		||||
            dateFormatString={dateFormatString}
 | 
			
		||||
          />
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        return (
 | 
			
		||||
| 
						 | 
				
			
			@ -1437,7 +1466,12 @@ export function RoomTimeline({
 | 
			
		|||
      const senderName = getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId);
 | 
			
		||||
 | 
			
		||||
      const timeJSX = (
 | 
			
		||||
        <Time ts={mEvent.getTs()} compact={messageLayout === MessageLayout.Compact} />
 | 
			
		||||
        <Time
 | 
			
		||||
          ts={mEvent.getTs()}
 | 
			
		||||
          compact={messageLayout === MessageLayout.Compact}
 | 
			
		||||
          hour24Clock={hour24Clock}
 | 
			
		||||
          dateFormatString={dateFormatString}
 | 
			
		||||
        />
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      return (
 | 
			
		||||
| 
						 | 
				
			
			@ -1482,7 +1516,12 @@ export function RoomTimeline({
 | 
			
		|||
      const senderName = getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId);
 | 
			
		||||
 | 
			
		||||
      const timeJSX = (
 | 
			
		||||
        <Time ts={mEvent.getTs()} compact={messageLayout === MessageLayout.Compact} />
 | 
			
		||||
        <Time
 | 
			
		||||
          ts={mEvent.getTs()}
 | 
			
		||||
          compact={messageLayout === MessageLayout.Compact}
 | 
			
		||||
          hour24Clock={hour24Clock}
 | 
			
		||||
          dateFormatString={dateFormatString}
 | 
			
		||||
        />
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      return (
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,6 +29,8 @@ import { useRoom } from '../../../hooks/useRoom';
 | 
			
		|||
import { StateEvent } from '../../../../types/matrix/room';
 | 
			
		||||
import { getToday, getYesterday, timeDayMonthYear, timeHourMinute } from '../../../utils/time';
 | 
			
		||||
import { DatePicker, TimePicker } from '../../../components/time-date';
 | 
			
		||||
import { useSetting } from '../../../state/hooks/settings';
 | 
			
		||||
import { settingsAtom } from '../../../state/settings';
 | 
			
		||||
 | 
			
		||||
type JumpToTimeProps = {
 | 
			
		||||
  onCancel: () => void;
 | 
			
		||||
| 
						 | 
				
			
			@ -45,6 +47,8 @@ export function JumpToTime({ onCancel, onSubmit }: JumpToTimeProps) {
 | 
			
		|||
  const createTs = useMemo(() => createStateEvent?.getTs() ?? 0, [createStateEvent]);
 | 
			
		||||
  const [ts, setTs] = useState(() => Date.now());
 | 
			
		||||
 | 
			
		||||
  const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
 | 
			
		||||
 | 
			
		||||
  const [timePickerCords, setTimePickerCords] = useState<RectCords>();
 | 
			
		||||
  const [datePickerCords, setDatePickerCords] = useState<RectCords>();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -125,7 +129,7 @@ export function JumpToTime({ onCancel, onSubmit }: JumpToTimeProps) {
 | 
			
		|||
                      after={<Icon size="50" src={Icons.ChevronBottom} />}
 | 
			
		||||
                      onClick={handleTimePicker}
 | 
			
		||||
                    >
 | 
			
		||||
                      <Text size="B300">{timeHourMinute(ts)}</Text>
 | 
			
		||||
                      <Text size="B300">{timeHourMinute(ts, hour24Clock)}</Text>
 | 
			
		||||
                    </Chip>
 | 
			
		||||
                    <PopOut
 | 
			
		||||
                      anchor={timePickerCords}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -682,6 +682,8 @@ export type MessageProps = {
 | 
			
		|||
  powerLevelTag?: PowerLevelTag;
 | 
			
		||||
  accessibleTagColors?: Map<string, string>;
 | 
			
		||||
  legacyUsernameColor?: boolean;
 | 
			
		||||
  hour24Clock: boolean;
 | 
			
		||||
  dateFormatString: string;
 | 
			
		||||
};
 | 
			
		||||
export const Message = as<'div', MessageProps>(
 | 
			
		||||
  (
 | 
			
		||||
| 
						 | 
				
			
			@ -711,6 +713,8 @@ export const Message = as<'div', MessageProps>(
 | 
			
		|||
      powerLevelTag,
 | 
			
		||||
      accessibleTagColors,
 | 
			
		||||
      legacyUsernameColor,
 | 
			
		||||
      hour24Clock,
 | 
			
		||||
      dateFormatString,
 | 
			
		||||
      children,
 | 
			
		||||
      ...props
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			@ -775,7 +779,12 @@ export const Message = as<'div', MessageProps>(
 | 
			
		|||
              </Text>
 | 
			
		||||
            </>
 | 
			
		||||
          )}
 | 
			
		||||
          <Time ts={mEvent.getTs()} compact={messageLayout === MessageLayout.Compact} />
 | 
			
		||||
          <Time
 | 
			
		||||
            ts={mEvent.getTs()}
 | 
			
		||||
            compact={messageLayout === MessageLayout.Compact}
 | 
			
		||||
            hour24Clock={hour24Clock}
 | 
			
		||||
            dateFormatString={dateFormatString}
 | 
			
		||||
          />
 | 
			
		||||
        </Box>
 | 
			
		||||
      </Box>
 | 
			
		||||
    );
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -102,6 +102,9 @@ function PinnedMessage({ room, eventId, renderContent, onOpen, canPinEvent }: Pi
 | 
			
		|||
  const theme = useTheme();
 | 
			
		||||
  const accessibleTagColors = useAccessibleTagColors(theme.kind, powerLevelTags);
 | 
			
		||||
 | 
			
		||||
  const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
 | 
			
		||||
  const [dateFormatString] = useSetting(settingsAtom, 'dateFormatString');
 | 
			
		||||
 | 
			
		||||
  const [unpinState, unpin] = useAsyncCallback(
 | 
			
		||||
    useCallback(() => {
 | 
			
		||||
      const pinEvent = getStateEvent(room, StateEvent.RoomPinnedEvents);
 | 
			
		||||
| 
						 | 
				
			
			@ -205,7 +208,11 @@ function PinnedMessage({ room, eventId, renderContent, onOpen, canPinEvent }: Pi
 | 
			
		|||
            </Username>
 | 
			
		||||
            {tagIconSrc && <PowerIcon size="100" iconSrc={tagIconSrc} />}
 | 
			
		||||
          </Box>
 | 
			
		||||
          <Time ts={pinnedEvent.getTs()} />
 | 
			
		||||
          <Time
 | 
			
		||||
            ts={pinnedEvent.getTs()}
 | 
			
		||||
            hour24Clock={hour24Clock}
 | 
			
		||||
            dateFormatString={dateFormatString}
 | 
			
		||||
          />
 | 
			
		||||
        </Box>
 | 
			
		||||
        {renderOptions()}
 | 
			
		||||
      </Box>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,6 +27,8 @@ import { SequenceCard } from '../../../components/sequence-card';
 | 
			
		|||
import { SequenceCardStyle } from '../styles.css';
 | 
			
		||||
import { LogoutDialog } from '../../../components/LogoutDialog';
 | 
			
		||||
import { stopPropagation } from '../../../utils/keyboard';
 | 
			
		||||
import { useSetting } from '../../../state/hooks/settings';
 | 
			
		||||
import { settingsAtom } from '../../../state/settings';
 | 
			
		||||
 | 
			
		||||
export function DeviceTilePlaceholder() {
 | 
			
		||||
  return (
 | 
			
		||||
| 
						 | 
				
			
			@ -41,6 +43,9 @@ export function DeviceTilePlaceholder() {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
function DeviceActiveTime({ ts }: { ts: number }) {
 | 
			
		||||
  const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
 | 
			
		||||
  const [dateFormatString] = useSetting(settingsAtom, 'dateFormatString');
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Text className={BreakWord} size="T200">
 | 
			
		||||
      <Text size="Inherit" as="span" priority="300">
 | 
			
		||||
| 
						 | 
				
			
			@ -49,7 +54,8 @@ function DeviceActiveTime({ ts }: { ts: number }) {
 | 
			
		|||
      <>
 | 
			
		||||
        {today(ts) && 'Today'}
 | 
			
		||||
        {yesterday(ts) && 'Yesterday'}
 | 
			
		||||
        {!today(ts) && !yesterday(ts) && timeDayMonYear(ts)} {timeHourMinute(ts)}
 | 
			
		||||
        {!today(ts) && !yesterday(ts) && timeDayMonYear(ts, dateFormatString)}{' '}
 | 
			
		||||
        {timeHourMinute(ts, hour24Clock)}
 | 
			
		||||
      </>
 | 
			
		||||
    </Text>
 | 
			
		||||
  );
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,15 +1,19 @@
 | 
			
		|||
import React, {
 | 
			
		||||
  ChangeEventHandler,
 | 
			
		||||
  FormEventHandler,
 | 
			
		||||
  KeyboardEventHandler,
 | 
			
		||||
  MouseEventHandler,
 | 
			
		||||
  useEffect,
 | 
			
		||||
  useState,
 | 
			
		||||
} from 'react';
 | 
			
		||||
import dayjs from 'dayjs';
 | 
			
		||||
import {
 | 
			
		||||
  as,
 | 
			
		||||
  Box,
 | 
			
		||||
  Button,
 | 
			
		||||
  Chip,
 | 
			
		||||
  config,
 | 
			
		||||
  Header,
 | 
			
		||||
  Icon,
 | 
			
		||||
  IconButton,
 | 
			
		||||
  Icons,
 | 
			
		||||
| 
						 | 
				
			
			@ -28,7 +32,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 +48,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 = {
 | 
			
		||||
| 
						 | 
				
			
			@ -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() {
 | 
			
		||||
  const [enterForNewline, setEnterForNewline] = useSetting(settingsAtom, 'enterForNewline');
 | 
			
		||||
  const [isMarkdown, setIsMarkdown] = useSetting(settingsAtom, 'isMarkdown');
 | 
			
		||||
| 
						 | 
				
			
			@ -637,6 +995,7 @@ export function General({ requestClose }: GeneralProps) {
 | 
			
		|||
          <PageContent>
 | 
			
		||||
            <Box direction="Column" gap="700">
 | 
			
		||||
              <Appearance />
 | 
			
		||||
              <DateAndTime />
 | 
			
		||||
              <Editor />
 | 
			
		||||
              <Messages />
 | 
			
		||||
            </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 { useIgnoredUsers } from '../../../hooks/useIgnoredUsers';
 | 
			
		||||
import { useReportRoomSupported } from '../../../hooks/useReportRoomSupported';
 | 
			
		||||
import { useSetting } from '../../../state/hooks/settings';
 | 
			
		||||
import { settingsAtom } from '../../../state/settings';
 | 
			
		||||
 | 
			
		||||
const COMPACT_CARD_WIDTH = 548;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -135,10 +137,19 @@ type NavigateHandler = (roomId: string, space: boolean) => void;
 | 
			
		|||
type InviteCardProps = {
 | 
			
		||||
  invite: InviteData;
 | 
			
		||||
  compact?: boolean;
 | 
			
		||||
  hour24Clock: boolean;
 | 
			
		||||
  dateFormatString: string;
 | 
			
		||||
  onNavigate: NavigateHandler;
 | 
			
		||||
  hideAvatar: boolean;
 | 
			
		||||
};
 | 
			
		||||
function InviteCard({ invite, compact, onNavigate, hideAvatar }: InviteCardProps) {
 | 
			
		||||
function InviteCard({
 | 
			
		||||
  invite,
 | 
			
		||||
  compact,
 | 
			
		||||
  hour24Clock,
 | 
			
		||||
  dateFormatString,
 | 
			
		||||
  onNavigate,
 | 
			
		||||
  hideAvatar,
 | 
			
		||||
}: InviteCardProps) {
 | 
			
		||||
  const mx = useMatrixClient();
 | 
			
		||||
  const userId = mx.getSafeUserId();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -295,7 +306,13 @@ function InviteCard({ invite, compact, onNavigate, hideAvatar }: InviteCardProps
 | 
			
		|||
        </Box>
 | 
			
		||||
        {invite.inviteTs && (
 | 
			
		||||
          <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>
 | 
			
		||||
| 
						 | 
				
			
			@ -384,8 +401,16 @@ type KnownInvitesProps = {
 | 
			
		|||
  invites: InviteData[];
 | 
			
		||||
  handleNavigate: NavigateHandler;
 | 
			
		||||
  compact: boolean;
 | 
			
		||||
  hour24Clock: boolean;
 | 
			
		||||
  dateFormatString: string;
 | 
			
		||||
};
 | 
			
		||||
function KnownInvites({ invites, handleNavigate, compact }: KnownInvitesProps) {
 | 
			
		||||
function KnownInvites({
 | 
			
		||||
  invites,
 | 
			
		||||
  handleNavigate,
 | 
			
		||||
  compact,
 | 
			
		||||
  hour24Clock,
 | 
			
		||||
  dateFormatString,
 | 
			
		||||
}: KnownInvitesProps) {
 | 
			
		||||
  return (
 | 
			
		||||
    <Box direction="Column" gap="200">
 | 
			
		||||
      <Text size="H4">Primary</Text>
 | 
			
		||||
| 
						 | 
				
			
			@ -396,6 +421,8 @@ function KnownInvites({ invites, handleNavigate, compact }: KnownInvitesProps) {
 | 
			
		|||
              key={invite.roomId}
 | 
			
		||||
              invite={invite}
 | 
			
		||||
              compact={compact}
 | 
			
		||||
              hour24Clock={hour24Clock}
 | 
			
		||||
              dateFormatString={dateFormatString}
 | 
			
		||||
              onNavigate={handleNavigate}
 | 
			
		||||
              hideAvatar={false}
 | 
			
		||||
            />
 | 
			
		||||
| 
						 | 
				
			
			@ -420,8 +447,16 @@ type UnknownInvitesProps = {
 | 
			
		|||
  invites: InviteData[];
 | 
			
		||||
  handleNavigate: NavigateHandler;
 | 
			
		||||
  compact: boolean;
 | 
			
		||||
  hour24Clock: boolean;
 | 
			
		||||
  dateFormatString: string;
 | 
			
		||||
};
 | 
			
		||||
function UnknownInvites({ invites, handleNavigate, compact }: UnknownInvitesProps) {
 | 
			
		||||
function UnknownInvites({
 | 
			
		||||
  invites,
 | 
			
		||||
  handleNavigate,
 | 
			
		||||
  compact,
 | 
			
		||||
  hour24Clock,
 | 
			
		||||
  dateFormatString,
 | 
			
		||||
}: UnknownInvitesProps) {
 | 
			
		||||
  const mx = useMatrixClient();
 | 
			
		||||
 | 
			
		||||
  const [declineAllStatus, declineAll] = useAsyncCallback(
 | 
			
		||||
| 
						 | 
				
			
			@ -459,6 +494,8 @@ function UnknownInvites({ invites, handleNavigate, compact }: UnknownInvitesProp
 | 
			
		|||
              key={invite.roomId}
 | 
			
		||||
              invite={invite}
 | 
			
		||||
              compact={compact}
 | 
			
		||||
              hour24Clock={hour24Clock}
 | 
			
		||||
              dateFormatString={dateFormatString}
 | 
			
		||||
              onNavigate={handleNavigate}
 | 
			
		||||
              hideAvatar
 | 
			
		||||
            />
 | 
			
		||||
| 
						 | 
				
			
			@ -483,8 +520,16 @@ type SpamInvitesProps = {
 | 
			
		|||
  invites: InviteData[];
 | 
			
		||||
  handleNavigate: NavigateHandler;
 | 
			
		||||
  compact: boolean;
 | 
			
		||||
  hour24Clock: boolean;
 | 
			
		||||
  dateFormatString: string;
 | 
			
		||||
};
 | 
			
		||||
function SpamInvites({ invites, handleNavigate, compact }: SpamInvitesProps) {
 | 
			
		||||
function SpamInvites({
 | 
			
		||||
  invites,
 | 
			
		||||
  handleNavigate,
 | 
			
		||||
  compact,
 | 
			
		||||
  hour24Clock,
 | 
			
		||||
  dateFormatString,
 | 
			
		||||
}: SpamInvitesProps) {
 | 
			
		||||
  const mx = useMatrixClient();
 | 
			
		||||
  const [showInvites, setShowInvites] = useState(false);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -608,6 +653,8 @@ function SpamInvites({ invites, handleNavigate, compact }: SpamInvitesProps) {
 | 
			
		|||
                key={invite.roomId}
 | 
			
		||||
                invite={invite}
 | 
			
		||||
                compact={compact}
 | 
			
		||||
                hour24Clock={hour24Clock}
 | 
			
		||||
                dateFormatString={dateFormatString}
 | 
			
		||||
                onNavigate={handleNavigate}
 | 
			
		||||
                hideAvatar
 | 
			
		||||
              />
 | 
			
		||||
| 
						 | 
				
			
			@ -671,6 +718,9 @@ export function Invites() {
 | 
			
		|||
  );
 | 
			
		||||
  const screenSize = useScreenSizeContext();
 | 
			
		||||
 | 
			
		||||
  const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
 | 
			
		||||
  const [dateFormatString] = useSetting(settingsAtom, 'dateFormatString');
 | 
			
		||||
 | 
			
		||||
  const handleNavigate = (roomId: string, space: boolean) => {
 | 
			
		||||
    if (space) {
 | 
			
		||||
      navigateSpace(roomId);
 | 
			
		||||
| 
						 | 
				
			
			@ -723,6 +773,8 @@ export function Invites() {
 | 
			
		|||
                  <KnownInvites
 | 
			
		||||
                    invites={knownInvites}
 | 
			
		||||
                    compact={compact}
 | 
			
		||||
                    hour24Clock={hour24Clock}
 | 
			
		||||
                    dateFormatString={dateFormatString}
 | 
			
		||||
                    handleNavigate={handleNavigate}
 | 
			
		||||
                  />
 | 
			
		||||
                )}
 | 
			
		||||
| 
						 | 
				
			
			@ -731,6 +783,8 @@ export function Invites() {
 | 
			
		|||
                  <UnknownInvites
 | 
			
		||||
                    invites={unknownInvites}
 | 
			
		||||
                    compact={compact}
 | 
			
		||||
                    hour24Clock={hour24Clock}
 | 
			
		||||
                    dateFormatString={dateFormatString}
 | 
			
		||||
                    handleNavigate={handleNavigate}
 | 
			
		||||
                  />
 | 
			
		||||
                )}
 | 
			
		||||
| 
						 | 
				
			
			@ -739,6 +793,8 @@ export function Invites() {
 | 
			
		|||
                  <SpamInvites
 | 
			
		||||
                    invites={spamInvites}
 | 
			
		||||
                    compact={compact}
 | 
			
		||||
                    hour24Clock={hour24Clock}
 | 
			
		||||
                    dateFormatString={dateFormatString}
 | 
			
		||||
                    handleNavigate={handleNavigate}
 | 
			
		||||
                  />
 | 
			
		||||
                )}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -205,6 +205,8 @@ type RoomNotificationsGroupProps = {
 | 
			
		|||
  hideActivity: boolean;
 | 
			
		||||
  onOpen: (roomId: string, eventId: string) => void;
 | 
			
		||||
  legacyUsernameColor?: boolean;
 | 
			
		||||
  hour24Clock: boolean;
 | 
			
		||||
  dateFormatString: string;
 | 
			
		||||
};
 | 
			
		||||
function RoomNotificationsGroupComp({
 | 
			
		||||
  room,
 | 
			
		||||
| 
						 | 
				
			
			@ -214,6 +216,8 @@ function RoomNotificationsGroupComp({
 | 
			
		|||
  hideActivity,
 | 
			
		||||
  onOpen,
 | 
			
		||||
  legacyUsernameColor,
 | 
			
		||||
  hour24Clock,
 | 
			
		||||
  dateFormatString,
 | 
			
		||||
}: RoomNotificationsGroupProps) {
 | 
			
		||||
  const mx = useMatrixClient();
 | 
			
		||||
  const useAuthentication = useMediaAuthentication();
 | 
			
		||||
| 
						 | 
				
			
			@ -496,7 +500,11 @@ function RoomNotificationsGroupComp({
 | 
			
		|||
                      </Username>
 | 
			
		||||
                      {tagIconSrc && <PowerIcon size="100" iconSrc={tagIconSrc} />}
 | 
			
		||||
                    </Box>
 | 
			
		||||
                    <Time ts={event.origin_server_ts} />
 | 
			
		||||
                    <Time
 | 
			
		||||
                      ts={event.origin_server_ts}
 | 
			
		||||
                      hour24Clock={hour24Clock}
 | 
			
		||||
                      dateFormatString={dateFormatString}
 | 
			
		||||
                    />
 | 
			
		||||
                  </Box>
 | 
			
		||||
                  <Box shrink="No" gap="200" alignItems="Center">
 | 
			
		||||
                    <Chip
 | 
			
		||||
| 
						 | 
				
			
			@ -549,6 +557,8 @@ export function Notifications() {
 | 
			
		|||
  const [mediaAutoLoad] = useSetting(settingsAtom, 'mediaAutoLoad');
 | 
			
		||||
  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);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -713,6 +723,8 @@ export function Notifications() {
 | 
			
		|||
                          legacyUsernameColor={
 | 
			
		||||
                            legacyUsernameColor || mDirects.has(groupRoom.roomId)
 | 
			
		||||
                          }
 | 
			
		||||
                          hour24Clock={hour24Clock}
 | 
			
		||||
                          dateFormatString={dateFormatString}
 | 
			
		||||
                        />
 | 
			
		||||
                      </VirtualTile>
 | 
			
		||||
                    );
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
import { atom } from 'jotai';
 | 
			
		||||
 | 
			
		||||
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 enum MessageLayout {
 | 
			
		||||
  Modern = 0,
 | 
			
		||||
| 
						 | 
				
			
			@ -35,6 +36,9 @@ export interface Settings {
 | 
			
		|||
  showNotifications: boolean;
 | 
			
		||||
  isNotificationSounds: boolean;
 | 
			
		||||
 | 
			
		||||
  hour24Clock: boolean;
 | 
			
		||||
  dateFormatString: string;
 | 
			
		||||
 | 
			
		||||
  developerTools: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -65,6 +69,9 @@ const defaultSettings: Settings = {
 | 
			
		|||
  showNotifications: true,
 | 
			
		||||
  isNotificationSounds: true,
 | 
			
		||||
 | 
			
		||||
  hour24Clock: false,
 | 
			
		||||
  dateFormatString: 'D MMM YYYY',
 | 
			
		||||
 | 
			
		||||
  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 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 timeAmPm = (ts: number): string => dayjs(ts).format('A');
 | 
			
		||||
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 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');
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue