feat(TextReplace): add search and naming functionality for text replacement rules (#1090)
* feat(TextReplace): add search functionality for text replacement rules * feat(TextReplace): add name for rules for better searching * fix(TextReplace): update rule removal condition to include name check and improve descriptions * feat(TextReplace): handle new rules while searching --------- Co-authored-by: Aperre <ape@aqpe.dev>
This commit is contained in:
parent
3e63b40bd2
commit
3a2f6ec046
1 changed files with 67 additions and 18 deletions
|
|
@ -35,7 +35,14 @@ import { React, Select, TextInput, UserStore, useState } from "@webpack/common";
|
|||
|
||||
const cl = classNameFactory("vc-textReplace-");
|
||||
|
||||
type Rule = Record<"find" | "replace" | "onlyIfIncludes" | "scope" | "id", string>;
|
||||
interface Rule {
|
||||
name?: string;
|
||||
find: string;
|
||||
replace: string;
|
||||
onlyIfIncludes: string;
|
||||
scope: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface TextReplaceProps {
|
||||
title: string;
|
||||
|
|
@ -44,7 +51,13 @@ interface TextReplaceProps {
|
|||
isRegex?: boolean;
|
||||
}
|
||||
|
||||
interface RuleWithIndex {
|
||||
rule: Rule;
|
||||
index: number;
|
||||
}
|
||||
|
||||
const makeEmptyRule: () => Rule = () => ({
|
||||
name: "",
|
||||
find: "",
|
||||
replace: "",
|
||||
onlyIfIncludes: "",
|
||||
|
|
@ -151,7 +164,23 @@ function TextRow({ label, description, value, onChange }: { label: string; descr
|
|||
|
||||
const isEmptyRule = (rule: Rule) => !rule.find;
|
||||
|
||||
function matchesRuleSearch(rule: Rule, query: string) {
|
||||
if (!query) return true;
|
||||
|
||||
const normalizedQuery = query.trim().toLowerCase();
|
||||
return [rule.name ?? "", rule.find, rule.replace, rule.onlyIfIncludes]
|
||||
.some(value => value.toLowerCase().includes(normalizedQuery));
|
||||
}
|
||||
|
||||
function normalizeRule(rule: Rule) {
|
||||
rule.name ??= "";
|
||||
rule.scope ??= "myMessages";
|
||||
rule.id ??= crypto.randomUUID();
|
||||
}
|
||||
|
||||
function TextReplace({ title, description, rulesArray, isRegex = false }: TextReplaceProps) {
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
|
||||
function onClickRemove(index: number) {
|
||||
rulesArray.splice(index, 1);
|
||||
}
|
||||
|
|
@ -160,7 +189,7 @@ function TextReplace({ title, description, rulesArray, isRegex = false }: TextRe
|
|||
rulesArray[index][key] = e;
|
||||
|
||||
// If a rule is empty after editing and is not the last rule, remove it
|
||||
if (rulesArray[index].find === "" && rulesArray[index].replace === "" && rulesArray[index].onlyIfIncludes === "" && index !== rulesArray.length - 1) {
|
||||
if (rulesArray[index].name === "" && rulesArray[index].find === "" && rulesArray[index].replace === "" && rulesArray[index].onlyIfIncludes === "" && index !== rulesArray.length - 1) {
|
||||
rulesArray.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
|
@ -171,19 +200,41 @@ function TextReplace({ title, description, rulesArray, isRegex = false }: TextRe
|
|||
{ label: "Apply to all messages", value: "allMessages" }
|
||||
];
|
||||
|
||||
const filteredRules = rulesArray.reduce((acc: RuleWithIndex[], rule, index) => {
|
||||
if (matchesRuleSearch(rule, searchQuery)) {
|
||||
acc.push({ rule, index });
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<HeadingSecondary>{title}</HeadingSecondary>
|
||||
<Paragraph>{description}</Paragraph>
|
||||
<TextInput
|
||||
placeholder="Search for a rule..."
|
||||
value={searchQuery}
|
||||
onChange={setSearchQuery}
|
||||
/>
|
||||
</div>
|
||||
<Flex flexDirection="column" style={{ gap: "0.5em", paddingBottom: "1.25em" }}>
|
||||
{rulesArray.map((rule, index) =>
|
||||
{!filteredRules.length && searchQuery && (
|
||||
<Paragraph>No rules match your search criteria.</Paragraph>
|
||||
)}
|
||||
{filteredRules.map(({ rule, index }) =>
|
||||
<ExpandableSection
|
||||
key={rule.id}
|
||||
renderContent={() => (
|
||||
<>
|
||||
<div className={cl("input-grid")}>
|
||||
<TextRow
|
||||
label="Name"
|
||||
description="An optional name to help you identify this rule."
|
||||
value={rule.name ?? ""}
|
||||
onChange={e => onChange(e, index, "name")}
|
||||
/>
|
||||
<TextRow
|
||||
label="Find"
|
||||
description={isRegex ? "The regex pattern" : "The text to replace"}
|
||||
|
|
@ -198,7 +249,7 @@ function TextReplace({ title, description, rulesArray, isRegex = false }: TextRe
|
|||
/>
|
||||
<TextRow
|
||||
label="Only if includes"
|
||||
description="This rule will only be applied if the message includes this text. This is optional"
|
||||
description="Optionally, only apply this rule if the message includes this text."
|
||||
value={rule.onlyIfIncludes}
|
||||
onChange={e => onChange(e, index, "onlyIfIncludes")}
|
||||
/>
|
||||
|
|
@ -223,15 +274,20 @@ function TextReplace({ title, description, rulesArray, isRegex = false }: TextRe
|
|||
)}
|
||||
>
|
||||
<Paragraph weight="medium" size="md">
|
||||
{isEmptyRule(rule)
|
||||
? `Empty Rule ${index + 1}`
|
||||
: `Rule ${index + 1} - ${rule.find}`
|
||||
{rule.name
|
||||
? rule.name
|
||||
: isEmptyRule(rule)
|
||||
? `Empty Rule ${index + 1}`
|
||||
: `Rule ${index + 1} - ${rule.find}`
|
||||
}
|
||||
</Paragraph>
|
||||
</ExpandableSection>
|
||||
)}
|
||||
<Button
|
||||
onClick={() => rulesArray.push(makeEmptyRule())}
|
||||
onClick={() => {
|
||||
setSearchQuery("");
|
||||
rulesArray.push(makeEmptyRule());
|
||||
}}
|
||||
disabled={rulesArray.length > 0 && isEmptyRule(rulesArray[rulesArray.length - 1])}
|
||||
>
|
||||
Add Rule
|
||||
|
|
@ -306,7 +362,7 @@ export default definePlugin({
|
|||
description: "Replace text in your messages. You can find pre-made rules in the #textreplace-rules channel in Vencord's Server",
|
||||
dependencies: ["MessagePopoverAPI"],
|
||||
tags: ["Chat", "Customisation", "Utility"],
|
||||
authors: [Devs.AutumnVN, Devs.TheKodeToad, EquicordDevs.Etorix],
|
||||
authors: [Devs.AutumnVN, Devs.TheKodeToad, EquicordDevs.Etorix, EquicordDevs.Ape],
|
||||
isModified: true,
|
||||
settings,
|
||||
modifyIncomingMessage,
|
||||
|
|
@ -324,15 +380,8 @@ export default definePlugin({
|
|||
start() {
|
||||
const { stringRules, regexRules } = settings.store;
|
||||
|
||||
stringRules.forEach(rule => {
|
||||
rule.scope ??= "myMessages";
|
||||
rule.id ??= crypto.randomUUID();
|
||||
});
|
||||
|
||||
regexRules.forEach(rule => {
|
||||
rule.scope ??= "myMessages";
|
||||
rule.id ??= crypto.randomUUID();
|
||||
});
|
||||
stringRules.forEach(normalizeRule);
|
||||
regexRules.forEach(normalizeRule);
|
||||
},
|
||||
|
||||
onBeforeMessageSend(channelId, msg) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue