41 次代码提交

修改 91 个文件,包含 2023 行新增681 行删除
+50
查看文件
@@ -0,0 +1,50 @@
name: Theme CD
on:
release:
types:
- published
jobs:
package:
name: Package Theme
runs-on: ubuntu-latest
if: github.event_name == 'release'
steps:
- uses: actions/checkout@v6
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 22
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Build
run: pnpm build
- name: Upload Artifacts
uses: christopherhx/gitea-upload-artifact@v4
with:
name: theme-package
path: dist
release:
name: Release
runs-on: ubuntu-latest
needs: package
if: github.event_name == 'release'
steps:
- uses: actions/checkout@v6
- name: Download Artifacts
uses: christopherhx/gitea-download-artifact@v4
with:
name: theme-package
path: dist
- name: Upload Release Assets
uses: akkuman/gitea-release-action@v1
with:
files: |-
dist/**
+2 -1
查看文件
@@ -10,6 +10,7 @@ lerna-debug.log*
node_modules node_modules
dist dist
dist-ssr dist-ssr
release
*.local *.local
# Editor directories and files # Editor directories and files
@@ -26,4 +27,4 @@ dist-ssr
.gradle .gradle
build build
dev/data docker/data
+3
查看文件
@@ -0,0 +1,3 @@
# Halo Dev Terminal
一款 Dev Terminal 风格的 Halo 主题。
@@ -1,9 +1,9 @@
services: services:
halo: halo:
image: registry.fit2cloud.com/halo/halo:2.18 image: registry.fit2cloud.com/halo/halo:2.22.12
volumes: volumes:
- ./data:/root/.halo2 - ./data:/root/.halo2
- ../theme-terminal:/root/.halo2/themes/theme-terminal - ../:/root/.halo2/themes/theme-terminal
ports: ports:
- "8090:8090" - "8090:8090"
environment: environment:
+2
查看文件
@@ -1,10 +1,12 @@
/// <reference types="vite/client" /> /// <reference types="vite/client" />
import type { Alpine } from "alpinejs"; import type { Alpine } from "alpinejs";
import type { TocManager } from "./src/alpine/toc";
export {}; export {};
declare global { declare global {
interface Window { interface Window {
Alpine: Alpine; Alpine: Alpine;
tocManager?: TocManager;
} }
} }
+8
查看文件
@@ -0,0 +1,8 @@
page.archives.title=Archives
page.links.title=Links
page.moments.title=Moments
page.tags.title=Tags
page.tag.title=Tag: {0}
page.categories.title=Categories
page.category.title=Category: {0}
page.about.title=About
+8
查看文件
@@ -0,0 +1,8 @@
page.archives.title=归档
page.links.title=链接
page.moments.title=瞬间
page.tags.title=标签
page.tag.title=标签:{0}
page.categories.title=分类
page.category.title=分类:{0}
page.about.title=关于
+54
查看文件
@@ -0,0 +1,54 @@
{
"name": "theme-terminal",
"private": true,
"version": "1.2.7",
"description": "A terminal like theme for Halo.",
"scripts": {
"dev": "vite build --watch",
"build": "tsc && vite build && theme-package",
"build-only": "tsc && vite build",
"lint": "eslint ./src --ext .js,.cjs,.mjs,.ts,.cts,.mts --ignore-path .gitignore",
"prettier": "prettier --write './src/**/*.{js,ts,css,json,ml,yaml,html}' './templates/**/*.html'"
},
"keywords": [
"halo",
"halo-theme"
],
"homepage": "https://dev/cm",
"author": {
"name": "devcm",
"url": "https://dev/cm"
},
"license": "MIT",
"repository": {
"url": "https://git.dev.cm/dev.cm/blog/src/branch/main/theme-terminal",
"type": "git"
},
"devDependencies": {
"@halo-dev/theme-package-cli": "^1.0.0",
"@iconify/json": "^2.1.132",
"@tailwindcss/aspect-ratio": "^0.4.2",
"@tailwindcss/typography": "^0.5.7",
"@types/alpinejs": "^3.7.1",
"@types/node": "^16.18.3",
"@typescript-eslint/eslint-plugin": "^5.42.0",
"@typescript-eslint/parser": "^5.42.0",
"autoprefixer": "^10.4.13",
"eslint": "^8.26.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.2.1",
"postcss": "^8.4.31",
"prettier": "^2.7.1",
"prettier-plugin-tailwindcss": "^0.1.13",
"sass": "^1.56.1",
"tailwindcss": "^3.2.1",
"tailwindcss-plugin-icons": "^2.1.1",
"typescript": "^4.8.4",
"vite": "^3.2.2",
"vite-plugin-purge-icons": "^0.9.1"
},
"dependencies": {
"@iconify/iconify": "^3.0.0",
"alpinejs": "^3.10.5"
}
}
+312
查看文件
@@ -15,6 +15,9 @@ importers:
specifier: ^3.10.5 specifier: ^3.10.5
version: 3.14.1 version: 3.14.1
devDependencies: devDependencies:
'@halo-dev/theme-package-cli':
specifier: ^1.0.0
version: 1.0.0
'@iconify/json': '@iconify/json':
specifier: ^2.1.132 specifier: ^2.1.132
version: 2.2.239 version: 2.2.239
@@ -112,6 +115,10 @@ packages:
resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
'@halo-dev/theme-package-cli@1.0.0':
resolution: {integrity: sha512-rblq0oE3r+ir9MmAomQN+GP/OW4jF2alFQrUoOGv/wibfCrhzhSPUkdeFcjpoRmIiwfl33DnSQGG8ruhrIWL2g==}
hasBin: true
'@humanwhocodes/config-array@0.11.14': '@humanwhocodes/config-array@0.11.14':
resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==}
engines: {node: '>=10.10.0'} engines: {node: '>=10.10.0'}
@@ -272,6 +279,10 @@ packages:
'@vue/shared@3.1.5': '@vue/shared@3.1.5':
resolution: {integrity: sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==} resolution: {integrity: sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==}
abort-controller@3.0.0:
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
engines: {node: '>=6.5'}
acorn-jsx@5.3.2: acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies: peerDependencies:
@@ -311,6 +322,14 @@ packages:
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
archiver-utils@5.0.2:
resolution: {integrity: sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==}
engines: {node: '>= 14'}
archiver@7.0.1:
resolution: {integrity: sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==}
engines: {node: '>= 14'}
arg@5.0.2: arg@5.0.2:
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
@@ -321,6 +340,9 @@ packages:
resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
engines: {node: '>=8'} engines: {node: '>=8'}
async@3.2.6:
resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==}
autoprefixer@10.4.20: autoprefixer@10.4.20:
resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==}
engines: {node: ^10 || ^12 || >=14} engines: {node: ^10 || ^12 || >=14}
@@ -331,9 +353,18 @@ packages:
axios@0.26.1: axios@0.26.1:
resolution: {integrity: sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==} resolution: {integrity: sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==}
b4a@1.6.7:
resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==}
balanced-match@1.0.2: balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
bare-events@2.5.4:
resolution: {integrity: sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==}
base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
binary-extensions@2.3.0: binary-extensions@2.3.0:
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -353,6 +384,13 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true hasBin: true
buffer-crc32@1.0.0:
resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==}
engines: {node: '>=8.0.0'}
buffer@6.0.3:
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
callsites@3.1.0: callsites@3.1.0:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'} engines: {node: '>=6'}
@@ -379,13 +417,33 @@ packages:
color-name@1.1.4: color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
commander@13.1.0:
resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==}
engines: {node: '>=18'}
commander@4.1.1: commander@4.1.1:
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
compress-commons@6.0.2:
resolution: {integrity: sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==}
engines: {node: '>= 14'}
concat-map@0.0.1: concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
core-util-is@1.0.3:
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
crc-32@1.2.2:
resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==}
engines: {node: '>=0.8'}
hasBin: true
crc32-stream@6.0.0:
resolution: {integrity: sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==}
engines: {node: '>= 14'}
cross-fetch@3.1.8: cross-fetch@3.1.8:
resolution: {integrity: sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==} resolution: {integrity: sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==}
@@ -627,12 +685,23 @@ packages:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
event-target-shim@5.0.1:
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
engines: {node: '>=6'}
events@3.3.0:
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
engines: {node: '>=0.8.x'}
fast-deep-equal@3.1.3: fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
fast-diff@1.3.0: fast-diff@1.3.0:
resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==}
fast-fifo@1.3.2:
resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==}
fast-glob@3.3.2: fast-glob@3.3.2:
resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
engines: {node: '>=8.6.0'} engines: {node: '>=8.6.0'}
@@ -685,6 +754,10 @@ packages:
resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
fs-extra@11.3.0:
resolution: {integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==}
engines: {node: '>=14.14'}
fs.realpath@1.0.0: fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
@@ -734,6 +807,9 @@ packages:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
ieee754@1.2.1:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
ignore@5.3.2: ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'} engines: {node: '>= 4'}
@@ -753,6 +829,9 @@ packages:
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
inherits@2.0.3:
resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==}
inherits@2.0.4: inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
@@ -784,6 +863,13 @@ packages:
resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
engines: {node: '>=8'} engines: {node: '>=8'}
is-stream@2.0.1:
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
engines: {node: '>=8'}
isarray@1.0.0:
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
isexe@2.0.0: isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
@@ -813,6 +899,10 @@ packages:
keyv@4.5.4: keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
lazystream@1.0.1:
resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==}
engines: {node: '>= 0.6.3'}
levn@0.4.1: levn@0.4.1:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
@@ -841,6 +931,9 @@ packages:
lodash.merge@4.6.2: lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
lru-cache@10.4.3: lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
@@ -855,6 +948,10 @@ packages:
minimatch@3.1.2: minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
minimatch@5.1.6:
resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
engines: {node: '>=10'}
minimatch@9.0.5: minimatch@9.0.5:
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
engines: {node: '>=16 || 14 >=14.17'} engines: {node: '>=16 || 14 >=14.17'}
@@ -953,6 +1050,9 @@ packages:
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
engines: {node: '>=8'} engines: {node: '>=8'}
path@0.12.7:
resolution: {integrity: sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==}
pathe@1.1.2: pathe@1.1.2:
resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==}
@@ -1035,6 +1135,13 @@ packages:
engines: {node: '>=10.13.0'} engines: {node: '>=10.13.0'}
hasBin: true hasBin: true
process-nextick-args@2.0.1:
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
process@0.11.10:
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
engines: {node: '>= 0.6.0'}
punycode@2.3.1: punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'} engines: {node: '>=6'}
@@ -1045,6 +1152,16 @@ packages:
read-cache@1.0.0: read-cache@1.0.0:
resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
readable-stream@2.3.8:
resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
readable-stream@4.7.0:
resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
readdir-glob@1.1.3:
resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==}
readdirp@3.6.0: readdirp@3.6.0:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'} engines: {node: '>=8.10.0'}
@@ -1078,6 +1195,12 @@ packages:
run-parallel@1.2.0: run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
safe-buffer@5.1.2:
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
sass@1.77.8: sass@1.77.8:
resolution: {integrity: sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==} resolution: {integrity: sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==}
engines: {node: '>=14.0.0'} engines: {node: '>=14.0.0'}
@@ -1108,6 +1231,9 @@ packages:
resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
streamx@2.22.1:
resolution: {integrity: sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==}
string-width@4.2.3: string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -1116,6 +1242,12 @@ packages:
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
engines: {node: '>=12'} engines: {node: '>=12'}
string_decoder@1.1.1:
resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
string_decoder@1.3.0:
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
strip-ansi@6.0.1: strip-ansi@6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -1151,6 +1283,12 @@ packages:
engines: {node: '>=14.0.0'} engines: {node: '>=14.0.0'}
hasBin: true hasBin: true
tar-stream@3.1.7:
resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==}
text-decoder@1.2.3:
resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==}
text-table@0.2.0: text-table@0.2.0:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
@@ -1209,6 +1347,9 @@ packages:
util-deprecate@1.0.2: util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
util@0.10.4:
resolution: {integrity: sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==}
vite-plugin-purge-icons@0.9.2: vite-plugin-purge-icons@0.9.2:
resolution: {integrity: sha512-vxJEMyNyckqLr/4HPsW9P6BMLUvOVMvjjFz3jLl4Wke1KWE8ITJUxIUwodxaOmEp9L2lxVk5an3TYeycZCfqFw==} resolution: {integrity: sha512-vxJEMyNyckqLr/4HPsW9P6BMLUvOVMvjjFz3jLl4Wke1KWE8ITJUxIUwodxaOmEp9L2lxVk5an3TYeycZCfqFw==}
engines: {node: '>= 12'} engines: {node: '>= 12'}
@@ -1275,6 +1416,10 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'} engines: {node: '>=10'}
zip-stream@6.0.1:
resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==}
engines: {node: '>= 14'}
snapshots: snapshots:
'@alloc/quick-lru@5.2.0': {} '@alloc/quick-lru@5.2.0': {}
@@ -1308,6 +1453,14 @@ snapshots:
'@eslint/js@8.57.0': {} '@eslint/js@8.57.0': {}
'@halo-dev/theme-package-cli@1.0.0':
dependencies:
archiver: 7.0.1
commander: 13.1.0
fs-extra: 11.3.0
js-yaml: 4.1.0
path: 0.12.7
'@humanwhocodes/config-array@0.11.14': '@humanwhocodes/config-array@0.11.14':
dependencies: dependencies:
'@humanwhocodes/object-schema': 2.0.3 '@humanwhocodes/object-schema': 2.0.3
@@ -1505,6 +1658,10 @@ snapshots:
'@vue/shared@3.1.5': {} '@vue/shared@3.1.5': {}
abort-controller@3.0.0:
dependencies:
event-target-shim: 5.0.1
acorn-jsx@5.3.2(acorn@8.12.1): acorn-jsx@5.3.2(acorn@8.12.1):
dependencies: dependencies:
acorn: 8.12.1 acorn: 8.12.1
@@ -1539,12 +1696,34 @@ snapshots:
normalize-path: 3.0.0 normalize-path: 3.0.0
picomatch: 2.3.1 picomatch: 2.3.1
archiver-utils@5.0.2:
dependencies:
glob: 10.4.5
graceful-fs: 4.2.11
is-stream: 2.0.1
lazystream: 1.0.1
lodash: 4.17.21
normalize-path: 3.0.0
readable-stream: 4.7.0
archiver@7.0.1:
dependencies:
archiver-utils: 5.0.2
async: 3.2.6
buffer-crc32: 1.0.0
readable-stream: 4.7.0
readdir-glob: 1.1.3
tar-stream: 3.1.7
zip-stream: 6.0.1
arg@5.0.2: {} arg@5.0.2: {}
argparse@2.0.1: {} argparse@2.0.1: {}
array-union@2.1.0: {} array-union@2.1.0: {}
async@3.2.6: {}
autoprefixer@10.4.20(postcss@8.4.41): autoprefixer@10.4.20(postcss@8.4.41):
dependencies: dependencies:
browserslist: 4.23.3 browserslist: 4.23.3
@@ -1561,8 +1740,15 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- debug - debug
b4a@1.6.7: {}
balanced-match@1.0.2: {} balanced-match@1.0.2: {}
bare-events@2.5.4:
optional: true
base64-js@1.5.1: {}
binary-extensions@2.3.0: {} binary-extensions@2.3.0: {}
brace-expansion@1.1.11: brace-expansion@1.1.11:
@@ -1585,6 +1771,13 @@ snapshots:
node-releases: 2.0.18 node-releases: 2.0.18
update-browserslist-db: 1.1.0(browserslist@4.23.3) update-browserslist-db: 1.1.0(browserslist@4.23.3)
buffer-crc32@1.0.0: {}
buffer@6.0.3:
dependencies:
base64-js: 1.5.1
ieee754: 1.2.1
callsites@3.1.0: {} callsites@3.1.0: {}
camelcase-css@2.0.1: {} camelcase-css@2.0.1: {}
@@ -1614,10 +1807,29 @@ snapshots:
color-name@1.1.4: {} color-name@1.1.4: {}
commander@13.1.0: {}
commander@4.1.1: {} commander@4.1.1: {}
compress-commons@6.0.2:
dependencies:
crc-32: 1.2.2
crc32-stream: 6.0.0
is-stream: 2.0.1
normalize-path: 3.0.0
readable-stream: 4.7.0
concat-map@0.0.1: {} concat-map@0.0.1: {}
core-util-is@1.0.3: {}
crc-32@1.2.2: {}
crc32-stream@6.0.0:
dependencies:
crc-32: 1.2.2
readable-stream: 4.7.0
cross-fetch@3.1.8: cross-fetch@3.1.8:
dependencies: dependencies:
node-fetch: 2.7.0 node-fetch: 2.7.0
@@ -1834,10 +2046,16 @@ snapshots:
esutils@2.0.3: {} esutils@2.0.3: {}
event-target-shim@5.0.1: {}
events@3.3.0: {}
fast-deep-equal@3.1.3: {} fast-deep-equal@3.1.3: {}
fast-diff@1.3.0: {} fast-diff@1.3.0: {}
fast-fifo@1.3.2: {}
fast-glob@3.3.2: fast-glob@3.3.2:
dependencies: dependencies:
'@nodelib/fs.stat': 2.0.5 '@nodelib/fs.stat': 2.0.5
@@ -1892,6 +2110,12 @@ snapshots:
jsonfile: 6.1.0 jsonfile: 6.1.0
universalify: 2.0.1 universalify: 2.0.1
fs-extra@11.3.0:
dependencies:
graceful-fs: 4.2.11
jsonfile: 6.1.0
universalify: 2.0.1
fs.realpath@1.0.0: {} fs.realpath@1.0.0: {}
fsevents@2.3.3: fsevents@2.3.3:
@@ -1948,6 +2172,8 @@ snapshots:
dependencies: dependencies:
function-bind: 1.1.2 function-bind: 1.1.2
ieee754@1.2.1: {}
ignore@5.3.2: {} ignore@5.3.2: {}
immutable@4.3.7: {} immutable@4.3.7: {}
@@ -1964,6 +2190,8 @@ snapshots:
once: 1.4.0 once: 1.4.0
wrappy: 1.0.2 wrappy: 1.0.2
inherits@2.0.3: {}
inherits@2.0.4: {} inherits@2.0.4: {}
is-binary-path@2.1.0: is-binary-path@2.1.0:
@@ -1986,6 +2214,10 @@ snapshots:
is-path-inside@3.0.3: {} is-path-inside@3.0.3: {}
is-stream@2.0.1: {}
isarray@1.0.0: {}
isexe@2.0.0: {} isexe@2.0.0: {}
jackspeak@3.4.3: jackspeak@3.4.3:
@@ -2016,6 +2248,10 @@ snapshots:
dependencies: dependencies:
json-buffer: 3.0.1 json-buffer: 3.0.1
lazystream@1.0.1:
dependencies:
readable-stream: 2.3.8
levn@0.4.1: levn@0.4.1:
dependencies: dependencies:
prelude-ls: 1.2.1 prelude-ls: 1.2.1
@@ -2037,6 +2273,8 @@ snapshots:
lodash.merge@4.6.2: {} lodash.merge@4.6.2: {}
lodash@4.17.21: {}
lru-cache@10.4.3: {} lru-cache@10.4.3: {}
merge2@1.4.1: {} merge2@1.4.1: {}
@@ -2050,6 +2288,10 @@ snapshots:
dependencies: dependencies:
brace-expansion: 1.1.11 brace-expansion: 1.1.11
minimatch@5.1.6:
dependencies:
brace-expansion: 2.0.1
minimatch@9.0.5: minimatch@9.0.5:
dependencies: dependencies:
brace-expansion: 2.0.1 brace-expansion: 2.0.1
@@ -2126,6 +2368,11 @@ snapshots:
path-type@4.0.0: {} path-type@4.0.0: {}
path@0.12.7:
dependencies:
process: 0.11.10
util: 0.10.4
pathe@1.1.2: {} pathe@1.1.2: {}
picocolors@1.0.1: {} picocolors@1.0.1: {}
@@ -2190,6 +2437,10 @@ snapshots:
prettier@2.8.8: {} prettier@2.8.8: {}
process-nextick-args@2.0.1: {}
process@0.11.10: {}
punycode@2.3.1: {} punycode@2.3.1: {}
queue-microtask@1.2.3: {} queue-microtask@1.2.3: {}
@@ -2198,6 +2449,28 @@ snapshots:
dependencies: dependencies:
pify: 2.3.0 pify: 2.3.0
readable-stream@2.3.8:
dependencies:
core-util-is: 1.0.3
inherits: 2.0.4
isarray: 1.0.0
process-nextick-args: 2.0.1
safe-buffer: 5.1.2
string_decoder: 1.1.1
util-deprecate: 1.0.2
readable-stream@4.7.0:
dependencies:
abort-controller: 3.0.0
buffer: 6.0.3
events: 3.3.0
process: 0.11.10
string_decoder: 1.3.0
readdir-glob@1.1.3:
dependencies:
minimatch: 5.1.6
readdirp@3.6.0: readdirp@3.6.0:
dependencies: dependencies:
picomatch: 2.3.1 picomatch: 2.3.1
@@ -2232,6 +2505,10 @@ snapshots:
dependencies: dependencies:
queue-microtask: 1.2.3 queue-microtask: 1.2.3
safe-buffer@5.1.2: {}
safe-buffer@5.2.1: {}
sass@1.77.8: sass@1.77.8:
dependencies: dependencies:
chokidar: 3.6.0 chokidar: 3.6.0
@@ -2252,6 +2529,13 @@ snapshots:
source-map-js@1.2.0: {} source-map-js@1.2.0: {}
streamx@2.22.1:
dependencies:
fast-fifo: 1.3.2
text-decoder: 1.2.3
optionalDependencies:
bare-events: 2.5.4
string-width@4.2.3: string-width@4.2.3:
dependencies: dependencies:
emoji-regex: 8.0.0 emoji-regex: 8.0.0
@@ -2264,6 +2548,14 @@ snapshots:
emoji-regex: 9.2.2 emoji-regex: 9.2.2
strip-ansi: 7.1.0 strip-ansi: 7.1.0
string_decoder@1.1.1:
dependencies:
safe-buffer: 5.1.2
string_decoder@1.3.0:
dependencies:
safe-buffer: 5.2.1
strip-ansi@6.0.1: strip-ansi@6.0.1:
dependencies: dependencies:
ansi-regex: 5.0.1 ansi-regex: 5.0.1
@@ -2321,6 +2613,16 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- ts-node - ts-node
tar-stream@3.1.7:
dependencies:
b4a: 1.6.7
fast-fifo: 1.3.2
streamx: 2.22.1
text-decoder@1.2.3:
dependencies:
b4a: 1.6.7
text-table@0.2.0: {} text-table@0.2.0: {}
thenify-all@1.6.0: thenify-all@1.6.0:
@@ -2368,6 +2670,10 @@ snapshots:
util-deprecate@1.0.2: {} util-deprecate@1.0.2: {}
util@0.10.4:
dependencies:
inherits: 2.0.3
vite-plugin-purge-icons@0.9.2(vite@3.2.10(@types/node@16.18.105)(sass@1.77.8)): vite-plugin-purge-icons@0.9.2(vite@3.2.10(@types/node@16.18.105)(sass@1.77.8)):
dependencies: dependencies:
'@purge-icons/core': 0.9.1 '@purge-icons/core': 0.9.1
@@ -2419,3 +2725,9 @@ snapshots:
yaml@2.5.0: {} yaml@2.5.0: {}
yocto-queue@0.1.0: {} yocto-queue@0.1.0: {}
zip-stream@6.0.1:
dependencies:
archiver-utils: 5.0.2
compress-commons: 6.0.2
readable-stream: 4.7.0
@@ -27,6 +27,7 @@ spec:
name: title name: title
label: 站点标题 label: 站点标题
help: 配置后,将展示站点标题。 help: 配置后,将展示站点标题。
value: 'Terminal'
- group: index - group: index
label: 首页设置 label: 首页设置
formSchema: formSchema:
+35
查看文件
@@ -0,0 +1,35 @@
export const headerDividing = () => ({
isSticky: false,
$el: null as HTMLElement | null,
_observer: null as IntersectionObserver | null,
init() {
const centerEl = this.$el as HTMLElement;
if (!centerEl) return;
const header = centerEl.closest('.header') as HTMLElement;
if (!header) return;
// 直接观察 header 元素本身
this._observer = new IntersectionObserver(
(entries) => {
const entry = entries[0];
this.isSticky = entry.boundingClientRect.top < 0 && !entry.isIntersecting;
},
{
threshold: 0, // 只要有任何部分离开就触发
rootMargin: '0px' // 无偏移
}
);
this._observer.observe(header);
},
destroy() {
if (this._observer) {
this._observer.disconnect();
this._observer = null;
}
}
});
+12
查看文件
@@ -0,0 +1,12 @@
interface MenuState {
isOpen: boolean;
handleToggleMenu(): void;
}
export const headerMenu = (): MenuState => ({
isOpen: false,
handleToggleMenu() {
this.isOpen = !this.isOpen;
}
})
+258
查看文件
@@ -0,0 +1,258 @@
// ============ 常量配置 ============
const TEXT_ELEMENTS = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'li'];
const HEADING_ELEMENTS = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
const NON_TEXT_ELEMENTS = ['img', 'table', 'video', 'audio', 'canvas', 'svg', 'iframe', 'pre'];
const ALL_ELEMENTS = [...TEXT_ELEMENTS, ...NON_TEXT_ELEMENTS];
const DIRECT_CHILD_SELECTOR = ALL_ELEMENTS.map(tag => `div > ${tag}`).join(', ');
const DEFAULT_LINE_HEIGHT_RATIO = 1.54;
// ============ 类型定义 ============
interface LineSpanOptions {
lineNum: number;
top: number;
height: number;
isGap?: boolean;
isBreakpoint?: boolean;
}
interface ElementMetrics {
top: number;
height: number;
marginTop: number;
marginBottom: number;
paddingTop: number;
lineHeight: number;
lines: number;
isHeading: boolean;
}
interface GapLinesOptions {
fragment: DocumentFragment;
gapStart: number;
gapEnd: number;
lineHeight: number;
startLine: number;
}
interface LineNumbersState {
$el?: HTMLElement;
totalLines: number;
resizeObserver: ResizeObserver | null;
init(): void;
calculateLineNumbers(): void;
destroy(): void;
}
// ============ 工具函数 ============
const isNonTextElement = (tagName: string): boolean =>
NON_TEXT_ELEMENTS.includes(tagName.toLowerCase());
const isHeadingElement = (tagName: string): boolean =>
HEADING_ELEMENTS.includes(tagName.toLowerCase());
const isEmptyElement = (element: HTMLElement): boolean => {
if (isNonTextElement(element.tagName.toLowerCase())) return false;
return !element.textContent?.trim();
};
const getStyleValue = (style: CSSStyleDeclaration, prop: 'paddingTop' | 'paddingBottom' | 'lineHeight' | 'fontSize' | 'marginTop' | 'marginBottom'): number =>
parseFloat(style[prop]) || 0;
const getDefaultLineHeight = (element: HTMLElement): number => {
const style = getComputedStyle(element);
return getStyleValue(style, 'lineHeight') || getStyleValue(style, 'fontSize') * DEFAULT_LINE_HEIGHT_RATIO;
};
// 获取元素度量信息(合并多次 getComputedStyle 调用)
const getElementMetrics = (element: HTMLElement, containerRect: DOMRect, defaultLineHeight: number): ElementMetrics => {
const tagName = element.tagName.toLowerCase();
const style = getComputedStyle(element);
const paddingTop = getStyleValue(style, 'paddingTop');
const paddingBottom = getStyleValue(style, 'paddingBottom');
const marginTop = getStyleValue(style, 'marginTop');
const marginBottom = getStyleValue(style, 'marginBottom');
const height = element.offsetHeight;
const contentHeight = height - paddingTop - paddingBottom;
const isNonText = isNonTextElement(tagName);
const lineHeight = isNonText ? contentHeight : (getStyleValue(style, 'lineHeight') || defaultLineHeight);
const lines = isNonText ? 1 : Math.max(1, Math.round(contentHeight / lineHeight));
return {
top: element.getBoundingClientRect().top - containerRect.top,
height,
marginTop,
marginBottom,
paddingTop,
lineHeight: contentHeight / lines,
lines,
isHeading: isHeadingElement(tagName)
};
};
// ============ DOM 创建 ============
const createLineSpan = (options: LineSpanOptions): HTMLSpanElement => {
const { lineNum, top, height, isGap = false, isBreakpoint = false } = options;
const span = document.createElement('span');
span.className = `line-number${isGap ? ' line-number--gap' : ''}${isBreakpoint ? ' line-number--breakpoint' : ''}`;
span.style.cssText = `top:${top}px;height:${height}px;line-height:${height}px`;
span.textContent = String(lineNum);
if (isBreakpoint) {
const breakpoint = document.createElement('span');
breakpoint.className = 'line-breakpoint';
breakpoint.textContent = '●';
span.appendChild(breakpoint);
}
return span;
};
// ============ 行号生成 ============
const appendGapLines = (options: GapLinesOptions): number => {
const { fragment, gapStart, gapEnd, lineHeight, startLine } = options;
const gapHeight = gapEnd - gapStart;
if (gapHeight < lineHeight) return startLine;
const gapLines = Math.round(gapHeight / lineHeight);
for (let i = 0; i < gapLines; i++) {
fragment.appendChild(createLineSpan({
lineNum: startLine + i,
top: gapStart + i * lineHeight,
height: lineHeight,
isGap: true
}));
}
return startLine + gapLines;
};
const appendElementLines = (fragment: DocumentFragment, metrics: ElementMetrics, startLine: number): number => {
const { top, paddingTop, lineHeight, lines, isHeading } = metrics;
const startTop = top + paddingTop;
for (let i = 0; i < lines; i++) {
fragment.appendChild(createLineSpan({
lineNum: startLine + i,
top: startTop + i * lineHeight,
height: lineHeight,
isBreakpoint: i === 0 && isHeading
}));
}
return startLine + lines;
};
// ============ 主组件 ============
export const postLineNum = (): LineNumbersState => ({
totalLines: 0,
resizeObserver: null,
init() {
const content = this.$el?.querySelector('.post-content') as HTMLElement;
if (!content) return;
const recalculate = () => this.calculateLineNumbers();
// 延迟计算确保 DOM 渲染完成
requestAnimationFrame(() => setTimeout(recalculate, 50));
// 监听内容区域大小变化(带防抖)
if (typeof ResizeObserver !== 'undefined') {
let resizeTimer: number;
this.resizeObserver = new ResizeObserver(() => {
clearTimeout(resizeTimer);
resizeTimer = window.setTimeout(recalculate, 100);
});
this.resizeObserver.observe(content);
}
// 窗口大小变化时重新计算
let windowResizeTimer: number;
window.addEventListener('resize', () => {
clearTimeout(windowResizeTimer);
windowResizeTimer = window.setTimeout(recalculate, 200);
});
// 监听图片加载(使用 once 避免重复触发)
content.querySelectorAll('img').forEach((img: HTMLImageElement) => {
if (!img.complete) {
img.addEventListener('load', recalculate, { once: true });
img.addEventListener('error', recalculate, { once: true });
}
});
},
destroy() {
this.resizeObserver?.disconnect();
},
calculateLineNumbers() {
const content = this.$el?.querySelector('.post-content') as HTMLElement;
const gutter = this.$el?.querySelector('.post-line-gutter') as HTMLElement;
if (!content || !gutter) return;
const container = (content.querySelector(':scope > div') || content) as HTMLElement;
const containerRect = container.getBoundingClientRect();
const defaultLineHeight = getDefaultLineHeight(content);
const elements = Array.from(container.querySelectorAll(DIRECT_CHILD_SELECTOR))
.filter(el => !isEmptyElement(el as HTMLElement)) as HTMLElement[];
const fragment = document.createDocumentFragment();
let currentLine = 1;
let lastBottom = 0;
// 派发清空目录事件
document.dispatchEvent(new CustomEvent('post-toc:clear'));
for (const element of elements) {
const metrics = getElementMetrics(element, containerRect, defaultLineHeight);
const tagName = element.tagName.toLowerCase();
// 如果是标题元素,派发添加目录事件
if (metrics.isHeading) {
const level = parseInt(tagName.charAt(1), 10);
const title = element.textContent?.trim() || '';
document.dispatchEvent(new CustomEvent('post-toc:add', {
detail: { level, title, element, tagName }
}));
}
// 计算实际的内容起始位置(考虑 margin-top
const contentStart = metrics.top - metrics.marginTop;
// 只有当间隙足够大时才填充行号
if (lastBottom > 0 && contentStart > lastBottom) {
currentLine = appendGapLines({
fragment,
gapStart: lastBottom,
gapEnd: contentStart,
lineHeight: defaultLineHeight,
startLine: currentLine
});
}
currentLine = appendElementLines(fragment, metrics, currentLine);
// 更新 lastBottom,包含 margin-bottom
lastBottom = metrics.top + metrics.height + metrics.marginBottom;
}
// 派发初始化滚动高亮事件
document.dispatchEvent(new CustomEvent('post-toc:init-highlight'));
gutter.innerHTML = '';
gutter.appendChild(fragment);
gutter.style.height = `${container.scrollHeight}px`;
this.totalLines = currentLine - 1;
}
});
+110
查看文件
@@ -0,0 +1,110 @@
// ============ 类型定义 ============
export interface PostTocItem {
id: string;
level: number;
title: string;
element: HTMLElement;
tagName: string;
}
// ============ 工具函数 ============
const generateId = (counter: number): string =>
`post-toc-${counter}-${Date.now()}`;
const ensureElementId = (element: HTMLElement, fallbackId: string): string => {
if (!element.id) element.id = fallbackId;
return element.id;
};
const findTopVisibleEntry = (entries: IntersectionObserverEntry[]): IntersectionObserverEntry | null => {
const visible = entries.filter(e => e.isIntersecting);
if (visible.length === 0) return null;
return visible.reduce((prev, curr) =>
curr.boundingClientRect.top < prev.boundingClientRect.top ? curr : prev
);
};
const createScrollObserver = (onActiveChange: (id: string) => void): IntersectionObserver =>
new IntersectionObserver(
(entries) => {
const topEntry = findTopVisibleEntry(entries);
if (topEntry) onActiveChange(topEntry.target.id);
},
{ rootMargin: '-80px 0px -70% 0px', threshold: [0, 0.5, 1] }
);
// ============ 事件常量 ============
export const TOC_EVENTS = {
CLEAR: 'post-toc:clear',
ADD: 'post-toc:add',
HIGHLIGHT: 'post-toc:init-highlight'
} as const;
// ============ Alpine 组件 ============
export const postToc = () => ({
items: [] as PostTocItem[],
activeId: '',
observer: null as IntersectionObserver | null,
// 私有属性
_isInitialized: false,
_counter: 0,
_minLevel: 6,
init() {
if (this._isInitialized) return;
document.addEventListener(TOC_EVENTS.CLEAR, () => this.clear());
document.addEventListener(TOC_EVENTS.ADD, (e) => {
const { level, title, element, tagName } = (e as CustomEvent).detail;
this.addItem(level, title, element, tagName);
});
document.addEventListener(TOC_EVENTS.HIGHLIGHT, () => this.initScrollHighlight());
this._isInitialized = true;
},
addItem(level: number, title: string, element: HTMLElement, tagName: string) {
this._counter++;
const id = ensureElementId(element, generateId(this._counter));
if (level < this._minLevel) this._minLevel = level;
this.items = [...this.items, { id, level, title, element, tagName }];
},
clear() {
this.items = [];
this.activeId = '';
this._counter = 0;
this._minLevel = 6;
this.observer?.disconnect();
},
initScrollHighlight() {
this.observer?.disconnect();
this.observer = createScrollObserver((id) => { this.activeId = id; });
this.items.forEach(item => this.observer?.observe(item.element));
},
scrollTo(id: string) {
const element = document.getElementById(id);
if (!element) return;
element.scrollIntoView({ behavior: 'smooth', block: 'start' });
this.activeId = id;
},
getIndent(level: number): string {
return `${(level - this._minLevel) * 16}px`;
}
});
+29
查看文件
@@ -0,0 +1,29 @@
interface ThemeModeState {
init(): void;
storedTheme: string;
handleToggleThemeMode(): void;
}
export const themeMode = ():ThemeModeState => ({
storedTheme: '',
init() {
const storedTheme = localStorage.getItem("theme-mode") || (window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");
if (!storedTheme) return;
document.documentElement.setAttribute("data-color-scheme", storedTheme);
this.storedTheme = storedTheme;
},
handleToggleThemeMode() {
const targetTheme = this.storedTheme === "dark" ? "light" : "dark";
document.documentElement.setAttribute("data-color-scheme", targetTheme);
this.storedTheme = targetTheme;
localStorage.setItem("theme-mode", targetTheme);
},
});
+51
查看文件
@@ -0,0 +1,51 @@
interface upvoteState {
init(): void;
upvotedNames: string[];
getIsUpvoted(id: string): boolean;
handleUpvote(name: string): void;
}
export const upvote = (key: string, group: string, plural: string): upvoteState => ({
upvotedNames: [],
init() {
this.upvotedNames = JSON.parse(localStorage.getItem(`walker.upvoted.${key}.names`) || "[]");
},
getIsUpvoted(id: string) {
return this.upvotedNames.includes(id);
},
async handleUpvote(name) {
if (this.getIsUpvoted(name)) return
const xhr = new XMLHttpRequest();
xhr.open("POST", "/apis/api.halo.run/v1alpha1/trackers/upvote");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onload = () => {
this.upvotedNames = [...this.upvotedNames, name];
localStorage.setItem(`walker.upvoted.${key}.names`, JSON.stringify(this.upvotedNames));
const upvoteNode = document.querySelector("[data-upvote-" + key + '-name="' + name + '"]');
if (!upvoteNode) {
return;
}
const upvoteCount = parseInt(upvoteNode.textContent || "0");
upvoteNode.textContent = upvoteCount + 1 + "";
};
xhr.onerror = function () {
alert("网络请求失败,请稍后再试");
};
xhr.send(
JSON.stringify({
group: group,
plural: plural,
name: name,
})
);
},
});
+25
查看文件
@@ -0,0 +1,25 @@
import "./styles/style.scss";
import "./styles/font-hack.scss";
import "./styles/font-pixel.scss";
import Alpine from 'alpinejs'
import {upvote} from './alpine/upvote'
import {themeMode} from './alpine/theme-mode'
import {postLineNum} from './alpine/post-line-num'
import {postToc} from './alpine/post-toc'
import {headerDividing} from './alpine/header-dividing'
import {headerMenu} from './alpine/header-menu'
import {typewriterEffect} from './utils'
window.Alpine = Alpine
Alpine.data('upvote', upvote)
Alpine.data('themeMode', themeMode)
Alpine.data('postLineNum', postLineNum)
Alpine.data('postToc', postToc)
Alpine.data('headerMenu', headerMenu)
Alpine.data('headerDividing', headerDividing)
Alpine.start()
document.addEventListener('DOMContentLoaded', () => typewriterEffect('.typed-text'))
+4
查看文件
@@ -0,0 +1,4 @@
@font-face {
font-family: Pixel;
src: url("../fonts/fusion-pixel-12px-proportional-zh_hans.woff2");
}
@@ -62,6 +62,7 @@
font-size: 0.75rem; font-size: 0.75rem;
} }
} }
.beian { .beian {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@@ -78,6 +79,5 @@
gap: 4px; gap: 4px;
padding: 0 8px; padding: 0 8px;
} }
} }
} }
+143
查看文件
@@ -0,0 +1,143 @@
@import './variables.scss';
.dividing {
flex: 1;
display: block;
width: 100%;
height: 25px;
position: relative;
transition: opacity 0.3s ease-in-out;
--dash-spacing: 10px;
--dash-width: 2px;
// 进入动画初始状态(仅 sticky 激活时播放)
opacity: 1;
transition: opacity 0.3s ease-in-out;
// 虚线背景层 - 带遮罩
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: repeating-linear-gradient(
90deg,
var(--foreground),
var(--foreground) var(--dash-width),
transparent 0,
transparent var(--dash-spacing)
);
background-size: var(--dash-spacing) 100%;
animation: line-flow 3s linear infinite;
// 遮罩层实现虚线淡入淡出效果
mask-image: linear-gradient(
90deg,
transparent 0%,
black 3%,
black 97%,
transparent 100%
);
-webkit-mask-image: linear-gradient(
90deg,
transparent 0%,
black 3%,
black 97%,
transparent 100%
);
}
// 扫描光效 - 模拟终端扫描线
&::after {
content: '';
position: absolute;
left: -5%;
top: 50%;
transform: translateY(-50%);
width: 30%;
height: 120%;
filter: blur(4px);
background: linear-gradient(
90deg,
transparent 0%,
var(--green) 80%,
transparent 100%
);
opacity: 0.3;
animation: scan-line 5s ease-in-out infinite;
pointer-events: none;
z-index: 10;
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
&.sticky {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 5px;
z-index: 100;
--dash-spacing: 6px;
--dash-width: 2px;
background-color: color-mix(in srgb, var(--background) 90%, transparent);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
// 进入动画:透明度由0到1
animation: sticky-in 0.5s ease-in-out forwards;
opacity: 1;
&::before {
top: calc(50% - 1px);
height: 2px;
}
&::after {
opacity: 0.6;
filter: blur(3px);
// 调整扫描线高度以适应 5px 容器
height: 200%;
width: 25%;
}
}
}
// 虚线流动动画 - 模拟加载进度
@keyframes line-flow {
0% {
background-position: 0 0;
}
100% {
background-position: var(--dash-spacing) 0;
}
}
// 扫描线动画 - 模拟终端扫描效果
@keyframes scan-line {
0% {
left: -5%;
width: 5%;
opacity: 0;
}
30% {
width: 30%;
opacity: .3;
}
70% {
opacity: .3;
}
100% {
left: 80%;
opacity: 0;
}
}
@keyframes sticky-in {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
+125
查看文件
@@ -0,0 +1,125 @@
@import "variables";
.header {
display: flex;
flex-direction: column;
position: relative;
&__inner {
display: flex;
align-items: center;
justify-content: space-between;
}
&__center {
display: flex;
align-items: center;
justify-content: center;
flex: 1;
}
.menu {
margin: 20px 0;
&__inner {
display: flex;
flex-wrap: wrap;
list-style: none;
margin: 0;
padding: 0;
li {
position: relative;
flex: 0 0 auto;
padding-bottom: 10px;
&.active {
color: var(--cyan);
}
&:not(:last-of-type) {
margin-right: 20px;
}
}
}
&__sub-inner {
position: absolute;
z-index: 99;
top: 35px;
left: 0;
display: none;
margin: 0;
padding: 10px;
list-style: none;
border: 2px solid var(--red);
background: var(--background);
box-shadow: 0 10px var(--background), -10px 10px var(--background), 10px 10px var(--background);
overflow: hidden;
li {
padding: 5px;
white-space: nowrap;
text-decoration: underline;
color: var(--green);
opacity: 0;
transform: translateX(-10px);
&:not(:last-of-type) {
margin-right: 0;
}
}
&.open {
display: block;
animation: terminal-frame-reveal 0.3s ease-out forwards;
// 逐行渲染效果 - 为每个子项添加延迟动画
li {
animation: terminal-line 0.2s ease-out forwards;
// 为前20个菜单项添加递增延迟
@for $i from 1 through 20 {
&:nth-child(#{$i}) {
animation-delay: #{($i * 0.05) + 0.1}s;
}
}
}
}
}
}
.button {
background-color: transparent;
color: var(--foreground);
margin: 0 0 0 .5rem;
padding: 0;
}
// 菜单框架渲染动画 - 从上到下逐层渲染边框和内容
@keyframes terminal-frame-reveal {
0% {
clip-path: inset(-10px -10px calc(100% + 10px) -10px);
opacity: 1;
}
100% {
clip-path: inset(-10px -10px -10px -10px);
opacity: 1;
}
}
// 菜单项逐行渲染动画 - 模拟终端文本逐行输出
@keyframes terminal-line {
0% {
opacity: 0;
transform: translateX(-2px);
}
50% {
opacity: 0.5;
}
100% {
opacity: 1;
transform: translateX(0);
}
}
}
@@ -10,11 +10,6 @@ html {
box-sizing: inherit; box-sizing: inherit;
} }
@font-face {
font-family: Ark-Pixel-12-proportional-zh_cn;
src: url("../fonts/fusion-pixel-12px-proportional-zh_hans.woff2");
}
body { body {
margin: 0; margin: 0;
padding: 0; padding: 0;
@@ -33,7 +28,7 @@ body {
} }
&.pixel_style { &.pixel_style {
font-family: Ark-Pixel-12-proportional-zh_cn, serif; font-family: Pixel, Monaco, Consolas, 'Ubuntu Mono', PingHei, 'PingFang SC', 'Microsoft YaHei', monospace;
} }
} }
@@ -178,15 +173,6 @@ blockquote {
padding-right: 0; padding-right: 0;
} }
&:before {
content: '';
font-family: Georgia, serif;
font-size: 3.875rem;
position: absolute;
left: -40px;
top: -20px;
}
p:first-of-type { p:first-of-type {
margin-top: 0; margin-top: 0;
} }
@@ -264,20 +250,3 @@ hr {
background: var(--foreground); background: var(--foreground);
height: 1px; height: 1px;
} }
.hidden {
display: none;
}
#theme-toggle {
background-color: transparent;
color: var(--foreground);
margin: 0 0 0 .5rem;
padding: 0;
}
#search-button {
background-color: transparent;
color: var(--foreground);
margin: 0 0 0 .5rem;
padding: 0;
}
+54
查看文件
@@ -0,0 +1,54 @@
@import "variables";
.post-line-gutter {
position: absolute;
left: -80px;
top: 0;
width: 60px;
user-select: none;
opacity: 0.5;
border-right: 1px solid color-mix(in srgb, var(--foreground) 15%, transparent);
@media (max-width: $tablet-max-width) {
display: none;
}
.line-number {
position: absolute;
right: 8px;
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 10px;
font-size: 0.85rem;
color: var(--foreground);
transition: opacity 0.15s ease;
opacity: 0.8;
&:hover {
opacity: 1;
}
}
.line-number--gap {
opacity: 0.5;
}
.line-number--breakpoint {
cursor: pointer;
.line-breakpoint {
position: absolute;
right: -3px;
color: var(--brightRed);
font-size: 0.875rem;
font-family: Hack, Monaco, Consolas, monospace;
transition: transform 0.15s ease, filter 0.15s ease;
}
&:hover .line-breakpoint {
transform: scale(1.2);
filter: brightness(1.2);
}
}
}
+199
查看文件
@@ -0,0 +1,199 @@
@import "variables";
.post-toc-sticky {
position: sticky;
top: 0;
z-index: 100;
width: 0;
height: 0;
overflow: visible;
transform: translate(-260px, 100px);
}
.post-toc {
position: relative;
width: 200px;
max-height: calc(100vh - 200px);
overflow-y: auto;
overflow-x: hidden;
font-size: 0.8rem;
line-height: 1.6;
color: var(--foreground);
opacity: 0.7;
transition: opacity 0.2s ease;
// 隐藏滚动条但保持可滚动
scrollbar-width: thin;
scrollbar-color: var(--foreground) transparent;
&:hover {
opacity: 1;
}
&::-webkit-scrollbar {
width: 4px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background-color: color-mix(in srgb, var(--foreground) 30%, transparent);
border-radius: 2px;
}
// 移动端和平板隐藏
@media (max-width: 1200px) {
display: none;
}
}
.toc-tree {
list-style: none;
margin: 0;
padding: 0;
padding-left: 10px;
position: relative;
// 左侧边框线(不连接顶部)
&::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 1px;
background-color: color-mix(in srgb, var(--foreground) 20%, transparent);
}
}
.toc-item {
position: relative;
display: flex;
align-items: flex-start;
padding: 2px 0;
cursor: pointer;
transition: background-color 0.15s ease;
border-radius: 3px;
&:hover {
background-color: color-mix(in srgb, var(--foreground) 8%, transparent);
}
// 折叠箭头
&__toggle {
flex-shrink: 0;
width: 16px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
color: var(--foreground);
opacity: 0.5;
font-size: 0.6rem;
transition: transform 0.2s ease;
user-select: none;
&--expanded {
transform: rotate(0deg);
}
&--collapsed {
transform: rotate(-90deg);
}
&--empty {
visibility: hidden;
}
}
// 标签图标 <>
&__icon {
flex-shrink: 0;
color: var(--red);
margin-right: 4px;
opacity: 0.7;
}
// 标签名 (h1, h2, div, etc.)
&__tag {
flex-shrink: 0;
color: var(--blue);
margin-right: 6px;
}
// 标题文字
&__title {
color: var(--foreground);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
opacity: 0.8;
transition: color 0.15s ease, opacity 0.15s ease;
}
// 左侧激活指示线
&::before {
content: '' !important;
position: absolute;
left: -10px !important;
top: 4px;
bottom: 4px;
width: 2px;
background-color: transparent;
border-radius: 1px;
}
// 活跃状态
&--active {
background-color: color-mix(in srgb, var(--brightBlue) 15%, transparent);
.toc-item__title {
color: var(--brightBlue);
opacity: 1;
}
.toc-item__tag {
color: var(--brightBlue);
}
&::before {
background-color: var(--brightBlue);
}
}
}
// 子级列表
.toc-children {
list-style: none;
margin: 0;
padding: 0;
padding-left: 16px;
&--collapsed {
display: none;
}
}
// 目录头部
.toc-header {
display: flex;
align-items: center;
padding: 4px 0 8px 0;
margin-bottom: 4px;
border-bottom: 1px solid color-mix(in srgb, var(--foreground) 15%, transparent);
color: var(--foreground);
opacity: 0.6;
font-size: 0.75rem;
&__icon {
margin-right: 6px;
color: var(--red);
}
&__title {
text-transform: uppercase;
letter-spacing: 0.5px;
}
}
+243
查看文件
@@ -0,0 +1,243 @@
@import "variables";
.index-content {
margin-top: 10px;
}
.framed {
border: 1px solid var(--red);
color: var(--red);
padding: 20px;
*:first-child {
margin-top: 0;
}
*:last-child {
margin-bottom: 0;
}
}
.posts {
width: 100%;
margin: 0 auto;
}
.post {
position: relative;
width: 100%;
text-align: left;
margin: 20px auto;
padding: 20px 0;
@media (max-width: $tablet-max-width) {
max-width: 660px;
}
&:not(:last-of-type) {
border-bottom: 1px solid var(--foreground);
}
%meta {
display: flex;
align-items: center;
font-size: 1rem;
margin-bottom: 10px;
color: var(--brightBlue);
}
&-meta {
@extend %meta;
}
&-meta-inline {
@extend %meta;
display: inline;
}
&-separator {
position: relative;
top: 1px;
display: inline-grid;
grid-template-columns: repeat(2, 3px);
grid-template-rows: repeat(2, 3px);
gap: 2px;
margin: 0 6px;
vertical-align: middle;
span {
width: 2px;
height: 2px;
background-color: var(--brightBlue);
border-radius: 50%;
opacity: 0.2;
}
// 每个点使用不同动画和时长,形成随机效果
span:nth-child(1) { animation: dot-blink-1 2.8s ease-in-out infinite; }
span:nth-child(2) { animation: dot-blink-2 2.2s ease-in-out infinite 0.4s; }
span:nth-child(3) { animation: dot-blink-3 3.4s ease-in-out infinite 1s; }
span:nth-child(4) { animation: dot-blink-4 2.6s ease-in-out infinite 0.2s; }
span:nth-child(5) { animation: dot-blink-2 3s ease-in-out infinite 0.6s; }
span:nth-child(6) { animation: dot-blink-1 2.4 ease-in-out infinite 0.8s; }
span:nth-child(7) { animation: dot-blink-4 3.2s ease-in-out infinite 0.3s; }
span:nth-child(8) { animation: dot-blink-3 2.7s ease-in-out infinite 0.5s; }
span:nth-child(9) { animation: dot-blink-1 2.9s ease-in-out infinite 0.1s; }
}
&-title {
--border: 3px dotted var(--blue);
position: relative;
color: var(--blue);
margin: 0 0 15px;
padding-bottom: 15px;
font-weight: normal;
border-bottom: var(--border);
&::after {
content: "";
position: absolute;
bottom: 2px;
display: block;
width: 100%;
border-bottom: var(--border);
}
a {
text-decoration: none;
}
}
%tags {
margin-left: 10px;
font-size: 1rem;
}
&-tags {
@extend %tags;
display: block;
}
&-tag {
color: var(--green);
}
&-tags-inline {
@extend %tags;
display: inline;
@media (max-width: $phone-max-width) {
display: block;
}
}
&-body {
position: relative;
margin-top: 30px;
}
&-content {
font-family: Hack, Monaco, Consolas, 'Ubuntu Mono', PingHei, 'PingFang SC', 'Microsoft YaHei', monospace;
line-height: 1.54;
}
&-cover {
border: 20px solid var(--foreground);
background: transparent;
margin: 40px 0;
padding: 20px;
@media (max-width: $phone-max-width) {
padding: 10px;
border-width: 10px;
}
}
ul {
list-style: none;
li:before {
content: '';
position: absolute;
left: -20px;
color: var(--foreground);
}
}
}
.post--regulation {
h1 {
justify-content: center;
}
h2 {
justify-content: center;
margin-bottom: 10px;
& + h2 {
margin-top: -10px;
margin-bottom: 20px;
}
}
}
.post-list {
padding: .25rem 0;
.post-date {
color: var(--foreground);
text-decoration: none;
}
a {
text-decoration: none;
}
.post-list-title {
text-decoration: underline;
}
.post-tag {
text-decoration: underline;
}
}
.comment-wrap {
position: relative;
z-index: 2;
}
// 4个不同的随机动画模式
@keyframes dot-blink-1 {
0%, 100% { opacity: 0.2; }
25% { opacity: 1; }
50% { opacity: 0.3; }
75% { opacity: 0.8; }
}
@keyframes dot-blink-2 {
0%, 100% { opacity: 0.3; }
30% { opacity: 0.9; }
60% { opacity: 0.2; }
85% { opacity: 0.7; }
}
@keyframes dot-blink-3 {
0%, 100% { opacity: 0.2; }
20% { opacity: 0.6; }
45% { opacity: 1; }
70% { opacity: 0.4; }
}
@keyframes dot-blink-4 {
0%, 100% { opacity: 0.4; }
15% { opacity: 0.2; }
40% { opacity: 0.8; }
65% { opacity: 1; }
90% { opacity: 0.3; }
}
+17
查看文件
@@ -0,0 +1,17 @@
@tailwind base;
@tailwind utilities;
@tailwind components;
@import 'main';
@import 'logo';
@import 'buttons';
@import 'header';
@import 'header-dividing';
@import 'post';
@import 'post-line-num';
@import 'post-toc';
@import 'pagination';
@import 'footer';
@import 'typed-text';
@import 'theme';
+15
查看文件
@@ -0,0 +1,15 @@
/* 光标样式 */
.typed-text::after {
content: '|';
display: inline-block;
opacity: 0;
animation: blink 0.7s infinite alternate; /* 闪烁动画 */
vertical-align: text-bottom;
}
/* 闪烁动画 */
@keyframes blink {
to {
opacity: 1;
}
}
+24
查看文件
@@ -0,0 +1,24 @@
// 文字打字机效果
export const typewriterEffect = (selectors: string) => {
const typedTextContainer = document.querySelector<HTMLDivElement>(selectors)
if (!typedTextContainer) return
const text = typedTextContainer.innerText
// 清除原有的文本
typedTextContainer.innerText = ''
// 实现打字机效果
let i = 0
const typewriter = () => {
if (i >= text.length) return
typedTextContainer.innerText += text.charAt(i++)
setTimeout(typewriter, Math.random() * 200 + 50)
}
typewriter()
}
@@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html <html
xmlns:th="https://www.thymeleaf.org" xmlns:th="https://www.thymeleaf.org"
th:replace="~{modules/layout :: html(header = null, content = ~{::content}, footer = null)}" th:replace="~{modules/layout :: html(title = |#{page.archives.title} - ${site.title}|, header = null, content = ~{::content}, footer = null)}"
> >
<th:block th:fragment="content"> <th:block th:fragment="content">
<div class="content"> <div class="content">
@@ -15,9 +15,20 @@
<span class="post-date" th:text="${#dates.format(post.spec.publishTime,'yyyy-MM-dd')}" <span class="post-date" th:text="${#dates.format(post.spec.publishTime,'yyyy-MM-dd')}"
>发布时间</span >发布时间</span
> >
:: <span class="post-list-title" th:text="${post.spec.title}">文章标题</span></a> <span class="post-separator">
<span></span>
<span></span>
<span></span>
<span></span>
</span>
<span class="post-list-title" th:text="${post.spec.title}">文章标题</span></a>
<span class="post-tags-inline"> <span class="post-tags-inline">
:: <span class="post-separator">
<span></span>
<span></span>
<span></span>
<span></span>
</span>
<span th:each="tag : ${post.tags}"> <span th:each="tag : ${post.tags}">
<a <a
th:href="${tag.status.permalink}" th:href="${tag.status.permalink}"

