feat(feedback): render links in the operator reply (open in a new tab)
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 13s
CI / ui (pull_request) Successful in 47s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m6s

Linkify the operator reply on the feedback screen: http/https/ftp/mailto/tel URLs
become anchors (target=_blank rel=noopener), everything else stays escaped text.
A small whitelisted regex (no dependency) — the reply is operator-authored, so
explicit schemes suffice; dangerous schemes (javascript:/data:) are never linked,
and \b avoids matching a scheme inside a word (hotel:, email:).
This commit is contained in:
Ilia Denisov
2026-06-15 13:25:36 +02:00
parent 55ed87fb11
commit 277954c47f
7 changed files with 110 additions and 9 deletions
+7 -2
View File
@@ -6,7 +6,7 @@
import { gateway } from '../lib/gateway';
import { connection } from '../lib/connection.svelte';
import { clientChannel } from '../lib/channel';
import { attachmentError, MAX_BODY } from '../lib/feedback';
import { attachmentError, linkify, MAX_BODY } from '../lib/feedback';
import type { FeedbackState } from '../lib/model';
// The feedback screen: a message (+ optional single attachment) the player sends to
@@ -23,6 +23,8 @@
const enabled = $derived(view?.canSend ?? false);
const reason = $derived(view?.blockedReason ?? '');
const canSend = $derived(enabled && body.trim().length > 0 && !sending && connection.online);
// The operator reply, split into text + http(s) links for rendering.
const replySegments = $derived(view?.reply ? linkify(view.reply.body) : []);
onMount(() => void load());
@@ -121,7 +123,7 @@
{#if view?.reply}
<section class="reply">
<h2>{t('feedback.replyTitle')}</h2>
<p class="replybody">{view.reply.body}</p>
<p class="replybody">{#each replySegments as seg, i (i)}{#if seg.href}<a href={seg.href} target="_blank" rel="noopener noreferrer">{seg.text}</a>{:else}{seg.text}{/if}{/each}</p>
</section>
{/if}
</div>
@@ -213,4 +215,7 @@
white-space: pre-wrap;
word-break: break-word;
}
.replybody a {
color: var(--accent);
}
</style>