Skip to content

Commit a090839

Browse files
committed
refactor: introduce ContentLayout to centralize head/nav/footer/mermaid logic
1 parent 7890825 commit a090839

11 files changed

Lines changed: 242 additions & 416 deletions

File tree

src/layouts/ContentLayout.astro

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
import NavBar from "../components/NavBar.astro";
3+
import Footer from "../components/Footer.astro";
4+
import "../styles/global.css";
5+
6+
export interface Props {
7+
title: string;
8+
description?: string;
9+
locale: 'zh' | 'en';
10+
currentPath: string;
11+
}
12+
13+
const { title, description, locale, currentPath } = Astro.props;
14+
---
15+
16+
<!doctype html>
17+
<html lang={locale}>
18+
<head>
19+
<meta charset="UTF-8" />
20+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
21+
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚡</text></svg>" />
22+
<title>{title} | Awesome AI</title>
23+
<meta name="description" content={description || title} />
24+
25+
<!-- Mermaid Diagram Support -->
26+
<script type="module">
27+
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
28+
mermaid.initialize({ theme: 'dark', securityLevel: 'loose' });
29+
30+
document.addEventListener('DOMContentLoaded', async () => {
31+
const pres = document.querySelectorAll('pre[data-language="mermaid"]');
32+
for (const pre of pres) {
33+
const rawText = pre.textContent;
34+
const div = document.createElement('div');
35+
div.className = 'mermaid';
36+
div.textContent = rawText;
37+
pre.replaceWith(div);
38+
}
39+
await mermaid.run({ nodes: document.querySelectorAll('.mermaid') });
40+
});
41+
</script>
42+
</head>
43+
<body class="bg-bg text-text font-sans antialiased flex flex-col min-h-screen">
44+
<NavBar locale={locale} currentPath={currentPath} />
45+
<slot />
46+
<Footer />
47+
</body>
48+
</html>

src/pages/ai/models/[slug].astro

Lines changed: 19 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
---
2-
import NavBar from "../../../components/NavBar.astro";
3-
import Footer from "../../../components/Footer.astro";
2+
import ContentLayout from "../../../layouts/ContentLayout.astro";
43
import "../../../styles/global.css";
54
import { t } from "../../../i18n/utils.ts";
65
import { getCollection } from "astro:content";
@@ -17,34 +16,9 @@ const { post } = Astro.props;
1716
const { Content } = await post.render();
1817
---
1918

20-
<html lang="zh">
21-
<head>
22-
<meta charset="UTF-8" />
23-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
24-
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚡</text></svg>" />
25-
<title>{post.data.title} | Awesome AI</title>
26-
<meta name="description" content={post.data.summary || post.data.title} />
27-
<script type="module">
28-
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
29-
mermaid.initialize({ theme: 'dark', securityLevel: 'loose' });
30-
31-
document.addEventListener('DOMContentLoaded', async () => {
32-
const pres = document.querySelectorAll('pre[data-language="mermaid"]');
33-
for (const pre of pres) {
34-
const rawText = pre.textContent;
35-
const div = document.createElement('div');
36-
div.className = 'mermaid';
37-
div.textContent = rawText;
38-
pre.replaceWith(div);
39-
}
40-
await mermaid.run({ nodes: document.querySelectorAll('.mermaid') });
41-
});
42-
</script>
43-
</head>
44-
<body class="bg-bg text-text font-sans antialiased flex flex-col min-h-screen">
45-
<NavBar locale="zh" currentPath="/ai" />
4619