之前

宽度:  |  高度:  |  大小: 11 KiB

之后

宽度:  |  高度:  |  大小: 11 KiB

@@ -1,14 +1,14 @@
<!DOCTYPE html> <!DOCTYPE html>
<html <html
xmlns:th="https://www.thymeleaf.org" xmlns:th="https://www.thymeleaf.org"
th:replace="~{modules/layout :: html(header = null, content = ~{::content}, footer = null)}" th:replace="~{modules/layout :: html(title = |#{page.categories.title} - ${site.title}|, header = null, content = ~{::content}, footer = null)}"
> >
<th:block th:fragment="content"> <th:block th:fragment="content">
<div class="content"> <div class="content">
<div class="post"> <div class="post">
<h1 class="post-title">所有分类</h1> <h1 class="post-title">所有分类</h1>
<ul> <ul>
<li th:replace="~{modules/category-tree :: single(categories=${categories})}" /> <li th:replace="~{modules/category-tree :: single(categories=${categories})}"></li>
</ul> </ul>
</div> </div>
</div> </div>
@@ -1,14 +1,14 @@
<!DOCTYPE html> <!DOCTYPE html>
<html <html
xmlns:th="https://www.thymeleaf.org" xmlns:th="https://www.thymeleaf.org"
th:replace="~{modules/layout :: html(header = null, content = ~{::content}, footer = null)}" th:replace="~{modules/layout :: html(title = |#{page.category.title(${category.spec.displayName})} - ${site.title}|, header = null, content = ~{::content}, footer = null)}"
> >
<th:block th:fragment="content"> <th:block th:fragment="content">
<div class="content"> <div class="content">
<div class="post"> <div class="post">
<h1 <h1
class="post-title" class="post-title"
th:text="'分类:'+${category.spec.displayName}+' ('+${category.status.visiblePostCount}+' 篇文章)'" th:text="'分类:'+${category.spec.displayName}+' ('+${category.status.visiblePostCount?:0}+' 篇文章)'"
> >
分类:分类名 (n 篇文章) 分类:分类名 (n 篇文章)
</h1> </h1>
@@ -19,9 +19,20 @@
<span class="post-date" th:text="${#dates.format(post.spec.publishTime,'yyyy-MM-dd')}" <span class="post-date" th:text="${#dates.format(post.spec.publishTime,'yyyy-MM-dd')}"
>发布时间</span >发布时间</span
> >
:: <span class="post-list-title" th:text="${post.spec.title}">文章标题</span></a> <span class="post-separator">
<span></span>
<span></span>
<span></span>
<span></span>
</span>
<span class="post-list-title" th:text="${post.spec.title}">文章标题</span></a>
<span class="post-tags-inline"> <span class="post-tags-inline">
:: <span class="post-separator">
<span></span>
<span></span>
<span></span>
<span></span>
</span>
<span th:each="tag : ${post.tags}"> <span th:each="tag : ${post.tags}">
<a <a
th:href="${tag.status.permalink}" th:href="${tag.status.permalink}"
@@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html <html
xmlns:th="https://www.thymeleaf.org" xmlns:th="https://www.thymeleaf.org"
th:replace="~{modules/layout :: html(header = null, content = ~{::content}, footer = null)}" th:replace="~{modules/layout :: html(title = ${site.title}, header = null, content = ~{::content}, footer = null)}"
> >
<th:block th:fragment="content"> <th:block th:fragment="content">
<div class="content"> <div class="content">
@@ -12,28 +12,30 @@
<div class="posts"> <div class="posts">
<div class="post on-list" th:each="post : ${posts.items}"> <div class="post on-list" th:each="post : ${posts.items}">
<h1 class="post-title"> <h1 class="post-title">
<a th:text="${post.spec.title}" th:href="${post.status.permalink}">Post Title</a> <a th:text="'< ' + ${post.spec.title} + ' >'" th:href="${post.status.permalink}">Post Title</a>
</h1> </h1>
<div class="post-meta"> <div class="post-meta">
<span class="post-date" th:text="${#dates.format(post.spec.publishTime,'yyyy-MM-dd')}"> <span class="post-date" th:text="${#dates.format(post.spec.publishTime,'yyyy-MM-dd')}">
Post CreateTime Post CreateTime
</span> </span>
<span <span class="post-separator">
class="post-author" <span></span>
th:with="contributor = ${post.contributors[0]}" <span></span>
th:text="${':: '+contributor.displayName}" <span></span>
>:: Author</span <span></span>
> </span>
</div> <span class="post-author" th:with="contributor = ${post.contributors[0]}" th:text="${contributor.displayName}">
Author
</span>
<span class="post-tags-inline" th:each="tag : ${post.tags}"> <span class="post-tags-inline" th:each="tag : ${post.tags}">
<a <a th:href="${tag.status.permalink}"
th:href="${tag.status.permalink}"
th:title="${tag.spec.displayName}" th:title="${tag.spec.displayName}"
th:text="'#'+${tag.spec.displayName}" th:text="'#'+${tag.spec.displayName}"
class="post-tag" class="post-tag"
>#Tag >#Tag
</a> </a>
</span> </span>
</div>
<div class="post-content" th:text="${post.status.excerpt}">Post Excerpt...</div> <div class="post-content" th:text="${post.status.excerpt}">Post Excerpt...</div>
<div> <div>
@@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html <html
xmlns:th="https://www.thymeleaf.org" xmlns:th="https://www.thymeleaf.org"
th:replace="~{modules/layout :: html(header = null, content = ~{::content}, footer = null)}" th:replace="~{modules/layout :: html(title = |#{page.links.title} - ${site.title}|, header = null, content = ~{::content}, footer = null)}"
> >
<th:block th:fragment="content"> <th:block th:fragment="content">
<script th:src="@{/assets/libs/pixelit.js}"></script> <script th:src="@{/assets/libs/pixelit.js}"></script>
@@ -1,7 +1,7 @@
<ul th:fragment="next (categories)"> <ul th:fragment="next (categories)">
<li th:fragment="single (categories)" th:each="category : ${categories}"> <li th:fragment="single (categories)" th:each="category : ${categories}">
<a th:href="@{${category.status.permalink}}"> <a th:href="@{${category.status.permalink}}">
<span th:text="${category.spec.displayName}+' ('+${category.status.visiblePostCount}+' 篇文章)'"> </span> <span th:text="${category.spec.displayName}+' ('+${category.status.visiblePostCount?:0}+' 篇文章)'"> </span>
</a> </a>
<th:block th:if="${not #lists.isEmpty(category.children)}"> <th:block th:if="${not #lists.isEmpty(category.children)}">
<th:block th:replace="~{modules/category-tree :: next (categories=${category.children})}"></th:block> <th:block th:replace="~{modules/category-tree :: next (categories=${category.children})}"></th:block>
@@ -25,5 +25,6 @@
></a ></a
></span> ></span>
</div> </div>
<halo:footer />
</div> </div>
</footer> </footer>
@@ -5,10 +5,12 @@
<img class="icon" th:if="${not #strings.isEmpty(theme.config.basic.logo)}" th:src="${theme.config.basic.logo}" alt="Logo" /> <img class="icon" th:if="${not #strings.isEmpty(theme.config.basic.logo)}" th:src="${theme.config.basic.logo}" alt="Logo" />
<div class="text" th:if="${not #strings.isEmpty(theme.config.basic.title)}" th:text="${theme.config.basic.title}"></div> <div class="text" th:if="${not #strings.isEmpty(theme.config.basic.title)}" th:text="${theme.config.basic.title}"></div>
</a> </a>
<div class="dividing"></div> <div class="header__center" x-data="headerDividing()">
<div class="dividing" :class="{ 'sticky': isSticky }"></div>
</div>
<button <button
id="search-button"
type="button" type="button"
class="button"
th:if="${pluginFinder.available('PluginSearchWidget')}" th:if="${pluginFinder.available('PluginSearchWidget')}"
onclick="SearchWidget.open()" onclick="SearchWidget.open()"
> >
@@ -24,7 +26,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1.5rem" height="1.5rem" viewBox="0 0 24 24"><path fill="currentColor" d="M6 2h8v2H6zM4 6V4h2v2zm0 8H2V6h2zm2 2H4v-2h2zm8 0v2H6v-2zm2-2h-2v2h2v2h2v2h2v2h2v-2h-2v-2h-2v-2h-2zm0-8h2v8h-2zm0 0V4h-2v2z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="1.5rem" height="1.5rem" viewBox="0 0 24 24"><path fill="currentColor" d="M6 2h8v2H6zM4 6V4h2v2zm0 8H2V6h2zm2 2H4v-2h2zm8 0v2H6v-2zm2-2h-2v2h2v2h2v2h2v2h2v-2h-2v-2h-2v-2h-2zm0-8h2v8h-2zm0 0V4h-2v2z"/></svg>
</th:block> </th:block>
</button> </button>
<button id="theme-toggle" type="button"> <button type="button" class="button" x-data="themeMode()" @click="handleToggleThemeMode()">
<th:block th:unless="${theme.config.basic.pixel_style}"> <th:block th:unless="${theme.config.basic.pixel_style}">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@@ -46,21 +48,26 @@
</button> </button>
</div> </div>
<nav class="menu"> <nav class="menu">
<ul th:if="${menu != null} and ${not #lists.isEmpty(menu.menuItems)}" class="menu__inner menu__inner--desktop"> <ul th:if="${menu != null} and ${not #lists.isEmpty(menu.menuItems)}" class="menu__inner">
<li th:each="menuItem : ${menu.menuItems}"> <li th:each="menuItem : ${menu.menuItems}" x-data="headerMenu()" @mouseenter="handleToggleMenu()" @mouseleave="handleToggleMenu()">
<a <a
class="text-gray-600 hover:text-blue-600" class="text-gray-600 hover:text-blue-600"
th:href="${menuItem.status.href}" th:href="${menuItem.status.href}"
th:text="${menuItem.status.displayName}" th:target="${menuItem.spec.target?.value}"
th:text="${menuItem.status.displayName + (not #lists.isEmpty(menuItem.children) ? '▾' : '')}"
></a> ></a>
<ul <ul
th:if="${not #lists.isEmpty(menuItem.children)}" th:if="${not #lists.isEmpty(menuItem.children)}"
@mouseenter="open()" class="menu__sub-inner"
@mouseleave="close()" :class="{'open': isOpen}"
class="menu__sub-inner-more hidden"
> >
<li th:each="childMenuItem : ${menuItem.children}"> <li th:each="childMenuItem : ${menuItem.children}">
<a th:href="${childMenuItem.status.href} " th:text="${childMenuItem.status.displayName} "></a> <a
class="text-gray-600 hover:text-blue-600"
th:href="${childMenuItem.status.href}"
th:target="${childMenuItem.spec.target?.value}"
th:text="${childMenuItem.status.displayName}"
></a>
</li> </li>
</ul> </ul>
</li> </li>
@@ -1,12 +1,16 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" th:fragment="html (header, content, footer)"> <html
xmlns:th="https://www.thymeleaf.org"
th:lang="${#locale.toLanguageTag}"
th:fragment="html (header, content, footer)">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/x-icon" th:href="${theme.config.basic.favicon}"> <link rel="icon" type="image/x-icon" th:href="${theme.config.basic.favicon}">
<title th:text="${site.title}"></title> <title th:text="${title}"></title>
<link rel="stylesheet" th:href="@{/assets/dist/style.css?version=v1.0.1}" href="./assets/dist/style.css" /> <link rel="manifest" th:href="@{/assets/dist/manifest.json}" />
<link rel="stylesheet" th:href="@{/assets/dist/main.css?version={version}(version=${theme.spec.version})}" href="./assets/dist/style.css" />
</head> </head>
<!-- 根据情况判断是否添加开启像素化样式 --> <!-- 根据情况判断是否添加开启像素化样式 -->
<body class="main" th:classappend="${theme.config.basic.pixel_style} ? 'pixel_style' : '' "> <body class="main" th:classappend="${theme.config.basic.pixel_style} ? 'pixel_style' : '' ">
@@ -27,6 +31,6 @@
<th:block th:replace="${footer}" /> <th:block th:replace="${footer}" />
</th:block> </th:block>
</div> </div>
<script th:src="@{/assets/dist/main.iife.js?version=v1.0.1}"></script> <script th:src="@{/assets/dist/main.js?version={version}(version=${theme.spec.version})}"></script>
</body> </body>
</html> </html>
@@ -1,11 +1,11 @@
<!DOCTYPE html> <!DOCTYPE html>
<html <html
xmlns:th="https://www.thymeleaf.org" xmlns:th="https://www.thymeleaf.org"
th:replace="~{modules/layout :: html(header = null, content = ~{::content}, footer = null)}" th:replace="~{modules/layout :: html(title = |#{page.moments.title} - ${site.title}|, header = null, content = ~{::content}, footer = null)}"
> >
<th:block th:fragment="content"> <th:block th:fragment="content">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/glightbox/dist/css/glightbox.min.css" /> <link rel="stylesheet" href="https://gcore.jsdelivr.net/npm/glightbox/dist/css/glightbox.min.css" />
<script src="https://cdn.jsdelivr.net/gh/mcstudios/glightbox/dist/js/glightbox.min.js"></script> <script src="https://gcore.jsdelivr.net/gh/mcstudios/glightbox/dist/js/glightbox.min.js"></script>
<div class="content"> <div class="content">
<div class="moments" x-data="upvote('moment','moment.halo.run','moments')"> <div class="moments" x-data="upvote('moment','moment.halo.run','moments')">
<div <div
@@ -18,8 +18,14 @@
<span class="post-date" th:text="${#dates.format(moment.spec.releaseTime,'yyyy-MM-dd')}"> <span class="post-date" th:text="${#dates.format(moment.spec.releaseTime,'yyyy-MM-dd')}">
Moment CreateTime Moment CreateTime
</span> </span>
<span class="post-author" th:with="owner = ${moment.owner}" th:text="${':: '+owner.displayName}"> <span class="post-separator">
:: Author <span></span>
<span></span>
<span></span>
<span></span>
</span>
<span class="post-author" th:with="owner = ${moment.owner}" th:text="${owner.displayName}">
Author
</span> </span>
</div> </div>
<div class="post-content" th:utext="${content.html}">Moment Content...</div> <div class="post-content" th:utext="${content.html}">Moment Content...</div>
@@ -46,7 +52,7 @@
<div class="mt-3 flex items-center gap-4"> <div class="mt-3 flex items-center gap-4">
<div <div
class="journal-likes inline-flex cursor-pointer items-center text-sm text-gray-400 transition-all hover:text-red-700" class="journal-likes inline-flex cursor-pointer items-center text-sm text-gray-400 transition-all hover:text-red-700"
x-bind:class="{'text-red-700': upvoted(name)}" :class="{'text-red-700': getIsUpvoted(name)}"
@click="handleUpvote(name)" @click="handleUpvote(name)"
> >
<i class="!h-4 !w-4 i-pixelarticons-heart"></i> <i class="!h-4 !w-4 i-pixelarticons-heart"></i>
@@ -60,7 +66,7 @@
<div <div
class="inline-flex cursor-pointer items-center text-sm text-gray-400 transition-all hover:text-black dark:hover:text-white" class="inline-flex cursor-pointer items-center text-sm text-gray-400 transition-all hover:text-black dark:hover:text-white"
:class="{'!text-black':showComment && storedTheme == 'light','!text-white':showComment && storedTheme == 'dark' }" :class="{'!text-black':showComment && storedTheme == 'light','!text-white':showComment && storedTheme == 'dark' }"
x-on:click="showComment = !showComment" @click="showComment = !showComment"
> >
<i class="!h-4 !w-4 i-pixelarticons-comment"></i> <i class="!h-4 !w-4 i-pixelarticons-comment"></i>
<span class="ml-1" th:text="${moment.stats.approvedComment}"> </span> <span class="ml-1" th:text="${moment.stats.approvedComment}"> </span>
@@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html <html
xmlns:th="https://www.thymeleaf.org" xmlns:th="https://www.thymeleaf.org"
th:replace="~{modules/layout :: html(header = null, content = ~{::content}, footer = null)}" th:replace="~{modules/layout :: html(title = |${singlePage.spec.title} - ${site.title}|, header = null, content = ~{::content}, footer = null)}"
> >
<th:block th:fragment="content"> <th:block th:fragment="content">
<div class="post"> <div class="post">
@@ -10,7 +10,13 @@
<span class="post-date" th:text="${#dates.format(singlePage.spec.publishTime,'yyyy-MM-dd')}"> <span class="post-date" th:text="${#dates.format(singlePage.spec.publishTime,'yyyy-MM-dd')}">
publishTime publishTime
</span> </span>
<span class="post-author" th:text="${':: '+singlePage.owner.displayName}">:: Author</span> <span class="post-separator">
<span></span>
<span></span>
<span></span>
<span></span>
</span>
<span class="post-author" th:text="${singlePage.owner.displayName}">Author</span>
</div> </div>
<div class="post-content"> <div class="post-content">
<div th:utext="${singlePage.content.content}">Post Content</div> <div th:utext="${singlePage.content.content}">Post Content</div>
+86
查看文件
@@ -0,0 +1,86 @@
<!DOCTYPE html>
<html
xmlns:th="https://www.thymeleaf.org"
th:replace="~{modules/layout :: html(title = |${post.spec.title} - ${site.title}|, header = null, content = ~{::content}, footer = null)}"
>
<th:block th:fragment="content">
<!-- 目录组件 -->
<div class="post-toc-sticky">
<div class="post-toc" x-data="postToc" x-init="init()">
<div class="toc-header">
<span class="toc-header__icon">&lt;&gt;</span>
<span class="toc-header__title">Outline</span>
</div>
<ul class="toc-tree">
<template x-for="(item, index) in items" :key="index">
<li class="toc-item"
:class="{'toc-item--active': activeId === item.id}"
:style="`padding-left: ${getIndent(item.level)}`"
@click="scrollTo(item.id)">
<span class="toc-item__toggle toc-item__toggle--empty"></span>
<span class="toc-item__icon">&lt;&gt;</span>
<span class="toc-item__tag" x-text="item.tagName"></span>
<span class="toc-item__title" x-text="item.title"></span>
</li>
</template>
</ul>
</div>
</div>
<div class="post" x-data="postLineNum" x-init="init()">
<h1 class="post-title" th:text="'< ' + ${post.spec.title} + ' >'">Post Title</h1>
<div class="post-meta">
<span class="post-date" th:text="${#dates.format(post.spec.publishTime,'yyyy-MM-dd')}"> publishTime </span>
<span class="post-separator">
<span></span>
<span></span>
<span></span>
<span></span>
</span>
<span class="post-author" th:text="${post.owner.displayName}">Author</span>
<span class="post-tags-inline" th:each="tag : ${post.tags}">
<a th:href="${tag.status.permalink}"
th:title="${tag.spec.displayName}"
th:text="'#'+${tag.spec.displayName}"
class="post-tag"
>#Tag
</a>
</span>
</div>
<div class="post-body">
<!-- 行数组件 -->
<div class="post-line-gutter"></div>
<div class="post-content">
<div th:utext="${post.content.content}">Post Content</div>
</div>
</div>
</div>
<div class="comment-wrap" th:if="${haloCommentEnabled}">
<h2>评论</h2>
<halo:comment
group="content.halo.run"
kind="Post"
th:attr="name=${post.metadata.name}"
/>
</div>
<div class="pagination">
<div class="pagination__title">
<span class="pagination__title-h">其他文章</span>
<hr />
</div>
<div class="pagination__buttons" th:with="postCursor=${postFinder.cursor(post.metadata?.name)}">
<span th:if="${postCursor.hasPrevious()}" class="button previous">
<a th:href="${postCursor.previous?.status?.permalink}">
<span class="button__icon"><&nbsp;</span>
<span class="button__text" th:text="${postCursor.previous?.spec?.title}">prevPost</span>
</a>
</span>
<span th:if="${postCursor.hasNext()}" class="button next">
<a th:href="${postCursor.next?.status?.permalink}">
<span class="button__text" th:text="${postCursor.next?.spec?.title}">nextPost</span>
<span class="button__icon">&nbsp;></span>
</a>
</span>
</div>
</div>
</th:block>
</html>
@@ -1,14 +1,14 @@
<!DOCTYPE html> <!DOCTYPE html>
<html <html
xmlns:th="https://www.thymeleaf.org" xmlns:th="https://www.thymeleaf.org"
th:replace="~{modules/layout :: html(header = null, content = ~{::content}, footer = null)}" th:replace="~{modules/layout :: html(title = |#{page.tag.title(${tag.spec.displayName})} - ${site.title}|, header = null, content = ~{::content}, footer = null)}"
> >
<th:block th:fragment="content"> <th:block th:fragment="content">
<div class="content"> <div class="content">
<div class="post"> <div class="post">
<h1 <h1
class="post-title" class="post-title"
th:text="'标签:'+${tag.spec.displayName}+' ('+${tag.status.visiblePostCount}+' 篇文章)'" th:text="'标签:'+${tag.spec.displayName}+' ('+${tag.status.visiblePostCount?:0}+' 篇文章)'"
> >
标签:标签名 (n 篇文章) 标签:标签名 (n 篇文章)
</h1> </h1>
@@ -21,9 +21,20 @@
<span class="post-date" th:text="${#dates.format(post.spec.publishTime,'yyyy-MM-dd')}" <span class="post-date" th:text="${#dates.format(post.spec.publishTime,'yyyy-MM-dd')}"
>发布时间</span >发布时间</span
> >
:: <span class="post-list-title" th:text="${post.spec.title}">文章标题</span></a> <span class="post-separator">
<span></span>
<span></span>
<span></span>
<span></span>
</span>
<span class="post-list-title" th:text="${post.spec.title}">文章标题</span></a>
<span class="post-tags-inline"> <span class="post-tags-inline">
:: <span class="post-separator">
<span></span>
<span></span>
<span></span>
<span></span>
</span>
<span th:each="tag : ${post.tags}"> <span th:each="tag : ${post.tags}">
<a <a
th:href="@{${post.status.permalink}}" th:href="@{${post.status.permalink}}"
@@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html <html
xmlns:th="https://www.thymeleaf.org" xmlns:th="https://www.thymeleaf.org"
th:replace="~{modules/layout :: html(header = null, content = ~{::content}, footer = null)}" th:replace="~{modules/layout :: html(title = |#{page.tags.title} - ${site.title}|, header = null, content = ~{::content}, footer = null)}"
> >
<th:block th:fragment="content"> <th:block th:fragment="content">
<div class="content"> <div class="content">
@@ -11,7 +11,7 @@
<li th:each="tag: ${tags}" class="tag-list"> <li th:each="tag: ${tags}" class="tag-list">
<a <a
th:href="${tag.status.permalink}" th:href="${tag.status.permalink}"
th:text="${tag.spec.displayName}+' ('+${tag.status.visiblePostCount}+' 篇文章)'" th:text="${tag.spec.displayName}+' ('+${tag.status.visiblePostCount?:0}+' 篇文章)'"
> >
标签名 (n 篇文章) 标签名 (n 篇文章)
</a> </a>
-12
查看文件
@@ -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 = false
-14
查看文件
@@ -1,14 +0,0 @@
module.exports = {
root: true,
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint", "prettier"],
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"prettier",
],
env: {
node: true,
},
};
-12
查看文件
@@ -1,12 +0,0 @@
# Halo Terminal
一款 Terminal 风格的 Halo 主题。
![screenshot](https://user-images.githubusercontent.com/27671436/203283319-32a7384f-7b46-4c9e-9ec7-4abb796fc7cf.png)
## 特性
- 支持明暗模式切换;
- 首页公告设置;
- 备案信息设置;
- 支持 Halo 内部的大部分页面,包括文章、页面、分类、标签、归档等。
- 支持 Halo 应用市场的部分内容管理插件,包括友情链接、瞬间。
-48
查看文件
@@ -1,48 +0,0 @@
{
"name": "theme-terminal",
"private": true,
"version": "1.0.0",
"description": "A terminal like theme for Halo.",
"scripts": {
"dev": "vite build --watch",
"build": "tsc && vite build",
"lint": "eslint ./src --ext .js,.cjs,.mjs,.ts,.cts,.mts --ignore-path .gitignore",
"prettier": "prettier --write './src/**/*.{js,ts,css,json,ml,yaml,html}' './templates/**/*.html'"
},
"keywords": [
"halo",
"halo-theme"
],
"homepage": "https://dev/cm",
"author": {
"name": "devcm",
"url": "https://dev/cm"
},
"license": "MIT",
"devDependencies": {
"@iconify/json": "^2.1.132",
"@tailwindcss/aspect-ratio": "^0.4.2",
"@tailwindcss/typography": "^0.5.7",
"@types/alpinejs": "^3.7.1",
"@types/node": "^16.18.3",
"@typescript-eslint/eslint-plugin": "^5.42.0",
"@typescript-eslint/parser": "^5.42.0",
"autoprefixer": "^10.4.13",
"eslint": "^8.26.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.2.1",
"postcss": "^8.4.31",
"prettier": "^2.7.1",
"prettier-plugin-tailwindcss": "^0.1.13",
"sass": "^1.56.1",
"tailwindcss": "^3.2.1",
"tailwindcss-plugin-icons": "^2.1.1",
"typescript": "^4.8.4",
"vite": "^3.2.2",
"vite-plugin-purge-icons": "^0.9.1"
},
"dependencies": {
"@iconify/iconify": "^3.0.0",
"alpinejs": "^3.10.5"
}
}
-45
查看文件
@@ -1,45 +0,0 @@
// 文字打字机效果
export const typewriterEffect = (selectors: string) => {
const typedTextContainer = document.querySelector<HTMLDivElement>(selectors)
if (!typedTextContainer) return
const text = typedTextContainer.innerText
// 清楚原有的文本
typedTextContainer.innerText = ''
// 实现打字机效果
let i = 0
const typewriter = () => {
if (i >= text.length) return
typedTextContainer.innerText += text.charAt(i++)
setTimeout(typewriter, Math.random() * 200 + 50)
}
typewriter()
}
// 切换黑白主题
export const handleToggleThemeMode = (selectors: string) => {
const toggleButton = document.querySelector<HTMLDivElement>(selectors);
if(!toggleButton) return
const storedTheme = localStorage.getItem("theme-mode") || (window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");
if (storedTheme) document.documentElement.setAttribute("data-color-scheme", storedTheme);
toggleButton.onclick = function () {
const currentTheme = document.documentElement.getAttribute("data-color-scheme");
const targetTheme = currentTheme === "dark" ? "light" : "dark";
document.documentElement.setAttribute("data-color-scheme", targetTheme);
localStorage.setItem("theme-mode", targetTheme);
};
}
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
二进制文件未显示。
-20
查看文件
@@ -1,20 +0,0 @@
import './styles/tailwind.css'
import './styles/style.scss'
import './styles/theme.scss'
import './styles/font-hack.scss'
import Alpine from 'alpinejs'
import upvote from './upvote'
import {typewriterEffect, handleToggleThemeMode} from './custom'
window.Alpine = Alpine
Alpine.data('upvote', upvote)
Alpine.start()
document.addEventListener('DOMContentLoaded', () => {
typewriterEffect('.typed-text')
handleToggleThemeMode('#theme-toggle')
})
@@ -1,34 +0,0 @@
/*!
* Hack typeface https://github.com/source-foundry/Hack
* License: https://github.com/source-foundry/Hack/blob/master/LICENSE.md
*/
/* FONT PATHS
* -------------------------- */
@font-face {
font-family: 'Hack';
/* Use full version (not a subset) for unicode icon support */
src: url('fonts/hack-regular.woff2?sha=3114f1256') format('woff2'), url('fonts/hack-regular.woff?sha=3114f1256') format('woff');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'Hack';
src: url('fonts/hack-bold-subset.woff2?sha=3114f1256') format('woff2'), url('fonts/hack-bold-subset.woff?sha=3114f1256') format('woff');
font-weight: 700;
font-style: normal;
}
@font-face {
font-family: 'Hack';
src: url('fonts/hack-italic-subset.woff2?sha=3114f1256') format('woff2'), url('fonts/hack-italic-webfont.woff?sha=3114f1256') format('woff');
font-weight: 400;
font-style: italic;
}
@font-face {
font-family: 'Hack';
src: url('fonts/hack-bolditalic-subset.woff2?sha=3114f1256') format('woff2'), url('fonts/hack-bolditalic-subset.woff?sha=3114f1256') format('woff');
font-weight: 700;
font-style: italic;
}
-87
查看文件
@@ -1,87 +0,0 @@
@import "variables";
@mixin menu {
position: absolute;
background: var(--background);
box-shadow: var(--shadow);
color: white;
border: 2px solid;
margin: 0;
padding: 10px;
list-style: none;
z-index: 99;
}
.header {
display: flex;
flex-direction: column;
position: relative;
&__inner {
display: flex;
align-items: center;
justify-content: space-between;
}
.dividing {
flex: 1;
background: repeating-linear-gradient(90deg, var(--foreground), var(--foreground) 2px, transparent 0, transparent 16px);
display: block;
width: 100%;
height: 25px;
}
.menu {
margin: 20px 0;
&__inner {
display: flex;
flex-wrap: wrap;
list-style: none;
margin: 0;
padding: 0;
color: var(--green);
li {
&.active {
color: var(--cyan);
}
&:not(:last-of-type) {
margin-right: 20px;
margin-bottom: 10px;
flex: 0 0 auto;
}
}
}
&__sub-inner {
position: relative;
list-style: none;
padding: 0;
margin: 0;
&:not(:only-child) {
margin-left: 20px;
}
&-more {
@include menu;
top: 35px;
left: 0;
&-trigger {
color: var(--foreground);
user-select: none;
cursor: pointer;
}
li {
margin: 0;
padding: 5px;
white-space: nowrap;
}
}
}
}
}
-158
查看文件
@@ -1,158 +0,0 @@
@import "variables";
.index-content {
margin-top: 10px;
}
.framed {
border: 1px solid var(--red);
color: var(--red);
padding: 20px;
*:first-child {
margin-top: 0;
}
*:last-child {
margin-bottom: 0;
}
}
.posts {
width: 100%;
margin: 0 auto;
}
.post {
width: 100%;
text-align: left;
margin: 20px auto;
padding: 20px 0;
@media (max-width: $tablet-max-width) {
max-width: 660px;
}
&:not(:last-of-type) {
border-bottom: 1px solid var(--foreground);
}
%meta {
font-size: 1rem;
margin-bottom: 10px;
color: var(--brightBlue);
}
&-meta {
@extend %meta;
}
&-meta-inline {
@extend %meta;
display: inline;
}
&-title {
--border: 2px dashed var(--blue);
position: relative;
color: var(--blue);
margin: 0 0 15px;
padding-bottom: 15px;
border-bottom: var(--border);
font-weight: normal;
a {
text-decoration: none;
}
}
%tags {
margin-bottom: 20px;
font-size: 1rem;
}
&-tags {
@extend %tags;
display: block;
}
&-tag {
color: var(--green);
}
&-tags-inline {
@extend %tags;
display: inline;
@media (max-width: $phone-max-width) {
display: block;
}
}
&-content {
margin-top: 30px;
font-family: Hack, Monaco, Consolas, 'Ubuntu Mono', PingHei, 'PingFang SC', 'Microsoft YaHei', monospace;
}
&-cover {
border: 20px solid var(--foreground);
background: transparent;
margin: 40px 0;
padding: 20px;
@media (max-width: $phone-max-width) {
padding: 10px;
border-width: 10px;
}
}
ul {
list-style: none;
li:before {
content: '';
position: absolute;
left: -20px;
color: var(--foreground);
}
}
}
.post--regulation {
h1 {
justify-content: center;
}
h2 {
justify-content: center;
margin-bottom: 10px;
&+ h2 {
margin-top: -10px;
margin-bottom: 20px;
}
}
}
.post-list {
padding: .25rem 0;
.post-date {
color: var(--foreground);
text-decoration: none;
}
a {
text-decoration: none;
}
.post-list-title {
text-decoration: underline;
}
.post-tag {
text-decoration: underline;
}
}
-8
查看文件
@@ -1,8 +0,0 @@
@import 'buttons';
@import 'header';
@import 'logo';
@import 'main';
@import 'post';
@import 'pagination';
@import 'footer';
@import 'typed-text';
-15
查看文件
@@ -1,15 +0,0 @@
/* 光标样式 */
.typed-text::after {
content: '|';
display: inline-block;
opacity: 0;
animation: blink 0.7s infinite alternate; /* 闪烁动画 */
vertical-align: text-bottom;
}
/* 闪烁动画 */
@keyframes blink {
to {
opacity: 1;
}
}
-53
查看文件
@@ -1,53 +0,0 @@
interface upvoteState {
upvotedNames: string[];
init(): void;
upvoted(id: string): boolean;
handleUpvote(name: string): void;
}
export default (key: string, group: string, plural: string): upvoteState => ({
upvotedNames: [],
init() {
this.upvotedNames = JSON.parse(localStorage.getItem(`walker.upvoted.${key}.names`) || '[]')
},
upvoted(id: string) {
return this.upvotedNames.includes(id)
},
async handleUpvote(name) {
if (this.upvoted(name)) {
return
}
const xhr = new XMLHttpRequest()
xhr.open('POST', '/apis/api.halo.run/v1alpha1/trackers/upvote')
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.onload = () => {
this.upvotedNames = [...this.upvotedNames, name]
localStorage.setItem(`walker.upvoted.${key}.names`, JSON.stringify(this.upvotedNames))
const upvoteNode = document.querySelector('[data-upvote-' + key + '-name="' + name + '"]')
if (!upvoteNode) {
return
}
const upvoteCount = parseInt(upvoteNode.textContent || '0')
upvoteNode.textContent = upvoteCount + 1 + ''
}
xhr.onerror = function () {
alert('网络请求失败,请稍后再试')
}
xhr.send(
JSON.stringify({
group: group,
plural: plural,
name: name,
}),
)
},
});
-55
查看文件
@@ -1,55 +0,0 @@
<!DOCTYPE html>
<html
xmlns:th="https://www.thymeleaf.org"
th:replace="~{modules/layout :: html(header = null, content = ~{::content}, footer = null)}"
>
<th:block th:fragment="content">
<div class="post">
<h1 class="post-title" th:text="${post.spec.title}">Post Title</h1>
<div class="post-meta">
<span class="post-date" th:text="${#dates.format(post.spec.publishTime,'yyyy-MM-dd')}"> publishTime </span>
<span class="post-author" th:text="${':: '+post.owner.displayName}">:: Author</span>
</div>
<span class="post-tags-inline" th:each="tag : ${post.tags}">
<a
th:href="${tag.status.permalink}"
th:title="${tag.spec.displayName}"
th:text="'#'+${tag.spec.displayName}"
class="post-tag"
>#Tag</a
>
</span>
<div class="post-content">
<div th:utext="${post.content.content}">Post Content</div>
</div>
</div>
<div th:if="${haloCommentEnabled}">
<h2>评论</h2>
<halo:comment
group="content.halo.run"
kind="Post"
th:attr="name=${post.metadata.name}"
/>
</div>
<div class="pagination">
<div class="pagination__title">
<span class="pagination__title-h">其他文章</span>
<hr />
</div>
<div class="pagination__buttons" th:with="postCursor=${postFinder.cursor(post.metadata?.name)}">
<span th:if="${postCursor.hasPrevious()}" class="button previous">
<a th:href="${postCursor.previous?.status?.permalink}">
<span class="button__icon"><&nbsp;</span>
<span class="button__text" th:text="${postCursor.previous?.spec?.title}">prevPost</span>
</a>
</span>
<span th:if="${postCursor.hasNext()}" class="button next">
<a th:href="${postCursor.next?.status?.permalink}">
<span class="button__text" th:text="${postCursor.next?.spec?.title}">nextPost</span>
<span class="button__icon">&nbsp;></span>
</a>
</span>
</div>
</div>
</th:block>
</html>
-19
查看文件
@@ -1,19 +0,0 @@
import path from "path";
import { fileURLToPath } from "url";
import { defineConfig } from "vite";
import PurgeIcons from "vite-plugin-purge-icons";
export default defineConfig({
plugins: [PurgeIcons()],
build: {
outDir: fileURLToPath(new URL("./templates/assets/dist", import.meta.url)),
emptyOutDir: true,
lib: {
entry: path.resolve(__dirname, "src/main.ts"),
name: "main",
fileName: "main",
formats: ["iife"],
},
},
});
+2 -2
查看文件
@@ -13,5 +13,5 @@ spec:
repo: https://git.dev.cm/theme-terminal repo: https://git.dev.cm/theme-terminal
settingName: "theme-terminal-setting" settingName: "theme-terminal-setting"
configMapName: "theme-terminal-configMap" configMapName: "theme-terminal-configMap"
version: 1.0.1 version: 1.2.7
require: ">=2.8.0" require: ">=2.22.0"
@@ -16,5 +16,5 @@
"noImplicitReturns": true, "noImplicitReturns": true,
"skipLibCheck": true "skipLibCheck": true
}, },
"include": ["src", "./env.d.ts"] "include": ["types", "src", "./env.d.ts"]
} }
+25
查看文件
@@ -0,0 +1,25 @@
import path from "path";
import { defineConfig } from "vite";
import PurgeIcons from "vite-plugin-purge-icons";
export default defineConfig({
root: "./src",
base: "./",
plugins: [PurgeIcons()],
build: {
manifest: true,
rollupOptions: {
input: path.resolve(__dirname, "src/main.ts"),
output: {
entryFileNames: "[name].js",
chunkFileNames: "[name].js",
assetFileNames: "[name][extname]",
},
treeshake: false,
preserveEntrySignatures: "allow-extension",
},
outDir: path.resolve(__dirname, "templates/assets/dist"),
emptyOutDir: true,
},
});