I’m trying to write an Applescript that would basically have the following workflow:
With a new note the script first allows me to select, with a group selector pop-up, a destination group
Script then moves the note to the selected destination group
Then script selects the destination group’s DT item link formatted as Markdown i.e. [Group_Name](DT-source_item_link). Clarification: this link should be to the first parent group.
Then adds/appends the Markdown group item link at the end of the first line of the (Markdown) record.
Finally the script changes the record name with the updated first line of the (Markdown) record, including the Markdown group link
Step 1 and 2 I believe could be achieved with the “display group selector” together with the “move” command. 3 could be achieved by combining the “group location” property of the record together with “get record with uuid” command. Step 4 would I’m somewhat lost without regex. Step 5 is somehow achieved with the “display name editor” command.
I’ve tried combining all of these steps but it is confusing and I’m getting lost in all of the window commands and lack of regex.
I would love to get some tips how to overcome my current “writer’s block” for my DT dream script!
Your steps 4 and 5 are not clear. What is “the Markdown record” here? Where does it come from? Perhaps giving short examples of what you have and what you want to have helps.
In step 3, I’d assume that you want to use group you just moved the record to. No need for “group location” then. You certainly don’t need a regular expression to append text to a markdown record. And if you want your script to change the name of a record, what’s the use of a dialog where the user enters the new name? It’s one or the other, I’d guess.
Use JavaScript instead of AppleScript if you need regular expressions.
Thanks for you reply! So an illustrative example would be:
I’m writing a new note in Markdown about Granny Smith apples. My first line is: The Granny Smith is an apple cultivar that originated in Australia in 1868.
I then launch my script, which opens up a group selector. I choose a group called “#Apples” placed in the “Research” data base, with the following location path Research/Agriculture/Fruits/#Apples.
The script moves the note to the “#Apples” group
The script appends at the end of the first line of my Markdown note the following: [\#Apples](DT-item-link) where DT-item-link is the source item link to the “#Apples” group
The record name is then updated to: The Granny Smith is an apple cultivar that originated in Australia in 1868. #Apples
The regex comment was because I’m currently able to get the full location path, but would like to extract the last folder name only i.e. in regex /\#.*$
Regardless of how to do that: I think you’re over-engineering here. What’s the point of having a link to the parent group in the MD document? Or why add the name of the group to the record’s name? It looks nice, but you’ll have to change a bunch of things if you find out that Granny Smith is not an apple but a pear…
Here’s an educational script in JavaScript that tries to do nearly what you want. I didn’t test it, though. Should be easy to convert to AppleScript.
Note the link is appended to the end of the MD document, not to the end of the first line. Easy to change, I think (using a regular expression)
(() => {
const app = Application("DEVONthink 3");
const record = app.selectedRecords[0];
const group = app.displayGroupSelector("Select group to move record to");
if (!group) return;
const groupLink = `[${group.name()}](x-devonthink-item://${group.uuid()})`;
record.plainText = record.plainText() + `\n\n${groupLink}\n`;
record.name = `${record.name()} ${group.name()}`;
app.move({record: record, to: group});
})()
Sorter is awesome but the location has to be selected with mouse (not keyboard). With +G/+M/group selector my own group hierarchies are quickly visible. I would want to use this feature for dozens of small mini-notes on a daily basis, hence Sorter is not as streamlined for this purpose.
For DTTG is the simple answer. I’ve created a Zettelkasten/Wiki-style network of hundreds of small research notes. I would like to access this system on the go as well. DTTG is good but it has some flaws (I find DTTG best for browsing more than processing or creating) – whenever you open a linked note you have to press 2-3 times to see in which group the record is situated. Since I’m using group hierarchies to provide context, this creates resistance. I want to instantly see in the record title that I’ve arrived to a note on the topic of “#Apples” (especially since not all notes are downloaded). Another option would be to write all of that context every time in every note, but that seems like plenty of redundant information.
My research often involves heavy PDFs in numerous piles and pipelines. I don’t know if it makes sense to download all of those 100Mbs on DTTG every time?
Likewise; context in the title
I use an applescript to process my Inbox entries
My organization is tag based and for context I append the tag-names to the title
I then launch my script, which opens up a group selector
How does the script identify the list for “group selector”?
My script has a hardcoded list of Type-Tags
after that, sub-tags lists are retrieved dynamically from the tags table
Sample code for a “selector” list is
set theNoteTypes to (choose from list notetypeList with prompt "Specify Note Type" with multiple selections allowed)
I am a bit late in the game but given I am trying to teach myself AppleScript, I thought it would be nice to create a solution for this question. I have created a solution that is perhaps a bit verbose. I prefer readability.
use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions
# SYNOPSIS
# getMatch(text, regexString) -> { overallMatch[, captureGroup1Match ...] } or {}
# DESCRIPTION
# Matches string s against regular expression (string) regex using bash's extended regular expression language and
# *returns the matching string and substrings matching capture groups, if any.*
#
# - AppleScript's case sensitivity setting is respected; i.e., matching is case-INsensitive by default, unless this subroutine is called inside
# a 'considering case' block.
# - The current user's locale is respected.
#
# IMPORTANT:
#
# Unlike doesMatch(), this subroutine does NOT support shortcut character classes such as \d.
# Instead, use one of the following POSIX classes (see `man re_format`):
# [[:alpha:]] [[:word:]] [[:lower:]] [[:upper:]] [[:ascii:]]
# [[:alnum:]] [[:digit:]] [[:xdigit:]]
# [[:blank:]] [[:space:]] [[:punct:]] [[:cntrl:]]
# [[:graph:]] [[:print:]]
#
# Also, `\b`, '\B', '\<', and '\>' are not supported; you can use `[[:<:]]` for '\<' and `[[:>:]]` for `\>`
#
# Always returns a *list*:
# - an empty list, if no match is found
# - otherwise, the first list element contains the matching string
# - if regex contains capture groups, additional elements return the strings captured by the capture groups; note that *named* capture groups are NOT supported.
# EXAMPLE
# my getMatch("127.0.0.1", "^([[:digit:]]{1,3})\\.([[:digit:]]{1,3})\\.([[:digit:]]{1,3})\\.([[:digit:]]{1,3})$") # -> { "127.0.0.1", "127", "0", "0", "1" }
# SOURCE
# https://stackoverflow.com/questions/997828/is-there-something-akin-to-regex-in-applescript-and-if-not-whats-the-alternat
on getMatch(s, regex)
local ignoreCase, extraCommand
set ignoreCase to "a" is "A"
if ignoreCase then
set extraCommand to "shopt -s nocasematch; "
else
set extraCommand to ""
end if
# Note:
# So that classes such as [[:alpha:]] work with different locales, we need to set the shell's locale explicitly to the current user's.
# Since `quoted form of` encloses its argument in single quotes, we must set compatibility option `shopt -s compat31` for the =~ operator to work.
# Rather than let the shell command fail we return '' in case of non-match to avoid having to deal with exception handling in AppleScript.
tell me to do shell script "export LANG='" & user locale of (system info) & ¬
".UTF-8'; shopt -s compat31; " & extraCommand & "[[ " & quoted form of s & " =~ " & ¬
quoted form of regex & " ]] && printf '%s\\n' \"${BASH_REMATCH[@]}\" || printf ''"
return paragraphs of result
end getMatch
tell application "DEVONthink 3"
-- Bail out if nothing is selected
if selection is {} then return
-- Only works with one record selected
if (count of selection) > 1 then return
-- Get a reference to the selected document
set theDocument to first item of (selection as list)
-- Also only accept markdown documents
if type of theDocument is not markdown then return
-- Get the text of the markdown document
set theText to the plain text of theDocument
-- Select the group
set theGroup to display group selector
-- Find the line that starts with a hash and at least one space
set theNewText to ""
set theTitle to ""
repeat with theLine in paragraphs of theText
set theMatch to my getMatch(theLine, "^#[[:space:]]*(.+)$")
if (count of theMatch) > 0 then
set theTitle to last item of theMatch
-- Append the group link to the end of the title line
set theLine to theLine & " [" & name of theGroup & ¬
"](x-devonthink-item://" & uuid of theGroup & ")"
log theLine
end if
set theNewText to theNewText & theLine & return
end repeat
-- Did we find a title
if (count of theTitle) = 0 then
error "Could not find the title line in the document"
end if
-- Move the document
set theDocument to move record theDocument to theGroup
-- Replace the title
set name of theDocument to (theTitle & " " & name of theGroup)
-- Replace the content of the document
set plain text of theDocument to (theNewText as string)
end tell
I guess you’re asking because shopt is a bash built-in, and the current default shell under macOS is zsh? The original code for the GetMatch handler was posted to StackExchange over 10 years ago, so it is a big gray at the temples.
A more portable variant might use egrep. In any case, I don’t understand the purpose of the regular expression search there, which seems to be searching for first-level headlines. And then it’s using the last of them (I think).
The RE is, BTW, not matching first-level headlines only. It would also find #no space here and ## second level heading at the beginning of a line. The Golden Rule of REs (which I just invented) says: Do not use the * unless you really, really want to match nothing, too. Instead, use a + if you need at least one match.
But as with my script, that’s not what the OP was asking for: They wanted to append the link to “the first line” of the MD file, not the first first-level heading. So something like
set firstLine to first paragraph of theText
(if that is even AppleScript) and then appending the link to firstLine should suffice. In JavaScript, I’d do something like
const splitText = r.plainText().split('\n\n');
splitText[0] += completeLink; /* probably a bit ugly because of missing space */
r.plaintext = splitText.join('\n\n');
I am not sure if one should use one or two newlines as paragraph separator. But that’s easy to figure out.