Automerge backed Markdown editor for the Atmosphere
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576import { getLinkpath } from '../../plugin/obsidian-stubs'import type { CachedMetadata, EmbedCache, LinkCache } from './types'
export interface LinkgraphFile { url: string path: string metadata: CachedMetadata recordReferences?: readonly string[]}
export interface LinkgraphResult { generation: number resolvedLinks: Record<string, Record<string, number>> unresolvedLinks: Record<string, Record<string, number>>}
const targetKey = (value: string): string => { const inner = value.startsWith('[[') && value.endsWith(']]') ? value.slice(2, -2) : value return getLinkpath(inner.split('|', 1)[0]).toLowerCase().replace(/\.md$/, '')}
/** Pure linkgraph reducer. The worker and synchronous compatibility path share this algorithm. */export function deriveLinkgraph(files: readonly LinkgraphFile[], generation = 0): LinkgraphResult { const byPath = new Map<string, LinkgraphFile>() const byName = new Map<string, LinkgraphFile[]>() for (const file of files) { byPath.set(targetKey(file.path), file) const key = file.path.split('/').pop()!.replace(/\.md$/i, '').toLowerCase() const matches = byName.get(key) ?? [] matches.push(file) byName.set(key, matches) const fullName = file.path.split('/').pop()!.toLowerCase() if (fullName !== key) { const named = byName.get(fullName) ?? [] named.push(file) byName.set(fullName, named) } }
const resolvedLinks: Record<string, Record<string, number>> = {} const unresolvedLinks: Record<string, Record<string, number>> = {} for (const source of files) { const resolved: Record<string, number> = {} const unresolved: Record<string, number> = {} const references: (LinkCache | EmbedCache)[] = [ ...(source.metadata.links ?? []), ...(source.metadata.embeds ?? []), ] // Keep Record occurrences separate from positioned Markdown references; // both count in the path graph, but only Records need wikilink unwrapping. const allTargets = [...references.map(reference => ({ raw: reference.link, record: false })), ...(source.recordReferences ?? []).map(raw => ({ raw, record: true }))] for (const { raw: rawReference, record } of allTargets) { const link = targetKey(rawReference) let target: LinkgraphFile | undefined if (link.includes('/')) { target = byPath.get(link) } else { const sourceDir = source.path.slice(0, source.path.lastIndexOf('/')) target = byPath.get(sourceDir ? `${sourceDir}/${link}` : link) if (!target) target = byName.get(link)?.find(file => !file.path.includes('/')) if (!target) target = byName.get(link)?.[0] } if (target) { resolved[target.path] = (resolved[target.path] ?? 0) + 1 } else { const unresolvedKey = record && rawReference.startsWith('[[') && rawReference.endsWith(']]') ? rawReference.slice(2, -2).split('|', 1)[0]! : rawReference unresolved[unresolvedKey] = (unresolved[unresolvedKey] ?? 0) + 1 } } resolvedLinks[source.path] = resolved unresolvedLinks[source.path] = unresolved } return { generation, resolvedLinks, unresolvedLinks }}