feat: UiProse prose component with link highjacking

This commit is contained in:
2026-01-13 22:19:23 -05:00
parent 764bc6aeea
commit 40becf1135
9 changed files with 909 additions and 3 deletions

View File

@@ -0,0 +1,65 @@
export function useProseLinks(refContent: Ref<HTMLElement | null>) {
const router = useRouter();
const { url } = useSiteConfig();
const siteUrl = new URL(url);
// Determine if the href is internal
const isInternal = (href: string) => {
if (!href) return false;
if (href.startsWith("/")) return true;
if (href.startsWith("#")) return false;
try {
const hrefUrl = new URL(href);
return hrefUrl.hostname === siteUrl.hostname;
}
catch {
return false;
}
};
// Convert href to relative path
const convertToRelative = (href: string) => {
if (href.startsWith("/")) return href;
try {
const hrefUrl = new URL(href);
if (hrefUrl.hostname === siteUrl.hostname) {
return hrefUrl.pathname + hrefUrl.search + hrefUrl.hash;
}
}
catch {
// Invalid URL
}
return href;
};
// Highjack click events to use router for internal links
const handleClick = (e: MouseEvent) => {
const target = e.target as HTMLElement;
const link = target.closest("a");
if (!link) return;
const href = link.getAttribute("href");
if (!href) return;
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || link.target === "_blank" || link.hasAttribute("download")) {
return;
}
if (isInternal(href)) {
e.preventDefault();
const path = convertToRelative(href);
router.push(path);
}
};
// Attach and detach event listeners
onMounted(() => {
const element = unref(refContent);
if (element) {
element.addEventListener("click", handleClick);
}
});
onBeforeUnmount(() => {
const element = unref(refContent);
if (element) {
element.removeEventListener("click", handleClick);
}
});
}