From 7c661139d5a7d6b27b5c9ed867de4918712ca964 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Tue, 10 Jun 2025 23:43:55 -0400 Subject: [PATCH] CustomTimeStamps: New Version --- README.md | 2 +- .../customTimestamps/index.tsx | 248 ++++++++++++++---- .../customTimestamps/style.css | 12 + src/utils/constants.ts | 4 + 4 files changed, 218 insertions(+), 48 deletions(-) create mode 100644 src/equicordplugins/customTimestamps/style.css diff --git a/README.md b/README.md index ddc5d46c..0f9e8f0b 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch - CopyUserMention by Cortex & castdrian - CustomFolderIcons by sadan - CustomSounds by TheKodeToad & SpikeHD -- CustomTimestamps by Rini & nvhrr +- CustomTimestamps by Rini, nvhrr, Suffocate, Obsidian - CustomUserColors by mochienya - CuteAnimeBoys by ShadyGoat - CuteNekos by echo diff --git a/src/equicordplugins/customTimestamps/index.tsx b/src/equicordplugins/customTimestamps/index.tsx index d90defe3..087e3670 100644 --- a/src/equicordplugins/customTimestamps/index.tsx +++ b/src/equicordplugins/customTimestamps/index.tsx @@ -1,100 +1,254 @@ /* * Vencord, a Discord client mod - * Copyright (c) 2024 Vendicated and contributors + * Copyright (c) 2025 Vendicated and contributors * SPDX-License-Identifier: GPL-3.0-or-later */ -import { definePluginSettings } from "@api/Settings"; +import "./style.css"; + +import { definePluginSettings, useSettings } from "@api/Settings"; import { Link } from "@components/Link"; import { Devs, EquicordDevs } from "@utils/constants"; +import { Margins } from "@utils/margins"; +import { useForceUpdater } from "@utils/react"; import definePlugin, { OptionType } from "@utils/types"; -import { Forms, moment } from "@webpack/common"; +import { Forms, moment, TextInput, useEffect, useState } from "@webpack/common"; -const settings = definePluginSettings({ +type TimeFormat = { + name: string; + description: string; + default: string; + offset: number; +}; +type TimeRowProps = { + id: string; + format: TimeFormat; + onChange: (key: string, value: string) => void; + pluginSettings: any; +}; + +const timeFormats: Record = { cozyFormat: { - type: OptionType.STRING, + name: "Cozy mode", + description: "Time format to use in messages on cozy mode", default: "[calendar]", - description: "time format to use in messages on cozy mode", + offset: 0, }, compactFormat: { - type: OptionType.STRING, + name: "Compact mode", + description: "Time format on compact mode and hovering messages", default: "LT", - description: "time format on compact mode and hovering messages", + offset: 0, }, tooltipFormat: { - type: OptionType.STRING, + name: "Tooltip", + description: "Time format to use on tooltips", default: "LLLL • [relative]", - description: "time format to use on tooltips", + offset: 0, + }, + ariaLabelFormat: { + name: "Aria label", + description: "Time format to use on aria labels", + default: "[calendar]", + offset: 0, }, sameDayFormat: { - type: OptionType.STRING, - default: "HH:mm:ss", - description: "[calendar] format for today" + name: "Same day", + description: "[calendar] format for today", + default: "[Today at ] HH:mm:ss", + offset: 0, }, lastDayFormat: { - type: OptionType.STRING, - default: "[yesterday] HH:mm:ss", - description: "[calendar] format for yesterday" + name: "Last day", + description: "[calendar] format for yesterday", + default: "[Yesterday at ] HH:mm:ss", + offset: -1000 * 60 * 60 * 24, }, lastWeekFormat: { - type: OptionType.STRING, + name: "Last week", + description: "[calendar] format for last week", default: "ddd DD.MM.YYYY HH:mm:ss", - description: "[calendar] format for last week" + offset: -1000 * 60 * 60 * 24 * 7, }, sameElseFormat: { - type: OptionType.STRING, + name: "Older date", + description: "[calendar] format for older dates", default: "ddd DD.MM.YYYY HH:mm:ss", - description: "[calendar] format for older dates" - }, -}); + offset: -1000 * 60 * 60 * 24 * 31, + } +}; + +const format = (date: Date, formatTemplate: string): string => { + const mmt = moment(date); + + moment.relativeTimeThreshold("s", 60); + moment.relativeTimeThreshold("ss", -1); + moment.relativeTimeThreshold("m", 60); + + const sameDayFormat = settings.store?.formats?.sameDayFormat || timeFormats.sameDayFormat.default; + const lastDayFormat = settings.store?.formats?.lastDayFormat || timeFormats.lastDayFormat.default; + const lastWeekFormat = settings.store?.formats?.lastWeekFormat || timeFormats.lastWeekFormat.default; + const sameElseFormat = settings.store?.formats?.sameElseFormat || timeFormats.sameElseFormat.default; + + return mmt.format(formatTemplate) + .replace("calendar", () => mmt.calendar(null, { + sameDay: sameDayFormat, + lastDay: lastDayFormat, + lastWeek: lastWeekFormat, + sameElse: sameElseFormat + })) + .replace("relative", () => mmt.fromNow()); +}; + +const TimeRow = (props: TimeRowProps) => { + const [state, setState] = useState(props.pluginSettings?.[props.id] || props.format.default); + const [preview, setPreview] = useState(""); + + const handleChange = (value: string) => { + setState(value); + props.onChange(props.id, value); + }; + + const updatePreview = () => setPreview(format(new Date(Date.now() + props.format.offset), state || props.format.default)); + + useEffect(() => { + updatePreview(); + const interval = setInterval(updatePreview, 1000); + return () => clearInterval(interval); + }, [state]); + + return ( + <> + {props.format.name} + {props.format.description} + + {preview} + + ); +}; + +const settings = definePluginSettings({ + formats: { + type: OptionType.COMPONENT, + description: "Customize the timestamp formats", + component: componentProps => { + const [settingsState, setSettingsState] = useState(useSettings().plugins?.CustomTimestamps?.formats ?? {}); + + const setNewValue = (key: string, value: string) => { + const newSettings = { ...settingsState, [key]: value }; + setSettingsState(newSettings); + componentProps.setValue(newSettings); + }; + + return Object.entries(timeFormats).map(([key, value]) => ( + + {key === "sameDayFormat" && ( +
+ + Calendar formats + + How to format the [calendar] value if used in the above timestamps. + +
+ )} + +
+ )); + } + } +}).withPrivateSettings<{ + formats: { + cozyFormat: string; + compactFormat: string; + tooltipFormat: string; + ariaLabelFormat: string; + sameDayFormat: string; + lastDayFormat: string; + lastWeekFormat: string; + sameElseFormat: string; + }; +}>(); export default definePlugin({ name: "CustomTimestamps", description: "Custom timestamps on messages and tooltips", - authors: [Devs.Rini, EquicordDevs.nvhhr], + authors: [Devs.Rini, EquicordDevs.nvhhr, EquicordDevs.Suffocate, Devs.Obsidian], settings, settingsAboutComponent: () => ( - <> - How to use: +
+ How to use: Moment.js formatting documentation -

+

Additionally you can use these in your inputs:
- [calendar] enables dynamic date formatting (see options below),
+ [calendar] enables dynamic date formatting such + as "Today" or "Yesterday".
[relative] gives you times such as "4 hours ago".
-

+
- +
), patches: [ { find: "#{intl::MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL}", replacement: [ { - match: /(?<=null!=\i\?).{0,25}\((\i),"LT"\):\(0,\i\.\i\)\(\i,!0\)/, - replace: '$self.format($1,"compactFormat","[calendar]"):$self.format($1,"cozyFormat","LT")', + // Aria label on timestamps + match: /\i.useMemo\(\(\)=>\(0,\i\.\i\)\((\i)\),\[\i]\),/, + replace: "$self.renderTimestamp($1,'ariaLabel')," }, { + // Timestamps on messages + match: /\i\.useMemo\(\(\)=>null!=\i\?\(0,\i\.\i\)\(\i,\i\):(\i)\?\(0,\i\.\i\)\((\i),"LT"\):\(0,\i\.\i\)\(\i,!0\),\[\i,\i,\i]\)/, + replace: "$self.renderTimestamp($2,$1?'compact':'cozy')", + }, + { + // Tooltips when hovering over message timestamps match: /(?<=text:)\(\)=>\(0,\i.\i\)\((\i),"LLLL"\)(?=,)/, - replace: '$self.format($1,"tooltipFormat","LLLL")', + replace: "$self.renderTimestamp($1,'tooltip')", }, ] + }, + { + find: ".full,tooltipClassName:", + replacement: { + // Tooltips for timestamp markdown (e.g. ) + match: /text:(\i).full,/, + replace: "text: $self.renderTimestamp(new Date($1.timestamp*1000),'tooltip')," + } } ], - format(date: Date, key: string, fallback: string) { - const t = moment(date); - const sameDayFormat = settings.store.sameDayFormat || "HH:mm:ss"; - const lastDayFormat = settings.store.lastDayFormat || "[yesterday] HH:mm:ss"; - const lastWeekFormat = settings.store.lastWeekFormat || "ddd DD.MM.YYYY HH:mm:ss"; - const sameElseFormat = settings.store.sameElseFormat || "ddd DD.MM.YYYY HH:mm:ss"; - return t.format(settings.store[key] || fallback) - .replace("relative", () => t.fromNow()) - .replace("calendar", () => t.calendar(null, { - sameDay: sameDayFormat, - lastDay: lastDayFormat, - lastWeek: lastWeekFormat, - sameElse: sameElseFormat - })); - }, + renderTimestamp: (date: Date, type: "cozy" | "compact" | "tooltip" | "ariaLabel") => { + const forceUpdater = useForceUpdater(); + let formatTemplate: string; + + switch (type) { + case "cozy": + formatTemplate = settings.use(["formats"]).formats?.cozyFormat || timeFormats.cozyFormat.default; + break; + case "compact": + formatTemplate = settings.use(["formats"]).formats?.compactFormat || timeFormats.compactFormat.default; + break; + case "tooltip": + formatTemplate = settings.use(["formats"]).formats?.tooltipFormat || timeFormats.tooltipFormat.default; + break; + case "ariaLabel": + formatTemplate = settings.use(["formats"]).formats?.ariaLabelFormat || timeFormats.ariaLabelFormat.default; + } + + useEffect(() => { + if (formatTemplate.includes("calendar") || formatTemplate.includes("relative")) { + const interval = setInterval(forceUpdater, 1000); + return () => clearInterval(interval); + } + }, []); + + return format(date, formatTemplate); + } }); diff --git a/src/equicordplugins/customTimestamps/style.css b/src/equicordplugins/customTimestamps/style.css new file mode 100644 index 00000000..0314d70a --- /dev/null +++ b/src/equicordplugins/customTimestamps/style.css @@ -0,0 +1,12 @@ +.vc-cmt-info-card { + background-color: var(--info-help-background); + border: 1px solid var(--info-help-foreground); + padding: 8px; + border-radius: var(--radius-xs); + margin: 10px 0; +} + +.vc-cmt-preview-text { + color: yellow; + margin: 8px 0; +} diff --git a/src/utils/constants.ts b/src/utils/constants.ts index da9ac47c..526ba090 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -1090,6 +1090,10 @@ export const EquicordDevs = Object.freeze({ name: "GroupXyz", id: 950033410229944331n }, + Suffocate: { + name: "Suffocate", + id: 772601756776923187n + }, } satisfies Record); // iife so #__PURE__ works correctly