From 4038d67ea0aec8422f993907bf97956b2e0e1ea7 Mon Sep 17 00:00:00 2001 From: hypercross Date: Mon, 11 May 2026 11:25:57 +0800 Subject: [PATCH] feat: add md-emfont component and format code - Add `md-emfont` custom element for dynamic font loading - Register `md-emfont` in component index - Reformat `Article.tsx` and `index.ts` using double quotes and consistent spacing - Fix indentation in `index.html` --- src/components/Article.tsx | 53 ++++++++++++++++++++------------ src/components/index.ts | 59 +++++++++++++++++++----------------- src/components/md-emfont.tsx | 59 ++++++++++++++++++++++++++++++++++++ src/index.html | 18 +++++------ 4 files changed, 133 insertions(+), 56 deletions(-) create mode 100644 src/components/md-emfont.tsx diff --git a/src/components/Article.tsx b/src/components/Article.tsx index c605e1b..c0e20aa 100644 --- a/src/components/Article.tsx +++ b/src/components/Article.tsx @@ -1,22 +1,32 @@ -import { Component, createEffect, onCleanup, Show, createResource, createMemo } from 'solid-js'; -import { parseMarkdown } from '../markdown'; -import { extractSection } from '../data-loader'; -import mermaid from 'mermaid'; -import {getIndexedData} from "../data-loader/file-index"; -import { resolvePath } from './utils/path'; -import { useLocation } from '@solidjs/router'; +import { + Component, + createEffect, + onCleanup, + Show, + createResource, + createMemo, +} from "solid-js"; +import { parseMarkdown } from "../markdown"; +import { extractSection } from "../data-loader"; +import mermaid from "mermaid"; +import { getIndexedData } from "../data-loader/file-index"; +import { resolvePath } from "./utils/path"; +import { useLocation } from "@solidjs/router"; export interface ArticleProps { src: string; - section?: string; // 指定要显示的标题(不含 #) - iconPath?: string; // 图标路径前缀,默认为 "./assets",空字符串表示禁用 + section?: string; // 指定要显示的标题(不含 #) + iconPath?: string; // 图标路径前缀,默认为 "./assets",空字符串表示禁用 onLoaded?: () => void; onError?: (error: Error) => void; - class?: string; // 额外的 class 用于样式控制 + class?: string; // 额外的 class 用于样式控制 scrollToHash?: boolean; // 是否自动滚动到 hash } -async function fetchArticleContent(params: { src: string; section?: string }): Promise { +async function fetchArticleContent(params: { + src: string; + section?: string; +}): Promise { const text = await getIndexedData(params.src); // 如果指定了 section,提取对应内容 return params.section ? extractSection(text, params.section) : text; @@ -28,17 +38,17 @@ async function fetchArticleContent(params: { src: string; section?: string }): P function scrollToHash(hash: string) { if (!hash) return; // 移除 # 前缀 - const id = hash.startsWith('#') ? hash.slice(1) : hash; + const id = hash.startsWith("#") ? hash.slice(1) : hash; if (!id) return; - + // 使用 decodeURIComponent 解码 ID(处理中文等特殊字符) const decodedId = decodeURIComponent(id); - + // 尝试查找元素 const element = document.getElementById(decodedId); if (element) { // 使用 scrollIntoView 滚动到元素 - element.scrollIntoView({ behavior: 'instant', block: 'start' }); + element.scrollIntoView({ behavior: "instant", block: "start" }); return true; } return false; @@ -52,12 +62,12 @@ export const Article: Component = (props) => { const location = useLocation(); const [content, { refetch }] = createResource( () => ({ src: props.src, section: props.section }), - fetchArticleContent + fetchArticleContent, ); // 解析 iconPath,默认为 "./assets",空字符串表示禁用 const iconPrefix = createMemo(() => { - if (props.iconPath === '') return undefined; // 空字符串禁用图标前缀 + if (props.iconPath === "") return undefined; // 空字符串禁用图标前缀 return resolvePath(props.src, props.iconPath ?? "./assets"); }); @@ -67,7 +77,7 @@ export const Article: Component = (props) => { props.onLoaded?.(); // 内容加载完成后,渲染 mermaid 图表 void mermaid.run(); - + // 内容渲染后检查 hash 并滚动 scrollToHash(location.hash); } @@ -78,7 +88,7 @@ export const Article: Component = (props) => { }); return ( -
+
加载中...
@@ -86,7 +96,10 @@ export const Article: Component = (props) => {
加载失败:{content.error?.message}
-
+
); diff --git a/src/components/index.ts b/src/components/index.ts index c234516..9719c58 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,28 +1,30 @@ // 导入自定义元素以注册 -import './md-dice'; -import './md-table'; -import './md-link'; -import './md-pins'; -import './md-bg'; -import './md-deck'; -import './md-commander/index'; -import './md-yarn-spinner'; -import './md-token'; -import './md-token-viewer'; +import "./md-dice"; +import "./md-table"; +import "./md-link"; +import "./md-pins"; +import "./md-bg"; +import "./md-emfont"; +import "./md-deck"; +import "./md-commander/index"; +import "./md-yarn-spinner"; +import "./md-token"; +import "./md-token-viewer"; // 导出组件 -export { Article } from './Article'; -export type { ArticleProps } from './Article'; -export { MobileSidebar, DesktopSidebar } from './Sidebar'; -export type { SidebarProps } from './Sidebar'; -export { FileTreeNode, HeadingNode } from './FileTree'; +export { Article } from "./Article"; +export type { ArticleProps } from "./Article"; +export { MobileSidebar, DesktopSidebar } from "./Sidebar"; +export type { SidebarProps } from "./Sidebar"; +export { FileTreeNode, HeadingNode } from "./FileTree"; // 导出数据类型 -export type { DiceProps } from './md-dice'; -export type { TableProps } from './md-table'; -export type { BgProps } from './md-bg'; -export type { YarnSpinnerProps } from './md-yarn-spinner'; -export type { TokenProps } from './md-token'; +export type { DiceProps } from "./md-dice"; +export type { TableProps } from "./md-table"; +export type { BgProps } from "./md-bg"; +export type { EmfontProps } from "./md-emfont"; +export type { YarnSpinnerProps } from "./md-yarn-spinner"; +export type { TokenProps } from "./md-token"; // 导出 md-commander 相关 export type { @@ -39,10 +41,13 @@ export type { TrackerAttributeType, TrackerCommand, TrackerViewMode, -} from './md-commander/types'; -export { TabBar } from './md-commander/TabBar'; -export { TrackerView } from './md-commander/TrackerView'; -export { CommanderEntries } from './md-commander/CommanderEntries'; -export { CommanderInput } from './md-commander/CommanderInput'; -export { useCommander } from './md-commander/hooks'; -export { initializeCommands, getCommands } from './md-commander/stores/commandsStore'; \ No newline at end of file +} from "./md-commander/types"; +export { TabBar } from "./md-commander/TabBar"; +export { TrackerView } from "./md-commander/TrackerView"; +export { CommanderEntries } from "./md-commander/CommanderEntries"; +export { CommanderInput } from "./md-commander/CommanderInput"; +export { useCommander } from "./md-commander/hooks"; +export { + initializeCommands, + getCommands, +} from "./md-commander/stores/commandsStore"; diff --git a/src/components/md-emfont.tsx b/src/components/md-emfont.tsx new file mode 100644 index 0000000..d7d7e12 --- /dev/null +++ b/src/components/md-emfont.tsx @@ -0,0 +1,59 @@ +import { customElement, noShadowDOM } from "solid-element"; +import { createEffect, onCleanup } from "solid-js"; + +export interface EmfontProps { + weight?: string; + words?: string; +} + +customElement( + "md-emfont", + { weight: "400", words: "" }, + (props, { element }) => { + noShadowDOM(); + + // 从 element 的 textContent 获取字体名称 + const font = element?.textContent?.trim() || ""; + + // 隐藏原始文本内容 + if (element) { + element.textContent = ""; + } + + // 从父节点 article 获取当前 article 元素 + const articleEl = element?.closest("article") as HTMLElement; + + // 构建 emfont CSS URL + const weight = props.weight || "400"; + // 从 article 文本内容中提取唯一字符作为 words,用于字体子集化 + const words = + props.words || [...new Set(articleEl?.textContent || "")].join(""); + const linkHref = `https://font.emtech.cc/css/${font}?weight=${weight}&words=${encodeURIComponent(words)}`; + + // 创建 元素并注入 + const linkEl = document.createElement("link"); + linkEl.rel = "stylesheet"; + linkEl.href = linkHref; + linkEl.dataset.mdEmfont = font; + document.head.appendChild(linkEl); + + createEffect(() => { + if (!articleEl) return; + articleEl.dataset.mdEmfont = font; + articleEl.style.fontFamily = font; + }); + + onCleanup(() => { + // 清理 article 上的样式 + if (articleEl?.dataset?.mdEmfont === font) { + articleEl.style.fontFamily = ""; + } + // 移除注入的 + if (linkEl.parentNode) { + linkEl.parentNode.removeChild(linkEl); + } + }); + + return null; + }, +); diff --git a/src/index.html b/src/index.html index ccc15e7..1d29f58 100644 --- a/src/index.html +++ b/src/index.html @@ -1,11 +1,11 @@ - + - - - - TTRPG Tools - - -
- + + + + TTRPG Tools + + +
+