CustomTimeStamps: New Version
Some checks are pending
Test / Test (push) Waiting to run
Release / Build Equicord (push) Waiting to run

This commit is contained in:
thororen1234 2025-06-10 23:43:55 -04:00
parent 3e4ea66584
commit 7c661139d5
No known key found for this signature in database
4 changed files with 218 additions and 48 deletions

View file

@ -44,7 +44,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch
- CopyUserMention by Cortex & castdrian - CopyUserMention by Cortex & castdrian
- CustomFolderIcons by sadan - CustomFolderIcons by sadan
- CustomSounds by TheKodeToad & SpikeHD - CustomSounds by TheKodeToad & SpikeHD
- CustomTimestamps by Rini & nvhrr - CustomTimestamps by Rini, nvhrr, Suffocate, Obsidian
- CustomUserColors by mochienya - CustomUserColors by mochienya
- CuteAnimeBoys by ShadyGoat - CuteAnimeBoys by ShadyGoat
- CuteNekos by echo - CuteNekos by echo

View file

@ -1,100 +1,254 @@
/* /*
* Vencord, a Discord client mod * 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 * 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 { Link } from "@components/Link";
import { Devs, EquicordDevs } from "@utils/constants"; import { Devs, EquicordDevs } from "@utils/constants";
import { Margins } from "@utils/margins";
import { useForceUpdater } from "@utils/react";
import definePlugin, { OptionType } from "@utils/types"; 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<string, TimeFormat> = {
cozyFormat: { cozyFormat: {
type: OptionType.STRING, name: "Cozy mode",
description: "Time format to use in messages on cozy mode",
default: "[calendar]", default: "[calendar]",
description: "time format to use in messages on cozy mode", offset: 0,
}, },
compactFormat: { compactFormat: {
type: OptionType.STRING, name: "Compact mode",
description: "Time format on compact mode and hovering messages",
default: "LT", default: "LT",
description: "time format on compact mode and hovering messages", offset: 0,
}, },
tooltipFormat: { tooltipFormat: {
type: OptionType.STRING, name: "Tooltip",
description: "Time format to use on tooltips",
default: "LLLL • [relative]", 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: { sameDayFormat: {
type: OptionType.STRING, name: "Same day",
default: "HH:mm:ss", description: "[calendar] format for today",
description: "[calendar] format for today" default: "[Today at ] HH:mm:ss",
offset: 0,
}, },
lastDayFormat: { lastDayFormat: {
type: OptionType.STRING, name: "Last day",
default: "[yesterday] HH:mm:ss", description: "[calendar] format for yesterday",
description: "[calendar] format for yesterday" default: "[Yesterday at ] HH:mm:ss",
offset: -1000 * 60 * 60 * 24,
}, },
lastWeekFormat: { lastWeekFormat: {
type: OptionType.STRING, name: "Last week",
description: "[calendar] format for last week",
default: "ddd DD.MM.YYYY HH:mm:ss", default: "ddd DD.MM.YYYY HH:mm:ss",
description: "[calendar] format for last week" offset: -1000 * 60 * 60 * 24 * 7,
}, },
sameElseFormat: { sameElseFormat: {
type: OptionType.STRING, name: "Older date",
description: "[calendar] format for older dates",
default: "ddd DD.MM.YYYY HH:mm:ss", 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 (
<>
<Forms.FormTitle tag="h5">{props.format.name}</Forms.FormTitle>
<Forms.FormText>{props.format.description}</Forms.FormText>
<TextInput value={state} onChange={handleChange} />
<Forms.FormText className={"vc-cmt-preview-text"}>{preview}</Forms.FormText>
</>
);
};
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]) => (
<Forms.FormSection key={key}>
{key === "sameDayFormat" && (
<div className={Margins.bottom20}>
<Forms.FormDivider style={{ marginBottom: "10px" }} />
<Forms.FormTitle tag="h1">Calendar formats</Forms.FormTitle>
<Forms.FormText>
How to format the [calendar] value if used in the above timestamps.
</Forms.FormText>
</div>
)}
<TimeRow
id={key}
format={value}
onChange={setNewValue}
pluginSettings={settingsState}
/>
</Forms.FormSection>
));
}
}
}).withPrivateSettings<{
formats: {
cozyFormat: string;
compactFormat: string;
tooltipFormat: string;
ariaLabelFormat: string;
sameDayFormat: string;
lastDayFormat: string;
lastWeekFormat: string;
sameElseFormat: string;
};
}>();
export default definePlugin({ export default definePlugin({
name: "CustomTimestamps", name: "CustomTimestamps",
description: "Custom timestamps on messages and tooltips", description: "Custom timestamps on messages and tooltips",
authors: [Devs.Rini, EquicordDevs.nvhhr], authors: [Devs.Rini, EquicordDevs.nvhhr, EquicordDevs.Suffocate, Devs.Obsidian],
settings, settings,
settingsAboutComponent: () => ( settingsAboutComponent: () => (
<> <div className={"vc-cmt-info-card"}>
<Forms.FormTitle tag="h3">How to use:</Forms.FormTitle> <Forms.FormTitle tag="h2">How to use:</Forms.FormTitle>
<Forms.FormText> <Forms.FormText>
<Link href="https://momentjs.com/docs/#/displaying/format/">Moment.js formatting documentation</Link> <Link href="https://momentjs.com/docs/#/displaying/format/">Moment.js formatting documentation</Link>
<p> <div className={Margins.top8}>
Additionally you can use these in your inputs:<br /> Additionally you can use these in your inputs:<br />
<b>[calendar]</b> enables dynamic date formatting (see options below),<br /> <b>[calendar]</b> enables dynamic date formatting such
as &quot;Today&quot; or &quot;Yesterday&quot;.<br />
<b>[relative]</b> gives you times such as &quot;4 hours ago&quot;.<br /> <b>[relative]</b> gives you times such as &quot;4 hours ago&quot;.<br />
</p> </div>
</Forms.FormText> </Forms.FormText>
</> </div>
), ),
patches: [ patches: [
{ {
find: "#{intl::MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL}", find: "#{intl::MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL}",
replacement: [ replacement: [
{ {
match: /(?<=null!=\i\?).{0,25}\((\i),"LT"\):\(0,\i\.\i\)\(\i,!0\)/, // Aria label on timestamps
replace: '$self.format($1,"compactFormat","[calendar]"):$self.format($1,"cozyFormat","LT")', 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"\)(?=,)/, 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. <t:1234567890>)
match: /text:(\i).full,/,
replace: "text: $self.renderTimestamp(new Date($1.timestamp*1000),'tooltip'),"
}
} }
], ],
format(date: Date, key: string, fallback: string) { renderTimestamp: (date: Date, type: "cozy" | "compact" | "tooltip" | "ariaLabel") => {
const t = moment(date); const forceUpdater = useForceUpdater();
const sameDayFormat = settings.store.sameDayFormat || "HH:mm:ss"; let formatTemplate: string;
const lastDayFormat = settings.store.lastDayFormat || "[yesterday] HH:mm:ss";
const lastWeekFormat = settings.store.lastWeekFormat || "ddd DD.MM.YYYY HH:mm:ss"; switch (type) {
const sameElseFormat = settings.store.sameElseFormat || "ddd DD.MM.YYYY HH:mm:ss"; case "cozy":
return t.format(settings.store[key] || fallback) formatTemplate = settings.use(["formats"]).formats?.cozyFormat || timeFormats.cozyFormat.default;
.replace("relative", () => t.fromNow()) break;
.replace("calendar", () => t.calendar(null, { case "compact":
sameDay: sameDayFormat, formatTemplate = settings.use(["formats"]).formats?.compactFormat || timeFormats.compactFormat.default;
lastDay: lastDayFormat, break;
lastWeek: lastWeekFormat, case "tooltip":
sameElse: sameElseFormat 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);
}
}); });

View file

@ -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;
}

View file

@ -1090,6 +1090,10 @@ export const EquicordDevs = Object.freeze({
name: "GroupXyz", name: "GroupXyz",
id: 950033410229944331n id: 950033410229944331n
}, },
Suffocate: {
name: "Suffocate",
id: 772601756776923187n
},
} satisfies Record<string, Dev>); } satisfies Record<string, Dev>);
// iife so #__PURE__ works correctly // iife so #__PURE__ works correctly