fix: fix errors and reformat code

This commit is contained in:
saicaca 2024-08-03 16:40:20 +08:00
parent 1f93499ece
commit 0ad144add3
44 changed files with 550 additions and 558 deletions

View File

@ -8,7 +8,7 @@
"formatter": { "formatter": {
"enabled": true, "enabled": true,
"formatWithErrors": false, "formatWithErrors": false,
"ignore": [], "ignore": ["src/config.ts"],
"indentStyle": "space", "indentStyle": "space",
"indentWidth": 2, "indentWidth": 2,
"lineWidth": 80 "lineWidth": 80

View File

@ -1,63 +1,68 @@
--- ---
import {getSortedPosts} from "../utils/content-utils"; import { getSortedPosts } from '../utils/content-utils'
import {getPostUrlBySlug} from "../utils/url-utils"; import { getPostUrlBySlug } from '../utils/url-utils'
import {i18n} from "../i18n/translation"; import { i18n } from '../i18n/translation'
import I18nKey from "../i18n/i18nKey"; 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
let posts = await getSortedPosts() let posts = await getSortedPosts()
if (Array.isArray(tags) && tags.length > 0) { if (Array.isArray(tags) && tags.length > 0) {
posts = posts.filter(post => posts = posts.filter(
Array.isArray(post.data.tags) && post.data.tags.some(tag => tags.includes(tag)) post =>
); Array.isArray(post.data.tags) &&
post.data.tags.some(tag => tags.includes(tag)),
)
} }
if (Array.isArray(categories) && categories.length > 0) { if (Array.isArray(categories) && categories.length > 0) {
posts = posts.filter(post => posts = posts.filter(
(post.data.category && categories.includes(post.data.category)) || post =>
(!post.data.category && categories.includes(UNCATEGORIZED)) (post.data.category && categories.includes(post.data.category)) ||
); (!post.data.category && categories.includes(UNCATEGORIZED)),
)
} }
const groups: {year: number, posts: typeof posts}[] = function () { const groups: { year: number; posts: typeof posts }[] = (function () {
const groupedPosts = posts.reduce((grouped: {[year: number]: typeof posts}, post) => { const groupedPosts = posts.reduce(
const year = post.data.published.getFullYear() (grouped: { [year: number]: typeof posts }, post) => {
if (!grouped[year]) { const year = post.data.published.getFullYear()
grouped[year] = [] if (!grouped[year]) {
} grouped[year] = []
grouped[year].push(post) }
return grouped grouped[year].push(post)
}, {}) return grouped
},
{},
)
// 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: parseInt(key), year: parseInt(key),
posts: groupedPosts[parseInt(key)] posts: groupedPosts[parseInt(key)],
})) }))
// sort years by latest first // sort years by latest first
groupedPostsArray.sort((a, b) => b.year - a.year) groupedPostsArray.sort((a, b) => b.year - a.year)
return groupedPostsArray; return groupedPostsArray
}(); })()
function formatDate(date: Date) { function formatDate(date: Date) {
const month = (date.getMonth() + 1).toString().padStart(2, '0'); const month = (date.getMonth() + 1).toString().padStart(2, '0')
const day = date.getDate().toString().padStart(2, '0'); const day = date.getDate().toString().padStart(2, '0')
return `${month}-${day}`; return `${month}-${day}`
} }
function formatTag(tag: string[]) { function formatTag(tag: string[]) {
return tag.map(t => `#${t}`).join(' '); return tag.map(t => `#${t}`).join(' ')
} }
--- ---
<div class="card-base px-8 py-6"> <div class="card-base px-8 py-6">

View File

@ -1,7 +1,6 @@
--- ---
import {siteConfig} from "../config"; import { siteConfig } from '../config'
--- ---
<div id="config-carrier" data-hue={siteConfig.themeColor.hue}> <div id="config-carrier" data-hue={siteConfig.themeColor.hue}>

View File

@ -1,8 +1,7 @@
--- ---
import {profileConfig} from "../config"; import { profileConfig } from '../config'
import {url} from "../utils/url-utils"; import { url } from '../utils/url-utils'
--- ---
<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="card-base max-w-[var(--page-width)] min-h-[4.5rem] rounded-b-none mx-auto flex items-center px-6">

View File

@ -1,10 +1,6 @@
<script lang="ts"> <script lang="ts">
import type { LIGHT_DARK_MODE } from '@/types/config.ts' import type { LIGHT_DARK_MODE } from '@/types/config.ts'
import { import { AUTO_MODE, DARK_MODE, LIGHT_MODE } from '@constants/constants.ts'
AUTO_MODE,
DARK_MODE,
LIGHT_MODE,
} from '@constants/constants.ts'
import I18nKey from '@i18n/i18nKey' import I18nKey from '@i18n/i18nKey'
import { i18n } from '@i18n/translation' import { i18n } from '@i18n/translation'
import Icon from '@iconify/svelte' import Icon from '@iconify/svelte'
@ -15,27 +11,18 @@ import {
} from '@utils/setting-utils.ts' } from '@utils/setting-utils.ts'
import { onMount } from 'svelte' import { onMount } from 'svelte'
const seq: LIGHT_DARK_MODE[] = [ const seq: LIGHT_DARK_MODE[] = [LIGHT_MODE, DARK_MODE, AUTO_MODE]
LIGHT_MODE,
DARK_MODE,
AUTO_MODE,
]
let mode: LIGHT_DARK_MODE = AUTO_MODE let mode: LIGHT_DARK_MODE = AUTO_MODE
onMount(() => { onMount(() => {
mode = getStoredTheme() mode = getStoredTheme()
const darkModePreference = window.matchMedia( const darkModePreference = window.matchMedia('(prefers-color-scheme: dark)')
'(prefers-color-scheme: dark)',
)
const changeThemeWhenSchemeChanged: Parameters< const changeThemeWhenSchemeChanged: Parameters<
typeof darkModePreference.addEventListener<'change'> typeof darkModePreference.addEventListener<'change'>
>[1] = e => { >[1] = e => {
applyThemeToDocument(mode) applyThemeToDocument(mode)
} }
darkModePreference.addEventListener( darkModePreference.addEventListener('change', changeThemeWhenSchemeChanged)
'change',
changeThemeWhenSchemeChanged,
)
return () => { return () => {
darkModePreference.removeEventListener( darkModePreference.removeEventListener(
'change', 'change',
@ -50,25 +37,24 @@ function switchScheme(newMode: LIGHT_DARK_MODE) {
} }
function toggleScheme() { function toggleScheme() {
let i = 0 let i = 0
for (; i < seq.length; i++) { for (; i < seq.length; i++) {
if (seq[i] === mode) { if (seq[i] === mode) {
break break
}
} }
switchScheme(seq[(i + 1) % seq.length]) }
switchScheme(seq[(i + 1) % seq.length])
} }
function showPanel() { function showPanel() {
const panel = document.querySelector('#light-dark-panel') const panel = document.querySelector('#light-dark-panel')
panel.classList.remove('float-panel-closed') panel.classList.remove('float-panel-closed')
} }
function hidePanel() { function hidePanel() {
const panel = document.querySelector('#light-dark-panel') const panel = document.querySelector('#light-dark-panel')
panel.classList.add('float-panel-closed') panel.classList.add('float-panel-closed')
} }
</script> </script>
<!-- z-50 make the panel higher than other float panels --> <!-- z-50 make the panel higher than other float panels -->

View File

@ -1,22 +1,23 @@
--- ---
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, type 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'
import {LinkPresets} from "../constants/link-presets"; import { LinkPresets } from '../constants/link-presets'
import LightDarkSwitch from "./LightDarkSwitch.svelte"; import LightDarkSwitch from './LightDarkSwitch.svelte'
import {url} from "../utils/url-utils"; import { url } from '../utils/url-utils'
const className = Astro.props.class; const className = Astro.props.class
let links: NavBarLink[] = navBarConfig.links.map((item: NavBarLink | LinkPreset): NavBarLink => { let links: NavBarLink[] = navBarConfig.links.map(
if (typeof item === "number") { (item: NavBarLink | LinkPreset): NavBarLink => {
return LinkPresets[item] if (typeof item === 'number') {
return LinkPresets[item]
} }
return item; return item
}); },
)
--- ---
<div id="navbar" class="sticky top-0 z-50 onload-animation"> <div id="navbar" class="sticky top-0 z-50 onload-animation">
<div class="absolute h-8 left-0 right-0 -top-8 bg-[var(--card-bg)] transition"></div> <!-- used for onload animation --> <div class="absolute h-8 left-0 right-0 -top-8 bg-[var(--card-bg)] transition"></div> <!-- used for onload animation -->

View File

@ -1,34 +1,43 @@
--- ---
import path from "path"; import path from 'path'
import PostMetadata from "./PostMeta.astro"; import PostMetadata from './PostMeta.astro'
import ImageWrapper from "./misc/ImageWrapper.astro"; import ImageWrapper from './misc/ImageWrapper.astro'
import { Icon } from 'astro-icon/components'; import { Icon } from 'astro-icon/components'
import {i18n} from "../i18n/translation"; import { i18n } from '../i18n/translation'
import I18nKey from "../i18n/i18nKey"; 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
published: Date; published: Date
tags: string[]; tags: string[]
category: string; category: string
image: string; image: string
description: string; description: string
draft: boolean; draft: boolean
style: string; style: string
} }
const { entry, title, url, published, tags, category, image, description, style } = Astro.props; const {
const className = Astro.props.class; entry,
title,
url,
published,
tags,
category,
image,
description,
style,
} = Astro.props
const className = Astro.props.class
const hasCover = image !== undefined && image !== null && image !== ''; const hasCover = image !== undefined && image !== null && image !== ''
const coverWidth = "28%"; const coverWidth = '28%'
const { remarkPluginFrontmatter } = await entry.render();
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]} style={style}> <div class:list={["card-base flex flex-col-reverse md:flex-col w-full rounded-[var(--radius-large)] overflow-hidden relative", className]} style={style}>
<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}]}> <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}]}>

