mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
Discord: update UIs to use the new config
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -30,6 +30,7 @@ vendor/
|
|||||||
|
|
||||||
# Vendor build artifacts
|
# Vendor build artifacts
|
||||||
vendor/a2ui/renderers/lit/dist/
|
vendor/a2ui/renderers/lit/dist/
|
||||||
|
.bundle.hash
|
||||||
|
|
||||||
# fastlane (iOS)
|
# fastlane (iOS)
|
||||||
apps/ios/fastlane/README.md
|
apps/ios/fastlane/README.md
|
||||||
|
|||||||
@@ -289,6 +289,12 @@ struct ConnectionsSettings: View {
|
|||||||
TextField("123456789, username#1234", text: self.$store.discordAllowFrom)
|
TextField("123456789, username#1234", text: self.$store.discordAllowFrom)
|
||||||
.textFieldStyle(.roundedBorder)
|
.textFieldStyle(.roundedBorder)
|
||||||
}
|
}
|
||||||
|
GridRow {
|
||||||
|
self.gridLabel("DMs enabled")
|
||||||
|
Toggle("", isOn: self.$store.discordDmEnabled)
|
||||||
|
.labelsHidden()
|
||||||
|
.toggleStyle(.checkbox)
|
||||||
|
}
|
||||||
GridRow {
|
GridRow {
|
||||||
self.gridLabel("Group DMs")
|
self.gridLabel("Group DMs")
|
||||||
Toggle("", isOn: self.$store.discordGroupEnabled)
|
Toggle("", isOn: self.$store.discordGroupEnabled)
|
||||||
@@ -310,6 +316,20 @@ struct ConnectionsSettings: View {
|
|||||||
TextField("20", text: self.$store.discordHistoryLimit)
|
TextField("20", text: self.$store.discordHistoryLimit)
|
||||||
.textFieldStyle(.roundedBorder)
|
.textFieldStyle(.roundedBorder)
|
||||||
}
|
}
|
||||||
|
GridRow {
|
||||||
|
self.gridLabel("Text chunk limit")
|
||||||
|
TextField("2000", text: self.$store.discordTextChunkLimit)
|
||||||
|
.textFieldStyle(.roundedBorder)
|
||||||
|
}
|
||||||
|
GridRow {
|
||||||
|
self.gridLabel("Reply to mode")
|
||||||
|
Picker("", selection: self.$store.discordReplyToMode) {
|
||||||
|
Text("off").tag("off")
|
||||||
|
Text("first").tag("first")
|
||||||
|
Text("all").tag("all")
|
||||||
|
}
|
||||||
|
.labelsHidden()
|
||||||
|
}
|
||||||
GridRow {
|
GridRow {
|
||||||
self.gridLabel("Slash command")
|
self.gridLabel("Slash command")
|
||||||
Toggle("", isOn: self.$store.discordSlashEnabled)
|
Toggle("", isOn: self.$store.discordSlashEnabled)
|
||||||
@@ -336,6 +356,79 @@ struct ConnectionsSettings: View {
|
|||||||
|
|
||||||
Divider().padding(.vertical, 2)
|
Divider().padding(.vertical, 2)
|
||||||
|
|
||||||
|
Text("Guilds")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 12) {
|
||||||
|
ForEach($store.discordGuilds) { $guild in
|
||||||
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
|
HStack {
|
||||||
|
TextField("guild id or slug", text: $guild.key)
|
||||||
|
.textFieldStyle(.roundedBorder)
|
||||||
|
Button("Remove") {
|
||||||
|
self.store.discordGuilds.removeAll { $0.id == guild.id }
|
||||||
|
}
|
||||||
|
.buttonStyle(.bordered)
|
||||||
|
}
|
||||||
|
|
||||||
|
Grid(alignment: .leadingFirstTextBaseline, horizontalSpacing: 14, verticalSpacing: 10) {
|
||||||
|
GridRow {
|
||||||
|
self.gridLabel("Slug")
|
||||||
|
TextField("optional slug", text: $guild.slug)
|
||||||
|
.textFieldStyle(.roundedBorder)
|
||||||
|
}
|
||||||
|
GridRow {
|
||||||
|
self.gridLabel("Require mention")
|
||||||
|
Toggle("", isOn: $guild.requireMention)
|
||||||
|
.labelsHidden()
|
||||||
|
.toggleStyle(.checkbox)
|
||||||
|
}
|
||||||
|
GridRow {
|
||||||
|
self.gridLabel("Users allowlist")
|
||||||
|
TextField("123456789, username#1234", text: $guild.users)
|
||||||
|
.textFieldStyle(.roundedBorder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text("Channels")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
|
ForEach($guild.channels) { $channel in
|
||||||
|
HStack(spacing: 10) {
|
||||||
|
TextField("channel id or slug", text: $channel.key)
|
||||||
|
.textFieldStyle(.roundedBorder)
|
||||||
|
Toggle("Allow", isOn: $channel.allow)
|
||||||
|
.toggleStyle(.checkbox)
|
||||||
|
Toggle("Require mention", isOn: $channel.requireMention)
|
||||||
|
.toggleStyle(.checkbox)
|
||||||
|
Button("Remove") {
|
||||||
|
guild.channels.removeAll { $0.id == channel.id }
|
||||||
|
}
|
||||||
|
.buttonStyle(.bordered)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Button("Add channel") {
|
||||||
|
guild.channels.append(DiscordGuildChannelForm())
|
||||||
|
}
|
||||||
|
.buttonStyle(.bordered)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(10)
|
||||||
|
.background(Color.secondary.opacity(0.08))
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||||
|
}
|
||||||
|
|
||||||
|
Button("Add guild") {
|
||||||
|
self.store.discordGuilds.append(DiscordGuildForm())
|
||||||
|
}
|
||||||
|
.buttonStyle(.bordered)
|
||||||
|
}
|
||||||
|
|
||||||
|
Divider().padding(.vertical, 2)
|
||||||
|
|
||||||
Text("Tool actions")
|
Text("Tool actions")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
|
|||||||
@@ -144,6 +144,42 @@ struct ConfigSnapshot: Codable {
|
|||||||
let issues: [Issue]?
|
let issues: [Issue]?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct DiscordGuildChannelForm: Identifiable {
|
||||||
|
let id = UUID()
|
||||||
|
var key: String
|
||||||
|
var allow: Bool
|
||||||
|
var requireMention: Bool
|
||||||
|
|
||||||
|
init(key: String = "", allow: Bool = true, requireMention: Bool = false) {
|
||||||
|
self.key = key
|
||||||
|
self.allow = allow
|
||||||
|
self.requireMention = requireMention
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DiscordGuildForm: Identifiable {
|
||||||
|
let id = UUID()
|
||||||
|
var key: String
|
||||||
|
var slug: String
|
||||||
|
var requireMention: Bool
|
||||||
|
var users: String
|
||||||
|
var channels: [DiscordGuildChannelForm]
|
||||||
|
|
||||||
|
init(
|
||||||
|
key: String = "",
|
||||||
|
slug: String = "",
|
||||||
|
requireMention: Bool = false,
|
||||||
|
users: String = "",
|
||||||
|
channels: [DiscordGuildChannelForm] = []
|
||||||
|
) {
|
||||||
|
self.key = key
|
||||||
|
self.slug = slug
|
||||||
|
self.requireMention = requireMention
|
||||||
|
self.users = users
|
||||||
|
self.channels = channels
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
@Observable
|
@Observable
|
||||||
final class ConnectionsStore {
|
final class ConnectionsStore {
|
||||||
@@ -169,11 +205,15 @@ final class ConnectionsStore {
|
|||||||
var telegramBusy = false
|
var telegramBusy = false
|
||||||
var discordEnabled = true
|
var discordEnabled = true
|
||||||
var discordToken: String = ""
|
var discordToken: String = ""
|
||||||
|
var discordDmEnabled = true
|
||||||
var discordAllowFrom: String = ""
|
var discordAllowFrom: String = ""
|
||||||
var discordGroupEnabled = false
|
var discordGroupEnabled = false
|
||||||
var discordGroupChannels: String = ""
|
var discordGroupChannels: String = ""
|
||||||
var discordMediaMaxMb: String = ""
|
var discordMediaMaxMb: String = ""
|
||||||
var discordHistoryLimit: String = ""
|
var discordHistoryLimit: String = ""
|
||||||
|
var discordTextChunkLimit: String = ""
|
||||||
|
var discordReplyToMode: String = "off"
|
||||||
|
var discordGuilds: [DiscordGuildForm] = []
|
||||||
var discordActionReactions = true
|
var discordActionReactions = true
|
||||||
var discordActionStickers = true
|
var discordActionStickers = true
|
||||||
var discordActionPolls = true
|
var discordActionPolls = true
|
||||||
@@ -401,6 +441,7 @@ final class ConnectionsStore {
|
|||||||
self.discordEnabled = discord?["enabled"]?.boolValue ?? true
|
self.discordEnabled = discord?["enabled"]?.boolValue ?? true
|
||||||
self.discordToken = discord?["token"]?.stringValue ?? ""
|
self.discordToken = discord?["token"]?.stringValue ?? ""
|
||||||
let discordDm = discord?["dm"]?.dictionaryValue
|
let discordDm = discord?["dm"]?.dictionaryValue
|
||||||
|
self.discordDmEnabled = discordDm?["enabled"]?.boolValue ?? true
|
||||||
if let allow = discordDm?["allowFrom"]?.arrayValue {
|
if let allow = discordDm?["allowFrom"]?.arrayValue {
|
||||||
let strings = allow.compactMap { entry -> String? in
|
let strings = allow.compactMap { entry -> String? in
|
||||||
if let str = entry.stringValue { return str }
|
if let str = entry.stringValue { return str }
|
||||||
@@ -434,6 +475,56 @@ final class ConnectionsStore {
|
|||||||
} else {
|
} else {
|
||||||
self.discordHistoryLimit = ""
|
self.discordHistoryLimit = ""
|
||||||
}
|
}
|
||||||
|
if let limit = discord?["textChunkLimit"]?.doubleValue ?? discord?["textChunkLimit"]?.intValue.map(Double.init) {
|
||||||
|
self.discordTextChunkLimit = String(Int(limit))
|
||||||
|
} else {
|
||||||
|
self.discordTextChunkLimit = ""
|
||||||
|
}
|
||||||
|
if let mode = discord?["replyToMode"]?.stringValue, ["off", "first", "all"].contains(mode) {
|
||||||
|
self.discordReplyToMode = mode
|
||||||
|
} else {
|
||||||
|
self.discordReplyToMode = "off"
|
||||||
|
}
|
||||||
|
if let guilds = discord?["guilds"]?.dictionaryValue {
|
||||||
|
self.discordGuilds = guilds
|
||||||
|
.map { key, value in
|
||||||
|
let entry = value.dictionaryValue ?? [:]
|
||||||
|
let slug = entry["slug"]?.stringValue ?? ""
|
||||||
|
let requireMention = entry["requireMention"]?.boolValue ?? false
|
||||||
|
let users = entry["users"]?.arrayValue?
|
||||||
|
.compactMap { item -> String? in
|
||||||
|
if let str = item.stringValue { return str }
|
||||||
|
if let intVal = item.intValue { return String(intVal) }
|
||||||
|
if let doubleVal = item.doubleValue { return String(Int(doubleVal)) }
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
.joined(separator: ", ") ?? ""
|
||||||
|
let channels: [DiscordGuildChannelForm]
|
||||||
|
if let channelMap = entry["channels"]?.dictionaryValue {
|
||||||
|
channels = channelMap.map { channelKey, channelValue in
|
||||||
|
let channelEntry = channelValue.dictionaryValue ?? [:]
|
||||||
|
let allow = channelEntry["allow"]?.boolValue ?? true
|
||||||
|
let channelRequireMention =
|
||||||
|
channelEntry["requireMention"]?.boolValue ?? false
|
||||||
|
return DiscordGuildChannelForm(
|
||||||
|
key: channelKey,
|
||||||
|
allow: allow,
|
||||||
|
requireMention: channelRequireMention)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channels = []
|
||||||
|
}
|
||||||
|
return DiscordGuildForm(
|
||||||
|
key: key,
|
||||||
|
slug: slug,
|
||||||
|
requireMention: requireMention,
|
||||||
|
users: users,
|
||||||
|
channels: channels)
|
||||||
|
}
|
||||||
|
.sorted { $0.key < $1.key }
|
||||||
|
} else {
|
||||||
|
self.discordGuilds = []
|
||||||
|
}
|
||||||
let discordActions = discord?["actions"]?.dictionaryValue
|
let discordActions = discord?["actions"]?.dictionaryValue
|
||||||
self.discordActionReactions = discordActions?["reactions"]?.boolValue ?? true
|
self.discordActionReactions = discordActions?["reactions"]?.boolValue ?? true
|
||||||
self.discordActionStickers = discordActions?["stickers"]?.boolValue ?? true
|
self.discordActionStickers = discordActions?["stickers"]?.boolValue ?? true
|
||||||
@@ -625,6 +716,11 @@ final class ConnectionsStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var dm: [String: Any] = (discord["dm"] as? [String: Any]) ?? [:]
|
var dm: [String: Any] = (discord["dm"] as? [String: Any]) ?? [:]
|
||||||
|
if self.discordDmEnabled {
|
||||||
|
dm.removeValue(forKey: "enabled")
|
||||||
|
} else {
|
||||||
|
dm["enabled"] = false
|
||||||
|
}
|
||||||
let allow = self.discordAllowFrom
|
let allow = self.discordAllowFrom
|
||||||
.split(separator: ",")
|
.split(separator: ",")
|
||||||
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
|
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
|
||||||
@@ -673,6 +769,53 @@ final class ConnectionsStore {
|
|||||||
discord.removeValue(forKey: "historyLimit")
|
discord.removeValue(forKey: "historyLimit")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let chunkLimit = self.discordTextChunkLimit.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
if chunkLimit.isEmpty {
|
||||||
|
discord.removeValue(forKey: "textChunkLimit")
|
||||||
|
} else if let value = Int(chunkLimit), value > 0 {
|
||||||
|
discord["textChunkLimit"] = value
|
||||||
|
} else {
|
||||||
|
discord.removeValue(forKey: "textChunkLimit")
|
||||||
|
}
|
||||||
|
|
||||||
|
let replyToMode = self.discordReplyToMode.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
if replyToMode.isEmpty || replyToMode == "off" {
|
||||||
|
discord.removeValue(forKey: "replyToMode")
|
||||||
|
} else if ["first", "all"].contains(replyToMode) {
|
||||||
|
discord["replyToMode"] = replyToMode
|
||||||
|
} else {
|
||||||
|
discord.removeValue(forKey: "replyToMode")
|
||||||
|
}
|
||||||
|
|
||||||
|
let guilds: [String: Any] = self.discordGuilds.reduce(into: [:]) { result, entry in
|
||||||
|
let key = entry.key.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
guard !key.isEmpty else { return }
|
||||||
|
var payload: [String: Any] = [:]
|
||||||
|
let slug = entry.slug.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
if !slug.isEmpty { payload["slug"] = slug }
|
||||||
|
if entry.requireMention { payload["requireMention"] = true }
|
||||||
|
let users = entry.users
|
||||||
|
.split(separator: ",")
|
||||||
|
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
|
||||||
|
.filter { !$0.isEmpty }
|
||||||
|
if !users.isEmpty { payload["users"] = users }
|
||||||
|
let channels: [String: Any] = entry.channels.reduce(into: [:]) { channelsResult, channel in
|
||||||
|
let channelKey = channel.key.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
guard !channelKey.isEmpty else { return }
|
||||||
|
var channelPayload: [String: Any] = [:]
|
||||||
|
if !channel.allow { channelPayload["allow"] = false }
|
||||||
|
if channel.requireMention { channelPayload["requireMention"] = true }
|
||||||
|
channelsResult[channelKey] = channelPayload
|
||||||
|
}
|
||||||
|
if !channels.isEmpty { payload["channels"] = channels }
|
||||||
|
result[key] = payload
|
||||||
|
}
|
||||||
|
if guilds.isEmpty {
|
||||||
|
discord.removeValue(forKey: "guilds")
|
||||||
|
} else {
|
||||||
|
discord["guilds"] = guilds
|
||||||
|
}
|
||||||
|
|
||||||
var actions: [String: Any] = (discord["actions"] as? [String: Any]) ?? [:]
|
var actions: [String: Any] = (discord["actions"] as? [String: Any]) ?? [:]
|
||||||
func setAction(_ key: String, value: Bool, defaultValue: Bool) {
|
func setAction(_ key: String, value: Bool, defaultValue: Bool) {
|
||||||
if value == defaultValue {
|
if value == defaultValue {
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
debc115fd2b264d3b3090e28b733551ca5f7532287d827c13fc579e5a7a95b9d
|
549aa1fff40f95b4a899940ad2cde0adc3991423136ba02c23bcdbd621765ad7
|
||||||
|
|||||||
@@ -210,6 +210,10 @@
|
|||||||
gap: 6px;
|
gap: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.field.full {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
|
||||||
.field span {
|
.field span {
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
|
|||||||
@@ -34,7 +34,12 @@ import {
|
|||||||
type SignalForm,
|
type SignalForm,
|
||||||
type TelegramForm,
|
type TelegramForm,
|
||||||
} from "./ui-types";
|
} from "./ui-types";
|
||||||
import { loadChatHistory, sendChat, handleChatEvent } from "./controllers/chat";
|
import {
|
||||||
|
loadChatHistory,
|
||||||
|
sendChat,
|
||||||
|
handleChatEvent,
|
||||||
|
type ChatEventPayload,
|
||||||
|
} from "./controllers/chat";
|
||||||
import { loadNodes } from "./controllers/nodes";
|
import { loadNodes } from "./controllers/nodes";
|
||||||
import { loadConfig } from "./controllers/config";
|
import { loadConfig } from "./controllers/config";
|
||||||
import {
|
import {
|
||||||
@@ -139,11 +144,15 @@ export class ClawdisApp extends LitElement {
|
|||||||
@state() discordForm: DiscordForm = {
|
@state() discordForm: DiscordForm = {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
token: "",
|
token: "",
|
||||||
|
dmEnabled: true,
|
||||||
allowFrom: "",
|
allowFrom: "",
|
||||||
groupEnabled: false,
|
groupEnabled: false,
|
||||||
groupChannels: "",
|
groupChannels: "",
|
||||||
mediaMaxMb: "",
|
mediaMaxMb: "",
|
||||||
historyLimit: "",
|
historyLimit: "",
|
||||||
|
textChunkLimit: "",
|
||||||
|
replyToMode: "off",
|
||||||
|
guilds: [],
|
||||||
actions: { ...defaultDiscordActions },
|
actions: { ...defaultDiscordActions },
|
||||||
slashEnabled: false,
|
slashEnabled: false,
|
||||||
slashName: "",
|
slashName: "",
|
||||||
@@ -350,7 +359,8 @@ export class ClawdisApp extends LitElement {
|
|||||||
].slice(0, 250);
|
].slice(0, 250);
|
||||||
|
|
||||||
if (evt.event === "chat") {
|
if (evt.event === "chat") {
|
||||||
const state = handleChatEvent(this, evt.payload as unknown);
|
const payload = evt.payload as ChatEventPayload | undefined;
|
||||||
|
const state = handleChatEvent(this, payload);
|
||||||
if (state === "final") void loadChatHistory(this);
|
if (state === "final") void loadChatHistory(this);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -469,20 +479,26 @@ export class ClawdisApp extends LitElement {
|
|||||||
if (this.theme !== "system") return;
|
if (this.theme !== "system") return;
|
||||||
this.applyResolvedTheme(event.matches ? "dark" : "light");
|
this.applyResolvedTheme(event.matches ? "dark" : "light");
|
||||||
};
|
};
|
||||||
if ("addEventListener" in this.themeMedia) {
|
if (typeof this.themeMedia.addEventListener === "function") {
|
||||||
this.themeMedia.addEventListener("change", this.themeMediaHandler);
|
this.themeMedia.addEventListener("change", this.themeMediaHandler);
|
||||||
} else {
|
return;
|
||||||
this.themeMedia.addListener(this.themeMediaHandler);
|
|
||||||
}
|
}
|
||||||
|
const legacy = this.themeMedia as MediaQueryList & {
|
||||||
|
addListener: (cb: (event: MediaQueryListEvent) => void) => void;
|
||||||
|
};
|
||||||
|
legacy.addListener(this.themeMediaHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
private detachThemeListener() {
|
private detachThemeListener() {
|
||||||
if (!this.themeMedia || !this.themeMediaHandler) return;
|
if (!this.themeMedia || !this.themeMediaHandler) return;
|
||||||
if ("removeEventListener" in this.themeMedia) {
|
if (typeof this.themeMedia.removeEventListener === "function") {
|
||||||
this.themeMedia.removeEventListener("change", this.themeMediaHandler);
|
this.themeMedia.removeEventListener("change", this.themeMediaHandler);
|
||||||
} else {
|
return;
|
||||||
this.themeMedia.removeListener(this.themeMediaHandler);
|
|
||||||
}
|
}
|
||||||
|
const legacy = this.themeMedia as MediaQueryList & {
|
||||||
|
removeListener: (cb: (event: MediaQueryListEvent) => void) => void;
|
||||||
|
};
|
||||||
|
legacy.removeListener(this.themeMediaHandler);
|
||||||
this.themeMedia = null;
|
this.themeMedia = null;
|
||||||
this.themeMediaHandler = null;
|
this.themeMediaHandler = null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export type ChatState = {
|
|||||||
lastError: string | null;
|
lastError: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ChatEventPayload = {
|
export type ChatEventPayload = {
|
||||||
runId: string;
|
runId: string;
|
||||||
sessionKey: string;
|
sessionKey: string;
|
||||||
state: "delta" | "final" | "aborted" | "error";
|
state: "delta" | "final" | "aborted" | "error";
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import {
|
|||||||
defaultDiscordActions,
|
defaultDiscordActions,
|
||||||
type DiscordActionForm,
|
type DiscordActionForm,
|
||||||
type DiscordForm,
|
type DiscordForm,
|
||||||
|
type DiscordGuildChannelForm,
|
||||||
|
type DiscordGuildForm,
|
||||||
type IMessageForm,
|
type IMessageForm,
|
||||||
type SignalForm,
|
type SignalForm,
|
||||||
type TelegramForm,
|
type TelegramForm,
|
||||||
@@ -96,6 +98,7 @@ export function applyConfigSnapshot(state: ConfigState, snapshot: ConfigSnapshot
|
|||||||
const discordDm = (discord.dm ?? {}) as Record<string, unknown>;
|
const discordDm = (discord.dm ?? {}) as Record<string, unknown>;
|
||||||
const slash = (discord.slashCommand ?? {}) as Record<string, unknown>;
|
const slash = (discord.slashCommand ?? {}) as Record<string, unknown>;
|
||||||
const discordActions = (discord.actions ?? {}) as Record<string, unknown>;
|
const discordActions = (discord.actions ?? {}) as Record<string, unknown>;
|
||||||
|
const discordGuilds = discord.guilds;
|
||||||
const readAction = (key: keyof DiscordActionForm) =>
|
const readAction = (key: keyof DiscordActionForm) =>
|
||||||
typeof discordActions[key] === "boolean"
|
typeof discordActions[key] === "boolean"
|
||||||
? (discordActions[key] as boolean)
|
? (discordActions[key] as boolean)
|
||||||
@@ -103,6 +106,7 @@ export function applyConfigSnapshot(state: ConfigState, snapshot: ConfigSnapshot
|
|||||||
state.discordForm = {
|
state.discordForm = {
|
||||||
enabled: typeof discord.enabled === "boolean" ? discord.enabled : true,
|
enabled: typeof discord.enabled === "boolean" ? discord.enabled : true,
|
||||||
token: typeof discord.token === "string" ? discord.token : "",
|
token: typeof discord.token === "string" ? discord.token : "",
|
||||||
|
dmEnabled: typeof discordDm.enabled === "boolean" ? discordDm.enabled : true,
|
||||||
allowFrom: toList(discordDm.allowFrom),
|
allowFrom: toList(discordDm.allowFrom),
|
||||||
groupEnabled:
|
groupEnabled:
|
||||||
typeof discordDm.groupEnabled === "boolean" ? discordDm.groupEnabled : false,
|
typeof discordDm.groupEnabled === "boolean" ? discordDm.groupEnabled : false,
|
||||||
@@ -111,6 +115,57 @@ export function applyConfigSnapshot(state: ConfigState, snapshot: ConfigSnapshot
|
|||||||
typeof discord.mediaMaxMb === "number" ? String(discord.mediaMaxMb) : "",
|
typeof discord.mediaMaxMb === "number" ? String(discord.mediaMaxMb) : "",
|
||||||
historyLimit:
|
historyLimit:
|
||||||
typeof discord.historyLimit === "number" ? String(discord.historyLimit) : "",
|
typeof discord.historyLimit === "number" ? String(discord.historyLimit) : "",
|
||||||
|
textChunkLimit:
|
||||||
|
typeof discord.textChunkLimit === "number"
|
||||||
|
? String(discord.textChunkLimit)
|
||||||
|
: "",
|
||||||
|
replyToMode:
|
||||||
|
discord.replyToMode === "first" || discord.replyToMode === "all"
|
||||||
|
? discord.replyToMode
|
||||||
|
: "off",
|
||||||
|
guilds: Array.isArray(discordGuilds)
|
||||||
|
? []
|
||||||
|
: typeof discordGuilds === "object" && discordGuilds
|
||||||
|
? Object.entries(discordGuilds as Record<string, unknown>).map(
|
||||||
|
([key, value]): DiscordGuildForm => {
|
||||||
|
const entry =
|
||||||
|
value && typeof value === "object"
|
||||||
|
? (value as Record<string, unknown>)
|
||||||
|
: {};
|
||||||
|
const channelsRaw =
|
||||||
|
entry.channels && typeof entry.channels === "object"
|
||||||
|
? (entry.channels as Record<string, unknown>)
|
||||||
|
: {};
|
||||||
|
const channels = Object.entries(channelsRaw).map(
|
||||||
|
([channelKey, channelValue]): DiscordGuildChannelForm => {
|
||||||
|
const channel =
|
||||||
|
channelValue && typeof channelValue === "object"
|
||||||
|
? (channelValue as Record<string, unknown>)
|
||||||
|
: {};
|
||||||
|
return {
|
||||||
|
key: channelKey,
|
||||||
|
allow:
|
||||||
|
typeof channel.allow === "boolean" ? channel.allow : true,
|
||||||
|
requireMention:
|
||||||
|
typeof channel.requireMention === "boolean"
|
||||||
|
? channel.requireMention
|
||||||
|
: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
slug: typeof entry.slug === "string" ? entry.slug : "",
|
||||||
|
requireMention:
|
||||||
|
typeof entry.requireMention === "boolean"
|
||||||
|
? entry.requireMention
|
||||||
|
: false,
|
||||||
|
users: toList(entry.users),
|
||||||
|
channels,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: [],
|
||||||
actions: {
|
actions: {
|
||||||
reactions: readAction("reactions"),
|
reactions: readAction("reactions"),
|
||||||
stickers: readAction("stickers"),
|
stickers: readAction("stickers"),
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import {
|
|||||||
defaultDiscordActions,
|
defaultDiscordActions,
|
||||||
type DiscordActionForm,
|
type DiscordActionForm,
|
||||||
type DiscordForm,
|
type DiscordForm,
|
||||||
|
type DiscordGuildChannelForm,
|
||||||
|
type DiscordGuildForm,
|
||||||
type IMessageForm,
|
type IMessageForm,
|
||||||
type SignalForm,
|
type SignalForm,
|
||||||
type TelegramForm,
|
type TelegramForm,
|
||||||
@@ -233,6 +235,8 @@ export async function saveDiscordConfig(state: ConnectionsState) {
|
|||||||
const allowFrom = parseList(form.allowFrom);
|
const allowFrom = parseList(form.allowFrom);
|
||||||
const groupChannels = parseList(form.groupChannels);
|
const groupChannels = parseList(form.groupChannels);
|
||||||
const dm = { ...(discord.dm ?? {}) } as Record<string, unknown>;
|
const dm = { ...(discord.dm ?? {}) } as Record<string, unknown>;
|
||||||
|
if (form.dmEnabled) delete dm.enabled;
|
||||||
|
else dm.enabled = false;
|
||||||
if (allowFrom.length > 0) dm.allowFrom = allowFrom;
|
if (allowFrom.length > 0) dm.allowFrom = allowFrom;
|
||||||
else delete dm.allowFrom;
|
else delete dm.allowFrom;
|
||||||
if (form.groupEnabled) dm.groupEnabled = true;
|
if (form.groupEnabled) dm.groupEnabled = true;
|
||||||
@@ -261,6 +265,51 @@ export async function saveDiscordConfig(state: ConnectionsState) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const chunkLimitRaw = form.textChunkLimit.trim();
|
||||||
|
if (chunkLimitRaw.length === 0) {
|
||||||
|
delete discord.textChunkLimit;
|
||||||
|
} else {
|
||||||
|
const chunkLimit = Number(chunkLimitRaw);
|
||||||
|
if (Number.isFinite(chunkLimit) && chunkLimit > 0) {
|
||||||
|
discord.textChunkLimit = chunkLimit;
|
||||||
|
} else {
|
||||||
|
delete discord.textChunkLimit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (form.replyToMode === "off") {
|
||||||
|
delete discord.replyToMode;
|
||||||
|
} else {
|
||||||
|
discord.replyToMode = form.replyToMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const guildsForm = Array.isArray(form.guilds) ? form.guilds : [];
|
||||||
|
const guilds: Record<string, unknown> = {};
|
||||||
|
guildsForm.forEach((guild: DiscordGuildForm) => {
|
||||||
|
const key = String(guild.key ?? "").trim();
|
||||||
|
if (!key) return;
|
||||||
|
const entry: Record<string, unknown> = {};
|
||||||
|
const slug = String(guild.slug ?? "").trim();
|
||||||
|
if (slug) entry.slug = slug;
|
||||||
|
if (guild.requireMention) entry.requireMention = true;
|
||||||
|
const users = parseList(guild.users);
|
||||||
|
if (users.length > 0) entry.users = users;
|
||||||
|
const channels: Record<string, unknown> = {};
|
||||||
|
const channelForms = Array.isArray(guild.channels) ? guild.channels : [];
|
||||||
|
channelForms.forEach((channel: DiscordGuildChannelForm) => {
|
||||||
|
const channelKey = String(channel.key ?? "").trim();
|
||||||
|
if (!channelKey) return;
|
||||||
|
const channelEntry: Record<string, unknown> = {};
|
||||||
|
if (channel.allow === false) channelEntry.allow = false;
|
||||||
|
if (channel.requireMention) channelEntry.requireMention = true;
|
||||||
|
channels[channelKey] = channelEntry;
|
||||||
|
});
|
||||||
|
if (Object.keys(channels).length > 0) entry.channels = channels;
|
||||||
|
guilds[key] = entry;
|
||||||
|
});
|
||||||
|
if (Object.keys(guilds).length > 0) discord.guilds = guilds;
|
||||||
|
else delete discord.guilds;
|
||||||
|
|
||||||
const actions: Partial<DiscordActionForm> = {};
|
const actions: Partial<DiscordActionForm> = {};
|
||||||
const applyAction = (key: keyof DiscordActionForm) => {
|
const applyAction = (key: keyof DiscordActionForm) => {
|
||||||
const value = form.actions[key];
|
const value = form.actions[key];
|
||||||
|
|||||||
@@ -11,11 +11,15 @@ export type TelegramForm = {
|
|||||||
export type DiscordForm = {
|
export type DiscordForm = {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
token: string;
|
token: string;
|
||||||
|
dmEnabled: boolean;
|
||||||
allowFrom: string;
|
allowFrom: string;
|
||||||
groupEnabled: boolean;
|
groupEnabled: boolean;
|
||||||
groupChannels: string;
|
groupChannels: string;
|
||||||
mediaMaxMb: string;
|
mediaMaxMb: string;
|
||||||
historyLimit: string;
|
historyLimit: string;
|
||||||
|
textChunkLimit: string;
|
||||||
|
replyToMode: "off" | "first" | "all";
|
||||||
|
guilds: DiscordGuildForm[];
|
||||||
actions: DiscordActionForm;
|
actions: DiscordActionForm;
|
||||||
slashEnabled: boolean;
|
slashEnabled: boolean;
|
||||||
slashName: string;
|
slashName: string;
|
||||||
@@ -23,6 +27,20 @@ export type DiscordForm = {
|
|||||||
slashEphemeral: boolean;
|
slashEphemeral: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type DiscordGuildForm = {
|
||||||
|
key: string;
|
||||||
|
slug: string;
|
||||||
|
requireMention: boolean;
|
||||||
|
users: string;
|
||||||
|
channels: DiscordGuildChannelForm[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DiscordGuildChannelForm = {
|
||||||
|
key: string;
|
||||||
|
allow: boolean;
|
||||||
|
requireMention: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export type DiscordActionForm = {
|
export type DiscordActionForm = {
|
||||||
reactions: boolean;
|
reactions: boolean;
|
||||||
stickers: boolean;
|
stickers: boolean;
|
||||||
|
|||||||
@@ -10,6 +10,24 @@ import type {
|
|||||||
TelegramForm,
|
TelegramForm,
|
||||||
} from "../ui-types";
|
} from "../ui-types";
|
||||||
|
|
||||||
|
const discordActionOptions = [
|
||||||
|
{ key: "reactions", label: "Reactions" },
|
||||||
|
{ key: "stickers", label: "Stickers" },
|
||||||
|
{ key: "polls", label: "Polls" },
|
||||||
|
{ key: "permissions", label: "Permissions" },
|
||||||
|
{ key: "messages", label: "Messages" },
|
||||||
|
{ key: "threads", label: "Threads" },
|
||||||
|
{ key: "pins", label: "Pins" },
|
||||||
|
{ key: "search", label: "Search" },
|
||||||
|
{ key: "memberInfo", label: "Member info" },
|
||||||
|
{ key: "roleInfo", label: "Role info" },
|
||||||
|
{ key: "channelInfo", label: "Channel info" },
|
||||||
|
{ key: "voiceStatus", label: "Voice status" },
|
||||||
|
{ key: "events", label: "Events" },
|
||||||
|
{ key: "roles", label: "Role changes" },
|
||||||
|
{ key: "moderation", label: "Moderation" },
|
||||||
|
] satisfies Array<{ key: keyof DiscordActionForm; label: string }>;
|
||||||
|
|
||||||
export type ConnectionsProps = {
|
export type ConnectionsProps = {
|
||||||
connected: boolean;
|
connected: boolean;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
@@ -54,24 +72,6 @@ export function renderConnections(props: ConnectionsProps) {
|
|||||||
const discord = props.snapshot?.discord ?? null;
|
const discord = props.snapshot?.discord ?? null;
|
||||||
const signal = props.snapshot?.signal ?? null;
|
const signal = props.snapshot?.signal ?? null;
|
||||||
const imessage = props.snapshot?.imessage ?? null;
|
const imessage = props.snapshot?.imessage ?? null;
|
||||||
const discordActionOptions: Array<{ key: keyof DiscordActionForm; label: string }> =
|
|
||||||
[
|
|
||||||
{ key: "reactions", label: "Reactions" },
|
|
||||||
{ key: "stickers", label: "Stickers" },
|
|
||||||
{ key: "polls", label: "Polls" },
|
|
||||||
{ key: "permissions", label: "Permissions" },
|
|
||||||
{ key: "messages", label: "Messages" },
|
|
||||||
{ key: "threads", label: "Threads" },
|
|
||||||
{ key: "pins", label: "Pins" },
|
|
||||||
{ key: "search", label: "Search" },
|
|
||||||
{ key: "memberInfo", label: "Member info" },
|
|
||||||
{ key: "roleInfo", label: "Role info" },
|
|
||||||
{ key: "channelInfo", label: "Channel info" },
|
|
||||||
{ key: "voiceStatus", label: "Voice status" },
|
|
||||||
{ key: "events", label: "Events" },
|
|
||||||
{ key: "roles", label: "Role changes" },
|
|
||||||
{ key: "moderation", label: "Moderation" },
|
|
||||||
];
|
|
||||||
const providerOrder: ProviderKey[] = [
|
const providerOrder: ProviderKey[] = [
|
||||||
"whatsapp",
|
"whatsapp",
|
||||||
"telegram",
|
"telegram",
|
||||||
@@ -500,6 +500,19 @@ function renderProvider(
|
|||||||
placeholder="123456789, username#1234"
|
placeholder="123456789, username#1234"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
<label class="field">
|
||||||
|
<span>DMs enabled</span>
|
||||||
|
<select
|
||||||
|
.value=${props.discordForm.dmEnabled ? "yes" : "no"}
|
||||||
|
@change=${(e: Event) =>
|
||||||
|
props.onDiscordChange({
|
||||||
|
dmEnabled: (e.target as HTMLSelectElement).value === "yes",
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<option value="yes">Enabled</option>
|
||||||
|
<option value="no">Disabled</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span>Group DMs</span>
|
<span>Group DMs</span>
|
||||||
<select
|
<select
|
||||||
@@ -546,6 +559,268 @@ function renderProvider(
|
|||||||
placeholder="20"
|
placeholder="20"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
<label class="field">
|
||||||
|
<span>Text chunk limit</span>
|
||||||
|
<input
|
||||||
|
.value=${props.discordForm.textChunkLimit}
|
||||||
|
@input=${(e: Event) =>
|
||||||
|
props.onDiscordChange({
|
||||||
|
textChunkLimit: (e.target as HTMLInputElement).value,
|
||||||
|
})}
|
||||||
|
placeholder="2000"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label class="field">
|
||||||
|
<span>Reply to mode</span>
|
||||||
|
<select
|
||||||
|
.value=${props.discordForm.replyToMode}
|
||||||
|
@change=${(e: Event) =>
|
||||||
|
props.onDiscordChange({
|
||||||
|
replyToMode: (e.target as HTMLSelectElement).value as
|
||||||
|
| "off"
|
||||||
|
| "first"
|
||||||
|
| "all",
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<option value="off">Off</option>
|
||||||
|
<option value="first">First</option>
|
||||||
|
<option value="all">All</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<div class="field full">
|
||||||
|
<span>Guilds</span>
|
||||||
|
<div class="card-sub">
|
||||||
|
Add each guild (id or slug) and optional channel rules. Empty channel
|
||||||
|
entries still allow that channel.
|
||||||
|
</div>
|
||||||
|
<div class="list">
|
||||||
|
${props.discordForm.guilds.map(
|
||||||
|
(guild, guildIndex) => html`
|
||||||
|
<div class="list-item">
|
||||||
|
<div class="list-main">
|
||||||
|
<div class="form-grid">
|
||||||
|
<label class="field">
|
||||||
|
<span>Guild id / slug</span>
|
||||||
|
<input
|
||||||
|
.value=${guild.key}
|
||||||
|
@input=${(e: Event) => {
|
||||||
|
const next = [...props.discordForm.guilds];
|
||||||
|
next[guildIndex] = {
|
||||||
|
...next[guildIndex],
|
||||||
|
key: (e.target as HTMLInputElement).value,
|
||||||
|
};
|
||||||
|
props.onDiscordChange({ guilds: next });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label class="field">
|
||||||
|
<span>Slug</span>
|
||||||
|
<input
|
||||||
|
.value=${guild.slug}
|
||||||
|
@input=${(e: Event) => {
|
||||||
|
const next = [...props.discordForm.guilds];
|
||||||
|
next[guildIndex] = {
|
||||||
|
...next[guildIndex],
|
||||||
|
slug: (e.target as HTMLInputElement).value,
|
||||||
|
};
|
||||||
|
props.onDiscordChange({ guilds: next });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label class="field">
|
||||||
|
<span>Require mention</span>
|
||||||
|
<select
|
||||||
|
.value=${guild.requireMention ? "yes" : "no"}
|
||||||
|
@change=${(e: Event) => {
|
||||||
|
const next = [...props.discordForm.guilds];
|
||||||
|
next[guildIndex] = {
|
||||||
|
...next[guildIndex],
|
||||||
|
requireMention:
|
||||||
|
(e.target as HTMLSelectElement).value === "yes",
|
||||||
|
};
|
||||||
|
props.onDiscordChange({ guilds: next });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value="yes">Yes</option>
|
||||||
|
<option value="no">No</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label class="field">
|
||||||
|
<span>Users allowlist</span>
|
||||||
|
<input
|
||||||
|
.value=${guild.users}
|
||||||
|
@input=${(e: Event) => {
|
||||||
|
const next = [...props.discordForm.guilds];
|
||||||
|
next[guildIndex] = {
|
||||||
|
...next[guildIndex],
|
||||||
|
users: (e.target as HTMLInputElement).value,
|
||||||
|
};
|
||||||
|
props.onDiscordChange({ guilds: next });
|
||||||
|
}}
|
||||||
|
placeholder="123456789, username#1234"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
${guild.channels.length
|
||||||
|
? html`
|
||||||
|
<div class="form-grid" style="margin-top: 8px;">
|
||||||
|
${guild.channels.map(
|
||||||
|
(channel, channelIndex) => html`
|
||||||
|
<label class="field">
|
||||||
|
<span>Channel id / slug</span>
|
||||||
|
<input
|
||||||
|
.value=${channel.key}
|
||||||
|
@input=${(e: Event) => {
|
||||||
|
const next = [...props.discordForm.guilds];
|
||||||
|
const channels = [
|
||||||
|
...(next[guildIndex].channels ?? []),
|
||||||
|
];
|
||||||
|
channels[channelIndex] = {
|
||||||
|
...channels[channelIndex],
|
||||||
|
key: (e.target as HTMLInputElement).value,
|
||||||
|
};
|
||||||
|
next[guildIndex] = {
|
||||||
|
...next[guildIndex],
|
||||||
|
channels,
|
||||||
|
};
|
||||||
|
props.onDiscordChange({ guilds: next });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label class="field">
|
||||||
|
<span>Allow</span>
|
||||||
|
<select
|
||||||
|
.value=${channel.allow ? "yes" : "no"}
|
||||||
|
@change=${(e: Event) => {
|
||||||
|
const next = [...props.discordForm.guilds];
|
||||||
|
const channels = [
|
||||||
|
...(next[guildIndex].channels ?? []),
|
||||||
|
];
|
||||||
|
channels[channelIndex] = {
|
||||||
|
...channels[channelIndex],
|
||||||
|
allow:
|
||||||
|
(e.target as HTMLSelectElement).value ===
|
||||||
|
"yes",
|
||||||
|
};
|
||||||
|
next[guildIndex] = {
|
||||||
|
...next[guildIndex],
|
||||||
|
channels,
|
||||||
|
};
|
||||||
|
props.onDiscordChange({ guilds: next });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value="yes">Yes</option>
|
||||||
|
<option value="no">No</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label class="field">
|
||||||
|
<span>Require mention</span>
|
||||||
|
<select
|
||||||
|
.value=${channel.requireMention ? "yes" : "no"}
|
||||||
|
@change=${(e: Event) => {
|
||||||
|
const next = [...props.discordForm.guilds];
|
||||||
|
const channels = [
|
||||||
|
...(next[guildIndex].channels ?? []),
|
||||||
|
];
|
||||||
|
channels[channelIndex] = {
|
||||||
|
...channels[channelIndex],
|
||||||
|
requireMention:
|
||||||
|
(e.target as HTMLSelectElement).value ===
|
||||||
|
"yes",
|
||||||
|
};
|
||||||
|
next[guildIndex] = {
|
||||||
|
...next[guildIndex],
|
||||||
|
channels,
|
||||||
|
};
|
||||||
|
props.onDiscordChange({ guilds: next });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value="yes">Yes</option>
|
||||||
|
<option value="no">No</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label class="field">
|
||||||
|
<span> </span>
|
||||||
|
<button
|
||||||
|
class="btn"
|
||||||
|
@click=${() => {
|
||||||
|
const next = [
|
||||||
|
...props.discordForm.guilds,
|
||||||
|
];
|
||||||
|
const channels = [
|
||||||
|
...(next[guildIndex].channels ?? []),
|
||||||
|
];
|
||||||
|
channels.splice(channelIndex, 1);
|
||||||
|
next[guildIndex] = {
|
||||||
|
...next[guildIndex],
|
||||||
|
channels,
|
||||||
|
};
|
||||||
|
props.onDiscordChange({ guilds: next });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</button>
|
||||||
|
</label>
|
||||||
|
`,
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
</div>
|
||||||
|
<div class="list-meta">
|
||||||
|
<span>Channels</span>
|
||||||
|
<button
|
||||||
|
class="btn"
|
||||||
|
@click=${() => {
|
||||||
|
const next = [...props.discordForm.guilds];
|
||||||
|
const channels = [
|
||||||
|
...(next[guildIndex].channels ?? []),
|
||||||
|
{ key: "", allow: true, requireMention: false },
|
||||||
|
];
|
||||||
|
next[guildIndex] = {
|
||||||
|
...next[guildIndex],
|
||||||
|
channels,
|
||||||
|
};
|
||||||
|
props.onDiscordChange({ guilds: next });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add channel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn danger"
|
||||||
|
@click=${() => {
|
||||||
|
const next = [...props.discordForm.guilds];
|
||||||
|
next.splice(guildIndex, 1);
|
||||||
|
props.onDiscordChange({ guilds: next });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Remove guild
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="btn"
|
||||||
|
style="margin-top: 8px;"
|
||||||
|
@click=${() =>
|
||||||
|
props.onDiscordChange({
|
||||||
|
guilds: [
|
||||||
|
...props.discordForm.guilds,
|
||||||
|
{
|
||||||
|
key: "",
|
||||||
|
slug: "",
|
||||||
|
requireMention: false,
|
||||||
|
users: "",
|
||||||
|
channels: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
Add guild
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span>Slash command</span>
|
<span>Slash command</span>
|
||||||
<select
|
<select
|
||||||
|
|||||||
Reference in New Issue
Block a user