mirror of
https://github.com/Ad-closeNN/blog-fuwari.git
synced 2026-05-31 01:20:06 -04:00
feat(theme): 主题相关合并一起、查看源代码按钮
This commit is contained in:
@@ -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>
|
|
||||||
@@ -4,10 +4,9 @@ import { navBarConfig, siteConfig } from "../config";
|
|||||||
import { LinkPresets } from "../constants/link-presets";
|
import { LinkPresets } from "../constants/link-presets";
|
||||||
import { LinkPreset, type NavBarLink } from "../types/config";
|
import { LinkPreset, type NavBarLink } from "../types/config";
|
||||||
import { url } from "../utils/url-utils";
|
import { url } from "../utils/url-utils";
|
||||||
import LightDarkSwitch from "./LightDarkSwitch.svelte";
|
|
||||||
import Search from "./Search.svelte";
|
import Search from "./Search.svelte";
|
||||||
import DisplaySettings from "./widget/DisplaySettings.svelte";
|
|
||||||
import NavMenuPanel from "./widget/NavMenuPanel.astro";
|
import NavMenuPanel from "./widget/NavMenuPanel.astro";
|
||||||
|
import ThemeSettingsBlock from "./widget/ThemeSettingsBlock.svelte";
|
||||||
|
|
||||||
const className = Astro.props.class;
|
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>
|
<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">
|
<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">
|
<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}
|
{siteConfig.title}
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
@@ -39,7 +38,7 @@ let links: NavBarLink[] = navBarConfig.links.map(
|
|||||||
>
|
>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
{l.name}
|
{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>
|
</div>
|
||||||
</a>;
|
</a>;
|
||||||
})}
|
})}
|
||||||
@@ -47,50 +46,17 @@ let links: NavBarLink[] = navBarConfig.links.map(
|
|||||||
<div class="flex">
|
<div class="flex">
|
||||||
<!--<SearchPanel client:load>-->
|
<!--<SearchPanel client:load>-->
|
||||||
<Search client:only="svelte"></Search>
|
<Search client:only="svelte"></Search>
|
||||||
{!siteConfig.themeColor.fixed && (
|
<ThemeSettingsBlock showThemeColor={!siteConfig.themeColor.fixed} client:only="svelte"></ThemeSettingsBlock>
|
||||||
<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>
|
|
||||||
<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">
|
<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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<NavMenuPanel links={links}></NavMenuPanel>
|
<NavMenuPanel links={links}></NavMenuPanel>
|
||||||
<DisplaySettings client:only="svelte"></DisplaySettings>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<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() {
|
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");
|
let menuBtn = document.getElementById("nav-menu-switch");
|
||||||
if (menuBtn) {
|
if (menuBtn) {
|
||||||
menuBtn.onclick = function () {
|
menuBtn.onclick = function () {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
import type { CollectionEntry } from "astro:content";
|
import type { CollectionEntry } from "astro:content";
|
||||||
|
import { render } from "astro:content";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { Icon } from "astro-icon/components";
|
import { Icon } from "astro-icon/components";
|
||||||
import I18nKey from "../i18n/i18nKey";
|
import I18nKey from "../i18n/i18nKey";
|
||||||
@@ -7,7 +8,6 @@ import { i18n } from "../i18n/translation";
|
|||||||
import { getDir } from "../utils/url-utils";
|
import { getDir } from "../utils/url-utils";
|
||||||
import ImageWrapper from "./misc/ImageWrapper.astro";
|
import ImageWrapper from "./misc/ImageWrapper.astro";
|
||||||
import PostMetadata from "./PostMeta.astro";
|
import PostMetadata from "./PostMeta.astro";
|
||||||
import { render } from 'astro:content';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
class?: string;
|
class?: string;
|
||||||
@@ -57,15 +57,15 @@ const { remarkPluginFrontmatter } = await render(entry);
|
|||||||
<span class="flex flex-wrap items-center gap-x-3 gap-y-2">
|
<span class="flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||||
{isPinned && (
|
{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">
|
<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>
|
||||||
)}
|
)}
|
||||||
<span class="min-w-0">
|
<span class="min-w-0">
|
||||||
{title}
|
{title}
|
||||||
<span class="inline-flex items-center align-middle whitespace-nowrap">
|
<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 is:inline 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="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>
|
</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-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 ">
|
<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">
|
class="transition opacity-0 group-hover:opacity-100 scale-50 group-hover:scale-100 text-white text-5xl">
|
||||||
</Icon>
|
</Icon>
|
||||||
</div>
|
</div>
|
||||||
@@ -119,7 +119,7 @@ const { remarkPluginFrontmatter } = await render(entry);
|
|||||||
absolute right-3 top-3 bottom-3 rounded-xl bg-[var(--enter-btn-bg)]
|
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
|
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">
|
class="transition text-[var(--primary)] text-4xl mx-auto">
|
||||||
</Icon>
|
</Icon>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Icon } from "astro-icon/components";
|
|||||||
import I18nKey from "../i18n/i18nKey";
|
import I18nKey from "../i18n/i18nKey";
|
||||||
import { i18n } from "../i18n/translation";
|
import { i18n } from "../i18n/translation";
|
||||||
import { formatDateToYYYYMMDD } from "../utils/date-utils";
|
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 {
|
interface Props {
|
||||||
class: string;
|
class: string;
|
||||||
@@ -33,7 +33,7 @@ const className = Astro.props.class;
|
|||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="meta-icon"
|
<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>
|
</div>
|
||||||
<span class="text-50 text-sm font-medium">{formatDateToYYYYMMDD(published)}</span>
|
<span class="text-50 text-sm font-medium">{formatDateToYYYYMMDD(published)}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -43,7 +43,7 @@ const className = Astro.props.class;
|
|||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="meta-icon"
|
<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>
|
</div>
|
||||||
<span class="text-50 text-sm font-medium">{formatDateToYYYYMMDD(updated)}</span>
|
<span class="text-50 text-sm font-medium">{formatDateToYYYYMMDD(updated)}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -54,7 +54,7 @@ const className = Astro.props.class;
|
|||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="meta-icon"
|
<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>
|
||||||
<div class="flex flex-row flex-nowrap items-center">
|
<div class="flex flex-row flex-nowrap items-center">
|
||||||
<a href={getCategoryUrl(category)} aria-label={`View all posts in the ${category} category`}
|
<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:list={["items-center", {"flex": !hideTagsForMobile, "hidden md:flex": hideTagsForMobile}]}>
|
||||||
<div class="meta-icon"
|
<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>
|
||||||
<div class="flex flex-row flex-nowrap items-center">
|
<div class="flex flex-row flex-nowrap items-center">
|
||||||
{(tags && tags.length > 0) && tags.map((tag, i) => (
|
{(tags && tags.length > 0) && tags.map((tag, i) => (
|
||||||
@@ -87,7 +87,7 @@ const className = Astro.props.class;
|
|||||||
{slug && (
|
{slug && (
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="meta-icon">
|
<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>
|
</div>
|
||||||
<span class="text-50 text-sm font-medium" id="page-views-display">加载中...</span>
|
<span class="text-50 text-sm font-medium" id="page-views-display">加载中...</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { Icon } from "astro-icon/components";
|
|||||||
<div class="back-to-top-wrapper hidden lg:block">
|
<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()">
|
<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]">
|
<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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -57,12 +57,12 @@ const getPageUrl = (p: number) => {
|
|||||||
{"disabled": page.url.prev == undefined}
|
{"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>
|
</a>
|
||||||
<div class="bg-[var(--card-bg)] flex flex-row rounded-lg items-center text-neutral-700 dark:text-neutral-300 font-bold">
|
<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) => {
|
{pages.map((p) => {
|
||||||
if (p == HIDDEN)
|
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)
|
if (p == page.currentPage)
|
||||||
return <div class="h-11 w-11 rounded-lg bg-[var(--primary)] flex items-center justify-center
|
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"
|
font-bold text-white dark:text-black/70"
|
||||||
@@ -79,6 +79,6 @@ const getPageUrl = (p: number) => {
|
|||||||
{"disabled": page.url.next == undefined}
|
{"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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -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>
|
<a href={licenseConf.url} target="_blank" class="link text-[var(--primary)] line-clamp-2">{licenseConf.name}</a>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</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>
|
|
||||||
@@ -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)]">
|
<div class="transition text-black/75 dark:text-white/75 font-bold group-hover:text-[var(--primary)] group-active:text-[var(--primary)]">
|
||||||
{link.name}
|
{link.name}
|
||||||
</div>
|
</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)]"
|
class="transition text-[1.25rem] text-[var(--primary)]"
|
||||||
>
|
>
|
||||||
</Icon>}
|
</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"
|
class="transition text-[0.75rem] text-black/25 dark:text-white/25 -translate-x-1"
|
||||||
>
|
>
|
||||||
</Icon>}
|
</Icon>}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const config = profileConfig;
|
|||||||
max-w-[12rem] lg:max-w-none overflow-hidden rounded-xl active:scale-95">
|
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
|
<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">
|
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">
|
class="transition opacity-0 scale-90 group-hover:scale-100 group-hover:opacity-100 text-white text-5xl">
|
||||||
</Icon>
|
</Icon>
|
||||||
</div>
|
</div>
|
||||||
@@ -36,12 +36,12 @@ const config = profileConfig;
|
|||||||
<div class="flex gap-2 justify-center mb-1">
|
<div class="flex gap-2 justify-center mb-1">
|
||||||
{config.links.length > 1 && config.links.map(item =>
|
{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">
|
<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>
|
</a>
|
||||||
)}
|
)}
|
||||||
{config.links.length == 1 && <a rel="me" aria-label={config.links[0].name} href={config.links[0].url} target="_blank"
|
{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">
|
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}
|
{config.links[0].name}
|
||||||
</a>}
|
</a>}
|
||||||
</div>
|
</div>
|
||||||
@@ -51,7 +51,7 @@ const config = profileConfig;
|
|||||||
<!-- 全站访问量统计 https://github.com/afoim/fuwari/blob/81f22decb17ff7ee1dd480c10773f7ba8f4df296/src/components/widget/Profile.astro -->
|
<!-- 全站访问量统计 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="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">
|
<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>
|
<span id="site-stats">加载中...</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -59,7 +59,7 @@ const config = profileConfig;
|
|||||||
<!-- 最新 Commit 提交信息 -->
|
<!-- 最新 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="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">
|
<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><a id="github-commit-link" href="#">
|
||||||
<span id="github-commit">加载中...</span></a></span>
|
<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>
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
---
|
---
|
||||||
import { Icon } from "astro-icon/components";
|
|
||||||
import I18nKey from "@i18n/i18nKey";
|
import I18nKey from "@i18n/i18nKey";
|
||||||
import { i18n } from "@i18n/translation";
|
import { i18n } from "@i18n/translation";
|
||||||
import { getTotalWords } from "@utils/content-utils";
|
import { getTotalWords } from "@utils/content-utils";
|
||||||
|
import { Icon } from "astro-icon/components";
|
||||||
|
|
||||||
const totalWords = await getTotalWords();
|
const totalWords = await getTotalWords();
|
||||||
const formattedTotalWords = totalWords.toLocaleString("zh-CN");
|
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="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">
|
<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>
|
<span>总计 {formattedTotalWords} {i18n(I18nKey.wordsCount)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const className = Astro.props.class;
|
|||||||
{isCollapsed && <div class="expand-btn px-4 -mb-2">
|
{isCollapsed && <div class="expand-btn px-4 -mb-2">
|
||||||
<button class="btn-plain rounded-lg w-full h-9">
|
<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">
|
<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>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</div>}
|
</div>}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { LinkPreset } from "./types/config";
|
|||||||
export const siteConfig: SiteConfig = {
|
export const siteConfig: SiteConfig = {
|
||||||
title: "Ad_closeNN 的小站",
|
title: "Ad_closeNN 的小站",
|
||||||
subtitle: "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.
|
lang: "zh_CN", // Language code, e.g. 'en', 'zh-CN', 'ja', etc.
|
||||||
themeColor: {
|
themeColor: {
|
||||||
hue: 160, // Default hue for the theme color, from 0 to 360. e.g. red: 0, teal: 200, cyan: 250, pink: 345
|
hue: 160, // Default hue for the theme color, from 0 to 360. e.g. red: 0, teal: 200, cyan: 250, pink: 345
|
||||||
|
|||||||
@@ -276,7 +276,7 @@ const bannerOffset =
|
|||||||
<ConfigCarrier></ConfigCarrier>
|
<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 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">
|
<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>
|
<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">
|
<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>
|
<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,
|
// SizeObserverPlugin,
|
||||||
// ClickScrollPlugin
|
// ClickScrollPlugin
|
||||||
} from 'overlayscrollbars';
|
} 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 {pathsEqual, url} from "../utils/url-utils";
|
||||||
import {
|
import {
|
||||||
BANNER_HEIGHT,
|
BANNER_HEIGHT,
|
||||||
@@ -519,6 +519,7 @@ function init() {
|
|||||||
// disableAnimation()() // TODO
|
// disableAnimation()() // TODO
|
||||||
loadTheme();
|
loadTheme();
|
||||||
loadHue();
|
loadHue();
|
||||||
|
applyCustomThemeStyles();
|
||||||
initCustomScrollbar();
|
initCustomScrollbar();
|
||||||
showBanner();
|
showBanner();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,9 +71,9 @@ const mainPanelTop = siteConfig.banner.enable
|
|||||||
class:list={["group onload-animation transition-all absolute flex justify-center items-center rounded-full " +
|
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}]}
|
"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>
|
<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}]}
|
{"group-hover:opacity-100": hasBannerLink}]}
|
||||||
name="fa6-solid:arrow-up-right-from-square">
|
name="fa6-solid:arrow-up-right-from-square">
|
||||||
</Icon>
|
</Icon>
|
||||||
|
|||||||
@@ -5,16 +5,20 @@ import MarkdownIt from "markdown-it";
|
|||||||
|
|
||||||
const markdown = new MarkdownIt();
|
const markdown = new MarkdownIt();
|
||||||
const siteNameHtml = markdown.renderInline("`Ad_closeNN 的小站`");
|
const siteNameHtml = markdown.renderInline("`Ad_closeNN 的小站`");
|
||||||
const siteDescriptionHtml = markdown.renderInline("`永远相信美好的事情即将发生`");
|
const siteDescriptionHtml = markdown.renderInline(
|
||||||
|
"`永远相信美好的事情即将发生`",
|
||||||
|
);
|
||||||
const siteUrlHtml = markdown.renderInline("`https://blog.adclosenn.top`");
|
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="友情链接">
|
<MainGridLayout title="友情链接">
|
||||||
<div class="card-base p-6 md:p-8">
|
<div class="card-base p-6 md:p-8">
|
||||||
<div class="flex items-center gap-2 mb-6">
|
<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">
|
<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>
|
</div>
|
||||||
<h1 class="text-2xl font-bold text-black dark:text-white">友情链接</h1>
|
<h1 class="text-2xl font-bold text-black dark:text-white">友情链接</h1>
|
||||||
</div>
|
</div>
|
||||||
@@ -246,7 +250,7 @@ const siteAvatarHtml = markdown.renderInline("`https://static.adclosenn.top/icon
|
|||||||
<!-- 申请友链 -->
|
<!-- 申请友链 -->
|
||||||
<div class="sponsors-section mt-8">
|
<div class="sponsors-section mt-8">
|
||||||
<h2 class="text-xl font-bold text-black dark:text-white mb-4 flex items-center gap-2">
|
<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)]"/>
|
<Icon is:inline name="material-symbols:group" class="text-[1.5rem] text-[var(--primary)]" />
|
||||||
将您的网站加入本站友链板块
|
将您的网站加入本站友链板块
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-sm text-black/60 dark:text-white/60">
|
<p class="text-sm text-black/60 dark:text-white/60">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
|
import { type CollectionEntry, render } from "astro:content";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { render, type CollectionEntry } from "astro:content";
|
|
||||||
import License from "@components/misc/License.astro";
|
import License from "@components/misc/License.astro";
|
||||||
import Markdown from "@components/misc/Markdown.astro";
|
import Markdown from "@components/misc/Markdown.astro";
|
||||||
import I18nKey from "@i18n/i18nKey";
|
import I18nKey from "@i18n/i18nKey";
|
||||||
@@ -40,9 +40,35 @@ const postSources = import.meta.glob("../../content/posts/**/*.{md,mdx}", {
|
|||||||
eager: true,
|
eager: true,
|
||||||
}) as Record<string, string>;
|
}) as Record<string, string>;
|
||||||
const normalizedPostSources = Object.fromEntries(
|
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) {
|
function parseAiSummaryValue(value: string) {
|
||||||
const trimmed = value.trim();
|
const trimmed = value.trim();
|
||||||
if (!trimmed) return "";
|
if (!trimmed) return "";
|
||||||
@@ -65,7 +91,9 @@ function extractFrontmatterValue(frontmatter: string, key: string) {
|
|||||||
return parseAiSummaryValue(inlineValue);
|
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 lines = afterValue.split(/\r?\n/);
|
||||||
const blockLines = [];
|
const blockLines = [];
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
@@ -82,12 +110,20 @@ function getAiSummaryMeta(entryId: string) {
|
|||||||
normalizedPostSources[`../../content/posts/${entryId}.mdx`.toLowerCase()];
|
normalizedPostSources[`../../content/posts/${entryId}.mdx`.toLowerCase()];
|
||||||
const frontmatter = source?.match(/^---\r?\n([\s\S]*?)\r?\n---/)?.[1];
|
const frontmatter = source?.match(/^---\r?\n([\s\S]*?)\r?\n---/)?.[1];
|
||||||
return {
|
return {
|
||||||
summary: frontmatter ? extractFrontmatterValue(frontmatter, "aiSummary") : "",
|
summary: frontmatter
|
||||||
model: frontmatter ? extractFrontmatterValue(frontmatter, "aiSummaryModel") : "",
|
? extractFrontmatterValue(frontmatter, "aiSummary")
|
||||||
|
: "",
|
||||||
|
model: frontmatter
|
||||||
|
? extractFrontmatterValue(frontmatter, "aiSummaryModel")
|
||||||
|
: "",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const aiSummaryMeta = getAiSummaryMeta(entry.id);
|
const aiSummaryMeta = getAiSummaryMeta(entry.id);
|
||||||
|
const postSourceUrl = getGitHubPostSourceUrl(
|
||||||
|
siteConfig.githubRepo,
|
||||||
|
getPostSourcePath(entry.id),
|
||||||
|
);
|
||||||
|
|
||||||
const jsonLd = {
|
const jsonLd = {
|
||||||
"@context": "https://schema.org",
|
"@context": "https://schema.org",
|
||||||
@@ -113,7 +149,6 @@ const showcover = entry.data.showcover;
|
|||||||
// 获取自定义的头图(Markdown 内部)
|
// 获取自定义的头图(Markdown 内部)
|
||||||
const customcover = entry.data.customcover;
|
const customcover = entry.data.customcover;
|
||||||
const isOutdated = entry.data.outdated;
|
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}>
|
<MainGridLayout banner={entry.data.image} title={entry.data.title} description={entry.data.description} lang={entry.data.lang} setOGTypeArticle={true} headings={headings}>
|
||||||
<script is:inline slot="head" type="application/ld+json" set:html={JSON.stringify(jsonLd)}></script>
|
<script is:inline slot="head" type="application/ld+json" set:html={JSON.stringify(jsonLd)}></script>
|
||||||
@@ -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 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="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">
|
<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>
|
||||||
<div class="text-sm">{remarkPluginFrontmatter.words} {" " + i18n(I18nKey.wordsCount)}</div>
|
<div class="text-sm">{remarkPluginFrontmatter.words} {" " + i18n(I18nKey.wordsCount)}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-row items-center pointer-events-none select-none cursor-default">
|
<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">
|
<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>
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
大约 {remarkPluginFrontmatter.minutes} {" " + i18n(remarkPluginFrontmatter.minutes === 1 ? I18nKey.minuteCount : I18nKey.minutesCount)}
|
大约 {remarkPluginFrontmatter.minutes} {" " + i18n(remarkPluginFrontmatter.minutes === 1 ? I18nKey.minuteCount : I18nKey.minutesCount)}
|
||||||
@@ -140,12 +175,25 @@ const isOutdated = entry.data.outdated;
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex flex-row items-center pointer-events-none select-none cursor-default">
|
<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">
|
<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>
|
||||||
<div class="text-sm" id="post-top-page-views">加载中...</div>
|
<div class="text-sm" id="post-top-page-views">加载中...</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-end gap-2 text-black/40 dark:text-white/40 md:shrink-0">
|
<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
|
<a
|
||||||
href={entry.data.nextSlug ? getPostUrlBySlug(entry.data.nextSlug) : "#"}
|
href={entry.data.nextSlug ? getPostUrlBySlug(entry.data.nextSlug) : "#"}
|
||||||
aria-label={entry.data.nextTitle ? `上一篇:${entry.data.nextTitle}` : "没有上一篇"}
|
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},
|
{"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>
|
||||||
<a
|
<a
|
||||||
href={entry.data.prevSlug ? getPostUrlBySlug(entry.data.prevSlug) : "#"}
|
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},
|
{"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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -204,6 +252,7 @@ const isOutdated = entry.data.outdated;
|
|||||||
]}>
|
]}>
|
||||||
<div class="flex items-start gap-2">
|
<div class="flex items-start gap-2">
|
||||||
<Icon
|
<Icon
|
||||||
|
is:inline
|
||||||
name={isOutdated ? "material-symbols:warning-outline-rounded" : "material-symbols:info-outline-rounded"}
|
name={isOutdated ? "material-symbols:warning-outline-rounded" : "material-symbols:info-outline-rounded"}
|
||||||
class="mt-0.5 shrink-0 text-lg"
|
class="mt-0.5 shrink-0 text-lg"
|
||||||
></Icon>
|
></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="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="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">
|
<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>
|
||||||
<div class="flex-1 min-w-0">
|
<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">
|
<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) : "#"}
|
<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}]}>
|
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" >
|
{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">
|
<div class="overflow-hidden transition overflow-ellipsis whitespace-nowrap max-w-[calc(100%_-_3rem)] text-black/75 dark:text-white/75">
|
||||||
{entry.data.nextTitle}
|
{entry.data.nextTitle}
|
||||||
</div>
|
</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">
|
<div class="overflow-hidden transition overflow-ellipsis whitespace-nowrap max-w-[calc(100%_-_3rem)] text-black/75 dark:text-white/75">
|
||||||
{entry.data.prevTitle}
|
{entry.data.prevTitle}
|
||||||
</div>
|
</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>}
|
</div>}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { AUTO_MODE, DARK_MODE, LIGHT_MODE } from "@constants/constants";
|
|||||||
export type SiteConfig = {
|
export type SiteConfig = {
|
||||||
title: string;
|
title: string;
|
||||||
subtitle: string;
|
subtitle: string;
|
||||||
|
githubRepo?: string;
|
||||||
|
|
||||||
lang:
|
lang:
|
||||||
| "en"
|
| "en"
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
import {
|
import { DARK_MODE, DEFAULT_THEME, LIGHT_MODE } from "@constants/constants.ts";
|
||||||
AUTO_MODE,
|
|
||||||
DARK_MODE,
|
|
||||||
DEFAULT_THEME,
|
|
||||||
LIGHT_MODE,
|
|
||||||
} from "@constants/constants.ts";
|
|
||||||
import { expressiveCodeConfig } from "@/config";
|
import { expressiveCodeConfig } from "@/config";
|
||||||
import type { LIGHT_DARK_MODE } from "@/types/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 {
|
export function getStoredTheme(): LIGHT_DARK_MODE {
|
||||||
return (localStorage.getItem("theme") as LIGHT_DARK_MODE) || DEFAULT_THEME;
|
return (localStorage.getItem("theme") as LIGHT_DARK_MODE) || DEFAULT_THEME;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user