Allow heading tags (h1-h6) in ContentSanitizer so news editor headings render

This commit is contained in:
2026-06-04 07:52:57 +02:00
parent 0b33a1b074
commit 15870ddb1f
191 changed files with 15453 additions and 1786 deletions

View File

@@ -12,7 +12,7 @@
<div class="text-muted news-head">
Written by {{ $item->uname }} on {{ Carbon::parse($item->post_date)->format('j F Y \@ H:i') }}
</div>
{!! Str::limit(strip_tags($item->preview ?? ''), 240, '...') !!}
{{ Str::limit(strip_tags($item->preview ?? ''), 240, '...') }}
<br>
<a class="clearfix btn btn-xs btn-info" href="{{ route('forum.thread.show', ['thread' => $item->topic_id, 'slug' => Str::slug($item->topic ?? '')]) }}" title="{{ strip_tags($item->topic) }}">More</a>
</div>

View File

@@ -43,6 +43,7 @@
<div class="form-group @error('description') has-error @enderror">
<label for="description" class="control-label">Description</label>
<textarea id="description" name="description" class="form-control" rows="8">{{ old('description', $artwork->description) }}</textarea>
<p class="help-block">Basic Markdown-style formatting and emoji are allowed. Raw HTML tags are not accepted.</p>
@error('description')
<span class="help-block">{{ $message }}</span>
@enderror

View File

@@ -6,7 +6,7 @@
'thread' => ['id' => $thread->id, 'title' => $thread->title, 'slug' => $thread->slug],
'csrfToken' => csrf_token(),
'errors' => $errors->toArray(),
], JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
], JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP);
@endphp
@section('content')

View File

@@ -6,7 +6,7 @@
'csrfToken' => csrf_token(),
'errors' => $errors->toArray(),
'oldValues' => ['title' => old('title', ''), 'content' => old('content', '')],
], JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
], JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP);
@endphp
@section('content')

View File

@@ -24,7 +24,7 @@
'threads' => $threadsData,
'pagination' => $paginationData,
'isAuthenticated' => auth()->check(),
], JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
], JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP);
@endphp
@section('content')

View File

@@ -3,7 +3,7 @@
@php
$forumIndexProps = json_encode([
'categories' => $categories ?? [],
], JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
], JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP);
@endphp
@section('content')

View File

@@ -163,7 +163,7 @@
'canModerate' => Gate::allows('moderate-forum'),
'csrfToken' => csrf_token(),
'status' => session('status'),
], JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
], JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP);
@endphp
<script type="application/json" id="forum-thread-props">{!! $forumThreadProps !!}</script>
@endsection

View File

@@ -20,7 +20,11 @@
</select>
<label class="label-control">Description:</label>
<textarea name="description" class="form-control summernote_lite" style="width:100%;height:100px">{{ old('description', $artwork->description) }}</textarea>
<textarea name="description" class="form-control" rows="6" style="width:100%">{{ old('description', $artwork->description) }}</textarea>
<p class="help-block">Basic Markdown-style formatting and emoji are allowed. Raw HTML tags are not accepted.</p>
@error('description')
<div class="text-danger" style="margin-top:6px;">{{ $message }}</div>
@enderror
</div>
<div class="col-md-5">
@@ -58,18 +62,3 @@
</form>
</div>
@endsection
@push('scripts')
<script>
$(document).ready(function(){
$('.summernote_lite').summernote({
toolbar: [
['style', ['bold', 'italic', 'underline', 'clear']],
['font', ['strikethrough']],
['fontsize', ['fontsize']],
['color', ['color']],
]
});
});
</script>
@endpush

View File

