Use Obsidian link parsing for wikilinks

This commit is contained in:
murashit 2026-05-26 16:46:47 +09:00
parent 41a96482c7
commit 1959bf8c0d
5 changed files with 44 additions and 25 deletions

View file

@ -1,5 +1,6 @@
import type { SkillMetadata } from "../../../generated/app-server/v2/SkillMetadata";
import type { UserInput } from "../../../generated/app-server/v2/UserInput";
import { parseObsidianWikiLink } from "../../../shared/obsidian/wikilinks";
export interface ParsedWikiLink {
raw: string;
@ -76,24 +77,8 @@ export function parsedSkillReferences(text: string): string[] {
}
function parseWikiLink(raw: string): ParsedWikiLink | null {
const separator = raw.indexOf("|");
const linkPart = (separator === -1 ? raw : raw.slice(0, separator)).trim();
const display = separator === -1 ? "" : raw.slice(separator + 1).trim();
if (!linkPart) return null;
const subpathStart = firstSubpathIndex(linkPart);
const target = subpathStart === -1 ? linkPart : linkPart.slice(0, subpathStart).trim();
const subpath = subpathStart === -1 ? "" : linkPart.slice(subpathStart).trim();
if (!target) return null;
return { raw, target, subpath, display };
}
function firstSubpathIndex(linkPart: string): number {
const headingIndex = linkPart.indexOf("#");
const blockIndex = linkPart.indexOf("^");
if (headingIndex === -1) return blockIndex;
if (blockIndex === -1) return headingIndex;
return Math.min(headingIndex, blockIndex);
const parsed = parseObsidianWikiLink(raw);
return parsed ? { raw, ...parsed } : null;
}
function firstEnabledSkillByName(skills: SkillMetadata[]): Map<string, SkillMetadata> {

View file

@ -0,0 +1,23 @@
import { parseLinktext } from "obsidian";
export interface ParsedObsidianWikiLink {
target: string;
subpath: string;
display: string;
}
export function parseObsidianWikiLink(raw: string): ParsedObsidianWikiLink | null {
const trimmed = raw.trim();
if (!trimmed) return null;
const separator = trimmed.indexOf("|");
const linktext = (separator === -1 ? trimmed : trimmed.slice(0, separator)).trim();
const display = separator === -1 ? "" : trimmed.slice(separator + 1).trim();
if (!linktext) return null;
const parsed = parseLinktext(linktext);
const target = parsed.path.trim();
const subpath = parsed.subpath.trim();
if (!target) return null;
return { target, subpath, display };
}

View file

@ -1,3 +1,5 @@
import { parseObsidianWikiLink } from "../obsidian/wikilinks";
export function shortSignature(value: string): string {
let hash = 0;
for (let index = 0; index < value.length; index += 1) {
@ -19,13 +21,13 @@ export function renderTextWithWikiLinks(parent: HTMLElement, text: string, openL
const rawLink = match[1];
const rawMatch = match[0];
if (rawLink === undefined) continue;
const separator = rawLink.indexOf("|");
const target = (separator === -1 ? rawLink : rawLink.slice(0, separator)).trim();
const label = (separator === -1 ? rawLink : rawLink.slice(separator + 1)).trim() || target;
const parsed = parseObsidianWikiLink(rawLink);
if (target.length === 0) {
if (!parsed) {
parent.appendChild(doc.createTextNode(rawMatch));
} else {
const target = `${parsed.target}${parsed.subpath}`;
const label = parsed.display || target;
const link = parent.createEl("a", {
cls: "internal-link codex-panel__wikilink",
text: label,

View file

@ -24,15 +24,15 @@ describe("view DOM helpers", () => {
const parent = document.createElement("div");
const openLink = vi.fn();
renderTextWithWikiLinks(parent, "See [[Target Note|label]] and [[Other]].", openLink);
renderTextWithWikiLinks(parent, "See [[Target Note#Heading|label]] and [[Other]].", openLink);
const links = parent.querySelectorAll<HTMLAnchorElement>("a.internal-link");
const firstLink = expectPresent(links[0]);
expect(links).toHaveLength(2);
expect(firstLink.textContent).toBe("label");
expect(firstLink.getAttribute("href")).toBe("Target Note");
expect(firstLink.getAttribute("href")).toBe("Target Note#Heading");
firstLink.click();
expect(openLink).toHaveBeenCalledWith("Target Note");
expect(openLink).toHaveBeenCalledWith("Target Note#Heading");
});
});

View file

@ -89,6 +89,15 @@ export function sortSearchResults(results: { match: { score: number } }[]): void
results.sort((a, b) => b.match.score - a.match.score);
}
export function parseLinktext(linktext: string): { path: string; subpath: string } {
const headingIndex = linktext.indexOf("#");
const blockIndex = linktext.indexOf("^");
const subpathStart = headingIndex === -1 ? blockIndex : blockIndex === -1 ? headingIndex : Math.min(headingIndex, blockIndex);
return subpathStart === -1
? { path: linktext, subpath: "" }
: { path: linktext.slice(0, subpathStart), subpath: linktext.slice(subpathStart) };
}
export class Modal {
readonly contentEl: HTMLElement;