refactor: improve code quality

This commit is contained in:
saicaca 2024-07-21 15:47:49 +08:00
parent 743d8dd9c2
commit 003c644146
20 changed files with 76 additions and 69 deletions

View File

@ -6,9 +6,9 @@ import I18nKey from "../i18n/i18nKey";
import {UNCATEGORIZED} from "@constants/constants"; import {UNCATEGORIZED} from "@constants/constants";
interface Props { interface Props {
keyword: string; keyword?: string;
tags: string[]; tags?: string[];
categories: string[]; categories?: string[];
} }
const { keyword, tags, categories} = Astro.props; const { keyword, tags, categories} = Astro.props;
@ -27,8 +27,8 @@ if (Array.isArray(categories) && categories.length > 0) {
); );
} }
const groups = function () { const groups: {year: number, posts: typeof posts}[] = function () {
const groupedPosts = posts.reduce((grouped, post) => { const groupedPosts = posts.reduce((grouped: {[year: number]: typeof posts}, post) => {
const year = post.data.published.getFullYear() const year = post.data.published.getFullYear()
if (!grouped[year]) { if (!grouped[year]) {
grouped[year] = [] grouped[year] = []
@ -39,8 +39,8 @@ const groups = function () {
// convert the object to an array // convert the object to an array
const groupedPostsArray = Object.keys(groupedPosts).map(key => ({ const groupedPostsArray = Object.keys(groupedPosts).map(key => ({
year: key, year: parseInt(key),
posts: groupedPosts[key] posts: groupedPosts[parseInt(key)]
})) }))
// sort years by latest first // sort years by latest first

View File

@ -1,7 +1,7 @@
--- ---
import { Icon } from 'astro-icon/components'; import { Icon } from 'astro-icon/components';
import DisplaySettings from "./widget/DisplaySettings.svelte"; import DisplaySettings from "./widget/DisplaySettings.svelte";
import {LinkPreset, NavBarLink} from "../types/config"; import {LinkPreset, type NavBarLink} from "../types/config";
import {navBarConfig, siteConfig} from "../config"; import {navBarConfig, siteConfig} from "../config";
import NavMenuPanel from "./widget/NavMenuPanel.astro"; import NavMenuPanel from "./widget/NavMenuPanel.astro";
import Search from "./Search.svelte"; import Search from "./Search.svelte";
@ -34,7 +34,7 @@ let links: NavBarLink[] = navBarConfig.links.map((item: NavBarLink | LinkPreset)
> >
<div class="flex items-center"> <div class="flex items-center">
{l.name} {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>} {l.external && <Icon size="0.875rem" 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> </div>
</a>; </a>;
})} })}
@ -80,22 +80,20 @@ function switchTheme() {
function loadButtonScript() { function loadButtonScript() {
let switchBtn = document.getElementById("scheme-switch"); let switchBtn = document.getElementById("scheme-switch");
switchBtn.addEventListener("click", function () { switchBtn!.addEventListener("click", function () {
switchTheme() switchTheme()
}); });
let settingBtn = document.getElementById("display-settings-switch"); let settingBtn = document.getElementById("display-settings-switch");
if (settingBtn) { settingBtn!.addEventListener("click", function () {
settingBtn.addEventListener("click", function () { let settingPanel = document.getElementById("display-setting");
let settingPanel = document.getElementById("display-setting"); settingPanel!.classList.toggle("float-panel-closed");
settingPanel.classList.toggle("float-panel-closed"); });
});
}
let menuBtn = document.getElementById("nav-menu-switch"); let menuBtn = document.getElementById("nav-menu-switch");
menuBtn.addEventListener("click", function () { menuBtn!.addEventListener("click", function () {
let menuPanel = document.getElementById("nav-menu-panel"); let menuPanel = document.getElementById("nav-menu-panel");
menuPanel.classList.toggle("float-panel-closed"); menuPanel!.classList.toggle("float-panel-closed");
}); });
} }

View File

@ -8,7 +8,7 @@ import I18nKey from "../i18n/i18nKey";
import {getDir} from "../utils/url-utils"; import {getDir} from "../utils/url-utils";
interface Props { interface Props {
class: string; class?: string;
entry: any; entry: any;
title: string; title: string;
url: string; url: string;
@ -17,11 +17,10 @@ interface Props {
category: string; category: string;
image: string; image: string;
description: string; description: string;
words: number;
draft: boolean; draft: boolean;
style: string; style: string;
} }
const { entry, title, url, published, tags, category, image, description, words, style } = Astro.props; const { entry, title, url, published, tags, category, image, description, style } = Astro.props;
const className = Astro.props.class; const className = Astro.props.class;
const hasCover = image !== undefined && image !== null && image !== ''; const hasCover = image !== undefined && image !== null && image !== '';

View File

@ -10,9 +10,9 @@ interface Props {
published: Date; published: Date;
tags: string[]; tags: string[];
category: string; category: string;
hideTagsForMobile: boolean; hideTagsForMobile?: boolean;
} }
const {published, tags, category, hideTagsForMobile} = Astro.props; const {published, tags, category, hideTagsForMobile = false} = Astro.props;
const className = Astro.props.class; const className = Astro.props.class;
--- ---

View File

@ -4,9 +4,9 @@ interface Props {
url?: string url?: string
label?: string label?: string
} }
const { badge, url, name } = Astro.props const { badge, url, label } = Astro.props
--- ---
<a href={url} aria-label={name}> <a href={url} aria-label={label}>
<button <button
class:list={` class:list={`
w-full w-full

View File

@ -59,7 +59,7 @@ const getPageUrl = (p: number) => {
--- ---
<div class:list={[className, "flex flex-row gap-3 justify-center"]} style={style}> <div class:list={[className, "flex flex-row gap-3 justify-center"]} style={style}>
<a href={url(page.url.prev)} aria-label={page.url.prev ? "Previous Page" : null} <a href={url(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", class:list={["btn-card overflow-hidden rounded-lg text-[var(--primary)] w-11 h-11",
{"disabled": page.url.prev == undefined} {"disabled": page.url.prev == undefined}
]} ]}
@ -81,7 +81,7 @@ const getPageUrl = (p: number) => {
>{p}</a> >{p}</a>
})} })}
</div> </div>
<a href={url(page.url.next)} aria-label={page.url.next ? "Next Page" : null} <a href={url(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", class:list={["btn-card overflow-hidden rounded-lg text-[var(--primary)] w-11 h-11",
{"disabled": page.url.next == undefined} {"disabled": page.url.next == undefined}
]} ]}

