diff --git a/src/components/StackedNote.vue b/src/components/StackedNote.vue index 93645df..0526763 100644 --- a/src/components/StackedNote.vue +++ b/src/components/StackedNote.vue @@ -35,6 +35,7 @@ import { useNoteOverlay } from '@/hooks/useNoteOverlay.hook' import { useFocus } from '@/hooks/useFocus.hook' import { useImages } from '@/hooks/useImages.hook' import LinkedNotes from '@/components/LinkedNotes.vue' +import { filenameToNoteTitle } from '@/utils/noteTitle' export default defineComponent({ name: 'StackedNote', @@ -56,7 +57,7 @@ export default defineComponent({ const titleClassName = computed(() => `title-${className.value}`) const { displayNoteOverlay } = useNoteOverlay(className.value, props.index) - const displayedTitle = computed(() => props.title.replaceAll('/', ' / ')) + const displayedTitle = computed(() => filenameToNoteTitle(props.title)) watch(content, () => { if (content.value) { diff --git a/src/data/DataType.enum.ts b/src/data/DataType.enum.ts index 3f1e5c2..a3e4f6c 100644 --- a/src/data/DataType.enum.ts +++ b/src/data/DataType.enum.ts @@ -1,5 +1,6 @@ export enum DataType { GithubAccessToken = 'GithubAccessToken', FavoriteRepo = 'FavoriteRepo', - Note = 'Note' + Note = 'Note', + BacklinkNote = 'BacklinkNote' } diff --git a/src/hooks/useBacklinks.hook.ts b/src/hooks/useBacklinks.hook.ts deleted file mode 100644 index cb8eebd..0000000 --- a/src/hooks/useBacklinks.hook.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { useUserRepoStore } from '@/modules/repo/store/userRepo.store' -import { watch } from 'vue' - -export const useBackLinks = () => { - const store = useUserRepoStore() - - watch(store, () => { - if (!store.userSettings?.backlink) { - return - } - - console.log("let's go backlinks!") - }) -} diff --git a/src/hooks/useComputeBacklinks.hook.ts b/src/hooks/useComputeBacklinks.hook.ts new file mode 100644 index 0000000..78c62b5 --- /dev/null +++ b/src/hooks/useComputeBacklinks.hook.ts @@ -0,0 +1,94 @@ +import { data } 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 { watch } from 'vue' + +const isMarkdown = (filename?: string) => filename?.endsWith('.md') ?? false + +export const useComputeBacklinks = () => { + const store = useUserRepoStore() + + watch(store, async () => { + if (!store.userSettings?.backlink) { + return + } + + const backlinks: Map = new Map() + + for (const file of store.files) { + if (!isMarkdown(file.path) || !file.sha) { + continue + } + const fileBacklinkId = data.generateId(DataType.BacklinkNote, file.sha) + const fileBacklink = await data.get( + fileBacklinkId + ) + if (fileBacklink) { + continue + } + 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) ?? [] + backlinks.set(backlinkFile.sha, [ + ...previousBacklinks, + { + sha: file.sha, + title: filenameToNoteTitle(file.path ?? '') + } + ]) + } + } + + for (const [sha, fileBacklinks] of backlinks) { + const fileBacklinkId = data.generateId(DataType.BacklinkNote, sha) + const backlinkNote: BacklinkNote = { + _id: fileBacklinkId, + $type: DataType.BacklinkNote, + sha: sha, + links: fileBacklinks + } + + await data.add(backlinkNote) + } + + const backlinksInDb = await data.getAll< + DataType.BacklinkNote, + BacklinkNote + >({ + prefix: DataType.BacklinkNote + }) + + console.log(backlinksInDb.filter((b) => b.links.length)) + }) +} diff --git a/src/hooks/useFile.hook.ts b/src/hooks/useFile.hook.ts index 0062d4c..2734cc5 100644 --- a/src/hooks/useFile.hook.ts +++ b/src/hooks/useFile.hook.ts @@ -18,6 +18,7 @@ export const useFile = (sha: string, retrieveContent = true) => { return } content.value = render(fileContent) + return content.value } const getCachedFileContent = async (): Promise => { diff --git a/src/hooks/useLinks.hook.ts b/src/hooks/useLinks.hook.ts index 5322473..a483bd4 100644 --- a/src/hooks/useLinks.hook.ts +++ b/src/hooks/useLinks.hook.ts @@ -1,9 +1,8 @@ import { noteEventBus } from '@/bus/noteEventBus' import { useUserRepoStore } from '@/modules/repo/store/userRepo.store' +import { isExternalLink } from '@/utils/link' import { onUnmounted } from '@vue/runtime-core' -const LINKS = ['http://', 'https://'] - export const useLinks = (className: string, sha?: string) => { const store = useUserRepoStore() @@ -16,7 +15,7 @@ export const useLinks = (className: string, sha?: string) => { return } - if (LINKS.some((link) => href.startsWith(link))) { + if (isExternalLink(href)) { window.open(href, '_blank') return } @@ -50,9 +49,7 @@ export const useLinks = (className: string, sha?: string) => { return } - const isExternalLink = LINKS.some((link) => href.startsWith(link)) - - if (isExternalLink) { + if (isExternalLink(href)) { element.classList.add('external-link') } }) diff --git a/src/modules/note/models/Backlink.ts b/src/modules/note/models/Backlink.ts new file mode 100644 index 0000000..749f332 --- /dev/null +++ b/src/modules/note/models/Backlink.ts @@ -0,0 +1,4 @@ +export interface Backlink { + sha: string + title: string +} diff --git a/src/modules/note/models/BacklinkNote.ts b/src/modules/note/models/BacklinkNote.ts new file mode 100644 index 0000000..91001ca --- /dev/null +++ b/src/modules/note/models/BacklinkNote.ts @@ -0,0 +1,8 @@ +import { DataType } from '@/data/DataType.enum' +import { Model } from '@/data/models/Model' +import { Backlink } from '@/modules/note/models/Backlink' + +export interface BacklinkNote extends Model { + sha: string + links: Backlink[] +} diff --git a/src/utils/link.ts b/src/utils/link.ts new file mode 100644 index 0000000..e680698 --- /dev/null +++ b/src/utils/link.ts @@ -0,0 +1,4 @@ +export const LINKS = ['http://', 'https://'] + +export const isExternalLink = (href: string) => + LINKS.some((link) => href.startsWith(link)) diff --git a/src/utils/noteTitle.ts b/src/utils/noteTitle.ts new file mode 100644 index 0000000..d3a6865 --- /dev/null +++ b/src/utils/noteTitle.ts @@ -0,0 +1,2 @@ +export const filenameToNoteTitle = (title: string) => + title.replaceAll('/', ' / ') diff --git a/src/views/Home.vue b/src/views/Home.vue index 78f0b60..6cce364 100644 --- a/src/views/Home.vue +++ b/src/views/Home.vue @@ -12,7 +12,7 @@ import { defineComponent, defineAsyncComponent, computed } from 'vue' import { useQueryStackedNotes } from '@/hooks/useQueryStackedNotes.hook' import NewVersion from '@/components/NewVersion.vue' import Authorize from '@/components/Authorize.vue' -import { useBackLinks } from '@/hooks/useBacklinks.hook' +import { useComputeBacklinks } from '@/hooks/useComputeBacklinks.hook' const FluxNote = defineAsyncComponent(() => import('@/components/FluxNote.vue')) @@ -34,7 +34,7 @@ export default defineComponent({ }, setup(props) { const { resetStackedNotes } = useQueryStackedNotes() - useBackLinks() + useComputeBacklinks() return { resetStackedNotes,