ttrpg-tools/src/components/md-link.tsx

88 lines
2.4 KiB
TypeScript
Raw Normal View History

2026-02-26 09:24:26 +08:00
import { customElement, noShadowDOM } from "solid-element";
import { createSignal, onCleanup } from "solid-js";
import { render } from "solid-js/web";
import { Article } from "./Article";
customElement("md-link", {}, (props, { element }) => {
noShadowDOM();
const [showArticle, setShowArticle] = createSignal(false);
let articleContainer: HTMLDivElement | undefined;
let disposeArticle: (() => void) | null = null;
// 从 element 的 textContent 获取链接目标
2026-02-26 09:37:28 +08:00
const rawLinkSrc = element?.textContent?.trim() || "";
// 从父节点 article 的 data-src 获取当前 markdown 文件完整路径
const articleEl = element?.closest('article[data-src]');
const articlePath = articleEl?.getAttribute('data-src') || '';
// 解析相对路径(相对于 markdown 文件所在目录)
const resolvePath = (base: string, relative: string): string => {
if (relative.startsWith('/')) {
return relative;
}
const baseDir = base.substring(0, base.lastIndexOf('/') + 1);
return baseDir + relative;
};
const linkSrc = resolvePath(articlePath, rawLinkSrc);
2026-02-26 09:24:26 +08:00
// 查找包含此元素的 p 标签
const parentP = element?.closest("p");
const toggleArticle = () => {
if (!parentP) return;
if (showArticle()) {
// 隐藏文章
if (articleContainer) {
if (disposeArticle) {
disposeArticle();
disposeArticle = null;
}
articleContainer.remove();
articleContainer = undefined;
}
setShowArticle(false);
} else {
// 显示文章
articleContainer = document.createElement("div");
articleContainer.classList.add("md-link-article");
2026-02-26 09:39:35 +08:00
articleContainer.classList.add("ml-4", "border-l-2", "border-gray-200", "pl-4");
2026-02-26 09:24:26 +08:00
parentP.after(articleContainer);
// 渲染 Article 组件
disposeArticle = render(() => (
2026-02-26 09:37:28 +08:00
<Article
2026-02-26 09:24:26 +08:00
src={linkSrc}
onLoaded={() => console.log("Article loaded:", linkSrc)}
onError={(err) => console.error("Article error:", err)}
/>
), articleContainer);
setShowArticle(true);
}
};
onCleanup(() => {
if (disposeArticle) {
disposeArticle();
disposeArticle = null;
}
});
return (
<a
href="#"
onClick={(e) => {
e.preventDefault();
toggleArticle();
}}
class="text-blue-600 hover:text-blue-800 hover:underline"
>
2026-02-26 09:37:28 +08:00
{rawLinkSrc}
2026-02-26 09:24:26 +08:00
</a>
);
});