Bullet Journaling with Drafts

I’ve loved bullet journaling for its simplicity and the tactile joy of using a pen & notebook, but I can’t deny the advantages of digital note-taking. After making extensive use of Kirk Strauser’s Quick Journaling action group, I bumped up against some things I wanted to do that weren’t currently supported. Specifically, I wanted tags added to the draft to transfer over to Things and Day One, I wanted to use Markdown Tasks before performing my end-of-day migration, and I wanted to be able to add individual drafts to a master index.

I reached out to @Kirk for advice and he gave me some scripting pointers, encouraging me to play around until I found something that works. I am pleased to share the results in the form of a new Bullet Journaling action group which you can find here:

Bullet Journal

This action group lets me write down whatever strikes me, whenever it strikes me, and makes sure it goes to the right place so I can actually act on it later.

Some additional actions were adapted from @nahumck’s GTD action group as well as several formatting tools from @agiletortoise.

I hope other bullet journalers looking for a digital alternative find these useful as well.

7 Likes

I do something pretty similar to this with TextExpander and Bear that has worked well for me. Thanks for sharing.

1 Like

TextExpander is a lifesaver! I use it all the time for dates, times, and everything else.

Does this action work out of the box or do I have to make some changes when I use it with the Day One app?

If you’re using Day One, it’s ready to go!

Thanks. I tried it. How can I change the date format to Thursday, 21 January 2021 instead of just numerals?

Great question! In both the “Append to Today’s Journal” and “Go to Today’s Journal” actions, you’ll need to modify one line of the script near the top.

Look for var ds = draft.processTemplate("[[date]]"); and simply change [[date]] to the format you would prefer. Drafts Template Tags support strftime and I used Strftime Cheat Sheet to find the format I prefer.

In your case, you may want to change the line to var ds = draft.processTemplate("[[date|%A, %3 %B %Y]]");
Correction 2021-01-21 19:54: This line should actually read, var ds = draft.processTemplate("[[date|%A, %e %B %Y]]");

Of course, you can experiment with other formats if you’d like. I prefer ISO 8601 Date and Time Format so that if I export my journal entries as individual text files, they’ll sort chronologically when I sort the files by name.

2 Likes

I did this, but it goes to a “Monthly Journal” that I seem to have created some time in the past, but I’m not using

It sounds like there may be an error in the formatting. When I inserted the suggested modification and created a test entry, Drafts created the appropriate journal with the appropriate text. I tried to recreate your conditions by creating a separate draft titled “Journal for January” and it still parsed it out correctly.

Maybe the issue lies elsewhere in the script? Here is the complete JavaScript I used to produce the above journal entry:

// This is the same as "Go to Today's Journal", but I don't know how to define them in one place and share that information
var ds = draft.processTemplate("[[date|%A, %3 %B %Y]]");
var bjTitle = "# Journal for " + ds;
var ts = draft.processTemplate("[[created|%H:%M]]");
var tag = "journal";
const tags = draft.tags;
var currentContent = draft.content;

// If the current draft doesn't start with "-", "*", or "@", then add a "- " to the front of it, give it a second-level header "##" and add a horizontal rule "---" beneath this section to visually distinguish each entry. 

if ("-*@".indexOf(currentContent[0]) == -1) {currentContent = "- " + "## " + ts + "\n\n" + "- " + currentContent + "\n\n" + "---";
} else {currentContent = "- " + "## " + ts + "\n" + currentContent + "\n\n" + "---";
}

// If the current draft doesn't end with a newline, then add one.
if (currentContent.slice(-1) != "\n") {
	currentContent = currentContent + "\n";
}

// Look for Today's Journal and complain if there are more than one matches.
var drafts = Draft.query(bjTitle, "all", [tag]);
if (drafts.length > 1) {
	app.displayErrorMessage(drafts.length + " drafts have title '" + bjTitle + "'");
   exit();
}

// It would be bad if we kept adding a draft's contents to itself. Keep that up a few times and the world would run out of hard drives to store it.
if (drafts.length > 0 && draft.uuid == drafts[0].uuid) {
	app.displayErrorMessage("Not appending this note to itself.");
	exit();
}

// Either create a new draft or use the existing one
if (drafts.length == 0) {
	var ourDraft = Draft.create();
	ourDraft.content = bjTitle + "\n\n";
	ourDraft.addTag(tag);
	ourDraft.update();
	app.displayInfoMessage("Created a new journal: " + bjTitle);
}
else
{
	var ourDraft = drafts[0];
}