47-
<main class="max-w-[780px] mx-auto px-4 sm:px-6 pt-[72px] sm:pt-[80px] pb-[40px] sm:pb-[60px] flex-1 w-full animate-fade-in">
20+
<ContentLayout title={post.data.title} description={post.data.summary || post.data.title} locale="zh" currentPath="/ai">
21+
<main class="max-w-[780px] mx-auto px-4 sm:px-6 pt-[72px] sm:pt-[80px] pb-[40px] sm:pb-[60px] flex-1 w-full animate-fade-in">
4822
<a href="/ai" class="inline-flex items-center gap-1.5 text-text-dim text-xs sm:text-sm mb-4 sm:mb-6 px-3 sm:px-4 py-2 rounded-lg border border-border bg-bg-card hover:text-text hover:bg-accent-glow hover:border-accent transition-all no-underline">
4923
{t("article.back_rankings")}
5024
</a>
@@ -78,7 +52,19 @@ const { Content } = await post.render();
7852
<Content />
7953
</article>
8054
</main>
81-
82-
<Footer />
83-
</body>
84-
</html>
55+
<script is:inline>
56+
window.copyArticle = function() {
57+
var el = document.getElementById('md-source');
58+
if (!el) return;
59+
navigator.clipboard.writeText(el.value).then(function() {
60+
var btn = document.querySelector('.copy-btn');
61+
if (btn) {
62+
btn.innerHTML = '<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><path d="M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z"></path></svg>';
63+
setTimeout(function() {
64+
btn.innerHTML = '<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 010 1.5h-1.5a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-1.5a.75.75 0 011.5 0v1.5A1.75 1.75 0 019.25 16h-7.5A1.75 1.75 0 010 14.25zM5 1.75C0 .784.784 0 1.75 0h7.5C10.216 0 11 .784 11 1.75v7.5A1.75 1.75 0 019.25 11h-7.5A1.75 1.75 0 010 9.25zm1.75-.25a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-7.5a.25.25 0 00-.25-.25z"></path></svg>';
65+
}, 1500);
66+
}
67+
});
68+
};
69+
</script>
70+
</ContentLayout>

src/pages/ai/tools/[slug].astro

Lines changed: 19 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
---
2-
import NavBar from "../../../components/NavBar.astro";
3-
import Footer from "../../../components/Footer.astro";
2+
import ContentLayout from "../../../layouts/ContentLayout.astro";
43
import "../../../styles/global.css";
54
import { t } from "../../../i18n/utils.ts";
65
import { getCollection } from "astro:content";
@@ -17,34 +16,9 @@ const { post } = Astro.props;
1716
const { Content } = await post.render();
1817
---
1918

20-
<html lang="zh">
21-
<head>
22-
<meta charset="UTF-8" />
23-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
24-
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚡</text></svg>" />
25-
<title>{post.data.title} | Awesome AI</title>
26-
<meta name="description" content={post.data.summary || post.data.title} />
27-
<script type="module">
28-
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
29-
mermaid.initialize({ theme: 'dark', securityLevel: 'loose' });
30-
31-
document.addEventListener('DOMContentLoaded', async () => {
32-
const pres = document.querySelectorAll('pre[data-language="mermaid"]');
33-
for (const pre of pres) {
34-
const rawText = pre.textContent;
35-
const div = document.createElement('div');
36-
div.className = 'mermaid';
37-
div.textContent = rawText;
38-
pre.replaceWith(div);
39-
}
40-
await mermaid.run({ nodes: document.querySelectorAll('.mermaid') });
41-
});
42-
</script>
43-
</head>
44-
<body class="bg-bg text-text font-sans antialiased flex flex-col min-h-screen">
45-
<NavBar locale="zh" currentPath="/ai/tools" />
4619

47-
<main class="max-w-[780px] mx-auto px-4 sm:px-6 pt-[72px] sm:pt-[80px] pb-[40px] sm:pb-[60px] flex-1 w-full animate-fade-in">
20+
<ContentLayout title={post.data.title} description={post.data.summary || post.data.title} locale="zh" currentPath="/ai/tools">
21+
<main class="max-w-[780px] mx-auto px-4 sm:px-6 pt-[72px] sm:pt-[80px] pb-[40px] sm:pb-[60px] flex-1 w-full animate-fade-in">
4822
<a href="/ai" class="inline-flex items-center gap-1.5 text-text-dim text-xs sm:text-sm mb-4 sm:mb-6 px-3 sm:px-4 py-2 rounded-lg border border-border bg-bg-card hover:text-text hover:bg-accent-glow hover:border-accent transition-all no-underline">
4923
{t("article.back_rankings")}
5024
</a>
@@ -63,7 +37,19 @@ const { Content } = await post.render();
6337
<Content />
6438
</article>
6539
</main>
66-
67-
<Footer />
68-
</body>
69-
</html>
40+
<script is:inline>
41+
window.copyArticle = function() {
42+
var el = document.getElementById('md-source');
43+
if (!el) return;
44+
navigator.clipboard.writeText(el.value).then(function() {
45+
var btn = document.querySelector('.copy-btn');
46+
if (btn) {
47+
btn.innerHTML = '<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><path d="M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z"></path></svg>';
48+
setTimeout(function() {
49+
btn.innerHTML = '<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 010 1.5h-1.5a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-1.5a.75.75 0 011.5 0v1.5A1.75 1.75 0 019.25 16h-7.5A1.75 1.75 0 010 14.25zM5 1.75C0 .784.784 0 1.75 0h7.5C10.216 0 11 .784 11 1.75v7.5A1.75 1.75 0 019.25 11h-7.5A1.75 1.75 0 010 9.25zm1.75-.25a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-7.5a.25.25 0 00-.25-.25z"></path></svg>';
50+
}, 1500);
51+
}
52+
});
53+
};
54+
</script>
55+
</ContentLayout>

