Return a string of text and the backlink via dataview

Hi
I have found a piece of brilliant code which is working very well. It collects text from my notes that includes a specific word. But I would also like to show the backlink - the note where the text is collected from. The piece of code:

dv.list(dv.pages().file.lists.where(t => t.text.includes("Specifik word")).map((t ) => t.text))

I would like it to render something like this:

  • Backlink. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam pretium nisi eu pellentesque feugiat. Pellentesque efficitur leo ante, at Specific word risus convallis quis. Duis tellus nulla, varius eu tortor sollicitudin, convallis euismod diam.

I really want to learn javascript but I don’t know how to insert the backlink because it seems like it’s a lambda function and I don’t know how to disassemble it. I have tried to read up on what map does. I have searched Google for the piece of code because I think that I’m not the only one who has made use of it but nothing returns. I have also asked ChatGBT which returns a useless piece of code. If someone can make any sense of it, I’ll paste it here:

dv.list(
  dv.pages().file.lists
    .where(t => t.text.includes("Specific word"))
    .map((t) => ({
      text: t.text,
      backlink: `[${t.file.basename}](obsidian://open?vault=${encodeURIComponent(t.file.vault.name)}&file=${encodeURIComponent(t.file.name)})`
    }))
)

I hope you can help. :pray:Sorry for my bad english

Sadly, a pure list item is not legible as a link target. You can only link to the corresponding file it originates from.

In general there are three plus one link targets: file, heading and block, and if you’re using task queries it’ll link back to the origin of the task.

Probably somewhat of an overkill (I’m not much on javascript myself), but you can use something like this:

```dataviewjs
// Query all pages
const allPages = dv.pages("");

// Crawl content and render list
const pages = await Promise.all(
    allPages.map(async (page) => {
        const content = await dv.io.load(page.file.path);
        const regexPattern = /.*\bSEARCHTERM\b.*/gm;
        const regexMatches = content.match(regexPattern);

        if (regexMatches && regexMatches.length > 0) {
            return {
                link: page.file.link,
                content: regexMatches.join('\n\n')
            };
        }

        return null;
    })
);

// Filter out null entries and render the result list
const processedPages = pages.filter(p => p !== null);

dv.list(
    processedPages.map(p => `${p.link}\n${p.content}`)
);
```
  • If you want to specify a folder, change const allPages = dv.pages(""); to const allPages = dv.pages('"FOLDERNAME"');
  • Change SEARCHTERM to your own string you want returned. Spaces between strings should be allowed but you may do well to use \s for space, like so: cats\sand\sdogs.
  • You can remove the \b’s from const regexPattern = /.*\bSEARCHTERM\b.*/gm; like so: const regexPattern = /.*SEARCHTERM.*/gm; to get more – sometimes unwanted – results.

Script returns the paragraph(s) containing searchterm specified.

Glædelig jul!

It works perfectly :partying_face: Thank you so much :pray:
Glædelig jul til dig også :wink:

1 Like

