Skip to content

Instantly share code, notes, and snippets.

@tuchandra
Last active January 10, 2023 03:07
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save tuchandra/c1481ace859959cce87fcd5b4d48ea79 to your computer and use it in GitHub Desktop.
Save tuchandra/c1481ace859959cce87fcd5b4d48ea79 to your computer and use it in GitHub Desktop.
Show "contextual backlinks" in Obsidian using Dataview and CustomJS
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;
}
}
@tuchandra
Copy link
Author

Demo / example (using a note titled "Readwise" in my vault) -

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment