diff --git a/package.json b/package.json
index 4e8dfef..8bcaafa 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
"@astrojs/node": "11.0.2",
"@astrojs/solid-js": "^5.1.3",
"astro": "^7.0.6",
+ "clipboard": "^2.0.11",
"dayjs": "^1.11.21",
"github-markdown-css": "^5.9.0",
"jsdom": "^29.1.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 81afef9..0e43751 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -17,6 +17,9 @@ importers:
astro:
specifier: ^7.0.6
version: 7.0.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.2)(@types/node@26.1.0)(rollup@4.62.2)
+ clipboard:
+ specifier: ^2.0.11
+ version: 2.0.11
dayjs:
specifier: ^1.11.21
version: 1.11.21
@@ -1265,6 +1268,9 @@ packages:
resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==}
engines: {node: '>=8'}
+ clipboard@2.0.11:
+ resolution: {integrity: sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==}
+
clsx@2.1.1:
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
engines: {node: '>=6'}
@@ -1337,6 +1343,9 @@ packages:
defu@6.1.7:
resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==}
+ delegate@3.2.0:
+ resolution: {integrity: sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==}
+
depd@2.0.0:
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
engines: {node: '>= 0.8'}
@@ -1487,6 +1496,9 @@ packages:
github-slugger@2.0.0:
resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==}
+ good-listener@1.2.2:
+ resolution: {integrity: sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==}
+
h3@1.15.11:
resolution: {integrity: sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==}
@@ -1877,6 +1889,9 @@ packages:
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
engines: {node: '>=v12.22.7'}
+ select@1.1.2:
+ resolution: {integrity: sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==}
+
semver@6.3.1:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
hasBin: true
@@ -1956,6 +1971,9 @@ packages:
symbol-tree@3.2.4:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
+ tiny-emitter@2.1.0:
+ resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==}
+
tiny-inflate@1.0.3:
resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==}
@@ -3295,6 +3313,12 @@ snapshots:
ci-info@4.4.0: {}
+ clipboard@2.0.11:
+ dependencies:
+ good-listener: 1.2.2
+ select: 1.1.2
+ tiny-emitter: 2.1.0
+
clsx@2.1.1: {}
comma-separated-tokens@2.0.3: {}
@@ -3356,6 +3380,8 @@ snapshots:
defu@6.1.7: {}
+ delegate@3.2.0: {}
+
depd@2.0.0: {}
dequal@2.0.3: {}
@@ -3515,6 +3541,10 @@ snapshots:
github-slugger@2.0.0: {}
+ good-listener@1.2.2:
+ dependencies:
+ delegate: 3.2.0
+
h3@1.15.11:
dependencies:
cookie-es: 1.2.3
@@ -3931,6 +3961,8 @@ snapshots:
dependencies:
xmlchars: 2.2.0
+ select@1.1.2: {}
+
semver@6.3.1: {}
semver@7.8.5: {}
@@ -4048,6 +4080,8 @@ snapshots:
symbol-tree@3.2.4: {}
+ tiny-emitter@2.1.0: {}
+
tiny-inflate@1.0.3: {}
tinyclip@0.1.15: {}
diff --git a/src/assets/icon/back-top.svg b/src/assets/icon/back-top.svg
deleted file mode 100644
index 16aa9dd..0000000
--- a/src/assets/icon/back-top.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/assets/icon/calendar.png b/src/assets/icon/calendar.png
deleted file mode 100644
index d16ac5b..0000000
Binary files a/src/assets/icon/calendar.png and /dev/null differ
diff --git a/src/assets/icon/client.png b/src/assets/icon/client.png
deleted file mode 100644
index da01127..0000000
Binary files a/src/assets/icon/client.png and /dev/null differ
diff --git a/src/assets/icon/eye.png b/src/assets/icon/eye.png
deleted file mode 100644
index 58f33e8..0000000
Binary files a/src/assets/icon/eye.png and /dev/null differ
diff --git a/src/assets/icon/folder.png b/src/assets/icon/folder.png
deleted file mode 100644
index ca84780..0000000
Binary files a/src/assets/icon/folder.png and /dev/null differ
diff --git a/src/assets/icon/github.svg b/src/assets/icon/github.svg
deleted file mode 100644
index 91ade14..0000000
--- a/src/assets/icon/github.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/assets/icon/tag.png b/src/assets/icon/tag.png
deleted file mode 100644
index dccd2d2..0000000
Binary files a/src/assets/icon/tag.png and /dev/null differ
diff --git a/src/components/BackTop.astro b/src/components/BackTop.astro
deleted file mode 100644
index dbca6e5..0000000
--- a/src/components/BackTop.astro
+++ /dev/null
@@ -1,69 +0,0 @@
----
-// 后面的 ?url 是 一个导入查询(import query),表示:不要把 SVG 当作组件或源码处理,而是返回这个资源的 URL 字符串。
-import backTopIcon from "@/assets/icon/back-top.svg?url";
----
-
-
-
-
-
-
diff --git a/src/components/BaseFooter.astro b/src/components/BaseFooter.astro
deleted file mode 100644
index 6bac2c8..0000000
--- a/src/components/BaseFooter.astro
+++ /dev/null
@@ -1,46 +0,0 @@
----
-import { metadata } from "../consts";
-
-const year = new Date().getFullYear();
----
-
-
-
-
-
diff --git a/src/components/BaseHeader.astro b/src/components/BaseHeader.astro
deleted file mode 100644
index 73f3fc7..0000000
--- a/src/components/BaseHeader.astro
+++ /dev/null
@@ -1,113 +0,0 @@
----
-import {metadata} from "../consts";
-import ThemeIcon from './ThemeIcon.astro'
-import GitHubIcon from '@/assets/icon/github.svg?url'
----
-
-
-
-
diff --git a/src/components/IndexPage.astro b/src/components/IndexPage.astro
new file mode 100644
index 0000000..49ea51c
--- /dev/null
+++ b/src/components/IndexPage.astro
@@ -0,0 +1,107 @@
+---
+import {siteConfig} from "@/consts.ts";
+import PostList from "@/components/Post/List.astro";
+import Pagination from "@/components/Post/Pagination.astro";
+import type {Post} from "@/types";
+
+const {siteTitle} = siteConfig;
+
+interface Props {
+ posts: Post[]
+ page: number
+ totalPage: number
+}
+
+const {posts, page, totalPage} = Astro.props
+---
+
+
+
+
diff --git a/src/components/IndexSidebar.astro b/src/components/IndexSidebar.astro
deleted file mode 100644
index c672f71..0000000
--- a/src/components/IndexSidebar.astro
+++ /dev/null
@@ -1,152 +0,0 @@
----
-import Folder from "@/assets/icon/folder.png?url";
-import TagIcon from "@/assets/icon/tag.png?url";
-import type { CategoriesStats,Tag } from "@/types";
-import { createHttp } from "@/lib/request";
-const http = createHttp(Astro.request.headers);
-
-const { data: categories } =
- await http.get>("categories/stats");
-
-const { data: tags } =
- await http.get>("posts/tags");
----
-
-
-
-
diff --git a/src/components/IndexView.astro b/src/components/IndexView.astro
deleted file mode 100644
index 0a21d5d..0000000
--- a/src/components/IndexView.astro
+++ /dev/null
@@ -1,46 +0,0 @@
----
-import type {Post} from "@/types/post";
-import IndexSidebar from "@/components/IndexSidebar.astro";
-import PostList from "@/components/Post/List.astro";
-import Pagination from "@/components/Post/Pagination.astro";
-
-interface Props {
- posts: Post[];
- page: number;
- totalPage: number;
-}
-
-const {posts, page, totalPage} = Astro.props;
----
-
-
-
-
diff --git a/src/components/LazyImage.astro b/src/components/LazyImage.astro
index 8ec026b..45d19a2 100644
--- a/src/components/LazyImage.astro
+++ b/src/components/LazyImage.astro
@@ -52,7 +52,6 @@ const {
.placeholder {
position: absolute;
inset: 0;
- background: #f3f4f6;
background-image: linear-gradient(
90deg,
#f3f4f6 0%,
diff --git a/src/components/Post/Item.astro b/src/components/Post/Item.astro
deleted file mode 100644
index aad7de3..0000000
--- a/src/components/Post/Item.astro
+++ /dev/null
@@ -1,185 +0,0 @@
----
-import type {Post} from '@/types';
-import Eye from "@/assets/icon/eye.png?url";
-import Folder from "@/assets/icon/folder.png?url";
-import Calendar from "@/assets/icon/calendar.png?url";
-import LazyImage from "@/components/LazyImage.astro";
-import {formatDatetime} from "@/lib/format.ts";
-
-interface Props {
- post: Post
-}
-
-const {post} = Astro.props;
-const published_at = formatDatetime(post.published_at, 'YYYY-MM-DD')
----
-
-
-
-
-
-
-
-
-
-
- {published_at}
-
-
-
- {post.category_name}
-
-
-
- {post.view_count}
-
-
-
- {post.title}
-
-
- {post.summary}
-
-
-
-
-
-
-
diff --git a/src/components/Post/List.astro b/src/components/Post/List.astro
index 80b39c3..771a3c9 100644
--- a/src/components/Post/List.astro
+++ b/src/components/Post/List.astro
@@ -1,37 +1,70 @@
---
-import type {Post} from "@/types/post";
-import PostItem from '@/components/Post/Item.astro'
+import type {Post} from "@/types";
+import {formatDatetime} from '@/lib/format.ts'
interface Props {
posts: Post[]
}
-
const {posts} = Astro.props
---
-
-
- {posts.map(post => (
-
- ))}
-
-
+
+ {posts.map(item => (
+
+
{formatDatetime(item.published_at, 'YY.MM.DD')}
+
+
→
+
+ ))}
+
diff --git a/src/components/Post/Pagination.astro b/src/components/Post/Pagination.astro
index bd46efc..16a3059 100644
--- a/src/components/Post/Pagination.astro
+++ b/src/components/Post/Pagination.astro
@@ -10,58 +10,40 @@ const {page, totalPage} = Astro.props
{totalPage > 1 &&
}
diff --git a/src/components/ThemeIcon.astro b/src/components/ThemeIcon.astro
deleted file mode 100644
index 4f84c41..0000000
--- a/src/components/ThemeIcon.astro
+++ /dev/null
@@ -1,75 +0,0 @@
----
----
-
-
-
-
-
diff --git a/src/consts.ts b/src/consts.ts
index ab3587a..85cf289 100644
--- a/src/consts.ts
+++ b/src/consts.ts
@@ -1,5 +1,6 @@
-export const metadata = {
+export const siteConfig = {
site: "https://ua42.com",
siteTitle: "Uroboros",
- postPageSize: 6
+ postPageSize: 7,
+ description: "Uroboros is a blog about programming, technology, and life."
}
diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro
index 9f9820d..cb4e2e9 100644
--- a/src/layouts/Layout.astro
+++ b/src/layouts/Layout.astro
@@ -1,42 +1,48 @@
---
import "@/styles/global.css";
-
-import BaseHeader from "@/components/BaseHeader.astro";
-import BaseFooter from "@/components/BaseFooter.astro";
-import BackTop from "@/components/BackTop.astro";
-
-import { metadata } from "@/consts.ts";
-const { siteTitle } = metadata;
+import {siteConfig} from "@/consts.ts";
+const {siteTitle} = siteConfig;
interface Props {
title?: string;
description?: string;
}
-const { title, description } = Astro.props;
+const {title, description} = Astro.props;
const pageTitle = title ? `${title} - ${siteTitle}` : siteTitle;
---
-
-
-
-
-
-
-
- {pageTitle}
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+ {pageTitle}
+
+
+
+
+
+
+
+
diff --git a/src/lib/highlight.js b/src/lib/highlight.js
deleted file mode 100644
index b3c5501..0000000
--- a/src/lib/highlight.js
+++ /dev/null
@@ -1,85 +0,0 @@
-import {JSDOM} from 'jsdom'
-import {createHighlighter} from 'shiki'
-
-const highlighter = await createHighlighter({
- themes: ['github-dark-dimmed'],
- langs: [
- "bash",
- "json",
- "yaml",
- "xml",
- "html",
- "css",
- "scss",
- "javascript",
- "typescript",
- "jsx",
- "tsx",
- "vue",
- "markdown",
- "sql",
- "graphql",
- "python",
- "java",
- "go",
- "rust",
- "c",
- "cpp",
- "csharp",
- "php",
- "ruby",
- "kotlin",
- "swift",
- "dockerfile",
- "nginx"
- ]
-})
-
-const resolveLang = (lang) => {
- const supported = highlighter.getLoadedLanguages?.() || []
- if (!lang) return 'text'
- if (supported.includes(lang)) return lang
- return 'text'
-}
-
-export const highlightHtmlWithJsdom = async (rawHtml) => {
- const dom = new JSDOM(rawHtml)
- const document = dom.window.document
-
- const codeElements = document.querySelectorAll('pre code')
-
- for (const codeEl of codeElements) {
- const preEl = codeEl.parentElement
- if (!preEl || preEl.tagName !== 'PRE') continue
-
- const codeContent = codeEl.textContent?.trim() || ''
-
- // 提取语言
- let lang = 'text'
- for (const cls of codeEl.classList) {
- if (cls.startsWith('language-')) {
- lang = cls.slice('language-'.length)
- break
- }
- }
-
- lang = resolveLang(lang)
-
- // 生成高亮 HTML
- const highlighted = highlighter.codeToHtml(codeContent, {
- lang,
- theme: 'github-dark-dimmed',
- })
-
- // 替换 DOM
- const wrapper = document.createElement('div')
- wrapper.innerHTML = highlighted.trim()
-
- const newNode = wrapper.firstChild
- if (newNode && preEl.parentNode) {
- preEl.parentNode.replaceChild(newNode, preEl)
- }
- }
-
- return dom.serialize()
-}
\ No newline at end of file
diff --git a/src/lib/processHtml.ts b/src/lib/processHtml.ts
new file mode 100644
index 0000000..8cbe07f
--- /dev/null
+++ b/src/lib/processHtml.ts
@@ -0,0 +1,120 @@
+import {JSDOM} from 'jsdom'
+import {createHighlighter} from 'shiki'
+
+const DEFAULT_THEME = 'github-dark-dimmed'
+
+const highlighter = await createHighlighter({
+ themes: [DEFAULT_THEME],
+ langs: [
+ "bash",
+ "json",
+ "yaml",
+ "xml",
+ "html",
+ "css",
+ "scss",
+ "javascript",
+ "typescript",
+ "jsx",
+ "tsx",
+ "vue",
+ "markdown",
+ "sql",
+ "python",
+ "go",
+ "dockerfile",
+ "nginx"
+ ]
+})
+
+const resolveLang = (lang: string) => {
+ const supported = highlighter.getLoadedLanguages?.() || []
+ if (!lang) return 'text'
+ if (supported.includes(lang)) return lang
+ return 'text'
+}
+
+export const processHtmlWithJsdom = async (rawHtml: string) => {
+
+ const dom = new JSDOM(rawHtml)
+ const document = dom.window.document
+
+ // 高亮处理代码块
+ const codeBlocks = document.querySelectorAll('pre code')
+
+ for (const codeBlock of codeBlocks) {
+ const preEl = codeBlock.parentElement
+ if (!preEl || preEl.tagName !== 'PRE') continue
+
+ const codeContent = codeBlock.textContent || ''
+ let lang = 'text'
+ for (const cls of codeBlock.classList) {
+ if (cls.startsWith('language-')) {
+ lang = cls.slice('language-'.length)
+ break
+ }
+ }
+
+ lang = resolveLang(lang)
+
+ // 高亮
+ const highlighted = highlighter.codeToHtml(codeContent, {
+ lang,
+ theme: DEFAULT_THEME,
+ })
+
+ // 替换 DOM
+ const wrapper = document.createElement('div')
+ wrapper.innerHTML = highlighted.trim()
+
+ const newNode = wrapper.firstElementChild
+ if (newNode && preEl.parentNode) {
+ const container = document.createElement('div')
+ container.className = 'code-block'
+
+ const header = document.createElement('div')
+ header.className = 'code-header'
+
+ const headerLeft = document.createElement('div')
+ headerLeft.className = 'code-header-left'
+
+ const headerRight = document.createElement('div')
+ headerRight.className = 'code-header-right'
+
+ // 装饰点
+ const actions = document.createElement('div')
+ actions.className = 'code-actions'
+
+ const close = document.createElement('span')
+ close.className = 'dot close'
+
+ const minimize = document.createElement('span')
+ minimize.className = 'dot minimize'
+
+ const maximize = document.createElement('span')
+ maximize.className = 'dot maximize'
+
+ actions.append(close, minimize, maximize)
+
+ // 显示语言
+ const langDisplay = document.createElement('div')
+ langDisplay.className = 'code-language'
+ langDisplay.textContent = lang ? lang : 'text'
+
+ const copyBtn = document.createElement('div')
+ copyBtn.className = 'code-copy-btn'
+ copyBtn.textContent = '复制代码'
+
+ headerLeft.append(actions, langDisplay)
+ headerRight.append(copyBtn)
+
+ header.append(headerLeft, headerRight)
+
+ container.append(header, newNode)
+
+ preEl.parentNode.replaceChild(container, preEl)
+ }
+ }
+
+ return dom.serialize()
+}
diff --git a/src/pages/404.astro b/src/pages/404.astro
index f91ecdd..f94a2f7 100644
--- a/src/pages/404.astro
+++ b/src/pages/404.astro
@@ -2,28 +2,6 @@
import Layout from "@/layouts/Layout.astro";
---
-
-
+
+
-
-
diff --git a/src/pages/archive.astro b/src/pages/archive.astro
index dd1465a..e69de29 100644
--- a/src/pages/archive.astro
+++ b/src/pages/archive.astro
@@ -1,198 +0,0 @@
----
-import Layout from "@/layouts/Layout.astro";
-import type {Archive} from "@/types/archive";
-import {createHttp} from "@/lib/request";
-
-const http = createHttp(Astro.request.headers);
-
-const {data} = await http.get>("posts/archives");
----
-
-
-
-
- {
- data.map((year) => (
-
-
- {year.year}
- 共 {year.total} 篇
-
-
- {year.list.map((month) => (
-
-
{month.month} 月
-
- {month.list.map((post) => (
- -
-
- {post.published_at_display}
-
- {post.title}
- {post.category_name}
-
- ))}
-
-
- ))}
-
-
- ))
- }
-
-
-
-
-
diff --git a/src/pages/embed/gitea.astro b/src/pages/embed/gitea.astro
index ecf3b48..3b582d4 100644
--- a/src/pages/embed/gitea.astro
+++ b/src/pages/embed/gitea.astro
@@ -1,6 +1,5 @@
---
const noindex = true;
-import {metadata} from '@/consts.ts'
---
diff --git a/src/pages/index.astro b/src/pages/index.astro
index e2c08f3..2a093e5 100644
--- a/src/pages/index.astro
+++ b/src/pages/index.astro
@@ -1,25 +1,23 @@
---
import Layout from "@/layouts/Layout.astro";
-import IndexView from "@/components/IndexView.astro";
-import type { Post } from "@/types/post";
-import { createHttp } from "@/lib/request";
-import { metadata } from "@/consts.ts";
+import {siteConfig} from "@/consts.ts";
+import {createHttp} from "@/lib/request.ts";
+import type {Post} from "@/types";
+import IndexPage from "@/components/IndexPage.astro";
+
+const {postPageSize, description} = siteConfig;
const http = createHttp(Astro.request.headers);
-const { list, total } = await http.get>("posts", {
+const {list, total} = await http.get>("posts", {
searchParams: {
page: 1,
- page_size: metadata.postPageSize,
+ page_size: postPageSize,
},
});
-const totalPage = Math.ceil(total / metadata.postPageSize);
+const totalPage = Math.ceil(total / postPageSize);
---
-
-
+
+
diff --git a/src/pages/page/[...page].astro b/src/pages/page/[...page].astro
index a1b2f9b..4fa2c03 100644
--- a/src/pages/page/[...page].astro
+++ b/src/pages/page/[...page].astro
@@ -1,36 +1,34 @@
---
import Layout from "@/layouts/Layout.astro";
-import type { Post } from "@/types/post";
-import { createHttp } from "@/lib/request";
-import { metadata } from "@/consts.ts";
-import IndexView from "@/components/IndexView.astro";
+import {siteConfig} from "@/consts.ts";
+import {createHttp} from "@/lib/request.ts";
+import type {Post} from "@/types";
+import IndexPage from "@/components/IndexPage.astro";
+const {page} = Astro.params;
-const { page } = Astro.params;
+const pageInt = Number(page);
-if (isNaN(Number(page))) {
+if (isNaN(pageInt) || pageInt <= 1) {
return Astro.redirect("/");
}
+const {postPageSize,description} = siteConfig;
+
const http = createHttp(Astro.request.headers);
-const { list, total } = await http.get>("posts", {
+const {list, total} = await http.get>("posts", {
searchParams: {
- page,
- page_size: metadata.postPageSize,
+ page: pageInt,
+ page_size: postPageSize,
},
});
+const totalPage = Math.ceil(total / postPageSize);
-const totalPage = Math.ceil(total / metadata.postPageSize);
-
-if (totalPage < Number(page)) {
+if (totalPage < pageInt) {
return Astro.redirect("/404");
}
---
-
-
+
+
diff --git a/src/pages/post/[...slug].astro b/src/pages/post/[...slug].astro
index 416ab79..efff5d3 100644
--- a/src/pages/post/[...slug].astro
+++ b/src/pages/post/[...slug].astro
@@ -3,159 +3,186 @@ import "@/styles/github-markdown.css";
import "@/styles/github-markdown-reset.css";
import "photoswipe/dist/photoswipe.css";
-import Layout from "@/layouts/Layout.astro";
-import { createHttp } from "@/lib/request.ts";
-import { highlightHtmlWithJsdom } from "@/lib/highlight.js";
-import { formatDatetime } from "@/lib/format.ts";
-import type { Post } from "@/types";
+import Layout from '@/layouts/Layout.astro';
+import {createHttp} from "@/lib/request.ts";
+import type {Post} from "@/types";
+import {processHtmlWithJsdom} from "@/lib/processHtml.ts";
+import {siteConfig} from '@/consts'
+import {formatDatetime} from "@/lib/format.ts";
-const { slug } = Astro.params;
+const {siteTitle} = siteConfig
-let data: Post;
+const {slug} = Astro.params;
const http = createHttp(Astro.request.headers);
+let data: Post;
+
try {
const res = await http.get>(`posts/${slug}`);
data = res.data;
} catch (error) {
return Astro.redirect("/404");
}
-const content = await highlightHtmlWithJsdom(data.content);
-const published_at = formatDatetime(data.published_at, "YYYY 年 M 月 D 日");
+
+const content = await processHtmlWithJsdom(data.content);
+const published_at = formatDatetime(data.published_at, "YYYY.MM.DD");
---
-
-
-
-
{data.title}
+
+
+
-
-
-
- {published_at}
+
+
+ {data.title}
-
-
+
{data.summary}
+
+
+
+
+
+ {
+ data.tags.length > 0 &&
+
+ {data.tags.map(tag => {tag.name})}
+
+ }
+
+
+
diff --git a/src/pages/search.astro b/src/pages/search.astro
deleted file mode 100644
index 354062f..0000000
--- a/src/pages/search.astro
+++ /dev/null
@@ -1,214 +0,0 @@
----
-import Layout from '@/layouts/Layout.astro'
-import type {CategoriesStats, Post, Tag} from "@/types";
-import {createHttp} from "@/lib/request.ts";
-import PostList from "@/components/Post/List.astro";
-
-const http = createHttp(Astro.request.headers);
-
-const searchParams = Astro.url.searchParams;
-const t = searchParams.get("t") ?? "";
-const c = searchParams.get("c") ?? "";
-
-const [{data: categories}, {data: tags}] = await Promise.all([
- http.get
>("categories/stats"),
- http.get>("posts/tags"),
-] as const)
-
-let f_count = 0
-const findCategory = categories.find(item => {
- if (item.code === c) {
- f_count++
- }
- return item.code === c
-})
-
-const findTag = tags.find(item => {
- if (item.code === t) {
- f_count++
- }
- return item.code === t
-})
-
-// 搜索条件必须匹配 不允许非法值
-const {data:posts} = await http.get>("posts/search", {
- searchParams: {
- category_code: findCategory?.code ?? "",
- tag_code: findTag?.code ?? "",
- }
-})
-
-const count = posts.length
-
-const pageTitle =
- f_count === 0 ? "所有文章" : [findCategory?.name, findTag?.name]
- .filter(Boolean)
- .join(" · ")
-
-const pageDescription =
- f_count === 0
- ? "浏览全部文章,按分类和标签筛选你感兴趣的内容。"
- : `浏览${pageTitle}相关文章,共 ${count} 篇。`
-
-const buildSearchUrl = (type: 'category' | 'tag', code: string) => {
- const params = new URLSearchParams()
-
- if (findTag?.code) {
- params.set('t', findTag.code)
- }
-
- if (findCategory?.code) {
- params.set('c', findCategory.code)
- }
-
- if (type === 'category') {
- if (c === code) {
- params.delete('c')
- } else {
- params.set('c', code)
- }
- } else {
- if (t === code) {
- params.delete('t')
- } else {
- params.set('t', code)
- }
- }
-
- const query = params.toString()
-
- return query ? `/search?${query}` : '/search'
-}
----
-
-
-
-
-
-
- {posts.length > 0 ?
-
:
- 没有找到相关文章
}
-
-
-
-
-
diff --git a/src/pages/sitemap-pages.xml.ts b/src/pages/sitemap-pages.xml.ts
index 63b1447..ae144e9 100644
--- a/src/pages/sitemap-pages.xml.ts
+++ b/src/pages/sitemap-pages.xml.ts
@@ -1,12 +1,10 @@
import type {APIRoute} from 'astro';
-import {metadata} from "@/consts";
+import {siteConfig} from "@/consts";
-const site = metadata.site
+const site = siteConfig.site
const staticUrls = [
'',
- '/archive',
- '/search',
];
export const GET: APIRoute = async () => {
diff --git a/src/pages/sitemap-posts.xml.ts b/src/pages/sitemap-posts.xml.ts
index fd9ec42..ac63499 100644
--- a/src/pages/sitemap-posts.xml.ts
+++ b/src/pages/sitemap-posts.xml.ts
@@ -1,10 +1,10 @@
import type {APIRoute} from 'astro';
-import {metadata} from "@/consts";
+import {siteConfig} from "@/consts";
import {createHttp} from "@/lib/request.ts";
import type {PostForSitemap} from "@/types";
import dayjs from "dayjs";
-const site = metadata.site
+const site =siteConfig.site
export const GET: APIRoute = async (context) => {
const http = createHttp(context.request.headers);
diff --git a/src/pages/sitemap.xml.ts b/src/pages/sitemap.xml.ts
index 46d1ece..04c2fe7 100644
--- a/src/pages/sitemap.xml.ts
+++ b/src/pages/sitemap.xml.ts
@@ -1,7 +1,7 @@
import type {APIRoute} from 'astro';
-import {metadata} from "@/consts";
+import {siteConfig} from "@/consts";
-const site = metadata.site
+const site = siteConfig.site
export const GET: APIRoute = () => {
const xml = `
diff --git a/src/styles/css-reset.css b/src/styles/css-reset.css
index 7c06853..cbe67dd 100644
--- a/src/styles/css-reset.css
+++ b/src/styles/css-reset.css
@@ -1,10 +1,30 @@
* {
margin: 0;
padding: 0;
- box-sizing: border-box;
}
a {
color: inherit;
text-decoration: none;
}
+
+::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+
+/* 滚动条轨道 */
+::-webkit-scrollbar-track {
+ background: transparent;
+}
+
+/* 滚动条滑块 */
+::-webkit-scrollbar-thumb {
+ background: #cbd5e1;
+ border-radius: 999px;
+}
+
+/* 鼠标悬停 */
+::-webkit-scrollbar-thumb:hover {
+ background: #94a3b8;
+}
diff --git a/src/styles/github-markdown-reset.css b/src/styles/github-markdown-reset.css
index 60e9870..91d41b9 100644
--- a/src/styles/github-markdown-reset.css
+++ b/src/styles/github-markdown-reset.css
@@ -1,5 +1,5 @@
-.markdown-body pre code{
- font-size: 1.1em;
+.markdown-body pre code {
+ font-size: 1.15em;
}
.markdown-body h1,
@@ -8,11 +8,77 @@
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
- border-bottom: none ;
+ border-bottom: none;
}
@media (max-width: 768px) {
- .markdown-body pre code{
+ .markdown-body pre code {
font-size: inherit;
}
}
+
+.markdown-body .highlight pre,
+.markdown-body pre {
+ border-radius: 0 0 6px 6px;
+}
+
+.markdown-body img {
+ margin: 12px 0 12px;
+}
+
+.code-block {
+
+}
+
+.code-header {
+ height: 36px;
+ background: #f5f5f5;
+ display: flex;
+ justify-content: space-between;
+ color: var(--text-primary);
+ align-items: center;
+ padding: 0 12px;
+ font-size: 14px;
+ border-radius: 6px 6px 0 0;
+}
+
+.code-header-left {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.code-actions {
+ display: flex;
+ align-items: center;
+ gap: 7px;
+}
+
+.code-actions .dot {
+ width: 12px;
+ height: 12px;
+ border-radius: 50%;
+ display: block;
+}
+
+.code-actions .close {
+ background: #ff5f57;
+}
+
+.code-actions .minimize {
+ background: #febc2e;
+}
+
+.code-actions .maximize {
+ background: #28c840;
+}
+
+.code-copy-btn {
+ font-size: 12px;
+ cursor: pointer;
+ user-select: none;
+}
+
+.code-copy-btn:hover {
+ color: #1d4ed8;
+}
diff --git a/src/styles/global.css b/src/styles/global.css
index f06a55f..23ce43a 100644
--- a/src/styles/global.css
+++ b/src/styles/global.css
@@ -2,63 +2,28 @@
@import "css-reset.css";
:root {
- --layout-width: 1080px;
- --background: #f6f7f9;
- --shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
- --shadow-hover: 0 8px 24px rgba(0, 0, 0, 0.10);
- --card-bg: #ffffff;
- --text-color: #2b2f36;
- --text-secondary: #6b7280;
- --border: #e5e7eb;
- --primary: #4f6df5;
- --tag-bg: #eef1fe;
- --tag-text: #4f6df5;
- --tag-hover-bg: #dfe6fd;
+ --layout-width: 760px;
+ --border-primary: 1px solid #dcdcdc;
+ --border-secondary: 1px solid #eee;
+
+ --text-primary: #24292e;
+ --text-secondary: #4b5563;
+ --text-tertiary: #9ca3af;
+ --text-disabled: #d1d5db;
+ --text-meta: #71717a;
+ --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ --article-text-color: #404040;
}
.dark {
- --background: #0a0a0a;
- --card-bg: #161b22;
- --text-color: #ffffff;
- --text-secondary: #e5e7eb;
- --border: #e5e7eb;
- --primary: #1e293b;
- --tag-bg: #27272a;
- --tag-text: #f3f4f6;
- --tag-hover-bg: #334155;
+
}
html,
body {
- min-height: 100vh;
- font-size: 16px;
- background: var(--background);
- color: var(--text-color);
-}
-
-body {
- display: flex;
- flex-direction: column;
-}
-
-.main {
- flex: 1;
- display: flex;
- flex-direction: column;
-}
-
-.page-content {
width: 100%;
- max-width: var(--layout-width);
- margin: 0 auto;
- padding: 32px;
- flex: 1;
- display: flex;
- flex-direction: column;
-}
-
-@media (max-width: 768px) {
- .page-content {
- padding: 16px;
- }
+ height: 100%;
+ min-height: 100%;
+ font-family: var(--font-family), serif;
+ color: var(--text-primary);
}