src/pages/daily/[slug].astro

Lines changed: 19 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
---
2-
import NavBar from "../../components/NavBar.astro";
3-
import Footer from "../../components/Footer.astro";
2+
import ContentLayout from "../../layouts/ContentLayout.astro";
43
import "../../styles/global.css";
54
import { t } from "../../i18n/utils.ts";
65
import { getCollection } from "astro:content";
@@ -20,34 +19,9 @@ const dateParts = post.data.date.split("-");
2019
const formattedDate = `${dateParts[0]} 年 ${parseInt(dateParts[1])} 月 ${parseInt(dateParts[2])} 日`;
2120
---
2221

23-
<html lang="zh">
24-
<head>
25-
<meta charset="UTF-8" />
26-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
27-
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚡</text></svg>" />
28-
<title>{post.data.title} | Awesome AI</title>
29-
<meta name="description" content={post.data.summary || post.data.title} />
30-
<script type="module">
31-
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
32-
mermaid.initialize({ theme: 'dark', securityLevel: 'loose' });
33-
34-
document.addEventListener('DOMContentLoaded', async () => {
35-
const pres = document.querySelectorAll('pre[data-language="mermaid"]');
36-
for (const pre of pres) {
37-
const rawText = pre.textContent;
38-
const div = document.createElement('div');
39-
div.className = 'mermaid';
40-
div.textContent = rawText;
41-
pre.replaceWith(div);
42-
}
43-
await mermaid.run({ nodes: document.querySelectorAll('.mermaid') });
44-
});
45-
</script>
46-
</head>
47-
<body class="bg-bg text-text font-sans antialiased flex flex-col min-h-screen">
48-
<NavBar locale="zh" currentPath="/daily" />
4922

