Config-Suche findet einzelne Einstellungen
Die Seitenleisten-Suche kannte nur Bereiche: „starboard" führte zu Community, aber nicht zum Schwellwert-Feld. Bei rund 60 Einstellungen heißt das raten, in welchem Bereich etwas liegt. setting-index.js hält jetzt fest, wo jede Einstellung wohnt, mit Suchwörtern und Synonymen. Die Suche zeigt Treffer als eigene Gruppe unter den Bereichen; ein Klick wechselt den Bereich, scrollt zum Feld und hebt es kurz hervor. Enter nimmt den ersten Treffer, wobei Einstellungen Vorrang vor Bereichen haben — wer „schwellwert" tippt, will das Feld. Die Felder tragen dafür data-setting="<schlüssel>"; das Attribut wurde skriptgesteuert ergänzt, ohne die Struktur anzufassen. Fünf Einstellungen teilen sich ein Feld mit einer anderen (etwa die beiden Markenfarben) — die zeigen über `target` auf den Nachbarn. Textfelder werden beim Sprung fokussiert, Kanal-Auswahlen bewusst nicht: deren Fokus klappt die Liste auf und verdeckt alles darunter. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+102
-60
@@ -9,6 +9,7 @@ import {
|
||||
IconSearch, IconSystem as IconTune,
|
||||
} from '../icons.jsx';
|
||||
import { moduleIcon } from '../module-icons.jsx';
|
||||
import { findSettings } from '../setting-index.js';
|
||||
import { Markdown } from '../markdown.jsx';
|
||||
|
||||
const API_SCOPES = [
|
||||
@@ -339,6 +340,27 @@ export default function Settings({ me }) {
|
||||
const foundTabs = query
|
||||
? visibleTabs.filter((t) => `${t.label} ${t.find}`.toLowerCase().includes(query))
|
||||
: visibleTabs;
|
||||
// … und zusätzlich auf einzelne Einstellungen, mit Sprung aufs Feld
|
||||
const foundSettings = query ? findSettings(query, canTab) : [];
|
||||
|
||||
/** Bereich wechseln, zum Feld scrollen und es kurz hervorheben */
|
||||
function jumpToSetting(entry) {
|
||||
setTab(entry.tab);
|
||||
setTabSearch('');
|
||||
const key = entry.target ?? entry.id;
|
||||
// Zwei Bilder warten, bis der neue Bereich wirklich im Dokument steht
|
||||
requestAnimationFrame(() => requestAnimationFrame(() => {
|
||||
const el = document.querySelector(`[data-setting="${key}"]`);
|
||||
if (!el) return;
|
||||
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
el.classList.add('field-found');
|
||||
setTimeout(() => el.classList.remove('field-found'), 2200);
|
||||
// Nur Textfelder fokussieren: bei der Kanal-Auswahl würde der Fokus
|
||||
// die Liste aufklappen und alles darunter verdecken.
|
||||
const feld = el.querySelector('input, textarea');
|
||||
if (feld && !feld.closest('.picksel')) feld.focus({ preventScroll: true });
|
||||
}));
|
||||
}
|
||||
// Tags + geplante Posts
|
||||
const [tags, setTags] = useState([]);
|
||||
const [tagDraft, setTagDraft] = useState({ name: '', content: '' });
|
||||
@@ -1039,7 +1061,7 @@ export default function Settings({ me }) {
|
||||
</label>
|
||||
</div>
|
||||
<div className="bot-card-right">
|
||||
<div className="field">
|
||||
<div className="field" data-setting="bot_name">
|
||||
<label>Bot-Name <span className="field-hint">Discord erlaubt ~2 Umbenennungen pro Stunde</span></label>
|
||||
<input type="text" maxLength={32} value={botName} onChange={(e) => setBotName(e.target.value)} />
|
||||
</div>
|
||||
@@ -1047,7 +1069,7 @@ export default function Settings({ me }) {
|
||||
<label>Beschreibung <span className="field-hint">„Über mich" im Bot-Profil (max 400 Zeichen)</span></label>
|
||||
<textarea rows={3} maxLength={400} placeholder="z. B. Community-Bot für EcoGame — Devlogs, Level, Tickets & mehr. 🤖" value={botDesc} onChange={(e) => setBotDesc(e.target.value)} />
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="bot_presence_status">
|
||||
<label>Bot-Status</label>
|
||||
<select value={form.bot_presence_status} onChange={(e) => setForm({ ...form, bot_presence_status: e.target.value })}>
|
||||
<option value="online">🟢 Online</option>
|
||||
@@ -1055,7 +1077,7 @@ export default function Settings({ me }) {
|
||||
<option value="dnd">⛔ Bitte nicht stören</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="bot_status_text">
|
||||
<label>Aktivität <span className="field-hint">leer = Server-Monitor darf die Spielerzahl anzeigen</span></label>
|
||||
<div className="field-row">
|
||||
<select style={{ flex: 'none', width: '9rem' }} value={form.bot_status_type} onChange={(e) => setForm({ ...form, bot_status_type: e.target.value })}>
|
||||
@@ -1072,11 +1094,11 @@ export default function Settings({ me }) {
|
||||
|
||||
<div className="settings-section">
|
||||
<h2 className="settings-title">// Embeds</h2>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="brand_name">
|
||||
<label>Brand-Name <span className="field-hint">erscheint in allen Embed-Footern („NAME // DEVLOG")</span></label>
|
||||
<input type="text" placeholder="D4RKST3R" value={form.brand_name} onChange={(e) => setForm({ ...form, brand_name: e.target.value })} />
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="brand_color">
|
||||
<label>Embed-Farben <span className="field-hint">primär (Embeds) · sekundär (Releases, Mod-Aktionen)</span></label>
|
||||
<div className="field-row">
|
||||
<input type="color" className="color-pick" value={form.brand_color} onChange={(e) => setForm({ ...form, brand_color: e.target.value })} />
|
||||
@@ -1088,7 +1110,7 @@ export default function Settings({ me }) {
|
||||
|
||||
<div className="settings-section">
|
||||
<h2 className="settings-title">// Aus der Env verlagert</h2>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="discord_guild_id">
|
||||
<label>Guild-ID <span className="field-hint">für sofortige Slash-Commands; greift beim nächsten Neustart</span></label>
|
||||
<input type="text" placeholder="Server-ID" value={form.discord_guild_id} onChange={(e) => setForm({ ...form, discord_guild_id: e.target.value })} />
|
||||
</div>
|
||||
@@ -1108,7 +1130,7 @@ export default function Settings({ me }) {
|
||||
<>
|
||||
<div className="settings-section">
|
||||
<h2 className="settings-title">// Kanäle</h2>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="devlog_channel_id">
|
||||
<label>Devlog-Kanal <span className="field-hint">öffentlich — hier postet der Bot die Devlogs</span></label>
|
||||
<div className="field-row">
|
||||
<PickSelect
|
||||
@@ -1120,7 +1142,7 @@ export default function Settings({ me }) {
|
||||
<button className="btn" onClick={() => sendTest('devlog')}>Test</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="commit_channel_id">
|
||||
<label>Commit-Kanal <span className="field-hint">privat — hier landen die Push-Embeds</span></label>
|
||||
<div className="field-row">
|
||||
<PickSelect
|
||||
@@ -1132,7 +1154,7 @@ export default function Settings({ me }) {
|
||||
<button className="btn" onClick={() => sendTest('commit')}>Test</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="release_channel_id">
|
||||
<label>Release-Kanal <span className="field-hint">Ankündigungen bei neuen Gitea-Releases — leer = aus</span></label>
|
||||
<div className="field-row">
|
||||
<PickSelect
|
||||
@@ -1148,7 +1170,7 @@ export default function Settings({ me }) {
|
||||
|
||||
<div className="settings-section">
|
||||
<h2 className="settings-title">// Devlog</h2>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="devlog_ping_role_id">
|
||||
<label>Ping-Rolle <span className="field-hint">wird bei jedem Devlog erwähnt; 🔔-Button am Post — leer = kein Ping</span></label>
|
||||
<PickSelect
|
||||
items={roleItems}
|
||||
@@ -1157,14 +1179,14 @@ export default function Settings({ me }) {
|
||||
empty="— kein Ping —"
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="weekly_recap_enabled">
|
||||
<label className="toggle">
|
||||
<input type="checkbox" checked={form.weekly_recap_enabled} onChange={(e) => setForm({ ...form, weekly_recap_enabled: e.target.checked })} />
|
||||
<span>Wochen-Rückblick (sonntags 20:00)</span>
|
||||
<span className="field-hint">Commits pro Tag, Devlogs & Top-Projekte</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="devlog_threads_enabled">
|
||||
<label className="toggle">
|
||||
<input type="checkbox" checked={form.devlog_threads_enabled} onChange={(e) => setForm({ ...form, devlog_threads_enabled: e.target.checked })} />
|
||||
<span>Diskussions-Thread unter jedem Devlog</span>
|
||||
@@ -1178,18 +1200,18 @@ export default function Settings({ me }) {
|
||||
|
||||
<div className="settings-section">
|
||||
<h2 className="settings-title">// Commit-Feed</h2>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="commit_feed_enabled">
|
||||
<label className="toggle">
|
||||
<input type="checkbox" checked={form.commit_feed_enabled} onChange={(e) => setForm({ ...form, commit_feed_enabled: e.target.checked })} />
|
||||
<span>Push-Embeds posten</span>
|
||||
<span className="field-hint">aus = weiter archivieren, nur nicht posten</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="commit_branch_filter">
|
||||
<label>Branch-Filter <span className="field-hint">kommagetrennt, leer = alle</span></label>
|
||||
<input type="text" placeholder="z. B. main, release" value={form.commit_branch_filter} onChange={(e) => setForm({ ...form, commit_branch_filter: e.target.value })} />
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="ignored_repos">
|
||||
<label>Ignorierte Repos <span className="field-hint">kommagetrennt, volle Namen</span></label>
|
||||
<input type="text" placeholder="z. B. D4rkst3r/d4rkbot" value={form.ignored_repos} onChange={(e) => setForm({ ...form, ignored_repos: e.target.value })} />
|
||||
</div>
|
||||
@@ -1201,7 +1223,7 @@ export default function Settings({ me }) {
|
||||
<>
|
||||
<div className="settings-section">
|
||||
<h2 className="settings-title">// Geburtstage</h2>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="birthday_channel_id">
|
||||
<label>Geburtstags-Kanal <span className="field-hint">Member tragen sich mit /geburtstag ein; Gratulation morgens ab 09:00 — leer = aus</span></label>
|
||||
<PickSelect
|
||||
items={channelItems}
|
||||
@@ -1210,7 +1232,7 @@ export default function Settings({ me }) {
|
||||
empty="— deaktiviert —"
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="birthday_role_id">
|
||||
<label>Geburtstags-Rolle <span className="field-hint">gibt's für den Tag, wird am nächsten Morgen wieder entfernt — leer = keine</span></label>
|
||||
<PickSelect
|
||||
items={roleItems}
|
||||
@@ -1223,7 +1245,7 @@ export default function Settings({ me }) {
|
||||
|
||||
<div className="settings-section">
|
||||
<h2 className="settings-title">// Playtester</h2>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="playtester_role_id">
|
||||
<label>Playtester-Rolle <span className="field-hint">Bewerbungs-Post via /playtester-setup; leer = aus</span></label>
|
||||
<PickSelect
|
||||
items={roleItems}
|
||||
@@ -1279,7 +1301,7 @@ export default function Settings({ me }) {
|
||||
|
||||
<div className="settings-section">
|
||||
<h2 className="settings-title">// Starboard & Galerie</h2>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="starboard_channel_id">
|
||||
<label>Starboard-Kanal <span className="field-hint">Nachrichten mit genug ⭐ landen hier</span></label>
|
||||
<div className="field-row">
|
||||
<PickSelect
|
||||
@@ -1291,7 +1313,7 @@ export default function Settings({ me }) {
|
||||
<input type="text" style={{ width: '6rem', flex: 'none' }} title="Schwellwert (Anzahl ⭐)" value={form.starboard_threshold} onChange={(e) => setForm({ ...form, starboard_threshold: e.target.value })} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="screenshot_channel_id">
|
||||
<label>Screenshot-Kanal <span className="field-hint">Bilder → öffentliche Galerie; Historie: /galerie-backfill</span></label>
|
||||
<PickSelect
|
||||
items={channelItems}
|
||||
@@ -1300,7 +1322,7 @@ export default function Settings({ me }) {
|
||||
empty="— deaktiviert —"
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="voting_channel_id">
|
||||
<label>Voting-Kanal <span className="field-hint">/wunsch postet hier; Rangliste auf /roadmap</span></label>
|
||||
<PickSelect
|
||||
items={channelItems}
|
||||
@@ -1313,21 +1335,21 @@ export default function Settings({ me }) {
|
||||
|
||||
<div className="settings-section">
|
||||
<h2 className="settings-title">// Level-System</h2>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="levels_enabled">
|
||||
<label className="toggle">
|
||||
<input type="checkbox" checked={form.levels_enabled} onChange={(e) => setForm({ ...form, levels_enabled: e.target.checked })} />
|
||||
<span>XP fürs Schreiben vergeben</span>
|
||||
<span className="field-hint">15–25 XP pro Nachricht, 60s Cooldown; /rank + Bestenliste auf /level</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="levels_announce">
|
||||
<label className="toggle">
|
||||
<input type="checkbox" checked={form.levels_announce} onChange={(e) => setForm({ ...form, levels_announce: e.target.checked })} />
|
||||
<span>Level-Up im Kanal feiern</span>
|
||||
<span className="field-hint">„🎉 Name ist jetzt Level 5!"</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="level_rewards">
|
||||
<label>Rollen-Belohnungen <span className="field-hint">Format: Level=Rollen-ID, kommagetrennt — z. B. 5=1234, 10=5678</span></label>
|
||||
<input type="text" placeholder="5=RollenID, 10=RollenID" value={form.level_rewards} onChange={(e) => setForm({ ...form, level_rewards: e.target.value })} />
|
||||
</div>
|
||||
@@ -1339,7 +1361,7 @@ export default function Settings({ me }) {
|
||||
<>
|
||||
<div className="settings-section">
|
||||
<h2 className="settings-title">// Rollen bei Join</h2>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="autorole_id">
|
||||
<label>Auto-Rolle <span className="field-hint">bekommt jeder neue Member automatisch — leer = aus</span></label>
|
||||
<PickSelect
|
||||
items={roleItems}
|
||||
@@ -1348,7 +1370,7 @@ export default function Settings({ me }) {
|
||||
empty="— deaktiviert —"
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="sticky_roles_enabled">
|
||||
<label className="toggle">
|
||||
<input type="checkbox" checked={form.sticky_roles_enabled} onChange={(e) => setForm({ ...form, sticky_roles_enabled: e.target.checked })} />
|
||||
<span>Sticky-Roles</span>
|
||||
@@ -1461,7 +1483,7 @@ export default function Settings({ me }) {
|
||||
<>
|
||||
<div className="settings-section">
|
||||
<h2 className="settings-title">// Moderation & Kontakt</h2>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="modmail_channel_id">
|
||||
<label>Modmail-Kanal <span className="field-hint">PRIVATER Staff-Kanal — DMs an den Bot landen hier als Threads</span></label>
|
||||
<PickSelect
|
||||
items={channelItems}
|
||||
@@ -1470,7 +1492,7 @@ export default function Settings({ me }) {
|
||||
empty="— deaktiviert —"
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="ticket_channel_id">
|
||||
<label>Ticket-Kanal <span className="field-hint">private Ticket-Threads; Post via /ticket-setup</span></label>
|
||||
<PickSelect
|
||||
items={channelItems}
|
||||
@@ -1479,7 +1501,7 @@ export default function Settings({ me }) {
|
||||
empty="— deaktiviert —"
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="welcome_channel_id">
|
||||
<label>Willkommens-Kanal <span className="field-hint">Begrüßungs-Embed für neue Member</span></label>
|
||||
<PickSelect
|
||||
items={channelItems}
|
||||
@@ -1488,7 +1510,7 @@ export default function Settings({ me }) {
|
||||
empty="— deaktiviert —"
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="modlog_channel_id">
|
||||
<label>Mod-Log-Kanal <span className="field-hint">PRIVAT — gelöschte/bearbeitete Nachrichten</span></label>
|
||||
<PickSelect
|
||||
items={channelItems}
|
||||
@@ -1506,7 +1528,7 @@ export default function Settings({ me }) {
|
||||
zeigt sofort, wie es aussieht — gespeichert wird erst mit dem Knopf unten.
|
||||
</p>
|
||||
|
||||
<div className="field">
|
||||
<div className="field" data-setting="welcome_card_enabled">
|
||||
<label className="toggle">
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -1541,7 +1563,7 @@ export default function Settings({ me }) {
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="field">
|
||||
<div className="field" data-setting="welcome_card_kicker">
|
||||
<label>Kleine Zeile <span className="field-hint">über dem Namen — leer = weglassen</span></label>
|
||||
<input
|
||||
type="text" maxLength={24} placeholder="WILLKOMMEN"
|
||||
@@ -1549,7 +1571,7 @@ export default function Settings({ me }) {
|
||||
onChange={(e) => setForm({ ...form, welcome_card_kicker: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="welcome_card_sub">
|
||||
<label>Zeile darunter <span className="field-hint">{'{count}'} wird zur Mitglieds-Nummer — leer = weglassen</span></label>
|
||||
<input
|
||||
type="text" maxLength={60} placeholder="Member #{count}"
|
||||
@@ -1557,7 +1579,7 @@ export default function Settings({ me }) {
|
||||
onChange={(e) => setForm({ ...form, welcome_card_sub: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="welcome_card_image">
|
||||
<label>Hintergrundbild <span className="field-hint">https-Adresse, 900×300 passt genau — leer = nur Farbe mit Raster</span></label>
|
||||
<input
|
||||
type="text" placeholder="https://cdn.d4rkst3r.de/welcome.png"
|
||||
@@ -1565,7 +1587,7 @@ export default function Settings({ me }) {
|
||||
onChange={(e) => setForm({ ...form, welcome_card_image: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="welcome_card_watermark">
|
||||
<label className="toggle">
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -1783,11 +1805,11 @@ export default function Settings({ me }) {
|
||||
<>
|
||||
<div className="settings-section">
|
||||
<h2 className="settings-title">// Allgemein</h2>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="public_url">
|
||||
<label>Öffentliche URL <span className="field-hint">Basis für Buttons, RSS, OAuth-Redirect</span></label>
|
||||
<input type="text" placeholder="https://bot.d4rkst3r.de" value={form.public_url} onChange={(e) => setForm({ ...form, public_url: e.target.value })} />
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="gitea_url">
|
||||
<label>Gitea-URL</label>
|
||||
<input type="text" placeholder="https://git.d4rkst3r.de" value={form.gitea_url} onChange={(e) => setForm({ ...form, gitea_url: e.target.value })} />
|
||||
</div>
|
||||
@@ -1801,23 +1823,23 @@ export default function Settings({ me }) {
|
||||
Sind sie gesetzt, leitet der Server falsch aufgerufene Seiten automatisch
|
||||
auf die richtige Adresse um.
|
||||
</p>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="hub_url">
|
||||
<label>Community-Hub <span className="field-hint">Devlogs, Roadmap, Server, Profile — Ziel für Devlog-Buttons, RSS und Link-Vorschauen</span></label>
|
||||
<input type="text" placeholder="https://hub.d4rkst3r.de" value={form.hub_url ?? ''} onChange={(e) => setForm({ ...form, hub_url: e.target.value })} />
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="bot_url">
|
||||
<label>Bot-Produktseite <span className="field-hint">Funktionen, Befehle und dieses Dashboard</span></label>
|
||||
<input type="text" placeholder="https://bot.d4rkst3r.de" value={form.bot_url ?? ''} onChange={(e) => setForm({ ...form, bot_url: e.target.value })} />
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="cookie_domain">
|
||||
<label>Login-Domain <span className="field-hint">z. B. <code>.d4rkst3r.de</code> — dann gilt die Anmeldung auf beiden Seiten; leer = getrennte Logins</span></label>
|
||||
<input type="text" placeholder=".d4rkst3r.de" value={form.cookie_domain ?? ''} onChange={(e) => setForm({ ...form, cookie_domain: e.target.value })} />
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="bug_report_repo">
|
||||
<label>Bug-Repo <span className="field-hint">Ziel für /bug-Issues{data.status.giteaTokenConfigured ? '' : ' — ⚠️ kein GITEA_API_TOKEN, /bug ist aus'}</span></label>
|
||||
<input type="text" placeholder="D4rkst3r/EcoGame" value={form.bug_report_repo} onChange={(e) => setForm({ ...form, bug_report_repo: e.target.value })} />
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="roadmap_repo">
|
||||
<label>Roadmap-Repo <span className="field-hint">Milestones → öffentliche /roadmap</span></label>
|
||||
<input type="text" placeholder="D4rkst3r/EcoGame" value={form.roadmap_repo} onChange={(e) => setForm({ ...form, roadmap_repo: e.target.value })} />
|
||||
</div>
|
||||
@@ -1867,15 +1889,15 @@ export default function Settings({ me }) {
|
||||
ist eine ladungsfähige Anschrift nötig (kein Postfach) — nur der Owner sieht
|
||||
und ändert diese Felder.
|
||||
</p>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="legal_name">
|
||||
<label>Name / Verantwortlicher</label>
|
||||
<input type="text" placeholder="Vorname Nachname" value={form.legal_name ?? ''} onChange={(e) => setForm({ ...form, legal_name: e.target.value })} />
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="legal_address">
|
||||
<label>Anschrift <span className="field-hint">mehrzeilig: Straße, PLZ Ort, Land</span></label>
|
||||
<textarea rows={3} placeholder={'Musterstraße 1\n12345 Musterstadt\nDeutschland'} value={form.legal_address ?? ''} onChange={(e) => setForm({ ...form, legal_address: e.target.value })} />
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="legal_email">
|
||||
<label>Kontakt-E-Mail <span className="field-hint">Pflichtangabe — muss erreichbar sein</span></label>
|
||||
<input type="email" placeholder="kontakt@d4rkst3r.de" value={form.legal_email ?? ''} onChange={(e) => setForm({ ...form, legal_email: e.target.value })} />
|
||||
</div>
|
||||
@@ -1887,14 +1909,14 @@ export default function Settings({ me }) {
|
||||
|
||||
<div className="settings-section">
|
||||
<h2 className="settings-title">// Web-Login (Member)</h2>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="member_gate_enabled">
|
||||
<label className="toggle">
|
||||
<input type="checkbox" checked={form.member_gate_enabled} onChange={(e) => setForm({ ...form, member_gate_enabled: e.target.checked })} />
|
||||
<span>Login nur für Discord-Member</span>
|
||||
<span className="field-hint">Nicht-Member werden nach dem Discord-Login abgewiesen (braucht gesetzte Guild-ID)</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="discord_invite_url">
|
||||
<label>Discord-Invite-Link <span className="field-hint">wird Abgewiesenen als „Zum Discord"-Button angezeigt</span></label>
|
||||
<input type="text" placeholder="https://discord.gg/…" value={form.discord_invite_url} onChange={(e) => setForm({ ...form, discord_invite_url: e.target.value })} />
|
||||
</div>
|
||||
@@ -1902,7 +1924,7 @@ export default function Settings({ me }) {
|
||||
|
||||
<div className="settings-section">
|
||||
<h2 className="settings-title">// Events, Social & Voice</h2>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="events_announce_channel_id">
|
||||
<label>Event-Ankündigungs-Kanal <span className="field-hint">neue Discord-Events werden hier angekündigt + Events-Seite</span></label>
|
||||
<PickSelect
|
||||
items={channelItems}
|
||||
@@ -1911,7 +1933,7 @@ export default function Settings({ me }) {
|
||||
empty="— deaktiviert —"
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="social_announce_channel_id">
|
||||
<label>Social-Kanal <span className="field-hint">🔴 Twitch-Live + ▶️ neue YouTube-Videos</span></label>
|
||||
<PickSelect
|
||||
items={channelItems}
|
||||
@@ -1920,7 +1942,7 @@ export default function Settings({ me }) {
|
||||
empty="— deaktiviert —"
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="youtube_channel_id">
|
||||
<div className="field-row">
|
||||
<input type="text" placeholder="YouTube-Kanal-ID (UC…)" value={form.youtube_channel_id} onChange={(e) => setForm({ ...form, youtube_channel_id: e.target.value })} />
|
||||
<input type="text" placeholder="Twitch-Login (z. B. d4rkst3r)" value={form.twitch_channel} onChange={(e) => setForm({ ...form, twitch_channel: e.target.value })} />
|
||||
@@ -1933,7 +1955,7 @@ export default function Settings({ me }) {
|
||||
<input type="password" placeholder="Client-Secret" value={twitchCreds.secret} onChange={(e) => setTwitchCreds({ ...twitchCreds, secret: e.target.value })} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="tempvoice_channel_id">
|
||||
<label>Temp-Voice-Hub <span className="field-hint">„Join to Create": Beitritt erzeugt eigenen Voice-Kanal (Bot braucht Kanäle verwalten + Member verschieben)</span></label>
|
||||
<PickSelect
|
||||
items={channelItems}
|
||||
@@ -1946,14 +1968,14 @@ export default function Settings({ me }) {
|
||||
|
||||
<div className="settings-section">
|
||||
<h2 className="settings-title">// Backups & Watchdog</h2>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="backup_enabled">
|
||||
<label className="toggle">
|
||||
<input type="checkbox" checked={form.backup_enabled} onChange={(e) => setForm({ ...form, backup_enabled: e.target.checked })} />
|
||||
<span>Nächtliches DB-Backup (03:00)</span>
|
||||
<span className="field-hint">gzip, 14 Tage Rotation</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="backup_channel_id">
|
||||
<label>Backup-Upload-Kanal <span className="field-hint">Offsite-Kopie in PRIVATEN Kanal — leer = nur lokal</span></label>
|
||||
<div className="field-row">
|
||||
<PickSelect
|
||||
@@ -1965,7 +1987,7 @@ export default function Settings({ me }) {
|
||||
<button className="btn" onClick={() => sendTest('backup')}>Backup jetzt</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="repo_backup_enabled">
|
||||
<label className="toggle">
|
||||
<input type="checkbox" checked={form.repo_backup_enabled} onChange={(e) => setForm({ ...form, repo_backup_enabled: e.target.checked })} />
|
||||
<span>Repo-Backups (04:00)</span>
|
||||
@@ -1975,7 +1997,7 @@ export default function Settings({ me }) {
|
||||
<button className="btn" onClick={() => sendTest('repobackup')}>Repos jetzt sichern</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="watchdog_urls">
|
||||
<label>Watchdog-URLs <span className="field-hint">kommagetrennt; DM bei Ausfall — leer = aus</span></label>
|
||||
<input type="text" placeholder="https://git.d4rkst3r.de, https://bot.d4rkst3r.de/health" value={form.watchdog_urls} onChange={(e) => setForm({ ...form, watchdog_urls: e.target.value })} />
|
||||
</div>
|
||||
@@ -2089,7 +2111,7 @@ export default function Settings({ me }) {
|
||||
Jeder Server bekommt sein eigenes Live-Status-Embed im Status-Kanal (🟢/🔴,
|
||||
Spieler-Balken, Map, Adresse) — aktualisiert alle 2 Minuten, DiscordGSM-Style.
|
||||
</p>
|
||||
<div className="field">
|
||||
<div className="field" data-setting="status_channel_id">
|
||||
<div className="field-row">
|
||||
<div style={{ flex: 1 }}>
|
||||
<label>Status-Kanal</label>
|
||||
@@ -2666,9 +2688,11 @@ export default function Settings({ me }) {
|
||||
aria-label="Bereich suchen"
|
||||
onChange={(e) => setTabSearch(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' && foundTabs[0]) {
|
||||
setTab(foundTabs[0].id);
|
||||
setTabSearch('');
|
||||
// Einstellungen zuerst: wer „schwellwert" tippt,
|
||||
// will das Feld, nicht den Bereich
|
||||
if (e.key === 'Enter') {
|
||||
if (foundSettings[0]) jumpToSetting(foundSettings[0]);
|
||||
else if (foundTabs[0]) { setTab(foundTabs[0].id); setTabSearch(''); }
|
||||
}
|
||||
if (e.key === 'Escape') setTabSearch('');
|
||||
}}
|
||||
@@ -2680,7 +2704,25 @@ export default function Settings({ me }) {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{foundTabs.length === 0 && (
|
||||
{foundSettings.length > 0 && (
|
||||
<div className="settings-nav-group">
|
||||
<span className="settings-nav-label">Einstellungen</span>
|
||||
{foundSettings.map((s) => (
|
||||
<button
|
||||
key={s.id}
|
||||
className="settings-hit"
|
||||
onClick={() => jumpToSetting(s)}
|
||||
>
|
||||
<span className="settings-hit-label">{s.label}</span>
|
||||
<span className="settings-hit-tab">
|
||||
{TABS.find((t) => t.id === s.tab)?.label ?? s.tab}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{foundTabs.length === 0 && foundSettings.length === 0 && (
|
||||
<p className="settings-nav-empty">Nichts gefunden.</p>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user