Connect-Links im Discord anklickbar, und Mods bekommen einen eigenen Knopf
Im Kanal stand woertlich das da:
[https://cfx.re/join/4odv68](https://cfx.re/join/4odv68)
Der Link war sein eigener Linktext. Discord macht aus der nackten URL zwischen
den eckigen Klammern zuerst selbst einen Link und kommt mit der
Masked-Link-Schreibweise dann nicht mehr durch — heraus kommen die Klammern im
Klartext. Der Kommentar an der Stelle behauptete ausserdem, so seien steam://
und Konsorten klickbar. Waren sie nie.
Jetzt sind es Link-Knoepfe unter dem Embed. Die Frage nach der Schreibweise
stellt sich damit gar nicht mehr, und ein Knopf sieht auch nach einem aus.
Discord nimmt dort nur http(s) an; fivem:// und steam:// stehen deshalb weiter
im Embed, aber als Codeblock zum Kopieren statt als toter Link.
Dazu ein eigenes Feld fuer den Mod-Download. Vorher landete der beim
Connect-Link, weil es kein anderes gab — und dann fuehrte "Verbinden" zu einer
ZIP-Datei. Farming Simulator liefert die Modliste ohnehin mit, also steht die
Anzahl jetzt auch im Embed und als Chip auf der Seite.
Beim Testen aufgefallen: eine neue Spalte hat jeden Aufrufer umgebracht, der
sie noch nicht kannte — better-sqlite3 wirft bei einem fehlenden benannten
Parameter. createGameserver und updateGameserver fuellen die Felder jetzt
einmal auf, statt das an jeder Aufrufstelle nachzuziehen.
Geprueft: was in einen Knopf darf und was nicht (https, http, fivem://,
steam://, leer, null, Leerzeichen, Satz mit Link drin), beide Knoepfe in einer
Reihe, nur einer, gar keiner — eine leere Reihe weist Discord ab —, https
landet im Knopf und nicht doppelt auch im Embed, fivem:// umgekehrt, Mods bei
47/0/null, und der Aufruf ohne die neue Spalte. Seite im laufenden Frontend
angesehen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -190,6 +190,8 @@ export default {
|
||||
'server.agoDays': 'vor %s Tagen',
|
||||
'server.neverOnline': 'Seit Beginn der Messung nicht erreichbar.',
|
||||
'server.version': 'Version',
|
||||
'server.mods': '%s Mods',
|
||||
'server.modsDownload': 'Mods laden',
|
||||
|
||||
// ── Galerie ───────────────────────────────────────
|
||||
'gallery.tag': '// Community',
|
||||
|
||||
@@ -189,6 +189,8 @@ export default {
|
||||
'server.agoDays': '%s days ago',
|
||||
'server.neverOnline': 'Not reachable since measuring began.',
|
||||
'server.version': 'Version',
|
||||
'server.mods': '%s mods',
|
||||
'server.modsDownload': 'Get mods',
|
||||
|
||||
// ── Gallery ───────────────────────────────────────
|
||||
'gallery.tag': '// Community',
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { apiGet } from '../api.js';
|
||||
import { SkeletonCards } from '../components/Skeleton.jsx';
|
||||
import { IconGame, IconMapPin, IconClock, IconCheck, IconTeam, IconPackage, IconZap } from '../icons.jsx';
|
||||
import { IconGame, IconMapPin, IconClock, IconCheck, IconTeam, IconPackage, IconZap, IconDownload, IconArchive } from '../icons.jsx';
|
||||
import { useT, useFmt } from '../i18n.jsx';
|
||||
import UptimeTrack from '../components/UptimeTrack.jsx';
|
||||
|
||||
@@ -130,6 +130,9 @@ function ServerCard({ s }) {
|
||||
<div className="srv-chips">
|
||||
{s.map && <span className="srv-chip"><IconMapPin size={12} /> {s.map}</span>}
|
||||
{s.version && <span className="srv-chip"><IconPackage size={12} /> {s.version}</span>}
|
||||
{/* Eigenes Zeichen — mit dem Paket-Icon der Version daneben
|
||||
sähen die beiden Chips wie zusammengehörig aus. */}
|
||||
{s.mods > 0 && <span className="srv-chip"><IconArchive size={12} /> {t('server.mods', s.mods)}</span>}
|
||||
{s.ping != null && <span className="srv-chip"><IconZap size={12} /> {s.ping} ms</span>}
|
||||
</div>
|
||||
)}
|
||||
@@ -177,13 +180,18 @@ function ServerCard({ s }) {
|
||||
|
||||
<UptimeTrack buckets={s.uptime} />
|
||||
|
||||
{(s.connect_url || s.address) && (
|
||||
{(s.connect_url || s.mods_url || s.address) && (
|
||||
<div className="srv-connect">
|
||||
{s.connect_url && (
|
||||
<a className="btn btn-save with-icon" href={s.connect_url}>
|
||||
<IconGame size={15} /> {t('server.connect')}
|
||||
</a>
|
||||
)}
|
||||
{s.mods_url && (
|
||||
<a className="btn btn-ghost with-icon" href={s.mods_url} rel="noreferrer">
|
||||
<IconDownload size={15} /> {t('server.modsDownload')}
|
||||
</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}
|
||||
|
||||
@@ -396,7 +396,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: '' };
|
||||
const emptyServer = { id: null, name: '', type: 'fivem', query_url: '', address: '', host: '', port: '', connect_url: '', image_url: '', token: '', mods_url: '' };
|
||||
const [servers, setServers] = useState([]);
|
||||
const [games, setGames] = useState([]);
|
||||
const [serverDraft, setServerDraft] = useState(emptyServer);
|
||||
@@ -3343,7 +3343,7 @@ export default function Settings({ me }) {
|
||||
<button className="btn btn-mini" onClick={() => setServerDraft({
|
||||
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 ?? '',
|
||||
image_url: s.image_url ?? '', token: s.token ?? '', mods_url: s.mods_url ?? '',
|
||||
})}>Bearbeiten</button>
|
||||
<button className="card-delete" title="Server + Embed entfernen" onClick={() => removeServer(s.id)}>✕</button>
|
||||
</div>
|
||||
@@ -3396,11 +3396,13 @@ export default function Settings({ me }) {
|
||||
</div>
|
||||
)}
|
||||
<div className="field">
|
||||
<label>Connect-Link + Anzeige-Adresse <span className="field-hint">Link ist im Embed klickbar (auch steam:// / fivem://); Adresse als Copy-Paste-Block</span></label>
|
||||
<label>Connect-Link + Anzeige-Adresse <span className="field-hint">https wird zum Knopf; steam:// / fivem:// stehen als Codeblock zum Kopieren. Adresse ebenfalls als Copy-Paste-Block</span></label>
|
||||
<div className="field-row">
|
||||
<input type="text" placeholder="fivem://connect/play.d4rkst3r.de" value={serverDraft.connect_url} onChange={(e) => setServerDraft({ ...serverDraft, connect_url: e.target.value })} />
|
||||
<input type="text" placeholder="https://cfx.re/join/abc123" value={serverDraft.connect_url} onChange={(e) => setServerDraft({ ...serverDraft, connect_url: e.target.value })} />
|
||||
<input type="text" placeholder="connect play.d4rkst3r.de" value={serverDraft.address} onChange={(e) => setServerDraft({ ...serverDraft, address: e.target.value })} />
|
||||
</div>
|
||||
<label style={{ marginTop: '.6rem' }}>Mod-Download <span className="field-hint">eigener Knopf neben „Verbinden" — bei LS zum Beispiel <code>http://host:port/all_mods_download?onlyActive=true</code>. Nur http(s), sonst nimmt Discord den Knopf nicht.</span></label>
|
||||
<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 })} />
|
||||
<div className="field-row" style={{ marginTop: '.6rem' }}>
|
||||
|
||||
Reference in New Issue
Block a user