50-
<main class="max-w-[780px] mx-auto px-4 sm:px-6 pt-[72px] sm:pt-[80px] pb-[40px] sm:pb-[60px] flex-1 w-full animate-fade-in">
23+
<ContentLayout title={post.data.title} description={post.data.summary || post.data.title} locale="zh" currentPath="/daily">
24+
<main class="max-w-[780px] mx-auto px-4 sm:px-6 pt-[72px] sm:pt-[80px] pb-[40px] sm:pb-[60px] flex-1 w-full animate-fade-in">
5125
<a href="/daily" class="inline-flex items-center gap-1.5 text-text-dim text-xs sm:text-sm mb-4 sm:mb-6 px-3 sm:px-4 py-2 rounded-lg border border-border bg-bg-card hover:text-text hover:bg-accent-glow hover:border-accent transition-all no-underline">
5226
{t("article.back")}
5327
</a>
@@ -69,23 +43,19 @@ const formattedDate = `${dateParts[0]} 年 ${parseInt(dateParts[1])} 月 ${parse
6943
<Content />
7044
</article>
7145
</main>
72-
73-
<Footer />
74-
75-
<script is:inline>
76-
window.copyArticle = function() {
77-
var el = document.getElementById('md-source');
78-
if (!el) return;
79-
navigator.clipboard.writeText(el.value).then(function() {
80-
var btn = document.querySelector('.copy-btn');
81-
if (btn) {
82-
btn.innerHTML = '<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><path d="M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z"></path></svg>';
83-
setTimeout(function() {
84-
btn.innerHTML = '<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 010 1.5h-1.5a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-1.5a.75.75 0 011.5 0v1.5A1.75 1.75 0 019.25 16h-7.5A1.75 1.75 0 010 14.25zM5 1.75C0 .784.784 0 1.75 0h7.5C10.216 0 11 .784 11 1.75v7.5A1.75 1.75 0 019.25 11h-7.5A1.75 1.75 0 010 9.25zm1.75-.25a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-7.5a.25.25 0 00-.25-.25z"></path></svg>';
85-
}, 1500);
86-
}
87-
});
88-
};
89-
</script>
90-
</body>
91-
</html>
46+
<script is:inline>
47+
window.copyArticle = function() {
48+
var el = document.getElementById('md-source');
49+
if (!el) return;
50+
navigator.clipboard.writeText(el.value).then(function() {
51+
var btn = document.querySelector('.copy-btn');
52+
if (btn) {
53+
btn.innerHTML = '<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><path d="M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z"></path></svg>';
54+
setTimeout(function() {
55+
btn.innerHTML = '<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 010 1.5h-1.5a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-1.5a.75.75 0 011.5 0v1.5A1.75 1.75 0 019.25 16h-7.5A1.75 1.75 0 010 14.25zM5 1.75C0 .784.784 0 1.75 0h7.5C10.216 0 11 .784 11 1.75v7.5A1.75 1.75 0 019.25 11h-7.5A1.75 1.75 0 010 9.25zm1.75-.25a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-7.5a.25.25 0 00-.25-.25z"></path></svg>';
56+
}, 1500);
57+
}
58+
});
59+
};
60+
</script>
61+
</ContentLayout>

src/pages/en/ai/models/[slug].astro

Lines changed: 19 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
---
2-
import NavBar from "../../../../components/NavBar.astro";
3-
import Footer from "../../../../components/Footer.astro";
2+
import ContentLayout from "../../../../layouts/ContentLayout.astro";
43
import "../../../../styles/global.css";
54
import { t } from "../../../../i18n/utils.ts";
65
import { getCollection } from "astro:content";
@@ -18,34 +17,9 @@ const { post } = Astro.props;
1817
const { Content } = await post.render();
1918
---
2019

21-
<html lang="en">
22-
<head>
23-
<meta charset="UTF-8" />
24-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
25-
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚡</text></svg>" />
26-
<title>{post.data.title} | Awesome AI</title>
27-
<meta name="description" content={post.data.summary || post.data.title} />
28-
<script type="module">
29-
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
30-
mermaid.initialize({ theme: 'dark', securityLevel: 'loose' });
31-
32-
document.addEventListener('DOMContentLoaded', async () => {
33-
const pres = document.querySelectorAll('pre[data-language="mermaid"]');
34-
for (const pre of pres) {
35-
const rawText = pre.textContent;
36-
const div = document.createElement('div');
37-
div.className = 'mermaid';
38-
div.textContent = rawText;
39-
pre.replaceWith(div);
40-
}
41-
await mermaid.run({ nodes: document.querySelectorAll('.mermaid') });
42-
});
43-
</script>
44-
</head>
45-
<body class="bg-bg text-text font-sans antialiased flex flex-col min-h-screen">
46-
<NavBar locale="en" currentPath="/en/ai" />
4720

48-
<main class="max-w-[780px] mx-auto px-4 sm:px-6 pt-[72px] sm:pt-[80px] pb-[40px] sm:pb-[60px] flex-1 w-full animate-fade-in">
21+
<ContentLayout title={post.data.title} description={post.data.summary || post.data.title} locale="en" currentPath="/en/ai">
22+
<main class="max-w-[780px] mx-auto px-4 sm:px-6 pt-[72px] sm:pt-[80px] pb-[40px] sm:pb-[60px] flex-1 w-full animate-fade-in">
4923
<a href="/en/ai" class="inline-flex items-center gap-1.5 text-text-dim text-xs sm:text-sm mb-4 sm:mb-6 px-3 sm:px-4 py-2 rounded-lg border border-border bg-bg-card hover:text-text hover:bg-accent-glow hover:border-accent transition-all no-underline">
5024
{t("article.back_rankings", "en")}
5125
</a>
@@ -79,7 +53,19 @@ const { Content } = await post.render();
7953
<Content />
8054
</article>
8155
</main>
82-
83-
<Footer />
84-
</body>
85-
</html>
56+
<script is:inline>
57+
window.copyArticle = function() {
58+
var el = document.getElementById('md-source');
59+
if (!el) return;
60+
navigator.clipboard.writeText(el.value).then(function() {
61+
var btn = document.querySelector('.copy-btn');
62+
if (btn) {
63+
btn.innerHTML = '<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><path d="M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z"></path></svg>';
64+
setTimeout(function() {
65+
btn.innerHTML = '<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 010 1.5h-1.5a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-1.5a.75.75 0 011.5 0v1.5A1.75 1.75 0 019.25 16h-7.5A1.75 1.75 0 010 14.25zM5 1.75C0 .784.784 0 1.75 0h7.5C10.216 0 11 .784 11 1.75v7.5A1.75 1.75 0 019.25 11h-7.5A1.75 1.75 0 010 9.25zm1.75-.25a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-7.5a.25.25 0 00-.25-.25z"></path></svg>';
66+
}, 1500);
67+
}
68+
});
69+
};
70+
</script>
71+
</ContentLayout>

0 commit comments

Comments
 (0)