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