refactor: code cleanup

This commit is contained in:
saicaca 2024-02-18 18:13:43 +08:00 committed by saica.go
parent 3cd21c2da9
commit af29b9160f
51 changed files with 872 additions and 2264 deletions

View File

@ -1 +0,0 @@
{}

View File

@ -1,14 +0,0 @@
{
"taxonomy": {
"tags": [
"Blogging",
"Customization",
"Demo",
"Example",
"Fuwari",
"Markdown",
"Video"
],
"categories": []
}
}

View File

@ -1,9 +1,6 @@
# Fuwari
> [!WARNING]
> This project is still very unfinished and the code is quite messy. Features may be changed or removed in the future.
Fuwari (not the final name maybe) is a static blog template built with [Astro](https://astro.build), a refactored version of [hexo-theme-vivia](https://github.com/saicaca/hexo-theme-vivia).
Fuwari is a static blog template built with [Astro](https://astro.build), a refactored version of [hexo-theme-vivia](https://github.com/saicaca/hexo-theme-vivia).
[**🖥Live Demo (Vercel)**](https://fuwari.vercel.app)

View File

@ -1,5 +1,4 @@
import tailwind from "@astrojs/tailwind"
import yaml from "@rollup/plugin-yaml"
import Compress from "astro-compress"
import icon from "astro-icon"
import { defineConfig } from "astro/config"
@ -9,7 +8,7 @@ import rehypeKatex from "rehype-katex"
import rehypeSlug from "rehype-slug"
import remarkMath from "remark-math"
import { remarkReadingTime } from "./src/plugins/remark-reading-time.mjs"
import vue from "@astrojs/vue"
import svelte from "@astrojs/svelte"
const oklchToHex = (str) => {
const DEFAULT_HUE = 250
@ -38,7 +37,7 @@ export default defineConfig({
Compress({
Image: false,
}),
vue()
svelte(),
],
markdown: {
remarkPlugins: [remarkMath, remarkReadingTime],
@ -71,7 +70,6 @@ export default defineConfig({
],
},
vite: {
plugins: [yaml()],
css: {
preprocessorOptions: {
stylus: {

View File

@ -14,11 +14,11 @@
},
"dependencies": {
"@astrojs/check": "^0.3.4",
"@astrojs/svelte": "^5.0.3",
"@astrojs/tailwind": "^5.1.0",
"@astrojs/vue": "^4.0.8",
"@fontsource-variable/jetbrains-mono": "^5.0.19",
"@fontsource/roboto": "^5.0.8",
"astro": "^4.1.1",
"astro": "^4.4.0",
"astro-icon": "1.0.2",
"colorjs.io": "^0.4.5",
"mdast-util-to-string": "^4.0.0",
@ -30,10 +30,9 @@
"rehype-slug": "^6.0.0",
"remark-math": "^6.0.0",
"sharp": "^0.32.6",
"svelte": "^4.2.9",
"tailwindcss": "^3.3.7",
"typescript": "^5.2.2",
"valine": "^1.5.1",
"vue": "^3.4.15"
"typescript": "^5.2.2"
},
"devDependencies": {
"@astrojs/ts-plugin": "^1.3.1",

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -1,10 +1,12 @@
/* This is a script to create a new post markdown file with front-matter */
import fs from "fs"
import path from "path"
function getDate() {
const today = new Date()
const year = today.getFullYear()
const month = String(today.getMonth() + 1).padStart(2, "0") //月份从0开始所以要加1
const month = String(today.getMonth() + 1).padStart(2, "0")
const day = String(today.getDate()).padStart(2, "0")
return `${year}-${month}-${day}`

View File

@ -1,4 +1,10 @@
---
import {getSortedPosts} from "../utils/content-utils";
import {getPostUrlBySlug} from "../utils/url-utils";
import {i18n} from "../i18n/translation";
import I18nKey from "../i18n/i18nKey";
import {UNCATEGORIZED} from "@constants/constants";
interface Props {
keyword: string;
tags: string[];
@ -6,13 +12,6 @@ interface Props {
}
const { keyword, tags, categories} = Astro.props;
import Button from "./control/Button.astro";
import {getSortedPosts} from "../utils/content-utils";
import {getPostUrlBySlug} from "../utils/url-utils";
import {i18n} from "../i18n/translation";
import I18nKey from "../i18n/i18nKey";
import {UNCATEGORIZED} from "@constants/constants";
let posts = await getSortedPosts()
if (Array.isArray(tags) && tags.length > 0) {
@ -64,48 +63,51 @@ function formatTag(tag: string[]) {
<div class="card-base px-8 py-6">
{
groups.map(group => (
<div>
<div class="flex flex-row w-full items-center h-[3.75rem]">
<div class="w-[15%] md:w-[10%] transition text-2xl font-bold text-right text-black/75 dark:text-white/75">{group.year}</div>
<div class="w-[15%] md:w-[10%]">
<div class="h-3 w-3 bg-none rounded-full outline outline-[var(--primary)] mx-auto -outline-offset-[2px] z-50 outline-3"></div>
</div>
<div class="w-[70%] md:w-[80%] transition text-left text-black/50 dark:text-white/50">{group.posts.length} {i18n(I18nKey.postsCount)}</div>
<div>
<div class="flex flex-row w-full items-center h-[3.75rem]">
<div class="w-[15%] md:w-[10%] transition text-2xl font-bold text-right text-75">{group.year}</div>
<div class="w-[15%] md:w-[10%]">
<div class="h-3 w-3 bg-none rounded-full outline outline-[var(--primary)] mx-auto -outline-offset-[2px] z-50 outline-3"></div>
</div>
{group.posts.map(post => (
<a href={getPostUrlBySlug(post.slug)} aria-label={post.data.title} class="group">
<Button light height="40px" class="w-full rounded-lg hover:text-[initial]">
<div class="flex flex-row justify-start items-center h-full">
<!-- date -->
<div class="w-[15%] md:w-[10%] transition text-sm text-right text-black/50 dark:text-white/50">{formatDate(post.data.published)}</div>
<!-- dot and line -->
<div class="w-[15%] md:w-[10%] relative dash-line h-full flex items-center">
<div class="transition-all mx-auto w-1 h-1 rounded group-hover:h-5
bg-[oklch(0.5_0.05_var(--hue))] group-hover:bg-[var(--primary)]
outline outline-4 z-50
outline-[var(--card-bg)]
group-hover:outline-[var(--btn-plain-bg-hover)]
group-active:outline-[var(--btn-plain-bg-active)]
"
></div>
</div>
<!-- post title -->
<div class="w-[70%] md:max-w-[65%] md:w-[65%] text-left font-bold
group-hover:translate-x-1 transition-all group-hover:text-[var(--primary)]
text-black/80 dark:text-white/80 pr-8 whitespace-nowrap overflow-ellipsis overflow-hidden"
>
{post.data.title}
</div>
<!-- tag list -->
<div class="hidden md:block md:w-[15%] text-left text-sm transition
whitespace-nowrap overflow-ellipsis overflow-hidden
text-black/30 dark:text-white/30"
>{formatTag(post.data.tags)}</div>
</div>
</Button>
</a>
))}
<div class="w-[70%] md:w-[80%] transition text-left text-50">{group.posts.length} {i18n(I18nKey.postsCount)}</div>
</div>
{group.posts.map(post => (
<a href={getPostUrlBySlug(post.slug)}
aria-label={post.data.title}
class="group btn-plain block h-10 w-full rounded-lg hover:text-[initial]"
>
<div class="flex flex-row justify-start items-center h-full">
<!-- date -->
<div class="w-[15%] md:w-[10%] transition text-sm text-right text-50">
{formatDate(post.data.published)}
</div>
<!-- dot and line -->
<div class="w-[15%] md:w-[10%] relative dash-line h-full flex items-center">
<div class="transition-all mx-auto w-1 h-1 rounded group-hover:h-5
bg-[oklch(0.5_0.05_var(--hue))] group-hover:bg-[var(--primary)]
outline outline-4 z-50
outline-[var(--card-bg)]
group-hover:outline-[var(--btn-plain-bg-hover)]
group-active:outline-[var(--btn-plain-bg-active)]
"
></div>
</div>
<!-- post title -->
<div class="w-[70%] md:max-w-[65%] md:w-[65%] text-left font-bold
group-hover:translate-x-1 transition-all group-hover:text-[var(--primary)]
text-75 pr-8 whitespace-nowrap overflow-ellipsis overflow-hidden"
>
{post.data.title}
</div>
<!-- tag list -->
<div class="hidden md:block md:w-[15%] text-left text-sm transition
whitespace-nowrap overflow-ellipsis overflow-hidden
text-30"
>{formatTag(post.data.tags)}</div>
</div>
</a>
))}
</div>
))
}
</div>

View File

@ -1,6 +0,0 @@
---
---
<div class="rounded-2xl drop-shadow-2xl bg-white">
<slot />
</div>

View File

@ -1,7 +0,0 @@
---
import { Icon } from 'astro-icon/components';
import ButtonLight from "./control/Button.astro";
---
<ButtonLight class="fill-black">
<Icon name="material-symbols:nightlight-badge-outline" class="w-6 h-6"/>
</ButtonLight>

View File

@ -1,60 +0,0 @@
---
interface Props {
title: string;
body: string;
href: string;
}
const { href, title, body } = Astro.props;
---
<li class="link-card">
<a href={href}>
<h2>
{title}
<span class="">&rarr;</span>
</h2>
<p>
{body}
</p>
</a>
</li>
<style>
.link-card {
list-style: none;
display: flex;
padding: 1px;
background-color: #23262d;
background-image: none;
background-size: 400%;
border-radius: 7px;
background-position: 100%;
transition: background-position 0.6s cubic-bezier(0.22, 1, 0.36, 1);
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1);
}
.link-card > a {
width: 100%;
text-decoration: none;
line-height: 1.4;
padding: calc(1.5rem - 1px);
border-radius: 8px;
color: white;
background-color: #23262d;
opacity: 0.8;
}
h2 {
margin: 0;
font-size: 1.25rem;
transition: color 0.6s cubic-bezier(0.22, 1, 0.36, 1);
}
p {
margin-top: 0.5rem;
margin-bottom: 0;
}
.link-card:is(:hover, :focus-within) {
background-position: 0;
background-image: var(--accent-gradient);
}
.link-card:is(:hover, :focus-within) h2 {
color: rgb(var(--accent-light));
}
</style>

View File

@ -5,7 +5,7 @@ import {profileConfig} from "../config";
---
<div class="card-base max-w-[var(--page-width)] min-h-[4.5rem] rounded-b-none mx-auto flex items-center px-6">
<div class="text-black/50 dark:text-white/50 text-sm">
<div class="text-50 text-sm">
© 2023 {profileConfig.name}. All Rights Reserved.
<br>
Powered by <a class="link text-[var(--primary)]" target="_blank" href="https://github.com/saicaca/fuwari">Fuwari</a>

View File

@ -199,11 +199,36 @@ color_set({
@apply bg-transparent text-[var(--primary)]
}
.text-deep {
.btn-card {
@apply transition flex items-center justify-center bg-[var(--card-bg)] hover:bg-[var(--btn-card-bg-hover)]
active:bg-[var(--btn-card-bg-active)]
}
.btn-card.disabled {
@apply pointer-events-none text-black/10 dark:text-white/10
}
.btn-plain {
@apply transition flex items-center justify-center bg-none hover:bg-[var(--btn-plain-bg-hover)] active:bg-[var(--btn-plain-bg-active)]
text-black/75 hover:text-[var(--primary)] dark:text-white/75 dark:hover:text-[var(--primary)]
}
.btn-regular {
@apply transition flex items-center justify-center bg-[var(--btn-regular-bg)] hover:bg-[var(--btn-regular-bg-hover)] active:bg-[var(--btn-regular-bg-active)]
text-[var(--btn-content)] dark:text-white/75
}
.text-90 {
@apply text-black/90 dark:text-white/90
}
.text-sub {
.text-75 {
@apply text-black/75 dark:text-white/75
}
.text-50 {
@apply text-black/50 dark:text-white/50
}
.text-30 {
@apply text-black/30 dark:text-white/30
}
.text-25 {
@apply text-black/25 dark:text-white/25
}
}
</style>

View File

@ -1,85 +1,65 @@
---
import Button from "./control/Button.astro";
import { Icon } from 'astro-icon/components';
import DisplaySetting from "./widget/DisplaySetting.astro";
import I18nKey from "../i18n/i18nKey";
import {i18n} from "../i18n/translation";
import DisplaySettings from "./widget/DisplaySettings.svelte";
import {LinkPreset, NavBarLink} from "../types/config";
import {navBarConfig, siteConfig} from "../config";
import NavMenuPanel from "./widget/NavMenuPanel.astro";
import SearchPanel from "./SearchPanel.vue"
import Search from "./Search.svelte";
import {LinkPresets} from "../constants/link-presets";
const className = Astro.props.class;
function isI18nKey(key: string): key is I18nKey {
return Object.values(I18nKey).includes(key);
}
let links: NavBarLink[] = navBarConfig.links.map((item) => {
let links: NavBarLink[] = navBarConfig.links.map((item: NavBarLink | LinkPreset): NavBarLink => {
if (typeof item === "number") {
return getLinkPresetInfo(item)
return LinkPresets[item]
}
return item;
});
function getLinkPresetInfo(p: LinkPreset): NavBarLink {
switch (p) {
case LinkPreset.Home:
return {
name: i18n(I18nKey.home),
url: "/page/1"
};
case LinkPreset.Archive:
return {
name: i18n(I18nKey.archive),
url: "/archive"
};
case LinkPreset.About:
return {
name: i18n(I18nKey.about),
url: "/about"
};
}
}
---
<div transition:animate="none" class:list={[
className,
"card-base sticky top-0 overflow-visible max-w-[var(--page-width)] h-[4.5rem] rounded-t-none mx-auto flex items-center justify-between px-4"]}>
<a href="/page/1"><Button height="3.25rem" class="px-5 font-bold rounded-lg active:scale-95" light>
<a href="/page/1" class="btn-plain h-[3.25rem] px-5 font-bold rounded-lg active:scale-95">
<div class="flex flex-row text-[var(--primary)] items-center text-md">
<Icon name="material-symbols:home-outline-rounded" size={"1.75rem"} class="mb-1 mr-2" />
{siteConfig.title}
</div>
</Button></a>
<div class="hidden md:block">
</a>
<div class="hidden md:flex">
{links.map((l) => {
return <a aria-label={l.name} href={l.url} target={l.external ? "_blank" : null}>
<Button light class="font-bold px-5 rounded-lg active:scale-95">
<div class="flex items-center">
{l.name}
{l.external && <Icon size="14" name="fa6-solid:arrow-up-right-from-square" class="transition -translate-y-[1px] ml-1 text-black/[0.2] dark:text-white/[0.2]"></Icon>}
</div>
</Button>
return <a aria-label={l.name} href={l.url} target={l.external ? "_blank" : null}
class="btn-plain h-11 font-bold px-5 rounded-lg active:scale-95"
>
<div class="flex items-center">
{l.name}
{l.external && <Icon size="14" name="fa6-solid:arrow-up-right-from-square" class="transition -translate-y-[1px] ml-1 text-black/[0.2] dark:text-white/[0.2]"></Icon>}
</div>
</a>;
})}
</div>
<div class="flex">
<SearchPanel client:load>
<Icon slot="search-icon" name="material-symbols:search" size={"1.25rem"} class="ml-3 transition my-auto text-black/30 dark:text-white/30"></Icon>
<!--<SearchPanel client:load>-->
<Search client:load>
<Icon slot="search-icon" name="material-symbols:search" size={"1.25rem"} class="absolute pointer-events-none ml-3 transition my-auto text-black/30 dark:text-white/30"></Icon>
<!--<Icon slot="arrow-icon" name="material-symbols:chevron-right-rounded" size={"1.25rem"} class="transition my-auto text-[var(&#45;&#45;primary)]"></Icon>-->
<Icon slot="arrow-icon" name="fa6-solid:chevron-right" size={"0.75rem"} class="transition translate-x-0.5 my-auto text-[var(--primary)]"></Icon>
<Button slot="search-switch" name="Search Panel" class="block lg:hidden rounded-lg active:scale-90" id="search-switch" iconName="material-symbols:search" iconSize={"1.25rem"} isIcon light></Button>
</SearchPanel>
<Button name="Display Settings" class="rounded-lg active:scale-90" id="display-settings-switch" iconName="material-symbols:palette-outline" iconSize={"1.25rem"} isIcon light></Button>
<Button name="Light/Dark Mode" class="rounded-lg flex items-center justify-center active:scale-90" id="scheme-switch" light height="2.75rem" width="2.75rem">
<Icon slot="search-switch" name="material-symbols:search" size={"1.25rem"}></Icon>
</Search>
<button aria-label="Display Settings" class="btn-plain h-11 w-11 rounded-lg active:scale-90" id="display-settings-switch">
<Icon name="material-symbols:palette-outline" size={"1.25rem"}></Icon>
</button>
<button aria-label="Light/Dark Mode" class="btn-plain h-11 w-11 rounded-lg active:scale-90" id="scheme-switch">
<Icon name="material-symbols:wb-sunny-outline-rounded" size={"1.25rem"} class="absolute opacity-[var(--display-light-icon)]"></Icon>
<Icon name="material-symbols:dark-mode-outline-rounded" size={"1.25rem"} class="absolute opacity-[var(--display-dark-icon)]"></Icon>
</Button>
<Button name="Nav Menu" class="rounded-lg active:scale-90 block md:hidden" id="nav-menu-switch" iconName="material-symbols:menu-rounded" iconSize={"1.25rem"} isIcon light></Button>
</button>
<button name="Nav Menu" class="btn-plain w-11 h-11 rounded-lg active:scale-90 md:hidden" id="nav-menu-switch">
<Icon name="material-symbols:menu-rounded" size={"1.25rem"}></Icon>
</button>
</div>
<DisplaySetting></DisplaySetting>
<NavMenuPanel links={links}></NavMenuPanel>
<DisplaySettings client:only="svelte">
<Icon slot="restore-icon" name="fa6-solid:arrow-rotate-left" size={"0.875rem"} class=""></Icon>
</DisplaySettings>
</div>
<style lang="stylus">
@ -125,12 +105,13 @@ document.addEventListener('astro:after-swap', () => {
{import.meta.env.PROD && <script is:raw>
async function loadPagefind() {
const pagefind = await import("/pagefind/pagefind.js")
const pagefind = await import('/pagefind/pagefind.js')
await pagefind.options({
"excerptLength": 20
'excerptLength': 20
})
pagefind.init()
window.pagefind = pagefind
pagefind.search('') // speed up the first search
}
loadPagefind()
</script>}

View File

@ -1,6 +1,12 @@
---
import path from "path";
import PostMetadata from "./PostMetadata.astro";
import PostMetadata from "./PostMeta.astro";
import ImageWrapper from "./misc/ImageWrapper.astro";
import { Icon } from 'astro-icon/components';
import {i18n} from "../i18n/translation";
import I18nKey from "../i18n/i18nKey";
import {getDir} from "../utils/url-utils";
interface Props {
class: string;
entry: any;
@ -16,14 +22,6 @@ interface Props {
}
const { entry, title, url, published, tags, category, image, description, words } = Astro.props;
const className = Astro.props.class;
// console.log(Astro.props);
import ImageBox from "./misc/ImageBox.astro";
import ButtonTag from "./control/ButtonTag.astro";
import { Icon } from 'astro-icon/components';
import Button from "./control/Button.astro";
import {i18n} from "../i18n/translation";
import I18nKey from "../i18n/i18nKey";
import {getDir} from "../utils/url-utils";
const hasCover = image !== undefined && image !== null && image !== '';
@ -35,8 +33,7 @@ const { remarkPluginFrontmatter } = await entry.render();
<div class:list={["card-base flex flex-col-reverse md:flex-col w-full rounded-[var(--radius-large)] overflow-hidden relative", className]}>
<div class:list={["pl-6 md:pl-9 pr-6 md:pr-2 pt-6 md:pt-7 pb-6 relative", {"w-full md:w-[calc(100%_-_52px_-_12px)]": !hasCover, "w-full md:w-[calc(100%_-_var(--coverWidth)_-_12px)]": hasCover}]}>
<a href={url}
class="transition w-full block font-bold mb-3 text-3xl
text-black/90 dark:text-white/90
class="transition w-full block font-bold mb-3 text-3xl text-90
hover:text-[var(--primary)] dark:hover:text-[var(--primary)]
active:text-[var(--title-active)] dark:active:text-[var(--title-active)]
before:w-1 before:h-5 before:rounded-md before:bg-[var(--primary)]
@ -48,10 +45,12 @@ const { remarkPluginFrontmatter } = await entry.render();
<!-- metadata -->
<PostMetadata published={published} tags={tags} category={category} hideTagsForMobile={true} class:list={{"mb-4": description, "mb-6": !description}}></PostMetadata>
<div class="transition text-black/75 dark:text-white/75 mb-3.5">
<!-- description -->
<div class="transition text-75 mb-3.5">
{ description }
</div>
<!-- word count and read time -->
<div class="text-sm text-black/30 dark:text-white/30 flex gap-4 transition">
<div>{remarkPluginFrontmatter.words} {" " + i18n(I18nKey.wordsCount)}</div>
<div>|</div>
@ -71,26 +70,23 @@ const { remarkPluginFrontmatter } = await entry.render();
class="transition opacity-0 group-hover:opacity-100 text-white text-5xl">
</Icon>
</div>
<ImageBox src={image} basePath={path.join("content/posts/", getDir(entry.id))} alt="Cover Image of the Post"
<ImageWrapper src={image} basePath={path.join("content/posts/", getDir(entry.id))} alt="Cover Image of the Post"
class="w-full h-full">
</ImageBox>
</ImageWrapper>
</a>}
{!hasCover &&
<a href={url} aria-label={title} class="hidden md:block">
<Button name="Enter the Post" width="3.25rem" height="full" class="absolute right-3 top-3 bottom-3 rounded-xl bg-[var(--enter-btn-bg)] hover:bg-[var(--enter-btn-bg-hover)] active:bg-[var(--enter-btn-bg-active)] active:scale-95">
<Icon name="material-symbols:chevron-right-rounded"
class="transition text-[var(--primary)] text-4xl mx-auto">
</Icon>
</Button>
<a href={url} aria-label={title} class="hidden md:flex btn-regular w-[3.25rem]
absolute right-3 top-3 bottom-3 rounded-xl bg-[var(--enter-btn-bg)]
hover:bg-[var(--enter-btn-bg-hover)] active:bg-[var(--enter-btn-bg-active)] active:scale-95
">
<Icon name="material-symbols:chevron-right-rounded"
class="transition text-[var(--primary)] text-4xl mx-auto">
</Icon>
</a>
}
</div>
<div class="transition border-t-[1px] border-dashed mx-6 border-black/10 dark:border-white/[0.15] last:border-t-0 md:hidden"></div>
<style lang="stylus" define:vars={{coverWidth}}>
:root
--btn-enter-bg oklch(0.98 0.005 var(--hue))
--btn-enter-bg-dark oklch(0.2 0.02 var(--hue))
</style>

View File

@ -22,7 +22,7 @@ const className = Astro.props.class;
>
<Icon name="material-symbols:calendar-today-outline-rounded" class="text-xl"></Icon>
</div>
<span class="text-black/50 dark:text-white/50 text-sm font-medium">{formatDateToYYYYMMDD(published)}</span>
<span class="text-50 text-sm font-medium">{formatDateToYYYYMMDD(published)}</span>
</div>
<!-- categories -->
@ -33,7 +33,7 @@ const className = Astro.props.class;
</div>
<div class="flex flex-row flex-nowrap">
<div><a href=`/archive/category/${category || 'uncategorized'}` aria-label=`View all posts in the ${category} category`
class="link-lg transition text-black/50 dark:text-white/50 text-sm font-medium
class="link-lg transition text-50 text-sm font-medium
hover:text-[var(--primary)] dark:hover:text-[var(--primary)] whitespace-nowrap">
{category || i18n(I18nKey.uncategorized)}
</a></div>
@ -51,12 +51,12 @@ const className = Astro.props.class;
class="with-divider"
>
<a href=`/archive/tag/${tag}` aria-label=`View all posts with the ${tag} tag`
class="link-lg transition text-black/50 dark:text-white/50 text-sm font-medium
class="link-lg transition text-50 text-sm font-medium
hover:text-[var(--primary)] dark:hover:text-[var(--primary)] whitespace-nowrap">
{tag}
</a>
</div>)}
{!(tags && tags.length > 0) && <div class="transition text-black/50 dark:text-white/50 text-sm font-medium">{i18n(I18nKey.noTags)}</div>}
{!(tags && tags.length > 0) && <div class="transition text-50 text-sm font-medium">{i18n(I18nKey.noTags)}</div>}
</div>
</div>
</div>
@ -70,7 +70,7 @@ const className = Astro.props.class;
text-[var(--btn-content)] mr-2
}
.with-divider {
@apply before:content-['/'] before:ml-1.5 before:mr-0.5 before:text-[var(--meta-divider)] before:text-sm
@apply before:content-['/'] before:ml-1.5 before:mr-1.5 before:text-[var(--meta-divider)] before:text-sm
before:font-medium before:first-of-type:hidden before:transition
}
}

View File

@ -0,0 +1,23 @@
---
import {getPostUrlBySlug} from "@utils/url-utils";
import PostCard from "./PostCard.astro";
const {page} = Astro.props;
---
<div class="transition flex flex-col rounded-[var(--radius-large)] bg-[var(--card-bg)] py-1 md:py-0 md:bg-transparent md:gap-4 mb-4">
{page.data.map((entry: { data: { draft: boolean; title: string; tags: string[]; category: string; published: Date; image: string; description: string; }; slug: string; }) => {
return (
<PostCard
entry={entry}
title={entry.data.title}
tags={entry.data.tags}
category={entry.data.category}
published={entry.data.published}
url={getPostUrlBySlug(entry.slug)}
image={entry.data.image}
description={entry.data.description}
draft={entry.data.draft}
></PostCard>
);
})}
</div>

View File

@ -1,83 +0,0 @@
---
import {formatDateToYYYYMMDD} from "../utils/date-utils";
interface Props {
title: string;
url: string;
published: Date;
tags: string[];
image: string;
description: string;
words: number;
}
const { title, url, published, tags, image, description, words } = Astro.props;
// console.log(Astro.props);
import ImageBox from "./misc/ImageBox.astro";
import ButtonTag from "./control/ButtonTag.astro";
import { Icon } from 'astro-icon/components';
// tags = ['Foo', 'Bar', 'Baz', 'Qux', 'Quux'];
// const cover = 'https://saicaca.github.io/vivia-preview/assets/79905307_p0.jpg';
// cover = null;
const hasCover = image !== undefined && image !== null && image !== '';
---
<div class="flex w-full rounded-[var(--radius-large)] overflow-hidden relative">
<div class:list={["card-base z-30 px-8 py-6 relative ",
{
'w-[calc(70%_+_var(--radius-large))]': hasCover,
'w-[calc(100%_-_76px_+_var(--radius-large))]': !hasCover,
}
]}>
<a href={url}
class="transition w-full block font-bold mb-1 text-3xl
text-neutral-900 dark:text-neutral-100
hover:text-[var(--primary)] dark:hover:text-[var(--primary)]
before:w-1 before:h-4 before:rounded-md before:bg-[var(--primary)]
before:absolute before:top-8 before:left-4
">
This is a very long title
</a>
<div class="flex text-neutral-500 dark:text-neutral-400 items-center mb-1">
<div>{formatDateToYYYYMMDD(published)}</div>
<div class="transition h-1 w-1 rounded-sm bg-neutral-400 dark:bg-neutral-600 mx-3"></div>
<div>Uncategorized</div>
<div class="transition h-1 w-1 rounded-sm bg-neutral-400 dark:bg-neutral-600 mx-3"></div>
<div>{words} words</div>
</div>
<div class="flex gap-2 mb-4">
{tags.map(t => (
<ButtonTag dot>{t}</ButtonTag>
))}
</div>
<div class="transition text-neutral-700 dark:text-neutral-300">This is the description of the article</div>
</div>
{!hasCover && <a href={url}
class="transition w-[72px]
bg-[var(--btn-enter-bg)] dark:bg-[var(--btn-enter-bg-dark)]
hover:bg-[var(--btn-card-bg-hover)] active:bg-[var(--btn-card-bg-active)]
absolute top-0 bottom-0 right-0 flex items-center">
<Icon name="material-symbols:chevron-right-rounded"
class="transition text-4xl text-[var(--primary)] ml-[22px]"></Icon>
</a>}
{hasCover && <a href={url}
class="group w-[30%] absolute top-0 bottom-0 right-0">
<div class="absolute z-10 w-full h-full group-hover:bg-black/30 group-active:bg-black/50 transition"></div>
<div class="absolute z-20 w-full h-full flex items-center justify-center ">
<Icon name="material-symbols:chevron-right-rounded"
class="transition opacity-0 group-hover:opacity-100 text-white text-5xl"></Icon>
</div>
<ImageBox src="https://saicaca.github.io/vivia-preview/assets/79905307_p0.jpg"
class="w-full h-full">
</ImageBox>
</a>}
</div>
<style lang="stylus">
:root
--btn-enter-bg oklch(0.98 0.005 var(--hue))
--btn-enter-bg-dark oklch(0.2 0.02 var(--hue))
</style>

View File

@ -1,16 +0,0 @@
---
interface Props {
name: string;
}
const {name} = Astro.props;
import BasicCard from "./BasicCard.astro";
---
<BasicCard >
<div class="p-4">
<div>{name}</div>
</div>
</BasicCard>

View File

@ -1,7 +0,0 @@
---
---
<div class="card-base w-full h-[200px]">
</div>

View File

@ -1,51 +1,50 @@
---
import { Icon } from 'astro-icon/components';
import Button from "./Button.astro";
---
<!-- There can't be a filter on parent element, or it will break `fixed` -->
<div class="back-to-top-wrapper hidden lg:block" transition:persist>
<div id="back-to-top-btn" class="back-to-top-btn hide flex items-center rounded-2xl overflow-hidden transition" onclick="topFunction()">
<Button name="Back to Top" card height="60px" width="60px">
<div id="back-to-top-btn" class="back-to-top-btn hide flex items-center rounded-2xl overflow-hidden transition" onclick="backToTop()">
<button aria-label="Back to Top" class="btn-card h-[3.75rem] w-[3.75rem]">
<Icon name="material-symbols:keyboard-arrow-up-rounded" class="mx-auto"></Icon>
</Button>
</button>
</div>
</div>
<style lang="stylus">
.back-to-top-wrapper
width: 60px
height: 60px
width: 3.75rem
height: 3.75rem
position: absolute
right: 0
top: 0
.back-to-top-btn
color: var(--primary)
font-size: 36px
font-size: 2.25rem
font-weight: bold
border: none
position: fixed
bottom: 240px
bottom: 15rem
opacity: 1
cursor: pointer
transform: translateX(80px)
transform: translateX(5rem)
i
font-size: 28px
font-size: 1.75rem
&.hide
transform: translateX(80px) scale(0.9)
transform: translateX(5rem) scale(0.9)
opacity: 0
pointer-events: none
&:active
transform: translateX(80px) scale(0.9)
transform: translateX(5rem) scale(0.9)
</style>
<script is:raw>
function topFunction() {
function backToTop() {
window.scroll({ top: 0, behavior: 'smooth' });
}
function scrollFunction() {
let btn = document.getElementById('back-to-top-btn');
if (document.body.scrollTop > 600 || document.documentElement.scrollTop > 600) {
@ -54,7 +53,5 @@ function scrollFunction() {
btn.classList.add('hide')
}
}
window.onscroll = function() {
scrollFunction();
}
window.onscroll = scrollFunction
</script>

View File

@ -1,76 +0,0 @@
---
interface Props {
id?: string;
name?: string;
isIcon?: boolean;
iconName?: string;
width?: string;
height?: string;
regular?: boolean;
light?: boolean
card?: boolean;
iconSize?: number,
class?: string
disabled?: boolean
}
const props = Astro.props;
const {
id,
name,
isIcon = false,
iconName,
width,
height = '2.75rem',
regular,
light,
iconSize = 24,
card,
disabled = false,
} = Astro.props;
const className = Astro.props.class;
import { Icon } from 'astro-icon/components';
---
<button id={id}
disabled={disabled}
aria-label={name}
class:list={[
className,
`
transition
h-[var(--height)]
`,
{
'w-[var(--width)]': width,
'w-[var(--height)]': isIcon,
'bg-none': light,
'hover:bg-[var(--btn-plain-bg-hover)]': light,
'active:bg-[var(--btn-plain-bg-active)]': light,
'text-black/75': light,
'hover:text-[var(--primary)]': light,
'dark:text-white/75': light || regular,
'dark:hover:text-[var(--primary)]': light,
'bg-[var(--btn-regular-bg)]': regular,
'hover:bg-[var(--btn-regular-bg-hover)]': regular,
'active:bg-[var(--btn-regular-bg-active)]': regular,
'text-[var(--btn-content)]': regular,
'bg-[var(--card-bg)]': card,
'enabled:hover:bg-[var(--btn-card-bg-hover)]': card,
'enabled:active:bg-[var(--btn-card-bg-active)]': card,
'disabled:text-black/10': card,
'disabled:dark:text-white/10': card,
}
]}
>
{props.isIcon && <Icon class="mx-auto" name={props.iconName} size={iconSize}></Icon> }
<slot />
</button>
<style define:vars={{ height, width, iconSize }}>
</style>

View File

@ -1,5 +1,4 @@
---
import Button from "./Button.astro";
interface Props {
size?: string;
dot?: boolean;
@ -8,9 +7,7 @@ interface Props {
}
const { size, dot, href, label }: Props = Astro.props;
---
<a href={href} aria-label={label}>
<Button regular height="2rem" class="text-sm px-3 flex flex-row items-center rounded-lg">
{dot && <div class="h-1 w-1 bg-[var(--btn-content)] dark:bg-[var(--card-bg)] transition rounded-md mr-2"></div>}
<slot></slot>
</Button>
<a href={href} aria-label={label} class="btn-regular h-8 text-sm px-3 rounded-lg">
{dot && <div class="h-1 w-1 bg-[var(--btn-content)] dark:bg-[var(--card-bg)] transition rounded-md mr-2"></div>}
<slot></slot>
</a>

View File

@ -11,7 +11,6 @@ const {page} = Astro.props;
const HIDDEN = -1;
const className = Astro.props.class;
import Button from "./Button.astro";
const ADJ_DIST = 2;
const VISIBLE = ADJ_DIST * 2 + 1;
@ -51,13 +50,22 @@ if (r < page.lastPage)
const parts: string[] = page.url.current.split('/');
const commonUrl: string = parts.slice(0, -1).join('/') + '/';
const getPageUrl = (p: number) => {
if (p == 1)
return commonUrl;
return commonUrl + p;
}
---
<div class:list={[className, "flex flex-row gap-3 justify-center"]}>
<a href={page.url.prev} aria-label={page.url.prev ? "Previous Page" : null}>
<Button isIcon card iconName="material-symbols:chevron-left-rounded" class:list={["text-[var(--primary)] rounded-lg", {"active:scale-90": page.url.prev != undefined}]} iconSize={28}
disabled = {page.url.prev == undefined}
></Button>
<a href={page.url.prev} aria-label={page.url.prev ? "Previous Page" : null}
class:list={["btn-card overflow-hidden rounded-lg text-[var(--primary)] w-11 h-11",
{"disabled": page.url.prev == undefined}
]}
>
<Icon name="material-symbols:chevron-left-rounded" size="1.75rem"></Icon>
</a>
<div class="bg-[var(--card-bg)] flex flex-row rounded-lg items-center text-neutral-700 dark:text-neutral-300 font-bold">
{pages.map((p) => {
@ -69,16 +77,16 @@ const commonUrl: string = parts.slice(0, -1).join('/') + '/';
>
{p}
</div>
return <a href={commonUrl + p} aria-label=`Page ${p}`>
<Button card iconName="material-symbols:chevron-left-rounded" class="rounded-lg active:scale-[0.85]" height="2.75rem" width="2.75rem">
{p}
</Button>
</a>
return <a href={getPageUrl(p)} aria-label=`Page ${p}`
class="btn-card w-11 h-11 rounded-lg overflow-hidden active:scale-[0.85]"
>{p}</a>
})}
</div>
<a href={page.url.next} aria-label={page.url.next ? "Next Page" : null}>
<Button isIcon card name="Next Page" iconName="material-symbols:chevron-right-rounded" class:list={["text-[var(--primary)] rounded-lg", {"active:scale-90": page.url.next != undefined}]} iconSize={28}
disabled = {page.url.next == undefined}
></Button>
<a href={page.url.next} aria-label={page.url.next ? "Next Page" : null}
class:list={["btn-card overflow-hidden rounded-lg text-[var(--primary)] w-11 h-11",
{"disabled": page.url.next == undefined}
]}
>
<Icon name="material-symbols:chevron-right-rounded" size="1.75rem"></Icon>
</a>
</div>

View File

@ -1,5 +1,10 @@
---
import {formatDateToYYYYMMDD} from "../../utils/date-utils";
import { Icon } from 'astro-icon/components';
import {licenseConfig, profileConfig} from "../../config";
import {i18n} from "../../i18n/translation";
import I18nKey from "../../i18n/i18nKey";
interface Props {
title: string;
slug: string;
@ -9,15 +14,8 @@ interface Props {
const { title, slug, pubDate } = Astro.props;
const className = Astro.props.class;
import { Icon } from 'astro-icon/components';
import {licenseConfig, profileConfig} from "../../config";
import {i18n} from "../../i18n/translation";
import I18nKey from "../../i18n/i18nKey";
const profileConf = profileConfig;
const licenseConf = licenseConfig;
const postUrl = decodeURIComponent(Astro.url.toString());
---

View File

@ -22,6 +22,12 @@ interface Props {
<WidgetLayout name={i18n(I18nKey.categories)} id="categories" isCollapsed={isCollapsed} collapsedHeight={COLLAPSED_HEIGHT}>
{categories.map((c) =>
<ButtonLink url={getCategoryUrl(c.name)} badge={c.count} label=`View all posts in the ${c.name} category`>{c.name}</ButtonLink>
<ButtonLink
url={getCategoryUrl(c.name)}
badge={c.count}
label=`View all posts in the ${c.name} category`
>
{c.name}
</ButtonLink>
)}
</WidgetLayout>

View File

@ -8,9 +8,6 @@ interface Props {
}
const links = Astro.props.links;
const enableBanner = siteConfig.banner.enable;
---
<div id="nav-menu-panel" class:list={["float-panel closed absolute transition-all fixed right-4 px-2 py-2"]}>
{links.map((link) => (

View File

@ -1,32 +1,29 @@
---
import ImageBox from "../misc/ImageBox.astro";
import Button from "../control/Button.astro";
import ImageWrapper from "../misc/ImageWrapper.astro";
import {Icon} from "astro-icon/components";
import {profileConfig} from "../../config";
interface props {
}
const className = Astro.props
const config = profileConfig;
---
<div class="card-base">
<a aria-label="Go to About Page" href="/about" class="group block relative mx-auto mt-4 lg:mx-3 lg:mt-3 mb-3 max-w-[240px] lg:max-w-none overflow-hidden rounded-xl active:scale-95">
<div class="absolute transition pointer-events-none group-hover:bg-black/30 group-active:bg-black/50 w-full h-full z-50 flex items-center justify-center">
<a aria-label="Go to About Page" href="/about"
class="group block relative mx-auto mt-4 lg:mx-3 lg:mt-3 mb-3
max-w-[240px] lg:max-w-none overflow-hidden rounded-xl active:scale-95">
<div class="absolute transition pointer-events-none group-hover:bg-black/30 group-active:bg-black/50
w-full h-full z-50 flex items-center justify-center">
<Icon name="fa6-regular:address-card"
class="transition opacity-0 group-hover:opacity-100 text-white text-5xl">
</Icon>
</div>
<ImageBox src={config.avatar} alt="Profile Image of the Author" class="mx-auto lg:w-full h-full lg:mt-0 "></ImageBox>
<ImageWrapper src={config.avatar} alt="Profile Image of the Author" class="mx-auto lg:w-full h-full lg:mt-0 "></ImageWrapper>
</a>
<div class="font-bold text-xl text-center mb-1 dark:text-neutral-50 transition">{config.name}</div>
<div class="h-1 w-5 bg-[var(--primary)] mx-auto rounded-full mb-2 transition"></div>
<div class="text-center text-neutral-400 mb-2.5 transition">{config.bio}</div>
<div class="flex gap-2 mx-2 justify-center mb-4">
{config.links.map(item =>
<a aria-label={item.name} href={item.url} target="_blank">
<Button isIcon iconName={item.icon} regular height="40px" class="rounded-lg active:scale-90"></Button>
<a aria-label={item.name} href={item.url} target="_blank" class="btn-regular rounded-lg h-10 w-10 active:scale-90">
<Icon name={item.icon} size="1.5rem"></Icon>
</a>
)}
</div>

View File

@ -1,20 +0,0 @@
---
import WidgetLayout from "./WidgetLayout.astro";
import ButtonLink from "../control/ButtonLink.astro";
import {getSortedPosts} from "../../utils/content-utils";
import {i18n} from "../../i18n/translation";
import I18nKey from "../../i18n/i18nKey";
import {getPostUrlBySlug} from "../../utils/url-utils";
let posts = await getSortedPosts()
const LIMIT = 3;
posts = posts.slice(0, LIMIT)
---
<WidgetLayout name={i18n(I18nKey.recentPosts)}>
{posts.map(post =>
<ButtonLink url={getPostUrlBySlug(post.slug)}>{post.data.title}</ButtonLink>
)}
</WidgetLayout>

View File

@ -1,6 +1,5 @@
---
import Profile from "./Profile.astro";
import RecentPost from "./RecentPost.astro";
import Tag from "./Tags.astro";
import Categories from "./Categories.astro";
@ -12,10 +11,6 @@ const className = Astro.props.class;
</div>
<div class="flex flex-col w-full gap-4 top-4 sticky top-4" transition:animate="none">
<Categories></Categories>
<!--<RecentPost></RecentPost>-->
<Tag></Tag>
</div>
</div>
<style>
</style>

View File

@ -1,5 +1,4 @@
---
import Button from "../control/Button.astro";
import { Icon } from 'astro-icon/components';
import {i18n} from "../../i18n/translation";
import I18nKey from "../../i18n/i18nKey";
@ -26,11 +25,11 @@ const {
<slot></slot>
</div>
{isCollapsed && <div class="expand-btn px-4 -mb-2">
<Button light class=" w-full rounded-lg" height="36px">
<button class="btn-plain w-full h-9 rounded-lg">
<div class="text-[var(--primary)] flex items-center justify-center gap-2 -translate-x-2">
<Icon name="material-symbols:more-horiz" size={28}></Icon> {i18n(I18nKey.more)}
</div>
</Button>
</button>
</div>}
</widget-layout>

View File

@ -3,8 +3,8 @@ import type {
NavBarConfig,
ProfileConfig,
SiteConfig,
} from './types/config.ts'
import { LinkPreset } from './types/config.ts'
} from './types/config'
import { LinkPreset } from './types/config'
export const siteConfig: SiteConfig = {
title: 'Fuwari',
@ -12,7 +12,7 @@ export const siteConfig: SiteConfig = {
lang: 'en',
themeHue: 250,
banner: {
enable: true,
enable: false,
src: 'assets/images/demo-banner.png',
},
}

View File

@ -1 +1,3 @@
export const UNCATEGORIZED = '__uncategorized__'
export const PAGE_SIZE = 8

View File

@ -0,0 +1,18 @@
import { LinkPreset, type NavBarLink } from '@/types/config'
import I18nKey from '@i18n/i18nKey'
import { i18n } from '@i18n/translation'
export const LinkPresets: { [key in LinkPreset]: NavBarLink } = {
[LinkPreset.Home]: {
name: i18n(I18nKey.home),
url: '/',
},
[LinkPreset.About]: {
name: i18n(I18nKey.about),
url: '/about',
},
[LinkPreset.Archive]: {
name: i18n(I18nKey.archive),
url: '/archive',
},
}

4
src/files.d.ts vendored
View File

@ -1,4 +0,0 @@
declare module '*.yml' {
const value: unknown
export default value
}

View File

@ -20,7 +20,7 @@ enum I18nKey {
postCount = 'postCount',
postsCount = 'postsCount',
primaryColor = 'primaryColor',
themeColor = 'themeColor',
more = 'more',

View File

@ -1,5 +1,5 @@
import Key from '../i18nKey.ts'
import type { Translation } from '../translation.ts'
import Key from '../i18nKey'
import type { Translation } from '../translation'
export const en: Translation = {
[Key.home]: 'Home',
@ -23,7 +23,7 @@ export const en: Translation = {
[Key.postCount]: 'post',
[Key.postsCount]: 'posts',
[Key.primaryColor]: 'Primary Color',
[Key.themeColor]: 'Theme Color',
[Key.more]: 'More',

View File

@ -1,5 +1,5 @@
import Key from '../i18nKey.ts'
import type { Translation } from '../translation.ts'
import Key from '../i18nKey'
import type { Translation } from '../translation'
export const ja: Translation = {
[Key.home]: 'Home',
@ -23,7 +23,7 @@ export const ja: Translation = {
[Key.postCount]: '件の投稿',
[Key.postsCount]: '件の投稿',
[Key.primaryColor]: '原色',
[Key.themeColor]: 'テーマカラー',
[Key.more]: 'もっと',

View File

@ -1,5 +1,5 @@
import Key from '../i18nKey.ts'
import type { Translation } from '../translation.ts'
import Key from '../i18nKey'
import type { Translation } from '../translation'
export const zh_CN: Translation = {
[Key.home]: '主页',
@ -23,7 +23,7 @@ export const zh_CN: Translation = {
[Key.postCount]: '篇文章',
[Key.postsCount]: '篇文章',
[Key.primaryColor]: '主题色',
[Key.themeColor]: '主题色',
[Key.more]: '更多',

View File

@ -1,5 +1,5 @@
import Key from '../i18nKey.ts'
import type { Translation } from '../translation.ts'
import Key from '../i18nKey'
import type { Translation } from '../translation'
export const zh_TW: Translation = {
[Key.home]: '首頁',
@ -23,7 +23,7 @@ export const zh_TW: Translation = {
[Key.postCount]: '篇文章',
[Key.postsCount]: '篇文章',
[Key.primaryColor]: '主題色',
[Key.themeColor]: '主題色',
[Key.more]: '更多',

View File

@ -1,9 +1,9 @@
import { siteConfig } from '../config.ts'
import type I18nKey from './i18nKey.ts'
import { en } from './languages/en.ts'
import { ja } from './languages/ja.ts'
import { zh_CN } from './languages/zh_CN.ts'
import { zh_TW } from './languages/zh_TW.ts'
import { siteConfig } from '../config'
import type I18nKey from './i18nKey'
import { en } from './languages/en'
import { ja } from './languages/ja'
import { zh_CN } from './languages/zh_CN'
import { zh_TW } from './languages/zh_TW'
export type Translation = {
[K in I18nKey]: string
@ -23,8 +23,7 @@ const map: { [key: string]: Translation } = {
}
export function getTranslation(lang: string): Translation {
lang = lang.toLowerCase()
return map[lang] || defaultTranslation
return map[lang.toLowerCase()] || defaultTranslation
}
export function i18n(key: I18nKey): string {

View File

@ -4,7 +4,7 @@ import '@fontsource/roboto/400.css';
import '@fontsource/roboto/500.css';
import '@fontsource/roboto/700.css';
import { ViewTransitions } from 'astro:transitions';
import ImageBox from "@components/misc/ImageBox.astro";
import ImageWrapper from "@components/misc/ImageWrapper.astro";
import { fade } from 'astro:transitions';
import {pathsEqual} from "@utils/url-utils";
@ -95,10 +95,10 @@ if (title) {
class:list={{'banner-home': isHomePage, 'banner-else': !isHomePage}}
>
<ImageBox id="boxtest" alt="Banner image of the blog" class:list={["object-center object-cover h-full", {"hidden": !siteConfig.banner.enable}]}
<ImageWrapper id="boxtest" alt="Banner image of the blog" class:list={["object-center object-cover h-full", {"hidden": !siteConfig.banner.enable}]}
src={siteConfig.banner.src} transition:animate="fade"
>
</ImageBox>
</ImageWrapper>
</div>
<slot />
</GlobalStyles>
@ -107,20 +107,7 @@ if (title) {
<style is:global>
:root {
--hue: var(--configHue);
--accent: 136, 58, 234;
--accent-light: 224, 204, 250;
--accent-dark: 49, 10, 101;
--accent-gradient: linear-gradient(45deg, rgb(var(--accent)), rgb(var(--accent-light)) 30%, white 60%);
--page-width: 1200px;
}
html {
background: #13151A;
background-size: 224px;
}
code {
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
Bitstream Vera Sans Mono, Courier New, monospace;
--page-width: 75rem;
}
</style>
<style>
@ -163,6 +150,7 @@ import {
SizeObserverPlugin,
ClickScrollPlugin
} from 'overlayscrollbars';
import {getHue, setHue} from "../utils/setting-utils";
/* Preload fonts */
// (async function() {
@ -206,51 +194,23 @@ function disableAnimation() {
}
}
function activateDisplaySettings() {
let output = document.getElementById("hueValue");
let slider = document.getElementById("colorSlider");
let configCarrier = document.getElementById("config-carrier");
output.innerHTML = slider.value; // Display the default slider value
let r = document.querySelector(':root');
function setHue(hue) {
localStorage.setItem('hue', hue);
output.innerHTML = hue;
slider.value = hue;
r.style.setProperty(`--hue`, hue);
}
let storedHue = localStorage.getItem('hue');
if (storedHue) {
setHue(storedHue);
} else {
setHue(configCarrier.dataset.hue);
}
slider.oninput = function() {
let hue = this.value;
output.innerHTML = this.value;
setHue(hue);
}
function setClickOutsideToClose(panel: string, ignores: string[]) {
document.addEventListener("click", event => {
let panelDom = document.getElementById(panel);
let tDom = event.target;
for (let ig of ignores) {
let ie = document.getElementById(ig)
if (ie == tDom || ie.contains(tDom)) {
return;
}
function setClickOutsideToClose(panel: string, ignores: string[]) {
document.addEventListener("click", event => {
let panelDom = document.getElementById(panel);
let tDom = event.target;
for (let ig of ignores) {
let ie = document.getElementById(ig)
if (ie == tDom || (ie?.contains(tDom))) {
return;
}
panelDom.classList.add("closed");
});
}
setClickOutsideToClose("display-setting", ["display-setting", "display-settings-switch"])
setClickOutsideToClose("nav-menu-panel", ["nav-menu-panel", "nav-menu-switch"])
setClickOutsideToClose("search-panel", ["search-panel", "search-bar", "search-switch"])
}
panelDom.classList.add("closed");
});
}
setClickOutsideToClose("display-setting", ["display-setting", "display-settings-switch"])
setClickOutsideToClose("nav-menu-panel", ["nav-menu-panel", "nav-menu-switch"])
setClickOutsideToClose("search-panel", ["search-panel", "search-bar", "search-switch"])
function loadTheme() {
if (localStorage.theme === 'dark' || (!('theme' in localStorage) &&
@ -264,10 +224,7 @@ function loadTheme() {
}
function loadHue() {
const hue = localStorage.hue;
if (hue) {
document.documentElement.style.setProperty('--hue', hue);
}
setHue(getHue())
}
function setBannerHeight() {
@ -315,7 +272,6 @@ function init() {
setBannerHeight();
loadTheme();
loadHue();
activateDisplaySettings();
initCustomScrollbar();
}

View File

@ -5,7 +5,6 @@ import SideBar from "@components/widget/SideBar.astro";
import {pathsEqual} from "@utils/url-utils";
import Footer from "@components/Footer.astro";
import BackToTop from "@components/control/BackToTop.astro";
import DisplaySetting from "@components/widget/DisplaySetting.astro";
import {siteConfig} from "@/config";
interface Props {
@ -13,17 +12,15 @@ interface Props {
banner?: string;
}
const { title, banner } = Astro.props;
const isHomePage = pathsEqual(Astro.url.pathname, '/') || pathsEqual(Astro.url.pathname, '/page/1');
const enableBanner = siteConfig.banner.enable;
const { title, banner } = Astro.props
const isHomePage = pathsEqual(Astro.url.pathname, '/') || pathsEqual(Astro.url.pathname, '/page/1')
const enableBanner = siteConfig.banner.enable
---
<Layout title={title} banner={banner}>
<div class=`max-w-[var(--page-width)] min-h-screen grid grid-cols-[280px_auto] grid-rows-[auto_auto_1fr_auto] lg:grid-rows-[auto_1fr_auto]
mx-auto gap-4 relative px-0 md:px-4`
<div class="max-w-[var(--page-width)] min-h-screen grid grid-cols-[17.5rem_auto] grid-rows-[auto_auto_1fr_auto] lg:grid-rows-[auto_1fr_auto]
mx-auto gap-4 relative px-0 md:px-4"
transition:animate="none"
>
<div id="top-row" class="col-span-2 grid-rows-1 z-50" class:list={["transition-all", {
@ -32,7 +29,7 @@ const enableBanner = siteConfig.banner.enable;
>
<Navbar transition:animate="fade" transition:persist></Navbar>
</div>
<SideBar class="row-start-3 row-end-4 col-span-2 lg:row-start-2 lg:row-end-3 lg:col-span-1 lg:max-w-[280px] " transition:persist></SideBar>
<SideBar class="row-start-3 row-end-4 col-span-2 lg:row-start-2 lg:row-end-3 lg:col-span-1 lg:max-w-[17.5rem]" transition:persist></SideBar>
<div class="row-start-2 row-end-3 col-span-2 lg:col-span-1 overflow-hidden" transition:animate="slide">
<!-- the overflow-hidden here prevent long text break the layout-->

View File

@ -5,7 +5,6 @@ import ArchivePanel from "@components/ArchivePanel.astro";
import {i18n} from "@i18n/translation";
import I18nKey from "@i18n/i18nKey";
export async function getStaticPaths() {
const categories = await getCategoryList();
return categories.map(category => {

View File

@ -1,3 +0,0 @@
---
---

View File

@ -1,10 +1,9 @@
---
import { getCollection } from 'astro:content';
import MainGridLayout from "@layouts/MainGridLayout.astro";
import ImageBox from "@components/misc/ImageBox.astro";
import ImageWrapper from "../../components/misc/ImageWrapper.astro";
import {Icon} from "astro-icon/components";
import PostMetadata from "@components/PostMetadata.astro";
import Button from "@components/control/Button.astro";
import PostMetadata from "../../components/PostMeta.astro";
import {i18n} from "@i18n/translation";
import I18nKey from "@i18n/i18nKey";
import {getDir, getPostUrlBySlug} from "@utils/url-utils";
@ -74,7 +73,7 @@ const { remarkPluginFrontmatter } = await entry.render();
<!-- always show cover as long as it has one -->
{entry.data.image &&
<ImageBox src={entry.data.image} basePath={path.join("content/posts/", getDir(entry.id))} class="mb-8 rounded-xl"/>
<ImageWrapper src={entry.data.image} basePath={path.join("content/posts/", getDir(entry.id))} class="mb-8 rounded-xl"/>
}
{!entry.data.image && <div class="border-[var(--line-divider)] border-dashed border-b-[1px] mb-5"></div>}
@ -90,21 +89,21 @@ const { remarkPluginFrontmatter } = await entry.render();
<div class="flex flex-col md:flex-row justify-between mb-4 gap-4 overflow-hidden w-full">
<a href={getPostUrlBySlug(entry.data.nextSlug)} class="w-full font-bold overflow-hidden active:scale-95">
{entry.data.nextSlug && <Button class="w-full max-w-full h-10 px-4 rounded-2xl flex items-center justify-start gap-4" card height="3.75rem">
{entry.data.nextSlug && <div class="btn-card rounded-2xl w-full h-[3.75rem] max-w-full px-4 flex items-center justify-start gap-4" >
<Icon name="material-symbols:chevron-left-rounded" size={32} class="text-[var(--primary)]" />
<div class="overflow-hidden transition overflow-ellipsis whitespace-nowrap max-w-[calc(100%_-_3rem)] text-black/75 dark:text-white/75">
{entry.data.nextTitle}
</div>
</Button>}
</div>}
</a>
<a href={getPostUrlBySlug(entry.data.prevSlug)} class="w-full font-bold overflow-hidden active:scale-95">
{entry.data.prevSlug && <Button class="w-full max-w-full h-10 px-4 rounded-2xl flex items-center justify-end gap-4" card height="3.75rem">
{entry.data.prevSlug && <div class="btn-card rounded-2xl w-full h-[3.75rem] max-w-full px-4 flex items-center justify-end gap-4">
<div class="overflow-hidden transition overflow-ellipsis whitespace-nowrap max-w-[calc(100%_-_3rem)] text-black/75 dark:text-white/75">
{entry.data.prevTitle}
</div>
<Icon name="material-symbols:chevron-right-rounded" size={32} class="text-[var(--primary)]" />
</Button>}
</div>}
</a>
</div>

View File

@ -1,11 +1,10 @@
import I18nKey from '@i18n/i18nKey'
import { i18n } from '@i18n/translation'
import { getCollection } from 'astro:content'
import {UNCATEGORIZED} from "@constants/constants.ts";
import {i18n} from "@i18n/translation.ts";
import I18nKey from "@i18n/i18nKey.ts";
export async function getSortedPosts() {
const allBlogPosts = await getCollection('posts', ({ data }) => {
return import.meta.env.PROD ? data.draft !== true : true;
return import.meta.env.PROD ? data.draft !== true : true
})
const sorted = allBlogPosts.sort((a, b) => {
const dateA = new Date(a.data.published)
@ -32,7 +31,7 @@ export type Tag = {
export async function getTagList(): Promise<Tag[]> {
const allBlogPosts = await getCollection('posts', ({ data }) => {
return import.meta.env.PROD ? data.draft !== true : true;
return import.meta.env.PROD ? data.draft !== true : true
})
const countMap: { [key: string]: number } = {}
@ -58,16 +57,18 @@ export type Category = {
export async function getCategoryList(): Promise<Category[]> {
const allBlogPosts = await getCollection('posts', ({ data }) => {
return import.meta.env.PROD ? data.draft !== true : true;
return import.meta.env.PROD ? data.draft !== true : true
})
const count: { [key: string]: number } = {}
allBlogPosts.map(post => {
if (!post.data.category) {
const ucKey = i18n(I18nKey.uncategorized);
const ucKey = i18n(I18nKey.uncategorized)
count[ucKey] = count[ucKey] ? count[ucKey] + 1 : 1
return
}
count[post.data.category] = count[post.data.category] ? count[post.data.category] + 1 : 1
count[post.data.category] = count[post.data.category]
? count[post.data.category] + 1
: 1
})
const lst = Object.keys(count).sort((a, b) => {

View File

@ -0,0 +1,19 @@
export function getDefaultHue(): number {
const fallback = '250'
const configCarrier = document.getElementById('config-carrier')
return parseInt(configCarrier?.dataset.hue || fallback)
}
export function getHue(): number {
const stored = localStorage.getItem('hue')
return stored ? parseInt(stored) : getDefaultHue()
}
export function setHue(hue: number): void {
localStorage.setItem('hue', String(hue))
const r = document.querySelector(':root')
if (!r) {
return
}
r.style.setProperty('--hue', hue)
}

View File

@ -1,5 +1,5 @@
import {i18n} from "@i18n/translation.ts";
import i18nKey from "@i18n/i18nKey.ts";
import i18nKey from '@i18n/i18nKey'
import { i18n } from '@i18n/translation'
export function pathsEqual(path1: string, path2: string) {
const normalizedPath1 = path1.replace(/^\/|\/$/g, '').toLowerCase()
@ -18,17 +18,16 @@ export function getPostUrlBySlug(slug: string): string | null {
}
export function getCategoryUrl(category: string): string | null {
if (!category)
return null
if (!category) return null
if (category === i18n(i18nKey.uncategorized))
return '/archive/category/uncategorized'
return '/archive/category/uncategorized'
return `/archive/category/${category}`
}
export function getDir(path: string): string {
const lastSlashIndex = path.lastIndexOf('/')
if (lastSlashIndex < 0) {
return '/'
}
return path.substring(0, lastSlashIndex + 1)
}
const lastSlashIndex = path.lastIndexOf('/')
if (lastSlashIndex < 0) {
return '/'
}
return path.substring(0, lastSlashIndex + 1)
}