View File

@ -24,6 +24,9 @@ if (isLocal) {
const files = import.meta.glob<ImageMetadata>("../../**", { import: 'default' }); const files = import.meta.glob<ImageMetadata>("../../**", { import: 'default' });
let normalizedPath = path.normalize(path.join("../../", basePath, src)).replace(/\\/g, "/"); let normalizedPath = path.normalize(path.join("../../", basePath, src)).replace(/\\/g, "/");
img = await (files[normalizedPath])(); img = await (files[normalizedPath])();
if (!img) {
console.error(`No image found for path ${normalizedPath}`);
}
} }
const imageClass = 'w-full h-full object-cover'; const imageClass = 'w-full h-full object-cover';
@ -31,7 +34,7 @@ const imageStyle = `object-position: ${position}`
--- ---
<div class:list={[className, 'overflow-hidden relative']}> <div class:list={[className, 'overflow-hidden relative']}>
<div class="transition absolute inset-0 dark:bg-black/10 bg-opacity-50 pointer-events-none"></div> <div class="transition absolute inset-0 dark:bg-black/10 bg-opacity-50 pointer-events-none"></div>
{isLocal && <Image src={img} alt={alt || ""} class={imageClass} style={imageStyle}/>} {isLocal && img && <Image src={img} alt={alt || ""} class={imageClass} style={imageStyle}/>}
{!isLocal && <img src={isPublic ? url(src) : src} alt={alt || ""} class={imageClass} style={imageStyle}/>} {!isLocal && <img src={isPublic ? url(src) : src} alt={alt || ""} class={imageClass} style={imageStyle}/>}
</div> </div>

