diff --git a/asky.config.ts b/asky.config.ts index 66c35c6..0890ecd 100644 --- a/asky.config.ts +++ b/asky.config.ts @@ -38,9 +38,31 @@ const config: AskyConfig = { menu: [ { label: '首页', url: '/' }, { label: '归档', url: '/archive' }, + { label: '链接', url: '/links' }, { label: '关于', url: '/about' } - ] + ], // 主导航菜单(对应原 wp_nav_menu 'primary' 位置) + + links: [ + { + title: '友情链接', + items: [ + { + name: 'Asky', + url: 'https://github.com/saresam/Asky', + description: 'Asky WordPress 主题原版', + image: '/images/none.png' + }, + { + name: 'Astro', + url: 'https://astro.build/', + description: '当前站点使用的静态站点框架', + image: '/images/none.png' + } + ] + } + ] + // 友情链接页面数据(对应原 WordPress 书签/链接分类) }, /* ===== 基本设置 ===== */ @@ -289,22 +311,6 @@ const config: AskyConfig = { // 是否显示评论者的 UA 图标(OS / 浏览器) }, - /* ===== 前台登录 ===== */ - login_bg: '', - // 后台登录界面背景图(为空则使用默认) - - exlogin_url: '', - // 指定登录地址(强制不使用后台 wp-login.php) - - exregister_url: '', - // 指定注册地址(作为登录页面的注册入口) - - ex_register_open: false, - // 允许用户在前台注册 - - login_urlskip: false, - // 登录后自动跳转(管理员→后台,用户→主页) - /* ===== 杂七杂八 ===== */ canvas_nest: false, // 开启蜂窝背景动效(屏幕宽度 >800px 时生效) @@ -354,6 +360,16 @@ export interface AskyConfig { language: string; charset: string; menu: Array<{ label: string; url: string; children?: Array<{ label: string; url: string }> }>; + links?: Array<{ + title: string; + description?: string; + items: Array<{ + name: string; + url: string; + description?: string; + image?: string; + }>; + }>; }; /* ===== 基本设置 ===== */ @@ -451,13 +467,6 @@ export interface AskyConfig { showUa?: boolean; }; - /* ===== 前台登录 ===== */ - login_bg: string; - exlogin_url: string; - exregister_url: string; - ex_register_open: boolean; - login_urlskip: boolean; - /* ===== 杂七杂八 ===== */ canvas_nest: boolean; flying_fish: boolean; diff --git a/astro.config.mjs b/astro.config.mjs index b698b64..9eacdd4 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -3,6 +3,9 @@ import { defineConfig } from 'astro/config'; export default defineConfig({ site: 'https://example.com', trailingSlash: 'ignore', + markdown: { + syntaxHighlight: false + }, build: { format: 'directory', assets: 'assets' diff --git a/public/style.css b/public/style.css index d1aa2d4..e4c9f1d 100644 --- a/public/style.css +++ b/public/style.css @@ -6935,3 +6935,74 @@ i.iconfont.icon-people{ } } + +/* Mac-style code blocks */ +.entry-content pre { + position: relative; + margin: 1.8em 0; + padding: 54px 22px 22px; + border: 1px solid rgba(255, 255, 255, .08); + border-radius: 8px; + background: #1f2329; + color: #d7dde8; + box-shadow: 0 14px 34px rgba(15, 23, 42, .16); + overflow: auto; +} + +.entry-content pre:before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 38px; + border-bottom: 1px solid rgba(255, 255, 255, .08); + border-radius: 8px 8px 0 0; + background: linear-gradient(#3a3f4b, #2b3039); +} + +.entry-content pre:after { + content: ""; + position: absolute; + top: 14px; + left: 16px; + width: 11px; + height: 11px; + border-radius: 50%; + background: #ff5f57; + box-shadow: 19px 0 #ffbd2e, 38px 0 #28c840; +} + +.entry-content pre code { + display: block; + margin: 0; + padding: 0; + border-radius: 0; + background: transparent; + color: inherit; + font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; + font-size: 14px; + line-height: 1.75; + white-space: pre; +} + +@media (max-width: 860px) { + .entry-content pre { + padding: 50px 16px 18px; + border-radius: 6px; + } + + .entry-content pre:before { + height: 36px; + border-radius: 6px 6px 0 0; + } + + .entry-content pre:after { + top: 13px; + left: 14px; + } + + .entry-content pre code { + font-size: 13px; + } +} diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro index 6c3a86c..26d73f9 100644 --- a/src/layouts/BaseLayout.astro +++ b/src/layouts/BaseLayout.astro @@ -8,6 +8,16 @@ export interface Props { isHome?: boolean; /** 当前页的特色图(pattern 装饰图) */ patternImg?: string; + /** 装饰图中显示的标题 */ + patternTitle?: string; + /** 装饰图中显示的描述 */ + patternDescription?: string; + /** 是否使用文章详情页的装饰图布局 */ + patternSingle?: boolean; + patternAuthor?: string; + patternAuthorUrl?: string; + patternAvatar?: string; + patternMeta?: string; /** 自定义 body class */ bodyClass?: string; } @@ -17,11 +27,19 @@ const { description, isHome = false, patternImg, + patternTitle, + patternDescription, + patternSingle = false, + patternAuthor, + patternAuthorUrl = '/', + patternAvatar, + patternMeta, bodyClass = '' } = Astro.props; const siteName = bloginfo('name'); const siteDesc = bloginfo('description'); +const siteUrl = bloginfo('url').replace(/\/$/, ''); const charset = bloginfo('charset'); const lang = bloginfo('language'); @@ -49,6 +67,9 @@ const stats = askyOption('site_statistics'); const statsLink = askyOption('site_statistics_link'); const sitemapLink = askyOption('site_map_link'); const themeSkin = askyOption('theme_skin'); + +const patternClass = `pattern-center${patternSingle ? ' single-center' : ''}`; +const patternHeaderClass = `pattern-header${patternSingle ? ' single-header' : ''}`; --- @@ -67,6 +88,7 @@ const themeSkin = askyOption('theme_skin'); )} + @@ -100,8 +122,32 @@ const themeSkin = askyOption('theme_skin'); {patternImg && ( -
-
+
+
+ {patternTitle && ( +
+

{patternTitle}

+ {patternSingle ? ( + <> + +

+ {patternAuthor && patternAvatar && ( + {patternAuthor} + )} + {patternAuthor && {patternAuthor}} + {patternMeta && ( + <> + {patternAuthor && ·} + {patternMeta} + + )} +

+ + ) : ( + patternDescription && {patternDescription} + )} +
+ )}
)} diff --git a/src/pages/about.astro b/src/pages/about.astro new file mode 100644 index 0000000..9e79621 --- /dev/null +++ b/src/pages/about.astro @@ -0,0 +1,34 @@ +--- +import BaseLayout from '../layouts/BaseLayout.astro'; +import Header from '../components/Header.astro'; +import Imgbox from '../components/Imgbox.astro'; +import MobileNav from '../components/MobileNav.astro'; +import { askyOption, bloginfo, isOptionOn } from '../lib/options'; + +const showHeader = isOptionOn('patternimg'); +const patternImg = showHeader ? undefined : '/images/hd.jpg'; +const siteName = bloginfo('name'); +const description = askyOption('admin_des', bloginfo('description')); +--- + + + +
+ + +
+
+
+ {showHeader && ( +
+

关于

+
+ )} +
+