I know I ask for much but is it possible to incorporate following code:

     const matches = contents.match(new RegExp(regex, 'sg')) || [];
      for (const callout of matches) {
        const match = callout.match(new RegExp(regex, 's'));
        let content = match[1].replace(/>/g, '>'); // Replace > with >
        content = content.replace(/^>\s*/gm, ''); // Remove > and spaces at the start of each line

This code is from another snippet I have from ChatGBT. It collets text from a callout and trim it so the result is without >. I have tried to paste it into the code you have written but can’t seem to get it right.

Below is the entire code if anyone is interested :wink: It works very well but is very specific and maybe overkill.

code
const {LangCon} = customJS; //My own function which convert english weekdays into danish
const startDate = '<% startDateOfWeek %>'; // Define the start date of the specific week (Monday)
const endDate = '<% endDateOfWeek %>'; // Define the end date of the specific week (Sunday)
const pages = dv.pages('"Pages"');
const regex = /<span class="css-name-here">(.*?)<\/span>/s;
const rows = {
  'Mandag': [], // Danish weekday names
  'Tirsdag': [],
  'Onsdag': [],
  'Torsdag': [],
  'Fredag': [],
  'Lørdag': [],
  'Søndag': []
};
for (const page of pages) {
  const fileName = page.file.name; // Get the file name (e.g., "2023-12-01")
  // Extract the date from the file name
  const fileDate = fileName.match(/^(\d{4}-\d{2}-\d{2})/);
  if (fileDate) {
    const contentDate = fileDate[1];
    if (contentDate >= startDate && contentDate <= endDate) {
      const file = app.vault.getAbstractFileByPath(page.file.path);
      const contents = await app.vault.read(file); // Read file
      // Extract the summary via regex
      const matches = contents.match(new RegExp(regex, 'sg')) || [];
      for (const callout of matches) {
        const match = callout.match(new RegExp(regex, 's'));
        let content = match[1].replace(/&gt;/g, '>'); // Replace &gt; with >
        content = content.replace(/^>\s*/gm, ''); // Remove > and spaces at the start of each line
        const day = new Date(contentDate).toLocaleString('en', { weekday: 'long' });
        const danishDay = LangCon.DayCon(day); // Convert English to Danish weekday
        rows[danishDay].push(content);
      }
    }
  }
}
let text = "";
// Create a list organized by days of the week
for (const [day, content] of Object.entries(rows)) {
  if (content.length > 0) {
    text += `<span class="css-name-here">\n`;
    text += `<h2 class="css-name-here">${day}</h2>\n`;
    content.forEach(item => text += `${item}\n`);
    text += `</span>\n`;
  }
}
dv.span(text);

I was stuck in the chimney…but here we go:

```dataviewjs
// Query all pages within the specified folder
const allPages = dv.pages("");

// Crawl content and render list
const pages = await Promise.all(
    allPages.map(async (page) => {
        const content = await dv.io.load(page.file.path);
        const regexPattern = /.*\bSEARCHTERM\b.*/gm;
        const regexMatches = content.match(regexPattern);

        if (regexMatches && regexMatches.length > 0) {
            // Apply replacements to each matched content before storing in pages
            const processedContent = regexMatches
                .map(match => {
                    let processed = match.replace(/&gt;/g, '>'); // Replace &gt; with >
                    processed = processed.replace(/^>\s*/gm, ''); // Remove > and spaces at the start of each line
                    return processed;
                })
                .join('<br><br>'); // Separate each processed match with two line breaks

            return {
                link: page.file.link,
                content: processedContent
            };
        }

        return null;
    })
);

// Filter out null entries and build the list string with proper formatting
const listOutput = pages
    .filter(p => p !== null)
    .map(p => `${p.link}\n${p.content}` + `<br><br>`); // Include breaks between list items as well

// Output the formatted list
dv.list(listOutput);
```
  • Using <br><br> instead of \n\n for line breaks as the latter didn’t cut it for me.
  • You can use processed = processed.replace(/(^>\s*)|(^-\s*)/gm, ''); to remove list dashes as well.
1 Like

Thank you again :pray: - it is exactly what I want :grin:

Not exactly what you wanted…I forgot to highlight the searchterm:

```dataviewjs
// Pages to query
const allPages = dv.pages("");

// Crawl content and render list
const pages = await Promise.all(
    allPages.map(async (page) => {
		const content = await dv.io.load(page.file.path);
        const searchTerm = "SEARCHTERM"; // Replace SEARCHTERM with your desired search term
        const regexPattern = new RegExp(`.*\\b(${searchTerm})\\b.*`, "gm");
        const regexMatches = content.match(regexPattern);

        if (regexMatches && regexMatches.length > 0) {
            // Apply replacements to each matched content before storing in pages
            const processedContent = regexMatches
                .map(match => {
                    let processed = match.replace(/&gt;/g, '>'); // Replace &gt; with >
                    processed = processed.replace(/^>\s*/gm, ''); // Remove > and spaces at the start of each line
					processed = processed.replace(new RegExp(`(${searchTerm})(?![^\\[]*\\]\\])`, "gm"), '**$&**');
                    return processed;
                })
                .join('<br><br>'); // Separate each processed match with two line breaks

            return {
                link: page.file.link,
                content: processedContent
            };
        }

        return null;
    })
);

// Filter out null entries and build the list string with proper formatting
const listOutput = pages
    .filter(p => p !== null)
    .map(p => `${p.link}\n${p.content}` + `<br><br>`); // Include breaks between list items as well

// Output the formatted list
dv.list(listOutput);
```
  • EDIT: fixed searchTerm being highlighted within Wikilinks.
  • NOTE: here you must not use \s’s for spaces. Just enter searchterm normally.
  • For proper highlighting, you can replace '**$&**' with '==$&==' if you like that better.
1 Like

The code before worked fine but thanks for the new version :pray: I also think it has become more manageable and easier for me to understand.

