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
|
- 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
|
||||||
|
|
|
@ -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 "Today" or "Yesterday".<br />
|
||||||
<b>[relative]</b> gives you times such as "4 hours ago".<br />
|
<b>[relative]</b> gives you times such as "4 hours ago".<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);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
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",
|
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
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue