-
-
Save tuchandra/c1481ace859959cce87fcd5b4d48ea79 to your computer and use it in GitHub Desktop.
Show "contextual backlinks" in Obsidian using Dataview and CustomJS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class DataviewUtils { | |
/** | |
* Usage (requires Dataview, CustomJS): | |
* | |
```dataviewjs | |
const { DataviewUtils } = customJS; | |
await DataviewUtils.prettyInlinks(dv); // or makeCalendar( | |
``` | |
* | |
*/ | |
/** | |
* Given a list of filenames or tags, find all files that link to any of them. | |
* Load the files (asynchronously), then filter the contents to the blocks | |
* containing the links. Return an object of { link, text } with the filtered | |
* ("relevant") blocks. | |
* | |
* @param {DataViewAPI} dv | |
* @param {string[]} linkTargets names of files for which we want inlinks | |
* @returns { link: dv.link, blocks: string[] }[] | |
*/ | |
async getInlinkingBlocks(dv, linkTargets) { | |
const currentLink = dv.current().file.link; | |
const pagesLinkingToTargets = linkTargets | |
.map((f) => dv.page(f)) | |
.filter((page) => page.file.inlinks.length > 0); | |
const inlinks = dv | |
.array( | |
pagesLinkingToTargets | |
.map((page) => [...page.file.inlinks]) | |
.flat() | |
.filter((link) => !dv.equal(link, currentLink)) | |
) | |
.distinct(); | |
const pages = await Promise.all( | |
inlinks.map(async (link) => { | |
const pageContent = await dv.io.load(link); | |
return { link, pageContent }; | |
}) | |
); | |
/** | |
* pageContent is just a string of the raw text on a page, so we have to: | |
* - split at line breaks, so we have a string[] of lines | |
* - remove the 'navigation' blocks from our daily notes | |
* - filter to blocks that contain any of the items in searchPages (e.g., dates) | |
* - for each of those ... | |
* - remove possible leading whitespace & bullet | |
* - remove possible header + more whitespace after | |
* | |
* After that, associate the remaining (cleaned & filtered) blocks with the | |
* link to that page, returning an array of { link, block[] } objects. | |
*/ | |
// This is tailored to my vault and should probably a function param | |
const excludePhrases = ["^exclude", "synced::", "read::"]; | |
const blocks = pages.map(({ link, pageContent }) => ({ | |
link, | |
blocks: [ | |
...pageContent | |
.split("\n") | |
// Exclude tag navigation | |
.filter((block) => block.slice(0, 2) !== "<<") | |
// Exclude any blocks with the phrases above | |
.filter((block) => excludePhrases.every(x => !block.includes(x))) | |
.filter((block) => | |
linkTargets.some((target) => block.includes(`[[${target}`)) | |
) | |
.map((block) => block.replace(/^\s*- /, "").replace(/^#*\s+/, "")), | |
], | |
})); | |
return blocks; | |
} | |
/** | |
* | |
* @param {DataViewAPI} dv | |
* @returns formatted inlinks with the source & containing block | |
*/ | |
async prettyInlinks(dv) { | |
const terms = [dv.current().file.name]; | |
const linksAndBlocks = await this.getInlinkingBlocks(dv, terms); | |
// pages: dv.page[] of the original pages to which we wanted to find links | |
const output = linksAndBlocks.map(({ link, blocks }) => | |
(blocks.length === 0) ? | |
null : | |
dv.paragraph( | |
// We are making a string that has the Markdown we want | |
`> [!quote] ${link} (_${blocks.length}_)` + | |
"\n" + | |
blocks.map((text) => `> ${text}`).join("\n> \n> --- \n> \n") | |
) | |
); | |
return output; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Demo / example (using a note titled "Readwise" in my vault) -