import { watch } from "vue" import { backlinkEventBus } from "@/bus/backlinkEventBus" import { data, generateId } from "@/data/data" import { DataType } from "@/data/DataType.enum" import { useFile } from "@/hooks/useFile.hook" import { Backlink } from "@/modules/note/models/Backlink" import { BacklinkNote } from "@/modules/note/models/BacklinkNote" import { resolvePath } from "@/modules/repo/services/resolvePath" import { useUserRepoStore } from "@/modules/repo/store/userRepo.store" import { isExternalLink } from "@/utils/link" import { filenameToNoteTitle } from "@/utils/noteTitle" import { confirmMessage } from "@/utils/notif" const isMarkdown = (filename?: string) => filename?.endsWith(".md") ?? false const yieldToMain = () => "scheduler" in globalThis ? ( globalThis as unknown as { scheduler: { yield: () => Promise } } ).scheduler.yield() : new Promise((r) => setTimeout(r, 0)) export const useComputeBacklinks = () => { const store = useUserRepoStore() watch( () => store.files, async () => { await new Promise((r) => setTimeout(r, 300)) if (!store.userSettings?.backlink) { return } let notifiedForComputation = false const backlinks: Map = new Map() for (const file of store.files) { await yieldToMain() if (!isMarkdown(file.path) || !file.sha) { continue } const fileBacklinkId = generateId(DataType.BacklinkNote, file.sha) const fileBacklink = await data.get< DataType.BacklinkNote, BacklinkNote >(fileBacklinkId) if (fileBacklink) { continue } if (!backlinks.has(file.sha)) { backlinks.set(file.sha, []) } const { getContent } = useFile(file.sha, false) const note = await getContent() if (!note) { return } const parser = new DOMParser() const htmlDoc = parser.parseFromString(note, "text/html") const links = htmlDoc.querySelectorAll("a") for (const link of links) { const href = link.getAttribute("href") ?? "" if (isExternalLink(href) || !isMarkdown(href)) { continue } const path = resolvePath(file.path ?? "", href) const backlinkFile = store.files.find((file) => file.path === path) if (!backlinkFile?.sha || !backlinkFile?.path) { continue } const previousBacklinks = backlinks.get(backlinkFile.sha) ?? [] if (previousBacklinks.find((bl) => bl.sha === file.sha)) { continue } if (!notifiedForComputation) { notifiedForComputation = true confirmMessage("Updating backlinks...") } backlinks.set(backlinkFile.sha, [ ...previousBacklinks, { sha: file.sha, title: filenameToNoteTitle(file.path ?? "") } ]) } } for (const [sha, fileBacklinks] of backlinks) { const fileBacklinkId = generateId(DataType.BacklinkNote, sha) const backlinkNote: BacklinkNote = { _id: fileBacklinkId, $type: DataType.BacklinkNote, sha: sha, links: fileBacklinks } await data.update(backlinkNote) backlinkEventBus.emit({ fileSha: sha }) } } ) }