Preserve CSS ordering on hot theme changes.

This commit is contained in:
2026-08-17 20:33:09 +00:00
parent a01d539cd0
commit ae19dc58b1
2 changed files with 35 additions and 17 deletions
+26 -15
View File
@@ -142,22 +142,33 @@ function swapRegions(doc) {
} else if (curUserStyle) {
curUserStyle.remove()
}
// Theme and other public stylesheets live in <head> and must be kept in
// sync; editor-only stylesheets are preserved. Diff-based: unchanged
// sheets keep their elements, so their @keyframes are never torn down
// (re-creating keyframes would replay the editor's slide-in animation).
const curLinks = [...document.head.querySelectorAll('link[rel="stylesheet"]')]
.filter((l) => !l.dataset.pagerite)
const freshHrefs = [...doc.head.querySelectorAll('link[rel="stylesheet"]')]
.map((l) => l.href)
for (const link of curLinks) {
if (!freshHrefs.includes(link.href)) link.remove()
// Theme and other public stylesheets live in <head>, rendered with stable
// ids by the backend; sync them positionally so the custom CSS (rendered
// last) always keeps winning by order. Diff-based: unchanged sheets keep
// their elements, so their @keyframes are never torn down (re-creating
// keyframes would replay the editor's slide-in animation).
const freshLinks = [...doc.head.querySelectorAll('link[rel="stylesheet"]')]
const freshIds = new Set(freshLinks.map((l) => l.id))
for (const link of [...document.head.querySelectorAll('link[rel="stylesheet"]')]) {
if (!link.dataset.pagerite && !freshIds.has(link.id)) link.remove()
}
const have = new Set(
[...document.head.querySelectorAll('link[rel="stylesheet"]')].map((l) => l.href),
)
for (const link of doc.head.querySelectorAll('link[rel="stylesheet"]')) {
if (!have.has(link.href)) document.head.appendChild(document.importNode(link, true))
// Insert missing sheets in the fresh document's order, each right after
// its predecessor's element. The first sheet rendered is always the base
// CSS, so its link doubles as the fallback anchor when nothing matched yet
// (e.g. no theme was selected before and the position is otherwise lost).
let anchor = null
for (const link of freshLinks) {
const cur = link.id && document.getElementById(link.id)
if (cur && cur.href === link.href) {
anchor = cur
continue
}
const el = document.importNode(link, true)
// Same id, new URL (theme switch): replace in place, keeping position.
if (cur) cur.replaceWith(el)
else if (anchor) anchor.after(el)
else document.getElementById('pagerite-base')?.after(el) ?? document.head.append(el)
anchor = el
}
document.title = doc.title
}
+9 -2
View File
@@ -106,8 +106,15 @@ def _layout(
doc.meta(name="pagerite:editor-src", content=script[-1])
if editor_css:
doc.meta(name="pagerite:editor-css", content=editor_css)
for url in urls:
doc.link(rel="stylesheet", href=url, blocking="render")
# Stylesheet links carry stable ids so the site editor's hot swap can
# keep each sheet at its rendered position (see swapRegions).
for i, url in enumerate(urls):
doc.link(
rel="stylesheet",
href=url,
blocking="render",
id="pagerite-base" if i == 0 else "pagerite-theme",
)
for src in modules:
doc.script(src=src, type="module")
if custom_css.strip():