Some improvements on the Daily Journal (Markdown)

I’ve used DEVONthink every day for coming on 7 years now, but only just discovered how great the Daily Journal template is for interstitial journaling during the workday.

To make effective use of it, however, I need to keep friction to minimum. That means that when hit the keyboard shortcut to open the journal, I want to be able to start typing immediately. I like to keep the default Markdown view set to Preview, so having to switch to Edit mode whenever I open the journal is one point of friction, and the other is getting the cursor onto the correct line to begin typing.

Thankfully both are easily patched in AppleScript!

While I was at it, I hid the news headlines from view so that it takes an intentional click to show them (for mental health reasons, e.g. horror in Gaza), and I implemented a custom weather forecast API that I maintain. That API won’t work out of the box so I commented it out, but it’s quite basic and easily adapted to any number of public APIs you might choose to use (and if you want an API key to test it out, or to see the underlying fastAPI script, hit me up).

I also revamped the group structure to ensure that every group name is unique. This is important to me because I use Apple Shortcuts to automate tasks in the same folders in DTTG, which tends to break down when there are multiple groups with the same name. I also personally just find this foldering structure more aesthetically appealing:
2024 > 2024-04 April > 2024-04-05 Friday.

(*
	Based on script by Chuck Lane October 2, 2013
	https://discourse.devontechnologies.com/t/daily-journal-script/16509
	Updated and optimized for DEVONthink 3 by Christian Grunenberg April 30, 2019
	Localized by Eric Böhnisch-Volkmann June 28, 2019
	Revised for Markdown by Christian Grunenberg Oct 19, 2020
        Revised some more by SIJ April 5, 2024
*)

property headerColor : {40000, 20000, 0}
property blackColor : {0, 0, 0}
property dateColor : {30000, 30000, 30000}
property numHeadlines : 4

-- Import helper library
tell application "Finder" to set pathToAdditions to ((path to application id "DNtp" as string) & "Contents:Resources:Template Script Additions.scpt") as alias
set helperLibrary to load script pathToAdditions


-- Retrieve the user's locale so that we can e.g. get localized quotes and headlines
set theLocale to user locale of (get system info)
if the (length of theLocale > 2) then
	set theLocale to (characters 1 through 2 of theLocale) as string
end if

-- Format the time, strip out the seconds but keep the AM/PM indicator
set theDate to current date
set theTime to time string of theDate
if (theTime contains "AM" or theTime contains "PM") then
	if character 5 of theTime is ":" then
		set theTime to (characters 1 through 4 of theTime) & (characters 8 through 10 of theTime) as string
	else
		set theTime to (characters 1 through 5 of theTime) & (characters 9 through 11 of theTime) as string
	end if
else if character 5 of theTime is ":" then
	set theTime to (characters 1 through 4 of theTime)
else
	set theTime to (characters 1 through 5 of theTime)
end if

-- Format the month number
set numMonth to (month of theDate as integer) as string
if the (length of numMonth) < 2 then set numMonth to "0" & numMonth

-- Format the day, calculate suffix for English if needed
set theDay to day of theDate as string
set shortDay to theDay -- shortDay won't have a leading zero
if the (length of theDay) < 2 then set theDay to "0" & theDay
set daySuffix to ""
if theLocale is not "de" then
	set suffixList to {"st", "nd", "rd"}
	set theIndex to last character of theDay as integer
	if (theIndex > 0) and (theIndex < 4) and the first character of theDay is not "1" then
		set daySuffix to item theIndex of suffixList
	else
		set daySuffix to "th"
	end if
end if

-- Format the year
set theYear to year of theDate as string

-- Format month and weekday names (localized)

set theMonth to month of theDate as string
set longWeekday to weekday of theDate as string
set longMonthName to month of theDate as string
set fullWeekdayName to weekday of theDate as string
set shortWeekday to characters 1 thru 3 of longWeekday