View File

@ -1,19 +1,19 @@
--- ---
import {formatDateToYYYYMMDD} from "../utils/date-utils"; import { formatDateToYYYYMMDD } from '../utils/date-utils'
import { Icon } from 'astro-icon/components'; import { Icon } from 'astro-icon/components'
import {i18n} from "../i18n/translation"; import { i18n } from '../i18n/translation'
import I18nKey from "../i18n/i18nKey"; import I18nKey from '../i18n/i18nKey'
import {url} from "../utils/url-utils"; import { url } from '../utils/url-utils'
interface Props { interface Props {
class: string; class: string
published: Date; published: Date
tags: string[]; tags: string[]
category: string; category: string
hideTagsForMobile?: boolean; hideTagsForMobile?: boolean
} }
const {published, tags, category, hideTagsForMobile = false} = Astro.props; const { published, tags, category, hideTagsForMobile = false } = Astro.props
const className = Astro.props.class; const className = Astro.props.class
--- ---
<div class:list={["flex flex-wrap text-neutral-500 dark:text-neutral-400 items-center gap-4 gap-x-4 gap-y-2", className]}> <div class:list={["flex flex-wrap text-neutral-500 dark:text-neutral-400 items-center gap-4 gap-x-4 gap-y-2", className]}>

View File

@ -1,8 +1,8 @@
--- ---
import {getPostUrlBySlug} from "@utils/url-utils"; import { getPostUrlBySlug } from '@utils/url-utils'
import PostCard from "./PostCard.astro"; import PostCard from './PostCard.astro'
const {page} = Astro.props; const { page } = Astro.props
let delay = 0 let delay = 0
const interval = 50 const interval = 50

View File

