- Restore explicit overflow-y:auto on #main-app for mobile (removed in
63f5d64) — implicit coercion from overflow-x:auto is not reliable in
all Safari/WebKit versions.
- Override position:sticky on .readme to position:relative on mobile.
The desktop sticky (left:0) is correct for horizontal scroll, but on
mobile vertical scroll it pinned the 100dvh-tall readme across the
entire viewport, hiding all stacked notes behind it.
122 lines
3.4 KiB
TypeScript
122 lines
3.4 KiB
TypeScript
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<void> } }
|
|
).scheduler.yield()
|
|
: new Promise<void>((r) => setTimeout(r, 0))
|
|
|
|
export const useComputeBacklinks = () => {
|
|
const store = useUserRepoStore()
|
|
|
|
watch(
|
|
() => store.files,
|
|
async () => {
|
|
await new Promise<void>((r) => setTimeout(r, 300))
|
|
|
|
if (!store.userSettings?.backlink) {
|
|
return
|
|
}
|
|
|
|
let notifiedForComputation = false
|
|
|
|
const backlinks: Map<string, Backlink[]> = 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 })
|
|
}
|
|
}
|
|
)
|
|
}
|