feat(theme): 主题相关合并一起、查看源代码按钮

This commit is contained in:
Ad-closeNN
2026-05-01 20:27:31 +08:00
parent 8615d000b2
commit 43f57c7e14
20 changed files with 539 additions and 272 deletions
-79
View File
@@ -1,79 +0,0 @@
<script lang="ts">
import { 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";
import type { LIGHT_DARK_MODE } from "@/types/config.ts";
const seq: LIGHT_DARK_MODE[] = [LIGHT_MODE, DARK_MODE];
let mode: LIGHT_DARK_MODE = LIGHT_MODE;
onMount(() => {
mode = getStoredTheme();
// 移除了系统主题监听功能
// 只在初始化时应用存储的主题
applyThemeToDocument(mode);
});
function switchScheme(newMode: LIGHT_DARK_MODE) {
mode = newMode;
setTheme(newMode);
}
function toggleScheme() {
let i = 0;
for (; i < seq.length; i++) {
if (seq[i] === mode) {
break;
}
}
switchScheme(seq[(i + 1) % seq.length]);
}
function showPanel() {
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");
}
</script>
<!-- z-50 make the panel higher than other float panels -->
<div class="relative z-50" role="menu" tabindex="-1" onmouseleave={hidePanel}>
<button aria-label="Light/Dark Mode" role="menuitem" class="relative btn-plain scale-animation rounded-lg h-11 w-11 active:scale-90" id="scheme-switch" onclick={toggleScheme} onmouseenter={showPanel}>
<div class="absolute" class:opacity-0={mode !== LIGHT_MODE}>
<Icon icon="material-symbols:wb-sunny-outline-rounded" class="text-[1.25rem]"></Icon>
</div>
<div class="absolute" class:opacity-0={mode !== DARK_MODE}>
<Icon icon="material-symbols:dark-mode-outline-rounded" class="text-[1.25rem]"></Icon>
</div>
</button>
<div id="light-dark-panel" class="hidden lg:block absolute transition float-panel-closed top-11 -right-2 pt-5">
<div class="card-base float-panel p-2">
<button class="flex transition whitespace-nowrap items-center !justify-start w-full btn-plain scale-animation rounded-lg h-9 px-3 font-medium active:scale-95 mb-0.5"
class:current-theme-btn={mode === LIGHT_MODE}
onclick={() => switchScheme(LIGHT_MODE)}
>
<Icon icon="material-symbols:wb-sunny-outline-rounded" class="text-[1.25rem] mr-3"></Icon>
{i18n(I18nKey.lightMode)}
</button>
<button class="flex transition whitespace-nowrap items-center !justify-start w-full btn-plain scale-animation rounded-lg h-9 px-3 font-medium active:scale-95 mb-0.5"
class:current-theme-btn={mode === DARK_MODE}
onclick={() => switchScheme(DARK_MODE)}
>
<Icon icon="material-symbols:dark-mode-outline-rounded" class="text-[1.25rem] mr-3"></Icon>
{i18n(I18nKey.darkMode)}
</button>
</div>
</div>
</div>
+5 -39
View File
@@ -4,10 +4,9 @@ import { navBarConfig, siteConfig } from "../config";
import { LinkPresets } from "../constants/link-presets";
import { LinkPreset, type NavBarLink } from "../types/config";
import { url } from "../utils/url-utils";
import LightDarkSwitch from "./LightDarkSwitch.svelte";
import Search from "./Search.svelte";
import DisplaySettings from "./widget/DisplaySettings.svelte";
import NavMenuPanel from "./widget/NavMenuPanel.astro";
import ThemeSettingsBlock from "./widget/ThemeSettingsBlock.svelte";
const className = Astro.props.class;
@@ -28,7 +27,7 @@ let links: NavBarLink[] = navBarConfig.links.map(
<div aria-hidden="true" class="pointer-events-none absolute inset-x-0 top-0 h-px bg-white/55 dark:bg-white/12"></div>
<a href={url('/')} class="btn-plain scale-animation rounded-lg h-[3.25rem] px-5 font-bold active:scale-95">
<div class="flex flex-row text-[var(--primary)] items-center text-md">
<Icon name="material-symbols:home-outline-rounded" class="text-[1.75rem] mb-1 mr-2" />
<Icon is:inline name="material-symbols:home-outline-rounded" class="text-[1.75rem] mb-1 mr-2" />
{siteConfig.title}
</div>
</a>
@@ -39,7 +38,7 @@ let links: NavBarLink[] = navBarConfig.links.map(
>
<div class="flex items-center">
{l.name}
{l.external && <Icon name="fa6-solid:arrow-up-right-from-square" class="text-[0.875rem] transition -translate-y-[1px] ml-1 text-black/[0.2] dark:text-white/[0.2]"></Icon>}
{l.external && <Icon is:inline name="fa6-solid:arrow-up-right-from-square" class="text-[0.875rem] transition -translate-y-[1px] ml-1 text-black/[0.2] dark:text-white/[0.2]"></Icon>}
</div>
</a>;
})}
@@ -47,50 +46,17 @@ let links: NavBarLink[] = navBarConfig.links.map(
<div class="flex">
<!--<SearchPanel client:load>-->
<Search client:only="svelte"></Search>
{!siteConfig.themeColor.fixed && (
<button aria-label="Display Settings" class="btn-plain scale-animation rounded-lg h-11 w-11 active:scale-90" id="display-settings-switch">
<Icon name="material-symbols:palette-outline" class="text-[1.25rem]"></Icon>
</button>
)}
<LightDarkSwitch client:only="svelte"></LightDarkSwitch>
<ThemeSettingsBlock showThemeColor={!siteConfig.themeColor.fixed} client:only="svelte"></ThemeSettingsBlock>
<button aria-label="Menu" name="Nav Menu" class="btn-plain scale-animation rounded-lg w-11 h-11 active:scale-90 md:!hidden" id="nav-menu-switch">
<Icon name="material-symbols:menu-rounded" class="text-[1.25rem]"></Icon>
<Icon is:inline name="material-symbols:menu-rounded" class="text-[1.25rem]"></Icon>
</button>
</div>
<NavMenuPanel links={links}></NavMenuPanel>
<DisplaySettings client:only="svelte"></DisplaySettings>
</div>
</div>
<script>
function switchTheme() {
if (localStorage.theme === 'dark') {
document.documentElement.classList.remove('dark');
localStorage.theme = 'light';
} else {
document.documentElement.classList.add('dark');
localStorage.theme = 'dark';
}
}
function loadButtonScript() {
let switchBtn = document.getElementById("scheme-switch");
if (switchBtn) {
switchBtn.onclick = function () {
switchTheme()
};
}
let settingBtn = document.getElementById("display-settings-switch");
if (settingBtn) {
settingBtn.onclick = function () {
let settingPanel = document.getElementById("display-setting");
if (settingPanel) {
settingPanel.classList.toggle("float-panel-closed");
}
};
}
let menuBtn = document.getElementById("nav-menu-switch");
if (menuBtn) {
menuBtn.onclick = function () {
+6 -6
View File
@@ -1,5 +1,6 @@
---
import type { CollectionEntry } from "astro:content";
import { render } from "astro:content";
import path from "node:path";
import { Icon } from "astro-icon/components";
import I18nKey from "../i18n/i18nKey";
@@ -7,7 +8,6 @@ import { i18n } from "../i18n/translation";
import { getDir } from "../utils/url-utils";
import ImageWrapper from "./misc/ImageWrapper.astro";
import PostMetadata from "./PostMeta.astro";
import { render } from 'astro:content';
interface Props {
class?: string;
@@ -57,15 +57,15 @@ const { remarkPluginFrontmatter } = await render(entry);
<span class="flex flex-wrap items-center gap-x-3 gap-y-2">
{isPinned && (
<span class="inline-flex items-center gap-1 rounded-full border border-[var(--primary)]/20 bg-[var(--primary)]/10 px-2.5 py-1 text-xs font-semibold text-[var(--primary)] leading-none">
<Icon name="material-symbols:keep-rounded" class="text-lg"></Icon>
<Icon is:inline name="material-symbols:keep-rounded" class="text-lg"></Icon>
置顶
</span>
)}
<span class="min-w-0">
{title}
<span class="inline-flex items-center align-middle whitespace-nowrap">
<Icon class="inline text-[2rem] text-[var(--primary)] translate-y-0.5 md:hidden" name="material-symbols:chevron-right-rounded" ></Icon>
<Icon class="text-[var(--primary)] text-[2rem] transition hidden md:inline translate-y-0.5 opacity-0 -translate-x-1 group-hover:opacity-100 group-hover:translate-x-0" name="material-symbols:chevron-right-rounded"></Icon>
<Icon is:inline class="inline text-[2rem] text-[var(--primary)] translate-y-0.5 md:hidden" name="material-symbols:chevron-right-rounded" ></Icon>
<Icon is:inline class="text-[var(--primary)] text-[2rem] transition hidden md:inline translate-y-0.5 opacity-0 -translate-x-1 group-hover:opacity-100 group-hover:translate-x-0" name="material-symbols:chevron-right-rounded"></Icon>
</span>
</span>
</span>
@@ -105,7 +105,7 @@ const { remarkPluginFrontmatter } = await render(entry);
]} >
<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"
<Icon is:inline name="material-symbols:chevron-right-rounded"
class="transition opacity-0 group-hover:opacity-100 scale-50 group-hover:scale-100 text-white text-5xl">
</Icon>
</div>
@@ -119,7 +119,7 @@ const { remarkPluginFrontmatter } = await render(entry);
absolute right-3 top-3 bottom-3 rounded-xl bg-[var(--enter-btn-bg)]
hover:bg-[var(--enter-btn-bg-hover)] active:bg-[var(--enter-btn-bg-active)] active:scale-95
">
<Icon name="material-symbols:chevron-right-rounded"
<Icon is:inline name="material-symbols:chevron-right-rounded"
class="transition text-[var(--primary)] text-4xl mx-auto">
</Icon>
</a>
+8 -8
View File
@@ -4,7 +4,7 @@ import { Icon } from "astro-icon/components";
import I18nKey from "../i18n/i18nKey";
import { i18n } from "../i18n/translation";
import { formatDateToYYYYMMDD } from "../utils/date-utils";
import { getCategoryUrl, getTagUrl, getDir, url } from "../utils/url-utils";
import { getCategoryUrl, getDir, getTagUrl, url } from "../utils/url-utils";
interface Props {
class: string;
@@ -14,7 +14,7 @@ interface Props {
category: string | null;
hideTagsForMobile?: boolean;
hideUpdateDate?: boolean;
slug?: string;
slug?: string;
}
const {
published,
@@ -23,7 +23,7 @@ const {
category,
hideTagsForMobile = false,
hideUpdateDate = false,
slug,
slug,
} = Astro.props;
const className = Astro.props.class;
---
@@ -33,7 +33,7 @@ const className = Astro.props.class;
<div class="flex items-center">
<div class="meta-icon"
>
<Icon name="material-symbols:calendar-today-outline-rounded" class="text-xl"></Icon>
<Icon is:inline name="material-symbols:calendar-today-outline-rounded" class="text-xl"></Icon>
</div>
<span class="text-50 text-sm font-medium">{formatDateToYYYYMMDD(published)}</span>
</div>
@@ -43,7 +43,7 @@ const className = Astro.props.class;
<div class="flex items-center">
<div class="meta-icon"
>
<Icon name="material-symbols:edit-calendar-outline-rounded" class="text-xl"></Icon>
<Icon is:inline name="material-symbols:edit-calendar-outline-rounded" class="text-xl"></Icon>
</div>
<span class="text-50 text-sm font-medium">{formatDateToYYYYMMDD(updated)}</span>
</div>
@@ -54,7 +54,7 @@ const className = Astro.props.class;
<div class="flex items-center">
<div class="meta-icon"
>
<Icon name="material-symbols:book-2-outline-rounded" class="text-xl"></Icon>
<Icon is:inline name="material-symbols:book-2-outline-rounded" class="text-xl"></Icon>
</div>
<div class="flex flex-row flex-nowrap items-center">
<a href={getCategoryUrl(category)} aria-label={`View all posts in the ${category} category`}
@@ -69,7 +69,7 @@ const className = Astro.props.class;
<div class:list={["items-center", {"flex": !hideTagsForMobile, "hidden md:flex": hideTagsForMobile}]}>
<div class="meta-icon"
>
<Icon name="material-symbols:tag-rounded" class="text-xl"></Icon>
<Icon is:inline name="material-symbols:tag-rounded" class="text-xl"></Icon>
</div>
<div class="flex flex-row flex-nowrap items-center">
{(tags && tags.length > 0) && tags.map((tag, i) => (
@@ -87,7 +87,7 @@ const className = Astro.props.class;
{slug && (
<div class="flex items-center">
<div class="meta-icon">
<Icon name="material-symbols:visibility-outline-rounded" class="text-xl"></Icon>
<Icon is:inline name="material-symbols:visibility-outline-rounded" class="text-xl"></Icon>
</div>
<span class="text-50 text-sm font-medium" id="page-views-display">加载中...</span>
</div>
+1 -1
View File
@@ -6,7 +6,7 @@ import { Icon } from "astro-icon/components";
<div class="back-to-top-wrapper hidden lg:block">
<div id="back-to-top-btn" class="back-to-top-btn hide flex items-center rounded-2xl overflow-hidden transition" onclick="backToTop()">
<button aria-label="Back to Top" class="btn-card h-[3.75rem] w-[3.75rem]">
<Icon name="material-symbols:keyboard-arrow-up-rounded" class="mx-auto"></Icon>
<Icon is:inline name="material-symbols:keyboard-arrow-up-rounded" class="mx-auto"></Icon>
</button>
</div>
</div>
+3 -3
View File
@@ -57,12 +57,12 @@ const getPageUrl = (p: number) => {
{"disabled": page.url.prev == undefined}
]}
>
<Icon name="material-symbols:chevron-left-rounded" class="text-[1.75rem]"></Icon>
<Icon is:inline name="material-symbols:chevron-left-rounded" class="text-[1.75rem]"></Icon>
</a>
<div class="bg-[var(--card-bg)] flex flex-row rounded-lg items-center text-neutral-700 dark:text-neutral-300 font-bold">
{pages.map((p) => {
if (p == HIDDEN)
return <Icon name="material-symbols:more-horiz" class="mx-1"/>;
return <Icon is:inline name="material-symbols:more-horiz" class="mx-1"/>;
if (p == page.currentPage)
return <div class="h-11 w-11 rounded-lg bg-[var(--primary)] flex items-center justify-center
font-bold text-white dark:text-black/70"
@@ -79,6 +79,6 @@ const getPageUrl = (p: number) => {
{"disabled": page.url.next == undefined}
]}
>
<Icon name="material-symbols:chevron-right-rounded" class="text-[1.75rem]"></Icon>
<Icon is:inline name="material-symbols:chevron-right-rounded" class="text-[1.75rem]"></Icon>
</a>
</div>
+1 -1
View File
@@ -39,5 +39,5 @@ const postUrl = decodeURIComponent(Astro.url.toString());
<a href={licenseConf.url} target="_blank" class="link text-[var(--primary)] line-clamp-2">{licenseConf.name}</a>
</div>
</div>
<Icon name="fa6-brands:creative-commons" class="transition text-[15rem] absolute pointer-events-none right-6 top-1/2 -translate-y-1/2 text-black/5 dark:text-white/5"></Icon>
<Icon is:inline name="fa6-brands:creative-commons" class="transition text-[15rem] absolute pointer-events-none right-6 top-1/2 -translate-y-1/2 text-black/5 dark:text-white/5"></Icon>
</div>
@@ -1,93 +0,0 @@
<script lang="ts">
import I18nKey from "@i18n/i18nKey";
import { i18n } from "@i18n/translation";
import Icon from "@iconify/svelte";
import { getDefaultHue, getHue, setHue } from "@utils/setting-utils";
let hue = getHue();
const defaultHue = getDefaultHue();
function resetHue() {
hue = getDefaultHue();
}
$: if (hue || hue === 0) {
setHue(hue);
}
</script>
<div id="display-setting" class="float-panel float-panel-closed absolute transition-all w-80 right-4 px-4 py-4">
<div class="flex flex-row gap-2 mb-3 items-center justify-between">
<div class="flex gap-2 font-bold text-lg text-neutral-900 dark:text-neutral-100 transition relative ml-3
before:w-1 before:h-4 before:rounded-md before:bg-[var(--primary)]
before:absolute before:-left-3 before:top-[0.33rem]"
>
{i18n(I18nKey.themeColor)}
<button aria-label="Reset to Default" class="btn-regular w-7 h-7 rounded-md active:scale-90"
class:opacity-0={hue === defaultHue} class:pointer-events-none={hue === defaultHue} on:click={resetHue}>
<div class="text-[var(--btn-content)]">
<Icon icon="fa6-solid:arrow-rotate-left" class="text-[0.875rem]"></Icon>
</div>
</button>
</div>
<div class="flex gap-1">
<div id="hueValue" class="transition bg-[var(--btn-regular-bg)] w-10 h-7 rounded-md flex justify-center
font-bold text-sm items-center text-[var(--btn-content)]">
{hue}
</div>
</div>
</div>
<div class="w-full h-6 px-1 bg-[oklch(0.80_0.10_0)] dark:bg-[oklch(0.70_0.10_0)] rounded select-none">
<input aria-label={i18n(I18nKey.themeColor)} type="range" min="0" max="360" bind:value={hue}
class="slider" id="colorSlider" step="5" style="width: 100%">
</div>
</div>
<style lang="stylus">
#display-setting
input[type="range"]
-webkit-appearance none
height 1.5rem
background-image var(--color-selection-bar)
transition background-image 0.15s ease-in-out
/* Input Thumb */
&::-webkit-slider-thumb
-webkit-appearance none
height 1rem
width 0.5rem
border-radius 0.125rem
background rgba(255, 255, 255, 0.7)
box-shadow none
&:hover
background rgba(255, 255, 255, 0.8)
&:active
background rgba(255, 255, 255, 0.6)
&::-moz-range-thumb
-webkit-appearance none
height 1rem
width 0.5rem
border-radius 0.125rem
border-width 0
background rgba(255, 255, 255, 0.7)
box-shadow none
&:hover
background rgba(255, 255, 255, 0.8)
&:active
background rgba(255, 255, 255, 0.6)
&::-ms-thumb
-webkit-appearance none
height 1rem
width 0.5rem
border-radius 0.125rem
background rgba(255, 255, 255, 0.7)
box-shadow none
&:hover
background rgba(255, 255, 255, 0.8)
&:active
background rgba(255, 255, 255, 0.6)
</style>
+2 -2
View File
@@ -19,11 +19,11 @@ const links = Astro.props.links;
<div class="transition text-black/75 dark:text-white/75 font-bold group-hover:text-[var(--primary)] group-active:text-[var(--primary)]">
{link.name}
</div>
{!link.external && <Icon name="material-symbols:chevron-right-rounded"
{!link.external && <Icon is:inline name="material-symbols:chevron-right-rounded"
class="transition text-[1.25rem] text-[var(--primary)]"
>
</Icon>}
{link.external && <Icon name="fa6-solid:arrow-up-right-from-square"
{link.external && <Icon is:inline name="fa6-solid:arrow-up-right-from-square"
class="transition text-[0.75rem] text-black/25 dark:text-white/25 -translate-x-1"
>
</Icon>}
+5 -5
View File
@@ -19,7 +19,7 @@ const config = profileConfig;
max-w-[12rem] lg:max-w-none overflow-hidden rounded-xl active:scale-95">
<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="fa6-regular:address-card"
<Icon is:inline name="fa6-regular:address-card"
class="transition opacity-0 scale-90 group-hover:scale-100 group-hover:opacity-100 text-white text-5xl">
</Icon>
</div>
@@ -36,12 +36,12 @@ const config = profileConfig;
<div class="flex gap-2 justify-center mb-1">
{config.links.length > 1 && config.links.map(item =>
<a rel="me" aria-label={item.name} href={item.url} target="_blank" class="btn-regular rounded-lg h-10 w-10 active:scale-90">
<Icon name={item.icon} class="text-[1.5rem]"></Icon>
<Icon is:inline name={item.icon} class="text-[1.5rem]"></Icon>
</a>
)}
{config.links.length == 1 && <a rel="me" aria-label={config.links[0].name} href={config.links[0].url} target="_blank"
class="btn-regular rounded-lg h-10 gap-2 px-3 font-bold active:scale-95">
<Icon name={config.links[0].icon} class="text-[1.5rem]"></Icon>
<Icon is:inline name={config.links[0].icon} class="text-[1.5rem]"></Icon>
{config.links[0].name}
</a>}
</div>
@@ -51,7 +51,7 @@ const config = profileConfig;
<!-- 全站访问量统计 https://github.com/afoim/fuwari/blob/81f22decb17ff7ee1dd480c10773f7ba8f4df296/src/components/widget/Profile.astro -->
<div class="text-center text-sm text-neutral-500 dark:text-neutral-400 mt-3 pt-3 border-t border-neutral-200 dark:border-neutral-700">
<div class="flex items-center justify-center gap-1">
<Icon name="material-symbols:visibility-outline" class="text-base"></Icon>
<Icon is:inline name="material-symbols:visibility-outline" class="text-base"></Icon>
<span id="site-stats">加载中...</span>
</div>
</div>
@@ -59,7 +59,7 @@ const config = profileConfig;
<!-- 最新 Commit 提交信息 -->
<div class="text-center text-sm text-neutral-500 dark:text-neutral-400 mt-3 pt-3 border-t border-neutral-200 dark:border-neutral-700">
<div class="flex items-center justify-center gap-1">
<Icon name="ic:baseline-commit" class="text-base"></Icon>
<Icon is:inline name="ic:baseline-commit" class="text-base"></Icon>
<span><a id="github-commit-link" href="#">
<span id="github-commit">加载中...</span></a></span>
@@ -0,0 +1,349 @@
<script lang="ts">
import { DARK_MODE, LIGHT_MODE } from "@constants/constants.ts";
import I18nKey from "@i18n/i18nKey";
import { i18n } from "@i18n/translation";
import Icon from "@iconify/svelte";
import {
applyCustomThemeStyles,
applyThemeToDocument,
getCodeFontFamily,
getCustomCss,
getDefaultHue,
getDisplayFontFamily,
getHue,
getStoredTheme,
setCodeFontFamily,
setCustomCss,
setDisplayFontFamily,
setHue,
setTheme,
} from "@utils/setting-utils.ts";
import { onMount } from "svelte";
import type { LIGHT_DARK_MODE } from "@/types/config.ts";
export let showThemeColor = true;
let hue = getHue();
const defaultHue = getDefaultHue();
let mode: LIGHT_DARK_MODE = LIGHT_MODE;
let customCss = "";
let displayFontFamily = "";
let codeFontFamily = "";
let customThemeReady = false;
onMount(() => {
mode = getStoredTheme();
customCss = getCustomCss();
displayFontFamily = getDisplayFontFamily();
codeFontFamily = getCodeFontFamily();
applyThemeToDocument(mode);
applyCustomThemeStyles();
customThemeReady = true;
});
function resetHue() {
hue = getDefaultHue();
}
function switchScheme(newMode: LIGHT_DARK_MODE) {
mode = newMode;
setTheme(newMode);
}
function togglePanel() {
document
.getElementById("display-setting")
?.classList.toggle("float-panel-closed");
}
function resetDisplayFontFamily() {
displayFontFamily = "";
}
function resetCodeFontFamily() {
codeFontFamily = "";
}
function resetCustomCss() {
customCss = "";
}
$: if (hue || hue === 0) {
setHue(hue);
}
$: if (customThemeReady) {
setCustomCss(customCss);
}
$: if (customThemeReady) {
setDisplayFontFamily(displayFontFamily);
}
$: if (customThemeReady) {
setCodeFontFamily(codeFontFamily);
}
</script>
<div>
<button
aria-label="主题设置"
class="relative btn-plain scale-animation rounded-lg h-11 w-11 active:scale-90"
id="display-settings-switch"
onclick={togglePanel}
>
<svg
aria-hidden="true"
class="h-[1.35rem] w-[1.35rem]"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M4 7h5" class="stroke-current" stroke-width="1.8" stroke-linecap="round" />
<path d="M15 7h5" class="stroke-current" stroke-width="1.8" stroke-linecap="round" />
<circle cx="12" cy="7" r="2.4" class="stroke-current" stroke-width="1.8" />
<path d="M4 17h5" class="stroke-current" stroke-width="1.8" stroke-linecap="round" />
<path d="M15 17h5" class="stroke-current" stroke-width="1.8" stroke-linecap="round" />
<circle cx="12" cy="17" r="2.4" class="stroke-current" stroke-width="1.8" />
<path d="M4 12h9" class="stroke-current opacity-70" stroke-width="1.8" stroke-linecap="round" />
<path d="M17 12h3" class="stroke-current opacity-70" stroke-width="1.8" stroke-linecap="round" />
<circle cx="15" cy="12" r="1.8" class="fill-current" />
</svg>
</button>
<div id="display-setting" class="float-panel float-panel-closed absolute transition-all z-50 w-[24rem] max-h-[calc(100vh_-_6rem)] overflow-y-auto right-4 px-4 py-4">
<div class="grid grid-cols-2 gap-2 mb-4">
<button
class="flex transition whitespace-nowrap items-center !justify-start w-full btn-plain scale-animation rounded-lg h-9 px-3 font-medium active:scale-95"
class:current-theme-btn={mode === LIGHT_MODE}
onclick={() => switchScheme(LIGHT_MODE)}
>
<Icon icon="material-symbols:wb-sunny-outline-rounded" class="text-[1.25rem] mr-2"></Icon>
{i18n(I18nKey.lightMode)}
</button>
<button
class="flex transition whitespace-nowrap items-center !justify-start w-full btn-plain scale-animation rounded-lg h-9 px-3 font-medium active:scale-95"
class:current-theme-btn={mode === DARK_MODE}
onclick={() => switchScheme(DARK_MODE)}
>
<Icon icon="material-symbols:dark-mode-outline-rounded" class="text-[1.25rem] mr-2"></Icon>
{i18n(I18nKey.darkMode)}
</button>
</div>
{#if showThemeColor}
<div class="flex flex-row gap-2 mb-3 items-center justify-between">
<div
class="select-none flex gap-2 font-bold text-lg text-neutral-900 dark:text-neutral-100 transition relative ml-3 before:w-1 before:h-4 before:rounded-md before:bg-[var(--primary)] before:absolute before:-left-3 before:top-[0.33rem]"
>
{i18n(I18nKey.themeColor)}
<button
aria-label="Reset to Default"
class="btn-regular w-7 h-7 rounded-md active:scale-90"
class:opacity-0={hue === defaultHue}
class:pointer-events-none={hue === defaultHue}
onclick={resetHue}
>
<div class="text-[var(--btn-content)]">
<Icon icon="fa6-solid:arrow-rotate-left" class="text-[0.875rem]"></Icon>
</div>
</button>
</div>
<div class="flex gap-1">
<div
id="hueValue"
class="transition bg-[var(--btn-regular-bg)] w-10 h-7 rounded-md flex justify-center font-bold text-sm items-center text-[var(--btn-content)]"
>
{hue}
</div>
</div>
</div>
<div class="w-full h-6 px-1 bg-[oklch(0.80_0.10_0)] dark:bg-[oklch(0.70_0.10_0)] rounded select-none">
<input
aria-label={i18n(I18nKey.themeColor)}
type="range"
min="0"
max="360"
bind:value={hue}
class="slider"
id="colorSlider"
step="5"
style="width: 100%"
/>
</div>
{/if}
<div class="mt-5 border-t border-black/10 pt-4 dark:border-white/10">
<div
class="pointer-events-none relative mb-3 ml-3 select-none text-lg font-bold text-neutral-900 transition before:absolute before:-left-3 before:top-[0.33rem] before:h-4 before:w-1 before:rounded-md before:bg-[var(--primary)] dark:text-neutral-100"
>
字体系列
</div>
<label class="mb-3 block">
<span class="mb-1 block text-sm font-medium text-black/60 transition dark:text-white/60">显示字体</span>
<div class="flex w-full gap-2">
<input
aria-label="显示字体"
class="theme-setting-input min-w-0 flex-1"
bind:value={displayFontFamily}
placeholder='"MiSans VF", Inter, sans-serif'
/>
<button
aria-label="重置显示字体"
class="btn-regular h-9 w-9 shrink-0 rounded-md active:scale-90"
class:opacity-0={!displayFontFamily}
class:pointer-events-none={!displayFontFamily}
onclick={resetDisplayFontFamily}
>
<Icon icon="fa6-solid:arrow-rotate-left" class="text-[0.875rem]"></Icon>
</button>
</div>
</label>
<label class="block">
<span class="mb-1 block text-sm font-medium text-black/60 transition dark:text-white/60">代码字体</span>
<div class="flex w-full gap-2">
<input
aria-label="代码字体"
class="theme-setting-input min-w-0 flex-1"
bind:value={codeFontFamily}
placeholder='"Cascadia Mono", "JetBrains Mono", monospace'
/>
<button
aria-label="重置代码字体"
class="btn-regular h-9 w-9 shrink-0 rounded-md active:scale-90"
class:opacity-0={!codeFontFamily}
class:pointer-events-none={!codeFontFamily}
onclick={resetCodeFontFamily}
>
<Icon icon="fa6-solid:arrow-rotate-left" class="text-[0.875rem]"></Icon>
</button>
</div>
</label>
</div>
<div class="mt-5 border-t border-black/10 pt-4 dark:border-white/10">
<div class="mb-2 flex items-center justify-between">
<div
class="pointer-events-none relative ml-3 select-none text-lg font-bold text-neutral-900 transition before:absolute before:-left-3 before:top-[0.33rem] before:h-4 before:w-1 before:rounded-md before:bg-[var(--primary)] dark:text-neutral-100"
>
自定义 CSS
</div>
<button
aria-label="重置自定义 CSS"
class="btn-regular h-7 w-7 rounded-md active:scale-90"
class:opacity-0={!customCss}
class:pointer-events-none={!customCss}
onclick={resetCustomCss}
>
<Icon icon="fa6-solid:arrow-rotate-left" class="text-[0.875rem]"></Icon>
</button>
</div>
<textarea
aria-label="自定义 CSS"
class="theme-setting-textarea"
bind:value={customCss}
spellcheck="false"
placeholder={":root { --radius-large: 1.5rem; }"}
></textarea>
<div class="mt-2 text-xs text-black/40 transition dark:text-white/40">
字体系列 会在 自定义 CSS 之后注入,所以字体设置优先级更高。
</div>
</div>
</div>
</div>
<style>
.theme-setting-input,
.theme-setting-textarea {
width: 100%;
border-radius: 0.5rem;
background: var(--btn-regular-bg);
color: var(--btn-content);
outline: none;
transition: background-color 0.15s ease-in-out;
}
.theme-setting-input {
height: 2.25rem;
padding: 0 0.75rem;
font-size: 0.875rem;
}
.theme-setting-textarea {
min-height: 8rem;
resize: vertical;
padding: 0.75rem;
font-family: "Cascadia Mono", "JetBrainsMono-VF", SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 0.8125rem;
line-height: 1.5;
}
.theme-setting-input:hover,
.theme-setting-textarea:hover {
background: var(--btn-regular-bg-hover);
}
.theme-setting-input:focus,
.theme-setting-textarea:focus {
background: var(--btn-regular-bg-active);
}
#display-setting input[type="range"] {
-webkit-appearance: none;
height: 1.5rem;
background-image: var(--color-selection-bar);
transition: background-image 0.15s ease-in-out;
}
#display-setting input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
height: 1rem;
width: 0.5rem;
border-radius: 0.125rem;
background: rgba(255, 255, 255, 0.7);
box-shadow: none;
}
#display-setting input[type="range"]::-webkit-slider-thumb:hover {
background: rgba(255, 255, 255, 0.8);
}
#display-setting input[type="range"]::-webkit-slider-thumb:active {
background: rgba(255, 255, 255, 0.6);
}
#display-setting input[type="range"]::-moz-range-thumb {
-webkit-appearance: none;
height: 1rem;
width: 0.5rem;
border-radius: 0.125rem;
border-width: 0;
background: rgba(255, 255, 255, 0.7);
box-shadow: none;
}
#display-setting input[type="range"]::-moz-range-thumb:hover {
background: rgba(255, 255, 255, 0.8);
}
#display-setting input[type="range"]::-moz-range-thumb:active {
background: rgba(255, 255, 255, 0.6);
}
#display-setting input[type="range"]::-ms-thumb {
-webkit-appearance: none;
height: 1rem;
width: 0.5rem;
border-radius: 0.125rem;
background: rgba(255, 255, 255, 0.7);
box-shadow: none;
}
#display-setting input[type="range"]::-ms-thumb:hover {
background: rgba(255, 255, 255, 0.8);
}
#display-setting input[type="range"]::-ms-thumb:active {
background: rgba(255, 255, 255, 0.6);
}
</style>
+2 -2
View File
@@ -1,15 +1,15 @@
---
import { Icon } from "astro-icon/components";
import I18nKey from "@i18n/i18nKey";
import { i18n } from "@i18n/translation";
import { getTotalWords } from "@utils/content-utils";
import { Icon } from "astro-icon/components";
const totalWords = await getTotalWords();
const formattedTotalWords = totalWords.toLocaleString("zh-CN");
---
<div class="text-center text-sm text-neutral-500 dark:text-neutral-400 mt-3 pt-3 border-t border-neutral-200 dark:border-neutral-700">
<div class="flex items-center justify-center gap-1">
<Icon name="material-symbols:article-outline" class="text-base"></Icon>
<Icon is:inline name="material-symbols:article-outline" class="text-base"></Icon>
<span>总计 {formattedTotalWords} {i18n(I18nKey.wordsCount)}</span>
</div>
</div>
+1 -1
View File
@@ -24,7 +24,7 @@ const className = Astro.props.class;
{isCollapsed && <div class="expand-btn px-4 -mb-2">
<button class="btn-plain rounded-lg w-full h-9">
<div class="text-[var(--primary)] flex items-center justify-center gap-2 -translate-x-2">
<Icon name="material-symbols:more-horiz" class="text-[1.75rem]"></Icon> {i18n(I18nKey.more)}
<Icon is:inline name="material-symbols:more-horiz" class="text-[1.75rem]"></Icon> {i18n(I18nKey.more)}
</div>
</button>
</div>}
+1
View File
@@ -11,6 +11,7 @@ import { LinkPreset } from "./types/config";
export const siteConfig: SiteConfig = {
title: "Ad_closeNN 的小站",
subtitle: "Ad_closeNN の 小站,时不时会刷新一些野生东西 | ✨ 欢迎友链 ✨",
githubRepo: "https://github.com/Ad-closeNN/blog-fuwari",
lang: "zh_CN", // Language code, e.g. 'en', 'zh-CN', 'ja', etc.
themeColor: {
hue: 160, // Default hue for the theme color, from 0 to 360. e.g. red: 0, teal: 200, cyan: 250, pink: 345
+3 -2
View File
@@ -276,7 +276,7 @@ const bannerOffset =
<ConfigCarrier></ConfigCarrier>
<div id="dns-warning-banner" class="hidden sticky top-0 z-[100] w-full bg-amber-50/90 dark:bg-amber-950/80 backdrop-blur-md border-b border-amber-200/60 dark:border-amber-700/40 text-amber-900 dark:text-amber-100 px-4 py-3 text-sm text-center">
<div class="select-none flex items-center justify-center gap-2 max-w-[var(--page-width)] mx-auto">
<Icon name="material-symbols:info-outline-rounded" class="pointer-events-none shrink-0 text-lg" aria-hidden="true"></Icon>
<Icon is:inline name="material-symbols:info-outline-rounded" class="pointer-events-none shrink-0 text-lg" aria-hidden="true"></Icon>
<span>本站近期遭到反诈部门 DNS 污染,可能无法正常访问。如有需要可开启代理或使用国外 DNS 服务器。</span>
<button id="dns-warning-close" class="select-auto shrink-0 btn-plain scale-animation rounded-lg w-7 h-7 flex items-center justify-center text-amber-600 dark:text-amber-400 hover:text-amber-800 dark:hover:text-amber-200 active:scale-90" aria-label="Close notice">
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M18 6 6 18"></path><path d="m6 6 12 12"></path></svg>
@@ -347,7 +347,7 @@ import {
// SizeObserverPlugin,
// ClickScrollPlugin
} from 'overlayscrollbars';
import {getHue, getStoredTheme, setHue, setTheme} from "../utils/setting-utils";
import {applyCustomThemeStyles, getHue, getStoredTheme, setHue, setTheme} from "../utils/setting-utils";
import {pathsEqual, url} from "../utils/url-utils";
import {
BANNER_HEIGHT,
@@ -519,6 +519,7 @@ function init() {
// disableAnimation()() // TODO
loadTheme();
loadHue();
applyCustomThemeStyles();
initCustomScrollbar();
showBanner();
}
+2 -2
View File
@@ -71,9 +71,9 @@ const mainPanelTop = siteConfig.banner.enable
class:list={["group onload-animation transition-all absolute flex justify-center items-center rounded-full " +
"px-3 right-4 -top-[3.25rem] bg-black/60 hover:bg-black/70 h-9", {"hover:pr-9 active:bg-black/80": hasBannerLink}]}
>
<Icon class="text-white/75 text-[1.25rem] mr-1" name="material-symbols:copyright-outline-rounded" ></Icon>
<Icon is:inline class="text-white/75 text-[1.25rem] mr-1" name="material-symbols:copyright-outline-rounded" ></Icon>
<div class="text-white/75 text-xs">{siteConfig.banner.credit.text}</div>
<Icon class:list={["transition absolute text-[oklch(0.75_0.14_var(--hue))] right-4 text-[0.75rem] opacity-0",
<Icon is:inline class:list={["transition absolute text-[oklch(0.75_0.14_var(--hue))] right-4 text-[0.75rem] opacity-0",
{"group-hover:opacity-100": hasBannerLink}]}
name="fa6-solid:arrow-up-right-from-square">
</Icon>
+12 -8
View File
@@ -5,16 +5,20 @@ import MarkdownIt from "markdown-it";
const markdown = new MarkdownIt();
const siteNameHtml = markdown.renderInline("`Ad_closeNN 的小站`");
const siteDescriptionHtml = markdown.renderInline("`永远相信美好的事情即将发生`");
const siteDescriptionHtml = markdown.renderInline(
"`永远相信美好的事情即将发生`",
);
const siteUrlHtml = markdown.renderInline("`https://blog.adclosenn.top`");
const siteAvatarHtml = markdown.renderInline("`https://static.adclosenn.top/icon/avatar.jpg`");
const siteAvatarHtml = markdown.renderInline(
"`https://static.adclosenn.top/icon/avatar.jpg`",
);
---
<MainGridLayout title="友情链接">
<div class="card-base p-6 md:p-8">
<div class="flex items-center gap-2 mb-6">
<div class="h-8 w-8 rounded-lg bg-[var(--primary)] flex items-center justify-center text-white dark:text-black/70">
<Icon name="material-symbols:diversity-3" class="text-[1.5rem]">
<Icon is:inline name="material-symbols:diversity-3" class="text-[1.5rem]" />
</div>
<h1 class="text-2xl font-bold text-black dark:text-white">友情链接</h1>
</div>
@@ -245,11 +249,11 @@ const siteAvatarHtml = markdown.renderInline("`https://static.adclosenn.top/icon
<!-- 申请友链 -->
<div class="sponsors-section mt-8">
<h2 class="text-xl font-bold text-black dark:text-white mb-4 flex items-center gap-2">
<Icon name="material-symbols:group" class="text-[1.5rem] text-[var(--primary)]"/>
将您的网站加入本站友链板块
</h2>
<p class="text-sm text-black/60 dark:text-white/60">
<h2 class="text-xl font-bold text-black dark:text-white mb-4 flex items-center gap-2">
<Icon is:inline name="material-symbols:group" class="text-[1.5rem] text-[var(--primary)]" />
将您的网站加入本站友链板块
</h2>
<p class="text-sm text-black/60 dark:text-white/60">
请自行提交 GitHub Issue <a target="_blank" href="https://github.com/Ad-closeNN/form/issues/new?template=friends-link.yml" class="transition link text-[var(--primary)] font-medium">点击这里提交</a>
</p>
<div class="mt-4 h-px bg-black/10 dark:bg-white/10"></div>
+63 -14
View File
@@ -1,6 +1,6 @@
---
import { type CollectionEntry, render } from "astro:content";
import path from "node:path";
import { render, type CollectionEntry } from "astro:content";
import License from "@components/misc/License.astro";
import Markdown from "@components/misc/Markdown.astro";
import I18nKey from "@i18n/i18nKey";
@@ -40,9 +40,35 @@ const postSources = import.meta.glob("../../content/posts/**/*.{md,mdx}", {
eager: true,
}) as Record<string, string>;
const normalizedPostSources = Object.fromEntries(
Object.entries(postSources).map(([key, source]) => [key.toLowerCase(), source]),
Object.entries(postSources).map(([key, source]) => [
key.toLowerCase(),
source,
]),
);
function getPostSourcePath(entryId: string) {
const normalizedEntryPath = `../../content/posts/${entryId}`.toLowerCase();
const sourceKey = Object.keys(postSources).find((key) => {
const normalizedKey = key.toLowerCase();
return (
normalizedKey === normalizedEntryPath ||
normalizedKey === `${normalizedEntryPath}.md` ||
normalizedKey === `${normalizedEntryPath}.mdx`
);
});
return sourceKey?.replace("../../content/posts/", "src/content/posts/");
}
function getGitHubPostSourceUrl(repo: string | undefined, sourcePath: string | undefined) {
if (!repo || !sourcePath) return "";
const repoUrl = repo.includes("://") ? repo : `https://github.com/${repo}`;
const normalizedRepo = repoUrl.replace(/\.git$/, "").replace(/\/+$/, "");
const encodedSourcePath = sourcePath.split("/").map(encodeURIComponent).join("/");
return `${normalizedRepo}/blob/main/${encodedSourcePath}?plain=1`;
}
function parseAiSummaryValue(value: string) {
const trimmed = value.trim();
if (!trimmed) return "";
@@ -65,7 +91,9 @@ function extractFrontmatterValue(frontmatter: string, key: string) {
return parseAiSummaryValue(inlineValue);
}
const afterValue = frontmatter.slice(valueMatch.index! + valueMatch[0].length);
const afterValue = frontmatter.slice(
valueMatch.index! + valueMatch[0].length,
);
const lines = afterValue.split(/\r?\n/);
const blockLines = [];
for (const line of lines) {
@@ -82,12 +110,20 @@ function getAiSummaryMeta(entryId: string) {
normalizedPostSources[`../../content/posts/${entryId}.mdx`.toLowerCase()];
const frontmatter = source?.match(/^---\r?\n([\s\S]*?)\r?\n---/)?.[1];
return {
summary: frontmatter ? extractFrontmatterValue(frontmatter, "aiSummary") : "",
model: frontmatter ? extractFrontmatterValue(frontmatter, "aiSummaryModel") : "",
summary: frontmatter
? extractFrontmatterValue(frontmatter, "aiSummary")
: "",
model: frontmatter
? extractFrontmatterValue(frontmatter, "aiSummaryModel")
: "",
};
}
const aiSummaryMeta = getAiSummaryMeta(entry.id);
const postSourceUrl = getGitHubPostSourceUrl(
siteConfig.githubRepo,
getPostSourcePath(entry.id),
);
const jsonLd = {
"@context": "https://schema.org",
@@ -113,7 +149,6 @@ const showcover = entry.data.showcover;
// 获取自定义的头图(Markdown 内部)
const customcover = entry.data.customcover;
const isOutdated = entry.data.outdated;
---
<MainGridLayout banner={entry.data.image} title={entry.data.title} description={entry.data.description} lang={entry.data.lang} setOGTypeArticle={true} headings={headings}>
<script is:inline slot="head" type="application/ld+json" set:html={JSON.stringify(jsonLd)}></script>
@@ -126,13 +161,13 @@ const isOutdated = entry.data.outdated;
<div class="flex flex-row flex-wrap text-black/30 dark:text-white/30 gap-5 transition">
<div class="flex flex-row items-center pointer-events-none select-none cursor-default">
<div class="transition h-6 w-6 rounded-md bg-black/5 dark:bg-white/10 text-black/50 dark:text-white/50 flex items-center justify-center mr-2">
<Icon name="material-symbols:notes-rounded"></Icon>
<Icon is:inline name="material-symbols:notes-rounded"></Icon>
</div>
<div class="text-sm">{remarkPluginFrontmatter.words} {" " + i18n(I18nKey.wordsCount)}</div>
</div>
<div class="flex flex-row items-center pointer-events-none select-none cursor-default">
<div class="transition h-6 w-6 rounded-md bg-black/5 dark:bg-white/10 text-black/50 dark:text-white/50 flex items-center justify-center mr-2">
<Icon name="material-symbols:schedule-outline-rounded"></Icon>
<Icon is:inline name="material-symbols:schedule-outline-rounded"></Icon>
</div>
<div class="text-sm">
大约 {remarkPluginFrontmatter.minutes} {" " + i18n(remarkPluginFrontmatter.minutes === 1 ? I18nKey.minuteCount : I18nKey.minutesCount)}
@@ -140,12 +175,25 @@ const isOutdated = entry.data.outdated;
</div>
<div class="flex flex-row items-center pointer-events-none select-none cursor-default">
<div class="transition h-6 w-6 rounded-md bg-black/5 dark:bg-white/10 text-black/50 dark:text-white/50 flex items-center justify-center mr-2">
<Icon name="material-symbols:visibility-outline-rounded"></Icon>
<Icon is:inline name="material-symbols:visibility-outline-rounded"></Icon>
</div>
<div class="text-sm" id="post-top-page-views">加载中...</div>
</div>
</div>
<div class="flex items-center justify-end gap-2 text-black/40 dark:text-white/40 md:shrink-0">
{postSourceUrl && (
<a
href={postSourceUrl}
aria-label="查看文章源代码"
title="查看文章源代码"
target="_blank"
rel="noopener noreferrer"
class="btn-card flex h-9 items-center gap-2 rounded-xl px-3 font-medium active:scale-95"
>
<Icon is:inline name="fa6-brands:github" class="text-[1rem] text-[var(--primary)]"></Icon>
<span class="hidden text-sm text-black/75 dark:text-white/75 sm:inline">查看源代码</span>
</a>
)}
<a
href={entry.data.nextSlug ? getPostUrlBySlug(entry.data.nextSlug) : "#"}
aria-label={entry.data.nextTitle ? `上一篇:${entry.data.nextTitle}` : "没有上一篇"}
@@ -155,7 +203,7 @@ const isOutdated = entry.data.outdated;
{"pointer-events-none opacity-40": !entry.data.nextSlug},
]}
>
<Icon name="material-symbols:chevron-left-rounded" class="text-[1.5rem] text-[var(--primary)]"></Icon>
<Icon is:inline name="material-symbols:chevron-left-rounded" class="text-[1.5rem] text-[var(--primary)]"></Icon>
</a>
<a
href={entry.data.prevSlug ? getPostUrlBySlug(entry.data.prevSlug) : "#"}
@@ -166,7 +214,7 @@ const isOutdated = entry.data.outdated;
{"pointer-events-none opacity-40": !entry.data.prevSlug},
]}
>
<Icon name="material-symbols:chevron-right-rounded" class="text-[1.5rem] text-[var(--primary)]"></Icon>
<Icon is:inline name="material-symbols:chevron-right-rounded" class="text-[1.5rem] text-[var(--primary)]"></Icon>
</a>
</div>
</div>
@@ -204,6 +252,7 @@ const isOutdated = entry.data.outdated;
]}>
<div class="flex items-start gap-2">
<Icon
is:inline
name={isOutdated ? "material-symbols:warning-outline-rounded" : "material-symbols:info-outline-rounded"}
class="mt-0.5 shrink-0 text-lg"
></Icon>
@@ -236,7 +285,7 @@ const isOutdated = entry.data.outdated;
<div class="mb-4 rounded-xl p-4 bg-gradient-to-r from-violet-500/10 to-indigo-500/10 dark:from-violet-500/15 dark:to-indigo-500/15 border border-violet-500/20 onload-animation">
<div class="flex items-start gap-3">
<div class="shrink-0 w-8 h-8 rounded-lg bg-violet-500/20 dark:bg-violet-500/30 flex items-center justify-center">
<Icon name="material-symbols:info-outline-rounded" class="text-violet-600 dark:text-violet-400 text-lg"></Icon>
<Icon is:inline name="material-symbols:info-outline-rounded" class="text-violet-600 dark:text-violet-400 text-lg"></Icon>
</div>
<div class="flex-1 min-w-0">
<div class="text-sm font-medium text-violet-700 dark:text-violet-300 mb-1 select-none pointer-events-none">
@@ -279,7 +328,7 @@ const isOutdated = entry.data.outdated;
<a href={entry.data.nextSlug ? getPostUrlBySlug(entry.data.nextSlug) : "#"}
class:list={["w-full font-bold overflow-hidden active:scale-95", {"pointer-events-none": !entry.data.nextSlug}]}>
{entry.data.nextSlug && <div class="btn-card rounded-2xl w-full h-[3.75rem] max-w-full px-4 flex items-center !justify-start gap-4" >
<Icon name="material-symbols:chevron-left-rounded" class="text-[2rem] text-[var(--primary)]" />
<Icon is:inline name="material-symbols:chevron-left-rounded" class="text-[2rem] text-[var(--primary)]" />
<div class="overflow-hidden transition overflow-ellipsis whitespace-nowrap max-w-[calc(100%_-_3rem)] text-black/75 dark:text-white/75">
{entry.data.nextTitle}
</div>
@@ -292,7 +341,7 @@ const isOutdated = entry.data.outdated;
<div class="overflow-hidden transition overflow-ellipsis whitespace-nowrap max-w-[calc(100%_-_3rem)] text-black/75 dark:text-white/75">
{entry.data.prevTitle}
</div>
<Icon name="material-symbols:chevron-right-rounded" class="text-[2rem] text-[var(--primary)]" />
<Icon is:inline name="material-symbols:chevron-right-rounded" class="text-[2rem] text-[var(--primary)]" />
</div>}
</a>
</div>
+1
View File
@@ -3,6 +3,7 @@ import type { AUTO_MODE, DARK_MODE, LIGHT_MODE } from "@constants/constants";
export type SiteConfig = {
title: string;
subtitle: string;
githubRepo?: string;
lang:
| "en"
+74 -6
View File
@@ -1,9 +1,4 @@
import {
AUTO_MODE,
DARK_MODE,
DEFAULT_THEME,
LIGHT_MODE,
} from "@constants/constants.ts";
import { DARK_MODE, DEFAULT_THEME, LIGHT_MODE } from "@constants/constants.ts";
import { expressiveCodeConfig } from "@/config";
import type { LIGHT_DARK_MODE } from "@/types/config";
@@ -52,3 +47,76 @@ export function setTheme(theme: LIGHT_DARK_MODE): void {
export function getStoredTheme(): LIGHT_DARK_MODE {
return (localStorage.getItem("theme") as LIGHT_DARK_MODE) || DEFAULT_THEME;
}
const CUSTOM_CSS_STORAGE_KEY = "custom-css";
const DISPLAY_FONT_STORAGE_KEY = "display-font-family";
const CODE_FONT_STORAGE_KEY = "code-font-family";
const CUSTOM_CSS_STYLE_ID = "custom-theme-css";
const CUSTOM_FONT_STYLE_ID = "custom-theme-font";
function getOrCreateStyleElement(id: string): HTMLStyleElement {
let style = document.getElementById(id) as HTMLStyleElement | null;
if (!style) {
style = document.createElement("style");
style.id = id;
document.head.appendChild(style);
}
return style;
}
function normalizeFontFamily(fontFamily: string): string {
return fontFamily.replace(/[{};]/g, "").trim();
}
export function getCustomCss(): string {
return localStorage.getItem(CUSTOM_CSS_STORAGE_KEY) || "";
}
export function setCustomCss(css: string): void {
localStorage.setItem(CUSTOM_CSS_STORAGE_KEY, css);
applyCustomThemeStyles();
}
export function getDisplayFontFamily(): string {
return localStorage.getItem(DISPLAY_FONT_STORAGE_KEY) || "";
}
export function setDisplayFontFamily(fontFamily: string): void {
localStorage.setItem(
DISPLAY_FONT_STORAGE_KEY,
normalizeFontFamily(fontFamily),
);
applyCustomThemeStyles();
}
export function getCodeFontFamily(): string {
return localStorage.getItem(CODE_FONT_STORAGE_KEY) || "";
}
export function setCodeFontFamily(fontFamily: string): void {
localStorage.setItem(CODE_FONT_STORAGE_KEY, normalizeFontFamily(fontFamily));
applyCustomThemeStyles();
}
export function applyCustomThemeStyles(): void {
getOrCreateStyleElement(CUSTOM_CSS_STYLE_ID).textContent = getCustomCss();
const displayFont = normalizeFontFamily(getDisplayFontFamily());
const codeFont = normalizeFontFamily(getCodeFontFamily());
const fontRules: string[] = [];
if (displayFont) {
fontRules.push(
`html, body, button, input, textarea, select { font-family: ${displayFont} !important; }`,
);
}
if (codeFont) {
fontRules.push(
`code, pre, kbd, samp, .expressive-code, .expressive-code * { font-family: ${codeFont} !important; }`,
);
}
getOrCreateStyleElement(CUSTOM_FONT_STYLE_ID).textContent =
fontRules.join("\n");
}