@ -1,65 +1,68 @@
<script lang="ts"> <script lang="ts">
import { onMount } from 'svelte' import { onMount } from 'svelte'
import {url} from "@utils/url-utils.ts" import { url } from '@utils/url-utils.ts'
import { i18n } from '@i18n/translation'; import { i18n } from '@i18n/translation'
import I18nKey from '@i18n/i18nKey'; import I18nKey from '@i18n/i18nKey'
let keywordDesktop = '' let keywordDesktop = ''
let keywordMobile = '' let keywordMobile = ''
let result = [] let result = []
const fakeResult = [{ const fakeResult = [
{
url: url('/'), url: url('/'),
meta: { meta: {
title: 'This Is a Fake Search Result' title: 'This Is a Fake Search Result',
}, },
excerpt: 'Because the search cannot work in the <mark>dev</mark> environment.' excerpt:
}, { 'Because the search cannot work in the <mark>dev</mark> environment.',
},
{
url: url('/'), url: url('/'),
meta: { meta: {
title: 'If You Want to Test the Search' title: 'If You Want to Test the Search',
}, },
excerpt: 'Try running <mark>npm build && npm preview</mark> instead.' excerpt: 'Try running <mark>npm build && npm preview</mark> instead.',
}] },
]
let search = (keyword: string, isDesktop: boolean) => {} let search = (keyword: string, isDesktop: boolean) => {}
onMount(() => { onMount(() => {
search = async (keyword: string, isDesktop: boolean) => { search = async (keyword: string, isDesktop: boolean) => {
let panel = document.getElementById('search-panel') let panel = document.getElementById('search-panel')
if (!panel) if (!panel) return
return
if (!keyword && isDesktop) { if (!keyword && isDesktop) {
panel.classList.add("float-panel-closed") panel.classList.add('float-panel-closed')
return return
}
let arr = [];
if (import.meta.env.PROD) {
const ret = await pagefind.search(keyword)
for (const item of ret.results) {
arr.push(await item.data())
}
} else {
// Mock data for non-production environment
// arr = JSON.parse('[{"url":"/","content":"Simple Guides for Fuwari. Cover image source: Source. This blog template is built with Astro. For the things that are not mentioned in this guide, you may find the answers in the Astro Docs. Front-matter of Posts. --- title: My First Blog Post published: 2023-09-09 description: This is the first post of my new Astro blog. image: ./cover.jpg tags: [Foo, Bar] category: Front-end draft: false ---AttributeDescription title. The title of the post. published. The date the post was published. description. A short description of the post. Displayed on index page. image. The cover image path of the post. 1. Start with http:// or https://: Use web image 2. Start with /: For image in public dir 3. With none of the prefixes: Relative to the markdown file. tags. The tags of the post. category. The category of the post. draft. If this post is still a draft, which wont be displayed. Where to Place the Post Files. Your post files should be placed in src/content/posts/ directory. You can also create sub-directories to better organize your posts and assets. src/content/posts/ ├── post-1.md └── post-2/ ├── cover.png └── index.md.","word_count":187,"filters":{},"meta":{"title":"This Is a Fake Search Result"},"anchors":[{"element":"h2","id":"front-matter-of-posts","text":"Front-matter of Posts","location":34},{"element":"h2","id":"where-to-place-the-post-files","text":"Where to Place the Post Files","location":151}],"weighted_locations":[{"weight":10,"balanced_score":57600,"location":3}],"locations":[3],"raw_content":"Simple Guides for Fuwari. Cover image source: Source. This blog template is built with Astro. For the things that are not mentioned in this guide, you may find the answers in the Astro Docs. Front-matter of Posts. --- title: My First Blog Post published: 2023-09-09 description: This is the first post of my new Astro blog. image: ./cover.jpg tags: [Foo, Bar] category: Front-end draft: false ---AttributeDescription title. The title of the post. published. The date the post was published. description. A short description of the post. Displayed on index page. image. The cover image path of the post. 1. Start with http:// or https://: Use web image 2. Start with /: For image in public dir 3. With none of the prefixes: Relative to the markdown file. tags. The tags of the post. category. The category of the post. draft. If this post is still a draft, which wont be displayed. Where to Place the Post Files. Your post files should be placed in src/content/posts/ directory. You can also create sub-directories to better organize your posts and assets. src/content/posts/ ├── post-1.md └── post-2/ ├── cover.png └── index.md.","raw_url":"/posts/guide/","excerpt":"Because the search cannot work in the <mark>dev</mark> environment.","sub_results":[{"title":"Simple Guides for Fuwari - Fuwari","url":"/posts/guide/","weighted_locations":[{"weight":10,"balanced_score":57600,"location":3}],"locations":[3],"excerpt":"Simple Guides for <mark>Fuwari.</mark> Cover image source: Source. This blog template is built with Astro. For the things that are not mentioned in this guide, you may find the answers"}]},{"url":"/","content":"About. This is the demo site for Fuwari. Sources of images used in this site. Unsplash. 星と少女 by Stella. Rabbit - v1.4 Showcase by Rabbit_YourMajesty.","word_count":25,"filters":{},"meta":{"title":"If You Want to Test the Search"},"anchors":[{"element":"h1","id":"about","text":"About","location":0},{"element":"h3","id":"sources-of-images-used-in-this-site","text":"Sources of images used in this site","location":8}],"weighted_locations":[{"weight":1,"balanced_score":576,"location":7}],"locations":[7],"raw_content":"About. This is the demo site for Fuwari. Sources of images used in this site. Unsplash. 星と少女 by Stella. Rabbit - v1.4 Showcase by Rabbit_YourMajesty.","raw_url":"/about/","excerpt":"Try running <mark>npm build && npm preview</mark> instead.","sub_results":[{"title":"About","url":"/about/#about","anchor":{"element":"h1","id":"about","text":"About","location":0},"weighted_locations":[{"weight":1,"balanced_score":576,"location":7}],"locations":[7],"excerpt":"About. This is the demo site for <mark>Fuwari.</mark>"}]}]')
arr = fakeResult
}
if (!arr.length && isDesktop) {
panel.classList.add("float-panel-closed")
return
}
if (isDesktop) {
panel.classList.remove("float-panel-closed")
}
result = arr
} }
let arr = []
if (import.meta.env.PROD) {
const ret = await pagefind.search(keyword)
for (const item of ret.results) {
arr.push(await item.data())
}
} else {
// Mock data for non-production environment
// arr = JSON.parse('[{"url":"/","content":"Simple Guides for Fuwari. Cover image source: Source. This blog template is built with Astro. For the things that are not mentioned in this guide, you may find the answers in the Astro Docs. Front-matter of Posts. --- title: My First Blog Post published: 2023-09-09 description: This is the first post of my new Astro blog. image: ./cover.jpg tags: [Foo, Bar] category: Front-end draft: false ---AttributeDescription title. The title of the post. published. The date the post was published. description. A short description of the post. Displayed on index page. image. The cover image path of the post. 1. Start with http:// or https://: Use web image 2. Start with /: For image in public dir 3. With none of the prefixes: Relative to the markdown file. tags. The tags of the post. category. The category of the post. draft. If this post is still a draft, which wont be displayed. Where to Place the Post Files. Your post files should be placed in src/content/posts/ directory. You can also create sub-directories to better organize your posts and assets. src/content/posts/ ├── post-1.md └── post-2/ ├── cover.png └── index.md.","word_count":187,"filters":{},"meta":{"title":"This Is a Fake Search Result"},"anchors":[{"element":"h2","id":"front-matter-of-posts","text":"Front-matter of Posts","location":34},{"element":"h2","id":"where-to-place-the-post-files","text":"Where to Place the Post Files","location":151}],"weighted_locations":[{"weight":10,"balanced_score":57600,"location":3}],"locations":[3],"raw_content":"Simple Guides for Fuwari. Cover image source: Source. This blog template is built with Astro. For the things that are not mentioned in this guide, you may find the answers in the Astro Docs. Front-matter of Posts. --- title: My First Blog Post published: 2023-09-09 description: This is the first post of my new Astro blog. image: ./cover.jpg tags: [Foo, Bar] category: Front-end draft: false ---AttributeDescription title. The title of the post. published. The date the post was published. description. A short description of the post. Displayed on index page. image. The cover image path of the post. 1. Start with http:// or https://: Use web image 2. Start with /: For image in public dir 3. With none of the prefixes: Relative to the markdown file. tags. The tags of the post. category. The category of the post. draft. If this post is still a draft, which wont be displayed. Where to Place the Post Files. Your post files should be placed in src/content/posts/ directory. You can also create sub-directories to better organize your posts and assets. src/content/posts/ ├── post-1.md └── post-2/ ├── cover.png └── index.md.","raw_url":"/posts/guide/","excerpt":"Because the search cannot work in the <mark>dev</mark> environment.","sub_results":[{"title":"Simple Guides for Fuwari - Fuwari","url":"/posts/guide/","weighted_locations":[{"weight":10,"balanced_score":57600,"location":3}],"locations":[3],"excerpt":"Simple Guides for <mark>Fuwari.</mark> Cover image source: Source. This blog template is built with Astro. For the things that are not mentioned in this guide, you may find the answers"}]},{"url":"/","content":"About. This is the demo site for Fuwari. Sources of images used in this site. Unsplash. 星と少女 by Stella. Rabbit - v1.4 Showcase by Rabbit_YourMajesty.","word_count":25,"filters":{},"meta":{"title":"If You Want to Test the Search"},"anchors":[{"element":"h1","id":"about","text":"About","location":0},{"element":"h3","id":"sources-of-images-used-in-this-site","text":"Sources of images used in this site","location":8}],"weighted_locations":[{"weight":1,"balanced_score":576,"location":7}],"locations":[7],"raw_content":"About. This is the demo site for Fuwari. Sources of images used in this site. Unsplash. 星と少女 by Stella. Rabbit - v1.4 Showcase by Rabbit_YourMajesty.","raw_url":"/about/","excerpt":"Try running <mark>npm build && npm preview</mark> instead.","sub_results":[{"title":"About","url":"/about/#about","anchor":{"element":"h1","id":"about","text":"About","location":0},"weighted_locations":[{"weight":1,"balanced_score":576,"location":7}],"locations":[7],"excerpt":"About. This is the demo site for <mark>Fuwari.</mark>"}]}]')
arr = fakeResult
}
if (!arr.length && isDesktop) {
panel.classList.add('float-panel-closed')
return
}
if (isDesktop) {
panel.classList.remove('float-panel-closed')
}
result = arr
}
}) })
const togglePanel = () => { const togglePanel = () => {
let panel = document.getElementById('search-panel') let panel = document.getElementById('search-panel')
panel?.classList.toggle("float-panel-closed") panel?.classList.toggle('float-panel-closed')
} }
$: search(keywordDesktop, true) $: search(keywordDesktop, true)