tell application id "DNtp"
	try
		activate
		-- Adjust the group path to include the full month name and full weekday name
		set basePath to "/Journal/" & theYear & "/" & theYear & "-" & numMonth & " " & longMonthName & "/"
		set groupPath to basePath & theYear & "-" & numMonth & "-" & theDay & " " & fullWeekdayName
		set myGroup to create location groupPath
		
		-- Update the record name to include only the date
		set recordName to theYear & "-" & numMonth & "-" & theDay & ".md"
		set myRecords to children of myGroup whose name is recordName and type is markdown
		
		
		if ((count of myRecords) is 0) then -- Create the document from scratch
			if my theLocale is "de" then
				set theHeadline to (longWeekday & "," & space & shortDay & "." & space & theMonth)
			else
				set theHeadline to (theMonth & space & shortDay & daySuffix & "," & space & longWeekday)
			end if
			
			-- set myWeather to my fetchWeatherForecast() -- As defined below, this function pulls in forecast information from an external weather API, in my case a custom self-hosted one, but there are public options too.
			set myEvents to my getEvents()
			set myNews to my getNews()
			set theContent to "---" & return & "type: daily-note" & return & "tags: daily-notes" & return & "---" & return & return
			set theContent to theContent & "# " & theHeadline & return & return & myWeather & return & return & "<details><summary>`Headlines` </summary>" & return & return -- For mental health reasons I do not like to see headlines without choosing to, so I've tucked them into a collapsible section.
			
			repeat with i from 1 to (count of items of myNews) by 2
				set theContent to theContent & " - [" & item i of myNews & "]"
				set theContent to theContent & "(" & item (i + 1) of myNews & ")   " & return
			end repeat
			
			set theContent to theContent & "</details>" & return
			
			set theContent to theContent & return & return & myEvents
			
			set myRecord to create record with {name:recordName, content:theContent, type:markdown, tags:theYear & "," & theMonth} in myGroup
		else -- Record already exists, just add new weather/time header
			set myRecord to item 1 of myRecords
		end if
		
		set theContent to plain text of myRecord
		set plain text of myRecord to theContent & return & return & "## " & theTime & return & "- "
		
		open tab for record myRecord
		
		tell application "System Events" -- this block ensures the markdown file is in edit mode.
			tell application process "DEVONthink 3"
				-- Ensure the application menu is available by bringing DEVONthink 3 to the front
				set frontmost to true
				delay 1 -- Wait for a second to ensure the menu is ready; adjust delay as needed
				
				-- Navigate the menu to switch to Source display
				click menu item "Source" of menu "Document Display" of menu item "Document Display" of menu "View" of menu bar 1
			end tell
		end tell
		
		-- Calculate the number of lines in the document
		set lineCount to the number of paragraphs of theContent
		
		-- Simulate "Down Arrow" key presses to move the cursor
		tell application "System Events"
			repeat (lineCount + 20) times -- I don't know why simply using the lineCount is never enough to reach the end of the document, but for me, giving it an extra 20 is consistently enough without being excessive.
				key code 125 -- Simulate "Down Arrow" key
				delay 0.01 -- A short delay to ensure each keystroke is processed; adjust as needed
			end repeat
		end tell
		
	on error errMsg number errNum
		display alert (localized string "An error occured when adding the document.") & space & errMsg
	end try
end tell

-- Get a daily quote
on getQuote()
	tell application id "DNtp"
		try
			set myQuote to ""
			if my theLocale is "de" then
				set getSource to download markup from "feed://www.zitate-online.de/zitatdestages.xml" -- "feed://zitate.net/zitate.rss?cfg=300010110"
			else
				set getSource to download markup from "feed://feeds.feedburner.com/quotationspage/qotd"
			end if
			set getFeed to get items of feed getSource
			if items of getFeed is not {} then
				set randItem to some item of getFeed
				if my theLocale is "de" then
					set myQuote to description of randItem
				else
					set myQuote to description of randItem & return & "=    " & title of randItem & "    ="
				end if
			end if
		end try
		return myQuote
	end tell
end getQuote

on getEvents()
	tell application "Calendar"
		set theDate to current date
		set startTime to theDate - (time of theDate)
		set endTime to theDate + (1 * days) - (time of theDate)
		set allEventsMarkdown to "## Events"
		
		repeat with theCalendar in calendars
			set theseEvents to (every event of theCalendar whose start date is greater than startTime and start date is less than endTime)
			repeat with anEvent in theseEvents
				set eventTitle to summary of anEvent
				set eventStart to start date of anEvent
				set eventEnd to end date of anEvent
				
				set startHour to hours of eventStart as string
				set startMinute to minutes of eventStart as string
				if length of startMinute = 1 then set startMinute to "0" & startMinute
				set endHour to hours of eventEnd as string
				set endMinute to minutes of eventEnd as string
				if length of endMinute = 1 then set endMinute to "0" & endMinute
				
				set formattedStart to startHour & ":" & startMinute
				set formattedEnd to endHour & ":" & endMinute
				
				set theEventMarkdown to " - " & formattedStart & " - " & formattedEnd & " — " & eventTitle
				set allEventsMarkdown to allEventsMarkdown & linefeed & theEventMarkdown
			end repeat
		end repeat
		
		return allEventsMarkdown
	end tell
