feat(vite): 资源拆分 & 样式优化

这个提交包含在:
2024-11-29 19:21:06 +08:00
未验证
父节点 41ac5b7998
当前提交 f65f9c7fde
修改 23 个文件,包含 263 行新增229 行删除
+1
查看文件
@@ -10,6 +10,7 @@ lerna-debug.log*
node_modules node_modules
dist dist
dist-ssr dist-ssr
publish
*.local *.local
# Editor directories and files # Editor directories and files
+1 -1
查看文件
@@ -1,6 +1,6 @@
services: services:
halo: halo:
image: registry.fit2cloud.com/halo/halo:2.18 image: registry.fit2cloud.com/halo/halo:2.20.10
volumes: volumes:
- ./data:/root/.halo2 - ./data:/root/.halo2
- ../theme-terminal:/root/.halo2/themes/theme-terminal - ../theme-terminal:/root/.halo2/themes/theme-terminal
+7 -2
查看文件
@@ -1,13 +1,14 @@
{ {
"name": "theme-terminal", "name": "theme-terminal",
"private": true, "private": true,
"version": "1.0.0", "version": "1.1.1",
"description": "A terminal like theme for Halo.", "description": "A terminal like theme for Halo.",
"scripts": { "scripts": {
"dev": "vite build --watch", "dev": "vite build --watch",
"build": "tsc && vite build", "build": "tsc && vite build",
"lint": "eslint ./src --ext .js,.cjs,.mjs,.ts,.cts,.mts --ignore-path .gitignore", "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'" "prettier": "prettier --write './src/**/*.{js,ts,css,json,ml,yaml,html}' './templates/**/*.html'",
"publish": "cp -r theme.yaml settings.yaml templates publish/ && zip -r theme-terminal.zip publish"
}, },
"keywords": [ "keywords": [
"halo", "halo",
@@ -19,6 +20,10 @@
"url": "https://dev/cm" "url": "https://dev/cm"
}, },
"license": "MIT", "license": "MIT",
"repository": {
"url": "https://git.dev.cm/dev.cm/blog/src/branch/main/theme-terminal",
"type": "git"
},
"devDependencies": { "devDependencies": {
"@iconify/json": "^2.1.132", "@iconify/json": "^2.1.132",
"@tailwindcss/aspect-ratio": "^0.4.2", "@tailwindcss/aspect-ratio": "^0.4.2",
+1
查看文件
@@ -27,6 +27,7 @@ spec:
name: title name: title
label: 站点标题 label: 站点标题
help: 配置后,将展示站点标题。 help: 配置后,将展示站点标题。
value: 'Terminal'
- group: index - group: index
label: 首页设置 label: 首页设置
formSchema: formSchema:
+12
查看文件
@@ -0,0 +1,12 @@
interface MenuState {
isOpen: boolean;
handleToggleMenu(): void;
}
export const menu = (): MenuState => ({
isOpen: false,
handleToggleMenu() {
this.isOpen = !this.isOpen;
}
})
+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,
})
);
},
});
-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);
};
}
+10 -11
查看文件
@@ -1,20 +1,19 @@
import './styles/tailwind.css' import "./styles/style.scss";
import './styles/style.scss' import "./styles/font-hack.scss";
import './styles/theme.scss' import "./styles/font-pixel.scss";
import './styles/font-hack.scss'
import Alpine from 'alpinejs' import Alpine from 'alpinejs'
import upvote from './upvote' import {upvote} from './alpine/upvote'
import {typewriterEffect, handleToggleThemeMode} from './custom' import {themeMode} from './alpine/themeMode'
import {menu} from './alpine/menu'
import {typewriterEffect} from './utils'
window.Alpine = Alpine window.Alpine = Alpine
Alpine.data('upvote', upvote) Alpine.data('upvote', upvote)
Alpine.data('themeMode', themeMode)
Alpine.data('menu', menu)
Alpine.start() Alpine.start()
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => typewriterEffect('.typed-text'))
typewriterEffect('.typed-text')
handleToggleThemeMode('#theme-toggle')
})
@@ -7,28 +7,28 @@
@font-face { @font-face {
font-family: 'Hack'; font-family: 'Hack';
/* Use full version (not a subset) for unicode icon support */ /* 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'); src: url('../fonts/hack-regular.woff2?sha=3114f1256') format('woff2'), url('../fonts/hack-regular.woff?sha=3114f1256') format('woff');
font-weight: 400; font-weight: 400;
font-style: normal; font-style: normal;
} }
@font-face { @font-face {
font-family: 'Hack'; font-family: 'Hack';
src: url('fonts/hack-bold-subset.woff2?sha=3114f1256') format('woff2'), url('fonts/hack-bold-subset.woff?sha=3114f1256') format('woff'); 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-weight: 700;
font-style: normal; font-style: normal;
} }
@font-face { @font-face {
font-family: 'Hack'; font-family: 'Hack';
src: url('fonts/hack-italic-subset.woff2?sha=3114f1256') format('woff2'), url('fonts/hack-italic-webfont.woff?sha=3114f1256') format('woff'); 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-weight: 400;
font-style: italic; font-style: italic;
} }
@font-face { @font-face {
font-family: 'Hack'; font-family: 'Hack';
src: url('fonts/hack-bolditalic-subset.woff2?sha=3114f1256') format('woff2'), url('fonts/hack-bolditalic-subset.woff?sha=3114f1256') format('woff'); 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-weight: 700;
font-style: italic; font-style: italic;
} }
@@ -0,0 +1,4 @@
@font-face {
font-family: Ark-Pixel-12-proportional-zh_cn;
src: url("../fonts/fusion-pixel-12px-proportional-zh_hans.woff2");
}
+18 -34
查看文件
@@ -1,17 +1,5 @@
@import "variables"; @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 { .header {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -25,7 +13,7 @@
.dividing { .dividing {
flex: 1; flex: 1;
background: repeating-linear-gradient(90deg, var(--foreground), var(--foreground) 2px, transparent 0, transparent 16px); background: repeating-linear-gradient(90deg, var(--foreground), var(--foreground) 2px, transparent 0, transparent 10px);
display: block; display: block;
width: 100%; width: 100%;
height: 25px; height: 25px;
@@ -40,46 +28,42 @@
list-style: none; list-style: none;
margin: 0; margin: 0;
padding: 0; padding: 0;
color: var(--green);
li { li {
position: relative;
flex: 0 0 auto;
padding-bottom: 10px;
&.active { &.active {
color: var(--cyan); color: var(--cyan);
} }
&:not(:last-of-type) { &:not(:last-of-type) {
margin-right: 20px; margin-right: 20px;
margin-bottom: 10px;
flex: 0 0 auto;
} }
} }
} }
&__sub-inner { &__sub-inner {
position: relative; position: absolute;
list-style: none; z-index: 99;
padding: 0;
margin: 0;
&:not(:only-child) {
margin-left: 20px;
}
&-more {
@include menu;
top: 35px; top: 35px;
left: 0; left: 0;
margin: 0;
&-trigger { padding: 10px;
color: var(--foreground); list-style: none;
user-select: none; border: 2px solid var(--red);
cursor: pointer; background: var(--background);
} box-shadow: 0 10px var(--background), -10px 10px var(--background), 10px 10px var(--background);
li { li {
margin: 0;
padding: 5px; padding: 5px;
white-space: nowrap; white-space: nowrap;
text-decoration: underline;
color: var(--green);
&:not(:last-of-type) {
margin-right: 0;
} }
} }
} }
-5
查看文件
@@ -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;
+12 -2
查看文件
@@ -52,13 +52,23 @@
} }
&-title { &-title {
--border: 2px dashed var(--blue); --border: 3px dotted var(--blue);
position: relative; position: relative;
color: var(--blue); color: var(--blue);
margin: 0 0 15px; margin: 0 0 15px;
padding-bottom: 15px; padding-bottom: 15px;
border-bottom: var(--border);
font-weight: normal; font-weight: normal;
border-bottom: var(--border);
&::after {
content: "";
position: absolute;
bottom: 2px;
display: block;
width: 100%;
border-bottom: var(--border);
}
a { a {
text-decoration: none; text-decoration: none;
+6
查看文件
@@ -1,3 +1,7 @@
@tailwind base;
@tailwind utilities;
@tailwind components;
@import 'buttons'; @import 'buttons';
@import 'header'; @import 'header';
@import 'logo'; @import 'logo';
@@ -6,3 +10,5 @@
@import 'pagination'; @import 'pagination';
@import 'footer'; @import 'footer';
@import 'typed-text'; @import 'typed-text';
@import 'theme';
-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,
}),
)
},
});
+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()
}
@@ -25,5 +25,6 @@
></a ></a
></span> ></span>
</div> </div>
<halo:footer />
</div> </div>
</footer> </footer>
+11 -8
查看文件
@@ -24,7 +24,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" 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 +46,24 @@
</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="menu()" @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: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="{'hidden': !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:text="${childMenuItem.status.displayName} "
></a>
</li> </li>
</ul> </ul>
</li> </li>
+3 -2
查看文件
@@ -6,7 +6,8 @@
<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="${site.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 +28,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>
+2 -2
查看文件
@@ -46,7 +46,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 +60,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>
+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.1.1
require: ">=2.8.0" require: ">=2.20.0"
+14 -8
查看文件
@@ -1,19 +1,25 @@
import path from "path"; import path from "path";
import { fileURLToPath } from "url";
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import PurgeIcons from "vite-plugin-purge-icons"; import PurgeIcons from "vite-plugin-purge-icons";
export default defineConfig({ export default defineConfig({
root: "./src",
base: '/themes/theme-terminal/assets/dist/',
plugins: [PurgeIcons()], plugins: [PurgeIcons()],
build: { build: {
outDir: fileURLToPath(new URL("./templates/assets/dist", import.meta.url)), manifest: true,
emptyOutDir: true, rollupOptions: {
lib: { input: path.resolve(__dirname, "src/main.ts"),
entry: path.resolve(__dirname, "src/main.ts"), output: {
name: "main", entryFileNames: "[name].js",
fileName: "main", chunkFileNames: "[name].js",
formats: ["iife"], assetFileNames: "[name][extname]",
}, },
treeshake: false,
preserveEntrySignatures: "allow-extension",
},
outDir: path.resolve(__dirname, "templates/assets/dist"),
emptyOutDir: true,
}, },
}); });