var existingContent = ourDraft.content;

// If the existing content doesn't end with a newline, then add one.
if (existingContent.slice(-1) != "\n") {
	existingContent = existingContent + "\n"
}

// Check each line for special characters, "-*@". If they are missing, prepend "- " to the line so it gets journaled.
var newContent = '';
var lines = currentContent.split("\n");
for (var line of lines) {
	if (line.length >0) {
		if ("-*@".indexOf(line[0]) == -1) {
			line = "- " + line;
			newContent += line + "\n";
			} else {
			newContent += line + "\n";
			}
		} else {
			newContent += line + "\n";
		}
	}

// Append the old draft to Today's Journal and save it.
existingContent = existingContent + newContent;
ourDraft.content = existingContent;
for (var tag of tags) {
	ourDraft.addTag(tag);
	}
ourDraft.update();
script.complete();

Correction 2021-01-21 19:54: The second line should actually read, var ds = draft.processTemplate("[[date|%A, %e %B %Y]]");

1 Like

There was an action called monthly journal, I deleted that. But it still goes to the same journal. How do you suggest I resolve this? Your action is very useful for me, because I use F2 and Day One. Day One stopped supporting native markdown, so this helps. So, what do you think I need to do?

That’s very curious! I may have to tap @agiletortoise since they created the “Monthly Journal” action. My understanding of JavaScript is admittedly limited, but the way “Append to Today’s Journal” and “Go to Today’s Journal” works is by searching your drafts for an exact title match. As long as you’ve set the date format to include the month, day, and year, it ought not to be confused by other similar drafts because there’s only one “Thursday, 21 January 2021”.

As long as you’ve updated both actions, you may be able to either remove or rename the other Monthly Journal draft, just to test it. Otherwise, I’ll have to ask @agiletortoise for guidance. Or @sylumer who’s great with this kind of stuff.

@yashodhankhare Okay, I think I made a typo in the modification I suggested. “3 January” certainly isn’t right—that’s what I get for typing on an iPhone!

Try this, and if it still doesn’t work we’ll have to wait for someone with more expertise: var ds = draft.processTemplate("[[date|%A, %e %B %Y]]");

I have one with a similar date action. It’s prefixed with the word ‘Thoughts’ and then the same date format. Want me to change that?

It might help, but other actions shouldn’t affect the behavior of this one. The way it decides which draft to “Append to…” is by searching for the exact string produced by the bjTitle variable. In our case, that should be # Journal for Tuesday, 21 January 2021 so it should create a new draft unless it finds that string in that order. If yours says something like # Thoughts for Tuesday, 21 January 2021, it ought not to append to it even though they’re similar. Since it’s not an exact match, it ought to create a new journal draft for today’s date instead.

I would hate to tell you to change your naming system for other drafts and potentially disrupt other actions’ functionality just to make this one action work, but for the sake of testing it might be worth a try.

Out of curiosity, what happens when you use the “Go to Today’s Journal” action with our modified ds variable, var ds = draft.processTemplate("[[date|%A, %e %B %Y]]");? Does it return an error message or create a new journal page?

It brings up the Monthly Journal. Let’s see if these guys can help us

Amazing set of actions! Whilst I am so tempted to add yet another app to my list I’m going to try and stay away. Do you know where I’d start trying to get this sending tasks to Todoist rather than Goodtasks?

I also use Obsidian for my journalling and general note taking but they don’t have the means to connect to Drafts so I suspect that bit of it may still need to be manual

thanks

Thanks for your reply! I looked into ToDoist for some integration options, and it looks like their API relies more on Python than a URL scheme. I’m afraid Python is a bit over my head, but I’ll see if there’s a way to make it work for you.

As far as Obsidian goes, that looks like a really cool tool! You’re the first person to mention it to me, but a cursory glance makes it seem that it relies on markdown files stored in a Finder directory. I believe Drafts has a Finder directory, so maybe there would be a way to send the journal entries there instead of Day One. I’ll look into that possibility, too!

I’ll thank you for your patience as I look for solutions. Nearly all my scripting experience is confined to Drafts and specifically to making these actions work, but I like to learn new things and I’ll see what I can do to help.

Drafts has extensive Todoist support and has wrapper classes for their whole API. There are many examples in the directory of todoist scripting as well, that might be starting points.

As far as Obsidian, you can make an Obsidian vault in the /iCloud Drive/Drafts folder, and Drafts can write files directly to that directory, and they will appear in Obsidian, no problem. There’s been some discussion of this on the forums as well if you search Obsidian.

