mirror of
https://github.com/Equicord/Equicord.git
synced 2025-06-21 12:27:02 -04:00
CustomTimeStamps: New Version
This commit is contained in:
parent
3e4ea66584
commit
7c661139d5
4 changed files with 218 additions and 48 deletions
|
@ -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
|
||||
|
|
|
@ -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 "Today" or "Yesterday".<br />
|
||||
<b>[relative]</b> gives you times such as "4 hours ago".<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);
|
||||
}
|
||||
});
|
||||
|
|
12
src/equicordplugins/customTimestamps/style.css
Normal file
12
src/equicordplugins/customTimestamps/style.css
Normal 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;
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue