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
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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user