mirror of
https://github.com/afoim/fuwari.git
synced 2026-01-31 00:53:19 +08:00
feat(导航): 添加其他网站页面和URL卡片组件
添加新的"其他网站"导航页面,包含多个外部链接 实现URL卡片组件用于美观展示外部链接,支持自动获取元数据 调整导航栏配置和样式以适配新功能
This commit is contained in:
@@ -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"),
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
21
src/content/posts/other-sites.md
Normal file
21
src/content/posts/other-sites.md
Normal 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"}
|
||||
110
src/plugins/rehype-component-url-card.mjs
Normal file
110
src/plugins/rehype-component-url-card.mjs
Normal 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,
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user