mirror of
https://github.com/saicaca/fuwari.git
synced 2026-01-11 14:52:52 +01:00
format all code (#386)
This commit is contained in:
committed by
GitHub
parent
7ea2f7f40f
commit
286b050fa8
@@ -7,7 +7,7 @@
|
|||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"ignoreUnknown": false,
|
"ignoreUnknown": false,
|
||||||
"ignore": []
|
"ignore": ["src/**/*.css","src/public/**/*", "dist/**/*", "node_modules/**/*"]
|
||||||
},
|
},
|
||||||
"formatter": {
|
"formatter": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
|||||||
@@ -1,67 +1,67 @@
|
|||||||
---
|
---
|
||||||
import { UNCATEGORIZED } from '@constants/constants'
|
import { UNCATEGORIZED } from "@constants/constants";
|
||||||
import I18nKey from '../i18n/i18nKey'
|
import I18nKey from "../i18n/i18nKey";
|
||||||
import { i18n } from '../i18n/translation'
|
import { i18n } from "../i18n/translation";
|
||||||
import { getSortedPosts } from '../utils/content-utils'
|
import { getSortedPosts } from "../utils/content-utils";
|
||||||
import { getPostUrlBySlug } from '../utils/url-utils'
|
import { getPostUrlBySlug } from "../utils/url-utils";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
keyword?: string
|
keyword?: string;
|
||||||
tags?: string[]
|
tags?: string[];
|
||||||
categories?: string[]
|
categories?: string[];
|
||||||
}
|
}
|
||||||
const { tags, categories } = Astro.props
|
const { 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(
|
posts = posts.filter(
|
||||||
post =>
|
(post) =>
|
||||||
Array.isArray(post.data.tags) &&
|
Array.isArray(post.data.tags) &&
|
||||||
post.data.tags.some(tag => tags.includes(tag)),
|
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(
|
posts = posts.filter(
|
||||||
post =>
|
(post) =>
|
||||||
(post.data.category && categories.includes(post.data.category)) ||
|
(post.data.category && categories.includes(post.data.category)) ||
|
||||||
(!post.data.category && categories.includes(UNCATEGORIZED)),
|
(!post.data.category && categories.includes(UNCATEGORIZED)),
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const groups: { year: number; posts: typeof posts }[] = (() => {
|
const groups: { year: number; posts: typeof posts }[] = (() => {
|
||||||
const groupedPosts = posts.reduce(
|
const groupedPosts = posts.reduce(
|
||||||
(grouped: { [year: number]: typeof posts }, post) => {
|
(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] = [];
|
||||||
}
|
}
|
||||||
grouped[year].push(post)
|
grouped[year].push(post);
|
||||||
return grouped
|
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: Number.parseInt(key),
|
year: Number.parseInt(key),
|
||||||
posts: groupedPosts[Number.parseInt(key)],
|
posts: groupedPosts[Number.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(" ");
|
||||||
}
|
}
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +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}>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
|
|
||||||
import { profileConfig } from '../config'
|
import { profileConfig } from "../config";
|
||||||
import { url } from '../utils/url-utils'
|
import { url } from "../utils/url-utils";
|
||||||
const currentYear = new Date().getFullYear()
|
const currentYear = new Date().getFullYear();
|
||||||
---
|
---
|
||||||
|
|
||||||
<!--<div class="border-t border-[var(--primary)] mx-16 border-dashed py-8 max-w-[var(--page-width)] flex flex-col items-center justify-center px-6">-->
|
<!--<div class="border-t border-[var(--primary)] mx-16 border-dashed py-8 max-w-[var(--page-width)] flex flex-col items-center justify-center px-6">-->
|
||||||
|
|||||||
@@ -1,59 +1,59 @@
|
|||||||
<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 { AUTO_MODE, DARK_MODE, LIGHT_MODE } from '@constants/constants.ts'
|
import { 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";
|
||||||
import {
|
import {
|
||||||
applyThemeToDocument,
|
applyThemeToDocument,
|
||||||
getStoredTheme,
|
getStoredTheme,
|
||||||
setTheme,
|
setTheme,
|
||||||
} from '@utils/setting-utils.ts'
|
} from "@utils/setting-utils.ts";
|
||||||
import { onMount } from 'svelte'
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
const seq: LIGHT_DARK_MODE[] = [LIGHT_MODE, DARK_MODE, AUTO_MODE]
|
const seq: LIGHT_DARK_MODE[] = [LIGHT_MODE, DARK_MODE, AUTO_MODE];
|
||||||
let mode: LIGHT_DARK_MODE = $state(AUTO_MODE)
|
let mode: LIGHT_DARK_MODE = $state(AUTO_MODE);
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
mode = getStoredTheme()
|
mode = getStoredTheme();
|
||||||
const darkModePreference = window.matchMedia('(prefers-color-scheme: dark)')
|
const darkModePreference = window.matchMedia("(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('change', changeThemeWhenSchemeChanged)
|
darkModePreference.addEventListener("change", changeThemeWhenSchemeChanged);
|
||||||
return () => {
|
return () => {
|
||||||
darkModePreference.removeEventListener(
|
darkModePreference.removeEventListener(
|
||||||
'change',
|
"change",
|
||||||
changeThemeWhenSchemeChanged,
|
changeThemeWhenSchemeChanged,
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
||||||
function switchScheme(newMode: LIGHT_DARK_MODE) {
|
function switchScheme(newMode: LIGHT_DARK_MODE) {
|
||||||
mode = newMode
|
mode = newMode;
|
||||||
setTheme(newMode)
|
setTheme(newMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
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>
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
---
|
---
|
||||||
import { Icon } from 'astro-icon/components'
|
import { Icon } from "astro-icon/components";
|
||||||
import DisplaySettings from './widget/DisplaySettings.svelte'
|
import { navBarConfig, siteConfig } from "../config";
|
||||||
import { LinkPreset, type NavBarLink } from '../types/config'
|
import { LinkPresets } from "../constants/link-presets";
|
||||||
import { navBarConfig, siteConfig } from '../config'
|
import { LinkPreset, type NavBarLink } from "../types/config";
|
||||||
import NavMenuPanel from './widget/NavMenuPanel.astro'
|
import { url } from "../utils/url-utils";
|
||||||
import Search from './Search.svelte'
|
import LightDarkSwitch from "./LightDarkSwitch.svelte";
|
||||||
import { LinkPresets } from '../constants/link-presets'
|
import Search from "./Search.svelte";
|
||||||
import LightDarkSwitch from './LightDarkSwitch.svelte'
|
import DisplaySettings from "./widget/DisplaySettings.svelte";
|
||||||
import { url } from '../utils/url-utils'
|
import NavMenuPanel from "./widget/NavMenuPanel.astro";
|
||||||
const className = Astro.props.class
|
const className = Astro.props.class;
|
||||||
|
|
||||||
let links: NavBarLink[] = navBarConfig.links.map(
|
let links: NavBarLink[] = navBarConfig.links.map(
|
||||||
(item: NavBarLink | LinkPreset): NavBarLink => {
|
(item: NavBarLink | LinkPreset): NavBarLink => {
|
||||||
if (typeof item === 'number') {
|
if (typeof item === "number") {
|
||||||
return LinkPresets[item]
|
return LinkPresets[item];
|
||||||
}
|
}
|
||||||
return item
|
return item;
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
---
|
---
|
||||||
<div id="navbar" class="z-50 onload-animation">
|
<div id="navbar" class="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 -->
|
||||||
|
|||||||
@@ -1,46 +1,46 @@
|
|||||||
---
|
---
|
||||||
import path from 'node:path'
|
import path from "node:path";
|
||||||
import { Icon } from 'astro-icon/components'
|
import { Icon } from "astro-icon/components";
|
||||||
import I18nKey from '../i18n/i18nKey'
|
import I18nKey from "../i18n/i18nKey";
|
||||||
import { i18n } from '../i18n/translation'
|
import { i18n } from "../i18n/translation";
|
||||||
import { getDir } from '../utils/url-utils'
|
import { getDir } from "../utils/url-utils";
|
||||||
import PostMetadata from './PostMeta.astro'
|
import PostMetadata from "./PostMeta.astro";
|
||||||
import ImageWrapper from './misc/ImageWrapper.astro'
|
import ImageWrapper from "./misc/ImageWrapper.astro";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
class?: string
|
class?: string;
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||||
entry: any
|
entry: any;
|
||||||
title: string
|
title: string;
|
||||||
url: string
|
url: string;
|
||||||
published: Date
|
published: Date;
|
||||||
updated?: Date
|
updated?: 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 {
|
const {
|
||||||
entry,
|
entry,
|
||||||
title,
|
title,
|
||||||
url,
|
url,
|
||||||
published,
|
published,
|
||||||
updated,
|
updated,
|
||||||
tags,
|
tags,
|
||||||
category,
|
category,
|
||||||
image,
|
image,
|
||||||
description,
|
description,
|
||||||
style,
|
style,
|
||||||
} = Astro.props
|
} = 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 !== "";
|
||||||
|
|
||||||
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}]}>
|
||||||
|
|||||||
@@ -1,21 +1,28 @@
|
|||||||
---
|
---
|
||||||
import { formatDateToYYYYMMDD } from '../utils/date-utils'
|
import { Icon } from "astro-icon/components";
|
||||||
import { Icon } from 'astro-icon/components'
|
import I18nKey from "../i18n/i18nKey";
|
||||||
import { i18n } from '../i18n/translation'
|
import { i18n } from "../i18n/translation";
|
||||||
import I18nKey from '../i18n/i18nKey'
|
import { formatDateToYYYYMMDD } from "../utils/date-utils";
|
||||||
import { url } from '../utils/url-utils'
|
import { url } from "../utils/url-utils";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
class: string
|
class: string;
|
||||||
published: Date
|
published: Date;
|
||||||
updated?: Date
|
updated?: Date;
|
||||||
tags: string[]
|
tags: string[];
|
||||||
category: string
|
category: string;
|
||||||
hideTagsForMobile?: boolean
|
hideTagsForMobile?: boolean;
|
||||||
hideUpdateDate?: boolean
|
hideUpdateDate?: boolean;
|
||||||
}
|
}
|
||||||
const { published, updated, tags, category, hideTagsForMobile = false, hideUpdateDate = false } = Astro.props
|
const {
|
||||||
const className = Astro.props.class
|
published,
|
||||||
|
updated,
|
||||||
|
tags,
|
||||||
|
category,
|
||||||
|
hideTagsForMobile = false,
|
||||||
|
hideUpdateDate = false,
|
||||||
|
} = Astro.props;
|
||||||
|
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]}>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
---
|
---
|
||||||
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;
|
||||||
---
|
---
|
||||||
<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">
|
<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; updated: Date; }; slug: string; }) => {
|
{page.data.map((entry: { data: { draft: boolean; title: string; tags: string[]; category: string; published: Date; image: string; description: string; updated: Date; }; slug: string; }) => {
|
||||||
|
|||||||
@@ -1,73 +1,73 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte'
|
import I18nKey from "@i18n/i18nKey";
|
||||||
import { url } from '@utils/url-utils.ts'
|
import { i18n } from "@i18n/translation";
|
||||||
import { i18n } from '@i18n/translation'
|
import Icon from "@iconify/svelte";
|
||||||
import I18nKey from '@i18n/i18nKey'
|
import { url } from "@utils/url-utils.ts";
|
||||||
import Icon from '@iconify/svelte'
|
import { onMount } from "svelte";
|
||||||
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:
|
excerpt:
|
||||||
'Because the search cannot work in the <mark>dev</mark> environment.',
|
"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) return
|
if (!panel) return;
|
||||||
|
|
||||||
if (!keyword && isDesktop) {
|
if (!keyword && isDesktop) {
|
||||||
panel.classList.add('float-panel-closed')
|
panel.classList.add("float-panel-closed");
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let arr = []
|
let arr = [];
|
||||||
if (import.meta.env.PROD) {
|
if (import.meta.env.PROD) {
|
||||||
const ret = await pagefind.search(keyword)
|
const ret = await pagefind.search(keyword);
|
||||||
for (const item of ret.results) {
|
for (const item of ret.results) {
|
||||||
arr.push(await item.data())
|
arr.push(await item.data());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Mock data for non-production environment
|
// 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 won’t 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 won’t 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 = 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 won’t 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 won’t 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
|
arr = fakeResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!arr.length && isDesktop) {
|
if (!arr.length && isDesktop) {
|
||||||
panel.classList.add('float-panel-closed')
|
panel.classList.add("float-panel-closed");
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDesktop) {
|
if (isDesktop) {
|
||||||
panel.classList.remove('float-panel-closed')
|
panel.classList.remove("float-panel-closed");
|
||||||
}
|
}
|
||||||
result = arr
|
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);
|
||||||
$: search(keywordMobile, false)
|
$: search(keywordMobile, false);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- search bar for desktop view -->
|
<!-- search bar for desktop view -->
|
||||||
|
|||||||
@@ -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` -->
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
---
|
---
|
||||||
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;
|
||||||
---
|
---
|
||||||
<a href={url} aria-label={label}>
|
<a href={url} aria-label={label}>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -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 { dot, href, label }: Props = Astro.props
|
const { 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>}
|
||||||
|
|||||||
@@ -1,53 +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
|
let l = page.currentPage;
|
||||||
let r = page.currentPage
|
let 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) pages.push(1)
|
if (l > 1) pages.push(1);
|
||||||
if (l === 3) pages.push(2)
|
if (l === 3) pages.push(2);
|
||||||
if (l > 3) pages.push(HIDDEN)
|
if (l > 3) pages.push(HIDDEN);
|
||||||
for (let i = l; i <= r; i++) pages.push(i)
|
for (let i = l; i <= r; i++) pages.push(i);
|
||||||
if (r < page.lastPage - 2) pages.push(HIDDEN)
|
if (r < page.lastPage - 2) pages.push(HIDDEN);
|
||||||
if (r === page.lastPage - 2) pages.push(page.lastPage - 1)
|
if (r === page.lastPage - 2) pages.push(page.lastPage - 1);
|
||||||
if (r < page.lastPage) pages.push(page.lastPage)
|
if (r < page.lastPage) pages.push(page.lastPage);
|
||||||
|
|
||||||
const getPageUrl = (p: number) => {
|
const getPageUrl = (p: number) => {
|
||||||
if (p === 1) return '/'
|
if (p === 1) 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}>
|
||||||
|
|||||||
@@ -1,47 +1,49 @@
|
|||||||
---
|
---
|
||||||
import path from 'node:path'
|
import path from "node: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 = !(
|
const isLocal = !(
|
||||||
src.startsWith('/') ||
|
src.startsWith("/") ||
|
||||||
src.startsWith('http') ||
|
src.startsWith("http") ||
|
||||||
src.startsWith('https') ||
|
src.startsWith("https") ||
|
||||||
src.startsWith('data:')
|
src.startsWith("data:")
|
||||||
)
|
);
|
||||||
const isPublic = src.startsWith('/')
|
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
|
||||||
// biome-ignore lint/suspicious/noImplicitAnyLet: <explanation>
|
// biome-ignore lint/suspicious/noImplicitAnyLet: <explanation>
|
||||||
let img
|
let img;
|
||||||
if (isLocal) {
|
if (isLocal) {
|
||||||
const files = import.meta.glob<ImageMetadata>('../../**', {
|
const files = import.meta.glob<ImageMetadata>("../../**", {
|
||||||
import: 'default',
|
import: "default",
|
||||||
})
|
});
|
||||||
let normalizedPath = path
|
let normalizedPath = path
|
||||||
.normalize(path.join('../../', basePath, src))
|
.normalize(path.join("../../", basePath, src))
|
||||||
.replace(/\\/g, '/')
|
.replace(/\\/g, "/");
|
||||||
const file = files[normalizedPath]
|
const file = files[normalizedPath];
|
||||||
if (!file) {
|
if (!file) {
|
||||||
console.error(`\n[ERROR] Image file not found: ${normalizedPath.replace('../../', 'src/')}`)
|
console.error(
|
||||||
}
|
`\n[ERROR] Image file not found: ${normalizedPath.replace("../../", "src/")}`,
|
||||||
img = await file()
|
);
|
||||||
|
}
|
||||||
|
img = await file();
|
||||||
}
|
}
|
||||||
|
|
||||||
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']}>
|
||||||
<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>
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
---
|
---
|
||||||
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 I18nKey from "../../i18n/i18nKey";
|
||||||
import { i18n } from '../../i18n/translation'
|
import { i18n } from "../../i18n/translation";
|
||||||
import I18nKey from '../../i18n/i18nKey'
|
import { formatDateToYYYYMMDD } from "../../utils/date-utils";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title: string
|
title: string;
|
||||||
slug: string
|
slug: string;
|
||||||
pubDate: Date
|
pubDate: Date;
|
||||||
class: string
|
class: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { title, pubDate } = Astro.props
|
const { title, 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">
|
||||||
|
|||||||
@@ -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">-->
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
---
|
---
|
||||||
import WidgetLayout from './WidgetLayout.astro'
|
import WidgetLayout from "./WidgetLayout.astro";
|
||||||
|
|
||||||
import { i18n } from '../../i18n/translation'
|
import I18nKey from "../../i18n/i18nKey";
|
||||||
import I18nKey from '../../i18n/i18nKey'
|
import { i18n } from "../../i18n/translation";
|
||||||
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}
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { i18n } from '@i18n/translation'
|
import I18nKey from "@i18n/i18nKey";
|
||||||
import I18nKey from '@i18n/i18nKey'
|
import { i18n } from "@i18n/translation";
|
||||||
import { getDefaultHue, getHue, setHue } from '@utils/setting-utils'
|
import Icon from "@iconify/svelte";
|
||||||
import Icon from '@iconify/svelte'
|
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>
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
---
|
---
|
||||||
import { type NavBarLink } from '../../types/config'
|
import { Icon } from "astro-icon/components";
|
||||||
import { Icon } from 'astro-icon/components'
|
import { type NavBarLink } from "../../types/config";
|
||||||
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) => (
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
---
|
---
|
||||||
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'
|
import ImageWrapper from "../misc/ImageWrapper.astro";
|
||||||
|
|
||||||
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/')}
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
---
|
---
|
||||||
import Profile from './Profile.astro'
|
import type { MarkdownHeading } from "astro";
|
||||||
import Tag from './Tags.astro'
|
import Categories from "./Categories.astro";
|
||||||
import Categories from './Categories.astro'
|
import Profile from "./Profile.astro";
|
||||||
import type { MarkdownHeading } from 'astro'
|
import Tag from "./Tags.astro";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
class? : string
|
class?: string;
|
||||||
headings? : MarkdownHeading[]
|
headings?: MarkdownHeading[];
|
||||||
}
|
}
|
||||||
|
|
||||||
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">
|
||||||
|
|||||||
@@ -1,29 +1,29 @@
|
|||||||
---
|
---
|
||||||
import type { MarkdownHeading } from 'astro';
|
import type { MarkdownHeading } from "astro";
|
||||||
import { siteConfig } from "../../config";
|
import { siteConfig } from "../../config";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
class?: string
|
class?: string;
|
||||||
headings: MarkdownHeading[]
|
headings: MarkdownHeading[];
|
||||||
}
|
}
|
||||||
|
|
||||||
let { headings = [] } = Astro.props;
|
let { headings = [] } = Astro.props;
|
||||||
|
|
||||||
let minDepth = 10;
|
let minDepth = 10;
|
||||||
for (const heading of headings) {
|
for (const heading of headings) {
|
||||||
minDepth = Math.min(minDepth, heading.depth);
|
minDepth = Math.min(minDepth, heading.depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
const className = Astro.props.class
|
const className = Astro.props.class;
|
||||||
|
|
||||||
const removeTailingHash = (text: string) => {
|
const removeTailingHash = (text: string) => {
|
||||||
let lastIndexOfHash = text.lastIndexOf('#');
|
let lastIndexOfHash = text.lastIndexOf("#");
|
||||||
if (lastIndexOfHash !== text.length - 1) {
|
if (lastIndexOfHash !== text.length - 1) {
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
return text.substring(0, lastIndexOfHash);
|
return text.substring(0, lastIndexOfHash);
|
||||||
}
|
};
|
||||||
|
|
||||||
let heading1Count = 1;
|
let heading1Count = 1;
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
---
|
---
|
||||||
|
|
||||||
import WidgetLayout from './WidgetLayout.astro'
|
import I18nKey from "../../i18n/i18nKey";
|
||||||
import ButtonTag from '../control/ButtonTag.astro'
|
import { i18n } from "../../i18n/translation";
|
||||||
import { getTagList } from '../../utils/content-utils'
|
import { getTagList } from "../../utils/content-utils";
|
||||||
import { i18n } from '../../i18n/translation'
|
import { url } from "../../utils/url-utils";
|
||||||
import I18nKey from '../../i18n/i18nKey'
|
import ButtonTag from "../control/ButtonTag.astro";
|
||||||
import { url } from '../../utils/url-utils'
|
import WidgetLayout from "./WidgetLayout.astro";
|
||||||
|
|
||||||
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">
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
---
|
---
|
||||||
import { Icon } from 'astro-icon/components'
|
import { Icon } from "astro-icon/components";
|
||||||
import { i18n } from '../../i18n/translation'
|
import I18nKey from "../../i18n/i18nKey";
|
||||||
import I18nKey from '../../i18n/i18nKey'
|
import { i18n } from "../../i18n/translation";
|
||||||
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 { id, name, isCollapsed, collapsedHeight, style } = Astro.props
|
const { 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
|
||||||
|
|||||||
147
src/config.ts
147
src/config.ts
@@ -1,82 +1,83 @@
|
|||||||
import type {
|
import type {
|
||||||
LicenseConfig,
|
LicenseConfig,
|
||||||
NavBarConfig,
|
NavBarConfig,
|
||||||
ProfileConfig,
|
ProfileConfig,
|
||||||
SiteConfig,
|
SiteConfig,
|
||||||
} from './types/config'
|
} from "./types/config";
|
||||||
import { LinkPreset } from './types/config'
|
import { LinkPreset } from "./types/config";
|
||||||
|
|
||||||
export const siteConfig: SiteConfig = {
|
export const siteConfig: SiteConfig = {
|
||||||
title: 'Fuwari',
|
title: "Fuwari",
|
||||||
subtitle: 'Demo Site',
|
subtitle: "Demo Site",
|
||||||
lang: 'en', // 'en', 'zh_CN', 'zh_TW', 'ja', 'ko', 'es', 'th'
|
lang: "en", // 'en', 'zh_CN', 'zh_TW', 'ja', 'ko', 'es', 'th'
|
||||||
themeColor: {
|
themeColor: {
|
||||||
hue: 250, // Default hue for the theme color, from 0 to 360. e.g. red: 0, teal: 200, cyan: 250, pink: 345
|
hue: 250, // Default hue for the theme color, from 0 to 360. e.g. red: 0, teal: 200, cyan: 250, pink: 345
|
||||||
fixed: false, // Hide the theme color picker for visitors
|
fixed: false, // Hide the theme color picker for visitors
|
||||||
},
|
},
|
||||||
banner: {
|
banner: {
|
||||||
enable: false,
|
enable: false,
|
||||||
src: 'assets/images/demo-banner.png', // Relative to the /src directory. Relative to the /public directory if it starts with '/'
|
src: "assets/images/demo-banner.png", // Relative to the /src directory. Relative to the /public directory if it starts with '/'
|
||||||
position: 'center', // Equivalent to object-position, only supports 'top', 'center', 'bottom'. 'center' by default
|
position: "center", // Equivalent to object-position, only supports 'top', 'center', 'bottom'. 'center' by default
|
||||||
credit: {
|
credit: {
|
||||||
enable: false, // Display the credit text of the banner image
|
enable: false, // Display the credit text of the banner image
|
||||||
text: '', // Credit text to be displayed
|
text: "", // Credit text to be displayed
|
||||||
url: '' // (Optional) URL link to the original artwork or artist's page
|
url: "", // (Optional) URL link to the original artwork or artist's page
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
toc: {
|
toc: {
|
||||||
enable: true, // Display the table of contents on the right side of the post
|
enable: true, // Display the table of contents on the right side of the post
|
||||||
depth: 2 // Maximum heading depth to show in the table, from 1 to 3
|
depth: 2, // Maximum heading depth to show in the table, from 1 to 3
|
||||||
},
|
},
|
||||||
favicon: [ // Leave this array empty to use the default favicon
|
favicon: [
|
||||||
// {
|
// Leave this array empty to use the default favicon
|
||||||
// src: '/favicon/icon.png', // Path of the favicon, relative to the /public directory
|
// {
|
||||||
// theme: 'light', // (Optional) Either 'light' or 'dark', set only if you have different favicons for light and dark mode
|
// src: '/favicon/icon.png', // Path of the favicon, relative to the /public directory
|
||||||
// sizes: '32x32', // (Optional) Size of the favicon, set only if you have favicons of different sizes
|
// theme: 'light', // (Optional) Either 'light' or 'dark', set only if you have different favicons for light and dark mode
|
||||||
// }
|
// sizes: '32x32', // (Optional) Size of the favicon, set only if you have favicons of different sizes
|
||||||
]
|
// }
|
||||||
}
|
],
|
||||||
|
};
|
||||||
|
|
||||||
export const navBarConfig: NavBarConfig = {
|
export const navBarConfig: NavBarConfig = {
|
||||||
links: [
|
links: [
|
||||||
LinkPreset.Home,
|
LinkPreset.Home,
|
||||||
LinkPreset.Archive,
|
LinkPreset.Archive,
|
||||||
LinkPreset.About,
|
LinkPreset.About,
|
||||||
{
|
{
|
||||||
name: 'GitHub',
|
name: "GitHub",
|
||||||
url: 'https://github.com/saicaca/fuwari', // Internal links should not include the base path, as it is automatically added
|
url: "https://github.com/saicaca/fuwari", // Internal links should not include the base path, as it is automatically added
|
||||||
external: true, // Show an external link icon and will open in a new tab
|
external: true, // Show an external link icon and will open in a new tab
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
};
|
||||||
|
|
||||||
export const profileConfig: ProfileConfig = {
|
export const profileConfig: ProfileConfig = {
|
||||||
avatar: 'assets/images/demo-avatar.png', // Relative to the /src directory. Relative to the /public directory if it starts with '/'
|
avatar: "assets/images/demo-avatar.png", // Relative to the /src directory. Relative to the /public directory if it starts with '/'
|
||||||
name: 'Lorem Ipsum',
|
name: "Lorem Ipsum",
|
||||||
bio: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
bio: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
|
||||||
links: [
|
links: [
|
||||||
{
|
{
|
||||||
name: 'Twitter',
|
name: "Twitter",
|
||||||
icon: 'fa6-brands:twitter', // Visit https://icones.js.org/ for icon codes
|
icon: "fa6-brands:twitter", // Visit https://icones.js.org/ for icon codes
|
||||||
// You will need to install the corresponding icon set if it's not already included
|
// You will need to install the corresponding icon set if it's not already included
|
||||||
// `pnpm add @iconify-json/<icon-set-name>`
|
// `pnpm add @iconify-json/<icon-set-name>`
|
||||||
url: 'https://twitter.com',
|
url: "https://twitter.com",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Steam',
|
name: "Steam",
|
||||||
icon: 'fa6-brands:steam',
|
icon: "fa6-brands:steam",
|
||||||
url: 'https://store.steampowered.com',
|
url: "https://store.steampowered.com",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'GitHub',
|
name: "GitHub",
|
||||||
icon: 'fa6-brands:github',
|
icon: "fa6-brands:github",
|
||||||
url: 'https://github.com/saicaca/fuwari',
|
url: "https://github.com/saicaca/fuwari",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
};
|
||||||
|
|
||||||
export const licenseConfig: LicenseConfig = {
|
export const licenseConfig: LicenseConfig = {
|
||||||
enable: true,
|
enable: true,
|
||||||
name: 'CC BY-NC-SA 4.0',
|
name: "CC BY-NC-SA 4.0",
|
||||||
url: 'https://creativecommons.org/licenses/by-nc-sa/4.0/',
|
url: "https://creativecommons.org/licenses/by-nc-sa/4.0/",
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
export const UNCATEGORIZED = '__uncategorized__'
|
export const UNCATEGORIZED = "__uncategorized__";
|
||||||
|
|
||||||
export const PAGE_SIZE = 8
|
export const PAGE_SIZE = 8;
|
||||||
|
|
||||||
export const LIGHT_MODE = 'light',
|
export const LIGHT_MODE = "light",
|
||||||
DARK_MODE = 'dark',
|
DARK_MODE = "dark",
|
||||||
AUTO_MODE = 'auto'
|
AUTO_MODE = "auto";
|
||||||
export const DEFAULT_THEME = AUTO_MODE
|
export const DEFAULT_THEME = AUTO_MODE;
|
||||||
|
|
||||||
// Banner height unit: vh
|
// Banner height unit: vh
|
||||||
export const BANNER_HEIGHT = 35
|
export const BANNER_HEIGHT = 35;
|
||||||
export const BANNER_HEIGHT_EXTEND = 30
|
export const BANNER_HEIGHT_EXTEND = 30;
|
||||||
export const BANNER_HEIGHT_HOME = BANNER_HEIGHT + BANNER_HEIGHT_EXTEND
|
export const BANNER_HEIGHT_HOME = BANNER_HEIGHT + BANNER_HEIGHT_EXTEND;
|
||||||
|
|
||||||
// The height the main panel overlaps the banner, unit: rem
|
// The height the main panel overlaps the banner, unit: rem
|
||||||
export const MAIN_PANEL_OVERLAPS_BANNER_HEIGHT = 3.5
|
export const MAIN_PANEL_OVERLAPS_BANNER_HEIGHT = 3.5;
|
||||||
|
|
||||||
// Page width: rem
|
// Page width: rem
|
||||||
export const PAGE_WIDTH = 75
|
export const PAGE_WIDTH = 75;
|
||||||
|
|||||||
@@ -1,44 +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',
|
src: "/favicon/favicon-light-128.png",
|
||||||
theme: 'light',
|
theme: "light",
|
||||||
sizes: '128x128',
|
sizes: "128x128",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
src: '/favicon/favicon-light-180.png',
|
src: "/favicon/favicon-light-180.png",
|
||||||
theme: 'light',
|
theme: "light",
|
||||||
sizes: '180x180',
|
sizes: "180x180",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
src: '/favicon/favicon-light-192.png',
|
src: "/favicon/favicon-light-192.png",
|
||||||
theme: 'light',
|
theme: "light",
|
||||||
sizes: '192x192',
|
sizes: "192x192",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
src: '/favicon/favicon-dark-32.png',
|
src: "/favicon/favicon-dark-32.png",
|
||||||
theme: 'dark',
|
theme: "dark",
|
||||||
sizes: '32x32',
|
sizes: "32x32",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
src: '/favicon/favicon-dark-128.png',
|
src: "/favicon/favicon-dark-128.png",
|
||||||
theme: 'dark',
|
theme: "dark",
|
||||||
sizes: '128x128',
|
sizes: "128x128",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
src: '/favicon/favicon-dark-180.png',
|
src: "/favicon/favicon-dark-180.png",
|
||||||
theme: 'dark',
|
theme: "dark",
|
||||||
sizes: '180x180',
|
sizes: "180x180",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
src: '/favicon/favicon-dark-192.png',
|
src: "/favicon/favicon-dark-192.png",
|
||||||
theme: 'dark',
|
theme: "dark",
|
||||||
sizes: '192x192',
|
sizes: "192x192",
|
||||||
},
|
},
|
||||||
]
|
];
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import { LinkPreset, type NavBarLink } from '@/types/config'
|
import { LinkPreset, type NavBarLink } from "@/types/config";
|
||||||
import I18nKey from '@i18n/i18nKey'
|
import I18nKey from "@i18n/i18nKey";
|
||||||
import { i18n } from '@i18n/translation'
|
import { i18n } from "@i18n/translation";
|
||||||
|
|
||||||
export const LinkPresets: { [key in LinkPreset]: NavBarLink } = {
|
export const LinkPresets: { [key in LinkPreset]: NavBarLink } = {
|
||||||
[LinkPreset.Home]: {
|
[LinkPreset.Home]: {
|
||||||
name: i18n(I18nKey.home),
|
name: i18n(I18nKey.home),
|
||||||
url: '/',
|
url: "/",
|
||||||
},
|
},
|
||||||
[LinkPreset.About]: {
|
[LinkPreset.About]: {
|
||||||
name: i18n(I18nKey.about),
|
name: i18n(I18nKey.about),
|
||||||
url: '/about/',
|
url: "/about/",
|
||||||
},
|
},
|
||||||
[LinkPreset.Archive]: {
|
[LinkPreset.Archive]: {
|
||||||
name: i18n(I18nKey.archive),
|
name: i18n(I18nKey.archive),
|
||||||
url: '/archive/',
|
url: "/archive/",
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
import { defineCollection, z } from 'astro:content'
|
import { defineCollection, z } from "astro:content";
|
||||||
|
|
||||||
const postsCollection = defineCollection({
|
const postsCollection = defineCollection({
|
||||||
schema: z.object({
|
schema: z.object({
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
published: z.date(),
|
published: z.date(),
|
||||||
updated: z.date().optional(),
|
updated: z.date().optional(),
|
||||||
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(""),
|
||||||
lang: z.string().optional().default(''),
|
lang: 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 = {
|
||||||
posts: postsCollection,
|
posts: postsCollection,
|
||||||
}
|
};
|
||||||
|
|||||||
10
src/global.d.ts
vendored
10
src/global.d.ts
vendored
@@ -1,8 +1,8 @@
|
|||||||
import type { AstroIntegration } from '@swup/astro'
|
import type { AstroIntegration } from "@swup/astro";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
// type from '@swup/astro' is incorrect
|
// type from '@swup/astro' is incorrect
|
||||||
swup: AstroIntegration
|
swup: AstroIntegration;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,37 +1,37 @@
|
|||||||
enum I18nKey {
|
enum I18nKey {
|
||||||
home = 'home',
|
home = "home",
|
||||||
about = 'about',
|
about = "about",
|
||||||
archive = 'archive',
|
archive = "archive",
|
||||||
search = 'search',
|
search = "search",
|
||||||
|
|
||||||
tags = 'tags',
|
tags = "tags",
|
||||||
categories = 'categories',
|
categories = "categories",
|
||||||
recentPosts = 'recentPosts',
|
recentPosts = "recentPosts",
|
||||||
|
|
||||||
comments = 'comments',
|
comments = "comments",
|
||||||
|
|
||||||
untitled = 'untitled',
|
untitled = "untitled",
|
||||||
uncategorized = 'uncategorized',
|
uncategorized = "uncategorized",
|
||||||
noTags = 'noTags',
|
noTags = "noTags",
|
||||||
|
|
||||||
wordCount = 'wordCount',
|
wordCount = "wordCount",
|
||||||
wordsCount = 'wordsCount',
|
wordsCount = "wordsCount",
|
||||||
minuteCount = 'minuteCount',
|
minuteCount = "minuteCount",
|
||||||
minutesCount = 'minutesCount',
|
minutesCount = "minutesCount",
|
||||||
postCount = 'postCount',
|
postCount = "postCount",
|
||||||
postsCount = 'postsCount',
|
postsCount = "postsCount",
|
||||||
|
|
||||||
themeColor = 'themeColor',
|
themeColor = "themeColor",
|
||||||
|
|
||||||
lightMode = 'lightMode',
|
lightMode = "lightMode",
|
||||||
darkMode = 'darkMode',
|
darkMode = "darkMode",
|
||||||
systemMode = 'systemMode',
|
systemMode = "systemMode",
|
||||||
|
|
||||||
more = 'more',
|
more = "more",
|
||||||
|
|
||||||
author = 'author',
|
author = "author",
|
||||||
publishedAt = 'publishedAt',
|
publishedAt = "publishedAt",
|
||||||
license = 'license',
|
license = "license",
|
||||||
}
|
}
|
||||||
|
|
||||||
export default I18nKey
|
export default I18nKey;
|
||||||
|
|||||||
@@ -1,38 +1,38 @@
|
|||||||
import Key from '../i18nKey'
|
import Key from "../i18nKey";
|
||||||
import type { Translation } from '../translation'
|
import type { Translation } from "../translation";
|
||||||
|
|
||||||
export const en: Translation = {
|
export const en: Translation = {
|
||||||
[Key.home]: 'Home',
|
[Key.home]: "Home",
|
||||||
[Key.about]: 'About',
|
[Key.about]: "About",
|
||||||
[Key.archive]: 'Archive',
|
[Key.archive]: "Archive",
|
||||||
[Key.search]: 'Search',
|
[Key.search]: "Search",
|
||||||
|
|
||||||
[Key.tags]: 'Tags',
|
[Key.tags]: "Tags",
|
||||||
[Key.categories]: 'Categories',
|
[Key.categories]: "Categories",
|
||||||
[Key.recentPosts]: 'Recent Posts',
|
[Key.recentPosts]: "Recent Posts",
|
||||||
|
|
||||||
[Key.comments]: 'Comments',
|
[Key.comments]: "Comments",
|
||||||
|
|
||||||
[Key.untitled]: 'Untitled',
|
[Key.untitled]: "Untitled",
|
||||||
[Key.uncategorized]: 'Uncategorized',
|
[Key.uncategorized]: "Uncategorized",
|
||||||
[Key.noTags]: 'No Tags',
|
[Key.noTags]: "No Tags",
|
||||||
|
|
||||||
[Key.wordCount]: 'word',
|
[Key.wordCount]: "word",
|
||||||
[Key.wordsCount]: 'words',
|
[Key.wordsCount]: "words",
|
||||||
[Key.minuteCount]: 'minute',
|
[Key.minuteCount]: "minute",
|
||||||
[Key.minutesCount]: 'minutes',
|
[Key.minutesCount]: "minutes",
|
||||||
[Key.postCount]: 'post',
|
[Key.postCount]: "post",
|
||||||
[Key.postsCount]: 'posts',
|
[Key.postsCount]: "posts",
|
||||||
|
|
||||||
[Key.themeColor]: 'Theme Color',
|
[Key.themeColor]: "Theme Color",
|
||||||
|
|
||||||
[Key.lightMode]: 'Light',
|
[Key.lightMode]: "Light",
|
||||||
[Key.darkMode]: 'Dark',
|
[Key.darkMode]: "Dark",
|
||||||
[Key.systemMode]: 'System',
|
[Key.systemMode]: "System",
|
||||||
|
|
||||||
[Key.more]: 'More',
|
[Key.more]: "More",
|
||||||
|
|
||||||
[Key.author]: 'Author',
|
[Key.author]: "Author",
|
||||||
[Key.publishedAt]: 'Published at',
|
[Key.publishedAt]: "Published at",
|
||||||
[Key.license]: 'License',
|
[Key.license]: "License",
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,38 +1,38 @@
|
|||||||
import Key from '../i18nKey'
|
import Key from "../i18nKey";
|
||||||
import type { Translation } from '../translation'
|
import type { Translation } from "../translation";
|
||||||
|
|
||||||
export const es: Translation = {
|
export const es: Translation = {
|
||||||
[Key.home]: 'Inicio',
|
[Key.home]: "Inicio",
|
||||||
[Key.about]: 'Sobre mí',
|
[Key.about]: "Sobre mí",
|
||||||
[Key.archive]: 'Archivo',
|
[Key.archive]: "Archivo",
|
||||||
[Key.search]: 'Buscar',
|
[Key.search]: "Buscar",
|
||||||
|
|
||||||
[Key.tags]: 'Etiquetas',
|
[Key.tags]: "Etiquetas",
|
||||||
[Key.categories]: 'Categorías',
|
[Key.categories]: "Categorías",
|
||||||
[Key.recentPosts]: 'Publicaciones recientes',
|
[Key.recentPosts]: "Publicaciones recientes",
|
||||||
|
|
||||||
[Key.comments]: 'Comentarios',
|
[Key.comments]: "Comentarios",
|
||||||
|
|
||||||
[Key.untitled]: 'Sin título',
|
[Key.untitled]: "Sin título",
|
||||||
[Key.uncategorized]: 'Sin categoría',
|
[Key.uncategorized]: "Sin categoría",
|
||||||
[Key.noTags]: 'Sin etiquetas',
|
[Key.noTags]: "Sin etiquetas",
|
||||||
|
|
||||||
[Key.wordCount]: 'palabra',
|
[Key.wordCount]: "palabra",
|
||||||
[Key.wordsCount]: 'palabras',
|
[Key.wordsCount]: "palabras",
|
||||||
[Key.minuteCount]: 'minuto',
|
[Key.minuteCount]: "minuto",
|
||||||
[Key.minutesCount]: 'minutos',
|
[Key.minutesCount]: "minutos",
|
||||||
[Key.postCount]: 'publicación',
|
[Key.postCount]: "publicación",
|
||||||
[Key.postsCount]: 'publicaciones',
|
[Key.postsCount]: "publicaciones",
|
||||||
|
|
||||||
[Key.themeColor]: 'Color del tema',
|
[Key.themeColor]: "Color del tema",
|
||||||
|
|
||||||
[Key.lightMode]: 'Claro',
|
[Key.lightMode]: "Claro",
|
||||||
[Key.darkMode]: 'Oscuro',
|
[Key.darkMode]: "Oscuro",
|
||||||
[Key.systemMode]: 'Sistema',
|
[Key.systemMode]: "Sistema",
|
||||||
|
|
||||||
[Key.more]: 'Más',
|
[Key.more]: "Más",
|
||||||
|
|
||||||
[Key.author]: 'Autor',
|
[Key.author]: "Autor",
|
||||||
[Key.publishedAt]: 'Publicado el',
|
[Key.publishedAt]: "Publicado el",
|
||||||
[Key.license]: 'Licencia',
|
[Key.license]: "Licencia",
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,38 +1,38 @@
|
|||||||
import Key from '../i18nKey'
|
import Key from "../i18nKey";
|
||||||
import type { Translation } from '../translation'
|
import type { Translation } from "../translation";
|
||||||
|
|
||||||
export const ja: Translation = {
|
export const ja: Translation = {
|
||||||
[Key.home]: 'Home',
|
[Key.home]: "Home",
|
||||||
[Key.about]: 'About',
|
[Key.about]: "About",
|
||||||
[Key.archive]: 'Archive',
|
[Key.archive]: "Archive",
|
||||||
[Key.search]: '検索',
|
[Key.search]: "検索",
|
||||||
|
|
||||||
[Key.tags]: 'タグ',
|
[Key.tags]: "タグ",
|
||||||
[Key.categories]: 'カテゴリ',
|
[Key.categories]: "カテゴリ",
|
||||||
[Key.recentPosts]: '最近の投稿',
|
[Key.recentPosts]: "最近の投稿",
|
||||||
|
|
||||||
[Key.comments]: 'コメント',
|
[Key.comments]: "コメント",
|
||||||
|
|
||||||
[Key.untitled]: 'タイトルなし',
|
[Key.untitled]: "タイトルなし",
|
||||||
[Key.uncategorized]: 'カテゴリなし',
|
[Key.uncategorized]: "カテゴリなし",
|
||||||
[Key.noTags]: 'タグなし',
|
[Key.noTags]: "タグなし",
|
||||||
|
|
||||||
[Key.wordCount]: '文字',
|
[Key.wordCount]: "文字",
|
||||||
[Key.wordsCount]: '文字',
|
[Key.wordsCount]: "文字",
|
||||||
[Key.minuteCount]: '分',
|
[Key.minuteCount]: "分",
|
||||||
[Key.minutesCount]: '分',
|
[Key.minutesCount]: "分",
|
||||||
[Key.postCount]: '件の投稿',
|
[Key.postCount]: "件の投稿",
|
||||||
[Key.postsCount]: '件の投稿',
|
[Key.postsCount]: "件の投稿",
|
||||||
|
|
||||||
[Key.themeColor]: 'テーマカラー',
|
[Key.themeColor]: "テーマカラー",
|
||||||
|
|
||||||
[Key.lightMode]: 'ライト',
|
[Key.lightMode]: "ライト",
|
||||||
[Key.darkMode]: 'ダーク',
|
[Key.darkMode]: "ダーク",
|
||||||
[Key.systemMode]: 'システム',
|
[Key.systemMode]: "システム",
|
||||||
|
|
||||||
[Key.more]: 'もっと',
|
[Key.more]: "もっと",
|
||||||
|
|
||||||
[Key.author]: '作者',
|
[Key.author]: "作者",
|
||||||
[Key.publishedAt]: '公開日',
|
[Key.publishedAt]: "公開日",
|
||||||
[Key.license]: 'ライセンス',
|
[Key.license]: "ライセンス",
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,38 +1,38 @@
|
|||||||
import Key from '../i18nKey'
|
import Key from "../i18nKey";
|
||||||
import type { Translation } from '../translation'
|
import type { Translation } from "../translation";
|
||||||
|
|
||||||
export const ko: Translation = {
|
export const ko: Translation = {
|
||||||
[Key.home]: '홈',
|
[Key.home]: "홈",
|
||||||
[Key.about]: '소개',
|
[Key.about]: "소개",
|
||||||
[Key.archive]: '아카이브',
|
[Key.archive]: "아카이브",
|
||||||
[Key.search]: '검색',
|
[Key.search]: "검색",
|
||||||
|
|
||||||
[Key.tags]: '태그',
|
[Key.tags]: "태그",
|
||||||
[Key.categories]: '카테고리',
|
[Key.categories]: "카테고리",
|
||||||
[Key.recentPosts]: '최근 게시물',
|
[Key.recentPosts]: "최근 게시물",
|
||||||
|
|
||||||
[Key.comments]: '댓글',
|
[Key.comments]: "댓글",
|
||||||
|
|
||||||
[Key.untitled]: '제목 없음',
|
[Key.untitled]: "제목 없음",
|
||||||
[Key.uncategorized]: '분류되지 않음',
|
[Key.uncategorized]: "분류되지 않음",
|
||||||
[Key.noTags]: '태그 없음',
|
[Key.noTags]: "태그 없음",
|
||||||
|
|
||||||
[Key.wordCount]: '단어',
|
[Key.wordCount]: "단어",
|
||||||
[Key.wordsCount]: '단어',
|
[Key.wordsCount]: "단어",
|
||||||
[Key.minuteCount]: '분',
|
[Key.minuteCount]: "분",
|
||||||
[Key.minutesCount]: '분',
|
[Key.minutesCount]: "분",
|
||||||
[Key.postCount]: '게시물',
|
[Key.postCount]: "게시물",
|
||||||
[Key.postsCount]: '게시물',
|
[Key.postsCount]: "게시물",
|
||||||
|
|
||||||
[Key.themeColor]: '테마 색상',
|
[Key.themeColor]: "테마 색상",
|
||||||
|
|
||||||
[Key.lightMode]: '밝은 모드',
|
[Key.lightMode]: "밝은 모드",
|
||||||
[Key.darkMode]: '어두운 모드',
|
[Key.darkMode]: "어두운 모드",
|
||||||
[Key.systemMode]: '시스템 모드',
|
[Key.systemMode]: "시스템 모드",
|
||||||
|
|
||||||
[Key.more]: '더 보기',
|
[Key.more]: "더 보기",
|
||||||
|
|
||||||
[Key.author]: '저자',
|
[Key.author]: "저자",
|
||||||
[Key.publishedAt]: '게시일',
|
[Key.publishedAt]: "게시일",
|
||||||
[Key.license]: '라이선스',
|
[Key.license]: "라이선스",
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,38 +1,38 @@
|
|||||||
import Key from '../i18nKey'
|
import Key from "../i18nKey";
|
||||||
import type { Translation } from '../translation'
|
import type { Translation } from "../translation";
|
||||||
|
|
||||||
export const th: Translation = {
|
export const th: Translation = {
|
||||||
[Key.home]: 'หน้าแรก',
|
[Key.home]: "หน้าแรก",
|
||||||
[Key.about]: 'เกี่ยวกับ',
|
[Key.about]: "เกี่ยวกับ",
|
||||||
[Key.archive]: 'คลัง',
|
[Key.archive]: "คลัง",
|
||||||
[Key.search]: 'ค้นหา',
|
[Key.search]: "ค้นหา",
|
||||||
|
|
||||||
[Key.tags]: 'ป้ายกำกับ',
|
[Key.tags]: "ป้ายกำกับ",
|
||||||
[Key.categories]: 'หมวดหมู่',
|
[Key.categories]: "หมวดหมู่",
|
||||||
[Key.recentPosts]: 'โพสต์ล่าสุด',
|
[Key.recentPosts]: "โพสต์ล่าสุด",
|
||||||
|
|
||||||
[Key.comments]: 'ความคิดเห็น',
|
[Key.comments]: "ความคิดเห็น",
|
||||||
|
|
||||||
[Key.untitled]: 'ไม่ได้ตั้งชื่อ',
|
[Key.untitled]: "ไม่ได้ตั้งชื่อ",
|
||||||
[Key.uncategorized]: 'ไม่ได้จัดหมวดหมู่',
|
[Key.uncategorized]: "ไม่ได้จัดหมวดหมู่",
|
||||||
[Key.noTags]: 'ไม่มีป้ายกำกับ',
|
[Key.noTags]: "ไม่มีป้ายกำกับ",
|
||||||
|
|
||||||
[Key.wordCount]: 'คำ',
|
[Key.wordCount]: "คำ",
|
||||||
[Key.wordsCount]: 'คำ',
|
[Key.wordsCount]: "คำ",
|
||||||
[Key.minuteCount]: 'นาที',
|
[Key.minuteCount]: "นาที",
|
||||||
[Key.minutesCount]: 'นาที',
|
[Key.minutesCount]: "นาที",
|
||||||
[Key.postCount]: 'โพสต์',
|
[Key.postCount]: "โพสต์",
|
||||||
[Key.postsCount]: 'โพสต์',
|
[Key.postsCount]: "โพสต์",
|
||||||
|
|
||||||
[Key.themeColor]: 'สีของธีม',
|
[Key.themeColor]: "สีของธีม",
|
||||||
|
|
||||||
[Key.lightMode]: 'สว่าง',
|
[Key.lightMode]: "สว่าง",
|
||||||
[Key.darkMode]: 'มืด',
|
[Key.darkMode]: "มืด",
|
||||||
[Key.systemMode]: 'ตามระบบ',
|
[Key.systemMode]: "ตามระบบ",
|
||||||
|
|
||||||
[Key.more]: 'ดูเพิ่ม',
|
[Key.more]: "ดูเพิ่ม",
|
||||||
|
|
||||||
[Key.author]: 'ผู้เขียน',
|
[Key.author]: "ผู้เขียน",
|
||||||
[Key.publishedAt]: 'เผยแพร่เมื่อ',
|
[Key.publishedAt]: "เผยแพร่เมื่อ",
|
||||||
[Key.license]: 'สัญญาอนุญาต',
|
[Key.license]: "สัญญาอนุญาต",
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,38 +1,38 @@
|
|||||||
import Key from '../i18nKey'
|
import Key from "../i18nKey";
|
||||||
import type { Translation } from '../translation'
|
import type { Translation } from "../translation";
|
||||||
|
|
||||||
export const zh_CN: Translation = {
|
export const zh_CN: Translation = {
|
||||||
[Key.home]: '主页',
|
[Key.home]: "主页",
|
||||||
[Key.about]: '关于',
|
[Key.about]: "关于",
|
||||||
[Key.archive]: '归档',
|
[Key.archive]: "归档",
|
||||||
[Key.search]: '搜索',
|
[Key.search]: "搜索",
|
||||||
|
|
||||||
[Key.tags]: '标签',
|
[Key.tags]: "标签",
|
||||||
[Key.categories]: '分类',
|
[Key.categories]: "分类",
|
||||||
[Key.recentPosts]: '最新文章',
|
[Key.recentPosts]: "最新文章",
|
||||||
|
|
||||||
[Key.comments]: '评论',
|
[Key.comments]: "评论",
|
||||||
|
|
||||||
[Key.untitled]: '无标题',
|
[Key.untitled]: "无标题",
|
||||||
[Key.uncategorized]: '未分类',
|
[Key.uncategorized]: "未分类",
|
||||||
[Key.noTags]: '无标签',
|
[Key.noTags]: "无标签",
|
||||||
|
|
||||||
[Key.wordCount]: '字',
|
[Key.wordCount]: "字",
|
||||||
[Key.wordsCount]: '字',
|
[Key.wordsCount]: "字",
|
||||||
[Key.minuteCount]: '分钟',
|
[Key.minuteCount]: "分钟",
|
||||||
[Key.minutesCount]: '分钟',
|
[Key.minutesCount]: "分钟",
|
||||||
[Key.postCount]: '篇文章',
|
[Key.postCount]: "篇文章",
|
||||||
[Key.postsCount]: '篇文章',
|
[Key.postsCount]: "篇文章",
|
||||||
|
|
||||||
[Key.themeColor]: '主题色',
|
[Key.themeColor]: "主题色",
|
||||||
|
|
||||||
[Key.lightMode]: '亮色',
|
[Key.lightMode]: "亮色",
|
||||||
[Key.darkMode]: '暗色',
|
[Key.darkMode]: "暗色",
|
||||||
[Key.systemMode]: '跟随系统',
|
[Key.systemMode]: "跟随系统",
|
||||||
|
|
||||||
[Key.more]: '更多',
|
[Key.more]: "更多",
|
||||||
|
|
||||||
[Key.author]: '作者',
|
[Key.author]: "作者",
|
||||||
[Key.publishedAt]: '发布于',
|
[Key.publishedAt]: "发布于",
|
||||||
[Key.license]: '许可协议',
|
[Key.license]: "许可协议",
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,38 +1,38 @@
|
|||||||
import Key from '../i18nKey'
|
import Key from "../i18nKey";
|
||||||
import type { Translation } from '../translation'
|
import type { Translation } from "../translation";
|
||||||
|
|
||||||
export const zh_TW: Translation = {
|
export const zh_TW: Translation = {
|
||||||
[Key.home]: '首頁',
|
[Key.home]: "首頁",
|
||||||
[Key.about]: '關於',
|
[Key.about]: "關於",
|
||||||
[Key.archive]: '彙整',
|
[Key.archive]: "彙整",
|
||||||
[Key.search]: '搜尋',
|
[Key.search]: "搜尋",
|
||||||
|
|
||||||
[Key.tags]: '標籤',
|
[Key.tags]: "標籤",
|
||||||
[Key.categories]: '分類',
|
[Key.categories]: "分類",
|
||||||
[Key.recentPosts]: '最新文章',
|
[Key.recentPosts]: "最新文章",
|
||||||
|
|
||||||
[Key.comments]: '評論',
|
[Key.comments]: "評論",
|
||||||
|
|
||||||
[Key.untitled]: '無標題',
|
[Key.untitled]: "無標題",
|
||||||
[Key.uncategorized]: '未分類',
|
[Key.uncategorized]: "未分類",
|
||||||
[Key.noTags]: '無標籤',
|
[Key.noTags]: "無標籤",
|
||||||
|
|
||||||
[Key.wordCount]: '字',
|
[Key.wordCount]: "字",
|
||||||
[Key.wordsCount]: '字',
|
[Key.wordsCount]: "字",
|
||||||
[Key.minuteCount]: '分鐘',
|
[Key.minuteCount]: "分鐘",
|
||||||
[Key.minutesCount]: '分鐘',
|
[Key.minutesCount]: "分鐘",
|
||||||
[Key.postCount]: '篇文章',
|
[Key.postCount]: "篇文章",
|
||||||
[Key.postsCount]: '篇文章',
|
[Key.postsCount]: "篇文章",
|
||||||
|
|
||||||
[Key.themeColor]: '主題色',
|
[Key.themeColor]: "主題色",
|
||||||
|
|
||||||
[Key.lightMode]: '亮色',
|
[Key.lightMode]: "亮色",
|
||||||
[Key.darkMode]: '暗色',
|
[Key.darkMode]: "暗色",
|
||||||
[Key.systemMode]: '跟隨系統',
|
[Key.systemMode]: "跟隨系統",
|
||||||
|
|
||||||
[Key.more]: '更多',
|
[Key.more]: "更多",
|
||||||
|
|
||||||
[Key.author]: '作者',
|
[Key.author]: "作者",
|
||||||
[Key.publishedAt]: '發佈於',
|
[Key.publishedAt]: "發佈於",
|
||||||
[Key.license]: '許可協議',
|
[Key.license]: "許可協議",
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,40 +1,40 @@
|
|||||||
import { siteConfig } from '../config'
|
import { siteConfig } from "../config";
|
||||||
import type I18nKey from './i18nKey'
|
import type I18nKey from "./i18nKey";
|
||||||
import { en } from './languages/en'
|
import { en } from "./languages/en";
|
||||||
import { es } from './languages/es'
|
import { es } from "./languages/es";
|
||||||
import { ja } from './languages/ja'
|
import { ja } from "./languages/ja";
|
||||||
import { ko } from './languages/ko'
|
import { ko } from "./languages/ko";
|
||||||
import { th } from './languages/th'
|
import { th } from "./languages/th";
|
||||||
import { zh_CN } from './languages/zh_CN'
|
import { zh_CN } from "./languages/zh_CN";
|
||||||
import { zh_TW } from './languages/zh_TW'
|
import { zh_TW } from "./languages/zh_TW";
|
||||||
|
|
||||||
export type Translation = {
|
export type Translation = {
|
||||||
[K in I18nKey]: string
|
[K in I18nKey]: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
const defaultTranslation = en
|
const defaultTranslation = en;
|
||||||
|
|
||||||
const map: { [key: string]: Translation } = {
|
const map: { [key: string]: Translation } = {
|
||||||
es: es,
|
es: es,
|
||||||
en: en,
|
en: en,
|
||||||
en_us: en,
|
en_us: en,
|
||||||
en_gb: en,
|
en_gb: en,
|
||||||
en_au: en,
|
en_au: en,
|
||||||
zh_cn: zh_CN,
|
zh_cn: zh_CN,
|
||||||
zh_tw: zh_TW,
|
zh_tw: zh_TW,
|
||||||
ja: ja,
|
ja: ja,
|
||||||
ja_jp: ja,
|
ja_jp: ja,
|
||||||
ko: ko,
|
ko: ko,
|
||||||
ko_kr: ko,
|
ko_kr: ko,
|
||||||
th: th,
|
th: th,
|
||||||
th_th: th,
|
th_th: th,
|
||||||
}
|
};
|
||||||
|
|
||||||
export function getTranslation(lang: string): Translation {
|
export function getTranslation(lang: string): Translation {
|
||||||
return map[lang.toLowerCase()] || defaultTranslation
|
return map[lang.toLowerCase()] || defaultTranslation;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function i18n(key: I18nKey): string {
|
export function i18n(key: I18nKey): string {
|
||||||
const lang = siteConfig.lang || 'en'
|
const lang = siteConfig.lang || "en";
|
||||||
return getTranslation(lang)[key]
|
return getTranslation(lang)[key];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,75 +1,75 @@
|
|||||||
---
|
---
|
||||||
import '@fontsource/roboto/400.css'
|
import "@fontsource/roboto/400.css";
|
||||||
import '@fontsource/roboto/500.css'
|
import "@fontsource/roboto/500.css";
|
||||||
import '@fontsource/roboto/700.css'
|
import "@fontsource/roboto/700.css";
|
||||||
|
|
||||||
import { profileConfig, siteConfig } from '@/config'
|
import { profileConfig, siteConfig } from "@/config";
|
||||||
import ConfigCarrier from '@components/ConfigCarrier.astro'
|
import ConfigCarrier from "@components/ConfigCarrier.astro";
|
||||||
import {
|
import {
|
||||||
AUTO_MODE,
|
AUTO_MODE,
|
||||||
BANNER_HEIGHT,
|
BANNER_HEIGHT,
|
||||||
BANNER_HEIGHT_EXTEND,
|
BANNER_HEIGHT_EXTEND,
|
||||||
BANNER_HEIGHT_HOME,
|
BANNER_HEIGHT_HOME,
|
||||||
DARK_MODE,
|
DARK_MODE,
|
||||||
DEFAULT_THEME,
|
DEFAULT_THEME,
|
||||||
LIGHT_MODE,
|
LIGHT_MODE,
|
||||||
PAGE_WIDTH,
|
PAGE_WIDTH,
|
||||||
} from '../constants/constants'
|
} from "../constants/constants";
|
||||||
import { defaultFavicons } from '../constants/icon'
|
import { defaultFavicons } from "../constants/icon";
|
||||||
import type { Favicon } from '../types/config'
|
import type { Favicon } from "../types/config";
|
||||||
import { url, pathsEqual } from '../utils/url-utils'
|
import { url, pathsEqual } from "../utils/url-utils";
|
||||||
import 'katex/dist/katex.css'
|
import "katex/dist/katex.css";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title?: string
|
title?: string;
|
||||||
banner?: string
|
banner?: string;
|
||||||
description?: string
|
description?: string;
|
||||||
lang?: string
|
lang?: string;
|
||||||
setOGTypeArticle?: boolean
|
setOGTypeArticle?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { title, banner, description, lang, setOGTypeArticle } = Astro.props
|
let { title, banner, description, lang, setOGTypeArticle } = Astro.props;
|
||||||
|
|
||||||
// apply a class to the body element to decide the height of the banner, only used for initial page load
|
// apply a class to the body element to decide the height of the banner, only used for initial page load
|
||||||
// Swup can update the body for each page visit, but it's after the page transition, causing a delay for banner height change
|
// Swup can update the body for each page visit, but it's after the page transition, causing a delay for banner height change
|
||||||
// so use Swup hooks instead to change the height immediately when a link is clicked
|
// so use Swup hooks instead to change the height immediately when a link is clicked
|
||||||
const isHomePage = pathsEqual(Astro.url.pathname, url('/'))
|
const isHomePage = pathsEqual(Astro.url.pathname, url("/"));
|
||||||
|
|
||||||
// defines global css variables
|
// defines global css variables
|
||||||
// why doing this in Layout instead of GlobalStyles: https://github.com/withastro/astro/issues/6728#issuecomment-1502203757
|
// why doing this in Layout instead of GlobalStyles: https://github.com/withastro/astro/issues/6728#issuecomment-1502203757
|
||||||
const configHue = siteConfig.themeColor.hue
|
const configHue = siteConfig.themeColor.hue;
|
||||||
if (!banner || typeof banner !== 'string' || banner.trim() === '') {
|
if (!banner || typeof banner !== "string" || banner.trim() === "") {
|
||||||
banner = siteConfig.banner.src
|
banner = siteConfig.banner.src;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO don't use post cover as banner for now
|
// TODO don't use post cover as banner for now
|
||||||
banner = siteConfig.banner.src
|
banner = siteConfig.banner.src;
|
||||||
|
|
||||||
const enableBanner = siteConfig.banner.enable
|
const enableBanner = siteConfig.banner.enable;
|
||||||
|
|
||||||
let pageTitle: string
|
let pageTitle: string;
|
||||||
if (title) {
|
if (title) {
|
||||||
pageTitle = `${title} - ${siteConfig.title}`
|
pageTitle = `${title} - ${siteConfig.title}`;
|
||||||
} else {
|
} else {
|
||||||
pageTitle = `${siteConfig.title} - ${siteConfig.subtitle}`
|
pageTitle = `${siteConfig.title} - ${siteConfig.subtitle}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const favicons: Favicon[] =
|
const favicons: Favicon[] =
|
||||||
siteConfig.favicon.length > 0 ? siteConfig.favicon : defaultFavicons
|
siteConfig.favicon.length > 0 ? siteConfig.favicon : defaultFavicons;
|
||||||
|
|
||||||
// const siteLang = siteConfig.lang.replace('_', '-')
|
// const siteLang = siteConfig.lang.replace('_', '-')
|
||||||
if (!lang) {
|
if (!lang) {
|
||||||
lang = `${siteConfig.lang}`
|
lang = `${siteConfig.lang}`;
|
||||||
}
|
}
|
||||||
const siteLang = lang.replace('_', '-')
|
const siteLang = lang.replace("_", "-");
|
||||||
|
|
||||||
const bannerOffsetByPosition = {
|
const bannerOffsetByPosition = {
|
||||||
top: `${BANNER_HEIGHT_EXTEND}vh`,
|
top: `${BANNER_HEIGHT_EXTEND}vh`,
|
||||||
center: `${BANNER_HEIGHT_EXTEND / 2}vh`,
|
center: `${BANNER_HEIGHT_EXTEND / 2}vh`,
|
||||||
bottom: '0',
|
bottom: "0",
|
||||||
}
|
};
|
||||||
const bannerOffset =
|
const bannerOffset =
|
||||||
bannerOffsetByPosition[siteConfig.banner.position || 'center']
|
bannerOffsetByPosition[siteConfig.banner.position || "center"];
|
||||||
---
|
---
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
|||||||
@@ -1,31 +1,44 @@
|
|||||||
---
|
---
|
||||||
import Footer from '@components/Footer.astro'
|
import Footer from "@components/Footer.astro";
|
||||||
import Navbar from '@components/Navbar.astro'
|
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 type { MarkdownHeading } from "astro";
|
||||||
import { Icon } from 'astro-icon/components'
|
import { Icon } from "astro-icon/components";
|
||||||
import { siteConfig } from '../config'
|
|
||||||
import type { MarkdownHeading } from 'astro'
|
|
||||||
import TOC from "../components/widget/TOC.astro";
|
|
||||||
import ImageWrapper from "../components/misc/ImageWrapper.astro";
|
import ImageWrapper from "../components/misc/ImageWrapper.astro";
|
||||||
import {BANNER_HEIGHT, BANNER_HEIGHT_EXTEND, MAIN_PANEL_OVERLAPS_BANNER_HEIGHT} from "../constants/constants";
|
import TOC from "../components/widget/TOC.astro";
|
||||||
|
import { siteConfig } from "../config";
|
||||||
|
import {
|
||||||
|
BANNER_HEIGHT,
|
||||||
|
BANNER_HEIGHT_EXTEND,
|
||||||
|
MAIN_PANEL_OVERLAPS_BANNER_HEIGHT,
|
||||||
|
} from "../constants/constants";
|
||||||
|
import Layout from "./Layout.astro";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title?: string
|
title?: string;
|
||||||
banner?: string
|
banner?: string;
|
||||||
description?: string
|
description?: string;
|
||||||
lang?: string
|
lang?: string;
|
||||||
setOGTypeArticle?: boolean;
|
setOGTypeArticle?: boolean;
|
||||||
headings? : MarkdownHeading[]
|
headings?: MarkdownHeading[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const { title, banner, description, lang, setOGTypeArticle, headings = [] } = Astro.props
|
const {
|
||||||
|
title,
|
||||||
|
banner,
|
||||||
|
description,
|
||||||
|
lang,
|
||||||
|
setOGTypeArticle,
|
||||||
|
headings = [],
|
||||||
|
} = Astro.props;
|
||||||
const hasBannerCredit =
|
const hasBannerCredit =
|
||||||
siteConfig.banner.enable && siteConfig.banner.credit.enable
|
siteConfig.banner.enable && siteConfig.banner.credit.enable;
|
||||||
const hasBannerLink = !!siteConfig.banner.credit.url
|
const hasBannerLink = !!siteConfig.banner.credit.url;
|
||||||
|
|
||||||
const mainPanelTop = siteConfig.banner.enable ? `calc(${BANNER_HEIGHT}vh - ${MAIN_PANEL_OVERLAPS_BANNER_HEIGHT}rem)` : "5.5rem"
|
const mainPanelTop = siteConfig.banner.enable
|
||||||
|
? `calc(${BANNER_HEIGHT}vh - ${MAIN_PANEL_OVERLAPS_BANNER_HEIGHT}rem)`
|
||||||
|
: "5.5rem";
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title={title} banner={banner} description={description} lang={lang} setOGTypeArticle={setOGTypeArticle}>
|
<Layout title={title} banner={banner} description={description} lang={lang} setOGTypeArticle={setOGTypeArticle}>
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
---
|
---
|
||||||
import type { GetStaticPaths } from 'astro'
|
import type { GetStaticPaths } from "astro";
|
||||||
import PostPage from '../components/PostPage.astro'
|
import PostPage from "../components/PostPage.astro";
|
||||||
import Pagination from '../components/control/Pagination.astro'
|
import Pagination from "../components/control/Pagination.astro";
|
||||||
import { PAGE_SIZE } from '../constants/constants'
|
import { PAGE_SIZE } from "../constants/constants";
|
||||||
import MainGridLayout from '../layouts/MainGridLayout.astro'
|
import MainGridLayout from "../layouts/MainGridLayout.astro";
|
||||||
import { getSortedPosts } from '../utils/content-utils'
|
import { getSortedPosts } from "../utils/content-utils";
|
||||||
|
|
||||||
export const getStaticPaths = (async ({ paginate }) => {
|
export const getStaticPaths = (async ({ paginate }) => {
|
||||||
const allBlogPosts = await getSortedPosts()
|
const allBlogPosts = await getSortedPosts();
|
||||||
return paginate(allBlogPosts, { pageSize: PAGE_SIZE })
|
return paginate(allBlogPosts, { pageSize: PAGE_SIZE });
|
||||||
}) satisfies GetStaticPaths
|
}) satisfies GetStaticPaths;
|
||||||
// https://github.com/withastro/astro/issues/6507#issuecomment-1489916992
|
// https://github.com/withastro/astro/issues/6507#issuecomment-1489916992
|
||||||
|
|
||||||
const { page } = Astro.props
|
const { page } = Astro.props;
|
||||||
|
|
||||||
const len = page.data.length
|
const len = page.data.length;
|
||||||
---
|
---
|
||||||
|
|
||||||
<MainGridLayout>
|
<MainGridLayout>
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
---
|
---
|
||||||
|
|
||||||
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 { render } from "astro:content";
|
||||||
import I18nKey from '../i18n/i18nKey'
|
import Markdown from "@components/misc/Markdown.astro";
|
||||||
import Markdown from '@components/misc/Markdown.astro'
|
import I18nKey from "../i18n/i18nKey";
|
||||||
import { render } from 'astro:content'
|
import { i18n } from "../i18n/translation";
|
||||||
|
|
||||||
const aboutPost = await getEntry('spec', 'about')
|
const aboutPost = await getEntry("spec", "about");
|
||||||
|
|
||||||
if (!aboutPost) {
|
if (!aboutPost) {
|
||||||
throw new Error("About page content not found");
|
throw new Error("About page content not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
const { Content } = await render(aboutPost)
|
const { Content } = await render(aboutPost);
|
||||||
---
|
---
|
||||||
<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">
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
---
|
---
|
||||||
import { getCategoryList } from '@utils/content-utils'
|
import ArchivePanel from "@components/ArchivePanel.astro";
|
||||||
import MainGridLayout from '@layouts/MainGridLayout.astro'
|
import I18nKey from "@i18n/i18nKey";
|
||||||
import ArchivePanel from '@components/ArchivePanel.astro'
|
import { i18n } from "@i18n/translation";
|
||||||
import { i18n } from '@i18n/translation'
|
import MainGridLayout from "@layouts/MainGridLayout.astro";
|
||||||
import I18nKey from '@i18n/i18nKey'
|
import { getCategoryList } from "@utils/content-utils";
|
||||||
|
|
||||||
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)}>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
---
|
---
|
||||||
import MainGridLayout from '@layouts/MainGridLayout.astro'
|
import ArchivePanel from "@components/ArchivePanel.astro";
|
||||||
import ArchivePanel from '@components/ArchivePanel.astro'
|
import { UNCATEGORIZED } from "@constants/constants";
|
||||||
import { i18n } from '@i18n/translation'
|
import I18nKey from "@i18n/i18nKey";
|
||||||
import I18nKey from '@i18n/i18nKey'
|
import { i18n } from "@i18n/translation";
|
||||||
import { UNCATEGORIZED } from '@constants/constants'
|
import MainGridLayout from "@layouts/MainGridLayout.astro";
|
||||||
---
|
---
|
||||||
|
|
||||||
<MainGridLayout title={i18n(I18nKey.archive)}>
|
<MainGridLayout title={i18n(I18nKey.archive)}>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
import MainGridLayout from '@layouts/MainGridLayout.astro'
|
import ArchivePanel from "@components/ArchivePanel.astro";
|
||||||
import ArchivePanel from '@components/ArchivePanel.astro'
|
import I18nKey from "@i18n/i18nKey";
|
||||||
import { i18n } from '@i18n/translation'
|
import { i18n } from "@i18n/translation";
|
||||||
import I18nKey from '@i18n/i18nKey'
|
import MainGridLayout from "@layouts/MainGridLayout.astro";
|
||||||
---
|
---
|
||||||
|
|
||||||
<MainGridLayout title={i18n(I18nKey.archive)}>
|
<MainGridLayout title={i18n(I18nKey.archive)}>
|
||||||
|
|||||||
@@ -1,30 +1,30 @@
|
|||||||
---
|
---
|
||||||
import ArchivePanel from '@components/ArchivePanel.astro'
|
import ArchivePanel from "@components/ArchivePanel.astro";
|
||||||
import I18nKey from '@i18n/i18nKey'
|
import I18nKey from "@i18n/i18nKey";
|
||||||
import { i18n } from '@i18n/translation'
|
import { i18n } from "@i18n/translation";
|
||||||
import MainGridLayout from '@layouts/MainGridLayout.astro'
|
import MainGridLayout from "@layouts/MainGridLayout.astro";
|
||||||
import { getSortedPosts } from '@utils/content-utils'
|
import { getSortedPosts } from "@utils/content-utils";
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
const posts = await getSortedPosts()
|
const posts = await getSortedPosts();
|
||||||
|
|
||||||
// タグを集めるための Set の型を指定
|
// タグを集めるための Set の型を指定
|
||||||
const allTags = posts.reduce<Set<string>>((acc, post) => {
|
const allTags = posts.reduce<Set<string>>((acc, post) => {
|
||||||
// biome-ignore lint/complexity/noForEach: <explanation>
|
// biome-ignore lint/complexity/noForEach: <explanation>
|
||||||
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) => ({
|
||||||
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)}>
|
||||||
|
|||||||
@@ -1,47 +1,49 @@
|
|||||||
---
|
---
|
||||||
import path from 'node:path'
|
import path from "node:path";
|
||||||
import License from '@components/misc/License.astro'
|
import License from "@components/misc/License.astro";
|
||||||
import Markdown from '@components/misc/Markdown.astro'
|
import Markdown from "@components/misc/Markdown.astro";
|
||||||
import I18nKey from '@i18n/i18nKey'
|
import I18nKey from "@i18n/i18nKey";
|
||||||
import { i18n } from '@i18n/translation'
|
import { i18n } from "@i18n/translation";
|
||||||
import MainGridLayout from '@layouts/MainGridLayout.astro'
|
import MainGridLayout from "@layouts/MainGridLayout.astro";
|
||||||
import { getDir, getPostUrlBySlug } from '@utils/url-utils'
|
import { getSortedPosts } from "@utils/content-utils";
|
||||||
import { Icon } from 'astro-icon/components'
|
import { getDir, getPostUrlBySlug } from "@utils/url-utils";
|
||||||
import { licenseConfig } from 'src/config'
|
import { Icon } from "astro-icon/components";
|
||||||
import PostMetadata from '../../components/PostMeta.astro'
|
import { licenseConfig } from "src/config";
|
||||||
import ImageWrapper from '../../components/misc/ImageWrapper.astro'
|
import PostMetadata from "../../components/PostMeta.astro";
|
||||||
import { profileConfig, siteConfig } from '../../config'
|
import ImageWrapper from "../../components/misc/ImageWrapper.astro";
|
||||||
import { formatDateToYYYYMMDD } from '../../utils/date-utils'
|
import { profileConfig, siteConfig } from "../../config";
|
||||||
import { getSortedPosts } from '@utils/content-utils'
|
import { formatDateToYYYYMMDD } from "../../utils/date-utils";
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
const blogEntries = await getSortedPosts();
|
const blogEntries = await getSortedPosts();
|
||||||
return blogEntries.map(entry => ({
|
return blogEntries.map((entry) => ({
|
||||||
params: { slug: entry.slug },
|
params: { slug: entry.slug },
|
||||||
props: { entry },
|
props: { entry },
|
||||||
}))
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
const { entry } = Astro.props
|
const { entry } = Astro.props;
|
||||||
const { Content, headings } = await entry.render()
|
const { Content, headings } = await entry.render();
|
||||||
|
|
||||||
const { remarkPluginFrontmatter } = await entry.render()
|
const { remarkPluginFrontmatter } = await entry.render();
|
||||||
|
|
||||||
const jsonLd = {
|
const jsonLd = {
|
||||||
'@context': 'https://schema.org',
|
"@context": "https://schema.org",
|
||||||
'@type': 'BlogPosting',
|
"@type": "BlogPosting",
|
||||||
headline: entry.data.title,
|
headline: entry.data.title,
|
||||||
description: entry.data.description || entry.data.title,
|
description: entry.data.description || entry.data.title,
|
||||||
keywords: entry.data.tags,
|
keywords: entry.data.tags,
|
||||||
author: {
|
author: {
|
||||||
'@type': 'Person',
|
"@type": "Person",
|
||||||
name: profileConfig.name,
|
name: profileConfig.name,
|
||||||
url: Astro.site,
|
url: Astro.site,
|
||||||
},
|
},
|
||||||
datePublished: formatDateToYYYYMMDD(entry.data.published),
|
datePublished: formatDateToYYYYMMDD(entry.data.published),
|
||||||
inLanguage: (entry.data.lang ? entry.data.lang.replace('_', '-') : siteConfig.lang.replace('_', '-')),
|
inLanguage: entry.data.lang
|
||||||
// TODO include cover image here
|
? entry.data.lang.replace("_", "-")
|
||||||
}
|
: siteConfig.lang.replace("_", "-"),
|
||||||
|
// TODO include cover image here
|
||||||
|
};
|
||||||
---
|
---
|
||||||
<MainGridLayout banner={entry.data.image} title={entry.data.title} description={entry.data.description} lang={entry.data.lang} setOGTypeArticle={true} headings={headings}>
|
<MainGridLayout banner={entry.data.image} title={entry.data.title} description={entry.data.description} lang={entry.data.lang} setOGTypeArticle={true} headings={headings}>
|
||||||
<script is:inline slot="head" type="application/ld+json" set:html={JSON.stringify(jsonLd)}></script>
|
<script is:inline slot="head" type="application/ld+json" set:html={JSON.stringify(jsonLd)}></script>
|
||||||
|
|||||||
@@ -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",
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,33 +1,33 @@
|
|||||||
import { siteConfig } from '@/config'
|
import { siteConfig } from "@/config";
|
||||||
import rss from '@astrojs/rss'
|
import rss from "@astrojs/rss";
|
||||||
import { getSortedPosts } from '@utils/content-utils'
|
import { getSortedPosts } from "@utils/content-utils";
|
||||||
import type { APIContext } from 'astro'
|
import type { APIContext } from "astro";
|
||||||
import MarkdownIt from 'markdown-it'
|
import MarkdownIt from "markdown-it";
|
||||||
import sanitizeHtml from 'sanitize-html'
|
import sanitizeHtml from "sanitize-html";
|
||||||
|
|
||||||
const parser = new MarkdownIt()
|
const parser = new MarkdownIt();
|
||||||
|
|
||||||
export async function GET(context: APIContext) {
|
export async function GET(context: APIContext) {
|
||||||
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 ?? 'https://fuwari.vercel.app',
|
site: context.site ?? "https://fuwari.vercel.app",
|
||||||
items: blog.map(post => {
|
items: blog.map((post) => {
|
||||||
const content =
|
const content =
|
||||||
typeof post.body === 'string' ? post.body : String(post.body || '')
|
typeof post.body === "string" ? post.body : String(post.body || "");
|
||||||
|
|
||||||
return {
|
return {
|
||||||
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(content), {
|
content: sanitizeHtml(parser.render(content), {
|
||||||
allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img']),
|
allowedTags: sanitizeHtml.defaults.allowedTags.concat(["img"]),
|
||||||
}),
|
}),
|
||||||
}
|
};
|
||||||
}),
|
}),
|
||||||
customData: `<language>${siteConfig.lang}</language>`,
|
customData: `<language>${siteConfig.lang}</language>`,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/// <reference types="mdast" />
|
/// <reference types="mdast" />
|
||||||
import { h } from 'hastscript'
|
import { h } from "hastscript";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an admonition component.
|
* Creates an admonition component.
|
||||||
@@ -11,23 +11,23 @@ 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(
|
return h(
|
||||||
'div',
|
"div",
|
||||||
{ class: 'hidden' },
|
{ class: "hidden" },
|
||||||
'Invalid admonition directive. (Admonition directives must be of block type ":::note{name="name"} <content> :::")',
|
'Invalid admonition directive. (Admonition directives must be of block type ":::note{name="name"} <content> :::")',
|
||||||
)
|
);
|
||||||
|
|
||||||
let label = null
|
let label = null;
|
||||||
if (properties?.['has-directive-label']) {
|
if (properties?.["has-directive-label"]) {
|
||||||
label = children[0] // The first child is the label
|
label = children[0]; // The first child is the label
|
||||||
// biome-ignore lint/style/noParameterAssign: <explanation>
|
// biome-ignore lint/style/noParameterAssign: <explanation>
|
||||||
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", { class: `admonition bdm-${type}` }, [
|
return h("blockquote", { class: `admonition bdm-${type}` }, [
|
||||||
h('span', { class: "bdm-title" }, label ? label : type.toUpperCase()),
|
h("span", { class: "bdm-title" }, label ? label : type.toUpperCase()),
|
||||||
...children,
|
...children,
|
||||||
])
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/// <reference types="mdast" />
|
/// <reference types="mdast" />
|
||||||
import { h } from 'hastscript'
|
import { h } from "hastscript";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a GitHub Card component.
|
* Creates a GitHub Card component.
|
||||||
@@ -10,54 +10,54 @@ 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', { class: 'hidden' }, [
|
return h("div", { 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(
|
return h(
|
||||||
'div',
|
"div",
|
||||||
{ class: 'hidden' },
|
{ class: "hidden" },
|
||||||
'Invalid repository. ("repo" attributte must be in the format "owner/repo")',
|
'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(`div#${cardUuid}-avatar`, { class: 'gc-avatar' })
|
const nAvatar = h(`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("div", { class: 'gc-titlebar' }, [
|
const nTitle = h("div", { class: "gc-titlebar" }, [
|
||||||
h('div', { class: 'gc-titlebar-left' }, [
|
h("div", { class: "gc-titlebar-left" }, [
|
||||||
h('div', { class: 'gc-owner' }, [
|
h("div", { class: "gc-owner" }, [
|
||||||
nAvatar,
|
nAvatar,
|
||||||
h('div', { class: 'gc-user' }, repo.split('/')[0]),
|
h("div", { class: "gc-user" }, repo.split("/")[0]),
|
||||||
]),
|
]),
|
||||||
h('div', { class: 'gc-divider' }, '/'),
|
h("div", { class: "gc-divider" }, "/"),
|
||||||
h('div', { class: 'gc-repo' }, repo.split('/')[1]),
|
h("div", { class: "gc-repo" }, repo.split("/")[1]),
|
||||||
]),
|
]),
|
||||||
h('div', { class: 'github-logo' }),
|
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(`div#${cardUuid}-stars`, { class: 'gc-stars' }, '00K')
|
const nStars = h(`div#${cardUuid}-stars`, { class: "gc-stars" }, "00K");
|
||||||
const nForks = h(`div#${cardUuid}-forks`, { class: 'gc-forks' }, '0K')
|
const nForks = h(`div#${cardUuid}-forks`, { class: "gc-forks" }, "0K");
|
||||||
const nLicense = h(`div#${cardUuid}-license`, { class: 'gc-license' }, '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 => {
|
||||||
if (data.description) {
|
if (data.description) {
|
||||||
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, '');
|
||||||
@@ -83,21 +83,21 @@ export function GithubCardComponent(properties, children) {
|
|||||||
console.warn("[GITHUB-CARD] (Error) Loading card for ${repo} | ${cardUuid}.")
|
console.warn("[GITHUB-CARD] (Error) Loading card for ${repo} | ${cardUuid}.")
|
||||||
})
|
})
|
||||||
`,
|
`,
|
||||||
)
|
);
|
||||||
|
|
||||||
return h(
|
return h(
|
||||||
`a#${cardUuid}-card`,
|
`a#${cardUuid}-card`,
|
||||||
{
|
{
|
||||||
class: 'card-github fetch-waiting no-styling',
|
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', { class: 'gc-infobar' }, [nStars, nForks, nLicense, nLanguage]),
|
h("div", { class: "gc-infobar" }, [nStars, nForks, nLicense, nLanguage]),
|
||||||
nScript,
|
nScript,
|
||||||
],
|
],
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,30 @@
|
|||||||
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, node => {
|
visit(tree, (node) => {
|
||||||
if (
|
if (
|
||||||
node.type === 'containerDirective' ||
|
node.type === "containerDirective" ||
|
||||||
node.type === 'leafDirective' ||
|
node.type === "leafDirective" ||
|
||||||
node.type === 'textDirective'
|
node.type === "textDirective"
|
||||||
) {
|
) {
|
||||||
// biome-ignore lint/suspicious/noAssignInExpressions: <explanation>
|
// biome-ignore lint/suspicious/noAssignInExpressions: <explanation>
|
||||||
const data = node.data || (node.data = {})
|
const data = node.data || (node.data = {});
|
||||||
node.attributes = node.attributes || {}
|
node.attributes = node.attributes || {};
|
||||||
if (
|
if (
|
||||||
node.children.length > 0 &&
|
node.children.length > 0 &&
|
||||||
node.children[0].data &&
|
node.children[0].data &&
|
||||||
node.children[0].data.directiveLabel
|
node.children[0].data.directiveLabel
|
||||||
) {
|
) {
|
||||||
// Add a flag to the node to indicate that it has a directive label
|
// Add a flag to the node to indicate that it has a directive label
|
||||||
node.attributes['has-directive-label'] = true
|
node.attributes["has-directive-label"] = true;
|
||||||
}
|
}
|
||||||
const hast = h(node.name, node.attributes)
|
const hast = h(node.name, node.attributes);
|
||||||
|
|
||||||
data.hName = hast.tagName
|
data.hName = hast.tagName;
|
||||||
data.hProperties = hast.properties
|
data.hProperties = hast.properties;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
// biome-ignore lint/suspicious/noShadowRestrictedNames: <explanation>
|
// biome-ignore lint/suspicious/noShadowRestrictedNames: <explanation>
|
||||||
import { toString } from 'mdast-util-to-string'
|
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 (const node of tree.children) {
|
for (const 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;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
// biome-ignore lint/suspicious/noShadowRestrictedNames: <explanation>
|
// biome-ignore lint/suspicious/noShadowRestrictedNames: <explanation>
|
||||||
import { toString } from 'mdast-util-to-string'
|
import { toString } from "mdast-util-to-string";
|
||||||
import getReadingTime from 'reading-time'
|
import getReadingTime from "reading-time";
|
||||||
|
|
||||||
export function remarkReadingTime() {
|
export function remarkReadingTime() {
|
||||||
return (tree, { data }) => {
|
return (tree, { data }) => {
|
||||||
const textOnPage = toString(tree)
|
const textOnPage = toString(tree);
|
||||||
const readingTime = getReadingTime(textOnPage)
|
const readingTime = getReadingTime(textOnPage);
|
||||||
data.astro.frontmatter.minutes = Math.max(
|
data.astro.frontmatter.minutes = Math.max(
|
||||||
1,
|
1,
|
||||||
Math.round(readingTime.minutes),
|
Math.round(readingTime.minutes),
|
||||||
)
|
);
|
||||||
data.astro.frontmatter.words = readingTime.words
|
data.astro.frontmatter.words = readingTime.words;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,88 +1,88 @@
|
|||||||
import type { AUTO_MODE, DARK_MODE, LIGHT_MODE } from '@constants/constants'
|
import type { AUTO_MODE, DARK_MODE, LIGHT_MODE } from "@constants/constants";
|
||||||
|
|
||||||
export type SiteConfig = {
|
export type SiteConfig = {
|
||||||
title: string
|
title: string;
|
||||||
subtitle: string
|
subtitle: string;
|
||||||
|
|
||||||
lang: string
|
lang: string;
|
||||||
|
|
||||||
themeColor: {
|
themeColor: {
|
||||||
hue: number
|
hue: number;
|
||||||
fixed: boolean
|
fixed: boolean;
|
||||||
}
|
};
|
||||||
banner: {
|
banner: {
|
||||||
enable: boolean
|
enable: boolean;
|
||||||
src: string
|
src: string;
|
||||||
position?: 'top' | 'center' | 'bottom'
|
position?: "top" | "center" | "bottom";
|
||||||
credit: {
|
credit: {
|
||||||
enable: boolean
|
enable: boolean;
|
||||||
text: string
|
text: string;
|
||||||
url?: string
|
url?: string;
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
toc: {
|
toc: {
|
||||||
enable: boolean
|
enable: boolean;
|
||||||
depth: 1 | 2 | 3
|
depth: 1 | 2 | 3;
|
||||||
}
|
};
|
||||||
|
|
||||||
favicon: Favicon[]
|
favicon: Favicon[];
|
||||||
}
|
};
|
||||||
|
|
||||||
export type Favicon = {
|
export type Favicon = {
|
||||||
src: string
|
src: string;
|
||||||
theme?: 'light' | 'dark'
|
theme?: "light" | "dark";
|
||||||
sizes?: string
|
sizes?: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export enum LinkPreset {
|
export enum LinkPreset {
|
||||||
Home = 0,
|
Home = 0,
|
||||||
Archive = 1,
|
Archive = 1,
|
||||||
About = 2,
|
About = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NavBarLink = {
|
export type NavBarLink = {
|
||||||
name: string
|
name: string;
|
||||||
url: string
|
url: string;
|
||||||
external?: boolean
|
external?: boolean;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type NavBarConfig = {
|
export type NavBarConfig = {
|
||||||
links: (NavBarLink | LinkPreset)[]
|
links: (NavBarLink | LinkPreset)[];
|
||||||
}
|
};
|
||||||
|
|
||||||
export type ProfileConfig = {
|
export type ProfileConfig = {
|
||||||
avatar?: string
|
avatar?: string;
|
||||||
name: string
|
name: string;
|
||||||
bio?: string
|
bio?: string;
|
||||||
links: {
|
links: {
|
||||||
name: string
|
name: string;
|
||||||
url: string
|
url: string;
|
||||||
icon: string
|
icon: string;
|
||||||
}[]
|
}[];
|
||||||
}
|
};
|
||||||
|
|
||||||
export type LicenseConfig = {
|
export type LicenseConfig = {
|
||||||
enable: boolean
|
enable: boolean;
|
||||||
name: string
|
name: string;
|
||||||
url: string
|
url: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type LIGHT_DARK_MODE =
|
export type LIGHT_DARK_MODE =
|
||||||
| typeof LIGHT_MODE
|
| typeof LIGHT_MODE
|
||||||
| typeof DARK_MODE
|
| typeof DARK_MODE
|
||||||
| typeof AUTO_MODE
|
| typeof AUTO_MODE;
|
||||||
|
|
||||||
export type BlogPostData = {
|
export type BlogPostData = {
|
||||||
body: string
|
body: string;
|
||||||
title: string
|
title: string;
|
||||||
published: Date
|
published: Date;
|
||||||
description: string
|
description: string;
|
||||||
tags: string[]
|
tags: string[];
|
||||||
draft?: boolean
|
draft?: boolean;
|
||||||
image?: string
|
image?: string;
|
||||||
category?: string
|
category?: string;
|
||||||
prevTitle?: string
|
prevTitle?: string;
|
||||||
prevSlug?: string
|
prevSlug?: string;
|
||||||
nextTitle?: string
|
nextTitle?: string;
|
||||||
nextSlug?: string
|
nextSlug?: string;
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,86 +1,84 @@
|
|||||||
import { getCollection } from 'astro:content'
|
import { getCollection } from "astro:content";
|
||||||
import I18nKey from '@i18n/i18nKey'
|
import I18nKey from "@i18n/i18nKey";
|
||||||
import { i18n } from '@i18n/translation'
|
import { i18n } from "@i18n/translation";
|
||||||
|
|
||||||
export async function getSortedPosts() {
|
export async function getSortedPosts() {
|
||||||
const allBlogPosts = (await getCollection('posts', ({ data }) => {
|
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(
|
const sorted = allBlogPosts.sort((a, b) => {
|
||||||
(a, b) => {
|
const dateA = new Date(a.data.published);
|
||||||
const dateA = new Date(a.data.published)
|
const dateB = new Date(b.data.published);
|
||||||
const dateB = new Date(b.data.published)
|
return dateA > dateB ? -1 : 1;
|
||||||
return dateA > dateB ? -1 : 1
|
});
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
for (let i = 1; i < sorted.length; i++) {
|
for (let i = 1; i < sorted.length; i++) {
|
||||||
sorted[i].data.nextSlug = sorted[i - 1].slug
|
sorted[i].data.nextSlug = sorted[i - 1].slug;
|
||||||
sorted[i].data.nextTitle = sorted[i - 1].data.title
|
sorted[i].data.nextTitle = sorted[i - 1].data.title;
|
||||||
}
|
}
|
||||||
for (let i = 0; i < sorted.length - 1; i++) {
|
for (let i = 0; i < sorted.length - 1; i++) {
|
||||||
sorted[i].data.prevSlug = sorted[i + 1].slug
|
sorted[i].data.prevSlug = sorted[i + 1].slug;
|
||||||
sorted[i].data.prevTitle = sorted[i + 1].data.title
|
sorted[i].data.prevTitle = sorted[i + 1].data.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sorted
|
return sorted;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Tag = {
|
export type Tag = {
|
||||||
name: string
|
name: string;
|
||||||
count: number
|
count: number;
|
||||||
}
|
};
|
||||||
|
|
||||||
export async function getTagList(): Promise<Tag[]> {
|
export async function getTagList(): Promise<Tag[]> {
|
||||||
const allBlogPosts = await getCollection<'posts'>('posts', ({ data }) => {
|
const allBlogPosts = await getCollection<"posts">("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 } = {}
|
const countMap: { [key: string]: number } = {};
|
||||||
allBlogPosts.map((post: { data: { tags: string[] } }) => {
|
allBlogPosts.map((post: { data: { tags: string[] } }) => {
|
||||||
post.data.tags.map((tag: string) => {
|
post.data.tags.map((tag: string) => {
|
||||||
if (!countMap[tag]) countMap[tag] = 0
|
if (!countMap[tag]) countMap[tag] = 0;
|
||||||
countMap[tag]++
|
countMap[tag]++;
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
// sort tags
|
// sort tags
|
||||||
const keys: string[] = Object.keys(countMap).sort((a, b) => {
|
const keys: string[] = Object.keys(countMap).sort((a, b) => {
|
||||||
return a.toLowerCase().localeCompare(b.toLowerCase())
|
return a.toLowerCase().localeCompare(b.toLowerCase());
|
||||||
})
|
});
|
||||||
|
|
||||||
return keys.map(key => ({ name: key, count: countMap[key] }))
|
return keys.map((key) => ({ name: key, count: countMap[key] }));
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Category = {
|
export type Category = {
|
||||||
name: string
|
name: string;
|
||||||
count: number
|
count: number;
|
||||||
}
|
};
|
||||||
|
|
||||||
export async function getCategoryList(): Promise<Category[]> {
|
export async function getCategoryList(): Promise<Category[]> {
|
||||||
const allBlogPosts = await getCollection<'posts'>('posts', ({ data }) => {
|
const allBlogPosts = await getCollection<"posts">("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 } = {}
|
const count: { [key: string]: number } = {};
|
||||||
allBlogPosts.map((post: { data: { category: string | number } }) => {
|
allBlogPosts.map((post: { data: { category: string | number } }) => {
|
||||||
if (!post.data.category) {
|
if (!post.data.category) {
|
||||||
const ucKey = i18n(I18nKey.uncategorized)
|
const ucKey = i18n(I18nKey.uncategorized);
|
||||||
count[ucKey] = count[ucKey] ? count[ucKey] + 1 : 1
|
count[ucKey] = count[ucKey] ? count[ucKey] + 1 : 1;
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
count[post.data.category] = count[post.data.category]
|
count[post.data.category] = count[post.data.category]
|
||||||
? count[post.data.category] + 1
|
? count[post.data.category] + 1
|
||||||
: 1
|
: 1;
|
||||||
})
|
});
|
||||||
|
|
||||||
const lst = Object.keys(count).sort((a, b) => {
|
const lst = Object.keys(count).sort((a, b) => {
|
||||||
return a.toLowerCase().localeCompare(b.toLowerCase())
|
return a.toLowerCase().localeCompare(b.toLowerCase());
|
||||||
})
|
});
|
||||||
|
|
||||||
const ret: Category[] = []
|
const ret: Category[] = [];
|
||||||
for (const c of lst) {
|
for (const c of lst) {
|
||||||
ret.push({ name: c, count: count[c] })
|
ret.push({ name: c, count: count[c] });
|
||||||
}
|
}
|
||||||
return ret
|
return ret;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
export function formatDateToYYYYMMDD(date: Date): string {
|
export function formatDateToYYYYMMDD(date: Date): string {
|
||||||
return date.toISOString().substring(0, 10)
|
return date.toISOString().substring(0, 10);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,54 +1,54 @@
|
|||||||
import type { LIGHT_DARK_MODE } from '@/types/config'
|
import type { LIGHT_DARK_MODE } from "@/types/config";
|
||||||
import {
|
import {
|
||||||
AUTO_MODE,
|
AUTO_MODE,
|
||||||
DARK_MODE,
|
DARK_MODE,
|
||||||
DEFAULT_THEME,
|
DEFAULT_THEME,
|
||||||
LIGHT_MODE,
|
LIGHT_MODE,
|
||||||
} from '@constants/constants.ts'
|
} from "@constants/constants.ts";
|
||||||
|
|
||||||
export function getDefaultHue(): number {
|
export function getDefaultHue(): number {
|
||||||
const fallback = '250'
|
const fallback = "250";
|
||||||
const configCarrier = document.getElementById('config-carrier')
|
const configCarrier = document.getElementById("config-carrier");
|
||||||
return Number.parseInt(configCarrier?.dataset.hue || fallback)
|
return Number.parseInt(configCarrier?.dataset.hue || fallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getHue(): number {
|
export function getHue(): number {
|
||||||
const stored = localStorage.getItem('hue')
|
const stored = localStorage.getItem("hue");
|
||||||
return stored ? Number.parseInt(stored) : getDefaultHue()
|
return stored ? Number.parseInt(stored) : getDefaultHue();
|
||||||
}
|
}
|
||||||
|
|
||||||
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') as HTMLElement
|
const r = document.querySelector(":root") as HTMLElement;
|
||||||
if (!r) {
|
if (!r) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
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:
|
||||||
document.documentElement.classList.remove('dark')
|
document.documentElement.classList.remove("dark");
|
||||||
break
|
break;
|
||||||
case DARK_MODE:
|
case DARK_MODE:
|
||||||
document.documentElement.classList.add('dark')
|
document.documentElement.classList.add("dark");
|
||||||
break
|
break;
|
||||||
case AUTO_MODE:
|
case AUTO_MODE:
|
||||||
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||||
document.documentElement.classList.add('dark')
|
document.documentElement.classList.add("dark");
|
||||||
} else {
|
} else {
|
||||||
document.documentElement.classList.remove('dark')
|
document.documentElement.classList.remove("dark");
|
||||||
}
|
}
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setTheme(theme: LIGHT_DARK_MODE): void {
|
export function setTheme(theme: LIGHT_DARK_MODE): void {
|
||||||
localStorage.setItem('theme', theme)
|
localStorage.setItem("theme", theme);
|
||||||
applyThemeToDocument(theme)
|
applyThemeToDocument(theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,35 @@
|
|||||||
import i18nKey from '@i18n/i18nKey'
|
import i18nKey from "@i18n/i18nKey";
|
||||||
import { i18n } from '@i18n/translation'
|
import { i18n } from "@i18n/translation";
|
||||||
|
|
||||||
export function pathsEqual(path1: string, path2: string) {
|
export function pathsEqual(path1: string, path2: string) {
|
||||||
const normalizedPath1 = path1.replace(/^\/|\/$/g, '').toLowerCase()
|
const normalizedPath1 = path1.replace(/^\/|\/$/g, "").toLowerCase();
|
||||||
const normalizedPath2 = path2.replace(/^\/|\/$/g, '').toLowerCase()
|
const normalizedPath2 = path2.replace(/^\/|\/$/g, "").toLowerCase();
|
||||||
return normalizedPath1 === normalizedPath2
|
return normalizedPath1 === normalizedPath2;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
return url(`/posts/${slug}/`)
|
return url(`/posts/${slug}/`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCategoryUrl(category: string): string {
|
export function getCategoryUrl(category: string): string {
|
||||||
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}/`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDir(path: string): string {
|
export function getDir(path: string): string {
|
||||||
const lastSlashIndex = path.lastIndexOf('/')
|
const lastSlashIndex = path.lastIndexOf("/");
|
||||||
if (lastSlashIndex < 0) {
|
if (lastSlashIndex < 0) {
|
||||||
return '/'
|
return "/";
|
||||||
}
|
}
|
||||||
return path.substring(0, lastSlashIndex + 1)
|
return path.substring(0, lastSlashIndex + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function url(path: string) {
|
export function url(path: string) {
|
||||||
return joinUrl('', import.meta.env.BASE_URL, path)
|
return joinUrl("", import.meta.env.BASE_URL, path);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user