2 Likes

Okay, let’s see if there’s anything I can add to this that’s of use :person_shrugging:t2:

Bullet Journalling Script in Drafts

Let’s try a slightly reworked version with a few tweaks in some of the logic, messages, and title matching.

// This is the same as "Go to Today's Journal", but I don't know how to define them in one place and share that information
let bjTitle = draft.processTemplate("# Journal for [[date|%A, %e %B %Y]]");
const tagsJournal = ["journal"];
const tags = draft.tags;
let draftJournal;

// If the current draft doesn't start with "-", "*", or "@", then add a "- " to the front of it, give it a second-level header 
// "##" and add a horizontal rule "---" beneath this section to visually distinguish each entry.
let currentContent = draft.content;
let strSeparator = "\n"
if ("-*@".indexOf(currentContent[0]) == -1) strSeparator = "\n\n- ";
currentContent = `- ## ${draft.processTemplate("[[created|%H:%M]]") + strSeparator + currentContent}\n\n---`;
// If the current draft doesn't end with a newline, then add one.
if (currentContent.slice(-1) != "\n") currentContent += "\n";

// Find matching drafts
let draftsMatching = Draft.queryByTitle(bjTitle, "all", tagsJournal);

// No match, create new journal
if (draftsMatching.length == 0)
{
	// Create the base journal
	draftJournal = Draft.create();
	draftJournal.content = bjTitle + "\n\n";
	// Add the bullet journal tags
	tagsJournal.forEach(function(strTag){draftJournal.addTag(strTag)});
	// Update and confirm
	draftJournal.update();
	app.displayInfoMessage("Created a new journal: " + bjTitle);
}

// Unique match, add to journal
if (draftsMatching.length == 1)
{
	// Abort if the draft we're trying to bullet journal is the day's bullet journal draft
	if (draft.uuid == draftsMatching[0].uuid) app.displayErrorMessage("Cannot self-bullet");
	// Not self-bulleting, so continue
	else
	{
		//Grab the matching draft and content
		draftJournal = draftsMatching[0];
	}
}

// Multiple matches, cannot proceed
if (draftsMatching.length > 1) app.displayErrorMessage(`Non-Unique Match: ${draftsMatching.length} drafts titled '${bjTitle}'`);
// We have a journal, let's populate with new content
else
{
	// Enforce end with a newline
	let contentWorking = draftJournal.content;
	if (contentWorking.slice(-1) != "\n") contentWorking += "\n";

	// Check each line for special characters, "-*@". 
	//If they are missing, prepend "- " to the line so it gets journaled.
	let newContent = "";
	let lines = currentContent.split("\n");
	lines.forEach(function(line)
	{
		if (line.length > 0)
		{
			if ("-*@".indexOf(line[0]) == -1)
			{
				line = "- " + line;
				newContent += line + "\n";
			}
			else newContent += line + "\n";
		}
		else newContent += line + "\n";
	});

	// Append the original draft content to the journal
	draftJournal.content = contentWorking + newContent;

	// Add the bullet journal tags
	tagsJournal.forEach(function(strTag){draftJournal.addTag(strTag)});
	draftJournal.update();
	app.displayInfoMessage(`Journaled draft ${draft.uuid}`);
}

My suspicion is that your monthly journal might contain the daily journal title somewhere in the body. but without having the data to check against it’s just guesswork.

Certainly I didn’t experience any particular issue with the first script, but the reworking might resolve it or perhaps help with narrowing down what is occurring.

Drafts to Obsidian

As Greg’s already noted, you can put your vault in the Drafts iCloud folder. But you’ll find that if you are using other apps to manage Obsidian on the go (e.g. 1Writer is probably the most popular choice), you may find that this limits this option.

If that is the case, you can utilise Shortcuts and Toolbox Pro or Scriptable to work around this. Toolbox Pro and Scriptable both support folder bookmarks, so you can export data from Drafts via these tools to other directories. On the Mac, you can utilise the TADpoLe draft function TA_exportOutsideMacSandbox() to save files outside of the Drafts folders.

Should you happen to be syncing to GitHub using the Obsidian Git plugin or equivalent, I recently released some functionality for working with GitHub in TADpoLe. There’s an accompanying blog post that explains how I have set it up, as I want to be able to capture in Drafts and push it off to Obsidian via GitHub for processing.

Hopefully some of the above will help.

2 Likes