🍊
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
---
|
||||
import { fmtTime } from "../utils/index";
|
||||
const { articleList } = Astro.props;
|
||||
// 文章页面样式
|
||||
import "../styles/Archive.less";
|
||||
---
|
||||
|
||||
<section class="vh-animation vh-archive-main">
|
||||
<div class="archive-list">
|
||||
{
|
||||
articleList.map((i: any) => (
|
||||
<div class="archive-list-item">
|
||||
<p class="title">
|
||||
<em>{i.name}</em>
|
||||
<i />
|
||||
<span>{i.data.length} total</span>
|
||||
</p>
|
||||
{i.data.map((_: any) => (
|
||||
<a href={`/article/${_.id}`}>
|
||||
<em>{fmtTime(_.date, "MM-DD")}</em>
|
||||
<i />
|
||||
<span>{_.title}</span>
|
||||
<cite>{_.tags.map((tag: string) => `#${tag}`).join(" ")}</cite>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
import { Image } from "astro:assets";
|
||||
import { getDescription, fmtTime } from "../utils/index";
|
||||
const { post, index } = Astro.props;
|
||||
// 获取文章的摘要
|
||||
const description = getDescription(post);
|
||||
// 获取封面图
|
||||
import getCover from "../utils/getCover";
|
||||
const ARTICLE_COVER: string = await getCover(post.data.cover);
|
||||
// 文章Item组件样式
|
||||
import "../styles/components/ArticleCard.less";
|
||||
---
|
||||
|
||||
<article class="vh-article-item vh-animation" style={`animation-delay:calc(var(--vh-animation-delay) + ${index * 50}ms)`}>
|
||||
<a class="vh-article-link" href={`/article/${post.data.id}`}>
|
||||
<section class="vh-article-banner"><Image src={ARTICLE_COVER} alt={post.data.title} width="10" height="10" /></section>
|
||||
<section class="vh-article-desc">
|
||||
<header>
|
||||
<span class={`vh-article-categories vh-categories-${post.data.categories}`}>{post.data.categories}</span>
|
||||
<h2 class="title">{post.data.title}</h2>
|
||||
</header>
|
||||
<p class="vh-article-excerpt">{description}</p>
|
||||
<footer>{fmtTime(post.data.date)}</footer>
|
||||
</section>
|
||||
</a>
|
||||
</article>
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
// 获取用户配置数据
|
||||
import SITE_CONFIG from "../config";
|
||||
import { fmtTime, fmtDate } from "../utils/index";
|
||||
const { Avatar, Author, Motto, WebSites } = SITE_CONFIG;
|
||||
// 获取文章数据
|
||||
import { getCategories, getTags, getRecommendArticles } from "../utils/getPostInfo";
|
||||
// 分类列表
|
||||
const categories = getCategories();
|
||||
// 热门标签
|
||||
const tags = getTags();
|
||||
// 最新文章
|
||||
const recommendArticles = getRecommendArticles();
|
||||
// 静态图片
|
||||
import { Image } from "astro:assets";
|
||||
// 侧边栏样式
|
||||
import "../styles/components/Aside.less";
|
||||
---
|
||||
|
||||
<aside class="vh-aside">
|
||||
<!-- 头像块 -->
|
||||
<section class="vh-aside-item user">
|
||||
<div onclick="window.history.back()" class="vh-aside-avatar">
|
||||
<Image class="vh-aside-avatar" src={Avatar} alt={Author} width="1" height="1" />
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> <path d="M512 256A256 256 0 1 0 0 256a256 256 0 1 0 512 0zM116.7 244.7l112-112c4.6-4.6 11.5-5.9 17.4-3.5s9.9 8.3 9.9 14.8l0 64 96 0c17.7 0 32 14.3 32 32l0 32c0 17.7-14.3 32-32 32l-96 0 0 64c0 6.5-3.9 12.3-9.9 14.8s-12.9 1.1-17.4-3.5l-112-112c-6.2-6.2-6.2-16.4 0-22.6z"></path></svg>
|
||||
</div>
|
||||
<span class="vh-aside-auther">{Author}</span>
|
||||
<p class="vh-aside-motto">{Motto}</p>
|
||||
<section class="vh-aside-links">
|
||||
{
|
||||
WebSites.map(item => (
|
||||
<a class="vh-aside-links-item" href={item.link} title={item.text} target="_blank" rel="noopener nofollow">
|
||||
<img src={item.icon} />
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<!-- 分类块 -->
|
||||
<section class="vh-aside-item categories">
|
||||
<h3>分类</h3>
|
||||
<div class="vh-aside-categories">
|
||||
{
|
||||
categories.map(i => (
|
||||
<a href={`/categories/${i.title}`}>
|
||||
<span>{i.title}</span>
|
||||
<i>{i.count}</i>
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 标签块 -->
|
||||
<section class="vh-aside-item tags">
|
||||
<h3>热门标签</h3>
|
||||
<div class="vh-aside-tags">
|
||||
{
|
||||
tags.map(i => (
|
||||
<a href={`/tags/${i}`}>
|
||||
<span>{i}</span>
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 最新文章块 -->
|
||||
<section class="vh-aside-item articles">
|
||||
<h3>推荐文章</h3>
|
||||
<div class="vh-aside-articles">
|
||||
{
|
||||
recommendArticles.map(async i => (
|
||||
<a href={`/article/${(await i).id}`}>
|
||||
<p class="cover">
|
||||
<img src={(await i).cover} />
|
||||
</p>
|
||||
<p class="info">
|
||||
<span>{(await i).title}</span>
|
||||
<time>{fmtDate((await i).date)}</time>
|
||||
</p>
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
</aside>
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
import "../styles/components/BackTop.less";
|
||||
---
|
||||
|
||||
<section class="vh-back-top">
|
||||
<svg viewBox="0 0 24 24">
|
||||
<defs>
|
||||
<linearGradient id="Gradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style="stop-color:blue;stop-opacity:1"></stop>
|
||||
<stop offset="50%" style="stop-color:violet;stop-opacity:1"></stop>
|
||||
<stop offset="100%" style="stop-color:indigo;stop-opacity:1"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<circle stroke="url(#Gradient)" cx="12" cy="12" r="10" fill="none" stroke-width="2" stroke-linecap="round" transform="rotate(-90, 12, 12)"></circle>
|
||||
</svg>
|
||||
<svg class="icon" viewBox="0 0 24 24"><path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m6 15l6-6l6 6"></path></svg>
|
||||
</section>
|
||||
<script>
|
||||
import updateRouter from "../utils/updateRouter";
|
||||
import BackTopInitFn from "../scripts/BackTop";
|
||||
// 初始化BackTop组件
|
||||
updateRouter("afterMount", BackTopInitFn);
|
||||
</script>
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
import "../styles/components/Comment.less";
|
||||
---
|
||||
|
||||
<section class="vh-comment"><section></section></section>
|
||||
|
||||
<script>
|
||||
import updateRouter from "../utils/updateRouter";
|
||||
import { LoadScript } from "../utils/index";
|
||||
import SITE_CONFIG from "../config";
|
||||
const { Twikoo } = SITE_CONFIG;
|
||||
declare const twikoo: any;
|
||||
// 初始化评论插件
|
||||
updateRouter("afterMount", async () => {
|
||||
if (!document.querySelector(".vh-comment>section")) return;
|
||||
await LoadScript("https://registry.npmmirror.com/twikoo/1.6.41/files/dist/twikoo.all.min.js");
|
||||
twikoo.init({
|
||||
envId: Twikoo.envId,
|
||||
el: ".vh-comment>section"
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
const { time, sitename, title, site, id, auther } = Astro.props;
|
||||
const articleURL = `${site}/article/${id}`;
|
||||
import "../styles/components/Copyright.less";
|
||||
---
|
||||
|
||||
<section class="vh-copyright">
|
||||
<p>本文由 <span>{auther}</span> 于 {time} 发布</p>
|
||||
<p>文章地址:<a href={articleURL}>{title}</a></p>
|
||||
<p>本博客所有文章除特别声明外,均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0" target="_blank" rel="noopener nofollow">CC BY-NC-SA 4.0</a> 许可协议。完整转载请注明来自 <a href={site}>{sitename}</a>!</p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"> <path d="M245.8 214.9l-33.2 17.3c-9.4-19.6-25.2-19.9-27.5-19.9-22.1 0-33.2 14.6-33.2 43.8 0 23.6 9.2 43.8 33.2 43.8 14.5 0 24.7-7.1 30.6-21.3l30.6 15.5c-6.2 11.5-25.7 39-65.1 39-22.6 0-74-10.3-74-77.1 0-58.7 43-77.1 72.6-77.1 30.7 0 52.7 12 66 35.9zm143.1 0l-32.8 17.3c-9.5-19.8-25.7-19.9-27.9-19.9-22.1 0-33.2 14.6-33.2 43.8 0 23.6 9.2 43.8 33.2 43.8 14.5 0 24.7-7.1 30.5-21.3l31 15.5c-2.1 3.8-21.4 39-65.1 39-22.7 0-74-9.9-74-77.1 0-58.7 43-77.1 72.6-77.1 30.7 0 52.6 12 65.6 35.9zM247.6 8.1C104.7 8.1 0 123.1 0 256.1c0 138.5 113.6 248 247.6 248 129.9 0 248.4-100.9 248.4-248 0-137.9-106.6-248-248.4-248zm.9 450.8c-112.5 0-203.7-93-203.7-202.8 0-105.4 85.4-203.3 203.7-203.3 112.5 0 202.8 89.5 202.8 203.3 0 121.7-99.7 202.8-202.8 202.8z"></path></svg>
|
||||
</section>
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
import SITE_CONFIG from "../config";
|
||||
// 当前页面的 URL 元地址
|
||||
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
|
||||
// 页面内容的元数据
|
||||
const { Title: title, Keywords, Description } = Astro.props;
|
||||
const { Site, Title, Subtitle, Author, Cover, DNSOptimization } = SITE_CONFIG;
|
||||
// 基础 样式
|
||||
import "../styles/Base.less";
|
||||
---
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no" />
|
||||
<title>{title ? `${title} | ${Title}` : `${Title} - ${Subtitle}`}</title>
|
||||
<meta name="keywords" content={(Keywords || ["Astro", "分享", "博客"]).join(", ")} />
|
||||
<meta name="description" content={Description} />
|
||||
<meta name="format-detection" content="telephone=no, email=no, date=no, address=no" />
|
||||
<!-- 元数据 -->
|
||||
<meta name="title" content={Title} />
|
||||
<meta name="site" content={Site} />
|
||||
<meta name="author" content={Author} />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<meta property="article:author" content={Author} />
|
||||
<meta name="robots" content="max-image-preview:large" />
|
||||
<!-- Open Graph -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:title" content={Title} />
|
||||
<meta property="og:url" content={canonicalURL} />
|
||||
<meta property="og:site_name" content={Title} />
|
||||
<meta property="og:description" content={Description} />
|
||||
<meta property="og:locale" content="zh_CN" />
|
||||
<meta property="og:image" content={Cover} />
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content={canonicalURL} />
|
||||
<meta property="twitter:title" content={Title} />
|
||||
<meta property="twitter:description" content={Description} />
|
||||
<meta property="twitter:image" content={Cover} />
|
||||
<!-- Sitemap -->
|
||||
<link rel="sitemap" href="/sitemap-index.xml" />
|
||||
<!-- DNS预解析 -->
|
||||
{DNSOptimization.map((url: string) => <link rel="dns-prefetch" href={url} />)}
|
||||
{DNSOptimization.map((url: string) => <link rel="preconnect" href={url} />)}
|
||||
<!-- LINK 标签 -->
|
||||
<link rel="canonical" href={canonicalURL} />
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<slot />
|
||||
</head>
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
// 导航高亮
|
||||
const { activeNav } = Astro.props;
|
||||
import SITE_CONFIG from "../config";
|
||||
const { Navs } = SITE_CONFIG;
|
||||
// 搜索框组件
|
||||
import Search from "./Search.astro";
|
||||
// 顶部 Header 样式
|
||||
import "../styles/components/Header.less";
|
||||
---
|
||||
|
||||
<header class="vh-header">
|
||||
<section class="main">
|
||||
<a href="/" class="index">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"> <path d="M543.8 287.6c17 0 32-14 32-32.1c1-9-3-17-11-24L309.5 7c-6-5-14-7-21-7s-15 1-22 8L10 231.5c-7 7-10 15-10 24c0 18 14 32.1 32 32.1l32 0 0 160.4c0 35.3 28.7 64 64 64l102.3 0-31.3-52.2c-4.1-6.8-2.6-15.5 3.5-20.5L288 368l-60.2-82.8c-10.9-15 8.2-33.5 22.8-22l117.9 92.6c8 6.3 8.2 18.4 .4 24.9L288 448l38.4 64 122.1 0c35.5 0 64.2-28.8 64-64.3l-.7-160.2 32 0z"></path></svg>
|
||||
Home
|
||||
</a>
|
||||
<div class="link-list">
|
||||
{
|
||||
Navs.map(i => (
|
||||
<a class={i.link.includes(activeNav) ? "active" : ""} href={i.link} target={i.target ? "_blank" : "_self"}>
|
||||
{i.text}
|
||||
<span set:html={i.icon} />
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<div class="nav-btn">
|
||||
<span class="nav-btn-item search-btn"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> <path d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z"></path></svg> 搜索</span>
|
||||
<span class="nav-btn-item menu-btn"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M0 96C0 78.3 14.3 64 32 64l384 0c17.7 0 32 14.3 32 32s-14.3 32-32 32L32 128C14.3 128 0 113.7 0 96zM0 256c0-17.7 14.3-32 32-32l384 0c17.7 0 32 14.3 32 32s-14.3 32-32 32L32 288c-17.7 0-32-14.3-32-32zM448 416c0 17.7-14.3 32-32 32L32 448c-17.7 0-32-14.3-32-32s14.3-32 32-32l384 0c17.7 0 32 14.3 32 32z"></path></svg></span>
|
||||
</div>
|
||||
<Search />
|
||||
</section>
|
||||
</header>
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
// 导航高亮
|
||||
const { activeNav } = Astro.props;
|
||||
import SITE_CONFIG from "../config";
|
||||
const { Navs, Title } = SITE_CONFIG;
|
||||
// 侧边栏 MobileSidebar 样式
|
||||
import "../styles/components/MobileSidebar.less";
|
||||
---
|
||||
|
||||
<nav class="vh-mobilesidebar">
|
||||
<section class="main">
|
||||
<div class="vh-mobilesidebar-list user-panel">
|
||||
<h3>{Title}</h3>
|
||||
</div>
|
||||
|
||||
<div class="vh-mobilesidebar-list">
|
||||
{
|
||||
Navs.map(i => (
|
||||
<a class={i.link.includes(activeNav) ? "active" : ""} href={i.link} target={i.target ? "_blank" : "_self"}>
|
||||
<span set:html={i.icon} />
|
||||
{i.text}
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
</nav>
|
||||
|
||||
<script>
|
||||
import updateRouter from "../utils/updateRouter";
|
||||
// 初始化搜索框
|
||||
const vhSearchInit = () => {
|
||||
const menuDOM: any = document.querySelector(".vh-header>.main>.nav-btn>span.menu-btn");
|
||||
const mobileSidebarDOM: any = document.querySelector("body>.vh-mobilesidebar");
|
||||
const addActive = () => setTimeout(() => mobileSidebarDOM.classList.add("active"));
|
||||
const removeActive = () => setTimeout(() => mobileSidebarDOM.classList.remove("active"));
|
||||
menuDOM.addEventListener("click", addActive);
|
||||
mobileSidebarDOM.addEventListener("click", removeActive);
|
||||
};
|
||||
// 初始化
|
||||
updateRouter("afterMount", vhSearchInit);
|
||||
</script>
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
const { data } = Astro.props;
|
||||
// 处理页码
|
||||
import { fmtPage } from "../utils/index";
|
||||
// 翻页组件样式
|
||||
import "../styles/components/Pagination.less";
|
||||
---
|
||||
|
||||
<section class="vh-article-pagination">
|
||||
<!-- 上一页 -->
|
||||
<a class={`vh-pagination-item${!data.prev ? " disabled" : ""}`} href={data.prev}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
|
||||
<path d="M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l192 192c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L77.3 256 246.6 86.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-192 192z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<!-- 首页 -->
|
||||
{
|
||||
data.first && data.prev && (
|
||||
<a class={`vh-pagination-item`} href={data.first}>
|
||||
1
|
||||
</a>
|
||||
)
|
||||
}
|
||||
<!-- 总页的前一页 -->
|
||||
{
|
||||
(!data.next || !data.last) && (
|
||||
<a class={`vh-pagination-item`} href={data.prev}>
|
||||
{fmtPage(data.prev)}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
<!-- 当前页 -->
|
||||
<a class="vh-pagination-item active">{fmtPage(data.current) || "1"}</a>
|
||||
<!-- 第一页的后一页 -->
|
||||
{
|
||||
(!data.prev || !data.first) && (
|
||||
<a class={`vh-pagination-item`} href={data.next}>
|
||||
{fmtPage(data.next)}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
<!-- 总页 -->
|
||||
{
|
||||
data.next && data.last && (
|
||||
<a class={`vh-pagination-item`} href={data.last}>
|
||||
{fmtPage(data.last)}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
<!-- 下一页 -->
|
||||
<a class={`vh-pagination-item${!data.next ? " disabled" : ""}`} href={data.next}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
|
||||
<path d="M310.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-192 192c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L242.7 256 73.4 86.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l192 192z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</section>
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
// 搜索样式
|
||||
import "../styles/components/Search.less";
|
||||
---
|
||||
|
||||
<section class="vh-search">
|
||||
<main>
|
||||
<div class="search-input">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#B4B4B4" d="m19.6 21l-6.3-6.3q-.75.6-1.725.95T9.5 16q-2.725 0-4.612-1.888T3 9.5t1.888-4.612T9.5 3t4.613 1.888T16 9.5q0 1.1-.35 2.075T14.7 13.3l6.3 6.3zM9.5 14q1.875 0 3.188-1.312T14 9.5t-1.312-3.187T9.5 5T6.313 6.313T5 9.5t1.313 3.188T9.5 14"></path></svg>
|
||||
<input type="text" placeholder="Search" />
|
||||
</div>
|
||||
<section class="vh-search-list"></section>
|
||||
</main>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
import updateRouter from "../utils/updateRouter";
|
||||
import { searchInputChange } from "../scripts/Search";
|
||||
// 初始化搜索框
|
||||
const vhSearchInit = () => {
|
||||
const searchDOM: any = document.querySelector(".vh-header>.main>.nav-btn>span.search-btn");
|
||||
const searchMainDOM: any = document.querySelector(".vh-header>.main>.vh-search>main");
|
||||
const searchListDOM: any = document.querySelector(".vh-header>.main>.vh-search");
|
||||
const addActive = () => setTimeout(() => searchListDOM.classList.add("active"));
|
||||
const removeActive = () => setTimeout(() => searchListDOM.classList.remove("active"));
|
||||
// 禁止默认事件
|
||||
searchMainDOM.addEventListener("click", (e: Event) => e.stopPropagation());
|
||||
searchDOM.addEventListener("click", addActive);
|
||||
searchListDOM.addEventListener("click", removeActive);
|
||||
// 搜索框初内容变化
|
||||
searchListDOM.querySelector(".search-input>input").addEventListener("input", searchInputChange);
|
||||
};
|
||||
// 初始化
|
||||
updateRouter("afterMount", vhSearchInit);
|
||||
</script>
|
||||
Reference in New Issue
Block a user