fixed tag rendering

This commit is contained in:
Mark Gregory
2026-06-17 10:57:02 +01:00
parent febdb7da7a
commit 12736df164
3 changed files with 126 additions and 23 deletions
@@ -133,6 +133,36 @@
</div>
@endif
@php
$cannedResponseBodies = $cannedResponses->mapWithKeys(function ($response) use ($ticket, $currentUser) {
$body = (string) $response->body;
$body = strtr($body, [
'{{ ticket.reference }}' => e((string) ($ticket->reference ?? '')),
'{{ ticket.subject }}' => e((string) ($ticket->subject ?? '')),
'{{ ticket.status }}' => e((string) ($ticket->status?->name ?? '')),
'{{ ticket.priority }}' => e((string) ($ticket->priority?->name ?? '')),
'{{ ticket.category }}' => e((string) ($ticket->category?->name ?? '')),
'{{ ticket.project }}' => e((string) ($ticket->project?->title ?? '')),
'{{ ticket.team }}' => e((string) ($ticket->team?->name ?? '')),
'{{ requester.name }}' => e((string) ($ticket->owner?->name ?? $ticket->user?->name ?? '')),
'{{ agent.name }}' => e((string) ($ticket->agent?->name ?? $currentUser?->name ?? '')),
'{{ actor.name }}' => e((string) ($currentUser?->name ?? '')),
'{{ ticket.url }}' => e(route('helpdesk.tickets.show', ['ticket' => $ticket->getKey()])),
]);
$body = html_entity_decode($body, ENT_QUOTES | ENT_HTML5, 'UTF-8');
$body = preg_replace('/<\s*(script|style)[^>]*>.*?<\s*\/\s*\1\s*>/is', '', $body) ?: '';
$body = preg_replace('/\s+on[a-z]+\s*=\s*("[^"]*"|\'[^\']*\'|[^\s>]+)/i', '', $body) ?: '';
$body = preg_replace('/javascript\s*:/i', '', $body) ?: '';
$body = strip_tags($body, '<p><br><strong><b><em><i><u><ul><ol><li><blockquote><pre><code><a>');
return [(string) $response->id => trim($body)];
})->all();
@endphp
<script type="application/json" id="helpdesk-canned-response-bodies">@json($cannedResponseBodies)</script>
@if ($cannedResponses->isNotEmpty())
<div class="mb-4 grid gap-3 md:grid-cols-[1fr_auto]">
<div>
@@ -140,7 +170,7 @@
<select id="ticket-canned-response" name="canned_response_id" class="hd-select block w-full rounded-xl border border-zinc-300 bg-white p-2.5 text-sm font-semibold text-zinc-900 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:border-zinc-700 dark:bg-zinc-950 dark:text-zinc-100" data-canned-response-select>
<option value="">Choose a saved response...</option>
@foreach ($cannedResponses as $response)
<option value="{{ $response->id }}" data-body="{{ e($response->body) }}">{{ $response->name }}</option>
<option value="{{ $response->id }}">{{ $response->name }}</option>
@endforeach
</select>
</div>
@@ -212,12 +242,11 @@
const editor = form?.querySelector('[data-helpdesk-rich-editor]');
const textarea = form?.querySelector('textarea[name="commentBody"]');
const selected = select?.selectedOptions?.[0];
const body = normaliseCannedHtml(selected?.dataset?.body || '');
const cannedResponses = getCannedResponseBodies();
const body = normaliseCannedHtml(cannedResponses[String(selected?.value || '')] || selected?.dataset?.body || '');
if (editor && textarea && body.trim() !== '') {
editor.innerHTML = editor.innerHTML.trim() === ''
? body
: editor.innerHTML.replace(/\s+$/, '') + '<br><br>' + body;
insertHtmlAtEnd(editor, body);
syncRichEditor(editor);
editor.focus();
}
@@ -343,6 +372,33 @@
collapseButton.classList.toggle('inline-flex', hasExpandedComments && ! hasExpandableComments);
}
function getCannedResponseBodies() {
const source = document.getElementById('helpdesk-canned-response-bodies');
if (! source) {
return {};
}
try {
return JSON.parse(source.textContent || '{}') || {};
} catch (error) {
console.error('Unable to read canned response bodies.', error);
return {};
}
}
function insertHtmlAtEnd(editor, html) {
const current = editor.innerHTML.trim();
const cleaned = html.trim();
if (current === '' || current === '<br>') {
editor.innerHTML = cleaned;
return;
}
editor.innerHTML = current + '<p><br></p>' + cleaned;
}
function decodeHtmlEntities(value) {
const textarea = document.createElement('textarea');
textarea.innerHTML = value;
@@ -350,9 +406,13 @@
}
function normaliseCannedHtml(value) {
let decoded = decodeHtmlEntities(value);
let decoded = decodeHtmlEntities(value || '');
for (let i = 0; i < 5; i += 1) {
if (! decoded.includes('&lt;') && ! decoded.includes('&gt;') && ! decoded.includes('&amp;lt;')) {
break;
}
while (decoded.includes('&lt;') || decoded.includes('&gt;') || decoded.includes('&amp;lt;')) {
const next = decodeHtmlEntities(decoded);
if (next === decoded) {
@@ -362,7 +422,22 @@
decoded = next;
}
return decoded;
decoded = decoded.trim();
if (decoded === '') {
return '';
}
if (/<[a-z][\s\S]*>/i.test(decoded)) {
return decoded;
}
return decoded
.split(/\n{2,}/)
.map((paragraph) => paragraph.trim())
.filter((paragraph) => paragraph !== '')
.map((paragraph) => '<p>' + escapeHtml(paragraph).replace(/\n/g, '<br>') + '</p>')
.join('');
}
function syncRichEditor(editor) {