diff --git a/.gitignore b/.gitignore index d3a6faf..9514a7c 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ lerna-debug.log* node_modules dist dist-ssr +publish *.local # Editor directories and files @@ -26,4 +27,4 @@ dist-ssr .gradle build -dev/data \ No newline at end of file +dev/data diff --git a/dev/docker-compose.yaml b/dev/docker-compose.yaml index e28d065..bf0fc2e 100644 --- a/dev/docker-compose.yaml +++ b/dev/docker-compose.yaml @@ -1,6 +1,6 @@ services: halo: - image: registry.fit2cloud.com/halo/halo:2.18 + image: registry.fit2cloud.com/halo/halo:2.20.10 volumes: - ./data:/root/.halo2 - ../theme-terminal:/root/.halo2/themes/theme-terminal diff --git a/theme-terminal/package.json b/theme-terminal/package.json index 5ee02bf..589b08e 100644 --- a/theme-terminal/package.json +++ b/theme-terminal/package.json @@ -1,48 +1,53 @@ { - "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" - } + "name": "theme-terminal", + "private": true, + "version": "1.1.1", + "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'", + "publish": "cp -r theme.yaml settings.yaml templates publish/ && zip -r theme-terminal.zip publish" + }, + "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": { + "@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" + } } \ No newline at end of file diff --git a/theme-terminal/settings.yaml b/theme-terminal/settings.yaml index 87fc258..32afca4 100644 --- a/theme-terminal/settings.yaml +++ b/theme-terminal/settings.yaml @@ -27,6 +27,7 @@ spec: name: title label: 站点标题 help: 配置后,将展示站点标题。 + value: 'Terminal' - group: index label: 首页设置 formSchema: diff --git a/theme-terminal/src/alpine/menu.ts b/theme-terminal/src/alpine/menu.ts new file mode 100644 index 0000000..c6167df --- /dev/null +++ b/theme-terminal/src/alpine/menu.ts @@ -0,0 +1,12 @@ +interface MenuState { + isOpen: boolean; + handleToggleMenu(): void; +} + +export const menu = (): MenuState => ({ + isOpen: false, + + handleToggleMenu() { + this.isOpen = !this.isOpen; + } +}) diff --git a/theme-terminal/src/alpine/themeMode.ts b/theme-terminal/src/alpine/themeMode.ts new file mode 100644 index 0000000..714e04e --- /dev/null +++ b/theme-terminal/src/alpine/themeMode.ts @@ -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); + }, +}); \ No newline at end of file diff --git a/theme-terminal/src/alpine/upvote.ts b/theme-terminal/src/alpine/upvote.ts new file mode 100644 index 0000000..dfa74ec --- /dev/null +++ b/theme-terminal/src/alpine/upvote.ts @@ -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, + }) + ); + }, +}); \ No newline at end of file diff --git a/theme-terminal/src/custom.ts b/theme-terminal/src/custom.ts deleted file mode 100644 index f1056ef..0000000 --- a/theme-terminal/src/custom.ts +++ /dev/null @@ -1,45 +0,0 @@ -// 文字打字机效果 -export const typewriterEffect = (selectors: string) => { - const typedTextContainer = document.querySelector(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(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); - }; -} \ No newline at end of file diff --git a/theme-terminal/src/main.ts b/theme-terminal/src/main.ts index 7243f06..d22c7dc 100644 --- a/theme-terminal/src/main.ts +++ b/theme-terminal/src/main.ts @@ -1,20 +1,19 @@ -import './styles/tailwind.css' -import './styles/style.scss' -import './styles/theme.scss' -import './styles/font-hack.scss' +import "./styles/style.scss"; +import "./styles/font-hack.scss"; +import "./styles/font-pixel.scss"; import Alpine from 'alpinejs' -import upvote from './upvote' -import {typewriterEffect, handleToggleThemeMode} from './custom' +import {upvote} from './alpine/upvote' +import {themeMode} from './alpine/themeMode' +import {menu} from './alpine/menu' +import {typewriterEffect} from './utils' window.Alpine = Alpine Alpine.data('upvote', upvote) +Alpine.data('themeMode', themeMode) +Alpine.data('menu', menu) Alpine.start() -document.addEventListener('DOMContentLoaded', () => { - typewriterEffect('.typed-text') - - handleToggleThemeMode('#theme-toggle') -}) +document.addEventListener('DOMContentLoaded', () => typewriterEffect('.typed-text')) diff --git a/theme-terminal/src/styles/font-hack-subset.scss b/theme-terminal/src/styles/font-hack-subset.scss index 31281aa..1eca712 100644 --- a/theme-terminal/src/styles/font-hack-subset.scss +++ b/theme-terminal/src/styles/font-hack-subset.scss @@ -7,28 +7,28 @@ @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'); + 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'); + 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'); + 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'); + 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; } diff --git a/theme-terminal/src/styles/font-pixel.scss b/theme-terminal/src/styles/font-pixel.scss new file mode 100644 index 0000000..eb1f582 --- /dev/null +++ b/theme-terminal/src/styles/font-pixel.scss @@ -0,0 +1,4 @@ +@font-face { + font-family: Ark-Pixel-12-proportional-zh_cn; + src: url("../fonts/fusion-pixel-12px-proportional-zh_hans.woff2"); +} \ No newline at end of file diff --git a/theme-terminal/src/styles/header.scss b/theme-terminal/src/styles/header.scss index b25c458..d5403f2 100644 --- a/theme-terminal/src/styles/header.scss +++ b/theme-terminal/src/styles/header.scss @@ -1,17 +1,5 @@ @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; @@ -25,7 +13,7 @@ .dividing { 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; width: 100%; height: 25px; @@ -40,46 +28,42 @@ list-style: none; margin: 0; padding: 0; - color: var(--green); li { + position: relative; + flex: 0 0 auto; + padding-bottom: 10px; + &.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; + position: absolute; + z-index: 99; + top: 35px; + left: 0; 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); - &:not(:only-child) { - margin-left: 20px; - } + li { + padding: 5px; + white-space: nowrap; + text-decoration: underline; + color: var(--green); - &-more { - @include menu; - top: 35px; - left: 0; - - &-trigger { - color: var(--foreground); - user-select: none; - cursor: pointer; - } - - li { - margin: 0; - padding: 5px; - white-space: nowrap; + &:not(:last-of-type) { + margin-right: 0; } } } diff --git a/theme-terminal/src/styles/main.scss b/theme-terminal/src/styles/main.scss index f221aca..d1bdc70 100644 --- a/theme-terminal/src/styles/main.scss +++ b/theme-terminal/src/styles/main.scss @@ -10,11 +10,6 @@ html { box-sizing: inherit; } -@font-face { - font-family: Ark-Pixel-12-proportional-zh_cn; - src: url("../fonts/fusion-pixel-12px-proportional-zh_hans.woff2"); -} - body { margin: 0; padding: 0; diff --git a/theme-terminal/src/styles/post.scss b/theme-terminal/src/styles/post.scss index 37085a9..24d97a9 100644 --- a/theme-terminal/src/styles/post.scss +++ b/theme-terminal/src/styles/post.scss @@ -52,13 +52,23 @@ } &-title { - --border: 2px dashed var(--blue); + --border: 3px dotted var(--blue); + position: relative; color: var(--blue); margin: 0 0 15px; padding-bottom: 15px; - border-bottom: var(--border); 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; diff --git a/theme-terminal/src/styles/style.scss b/theme-terminal/src/styles/style.scss index dd57a6a..46b1935 100644 --- a/theme-terminal/src/styles/style.scss +++ b/theme-terminal/src/styles/style.scss @@ -1,3 +1,7 @@ +@tailwind base; +@tailwind utilities; +@tailwind components; + @import 'buttons'; @import 'header'; @import 'logo'; @@ -6,3 +10,5 @@ @import 'pagination'; @import 'footer'; @import 'typed-text'; + +@import 'theme'; \ No newline at end of file diff --git a/theme-terminal/src/upvote.ts b/theme-terminal/src/upvote.ts deleted file mode 100644 index 61f88c7..0000000 --- a/theme-terminal/src/upvote.ts +++ /dev/null @@ -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, - }), - ) - }, -}); \ No newline at end of file diff --git a/theme-terminal/src/utils.ts b/theme-terminal/src/utils.ts new file mode 100644 index 0000000..659f5c6 --- /dev/null +++ b/theme-terminal/src/utils.ts @@ -0,0 +1,24 @@ +// 文字打字机效果 +export const typewriterEffect = (selectors: string) => { + const typedTextContainer = document.querySelector(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() +} \ No newline at end of file diff --git a/theme-terminal/templates/modules/footer.html b/theme-terminal/templates/modules/footer.html index 588e56e..cedec36 100644 --- a/theme-terminal/templates/modules/footer.html +++ b/theme-terminal/templates/modules/footer.html @@ -25,5 +25,6 @@ > + diff --git a/theme-terminal/templates/modules/header.html b/theme-terminal/templates/modules/header.html index 9886acd..e633611 100644 --- a/theme-terminal/templates/modules/header.html +++ b/theme-terminal/templates/modules/header.html @@ -24,7 +24,7 @@ -