{siteName}

+

{description}

+
+
+
+
+ diff --git a/src/pages/archive.astro b/src/pages/archive.astro index a7bc289..be38c16 100644 --- a/src/pages/archive.astro +++ b/src/pages/archive.astro @@ -12,45 +12,49 @@ const all = (await getCollection('posts')).sort( const groups = new Map(); for (const p of all) { - const key = `${p.data.date.getFullYear()}-${String(p.data.date.getMonth() + 1).padStart(2, '0')}`; + const key = `${p.data.date.getFullYear()}-${p.data.date.getMonth() + 1}`; if (!groups.has(key)) groups.set(key, []); groups.get(key)!.push(p); } -const sortedKeys = [...groups.keys()].sort((a, b) => b.localeCompare(a)); +const sortedKeys = [...groups.keys()].sort((a, b) => { + const [ay, am] = a.split('-').map(Number); + const [by, bm] = b.split('-').map(Number); + return by === ay ? bm - am : by - ay; +}); const showHeader = isOptionOn('patternimg'); +const patternImg = showHeader ? undefined : '/images/hd.jpg'; --- - +
-
-
- {showHeader && ( - - )} - -
+
-
-
+
+ + diff --git a/src/pages/links.astro b/src/pages/links.astro new file mode 100644 index 0000000..219cce3 --- /dev/null +++ b/src/pages/links.astro @@ -0,0 +1,39 @@ +--- +import BaseLayout from '../layouts/BaseLayout.astro'; +import Header from '../components/Header.astro'; +import Imgbox from '../components/Imgbox.astro'; +import MobileNav from '../components/MobileNav.astro'; +import { getConfig, isOptionOn } from '../lib/options'; + +const linkGroups = getConfig().site.links ?? []; +const showHeader = isOptionOn('patternimg'); +const patternImg = showHeader ? undefined : '/images/hd.jpg'; +--- + + + +
+ + + + diff --git a/src/pages/posts/[slug].astro b/src/pages/posts/[slug].astro index 821c9a5..8f7e627 100644 --- a/src/pages/posts/[slug].astro +++ b/src/pages/posts/[slug].astro @@ -7,7 +7,7 @@ import AuthorProfile from '../../components/AuthorProfile.astro'; import PostNextPrev from '../../components/PostNextPrev.astro'; import ShareLike from '../../components/ShareLike.astro'; import Comments from '../../components/Comments.astro'; -import { isOptionOn } from '../../lib/options'; +import { askyOption, isOptionOn } from '../../lib/options'; import { getCollection } from 'astro:content'; export async function getStaticPaths() { @@ -35,12 +35,21 @@ const showPattern = isOptionOn('patternimg'); const buildTime = post.data.date.toLocaleString('zh-CN'); const tags = post.data.tags ?? []; const fullUrl = new URL(`/posts/${post.slug}`, Astro.site ?? 'https://example.com').toString(); +const patternImg = showPattern ? undefined : (post.data.thumbnail ?? '/images/hd.jpg'); +const authorName = post.data.author ?? 'Admin'; +const authorAvatar = askyOption('focus_logo') || '/images/avatar.jpg'; ---
@@ -71,7 +80,7 @@ const fullUrl = new URL(`/posts/${post.slug}`, Astro.site ?? 'https://example.co {tags.length > 0 && ( )} @@ -79,7 +88,7 @@ const fullUrl = new URL(`/posts/${post.slug}`, Astro.site ?? 'https://example.co - + diff --git a/src/pages/rss.xml.ts b/src/pages/rss.xml.ts new file mode 100644 index 0000000..75a0d73 --- /dev/null +++ b/src/pages/rss.xml.ts @@ -0,0 +1,63 @@ +import { getCollection } from 'astro:content'; +import { bloginfo } from '../lib/options'; + +function escapeXml(value: string): string { + return value + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + +function stripMarkdown(value: string): string { + return value + .replace(/```[\s\S]*?```/g, '') + .replace(/`([^`]+)`/g, '$1') + .replace(/!\[[^\]]*]\([^)]*\)/g, '') + .replace(/\[([^\]]+)]\([^)]*\)/g, '$1') + .replace(/[#>*_~\-]+/g, ' ') + .replace(/\s+/g, ' ') + .trim(); +} + +export async function GET() { + const siteUrl = bloginfo('url').replace(/\/$/, ''); + const siteName = bloginfo('name'); + const siteDescription = bloginfo('description'); + const posts = (await getCollection('posts')).sort( + (a, b) => b.data.date.getTime() - a.data.date.getTime() + ); + + const items = posts.map((post) => { + const link = `${siteUrl}/posts/${post.slug}`; + const description = post.data.description ?? stripMarkdown(post.body).slice(0, 280); + + return ` + ${escapeXml(post.data.title)} + ${escapeXml(link)} + ${escapeXml(link)} + ${post.data.date.toUTCString()} + ${escapeXml(description)} + `; + }).join('\n'); + + const xml = ` + + + ${escapeXml(siteName)} + ${escapeXml(siteUrl)} + ${escapeXml(siteDescription)} + ${escapeXml(bloginfo('language'))} + ${new Date().toUTCString()} + +${items} + +`; + + return new Response(xml, { + headers: { + 'Content-Type': 'application/rss+xml; charset=utf-8' + } + }); +} diff --git a/src/pages/tag/[tag].astro b/src/pages/tag/[tag].astro new file mode 100644 index 0000000..e41a379 --- /dev/null +++ b/src/pages/tag/[tag].astro @@ -0,0 +1,64 @@ +--- +import BaseLayout from '../../layouts/BaseLayout.astro'; +import Header from '../../components/Header.astro'; +import Imgbox from '../../components/Imgbox.astro'; +import MobileNav from '../../components/MobileNav.astro'; +import PostListThumb from '../../components/PostListThumb.astro'; +import PostList from '../../components/PostList.astro'; +import { askyOption, isOptionOn } from '../../lib/options'; +import { getCollection } from 'astro:content'; + +export async function getStaticPaths() { + const all = (await getCollection('posts')).sort( + (a, b) => b.data.date.getTime() - a.data.date.getTime() + ); + + const tags = [...new Set(all.flatMap((post) => post.data.tags ?? []))].sort((a, b) => + a.localeCompare(b, 'zh-CN') + ); + + return tags.map((tag) => ({ + params: { tag }, + props: { + tag, + posts: all.filter((post) => (post.data.tags ?? []).includes(tag)) + } + })); +} + +const { tag, posts } = Astro.props; +const items = posts.map((p) => ({ + id: p.id, + permalink: `/posts/${p.slug}`, + title: p.data.title, + date: p.data.date.toISOString(), + excerpt: p.data.description ?? p.body.replace(/[#*`>_\[\]\(\)]/g, '').slice(0, 280), + thumbnail: p.data.thumbnail, + sticky: p.data.sticky +})); + +const showHeader = isOptionOn('patternimg'); +const patternImg = showHeader ? undefined : '/images/hd.jpg'; +const title = `标签:${tag}`; +const description = `共 ${items.length} 篇文章`; +const style = askyOption('post_list_style'); +--- + + + +
+ + +
+
+ {showHeader && ( + + )} + + {style === 'standard' ? : } +
+
+