feat(导航): 添加其他网站页面和URL卡片组件

添加新的"其他网站"导航页面,包含多个外部链接
实现URL卡片组件用于美观展示外部链接,支持自动获取元数据
调整导航栏配置和样式以适配新功能
This commit is contained in:
二叉树树
2026-01-01 19:42:26 +08:00
parent 6b60fdcbab
commit d378c49be0
6 changed files with 265 additions and 2 deletions

View File

@@ -15,6 +15,7 @@ import remarkSectionize from "remark-sectionize";
import { imageFallbackConfig, siteConfig } from "./src/config.ts";
import { AdmonitionComponent } from "./src/plugins/rehype-component-admonition.mjs";
import { GithubCardComponent } from "./src/plugins/rehype-component-github-card.mjs";
import { UrlCardComponent } from "./src/plugins/rehype-component-url-card.mjs";
import rehypeImageFallback from "./src/plugins/rehype-image-fallback.mjs";
import { parseDirectiveNode } from "./src/plugins/remark-directive-rehype.js";
import { remarkExcerpt } from "./src/plugins/remark-excerpt.js";
@@ -141,6 +142,7 @@ export default defineConfig({
{
components: {
github: GithubCardComponent,
url: UrlCardComponent,
note: (x, y) => AdmonitionComponent(x, y, "note"),
tip: (x, y) => AdmonitionComponent(x, y, "tip"),
important: (x, y) => AdmonitionComponent(x, y, "important"),

View File

@@ -29,7 +29,7 @@ const config = profileConfig;
</div>
<!-- 全站访问量统计 -->
<div class="grid grid-cols-2 mt-3 pt-3 border-t border-neutral-200 dark:border-neutral-700">
<div class="grid grid-cols-2 mt-3 pt-3 border-t border-neutral-300 dark:border-neutral-700">
<div class="text-center">
<div class="text-xs text-neutral-500 mb-1 flex items-center justify-center gap-1">
<Icon name="material-symbols:visibility-outline" class="text-base"></Icon>
@@ -37,7 +37,7 @@ const config = profileConfig;
</div>
<div id="site-views" class="font-bold text-lg text-neutral-700 dark:text-neutral-300">-</div>
</div>
<div class="text-center border-l border-neutral-200 dark:border-neutral-700">
<div class="text-center border-l border-neutral-300 dark:border-neutral-700">
<div class="text-xs text-neutral-500 mb-1 flex items-center justify-center gap-1">
<Icon name="material-symbols:person" class="text-base"></Icon>
<span class="text-xs">访客数</span>

View File

@@ -76,6 +76,11 @@ export const navBarConfig: NavBarConfig = {
url: "/sponsors/", // Internal links should not include the base path, as it is automatically added
external: false, // Show an external link icon and will open in a new tab
},
{
name: "他站",
url: "/other-sites/", // Internal links should not include the base path, as it is automatically added
external: true, // Show an external link icon and will open in a new tab
},
{
name: "统计",
url: "https://umami.acofork.com/share/CdkXbGgZr6ECKOyK", // Internal links should not include the base path, as it is automatically added

View File

@@ -0,0 +1,21 @@
---
title: 其他网站
published: 1999-01-01T19:25:08
description: 'AcoFork 的其他网站'
image: ''
draft: false
lang: ''
---
::url{href="https://http.acofork.com"}
::url{href="https://pic.acofork.com"}
::url{href="https://gallery.acofork.com"}
::url{href="https://img.072103.xyz"}
::url{href="https://eopfapi.acofork.com/pic"}
::url{href="https://eoddos.2x.nz"}
::url{href="https://u.2x.nz"}
::url{href="https://e3.2x.nz"}
::url{href="https://pan.2x.nz"}
::url{href="https://nas.acofork.com"}
::url{href="https://vw.acofork.com"}

View File

@@ -0,0 +1,110 @@
/// <reference types="mdast" />
import { h } from "hastscript";
/**
* Creates a URL Card component.
*
* @param {Object} properties - The properties of the component.
* @param {string} properties.href - The URL to display.
* @param {import('mdast').RootContent[]} children - The children elements of the component.
* @returns {import('mdast').Parent} The created URL Card component.
*/
export function UrlCardComponent(properties, children) {
if (Array.isArray(children) && children.length !== 0)
return h("div", { class: "hidden" }, [
'Invalid directive. ("url" directive must be leaf type "::url{href="https://example.com"}")',
]);
if (!properties.href)
return h(
"div",
{ class: "hidden" },
'Invalid URL. ("href" attribute must be provided)',
);
const url = properties.href;
const cardUuid = `UC${Math.random().toString(36).slice(-6)}`; // Collisions are not important
const nImage = h(`div#${cardUuid}-image`, { class: "uc-image" });
const nTitle = h("div", { class: "uc-titlebar" }, [
h("div", { class: "uc-titlebar-left" }, [
h(`div#${cardUuid}-favicon`, { class: "uc-favicon" }),
h("div", { class: "uc-domain" }, new URL(url).hostname),
]),
]);
const nDescription = h(
`div#${cardUuid}-description`,
{ class: "uc-description" },
"Waiting for api.microlink.io...",
);
const nTitleText = h(
`div#${cardUuid}-title`,
{ class: "uc-title-text" },
"Loading..."
);
const nScript = h(
`script#${cardUuid}-script`,
{ type: "text/javascript", defer: true },
`
fetch('https://api.microlink.io?url=${encodeURIComponent(url)}').then(response => response.json()).then(data => {
if (data.status === 'success') {
const meta = data.data;
document.getElementById('${cardUuid}-title').innerText = meta.title || "${url}";
document.getElementById('${cardUuid}-description').innerText = meta.description || "No description available";
const faviconEl = document.getElementById('${cardUuid}-favicon');
if (meta.logo?.url) {
faviconEl.style.backgroundImage = 'url(' + meta.logo.url + ')';
faviconEl.style.backgroundColor = 'transparent';
} else {
faviconEl.style.display = 'none';
}
const imageEl = document.getElementById('${cardUuid}-image');
if (meta.image?.url) {
imageEl.style.backgroundImage = 'url(' + meta.image.url + ')';
} else {
imageEl.style.display = 'none';
document.getElementById('${cardUuid}-container').classList.add('no-image');
}
document.getElementById('${cardUuid}-card').classList.remove("fetch-waiting");
console.log("[URL-CARD] Loaded card for ${url} | ${cardUuid}.")
} else {
throw new Error('Microlink API failed');
}
}).catch(err => {
const c = document.getElementById('${cardUuid}-card');
c?.classList.add("fetch-error");
document.getElementById('${cardUuid}-title').innerText = "Error loading preview";
document.getElementById('${cardUuid}-description').innerText = "Failed to fetch metadata for ${url}";
console.warn("[URL-CARD] (Error) Loading card for ${url} | ${cardUuid}.", err)
})
`,
);
return h(
`a#${cardUuid}-card`,
{
class: "card-url fetch-waiting no-styling",
href: url,
target: "_blank",
url,
},
[
h(`div#${cardUuid}-container`, { class: "uc-container" }, [
h("div", { class: "uc-content" }, [
nTitle,
nTitleText,
nDescription,
]),
nImage,
]),
nScript,
],
);
}

View File

@@ -243,3 +243,128 @@ a.card-github.fetch-error
transition-property: all
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1)
transition-duration: 0.15s
a.card-url
display: block
background: var(--license-block-bg)
position: relative
margin: 0.5rem 0
padding: 1rem
color: var(--tw-prose-body)
border-radius: var(--radius-large)
text-decoration-thickness: 0px
text-decoration-line: none
overflow: hidden
transition: background-color 0.15s ease-in-out, transform 0.15s ease-in-out
&:hover
background-color: var(--btn-regular-bg-hover)
.uc-title-text
color: var(--primary)
&:active
scale: .98
background-color: var(--btn-regular-bg-active)
.uc-container
display: flex
flex-direction: row
justify-content: space-between
gap: 1rem
&.no-image
.uc-image
display: none
.uc-content
display: flex
flex-direction: column
flex: 1
min-width: 0
justify-content: center
.uc-image
width: 6rem
height: 6rem
flex-shrink: 0
background-size: cover
background-position: center
border-radius: 0.5rem
background-color: var(--card-bg)
display: block
@media (max-width: 640px)
width: 4rem
height: 4rem
.uc-titlebar
display: flex
align-items: center
margin-bottom: 0.25rem
font-size: 0.75rem
color: var(--tw-prose-body)
opacity: 0.8
.uc-titlebar-left
display: flex
align-items: center
gap: 0.5rem
.uc-favicon
width: 1rem
height: 1rem
background-size: cover
background-color: var(--tw-prose-body)
border-radius: 2px
opacity: 0.5
.uc-domain
font-weight: 500
.uc-title-text
font-size: 1rem
font-weight: bold
margin-bottom: 0.25rem
line-height: 1.4
color: var(--tw-prose-headings)
transition: color 0.15s
display: -webkit-box
-webkit-line-clamp: 1
-webkit-box-orient: vertical
overflow: hidden
.uc-description
font-size: 0.875rem
color: var(--tw-prose-body)
opacity: 0.8
line-height: 1.4
display: -webkit-box
-webkit-line-clamp: 2
-webkit-box-orient: vertical
overflow: hidden
a.card-url.fetch-waiting
pointer-events: none
opacity: 0.7
.uc-title-text, .uc-description, .uc-domain
background-color: var(--tw-prose-body)
color: transparent
opacity: 0.5
animation: pulsate 2s infinite linear
user-select: none
border-radius: 0.25rem
width: fit-content
.uc-image
opacity: 0.5
animation: pulsate 2s infinite linear
background-color: var(--tw-prose-body)
a.card-url.fetch-error
pointer-events: all
opacity: 1
.uc-image
display: none