Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save magma-chili/207c9d4d95df4dd6f76c87d8efa34e83 to your computer and use it in GitHub Desktop.
Save magma-chili/207c9d4d95df4dd6f76c87d8efa34e83 to your computer and use it in GitHub Desktop.
πŸͺ‘ Table of notes with a particular heading and the heading's content πŸ“Œ
Error in user YAML: Alias parsing is not enabled.
---
title: &title "Output a table of notes with a particular heading and the heading's content"
UUID: &UUID 7fa1055a-f5bf-4d85-b3d6-32e4a3c2105b
alias:
  - *title
  - *UUID
created: 2023-04-26T04:31:24
---

%%

[!metadata]- Dataview fields

  • [description:: A dataviewjs snippet to search a vault for notes with a given heading in a given path and output a link to those notes in one column with the heading's content in the other. Optionally, output subheadings too which is more resource intensive.]
  • [directions:: Copy the [[#Code|codeblock]] to your vault and modify the value of findHeading to the heading your want to find (without hash # characters), as well as the value of rootFolderPath to the folder you would like to search (leave blank for the root of your vault).]
  • [type of:: [[Dataview JS snippets πŸ“‘|Dataview JS snippet]]]

%%

%% πŸͺ¦ Title header %%

$=dv.header(1, jsinit.dvTitleType(dv))

[!abstract] $=jsinit.dvDescription(dv)

[!metadata] $=await jsinit.dvView(dv, 'table', {preset: 'meta', files: true})

~~~dataviewjs
const findHeading = 'Notes',
  rootFolderPath = 'path/to/folder',
  filesWithHeading = app.metadataCache
    .getCachedFiles()
    .filter((p) => RegExp(`^${rootFolderPath}`).exec(p))
    .filter((p) => app.metadataCache.getCache(p).headings?.map((h) => h.heading === findHeading)),
  getPageContentByHeading = async (path, heading, subheadings = false) => {
    const tfile = app.vault.getAbstractFileByPath(path),
      { headings } = app.metadataCache.getFileCache(tfile),
      foundHeading = headings.find((e) => e.heading === heading);

    let content, siblingHeadings, nextHeading, startPos, endPos, headingContent;
    if (foundHeading) {
      content = await app.vault.cachedRead(tfile);
      siblingHeadings = headings.filter((e) => e.level === foundHeading?.level);
      nextHeading = subheadings
        ? siblingHeadings[siblingHeadings.indexOf(foundHeading) + 1]
        : headings[headings.indexOf(foundHeading) + 1];
      startPos = foundHeading.position.end.offset + 1;
      endPos = nextHeading?.position.start.offset - 1;
      headingContent = endPos ? content.substring(startPos, endPos).trim() : content.substring(startPos).trim();

      const matchImageEmbeds = /!\[\[(?<file>.*?\.(?:png|jpg))(?:\|(?<caption>.*?))?\]\]|\(?<caption>.*?)\)\[(?<file>.*?)\]/g;
      let foundImageEmbed;
      while ((foundImageEmbed = matchImageEmbeds.exec(headingContent)) !== null) {
        headingContent = headingContent.replace(
          foundImageEmbed[0],
          `<img src="${app.vault.getResourcePath(
            app.vault.getFiles().find((f) => f.name === foundImageEmbed.groups.file) ?? {
              path: foundImageEmbed.groups.file,
            }
          )}">${foundImageEmbed.groups.caption ?? ''}</img>`
        );
      }
    }

    return headingContent;
  },
  tableData = (
    await Promise.all(
      filesWithHeading.map(async (p) => [
        dv.page(p).file.link,
        await getPageContentByHeading(p, findHeading /* , true */),
      ])
    )
  ).filter((v) => v[1])
  .sort((a, b) => b[0].path.localeCompare(a[0].path, 'en', { sensitivity: 'base' }));

await dv.table(['Path', findHeading], dv.array(tableData));
dv.container.querySelectorAll('tr > td ~ td').forEach((e) => (e.style.whiteSpace = 'normal'));

~~~

Example %% fold %%

const findHeading = 'πŸ“₯ Inbox',
  rootFolderPath = '2 - Areas πŸ—ΊοΈ/Periodic Notes πŸ“Œ',
  filesWithHeading = app.metadataCache
    .getCachedFiles()
    .filter((p) => RegExp(`^${rootFolderPath}`).exec(p))
    .filter((p) => app.metadataCache.getCache(p).headings?.map((h) => h.heading === findHeading)),
  getPageContentByHeading = async (path, heading, subheadings = false) => {
    const tfile = app.vault.getAbstractFileByPath(path),
      { headings } = app.metadataCache.getFileCache(tfile),
      foundHeading = headings.find((e) => e.heading === heading);

    let content, siblingHeadings, nextHeading, startPos, endPos, headingContent;
    if (foundHeading) {
      content = await app.vault.cachedRead(tfile);
      siblingHeadings = headings.filter((e) => e.level === foundHeading?.level);
      nextHeading = subheadings
        ? siblingHeadings[siblingHeadings.indexOf(foundHeading) + 1]
        : headings[headings.indexOf(foundHeading) + 1];
      startPos = foundHeading.position.end.offset + 1;
      endPos = nextHeading?.position.start.offset - 1;
      headingContent = endPos ? content.substring(startPos, endPos).trim() : content.substring(startPos).trim();

      const matchImageEmbeds = /!\[\[(?<file>.*?\.(?:png|jpg))(?:\|(?<caption>.*?))?\]\]/g;
      let foundImageEmbed;
      while ((foundImageEmbed = matchImageEmbeds.exec(headingContent)) !== null) {
        headingContent = headingContent.replace(
          foundImageEmbed[0],
          `<img src="${app.vault.getResourcePath(
            app.vault.getFiles().find((f) => f.name === foundImageEmbed.groups.file) ?? {
              path: foundImageEmbed.groups.file,
            }
          )}">${foundImageEmbed.groups.caption ?? ''}</img>`
        );
      }
    }

    return headingContent;
  },
  tableData = (
    await Promise.all(
      filesWithHeading.map(async (p) => [
        dv.page(p).file.link,
        await getPageContentByHeading(p, findHeading, true),
      ])
    )
  ).filter((v) => v[1])
  .sort((a, b) => b[0].path.localeCompare(a[0].path, 'en', { sensitivity: 'base' }));

await dv.table(['Path', findHeading], dv.array(tableData).limit(60));
dv.container.querySelectorAll('tr > td ~ td').forEach((e) => (e.style.whiteSpace = 'normal'));

%% 🍞 Breadcrumbs %%

await jsinit.dvView(dv, 'breadcrumbs', {direction: 'up'})
await jsinit.dvView(dv, 'breadcrumbs', {direction: 'down'})

%% πŸ“‚ Sub-files %%

await jsinit.dvView(dv, 'table', { files: true })

πŸ“₯ Inbox

β˜‘οΈ New Tasks

  • #βœ”οΈ/fix [[#^41d4dd|embed bugs]] %% fold %%
    • [<] [timestamp:: 2023-04-26T07:05:46]

πŸͺ΅ Logs

⌚ Changes

  • #πŸͺ΅/modified the snippet to embed images properly %% fold %%
    • [<] [timestamp:: 2023-04-26T06:17:04]

πŸ—’οΈ Notes

πŸ› Bugs

  • [i] #πŸ› doesn't output all embeds correctly %% fold %% ^41d4dd
    • only matches [[wikilink]] not [markdown links](markdownn links)
    • only matches by filename, which will fail if a link uses a path
    • [<] [timestamp:: 2023-04-26T04:33:23]
@magma-chili
Copy link
Author

magma-chili commented Aug 3, 2023

Switched from app.vault.read(...) to app.vault.cachedRead(...) to improve performance.

Thanks @mariomui! https://discord.com/channels/686053708261228577/1014259487445622855/1136683511244390544

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