View File

@ -1,5 +1,5 @@
--- ---
import { Icon } from 'astro-icon/components'; import { Icon } from 'astro-icon/components'
--- ---
<!-- There can't be a filter on parent element, or it will break `fixed` --> <!-- There can't be a filter on parent element, or it will break `fixed` -->

View File

@ -1,8 +1,8 @@
--- ---
interface Props { interface Props {
badge?: string badge?: string
url?: string url?: string
label?: string label?: string
} }
const { badge, url, label } = Astro.props const { badge, url, label } = Astro.props
--- ---

View File

@ -1,11 +1,11 @@
--- ---
interface Props { interface Props {
size?: string; size?: string
dot?: boolean; dot?: boolean
href?: string; href?: string
label?: string; label?: string
} }
const { size, dot, href, label }: Props = Astro.props; const { size, dot, href, label }: Props = Astro.props
--- ---
<a href={href} aria-label={label} class="btn-regular h-8 text-sm px-3 rounded-lg"> <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>} {dot && <div class="h-1 w-1 bg-[var(--btn-content)] dark:bg-[var(--card-bg)] transition rounded-md mr-2"></div>}

View File

@ -1,61 +1,53 @@
--- ---
import type { Page } from "astro"; import type { Page } from 'astro'
import { Icon } from 'astro-icon/components'; import { Icon } from 'astro-icon/components'
import {url} from "../../utils/url-utils"; import { url } from '../../utils/url-utils'
interface Props { interface Props {
page: Page; page: Page
class?: string; class?: string
style?: string; style?: string
} }
const {page, style} = Astro.props; const { page, style } = Astro.props
const HIDDEN = -1; const HIDDEN = -1
const className = Astro.props.class; const className = Astro.props.class
const ADJ_DIST = 2; const ADJ_DIST = 2
const VISIBLE = ADJ_DIST * 2 + 1; const VISIBLE = ADJ_DIST * 2 + 1
// for test // for test
let count = 1; let count = 1
let l = page.currentPage, r = page.currentPage; let l = page.currentPage,
r = page.currentPage
while (0 < l - 1 && r + 1 <= page.lastPage && count + 2 <= VISIBLE) { while (0 < l - 1 && r + 1 <= page.lastPage && count + 2 <= VISIBLE) {
count += 2; count += 2
l--; l--
r++; r++
} }
while (0 < l - 1 && count < VISIBLE) { while (0 < l - 1 && count < VISIBLE) {
count++; count++
l--; l--
} }
while (r + 1 <= page.lastPage && count < VISIBLE) { while (r + 1 <= page.lastPage && count < VISIBLE) {
count++; count++
r++; r++
} }
let pages: number[] = []; let pages: number[] = []
if (l > 1) if (l > 1) pages.push(1)
pages.push(1); if (l == 3) pages.push(2)
if (l == 3) if (l > 3) pages.push(HIDDEN)
pages.push(2); for (let i = l; i <= r; i++) pages.push(i)
if (l > 3) if (r < page.lastPage - 2) pages.push(HIDDEN)
pages.push(HIDDEN); if (r == page.lastPage - 2) pages.push(page.lastPage - 1)
for (let i = l; i <= r; i++) if (r < page.lastPage) pages.push(page.lastPage)
pages.push(i);
if (r < page.lastPage - 2)
pages.push(HIDDEN);
if (r == page.lastPage - 2)
pages.push(page.lastPage - 1);
if (r < page.lastPage)
pages.push(page.lastPage);
const getPageUrl = (p: number) => { const getPageUrl = (p: number) => {
if (p == 1) if (p == 1) return '/'
return '/'; return `/${p}/`
return `/${p}/`;
} }
--- ---
<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}>

View File

