@kamod-ch/preactpress 1.0.3 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/app.d.ts.map +1 -1
- package/dist/client/app.js +34 -4
- package/dist/client/app.js.map +1 -1
- package/dist/client/mermaid.d.ts +2 -0
- package/dist/client/mermaid.d.ts.map +1 -0
- package/dist/client/mermaid.js +68 -0
- package/dist/client/mermaid.js.map +1 -0
- package/dist/client/theme-default/Layout.d.ts.map +1 -1
- package/dist/client/theme-default/Layout.js +43 -0
- package/dist/client/theme-default/Layout.js.map +1 -1
- package/dist/node/markdown.d.ts.map +1 -1
- package/dist/node/markdown.js +24 -6
- package/dist/node/markdown.js.map +1 -1
- package/dist/shared/scrollRestoration.d.ts +2 -1
- package/dist/shared/scrollRestoration.d.ts.map +1 -1
- package/dist/shared/scrollRestoration.js +36 -14
- package/dist/shared/scrollRestoration.js.map +1 -1
- package/package.json +2 -1
- package/src/client/app.tsx +33 -3
- package/src/client/mermaid.ts +74 -0
- package/src/client/theme-default/Layout.tsx +43 -0
- package/src/client/theme-default/styles.css +181 -5
- package/src/shared/scrollRestoration.ts +45 -14
- package/templates/docs/.preactpress/config.ts +5 -0
- package/templates/docs/examples/cloudflare-pages.md +117 -0
- package/templates/docs/examples/custom-theme.md +266 -0
- package/templates/docs/examples/mermaid.md +53 -0
- package/templates/docs/examples/preact-signals.md +102 -0
- package/templates/docs/examples/rss.md +104 -0
- package/templates/docs/guide/custom-themes.md +1 -1
- package/templates/docs/guide/deploy.md +2 -0
- package/templates/docs/guide/markdown-and-mdx.md +1 -1
- package/templates/docs/markdown-examples.md +10 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scrollRestoration.js","sourceRoot":"","sources":["../../src/shared/scrollRestoration.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,gBAAgB,GAAG,WAAW,CAAC;AAE5C,IAAI,qBAAqB,GAAG,KAAK,CAAC;AAElC,MAAM,UAAU,sBAAsB;IACpC,IAAI,OAAO,MAAM,KAAK,WAAW;QAAE,OAAO,GAAG,EAAE,GAAE,CAAC,CAAC;IAEnD,IAAI,mBAAmB,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QAC1C,MAAM,CAAC,OAAO,CAAC,iBAAiB,GAAG,QAAQ,CAAC;IAC9C,CAAC;IAED,
|
|
1
|
+
{"version":3,"file":"scrollRestoration.js","sourceRoot":"","sources":["../../src/shared/scrollRestoration.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kamod-ch/preactpress",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Vite and Preact powered static site generator",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"markdown",
|
|
@@ -103,6 +103,7 @@
|
|
|
103
103
|
"markdown-it": "^14.2.0",
|
|
104
104
|
"markdown-it-emoji": "^3.0.0",
|
|
105
105
|
"markdown-it-mathjax3": "^5.2.0",
|
|
106
|
+
"mermaid": "^11.16.0",
|
|
106
107
|
"minimist": "^1.2.8",
|
|
107
108
|
"node-html-parser": "^7.1.0",
|
|
108
109
|
"picocolors": "^1.1.1",
|
package/src/client/app.tsx
CHANGED
|
@@ -10,9 +10,11 @@ import {
|
|
|
10
10
|
consumeScrollRestoreOnPopstate,
|
|
11
11
|
restoreScrollPositionAfterLayout,
|
|
12
12
|
saveScrollPositionBeforeNavigation,
|
|
13
|
+
scrollToTopAfterLayout,
|
|
13
14
|
setupScrollRestoration,
|
|
14
15
|
} from "../shared/scrollRestoration.js";
|
|
15
16
|
import { getCachedPage, loadPage, prefetchPage, seedPage } from "./loadPage.js";
|
|
17
|
+
import { renderMermaidDiagrams } from "./mermaid.js";
|
|
16
18
|
import { setupViewportPrefetch } from "./prefetchLinks.js";
|
|
17
19
|
import {
|
|
18
20
|
localeFromRoute,
|
|
@@ -62,6 +64,9 @@ export function App({ routePath, initialPage }: { routePath: string; initialPage
|
|
|
62
64
|
const [currentRoute, setCurrentRoute] = useState(() => normalizeRoute(routePath));
|
|
63
65
|
const [page, setPage] = useState<PageView>(() => initialPage ?? loadingPage(routePath));
|
|
64
66
|
const pendingScrollRestore = useRef(false);
|
|
67
|
+
const pendingScrollTop = useRef(false);
|
|
68
|
+
const [scrollRestoreNonce, setScrollRestoreNonce] = useState(0);
|
|
69
|
+
const [scrollTopNonce, setScrollTopNonce] = useState(0);
|
|
65
70
|
const availableRoutes = new Set(routes);
|
|
66
71
|
const activeLocale = localeFromRoute(currentRoute, i18n);
|
|
67
72
|
const activeSite = siteForRoute(site, currentRoute, i18n);
|
|
@@ -81,7 +86,10 @@ export function App({ routePath, initialPage }: { routePath: string; initialPage
|
|
|
81
86
|
setPage(loaded);
|
|
82
87
|
if (pendingScrollRestore.current) {
|
|
83
88
|
pendingScrollRestore.current = false;
|
|
84
|
-
|
|
89
|
+
setScrollRestoreNonce((nonce) => nonce + 1);
|
|
90
|
+
} else if (pendingScrollTop.current) {
|
|
91
|
+
pendingScrollTop.current = false;
|
|
92
|
+
setScrollTopNonce((nonce) => nonce + 1);
|
|
85
93
|
}
|
|
86
94
|
}
|
|
87
95
|
})
|
|
@@ -95,6 +103,13 @@ export function App({ routePath, initialPage }: { routePath: string; initialPage
|
|
|
95
103
|
meta: {},
|
|
96
104
|
headings: [],
|
|
97
105
|
});
|
|
106
|
+
if (pendingScrollRestore.current) {
|
|
107
|
+
pendingScrollRestore.current = false;
|
|
108
|
+
setScrollRestoreNonce((nonce) => nonce + 1);
|
|
109
|
+
} else if (pendingScrollTop.current) {
|
|
110
|
+
pendingScrollTop.current = false;
|
|
111
|
+
setScrollTopNonce((nonce) => nonce + 1);
|
|
112
|
+
}
|
|
98
113
|
}
|
|
99
114
|
});
|
|
100
115
|
return () => {
|
|
@@ -102,6 +117,16 @@ export function App({ routePath, initialPage }: { routePath: string; initialPage
|
|
|
102
117
|
};
|
|
103
118
|
}, [activeSite.description, currentRoute]);
|
|
104
119
|
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
if (scrollRestoreNonce === 0) return;
|
|
122
|
+
void restoreScrollPositionAfterLayout();
|
|
123
|
+
}, [scrollRestoreNonce]);
|
|
124
|
+
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
if (scrollTopNonce === 0) return;
|
|
127
|
+
void scrollToTopAfterLayout();
|
|
128
|
+
}, [scrollTopNonce]);
|
|
129
|
+
|
|
105
130
|
useEffect(() => {
|
|
106
131
|
const prefetch = (route: string) => prefetchPage(route, site.base);
|
|
107
132
|
const stopViewportPrefetch = setupViewportPrefetch(routeFromHref, prefetch);
|
|
@@ -138,9 +163,10 @@ export function App({ routePath, initialPage }: { routePath: string; initialPage
|
|
|
138
163
|
}
|
|
139
164
|
event.preventDefault();
|
|
140
165
|
saveScrollPositionBeforeNavigation();
|
|
166
|
+
pendingScrollTop.current = true;
|
|
141
167
|
window.history.pushState({}, "", url);
|
|
142
168
|
setCurrentRoute(route);
|
|
143
|
-
window.scrollTo({ top: 0, behavior: "auto" });
|
|
169
|
+
window.scrollTo({ top: 0, left: 0, behavior: "auto" });
|
|
144
170
|
};
|
|
145
171
|
const onMouseEnter = (event: MouseEvent) => {
|
|
146
172
|
const link = anchorFromEvent(event);
|
|
@@ -160,9 +186,13 @@ export function App({ routePath, initialPage }: { routePath: string; initialPage
|
|
|
160
186
|
};
|
|
161
187
|
}, [currentRoute, mpa]);
|
|
162
188
|
|
|
189
|
+
useEffect(() => {
|
|
190
|
+
void renderMermaidDiagrams();
|
|
191
|
+
}, [currentRoute, page]);
|
|
192
|
+
|
|
163
193
|
useEffect(() => {
|
|
164
194
|
if (currentRoute !== normalizeRoute(routePath)) {
|
|
165
|
-
document.getElementById("content")?.focus();
|
|
195
|
+
document.getElementById("content")?.focus({ preventScroll: true });
|
|
166
196
|
}
|
|
167
197
|
}, [currentRoute, routePath]);
|
|
168
198
|
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
let renderRun = 0;
|
|
2
|
+
|
|
3
|
+
function isDarkTheme(): boolean {
|
|
4
|
+
if (typeof document === "undefined" || typeof window === "undefined") return false;
|
|
5
|
+
const explicit = document.documentElement.dataset.theme;
|
|
6
|
+
if (explicit === "dark") return true;
|
|
7
|
+
if (explicit === "light") return false;
|
|
8
|
+
return window.matchMedia?.("(prefers-color-scheme: dark)").matches ?? false;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function mermaidSourceFromElement(element: HTMLElement): string {
|
|
12
|
+
if (element.dataset.mermaidSource) return element.dataset.mermaidSource;
|
|
13
|
+
return element.textContent ?? "";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function normalizeMermaidBlocks(): HTMLElement[] {
|
|
17
|
+
const blocks = Array.from(document.querySelectorAll<HTMLElement>(".pp-mermaid"));
|
|
18
|
+
|
|
19
|
+
for (const code of document.querySelectorAll<HTMLElement>(
|
|
20
|
+
'pre > code.language-mermaid, pre > code[class*="language-mermaid"]',
|
|
21
|
+
)) {
|
|
22
|
+
const pre = code.parentElement;
|
|
23
|
+
if (!pre || pre.dataset.mermaidNormalized === "true") continue;
|
|
24
|
+
|
|
25
|
+
const block = document.createElement("div");
|
|
26
|
+
block.className = "pp-mermaid";
|
|
27
|
+
block.dataset.mermaidSource = code.textContent ?? "";
|
|
28
|
+
block.textContent = code.textContent ?? "";
|
|
29
|
+
pre.replaceWith(block);
|
|
30
|
+
pre.dataset.mermaidNormalized = "true";
|
|
31
|
+
blocks.push(block);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return blocks;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function renderMermaidDiagrams(): Promise<void> {
|
|
38
|
+
if (typeof document === "undefined") return;
|
|
39
|
+
|
|
40
|
+
const blocks = normalizeMermaidBlocks().filter(
|
|
41
|
+
(block) => block.dataset.mermaidRendered !== "true",
|
|
42
|
+
);
|
|
43
|
+
if (blocks.length === 0) return;
|
|
44
|
+
|
|
45
|
+
const run = ++renderRun;
|
|
46
|
+
const { default: mermaid } = await import("mermaid");
|
|
47
|
+
if (run !== renderRun) return;
|
|
48
|
+
|
|
49
|
+
mermaid.initialize({
|
|
50
|
+
startOnLoad: false,
|
|
51
|
+
securityLevel: "strict",
|
|
52
|
+
theme: isDarkTheme() ? "dark" : "default",
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
await Promise.all(
|
|
56
|
+
blocks.map(async (block, index) => {
|
|
57
|
+
const source = mermaidSourceFromElement(block).trim();
|
|
58
|
+
if (!source) return;
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const id = `pp-mermaid-${run}-${index}`;
|
|
62
|
+
const { svg } = await mermaid.render(id, source);
|
|
63
|
+
if (run !== renderRun) return;
|
|
64
|
+
block.innerHTML = svg;
|
|
65
|
+
block.dataset.mermaidRendered = "true";
|
|
66
|
+
block.dataset.mermaidSource = source;
|
|
67
|
+
} catch (error) {
|
|
68
|
+
block.dataset.mermaidError = "true";
|
|
69
|
+
block.textContent = source;
|
|
70
|
+
console.warn("Failed to render Mermaid diagram", error);
|
|
71
|
+
}
|
|
72
|
+
}),
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -150,6 +150,49 @@ const Layout: FunctionalComponent<LayoutProps> = ({
|
|
|
150
150
|
setMobileMenuOpen(false);
|
|
151
151
|
}, [routePath]);
|
|
152
152
|
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
const copyText = async (text: string) => {
|
|
155
|
+
if (navigator.clipboard?.writeText) {
|
|
156
|
+
await navigator.clipboard.writeText(text);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const textarea = document.createElement("textarea");
|
|
160
|
+
textarea.value = text;
|
|
161
|
+
textarea.setAttribute("readonly", "");
|
|
162
|
+
textarea.style.position = "fixed";
|
|
163
|
+
textarea.style.top = "-9999px";
|
|
164
|
+
document.body.append(textarea);
|
|
165
|
+
textarea.select();
|
|
166
|
+
document.execCommand("copy");
|
|
167
|
+
textarea.remove();
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const onClick = (event: MouseEvent) => {
|
|
171
|
+
const target = event.target;
|
|
172
|
+
const candidate = target instanceof Element ? target.closest(".pp-code-copy") : null;
|
|
173
|
+
if (!(candidate instanceof HTMLButtonElement)) return;
|
|
174
|
+
const button = candidate;
|
|
175
|
+
const code = button.closest(".pp-code-block")?.querySelector("code")?.textContent ?? "";
|
|
176
|
+
if (!code) return;
|
|
177
|
+
|
|
178
|
+
event.preventDefault();
|
|
179
|
+
void copyText(code).then(() => {
|
|
180
|
+
const icon = button.querySelector(".pp-code-copy-icon");
|
|
181
|
+
if (icon && button.dataset.checkIcon) icon.innerHTML = button.dataset.checkIcon;
|
|
182
|
+
button.classList.add("copied");
|
|
183
|
+
button.setAttribute("aria-label", button.dataset.copiedLabel ?? "Copied");
|
|
184
|
+
window.setTimeout(() => {
|
|
185
|
+
if (icon && button.dataset.copyIcon) icon.innerHTML = button.dataset.copyIcon;
|
|
186
|
+
button.classList.remove("copied");
|
|
187
|
+
button.setAttribute("aria-label", "Copy code");
|
|
188
|
+
}, 2000);
|
|
189
|
+
});
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
document.addEventListener("click", onClick);
|
|
193
|
+
return () => document.removeEventListener("click", onClick);
|
|
194
|
+
}, []);
|
|
195
|
+
|
|
153
196
|
useEffect(() => {
|
|
154
197
|
if (!mobileMenuOpen) return;
|
|
155
198
|
|
|
@@ -759,14 +759,188 @@ body::selection {
|
|
|
759
759
|
margin: 16px 0;
|
|
760
760
|
}
|
|
761
761
|
|
|
762
|
+
.pp-doc-content .pp-mermaid {
|
|
763
|
+
margin: 1.25rem 0;
|
|
764
|
+
padding: 1.25rem;
|
|
765
|
+
overflow: auto;
|
|
766
|
+
border: 1px solid color-mix(in srgb, var(--pp-border) 82%, transparent);
|
|
767
|
+
border-radius: 14px;
|
|
768
|
+
background: var(--pp-code-bg);
|
|
769
|
+
color: var(--pp-code-text);
|
|
770
|
+
text-align: center;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
.pp-doc-content .pp-mermaid[data-mermaid-error="true"] {
|
|
774
|
+
text-align: left;
|
|
775
|
+
white-space: pre;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
.pp-doc-content .pp-mermaid svg {
|
|
779
|
+
max-width: 100%;
|
|
780
|
+
height: auto;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
.pp-doc-content .pp-code-block {
|
|
784
|
+
position: relative;
|
|
785
|
+
margin: 1.25rem 0;
|
|
786
|
+
border: 1px solid color-mix(in srgb, var(--pp-border) 82%, transparent);
|
|
787
|
+
border-radius: 14px;
|
|
788
|
+
background: var(--pp-code-bg);
|
|
789
|
+
box-shadow:
|
|
790
|
+
0 14px 34px color-mix(in srgb, #000 8%, transparent),
|
|
791
|
+
inset 0 1px 0 color-mix(in srgb, #fff 45%, transparent);
|
|
792
|
+
overflow: hidden;
|
|
793
|
+
}
|
|
794
|
+
|
|
762
795
|
.pp-doc-content pre {
|
|
763
|
-
padding: 16px;
|
|
764
|
-
border-radius:
|
|
796
|
+
padding: 16px 18px;
|
|
797
|
+
border-radius: 0;
|
|
765
798
|
overflow: auto;
|
|
766
|
-
border:
|
|
799
|
+
border: 0;
|
|
767
800
|
background: var(--pp-code-bg);
|
|
768
801
|
color: var(--pp-code-text);
|
|
769
|
-
line-height: 1.
|
|
802
|
+
line-height: 1.38;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
.pp-doc-content .pp-code-block pre {
|
|
806
|
+
margin: 0;
|
|
807
|
+
max-height: min(68vh, 720px);
|
|
808
|
+
padding-top: 2.7rem;
|
|
809
|
+
padding-right: 3.75rem;
|
|
810
|
+
line-height: 0;
|
|
811
|
+
scrollbar-width: thin;
|
|
812
|
+
scrollbar-color: color-mix(in srgb, var(--pp-muted) 40%, transparent) transparent;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
.pp-doc-content .pp-code-block .shiki.shiki-themes.vitesse-light.vitesse-dark {
|
|
816
|
+
padding-bottom: 1rem;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
.pp-doc-content .pp-code-block pre code {
|
|
820
|
+
display: block;
|
|
821
|
+
min-width: max-content;
|
|
822
|
+
font-size: 0.9rem;
|
|
823
|
+
line-height: 1.32;
|
|
824
|
+
tab-size: 2;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
.pp-doc-content .pp-code-block .line {
|
|
828
|
+
display: block;
|
|
829
|
+
line-height: 0;
|
|
830
|
+
counter-increment: pp-code-line;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
.pp-doc-content .pp-code-block code {
|
|
834
|
+
counter-reset: pp-code-line;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
.pp-doc-content .pp-code-block .line::before {
|
|
838
|
+
content: counter(pp-code-line);
|
|
839
|
+
display: inline-block;
|
|
840
|
+
width: 2rem;
|
|
841
|
+
margin-right: 1rem;
|
|
842
|
+
color: color-mix(in srgb, var(--pp-muted) 42%, transparent);
|
|
843
|
+
font-variant-numeric: tabular-nums;
|
|
844
|
+
text-align: right;
|
|
845
|
+
user-select: none;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
.pp-doc-content .pp-code-block[data-language="plaintext"] pre {
|
|
849
|
+
padding-top: 18px;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
.pp-doc-content .pp-code-lang {
|
|
853
|
+
position: absolute;
|
|
854
|
+
top: 12px;
|
|
855
|
+
left: 14px;
|
|
856
|
+
z-index: 2;
|
|
857
|
+
max-width: calc(100% - 4.75rem);
|
|
858
|
+
overflow: hidden;
|
|
859
|
+
border: 1px solid color-mix(in srgb, var(--pp-border) 72%, transparent);
|
|
860
|
+
border-radius: 999px;
|
|
861
|
+
padding: 0.18rem 0.55rem;
|
|
862
|
+
background: color-mix(in srgb, var(--pp-bg) 72%, transparent);
|
|
863
|
+
color: var(--pp-muted);
|
|
864
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
865
|
+
font-size: 0.72rem;
|
|
866
|
+
line-height: 1.35;
|
|
867
|
+
text-overflow: ellipsis;
|
|
868
|
+
text-transform: uppercase;
|
|
869
|
+
letter-spacing: 0.04em;
|
|
870
|
+
backdrop-filter: blur(12px);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
.pp-doc-content .shiki,
|
|
874
|
+
.pp-doc-content .shiki span {
|
|
875
|
+
color: var(--shiki-light, var(--pp-code-text)) !important;
|
|
876
|
+
background-color: var(--shiki-light-bg, var(--pp-code-bg)) !important;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
@media (prefers-color-scheme: dark) {
|
|
880
|
+
:root:not([data-theme]) .pp-doc-content .shiki,
|
|
881
|
+
:root:not([data-theme]) .pp-doc-content .shiki span {
|
|
882
|
+
color: var(--shiki-dark, var(--pp-code-text)) !important;
|
|
883
|
+
background-color: var(--shiki-dark-bg, var(--pp-code-bg)) !important;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
:root[data-theme="dark"] .pp-doc-content .shiki,
|
|
888
|
+
:root[data-theme="dark"] .pp-doc-content .shiki span {
|
|
889
|
+
color: var(--shiki-dark, var(--pp-code-text)) !important;
|
|
890
|
+
background-color: var(--shiki-dark-bg, var(--pp-code-bg)) !important;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
.pp-doc-content .pp-code-copy {
|
|
894
|
+
position: absolute;
|
|
895
|
+
top: 10px;
|
|
896
|
+
right: 10px;
|
|
897
|
+
z-index: 2;
|
|
898
|
+
display: grid;
|
|
899
|
+
place-items: center;
|
|
900
|
+
width: 34px;
|
|
901
|
+
height: 34px;
|
|
902
|
+
padding: 0;
|
|
903
|
+
border: 1px solid color-mix(in srgb, var(--pp-border) 72%, transparent);
|
|
904
|
+
border-radius: 10px;
|
|
905
|
+
background: color-mix(in srgb, var(--pp-bg) 72%, transparent);
|
|
906
|
+
color: var(--pp-muted);
|
|
907
|
+
opacity: 0;
|
|
908
|
+
transform: translateY(-3px) scale(0.96);
|
|
909
|
+
cursor: pointer;
|
|
910
|
+
backdrop-filter: blur(12px);
|
|
911
|
+
transition:
|
|
912
|
+
opacity 0.18s ease,
|
|
913
|
+
transform 0.18s ease,
|
|
914
|
+
color 0.18s ease,
|
|
915
|
+
border-color 0.18s ease,
|
|
916
|
+
background 0.18s ease;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
.pp-doc-content .pp-code-block:hover .pp-code-copy,
|
|
920
|
+
.pp-doc-content .pp-code-copy:focus-visible,
|
|
921
|
+
.pp-doc-content .pp-code-copy.copied {
|
|
922
|
+
opacity: 1;
|
|
923
|
+
transform: translateY(0) scale(1);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
.pp-doc-content .pp-code-copy:hover,
|
|
927
|
+
.pp-doc-content .pp-code-copy.copied {
|
|
928
|
+
color: var(--pp-accent);
|
|
929
|
+
border-color: color-mix(in srgb, var(--pp-accent) 44%, var(--pp-border));
|
|
930
|
+
background: color-mix(in srgb, var(--pp-bg) 88%, var(--pp-accent) 12%);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
.pp-doc-content .pp-code-copy svg {
|
|
934
|
+
width: 17px;
|
|
935
|
+
height: 17px;
|
|
936
|
+
fill: currentColor;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
.pp-doc-content.pp-code-wrap .pp-code-block pre code,
|
|
940
|
+
.pp-doc-content .pp-code-block.pp-code-wrap pre code {
|
|
941
|
+
min-width: 0;
|
|
942
|
+
white-space: pre-wrap;
|
|
943
|
+
overflow-wrap: anywhere;
|
|
770
944
|
}
|
|
771
945
|
|
|
772
946
|
.pp-doc-content :not(pre) > code {
|
|
@@ -970,9 +1144,11 @@ body::selection {
|
|
|
970
1144
|
display: block;
|
|
971
1145
|
}
|
|
972
1146
|
|
|
973
|
-
.pp-doc-content .pp-code-group-block .
|
|
1147
|
+
.pp-doc-content .pp-code-group-block .pp-code-block {
|
|
974
1148
|
margin: 0;
|
|
1149
|
+
border: 0;
|
|
975
1150
|
border-radius: 0;
|
|
1151
|
+
box-shadow: none;
|
|
976
1152
|
}
|
|
977
1153
|
|
|
978
1154
|
.pp-doc-content table {
|
|
@@ -9,15 +9,14 @@ export function setupScrollRestoration(): () => void {
|
|
|
9
9
|
window.history.scrollRestoration = "manual";
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
13
12
|
const onScroll = () => {
|
|
14
|
-
|
|
15
|
-
timeout = setTimeout(persistScrollPosition, 150);
|
|
13
|
+
persistScrollPosition();
|
|
16
14
|
};
|
|
17
15
|
window.addEventListener("scroll", onScroll, { passive: true });
|
|
16
|
+
window.addEventListener("pagehide", persistScrollPosition);
|
|
18
17
|
return () => {
|
|
19
18
|
window.removeEventListener("scroll", onScroll);
|
|
20
|
-
|
|
19
|
+
window.removeEventListener("pagehide", persistScrollPosition);
|
|
21
20
|
};
|
|
22
21
|
}
|
|
23
22
|
|
|
@@ -44,17 +43,49 @@ export function restoreScrollPosition(): void {
|
|
|
44
43
|
window.scrollTo({ top: readScrollPositionFromHistory(), left: 0, behavior: "auto" });
|
|
45
44
|
}
|
|
46
45
|
|
|
47
|
-
export function restoreScrollPositionAfterLayout(): void {
|
|
46
|
+
export async function restoreScrollPositionAfterLayout(): Promise<void> {
|
|
47
|
+
await scrollAfterLayout(readScrollPositionFromHistory());
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function scrollToTopAfterLayout(): Promise<void> {
|
|
51
|
+
await scrollAfterLayout(0);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function scrollAfterLayout(top: number): Promise<void> {
|
|
48
55
|
if (typeof window === "undefined") return;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
});
|
|
56
|
+
await nextAnimationFrame();
|
|
57
|
+
await nextAnimationFrame();
|
|
58
|
+
await waitForImagesToSettle();
|
|
59
|
+
await nextAnimationFrame();
|
|
60
|
+
|
|
61
|
+
const html = document.documentElement;
|
|
62
|
+
const previous = html.style.scrollBehavior;
|
|
63
|
+
html.style.scrollBehavior = "auto";
|
|
64
|
+
window.scrollTo({ top, left: 0, behavior: "auto" });
|
|
65
|
+
html.style.scrollBehavior = previous;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function nextAnimationFrame(): Promise<void> {
|
|
69
|
+
return new Promise((resolve) => requestAnimationFrame(() => resolve()));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function waitForImagesToSettle(timeoutMs = 1500): Promise<void> {
|
|
73
|
+
if (typeof document === "undefined") return;
|
|
74
|
+
const images = Array.from(document.images).filter((image) => !image.complete);
|
|
75
|
+
if (images.length === 0) return;
|
|
76
|
+
|
|
77
|
+
await Promise.race([
|
|
78
|
+
Promise.allSettled(
|
|
79
|
+
images.map(
|
|
80
|
+
(image) =>
|
|
81
|
+
new Promise<void>((resolve) => {
|
|
82
|
+
image.addEventListener("load", () => resolve(), { once: true });
|
|
83
|
+
image.addEventListener("error", () => resolve(), { once: true });
|
|
84
|
+
}),
|
|
85
|
+
),
|
|
86
|
+
),
|
|
87
|
+
new Promise<void>((resolve) => setTimeout(resolve, timeoutMs)),
|
|
88
|
+
]);
|
|
58
89
|
}
|
|
59
90
|
|
|
60
91
|
export function skipNextScrollRestore(): void {
|
|
@@ -77,9 +77,14 @@ export default defineConfig({
|
|
|
77
77
|
items: [
|
|
78
78
|
{ text: "Markdown examples", link: "/markdown-examples" },
|
|
79
79
|
{ text: "Interactive MDX", link: "/interactive" },
|
|
80
|
+
{ text: "Preact Signals", link: "/examples/preact-signals" },
|
|
81
|
+
{ text: "Mermaid diagrams", link: "/examples/mermaid" },
|
|
82
|
+
{ text: "RSS / Atom feed", link: "/examples/rss" },
|
|
80
83
|
{ text: "Algolia DocSearch", link: "/examples/algolia-docsearch" },
|
|
81
84
|
{ text: "Content loader", link: "/examples/content-loader" },
|
|
82
85
|
{ text: "Dynamic routes", link: "/examples/dynamic-routes" },
|
|
86
|
+
{ text: "Custom theme", link: "/examples/custom-theme" },
|
|
87
|
+
{ text: "Cloudflare Pages", link: "/examples/cloudflare-pages" },
|
|
83
88
|
{ text: "Static assets", link: "/examples/static-assets" },
|
|
84
89
|
],
|
|
85
90
|
},
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Cloudflare Pages deployment
|
|
3
|
+
description: Deploy a PreactPress static site to Cloudflare Pages.
|
|
4
|
+
tags:
|
|
5
|
+
- deploy
|
|
6
|
+
- examples
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
PreactPress outputs static files, so Cloudflare Pages can serve the generated `dist/` directory without a Node server.
|
|
10
|
+
|
|
11
|
+
## Prerequisites
|
|
12
|
+
|
|
13
|
+
- A PreactPress site committed to GitHub or GitLab
|
|
14
|
+
- Node.js 20 or newer
|
|
15
|
+
- A package script like:
|
|
16
|
+
|
|
17
|
+
```json [package.json]
|
|
18
|
+
{
|
|
19
|
+
"scripts": {
|
|
20
|
+
"check": "preactpress check",
|
|
21
|
+
"build": "preactpress build"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Configure PreactPress
|
|
27
|
+
|
|
28
|
+
Set the final production URL in `.preactpress/config.ts`:
|
|
29
|
+
|
|
30
|
+
```ts [.preactpress/config.ts]
|
|
31
|
+
import { defineConfig } from "@kamod-ch/preactpress/config";
|
|
32
|
+
|
|
33
|
+
export default defineConfig({
|
|
34
|
+
site: {
|
|
35
|
+
title: "My Docs",
|
|
36
|
+
description: "Documentation built with PreactPress",
|
|
37
|
+
url: "https://docs.example.com",
|
|
38
|
+
base: "/",
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Use `base: "/"` for normal Cloudflare Pages deployments. Only change `base` if you intentionally serve the site from a subpath.
|
|
44
|
+
|
|
45
|
+
## Deploy with the Cloudflare dashboard
|
|
46
|
+
|
|
47
|
+
1. Open **Cloudflare Dashboard → Workers & Pages → Create application → Pages**.
|
|
48
|
+
2. Connect your GitHub or GitLab repository.
|
|
49
|
+
3. Choose your PreactPress project.
|
|
50
|
+
4. Use these build settings:
|
|
51
|
+
|
|
52
|
+
| Setting | Value |
|
|
53
|
+
| ------------------- | ---------------- |
|
|
54
|
+
| Framework preset | `None` / custom |
|
|
55
|
+
| Build command | `pnpm run build` |
|
|
56
|
+
| Build output folder | `dist` |
|
|
57
|
+
| Root directory | project root |
|
|
58
|
+
|
|
59
|
+
If your docs live in a monorepo subfolder, set **Root directory** to that folder, for example:
|
|
60
|
+
|
|
61
|
+
```txt
|
|
62
|
+
packages/docs
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Environment variables
|
|
66
|
+
|
|
67
|
+
Set the Node version in Cloudflare Pages:
|
|
68
|
+
|
|
69
|
+
| Variable | Value |
|
|
70
|
+
| -------------- | ----- |
|
|
71
|
+
| `NODE_VERSION` | `22` |
|
|
72
|
+
|
|
73
|
+
Cloudflare Pages usually detects pnpm from `pnpm-lock.yaml`. If needed, set:
|
|
74
|
+
|
|
75
|
+
| Variable | Value |
|
|
76
|
+
| -------------- | --------- |
|
|
77
|
+
| `PNPM_VERSION` | `10.12.4` |
|
|
78
|
+
|
|
79
|
+
## Optional: run checks before build
|
|
80
|
+
|
|
81
|
+
For stricter deployments, use this build command:
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
pnpm run check && pnpm run build
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
This fails the deployment when routes, nav links, sidebar links, or internal Markdown links are invalid.
|
|
88
|
+
|
|
89
|
+
## Deploy with Wrangler
|
|
90
|
+
|
|
91
|
+
You can also deploy the built `dist/` directory manually with Wrangler:
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
pnpm run check
|
|
95
|
+
pnpm run build
|
|
96
|
+
pnpm dlx wrangler pages deploy dist --project-name my-preactpress-site
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
For a monorepo, run those commands from the site package directory or pass the correct output path.
|
|
100
|
+
|
|
101
|
+
## Custom domains
|
|
102
|
+
|
|
103
|
+
After the first deployment, add your domain in **Pages → Custom domains**. Then update `site.url` to match the production domain:
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
export default defineConfig({
|
|
107
|
+
site: {
|
|
108
|
+
url: "https://docs.example.com",
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
This keeps canonical URLs, Open Graph metadata, `sitemap.xml`, `robots.txt`, and feeds correct.
|
|
114
|
+
|
|
115
|
+
## Cache notes
|
|
116
|
+
|
|
117
|
+
Cloudflare can cache hashed files under `assets/` aggressively because their filenames change when content changes. Avoid immutable caching for HTML, `preactpress-search.json`, and `preactpress-content/*.json`.
|