@@ -2,7 +2,7 @@
<a href="{{ route('news.show', $article->slug) }}" class="block">
<div class="relative aspect-[16/9] overflow-hidden bg-black/20">
@if($article->cover_url)
<img src="{{ $article->cover_url }}" @if($article->cover_srcset) srcset="{{ $article->cover_srcset }}" sizes="(max-width: 767px) 100vw, (max-width: 1279px) 50vw, 390px" @endif alt="{{ $article->title }}" class="h-full w-full object-cover transition duration-300 group-hover:scale-[1.04]">
<img src="{{ $article->cover_mobile_url ?: $article->cover_url }}" @if($article->cover_srcset) srcset="{{ $article->cover_srcset }}" sizes="(max-width: 767px) 100vw, (max-width: 1279px) 50vw, 390px" @endif alt="{{ $article->title }}" class="h-full w-full object-cover transition duration-300 group-hover:scale-[1.04]">
@else
<div class="absolute inset-0 bg-[radial-gradient(circle_at_top_left,rgba(56,189,248,0.14),transparent_45%),linear-gradient(180deg,rgba(15,23,42,0.92),rgba(2,6,23,0.98))]"></div>
@endif
@@ -12,7 +12,7 @@
<div class="flex h-full flex-col p-5">
<div class="flex flex-wrap items-center gap-2">
<span class="inline-flex items-center rounded-full border border-white/[0.08] bg-white/[0.04] px-2.5 py-1 text-[11px] font-semibold uppercase tracking-[0.14em] text-white/70">{{ $article->type_label }}</span>
<a href="{{ route('news.type', $article->type) }}" class="inline-flex items-center rounded-full border border-white/[0.08] bg-white/[0.04] px-2.5 py-1 text-[11px] font-semibold uppercase tracking-[0.14em] text-white/70 transition hover:border-white/20 hover:text-white">{{ $article->type_label }}</a>
@if($article->category)
<a href="{{ route('news.category', $article->category->slug) }}" class="inline-flex items-center rounded-full border border-sky-400/20 bg-sky-500/10 px-2.5 py-1 text-[11px] font-semibold uppercase tracking-[0.14em] text-sky-200">{{ $article->category->name }}</a>
@endif

View File

@@ -6,7 +6,8 @@
</div>
<div class="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
@foreach($relatedEntities as $entity)
<a href="{{ $entity['url'] ?? '#' }}" class="group overflow-hidden rounded-[24px] border border-white/[0.08] bg-white/[0.03] transition hover:-translate-y-0.5 hover:border-white/[0.16]">
@php($isExternalSource = ($entity['entity_type'] ?? null) === 'source')
<a href="{{ $entity['url'] ?? '#' }}" @if($isExternalSource) target="_blank" rel="noopener noreferrer" @endif class="group overflow-hidden rounded-[24px] border border-white/[0.08] bg-white/[0.03] transition hover:-translate-y-0.5 hover:border-white/[0.16]">
<div class="relative aspect-[16/9] overflow-hidden bg-black/20">
@if(!empty($entity['image']))
<img src="{{ $entity['image'] }}" alt="{{ $entity['title'] }}" class="h-full w-full object-cover transition duration-300 group-hover:scale-[1.04]">

View File

@@ -85,7 +85,7 @@
<div class="grid lg:grid-cols-[1.25fr_0.95fr]">
<div class="relative min-h-[280px] overflow-hidden bg-black/20">
@if($featured->cover_url)
<img src="{{ $featured->cover_url }}" @if($featured->cover_srcset) srcset="{{ $featured->cover_srcset }}" sizes="(max-width: 1023px) 100vw, 768px" @endif alt="{{ $featured->title }}" class="h-full w-full object-cover transition duration-500 group-hover:scale-[1.03]">
<img src="{{ $featured->cover_desktop_url ?: $featured->cover_url }}" @if($featured->cover_srcset) srcset="{{ $featured->cover_srcset }}" sizes="(max-width: 1023px) 100vw, 768px" @endif alt="{{ $featured->title }}" class="h-full w-full object-cover transition duration-500 group-hover:scale-[1.03]">
@else
<div class="absolute inset-0 bg-[radial-gradient(circle_at_top_left,rgba(56,189,248,0.14),transparent_45%),linear-gradient(180deg,rgba(15,23,42,0.96),rgba(2,6,23,0.98))]"></div>
@endif

View File

