MessageTags: add argument support & considerably improve UX
This commit is contained in:
parent
f766ea6b49
commit
143bb60051
5 changed files with 293 additions and 133 deletions
134
src/plugins/messageTags/CreateTagModal.tsx
Normal file
134
src/plugins/messageTags/CreateTagModal.tsx
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2026 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { BaseText } from "@components/BaseText";
|
||||||
|
import { Button } from "@components/Button";
|
||||||
|
import { Card } from "@components/Card";
|
||||||
|
import { InlineCode } from "@components/CodeBlock";
|
||||||
|
import { ExpandableSection } from "@components/ExpandableCard";
|
||||||
|
import { Flex } from "@components/Flex";
|
||||||
|
import { HeadingSecondary } from "@components/Heading";
|
||||||
|
import { InfoIcon } from "@components/Icons";
|
||||||
|
import { Margins } from "@components/margins";
|
||||||
|
import { Paragraph } from "@components/Paragraph";
|
||||||
|
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||||
|
import { TextArea, TextInput, useState } from "@webpack/common";
|
||||||
|
|
||||||
|
import { parseTagArguments, registerTagCommand } from ".";
|
||||||
|
import { addTag, getTag, Tag } from "./settings";
|
||||||
|
|
||||||
|
export function openCreateTagModal(initialValue: Tag = { name: "", message: "" }) {
|
||||||
|
openModal(modalProps => (
|
||||||
|
<Modal initialValue={initialValue} modalProps={modalProps} />
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
const EXAMPLE_RESPONSE = "Hello {{user}}! I am feeling {{mood = great}}.";
|
||||||
|
|
||||||
|
function Modal({ initialValue, modalProps }: { initialValue: Tag; modalProps: ModalProps; }) {
|
||||||
|
const [name, setName] = useState(initialValue.name);
|
||||||
|
const [message, setMessage] = useState(initialValue.message.replaceAll("\\n", "\n"));
|
||||||
|
|
||||||
|
const detectedArguments = parseTagArguments(message);
|
||||||
|
const hasReservedEphemeral = detectedArguments.some(arg => arg.name === "ephemeral");
|
||||||
|
const nameAlreadyExists = name !== initialValue.name && getTag(name);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
|
||||||
|
<ModalHeader>
|
||||||
|
<BaseText size="lg" weight="semibold" style={{ flexGrow: 1 }}>Create Tag</BaseText>
|
||||||
|
<ModalCloseButton onClick={modalProps.onClose} />
|
||||||
|
</ModalHeader>
|
||||||
|
|
||||||
|
<ModalContent>
|
||||||
|
<Flex flexDirection="column" gap={12}>
|
||||||
|
<Paragraph>Create a new tag which will be registered as a slash command.</Paragraph>
|
||||||
|
|
||||||
|
<section className={Margins.top8}>
|
||||||
|
<HeadingSecondary>Name</HeadingSecondary>
|
||||||
|
<TextInput value={name} onChange={setName} placeholder="greet" />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<HeadingSecondary>Response</HeadingSecondary>
|
||||||
|
<TextArea value={message} onChange={setMessage} placeholder={EXAMPLE_RESPONSE} />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{detectedArguments.length > 0 && (
|
||||||
|
<section>
|
||||||
|
<HeadingSecondary>Detected Arguments</HeadingSecondary>
|
||||||
|
<Paragraph>
|
||||||
|
<ul>
|
||||||
|
{detectedArguments.map(arg => (
|
||||||
|
<li key={arg.name}>
|
||||||
|
— <b>{arg.name}</b>{arg.defaultValue ? ` (default: ${arg.defaultValue})` : ""}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</Paragraph>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<ExpandableSection
|
||||||
|
renderContent={() => (
|
||||||
|
<Flex flexDirection="column" gap={12}>
|
||||||
|
<Paragraph>
|
||||||
|
Your response can include variables wrapped in double curly braces which will become command arguments, for example <InlineCode>{"Hello {{user}}"}</InlineCode>.
|
||||||
|
</Paragraph>
|
||||||
|
<Paragraph>
|
||||||
|
You can specify arguments with default values by using an equals sign, for example <InlineCode>{"Hello {{user = pal}}"}</InlineCode>.
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Paragraph><b>Example Command response:</b> <InlineCode>{EXAMPLE_RESPONSE}</InlineCode></Paragraph>
|
||||||
|
<Paragraph><b>Example usage:</b> <InlineCode>{"/greet user:@Clyde"}</InlineCode></Paragraph>
|
||||||
|
<Paragraph><b>Example output:</b> <InlineCode>{"Hello @Clyde! I am feeling great."}</InlineCode></Paragraph>
|
||||||
|
</section>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Flex alignItems="center" gap={8}>
|
||||||
|
<InfoIcon color="var(--text-muted)" height={16} width={16} />
|
||||||
|
View Arguments guide
|
||||||
|
</Flex>
|
||||||
|
</ExpandableSection>
|
||||||
|
{hasReservedEphemeral &&
|
||||||
|
<Card variant="danger" className={Margins.top8} defaultPadding>
|
||||||
|
<Paragraph>The argument name "ephemeral" is reserved and cannot be used.</Paragraph>
|
||||||
|
</Card>
|
||||||
|
}
|
||||||
|
{nameAlreadyExists &&
|
||||||
|
<Card variant="warning" className={Margins.top8} defaultPadding>
|
||||||
|
<Paragraph>A tag with the name <InlineCode>{name}</InlineCode> already exists and will be overwritten.</Paragraph>
|
||||||
|
</Card>
|
||||||
|
}
|
||||||
|
</Flex>
|
||||||
|
</ModalContent>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<Flex>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
onClick={modalProps.onClose}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
const tag = { name, message };
|
||||||
|
addTag(tag);
|
||||||
|
registerTagCommand(tag);
|
||||||
|
modalProps.onClose();
|
||||||
|
}}
|
||||||
|
disabled={!name || !message || hasReservedEphemeral}
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalRoot>
|
||||||
|
);
|
||||||
|
}
|
||||||
41
src/plugins/messageTags/SettingsTagList.tsx
Normal file
41
src/plugins/messageTags/SettingsTagList.tsx
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2026 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { BaseText } from "@components/BaseText";
|
||||||
|
import { Button } from "@components/Button";
|
||||||
|
import { Card } from "@components/Card";
|
||||||
|
import { Flex } from "@components/Flex";
|
||||||
|
import { DeleteIcon, PencilIcon } from "@components/Icons";
|
||||||
|
import { Margins } from "@components/margins";
|
||||||
|
import { Paragraph } from "@components/Paragraph";
|
||||||
|
|
||||||
|
import { openCreateTagModal } from "./CreateTagModal";
|
||||||
|
import { removeTag, settings } from "./settings";
|
||||||
|
|
||||||
|
export function SettingsTagList() {
|
||||||
|
const { tagsList } = settings.use(["tagsList"]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className={Margins.top8}>
|
||||||
|
<BaseText size="md" weight="semibold">Registered Tags</BaseText>
|
||||||
|
<Flex flexDirection="column" gap="0.5em" className={Margins.top8}>
|
||||||
|
{Object.values(tagsList).map(tag => (
|
||||||
|
<Card key={tag.name} className="vc-messageTags-card">
|
||||||
|
<Paragraph size="md" weight="medium">{tag.name}</Paragraph>
|
||||||
|
|
||||||
|
<Button variant="secondary" size="iconOnly" onClick={() => openCreateTagModal(tag)}>
|
||||||
|
<PencilIcon aria-label="Edit Tag" width={20} height={20} />
|
||||||
|
</Button>
|
||||||
|
<Button variant="dangerSecondary" size="iconOnly" onClick={() => removeTag(tag.name)}>
|
||||||
|
<DeleteIcon aria-label="Delete Tag" width={20} height={20} />
|
||||||
|
</Button>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
<Button onClick={() => openCreateTagModal()}>Create Tag</Button>
|
||||||
|
</Flex>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -16,81 +16,85 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import "./styles.css";
|
||||||
|
|
||||||
import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, registerCommand, sendBotMessage, unregisterCommand } from "@api/Commands";
|
import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, registerCommand, sendBotMessage, unregisterCommand } from "@api/Commands";
|
||||||
import { definePluginSettings } from "@api/Settings";
|
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import { sendMessage } from "@utils/discord";
|
||||||
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
|
import { openCreateTagModal } from "./CreateTagModal";
|
||||||
|
import { getTag, getTags, removeTag, settings, Tag } from "./settings";
|
||||||
|
|
||||||
const EMOTE = "<:luna:1035316192220553236>";
|
|
||||||
const DATA_KEY = "MessageTags_TAGS";
|
|
||||||
const MessageTagsMarker = Symbol("MessageTags");
|
const MessageTagsMarker = Symbol("MessageTags");
|
||||||
|
const ArgumentRegex = /{{(.+?)}}/g;
|
||||||
|
|
||||||
interface Tag {
|
export function parseTagArguments(message: string) {
|
||||||
name: string;
|
const args = [] as { name: string, defaultValue: string | null; }[];
|
||||||
message: string;
|
|
||||||
|
for (const [, value] of message.matchAll(ArgumentRegex)) {
|
||||||
|
const [name, defaultValue] = value.split("=").map(s => s.trim());
|
||||||
|
|
||||||
|
if (!name) continue;
|
||||||
|
if (args.some(arg => arg.name === name)) continue;
|
||||||
|
|
||||||
|
args.push({ name: name.toLowerCase(), defaultValue: defaultValue ?? null });
|
||||||
|
}
|
||||||
|
|
||||||
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTags() {
|
export function registerTagCommand(tag: Tag) {
|
||||||
return settings.store.tagsList;
|
const tagArguments = parseTagArguments(tag.message);
|
||||||
}
|
|
||||||
|
|
||||||
function getTag(name: string) {
|
|
||||||
return settings.store.tagsList[name] ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addTag(tag: Tag) {
|
|
||||||
settings.store.tagsList[tag.name] = tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeTag(name: string) {
|
|
||||||
delete settings.store.tagsList[name];
|
|
||||||
}
|
|
||||||
|
|
||||||
function createTagCommand(tag: Tag) {
|
|
||||||
registerCommand({
|
registerCommand({
|
||||||
name: tag.name,
|
name: tag.name,
|
||||||
description: tag.name,
|
description: tag.name,
|
||||||
inputType: ApplicationCommandInputType.BUILT_IN_TEXT,
|
inputType: ApplicationCommandInputType.BUILT_IN,
|
||||||
execute: async (_, ctx) => {
|
options: [
|
||||||
if (!getTag(tag.name)) {
|
...tagArguments.map(arg => ({
|
||||||
sendBotMessage(ctx.channel.id, {
|
name: arg.name,
|
||||||
content: `${EMOTE} The tag **${tag.name}** does not exist anymore! Please reload ur Discord to fix :)`
|
description: arg.name,
|
||||||
});
|
type: ApplicationCommandOptionType.STRING,
|
||||||
return { content: `/${tag.name}` };
|
required: arg.defaultValue === null
|
||||||
|
})),
|
||||||
|
{
|
||||||
|
name: "ephemeral",
|
||||||
|
description: "Whether the response should only be visible to you",
|
||||||
|
type: ApplicationCommandOptionType.BOOLEAN,
|
||||||
|
required: false
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
|
||||||
if (settings.store.clyde) sendBotMessage(ctx.channel.id, {
|
execute: async (args, ctx) => {
|
||||||
content: `${EMOTE} The tag **${tag.name}** has been sent!`
|
const ephemeral = findOption(args, "ephemeral", false);
|
||||||
});
|
|
||||||
return { content: tag.message.replaceAll("\\n", "\n") };
|
const response = tag.message
|
||||||
|
.replace(ArgumentRegex, (fullMatch, value: string) => {
|
||||||
|
const [argName, defaultValue] = value.split("=").map(s => s.trim());
|
||||||
|
return findOption(args, argName, null) ?? defaultValue ?? fullMatch;
|
||||||
|
})
|
||||||
|
.replaceAll("\\n", "\n");
|
||||||
|
|
||||||
|
const doSend = ephemeral ? sendBotMessage : sendMessage;
|
||||||
|
doSend(ctx.channel.id, { content: response });
|
||||||
},
|
},
|
||||||
[MessageTagsMarker]: true,
|
[MessageTagsMarker]: true,
|
||||||
}, "CustomTags");
|
}, "CustomTags");
|
||||||
}
|
}
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
|
||||||
clyde: {
|
|
||||||
name: "Clyde message on send",
|
|
||||||
description: "If enabled, clyde will send you an ephemeral message when a tag was used.",
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
tagsList: {
|
|
||||||
type: OptionType.CUSTOM,
|
|
||||||
default: {} as Record<string, Tag>,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "MessageTags",
|
name: "MessageTags",
|
||||||
description: "Allows you to save messages and to use them with a simple command.",
|
description: "Allows you to create custom slash commands",
|
||||||
authors: [Devs.Luna],
|
authors: [Devs.Luna, Devs.Ven],
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
const tags = getTags();
|
const tags = getTags();
|
||||||
for (const tagName in tags) {
|
for (const tagName in tags) {
|
||||||
createTagCommand(tags[tagName]);
|
registerTagCommand(tags[tagName]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -104,131 +108,62 @@ export default definePlugin({
|
||||||
name: "create",
|
name: "create",
|
||||||
description: "Create a new tag",
|
description: "Create a new tag",
|
||||||
type: ApplicationCommandOptionType.SUB_COMMAND,
|
type: ApplicationCommandOptionType.SUB_COMMAND,
|
||||||
options: [
|
|
||||||
{
|
|
||||||
name: "tag-name",
|
|
||||||
description: "The name of the tag to trigger the response",
|
|
||||||
type: ApplicationCommandOptionType.STRING,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "message",
|
|
||||||
description: "The message that you will send when using this tag",
|
|
||||||
type: ApplicationCommandOptionType.STRING,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "list",
|
name: "list",
|
||||||
description: "List all tags from yourself",
|
description: "List all your tags",
|
||||||
type: ApplicationCommandOptionType.SUB_COMMAND,
|
type: ApplicationCommandOptionType.SUB_COMMAND,
|
||||||
options: []
|
options: []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "delete",
|
name: "delete",
|
||||||
description: "Remove a tag from your yourself",
|
description: "Remove a tag by name",
|
||||||
type: ApplicationCommandOptionType.SUB_COMMAND,
|
type: ApplicationCommandOptionType.SUB_COMMAND,
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
name: "tag-name",
|
name: "tag-name",
|
||||||
description: "The name of the tag to trigger the response",
|
description: "The name of the tag",
|
||||||
type: ApplicationCommandOptionType.STRING,
|
type: ApplicationCommandOptionType.STRING,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "preview",
|
|
||||||
description: "Preview a tag without sending it publicly",
|
|
||||||
type: ApplicationCommandOptionType.SUB_COMMAND,
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
name: "tag-name",
|
|
||||||
description: "The name of the tag to trigger the response",
|
|
||||||
type: ApplicationCommandOptionType.STRING,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
|
|
||||||
async execute(args, ctx) {
|
async execute(args, ctx) {
|
||||||
|
|
||||||
switch (args[0].name) {
|
switch (args[0].name) {
|
||||||
case "create": {
|
case "create": {
|
||||||
const name: string = findOption(args[0].options, "tag-name", "");
|
openCreateTagModal();
|
||||||
const message: string = findOption(args[0].options, "message", "");
|
break;
|
||||||
|
|
||||||
if (getTag(name))
|
|
||||||
return sendBotMessage(ctx.channel.id, {
|
|
||||||
content: `${EMOTE} A Tag with the name **${name}** already exists!`
|
|
||||||
});
|
|
||||||
|
|
||||||
const tag = {
|
|
||||||
name: name,
|
|
||||||
message: message
|
|
||||||
};
|
|
||||||
|
|
||||||
createTagCommand(tag);
|
|
||||||
addTag(tag);
|
|
||||||
|
|
||||||
sendBotMessage(ctx.channel.id, {
|
|
||||||
content: `${EMOTE} Successfully created the tag **${name}**!`
|
|
||||||
});
|
|
||||||
break; // end 'create'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case "delete": {
|
case "delete": {
|
||||||
const name: string = findOption(args[0].options, "tag-name", "");
|
const name: string = findOption(args[0].options, "tag-name", "");
|
||||||
|
|
||||||
if (!getTag(name))
|
if (!getTag(name))
|
||||||
return sendBotMessage(ctx.channel.id, {
|
return sendBotMessage(ctx.channel.id, {
|
||||||
content: `${EMOTE} A Tag with the name **${name}** does not exist!`
|
content: `A Tag with the name **${name}** does not exist!`
|
||||||
});
|
});
|
||||||
|
|
||||||
unregisterCommand(name);
|
unregisterCommand(name);
|
||||||
removeTag(name);
|
removeTag(name);
|
||||||
|
|
||||||
sendBotMessage(ctx.channel.id, {
|
sendBotMessage(ctx.channel.id, {
|
||||||
content: `${EMOTE} Successfully deleted the tag **${name}**!`
|
content: `Successfully deleted the tag **${name}**!`
|
||||||
});
|
});
|
||||||
break; // end 'delete'
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "list": {
|
case "list": {
|
||||||
sendBotMessage(ctx.channel.id, {
|
const content = Object.values(getTags())
|
||||||
embeds: [
|
.map(tag => `\`${tag.name}\`: ${tag.message.slice(0, 72).replaceAll("\\n", " ")}${tag.message.length > 72 ? "..." : ""}`)
|
||||||
{
|
.join("\n");
|
||||||
title: "All Tags:",
|
|
||||||
description: Object.values(getTags())
|
|
||||||
.map(tag => `\`${tag.name}\`: ${tag.message.slice(0, 72).replaceAll("\\n", " ")}${tag.message.length > 72 ? "..." : ""}`)
|
|
||||||
.join("\n") || `${EMOTE} Woops! There are no tags yet, use \`/tags create\` to create one!`,
|
|
||||||
// @ts-expect-error
|
|
||||||
color: 0xd77f7f,
|
|
||||||
type: "rich",
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
break; // end 'list'
|
|
||||||
}
|
|
||||||
case "preview": {
|
|
||||||
const name: string = findOption(args[0].options, "tag-name", "");
|
|
||||||
const tag = getTag(name);
|
|
||||||
|
|
||||||
if (!tag)
|
|
||||||
return sendBotMessage(ctx.channel.id, {
|
|
||||||
content: `${EMOTE} A Tag with the name **${name}** does not exist!`
|
|
||||||
});
|
|
||||||
|
|
||||||
sendBotMessage(ctx.channel.id, {
|
sendBotMessage(ctx.channel.id, {
|
||||||
content: tag.message.replaceAll("\\n", "\n")
|
content: content || "Woops! There are no tags yet, use `/tags create` to create one!",
|
||||||
});
|
});
|
||||||
break; // end 'preview'
|
|
||||||
}
|
|
||||||
|
|
||||||
default: {
|
|
||||||
sendBotMessage(ctx.channel.id, {
|
|
||||||
content: "Invalid sub-command"
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
42
src/plugins/messageTags/settings.ts
Normal file
42
src/plugins/messageTags/settings.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2026 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { definePluginSettings } from "@api/Settings";
|
||||||
|
import { OptionType } from "@utils/types";
|
||||||
|
|
||||||
|
import { SettingsTagList } from "./SettingsTagList";
|
||||||
|
|
||||||
|
export const settings = definePluginSettings({
|
||||||
|
tagsList: {
|
||||||
|
type: OptionType.CUSTOM,
|
||||||
|
default: {} as Record<string, Tag>,
|
||||||
|
},
|
||||||
|
tagComponent: {
|
||||||
|
type: OptionType.COMPONENT,
|
||||||
|
component: SettingsTagList
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export interface Tag {
|
||||||
|
name: string;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTags() {
|
||||||
|
return settings.store.tagsList;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTag(name: string) {
|
||||||
|
return settings.store.tagsList[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addTag(tag: Tag) {
|
||||||
|
settings.store.tagsList[tag.name] = tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeTag(name: string) {
|
||||||
|
delete settings.store.tagsList[name];
|
||||||
|
}
|
||||||
8
src/plugins/messageTags/styles.css
Normal file
8
src/plugins/messageTags/styles.css
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
.vc-messageTags-card {
|
||||||
|
padding: 0.5em;
|
||||||
|
padding-left: 1em;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr min-content min-content;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25em;
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue