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

@@ -0,0 +1,110 @@
import React from 'react'
import { Head, Link, router, usePage } from '@inertiajs/react'
import EnhanceStatusBadge from '../../../components/enhance/EnhanceStatusBadge'
import EnhanceStubWarning from '../../../components/enhance/EnhanceStubWarning'
import { formatEnhanceDate } from '../../../utils/enhanceFormatting'
function formatDate(value) {
return formatEnhanceDate(value)
}
export default function ModerationEnhanceIndex() {
const { props } = usePage()
const [filters, setFilters] = React.useState(props.filters || {})
const jobs = props.jobs?.data || []
const flash = props.flash || {}
React.useEffect(() => {
setFilters(props.filters || {})
}, [props.filters])
function applyFilters(event) {
event.preventDefault()
router.get(props.indexUrl, filters, { preserveScroll: true, preserveState: true, replace: true })
}
return (
<div className="w-full px-6 pb-16 pt-8">
<Head title="Enhance Jobs" />
<section className="rounded-[32px] border border-white/10 bg-[radial-gradient(circle_at_top_left,rgba(56,189,248,0.16),transparent_36%),linear-gradient(180deg,rgba(15,23,42,0.96),rgba(2,6,23,0.92))] p-6 shadow-[0_24px_70px_rgba(2,6,23,0.32)]">
<div>
<p className="text-[11px] font-semibold uppercase tracking-[0.28em] text-sky-200/80">Moderation surface</p>
<h1 className="mt-3 text-3xl font-semibold tracking-[-0.04em] text-white">Enhance Jobs</h1>
<p className="mt-2 max-w-3xl text-sm leading-relaxed text-slate-300">Review queued, processing, failed, and completed image upscale jobs without changing original artwork assets.</p>
</div>
<form onSubmit={applyFilters} className="mt-6 grid gap-3 md:grid-cols-2 xl:grid-cols-7">
<select value={filters.status || 'all'} onChange={(event) => setFilters((current) => ({ ...current, status: event.target.value }))} className="rounded-2xl border border-white/10 bg-slate-950/70 px-4 py-3 text-sm text-white outline-none">
{(props.options?.statuses || []).map((option) => <option key={option} value={option}>{option === 'all' ? 'All statuses' : option}</option>)}
</select>
<select value={filters.engine || 'all'} onChange={(event) => setFilters((current) => ({ ...current, engine: event.target.value }))} className="rounded-2xl border border-white/10 bg-slate-950/70 px-4 py-3 text-sm text-white outline-none">
{(props.options?.engines || []).map((option) => <option key={option} value={option}>{option === 'all' ? 'All engines' : option}</option>)}
</select>
<select value={filters.mode || 'all'} onChange={(event) => setFilters((current) => ({ ...current, mode: event.target.value }))} className="rounded-2xl border border-white/10 bg-slate-950/70 px-4 py-3 text-sm text-white outline-none">
{(props.options?.modes || []).map((option) => <option key={option} value={option}>{option === 'all' ? 'All modes' : option}</option>)}
</select>
<select value={filters.scale || 'all'} onChange={(event) => setFilters((current) => ({ ...current, scale: event.target.value }))} className="rounded-2xl border border-white/10 bg-slate-950/70 px-4 py-3 text-sm text-white outline-none">
{(props.options?.scales || []).map((option) => <option key={String(option)} value={option}>{option === 'all' ? 'All scales' : `${option}x`}</option>)}
</select>
<input value={filters.user || ''} onChange={(event) => setFilters((current) => ({ ...current, user: event.target.value }))} placeholder="User name or username" className="rounded-2xl border border-white/10 bg-slate-950/70 px-4 py-3 text-sm text-white outline-none" />
<input type="date" value={filters.date_from || ''} onChange={(event) => setFilters((current) => ({ ...current, date_from: event.target.value }))} className="rounded-2xl border border-white/10 bg-slate-950/70 px-4 py-3 text-sm text-white outline-none" />
<input type="date" value={filters.date_to || ''} onChange={(event) => setFilters((current) => ({ ...current, date_to: event.target.value }))} className="rounded-2xl border border-white/10 bg-slate-950/70 px-4 py-3 text-sm text-white outline-none" />
<button type="submit" className="rounded-2xl border border-white/10 bg-white/[0.06] px-5 py-3 text-xs font-semibold uppercase tracking-[0.14em] text-white transition hover:bg-white/[0.1] xl:col-span-7">Apply filters</button>
</form>
</section>
<EnhanceStubWarning config={props.enhanceConfig} moderation className="mt-6" />
{flash.success ? <div className="mt-6 rounded-2xl border border-emerald-300/20 bg-emerald-400/10 px-4 py-3 text-sm text-emerald-50">{flash.success}</div> : null}
{flash.error ? <div className="mt-6 rounded-2xl border border-rose-300/20 bg-rose-400/10 px-4 py-3 text-sm text-rose-100">{flash.error}</div> : null}
<div className="mt-8 overflow-hidden rounded-[28px] border border-white/10 bg-[#08111d] shadow-[0_18px_48px_rgba(2,6,23,0.2)]">
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-white/[0.07] text-left text-xs font-semibold uppercase tracking-wider text-slate-500">
<th className="px-5 py-3.5">Preview</th>
<th className="px-5 py-3.5">Job</th>
<th className="px-5 py-3.5">User</th>
<th className="px-5 py-3.5">Artwork</th>
<th className="px-5 py-3.5">Status</th>
<th className="px-5 py-3.5">Mode</th>
<th className="px-5 py-3.5">Scale</th>
<th className="px-5 py-3.5">Dimensions</th>
<th className="px-5 py-3.5">Created</th>
<th className="px-5 py-3.5 text-right">Actions</th>
</tr>
</thead>
<tbody className="divide-y divide-white/[0.04]">
{jobs.length === 0 ? <tr><td colSpan={10} className="px-5 py-12 text-center text-slate-400">No enhance jobs match the current filters.</td></tr> : null}
{jobs.map((job) => (
<tr key={job.id} className="transition hover:bg-white/[0.025]">
<td className="px-5 py-4">
<div className="h-16 w-16 overflow-hidden rounded-xl border border-white/10 bg-black/20">
{job.preview_url || job.source_url ? <img src={job.preview_url || job.source_url} alt={`Enhance job ${job.id}`} className="h-full w-full object-cover" /> : null}
</div>
</td>
<td className="px-5 py-4 text-white">
<div className="font-semibold">#{job.id}</div>
<div className="mt-1 text-xs uppercase tracking-[0.16em] text-slate-500">{job.engine}</div>
</td>
<td className="px-5 py-4 text-slate-300">{job.user?.name || '—'}{job.user?.username ? <div className="text-xs text-slate-500">@{job.user.username}</div> : null}</td>
<td className="px-5 py-4 text-slate-300">{job.artwork?.title ? <a href={job.artwork.url} className="text-sky-300 hover:text-sky-200">{job.artwork.title}</a> : '—'}</td>
<td className="px-5 py-4"><EnhanceStatusBadge status={job.status} /></td>
<td className="px-5 py-4 text-slate-300">{job.mode}</td>
<td className="px-5 py-4 text-slate-300">{job.scale}x</td>
<td className="px-5 py-4 text-slate-300">{job.input_width} × {job.input_height}{job.output_width && job.output_height ? <div className="text-xs text-slate-500"> {job.output_width} × {job.output_height}</div> : null}</td>
<td className="px-5 py-4 text-slate-400">{formatDate(job.created_at)}</td>
<td className="px-5 py-4 text-right">
<Link href={job.show_url} className="rounded-full border border-white/10 bg-white/[0.05] px-4 py-2 text-xs font-semibold uppercase tracking-[0.14em] text-white transition hover:bg-white/[0.09]">Open</Link>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
)
}