Now, not to pester OP or anyone else for their attention about yet more of the same, but – mainly for my own use cases to do with longform writing – I decided to make and share an upgrade of what came above, in the form of a Templater Js script with the DV query embedded, which lets the user interact with what they want output.
It seems to be working well.

<%*
const currentFile = app.workspace.getActiveFile();
if (!currentFile) return;

let folderRegex = /(.*)([/\\])/; // Default regex for full vault – all OS environments are taken into consideration

const userinput = await tp.system.suggester(
  ["Query Full Vault", "Specify Unique Folder"],
  ["Query Full Vault", "Specify Unique Folder"],
  false,
  "Choose an option:"
);

if (userinput === "Specify Unique Folder") {
    const items = app.vault.getAllLoadedFiles().filter(x => x instanceof tp.obsidian.TFolder);
    const selectedItem = (await tp.system.suggester((item) => item.path, items)).path;

    if (selectedItem) {
        // Update folderRegex based on user selection
        folderRegex = new RegExp(`(${selectedItem.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})([/\\\\])`);
    } else {
        return; // Exit if user cancels folder selection
    }
}

const searchTerm = await tp.system.prompt("Enter search term:");
if (!searchTerm) return;

const editor = app.workspace.activeEditor.editor;
const fileContent = await app.vault.read(currentFile);

const dynamicContent = `
\%\%
\`\`\`dataviewjs
// Define the folder regex for filtering allPages
const folderRegex = ${folderRegex}; // Dynamically updated folder regex
const searchTerm = "${searchTerm}"; // Injecting searchTerm here as well

// Query all pages and filter based on folderRegex
const allPages = dv.pages("").filter(page => {
    const path = page.file.path;
    return folderRegex.test(path);
});

// Crawl content and render list
const pages = await Promise.all(
    allPages.map(async (page) => {
		const content = await dv.io.load(page.file.path);
        const regexPattern = new RegExp(\`.*(\${searchTerm}).*\`, "gmi");
        const regexMatches = content.match(regexPattern);

        if (regexMatches && regexMatches.length > 0) {
            // Apply replacements to each matched content before storing in pages
            const processedContent = regexMatches
                .map(match => {
                    let processed = match.replace(/&gt;/g, '>'); // Replace &gt; with >
					processed = processed.replace(/(^>\\s*)|(^-\\s*)/gm, ''); // Remove > and - plus spaces at the start of each line
					processed = processed.replace(new RegExp(\`(\${searchTerm})(?![^\\\\[]*\\\\]\\\\])\`, "gmi"), '==\$&==');
                    return processed;
                })
                .join('<br><br>'); // Separate each processed match with two line breaks

            return {
                link: page.file.link,
                content: processedContent
            };
        }

        return null;
    })
);

// Filter out null entries and build the list string with proper formatting
const listOutput = pages
    .filter(p => p !== null)
    .map(p => \`\${p.link}\\n\${p.content}\` + \`<br><br>\`); // Include breaks between list items as well

// Output the formatted list fltering out the current file's line containing searchTerm
dv.list(listOutput.filter(line => !line.includes('const searchTerm')));
\`\`\`
\%\%
`;

const updatedContent = fileContent + '\n\n' + dynamicContent;
await app.vault.modify(currentFile, updatedContent);
_%>