View File

@ -40,5 +40,5 @@ const postUrl = decodeURIComponent(Astro.url.toString());
<a href={licenseConf.url} target="_blank" class="link text-[var(--primary)] whitespace-nowrap">{licenseConf.name}</a> <a href={licenseConf.url} target="_blank" class="link text-[var(--primary)] whitespace-nowrap">{licenseConf.name}</a>
</div> </div>
</div> </div>
<Icon name="fa6-brands:creative-commons" class="transition absolute pointer-events-none right-6 top-1/2 -translate-y-1/2 text-black/5 dark:text-white/5" size="240"></Icon> <Icon name="fa6-brands:creative-commons" class="transition absolute pointer-events-none right-6 top-1/2 -translate-y-1/2 text-black/5 dark:text-white/5" size="15rem"></Icon>
</div> </div>

View File

@ -3,7 +3,7 @@ import WidgetLayout from "./WidgetLayout.astro";
import {i18n} from "../../i18n/translation"; import {i18n} from "../../i18n/translation";
import I18nKey from "../../i18n/i18nKey"; import I18nKey from "../../i18n/i18nKey";
import {Category, getCategoryList} from "../../utils/content-utils"; import {getCategoryList} from "../../utils/content-utils";
import {getCategoryUrl} from "../../utils/url-utils"; import {getCategoryUrl} from "../../utils/url-utils";
import ButtonLink from "../control/ButtonLink.astro"; import ButtonLink from "../control/ButtonLink.astro";
@ -29,7 +29,7 @@ const style = Astro.props.style
{categories.map((c) => {categories.map((c) =>
<ButtonLink <ButtonLink
url={getCategoryUrl(c.name)} url={getCategoryUrl(c.name)}
badge={c.count} badge={String(c.count)}
label=`View all posts in the ${c.name} category` label=`View all posts in the ${c.name} category`
> >
{c.name} {c.name}

View File

@ -1,5 +1,5 @@
--- ---
import {NavBarLink} from "../../types/config"; import {type NavBarLink} from "../../types/config";
import {Icon} from "astro-icon/components"; import {Icon} from "astro-icon/components";
import {url} from "../../utils/url-utils"; import {url} from "../../utils/url-utils";
@ -20,11 +20,11 @@ const links = Astro.props.links;
{link.name} {link.name}
</div> </div>
{!link.external && <Icon name="material-symbols:chevron-right-rounded" {!link.external && <Icon name="material-symbols:chevron-right-rounded"
class="transition text-[var(--primary)]" size="20" class="transition text-[var(--primary)]" size="1.25rem"
> >
</Icon>} </Icon>}
{link.external && <Icon name="fa6-solid:arrow-up-right-from-square" {link.external && <Icon name="fa6-solid:arrow-up-right-from-square"
class="transition text-black/25 dark:text-white/25 -translate-x-1" size="12" class="transition text-black/25 dark:text-white/25 -translate-x-1" size="0.75rem"
> >
</Icon>} </Icon>}
</a> </a>

View File

@ -16,7 +16,7 @@ const config = profileConfig;
class="transition opacity-0 scale-90 group-hover:scale-100 group-hover:opacity-100 text-white text-5xl"> class="transition opacity-0 scale-90 group-hover:scale-100 group-hover:opacity-100 text-white text-5xl">
</Icon> </Icon>
</div> </div>
<ImageWrapper src={config.avatar} alt="Profile Image of the Author" class="mx-auto lg:w-full h-full lg:mt-0 "></ImageWrapper> <ImageWrapper src={config.avatar || ""} alt="Profile Image of the Author" class="mx-auto lg:w-full h-full lg:mt-0 "></ImageWrapper>
</a> </a>
<div class="px-2"> <div class="px-2">
<div class="font-bold text-xl text-center mb-1 dark:text-neutral-50 transition">{config.name}</div> <div class="font-bold text-xl text-center mb-1 dark:text-neutral-50 transition">{config.name}</div>

View File

@ -21,7 +21,7 @@ const {
const className = Astro.props.class const className = Astro.props.class
--- ---
<widget-layout data-id={id} data-is-collapsed={isCollapsed} class={"pb-4 card-base " + className} style={style}> <widget-layout data-id={id} data-is-collapsed={String(isCollapsed)} class={"pb-4 card-base " + className} style={style}>
<div class="font-bold transition text-lg text-neutral-900 dark:text-neutral-100 relative ml-8 mt-4 mb-2 <div class="font-bold transition text-lg text-neutral-900 dark:text-neutral-100 relative ml-8 mt-4 mb-2
before:w-1 before:h-4 before:rounded-md before:bg-[var(--primary)] before:w-1 before:h-4 before:rounded-md before:bg-[var(--primary)]
before:absolute before:left-[-16px] before:top-[5.5px]">{name}</div> before:absolute before:left-[-16px] before:top-[5.5px]">{name}</div>
@ -31,7 +31,7 @@ const className = Astro.props.class
{isCollapsed && <div class="expand-btn px-4 -mb-2"> {isCollapsed && <div class="expand-btn px-4 -mb-2">
<button class="btn-plain rounded-lg w-full h-9"> <button class="btn-plain rounded-lg w-full h-9">
<div class="text-[var(--primary)] flex items-center justify-center gap-2 -translate-x-2"> <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)} <Icon name="material-symbols:more-horiz" size="1.75rem"></Icon> {i18n(I18nKey.more)}
</div> </div>
</button> </button>
</div>} </div>}
@ -48,15 +48,15 @@ const className = Astro.props.class
constructor() { constructor() {
super(); super();
if (this.dataset.isCollapsed === undefined || this.dataset.isCollapsed === false) if (this.dataset.isCollapsed !== "true")
return; return;
const id = this.dataset.id; const id = this.dataset.id;
const btn = this.querySelector('.expand-btn'); const btn = this.querySelector('.expand-btn');
const wrapper = this.querySelector(`#${id}`) const wrapper = this.querySelector(`#${id}`)
btn.addEventListener('click', () => { btn!.addEventListener('click', () => {
wrapper.classList.remove('collapsed'); wrapper!.classList.remove('collapsed');
btn.classList.add('hidden'); btn!.classList.add('hidden');
}) })
} }
} }

View File

@ -4,11 +4,17 @@ const postsCollection = defineCollection({
schema: z.object({ schema: z.object({
title: z.string(), title: z.string(),
published: z.date(), published: z.date(),
draft: z.boolean().optional(), draft: z.boolean().optional().default(false),
description: z.string().optional(), description: z.string().optional().default(""),
image: z.string().optional(), image: z.string().optional().default(""),
tags: z.array(z.string()).optional(), tags: z.array(z.string()).optional().default([]),
category: z.string().optional(), category: z.string().optional().default(""),
/* For internal use */
prevTitle: z.string().default(""),
prevSlug: z.string().default(""),
nextTitle: z.string().default(""),
nextSlug: z.string().default(""),
}), }),
}) })
export const collections = { export const collections = {

View File

@ -8,14 +8,14 @@ import ImageWrapper from "@components/misc/ImageWrapper.astro";
import {pathsEqual} from "@utils/url-utils"; import {pathsEqual} from "@utils/url-utils";
import ConfigCarrier from "@components/ConfigCarrier.astro"; import ConfigCarrier from "@components/ConfigCarrier.astro";
import {profileConfig, siteConfig} from "@/config"; import {profileConfig, siteConfig} from "@/config";
import {Favicon} from "../types/config"; import {type Favicon} from "../types/config";
import {defaultFavicons} from "../constants/icon"; import {defaultFavicons} from "../constants/icon";
import {LIGHT_MODE, DARK_MODE, AUTO_MODE, DEFAULT_THEME} from "../constants/constants"; import {LIGHT_MODE, DARK_MODE, AUTO_MODE, DEFAULT_THEME} from "../constants/constants";
import {url} from "../utils/url-utils"; import {url} from "../utils/url-utils";
interface Props { interface Props {
title: string; title?: string;
banner: string; banner?: string;
description?: string; description?: string;
} }
@ -23,8 +23,6 @@ let { title, banner, description } = Astro.props;
const isHomePage = pathsEqual(Astro.url.pathname, '/'); const isHomePage = pathsEqual(Astro.url.pathname, '/');
const testPathName = Astro.url.pathname;
const anim = { const anim = {
old: { old: {
name: 'fadeIn', name: 'fadeIn',
@ -73,7 +71,7 @@ const siteLang = siteConfig.lang.replace('_', '-')
--- ---
<!DOCTYPE html> <!DOCTYPE html>
<html lang={siteLang} isHome={isHomePage} pathname={testPathName} class="bg-[var(--page-bg)] transition text-[14px] md:text-[16px]"> <html lang={siteLang} data-isHome={String(isHomePage)} class="bg-[var(--page-bg)] transition text-[14px] md:text-[16px]">
<head> <head>
<title>{pageTitle}</title> <title>{pageTitle}</title>
@ -232,13 +230,14 @@ function setClickOutsideToClose(panel: string, ignores: string[]) {
document.addEventListener("click", event => { document.addEventListener("click", event => {
let panelDom = document.getElementById(panel); let panelDom = document.getElementById(panel);
let tDom = event.target; let tDom = event.target;
if (!(tDom instanceof Node)) return; // Ensure the event target is an HTML Node
for (let ig of ignores) { for (let ig of ignores) {
let ie = document.getElementById(ig) let ie = document.getElementById(ig)
if (ie == tDom || (ie?.contains(tDom))) { if (ie == tDom || (ie?.contains(tDom))) {
return; return;
} }
} }
panelDom.classList.add("float-panel-closed"); panelDom!.classList.add("float-panel-closed");
}); });
} }
setClickOutsideToClose("display-setting", ["display-setting", "display-settings-switch"]) setClickOutsideToClose("display-setting", ["display-setting", "display-settings-switch"])
@ -257,7 +256,8 @@ function loadHue() {
function setBannerHeight() { function setBannerHeight() {
const banner = document.getElementById('banner-wrapper'); const banner = document.getElementById('banner-wrapper');
if (document.documentElement.hasAttribute('isHome')) { if (!banner) return
if (document.documentElement.dataset.isHome === "true") {
banner.classList.remove('banner-else'); banner.classList.remove('banner-else');
banner.classList.add('banner-home'); banner.classList.add('banner-home');
} else { } else {
@ -267,11 +267,13 @@ function setBannerHeight() {
} }
function initCustomScrollbar() { function initCustomScrollbar() {
const bodyElement = document.querySelector('body');
if (!bodyElement) return;
OverlayScrollbars( OverlayScrollbars(
// docs say that a initialization to the body element would affect native functionality like window.scrollTo // docs say that a initialization to the body element would affect native functionality like window.scrollTo
// but just leave it here for now // but just leave it here for now
{ {
target: document.querySelector('body'), target: bodyElement,
cancel: { cancel: {
nativeScrollbarsOverlaid: true, // don't initialize the overlay scrollbar if there is a native one nativeScrollbarsOverlaid: true, // don't initialize the overlay scrollbar if there is a native one
} }

View File

@ -8,7 +8,7 @@ import BackToTop from "@components/control/BackToTop.astro";
import {siteConfig} from "@/config"; import {siteConfig} from "@/config";
interface Props { interface Props {
title: string; title?: string;
banner?: string; banner?: string;
description?: string; description?: string;
} }

View File

@ -16,7 +16,7 @@ export async function getStaticPaths() {
}); });
} }
const { category } = Astro.params; const category = Astro.params.category as string;
--- ---

View File

@ -5,7 +5,6 @@ import ArchivePanel from "@components/ArchivePanel.astro";
import {i18n} from "@i18n/translation"; import {i18n} from "@i18n/translation";
import I18nKey from "@i18n/i18nKey"; import I18nKey from "@i18n/i18nKey";
export async function getStaticPaths() { export async function getStaticPaths() {
let posts = await getSortedPosts() let posts = await getSortedPosts()
@ -25,7 +24,7 @@ export async function getStaticPaths() {
}); });
} }
const { tag } = Astro.params; const tag= Astro.params.tag as string;
--- ---

View File

@ -108,21 +108,23 @@ const jsonLd = {
</div> </div>
<div class="flex flex-col md:flex-row justify-between mb-4 gap-4 overflow-hidden w-full"> <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"> <a href={entry.data.nextSlug ? getPostUrlBySlug(entry.data.nextSlug) : "#"}
class:list={["w-full font-bold overflow-hidden active:scale-95", {"pointer-events-none": !entry.data.nextSlug}]}>
{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" > {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)]" /> <Icon name="material-symbols:chevron-left-rounded" size="2rem" 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"> <div class="overflow-hidden transition overflow-ellipsis whitespace-nowrap max-w-[calc(100%_-_3rem)] text-black/75 dark:text-white/75">
{entry.data.nextTitle} {entry.data.nextTitle}
</div> </div>
</div>} </div>}
</a> </a>
<a href={getPostUrlBySlug(entry.data.prevSlug)} class="w-full font-bold overflow-hidden active:scale-95"> <a href={entry.data.prevSlug ? getPostUrlBySlug(entry.data.prevSlug) : "#"}
class:list={["w-full font-bold overflow-hidden active:scale-95", {"pointer-events-none": !entry.data.prevSlug}]}>
{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"> {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"> <div class="overflow-hidden transition overflow-ellipsis whitespace-nowrap max-w-[calc(100%_-_3rem)] text-black/75 dark:text-white/75">
{entry.data.prevTitle} {entry.data.prevTitle}
</div> </div>
<Icon name="material-symbols:chevron-right-rounded" size={32} class="text-[var(--primary)]" /> <Icon name="material-symbols:chevron-right-rounded" size="2rem" class="text-[var(--primary)]" />
</div>} </div>}
</a> </a>
</div> </div>

View File

@ -14,11 +14,11 @@ export function getHue(): number {
export function setHue(hue: number): void { export function setHue(hue: number): void {
localStorage.setItem('hue', String(hue)) localStorage.setItem('hue', String(hue))
const r = document.querySelector(':root') const r = document.querySelector(':root') as HTMLElement
if (!r) { if (!r) {
return return
} }
r.style.setProperty('--hue', hue) r.style.setProperty('--hue', String(hue))
} }

View File

@ -12,13 +12,11 @@ function joinUrl(...parts: string[]): string {
return joined.replace(/\/+/g, '/'); return joined.replace(/\/+/g, '/');
} }
export function getPostUrlBySlug(slug: string): string | null { export function getPostUrlBySlug(slug: string): string {
if (!slug) return null
return url(`/posts/${slug}/`) return url(`/posts/${slug}/`)
} }
export function getCategoryUrl(category: string): string | null { export function getCategoryUrl(category: string): string {
if (!category) return null
if (category === i18n(i18nKey.uncategorized)) if (category === i18n(i18nKey.uncategorized))
return url('/archive/category/uncategorized/') return url('/archive/category/uncategorized/')
return url(`/archive/category/${category}/`) return url(`/archive/category/${category}/`)