DEV Community

Cover image for Source Your Own Daily-Quotes in Obsidian
Jonas Schumacher
Jonas Schumacher

Posted on • Updated on

Source Your Own Daily-Quotes in Obsidian

Hey! This post has gotten a bit old and I'm no longer using the solution described here myself. I will keep the post around, but now recommend taking a look at either this plugin or this more elegant solution. Though I can't vouch for either myself, I no longer feel confident in my own solution decribed below as well.


In this post you will learn how to collect quotes in Obsidian and then use them inside your daily notes for inspiration. Either as a replacement or in addition to the quotes from https://quotes.rest/ many people already use.

Intro

If you use Obsidian there's a good chance you heard about the concept of daily notes. They are a great tool to add to your workflow and Obsidian makes it very easy to use them via its native Daily Note Plug-In. Also, if you have looked for inspiration in how other people set-up and use their daily notes, it's safe to assume you've seen at least a few that make use of Templater's tp.web.daily_quote() function to query https://quotes.rest/ for an inspirational quote to display for the day.

This is great! But in keeping with the spirit of letting your notes represent your interests and helping you remember what you learn, I thought it would be cool to collect my own quotes from the things I read, watch and listen to and then display a random one of those in each of my daily notes.

What You'll Need

If you want to replicate the whole workflow described below, you will need Obsidian itself and then:

  • Enable the "Daily notes" plugin from the Core plugins
  • Install the Templater Community plugin

How I Collect My Quotes

When I process some kind of media to incorporate it into my vault, I collect quotes I find interesting, beautiful or which simply describe the contents of the piece of media eloquently or succinctly.

For each of these, I create a new note with a title of the form (QUOTE) "This is the beautiful, eloquent quote I captured." (So to be clear: the quote itself goes inside the title.)

I then you'll need to put at least the tag #quote inside the body. You can additionally put whatever you want in there. But for this to work, you will need at least that tag - or another tag of your choosing you will put in all and only your quote notes - inside the note.

So, to recap: quote goes in the title, #quote (or a tag of your choosing) in the body.

Create A Script to Link to a Random Quote

Now, create a new note wherever you create your templates and put the following script in there:

<%* 
    const quoteFiles = []

    app.metadataCache.getCachedFiles().forEach(filename => { // get all filenames in the vault and iterate through all of them, calling a function for each of them
        let { tags } = app.metadataCache.getCache(filename) // get the tags in the  file w/ the given name
        tags = (tags || []).filter(t => t.tag && "#quote" === t.tag) // filter out all tags that are not "#quote"
        if (tags.length > 0 && filename.startsWith("(QUOTE)")) { // list will contain at least one tag for the relevant notes, also filter out all notes that dont start w/ "QUOTE"
            quoteFiles.push(filename.slice(0, filename.length - 3)) // cut off last three characters from filename, otherwise the links would contain `.md` at the end
        }
    })

    const randomIndex = Math.floor(Math.random() * quoteFiles.length)

    tR += `[[${quoteFiles[randomIndex]}]]`
%>
Enter fullscreen mode Exit fullscreen mode

And then put this one inside your daily note template:

<% tp.file.include("[[Random Quote]]") %>
Enter fullscreen mode Exit fullscreen mode

That's it!

Here's an example from my daily note from today:

an example from my vault

