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
- 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

View file

@ -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<string, TimeFormat> = {
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 (
<>
<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({
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: () => (
<>
<Forms.FormTitle tag="h3">How to use:</Forms.FormTitle>
<div className={"vc-cmt-info-card"}>
<Forms.FormTitle tag="h2">How to use:</Forms.FormTitle>
<Forms.FormText>
<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 />
<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 />
</p>
</div>
</Forms.FormText>
</>
</div>
),
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. <t:1234567890>)
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);
}
});

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",
id: 950033410229944331n
},
Suffocate: {
name: "Suffocate",
id: 772601756776923187n
},
} satisfies Record<string, Dev>);
// iife so #__PURE__ works correctly