Fix lint issues, format files & more config
1
.vscode/extensions.json
vendored
|
@ -2,6 +2,7 @@
|
|||
"recommendations": [
|
||||
"biomejs.biome",
|
||||
"astro-build.astro-vscode",
|
||||
"svelte.svelte-vscode",
|
||||
"davidanson.markdownlint"
|
||||
]
|
||||
}
|
4
.vscode/settings.json
vendored
|
@ -13,8 +13,10 @@
|
|||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"[svelte]": {
|
||||
"editor.defaultFormatter": "biomejs.biome",
|
||||
},
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll": "explicit",
|
||||
"quickfix.biome": "always",
|
||||
"source.organizeImports.biome": "always"
|
||||
},
|
||||
|
|
43
biome.json
|
@ -1,7 +1,9 @@
|
|||
{
|
||||
"$schema": "https://biomejs.dev/schemas/1.4.1/schema.json",
|
||||
"extends": [],
|
||||
"files": { "ignoreUnknown": true },
|
||||
"files": {
|
||||
"ignoreUnknown": true
|
||||
},
|
||||
"organizeImports": {
|
||||
"enabled": true
|
||||
},
|
||||
|
@ -17,16 +19,20 @@
|
|||
"parser": {
|
||||
"unsafeParameterDecoratorsEnabled": true
|
||||
},
|
||||
"globals": [
|
||||
"Astro"
|
||||
],
|
||||
"formatter": {
|
||||
"quoteStyle": "single",
|
||||
"jsxQuoteStyle": "single",
|
||||
"trailingComma": "all",
|
||||
"semicolons": "asNeeded",
|
||||
"quoteStyle": "double",
|
||||
"jsxQuoteStyle": "double",
|
||||
"semicolons": "always",
|
||||
"arrowParentheses": "asNeeded"
|
||||
}
|
||||
},
|
||||
"json": {
|
||||
"parser": { "allowComments": true },
|
||||
"parser": {
|
||||
"allowComments": true
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "space",
|
||||
|
@ -41,7 +47,8 @@
|
|||
"recommended": true
|
||||
},
|
||||
"complexity": {
|
||||
"recommended": true
|
||||
"recommended": true,
|
||||
"noForEach": "off"
|
||||
},
|
||||
"correctness": {
|
||||
"recommended": true
|
||||
|
@ -53,14 +60,32 @@
|
|||
"recommended": true
|
||||
},
|
||||
"style": {
|
||||
"recommended": true
|
||||
"recommended": true,
|
||||
"noParameterAssign": "off",
|
||||
"useNodejsImportProtocol": "off"
|
||||
},
|
||||
"suspicious": {
|
||||
"recommended": true
|
||||
"recommended": true,
|
||||
"noExplicitAny": "off"
|
||||
},
|
||||
"nursery": {
|
||||
"recommended": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"include": [
|
||||
"*.svelte",
|
||||
"*.astro"
|
||||
],
|
||||
"linter": {
|
||||
"rules": {
|
||||
"style": {
|
||||
"useConst": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "fuwari",
|
||||
"name": "blog.devcomp.xyz",
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
|
@ -9,8 +9,8 @@
|
|||
"preview": "astro preview",
|
||||
"astro": "astro",
|
||||
"new-post": "node scripts/new-post.js",
|
||||
"format": "biome format --write ./src",
|
||||
"lint": "biome check --apply ./src"
|
||||
"fmt": "biome format --write ./src",
|
||||
"lint": "biome check ./src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/check": "^0.7.0",
|
||||
|
|
BIN
public/favicon-dark.png
Normal file
After Width: | Height: | Size: 74 KiB |
BIN
public/favicon-light.png
Normal file
After Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 426 B |
Before Width: | Height: | Size: 2 KiB |
Before Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 554 B |
|
@ -1,63 +1,65 @@
|
|||
---
|
||||
import {getSortedPosts} from "../utils/content-utils";
|
||||
import {getPostUrlBySlug} from "../utils/url-utils";
|
||||
import {i18n} from "../i18n/translation";
|
||||
import { UNCATEGORIZED } from "@constants/constants";
|
||||
import I18nKey from "../i18n/i18nKey";
|
||||
import {UNCATEGORIZED} from "@constants/constants";
|
||||
import { i18n } from "../i18n/translation";
|
||||
import { getSortedPosts } from "../utils/content-utils";
|
||||
import { getPostUrlBySlug } from "../utils/url-utils";
|
||||
|
||||
interface Props {
|
||||
keyword: string;
|
||||
keyword?: string;
|
||||
tags: string[];
|
||||
categories: string[];
|
||||
categories?: string[];
|
||||
}
|
||||
const { keyword, tags, categories} = Astro.props;
|
||||
const { keyword, tags, categories } = Astro.props;
|
||||
|
||||
let posts = await getSortedPosts()
|
||||
let posts = await getSortedPosts();
|
||||
|
||||
if (Array.isArray(tags) && tags.length > 0) {
|
||||
posts = posts.filter(post =>
|
||||
Array.isArray(post.data.tags) && post.data.tags.some(tag => tags.includes(tag))
|
||||
posts = posts.filter(
|
||||
post =>
|
||||
Array.isArray(post.data.tags) &&
|
||||
post.data.tags.some(tag => tags.includes(tag)),
|
||||
);
|
||||
}
|
||||
|
||||
if (Array.isArray(categories) && categories.length > 0) {
|
||||
posts = posts.filter(post =>
|
||||
posts = posts.filter(
|
||||
post =>
|
||||
(post.data.category && categories.includes(post.data.category)) ||
|
||||
(!post.data.category && categories.includes(UNCATEGORIZED))
|
||||
(!post.data.category && categories.includes(UNCATEGORIZED)),
|
||||
);
|
||||
}
|
||||
|
||||
const groups = function () {
|
||||
const groups = (() => {
|
||||
const groupedPosts = posts.reduce((grouped, post) => {
|
||||
const year = post.data.published.getFullYear()
|
||||
const year = post.data.published.getFullYear();
|
||||
if (!grouped[year]) {
|
||||
grouped[year] = []
|
||||
grouped[year] = [];
|
||||
}
|
||||
grouped[year].push(post)
|
||||
return grouped
|
||||
}, {})
|
||||
grouped[year].push(post);
|
||||
return grouped;
|
||||
}, {});
|
||||
|
||||
// convert the object to an array
|
||||
const groupedPostsArray = Object.keys(groupedPosts).map(key => ({
|
||||
year: key,
|
||||
posts: groupedPosts[key]
|
||||
}))
|
||||
posts: groupedPosts[key],
|
||||
}));
|
||||
|
||||
// sort years by latest first
|
||||
groupedPostsArray.sort((a, b) => b.year - a.year)
|
||||
groupedPostsArray.sort((a, b) => b.year - a.year);
|
||||
return groupedPostsArray;
|
||||
}();
|
||||
})();
|
||||
|
||||
function formatDate(date: Date) {
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
||||
const day = date.getDate().toString().padStart(2, '0');
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
||||
const day = date.getDate().toString().padStart(2, "0");
|
||||
return `${month}-${day}`;
|
||||
}
|
||||
|
||||
function formatTag(tag: string[]) {
|
||||
return tag.map(t => `#${t}`).join(' ');
|
||||
return tag.map(t => `#${t}`).join(" ");
|
||||
}
|
||||
|
||||
---
|
||||
|
||||
<div class="card-base px-8 py-6">
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
---
|
||||
|
||||
import {siteConfig} from "../config";
|
||||
|
||||
import { siteConfig } from "../config";
|
||||
---
|
||||
|
||||
<div id="config-carrier" data-hue={siteConfig.themeColor.hue}>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
---
|
||||
|
||||
import {profileConfig} from "../config";
|
||||
|
||||
import { profileConfig } from "../config";
|
||||
---
|
||||
|
||||
<div class="card-base max-w-[var(--page-width)] min-h-[4.5rem] rounded-b-none mx-auto flex items-center px-6">
|
||||
|
|
|
@ -1,74 +1,60 @@
|
|||
<script lang="ts">
|
||||
import type { LIGHT_DARK_MODE } from '@/types/config.ts'
|
||||
import {
|
||||
AUTO_MODE,
|
||||
DARK_MODE,
|
||||
LIGHT_MODE,
|
||||
} from '@constants/constants.ts'
|
||||
import I18nKey from '@i18n/i18nKey'
|
||||
import { i18n } from '@i18n/translation'
|
||||
import Icon from '@iconify/svelte'
|
||||
import type { LIGHT_DARK_MODE } from "@/types/config.ts";
|
||||
import { AUTO_MODE, DARK_MODE, LIGHT_MODE } from "@constants/constants.ts";
|
||||
import I18nKey from "@i18n/i18nKey";
|
||||
import { i18n } from "@i18n/translation";
|
||||
import Icon from "@iconify/svelte";
|
||||
import {
|
||||
applyThemeToDocument,
|
||||
getStoredTheme,
|
||||
setTheme,
|
||||
} from '@utils/setting-utils.ts'
|
||||
import { onMount } from 'svelte'
|
||||
} from "@utils/setting-utils.ts";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
const seq: LIGHT_DARK_MODE[] = [
|
||||
LIGHT_MODE,
|
||||
DARK_MODE,
|
||||
AUTO_MODE,
|
||||
]
|
||||
let mode: LIGHT_DARK_MODE = AUTO_MODE
|
||||
const seq: LIGHT_DARK_MODE[] = [LIGHT_MODE, DARK_MODE, AUTO_MODE];
|
||||
let mode: LIGHT_DARK_MODE = AUTO_MODE;
|
||||
|
||||
onMount(() => {
|
||||
mode = getStoredTheme()
|
||||
const darkModePreference = window.matchMedia(
|
||||
'(prefers-color-scheme: dark)',
|
||||
)
|
||||
mode = getStoredTheme();
|
||||
const darkModePreference = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
const changeThemeWhenSchemeChanged: Parameters<
|
||||
typeof darkModePreference.addEventListener<'change'>
|
||||
typeof darkModePreference.addEventListener<"change">
|
||||
>[1] = e => {
|
||||
applyThemeToDocument(mode)
|
||||
}
|
||||
darkModePreference.addEventListener(
|
||||
'change',
|
||||
changeThemeWhenSchemeChanged,
|
||||
)
|
||||
applyThemeToDocument(mode);
|
||||
};
|
||||
darkModePreference.addEventListener("change", changeThemeWhenSchemeChanged);
|
||||
return () => {
|
||||
darkModePreference.removeEventListener(
|
||||
'change',
|
||||
"change",
|
||||
changeThemeWhenSchemeChanged,
|
||||
)
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
function switchScheme(newMode: LIGHT_DARK_MODE) {
|
||||
mode = newMode
|
||||
setTheme(newMode)
|
||||
mode = newMode;
|
||||
setTheme(newMode);
|
||||
}
|
||||
|
||||
function toggleScheme() {
|
||||
let i = 0
|
||||
let i = 0;
|
||||
for (; i < seq.length; i++) {
|
||||
if (seq[i] === mode) {
|
||||
break
|
||||
break;
|
||||
}
|
||||
}
|
||||
switchScheme(seq[(i + 1) % seq.length])
|
||||
switchScheme(seq[(i + 1) % seq.length]);
|
||||
}
|
||||
|
||||
function showPanel() {
|
||||
const panel = document.querySelector('#light-dark-panel')
|
||||
panel.classList.remove('float-panel-closed')
|
||||
const panel = document.querySelector("#light-dark-panel");
|
||||
panel?.classList.remove("float-panel-closed");
|
||||
}
|
||||
|
||||
function hidePanel() {
|
||||
const panel = document.querySelector('#light-dark-panel')
|
||||
panel.classList.add('float-panel-closed')
|
||||
const panel = document.querySelector("#light-dark-panel");
|
||||
panel?.classList.add("float-panel-closed");
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<!-- z-50 make the panel higher than other float panels -->
|
||||
|
|
|
@ -1,22 +1,23 @@
|
|||
---
|
||||
import { Icon } from 'astro-icon/components';
|
||||
import DisplaySettings from "./widget/DisplaySettings.svelte";
|
||||
import {LinkPreset, NavBarLink} from "../types/config";
|
||||
import {navBarConfig, siteConfig} from "../config";
|
||||
import NavMenuPanel from "./widget/NavMenuPanel.astro";
|
||||
import Search from "./Search.svelte";
|
||||
import {LinkPresets} from "../constants/link-presets";
|
||||
import { Icon } from "astro-icon/components";
|
||||
import { navBarConfig, siteConfig } from "../config";
|
||||
import { LinkPresets } from "../constants/link-presets";
|
||||
import type { LinkPreset, NavBarLink } from "../types/config";
|
||||
import { url } from "../utils/url-utils";
|
||||
import LightDarkSwitch from "./LightDarkSwitch.svelte";
|
||||
import {url} from "../utils/url-utils";
|
||||
import Search from "./Search.svelte";
|
||||
import DisplaySettings from "./widget/DisplaySettings.svelte";
|
||||
import NavMenuPanel from "./widget/NavMenuPanel.astro";
|
||||
const className = Astro.props.class;
|
||||
|
||||
let links: NavBarLink[] = navBarConfig.links.map((item: NavBarLink | LinkPreset): NavBarLink => {
|
||||
const links: NavBarLink[] = navBarConfig.links.map(
|
||||
(item: NavBarLink | LinkPreset): NavBarLink => {
|
||||
if (typeof item === "number") {
|
||||
return LinkPresets[item]
|
||||
return LinkPresets[item];
|
||||
}
|
||||
return item;
|
||||
});
|
||||
|
||||
},
|
||||
);
|
||||
---
|
||||
<div class:list={[
|
||||
className,
|
||||
|
@ -80,7 +81,7 @@ function switchTheme() {
|
|||
|
||||
function loadButtonScript() {
|
||||
let switchBtn = document.getElementById("scheme-switch");
|
||||
switchBtn.addEventListener("click", function () {
|
||||
switchBtn!.addEventListener("click", function () {
|
||||
switchTheme()
|
||||
});
|
||||
|
||||
|
@ -88,14 +89,14 @@ function loadButtonScript() {
|
|||
if (settingBtn) {
|
||||
settingBtn.addEventListener("click", function () {
|
||||
let settingPanel = document.getElementById("display-setting");
|
||||
settingPanel.classList.toggle("float-panel-closed");
|
||||
settingPanel!.classList.toggle("float-panel-closed");
|
||||
});
|
||||
}
|
||||
|
||||
let menuBtn = document.getElementById("nav-menu-switch");
|
||||
menuBtn.addEventListener("click", function () {
|
||||
menuBtn!.addEventListener("click", function () {
|
||||
let menuPanel = document.getElementById("nav-menu-panel");
|
||||
menuPanel.classList.toggle("float-panel-closed");
|
||||
menuPanel!.classList.toggle("float-panel-closed");
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
---
|
||||
import path from "path";
|
||||
import { Icon } from "astro-icon/components";
|
||||
import I18nKey from "../i18n/i18nKey";
|
||||
import { i18n } from "../i18n/translation";
|
||||
import { getDir } from "../utils/url-utils";
|
||||
import PostMetadata from "./PostMeta.astro";
|
||||
import ImageWrapper from "./misc/ImageWrapper.astro";
|
||||
import { Icon } from 'astro-icon/components';
|
||||
import {i18n} from "../i18n/translation";
|
||||
import I18nKey from "../i18n/i18nKey";
|
||||
import {getDir} from "../utils/url-utils";
|
||||
|
||||
interface Props {
|
||||
class: string;
|
||||
class?: string;
|
||||
entry: any;
|
||||
title: string;
|
||||
url: string;
|
||||
|
@ -17,19 +17,29 @@ interface Props {
|
|||
category: string;
|
||||
image: string;
|
||||
description: string;
|
||||
words: number;
|
||||
words?: number;
|
||||
draft: boolean;
|
||||
style: string;
|
||||
}
|
||||
const { entry, title, url, published, tags, category, image, description, words, style } = Astro.props;
|
||||
const {
|
||||
entry,
|
||||
title,
|
||||
url,
|
||||
published,
|
||||
tags,
|
||||
category,
|
||||
image,
|
||||
description,
|
||||
words,
|
||||
style,
|
||||
} = Astro.props;
|
||||
const className = Astro.props.class;
|
||||
|
||||
const hasCover = image !== undefined && image !== null && image !== '';
|
||||
const hasCover = image !== undefined && image !== null && image !== "";
|
||||
|
||||
const coverWidth = "28%";
|
||||
|
||||
const { 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={["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,9 +1,9 @@
|
|||
---
|
||||
import {formatDateToYYYYMMDD} from "../utils/date-utils";
|
||||
import { Icon } from 'astro-icon/components';
|
||||
import {i18n} from "../i18n/translation";
|
||||
import { Icon } from "astro-icon/components";
|
||||
import I18nKey from "../i18n/i18nKey";
|
||||
import {url} from "../utils/url-utils";
|
||||
import { i18n } from "../i18n/translation";
|
||||
import { formatDateToYYYYMMDD } from "../utils/date-utils";
|
||||
import { url } from "../utils/url-utils";
|
||||
|
||||
interface Props {
|
||||
class: string;
|
||||
|
@ -12,7 +12,7 @@ interface Props {
|
|||
category: string;
|
||||
hideTagsForMobile: boolean;
|
||||
}
|
||||
const {published, tags, category, hideTagsForMobile} = Astro.props;
|
||||
const { published, tags, category, hideTagsForMobile } = Astro.props;
|
||||
const className = Astro.props.class;
|
||||
---
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
---
|
||||
import {getPostUrlBySlug} from "@utils/url-utils";
|
||||
import { getPostUrlBySlug } from "@utils/url-utils";
|
||||
import PostCard from "./PostCard.astro";
|
||||
|
||||
const {page} = Astro.props;
|
||||
const { page } = Astro.props;
|
||||
|
||||
let delay = 0
|
||||
const interval = 50
|
||||
let delay = 0;
|
||||
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">
|
||||
{page.data.map((entry: { data: { draft: boolean; title: string; tags: string[]; category: string; published: Date; image: string; description: string; }; slug: string; }) => {
|
||||
|
@ -16,7 +16,7 @@ const interval = 50
|
|||
tags={entry.data.tags}
|
||||
category={entry.data.category}
|
||||
published={entry.data.published}
|
||||
url={getPostUrlBySlug(entry.slug)}
|
||||
url={getPostUrlBySlug(entry.slug)!}
|
||||
image={entry.data.image}
|
||||
description={entry.data.description}
|
||||
draft={entry.data.draft}
|
||||
|
|
|
@ -1,69 +1,74 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte'
|
||||
import {url} from "@utils/url-utils.ts"
|
||||
import { i18n } from '@i18n/translation';
|
||||
import I18nKey from '@i18n/i18nKey';
|
||||
let keywordDesktop = ''
|
||||
let keywordMobile = ''
|
||||
let result = []
|
||||
const fakeResult = [{
|
||||
url: url('/'),
|
||||
meta: {
|
||||
title: 'This Is a Fake Search Result'
|
||||
},
|
||||
excerpt: 'Because the search cannot work in the <mark>dev</mark> environment.'
|
||||
}, {
|
||||
url: url('/'),
|
||||
meta: {
|
||||
title: 'If You Want to Test the Search'
|
||||
},
|
||||
excerpt: 'Try running <mark>npm build && npm preview</mark> instead.'
|
||||
}]
|
||||
import I18nKey from "@i18n/i18nKey";
|
||||
import { i18n } from "@i18n/translation";
|
||||
import { url } from "@utils/url-utils.ts";
|
||||
import { onMount } from "svelte";
|
||||
let keywordDesktop = "";
|
||||
let keywordMobile = "";
|
||||
let result: any[] = [];
|
||||
|
||||
let search = (keyword: string, isDesktop: boolean) => {}
|
||||
const fakeResult = [
|
||||
{
|
||||
url: url("/"),
|
||||
meta: {
|
||||
title: "This Is a Fake Search Result",
|
||||
},
|
||||
excerpt:
|
||||
"Because the search cannot work in the <mark>dev</mark> environment.",
|
||||
},
|
||||
{
|
||||
url: url("/"),
|
||||
meta: {
|
||||
title: "If You Want to Test the Search",
|
||||
},
|
||||
excerpt: "Try running <mark>npm build && npm preview</mark> instead.",
|
||||
},
|
||||
];
|
||||
|
||||
let search = (keyword: string, isDesktop: boolean) => {};
|
||||
|
||||
onMount(() => {
|
||||
search = async (keyword: string, isDesktop: boolean) => {
|
||||
let panel = document.getElementById('search-panel')
|
||||
if (!panel)
|
||||
return
|
||||
const panel = document.getElementById("search-panel");
|
||||
if (!panel) return;
|
||||
|
||||
if (!keyword && isDesktop) {
|
||||
panel.classList.add("float-panel-closed")
|
||||
return
|
||||
panel.classList.add("float-panel-closed");
|
||||
return;
|
||||
}
|
||||
|
||||
let arr = [];
|
||||
if (import.meta.env.PROD) {
|
||||
const ret = await pagefind.search(keyword)
|
||||
// @ts-ignore
|
||||
const ret = await pagefind.search(keyword);
|
||||
for (const item of ret.results) {
|
||||
arr.push(await item.data())
|
||||
arr.push(await item.data());
|
||||
}
|
||||
} else {
|
||||
// Mock data for non-production environment
|
||||
// arr = JSON.parse('[{"url":"/","content":"Simple Guides for Fuwari. Cover image source: Source. This blog template is built with Astro. For the things that are not mentioned in this guide, you may find the answers in the Astro Docs. Front-matter of Posts. --- title: My First Blog Post published: 2023-09-09 description: This is the first post of my new Astro blog. image: ./cover.jpg tags: [Foo, Bar] category: Front-end draft: false ---AttributeDescription title. The title of the post. published. The date the post was published. description. A short description of the post. Displayed on index page. image. The cover image path of the post. 1. Start with http:// or https://: Use web image 2. Start with /: For image in public dir 3. With none of the prefixes: Relative to the markdown file. tags. The tags of the post. category. The category of the post. draft. If this post is still a draft, which 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) {
|
||||
panel.classList.add("float-panel-closed")
|
||||
return
|
||||
panel.classList.add("float-panel-closed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDesktop) {
|
||||
panel.classList.remove("float-panel-closed")
|
||||
panel.classList.remove("float-panel-closed");
|
||||
}
|
||||
result = arr
|
||||
}
|
||||
})
|
||||
result = arr;
|
||||
};
|
||||
});
|
||||
|
||||
const togglePanel = () => {
|
||||
let panel = document.getElementById('search-panel')
|
||||
panel?.classList.toggle("float-panel-closed")
|
||||
}
|
||||
const panel = document.getElementById("search-panel");
|
||||
panel?.classList.toggle("float-panel-closed");
|
||||
};
|
||||
|
||||
$: search(keywordDesktop, true)
|
||||
$: search(keywordMobile, false)
|
||||
$: search(keywordDesktop, true);
|
||||
$: search(keywordMobile, false);
|
||||
</script>
|
||||
|
||||
<!-- 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` -->
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
---
|
||||
interface Props {
|
||||
badge?: string
|
||||
url?: string
|
||||
label?: string
|
||||
badge?: string;
|
||||
url?: string;
|
||||
label?: string;
|
||||
}
|
||||
const { badge, url, name } = Astro.props
|
||||
const { badge, url, name } = Astro.props;
|
||||
---
|
||||
<a href={url} aria-label={name}>
|
||||
<button
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
---
|
||||
import type { Page } from "astro";
|
||||
import { Icon } from 'astro-icon/components';
|
||||
import {url} from "../../utils/url-utils";
|
||||
import { Icon } from "astro-icon/components";
|
||||
import { url } from "../../utils/url-utils";
|
||||
interface Props {
|
||||
page: Page;
|
||||
class?: string;
|
||||
style?: string;
|
||||
}
|
||||
|
||||
const {page, style} = Astro.props;
|
||||
const { page, style } = Astro.props;
|
||||
|
||||
const HIDDEN = -1;
|
||||
|
||||
|
@ -19,7 +19,8 @@ const VISIBLE = ADJ_DIST * 2 + 1;
|
|||
|
||||
// for test
|
||||
let count = 1;
|
||||
let l = page.currentPage, r = page.currentPage;
|
||||
let l = page.currentPage;
|
||||
let r = page.currentPage;
|
||||
while (0 < l - 1 && r + 1 <= page.lastPage && count + 2 <= VISIBLE) {
|
||||
count += 2;
|
||||
l--;
|
||||
|
@ -34,32 +35,23 @@ while (r + 1 <= page.lastPage && count < VISIBLE) {
|
|||
r++;
|
||||
}
|
||||
|
||||
let pages: number[] = [];
|
||||
if (l > 1)
|
||||
pages.push(1);
|
||||
if (l == 3)
|
||||
pages.push(2);
|
||||
if (l > 3)
|
||||
pages.push(HIDDEN);
|
||||
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(page.lastPage - 1);
|
||||
if (r < page.lastPage)
|
||||
pages.push(page.lastPage);
|
||||
const pages: number[] = [];
|
||||
if (l > 1) pages.push(1);
|
||||
if (l === 3) pages.push(2);
|
||||
if (l > 3) pages.push(HIDDEN);
|
||||
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(page.lastPage - 1);
|
||||
if (r < page.lastPage) pages.push(page.lastPage);
|
||||
|
||||
const getPageUrl = (p: number) => {
|
||||
if (p == 1)
|
||||
return '/';
|
||||
if (p === 1) return "/";
|
||||
return `/${p}/`;
|
||||
}
|
||||
|
||||
};
|
||||
---
|
||||
|
||||
<div class:list={[className, "flex flex-row gap-3 justify-center"]} style={style}>
|
||||
<a href={url(page.url.prev)} aria-label={page.url.prev ? "Previous Page" : null}
|
||||
<a href={url(page.url.prev!)} aria-label={page.url.prev ? "Previous Page" : null}
|
||||
class:list={["btn-card overflow-hidden rounded-lg text-[var(--primary)] w-11 h-11",
|
||||
{"disabled": page.url.prev == undefined}
|
||||
]}
|
||||
|
@ -81,7 +73,7 @@ const getPageUrl = (p: number) => {
|
|||
>{p}</a>
|
||||
})}
|
||||
</div>
|
||||
<a href={url(page.url.next)} aria-label={page.url.next ? "Next Page" : null}
|
||||
<a href={url(page.url.next!)} aria-label={page.url.next ? "Next Page" : null}
|
||||
class:list={["btn-card overflow-hidden rounded-lg text-[var(--primary)] w-11 h-11",
|
||||
{"disabled": page.url.next == undefined}
|
||||
]}
|
||||
|
|
|
@ -1,37 +1,46 @@
|
|||
---
|
||||
import path from "path";
|
||||
interface Props {
|
||||
id?: string
|
||||
id?: string;
|
||||
src: string;
|
||||
class?: string;
|
||||
alt?: string
|
||||
alt?: string;
|
||||
position?: string;
|
||||
basePath?: string
|
||||
basePath?: string;
|
||||
}
|
||||
import { Image } from 'astro:assets';
|
||||
import { Image } from "astro:assets";
|
||||
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 isLocal = !(src.startsWith('/') || src.startsWith('http') || src.startsWith('https') || src.startsWith('data:'));
|
||||
const isPublic = src.startsWith('/');
|
||||
const isLocal = !(
|
||||
src.startsWith("/") ||
|
||||
src.startsWith("http") ||
|
||||
src.startsWith("https") ||
|
||||
src.startsWith("data:")
|
||||
);
|
||||
const isPublic = src.startsWith("/");
|
||||
|
||||
// TODO temporary workaround for images dynamic import
|
||||
// https://github.com/withastro/astro/issues/3373
|
||||
let img;
|
||||
let img: ImageMetadata;
|
||||
if (isLocal) {
|
||||
const files = import.meta.glob<ImageMetadata>("../../**", { import: 'default' });
|
||||
let normalizedPath = path.normalize(path.join("../../", basePath, src)).replace(/\\/g, "/");
|
||||
img = await (files[normalizedPath])();
|
||||
const files = import.meta.glob<ImageMetadata>("../../**", {
|
||||
import: "default",
|
||||
});
|
||||
const normalizedPath = path
|
||||
.normalize(path.join("../../", basePath, src))
|
||||
.replace(/\\/g, "/");
|
||||
img = await files[normalizedPath]();
|
||||
}
|
||||
|
||||
const imageClass = 'w-full h-full object-cover';
|
||||
const imageStyle = `object-position: ${position}`
|
||||
const imageClass = "w-full h-full object-cover";
|
||||
const imageStyle = `object-position: ${position}`;
|
||||
---
|
||||
<div class:list={[className, 'overflow-hidden relative']}>
|
||||
<div class="transition absolute inset-0 dark:bg-black/10 bg-opacity-50 pointer-events-none"></div>
|
||||
{isLocal && <Image src={img} alt={alt || ""} class={imageClass} style={imageStyle}/>}
|
||||
{isLocal && <Image src={img!} alt={alt || ""} class={imageClass} style={imageStyle}/>}
|
||||
{!isLocal && <img src={isPublic ? url(src) : src} alt={alt || ""} class={imageClass} style={imageStyle}/>}
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
---
|
||||
import {formatDateToYYYYMMDD} from "../../utils/date-utils";
|
||||
import { Icon } from 'astro-icon/components';
|
||||
import {licenseConfig, profileConfig} from "../../config";
|
||||
import {i18n} from "../../i18n/translation";
|
||||
import { Icon } from "astro-icon/components";
|
||||
import { licenseConfig, profileConfig } from "../../config";
|
||||
import I18nKey from "../../i18n/i18nKey";
|
||||
import { i18n } from "../../i18n/translation";
|
||||
import { formatDateToYYYYMMDD } from "../../utils/date-utils";
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
|
@ -17,7 +17,6 @@ const className = Astro.props.class;
|
|||
const profileConf = profileConfig;
|
||||
const licenseConf = licenseConfig;
|
||||
const postUrl = decodeURIComponent(Astro.url.toString());
|
||||
|
||||
---
|
||||
<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">
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
import '@fontsource-variable/jetbrains-mono';
|
||||
import '@fontsource-variable/jetbrains-mono/wght-italic.css';
|
||||
import "@fontsource-variable/jetbrains-mono";
|
||||
import "@fontsource-variable/jetbrains-mono/wght-italic.css";
|
||||
|
||||
interface Props {
|
||||
class: string;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
---
|
||||
import WidgetLayout from "./WidgetLayout.astro";
|
||||
|
||||
import {i18n} from "../../i18n/translation";
|
||||
import I18nKey from "../../i18n/i18nKey";
|
||||
import {Category, getCategoryList} from "../../utils/content-utils";
|
||||
import {getCategoryUrl} from "../../utils/url-utils";
|
||||
import { i18n } from "../../i18n/translation";
|
||||
import { Category, getCategoryList } from "../../utils/content-utils";
|
||||
import { getCategoryUrl } from "../../utils/url-utils";
|
||||
import ButtonLink from "../control/ButtonLink.astro";
|
||||
|
||||
const categories = await getCategoryList();
|
||||
|
@ -18,9 +18,8 @@ interface Props {
|
|||
class?: string;
|
||||
style?: string;
|
||||
}
|
||||
const className = Astro.props.class
|
||||
const style = Astro.props.style
|
||||
|
||||
const className = Astro.props.class;
|
||||
const style = Astro.props.style;
|
||||
---
|
||||
|
||||
<WidgetLayout name={i18n(I18nKey.categories)} id="categories" isCollapsed={isCollapsed} collapsedHeight={COLLAPSED_HEIGHT}
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
<script lang="ts">
|
||||
import {i18n} from '@i18n/translation';
|
||||
import I18nKey from '@i18n/i18nKey';
|
||||
import {getDefaultHue, getHue, setHue} from '@utils/setting-utils';
|
||||
import I18nKey from "@i18n/i18nKey";
|
||||
import { i18n } from "@i18n/translation";
|
||||
import { getDefaultHue, getHue, setHue } from "@utils/setting-utils";
|
||||
|
||||
let hue = getHue()
|
||||
const defaultHue = getDefaultHue()
|
||||
let hue = getHue();
|
||||
const defaultHue = getDefaultHue();
|
||||
|
||||
function resetHue() {
|
||||
hue = getDefaultHue()
|
||||
hue = getDefaultHue();
|
||||
}
|
||||
|
||||
$: if (hue || hue === 0) {
|
||||
setHue(hue)
|
||||
setHue(hue);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
---
|
||||
import {NavBarLink} from "../../types/config";
|
||||
import {Icon} from "astro-icon/components";
|
||||
import {url} from "../../utils/url-utils";
|
||||
import { Icon } from "astro-icon/components";
|
||||
import type { NavBarLink } from "../../types/config";
|
||||
import { url } from "../../utils/url-utils";
|
||||
|
||||
interface Props {
|
||||
links: NavBarLink[],
|
||||
links: NavBarLink[];
|
||||
}
|
||||
|
||||
const links = Astro.props.links;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
import { Icon } from "astro-icon/components";
|
||||
import { profileConfig } from "../../config";
|
||||
import { url } from "../../utils/url-utils";
|
||||
import ImageWrapper from "../misc/ImageWrapper.astro";
|
||||
import {Icon} from "astro-icon/components";
|
||||
import {profileConfig} from "../../config";
|
||||
import {url} from "../../utils/url-utils";
|
||||
|
||||
const config = profileConfig;
|
||||
---
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
import Categories from "./Categories.astro";
|
||||
import Profile from "./Profile.astro";
|
||||
import Tag from "./Tags.astro";
|
||||
import Categories from "./Categories.astro";
|
||||
|
||||
const className = Astro.props.class;
|
||||
---
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
---
|
||||
|
||||
import WidgetLayout from "./WidgetLayout.astro";
|
||||
import ButtonTag from "../control/ButtonTag.astro";
|
||||
import {getTagList} from "../../utils/content-utils";
|
||||
import {i18n} from "../../i18n/translation";
|
||||
import I18nKey from "../../i18n/i18nKey";
|
||||
import {url} from "../../utils/url-utils";
|
||||
import { i18n } from "../../i18n/translation";
|
||||
import { getTagList } from "../../utils/content-utils";
|
||||
import { url } from "../../utils/url-utils";
|
||||
import ButtonTag from "../control/ButtonTag.astro";
|
||||
import WidgetLayout from "./WidgetLayout.astro";
|
||||
|
||||
const tags = await getTagList();
|
||||
|
||||
|
@ -17,9 +17,8 @@ interface Props {
|
|||
class?: string;
|
||||
style?: string;
|
||||
}
|
||||
const className = Astro.props.class
|
||||
const style = Astro.props.style
|
||||
|
||||
const className = Astro.props.class;
|
||||
const style = Astro.props.style;
|
||||
---
|
||||
<WidgetLayout name={i18n(I18nKey.tags)} id="tags" isCollapsed={isCollapsed} collapsedHeight={COLLAPSED_HEIGHT} class={className} style={style}>
|
||||
<div class="flex gap-2 flex-wrap">
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
import { Icon } from 'astro-icon/components';
|
||||
import {i18n} from "../../i18n/translation";
|
||||
import { Icon } from "astro-icon/components";
|
||||
import I18nKey from "../../i18n/i18nKey";
|
||||
import { i18n } from "../../i18n/translation";
|
||||
interface Props {
|
||||
id: string;
|
||||
name?: string;
|
||||
|
@ -11,15 +11,8 @@ interface Props {
|
|||
style?: string;
|
||||
}
|
||||
const props = Astro.props;
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
isCollapsed,
|
||||
collapsedHeight,
|
||||
style,
|
||||
} = Astro.props
|
||||
const className = Astro.props.class
|
||||
|
||||
const { id, name, isCollapsed, collapsedHeight, style } = Astro.props;
|
||||
const className = Astro.props.class;
|
||||
---
|
||||
<widget-layout data-id={id} data-is-collapsed={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
|
||||
|
|
|
@ -3,56 +3,60 @@ import type {
|
|||
NavBarConfig,
|
||||
ProfileConfig,
|
||||
SiteConfig,
|
||||
} from './types/config'
|
||||
import { LinkPreset } from './types/config'
|
||||
} from "./types/config";
|
||||
import { LinkPreset } from "./types/config";
|
||||
|
||||
export const siteConfig: SiteConfig = {
|
||||
title: "compey's blog",
|
||||
subtitle: 'Demo Site',
|
||||
lang: 'en',
|
||||
subtitle: "Demo Site",
|
||||
lang: "en",
|
||||
themeColor: {
|
||||
hue: 295,
|
||||
fixed: true,
|
||||
},
|
||||
banner: {
|
||||
enable: true,
|
||||
src: 'assets/images/demo-banner.png', // TODO: yes
|
||||
position: 'center',
|
||||
src: "assets/images/demo-banner.png",
|
||||
position: "center",
|
||||
},
|
||||
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
|
||||
// sizes: '32x32', // (Optional) Size of the favicon, set only if you have favicons of different sizes
|
||||
// }
|
||||
{
|
||||
src: "/favicon-dark.png",
|
||||
theme: "dark",
|
||||
sizes: "32x32",
|
||||
},
|
||||
{
|
||||
src: "/favicon-light.png",
|
||||
theme: "light",
|
||||
sizes: "32x32",
|
||||
},
|
||||
],
|
||||
}
|
||||
};
|
||||
|
||||
export const navBarConfig: NavBarConfig = {
|
||||
links: [LinkPreset.Home, LinkPreset.Archive, LinkPreset.About],
|
||||
}
|
||||
};
|
||||
|
||||
export const profileConfig: ProfileConfig = {
|
||||
avatar: 'assets/images/CompeyDev.png', // Relative to the /src directory. Relative to the /public directory if it starts with '/'
|
||||
name: 'compey',
|
||||
bio: 'ur local developer | 8 bit enthusiast | rust <3',
|
||||
avatar: "assets/images/CompeyDev.png", // Relative to the /src directory. Relative to the /public directory if it starts with '/'
|
||||
name: "compey",
|
||||
bio: "ur local developer | 8 bit enthusiast | rust <3",
|
||||
links: [
|
||||
{
|
||||
name: 'GitHub',
|
||||
icon: 'fa6-brands:github',
|
||||
url: 'https://github.com/CompeyDeve',
|
||||
name: "GitHub",
|
||||
icon: "fa6-brands:github",
|
||||
url: "https://github.com/CompeyDeve",
|
||||
},
|
||||
{
|
||||
name: 'Twitter',
|
||||
icon: 'fa6-brands:twitter',
|
||||
url: 'https://twitter.com/DevComp_',
|
||||
name: "Twitter",
|
||||
icon: "fa6-brands:twitter",
|
||||
url: "https://twitter.com/DevComp_",
|
||||
},
|
||||
],
|
||||
}
|
||||
};
|
||||
|
||||
export const licenseConfig: LicenseConfig = {
|
||||
enable: true,
|
||||
name: 'CC BY-NC-SA 4.0',
|
||||
url: 'https://creativecommons.org/licenses/by-nc-sa/4.0/',
|
||||
}
|
||||
name: "CC BY-NC-SA 4.0",
|
||||
url: "https://creativecommons.org/licenses/by-nc-sa/4.0/",
|
||||
};
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
export const UNCATEGORIZED = '__uncategorized__'
|
||||
export const UNCATEGORIZED = "__uncategorized__";
|
||||
|
||||
export const PAGE_SIZE = 8
|
||||
export const PAGE_SIZE = 8;
|
||||
|
||||
export const LIGHT_MODE = 'light', DARK_MODE = 'dark', AUTO_MODE = 'auto'
|
||||
export const DEFAULT_THEME = AUTO_MODE
|
||||
export const LIGHT_MODE = "light",
|
||||
DARK_MODE = "dark",
|
||||
AUTO_MODE = "auto";
|
||||
export const DEFAULT_THEME = AUTO_MODE;
|
||||
|
|
|
@ -1,37 +1,44 @@
|
|||
import type {Favicon} from "@/types/config.ts";
|
||||
import type { Favicon } from "@/types/config.ts";
|
||||
|
||||
export const defaultFavicons: Favicon[] = [
|
||||
{
|
||||
src: '/favicon/favicon-light-32.png',
|
||||
theme: 'light',
|
||||
sizes: '32x32',
|
||||
}, {
|
||||
src: '/favicon/favicon-light-128.png',
|
||||
theme: 'light',
|
||||
sizes: '128x128',
|
||||
}, {
|
||||
src: '/favicon/favicon-light-180.png',
|
||||
theme: 'light',
|
||||
sizes: '180x180',
|
||||
}, {
|
||||
src: '/favicon/favicon-light-192.png',
|
||||
theme: 'light',
|
||||
sizes: '192x192',
|
||||
}, {
|
||||
src: '/favicon/favicon-dark-32.png',
|
||||
theme: 'dark',
|
||||
sizes: '32x32',
|
||||
}, {
|
||||
src: '/favicon/favicon-dark-128.png',
|
||||
theme: 'dark',
|
||||
sizes: '128x128',
|
||||
}, {
|
||||
src: '/favicon/favicon-dark-180.png',
|
||||
theme: 'dark',
|
||||
sizes: '180x180',
|
||||
}, {
|
||||
src: '/favicon/favicon-dark-192.png',
|
||||
theme: 'dark',
|
||||
sizes: '192x192',
|
||||
}
|
||||
]
|
||||
src: "/favicon/favicon-light-32.png",
|
||||
theme: "light",
|
||||
sizes: "32x32",
|
||||
},
|
||||
{
|
||||
src: "/favicon/favicon-light-128.png",
|
||||
theme: "light",
|
||||
sizes: "128x128",
|
||||
},
|
||||
{
|
||||
src: "/favicon/favicon-light-180.png",
|
||||
theme: "light",
|
||||
sizes: "180x180",
|
||||
},
|
||||
{
|
||||
src: "/favicon/favicon-light-192.png",
|
||||
theme: "light",
|
||||
sizes: "192x192",
|
||||
},
|
||||
{
|
||||
src: "/favicon/favicon-dark-32.png",
|
||||
theme: "dark",
|
||||
sizes: "32x32",
|
||||
},
|
||||
{
|
||||
src: "/favicon/favicon-dark-128.png",
|
||||
theme: "dark",
|
||||
sizes: "128x128",
|
||||
},
|
||||
{
|
||||
src: "/favicon/favicon-dark-180.png",
|
||||
theme: "dark",
|
||||
sizes: "180x180",
|
||||
},
|
||||
{
|
||||
src: "/favicon/favicon-dark-192.png",
|
||||
theme: "dark",
|
||||
sizes: "192x192",
|
||||
},
|
||||
];
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
import { LinkPreset, type NavBarLink } from '@/types/config'
|
||||
import I18nKey from '@i18n/i18nKey'
|
||||
import { i18n } from '@i18n/translation'
|
||||
import { LinkPreset, type NavBarLink } from "@/types/config";
|
||||
import I18nKey from "@i18n/i18nKey";
|
||||
import { i18n } from "@i18n/translation";
|
||||
|
||||
export const LinkPresets: { [key in LinkPreset]: NavBarLink } = {
|
||||
[LinkPreset.Home]: {
|
||||
name: i18n(I18nKey.home),
|
||||
url: '/',
|
||||
url: "/",
|
||||
},
|
||||
[LinkPreset.About]: {
|
||||
name: i18n(I18nKey.about),
|
||||
url: '/about/',
|
||||
url: "/about/",
|
||||
},
|
||||
[LinkPreset.Archive]: {
|
||||
name: i18n(I18nKey.archive),
|
||||
url: '/archive/',
|
||||
url: "/archive/",
|
||||
},
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { defineCollection, z } from 'astro:content'
|
||||
import { defineCollection, z } from "astro:content";
|
||||
|
||||
const postsCollection = defineCollection({
|
||||
schema: z.object({
|
||||
|
@ -10,7 +10,7 @@ const postsCollection = defineCollection({
|
|||
tags: z.array(z.string()).optional(),
|
||||
category: z.string().optional(),
|
||||
}),
|
||||
})
|
||||
});
|
||||
export const collections = {
|
||||
posts: postsCollection,
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
title: Luau for JS Devs
|
||||
published: 2024-07-05
|
||||
description: 'An introduction to a superior alternative to JavaScript: Luau'
|
||||
description: 'An introduction to Luau for JavaScript developers'
|
||||
image: 'https://github.com/luau-lang.png'
|
||||
tags: ['tooling', 'languages', 'web-development', 'luau', 'javascript']
|
||||
category: 'Languages'
|
||||
|
|
|
@ -1,9 +1,21 @@
|
|||
# About
|
||||
This is the demo site for [Fuwari](https://github.com/saicaca/fuwari).
|
||||
<!-- markdownlint-disable MD033 -->
|
||||
<!-- markdownlint-disable MD041 -->
|
||||
|
||||
::github{repo="saicaca/fuwari"}
|
||||
# 👋 hiya, i'm DevComp
|
||||
|
||||
> ### Sources of images used in this site
|
||||
> - [Unsplash](https://unsplash.com/)
|
||||
> - [星と少女](https://www.pixiv.net/artworks/108916539) by [Stella](https://www.pixiv.net/users/93273965)
|
||||
> - [Rabbit - v1.4 Showcase](https://civitai.com/posts/586908) by [Rabbit_YourMajesty](https://civitai.com/user/Rabbit_YourMajesty)
|
||||
<a href="https://lanyard-visualizer-plskz.vercel.app/profile/893762371770802227">
|
||||
<img width="40%" alt="Discord Status" src="https://lanyard-profile-readme.vercel.app/api/893762371770802227?hideTimestamp=true&idleMessage=Just%20%20chillin'...&bg=1e1e2e&borderRadius=10px" align="right" />
|
||||
</a>
|
||||
|
||||
![Systems](https://skillicons.dev/icons?i=rust,go,java,lua,bash,neovim,linux,docker&perline=10)
|
||||
![Web](https://skillicons.dev/icons?i=html,css,js,ts,prisma,mysql,nextjs,vercel&perline=10)
|
||||
|
||||
## Contact
|
||||
|
||||
If you would like to get in touch with me, reach out to me via:
|
||||
|
||||
- Discord - DevComp
|
||||
- Twitter - [@DevComp_](https://twitter.com/DevComp_)
|
||||
- Email - [mailto:hi@devcomp.xyz](hi@devcomp.xyz)
|
||||
|
||||
[![Hits](https://hits-app.vercel.app/hits?url=https://github.com/CompeyDev&bgLeft=cba6f7&bgRight=1e1e2e&label=visits)](https://hits-app.vercel.app/)
|
||||
|
|
|
@ -1,37 +1,37 @@
|
|||
enum I18nKey {
|
||||
home = 'home',
|
||||
about = 'about',
|
||||
archive = 'archive',
|
||||
search = 'search',
|
||||
home = "home",
|
||||
about = "about",
|
||||
archive = "archive",
|
||||
search = "search",
|
||||
|
||||
tags = 'tags',
|
||||
categories = 'categories',
|
||||
recentPosts = 'recentPosts',
|
||||
tags = "tags",
|
||||
categories = "categories",
|
||||
recentPosts = "recentPosts",
|
||||
|
||||
comments = 'comments',
|
||||
comments = "comments",
|
||||
|
||||
untitled = 'untitled',
|
||||
uncategorized = 'uncategorized',
|
||||
noTags = 'noTags',
|
||||
untitled = "untitled",
|
||||
uncategorized = "uncategorized",
|
||||
noTags = "noTags",
|
||||
|
||||
wordCount = 'wordCount',
|
||||
wordsCount = 'wordsCount',
|
||||
minuteCount = 'minuteCount',
|
||||
minutesCount = 'minutesCount',
|
||||
postCount = 'postCount',
|
||||
postsCount = 'postsCount',
|
||||
wordCount = "wordCount",
|
||||
wordsCount = "wordsCount",
|
||||
minuteCount = "minuteCount",
|
||||
minutesCount = "minutesCount",
|
||||
postCount = "postCount",
|
||||
postsCount = "postsCount",
|
||||
|
||||
themeColor = 'themeColor',
|
||||
themeColor = "themeColor",
|
||||
|
||||
lightMode = 'lightMode',
|
||||
darkMode = 'darkMode',
|
||||
systemMode = 'systemMode',
|
||||
lightMode = "lightMode",
|
||||
darkMode = "darkMode",
|
||||
systemMode = "systemMode",
|
||||
|
||||
more = 'more',
|
||||
more = "more",
|
||||
|
||||
author = 'author',
|
||||
publishedAt = 'publishedAt',
|
||||
license = 'license',
|
||||
author = "author",
|
||||
publishedAt = "publishedAt",
|
||||
license = "license",
|
||||
}
|
||||
|
||||
export default I18nKey
|
||||
export default I18nKey;
|
||||
|
|
|
@ -1,38 +1,38 @@
|
|||
import Key from '../i18nKey'
|
||||
import type { Translation } from '../translation'
|
||||
import Key from "../i18nKey";
|
||||
import type { Translation } from "../translation";
|
||||
|
||||
export const en: Translation = {
|
||||
[Key.home]: 'Home',
|
||||
[Key.about]: 'About',
|
||||
[Key.archive]: 'Archive',
|
||||
[Key.search]: 'Search',
|
||||
[Key.home]: "Home",
|
||||
[Key.about]: "About",
|
||||
[Key.archive]: "Archive",
|
||||
[Key.search]: "Search",
|
||||
|
||||
[Key.tags]: 'Tags',
|
||||
[Key.categories]: 'Categories',
|
||||
[Key.recentPosts]: 'Recent Posts',
|
||||
[Key.tags]: "Tags",
|
||||
[Key.categories]: "Categories",
|
||||
[Key.recentPosts]: "Recent Posts",
|
||||
|
||||
[Key.comments]: 'Comments',
|
||||
[Key.comments]: "Comments",
|
||||
|
||||
[Key.untitled]: 'Untitled',
|
||||
[Key.uncategorized]: 'Uncategorized',
|
||||
[Key.noTags]: 'No Tags',
|
||||
[Key.untitled]: "Untitled",
|
||||
[Key.uncategorized]: "Uncategorized",
|
||||
[Key.noTags]: "No Tags",
|
||||
|
||||
[Key.wordCount]: 'word',
|
||||
[Key.wordsCount]: 'words',
|
||||
[Key.minuteCount]: 'minute',
|
||||
[Key.minutesCount]: 'minutes',
|
||||
[Key.postCount]: 'post',
|
||||
[Key.postsCount]: 'posts',
|
||||
[Key.wordCount]: "word",
|
||||
[Key.wordsCount]: "words",
|
||||
[Key.minuteCount]: "minute",
|
||||
[Key.minutesCount]: "minutes",
|
||||
[Key.postCount]: "post",
|
||||
[Key.postsCount]: "posts",
|
||||
|
||||
[Key.themeColor]: 'Theme Color',
|
||||
[Key.themeColor]: "Theme Color",
|
||||
|
||||
[Key.lightMode]: 'Light',
|
||||
[Key.darkMode]: 'Dark',
|
||||
[Key.systemMode]: 'System',
|
||||
[Key.lightMode]: "Light",
|
||||
[Key.darkMode]: "Dark",
|
||||
[Key.systemMode]: "System",
|
||||
|
||||
[Key.more]: 'More',
|
||||
[Key.more]: "More",
|
||||
|
||||
[Key.author]: 'Author',
|
||||
[Key.publishedAt]: 'Published at',
|
||||
[Key.license]: 'License',
|
||||
}
|
||||
[Key.author]: "Author",
|
||||
[Key.publishedAt]: "Published at",
|
||||
[Key.license]: "License",
|
||||
};
|
||||
|
|
|
@ -1,38 +1,38 @@
|
|||
import Key from '../i18nKey'
|
||||
import type { Translation } from '../translation'
|
||||
import Key from "../i18nKey";
|
||||
import type { Translation } from "../translation";
|
||||
|
||||
export const ja: Translation = {
|
||||
[Key.home]: 'Home',
|
||||
[Key.about]: 'About',
|
||||
[Key.archive]: 'Archive',
|
||||
[Key.search]: '検索',
|
||||
[Key.home]: "Home",
|
||||
[Key.about]: "About",
|
||||
[Key.archive]: "Archive",
|
||||
[Key.search]: "検索",
|
||||
|
||||
[Key.tags]: 'タグ',
|
||||
[Key.categories]: 'カテゴリ',
|
||||
[Key.recentPosts]: '最近の投稿',
|
||||
[Key.tags]: "タグ",
|
||||
[Key.categories]: "カテゴリ",
|
||||
[Key.recentPosts]: "最近の投稿",
|
||||
|
||||
[Key.comments]: 'コメント',
|
||||
[Key.comments]: "コメント",
|
||||
|
||||
[Key.untitled]: 'タイトルなし',
|
||||
[Key.uncategorized]: 'カテゴリなし',
|
||||
[Key.noTags]: 'タグなし',
|
||||
[Key.untitled]: "タイトルなし",
|
||||
[Key.uncategorized]: "カテゴリなし",
|
||||
[Key.noTags]: "タグなし",
|
||||
|
||||
[Key.wordCount]: '文字',
|
||||
[Key.wordsCount]: '文字',
|
||||
[Key.minuteCount]: '分',
|
||||
[Key.minutesCount]: '分',
|
||||
[Key.postCount]: '件の投稿',
|
||||
[Key.postsCount]: '件の投稿',
|
||||
[Key.wordCount]: "文字",
|
||||
[Key.wordsCount]: "文字",
|
||||
[Key.minuteCount]: "分",
|
||||
[Key.minutesCount]: "分",
|
||||
[Key.postCount]: "件の投稿",
|
||||
[Key.postsCount]: "件の投稿",
|
||||
|
||||
[Key.themeColor]: 'テーマカラー',
|
||||
[Key.themeColor]: "テーマカラー",
|
||||
|
||||
[Key.lightMode]: 'ライト',
|
||||
[Key.darkMode]: 'ダーク',
|
||||
[Key.systemMode]: 'システム',
|
||||
[Key.lightMode]: "ライト",
|
||||
[Key.darkMode]: "ダーク",
|
||||
[Key.systemMode]: "システム",
|
||||
|
||||
[Key.more]: 'もっと',
|
||||
[Key.more]: "もっと",
|
||||
|
||||
[Key.author]: '作者',
|
||||
[Key.publishedAt]: '公開日',
|
||||
[Key.license]: 'ライセンス',
|
||||
}
|
||||
[Key.author]: "作者",
|
||||
[Key.publishedAt]: "公開日",
|
||||
[Key.license]: "ライセンス",
|
||||
};
|
||||
|
|
|
@ -1,38 +1,38 @@
|
|||
import Key from '../i18nKey'
|
||||
import type { Translation } from '../translation'
|
||||
import Key from "../i18nKey";
|
||||
import type { Translation } from "../translation";
|
||||
|
||||
export const zh_CN: Translation = {
|
||||
[Key.home]: '主页',
|
||||
[Key.about]: '关于',
|
||||
[Key.archive]: '归档',
|
||||
[Key.search]: '搜索',
|
||||
[Key.home]: "主页",
|
||||
[Key.about]: "关于",
|
||||
[Key.archive]: "归档",
|
||||
[Key.search]: "搜索",
|
||||
|
||||
[Key.tags]: '标签',
|
||||
[Key.categories]: '分类',
|
||||
[Key.recentPosts]: '最新文章',
|
||||
[Key.tags]: "标签",
|
||||
[Key.categories]: "分类",
|
||||
[Key.recentPosts]: "最新文章",
|
||||
|
||||
[Key.comments]: '评论',
|
||||
[Key.comments]: "评论",
|
||||
|
||||
[Key.untitled]: '无标题',
|
||||
[Key.uncategorized]: '未分类',
|
||||
[Key.noTags]: '无标签',
|
||||
[Key.untitled]: "无标题",
|
||||
[Key.uncategorized]: "未分类",
|
||||
[Key.noTags]: "无标签",
|
||||
|
||||
[Key.wordCount]: '字',
|
||||
[Key.wordsCount]: '字',
|
||||
[Key.minuteCount]: '分钟',
|
||||
[Key.minutesCount]: '分钟',
|
||||
[Key.postCount]: '篇文章',
|
||||
[Key.postsCount]: '篇文章',
|
||||
[Key.wordCount]: "字",
|
||||
[Key.wordsCount]: "字",
|
||||
[Key.minuteCount]: "分钟",
|
||||
[Key.minutesCount]: "分钟",
|
||||
[Key.postCount]: "篇文章",
|
||||
[Key.postsCount]: "篇文章",
|
||||
|
||||
[Key.themeColor]: '主题色',
|
||||
[Key.themeColor]: "主题色",
|
||||
|
||||
[Key.lightMode]: '亮色',
|
||||
[Key.darkMode]: '暗色',
|
||||
[Key.systemMode]: '跟随系统',
|
||||
[Key.lightMode]: "亮色",
|
||||
[Key.darkMode]: "暗色",
|
||||
[Key.systemMode]: "跟随系统",
|
||||
|
||||
[Key.more]: '更多',
|
||||
[Key.more]: "更多",
|
||||
|
||||
[Key.author]: '作者',
|
||||
[Key.publishedAt]: '发布于',
|
||||
[Key.license]: '许可协议',
|
||||
}
|
||||
[Key.author]: "作者",
|
||||
[Key.publishedAt]: "发布于",
|
||||
[Key.license]: "许可协议",
|
||||
};
|
||||
|
|
|
@ -1,38 +1,38 @@
|
|||
import Key from '../i18nKey'
|
||||
import type { Translation } from '../translation'
|
||||
import Key from "../i18nKey";
|
||||
import type { Translation } from "../translation";
|
||||
|
||||
export const zh_TW: Translation = {
|
||||
[Key.home]: '首頁',
|
||||
[Key.about]: '關於',
|
||||
[Key.archive]: '彙整',
|
||||
[Key.search]: '搜尋',
|
||||
[Key.home]: "首頁",
|
||||
[Key.about]: "關於",
|
||||
[Key.archive]: "彙整",
|
||||
[Key.search]: "搜尋",
|
||||
|
||||
[Key.tags]: '標籤',
|
||||
[Key.categories]: '分類',
|
||||
[Key.recentPosts]: '最新文章',
|
||||
[Key.tags]: "標籤",
|
||||
[Key.categories]: "分類",
|
||||
[Key.recentPosts]: "最新文章",
|
||||
|
||||
[Key.comments]: '評論',
|
||||
[Key.comments]: "評論",
|
||||
|
||||
[Key.untitled]: '無標題',
|
||||
[Key.uncategorized]: '未分類',
|
||||
[Key.noTags]: '無標籤',
|
||||
[Key.untitled]: "無標題",
|
||||
[Key.uncategorized]: "未分類",
|
||||
[Key.noTags]: "無標籤",
|
||||
|
||||
[Key.wordCount]: '字',
|
||||
[Key.wordsCount]: '字',
|
||||
[Key.minuteCount]: '分鐘',
|
||||
[Key.minutesCount]: '分鐘',
|
||||
[Key.postCount]: '篇文章',
|
||||
[Key.postsCount]: '篇文章',
|
||||
[Key.wordCount]: "字",
|
||||
[Key.wordsCount]: "字",
|
||||
[Key.minuteCount]: "分鐘",
|
||||
[Key.minutesCount]: "分鐘",
|
||||
[Key.postCount]: "篇文章",
|
||||
[Key.postsCount]: "篇文章",
|
||||
|
||||
[Key.themeColor]: '主題色',
|
||||
[Key.themeColor]: "主題色",
|
||||
|
||||
[Key.lightMode]: '亮色',
|
||||
[Key.darkMode]: '暗色',
|
||||
[Key.systemMode]: '跟隨系統',
|
||||
[Key.lightMode]: "亮色",
|
||||
[Key.darkMode]: "暗色",
|
||||
[Key.systemMode]: "跟隨系統",
|
||||
|
||||
[Key.more]: '更多',
|
||||
[Key.more]: "更多",
|
||||
|
||||
[Key.author]: '作者',
|
||||
[Key.publishedAt]: '發佈於',
|
||||
[Key.license]: '許可協議',
|
||||
}
|
||||
[Key.author]: "作者",
|
||||
[Key.publishedAt]: "發佈於",
|
||||
[Key.license]: "許可協議",
|
||||
};
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import { siteConfig } from '../config'
|
||||
import type I18nKey from './i18nKey'
|
||||
import { en } from './languages/en'
|
||||
import { ja } from './languages/ja'
|
||||
import { zh_CN } from './languages/zh_CN'
|
||||
import { zh_TW } from './languages/zh_TW'
|
||||
import { siteConfig } from "../config";
|
||||
import type I18nKey from "./i18nKey";
|
||||
import { en } from "./languages/en";
|
||||
import { ja } from "./languages/ja";
|
||||
import { zh_CN } from "./languages/zh_CN";
|
||||
import { zh_TW } from "./languages/zh_TW";
|
||||
|
||||
export type Translation = {
|
||||
[K in I18nKey]: string
|
||||
}
|
||||
[K in I18nKey]: string;
|
||||
};
|
||||
|
||||
const defaultTranslation = en
|
||||
const defaultTranslation = en;
|
||||
|
||||
const map: { [key: string]: Translation } = {
|
||||
en: en,
|
||||
|
@ -20,13 +20,13 @@ const map: { [key: string]: Translation } = {
|
|||
zh_tw: zh_TW,
|
||||
ja: ja,
|
||||
ja_jp: ja,
|
||||
}
|
||||
};
|
||||
|
||||
export function getTranslation(lang: string): Translation {
|
||||
return map[lang.toLowerCase()] || defaultTranslation
|
||||
return map[lang.toLowerCase()] || defaultTranslation;
|
||||
}
|
||||
|
||||
export function i18n(key: I18nKey): string {
|
||||
const lang = siteConfig.lang || 'en'
|
||||
return getTranslation(lang)[key]
|
||||
const lang = siteConfig.lang || "en";
|
||||
return getTranslation(lang)[key];
|
||||
}
|
||||
|
|
|
@ -1,17 +1,22 @@
|
|||
---
|
||||
import GlobalStyles from "@components/GlobalStyles.astro";
|
||||
import '@fontsource/roboto/400.css';
|
||||
import '@fontsource/roboto/500.css';
|
||||
import '@fontsource/roboto/700.css';
|
||||
import "@fontsource/roboto/400.css";
|
||||
import "@fontsource/roboto/500.css";
|
||||
import "@fontsource/roboto/700.css";
|
||||
import ImageWrapper from "@components/misc/ImageWrapper.astro";
|
||||
|
||||
import {pathsEqual} from "@utils/url-utils";
|
||||
import { profileConfig, siteConfig } from "@/config";
|
||||
import ConfigCarrier from "@components/ConfigCarrier.astro";
|
||||
import {profileConfig, siteConfig} from "@/config";
|
||||
import {Favicon} from "../types/config";
|
||||
import {defaultFavicons} from "../constants/icon";
|
||||
import {LIGHT_MODE, DARK_MODE, AUTO_MODE, DEFAULT_THEME} from "../constants/constants";
|
||||
import {url} from "../utils/url-utils";
|
||||
import { pathsEqual } from "@utils/url-utils";
|
||||
import {
|
||||
AUTO_MODE,
|
||||
DARK_MODE,
|
||||
DEFAULT_THEME,
|
||||
LIGHT_MODE,
|
||||
} from "../constants/constants";
|
||||
import { defaultFavicons } from "../constants/icon";
|
||||
import type { Favicon } from "../types/config";
|
||||
import { url } from "../utils/url-utils";
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
|
@ -21,25 +26,25 @@ interface Props {
|
|||
|
||||
let { title, banner, description } = Astro.props;
|
||||
|
||||
const isHomePage = pathsEqual(Astro.url.pathname, '/');
|
||||
const isHomePage = pathsEqual(Astro.url.pathname, "/");
|
||||
|
||||
const testPathName = Astro.url.pathname;
|
||||
|
||||
const anim = {
|
||||
old: {
|
||||
name: 'fadeIn',
|
||||
duration: '4s',
|
||||
easing: 'linear',
|
||||
fillMode: 'forwards',
|
||||
mixBlendMode: 'normal',
|
||||
name: "fadeIn",
|
||||
duration: "4s",
|
||||
easing: "linear",
|
||||
fillMode: "forwards",
|
||||
mixBlendMode: "normal",
|
||||
},
|
||||
new: {
|
||||
name: 'fadeOut',
|
||||
duration: '4s',
|
||||
easing: 'linear',
|
||||
fillMode: 'backwards',
|
||||
mixBlendMode: 'normal',
|
||||
}
|
||||
name: "fadeOut",
|
||||
duration: "4s",
|
||||
easing: "linear",
|
||||
fillMode: "backwards",
|
||||
mixBlendMode: "normal",
|
||||
},
|
||||
};
|
||||
|
||||
const myFade = {
|
||||
|
@ -50,7 +55,7 @@ const myFade = {
|
|||
// defines global css variables
|
||||
// why doing this in Layout instead of GlobalStyles: https://github.com/withastro/astro/issues/6728#issuecomment-1502203757
|
||||
const configHue = siteConfig.themeColor.hue;
|
||||
if (!banner || typeof banner !== 'string' || banner.trim() === '') {
|
||||
if (!banner || typeof banner !== "string" || banner.trim() === "") {
|
||||
banner = siteConfig.banner.src;
|
||||
}
|
||||
|
||||
|
@ -59,17 +64,17 @@ banner = siteConfig.banner.src;
|
|||
|
||||
const enableBanner = siteConfig.banner.enable;
|
||||
|
||||
let pageTitle;
|
||||
let pageTitle: string;
|
||||
if (title) {
|
||||
pageTitle = `${title} - ${siteConfig.title}`;
|
||||
} else {
|
||||
pageTitle = `${siteConfig.title} - ${siteConfig.subtitle}`;
|
||||
}
|
||||
|
||||
const favicons: Favicon[] = siteConfig.favicon.length > 0 ? siteConfig.favicon : defaultFavicons
|
||||
|
||||
const siteLang = siteConfig.lang.replace('_', '-')
|
||||
const favicons: Favicon[] =
|
||||
siteConfig.favicon.length > 0 ? siteConfig.favicon : defaultFavicons;
|
||||
|
||||
const siteLang = siteConfig.lang.replace("_", "-");
|
||||
---
|
||||
|
||||
<!DOCTYPE html>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
---
|
||||
import Layout from "./Layout.astro";
|
||||
import Navbar from "@components/Navbar.astro";
|
||||
import SideBar from "@components/widget/SideBar.astro";
|
||||
import {pathsEqual} from "@utils/url-utils";
|
||||
import { siteConfig } from "@/config";
|
||||
import Footer from "@components/Footer.astro";
|
||||
import Navbar from "@components/Navbar.astro";
|
||||
import BackToTop from "@components/control/BackToTop.astro";
|
||||
import {siteConfig} from "@/config";
|
||||
import SideBar from "@components/widget/SideBar.astro";
|
||||
import { pathsEqual } from "@utils/url-utils";
|
||||
import Layout from "./Layout.astro";
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
|
@ -13,10 +13,9 @@ interface Props {
|
|||
description?: string;
|
||||
}
|
||||
|
||||
const { title, banner, description } = Astro.props
|
||||
const isHomePage = pathsEqual(Astro.url.pathname, '/')
|
||||
const enableBanner = siteConfig.banner.enable
|
||||
|
||||
const { title, banner, description } = Astro.props;
|
||||
const isHomePage = pathsEqual(Astro.url.pathname, "/");
|
||||
const enableBanner = siteConfig.banner.enable;
|
||||
---
|
||||
|
||||
<Layout title={title} banner={banner} description={description}>
|
||||
|
|
|
@ -1,21 +1,20 @@
|
|||
---
|
||||
import MainGridLayout from "../layouts/MainGridLayout.astro";
|
||||
import PostCard from "../components/PostCard.astro";
|
||||
import Pagination from "../components/control/Pagination.astro";
|
||||
import {getSortedPosts} from "../utils/content-utils";
|
||||
import {getPostUrlBySlug} from "../utils/url-utils";
|
||||
import {PAGE_SIZE} from "../constants/constants";
|
||||
import PostPage from "../components/PostPage.astro";
|
||||
import Pagination from "../components/control/Pagination.astro";
|
||||
import { PAGE_SIZE } from "../constants/constants";
|
||||
import MainGridLayout from "../layouts/MainGridLayout.astro";
|
||||
import { getSortedPosts } from "../utils/content-utils";
|
||||
import { getPostUrlBySlug } from "../utils/url-utils";
|
||||
|
||||
export async function getStaticPaths({ paginate }) {
|
||||
const allBlogPosts = await getSortedPosts();
|
||||
return paginate(allBlogPosts, { pageSize: PAGE_SIZE });
|
||||
}
|
||||
|
||||
const {page} = Astro.props;
|
||||
const { page } = Astro.props;
|
||||
|
||||
const len = page.data.length;
|
||||
|
||||
---
|
||||
|
||||
<MainGridLayout>
|
||||
|
|
|
@ -2,15 +2,14 @@
|
|||
|
||||
import MainGridLayout from "../layouts/MainGridLayout.astro";
|
||||
|
||||
import { getEntry } from 'astro:content'
|
||||
import {i18n} from "../i18n/translation";
|
||||
import I18nKey from "../i18n/i18nKey";
|
||||
import { getEntry } from "astro:content";
|
||||
import Markdown from "@components/misc/Markdown.astro";
|
||||
import I18nKey from "../i18n/i18nKey";
|
||||
import { i18n } from "../i18n/translation";
|
||||
|
||||
const aboutPost = await getEntry('spec', 'about')
|
||||
|
||||
const { Content } = await aboutPost.render()
|
||||
const aboutPost = await getEntry("spec", "about");
|
||||
|
||||
const { Content } = await aboutPost.render();
|
||||
---
|
||||
<MainGridLayout title={i18n(I18nKey.about)} description={i18n(I18nKey.about)}>
|
||||
<div class="flex w-full rounded-[var(--radius-large)] overflow-hidden relative min-h-32">
|
||||
|
|
|
@ -1,23 +1,22 @@
|
|||
---
|
||||
import {getCategoryList, getSortedPosts} from "@utils/content-utils";
|
||||
import MainGridLayout from "@layouts/MainGridLayout.astro";
|
||||
import ArchivePanel from "@components/ArchivePanel.astro";
|
||||
import {i18n} from "@i18n/translation";
|
||||
import I18nKey from "@i18n/i18nKey";
|
||||
import { i18n } from "@i18n/translation";
|
||||
import MainGridLayout from "@layouts/MainGridLayout.astro";
|
||||
import { getCategoryList, getSortedPosts } from "@utils/content-utils";
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const categories = await getCategoryList();
|
||||
return categories.map(category => {
|
||||
return {
|
||||
params: {
|
||||
category: category.name
|
||||
}
|
||||
}
|
||||
category: category.name,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const { category } = Astro.params;
|
||||
|
||||
---
|
||||
|
||||
<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 {i18n} from "@i18n/translation";
|
||||
import { UNCATEGORIZED } from "@constants/constants";
|
||||
import I18nKey from "@i18n/i18nKey";
|
||||
import {UNCATEGORIZED} from "@constants/constants";
|
||||
import { i18n } from "@i18n/translation";
|
||||
import MainGridLayout from "@layouts/MainGridLayout.astro";
|
||||
---
|
||||
|
||||
<MainGridLayout title={i18n(I18nKey.archive)}>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
---
|
||||
import { getCollection, getEntry } from "astro:content";
|
||||
import MainGridLayout from "@layouts/MainGridLayout.astro";
|
||||
import ArchivePanel from "@components/ArchivePanel.astro";
|
||||
import {i18n} from "@i18n/translation";
|
||||
import I18nKey from "@i18n/i18nKey";
|
||||
import { i18n } from "@i18n/translation";
|
||||
import MainGridLayout from "@layouts/MainGridLayout.astro";
|
||||
---
|
||||
|
||||
<MainGridLayout title={i18n(I18nKey.archive)}>
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
---
|
||||
import {getSortedPosts} from "@utils/content-utils";
|
||||
import MainGridLayout from "@layouts/MainGridLayout.astro";
|
||||
import ArchivePanel from "@components/ArchivePanel.astro";
|
||||
import {i18n} from "@i18n/translation";
|
||||
import I18nKey from "@i18n/i18nKey";
|
||||
|
||||
import { i18n } from "@i18n/translation";
|
||||
import MainGridLayout from "@layouts/MainGridLayout.astro";
|
||||
import { getSortedPosts } from "@utils/content-utils";
|
||||
|
||||
export async function getStaticPaths() {
|
||||
let posts = await getSortedPosts()
|
||||
const posts = await getSortedPosts();
|
||||
|
||||
const allTags = posts.reduce((acc, post) => {
|
||||
post.data.tags.forEach(tag => acc.add(tag));
|
||||
post.data.tags?.forEach(tag => acc.add(tag));
|
||||
return acc;
|
||||
}, new Set());
|
||||
|
||||
|
@ -19,14 +18,13 @@ export async function getStaticPaths() {
|
|||
return allTagsArray.map(tag => {
|
||||
return {
|
||||
params: {
|
||||
tag: tag
|
||||
}
|
||||
}
|
||||
tag: tag,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const { tag } = Astro.params;
|
||||
|
||||
const { tag } = Astro.params as { tag: string };
|
||||
---
|
||||
|
||||
<MainGridLayout title={i18n(I18nKey.archive)} description={i18n(I18nKey.archive)}>
|
||||
|
|
|
@ -1,25 +1,26 @@
|
|||
---
|
||||
import { getCollection } from 'astro:content';
|
||||
import MainGridLayout from "@layouts/MainGridLayout.astro";
|
||||
import ImageWrapper from "../../components/misc/ImageWrapper.astro";
|
||||
import {Icon} from "astro-icon/components";
|
||||
import PostMetadata from "../../components/PostMeta.astro";
|
||||
import {i18n} from "@i18n/translation";
|
||||
import I18nKey from "@i18n/i18nKey";
|
||||
import {getDir, getPostUrlBySlug} from "@utils/url-utils";
|
||||
import License from "@components/misc/License.astro";
|
||||
import {licenseConfig} from "src/config";
|
||||
import Markdown from "@components/misc/Markdown.astro";
|
||||
import path from "path";
|
||||
import {profileConfig} from "../../config";
|
||||
import {formatDateToYYYYMMDD} from "../../utils/date-utils";
|
||||
import { getCollection } from "astro:content";
|
||||
import License from "@components/misc/License.astro";
|
||||
import Markdown from "@components/misc/Markdown.astro";
|
||||
import I18nKey from "@i18n/i18nKey";
|
||||
import { i18n } from "@i18n/translation";
|
||||
import MainGridLayout from "@layouts/MainGridLayout.astro";
|
||||
import { getDir, getPostUrlBySlug } from "@utils/url-utils";
|
||||
import { Icon } from "astro-icon/components";
|
||||
import { licenseConfig } from "src/config";
|
||||
import PostMetadata from "../../components/PostMeta.astro";
|
||||
import ImageWrapper from "../../components/misc/ImageWrapper.astro";
|
||||
import { profileConfig } from "../../config";
|
||||
import { formatDateToYYYYMMDD } from "../../utils/date-utils";
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const blogEntries = await getCollection('posts', ({ data }) => {
|
||||
const blogEntries = await getCollection("posts", ({ data }) => {
|
||||
return import.meta.env.PROD ? data.draft !== true : true;
|
||||
});
|
||||
return blogEntries.map(entry => ({
|
||||
params: { slug: entry.slug }, props: { entry },
|
||||
params: { slug: entry.slug },
|
||||
props: { entry },
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -31,18 +32,17 @@ const { remarkPluginFrontmatter } = await entry.render();
|
|||
const jsonLd = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BlogPosting",
|
||||
"headline": entry.data.title,
|
||||
"description": entry.data.description || entry.data.title,
|
||||
"keywords": entry.data.tags,
|
||||
"author": {
|
||||
headline: entry.data.title,
|
||||
description: entry.data.description || entry.data.title,
|
||||
keywords: entry.data.tags,
|
||||
author: {
|
||||
"@type": "Person",
|
||||
"name": profileConfig.name,
|
||||
"url": Astro.site
|
||||
name: profileConfig.name,
|
||||
url: Astro.site,
|
||||
},
|
||||
"datePublished": formatDateToYYYYMMDD(entry.data.published),
|
||||
datePublished: formatDateToYYYYMMDD(entry.data.published),
|
||||
// TODO include cover image here
|
||||
}
|
||||
|
||||
};
|
||||
---
|
||||
<MainGridLayout banner={entry.data.image} title={entry.data.title} description={entry.data.description}>
|
||||
<script 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 = `
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
Sitemap: ${new URL('sitemap-index.xml', import.meta.env.SITE).href}
|
||||
Sitemap: ${new URL("sitemap-index.xml", import.meta.env.SITE).href}
|
||||
`.trim();
|
||||
|
||||
export const GET: APIRoute = () => {
|
||||
return new Response(robotsTxt, {
|
||||
headers: {
|
||||
'Content-Type': 'text/plain; charset=utf-8',
|
||||
"Content-Type": "text/plain; charset=utf-8",
|
||||
},
|
||||
});
|
||||
};
|
|
@ -1,23 +1,23 @@
|
|||
import rss from '@astrojs/rss';
|
||||
import {siteConfig} from '@/config';
|
||||
import { getCollection } from 'astro:content';
|
||||
import sanitizeHtml from 'sanitize-html';
|
||||
import MarkdownIt from 'markdown-it';
|
||||
import { getCollection } from "astro:content";
|
||||
import { siteConfig } from "@/config";
|
||||
import rss from "@astrojs/rss";
|
||||
import MarkdownIt from "markdown-it";
|
||||
import sanitizeHtml from "sanitize-html";
|
||||
const parser = new MarkdownIt();
|
||||
|
||||
export async function GET(context: any) {
|
||||
const blog = await getCollection('posts');
|
||||
const blog = await getCollection("posts");
|
||||
return rss({
|
||||
title: siteConfig.title,
|
||||
description: siteConfig.subtitle || 'No description',
|
||||
description: siteConfig.subtitle || "No description",
|
||||
site: context.site,
|
||||
items: blog.map((post) => ({
|
||||
items: blog.map(post => ({
|
||||
title: post.data.title,
|
||||
pubDate: post.data.published,
|
||||
description: post.data.description,
|
||||
link: `/posts/${post.slug}/`,
|
||||
content: sanitizeHtml(parser.render(post.body), {
|
||||
allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img'])
|
||||
allowedTags: sanitizeHtml.defaults.allowedTags.concat(["img"]),
|
||||
}),
|
||||
})),
|
||||
customData: `<language>${siteConfig.lang}</language>`,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/// <reference types="mdast" />
|
||||
import { h } from 'hastscript'
|
||||
import { h } from "hastscript";
|
||||
|
||||
/**
|
||||
* Creates an admonition component.
|
||||
|
@ -12,20 +12,21 @@ import { h } from 'hastscript'
|
|||
*/
|
||||
export function AdmonitionComponent(properties, children, type) {
|
||||
if (!Array.isArray(children) || children.length === 0)
|
||||
return h("div",
|
||||
{ class: 'hidden' },
|
||||
'Invalid admonition directive. (Admonition directives must be of block type ":::note{name="name"} <content> :::")'
|
||||
return h(
|
||||
"div",
|
||||
{ class: "hidden" },
|
||||
'Invalid admonition directive. (Admonition directives must be of block type ":::note{name="name"} <content> :::")',
|
||||
);
|
||||
|
||||
let label = null
|
||||
if (properties && properties['has-directive-label']) {
|
||||
let label = null;
|
||||
if (properties?.["has-directive-label"]) {
|
||||
label = children[0]; // The first child is the label
|
||||
children = children.slice(1);
|
||||
label.tagName = "div"; // Change the tag <p> to <div>
|
||||
}
|
||||
|
||||
return h(`blockquote`,
|
||||
{ class: `admonition bdm-${type}` },
|
||||
[ h("span", { class: `bdm-title` }, label ? label : type.toUpperCase()), ...children]
|
||||
);
|
||||
return h("blockquote", { class: `admonition bdm-${type}` }, [
|
||||
h("span", { class: "bdm-title" }, label ? label : type.toUpperCase()),
|
||||
...children,
|
||||
]);
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/// <reference types="mdast" />
|
||||
import { h } from 'hastscript'
|
||||
import { h } from "hastscript";
|
||||
|
||||
/**
|
||||
* Creates a GitHub Card component.
|
||||
|
@ -11,67 +11,48 @@ import { h } from 'hastscript'
|
|||
*/
|
||||
export function GithubCardComponent(properties, children) {
|
||||
if (Array.isArray(children) && children.length !== 0)
|
||||
return h("div",
|
||||
{ class: 'hidden' },
|
||||
['Invalid directive. ("github" directive must be leaf type "::github{repo="owner/repo"}")']
|
||||
);
|
||||
return h("div", { class: "hidden" }, [
|
||||
'Invalid directive. ("github" directive must be leaf type "::github{repo="owner/repo"}")',
|
||||
]);
|
||||
|
||||
if (!properties.repo || !properties.repo.includes("/"))
|
||||
return h("div",
|
||||
{ class: 'hidden' },
|
||||
'Invalid repository. ("repo" attributte must be in the format "owner/repo")'
|
||||
return h(
|
||||
"div",
|
||||
{ class: "hidden" },
|
||||
'Invalid repository. ("repo" attributte must be in the format "owner/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(
|
||||
`span#${cardUuid}-language`,
|
||||
{ class: "gc-language" },
|
||||
"Waiting..."
|
||||
)
|
||||
"Waiting...",
|
||||
);
|
||||
|
||||
const nTitle = h(
|
||||
`div`,
|
||||
{ class: "gc-titlebar" },
|
||||
[
|
||||
h("div", { class: "gc-titlebar-left"}, [
|
||||
const nTitle = h("div", { class: "gc-titlebar" }, [
|
||||
h("div", { class: "gc-titlebar-left" }, [
|
||||
h("div", { class: "gc-owner" }, [
|
||||
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-repo" }, repo.split("/")[1] )
|
||||
h("div", { class: "gc-divider" }, "/"),
|
||||
h("div", { class: "gc-repo" }, repo.split("/")[1]),
|
||||
]),
|
||||
h("div", { class: "github-logo"})
|
||||
]
|
||||
)
|
||||
h("div", { class: "github-logo" }),
|
||||
]);
|
||||
|
||||
const nDescription = h(
|
||||
`div#${cardUuid}-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 nForks = h(
|
||||
`div#${cardUuid}-forks`,
|
||||
{ class: "gc-forks" },
|
||||
"0K"
|
||||
)
|
||||
const nLicense = h(
|
||||
`div#${cardUuid}-license`,
|
||||
{ class: "gc-license" },
|
||||
"0K"
|
||||
)
|
||||
const nStars = h(`div#${cardUuid}-stars`, { class: "gc-stars" }, "00K");
|
||||
const nForks = h(`div#${cardUuid}-forks`, { class: "gc-forks" }, "0K");
|
||||
const nLicense = h(`div#${cardUuid}-license`, { class: "gc-license" }, "0K");
|
||||
|
||||
const nScript = h(
|
||||
`script#${cardUuid}-script`,
|
||||
|
@ -98,22 +79,22 @@ export function GithubCardComponent(properties, children) {
|
|||
c.classList.add("fetch-error");
|
||||
console.warn("[GITHUB-CARD] (Error) Loading card for ${repo} | ${cardUuid}.")
|
||||
})
|
||||
`
|
||||
)
|
||||
`,
|
||||
);
|
||||
|
||||
return h(`a#${cardUuid}-card`,
|
||||
{ class: "card-github fetch-waiting no-styling",
|
||||
return h(
|
||||
`a#${cardUuid}-card`,
|
||||
{
|
||||
class: "card-github fetch-waiting no-styling",
|
||||
href: `https://github.com/${repo}`,
|
||||
target: '_blank',
|
||||
repo },
|
||||
target: "_blank",
|
||||
repo,
|
||||
},
|
||||
[
|
||||
nTitle,
|
||||
nDescription,
|
||||
h("div",
|
||||
{ class: "gc-infobar" },
|
||||
[nStars, nForks, nLicense, nLanguage]
|
||||
),
|
||||
nScript
|
||||
]
|
||||
h("div", { class: "gc-infobar" }, [nStars, nForks, nLicense, nLanguage]),
|
||||
nScript,
|
||||
],
|
||||
);
|
||||
}
|
|
@ -1,27 +1,33 @@
|
|||
// biome-ignore lint/suspicious/noShadowRestrictedNames: <explanation>
|
||||
import { h } from 'hastscript';
|
||||
import {visit} from 'unist-util-visit'
|
||||
import { h } from "hastscript";
|
||||
import { visit } from "unist-util-visit";
|
||||
|
||||
export function parseDirectiveNode() {
|
||||
return (tree, { data }) => {
|
||||
visit(tree, function (node) {
|
||||
visit(tree, node => {
|
||||
if (
|
||||
node.type === 'containerDirective' ||
|
||||
node.type === 'leafDirective' ||
|
||||
node.type === 'textDirective'
|
||||
node.type === "containerDirective" ||
|
||||
node.type === "leafDirective" ||
|
||||
node.type === "textDirective"
|
||||
) {
|
||||
if (!node.data) {
|
||||
node.data = {};
|
||||
}
|
||||
|
||||
const data = node.data;
|
||||
node.attributes = node.attributes || {};
|
||||
if (
|
||||
node.children.length > 0 &&
|
||||
node.children[0].data &&
|
||||
node.children[0].data.directiveLabel
|
||||
) {
|
||||
const data = node.data || (node.data = {})
|
||||
node.attributes = node.attributes || {}
|
||||
if (node.children.length > 0 && node.children[0].data && node.children[0].data.directiveLabel) {
|
||||
// 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.hProperties = hast.properties
|
||||
}
|
||||
})
|
||||
data.hName = hast.tagName;
|
||||
data.hProperties = hast.properties;
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
// biome-ignore lint/suspicious/noShadowRestrictedNames: <explanation>
|
||||
import { toString } from 'mdast-util-to-string'
|
||||
import getReadingTime from 'reading-time'
|
||||
import { toString } from "mdast-util-to-string";
|
||||
import getReadingTime from "reading-time";
|
||||
|
||||
export function remarkReadingTime() {
|
||||
return (tree, { data }) => {
|
||||
const textOnPage = toString(tree)
|
||||
const readingTime = getReadingTime(textOnPage)
|
||||
const textOnPage = toString(tree);
|
||||
const readingTime = getReadingTime(textOnPage);
|
||||
data.astro.frontmatter.minutes = Math.max(
|
||||
1,
|
||||
Math.round(readingTime.minutes),
|
||||
)
|
||||
data.astro.frontmatter.words = readingTime.words
|
||||
}
|
||||
);
|
||||
data.astro.frontmatter.words = readingTime.words;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,29 +1,29 @@
|
|||
import type { LIGHT_MODE, DARK_MODE, AUTO_MODE } from "@constants/constants"
|
||||
import type { AUTO_MODE, DARK_MODE, LIGHT_MODE } from "@constants/constants";
|
||||
|
||||
export type SiteConfig = {
|
||||
title: string
|
||||
subtitle: string
|
||||
title: string;
|
||||
subtitle: string;
|
||||
|
||||
lang: string
|
||||
lang: string;
|
||||
|
||||
themeColor: {
|
||||
hue: number
|
||||
fixed: boolean
|
||||
}
|
||||
hue: number;
|
||||
fixed: boolean;
|
||||
};
|
||||
banner: {
|
||||
enable: boolean
|
||||
src: string
|
||||
position?: string
|
||||
}
|
||||
enable: boolean;
|
||||
src: string;
|
||||
position?: string;
|
||||
};
|
||||
|
||||
favicon: Favicon[]
|
||||
}
|
||||
favicon: Favicon[];
|
||||
};
|
||||
|
||||
export type Favicon = {
|
||||
src: string
|
||||
theme?: 'light' | 'dark'
|
||||
sizes?: string
|
||||
}
|
||||
src: string;
|
||||
theme?: "light" | "dark";
|
||||
sizes?: string;
|
||||
};
|
||||
|
||||
export enum LinkPreset {
|
||||
Home = 0,
|
||||
|
@ -32,30 +32,33 @@ export enum LinkPreset {
|
|||
}
|
||||
|
||||
export type NavBarLink = {
|
||||
name: string
|
||||
url: string
|
||||
external?: boolean
|
||||
}
|
||||
name: string;
|
||||
url: string;
|
||||
external?: boolean;
|
||||
};
|
||||
|
||||
export type NavBarConfig = {
|
||||
links: (NavBarLink | LinkPreset)[]
|
||||
}
|
||||
links: (NavBarLink | LinkPreset)[];
|
||||
};
|
||||
|
||||
export type ProfileConfig = {
|
||||
avatar?: string
|
||||
name: string
|
||||
bio?: string
|
||||
avatar?: string;
|
||||
name: string;
|
||||
bio?: string;
|
||||
links: {
|
||||
name: string
|
||||
url: string
|
||||
icon: string
|
||||
}[]
|
||||
}
|
||||
name: string;
|
||||
url: string;
|
||||
icon: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
export type LicenseConfig = {
|
||||
enable: boolean
|
||||
name: string
|
||||
url: string
|
||||
}
|
||||
enable: boolean;
|
||||
name: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
export type LIGHT_DARK_MODE = typeof LIGHT_MODE | typeof DARK_MODE | typeof AUTO_MODE
|
||||
export type LIGHT_DARK_MODE =
|
||||
| typeof LIGHT_MODE
|
||||
| typeof DARK_MODE
|
||||
| typeof AUTO_MODE;
|
||||
|
|
|
@ -1,83 +1,83 @@
|
|||
import I18nKey from '@i18n/i18nKey'
|
||||
import { i18n } from '@i18n/translation'
|
||||
import { getCollection } from 'astro:content'
|
||||
import { getCollection } from "astro:content";
|
||||
import I18nKey from "@i18n/i18nKey";
|
||||
import { i18n } from "@i18n/translation";
|
||||
|
||||
export async function getSortedPosts() {
|
||||
const allBlogPosts = await getCollection('posts', ({ data }) => {
|
||||
return import.meta.env.PROD ? data.draft !== true : true
|
||||
})
|
||||
const allBlogPosts = await getCollection("posts", ({ data }) => {
|
||||
return import.meta.env.PROD ? data.draft !== true : true;
|
||||
});
|
||||
const sorted = allBlogPosts.sort((a, b) => {
|
||||
const dateA = new Date(a.data.published)
|
||||
const dateB = new Date(b.data.published)
|
||||
return dateA > dateB ? -1 : 1
|
||||
})
|
||||
const dateA = new Date(a.data.published);
|
||||
const dateB = new Date(b.data.published);
|
||||
return dateA > dateB ? -1 : 1;
|
||||
});
|
||||
|
||||
for (let i = 1; i < sorted.length; i++) {
|
||||
sorted[i].data.nextSlug = sorted[i - 1].slug
|
||||
sorted[i].data.nextTitle = sorted[i - 1].data.title
|
||||
sorted[i].data.nextSlug = sorted[i - 1].slug;
|
||||
sorted[i].data.nextTitle = sorted[i - 1].data.title;
|
||||
}
|
||||
for (let i = 0; i < sorted.length - 1; i++) {
|
||||
sorted[i].data.prevSlug = sorted[i + 1].slug
|
||||
sorted[i].data.prevTitle = sorted[i + 1].data.title
|
||||
sorted[i].data.prevSlug = sorted[i + 1].slug;
|
||||
sorted[i].data.prevTitle = sorted[i + 1].data.title;
|
||||
}
|
||||
|
||||
return sorted
|
||||
return sorted;
|
||||
}
|
||||
|
||||
export type Tag = {
|
||||
name: string
|
||||
count: number
|
||||
}
|
||||
name: string;
|
||||
count: number;
|
||||
};
|
||||
|
||||
export async function getTagList(): Promise<Tag[]> {
|
||||
const allBlogPosts = await getCollection('posts', ({ data }) => {
|
||||
return import.meta.env.PROD ? data.draft !== true : true
|
||||
})
|
||||
const allBlogPosts = await getCollection("posts", ({ data }) => {
|
||||
return import.meta.env.PROD ? data.draft !== true : true;
|
||||
});
|
||||
|
||||
const countMap: { [key: string]: number } = {}
|
||||
const countMap: { [key: string]: number } = {};
|
||||
allBlogPosts.map(post => {
|
||||
post.data.tags.map((tag: string) => {
|
||||
if (!countMap[tag]) countMap[tag] = 0
|
||||
countMap[tag]++
|
||||
})
|
||||
})
|
||||
if (!countMap[tag]) countMap[tag] = 0;
|
||||
countMap[tag]++;
|
||||
});
|
||||
});
|
||||
|
||||
// sort tags
|
||||
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 = {
|
||||
name: string
|
||||
count: number
|
||||
}
|
||||
name: string;
|
||||
count: number;
|
||||
};
|
||||
|
||||
export async function getCategoryList(): Promise<Category[]> {
|
||||
const allBlogPosts = await getCollection('posts', ({ data }) => {
|
||||
return import.meta.env.PROD ? data.draft !== true : true
|
||||
})
|
||||
const count: { [key: string]: number } = {}
|
||||
const allBlogPosts = await getCollection("posts", ({ data }) => {
|
||||
return import.meta.env.PROD ? data.draft !== true : true;
|
||||
});
|
||||
const count: { [key: string]: number } = {};
|
||||
allBlogPosts.map(post => {
|
||||
if (!post.data.category) {
|
||||
const ucKey = i18n(I18nKey.uncategorized)
|
||||
count[ucKey] = count[ucKey] ? count[ucKey] + 1 : 1
|
||||
return
|
||||
const ucKey = i18n(I18nKey.uncategorized);
|
||||
count[ucKey] = count[ucKey] ? count[ucKey] + 1 : 1;
|
||||
return;
|
||||
}
|
||||
count[post.data.category] = count[post.data.category]
|
||||
? count[post.data.category] + 1
|
||||
: 1
|
||||
})
|
||||
: 1;
|
||||
});
|
||||
|
||||
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) {
|
||||
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 {
|
||||
return date.toISOString().substring(0, 10)
|
||||
return date.toISOString().substring(0, 10);
|
||||
}
|
||||
|
|
|
@ -1,50 +1,54 @@
|
|||
import {AUTO_MODE, DARK_MODE, DEFAULT_THEME, LIGHT_MODE} from "@constants/constants.ts";
|
||||
import type { LIGHT_DARK_MODE } from '@/types/config'
|
||||
import type { LIGHT_DARK_MODE } from "@/types/config";
|
||||
import {
|
||||
AUTO_MODE,
|
||||
DARK_MODE,
|
||||
DEFAULT_THEME,
|
||||
LIGHT_MODE,
|
||||
} from "@constants/constants.ts";
|
||||
|
||||
export function getDefaultHue(): number {
|
||||
const fallback = '250'
|
||||
const configCarrier = document.getElementById('config-carrier')
|
||||
return parseInt(configCarrier?.dataset.hue || fallback)
|
||||
const fallback = "250";
|
||||
const configCarrier = document.getElementById("config-carrier");
|
||||
return Number.parseInt(configCarrier?.dataset.hue || fallback);
|
||||
}
|
||||
|
||||
export function getHue(): number {
|
||||
const stored = localStorage.getItem('hue')
|
||||
return stored ? parseInt(stored) : getDefaultHue()
|
||||
const stored = localStorage.getItem("hue");
|
||||
return stored ? Number.parseInt(stored) : getDefaultHue();
|
||||
}
|
||||
|
||||
export function setHue(hue: number): void {
|
||||
localStorage.setItem('hue', String(hue))
|
||||
const r = document.querySelector(':root')
|
||||
localStorage.setItem("hue", String(hue));
|
||||
const r = document.querySelector(":root");
|
||||
if (!r) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
r.style.setProperty('--hue', hue)
|
||||
r.style.setProperty("--hue", hue);
|
||||
}
|
||||
|
||||
|
||||
export function applyThemeToDocument(theme: LIGHT_DARK_MODE) {
|
||||
switch (theme) {
|
||||
case LIGHT_MODE:
|
||||
document.documentElement.classList.remove('dark')
|
||||
break
|
||||
document.documentElement.classList.remove("dark");
|
||||
break;
|
||||
case DARK_MODE:
|
||||
document.documentElement.classList.add('dark')
|
||||
break
|
||||
document.documentElement.classList.add("dark");
|
||||
break;
|
||||
case AUTO_MODE:
|
||||
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
document.documentElement.classList.add('dark')
|
||||
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||
document.documentElement.classList.add("dark");
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark')
|
||||
document.documentElement.classList.remove("dark");
|
||||
}
|
||||
break
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
export function setTheme(theme: LIGHT_DARK_MODE): void {
|
||||
localStorage.setItem('theme', theme)
|
||||
applyThemeToDocument(theme)
|
||||
localStorage.setItem("theme", theme);
|
||||
applyThemeToDocument(theme);
|
||||
}
|
||||
|
||||
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,37 +1,37 @@
|
|||
import i18nKey from '@i18n/i18nKey'
|
||||
import { i18n } from '@i18n/translation'
|
||||
import i18nKey from "@i18n/i18nKey";
|
||||
import { i18n } from "@i18n/translation";
|
||||
|
||||
export function pathsEqual(path1: string, path2: string) {
|
||||
const normalizedPath1 = path1.replace(/^\/|\/$/g, '').toLowerCase()
|
||||
const normalizedPath2 = path2.replace(/^\/|\/$/g, '').toLowerCase()
|
||||
return normalizedPath1 === normalizedPath2
|
||||
const normalizedPath1 = path1.replace(/^\/|\/$/g, "").toLowerCase();
|
||||
const normalizedPath2 = path2.replace(/^\/|\/$/g, "").toLowerCase();
|
||||
return normalizedPath1 === normalizedPath2;
|
||||
}
|
||||
|
||||
function joinUrl(...parts: string[]): string {
|
||||
const joined = parts.join('/')
|
||||
return joined.replace(/\/+/g, '/');
|
||||
const joined = parts.join("/");
|
||||
return joined.replace(/\/+/g, "/");
|
||||
}
|
||||
|
||||
export function getPostUrlBySlug(slug: string): string | null {
|
||||
if (!slug) return null
|
||||
return url(`/posts/${slug}/`)
|
||||
if (!slug) return null;
|
||||
return url(`/posts/${slug}/`);
|
||||
}
|
||||
|
||||
export function getCategoryUrl(category: string): string | null {
|
||||
if (!category) return null
|
||||
if (!category) return null;
|
||||
if (category === i18n(i18nKey.uncategorized))
|
||||
return url('/archive/category/uncategorized/')
|
||||
return url(`/archive/category/${category}/`)
|
||||
return url("/archive/category/uncategorized/");
|
||||
return url(`/archive/category/${category}/`);
|
||||
}
|
||||
|
||||
export function getDir(path: string): string {
|
||||
const lastSlashIndex = path.lastIndexOf('/')
|
||||
const lastSlashIndex = path.lastIndexOf("/");
|
||||
if (lastSlashIndex < 0) {
|
||||
return '/'
|
||||
return "/";
|
||||
}
|
||||
return path.substring(0, lastSlashIndex + 1)
|
||||
return path.substring(0, lastSlashIndex + 1);
|
||||
}
|
||||
|
||||
export function url(path: string) {
|
||||
return joinUrl('', import.meta.env.BASE_URL, path)
|
||||
return joinUrl("", import.meta.env.BASE_URL, path);
|
||||
}
|