(The quote is from Jeroen Fieren's great post The Opposite Collector’s Fallacy)

Thanks

In creating this, I had help from users torantine, SkepticMystic and koala from the Obsidian Discord Server. Also, the function above is heavily based on the findTargets() function from the tag-wrangler plugin.

Also, thanks to @thedyslexicpoet for catching a bug in my original script.

Edits

2021/07/24:

Fixed a potential bug in the script by removing the ' in don't in this line:

if (tags.length > 0 && filename.startsWith("(QUOTE)")) { // list will contain at least one tag for the relevant notes, also filter out all notes that don't start w/ "QUOTE"
Enter fullscreen mode Exit fullscreen mode

Turning it into this:

if (tags.length > 0 && filename.startsWith("(QUOTE)")) { // list will contain at least one tag for the relevant notes, also filter out all notes that dont start w/ "QUOTE"
Enter fullscreen mode Exit fullscreen mode

Top comments (7)

Collapse
 
thedyslexicpoet profile image
TheDyslexicPoet

Hi. Thanks for putting this together. I gave it a go, but unfortunately the script just returns [[undefined]]. Also I'm not sure how you're putting quotation marks in the title of your quote note?

Collapse
 
jonasmerlin profile image
Jonas Schumacher • Edited

Hey! Sorry to hear the script isn't working for you. I'll need a bit more context to find a way to debug this though: did you copy the script above exactly and did you create at least one note that starts with (QUOTE) and contains the tag #quote? If you did: does this note lie at the root of your vault or is it in a folder? And where and how did you include the script to get a quote? It would be great if you could provide the real files with their respective paths of course, but no worries if that is not possible for now.

Regarding the quotes: I just name them that way in Obsidian. Are you on Windows?

Collapse
 
thedyslexicpoet profile image
TheDyslexicPoet

Thanks for your reply. Your script is now working perfectly. My mistake was that I had my quote files in a folder (as I reserve my root for MOCs). After putting the quotes into the root, your script started working. What would I have to change for the script to look for files in a specific folder? (My coding skills are fairly basic)

I did encounter another error as it was not liking one of your comments (the apostrophe in don't) - I attached an image. I just removed the apostrophe... As far as quotes go, yes, I'm on windows. I forget that linux/os x allows more symbols in filenames.

For interest sake, this is how I've modified your script for my purpose. For my journal, I want to answer some random questions. Your method with some loops added now grabs 3 unique questions. I've also removed the first part of the filename and with another adjustment (actualquestionFiles keeping the full title), I've retained the backlink whilst being able to present the question without (QUESTION) at the start.


<%* 
    const questionFiles = []
    const actualquestionFiles = []
    app.metadataCache.getCachedFiles().forEach(filename => { // get all filenames in the vault and iterate through all of them, calling a function for each of them
        let { tags } = app.metadataCache.getCache(filename) // get the tags in the  file w/ the given name
        tags = (tags || []).filter(t => t.tag && "#dailyquestion" === t.tag) // filter out all tags that are not "#dailyquestion"

       if (tags.length > 0 && filename.startsWith("(QUESTION)")) { // list will contain at least one tag for the relevant notes, also filter out all notes, also filter out all notes that do not start w/ "(QUESTION)"
            questionFiles.push(filename.slice(11, filename.length - 3)) // cuts off "(QUESTION) " AND the last three characters from filename, otherwise the links would contain `.md` at the end
        }

        // gets the full question title for correct backlinking
       if (tags.length > 0 && filename.startsWith("(QUESTION)")) { 
            actualquestionFiles.push(filename.slice(0, filename.length - 3)) 
        }

    })

    // find Q1
    var randomIndex1 = Math.floor(Math.random() * questionFiles.length)

    // run loop until Q2 is different from Q1 
    do {
        randomIndex2 = Math.floor(Math.random() * questionFiles.length)
    } while (randomIndex1 === randomIndex2);

    // run loop until Q3 is different from Q2 and Q1
    do {
        randomIndex3 = Math.floor(Math.random() * questionFiles.length)
    } while (randomIndex3 === randomIndex2 || randomIndex3 === randomIndex1);

    // has correct backlink but shows only the question
    tR += "### " + `[[${actualquestionFiles[randomIndex1]}|${questionFiles[randomIndex1]}?]]` + "\r\n" + "\r\n"
    tR += "### " + `[[${actualquestionFiles[randomIndex2]}|${questionFiles[randomIndex2]}?]]` + "\r\n" + "\r\n"
    tR += "### " + `[[${actualquestionFiles[randomIndex3]}|${questionFiles[randomIndex3]}?]]`
%>
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
jonasmerlin profile image
Jonas Schumacher • Edited

Thanks for catching that quotes bug! I added the comments when writing the post, not in the original script and didn't test it with them in. I will edit it in the post and add you to the thanks section. 😄

There are multiple ways to make it work with notes anywhere. The easiest would be to simply drop the requirement that the note's name starts with (QUOTE) (or (QUESTION) in your case). This is actually a remnant of the way my own vault is structured and if you will only ever use the #dailyquestion tag with these questions, filtering for it would be enough. You could still name them with a leading (QUESTION), mind you. The problem is simply that folders are appended to the beginning of the name of a note, and since we check the beginning of a note's name for (QUESTION), notes that are in folders get filtered out. These edits should do the trick I think:

if (tags.length > 0) { // list will contain at least one tag for the relevant notes, also filter out all notes"
  questionFiles.push(filename.slice(11, filename.length - 3)) // cuts off "(QUESTION) " AND the last three characters from filename, otherwise the links would contain `.md` at the end
}

// gets the full question title for correct backlinking
if (tags.length) { 
  actualquestionFiles.push(filename.slice(0, filename.length - 3)) 
}
Enter fullscreen mode Exit fullscreen mode

Another way to do it would be to split the note's name on all occurences of / and then use the last element of the resulting list as the "canonical" name of that note. If you'd like a small challange to level up your coding skills, you could try to do this with split() yourself. If you don't - or if you can't seem to make it work - write me and I'll edit the script for you. Nice work on editing it for your usecase btw.! And thanks for sharing, I think I will take inspiration from this for my daily notes as well.

Thread Thread
 
dane_mcneill_b440e54958eb profile image
Dane McNeill

It might be that I am just too new with obsidian to follow these directions. But I can’t get it to work for me. Is the templates folder the same folder as the route folder? I put them in my templates folder hoping that was the case. Also can you provide an example of a quote in text format so I can copy and paste it so when I put that in my templates folder as well I know at least I haven’t coded it incorrectly and that is not the source of my problem.

Finally, how would the solution be modified to put all of the quotes in a folder so they could be organized that way instead of being in a route folder?

Thank you

Thread Thread
 
jonasmerlin profile image
Jonas Schumacher

Hey! Sorry to keep you waiting. This post has gotten a bit long in the tooth I feel and I remember seeing purpose made plugins for this usecase around. (Edit: I think this was it: github.com/decatetsu/local-quotes) So my recommendation would be to use one of those or use the quite elegant solution described here: forum.obsidian.md/t/displaying-a-r...

Should you still have your mind set on using something similar to what I describe in the post - or wanted to use it to get into writing Obsidian scripts - let me know and I'll take a stab at re-understanding and explaining what I came up with there :D

Thread Thread
 
kadena_az profile image
k:soonsoon

Will do. Thanks for the reply!!