end getEvents

--Get the news headlines
on getNews()
	set myNews to {}
	tell application id "DNtp"
		try
			if my theLocale is "de" then
				set getNewsSource to download markup from "feed://www.tagesschau.de/xml/rss2"
			else
				set getNewsSource to download markup from "feed://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml"
			end if
			set getNewsFeed to items 1 thru numHeadlines of (get items of feed getNewsSource)
			repeat with theItems in getNewsFeed
				set end of myNews to title of theItems
				set end of myNews to link of theItems
			end repeat
		end try
		return myNews
	end tell
end getNews


on fetchWeatherForecast() -- here is my custom weather forecast function. If you want to test it before finding your own weather API provider, I can give you an API key, but otherwise this is included just to show the concept.
	set currentDate to current date
	set y to year of currentDate as text
	set m to month of currentDate as integer
	set d to day of currentDate as integer
	set monthPadded to text -2 thru -1 of ("0" & m)
	set dayPadded to text -2 thru -1 of ("0" & d) 
	
	set apiURL to "https://api.lone.blue/weather/on/" & y & "-" & monthPadded & "-" & dayPadded & "?api_key=sk-REDACTED" -- Using your working API URL and key
	log "Fetching weather from URL: " & apiURL -- This will output the URL being used to Script Editor's log
	
	try
		set weatherContent to do shell script "curl " & quoted form of apiURL
		return weatherContent
	on error
		return "Weather forecast unavailable."
	end try
end fetchWeatherForecast

5 Likes

Forgive my ignorance, but can you show me the Mac shortcut to open your daily journal template? Thank you

Oh, I created a shortcut in System Settings > Keyboard > Keyboard Shortcuts > App Shortcuts. Add a new item there, select DT3, for menu title enter this (it must match perfectly): Daily Journal — Markdown

And assign it whatever shortcut you desire.

2 Likes

Can you show me a screenshot? Thank you

You can use CustomShortcuts from our friend at Houdah Software for a little easier way to do this.

1 Like

Thanks Jim, greatly appreciated!:blush:

My pleasure :slight_smile:

You might like to look at the Johnny Decimal system. I don’t use it myself, but I’ve been thinking about it.

Ended up in this thread because I’m interested in getting back into the daily journal thing, but I need it to work in DTTG. I’ve thought about Day One, but I don’t really want yet another app, and would like the journal entries in CommonMark, one file per day.

The other thing I can’t work out a good solution for is photos. I think ideally there would be a way to link from DT to the Apple Photos app and pull up specific photos to accompany the journal entry. Is that possible?

would like the journal entries in CommonMark, one file per day.

DEVONthink and DEVONthink To Go use MultiMarkdown (v6 currently).

I think ideally there would be a way to link from DT to the Apple Photos app and pull up specific photos to accompany the journal entry. Is that possible?

No, there is no such linkage but you can drag and drop from Photos.app into a Markdown document.

Every photo has a photo id
for eample EFD4CC35 -BDC0-49FF-AC25-1B351ADCB5ED/L0/001
I paste this into my DTNote after retrieving with an applescript

Click to see script
tell application "Photos"
	set currentSelection to the selection
	if currentSelection is {} then
		display dialog "No photos selected"
	else
		set the clipboard to "PhotoId  " & id of item 1 of currentSelection
		display dialog (the clipboard) & " copied to clipboard"
	end if
end tell

and another script to jump to the photo

Click to see script
tell application "System Events" to keystroke "c" using {command down}
set thePhotoid to the clipboard

tell application "Photos"
	set thePhotos to every media item whose id = thePhotoid
	set thePhoto to item 1 of the thePhotos
	activate
	tell thePhoto to spotlight
end tell
1 Like