Merge branch 'dev'
Some checks failed
Release Browser Extension / Publish (push) Failing after 1m59s
Some checks failed
Release Browser Extension / Publish (push) Failing after 1m59s
This commit is contained in:
commit
a8309bc8da
30 changed files with 174 additions and 99 deletions
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
|
|
@ -3,6 +3,7 @@ on:
|
|||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
env:
|
||||
FORCE_COLOR: true
|
||||
GITHUB_TOKEN: ${{ secrets.ETOKEN }}
|
||||
|
|
@ -65,10 +66,15 @@ jobs:
|
|||
rm release/*.map
|
||||
|
||||
- name: Upload Equicord Stable
|
||||
if: ${{ github.ref_name == 'main' }}
|
||||
run: |
|
||||
gh release upload latest --clobber dist/release/*
|
||||
|
||||
- name: Create Tag
|
||||
uses: butlerlogic/action-autotag@1.1.2
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
tag_prefix: "v"
|
||||
|
||||
- name: Upload Plugin JSONs to Equibored repo
|
||||
run: |
|
||||
git config --global user.name "GitHub-Actions"
|
||||
|
|
|
|||
6
.github/workflows/test.yml
vendored
6
.github/workflows/test.yml
vendored
|
|
@ -37,9 +37,3 @@ jobs:
|
|||
|
||||
- name: Test if plugin structure is valid
|
||||
run: pnpm generatePluginJson
|
||||
|
||||
- name: Create Tag
|
||||
if: ${{ github.ref_name == 'main' }}
|
||||
uses: butlerlogic/action-autotag@1.1.2
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "equicord",
|
||||
"private": "true",
|
||||
"version": "1.12.10",
|
||||
"version": "1.12.11",
|
||||
"description": "The other cutest Discord client mod",
|
||||
"homepage": "https://github.com/Equicord/Equicord#readme",
|
||||
"bugs": {
|
||||
|
|
|
|||
7
packages/discord-types/src/components.d.ts
vendored
7
packages/discord-types/src/components.d.ts
vendored
|
|
@ -44,12 +44,7 @@ export type FormDivider = ComponentType<{
|
|||
}>;
|
||||
|
||||
|
||||
export type FormText = ComponentType<PropsWithChildren<{
|
||||
disabled?: boolean;
|
||||
selectable?: boolean;
|
||||
/** defaults to FormText.Types.DEFAULT */
|
||||
type?: string;
|
||||
}> & TextProps> & { Types: FormTextTypes; };
|
||||
export type FormText = ComponentType<TextProps>;
|
||||
|
||||
export type Tooltip = ComponentType<{
|
||||
text: ReactNode | ComponentType;
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ export default definePlugin({
|
|||
{
|
||||
find: "PrivateChannel.renderAvatar",
|
||||
replacement: {
|
||||
match: /(subText:\i\(\),)/,
|
||||
match: /(withDisplayNameStyles\]:\i\}\),children:\i\}\),)/,
|
||||
replace: "$1style:{color:`${$self.colorDMList(arguments[0])}`},"
|
||||
},
|
||||
predicate: () => settings.store.dmList,
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ function warningEmbedNotice(trigger) {
|
|||
<Forms.FormText>
|
||||
Your message contains a term on the automod preset list. (Term "{trigger}")
|
||||
</Forms.FormText>
|
||||
<Forms.FormText type={Forms.FormText.Types.DESCRIPTION}>
|
||||
<Forms.FormText>
|
||||
There is a high chance your message will be blocked and potentially moderated by a server moderator.
|
||||
</Forms.FormText>
|
||||
</div>,
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ function ResetCard() {
|
|||
<Forms.FormText>
|
||||
Saving your base profile will allow you to have a backup of your actual profile
|
||||
</Forms.FormText>
|
||||
<Forms.FormText type={Forms.FormText.Types.DESCRIPTION}>
|
||||
<Forms.FormText>
|
||||
If you save, it will overwrite your previous data.
|
||||
</Forms.FormText>
|
||||
</div>,
|
||||
|
|
@ -107,7 +107,7 @@ function ResetCard() {
|
|||
<Forms.FormText>
|
||||
Loading your base profile will restore your actual profile settings
|
||||
</Forms.FormText>
|
||||
<Forms.FormText type={Forms.FormText.Types.DESCRIPTION}>
|
||||
<Forms.FormText>
|
||||
If you load, it will overwrite your current profile configuration.
|
||||
</Forms.FormText>
|
||||
</div>,
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ function SettingSection() {
|
|||
return (
|
||||
<Forms.FormSection>
|
||||
<Forms.FormTitle>Media source to stream</Forms.FormTitle>
|
||||
<Forms.FormText type={Forms.FormText.Types.DESCRIPTION}>Resets to main screen if not found</Forms.FormText>
|
||||
<Forms.FormText>Resets to main screen if not found</Forms.FormText>
|
||||
<ScreenSetting />
|
||||
</Forms.FormSection>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -322,8 +322,8 @@ export default definePlugin({
|
|||
{
|
||||
find: "location:\"RecentsPopout\"});",
|
||||
replacement: {
|
||||
match: /:(\i)===\i\.\i\.MENTIONS\?\(0,.+?setTab:(\i),onJump:(\i),badgeState:\i,closePopout:(\i)/,
|
||||
replace: ": $1 === 8 ? $self.tryKeywordMenu($2, $3, $4) $&"
|
||||
match: /(?<=setTab:(\i),badgeState:\i,closePopout:(\i).{0,50}):(\i)===\i\.\i\.MENTIONS\?\(0,.+?onJump:(\i)/,
|
||||
replace: ": $3 === 8 ? $self.tryKeywordMenu($1, $2, $4) $&"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ import { makeRange, OptionType } from "@utils/types";
|
|||
import { Button, Forms, MaskedLink, showToast, Toasts } from "@webpack/common";
|
||||
|
||||
import hoverOnlyStyle from "./hoverOnly.css?managed";
|
||||
import { clearLyricsCache } from "./spotify/lyrics/api";
|
||||
import { clearLyricsCache, removeTranslations } from "./spotify/lyrics/api";
|
||||
import languages from "./spotify/lyrics/providers/translator/languages";
|
||||
import { Provider } from "./spotify/lyrics/providers/types";
|
||||
|
||||
const sliderOptions = {
|
||||
|
|
@ -61,6 +62,15 @@ export const settings = definePluginSettings({
|
|||
{ value: Provider.Lrclib, label: "LRCLIB" },
|
||||
],
|
||||
},
|
||||
TranslateTo: {
|
||||
description: "Translate lyrics to - Changing this will clear existing translations",
|
||||
type: OptionType.SELECT,
|
||||
options: languages,
|
||||
onChange: async () => {
|
||||
await removeTranslations();
|
||||
showToast("Translations cleared", Toasts.Type.SUCCESS);
|
||||
}
|
||||
},
|
||||
LyricsConversion: {
|
||||
description: "Automatically translate or romanize lyrics",
|
||||
type: OptionType.SELECT,
|
||||
|
|
|
|||
|
|
@ -29,8 +29,7 @@ export const lyricFetchers = {
|
|||
export const providers = Object.keys(lyricFetchers) as Provider[];
|
||||
|
||||
export async function getLyrics(track: Track | null): Promise<LyricsData | null> {
|
||||
if (!track) return null;
|
||||
if (!track.id) return null;
|
||||
if (!track || !track.id) return null;
|
||||
|
||||
const cacheKey = track.id;
|
||||
const cached = await DataStore.get(LyricsCacheKey) as Record<string, LyricsData | null>;
|
||||
|
|
@ -76,7 +75,7 @@ export async function clearLyricsCache() {
|
|||
|
||||
export async function getLyricsCount(): Promise<number> {
|
||||
const cache = await DataStore.get(LyricsCacheKey) as Record<string, LyricsData | null>;
|
||||
return Object.keys(cache).length;
|
||||
return Object.keys(cache ?? {}).length;
|
||||
}
|
||||
|
||||
export async function updateLyrics(trackId: string, newLyrics: SyncedLyric[], provider: Provider) {
|
||||
|
|
|
|||
|
|
@ -14,51 +14,45 @@ import { LyricsContextMenu } from "./ctxMenu";
|
|||
import { LyricsModal } from "./modal";
|
||||
import { cl, NoteSvg, useLyrics } from "./util";
|
||||
|
||||
const prevCl = cl("prev");
|
||||
const nextCl = cl("next");
|
||||
const currentCl = cl("current");
|
||||
|
||||
function LyricsDisplay({ scroll = true }: { scroll?: boolean; }) {
|
||||
const { ShowMusicNoteOnNoLyrics } = settings.use(["ShowMusicNoteOnNoLyrics"]);
|
||||
const { lyricsInfo, lyricRefs, currLrcIndex } = useLyrics({ scroll });
|
||||
|
||||
const currentLyrics = lyricsInfo?.lyricsVersions[lyricsInfo.useLyric] || null;
|
||||
|
||||
const NoteElement = NoteSvg(cl("music-note"));
|
||||
|
||||
const makeClassName = (index: number): string => {
|
||||
if (currLrcIndex === null) return "";
|
||||
if (currLrcIndex == null) return prevCl;
|
||||
|
||||
const diff = index - currLrcIndex;
|
||||
|
||||
if (diff === 0) return cl("current");
|
||||
return cl(diff > 0 ? "next" : "prev");
|
||||
if (diff === 0) return currentCl;
|
||||
return diff > 0 ? nextCl : prevCl;
|
||||
};
|
||||
|
||||
if (!lyricsInfo) {
|
||||
return ShowMusicNoteOnNoLyrics ? (
|
||||
<div className="vc-spotify-lyrics"
|
||||
onContextMenu={e => ContextMenuApi.openContextMenu(e, () => <LyricsContextMenu />)}
|
||||
>
|
||||
<TooltipContainer text="No synced lyrics found">
|
||||
{NoteElement}
|
||||
</TooltipContainer>
|
||||
</div>
|
||||
) : null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="vc-spotify-lyrics"
|
||||
onClick={() => openModal(props => <LyricsModal rootProps={props} />)}
|
||||
onClick={() => openModal(props => <LyricsModal props={props} />)}
|
||||
onContextMenu={e => ContextMenuApi.openContextMenu(e, () => <LyricsContextMenu />)}
|
||||
>
|
||||
{currentLyrics?.map((line, i) => (
|
||||
{currentLyrics ? currentLyrics.map((line, i) => (
|
||||
<div ref={lyricRefs[i]} key={i}>
|
||||
<Text
|
||||
variant={currLrcIndex === i ? "text-sm/normal" : "text-xs/normal"}
|
||||
className={makeClassName(i)}
|
||||
>
|
||||
{line.text || NoteElement}
|
||||
{line.text || NoteSvg()}
|
||||
</Text>
|
||||
</div>
|
||||
))}
|
||||
)) : ShowMusicNoteOnNoLyrics ? (
|
||||
<TooltipContainer text="No synced lyrics found">
|
||||
<NoteSvg />
|
||||
</TooltipContainer>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,14 @@ const formatTime = (time: number) => {
|
|||
return `${minutes}:${seconds.toString().padStart(2, "0")}`;
|
||||
};
|
||||
|
||||
function ModalHeaderContent({ track }: { track: Track; }) {
|
||||
function ModalHeaderContent({ track }: { track: Track | null; }) {
|
||||
if (!track) {
|
||||
return (
|
||||
<ModalHeader>
|
||||
<Text variant="text-sm/semibold">No track playing</Text>
|
||||
</ModalHeader>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<ModalHeader>
|
||||
<div className={cl("header-content")}>
|
||||
|
|
@ -43,27 +50,30 @@ function ModalHeaderContent({ track }: { track: Track; }) {
|
|||
);
|
||||
}
|
||||
|
||||
export function LyricsModal({ rootProps }: { rootProps: ModalProps; }) {
|
||||
const modalCurrentLine = cl("modal-line-current");
|
||||
const modalLine = cl("modal-line");
|
||||
|
||||
export function LyricsModal({ props }: { props: ModalProps; }) {
|
||||
const { track, lyricsInfo, currLrcIndex } = useLyrics({ scroll: false });
|
||||
const currentLyrics = lyricsInfo?.lyricsVersions[lyricsInfo.useLyric] || null;
|
||||
const currentLyrics = lyricsInfo?.lyricsVersions[lyricsInfo.useLyric];
|
||||
|
||||
return (
|
||||
<ModalRoot {...rootProps}>
|
||||
<ModalHeaderContent track={track!} />
|
||||
<ModalRoot {...props}>
|
||||
<ModalHeaderContent track={track} />
|
||||
<ModalContent>
|
||||
<div className={cl("lyrics-modal-container") + ` ${scrollClasses.auto}`}>
|
||||
<div className={`${cl("lyrics-modal-container")} ${scrollClasses.auto}`}>
|
||||
{currentLyrics ? (
|
||||
currentLyrics.map((line, i) => (
|
||||
<Text
|
||||
key={i}
|
||||
variant={currLrcIndex === i ? "text-md/semibold" : "text-sm/normal"}
|
||||
selectable
|
||||
className={currLrcIndex === i ? cl("modal-line-current") : cl("modal-line")}
|
||||
className={currLrcIndex === i ? modalCurrentLine : modalLine}
|
||||
>
|
||||
<span className={cl("modal-timestamp")} onClick={() => SpotifyStore.seek(line.time * 1000)}>
|
||||
{formatTime(line.time)}
|
||||
</span>
|
||||
{line.text || NoteSvg(cl("modal-note"))}
|
||||
{line.text || NoteSvg()}
|
||||
</Text>
|
||||
))
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -12,23 +12,51 @@ import { settings } from "../../../settings";
|
|||
import { SpotifyStore } from "../../SpotifyStore";
|
||||
import { SpotifyLrcStore } from "../providers/store";
|
||||
import { SyncedLyric } from "../providers/types";
|
||||
|
||||
export const scrollClasses = findByPropsLazy("auto", "customTheme");
|
||||
|
||||
export const cl = classNameFactory("vc-spotify-lyrics-");
|
||||
|
||||
export function NoteSvg(className: string) {
|
||||
export function NoteSvg() {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 480 720" fill="currentColor" className={className} >
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 480 720" fill="currentColor" className={cl("music-note")}>
|
||||
<path d="m160,-240 q -66,0 -113,-47 -47,-47 -47,-113 0,-66 47,-113 47,-47 113,-47 23,0 42.5,5.5 19.5,5.5 37.5,16.5 v -422 h 240 v 160 H 320 v 400 q 0,66 -47,113 -47,47 -113,47 z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
const calculateIndexes = (lyrics: SyncedLyric[], position: number, delay: number) => {
|
||||
const posInSec = position / 1000;
|
||||
const currentIndex = lyrics.findIndex(l => l.time - (delay / 1000) > posInSec && l.time < posInSec + 8) - 1;
|
||||
const nextLyric = lyrics.findIndex(l => l.time >= posInSec);
|
||||
return [currentIndex, nextLyric];
|
||||
|
||||
const getIndexes = (lyrics: SyncedLyric[], position: number, delay: number) => {
|
||||
const posInSec = (position + delay) / 1000;
|
||||
|
||||
let left = 0, right = lyrics.length - 1;
|
||||
let currentIndex: number | null = null;
|
||||
|
||||
while (left <= right) {
|
||||
const mid = Math.floor((left + right) / 2);
|
||||
const curr = lyrics[mid];
|
||||
const next = lyrics[mid + 1];
|
||||
|
||||
if (curr.time <= posInSec && (!next || next.time > posInSec)) {
|
||||
currentIndex = mid;
|
||||
break;
|
||||
}
|
||||
|
||||
if (curr.time > posInSec) {
|
||||
right = mid - 1;
|
||||
} else {
|
||||
left = mid + 1;
|
||||
}
|
||||
}
|
||||
|
||||
const nextIdx = currentIndex !== null ? currentIndex + 1 : left;
|
||||
const nextLyricIdx = nextIdx < lyrics.length ? nextIdx : null;
|
||||
|
||||
if (currentIndex !== null && posInSec - lyrics[currentIndex].time > 8) {
|
||||
return [null, nextLyricIdx];
|
||||
}
|
||||
|
||||
return [currentIndex, nextLyricIdx];
|
||||
};
|
||||
|
||||
export function useLyrics({ scroll = true }: { scroll?: boolean; } = {}) {
|
||||
|
|
@ -47,7 +75,7 @@ export function useLyrics({ scroll = true }: { scroll?: boolean; } = {}) {
|
|||
const [position, setPosition] = useState(storePosition);
|
||||
const [lyricRefs, setLyricRefs] = useState<React.RefObject<HTMLDivElement | null>[]>([]);
|
||||
|
||||
const currentLyrics = lyricsInfo?.lyricsVersions[lyricsInfo.useLyric] || null;
|
||||
const currentLyrics = lyricsInfo?.lyricsVersions[lyricsInfo.useLyric];
|
||||
|
||||
useEffect(() => {
|
||||
if (currentLyrics) {
|
||||
|
|
@ -57,12 +85,15 @@ export function useLyrics({ scroll = true }: { scroll?: boolean; } = {}) {
|
|||
|
||||
|
||||
useEffect(() => {
|
||||
if (currentLyrics && position) {
|
||||
const [currentIndex, nextLyric] = calculateIndexes(currentLyrics, position, LyricDelay);
|
||||
if (currentLyrics && position != null) {
|
||||
const [currentIndex, nextLyricIndex] = getIndexes(currentLyrics, position, LyricDelay);
|
||||
setCurrLrcIndex(currentIndex);
|
||||
setNextLyric(nextLyric);
|
||||
setNextLyric(nextLyricIndex);
|
||||
} else {
|
||||
setCurrLrcIndex(null);
|
||||
setNextLyric(null);
|
||||
}
|
||||
}, [currentLyrics, position]);
|
||||
}, [currentLyrics, position, LyricDelay]);
|
||||
|
||||
useEffect(() => {
|
||||
if (scroll && currLrcIndex !== null) {
|
||||
|
|
@ -73,17 +104,15 @@ export function useLyrics({ scroll = true }: { scroll?: boolean; } = {}) {
|
|||
lyricRefs[nextLyric]?.current?.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||
}
|
||||
}
|
||||
}, [currLrcIndex, nextLyric, scroll]);
|
||||
}, [currLrcIndex, nextLyric, scroll, lyricRefs]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isPlaying) {
|
||||
if (!isPlaying) return;
|
||||
|
||||
setPosition(SpotifyStore.position);
|
||||
const interval = setInterval(() => {
|
||||
setPosition(p => p + 1000);
|
||||
}, 1000);
|
||||
const interval = setInterval(() => setPosition(p => p + 1000), 1000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}
|
||||
}, [storePosition, isPlaying]);
|
||||
|
||||
return { track, lyricsInfo, lyricRefs, currLrcIndex, nextLyric };
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import settings from "../../../..";
|
||||
import { LyricsData, Provider, SyncedLyric } from "../types";
|
||||
import { Provider, SyncedLyric } from "../types";
|
||||
|
||||
// stolen from src\plugins\translate\utils.ts
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ async function googleTranslate(text: string, targetLang: string, romanize: boole
|
|||
}
|
||||
|
||||
async function processLyrics(
|
||||
lyrics: LyricsData["lyricsVersions"][Provider],
|
||||
lyrics: SyncedLyric[],
|
||||
targetLang: string,
|
||||
romanize: boolean
|
||||
): Promise<SyncedLyric[] | null> {
|
||||
|
|
@ -67,7 +67,7 @@ async function processLyrics(
|
|||
})
|
||||
);
|
||||
|
||||
if (processedLyricsResp[0][1] === null) return null;
|
||||
if (processedLyricsResp.every(mapping => mapping[1] === null)) return null;
|
||||
|
||||
return lyrics.map(lyric => ({
|
||||
...lyric,
|
||||
|
|
@ -75,13 +75,12 @@ async function processLyrics(
|
|||
}));
|
||||
}
|
||||
|
||||
async function translateLyrics(lyrics: LyricsData["lyricsVersions"][Provider]): Promise<SyncedLyric[] | null> {
|
||||
const language = settings.store.TranslateTo;
|
||||
return processLyrics(lyrics, language, false);
|
||||
async function translateLyrics(lyrics: SyncedLyric[]) {
|
||||
return await processLyrics(lyrics, settings.store.TranslateTo, false);
|
||||
}
|
||||
|
||||
async function romanizeLyrics(lyrics: LyricsData["lyricsVersions"][Provider]): Promise<SyncedLyric[] | null> {
|
||||
return processLyrics(lyrics, "", true);
|
||||
async function romanizeLyrics(lyrics: SyncedLyric[]) {
|
||||
return await processLyrics(lyrics, "", true);
|
||||
}
|
||||
|
||||
export const lyricsAlternativeFetchers = {
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ export default definePlugin({
|
|||
{
|
||||
find: "this.renderEmbeddedActivity()",
|
||||
replacement: {
|
||||
match: /(?<=render\(\).{0,500}children.{0,50})"div"(?=.{0,500}this\.renderEmbeddedActivity\(\))/,
|
||||
match: /"div"(?=.{0,100}this.renderActions)/,
|
||||
replace: "$self.WrapperComponent"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,8 +126,8 @@ export default definePlugin({
|
|||
find: "#{intl::USER_SETTINGS_ACTIONS_MENU_LABEL}",
|
||||
replacement: [
|
||||
{
|
||||
match: /(EXPERIMENTS:.+?)(\(0,\i.\i\)\(\))(?=\.filter\(\i=>\{let\{section:\i\}=)/,
|
||||
replace: "$1$self.wrapMenu($2)"
|
||||
match: /(\(0,\i.\i\)\(\))(?=\.filter\(\i=>\{let\{section:\i\}=)/,
|
||||
replace: "$self.wrapMenu($1)"
|
||||
},
|
||||
{
|
||||
match: /case \i\.\i\.DEVELOPER_OPTIONS:return \i;/,
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ const AllowLevelSettings = ErrorBoundary.wrap(() => {
|
|||
return (
|
||||
<Forms.FormSection>
|
||||
<Forms.FormTitle tag="h3">Filter List</Forms.FormTitle>
|
||||
<Forms.FormText className={Margins.bottom8} type={Forms.FormText.Types.DESCRIPTION}>Always allow loggers of these types</Forms.FormText>
|
||||
<Forms.FormText className={Margins.bottom8}>Always allow loggers of these types</Forms.FormText>
|
||||
<Flex flexDirection="row">
|
||||
{Object.keys(settings.store.allowLevel).map(key => (
|
||||
<AllowLevelSetting key={key} settingKey={key as keyof AllowLevels} />
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export const settings = definePluginSettings({
|
|||
|
||||
return <div>
|
||||
<DecorSection hideTitle hideDivider noMargin />
|
||||
<Forms.FormText type="description" className={classes(Margins.top8, Margins.bottom8)}>
|
||||
<Forms.FormText className={classes(Margins.top8, Margins.bottom8)}>
|
||||
You can also access Decor decorations from the <Link
|
||||
href="/settings/profile-customization"
|
||||
onClick={e => {
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ function SectionHeader({ section }: SectionHeaderProps) {
|
|||
/>}
|
||||
</Flex>
|
||||
{hasSubtitle &&
|
||||
<Forms.FormText type="description" className={Margins.bottom8}>
|
||||
<Forms.FormText className={Margins.bottom8}>
|
||||
{section.subtitle}
|
||||
</Forms.FormText>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ function CreateDecorationModal(props: ModalProps) {
|
|||
filters={[{ name: "Decoration file", extensions: ["png", "apng"] }]}
|
||||
onFileSelect={setFile}
|
||||
/>
|
||||
<Forms.FormText type="description" className={Margins.top8}>
|
||||
<Forms.FormText className={Margins.top8}>
|
||||
File should be APNG or PNG.
|
||||
</Forms.FormText>
|
||||
</Forms.FormSection>
|
||||
|
|
@ -107,7 +107,7 @@ function CreateDecorationModal(props: ModalProps) {
|
|||
value={name}
|
||||
onChange={setName}
|
||||
/>
|
||||
<Forms.FormText type="description" className={Margins.top8}>
|
||||
<Forms.FormText className={Margins.top8}>
|
||||
This name will be used when referring to this decoration.
|
||||
</Forms.FormText>
|
||||
</Forms.FormSection>
|
||||
|
|
|
|||
|
|
@ -805,7 +805,7 @@ export default definePlugin({
|
|||
however you do not have permissions to embed links in the current channel.
|
||||
Are you sure you want to send this message? Your FakeNitro items will appear as a link only.
|
||||
</Forms.FormText>
|
||||
<Forms.FormText type={Forms.FormText.Types.DESCRIPTION}>
|
||||
<Forms.FormText>
|
||||
You can disable this notice in the plugin settings.
|
||||
</Forms.FormText>
|
||||
</div>,
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ function recalculateActivities() {
|
|||
function ImportCustomRPCComponent() {
|
||||
return (
|
||||
<Flex flexDirection="column">
|
||||
<Forms.FormText type={Forms.FormText.Types.DESCRIPTION}>Import the application id of the CustomRPC plugin to the filter list</Forms.FormText>
|
||||
<Forms.FormText>Import the application id of the CustomRPC plugin to the filter list</Forms.FormText>
|
||||
<div>
|
||||
<Button
|
||||
onClick={() => {
|
||||
|
|
@ -133,7 +133,7 @@ function IdsListComponent(props: { setValue: (value: string) => void; }) {
|
|||
return (
|
||||
<Forms.FormSection>
|
||||
<Forms.FormTitle tag="h3">Filter List</Forms.FormTitle>
|
||||
<Forms.FormText className={Margins.bottom8} type={Forms.FormText.Types.DESCRIPTION}>Comma separated list of activity IDs to filter (Useful for filtering specific RPC activities and CustomRPC</Forms.FormText>
|
||||
<Forms.FormText className={Margins.bottom8}>Comma separated list of activity IDs to filter (Useful for filtering specific RPC activities and CustomRPC</Forms.FormText>
|
||||
<TextInput
|
||||
type="text"
|
||||
value={idsList}
|
||||
|
|
|
|||
|
|
@ -61,6 +61,11 @@ export const settings = definePluginSettings({
|
|||
type: OptionType.CUSTOM,
|
||||
default: {} as Record<string, Category[]>,
|
||||
description: "",
|
||||
},
|
||||
disableCreateDMButton: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Disabled the create dm button",
|
||||
default: true
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -154,6 +159,14 @@ export default definePlugin({
|
|||
replace: "$self.getAllUncollapsedChannels().concat($&.filter(c=>!$self.isPinned(c)))"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: ".createDMButtonContainer",
|
||||
replacement: {
|
||||
match: /"full-width"===\i/,
|
||||
replace: "$&&&false"
|
||||
},
|
||||
predicate: () => settings.store.disableCreateDMButton
|
||||
},
|
||||
],
|
||||
|
||||
sections: null as number[] | null,
|
||||
|
|
|
|||
|
|
@ -225,7 +225,7 @@ export default definePlugin({
|
|||
},
|
||||
{
|
||||
// Hide the new version of unreads box for hidden channels
|
||||
find: '="ChannelListUnreadsStore",',
|
||||
find: '"ChannelListUnreadsStore",',
|
||||
replacement: {
|
||||
match: /(?<=\.id\)\))(?=&&\(0,\i\.\i\)\((\i)\))/,
|
||||
replace: (_, channel) => `&&!$self.isHiddenChannel(${channel})`
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ export default definePlugin({
|
|||
replace: "false"
|
||||
},
|
||||
{
|
||||
match: /return\s*?\[.{0,50}?(?=\?\(0,\i\.jsxs?.{0,100}?id:"copy-image")/,
|
||||
match: /return\s*?\[.{0,100}?(?=\?\(0,\i\.jsxs?.{0,100}?id:"copy-image")/,
|
||||
replace: "return [true"
|
||||
},
|
||||
{
|
||||
|
|
|
|||
25
src/webpack/common/FormText.tsx
Normal file
25
src/webpack/common/FormText.tsx
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import * as t from "@vencord/discord-types";
|
||||
|
||||
import { Text } from "./components";
|
||||
|
||||
// TODO: replace with our own component soon
|
||||
export const FormText: t.FormText = function FormText(props) {
|
||||
const variant = props.variant || "text-sm/normal";
|
||||
return (
|
||||
<Text
|
||||
variant={variant}
|
||||
{...props}
|
||||
>
|
||||
{props.children}
|
||||
</Text>
|
||||
);
|
||||
} as any;
|
||||
|
||||
// @ts-expect-error
|
||||
FormText.Types = {};
|
||||
|
|
@ -20,11 +20,11 @@ import { LazyComponent } from "@utils/lazyReact";
|
|||
import * as t from "@vencord/discord-types";
|
||||
import { filters, mapMangledModuleLazy, waitFor } from "@webpack";
|
||||
|
||||
import { FormText } from "./FormText";
|
||||
import { waitForComponent } from "./internal";
|
||||
|
||||
|
||||
const FormTitle = waitForComponent<t.FormTitle>("FormTitle", filters.componentByCode('["defaultMargin".concat', '="h5"'));
|
||||
const FormText = waitForComponent<t.FormText>("FormText", filters.componentByCode(".SELECTABLE),", ".DISABLED:"));
|
||||
const FormSection = waitForComponent<t.FormSection>("FormSection", filters.componentByCode(".titleId)"));
|
||||
const FormDivider = waitForComponent<t.FormDivider>("FormDivider", filters.componentByCode(".divider,", ",style:", '"div"', /\.divider,\i\),style:/));
|
||||
|
||||
|
|
|
|||
|
|
@ -212,6 +212,7 @@ export const DisplayProfileUtils: t.DisplayProfileUtils = mapMangledModuleLazy(/
|
|||
export const DateUtils: t.DateUtils = mapMangledModuleLazy("millisecondsInUnit:", {
|
||||
calendarFormat: filters.byCode("sameElse"),
|
||||
dateFormat: filters.byCode('":'),
|
||||
isSameDay: filters.byCode("Math.abs(+"),
|
||||
// TODO: the +? are for compat with the old version - Remove them once no longer needed
|
||||
isSameDay: filters.byCode(/Math\.abs\(\+?\i-\+?\i\)/),
|
||||
diffAsUnits: filters.byCode("days:0", "millisecondsInUnit")
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue