This commit is contained in:
Ryan Wang
2026-04-07 22:52:16 +08:00
parent ec2fcc6e4a
commit cf680c4d17
61 changed files with 6826 additions and 4820 deletions
-12
View File
@@ -1,12 +0,0 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = false
insert_final_newline = true
-20
View File
@@ -1,20 +0,0 @@
/* eslint-env node */
require("@rushstack/eslint-patch/modern-module-resolution");
module.exports = {
root: true,
extends: [
"plugin:vue/vue3-essential",
"eslint:recommended",
"@vue/eslint-config-typescript/recommended",
"@vue/eslint-config-prettier",
],
env: {
node: true,
"vue/setup-compiler-macros": true,
},
rules: {
"vue/multi-word-component-names": 0,
"@typescript-eslint/ban-ts-comment": 0,
},
};
+17 -22
View File
@@ -1,29 +1,24 @@
# Logs
logs
*.log
# build output
dist/
# generated types
.astro/
# dependencies
node_modules/
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
# environment variables
.env
.env.production
# macOS-specific files
.DS_Store
dist
dist-ssr
histoire-dist
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# jetbrains setting folder
.idea/
-2
View File
@@ -1,2 +0,0 @@
# Expose Astro dependencies for `pnpm` users
shamefully-hoist=true
+3
View File
@@ -0,0 +1,3 @@
templates/
pnpm-lock.yaml
pnpm-workspace.yaml
-11
View File
@@ -1,11 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"command": "./node_modules/.bin/astro dev",
"name": "Development server",
"request": "launch",
"type": "node-terminal"
}
]
}
+6
View File
@@ -0,0 +1,6 @@
{
"emmet.includeLanguages": {
"astro": "html"
},
"i18n-ally.localesPaths": ["demo-themes/theme-earth/i18n"]
}
+1 -60
View File
@@ -1,60 +1 @@
# theme-astro-starter(Experimental)
Halo 2.0 基于 [Astro](https://astro.build) 和 [Thymeleaf](https://www.thymeleaf.org/) 的主题快速开始模板。
> 目前还处于实验和探索的阶段。
## 原理
1. Astro 作为 SSG 渲染框架,用于组件封装、页面框架组织、静态页面生成、静态资源的构建。
2. Thymeleaf 作为后端页面渲染框架,并且负责提供数据进行页面数据的渲染。由于 Thymeleaf 的语法是基于 HTML 元素的 Attributes 扩展,所以理论上并不会破坏 HTML 的结构。那么就可以在 Astro 组件中使用 Halo 提供的数据和页面进行绑定。这样就实现了在开发环境并不依赖于 Halo 后端,可以在开发过程中保留 hmr。并且可以做到在构建之后可以直接给 Halo 使用。
## 特性
1. 得益于 Astro 框架,可以使用现代的前端技术栈开发页面。包括但不限于 Vue React Svelte 等,意味着可以使用这些现代前端框架进行组件开发。
2. 在开发环境可以实现 hmr。
3. 在构建页面之后,会自动处理构建产物的资源地址,转换为 Thymeleaf 可识别的语法。
4. 最终构建的产物可通过 HTML 直接访问,也可以部署在任何静态页面服务器,可作为页面预览原型。但因为没有经过 Halo 进行数据绑定,所以没有数据填充。
## 开发
项目结构:
```text
├── astro.config.mjs Astro 配置文件
├── package.json
├── pnpm-lock.yaml
├── src
│ ├── components
│ │ ├── Card.astro
│ │ └── PostCard.vue Vue 3 组件
│ ├── layouts Astro 布局组件
│ │ └── Layout.astro
│ └── pages
│ ├── index.astro Astro 页面组件
│ └── post.astro
├── templates
│ ├── assets bundle 之后的静态资源文件
│ │ ├── 4669d51c.dbc606a6.css
│ │ ├── PostCard.42ac9187.js
│ │ ├── chunks
│ │ │ └── runtime-core.esm-bundler.e59bb94c.js
│ │ └── client.e67fc49c.js
│ ├── favicon.ico
│ ├── index.html 构建之后的静态页面,作为 Thymeleaf 渲染模板
│ └── post.html
├── theme.yaml 主题描述文件
└── tsconfig.json
```
```bash
git clone https://github.com/ruibaby/theme-astro-starter
pnpm install
# 启动 Vite 开发环境
pnpm dev
# 构建最终产物,构建之后可以直接在 Halo 当中安装使用
pnpm build
```
WIP
+7 -9
View File
@@ -1,16 +1,14 @@
// @ts-check
import { defineConfig } from "astro/config";
import vue from "@astrojs/vue";
import tailwind from "@astrojs/tailwind";
import astroHaloThemeIntegration from "@halo-dev/astro-halo-theme-integration";
// https://astro.build/config
import vue from "@astrojs/vue";
export default defineConfig({
integrations: [vue(), tailwind(), astroHaloThemeIntegration()],
outDir: "./templates",
base: "/themes/astro-starter",
build: {
assets: "assets",
format: "file",
},
server: {
port: 4000,
},
outDir: "./templates",
integrations: [vue()],
});
Vendored
-8
View File
@@ -1,8 +0,0 @@
/// <reference types="vite/client" />
declare module "*.vue" {
import type { DefineComponent } from "vue";
// eslint-disable-next-line
const component: DefineComponent<{}, {}, any>;
export default component;
}
-3
View File
@@ -1,3 +0,0 @@
foo=这是一段测试文本
bar=这是另外一段测试文本
hello=你好,{0}
-3
View File
@@ -1,3 +0,0 @@
foo=foo
bar=bar
hello=Hello, {0}
-3
View File
@@ -1,3 +0,0 @@
foo=这是一段测试文本
bar=这是另外一段测试文本
hello=你好,{0}
+29 -22
View File
@@ -1,31 +1,38 @@
{
"name": "@halo-dev/theme-astro-starter",
"version": "0.0.1",
"private": true,
"type": "module",
"engines": {
"node": ">=22.12.0"
},
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro build",
"preview": "astro preview"
"preview": "astro preview",
"astro": "astro",
"format": "prettier --write ."
},
"devDependencies": {
"@astrojs/tailwind": "^1.0.0",
"@astrojs/vue": "^1.0.0",
"@halo-dev/astro-halo-theme-integration": "^0.0.1",
"@rushstack/eslint-patch": "^1.1.4",
"@types/node": "16.11.47",
"@vue/eslint-config-prettier": "^7.0.0",
"@vue/eslint-config-typescript": "^11.0.0",
"@vue/tsconfig": "^0.1.3",
"astro": "^1.1.1",
"eslint": "^8.23.0",
"eslint-plugin-vue": "^9.4.0",
"prettier": "^2.7.1",
"prettier-plugin-tailwindcss": "^0.1.13",
"vue": "^3.2.37"
"prettier": {
"plugins": [
"prettier-plugin-astro"
],
"overrides": [
{
"files": "*.astro",
"options": {
"parser": "astro"
}
}
]
},
"dependencies": {
"@headlessui/vue": "^1.6.7",
"dayjs": "^1.11.5"
"@astrojs/mdx": "^5.0.3",
"@astrojs/rss": "^4.0.18",
"@astrojs/vue": "^6.0.1",
"astro": "^6.1.4",
"sharp": "^0.34.5",
"vue": "^3.5.32"
},
"devDependencies": {
"prettier": "3.8.1",
"prettier-plugin-astro": "0.14.1"
}
}
+5565 -3942
View File
File diff suppressed because it is too large Load Diff
+3
View File
@@ -0,0 +1,3 @@
allowBuilds:
esbuild: true
sharp: true
-3
View File
@@ -1,3 +0,0 @@
module.exports = {
plugins: [require("prettier-plugin-tailwindcss")],
};
-17
View File
@@ -1,17 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>About</title>
</head>
<body>
<p
th:text="没有经过 Astro 渲染,原生使用 Thymeleaf 语法。在构建时,将被 Copy 到 templates 目录。(Thymeleaf)"
>
没有经过 Astro 渲染,原生使用 Thymeleaf 语法。在构建时,将被 Copy 到
templates 目录。(Native)
</p>
</body>
</html>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

-72
View File
@@ -1,72 +0,0 @@
apiVersion: v1alpha1
kind: Setting
metadata:
name: theme-astro-starter-setting
spec:
- group: basic
label: 基本设置
formSchema:
- $el: h2
children: Welcome to Theme Astro Starter World
- $formkit: select
name: sidebar_width
label: 侧边栏宽度
options:
'20': 20%
'30': 30%
'40': 40%
'50': 50%
- $formkit: text
name: sidebar_background
label: 侧边栏背景图
- $formkit: text
name: top_logo
label: 左上角图标
- $formkit: radio
name: circle_avatar
label: 原型头像
options:
- label: 开启
value: true
- label: 关闭
value: false
value: false
- $formkit: radio
name: katex
label: KaTex 公式渲染
options:
- label: 开启
value: true
- label: 关闭
value: false
value: false
- $formkit: textarea
name: codeblock_highlight_languages
label: 文章代码高亮语言
help: >-
默认已经包含:php,diff,java,css,bash,json,perl,swift,plaintext,ruby,yaml,sql,vbnet,ini,scss,less,cpp,typescript,csharp,rust,python,python-repl,objectivec,r,shell,makefile,go,xml,markdown,lua,javascript,php-template,kotlin,c
如果需要添加其他语言,请按照相同格式添加。
- group: social
label: 社交资料
formSchema:
- $formkit: radio
name: rss
label: RSS
options:
- label: 开启
value: true
- label: 关闭
value: false
value: true
- $formkit: text
name: twitter
label: Twitter
- $formkit: text
name: telegram
label: Telegram
- $formkit: text
name: github
label: GitHub
- $formkit: text
name: email
label: Email
-74
View File
@@ -1,74 +0,0 @@
---
export interface Props {
title: string;
body: string;
href: string;
}
const { href, title, body } = Astro.props;
---
<li class="link-card">
<a href={href}>
<h2>
{title}
<span>&rarr;</span>
</h2>
<p>
{body}
</p>
</a>
</li>
<style>
:root {
--link-gradient: linear-gradient(45deg, #4f39fa, #da62c4 30%, var(--color-border) 60%);
}
.link-card {
list-style: none;
display: flex;
padding: 0.15rem;
background-image: var(--link-gradient);
background-size: 400%;
border-radius: 0.5rem;
background-position: 100%;
transition: background-position 0.6s cubic-bezier(0.22, 1, 0.36, 1);
}
.link-card>a {
width: 100%;
text-decoration: none;
line-height: 1.4;
padding: 1em 1.3em;
border-radius: 0.35rem;
color: var(--text-color);
background-color: white;
opacity: 0.8;
}
h2 {
margin: 0;
transition: color 0.6s cubic-bezier(0.22, 1, 0.36, 1);
}
p {
margin-top: 0.75rem;
margin-bottom: 0;
}
h2 span {
display: inline-block;
transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1);
}
.link-card:is(:hover, :focus-within) {
background-position: 0;
}
.link-card:is(:hover, :focus-within) h2 {
color: #4f39fa;
}
.link-card:is(:hover, :focus-within) h2 span {
transform: translateX(2px);
}
</style>
+33
View File
@@ -0,0 +1,33 @@
---
// TODO: Only execute when compiling Astro templates, so it will not be dynamically updated
const today = new Date();
---
<footer>
<div class="footer-inner">
<span>
&copy; {today.getFullYear()}
<th:block th:text="${site.title}"></th:block>.
</span>
</div>
</footer>
<style>
footer {
border-top: 1px solid var(--rule);
background: var(--bg);
}
.footer-inner {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
width: 820px;
max-width: calc(100% - 2.5rem);
margin: 0 auto;
padding: 1.5rem 0 2.5rem;
color: var(--ink-3);
font-size: 0.88rem;
}
</style>
+101
View File
@@ -0,0 +1,101 @@
---
import ThemeSwitcher from "./ThemeSwitcher.vue";
---
<header>
<div class="header-inner">
<a class="brand" href="/" th:text="${site.title}"></a>
<nav
th:with="menu = ${menuFinder.getPrimary()}"
aria-label="Main navigation"
>
<a
th:each="menuItem : ${menu.menuItems}"
th:href="@{${menuItem.status.href}}"
th:target="${menuItem.spec.target?.value}"
th:text="${menuItem.status.displayName}"
>
</a>
</nav>
<ThemeSwitcher client:load />
</div>
</header>
<style>
header {
background: var(--bg);
border-bottom: 1px solid var(--rule);
position: sticky;
top: 0;
z-index: 100;
}
.header-inner {
display: flex;
align-items: center;
justify-content: space-between;
gap: 2rem;
width: 820px;
max-width: calc(100% - 2.5rem);
margin: 0 auto;
height: 58px;
}
.brand {
font-size: 1rem;
font-weight: 700;
color: var(--ink);
text-decoration: none;
letter-spacing: -0.02em;
flex-shrink: 0;
}
.brand:hover {
color: var(--accent);
}
nav {
display: flex;
align-items: center;
gap: 0.2rem;
}
nav :global(a) {
display: inline-block;
padding: 0.3rem 0.65rem;
border-radius: 6px;
font-size: 0.9rem;
color: var(--ink-2);
text-decoration: none;
transition:
color 0.15s ease,
background 0.15s ease;
}
nav :global(a:hover) {
color: var(--ink);
background: var(--bg-raised);
}
nav :global(a.active) {
color: var(--ink);
background: var(--bg-raised);
font-weight: 600;
}
@media (max-width: 680px) {
.header-inner {
flex-direction: column;
align-items: flex-start;
height: auto;
padding: 1rem 0;
gap: 0.75rem;
}
nav {
flex-wrap: wrap;
gap: 0.1rem;
margin-left: -0.65rem;
}
}
</style>
-52
View File
@@ -1,52 +0,0 @@
<script lang="ts" setup>
import { TransitionRoot } from "@headlessui/vue";
import { ref } from "vue";
const isShowing = ref(true);
function resetIsShowing() {
isShowing.value = false;
setTimeout(() => {
isShowing.value = true;
}, 500);
}
</script>
<template>
<div class="p-4">
<span class="text-xl font-bold" th:text="@{post.title}">Hello Halo</span>
<div class="flex flex-col items-center py-16">
<div class="h-32 w-32">
<TransitionRoot
:show="isShowing"
appear
as="template"
enter="transform transition duration-[400ms]"
enter-from="opacity-0 rotate-[-120deg] scale-50"
enter-to="opacity-100 rotate-0 scale-100"
leave="transform duration-200 transition ease-in-out"
leave-from="opacity-100 rotate-0 scale-100 "
leave-to="opacity-0 scale-95 "
>
<div class="h-full w-full rounded-md bg-white shadow-lg" />
</TransitionRoot>
</div>
<button
class="mt-8 flex transform items-center rounded-full bg-black bg-opacity-20 px-3 py-2 text-sm font-medium text-white transition hover:scale-105 hover:bg-opacity-30 focus:outline-none active:bg-opacity-40"
@click="resetIsShowing"
>
<svg class="h-5 w-5 opacity-70" fill="none" viewBox="0 0 20 20">
<path
d="M14.9497 14.9498C12.2161 17.6835 7.78392 17.6835 5.05025 14.9498C2.31658 12.2162 2.31658 7.784 5.05025 5.05033C7.78392 2.31666 12.2161 2.31666 14.9497 5.05033C15.5333 5.63385 15.9922 6.29475 16.3266 7M16.9497 2L17 7H16.3266M12 7L16.3266 7"
stroke="currentColor"
stroke-width="1.5"
/>
</svg>
<span class="ml-3">Click to transition</span>
</button>
</div>
</div>
</template>
+90
View File
@@ -0,0 +1,90 @@
<script lang="ts" setup>
import { ref, onMounted } from "vue";
const isDark = ref(false);
onMounted(() => {
isDark.value = document.documentElement.classList.contains("dark");
});
function toggle() {
isDark.value = !isDark.value;
document.documentElement.classList.toggle("dark", isDark.value);
localStorage.setItem("theme", isDark.value ? "dark" : "light");
}
</script>
<template>
<button
class="theme-toggle"
type="button"
:aria-label="isDark ? 'Switch to light mode' : 'Switch to dark mode'"
@click="toggle"
>
<!-- Moon: shown in light mode -->
<svg
v-if="!isDark"
xmlns="http://www.w3.org/2000/svg"
width="17"
height="17"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
</svg>
<!-- Sun: shown in dark mode -->
<svg
v-else
xmlns="http://www.w3.org/2000/svg"
width="17"
height="17"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<circle cx="12" cy="12" r="5" />
<line x1="12" y1="1" x2="12" y2="3" />
<line x1="12" y1="21" x2="12" y2="23" />
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
<line x1="1" y1="12" x2="3" y2="12" />
<line x1="21" y1="12" x2="23" y2="12" />
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
</svg>
</button>
</template>
<style scoped>
.theme-toggle {
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
width: 34px;
height: 34px;
padding: 0;
border: none;
border-radius: 8px;
background: transparent;
color: var(--ink-2);
cursor: pointer;
transition:
background 0.15s ease,
color 0.15s ease;
}
.theme-toggle:hover {
background: var(--bg-raised);
color: var(--ink);
}
</style>
+13
View File
@@ -0,0 +1,13 @@
declare namespace astroHTML.JSX {
interface HTMLAttributes {
"th:each"?: string;
"th:href"?: string;
"th:text"?: string;
"th:if"?: string;
"th:unless"?: string;
"th:utext"?: string;
"th:src"?: string;
"th:with"?: string;
"th:target"?: string;
}
}
+45 -62
View File
@@ -1,69 +1,52 @@
---
export interface Props {
title: string;
import "../styles/global.css";
import Footer from "../components/Footer.astro";
import Header from "../components/Header.astro";
interface Props {
pageTitle?: string;
contentClass?: string;
}
const { title } = Astro.props as Props;
const { pageTitle, contentClass } = Astro.props;
const hasHeading = Boolean(pageTitle);
---
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<title>{title}</title>
</head>
<body>
<div class="flex justify-between">
<nav class="flex gap-3">
<a href="/" th:href="@{/}">Home</a>
<a href="/post" th:href="@{/post}">Post</a>
<a href="/links" th:href="@{/links}">Links</a>
</nav>
<div>
<a href="/">默认</a>
<a href="/?language=zh">中文</a>
<a href="/?language=en">英文</a>
</div>
</div>
<slot />
</body>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<script is:inline>
(function () {
var stored = localStorage.getItem("theme");
var prefersDark = window.matchMedia(
"(prefers-color-scheme: dark)",
).matches;
if (stored === "dark" || (!stored && prefersDark)) {
document.documentElement.classList.add("dark");
}
})();
</script>
<slot name="head" />
</head>
<body>
<Header />
<main class="layout-main">
<article class="page-shell">
{
hasHeading && (
<header class="page-heading">
{pageTitle && <h1>{pageTitle}</h1>}
</header>
)
}
<div class:list={["page-content", contentClass]}>
<slot />
</div>
</article>
</main>
<Footer />
</body>
</html>
<style>
:root {
--font-size-base: clamp(1rem, 0.34vw + 0.91rem, 1.19rem);
--font-size-lg: clamp(1.2rem, 0.7vw + 1.2rem, 1.5rem);
--font-size-xl: clamp(2.44rem, 2.38vw + 1.85rem, 3.75rem);
--color-text: hsl(12, 5%, 4%);
--color-bg: hsl(10, 21%, 95%);
--color-border: hsl(17, 24%, 90%);
}
html {
font-family: system-ui, sans-serif;
font-size: var(--font-size-base);
color: var(--color-text);
background-color: var(--color-bg);
}
body {
margin: 0;
}
:global(h1) {
font-size: var(--font-size-xl);
}
:global(h2) {
font-size: var(--font-size-lg);
}
:global(code) {
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
Bitstream Vera Sans Mono, Courier New, monospace;
}
</style>
+65
View File
@@ -0,0 +1,65 @@
---
import Layout from "../layouts/Layout.astro";
---
<Layout pageTitle="归档">
<Fragment slot="head">
<title th:text="|归档 - ${site.title}|"></title>
</Fragment>
<section class="archive-list" aria-label="Archive list">
<th:block th:each="archive : ${archives.items}">
<th:block th:each="month : ${archive.months}">
<div class="archive-group">
<h2
class="archive-group__year"
th:text="|${archive.year} · ${month.month}|"
>
</h2>
<ul class="archive-group__items">
<li th:each="post : ${month.posts}" class="archive-entry">
<span
class="archive-entry__date"
th:text="${#dates.format(post.spec.publishTime, 'MM/dd')}"
></span>
<div>
<h3 class="archive-entry__title">
<a
th:href="@{${post.status.permalink}}"
th:text="${post.spec.title}"></a>
</h3>
<p
class="archive-entry__summary"
th:text="${post.status.excerpt}"
>
</p>
</div>
</li>
</ul>
</div>
</th:block>
</th:block>
<div th:if="${archives.total == 0}" class="archive-empty">暂无文章。</div>
</section>
<nav
th:if="${archives.totalPages gt 1}"
class="pagination"
aria-label="Pagination"
>
<a
th:if="${archives.hasPrevious()}"
th:href="@{${archives.prevUrl}}"
class="pagination__prev">&larr; 较新</a
>
<span
class="pagination__info"
th:text="|${archives.page} / ${archives.totalPages}|"></span>
<a
th:if="${archives.hasNext()}"
th:href="@{${archives.nextUrl}}"
class="pagination__next">较旧 &rarr;</a
>
</nav>
</Layout>
+25
View File
@@ -0,0 +1,25 @@
---
import Layout from "../layouts/Layout.astro";
---
<Layout pageTitle="分类">
<Fragment slot="head">
<title th:text="|分类 - ${site.title}|"></title>
</Fragment>
<nav class="taxonomy-list" aria-label="Category list">
<div
th:each="category : ${categories}"
class="taxonomy-list__item"
>
<a
class="taxonomy-list__link"
th:href="@{${category.status.permalink}}"
th:text="${category.spec.displayName}"
>
</a>
<span class="taxonomy-list__count" th:text="|${category.postCount} 篇|">
</span>
</div>
</nav>
</Layout>
+59
View File
@@ -0,0 +1,59 @@
---
import Layout from "../layouts/Layout.astro";
---
<Layout>
<Fragment slot="head">
<title th:text="|${category.spec.displayName} - ${site.title}|"></title>
</Fragment>
<div class="page-heading">
<h1 th:text="${category.spec.displayName}"></h1>
<p class="page-meta">
<span th:text="|${posts.total} 篇文章|"></span>
</p>
</div>
<ul class="post-feed__list" role="list">
<li th:each="post : ${posts.items}" class="post-feed__item">
<a class="post-feed__link" th:href="@{${post.status.permalink}}">
<div class="post-feed__body">
<p
class="post-feed__date"
th:text="${#dates.format(post.spec.publishTime, 'yyyy-MM-dd')}"
>
</p>
<h2 class="post-feed__title" th:text="${post.spec.title}"></h2>
<p class="post-feed__excerpt" th:text="${post.status.excerpt}"></p>
</div>
<img
th:unless="${#strings.isEmpty(post.spec.cover)}"
class="post-feed__image"
th:src="${post.spec.cover}"
/>
</a>
</li>
</ul>
<div th:if="${posts.total == 0}" class="feed-empty">暂无文章。</div>
<nav
th:if="${posts.totalPages gt 1}"
class="pagination"
aria-label="Pagination"
>
<a
th:if="${posts.hasPrevious()}"
th:href="@{${posts.prevUrl}}"
class="pagination__prev">&larr; 较新</a
>
<span
class="pagination__info"
th:text="|${posts.page} / ${posts.totalPages}|"></span>
<a
th:if="${posts.hasNext()}"
th:href="@{${posts.nextUrl}}"
class="pagination__next">较旧 &rarr;</a
>
</nav>
</Layout>
+44 -90
View File
@@ -1,95 +1,49 @@
---
import Layout from "../layouts/Layout.astro";
import Card from "../components/Card.astro";
import PostCard from "../components/PostCard.vue"
---
<Layout title="Welcome to Astro.">
<main>
<h1>Welcome to <span class="text-gradient">Astro</span></h1>
<p class="instructions">
Check out the <code>src/pages</code> directory to get started.<br />
<strong>Code Challenge:</strong> Tweak the "Welcome to Astro" message above.
</p>
<ul role="list" class="link-card-grid">
<Card href="https://docs.astro.build/" title="Documentation"
body="Learn how Astro works and explore the official API docs." />
<Card href="https://astro.build/integrations/" title="Integrations"
body="Supercharge your project with new frameworks and libraries." />
<Card href="https://astro.build/themes/" title="Themes"
body="Explore a galaxy of community-built starter themes." />
<Card href="https://astro.build/chat/" title="Chat" body="Come say hi to our amazing Discord community. ❤️" />
</ul>
<ul class="list-decimal list-inside">
<li th:text="#{foo}"></li>
<li th:text="#{bar}"></li>
<li th:text="#{hello('Ryan Wang')}"></li>
</ul>
<PostCard client:visible />
<p th:each="post : ${postFinder.list(1, 10)}">
<span th:text="${post.name}">Onions</span>
</p>
</main>
<Layout>
<Fragment slot="head">
<title th:text="${site.title}"></title>
</Fragment>
<ul class="post-feed__list" role="list">
<li th:each="post : ${posts.items}" class="post-feed__item">
<a class="post-feed__link" th:href="@{${post.status.permalink}}">
<div class="post-feed__body">
<p
class="post-feed__date"
th:text="${#dates.format(post.spec.publishTime, 'yyyy-MM-dd')}"
>
</p>
<h2 class="post-feed__title" th:text="${post.spec.title}"></h2>
<p class="post-feed__excerpt" th:text="${post.status.excerpt}"></p>
</div>
<img
th:unless="${#strings.isEmpty(post.spec.cover)}"
class="post-feed__image"
th:src="${post.spec.cover}"
/>
</a>
</li>
</ul>
<nav
th:if="${posts.totalPages gt 1}"
class="pagination"
aria-label="Pagination"
>
<a
th:if="${posts.hasPrevious()}"
th:href="@{${posts.prevUrl}}"
class="pagination__prev">&larr; 较新</a
>
<span
class="pagination__info"
th:text="|${posts.page} / ${posts.totalPages}|"></span>
<a
th:if="${posts.hasNext()}"
th:href="@{${posts.nextUrl}}"
class="pagination__next">较旧 &rarr;</a
>
</nav>
</Layout>
<style>
:root {
--astro-gradient: linear-gradient(0deg, #4f39fa, #da62c4);
}
h1 {
margin: 2rem 0;
}
main {
margin: auto;
padding: 1em;
max-width: 70ch;
}
.text-gradient {
font-weight: 900;
background-image: var(--astro-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-size: 100% 200%;
background-position-y: 100%;
border-radius: 0.4rem;
animation: pulse 4s ease-in-out infinite;
}
@keyframes pulse {
0%,
100% {
background-position-y: 0%;
}
50% {
background-position-y: 80%;
}
}
.instructions {
line-height: 1.6;
margin: 1rem 0;
background: #4f39fa;
padding: 1rem;
border-radius: 0.4rem;
color: var(--color-bg);
}
.instructions code {
font-size: 0.875em;
border: 0.1em solid var(--color-border);
border-radius: 4px;
padding: 0.15em 0.25em;
}
.link-card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(24ch, 1fr));
gap: 1rem;
padding: 0;
}
</style>
-73
View File
@@ -1,73 +0,0 @@
---
import Layout from "../layouts/Layout.astro";
const links = [
{
"id": 1,
"name": "Ryan Wang's Blog",
"url": "https://ryanc.cc/",
"logo": "https://ryanc.cc/avatar",
"description": "Hello Halo",
"team": "",
"priority": 0
},
{
"id": 40,
"name": "guqing's blog",
"url": "https://www.guqing.xyz/",
"logo": "https://www.guqing.xyz/avatar",
"description": "毕生所求无它,爱与自由而已",
"team": "",
"priority": 0
},
{
"id": 104,
"name": "JohnNiang's Blog",
"url": "https://johnniang.me",
"logo": "https://johnniang.me/avatar",
"description": "Core contributor of Halo organization.",
"team": "",
"priority": 0
},
{
"id": 152,
"name": "烟霞志",
"url": "https://simplestark.com",
"logo": "https://simplestark.com/avatar",
"description": "空有烟霞之志,叹无水云之身。",
"team": "",
"priority": 0
},
]
---
<Layout title="Links">
<main>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-2">
{links.map((data) => <a href={data.url} th:href="#{data.url}" target="_blank">
<div
class="relative rounded border border-gray-300 bg-white px-5 py-4 shadow-sm hover:shadow flex items-center space-x-3 hover:border-gray-400">
<div class="flex-shrink-0">
<img class="h-12 w-12 rounded-full" th:src="#{data.logo}" th:alt="#{data.name}" src={data.logo}
alt={data.name}>
</div>
<div class="flex-1 min-w-0">
<div>
<p class="text-sm font-medium text-gray-900 truncate" th:text="#{data.name}">{data.name}</p>
<p class="text-sm text-gray-500 truncate" th:text="#{data.description}">{data.description}</p>
</div>
</div>
</div>
</a>)}
</div>
</main>
</Layout>
<style>
main {
margin: auto;
padding: 1em;
max-width: 70ch;
}
</style>
+25
View File
@@ -0,0 +1,25 @@
---
import Layout from "../layouts/Layout.astro";
---
<Layout contentClass="prose">
<Fragment slot="head">
<title th:text="|${singlePage.spec.title} - ${site.title}|"></title>
</Fragment>
<div class="page-heading">
<h1 th:text="${singlePage.spec.title}"></h1>
<p class="page-meta">
<span
th:text="${#dates.format(singlePage.spec.publishTime, 'yyyy-MM-dd')}"
></span>
</p>
</div>
<div th:utext="${singlePage.content.content}"></div>
<halo:comment
group="content.halo.run"
kind="SinglePage"
th:attr="name=${singlePage.metadata.name}"></halo:comment>
</Layout>
+22 -10
View File
@@ -1,14 +1,26 @@
---
import Layout from "../layouts/Layout.astro";
import PostCard from "../components/PostCard.vue"
import dayjs from 'dayjs'
const now = dayjs()
---
<Layout title="Welcome to Post Page.">
<time>{now}</time>
<main>
<PostCard client:visible />
</main>
</Layout>
<Layout contentClass="prose">
<Fragment slot="head">
<title th:text="|${post.spec.title} - ${site.title}|"></title>
</Fragment>
<div class="page-heading">
<h1 th:text="${post.spec.title}"></h1>
<p class="page-meta">
<span th:text="${#dates.format(post.spec.publishTime, 'yyyy-MM-dd')}"
></span>
</p>
</div>
<th:block th:utext="${post.content.content}"></th:block>
<halo:comment
group="content.halo.run"
kind="Post"
th:attr="name=${post.metadata.name}"
>
</halo:comment>
</Layout>
+59
View File
@@ -0,0 +1,59 @@
---
import Layout from "../layouts/Layout.astro";
---
<Layout>
<Fragment slot="head">
<title th:text="|${tag.spec.displayName} - ${site.title}|"></title>
</Fragment>
<div class="page-heading">
<h1 th:text="${tag.spec.displayName}"></h1>
<p class="page-meta">
<span th:text="|${posts.total} 篇文章|"></span>
</p>
</div>
<ul class="post-feed__list" role="list">
<li th:each="post : ${posts.items}" class="post-feed__item">
<a class="post-feed__link" th:href="@{${post.status.permalink}}">
<div class="post-feed__body">
<p
class="post-feed__date"
th:text="${#dates.format(post.spec.publishTime, 'yyyy-MM-dd')}"
>
</p>
<h2 class="post-feed__title" th:text="${post.spec.title}"></h2>
<p class="post-feed__excerpt" th:text="${post.status.excerpt}"></p>
</div>
<img
th:unless="${#strings.isEmpty(post.spec.cover)}"
class="post-feed__image"
th:src="${post.spec.cover}"
/>
</a>
</li>
</ul>
<div th:if="${posts.total == 0}" class="feed-empty">暂无文章。</div>
<nav
th:if="${posts.totalPages gt 1}"
class="pagination"
aria-label="Pagination"
>
<a
th:if="${posts.hasPrevious()}"
th:href="@{${posts.prevUrl}}"
class="pagination__prev">&larr; 较新</a
>
<span
class="pagination__info"
th:text="|${posts.page} / ${posts.totalPages}|"></span>
<a
th:if="${posts.hasNext()}"
th:href="@{${posts.nextUrl}}"
class="pagination__next">较旧 &rarr;</a
>
</nav>
</Layout>
+21
View File
@@ -0,0 +1,21 @@
---
import Layout from "../layouts/Layout.astro";
---
<Layout pageTitle="标签">
<Fragment slot="head">
<title th:text="|标签 - ${site.title}|"></title>
</Fragment>
<div class="tag-cloud" role="list" aria-label="Tag list">
<a
th:each="tag : ${tags}"
class="tag-pill"
role="listitem"
th:href="@{${tag.status.permalink}}"
>
<span th:text="${tag.spec.displayName}"></span>
<span class="tag-pill__count" th:text="${tag.postCount}"></span>
</a>
</div>
</Layout>
+465
View File
@@ -0,0 +1,465 @@
:root {
--bg: #faf8f5;
--bg-raised: #f2ede8;
--ink: #1a1410;
--ink-2: #7a7068;
--ink-3: #b5aca2;
--rule: #ddd8d0;
--accent: #c2410c;
--accent-bg: #fff4ed;
--black: 26, 20, 16;
--gray: 122, 112, 104;
--gray-light: 221, 216, 208;
--gray-dark: 44, 36, 30;
}
html.dark {
--bg: #181613;
--bg-raised: #242018;
--ink: #ede9e3;
--ink-2: #9a9088;
--ink-3: #5e5650;
--rule: #2e2a24;
--accent: #f47040;
--accent-bg: #2a1a0c;
}
*,
*::before,
*::after {
box-sizing: border-box;
min-width: 0;
}
body {
font-family: ui-sans-serif, sans-serif;
margin: 0;
padding: 0;
background-color: var(--bg);
color: var(--ink);
font-size: 17px;
line-height: 1.75;
-webkit-font-smoothing: antialiased;
word-wrap: break-word;
overflow-wrap: break-word;
}
main {
width: 820px;
max-width: calc(100% - 2.5rem);
margin: 0 auto;
padding: 3rem 0 5rem;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: ui-sans-serif, sans-serif;
margin: 0 0 0.5em;
color: var(--ink);
line-height: 1.2;
font-weight: 700;
letter-spacing: -0.01em;
}
h1 {
font-size: 2.4rem;
}
h2 {
font-size: 1.7rem;
}
h3 {
font-size: 1.3rem;
}
h4 {
font-size: 1.1rem;
}
h5 {
font-size: 1rem;
}
strong,
b {
font-weight: 700;
}
a {
color: var(--accent);
text-decoration: underline;
text-decoration-thickness: 1px;
text-underline-offset: 0.2em;
}
a:hover {
color: var(--ink);
}
p {
margin: 0 0 1em;
}
textarea {
width: 100%;
font-size: 16px;
}
input {
font-size: 16px;
}
table {
display: block;
width: 100%;
max-width: 100%;
overflow-x: auto;
}
img,
video,
iframe,
embed,
object {
max-width: 100%;
height: auto;
}
img {
border-radius: 10px;
}
code {
font-size: 0.88em;
padding: 0.15em 0.4em;
background: var(--bg-raised);
border-radius: 4px;
border: 1px solid var(--rule);
}
pre {
padding: 1.4em 1.6em;
border-radius: 10px;
overflow-x: auto;
background: var(--ink);
color: #f8f8f2;
}
pre > code {
all: unset;
font-size: 0.9em;
color: inherit;
}
blockquote {
margin: 1.5em 0;
padding: 0.75em 0 0.75em 1.5em;
border-left: 3px solid var(--accent);
color: var(--ink-2);
font-size: 1.1em;
}
hr {
margin: 2.5rem 0;
border: none;
border-top: 1px solid var(--rule);
}
/* ─── Page shell ─────────────────────────────────── */
.page-shell {
display: grid;
gap: 2.5rem;
}
.page-heading {
padding-bottom: 1.5rem;
border-bottom: 1px solid var(--rule);
}
.page-heading h1 {
margin-bottom: 0.25em;
}
.page-meta {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.6rem;
margin-bottom: 0.6rem;
color: var(--ink-3);
font-size: 0.78rem;
letter-spacing: 0.1em;
text-transform: uppercase;
}
.page-content {
display: grid;
gap: 2rem;
}
/* ─── Post feed ──────────────────────────────────── */
.post-feed__list {
list-style: none;
margin: 0;
padding: 0;
}
.post-feed__item + .post-feed__item {
border-top: 1px solid var(--rule);
}
.post-feed__link {
display: grid;
grid-template-columns: minmax(0, 1fr) 200px;
gap: 2rem;
align-items: start;
padding: 2rem 0;
color: inherit;
text-decoration: none;
}
.post-feed__item:first-child .post-feed__link {
padding-top: 0;
}
.post-feed__body {
display: flex;
flex-direction: column;
gap: 0.55rem;
align-self: center;
}
.post-feed__date {
margin: 0;
color: var(--ink-3);
font-size: 0.8rem;
letter-spacing: 0.1em;
text-transform: uppercase;
}
.post-feed__title {
margin: 0;
font-size: 1.35rem;
line-height: 1.3;
color: var(--ink);
transition: color 0.15s ease;
}
.post-feed__excerpt {
margin: 0;
color: var(--ink-2);
font-size: 0.96rem;
line-height: 1.6;
}
.post-feed__image {
display: block;
width: 100%;
aspect-ratio: 4 / 3;
object-fit: cover;
border-radius: 10px;
transition: opacity 0.2s ease;
}
.post-feed__link:hover .post-feed__title {
color: var(--accent);
}
.post-feed__link:hover .post-feed__image {
opacity: 0.88;
}
/* ─── Taxonomy list (categories) ─────────────────── */
.taxonomy-list__item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: 0.55rem 0;
}
.taxonomy-list__link {
color: var(--ink);
font-size: 1.05rem;
font-weight: 600;
text-decoration: none;
transition: color 0.15s ease;
}
.taxonomy-list__link:hover {
color: var(--accent);
}
.taxonomy-list__count {
display: inline-flex;
align-items: center;
padding: 0.2em 0.7em;
border-radius: 999px;
background: var(--bg-raised);
border: 1px solid var(--rule);
color: var(--ink-2);
font-size: 0.82rem;
white-space: nowrap;
}
/* ─── Tag cloud (badges) ─────────────────────────── */
.tag-cloud {
display: flex;
flex-wrap: wrap;
gap: 0.6rem;
}
.tag-pill {
display: inline-flex;
align-items: center;
gap: 0.4rem;
padding: 0.35em 0.85em;
border-radius: 6px;
background: var(--bg-raised);
border: 1px solid var(--rule);
color: var(--ink-2);
font-size: 0.88rem;
text-decoration: none;
transition:
background 0.15s ease,
color 0.15s ease,
border-color 0.15s ease;
}
.tag-pill:hover {
background: var(--accent-bg);
color: var(--accent);
border-color: var(--accent);
}
.tag-pill__count {
font-size: 0.8em;
color: var(--ink-3);
}
/* ─── Archive ────────────────────────────────────── */
.archive-list {
display: grid;
gap: 3rem;
}
.archive-group {
display: grid;
grid-template-columns: 80px minmax(0, 1fr);
gap: 2rem;
}
.archive-group__year {
margin: 0;
font-size: 1.05rem;
font-weight: 700;
color: var(--ink-3);
letter-spacing: -0.01em;
padding-top: 0.2rem;
}
.archive-group__items {
list-style: none;
margin: 0;
padding: 0;
}
.archive-entry {
display: grid;
grid-template-columns: 80px minmax(0, 1fr);
gap: 1rem;
padding: 0.5rem 0;
}
.archive-entry__date {
color: var(--ink-3);
font-size: 0.85rem;
padding-top: 0.15rem;
}
.archive-entry__title {
margin: 0 0 0.2rem;
font-size: 1rem;
font-weight: 600;
}
.archive-entry__summary {
margin: 0;
font-size: 0.9rem;
color: var(--ink-2);
}
/* ─── Responsive ─────────────────────────────────── */
@media (max-width: 680px) {
main {
max-width: calc(100% - 2rem);
padding: 2rem 0 4rem;
}
h1 {
font-size: 1.9rem;
}
h2 {
font-size: 1.4rem;
}
.post-feed__link {
grid-template-columns: 1fr;
gap: 1rem;
padding: 1.5rem 0;
}
.post-feed__image {
aspect-ratio: 16 / 9;
order: -1;
}
.taxonomy-list__item,
.archive-group,
.archive-entry {
grid-template-columns: 1fr;
}
.archive-group {
gap: 0.5rem;
}
.taxonomy-list__item {
align-items: flex-start;
flex-direction: column;
gap: 0.5rem;
}
}
/* ─── Pagination ─────────────────────────────────── */
.pagination {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding-top: 2rem;
border-top: 1px solid var(--rule);
font-size: 0.95rem;
}
.pagination__prev,
.pagination__next {
color: var(--ink-2);
text-decoration: none;
transition: color 0.15s ease;
}
.pagination__prev:hover,
.pagination__next:hover {
color: var(--accent);
}
.pagination__info {
color: var(--ink-3);
}
.archive-empty,
.feed-empty {
color: var(--ink-3);
font-size: 0.95rem;
padding: 2rem 0;
}
-8
View File
@@ -1,8 +0,0 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./src/**/*.{astro,html,js,jsx,md,svelte,ts,tsx,vue}"],
theme: {
extend: {},
},
plugins: [],
};
-17
View File
@@ -1,17 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>About</title>
</head>
<body>
<p
th:text="没有经过 Astro 渲染,原生使用 Thymeleaf 语法。在构建时,将被 Copy 到 templates 目录。(Thymeleaf)"
>
没有经过 Astro 渲染,原生使用 Thymeleaf 语法。在构建时,将被 Copy 到
templates 目录。(Native)
</p>
</body>
</html>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
import{c as r,a as l,b as s,d,o as c,r as u,e as i}from"./runtime-core.esm-bundler.25dbJ6OW.js";const x=(a,t)=>{const e=a.__vccOpts||a;for(const[n,o]of t)e[n]=o;return e},v=d({__name:"ThemeSwitcher",setup(a,{expose:t}){t();const e=u(!1);c(()=>{e.value=document.documentElement.classList.contains("dark")});function n(){e.value=!e.value,document.documentElement.classList.toggle("dark",e.value),localStorage.setItem("theme",e.value?"dark":"light")}const o={isDark:e,toggle:n};return Object.defineProperty(o,"__isScriptSetup",{enumerable:!1,value:!0}),o}}),_=["aria-label"],h={key:0,xmlns:"http://www.w3.org/2000/svg",width:"17",height:"17",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round","aria-hidden":"true"},m={key:1,xmlns:"http://www.w3.org/2000/svg",width:"17",height:"17",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round","aria-hidden":"true"};function y(a,t,e,n,o,k){return i(),r("button",{class:"theme-toggle",type:"button","aria-label":n.isDark?"Switch to light mode":"Switch to dark mode",onClick:n.toggle},[n.isDark?(i(),r("svg",m,[...t[1]||(t[1]=[s('<circle cx="12" cy="12" r="5" data-v-976d6eb8></circle><line x1="12" y1="1" x2="12" y2="3" data-v-976d6eb8></line><line x1="12" y1="21" x2="12" y2="23" data-v-976d6eb8></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64" data-v-976d6eb8></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78" data-v-976d6eb8></line><line x1="1" y1="12" x2="3" y2="12" data-v-976d6eb8></line><line x1="21" y1="12" x2="23" y2="12" data-v-976d6eb8></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36" data-v-976d6eb8></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22" data-v-976d6eb8></line>',9)])])):(i(),r("svg",h,[...t[0]||(t[0]=[l("path",{d:"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"},null,-1)])]))],8,_)}const g=x(v,[["render",y],["__scopeId","data-v-976d6eb8"]]);export{g as default};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
:root{--astro-gradient: linear-gradient(0deg, #4f39fa, #da62c4)}h1:where(.astro-AQ2P6PWU){margin:2rem 0}main:where(.astro-AQ2P6PWU){margin:auto;padding:1em;max-width:70ch}.text-gradient:where(.astro-AQ2P6PWU){font-weight:900;background-image:var(--astro-gradient);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-size:100% 200%;background-position-y:100%;border-radius:.4rem;-webkit-animation:pulse 4s ease-in-out infinite;animation:pulse 4s ease-in-out infinite}@-webkit-keyframes pulse{0%,to{background-position-y:0%}50%{background-position-y:80%}}@keyframes pulse{0%,to{background-position-y:0%}50%{background-position-y:80%}}.instructions:where(.astro-AQ2P6PWU){line-height:1.6;margin:1rem 0;background:#4f39fa;padding:1rem;border-radius:.4rem;color:var(--color-bg)}.instructions:where(.astro-AQ2P6PWU) code:where(.astro-AQ2P6PWU){font-size:.875em;border:.1em solid var(--color-border);border-radius:4px;padding:.15em .25em}.link-card-grid:where(.astro-AQ2P6PWU){display:grid;grid-template-columns:repeat(auto-fit,minmax(24ch,1fr));gap:1rem;padding:0}:root{--link-gradient: linear-gradient(45deg, #4f39fa, #da62c4 30%, var(--color-border) 60%)}.link-card:where(.astro-DYDDWWYO){list-style:none;display:flex;padding:.15rem;background-image:var(--link-gradient);background-size:400%;border-radius:.5rem;background-position:100%;transition:background-position .6s cubic-bezier(.22,1,.36,1)}.link-card:where(.astro-DYDDWWYO)>a:where(.astro-DYDDWWYO){width:100%;text-decoration:none;line-height:1.4;padding:1em 1.3em;border-radius:.35rem;color:var(--text-color);background-color:#fff;opacity:.8}h2:where(.astro-DYDDWWYO){margin:0;transition:color .6s cubic-bezier(.22,1,.36,1)}p:where(.astro-DYDDWWYO){margin-top:.75rem;margin-bottom:0}h2:where(.astro-DYDDWWYO) span:where(.astro-DYDDWWYO){display:inline-block;transition:transform .3s cubic-bezier(.22,1,.36,1)}.link-card:where(.astro-DYDDWWYO):is(:hover,:focus-within){background-position:0}.link-card:where(.astro-DYDDWWYO):is(:hover,:focus-within) h2:where(.astro-DYDDWWYO){color:#4f39fa}.link-card:where(.astro-DYDDWWYO):is(:hover,:focus-within) h2:where(.astro-DYDDWWYO) span:where(.astro-DYDDWWYO){transform:translate(2px)}
-1
View File
@@ -1 +0,0 @@
main:where(.astro-SLXJX4NQ){margin:auto;padding:1em;max-width:70ch}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

+13 -86
View File
File diff suppressed because one or more lines are too long
-78
View File
@@ -1,78 +0,0 @@
<!DOCTYPE html><html lang="en" class="astro-TFKA5VKL"><head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<title>Links</title>
<link rel="stylesheet" href="/assets/index-links-post.be069062.css" th:href="@{/assets/index-links-post.be069062.css}">
<link rel="stylesheet" href="/assets/links.5e24488a.css" th:href="@{/assets/links.5e24488a.css}"></head>
<body class="astro-TFKA5VKL">
<div class="flex justify-between astro-TFKA5VKL">
<nav class="flex gap-3 astro-TFKA5VKL">
<a href="/" th:href="@{/}" class="astro-TFKA5VKL">Home</a>
<a href="/post" th:href="@{/post}" class="astro-TFKA5VKL">Post</a>
<a href="/links" th:href="@{/links}" class="astro-TFKA5VKL">Links</a>
</nav>
<div class="astro-TFKA5VKL">
<a href="/" class="astro-TFKA5VKL">默认</a>
<a href="/?language=zh" class="astro-TFKA5VKL">中文</a>
<a href="/?language=en" class="astro-TFKA5VKL">英文</a>
</div>
</div>
<main class="astro-SLXJX4NQ">
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-2 astro-SLXJX4NQ">
<a href="https://ryanc.cc/" th:href="#{data.url}" target="_blank" class="astro-SLXJX4NQ">
<div class="relative rounded border border-gray-300 bg-white px-5 py-4 shadow-sm hover:shadow flex items-center space-x-3 hover:border-gray-400 astro-SLXJX4NQ">
<div class="flex-shrink-0 astro-SLXJX4NQ">
<img class="h-12 w-12 rounded-full astro-SLXJX4NQ" th:src="#{data.logo}" th:alt="#{data.name}" src="https://ryanc.cc/avatar" alt="Ryan Wang's Blog">
</div>
<div class="flex-1 min-w-0 astro-SLXJX4NQ">
<div class="astro-SLXJX4NQ">
<p class="text-sm font-medium text-gray-900 truncate astro-SLXJX4NQ" th:text="#{data.name}">Ryan Wang's Blog</p>
<p class="text-sm text-gray-500 truncate astro-SLXJX4NQ" th:text="#{data.description}">Hello Halo</p>
</div>
</div>
</div>
</a><a href="https://www.guqing.xyz/" th:href="#{data.url}" target="_blank" class="astro-SLXJX4NQ">
<div class="relative rounded border border-gray-300 bg-white px-5 py-4 shadow-sm hover:shadow flex items-center space-x-3 hover:border-gray-400 astro-SLXJX4NQ">
<div class="flex-shrink-0 astro-SLXJX4NQ">
<img class="h-12 w-12 rounded-full astro-SLXJX4NQ" th:src="#{data.logo}" th:alt="#{data.name}" src="https://www.guqing.xyz/avatar" alt="guqing's blog">
</div>
<div class="flex-1 min-w-0 astro-SLXJX4NQ">
<div class="astro-SLXJX4NQ">
<p class="text-sm font-medium text-gray-900 truncate astro-SLXJX4NQ" th:text="#{data.name}">guqing's blog</p>
<p class="text-sm text-gray-500 truncate astro-SLXJX4NQ" th:text="#{data.description}">毕生所求无它,爱与自由而已</p>
</div>
</div>
</div>
</a><a href="https://johnniang.me" th:href="#{data.url}" target="_blank" class="astro-SLXJX4NQ">
<div class="relative rounded border border-gray-300 bg-white px-5 py-4 shadow-sm hover:shadow flex items-center space-x-3 hover:border-gray-400 astro-SLXJX4NQ">
<div class="flex-shrink-0 astro-SLXJX4NQ">
<img class="h-12 w-12 rounded-full astro-SLXJX4NQ" th:src="#{data.logo}" th:alt="#{data.name}" src="https://johnniang.me/avatar" alt="JohnNiang's Blog">
</div>
<div class="flex-1 min-w-0 astro-SLXJX4NQ">
<div class="astro-SLXJX4NQ">
<p class="text-sm font-medium text-gray-900 truncate astro-SLXJX4NQ" th:text="#{data.name}">JohnNiang's Blog</p>
<p class="text-sm text-gray-500 truncate astro-SLXJX4NQ" th:text="#{data.description}">Core contributor of Halo organization.</p>
</div>
</div>
</div>
</a><a href="https://simplestark.com" th:href="#{data.url}" target="_blank" class="astro-SLXJX4NQ">
<div class="relative rounded border border-gray-300 bg-white px-5 py-4 shadow-sm hover:shadow flex items-center space-x-3 hover:border-gray-400 astro-SLXJX4NQ">
<div class="flex-shrink-0 astro-SLXJX4NQ">
<img class="h-12 w-12 rounded-full astro-SLXJX4NQ" th:src="#{data.logo}" th:alt="#{data.name}" src="https://simplestark.com/avatar" alt="烟霞志">
</div>
<div class="flex-1 min-w-0 astro-SLXJX4NQ">
<div class="astro-SLXJX4NQ">
<p class="text-sm font-medium text-gray-900 truncate astro-SLXJX4NQ" th:text="#{data.name}">烟霞志</p>
<p class="text-sm text-gray-500 truncate astro-SLXJX4NQ" th:text="#{data.description}">空有烟霞之志,叹无水云之身。</p>
</div>
</div>
</div>
</a>
</div>
</main>
</body></html>
File diff suppressed because one or more lines are too long
+13 -24
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+13 -12
View File
@@ -1,17 +1,18 @@
apiVersion: theme.halo.run/v1alpha1
kind: Theme
metadata:
name: astro-starter
spec:
displayName: Astro Starter
author:
name: Halo OSS Organization
website: "https://halo.run"
description: Halo 2.0 Theme base on Astro and Thymeleaf
logo: "https://halo.run/logo"
website: "https://halo.run"
repo: "https://github.com/halo-sigs/theme-astro-starter"
settingName: "theme-astro-starter-setting"
configMapName: "theme-astro-starter-configMap"
version: "1.0"
require: "*"
metadata:
name: theme-astro-starter
name: Your Name
website: https://github.com/YourName
description: A starter theme for Halo.
logo: https://www.halo.run/logo
homepage: https://github.com/halo-sigs/theme-astro-starter
issues: https://github.com/halo-sigs/theme-astro-starter/issues
version: "1.0.0"
requires: ">=2.0.0"
license:
- name: "GPL-3.0"
url: "https://www.gnu.org/licenses/gpl-3.0.en.html"
+5 -16
View File
@@ -1,20 +1,9 @@
{
"extends": "astro/tsconfigs/strict",
"include": [".astro/types.d.ts", "**/*"],
"exclude": ["dist"],
"compilerOptions": {
// Enable top-level await, and other modern ESM features.
"lib": [
"dom"
],
"target": "ESNext",
"module": "ESNext",
// Enable node-style module resolution, for things like npm package imports.
"moduleResolution": "node",
// Enable JSON imports.
"resolveJsonModule": true,
// Enable stricter transpilation for better output.
"isolatedModules": true,
// Add type definitions for our Astro runtime.
"types": [
"astro/client"
]
"strictNullChecks": true,
"jsx": "preserve"
}
}