mirror of
https://github.com/saicaca/fuwari.git
synced 2026-01-11 23:02:53 +01:00
feat: use .ts instead of .yaml for configurations
This commit is contained in:
BIN
src/assets/images/demo-avatar.png
Normal file
BIN
src/assets/images/demo-avatar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 406 KiB |
BIN
src/assets/images/demo-banner.png
Normal file
BIN
src/assets/images/demo-banner.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 877 KiB |
@@ -1,8 +1,8 @@
|
||||
---
|
||||
|
||||
import {getConfig} from "../utils/config-utils";
|
||||
import {siteConfig} from "../config";
|
||||
|
||||
---
|
||||
|
||||
<div id="config-carrier" data-hue={getConfig().appearance.hue}>
|
||||
<div id="config-carrier" data-hue={siteConfig.themeHue}>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
---
|
||||
|
||||
import {getConfig} from "../utils/config-utils";
|
||||
import {profileConfig} from "../config";
|
||||
|
||||
---
|
||||
|
||||
<div class="card-base max-w-[var(--page-width)] min-h-[72px] rounded-b-none mx-auto flex items-center px-6">
|
||||
<div class="text-black/50 dark:text-white/50 text-sm">
|
||||
© 2023 {getConfig().profile.author}. All Rights Reserved.
|
||||
© 2023 {profileConfig.name}. All Rights Reserved.
|
||||
<br>
|
||||
Powered by <a class="link text-[var(--primary)]" target="_blank" href="https://github.com/saicaca/fuwari">Fuwari</a>
|
||||
</div>
|
||||
|
||||
@@ -75,6 +75,8 @@ color_set({
|
||||
|
||||
--deep-text: oklch(0.25 0.02 var(--hue))
|
||||
|
||||
--title-active: oklch(0.6 0.1 var(--hue))
|
||||
|
||||
--line-divider: black(0.08) white(0.08)
|
||||
|
||||
--line-color: black(0.1) white(0.1)
|
||||
|
||||
@@ -2,20 +2,41 @@
|
||||
import Button from "./control/Button.astro";
|
||||
import { Icon } from 'astro-icon/components';
|
||||
import DisplaySetting from "./widget/DisplaySetting.astro";
|
||||
import {getConfig} from "../utils/config-utils";
|
||||
import I18nKey from "../i18n/i18nKey";
|
||||
import {i18n} from "../i18n/translation";
|
||||
import {LinkPreset, NavBarLink} from "../types/config";
|
||||
import {navBarConfig, siteConfig} from "../config";
|
||||
const className = Astro.props.class;
|
||||
|
||||
function isI18nKey(key: string): key is I18nKey {
|
||||
return Object.values(I18nKey).includes(key);
|
||||
}
|
||||
|
||||
function getLinkName(name: string) {
|
||||
if (isI18nKey(name)) {
|
||||
return i18n(name);
|
||||
let links: NavBarLink[] = navBarConfig.links.map((item) => {
|
||||
if (typeof item === "number") {
|
||||
return getLinkPresetInfo(item)
|
||||
}
|
||||
return item;
|
||||
});
|
||||
|
||||
function getLinkPresetInfo(p: LinkPreset): NavBarLink {
|
||||
switch (p) {
|
||||
case LinkPreset.Home:
|
||||
return {
|
||||
name: i18n(I18nKey.home),
|
||||
url: "/page/1"
|
||||
};
|
||||
case LinkPreset.Archive:
|
||||
return {
|
||||
name: i18n(I18nKey.archive),
|
||||
url: "/archive"
|
||||
};
|
||||
case LinkPreset.About:
|
||||
return {
|
||||
name: i18n(I18nKey.about),
|
||||
url: "/about"
|
||||
};
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
---
|
||||
@@ -25,12 +46,12 @@ function getLinkName(name: string) {
|
||||
<a href="/page/1"><Button height="52px" class="px-5 font-bold rounded-lg active:scale-95" light>
|
||||
<div class="flex flex-row text-[var(--primary)] items-center text-md">
|
||||
<Icon name="material-symbols:home-outline-rounded" size={28} class="mb-1 mr-2" />
|
||||
{getConfig().title}
|
||||
{siteConfig.title}
|
||||
</div>
|
||||
</Button></a>
|
||||
<div>
|
||||
{Object.keys(getConfig().menu).map((key) => {
|
||||
return <a aria-label={getLinkName(key)} href={getConfig().menu[key]}><Button light class="font-bold px-5 rounded-lg active:scale-95">{getLinkName(key)}</Button></a>
|
||||
{links.map((l) => {
|
||||
return <a aria-label={l.name} href={l.url} target={l.external ? "_blank" : null}><Button light class="font-bold px-5 rounded-lg active:scale-95">{l.name}</Button></a>;
|
||||
})}
|
||||
</div>
|
||||
<div class="flex">
|
||||
|
||||
@@ -35,6 +35,7 @@ const { remarkPluginFrontmatter } = await entry.render();
|
||||
class="transition w-full block font-bold mb-3 text-3xl
|
||||
text-black/90 dark:text-white/90
|
||||
hover:text-[var(--primary)] dark:hover:text-[var(--primary)]
|
||||
active:text-[var(--title-active)] dark:active:text-[var(--title-active)]
|
||||
before:w-1 before:h-5 before:rounded-md before:bg-[var(--primary)]
|
||||
before:absolute md:before:top-[35px] before:left-[18px]
|
||||
">
|
||||
@@ -44,7 +45,7 @@ const { remarkPluginFrontmatter } = await entry.render();
|
||||
<!-- metadata -->
|
||||
<PostMetadata published={published} tags={tags} categories={categories} class:list={{"mb-4": description, "mb-6": !description}}></PostMetadata>
|
||||
|
||||
<div class="transition text-black/75 dark:text-white/75 mb-4">
|
||||
<div class="transition text-black/75 dark:text-white/75 mb-3.5">
|
||||
{ description }
|
||||
</div>
|
||||
|
||||
@@ -61,8 +62,8 @@ const { remarkPluginFrontmatter } = await entry.render();
|
||||
"max-h-[20vh] md:max-h-none mx-4 mt-4 md:mx-0 md:mt-0",
|
||||
"md:w-[var(--coverWidth)] relative md:absolute md:top-3 md:bottom-3 md:right-3 rounded-xl overflow-hidden active:scale-95"
|
||||
]} >
|
||||
<div class="absolute z-10 w-full h-full group-hover:bg-black/30 group-active:bg-black/50 transition"></div>
|
||||
<div class="absolute z-20 w-full h-full flex items-center justify-center ">
|
||||
<div class="absolute pointer-events-none z-10 w-full h-full group-hover:bg-black/30 group-active:bg-black/50 transition"></div>
|
||||
<div class="absolute pointer-events-none z-20 w-full h-full flex items-center justify-center ">
|
||||
<Icon name="material-symbols:chevron-right-rounded"
|
||||
class="transition opacity-0 group-hover:opacity-100 text-white text-5xl">
|
||||
</Icon>
|
||||
|
||||
@@ -5,11 +5,23 @@ interface Props {
|
||||
class?: string;
|
||||
alt?: string
|
||||
}
|
||||
import { Image } from 'astro:assets';
|
||||
|
||||
const {id, src, alt} = Astro.props;
|
||||
const className = Astro.props.class;
|
||||
|
||||
const isLocal = !(src.startsWith('/') || src.startsWith('http') || src.startsWith('https') || src.startsWith('data:'));
|
||||
|
||||
let img;
|
||||
if (isLocal) {
|
||||
img = (await import("../../" + src)).default;
|
||||
}
|
||||
|
||||
---
|
||||
<div class:list={[className, 'overflow-hidden relative']}>
|
||||
<div class="transition absolute inset-0 dark:bg-black/10 bg-opacity-50"></div>
|
||||
<img src={src} alt={alt} class="w-full h-full object-center object-cover">
|
||||
<div class="transition absolute inset-0 dark:bg-black/10 bg-opacity-50 pointer-events-none"></div>
|
||||
{isLocal && <Image src={img} alt={alt || ""} class="w-full h-full object-center object-cover" />}
|
||||
{!isLocal && <img src={src} alt={alt || ""} class="w-full h-full object-center object-cover" />}
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
---
|
||||
import {getConfig} from "../../utils/config-utils";
|
||||
import {i18n} from "../../i18n/translation";
|
||||
import I18nKey from "../../i18n/i18nKey";
|
||||
import {siteConfig} from "../../config";
|
||||
|
||||
const enableBanner = getConfig().banner.enable;
|
||||
const enableBanner = siteConfig.banner.enable;
|
||||
|
||||
---
|
||||
<div id="display-setting" class:list={["card-base closed absolute transition-all w-[320px] fixed right-4 border-[var(--primary)] px-4 py-4",
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
---
|
||||
import ImageBox from "../misc/ImageBox.astro";
|
||||
import Button from "../control/Button.astro";
|
||||
import {getConfig} from "../../utils/config-utils";
|
||||
import {Icon} from "astro-icon/components";
|
||||
import {profileConfig} from "../../config";
|
||||
interface props {
|
||||
|
||||
}
|
||||
const className = Astro.props
|
||||
|
||||
const vConf = getConfig();
|
||||
const config = profileConfig;
|
||||
|
||||
---
|
||||
<div class="card-base">
|
||||
<a aria-label="Go to About Page" href="/about" class="group block relative m-3 overflow-hidden rounded-xl active:scale-95">
|
||||
<div class="absolute transition group-hover:bg-black/30 group-active:bg-black/50 w-full h-full z-50 flex items-center justify-center">
|
||||
<div class="absolute transition pointer-events-none group-hover:bg-black/30 group-active:bg-black/50 w-full h-full z-50 flex items-center justify-center">
|
||||
<Icon name="material-symbols:person-outline-rounded"
|
||||
class="transition opacity-0 group-hover:opacity-100 text-white text-6xl">
|
||||
</Icon>
|
||||
</div>
|
||||
<ImageBox src={vConf.profile.avatar} alt="Profile Image of the Author" class="mt-1 mx-auto w-[240px] lg:w-full h-full lg:mt-0 "></ImageBox>
|
||||
<ImageBox src={config.avatar} alt="Profile Image of the Author" class="mt-1 mx-auto w-[240px] lg:w-full h-full lg:mt-0 "></ImageBox>
|
||||
</a>
|
||||
<div class="font-bold text-xl text-center mb-1 dark:text-neutral-50 transition">{vConf.profile.author}</div>
|
||||
<div class="font-bold text-xl text-center mb-1 dark:text-neutral-50 transition">{config.name}</div>
|
||||
<div class="h-1 w-5 bg-[var(--primary)] mx-auto rounded-full mb-3 transition"></div>
|
||||
<div class="text-center text-neutral-400 mb-2 transition">{vConf.profile.subtitle}</div>
|
||||
<div class="text-center text-neutral-400 mb-2 transition">{config.bio}</div>
|
||||
<div class="flex gap-2 mx-2 justify-center mb-4">
|
||||
{vConf.profile.links.map(item =>
|
||||
{config.links.map(item =>
|
||||
<a aria-label={item.name} href={item.url} target="_blank">
|
||||
<Button isIcon iconName={item.icon} regular height="40px" class="rounded-lg active:scale-90"></Button>
|
||||
</a>
|
||||
|
||||
47
src/config.ts
Normal file
47
src/config.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import type {NavBarConfig, ProfileConfig, SiteConfig} from "./types/config.ts";
|
||||
import {LinkPreset} from "./types/config.ts";
|
||||
|
||||
export const siteConfig: SiteConfig = {
|
||||
title: 'Fuwari',
|
||||
subtitle: 'Demo Site',
|
||||
lang: 'en-US',
|
||||
themeHue: 250,
|
||||
banner: {
|
||||
enable: true,
|
||||
src: 'assets/images/demo-banner.png',
|
||||
}
|
||||
}
|
||||
|
||||
export const navBarConfig: NavBarConfig = {
|
||||
links: [
|
||||
LinkPreset.Home,
|
||||
LinkPreset.Archive,
|
||||
LinkPreset.About,
|
||||
{
|
||||
name: 'GitHub',
|
||||
url: 'https://github.com/saicaca/fuwari',
|
||||
external: true,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const profileConfig: ProfileConfig = {
|
||||
avatar: 'assets/images/demo-avatar.png',
|
||||
name: 'Lorem Ipsum',
|
||||
bio: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
||||
links: [
|
||||
{
|
||||
name: 'Twitter',
|
||||
icon: 'fa6-brands:twitter',
|
||||
url: 'https://twitter.com',
|
||||
}, {
|
||||
name: 'Steam',
|
||||
icon: 'fa6-brands:steam',
|
||||
url: 'https://store.steampowered.com',
|
||||
}, {
|
||||
name: 'GitHub',
|
||||
icon: 'fa6-brands:github',
|
||||
url: 'https://github.com/saicaca/fuwari',
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -6,16 +6,17 @@ image: 'https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/208fc754-890d-4adb-9753
|
||||
tags: ["Fuwari", "Blogging", "Customization"]
|
||||
---
|
||||
|
||||
# How to set a cover image using the cover attribute
|
||||
## Set the cover image using the `image` attribute
|
||||
|
||||
Select an Image: Before you start, make sure you have an image you want to use as a cover. Let's assume its filename is my-cover-image.jpg and it's located in an images directory at the root of your site.
|
||||
|
||||
Edit your article: At the top of your Markdown file, include the frontmatter section. Add a cover attribute and specify the path to your cover image.
|
||||
Edit your article: At the top of your Markdown file, include the frontmatter section. Set the `image` attribute to the path of your image file.
|
||||
|
||||
```markdown
|
||||
---
|
||||
title: "Your Article Title"
|
||||
published: 2023-10-05
|
||||
cover: "/images/my-cover-image.jpg"
|
||||
image: "/images/my-cover-image.jpg"
|
||||
---
|
||||
```
|
||||
Web URLs are also supported.
|
||||
|
||||
@@ -2,7 +2,7 @@ import {en} from "./languages/en.ts";
|
||||
import {zh_TW} from "./languages/zh_TW.ts";
|
||||
import {zh_CN} from "./languages/zh_CN.ts";
|
||||
import type I18nKey from "./i18nKey.ts";
|
||||
import {getConfig} from "../utils/config-utils.ts";
|
||||
import {siteConfig} from "../config.ts";
|
||||
|
||||
export type Translation = {
|
||||
[K in I18nKey]: string;
|
||||
@@ -25,6 +25,6 @@ export function getTranslation(lang: string): Translation {
|
||||
}
|
||||
|
||||
export function i18n(key: I18nKey): string {
|
||||
const lang = getConfig().lang || "en";
|
||||
const lang = siteConfig.lang || "en";
|
||||
return getTranslation(lang)[key];
|
||||
}
|
||||
@@ -7,9 +7,9 @@ import { ViewTransitions } from 'astro:transitions';
|
||||
import ImageBox from "../components/misc/ImageBox.astro";
|
||||
|
||||
import { fade } from 'astro:transitions';
|
||||
import {getConfig} from "../utils/config-utils";
|
||||
import {pathsEqual} from "../utils/url-utils";
|
||||
import ConfigCarrier from "../components/ConfigCarrier.astro";
|
||||
import {siteConfig} from "../config";
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
@@ -46,18 +46,19 @@ const myFade = {
|
||||
|
||||
// defines global css variables
|
||||
// why doing this in Layout instead of GlobalStyles: https://github.com/withastro/astro/issues/6728#issuecomment-1502203757
|
||||
const viConf = getConfig();
|
||||
const configHue = viConf.appearance.hue;
|
||||
const configHue = siteConfig.themeHue;
|
||||
if (!banner || typeof banner !== 'string' || banner.trim() === '') {
|
||||
banner = viConf.banner.url;
|
||||
banner = siteConfig.banner.src;
|
||||
}
|
||||
|
||||
// TODO don't use post cover as banner for now
|
||||
banner = viConf.banner.url;
|
||||
banner = siteConfig.banner.src;
|
||||
|
||||
let pageTitle = getConfig().title;
|
||||
let pageTitle;
|
||||
if (title) {
|
||||
pageTitle = `${title} - ${pageTitle}`;
|
||||
pageTitle = `${title} - ${siteConfig.title}`;
|
||||
} else {
|
||||
pageTitle = `${siteConfig.title} - ${siteConfig.subtitle}`;
|
||||
}
|
||||
|
||||
---
|
||||
@@ -94,8 +95,8 @@ if (title) {
|
||||
class:list={{'banner-home': isHomePage, 'banner-else': !isHomePage}}
|
||||
|
||||
>
|
||||
<ImageBox id="boxtest" alt="Banner image of the blog" class:list={["object-center object-cover h-full", {"hidden": !viConf.banner.enable}]}
|
||||
src={banner} transition:animate="fade"
|
||||
<ImageBox id="boxtest" alt="Banner image of the blog" class:list={["object-center object-cover h-full", {"hidden": !siteConfig.banner.enable}]}
|
||||
src={siteConfig.banner.src} transition:animate="fade"
|
||||
>
|
||||
</ImageBox>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@ import {pathsEqual} from "../utils/url-utils";
|
||||
import Footer from "../components/Footer.astro";
|
||||
import BackToTop from "../components/control/BackToTop.astro";
|
||||
import DisplaySetting from "../components/widget/DisplaySetting.astro";
|
||||
import {getConfig} from "../utils/config-utils";
|
||||
import {siteConfig} from "../config";
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
@@ -17,8 +17,7 @@ const { title, banner } = Astro.props;
|
||||
|
||||
const isHomePage = pathsEqual(Astro.url.pathname, '/') || pathsEqual(Astro.url.pathname, '/page/1');
|
||||
|
||||
|
||||
const enableBanner = getConfig().banner.enable;
|
||||
const enableBanner = siteConfig.banner.enable;
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import ImageBox from "../../components/misc/ImageBox.astro";
|
||||
import {Icon} from "astro-icon/components";
|
||||
import PostMetadata from "../../components/PostMetadata.astro";
|
||||
import Button from "../../components/control/Button.astro";
|
||||
import {getConfig} from "../../utils/config-utils";
|
||||
import {i18n} from "../../i18n/translation";
|
||||
import I18nKey from "../../i18n/i18nKey";
|
||||
import {getPostUrlBySlug} from "../../utils/url-utils";
|
||||
@@ -109,16 +108,30 @@ const { remarkPluginFrontmatter } = await entry.render();
|
||||
<style lang="stylus" is:global>
|
||||
.custom-md
|
||||
a
|
||||
position: relative
|
||||
background: none
|
||||
margin: -4px
|
||||
padding: 4px
|
||||
border-radius: 6px
|
||||
color: var(--primary)
|
||||
text-decoration none
|
||||
text-decoration-line: none;
|
||||
/*&:after*/
|
||||
/* content: ''*/
|
||||
/* position: absolute*/
|
||||
/* left: 2px*/
|
||||
/* right: 2px*/
|
||||
/* bottom: 4px*/
|
||||
/* height: 6px*/
|
||||
/* border-radius: 3px*/
|
||||
/* background: var(--link-hover)*/
|
||||
/* transition: background 0.15s ease-in-out;*/
|
||||
/* z-index: -1;*/
|
||||
&:hover
|
||||
background: var(--link-hover)
|
||||
&:active
|
||||
background: var(--link-active)
|
||||
/*&:after*/
|
||||
/* background: var(--link-active)*/
|
||||
code
|
||||
font-family: monospace
|
||||
background: var(--inline-code-bg)
|
||||
@@ -132,6 +145,9 @@ const { remarkPluginFrontmatter } = await entry.render();
|
||||
content: none
|
||||
pre
|
||||
background: var(--codeblock-bg) !important
|
||||
border-radius: 12px
|
||||
padding-left: 20px
|
||||
padding-right: 20px
|
||||
code
|
||||
padding: 0
|
||||
background: none
|
||||
@@ -172,7 +188,7 @@ const { remarkPluginFrontmatter } = await entry.render();
|
||||
border-color: var(--line-divider)
|
||||
border-style: dashed
|
||||
iframe
|
||||
border-radius: 8px
|
||||
border-radius: 12px
|
||||
margin-left: auto
|
||||
margin-right: auto
|
||||
max-width: 100%
|
||||
|
||||
39
src/types/config.ts
Normal file
39
src/types/config.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
export type SiteConfig = {
|
||||
title: string,
|
||||
subtitle: string,
|
||||
|
||||
lang: string,
|
||||
|
||||
themeHue: number,
|
||||
banner: {
|
||||
enable: boolean,
|
||||
src: string,
|
||||
}
|
||||
};
|
||||
|
||||
export enum LinkPreset {
|
||||
Home,
|
||||
Archive,
|
||||
About,
|
||||
}
|
||||
|
||||
export type NavBarLink = {
|
||||
name: string,
|
||||
url: string,
|
||||
external?: boolean
|
||||
}
|
||||
|
||||
export type NavBarConfig = {
|
||||
links: (NavBarLink | LinkPreset)[],
|
||||
}
|
||||
|
||||
export type ProfileConfig = {
|
||||
avatar?: string,
|
||||
name: string,
|
||||
bio?: string,
|
||||
links: {
|
||||
name: string,
|
||||
url: string,
|
||||
icon: string,
|
||||
}[],
|
||||
};
|
||||
@@ -1,40 +0,0 @@
|
||||
// @ts-ignore
|
||||
import _config from '../../fuwari.config.yml';
|
||||
|
||||
interface FuwariConfig {
|
||||
title: string;
|
||||
menu: {
|
||||
[key: string]: string;
|
||||
};
|
||||
lang: string;
|
||||
appearance: {
|
||||
hue: number;
|
||||
};
|
||||
favicon: string;
|
||||
banner: {
|
||||
enable: boolean;
|
||||
url: string;
|
||||
position: string;
|
||||
onAllPages: boolean;
|
||||
};
|
||||
sidebar: {
|
||||
widgets: {
|
||||
normal: string | string[];
|
||||
sticky: string | string[];
|
||||
};
|
||||
};
|
||||
profile: {
|
||||
avatar: string;
|
||||
author: string;
|
||||
subtitle: string;
|
||||
links: {
|
||||
name: string;
|
||||
icon: string;
|
||||
url: string;
|
||||
}[];
|
||||
}
|
||||
}
|
||||
|
||||
const config: FuwariConfig = _config;
|
||||
|
||||
export const getConfig = () => config;
|
||||
Reference in New Issue
Block a user