feat(web): switch to the Alpine CSP build; drop 'unsafe-eval' from CSP
Standard Alpine evaluates every attribute expression through new Function(), which a strict Content-Security-Policy cannot allow. Swap to the official CSP-safe build (@alpinejs/csp), whose parser evaluates expressions against registered component scope without eval. script-src now carries only 'self' and 'unsafe-inline' (the latter still required by the pre-paint theme flash guard). Bindings rewritten out of the unsupported expression shapes: - 21 multi-statement sequences across admin, audio_reader, docs, header, index, and reader became single component methods (init/loadWatchStatus hooks, commitScrub, pickSkip, pickSpeed, pickDefaultSpeed, chooseSleepOption, chooseSleepClockMode, toggleFxInvert, openPageThumbs, submitNote, clearSearch, pickReadingTheme) - the header sidebar's inline x-data (openPanel/themeMode IIFE) became a registered sidebarShell component; openPanel itself moved to the header root so every sidebar panel shares one open state - the appearance-panel moved into the component init() - header init() now also seeds openPanel from localStorage (or the admin section on /admin pages) Alpine magics (, , , ) remain available — the CSP evaluator injects them into scope. The Playwright suite covers the converted surfaces end to end.
This commit is contained in:
1 parent
a730a62280
commit
eb57b7208b
15 files changed
+154
-23
No files matched your search
@@ -554,7 +554,7 @@ templ ReaderChrome(metadata ReaderMetadata) {
|
||||
</div>
|
||||
<div class="reader-tools-row" x-show="comicFlow !== 'webtoon'">
|
||||
<span>Night mode</span>
|
||||
<button @click="fxInvert = !fxInvert; applyFxFilter(true)" class="reader-popover-btn text-xs px-2" :class="fxInvert ? 'text-blue-400' : ''" x-text="fxInvert ? 'On' : 'Off'">Off</button>
|
||||
<button @click="toggleFxInvert()" class="reader-popover-btn text-xs px-2" :class="fxInvert ? 'text-blue-400' : ''" x-text="fxInvert ? 'On' : 'Off'">Off</button>
|
||||
</div>
|
||||
<div class="reader-tools-row" x-show="isPDF && !treatAsComic">
|
||||
<span>Pointer</span>
|
||||
@@ -593,7 +593,7 @@ templ ReaderTOCDrawer() {
|
||||
<button :class="tocTab === 'contents' ? 'active' : ''" @click="tocTab = 'contents'">
|
||||
Contents <span class="reader-tab-count" x-text="tocItems.length">0</span>
|
||||
</button>
|
||||
<button :class="tocTab === 'pages' ? 'active' : ''" @click="tocTab = 'pages'; initPageThumbs()">
|
||||
<button :class="tocTab === 'pages' ? 'active' : ''" @click="openPageThumbs()">
|
||||
Pages <span class="reader-tab-count" x-text="pageThumbList.length || ''">0</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -691,7 +691,7 @@ templ ReaderAnnotationsDrawer() {
|
||||
x-text="noteCountLabel(newNoteText)"
|
||||
></div>
|
||||
<button
|
||||
@click="addNote(newNoteText); newNoteText = ''"
|
||||
@click="submitNote()"
|
||||
:disabled="noteOverLimit(newNoteText)"
|
||||
class="w-full py-1.5 mt-1 mb-3 text-sm bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
@@ -820,7 +820,7 @@ templ ReaderSearchDrawer() {
|
||||
/>
|
||||
<button
|
||||
x-show="searchQuery || searchGroups.length"
|
||||
@click="searchQuery = ''; clearSearchResults()"
|
||||
@click="clearSearch()"
|
||||
class="p-2 rounded-lg hover:bg-gray-700 shrink-0"
|
||||
title="Clear results"
|
||||
aria-label="Clear results"
|
||||
@@ -908,7 +908,7 @@ templ ReaderSettingsDrawer() {
|
||||
<template x-for="sw in themeSwatches" :key="sw.value">
|
||||
<button
|
||||
type="button"
|
||||
@click="readingTheme = sw.value; applyTheme()"
|
||||
@click="pickReadingTheme(sw.value)"
|
||||
class="theme-swatch"
|
||||
:class="readingTheme === sw.value ? 'ring-2 ring-blue-500' : ''"
|
||||
:style="'background-color:' + sw.bg + '; color:' + sw.fg"
|
||||
|
||||
Reference in new issue
Block a user