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>
|
|
|
|
|
);
|
|
|
|
|
});
|