965 字
5 分钟
Fuwari改造之时间轴组件
成果展示
2026
这是一个时间轴演示!
时间
事件
02-08
这里可以自定义
0208
随便写
这里也可以自定义
0208
2025
可以有多个时间轴!
时间
事件
02-08
这里可以自定义
0208
随便写
这里也可以自定义
0208
修改过程
19 collapsed lines
import sitemap from "@astrojs/sitemap";import svelte from "@astrojs/svelte";import tailwind from "@astrojs/tailwind";import { pluginCollapsibleSections } from "@expressive-code/plugin-collapsible-sections";import { pluginLineNumbers } from "@expressive-code/plugin-line-numbers";import swup from "@swup/astro";import expressiveCode from "astro-expressive-code";import icon from "astro-icon";import { defineConfig } from "astro/config";import rehypeAutolinkHeadings from "rehype-autolink-headings";import rehypeComponents from "rehype-components"; /* Render the custom directive content */import rehypeKatex from "rehype-katex";import rehypeSlug from "rehype-slug";import remarkDirective from "remark-directive"; /* Handle directives */import remarkGithubAdmonitionsToDirectives from "remark-github-admonitions-to-directives";import remarkMath from "remark-math";import remarkSectionize from "remark-sectionize";import { expressiveCodeConfig } from "./src/config.ts";import { pluginLanguageBadge } from "./src/plugins/expressive-code/language-badge.ts";import { AdmonitionComponent } from "./src/plugins/rehype-component-admonition.mjs";import { GithubCardComponent } from "./src/plugins/rehype-component-github-card.mjs";import { parseDirectiveNode } from "./src/plugins/remark-directive-rehype.js";import { remarkTimeline } from "./src/plugins/remark-timeline.js";import { remarkExcerpt } from "./src/plugins/remark-excerpt.js";import { remarkReadingTime } from "./src/plugins/remark-reading-time.mjs";import { pluginCustomCopyButton } from "./src/plugins/expressive-code/custom-copy-button.js";83 collapsed lines
// https://astro.build/configexport default defineConfig({ site: "https://fuwari.vercel.app/", base: "/", trailingSlash: "always", integrations: [ tailwind({ nesting: true, }), swup({ theme: false, animationClass: "transition-swup-", // see https://swup.js.org/options/#animationselector // the default value `transition-` cause transition delay // when the Tailwind class `transition-all` is used containers: ["main", "#toc", "#series"], smoothScrolling: true, cache: true, preload: true, accessibility: true, updateHead: true, updateBodyClass: false, globalInstance: true, }), icon({ include: { "preprocess: vitePreprocess(),": ["*"], "fa6-brands": ["*"], "fa6-regular": ["*"], "fa6-solid": ["*"], }, }), expressiveCode({ themes: [expressiveCodeConfig.theme, expressiveCodeConfig.theme], plugins: [ pluginCollapsibleSections(), pluginLineNumbers(), pluginLanguageBadge(), pluginCustomCopyButton() ], defaultProps: { wrap: true, overridesByLang: { 'shellsession': { showLineNumbers: false, }, }, }, styleOverrides: { codeBackground: "var(--codeblock-bg)", borderRadius: "0.75rem", borderColor: "none", codeFontSize: "0.875rem", codeFontFamily: "'JetBrains Mono Variable', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace", codeLineHeight: "1.5rem", frames: { editorBackground: "var(--codeblock-bg)", terminalBackground: "var(--codeblock-bg)", terminalTitlebarBackground: "var(--codeblock-topbar-bg)", editorTabBarBackground: "var(--codeblock-topbar-bg)", editorActiveTabBackground: "none", editorActiveTabIndicatorBottomColor: "var(--primary)", editorActiveTabIndicatorTopColor: "none", editorTabBarBorderBottomColor: "var(--codeblock-topbar-bg)", terminalTitlebarBorderBottomColor: "none" }, textMarkers: { delHue: 0, insHue: 180, markHue: 250 } }, frames: { showCopyToClipboardButton: false, } }), svelte(), sitemap(), ], markdown: { remarkPlugins: [ remarkMath, remarkReadingTime, remarkExcerpt, remarkGithubAdmonitionsToDirectives, remarkDirective, remarkTimeline, remarkSectionize, parseDirectiveNode, ],58 collapsed lines
rehypePlugins: [ rehypeKatex, rehypeSlug, [ rehypeComponents, { components: { github: GithubCardComponent, note: (x, y) => AdmonitionComponent(x, y, "note"), tip: (x, y) => AdmonitionComponent(x, y, "tip"), important: (x, y) => AdmonitionComponent(x, y, "important"), caution: (x, y) => AdmonitionComponent(x, y, "caution"), warning: (x, y) => AdmonitionComponent(x, y, "warning"), }, }, ], [ rehypeAutolinkHeadings, { behavior: "append", properties: { className: ["anchor"], }, content: { type: "element", tagName: "span", properties: { className: ["anchor-icon"], "data-pagefind-ignore": true, }, children: [ { type: "text", value: "#", }, ], }, }, ], ], }, vite: { build: { rollupOptions: { onwarn(warning, warn) { // temporarily suppress this warning if ( warning.message.includes("is dynamically imported by") && warning.message.includes("but also statically imported by") ) { return; } warn(warning); }, }, }, },});在 src/plugins 目录下创建一个新的文件叫 remark-timeline.js
import { h } from "hastscript";import { visit } from "unist-util-visit";import { toString } from "mdast-util-to-string";
export function remarkTimeline() { return (tree) => { visit(tree, "containerDirective", (node) => { if (node.name !== "timeline") return;
const data = node.data || (node.data = {}); const content = toString(node); const lines = content.split("\n").filter(l => l.trim() !== "");
const children = lines.map(line => { line = line.trim(); // Match year header: [2026 : xxxx] const yearMatch = line.match(/^\[(.*?)\s?:\s?(.*?)\]$/); if (yearMatch) { const year = yearMatch[1]; const desc = yearMatch[2]; return h("div", { className: "flex flex-row w-full items-center h-[3.75rem]" }, [ h("div", { className: "w-[15%] md:w-[10%] transition text-2xl font-bold text-right text-75" }, year), h("div", { className: "w-[15%] md:w-[10%]" }, [ h("div", { className: "h-3 w-3 bg-none rounded-full outline outline-[var(--primary)] mx-auto -outline-offset-[2px] z-50 outline-3" }) ]), h("div", { className: "w-[70%] md:w-[80%] transition text-left text-50" }, desc) ]); }
// Match entry: Date || Title const entryMatch = line.split("||"); if (entryMatch.length >= 2) { const date = entryMatch[0].trim(); const title = entryMatch[1].trim(); return h("div", { className: "group btn-plain !block h-10 w-full rounded-lg hover:text-[initial] transition-all", style: "overflow:visible;"}, [ h("div", { className: "flex flex-row justify-start items-center h-full" }, [ // date h("div", { className: "w-[15%] md:w-[10%] transition text-sm text-right text-50" }, date), // dot and line h("div", { className: "w-[15%] md:w-[10%] relative dash-line h-full flex items-center" }, [ h("div", { className: "transition-all mx-auto w-1 h-1 rounded group-hover:h-5 bg-[oklch(0.5_0.05_var(--hue))] group-hover:bg-[var(--primary)] outline outline-4 z-50 outline-[var(--card-bg)] group-hover:outline-[var(--btn-plain-bg-hover)] group-active:outline-[var(--btn-plain-bg-active)]" }) ]), // title h("div", { className: "w-[70%] md:max-w-[75%] transition-all text-left font-bold text-75 group-hover:translate-x-1 group-hover:text-[var(--primary)] whitespace-nowrap overflow-ellipsis overflow-hidden" }, title) ]) ]); }
return h("div", { className: "text-red-500" }, `Invalid format: ${line}`); });
data.hName = "div"; data.hProperties = { className: "card-base timeline-container font-sans", style: "margin-bottom: 2rem; overflow: visible;", }; data.hChildren = children; }); };}使用方法
:::timeline[2026 : 这是一个时间轴演示!]时间 || 事件02-08 || 这里可以自定义0208 || 随便写这里也可以自定义 || 0208[2025 : 可以有多个时间轴!]时间 || 事件02-08 || 这里可以自定义0208 || 随便写这里也可以自定义 || 0208:::支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!
正在绘制分享海报...