Usage: save the script in your Templater folder and then in the current working editor run the Templater template and navigate through drop-downs and/or suggesters and finally enter search term.
If current working editor is set to Live Preview, search results – if any – will be printed in the file. (If there are no results, you may or will not get a notice about there being zero results, for some reason.)
Using ==highlight== format and added support for case insensitivity (changed gm to gmi).
'==\$&==' can be changed to some other markdown format, if needed.
I have removed the \b’s to have compounds also come up in the results. If anyone wants strict results, exchange const regexPattern = new RegExp(\`.*(\${searchTerm}).*\`, "gmi"); with const regexPattern = new RegExp(\`.*\\\\b(\${searchTerm})\\\\b.*\`, "gmi");.
(In the embedded DV query, backticks, dollar signs and backslashs had to be escaped with \, if anyone is interested making similar things in the future.)

The line this.app.workspace.activeLeaf.view.editor.setCursor({line: 99999, ch: 0}); can be added above const updatedContent = fileContent + '\n\n' + dynamicContent; if we want to add results to the very last line of the file.

EDIT. Changed the dv.list line to dv.list(listOutput.filter(line => !line.includes('const searchTerm'))); to filter out the unwanted line.

For anyone new with Templater and how to set it up, a tut-let of some sort I found in the following thread:

Search term accepts regular expressions so the script can be used for ‘range or so-called proximity search’: e.g. <searchterm1>.*?<searchterm2> will find the two terms in their closest vicinity of one another and the full paragraph will be printed for context. The < and > are not part of the syntax, of course.

  • Same thing as in the Obsidian search modal, but there you need to put in at least the opening / slash to indicate we want to use regex.
  • The search done in the DV query like this is more superior as one can copy out the results for further processing.

This proximity search helped me a great deal years ago when I started out being more efficient researching for material among my 3k+ books and papers (was and am using DocFetcher for PDFs).

EDIT.:
In the meantime I added abililty for further filtering:

<%*
const currentFile = app.workspace.getActiveFile();
if (!currentFile) return;

let folderRegex = /(.*)([/\\])/; // Default regex for full vault – all OS environments are taken into consideration

const userinput = await tp.system.suggester(
  ["Query Full Vault", "Specify Unique Folder"],
  ["Query Full Vault", "Specify Unique Folder"],
  false,
  "Choose an option:"
);

if (userinput === "Specify Unique Folder") {
    const items = app.vault.getAllLoadedFiles().filter(x => x instanceof tp.obsidian.TFolder);
    const selectedItem = (await tp.system.suggester((item) => item.path, items)).path;

    if (selectedItem) {
        // Update folderRegex based on user selection
        folderRegex = new RegExp(`(${selectedItem.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})([/\\\\])`);
    } else {
        return; // Exit if user cancels folder selection
    }
}

const searchTerm = await tp.system.prompt("Enter search term:");
if (!searchTerm) return;

const filterRegex = await tp.system.prompt("Exclude from results if string is present (or regex matched) in paragraph:");
const additionalFilter = await tp.system.prompt("Filter search results by string or regex:");

const editor = app.workspace.activeEditor.editor;
const fileContent = await app.vault.read(currentFile);

const dynamicContent = `
\%\%
\`\`\`dataviewjs
// Define the folder regex for filtering allPages
const folderRegex = ${folderRegex}; // Dynamically updated folder regex
const searchTerm = "${searchTerm}"; // Injecting searchTerm here as well
const filterRegex = ${filterRegex ? `new RegExp(\`${filterRegex}\`, "gmi")` : 'null'}; // Exclude from results regex
const additionalFilter = ${additionalFilter ? `new RegExp(\`${additionalFilter}\`, "gmi")` : 'null'}; // Additional filter regex

// Query all pages and filter based on folderRegex
const allPages = dv.pages("").filter(page => {
    const path = page.file.path;
    return folderRegex.test(path);
});

// Crawl content and render list
const pages = await Promise.all(
    allPages.map(async (page) => {
        const content = await dv.io.load(page.file.path);
        const regexPattern = new RegExp(\`.*(\${searchTerm}).*\`, "gmi");
        const regexMatches = content.match(regexPattern);

        if (regexMatches && regexMatches.length > 0) {
            // Apply replacements to each matched content before storing in pages
            const processedContent = regexMatches
                .map(match => {
                    let processed = match.replace(/&gt;/g, '>'); // Replace &gt; with >
                    processed = processed.replace(/(^>\\s*)|(^-\\s*)/gm, ''); // Remove > and spaces at the start of each line
                    processed = processed.replace(new RegExp(\`(\${searchTerm})(?![^\\\\[]*\\\\]\\\\])\`, "gmi"), '==\$&==');
                    return processed;
                })
                .join('<br><br>'); // Separate each processed match with two line breaks

            return {
                link: page.file.link,
                content: processedContent
            };
        }

        return null;
    })
);

// Apply additional filters and build the list string with proper formatting
const listOutput = pages
    .filter(p => p !== null)
    .filter(p => !filterRegex || !filterRegex.test(p.content)) // Exclude matches that meet the exclude filter condition
    .filter(p => !additionalFilter || additionalFilter.test(p.content)) // Apply additional filter if provided
    .map(p => \`\${p.link}\\n\${p.content}\` + \`<br><br>\`); // Include breaks between list items as well

// Output the formatted list excluding the searchTerm line
dv.list(listOutput.filter(line => !line.includes('const searchTerm'))); 
\`\`\`
\%\%
`;

this.app.workspace.activeLeaf.view.editor.setCursor({line: 99999, ch: 0});
const updatedContent = fileContent + '\n\n' + dynamicContent;
await app.vault.modify(currentFile, updatedContent);
_%>
  • If one wants no filters to be applied, an enter must be pressed in the box (can do it twice in succession and be done with it).

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.