@ -1,35 +1,44 @@
--- ---
import path from "path"; import path from 'path'
interface Props { interface Props {
id?: string id?: string
src: string; src: string
class?: string; class?: string
alt?: string alt?: string
position?: string; position?: string
basePath?: string basePath?: string
} }
import { Image } from 'astro:assets'; import { Image } from 'astro:assets'
import { url } from "../../utils/url-utils"; import { url } from '../../utils/url-utils'
const {id, src, alt, position = 'center', basePath = '/'} = Astro.props; const { id, src, alt, position = 'center', basePath = '/' } = Astro.props
const className = Astro.props.class; const className = Astro.props.class
const isLocal = !(src.startsWith('/') || src.startsWith('http') || src.startsWith('https') || src.startsWith('data:')); const isLocal = !(
const isPublic = src.startsWith('/'); src.startsWith('/') ||
src.startsWith('http') ||
src.startsWith('https') ||
src.startsWith('data:')
)
const isPublic = src.startsWith('/')
// TODO temporary workaround for images dynamic import // TODO temporary workaround for images dynamic import
// https://github.com/withastro/astro/issues/3373 // https://github.com/withastro/astro/issues/3373
let img; let img
if (isLocal) { if (isLocal) {
const files = import.meta.glob<ImageMetadata>("../../**", { import: 'default' }); const files = import.meta.glob<ImageMetadata>('../../**', {
let normalizedPath = path.normalize(path.join("../../", basePath, src)).replace(/\\/g, "/"); import: 'default',
img = await (files[normalizedPath])(); })
if (!img) { let normalizedPath = path
console.error(`No image found for path ${normalizedPath}`); .normalize(path.join('../../', basePath, src))
} .replace(/\\/g, '/')
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'
const imageStyle = `object-position: ${position}` const imageStyle = `object-position: ${position}`
--- ---
<div id={id} class:list={[className, 'overflow-hidden relative']}> <div id={id} class:list={[className, 'overflow-hidden relative']}>

View File

@ -1,23 +1,22 @@
--- ---
import {formatDateToYYYYMMDD} from "../../utils/date-utils"; import { formatDateToYYYYMMDD } from '../../utils/date-utils'
import { Icon } from 'astro-icon/components'; import { Icon } from 'astro-icon/components'
import {licenseConfig, profileConfig} from "../../config"; import { licenseConfig, profileConfig } from '../../config'
import {i18n} from "../../i18n/translation"; import { i18n } from '../../i18n/translation'
import I18nKey from "../../i18n/i18nKey"; import I18nKey from '../../i18n/i18nKey'
interface Props { interface Props {
title: string; title: string
slug: string; slug: string
pubDate: Date; pubDate: Date
class: string; class: string
} }
const { title, slug, pubDate } = Astro.props; const { title, slug, pubDate } = Astro.props
const className = Astro.props.class; const className = Astro.props.class
const profileConf = profileConfig; const profileConf = profileConfig
const licenseConf = licenseConfig; const licenseConf = licenseConfig
const postUrl = decodeURIComponent(Astro.url.toString()); const postUrl = decodeURIComponent(Astro.url.toString())
--- ---
<div class=`relative transition overflow-hidden bg-[var(--license-block-bg)] py-5 px-6 ${className}`> <div class=`relative transition overflow-hidden bg-[var(--license-block-bg)] py-5 px-6 ${className}`>
<div class="transition font-bold text-black/75 dark:text-white/75"> <div class="transition font-bold text-black/75 dark:text-white/75">

View File

@ -1,11 +1,11 @@
--- ---
import '@fontsource-variable/jetbrains-mono'; import '@fontsource-variable/jetbrains-mono'
import '@fontsource-variable/jetbrains-mono/wght-italic.css'; import '@fontsource-variable/jetbrains-mono/wght-italic.css'
interface Props { interface Props {
class: string; class: string
} }
const className = Astro.props.class; const className = Astro.props.class
--- ---
<div data-pagefind-body class=`prose dark:prose-invert prose-base max-w-none custom-md ${className}`> <div data-pagefind-body class=`prose dark:prose-invert prose-base max-w-none custom-md ${className}`>
<!--<div class="prose dark:prose-invert max-w-none custom-md">--> <!--<div class="prose dark:prose-invert max-w-none custom-md">-->
@ -46,12 +46,13 @@ const className = Astro.props.class;
wrapper.appendChild(codeBlock); wrapper.appendChild(codeBlock);
wrapper.appendChild(copyButton); wrapper.appendChild(copyButton);
let timeout; let timeout: ReturnType<typeof setTimeout>;
copyButton.addEventListener("click", async () => { copyButton.addEventListener("click", async () => {
if (timeout) { if (timeout) {
clearTimeout(timeout); clearTimeout(timeout);
} }
let text = codeBlock?.querySelector("code")?.innerText; let text = codeBlock?.querySelector("code")?.innerText;
if (text === undefined) return;
await navigator.clipboard.writeText(text); await navigator.clipboard.writeText(text);
copyButton.classList.add("success"); copyButton.classList.add("success");
timeout = setTimeout(() => { timeout = setTimeout(() => {

View File

@ -1,26 +1,25 @@
--- ---
import WidgetLayout from "./WidgetLayout.astro"; 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 {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'
const categories = await getCategoryList(); const categories = await getCategoryList()
const COLLAPSED_HEIGHT = "7.5rem"; const COLLAPSED_HEIGHT = '7.5rem'
const COLLAPSE_THRESHOLD = 5; const COLLAPSE_THRESHOLD = 5
const isCollapsed = categories.length >= COLLAPSE_THRESHOLD; const isCollapsed = categories.length >= COLLAPSE_THRESHOLD
interface Props { interface Props {
class?: string; class?: string
style?: string; style?: string
} }
const className = Astro.props.class const className = Astro.props.class
const style = Astro.props.style const style = Astro.props.style
--- ---
<WidgetLayout name={i18n(I18nKey.categories)} id="categories" isCollapsed={isCollapsed} collapsedHeight={COLLAPSED_HEIGHT} <WidgetLayout name={i18n(I18nKey.categories)} id="categories" isCollapsed={isCollapsed} collapsedHeight={COLLAPSED_HEIGHT}

View File

@ -1,17 +1,17 @@
<script lang="ts"> <script lang="ts">
import {i18n} from '@i18n/translation'; import { i18n } from '@i18n/translation'
import I18nKey from '@i18n/i18nKey'; import I18nKey from '@i18n/i18nKey'
import {getDefaultHue, getHue, setHue} from '@utils/setting-utils'; import { getDefaultHue, getHue, setHue } from '@utils/setting-utils'
let hue = getHue() let hue = getHue()
const defaultHue = getDefaultHue() const defaultHue = getDefaultHue()
function resetHue() { function resetHue() {
hue = getDefaultHue() hue = getDefaultHue()
} }
$: if (hue || hue === 0) { $: if (hue || hue === 0) {
setHue(hue) setHue(hue)
} }
</script> </script>

View File

@ -1,13 +1,13 @@
--- ---
import {type 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'
interface Props { interface Props {
links: NavBarLink[], links: NavBarLink[]
} }
const links = Astro.props.links; const links = Astro.props.links
--- ---
<div id="nav-menu-panel" class:list={["float-panel float-panel-closed absolute transition-all fixed right-4 px-2 py-2"]}> <div id="nav-menu-panel" class:list={["float-panel float-panel-closed absolute transition-all fixed right-4 px-2 py-2"]}>
{links.map((link) => ( {links.map((link) => (

View File

@ -1,10 +1,10 @@
--- ---
import ImageWrapper from "../misc/ImageWrapper.astro"; import ImageWrapper from '../misc/ImageWrapper.astro'
import {Icon} from "astro-icon/components"; import { Icon } from 'astro-icon/components'
import {profileConfig} from "../../config"; import { profileConfig } from '../../config'
import {url} from "../../utils/url-utils"; import { url } from '../../utils/url-utils'
const config = profileConfig; const config = profileConfig
--- ---
<div class="card-base p-3"> <div class="card-base p-3">
<a aria-label="Go to About Page" href={url('/about/')} <a aria-label="Go to About Page" href={url('/about/')}

View File

@ -1,9 +1,9 @@
--- ---
import Profile from "./Profile.astro"; import Profile from './Profile.astro'
import Tag from "./Tags.astro"; import Tag from './Tags.astro'
import Categories from "./Categories.astro"; import Categories from './Categories.astro'
const className = Astro.props.class; const className = Astro.props.class
--- ---
<div id="sidebar" class:list={[className, "w-full"]}> <div id="sidebar" class:list={[className, "w-full"]}>
<div class="flex flex-col w-full gap-4 mb-4"> <div class="flex flex-col w-full gap-4 mb-4">

View File

@ -1,25 +1,24 @@
--- ---
import WidgetLayout from "./WidgetLayout.astro"; import WidgetLayout from './WidgetLayout.astro'
import ButtonTag from "../control/ButtonTag.astro"; import ButtonTag from '../control/ButtonTag.astro'
import {getTagList} from "../../utils/content-utils"; import { getTagList } from '../../utils/content-utils'
import {i18n} from "../../i18n/translation"; import { i18n } from '../../i18n/translation'
import I18nKey from "../../i18n/i18nKey"; import I18nKey from '../../i18n/i18nKey'
import {url} from "../../utils/url-utils"; import { url } from '../../utils/url-utils'
const tags = await getTagList(); const tags = await getTagList()
const COLLAPSED_HEIGHT = "7.5rem"; const COLLAPSED_HEIGHT = '7.5rem'
const isCollapsed = tags.length >= 20; const isCollapsed = tags.length >= 20
interface Props { interface Props {
class?: string; class?: string
style?: string; style?: string
} }
const className = Astro.props.class const className = Astro.props.class
const style = Astro.props.style const style = Astro.props.style
--- ---
<WidgetLayout name={i18n(I18nKey.tags)} id="tags" isCollapsed={isCollapsed} collapsedHeight={COLLAPSED_HEIGHT} class={className} style={style}> <WidgetLayout name={i18n(I18nKey.tags)} id="tags" isCollapsed={isCollapsed} collapsedHeight={COLLAPSED_HEIGHT} class={className} style={style}>
<div class="flex gap-2 flex-wrap"> <div class="flex gap-2 flex-wrap">

View File

@ -1,25 +1,18 @@
--- ---
import { Icon } from 'astro-icon/components'; import { Icon } from 'astro-icon/components'
import {i18n} from "../../i18n/translation"; import { i18n } from '../../i18n/translation'
import I18nKey from "../../i18n/i18nKey"; import I18nKey from '../../i18n/i18nKey'
interface Props { interface Props {
id: string; id: string
name?: string; name?: string
isCollapsed?: boolean; isCollapsed?: boolean
collapsedHeight?: string; collapsedHeight?: string
class?: string; class?: string
style?: string; style?: string
} }
const props = Astro.props; const props = Astro.props
const { const { id, name, isCollapsed, collapsedHeight, style } = Astro.props
id,
name,
isCollapsed,
collapsedHeight,
style,
} = Astro.props
const className = Astro.props.class const className = Astro.props.class
--- ---
<widget-layout data-id={id} data-is-collapsed={String(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

View File

@ -2,5 +2,7 @@ export const UNCATEGORIZED = '__uncategorized__'
export const PAGE_SIZE = 8 export const PAGE_SIZE = 8
export const LIGHT_MODE = 'light', DARK_MODE = 'dark', AUTO_MODE = 'auto' export const LIGHT_MODE = 'light',
DARK_MODE = 'dark',
AUTO_MODE = 'auto'
export const DEFAULT_THEME = AUTO_MODE export const DEFAULT_THEME = AUTO_MODE

View File

@ -1,37 +1,44 @@
import type {Favicon} from "@/types/config.ts"; import type { Favicon } from '@/types/config.ts'
export const defaultFavicons: Favicon[] = [ export const defaultFavicons: Favicon[] = [
{ {
src: '/favicon/favicon-light-32.png', src: '/favicon/favicon-light-32.png',
theme: 'light', theme: 'light',
sizes: '32x32', sizes: '32x32',
}, { },
src: '/favicon/favicon-light-128.png', {
theme: 'light', src: '/favicon/favicon-light-128.png',
sizes: '128x128', theme: 'light',
}, { sizes: '128x128',
src: '/favicon/favicon-light-180.png', },
theme: 'light', {
sizes: '180x180', src: '/favicon/favicon-light-180.png',
}, { theme: 'light',
src: '/favicon/favicon-light-192.png', sizes: '180x180',
theme: 'light', },
sizes: '192x192', {
}, { src: '/favicon/favicon-light-192.png',
src: '/favicon/favicon-dark-32.png', theme: 'light',
theme: 'dark', sizes: '192x192',
sizes: '32x32', },
}, { {
src: '/favicon/favicon-dark-128.png', src: '/favicon/favicon-dark-32.png',
theme: 'dark', theme: 'dark',
sizes: '128x128', sizes: '32x32',
}, { },
src: '/favicon/favicon-dark-180.png', {
theme: 'dark', src: '/favicon/favicon-dark-128.png',
sizes: '180x180', theme: 'dark',
}, { sizes: '128x128',
src: '/favicon/favicon-dark-192.png', },
theme: 'dark', {
sizes: '192x192', src: '/favicon/favicon-dark-180.png',
} theme: 'dark',
sizes: '180x180',
},
{
src: '/favicon/favicon-dark-192.png',
theme: 'dark',
sizes: '192x192',
},
] ]

View File

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

View File

@ -22,7 +22,7 @@ const map: { [key: string]: Translation } = {
ja: ja, ja: ja,
ja_jp: ja, ja_jp: ja,
ko: ko, ko: ko,
ko_kr: ko ko_kr: ko,
} }
export function getTranslation(lang: string): Translation { export function getTranslation(lang: string): Translation {

View File

@ -297,7 +297,7 @@ const setup = () => {
document.documentElement.style.setProperty('--content-delay', '0ms') document.documentElement.style.setProperty('--content-delay', '0ms')
}) })
window.swup.hooks.on('content:replace', initCustomScrollbar) window.swup.hooks.on('content:replace', initCustomScrollbar)
window.swup.hooks.on('visit:start', (visit) => { window.swup.hooks.on('visit:start', (visit: {to: {url: string}}) => {
// change banner height immediately when a link is clicked // change banner height immediately when a link is clicked
const bodyElement = document.querySelector('body') const bodyElement = document.querySelector('body')
if (pathsEqual(visit.to.url, url('/'))) { if (pathsEqual(visit.to.url, url('/'))) {
@ -364,7 +364,6 @@ const setup = () => {
window.swup.hooks.on( window.swup.hooks.on(
"content:replace", "content:replace",
() => { () => {
console.log("content:replace")
lightbox?.destroy?.() lightbox?.destroy?.()
}, },
{ before: true }, { before: true },

View File

@ -4,8 +4,8 @@ import Navbar from '@components/Navbar.astro'
import BackToTop from '@components/control/BackToTop.astro' import BackToTop from '@components/control/BackToTop.astro'
import SideBar from '@components/widget/SideBar.astro' import SideBar from '@components/widget/SideBar.astro'
import Layout from './Layout.astro' import Layout from './Layout.astro'
import { Icon } from 'astro-icon/components'; import { Icon } from 'astro-icon/components'
import { siteConfig } from '../config'; import { siteConfig } from '../config'
interface Props { interface Props {
title?: string title?: string
@ -14,8 +14,9 @@ interface Props {
} }
const { title, banner, description } = Astro.props const { title, banner, description } = Astro.props
const hasBannerCredit = siteConfig.banner.enable && siteConfig.banner.credit.enable const hasBannerCredit =
const hasBannerLink = !!(siteConfig.banner.credit.url) siteConfig.banner.enable && siteConfig.banner.credit.enable
const hasBannerLink = !!siteConfig.banner.credit.url
--- ---
<Layout title={title} banner={banner} description={description}> <Layout title={title} banner={banner} description={description}>

View File

@ -1,16 +1,15 @@
--- ---
import MainGridLayout from "../layouts/MainGridLayout.astro"; import MainGridLayout from '../layouts/MainGridLayout.astro'
import { getEntry } from 'astro:content' import { getEntry } from 'astro:content'
import {i18n} from "../i18n/translation"; import { i18n } from '../i18n/translation'
import I18nKey from "../i18n/i18nKey"; import I18nKey from '../i18n/i18nKey'
import Markdown from "@components/misc/Markdown.astro"; import Markdown from '@components/misc/Markdown.astro'
const aboutPost = await getEntry('spec', 'about') const aboutPost = await getEntry('spec', 'about')
const { Content } = await aboutPost.render() const { Content } = await aboutPost.render()
--- ---
<MainGridLayout title={i18n(I18nKey.about)} description={i18n(I18nKey.about)}> <MainGridLayout title={i18n(I18nKey.about)} description={i18n(I18nKey.about)}>
<div class="flex w-full rounded-[var(--radius-large)] overflow-hidden relative min-h-32"> <div class="flex w-full rounded-[var(--radius-large)] overflow-hidden relative min-h-32">

View File

@ -1,23 +1,22 @@
--- ---
import {getCategoryList} from "@utils/content-utils"; import { getCategoryList } from '@utils/content-utils'
import MainGridLayout from "@layouts/MainGridLayout.astro"; import MainGridLayout from '@layouts/MainGridLayout.astro'
import ArchivePanel from "@components/ArchivePanel.astro"; 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() {
const categories = await getCategoryList(); const categories = await getCategoryList()
return categories.map(category => { return categories.map(category => {
return { return {
params: { params: {
category: category.name category: category.name,
} },
} }
}); })
} }
const category = Astro.params.category as string; const category = Astro.params.category as string
--- ---
<MainGridLayout title={i18n(I18nKey.archive)} description={i18n(I18nKey.archive)}> <MainGridLayout title={i18n(I18nKey.archive)} description={i18n(I18nKey.archive)}>

View File

@ -1,9 +1,9 @@
--- ---
import MainGridLayout from "@layouts/MainGridLayout.astro"; import MainGridLayout from '@layouts/MainGridLayout.astro'
import ArchivePanel from "@components/ArchivePanel.astro"; 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'
import {UNCATEGORIZED} from "@constants/constants"; import { UNCATEGORIZED } from '@constants/constants'
--- ---
<MainGridLayout title={i18n(I18nKey.archive)}> <MainGridLayout title={i18n(I18nKey.archive)}>

View File

@ -1,8 +1,8 @@
--- ---
import MainGridLayout from "@layouts/MainGridLayout.astro"; import MainGridLayout from '@layouts/MainGridLayout.astro'
import ArchivePanel from "@components/ArchivePanel.astro"; 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'
--- ---
<MainGridLayout title={i18n(I18nKey.archive)}> <MainGridLayout title={i18n(I18nKey.archive)}>

View File

@ -1,31 +1,30 @@
--- ---
import {getSortedPosts} from "@utils/content-utils"; import { getSortedPosts } from '@utils/content-utils'
import MainGridLayout from "@layouts/MainGridLayout.astro"; import MainGridLayout from '@layouts/MainGridLayout.astro'
import ArchivePanel from "@components/ArchivePanel.astro"; 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()
const allTags = posts.reduce((acc, post) => { const allTags = posts.reduce((acc, post) => {
post.data.tags.forEach(tag => acc.add(tag)); post.data.tags.forEach(tag => acc.add(tag))
return acc; return acc
}, new Set()); }, new Set())
const allTagsArray = Array.from(allTags); const allTagsArray = Array.from(allTags)
return allTagsArray.map(tag => { return allTagsArray.map(tag => {
return { return {
params: { params: {
tag: tag tag: tag,
} },
} }
}); })
} }
const tag= Astro.params.tag as string; const tag = Astro.params.tag as string
--- ---
<MainGridLayout title={i18n(I18nKey.archive)} description={i18n(I18nKey.archive)}> <MainGridLayout title={i18n(I18nKey.archive)} description={i18n(I18nKey.archive)}>

View File

@ -1,16 +1,16 @@
import type { APIRoute } from 'astro'; import type { APIRoute } from 'astro'
const robotsTxt = ` const robotsTxt = `
User-agent: * User-agent: *
Allow: / Allow: /
Sitemap: ${new URL('sitemap-index.xml', import.meta.env.SITE).href} Sitemap: ${new URL('sitemap-index.xml', import.meta.env.SITE).href}
`.trim(); `.trim()
export const GET: APIRoute = () => { export const GET: APIRoute = () => {
return new Response(robotsTxt, { return new Response(robotsTxt, {
headers: { headers: {
'Content-Type': 'text/plain; charset=utf-8', 'Content-Type': 'text/plain; charset=utf-8',
}, },
}); })
}; }

View File

@ -1,25 +1,25 @@
import rss from '@astrojs/rss'; import rss from '@astrojs/rss'
import {siteConfig} from '@/config'; import { siteConfig } from '@/config'
import sanitizeHtml from 'sanitize-html'; import sanitizeHtml from 'sanitize-html'
import MarkdownIt from 'markdown-it'; import MarkdownIt from 'markdown-it'
import {getSortedPosts} from "@utils/content-utils.ts"; import { getSortedPosts } from '@utils/content-utils.ts'
const parser = new MarkdownIt(); const parser = new MarkdownIt()
export async function GET(context: any) { export async function GET(context: any) {
const blog = await getSortedPosts(); const blog = await getSortedPosts()
return rss({ return rss({
title: siteConfig.title, title: siteConfig.title,
description: siteConfig.subtitle || 'No description', description: siteConfig.subtitle || 'No description',
site: context.site, site: context.site,
items: blog.map((post) => ({ items: blog.map(post => ({
title: post.data.title, title: post.data.title,
pubDate: post.data.published, pubDate: post.data.published,
description: post.data.description, description: post.data.description,
link: `/posts/${post.slug}/`, link: `/posts/${post.slug}/`,
content: sanitizeHtml(parser.render(post.body), { content: sanitizeHtml(parser.render(post.body), {
allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img']) allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img']),
}), }),
})), })),
customData: `<language>${siteConfig.lang}</language>`, customData: `<language>${siteConfig.lang}</language>`,
}); })
} }

View File

@ -11,21 +11,22 @@ import { h } from 'hastscript'
* @returns {import('mdast').Parent} The created admonition component. * @returns {import('mdast').Parent} The created admonition component.
*/ */
export function AdmonitionComponent(properties, children, type) { export function AdmonitionComponent(properties, children, type) {
if (!Array.isArray(children) || children.length === 0) if (!Array.isArray(children) || children.length === 0)
return h("div", return h(
{ class: 'hidden' }, 'div',
'Invalid admonition directive. (Admonition directives must be of block type ":::note{name="name"} <content> :::")' { class: 'hidden' },
); 'Invalid admonition directive. (Admonition directives must be of block type ":::note{name="name"} <content> :::")',
)
let label = null let label = null
if (properties && properties['has-directive-label']) { if (properties && properties['has-directive-label']) {
label = children[0]; // The first child is the label label = children[0] // The first child is the label
children = children.slice(1); children = children.slice(1)
label.tagName = "div"; // Change the tag <p> to <div> label.tagName = 'div' // Change the tag <p> to <div>
} }
return h(`blockquote`, return h(`blockquote`, { class: `admonition bdm-${type}` }, [
{ class: `admonition bdm-${type}` }, h('span', { class: `bdm-title` }, label ? label : type.toUpperCase()),
[ h("span", { class: `bdm-title` }, label ? label : type.toUpperCase()), ...children] ...children,
); ])
} }

View File

@ -10,72 +10,53 @@ import { h } from 'hastscript'
* @returns {import('mdast').Parent} The created GitHub Card component. * @returns {import('mdast').Parent} The created GitHub Card component.
*/ */
export function GithubCardComponent(properties, children) { export function GithubCardComponent(properties, children) {
if (Array.isArray(children) && children.length !== 0) if (Array.isArray(children) && children.length !== 0)
return h("div", return h('div', { class: 'hidden' }, [
{ class: 'hidden' }, 'Invalid directive. ("github" directive must be leaf type "::github{repo="owner/repo"}")',
['Invalid directive. ("github" directive must be leaf type "::github{repo="owner/repo"}")'] ])
);
if (!properties.repo || !properties.repo.includes("/")) if (!properties.repo || !properties.repo.includes('/'))
return h("div", return h(
{ class: 'hidden' }, 'div',
'Invalid repository. ("repo" attributte must be in the format "owner/repo")' { class: 'hidden' },
); 'Invalid repository. ("repo" attributte must be in the format "owner/repo")',
)
const repo = properties.repo; const repo = properties.repo
const cardUuid = `GC${Math.random().toString(36).slice(-6)}` // Collisions are not important const cardUuid = `GC${Math.random().toString(36).slice(-6)}` // Collisions are not important
const nAvatar = h( const nAvatar = h(`div#${cardUuid}-avatar`, { class: 'gc-avatar' })
`div#${cardUuid}-avatar`,
{ class: "gc-avatar"},
)
const nLanguage = h( const nLanguage = h(
`span#${cardUuid}-language`, `span#${cardUuid}-language`,
{ class: "gc-language" }, { class: 'gc-language' },
"Waiting..." 'Waiting...',
) )
const nTitle = h( const nTitle = h(`div`, { class: 'gc-titlebar' }, [
`div`, h('div', { class: 'gc-titlebar-left' }, [
{ class: "gc-titlebar" }, h('div', { class: 'gc-owner' }, [
[ nAvatar,
h("div", { class: "gc-titlebar-left"}, [ h('div', { class: 'gc-user' }, repo.split('/')[0]),
h("div", { class: "gc-owner" }, [
nAvatar,
h("div", { class: "gc-user" }, repo.split("/")[0] ),
]),
h("div", { class: "gc-divider" }, "/" ),
h("div", { class: "gc-repo" }, repo.split("/")[1] )
]), ]),
h("div", { class: "github-logo"}) h('div', { class: 'gc-divider' }, '/'),
] h('div', { class: 'gc-repo' }, repo.split('/')[1]),
) ]),
h('div', { class: 'github-logo' }),
])
const nDescription = h( const nDescription = h(
`div#${cardUuid}-description`, `div#${cardUuid}-description`,
{ class: "gc-description" }, { class: 'gc-description' },
"Waiting for api.github.com..." 'Waiting for api.github.com...',
) )
const nStars = h( const nStars = h(`div#${cardUuid}-stars`, { class: 'gc-stars' }, '00K')
`div#${cardUuid}-stars`, const nForks = h(`div#${cardUuid}-forks`, { class: 'gc-forks' }, '0K')
{ class: "gc-stars" }, const nLicense = h(`div#${cardUuid}-license`, { class: 'gc-license' }, '0K')
"00K"
)
const nForks = h(
`div#${cardUuid}-forks`,
{ class: "gc-forks" },
"0K"
)
const nLicense = h(
`div#${cardUuid}-license`,
{ class: "gc-license" },
"0K"
)
const nScript = h( const nScript = h(
`script#${cardUuid}-script`, `script#${cardUuid}-script`,
{ type: "text/javascript", defer: true }, { type: 'text/javascript', defer: true },
` `
fetch('https://api.github.com/repos/${repo}', { referrerPolicy: "no-referrer" }).then(response => response.json()).then(data => { fetch('https://api.github.com/repos/${repo}', { referrerPolicy: "no-referrer" }).then(response => response.json()).then(data => {
document.getElementById('${cardUuid}-description').innerText = data.description.replace(/:[a-zA-Z0-9_]+:/g, ''); document.getElementById('${cardUuid}-description').innerText = data.description.replace(/:[a-zA-Z0-9_]+:/g, '');
@ -97,22 +78,22 @@ export function GithubCardComponent(properties, children) {
c.classList.add("fetch-error"); c.classList.add("fetch-error");
console.warn("[GITHUB-CARD] (Error) Loading card for ${repo} | ${cardUuid}.") console.warn("[GITHUB-CARD] (Error) Loading card for ${repo} | ${cardUuid}.")
}) })
` `,
) )
return h(`a#${cardUuid}-card`, return h(
{ class: "card-github fetch-waiting no-styling", `a#${cardUuid}-card`,
{
class: 'card-github fetch-waiting no-styling',
href: `https://github.com/${repo}`, href: `https://github.com/${repo}`,
target: '_blank', target: '_blank',
repo }, repo,
},
[ [
nTitle, nTitle,
nDescription, nDescription,
h("div", h('div', { class: 'gc-infobar' }, [nStars, nForks, nLicense, nLanguage]),
{ class: "gc-infobar" }, nScript,
[nStars, nForks, nLicense, nLanguage] ],
), )
nScript
]
);
} }

View File

@ -1,27 +1,30 @@
// biome-ignore lint/suspicious/noShadowRestrictedNames: <explanation> // biome-ignore lint/suspicious/noShadowRestrictedNames: <explanation>
import { h } from 'hastscript'; import { h } from 'hastscript'
import {visit} from 'unist-util-visit' import { visit } from 'unist-util-visit'
export function parseDirectiveNode() { export function parseDirectiveNode() {
return (tree, { data }) => { return (tree, { data }) => {
visit(tree, function (node) { visit(tree, function (node) {
if ( if (
node.type === 'containerDirective' || node.type === 'containerDirective' ||
node.type === 'leafDirective' || node.type === 'leafDirective' ||
node.type === 'textDirective' node.type === 'textDirective'
) { ) {
const data = node.data || (node.data = {}) const data = node.data || (node.data = {})
node.attributes = node.attributes || {} node.attributes = node.attributes || {}
if (node.children.length > 0 && node.children[0].data && node.children[0].data.directiveLabel) { if (
// Add a flag to the node to indicate that it has a directive label node.children.length > 0 &&
node.attributes['has-directive-label'] = true node.children[0].data &&
} node.children[0].data.directiveLabel
const hast = h(node.name, node.attributes) ) {
// Add a flag to the node to indicate that it has a directive label
node.attributes['has-directive-label'] = true
}
const hast = h(node.name, node.attributes)
data.hName = hast.tagName data.hName = hast.tagName
data.hProperties = hast.properties data.hProperties = hast.properties
} }
}) })
} }
} }

View File

@ -2,15 +2,15 @@ import { toString } from 'mdast-util-to-string'
/* Use the post's first paragraph as the excerpt */ /* Use the post's first paragraph as the excerpt */
export function remarkExcerpt() { export function remarkExcerpt() {
return (tree, { data }) => { return (tree, { data }) => {
let excerpt = ''; let excerpt = ''
for (let node of tree.children) { for (let node of tree.children) {
if (node.type !== 'paragraph') { if (node.type !== 'paragraph') {
continue continue
} }
excerpt = toString(node) excerpt = toString(node)
break break
} }
data.astro.frontmatter.excerpt = excerpt data.astro.frontmatter.excerpt = excerpt
}; }
} }

View File

@ -1,4 +1,4 @@
import type { LIGHT_MODE, DARK_MODE, AUTO_MODE } from "@constants/constants" import type { LIGHT_MODE, DARK_MODE, AUTO_MODE } from '@constants/constants'
export type SiteConfig = { export type SiteConfig = {
title: string title: string
@ -63,4 +63,7 @@ export type LicenseConfig = {
url: string url: string
} }
export type LIGHT_DARK_MODE = typeof LIGHT_MODE | typeof DARK_MODE | typeof AUTO_MODE export type LIGHT_DARK_MODE =
| typeof LIGHT_MODE
| typeof DARK_MODE
| typeof AUTO_MODE

View File

@ -1,4 +1,9 @@
import {AUTO_MODE, DARK_MODE, DEFAULT_THEME, LIGHT_MODE} from "@constants/constants.ts"; import {
AUTO_MODE,
DARK_MODE,
DEFAULT_THEME,
LIGHT_MODE,
} from '@constants/constants.ts'
import type { LIGHT_DARK_MODE } from '@/types/config' import type { LIGHT_DARK_MODE } from '@/types/config'
export function getDefaultHue(): number { export function getDefaultHue(): number {
@ -21,7 +26,6 @@ export function setHue(hue: number): void {
r.style.setProperty('--hue', String(hue)) r.style.setProperty('--hue', String(hue))
} }
export function applyThemeToDocument(theme: LIGHT_DARK_MODE) { export function applyThemeToDocument(theme: LIGHT_DARK_MODE) {
switch (theme) { switch (theme) {
case LIGHT_MODE: case LIGHT_MODE:
@ -46,5 +50,5 @@ export function setTheme(theme: LIGHT_DARK_MODE): void {
} }
export function getStoredTheme(): LIGHT_DARK_MODE { export function getStoredTheme(): LIGHT_DARK_MODE {
return localStorage.getItem('theme') as LIGHT_DARK_MODE || DEFAULT_THEME return (localStorage.getItem('theme') as LIGHT_DARK_MODE) || DEFAULT_THEME
} }

View File

@ -9,7 +9,7 @@ export function pathsEqual(path1: string, path2: string) {
function joinUrl(...parts: string[]): string { function joinUrl(...parts: string[]): string {
const joined = parts.join('/') const joined = parts.join('/')
return joined.replace(/\/+/g, '/'); return joined.replace(/\/+/g, '/')
} }
export function getPostUrlBySlug(slug: string): string { export function getPostUrlBySlug(slug: string): string {