@@ -7,7 +7,7 @@
? url($article->effective_og_image)
: url((string) config('seo.fallback_image_path', '/gfx/skinbase_back_001.webp'));
$articleCoverSizes = '(max-width: 767px) calc(100vw - 3rem), (max-width: 1279px) calc(100vw - 5rem), 768px';
$articleCoverPreloadHref = $article->cover_desktop_url ?: $article->cover_url;
$articleCoverPreloadHref = $article->cover_large_url ?: ($article->cover_desktop_url ?: $article->cover_url);
$seo = \App\Support\Seo\SeoDataBuilder::fromArray([
'title' => $article->meta_title ?: $article->title,
'description' => $article->meta_description ?: Str::limit(strip_tags((string) $article->excerpt), 160),
@@ -135,7 +135,7 @@
>
<x-slot name="actions">
<div class="flex flex-wrap items-center gap-2 text-sm text-white/60">
<span class="inline-flex items-center rounded-full border border-white/[0.08] bg-white/[0.04] px-3 py-1.5 text-white/75">{{ $article->type_label }}</span>
<a href="{{ route('news.type', $article->type) }}" class="inline-flex items-center rounded-full border border-white/[0.08] bg-white/[0.04] px-3 py-1.5 text-white/75 transition hover:border-white/20 hover:text-white">{{ $article->type_label }}</a>
@if($article->category)
<a href="{{ route('news.category', $article->category->slug) }}" class="inline-flex items-center rounded-full border border-sky-400/20 bg-sky-500/10 px-3 py-1.5 text-sky-200">{{ $article->category->name }}</a>
@endif
@@ -173,15 +173,15 @@
@if($article->cover_url)
<div class="overflow-hidden rounded-[32px] border border-white/[0.06] bg-black/20 shadow-[0_24px_60px_rgba(0,0,0,0.24)]">
<a
href="{{ $articleCoverPreloadHref }}"
href="{{ $article->cover_url }}"
class="group block focus:outline-none focus-visible:ring-2 focus-visible:ring-sky-300/70 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-950"
aria-label="Open full cover image"
data-news-image-preview
data-news-image-src="{{ $articleCoverPreloadHref }}"
data-news-image-src="{{ $article->cover_url }}"
data-news-image-alt="{{ $article->title }}"
>
<div class="relative">
<img src="{{ $article->cover_url }}" @if($article->cover_srcset) srcset="{{ $article->cover_srcset }}" sizes="{{ $articleCoverSizes }}" @endif alt="{{ $article->title }}" fetchpriority="high" loading="eager" decoding="async" class="h-auto max-h-[520px] w-full object-cover transition duration-300 group-hover:scale-[1.01]">
<img src="{{ $article->cover_large_url ?: ($article->cover_desktop_url ?: $article->cover_url) }}" @if($article->cover_srcset) srcset="{{ $article->cover_srcset }}" sizes="{{ $articleCoverSizes }}" @endif alt="{{ $article->title }}" fetchpriority="high" loading="eager" decoding="async" class="h-auto max-h-[520px] w-full object-cover transition duration-300 group-hover:scale-[1.01]">
<div class="pointer-events-none absolute inset-x-4 bottom-4 flex items-center justify-between gap-3 rounded-full border border-white/10 bg-slate-950/72 px-4 py-2 text-xs font-semibold uppercase tracking-[0.18em] text-white/82 backdrop-blur-sm">
<span>Open Image</span>
<span class="inline-flex items-center gap-2 text-sky-200/90">

View File

@@ -0,0 +1,61 @@
@extends('news.layout', [
'metaTitle' => $typeLabel . ' — News',
'metaDescription' => 'News articles of type ' . $typeLabel . '.',
'metaCanonical' => route('news.type', $type),
])
@section('news_content')
@php
$headerBreadcrumbs = collect([
(object) ['name' => 'Community', 'url' => route('community.activity')],
(object) ['name' => 'Announcements', 'url' => route('news.index')],
(object) ['name' => $typeLabel, 'url' => route('news.type', $type)],
]);
$seo = \App\Support\Seo\SeoDataBuilder::fromArray([
'title' => $typeLabel . ' — News',
'description' => 'Browse all news articles of type ' . $typeLabel . '.',
'canonical' => route('news.type', $type),
'breadcrumbs' => $headerBreadcrumbs,
'structured_data' => [
[
'@context' => 'https://schema.org',
'@type' => 'CollectionPage',
'name' => $typeLabel . ' — News',
'description' => 'Browse all news articles of type ' . $typeLabel . '.',
'url' => route('news.type', $type),
],
],
])->build();
@endphp
<x-nova-page-header
section="Community"
:title="$typeLabel"
icon="fa-newspaper"
:breadcrumbs="$headerBreadcrumbs"
:description="'Browse all ' . $typeLabel . ' articles.'"
headerClass="pb-6"
innerClass="mx-auto max-w-7xl"
/>
<div class="mx-auto max-w-7xl px-6 pt-8 pb-16 md:px-10">
<div class="grid gap-8 xl:grid-cols-[minmax(0,1fr)_320px]">
<section>
@if($articles->isEmpty())
<div class="rounded-[28px] border border-white/[0.06] bg-white/[0.025] px-8 py-14 text-center text-white/45">No {{ $typeLabel }} articles have been published yet.</div>
@else
<div class="grid gap-5 md:grid-cols-2">
@foreach($articles as $article)
@include('news._article_card', ['article' => $article])
@endforeach
</div>
<div class="mt-8 flex justify-center">{{ $articles->links() }}</div>
@endif
</section>
<aside class="space-y-4">
@include('news._sidebar', ['categories' => $categories, 'trending' => $trending, 'tags' => $tags])
</aside>
</div>
</div>
@endsection