feat: add FrontMatter CMS, biome, translation, etc.
* add Frontmatter CMS * add biome * update * update * fixed & add docs * fix translation.ts * fix translation
This commit is contained in:
parent
f9a78b3e3b
commit
197d524b53
|
@ -0,0 +1 @@
|
||||||
|
{}
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"taxonomy": {
|
||||||
|
"tags": [
|
||||||
|
"Blogging",
|
||||||
|
"Customization",
|
||||||
|
"Demo",
|
||||||
|
"Example",
|
||||||
|
"Fuwari",
|
||||||
|
"Markdown",
|
||||||
|
"Video"
|
||||||
|
],
|
||||||
|
"categories": []
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"recommendations": ["biomejs.biome", "astro-build.astro-vscode"]
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.defaultFormatter": "biomejs.biome",
|
||||||
|
"[javascript]": {
|
||||||
|
"editor.defaultFormatter": "biomejs.biome"
|
||||||
|
},
|
||||||
|
"[javascriptreact]": {
|
||||||
|
"editor.defaultFormatter": "biomejs.biome"
|
||||||
|
},
|
||||||
|
"[typescript]": {
|
||||||
|
"editor.defaultFormatter": "biomejs.biome"
|
||||||
|
},
|
||||||
|
"[typescriptreact]": {
|
||||||
|
"editor.defaultFormatter": "biomejs.biome"
|
||||||
|
},
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll": "explicit",
|
||||||
|
"quickfix.biome": "always",
|
||||||
|
"source.organizeImports.biome": "always"
|
||||||
|
},
|
||||||
|
"frontMatter.dashboard.openOnStart": false
|
||||||
|
}
|
16
README.md
16
README.md
|
@ -25,7 +25,7 @@ Fuwari (not the final name maybe) is a static blog template built with [Astro](h
|
||||||
|
|
||||||
1. [Generate a new repository](https://github.com/saicaca/fuwari/generate) from this template.
|
1. [Generate a new repository](https://github.com/saicaca/fuwari/generate) from this template.
|
||||||
2. Edit the config file `src/config.ts` to customize your blog.
|
2. Edit the config file `src/config.ts` to customize your blog.
|
||||||
3. Run `npm run new-post -- <filename>` to create a new post and edit it in `src/content/posts/`.
|
3. Run `pnpm run new-post -- <filename>` to create a new post and edit it in `src/content/posts/`.
|
||||||
4. Deploy your blog to Vercel, Netlify, GitHub Pages, etc. following [the guides](https://docs.astro.build/en/guides/deploy/).
|
4. Deploy your blog to Vercel, Netlify, GitHub Pages, etc. following [the guides](https://docs.astro.build/en/guides/deploy/).
|
||||||
|
|
||||||
## ⚙️ Frontmatter of Posts
|
## ⚙️ Frontmatter of Posts
|
||||||
|
@ -47,10 +47,10 @@ All commands are run from the root of the project, from a terminal:
|
||||||
|
|
||||||
| Command | Action |
|
| Command | Action |
|
||||||
|:---------------------------------|:-------------------------------------------------|
|
|:---------------------------------|:-------------------------------------------------|
|
||||||
| `npm install` | Installs dependencies |
|
| `pnpm install` | Installs dependencies |
|
||||||
| `npm run dev` | Starts local dev server at `localhost:4321` |
|
| `pnpm run dev` | Starts local dev server at `localhost:4321` |
|
||||||
| `npm run build` | Build your production site to `./dist/` |
|
| `pnpm run build` | Build your production site to `./dist/` |
|
||||||
| `npm run preview` | Preview your build locally, before deploying |
|
| `pnpm run preview` | Preview your build locally, before deploying |
|
||||||
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
|
| `pnpm run astro ...` | Run CLI commands like `astro add`, `astro check` |
|
||||||
| `npm run astro -- --help` | Get help using the Astro CLI |
|
| `pnpm run astro -- --help` | Get help using the Astro CLI |
|
||||||
| `npm run new-post -- <filename>` | Create a new post |
|
| `pnpm run new-post -- <filename>` | Create a new post |
|
||||||
|
|
111
astro.config.mjs
111
astro.config.mjs
|
@ -1,57 +1,74 @@
|
||||||
import { defineConfig } from 'astro/config';
|
import tailwind from "@astrojs/tailwind"
|
||||||
import yaml from '@rollup/plugin-yaml';
|
import yaml from "@rollup/plugin-yaml"
|
||||||
import icon from "astro-icon";
|
import Compress from "astro-compress"
|
||||||
|
import icon from "astro-icon"
|
||||||
|
import { defineConfig } from "astro/config"
|
||||||
|
import Color from "colorjs.io"
|
||||||
|
import rehypeAutolinkHeadings from "rehype-autolink-headings"
|
||||||
|
import rehypeKatex from "rehype-katex"
|
||||||
|
import rehypeSlug from "rehype-slug"
|
||||||
|
import remarkMath from "remark-math"
|
||||||
|
import { remarkReadingTime } from "./src/plugins/remark-reading-time.mjs"
|
||||||
|
|
||||||
import tailwind from "@astrojs/tailwind";
|
const oklchToHex = (str) => {
|
||||||
import {remarkReadingTime} from "./src/plugins/remark-reading-time.mjs";
|
const DEFAULT_HUE = 250
|
||||||
|
const regex = /-?\d+(\.\d+)?/g
|
||||||
import rehypeKatex from "rehype-katex";
|
const matches = str.string.match(regex)
|
||||||
|
const lch = [matches[0], matches[1], DEFAULT_HUE]
|
||||||
import Color from 'colorjs.io';
|
return new Color("oklch", lch).to("srgb").toString({
|
||||||
import remarkMath from "remark-math";
|
format: "hex",
|
||||||
import rehypeAutolinkHeadings from "rehype-autolink-headings";
|
})
|
||||||
import rehypeSlug from "rehype-slug";
|
|
||||||
|
|
||||||
// https://astro.build/config
|
|
||||||
|
|
||||||
|
|
||||||
const oklchToHex = function (str) {
|
|
||||||
const DEFAULT_HUE = 250;
|
|
||||||
const regex = /-?\d+(\.\d+)?/g;
|
|
||||||
const matches = str.string.match(regex);
|
|
||||||
const lch = [matches[0], matches[1], DEFAULT_HUE];
|
|
||||||
return new Color("oklch", lch).to("srgb").toString({format: "hex"});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://astro.build/config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
site: 'https://fuwari.vercel.app/',
|
site: "https://fuwari.vercel.app/",
|
||||||
base: '/',
|
base: "/",
|
||||||
integrations: [
|
integrations: [
|
||||||
tailwind(),
|
tailwind(),
|
||||||
icon({
|
icon({
|
||||||
include: {
|
include: {
|
||||||
'material-symbols': ['*'],
|
"material-symbols": ["*"],
|
||||||
'fa6-brands': ['*'],
|
"fa6-brands": ["*"],
|
||||||
'fa6-regular': ['*'],
|
"fa6-regular": ["*"],
|
||||||
'fa6-solid': ['*']
|
"fa6-solid": ["*"],
|
||||||
}
|
},
|
||||||
})
|
}),
|
||||||
|
Compress({
|
||||||
|
Image: false,
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
markdown: {
|
markdown: {
|
||||||
remarkPlugins: [remarkMath, remarkReadingTime],
|
remarkPlugins: [remarkMath, remarkReadingTime],
|
||||||
rehypePlugins: [rehypeKatex, rehypeSlug,
|
rehypePlugins: [
|
||||||
[rehypeAutolinkHeadings, {
|
rehypeKatex,
|
||||||
behavior: 'append',
|
rehypeSlug,
|
||||||
properties: {className: ['anchor']},
|
[
|
||||||
content: {
|
rehypeAutolinkHeadings,
|
||||||
type: 'element',
|
{
|
||||||
tagName: 'span',
|
behavior: "append",
|
||||||
properties: {className: ['anchor-icon']},
|
properties: {
|
||||||
children: [{type: 'text', value: '#'}]
|
className: ["anchor"],
|
||||||
}}]]
|
},
|
||||||
|
content: {
|
||||||
|
type: "element",
|
||||||
|
tagName: "span",
|
||||||
|
properties: {
|
||||||
|
className: ["anchor-icon"],
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
value: "#",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
},
|
},
|
||||||
redirects: {
|
redirects: {
|
||||||
'/': '/page/1',
|
"/": "/page/1",
|
||||||
},
|
},
|
||||||
vite: {
|
vite: {
|
||||||
plugins: [yaml()],
|
plugins: [yaml()],
|
||||||
|
@ -59,10 +76,10 @@ export default defineConfig({
|
||||||
preprocessorOptions: {
|
preprocessorOptions: {
|
||||||
stylus: {
|
stylus: {
|
||||||
define: {
|
define: {
|
||||||
oklchToHex: oklchToHex
|
oklchToHex: oklchToHex,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://biomejs.dev/schemas/1.4.1/schema.json",
|
||||||
|
"extends": [],
|
||||||
|
"files": { "ignoreUnknown": true },
|
||||||
|
"organizeImports": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"formatter": {
|
||||||
|
"enabled": true,
|
||||||
|
"formatWithErrors": false,
|
||||||
|
"ignore": [],
|
||||||
|
"indentStyle": "space",
|
||||||
|
"indentWidth": 2,
|
||||||
|
"lineWidth": 80
|
||||||
|
},
|
||||||
|
"javascript": {
|
||||||
|
"parser": {
|
||||||
|
"unsafeParameterDecoratorsEnabled": true
|
||||||
|
},
|
||||||
|
"formatter": {
|
||||||
|
"quoteStyle": "single",
|
||||||
|
"jsxQuoteStyle": "single",
|
||||||
|
"trailingComma": "all",
|
||||||
|
"semicolons": "asNeeded",
|
||||||
|
"arrowParentheses": "asNeeded"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"json": {
|
||||||
|
"parser": { "allowComments": true },
|
||||||
|
"formatter": {
|
||||||
|
"enabled": true,
|
||||||
|
"indentStyle": "space",
|
||||||
|
"indentWidth": 2,
|
||||||
|
"lineWidth": 80
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"linter": {
|
||||||
|
"ignore": [],
|
||||||
|
"rules": {
|
||||||
|
"a11y": {
|
||||||
|
"recommended": true
|
||||||
|
},
|
||||||
|
"complexity": {
|
||||||
|
"recommended": true
|
||||||
|
},
|
||||||
|
"correctness": {
|
||||||
|
"recommended": true
|
||||||
|
},
|
||||||
|
"performance": {
|
||||||
|
"recommended": true
|
||||||
|
},
|
||||||
|
"security": {
|
||||||
|
"recommended": true
|
||||||
|
},
|
||||||
|
"style": {
|
||||||
|
"recommended": true
|
||||||
|
},
|
||||||
|
"suspicious": {
|
||||||
|
"recommended": true
|
||||||
|
},
|
||||||
|
"nursery": {
|
||||||
|
"recommended": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://frontmatter.codes/frontmatter.schema.json",
|
||||||
|
"frontMatter.framework.id": "astro",
|
||||||
|
"frontMatter.preview.host": "http://localhost:4321",
|
||||||
|
"frontMatter.content.publicFolder": "public",
|
||||||
|
"frontMatter.content.pageFolders": [
|
||||||
|
{
|
||||||
|
"title": "posts",
|
||||||
|
"path": "[[workspace]]/src/content/posts"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"frontMatter.taxonomy.contentTypes": [
|
||||||
|
{
|
||||||
|
"name": "default",
|
||||||
|
"pageBundle": true,
|
||||||
|
"previewPath": "'blog'",
|
||||||
|
"filePrefix": null,
|
||||||
|
"clearEmpty": true,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"title": "title",
|
||||||
|
"name": "title",
|
||||||
|
"type": "string",
|
||||||
|
"single": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "description",
|
||||||
|
"name": "description",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "published",
|
||||||
|
"name": "published",
|
||||||
|
"type": "datetime",
|
||||||
|
"default": "{{now}}",
|
||||||
|
"isPublishDate": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "preview",
|
||||||
|
"name": "image",
|
||||||
|
"type": "image",
|
||||||
|
"isPreviewImage": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "tags",
|
||||||
|
"name": "tags",
|
||||||
|
"type": "list"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "category",
|
||||||
|
"name": "category",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "draft",
|
||||||
|
"name": "draft",
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
32
package.json
32
package.json
|
@ -8,37 +8,41 @@
|
||||||
"build": "astro build",
|
"build": "astro build",
|
||||||
"preview": "astro preview",
|
"preview": "astro preview",
|
||||||
"astro": "astro",
|
"astro": "astro",
|
||||||
"new-post": "node scripts/new-post.js"
|
"new-post": "node scripts/new-post.js",
|
||||||
|
"format": "biome format --write ./src",
|
||||||
|
"lint": "biome check --apply ./src"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/check": "^0.2.0",
|
"@astrojs/check": "^0.3.4",
|
||||||
"@astrojs/tailwind": "^5.0.2",
|
"@astrojs/tailwind": "^5.1.0",
|
||||||
"@astrojs/vue": "^3.0.1",
|
"@astrojs/vue": "^4.0.8",
|
||||||
"@fontsource-variable/jetbrains-mono": "^5.0.18",
|
"@fontsource-variable/jetbrains-mono": "^5.0.19",
|
||||||
"@fontsource/roboto": "^5.0.8",
|
"@fontsource/roboto": "^5.0.8",
|
||||||
"astro": "^3.5.0",
|
"astro": "^4.1.1",
|
||||||
"astro-icon": "1.0.0-next.2",
|
"astro-icon": "1.0.2",
|
||||||
"colorjs.io": "^0.4.5",
|
"colorjs.io": "^0.4.5",
|
||||||
"mdast-util-to-string": "^4.0.0",
|
"mdast-util-to-string": "^4.0.0",
|
||||||
"overlayscrollbars": "^2.4.4",
|
"overlayscrollbars": "^2.4.6",
|
||||||
"reading-time": "^1.5.0",
|
"reading-time": "^1.5.0",
|
||||||
"rehype-autolink-headings": "^7.1.0",
|
"rehype-autolink-headings": "^7.1.0",
|
||||||
"rehype-katex": "^7.0.0",
|
"rehype-katex": "^7.0.0",
|
||||||
"rehype-slug": "^6.0.0",
|
"rehype-slug": "^6.0.0",
|
||||||
"remark-math": "^6.0.0",
|
"remark-math": "^6.0.0",
|
||||||
"sharp": "^0.32.6",
|
"sharp": "^0.32.6",
|
||||||
"tailwindcss": "^3.3.3",
|
"tailwindcss": "^3.3.7",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
"valine": "^1.5.1"
|
"valine": "^1.5.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@astrojs/ts-plugin": "^1.1.3",
|
"@astrojs/ts-plugin": "^1.3.1",
|
||||||
"@iconify-json/fa6-brands": "^1.1.13",
|
"@biomejs/biome": "1.4.1",
|
||||||
"@iconify-json/fa6-regular": "^1.1.13",
|
"@iconify-json/fa6-brands": "^1.1.18",
|
||||||
"@iconify-json/fa6-solid": "^1.1.15",
|
"@iconify-json/fa6-regular": "^1.1.18",
|
||||||
"@iconify-json/material-symbols": "^1.1.59",
|
"@iconify-json/fa6-solid": "^1.1.20",
|
||||||
|
"@iconify-json/material-symbols": "^1.1.69",
|
||||||
"@rollup/plugin-yaml": "^4.1.2",
|
"@rollup/plugin-yaml": "^4.1.2",
|
||||||
"@tailwindcss/typography": "^0.5.10",
|
"@tailwindcss/typography": "^0.5.10",
|
||||||
|
"astro-compress": "github:astro-community/AstroCompress#no-sharp",
|
||||||
"stylus": "^0.59.0"
|
"stylus": "^0.59.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
3541
pnpm-lock.yaml
3541
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -1,41 +1,40 @@
|
||||||
import fs from 'fs';
|
import fs from "fs"
|
||||||
import path from 'path';
|
import path from "path"
|
||||||
|
|
||||||
function getDate() {
|
function getDate() {
|
||||||
const today = new Date();
|
const today = new Date()
|
||||||
const year = today.getFullYear();
|
const year = today.getFullYear()
|
||||||
const month = String(today.getMonth() + 1).padStart(2, '0'); //月份从0开始,所以要加1
|
const month = String(today.getMonth() + 1).padStart(2, "0") //月份从0开始,所以要加1
|
||||||
const day = String(today.getDate()).padStart(2, '0');
|
const day = String(today.getDate()).padStart(2, "0")
|
||||||
|
|
||||||
return `${year}-${month}-${day}`;
|
return `${year}-${month}-${day}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const args = process.argv.slice(2);
|
const args = process.argv.slice(2)
|
||||||
|
|
||||||
if (args.length === 0) {
|
if (args.length === 0) {
|
||||||
console.error(`Error: No filename argument provided
|
console.error(`Error: No filename argument provided
|
||||||
Usage: npm run new-post -- <filename>`);
|
Usage: npm run new-post -- <filename>`)
|
||||||
process.exit(1); // Terminate the script and return error code 1
|
process.exit(1) // Terminate the script and return error code 1
|
||||||
}
|
}
|
||||||
|
|
||||||
let fileName = args[0];
|
let fileName = args[0]
|
||||||
|
|
||||||
// Add .md extension if not present
|
// Add .md extension if not present
|
||||||
const fileExtensionRegex = /\.(md|mdx)$/i;
|
const fileExtensionRegex = /\.(md|mdx)$/i
|
||||||
if (!fileExtensionRegex.test(fileName)) {
|
if (!fileExtensionRegex.test(fileName)) {
|
||||||
fileName += '.md';
|
fileName += ".md"
|
||||||
}
|
}
|
||||||
|
|
||||||
const targetDir = './src/content/posts/';
|
const targetDir = "./src/content/posts/"
|
||||||
const fullPath = path.join(targetDir, fileName);
|
const fullPath = path.join(targetDir, fileName)
|
||||||
|
|
||||||
if (fs.existsSync(fullPath)) {
|
if (fs.existsSync(fullPath)) {
|
||||||
console.error(`Error:File ${fullPath} already exists `);
|
console.error(`Error:File ${fullPath} already exists `)
|
||||||
process.exit(1);
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
const content =
|
const content = `---
|
||||||
`---
|
|
||||||
title: ${args[0]}
|
title: ${args[0]}
|
||||||
published: ${getDate()}
|
published: ${getDate()}
|
||||||
description:
|
description:
|
||||||
|
@ -43,8 +42,8 @@ image:
|
||||||
tags: []
|
tags: []
|
||||||
category:
|
category:
|
||||||
---
|
---
|
||||||
`;
|
`
|
||||||
|
|
||||||
fs.writeFileSync(path.join(targetDir, fileName), content);
|
fs.writeFileSync(path.join(targetDir, fileName), content)
|
||||||
|
|
||||||
console.log(`Post ${fullPath} created`);
|
console.log(`Post ${fullPath} created`)
|
||||||
|
|
|
@ -11,6 +11,7 @@ interface Props {
|
||||||
image: string;
|
image: string;
|
||||||
description: string;
|
description: string;
|
||||||
words: number;
|
words: number;
|
||||||
|
draft: boolean;
|
||||||
}
|
}
|
||||||
const { entry, title, url, published, tags, category, image, description, words } = Astro.props;
|
const { entry, title, url, published, tags, category, image, description, words } = Astro.props;
|
||||||
const className = Astro.props.class;
|
const className = Astro.props.class;
|
||||||
|
|
|
@ -1,53 +1,60 @@
|
||||||
import type {LicenseConfig, NavBarConfig, ProfileConfig, SiteConfig} from "./types/config.ts";
|
import type {
|
||||||
import {LinkPreset} from "./types/config.ts";
|
LicenseConfig,
|
||||||
|
NavBarConfig,
|
||||||
|
ProfileConfig,
|
||||||
|
SiteConfig,
|
||||||
|
} from './types/config.ts'
|
||||||
|
import { LinkPreset } from './types/config.ts'
|
||||||
|
|
||||||
export const siteConfig: SiteConfig = {
|
export const siteConfig: SiteConfig = {
|
||||||
title: 'Fuwari',
|
title: 'Fuwari',
|
||||||
subtitle: 'Demo Site',
|
subtitle: 'Demo Site',
|
||||||
lang: 'en',
|
lang: 'en',
|
||||||
themeHue: 250,
|
themeHue: 250,
|
||||||
banner: {
|
banner: {
|
||||||
enable: true,
|
enable: true,
|
||||||
src: 'assets/images/demo-banner.png',
|
src: 'assets/images/demo-banner.png',
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
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',
|
url: 'https://github.com/saicaca/fuwari',
|
||||||
external: true,
|
external: true,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
export const profileConfig: ProfileConfig = {
|
export const profileConfig: ProfileConfig = {
|
||||||
avatar: 'assets/images/demo-avatar.png',
|
avatar: 'assets/images/demo-avatar.png',
|
||||||
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',
|
icon: 'fa6-brands:twitter',
|
||||||
url: 'https://twitter.com',
|
url: 'https://twitter.com',
|
||||||
}, {
|
},
|
||||||
name: 'Steam',
|
{
|
||||||
icon: 'fa6-brands:steam',
|
name: 'Steam',
|
||||||
url: 'https://store.steampowered.com',
|
icon: 'fa6-brands:steam',
|
||||||
}, {
|
url: 'https://store.steampowered.com',
|
||||||
name: 'GitHub',
|
},
|
||||||
icon: 'fa6-brands:github',
|
{
|
||||||
url: 'https://github.com/saicaca/fuwari',
|
name: 'GitHub',
|
||||||
}
|
icon: 'fa6-brands:github',
|
||||||
]
|
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,15 +1,16 @@
|
||||||
import { z, defineCollection } from "astro:content";
|
import { defineCollection, z } from 'astro:content'
|
||||||
|
|
||||||
const blogCollection = defineCollection({
|
const postsCollection = defineCollection({
|
||||||
schema: z.object({
|
schema: z.object({
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
published: z.date(),
|
published: z.date(),
|
||||||
description: z.string().optional(),
|
draft: z.boolean(),
|
||||||
image: z.string().optional(),
|
description: z.string().optional(),
|
||||||
tags: z.array(z.string()).optional(),
|
image: z.string().optional(),
|
||||||
category: z.string().optional(),
|
tags: z.array(z.string()).optional(),
|
||||||
})
|
category: z.string().optional(),
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
export const collections = {
|
export const collections = {
|
||||||
'blog': blogCollection,
|
posts: postsCollection,
|
||||||
}
|
}
|
|
@ -1,10 +1,11 @@
|
||||||
---
|
---
|
||||||
title: 'Cover Image Example'
|
title: "Cover Image Example"
|
||||||
published: 2023-09-01
|
published: 2023-09-01
|
||||||
description: 'How to set a cover image using the cover attribute.'
|
description: "How to set a cover image using the cover attribute."
|
||||||
image: 'https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/208fc754-890d-4adb-9753-2c963332675d/width=2048/01651-1456859105-(colour_1.5),girl,_Blue,yellow,green,cyan,purple,red,pink,_best,8k,UHD,masterpiece,male%20focus,%201boy,gloves,%20ponytail,%20long%20hair,.jpeg'
|
image: "https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/208fc754-890d-4adb-9753-2c963332675d/width=2048/01651-1456859105-(colour_1.5),girl,_Blue,yellow,green,cyan,purple,red,pink,_best,8k,UHD,masterpiece,male%20focus,%201boy,gloves,%20ponytail,%20long%20hair,.jpeg"
|
||||||
tags: ["Fuwari", "Blogging", "Customization"]
|
tags: ["Fuwari", "Blogging", "Customization"]
|
||||||
category:
|
category: test
|
||||||
|
draft: false
|
||||||
---
|
---
|
||||||
|
|
||||||
## Set the cover image using the `image` attribute
|
## Set the cover image using the `image` attribute
|
||||||
|
@ -20,4 +21,5 @@ published: 2023-10-05
|
||||||
image: "/images/my-cover-image.jpg"
|
image: "/images/my-cover-image.jpg"
|
||||||
---
|
---
|
||||||
```
|
```
|
||||||
|
|
||||||
Web URLs are also supported.
|
Web URLs are also supported.
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
---
|
||||||
|
title: Draft Example
|
||||||
|
published: 2024-01-11T04:40:26.381Z
|
||||||
|
tags: [Markdown, Blogging, Demo]
|
||||||
|
category: Example
|
||||||
|
draft: true
|
||||||
|
---
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# This Article is a Draft
|
||||||
|
|
||||||
|
This article is currently in a draft state and is not published. Therefore, it will not be visible to the general audience. The content is still a work in progress and may require further editing and review.
|
||||||
|
|
||||||
|
When the article is ready for publication, you can update the "draft" field to "false" in the Frontmatter:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
title: Draft Example
|
||||||
|
published: 2024-01-11T04:40:26.381Z
|
||||||
|
tags: [Markdown, Blogging, Demo]
|
||||||
|
category: Example
|
||||||
|
draft: false
|
||||||
|
---
|
|
@ -2,22 +2,21 @@
|
||||||
title: Markdown Example
|
title: Markdown Example
|
||||||
published: 2023-10-01
|
published: 2023-10-01
|
||||||
description: A simple example of a Markdown blog post.
|
description: A simple example of a Markdown blog post.
|
||||||
image:
|
|
||||||
tags: [Markdown, Blogging, Demo]
|
tags: [Markdown, Blogging, Demo]
|
||||||
category: Example
|
category: Example
|
||||||
|
draft: false
|
||||||
---
|
---
|
||||||
|
|
||||||
An h1 header
|
# An h1 header
|
||||||
============
|
|
||||||
|
|
||||||
Paragraphs are separated by a blank line.
|
Paragraphs are separated by a blank line.
|
||||||
|
|
||||||
2nd paragraph. *Italic*, **bold**, and `monospace`. Itemized lists
|
2nd paragraph. _Italic_, **bold**, and `monospace`. Itemized lists
|
||||||
look like:
|
look like:
|
||||||
|
|
||||||
* this one
|
- this one
|
||||||
* that one
|
- that one
|
||||||
* the other one
|
- the other one
|
||||||
|
|
||||||
Note that --- not considering the asterisk --- the actual text
|
Note that --- not considering the asterisk --- the actual text
|
||||||
content starts at 4-columns in.
|
content starts at 4-columns in.
|
||||||
|
@ -32,10 +31,7 @@ Use 3 dashes for an em-dash. Use 2 dashes for ranges (ex., "it's all
|
||||||
in chapters 12--14"). Three dots ... will be converted to an ellipsis.
|
in chapters 12--14"). Three dots ... will be converted to an ellipsis.
|
||||||
Unicode is supported. ☺
|
Unicode is supported. ☺
|
||||||
|
|
||||||
|
## An h2 header
|
||||||
|
|
||||||
An h2 header
|
|
||||||
------------
|
|
||||||
|
|
||||||
Here's a numbered list:
|
Here's a numbered list:
|
||||||
|
|
||||||
|
@ -52,50 +48,48 @@ from the left side). Here's a code sample:
|
||||||
As you probably guessed, indented 4 spaces. By the way, instead of
|
As you probably guessed, indented 4 spaces. By the way, instead of
|
||||||
indenting the block, you can use delimited blocks, if you like:
|
indenting the block, you can use delimited blocks, if you like:
|
||||||
|
|
||||||
~~~
|
```
|
||||||
define foobar() {
|
define foobar() {
|
||||||
print "Welcome to flavor country!";
|
print "Welcome to flavor country!";
|
||||||
}
|
}
|
||||||
~~~
|
```
|
||||||
|
|
||||||
(which makes copying & pasting easier). You can optionally mark the
|
(which makes copying & pasting easier). You can optionally mark the
|
||||||
delimited block for Pandoc to syntax highlight it:
|
delimited block for Pandoc to syntax highlight it:
|
||||||
|
|
||||||
~~~python
|
```python
|
||||||
import time
|
import time
|
||||||
# Quick, count to ten!
|
# Quick, count to ten!
|
||||||
for i in range(10):
|
for i in range(10):
|
||||||
# (but not *too* quick)
|
# (but not *too* quick)
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
print i
|
print i
|
||||||
~~~
|
```
|
||||||
|
|
||||||
|
### An h3 header
|
||||||
|
|
||||||
### An h3 header ###
|
|
||||||
|
|
||||||
Now a nested list:
|
Now a nested list:
|
||||||
|
|
||||||
1. First, get these ingredients:
|
1. First, get these ingredients:
|
||||||
|
|
||||||
* carrots
|
- carrots
|
||||||
* celery
|
- celery
|
||||||
* lentils
|
- lentils
|
||||||
|
|
||||||
2. Boil some water.
|
2. Boil some water.
|
||||||
|
|
||||||
3. Dump everything in the pot and follow
|
3. Dump everything in the pot and follow
|
||||||
this algorithm:
|
this algorithm:
|
||||||
|
|
||||||
find wooden spoon
|
find wooden spoon
|
||||||
uncover pot
|
uncover pot
|
||||||
stir
|
stir
|
||||||
cover pot
|
cover pot
|
||||||
balance wooden spoon precariously on pot handle
|
balance wooden spoon precariously on pot handle
|
||||||
wait 10 minutes
|
wait 10 minutes
|
||||||
goto first step (or shut off burner when done)
|
goto first step (or shut off burner when done)
|
||||||
|
|
||||||
Do not bump wooden spoon or it will fall.
|
Do not bump wooden spoon or it will fall.
|
||||||
|
|
||||||
Notice again how text always lines up on 4-space indents (including
|
Notice again how text always lines up on 4-space indents (including
|
||||||
that last line which continues item 3 above).
|
that last line which continues item 3 above).
|
||||||
|
@ -108,32 +102,38 @@ doc](#an-h2-header). Here's a footnote [^1].
|
||||||
|
|
||||||
Tables can look like this:
|
Tables can look like this:
|
||||||
|
|
||||||
size material color
|
size material color
|
||||||
---- ------------ ------------
|
|
||||||
9 leather brown
|
---
|
||||||
10 hemp canvas natural
|
|
||||||
11 glass transparent
|
9 leather brown
|
||||||
|
10 hemp canvas natural
|
||||||
|
11 glass transparent
|
||||||
|
|
||||||
Table: Shoes, their sizes, and what they're made of
|
Table: Shoes, their sizes, and what they're made of
|
||||||
|
|
||||||
(The above is the caption for the table.) Pandoc also supports
|
(The above is the caption for the table.) Pandoc also supports
|
||||||
multi-line tables:
|
multi-line tables:
|
||||||
|
|
||||||
-------- -----------------------
|
---
|
||||||
keyword text
|
|
||||||
-------- -----------------------
|
keyword text
|
||||||
red Sunsets, apples, and
|
|
||||||
|
---
|
||||||
|
|
||||||
|
red Sunsets, apples, and
|
||||||
other red or reddish
|
other red or reddish
|
||||||
things.
|
things.
|
||||||
|
|
||||||
green Leaves, grass, frogs
|
green Leaves, grass, frogs
|
||||||
and other things it's
|
and other things it's
|
||||||
not easy being.
|
not easy being.
|
||||||
-------- -----------------------
|
|
||||||
|
---
|
||||||
|
|
||||||
A horizontal rule follows.
|
A horizontal rule follows.
|
||||||
|
|
||||||
***
|
---
|
||||||
|
|
||||||
Here's a definition list:
|
Here's a definition list:
|
||||||
|
|
||||||
|
@ -150,7 +150,7 @@ term/definition pair to spread things out more.)
|
||||||
Here's a "line block":
|
Here's a "line block":
|
||||||
|
|
||||||
| Line one
|
| Line one
|
||||||
| Line too
|
| Line too
|
||||||
| Line tree
|
| Line tree
|
||||||
|
|
||||||
and images can be specified like so:
|
and images can be specified like so:
|
||||||
|
|
|
@ -2,10 +2,11 @@
|
||||||
title: Include Video in the Posts
|
title: Include Video in the Posts
|
||||||
published: 2022-08-01
|
published: 2022-08-01
|
||||||
description: This post demonstrates how to include embedded video in a blog post.
|
description: This post demonstrates how to include embedded video in a blog post.
|
||||||
image:
|
|
||||||
tags: [Example, Video]
|
tags: [Example, Video]
|
||||||
category: Example
|
category: Example
|
||||||
|
draft: false
|
||||||
---
|
---
|
||||||
|
|
||||||
Just copy the embed code from YouTube or other platforms, and paste it in the markdown file.
|
Just copy the embed code from YouTube or other platforms, and paste it in the markdown file.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
@ -19,7 +20,9 @@ published: 2023-10-19
|
||||||
```
|
```
|
||||||
|
|
||||||
## YouTube
|
## YouTube
|
||||||
|
|
||||||
<iframe width="100%" height="468" src="https://www.youtube.com/embed/5gIf0_xpFPI?si=N1WTorLKL0uwLsU_" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
<iframe width="100%" height="468" src="https://www.youtube.com/embed/5gIf0_xpFPI?si=N1WTorLKL0uwLsU_" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||||
|
|
||||||
## Bilibili
|
## Bilibili
|
||||||
|
|
||||||
<iframe width="100%" height="468" src="//player.bilibili.com/player.html?bvid=BV1fK4y1s7Qf&p=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"> </iframe>
|
<iframe width="100%" height="468" src="//player.bilibili.com/player.html?bvid=BV1fK4y1s7Qf&p=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"> </iframe>
|
|
@ -0,0 +1,2 @@
|
||||||
|
/// <reference types="astro/client" />
|
||||||
|
/// <reference path="../.astro/types.d.ts" />
|
|
@ -1,4 +1,4 @@
|
||||||
declare module "*.yml" {
|
declare module '*.yml' {
|
||||||
const value: any;
|
const value: unknown
|
||||||
export default value;
|
export default value
|
||||||
}
|
}
|
|
@ -1,32 +1,32 @@
|
||||||
enum I18nKey {
|
enum I18nKey {
|
||||||
home = "home",
|
home = 'home',
|
||||||
about = "about",
|
about = 'about',
|
||||||
archive = "archive",
|
archive = 'archive',
|
||||||
|
|
||||||
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',
|
||||||
|
|
||||||
primaryColor = "primaryColor",
|
primaryColor = 'primaryColor',
|
||||||
|
|
||||||
more = "more",
|
more = 'more',
|
||||||
|
|
||||||
author = "author",
|
author = 'author',
|
||||||
publishedAt = "publishedAt",
|
publishedAt = 'publishedAt',
|
||||||
license = "license",
|
license = 'license',
|
||||||
}
|
}
|
||||||
|
|
||||||
export default I18nKey;
|
export default I18nKey
|
||||||
|
|
|
@ -1,33 +1,33 @@
|
||||||
import type { Translation } from "../translation.ts";
|
import Key from '../i18nKey.ts'
|
||||||
import Key from "../i18nKey.ts";
|
import type { Translation } from '../translation.ts'
|
||||||
|
|
||||||
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.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.primaryColor]: "Primary Color",
|
[Key.primaryColor]: 'Primary Color',
|
||||||
|
|
||||||
[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',
|
||||||
};
|
}
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
import Key from '../i18nKey.ts'
|
||||||
|
import type { Translation } from '../translation.ts'
|
||||||
|
|
||||||
|
export const ja: Translation = {
|
||||||
|
[Key.home]: 'Home',
|
||||||
|
[Key.about]: 'About',
|
||||||
|
[Key.archive]: 'Archive',
|
||||||
|
|
||||||
|
[Key.tags]: 'タグ',
|
||||||
|
[Key.categories]: 'カテゴリ',
|
||||||
|
[Key.recentPosts]: '最近の投稿',
|
||||||
|
|
||||||
|
[Key.comments]: 'コメント',
|
||||||
|
|
||||||
|
[Key.untitled]: 'タイトルなし',
|
||||||
|
[Key.uncategorized]: 'カテゴリなし',
|
||||||
|
[Key.noTags]: 'タグなし',
|
||||||
|
|
||||||
|
[Key.wordCount]: '文字',
|
||||||
|
[Key.wordsCount]: '文字',
|
||||||
|
[Key.minuteCount]: '分',
|
||||||
|
[Key.minutesCount]: '分',
|
||||||
|
[Key.postCount]: 'post',
|
||||||
|
[Key.postsCount]: 'posts',
|
||||||
|
|
||||||
|
[Key.primaryColor]: '原色',
|
||||||
|
|
||||||
|
[Key.more]: 'もっと',
|
||||||
|
|
||||||
|
[Key.author]: '作者',
|
||||||
|
[Key.publishedAt]: '公開日',
|
||||||
|
[Key.license]: 'ライセンス',
|
||||||
|
}
|
|
@ -1,33 +1,33 @@
|
||||||
import type { Translation } from "../translation.ts";
|
import Key from '../i18nKey.ts'
|
||||||
import Key from "../i18nKey.ts";
|
import type { Translation } from '../translation.ts'
|
||||||
|
|
||||||
export const zh_CN: Translation = {
|
export const zh_CN: Translation = {
|
||||||
[Key.home]: "主页",
|
[Key.home]: '主页',
|
||||||
[Key.about]: "关于",
|
[Key.about]: '关于',
|
||||||
[Key.archive]: "归档",
|
[Key.archive]: '归档',
|
||||||
|
|
||||||
[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.primaryColor]: "主题色",
|
[Key.primaryColor]: '主题色',
|
||||||
|
|
||||||
[Key.more]: "更多",
|
[Key.more]: '更多',
|
||||||
|
|
||||||
[Key.author]: "作者",
|
[Key.author]: '作者',
|
||||||
[Key.publishedAt]: "发布于",
|
[Key.publishedAt]: '发布于',
|
||||||
[Key.license]: "许可协议",
|
[Key.license]: '许可协议',
|
||||||
};
|
}
|
||||||
|
|
|
@ -1,33 +1,33 @@
|
||||||
import type { Translation } from "../translation.ts";
|
import Key from '../i18nKey.ts'
|
||||||
import Key from "../i18nKey.ts";
|
import type { Translation } from '../translation.ts'
|
||||||
|
|
||||||
export const zh_TW: Translation = {
|
export const zh_TW: Translation = {
|
||||||
[Key.home]: "首頁",
|
[Key.home]: '首頁',
|
||||||
[Key.about]: "關於",
|
[Key.about]: '關於',
|
||||||
[Key.archive]: "彙整",
|
[Key.archive]: '彙整',
|
||||||
|
|
||||||
[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.primaryColor]: "主題色",
|
[Key.primaryColor]: '主題色',
|
||||||
|
|
||||||
[Key.more]: "更多",
|
[Key.more]: '更多',
|
||||||
|
|
||||||
[Key.author]: "作者",
|
[Key.author]: '作者',
|
||||||
[Key.publishedAt]: "發佈於",
|
[Key.publishedAt]: '發佈於',
|
||||||
[Key.license]: "許可協議",
|
[Key.license]: '許可協議',
|
||||||
};
|
}
|
||||||
|
|
|
@ -1,30 +1,33 @@
|
||||||
import {en} from "./languages/en.ts";
|
import { siteConfig } from '../config.ts'
|
||||||
import {zh_TW} from "./languages/zh_TW.ts";
|
import type I18nKey from './i18nKey.ts'
|
||||||
import {zh_CN} from "./languages/zh_CN.ts";
|
import { en } from './languages/en.ts'
|
||||||
import type I18nKey from "./i18nKey.ts";
|
import { ja } from './languages/ja.ts'
|
||||||
import {siteConfig} from "../config.ts";
|
import { zh_CN } from './languages/zh_CN.ts'
|
||||||
|
import { zh_TW } from './languages/zh_TW.ts'
|
||||||
|
|
||||||
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 } = {
|
||||||
"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_jp: ja,
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTranslation(lang: string): Translation {
|
export function getTranslation(lang: string): Translation {
|
||||||
lang = lang.toLowerCase();
|
lang = lang.toLowerCase()
|
||||||
return map[lang] || defaultTranslation;
|
return map[lang] || 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,15 +1,15 @@
|
||||||
---
|
---
|
||||||
import GlobalStyles from "../components/GlobalStyles.astro";
|
import GlobalStyles from "@components/GlobalStyles.astro";
|
||||||
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 { ViewTransitions } from 'astro:transitions';
|
import { ViewTransitions } from 'astro:transitions';
|
||||||
import ImageBox from "../components/misc/ImageBox.astro";
|
import ImageBox from "@components/misc/ImageBox.astro";
|
||||||
|
|
||||||
import { fade } from 'astro:transitions';
|
import { fade } from 'astro:transitions';
|
||||||
import {pathsEqual} from "../utils/url-utils";
|
import {pathsEqual} from "@utils/url-utils";
|
||||||
import ConfigCarrier from "../components/ConfigCarrier.astro";
|
import ConfigCarrier from "@components/ConfigCarrier.astro";
|
||||||
import {siteConfig} from "../config";
|
import {siteConfig} from "@/config";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
---
|
---
|
||||||
import Layout from "./Layout.astro";
|
import Layout from "./Layout.astro";
|
||||||
import Navbar from "../components/Navbar.astro";
|
import Navbar from "@components/Navbar.astro";
|
||||||
import SideBar from "../components/widget/SideBar.astro";
|
import SideBar from "@components/widget/SideBar.astro";
|
||||||
import {pathsEqual} from "../utils/url-utils";
|
import {pathsEqual} from "@utils/url-utils";
|
||||||
import Footer from "../components/Footer.astro";
|
import Footer from "@components/Footer.astro";
|
||||||
import BackToTop from "../components/control/BackToTop.astro";
|
import BackToTop from "@components/control/BackToTop.astro";
|
||||||
import DisplaySetting from "../components/widget/DisplaySetting.astro";
|
import DisplaySetting from "@components/widget/DisplaySetting.astro";
|
||||||
import {siteConfig} from "../config";
|
import {siteConfig} from "@/config";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
|
|
|
@ -5,7 +5,7 @@ import MainGridLayout from "../layouts/MainGridLayout.astro";
|
||||||
import { getEntry } from 'astro:content'
|
import { getEntry } from 'astro:content'
|
||||||
import {i18n} from "../i18n/translation";
|
import {i18n} from "../i18n/translation";
|
||||||
import I18nKey from "../i18n/i18nKey";
|
import I18nKey from "../i18n/i18nKey";
|
||||||
import Markdown from "../components/misc/Markdown.astro";
|
import Markdown from "@components/misc/Markdown.astro";
|
||||||
|
|
||||||
const aboutPost = await getEntry('spec', 'about')
|
const aboutPost = await getEntry('spec', 'about')
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
---
|
---
|
||||||
import {getCategoryList, getSortedPosts} from "../../../utils/content-utils";
|
import {getCategoryList, getSortedPosts} from "@utils/content-utils";
|
||||||
import MainGridLayout from "../../../layouts/MainGridLayout.astro";
|
import MainGridLayout from "@layouts/MainGridLayout.astro";
|
||||||
import ArchivePanel from "../../../components/ArchivePanel.astro";
|
import ArchivePanel from "@components/ArchivePanel.astro";
|
||||||
import {i18n} from "../../../i18n/translation";
|
import {i18n} from "@i18n/translation";
|
||||||
import I18nKey from "../../../i18n/i18nKey";
|
import I18nKey from "@i18n/i18nKey";
|
||||||
|
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
---
|
---
|
||||||
import { getCollection, getEntry } from "astro:content";
|
import { getCollection, getEntry } from "astro:content";
|
||||||
import MainGridLayout from "../../layouts/MainGridLayout.astro";
|
import MainGridLayout from "@layouts/MainGridLayout.astro";
|
||||||
import ArchivePanel from "../../components/ArchivePanel.astro";
|
import ArchivePanel from "@components/ArchivePanel.astro";
|
||||||
import {i18n} from "../../i18n/translation";
|
import {i18n} from "@i18n/translation";
|
||||||
import I18nKey from "../../i18n/i18nKey";
|
import I18nKey from "@i18n/i18nKey";
|
||||||
---
|
---
|
||||||
|
|
||||||
<MainGridLayout title={i18n(I18nKey.archive)}>
|
<MainGridLayout title={i18n(I18nKey.archive)}>
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
---
|
---
|
||||||
|
import {getSortedPosts} from "@utils/content-utils";
|
||||||
import {getSortedPosts} from "../../../utils/content-utils";
|
import MainGridLayout from "@layouts/MainGridLayout.astro";
|
||||||
import MainGridLayout from "../../../layouts/MainGridLayout.astro";
|
import ArchivePanel from "@components/ArchivePanel.astro";
|
||||||
import ArchivePanel from "../../../components/ArchivePanel.astro";
|
import {i18n} from "@i18n/translation";
|
||||||
import {i18n} from "../../../i18n/translation";
|
import I18nKey from "@i18n/i18nKey";
|
||||||
import I18nKey from "../../../i18n/i18nKey";
|
|
||||||
|
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
---
|
---
|
||||||
import MainGridLayout from "../../layouts/MainGridLayout.astro";
|
import MainGridLayout from "@layouts/MainGridLayout.astro";
|
||||||
import TitleCard from "../../components/TitleCardNew.astro";
|
import TitleCard from "@components/TitleCardNew.astro";
|
||||||
import Pagination from "../../components/control/Pagination.astro";
|
import Pagination from "@components/control/Pagination.astro";
|
||||||
import {getSortedPosts} from "../../utils/content-utils";
|
import {getSortedPosts} from "@utils/content-utils";
|
||||||
import {getPostUrlBySlug} from "../../utils/url-utils";
|
import {getPostUrlBySlug} from "@utils/url-utils";
|
||||||
|
|
||||||
export async function getStaticPaths({ paginate }) {
|
export async function getStaticPaths({ paginate }) {
|
||||||
const allBlogPosts = await getSortedPosts();
|
const allBlogPosts = await getSortedPosts();
|
||||||
|
@ -17,18 +17,35 @@ const {page} = Astro.props;
|
||||||
<!-- 显示当前页面。也可以使用 Astro.params.page -->
|
<!-- 显示当前页面。也可以使用 Astro.params.page -->
|
||||||
<MainGridLayout>
|
<MainGridLayout>
|
||||||
<div class="flex flex-col gap-4 mb-4">
|
<div class="flex flex-col gap-4 mb-4">
|
||||||
{page.data.map(entry =>
|
{page.data.map((entry: { data: { draft: boolean; title: string; tags: string[]; category: string; published: Date; image: string; description: string; }; slug: string; }) => {
|
||||||
<TitleCard
|
// ここで draft が true の場合は何もレンダリングしない
|
||||||
entry={entry}
|
if (import.meta.env.PROD && entry.data.draft) {
|
||||||
title={entry.data.title}
|
return null;
|
||||||
tags={entry.data.tags}
|
}
|
||||||
category={entry.data.category}
|
|
||||||
published={entry.data.published}
|
return (
|
||||||
url={getPostUrlBySlug(entry.slug)}
|
<TitleCard
|
||||||
image={entry.data.image}
|
entry={entry}
|
||||||
description={entry.data.description}
|
title={entry.data.title}
|
||||||
></TitleCard>
|
tags={entry.data.tags}
|
||||||
)}
|
category={entry.data.category}
|
||||||
|
published={entry.data.published}
|
||||||
|
url={getPostUrlBySlug(entry.slug)}
|
||||||
|
image={entry.data.image}
|
||||||
|
description={entry.data.description}
|
||||||
|
draft={entry.data.draft}
|
||||||
|
></TitleCard>
|
||||||
|
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
<Pagination class="mx-auto" page={page}></Pagination>
|
<Pagination class="mx-auto" page={page}></Pagination>
|
||||||
</MainGridLayout>
|
</MainGridLayout>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
|
console.log("開発環境");
|
||||||
|
} else {
|
||||||
|
console.log("本番環境");
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -1,19 +1,21 @@
|
||||||
---
|
---
|
||||||
import { getCollection } from 'astro:content';
|
import { getCollection } from 'astro:content';
|
||||||
import MainGridLayout from "../../layouts/MainGridLayout.astro";
|
import MainGridLayout from "@layouts/MainGridLayout.astro";
|
||||||
import ImageBox from "../../components/misc/ImageBox.astro";
|
import ImageBox from "@components/misc/ImageBox.astro";
|
||||||
import {Icon} from "astro-icon/components";
|
import {Icon} from "astro-icon/components";
|
||||||
import PostMetadata from "../../components/PostMetadata.astro";
|
import PostMetadata from "@components/PostMetadata.astro";
|
||||||
import Button from "../../components/control/Button.astro";
|
import Button from "@components/control/Button.astro";
|
||||||
import {i18n} from "../../i18n/translation";
|
import {i18n} from "@i18n/translation";
|
||||||
import I18nKey from "../../i18n/i18nKey";
|
import I18nKey from "@i18n/i18nKey";
|
||||||
import {getPostUrlBySlug} from "../../utils/url-utils";
|
import {getPostUrlBySlug} from "@utils/url-utils";
|
||||||
import License from "../../components/misc/License.astro";
|
import License from "@components/misc/License.astro";
|
||||||
import {licenseConfig} from "../../config";
|
import {licenseConfig} from "src/config";
|
||||||
import Markdown from "../../components/misc/Markdown.astro";
|
import Markdown from "@components/misc/Markdown.astro";
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
const blogEntries = await getCollection('posts');
|
const blogEntries = await getCollection('posts', ({ data }) => {
|
||||||
|
return import.meta.env.PROD ? data.draft !== true : true;
|
||||||
|
});
|
||||||
return blogEntries.map(entry => ({
|
return blogEntries.map(entry => ({
|
||||||
params: { slug: entry.slug }, props: { entry },
|
params: { slug: entry.slug }, props: { entry },
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
import getReadingTime from 'reading-time';
|
// 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'
|
||||||
|
|
||||||
export function remarkReadingTime() {
|
export function remarkReadingTime() {
|
||||||
return function (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(1, Math.round(readingTime.minutes));
|
data.astro.frontmatter.minutes = Math.max(
|
||||||
data.astro.frontmatter.words = readingTime.words;
|
1,
|
||||||
};
|
Math.round(readingTime.minutes),
|
||||||
|
)
|
||||||
|
data.astro.frontmatter.words = readingTime.words
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,45 +1,45 @@
|
||||||
export type SiteConfig = {
|
export type SiteConfig = {
|
||||||
title: string,
|
title: string
|
||||||
subtitle: string,
|
subtitle: string
|
||||||
|
|
||||||
lang: string,
|
lang: string
|
||||||
|
|
||||||
themeHue: number,
|
themeHue: number
|
||||||
banner: {
|
banner: {
|
||||||
enable: boolean,
|
enable: boolean
|
||||||
src: string,
|
src: string
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export enum LinkPreset {
|
export enum LinkPreset {
|
||||||
Home,
|
Home = 0,
|
||||||
Archive,
|
Archive = 1,
|
||||||
About,
|
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
|
||||||
}
|
}
|
|
@ -1,74 +1,74 @@
|
||||||
import {CollectionEntry, getCollection} from "astro:content";
|
import { getCollection } from 'astro:content'
|
||||||
|
|
||||||
export async function getSortedPosts() {
|
export async function getSortedPosts() {
|
||||||
const allBlogPosts = await getCollection("posts");
|
const allBlogPosts = await getCollection('posts')
|
||||||
const sorted = allBlogPosts.sort((a, b) => {
|
const sorted = allBlogPosts.sort((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");
|
const allBlogPosts = await getCollection('posts')
|
||||||
|
|
||||||
const countMap: { [key: string]: number } = {};
|
const countMap: { [key: string]: number } = {}
|
||||||
allBlogPosts.map((post) => {
|
allBlogPosts.map(post => {
|
||||||
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");
|
const allBlogPosts = await getCollection('posts')
|
||||||
let count : {[key: string]: number} = {};
|
const count: { [key: string]: number } = {}
|
||||||
allBlogPosts.map((post) => {
|
allBlogPosts.map(post => {
|
||||||
if (!post.data.category) {
|
if (!post.data.category) {
|
||||||
return;
|
return
|
||||||
}
|
|
||||||
if (!count[post.data.category]) {
|
|
||||||
count[post.data.category] = 0;
|
|
||||||
}
|
|
||||||
count[post.data.category]++;
|
|
||||||
});
|
|
||||||
|
|
||||||
let lst = Object.keys(count).sort((a, b) => {
|
|
||||||
return a.toLowerCase().localeCompare(b.toLowerCase());
|
|
||||||
});
|
|
||||||
|
|
||||||
let ret : Category[] = [];
|
|
||||||
for (const c of lst) {
|
|
||||||
ret.push({name: c, count: count[c]});
|
|
||||||
}
|
}
|
||||||
return ret;
|
if (!count[post.data.category]) {
|
||||||
|
count[post.data.category] = 0
|
||||||
|
}
|
||||||
|
count[post.data.category]++
|
||||||
|
})
|
||||||
|
|
||||||
|
const lst = Object.keys(count).sort((a, b) => {
|
||||||
|
return a.toLowerCase().localeCompare(b.toLowerCase())
|
||||||
|
})
|
||||||
|
|
||||||
|
const ret: Category[] = []
|
||||||
|
for (const c of lst) {
|
||||||
|
ret.push({ name: c, count: count[c] })
|
||||||
|
}
|
||||||
|
return ret
|
||||||
}
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
export function formatDateToYYYYMMDD(date: Date): string {
|
export function formatDateToYYYYMMDD(date: Date): string {
|
||||||
const year = date.getFullYear();
|
const year = date.getFullYear()
|
||||||
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 `${year}-${month}-${day}`;
|
return `${year}-${month}-${day}`
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,20 @@
|
||||||
|
|
||||||
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, '$1');
|
return joined.replace(/([^:]\/)\/+/g, '$1')
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPostUrlBySlug(slug: string): string | null {
|
export function getPostUrlBySlug(slug: string): string | null {
|
||||||
if (!slug)
|
if (!slug) return null
|
||||||
return null;
|
return `/posts/${slug}`
|
||||||
return `/posts/${slug}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCategoryUrl(category: string): string | null {
|
export function getCategoryUrl(category: string): string | null {
|
||||||
if (!category)
|
if (!category) return null
|
||||||
return null;
|
return `/archive/category/${category}`
|
||||||
return `/archive/category/${category}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,14 @@
|
||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
const defaultTheme = require("tailwindcss/defaultTheme");
|
const defaultTheme = require("tailwindcss/defaultTheme")
|
||||||
module.exports = {
|
module.exports = {
|
||||||
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
|
content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"],
|
||||||
darkMode: 'class', // allows toggling dark mode manually
|
darkMode: "class", // allows toggling dark mode manually
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
sans: ['Roboto', 'sans-serif', ...defaultTheme.fontFamily.sans],
|
sans: ["Roboto", "sans-serif", ...defaultTheme.fontFamily.sans],
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [require("@tailwindcss/typography")],
|
||||||
require('@tailwindcss/typography'),
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,22 @@
|
||||||
{
|
{
|
||||||
"extends": "astro/tsconfigs/strict",
|
"extends": "astro/tsconfigs/strict",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
"strictNullChecks": true,
|
"strictNullChecks": true,
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
"name": "@astrojs/ts-plugin"
|
"name": "@astrojs/ts-plugin"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"paths": {
|
||||||
|
"@components/*": ["src/components/*"],
|
||||||
|
"@assets/*": ["src/assets/*"],
|
||||||
|
"@utils/*": ["src/utils/*"],
|
||||||
|
"@i18n/*": ["src/i18n/*"],
|
||||||
|
"@layouts/*": ["src/layouts/*"],
|
||||||
|
"@/*": ["src/*"]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["src/**/*"]
|
||||||
"src/**/**",
|
|
||||||
"src/**/**/**",
|
|
||||||
]
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue