Freie Links je Server statt fester Felder
connect_url und mods_url waren zwei Spalten fuer zwei Zwecke — und beim naechsten Zweck waere eine dritte dazugekommen. Jetzt gibt es eine Liste: Beschriftung plus Adresse, so viele wie noetig. Regeln, TeamSpeak, Shop, Bewerbung. Als JSON-Spalte, nicht als eigene Tabelle: es haengt an genau einem Server, wird immer komplett gelesen und immer komplett geschrieben. Gelesen wird es als Liste, geschrieben darf es als Liste hereinkommen — better-sqlite3 kann kein Array binden und wuerde sonst werfen. Kaputtes JSON ergibt eine leere Liste statt eines Absturzes im Monitor. Discord nimmt fuenf Knoepfe je Reihe; ab jetzt werden sie umgebrochen, bei zwei Reihen ist Schluss. Die Grenze steht im Code und nicht im Fehler beim Senden, den niemand sieht. Zu lange Beschriftungen werden auf 80 Zeichen gekuerzt, halbe Zeilen fliegen raus statt als leerer Knopf zu erscheinen. Was nicht http(s) ist — ts3server://, steam:// —, wird wie bisher ein Codeblock im Embed zum Kopieren. Dabei umbenannt: das Embed-Feld fuer einen nicht klickbaren Connect-Link hiess "Connect", der Knopf daneben "Verbinden". Jetzt heisst beides gleich. Geprueft: Umbruch bei zwoelf Links, Reihenfolge mit Connect und Mods vorn, halbe Zeilen, ueberlange Beschriftung, nicht klickbare Schemata, fehlendes und unbrauchbares links-Feld, dazu Speichern, Aendern und kaputtes JSON in der Datenbank. Panel und /server-Seite im laufenden Frontend angesehen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -180,7 +180,7 @@ function ServerCard({ s }) {
|
||||
|
||||
<UptimeTrack buckets={s.uptime} />
|
||||
|
||||
{(s.connect_url || s.mods_url || s.address) && (
|
||||
{(s.connect_url || s.mods_url || s.links?.length > 0 || s.address) && (
|
||||
<div className="srv-connect">
|
||||
{s.connect_url && (
|
||||
<a className="btn btn-save with-icon" href={s.connect_url}>
|
||||
@@ -192,6 +192,13 @@ function ServerCard({ s }) {
|
||||
<IconDownload size={15} /> {t('server.modsDownload')}
|
||||
</a>
|
||||
)}
|
||||
{/* Frei eingetragene Links. `noreferrer` weil sie überall
|
||||
hinführen können — das ist ja der Sinn. */}
|
||||
{(s.links ?? []).map((l) => (
|
||||
<a className="btn btn-ghost" key={`${l.label}${l.url}`} href={l.url} rel="noreferrer">
|
||||
{l.label}
|
||||
</a>
|
||||
))}
|
||||
{s.address && (
|
||||
<button className="alpha-key srv-addr" onClick={copyAddress} title={t('server.copyAddress')}>
|
||||
{copied ? <span className="with-icon"><IconCheck size={14} /> {t('server.copied')}</span> : s.address}
|
||||
|
||||
@@ -406,7 +406,7 @@ export default function Settings({ me }) {
|
||||
const [triggers, setTriggers] = useState([]);
|
||||
const [triggerDraft, setTriggerDraft] = useState({ keyword: '', reply: '' });
|
||||
// Game-Server — die Spielliste kommt aus gamedig (siehe /api/gamedig-games)
|
||||
const emptyServer = { id: null, name: '', type: 'fivem', query_url: '', address: '', host: '', port: '', connect_url: '', image_url: '', token: '', mods_url: '' };
|
||||
const emptyServer = { id: null, name: '', type: 'fivem', query_url: '', address: '', host: '', port: '', connect_url: '', image_url: '', token: '', mods_url: '', links: [] };
|
||||
const [servers, setServers] = useState([]);
|
||||
const [games, setGames] = useState([]);
|
||||
const [serverDraft, setServerDraft] = useState(emptyServer);
|
||||
@@ -3354,6 +3354,7 @@ export default function Settings({ me }) {
|
||||
id: s.id, name: s.name, type: s.type, query_url: s.query_url ?? '',
|
||||
address: s.address ?? '', host: s.host ?? '', port: s.port ?? '', connect_url: s.connect_url ?? '',
|
||||
image_url: s.image_url ?? '', token: s.token ?? '', mods_url: s.mods_url ?? '',
|
||||
links: s.links ?? [],
|
||||
})}>Bearbeiten</button>
|
||||
<button className="card-delete" title="Server + Embed entfernen" onClick={() => removeServer(s.id)}>✕</button>
|
||||
</div>
|
||||
@@ -3423,6 +3424,47 @@ export default function Settings({ me }) {
|
||||
<input type="text" placeholder="http://host:8200/all_mods_download?onlyActive=true" value={serverDraft.mods_url} onChange={(e) => setServerDraft({ ...serverDraft, mods_url: e.target.value })} />
|
||||
<label style={{ marginTop: '.6rem' }}>Logo-URL <span className="field-hint">https-Bild (z. B. vom CDN) — erscheint auf der /server-Seite und als Embed-Thumbnail</span></label>
|
||||
<input type="text" placeholder="https://cdn.d4rkst3r.de/logos/eco.png" value={serverDraft.image_url} onChange={(e) => setServerDraft({ ...serverDraft, image_url: e.target.value })} />
|
||||
|
||||
<label style={{ marginTop: '.9rem' }}>
|
||||
Weitere Links <span className="field-hint">
|
||||
Regeln, TeamSpeak, Shop … je ein Knopf unter dem Embed und auf der /server-Seite.
|
||||
Mit „Verbinden" und „Mods" zusammen höchstens zehn — mehr nimmt Discord nicht.
|
||||
</span>
|
||||
</label>
|
||||
{serverDraft.links.map((l, i) => (
|
||||
<div className="field-row" key={i} style={{ marginBottom: '.4rem' }}>
|
||||
<input
|
||||
type="text" style={{ flex: 'none', width: '11rem' }} placeholder="Beschriftung, z. B. 📜 Regeln"
|
||||
value={l.label}
|
||||
onChange={(e) => setServerDraft({
|
||||
...serverDraft,
|
||||
links: serverDraft.links.map((x, j) => (j === i ? { ...x, label: e.target.value } : x)),
|
||||
})}
|
||||
/>
|
||||
<input
|
||||
type="text" placeholder="https://…"
|
||||
value={l.url}
|
||||
onChange={(e) => setServerDraft({
|
||||
...serverDraft,
|
||||
links: serverDraft.links.map((x, j) => (j === i ? { ...x, url: e.target.value } : x)),
|
||||
})}
|
||||
/>
|
||||
<button
|
||||
className="card-delete" title="Link entfernen"
|
||||
onClick={() => setServerDraft({
|
||||
...serverDraft,
|
||||
links: serverDraft.links.filter((_, j) => j !== i),
|
||||
})}
|
||||
>✕</button>
|
||||
</div>
|
||||
))}
|
||||
{serverDraft.links.length < 8 && (
|
||||
<button
|
||||
className="btn btn-ghost btn-mini"
|
||||
onClick={() => setServerDraft({ ...serverDraft, links: [...serverDraft.links, { label: '', url: '' }] })}
|
||||
>+ Link</button>
|
||||
)}
|
||||
|
||||
<div className="field-row" style={{ marginTop: '.6rem' }}>
|
||||
<button className="btn btn-save" onClick={saveServer}>{serverDraft.id ? 'Aktualisieren' : 'Hinzufügen'}</button>
|
||||
{serverDraft.id && <button className="btn btn-ghost" onClick={() => setServerDraft(emptyServer)}>Abbrechen</button>}
|
||||
|
||||
Reference in New Issue
Block a user