mirror of
https://github.com/joelhooks/joelclaw.git
synced 2026-09-19 01:24:04 +08:00
016dccc295
- Titles wrap fully instead of truncating - Relevance text shows complete (no line-clamp) - Date shown as relative time (2d ago) above title, not stealing width - Single type badge (repo/article/video) replaces full tag list - Source URL removed from index (shown on detail page) - Tighter card spacing for better scan density - Re-removed duplicate self-hosting discovery (also from Vault source)
126 lines
3.6 KiB
TypeScript
126 lines
3.6 KiB
TypeScript
import type { Image, Link, Root, Text } from "mdast";
|
|
import { SKIP, visit } from "unist-util-visit";
|
|
|
|
const WIKILINK_PATTERN = /(!)?\[\[([^[\]]+)\]\]/g;
|
|
const IMAGE_EXTENSIONS = new Set([
|
|
"png",
|
|
"jpg",
|
|
"jpeg",
|
|
"gif",
|
|
"webp",
|
|
"svg",
|
|
"avif",
|
|
]);
|
|
|
|
function splitTargetAndAlias(value: string): { target: string; alias?: string } {
|
|
const pipeIndex = value.indexOf("|");
|
|
if (pipeIndex === -1) return { target: value.trim() };
|
|
return {
|
|
target: value.slice(0, pipeIndex).trim(),
|
|
alias: value.slice(pipeIndex + 1).trim(),
|
|
};
|
|
}
|
|
|
|
function stripMarkdownExtension(value: string): string {
|
|
return value.replace(/\.mdx?$/i, "");
|
|
}
|
|
|
|
function toAdrHref(target: string): string {
|
|
const normalized = stripMarkdownExtension(target)
|
|
.replace(/\\/g, "/")
|
|
.replace(/^\.?\//, "")
|
|
.trim();
|
|
return `/adrs/${encodeURI(normalized)}`;
|
|
}
|
|
|
|
function toImageHref(target: string): string {
|
|
const normalized = stripMarkdownExtension(target)
|
|
.replace(/\\/g, "/")
|
|
.replace(/^\.?\//, "")
|
|
.trim();
|
|
return `/images/${encodeURI(normalized)}`;
|
|
}
|
|
|
|
function isImagePath(target: string): boolean {
|
|
const ext = target.split(".").pop()?.toLowerCase();
|
|
return ext ? IMAGE_EXTENSIONS.has(ext) : false;
|
|
}
|
|
|
|
/**
|
|
* Convert Obsidian wikilinks/embeds to standard markdown nodes for MDX.
|
|
*
|
|
* Examples:
|
|
* - ![[adr/foo.jpg|Alt]] -> 
|
|
* - [[0077-memory-system-next-phase]] -> [0077-memory-system-next-phase](/adrs/0077-memory-system-next-phase)
|
|
* - ![[0077-memory-system-next-phase]] -> [0077-memory-system-next-phase](/adrs/0077-memory-system-next-phase)
|
|
*/
|
|
export function remarkObsidian() {
|
|
return (tree: Root) => {
|
|
visit(tree, "text", (node: Text, index, parent) => {
|
|
if (!parent || index === undefined) return;
|
|
if (parent.type === "link") return;
|
|
|
|
const source = node.value;
|
|
if (!source.includes("[[")) return;
|
|
|
|
WIKILINK_PATTERN.lastIndex = 0;
|
|
let lastIndex = 0;
|
|
let matched = false;
|
|
let match: RegExpExecArray | null;
|
|
const replacementNodes: Array<Text | Link | Image> = [];
|
|
|
|
while ((match = WIKILINK_PATTERN.exec(source)) !== null) {
|
|
const isEmbed = Boolean(match[1]);
|
|
const rawInner = match[2];
|
|
if (!rawInner) continue;
|
|
matched = true;
|
|
|
|
if (match.index > lastIndex) {
|
|
replacementNodes.push({
|
|
type: "text",
|
|
value: source.slice(lastIndex, match.index),
|
|
});
|
|
}
|
|
|
|
const { target, alias } = splitTargetAndAlias(rawInner);
|
|
const cleanTarget = stripMarkdownExtension(target).trim();
|
|
|
|
if (!cleanTarget) {
|
|
replacementNodes.push({ type: "text", value: match[0] });
|
|
lastIndex = match.index + match[0].length;
|
|
continue;
|
|
}
|
|
|
|
if (isEmbed && isImagePath(cleanTarget)) {
|
|
replacementNodes.push({
|
|
type: "image",
|
|
url: toImageHref(cleanTarget),
|
|
alt: alias || cleanTarget.split("/").pop() || cleanTarget,
|
|
});
|
|
} else {
|
|
const label = alias || cleanTarget;
|
|
replacementNodes.push({
|
|
type: "link",
|
|
url: toAdrHref(cleanTarget),
|
|
children: [{ type: "text", value: label }],
|
|
});
|
|
}
|
|
|
|
lastIndex = match.index + match[0].length;
|
|
}
|
|
|
|
if (!matched) return;
|
|
|
|
if (lastIndex < source.length) {
|
|
replacementNodes.push({
|
|
type: "text",
|
|
value: source.slice(lastIndex),
|
|
});
|
|
}
|
|
|
|
parent.children.splice(index, 1, ...replacementNodes);
|
|
return [SKIP, index + replacementNodes.length];
|
|
});
|
|
};
|
|
}
|