I use DEVONthink and rely on Wikilinks to provide context to my notes by using them as “heading” categories. Over time, I’ve accumulated a large number of these Wikilink categories. However, I don’t want to manually click each link in my notes to create the corresponding Wikilink pages.
Is there a way to automate this process? For example, could a script go through all my notes and automatically create the linked Wikilink pages for me?
Once these pages are created, I plan to add item links to track how many notes are connected to each heading. Additionally, I would like to group these “heading pages” into a separate, dedicated group to make them easier to manage and distinguish from my regular notes. This grouping would serve as a catalog for the headings.
use AppleScript version "2.4"
use scripting additions
tell application id "DNtp"
try
-- Get the current database and selected records
set theDB to current database
if theDB is missing value then error "Please open a database first."
set selectedRecords to selected records
if (count of selectedRecords) is 0 then error "Please select some documents to process."
-- Get or create the headings group
set headingsGroup to get record at "/Heading Pages" in theDB
if headingsGroup is missing value then
-- Create the Heading Pages group
set headingsGroup to create record with {name:"Heading Pages", type:group} in theDB
set comment of headingsGroup to "This group contains automatically generated pages for all wikilinks found in your documents. Each page tracks which documents reference that particular heading."
display alert "Created Heading Pages Group" message "Created a new group to store wikilink pages."
end if
-- Show progress
show progress indicator "Processing Records" steps (count of selectedRecords)
-- Process each selected record
repeat with theRecord in selectedRecords
if type of theRecord is in {markdown, txt, rtf, rtfd} then
step progress indicator ("Processing " & (name of theRecord as string))
-- Get content and look for wikilinks
set theContent to plain text of theRecord
-- Save original delimiters
set savedDelimiters to AppleScript's text item delimiters
-- Find all wikilinks in the content
set wikilinks to {}
set currentPos to 1
set contentLength to length of theContent
repeat while currentPos ≤ contentLength
-- Find next "[[" marker
set startPos to offset of "[[" in (text currentPos thru contentLength of theContent)
if startPos is 0 then exit repeat
-- Adjust position to actual location
set startPos to currentPos + startPos + 1
-- Find closing "]]"
set remainingText to text startPos thru contentLength of theContent
set endPos to offset of "]]" in remainingText
if endPos is 0 then exit repeat
-- Extract the wikilink text
set linkText to text 1 thru (endPos - 1) of remainingText
-- Add to wikilinks if not empty and not already found
if linkText is not "" and linkText is not in wikilinks then
set end of wikilinks to linkText
end if
-- Move past this wikilink
set currentPos to startPos + endPos + 1
end repeat
-- Process found wikilinks
repeat with linkText in wikilinks
-- Create or get wikilink page
set wikiPage to get record at ("/Heading Pages/" & linkText) in theDB
if wikiPage is missing value then
set wikiPage to create record with {name:linkText, type:markdown} in headingsGroup
set plain text of wikiPage to "# " & linkText & return & return & "## Documents referencing this heading" & return
end if
-- Add reference only if it doesn't exist
set docURL to reference URL of theRecord
set docLink to "- [" & (name of theRecord as string) & "](" & docURL & ")"
set pageContent to plain text of wikiPage
if pageContent does not contain docLink then
if last character of pageContent is not return then
set pageContent to pageContent & return
end if
set plain text of wikiPage to pageContent & docLink & return
end if
end repeat
-- Restore delimiters
set AppleScript's text item delimiters to savedDelimiters
end if
end repeat
hide progress indicator
display alert "Complete" message "Finished processing selected documents."
on error error_message number error_number
-- Restore delimiters in case of error
set AppleScript's text item delimiters to savedDelimiters
hide progress indicator
if error_number is not -128 then
display alert "Error" message error_message
end if
end try
end tell
for some reason, if I add a heading to a current file, and rerun the scrip on it with your alteration the file link is not updated in the heading page.
The one I posted does update, but then it freezes…so i have to restart dt
I just realized that I have a similar need for a batch of my older Obsidian notes, so I created the following JXA script:
// Select some documents and run this script.
// This script creates new markdown documents from each unique wikilink in selected documents. It does NOT change the content of original documents.
(() => {
const app = Application('DEVONthink 3');
app.includeStandardAdditions = true;
let linkNames = [];
app.selectedRecords().filter(r => r.type() === 'markdown' && r.plainText().includes("[[")).forEach(r => {
linkNames = linkNames.concat(r.plainText().match(/\[\[.*?\]\]/gm)?.map(item => item.slice(2, -2)) || []);
});
[...new Set(linkNames)].forEach(item => {
app.createRecordWith({
type: 'markdown',
name: item
}, {in: app.databases['Notes'].incomingGroup()});
});
})()
Worked well on my notes. All new documents are created in the inbox of database Notes, or use your desired destination group instead. It’s also much simpler than the AI-generated AppleScript.
This script does NOT check the wikilinks against existing documents. To do that, run Menu bar > Tools > Item Links > Convert Wikilinks to Item Links before running this script.
I created a database called Notes, and also a group called Notes, but your script just placed them in the same folder where the files are.
Also, it doesn’t show the incoming, or outgoing links.
# Tools
## Documents referencing this heading
- [Elephant](x-devonthink-item://A3C39898-A237-413F-857E-441FCAB12D6C)
currently, the output of the applescript creates this
Just run Menu bar > Tools > Item Links > Convert Wikilinks to Item Links.
And change the script parameter from inbox to your desired group, if you don’t feel like moving manually.
I assume you’re doing this as a one-off task, so it is unnecessary to automate everything. If you need to do this regularly, consider not using wikilinks in the first place.
While, I understand your motivation, it’s more difficult to assist if you have no experience with what some LLM spits out. It’s no different than using a phrasebook in a foreign country. If I say the wrong thing and someone responds, say in French, I’m not going to understand what’s being said and have no clue what to do except maybe shrug
If you want to purse more automation, I would recommend you commit some time and effort into actually learning how to script. This will give you a better frame of reference for discussion and a cooperative effort in troubleshooting. Something to think about.
PS: The choice of AppleScript versus JXA is personal. There isn’t a best option, though each has their own strengths and weaknesses. The choice is usually more about familiarity and comfortability with the syntax.
This is not a high bar to set, nor would I be “ok if DT freezes” due to some script I wrote. That would be a bug of some sort or another. If I installed a new radio in your auto and it worked great… but you couldn’t turn off the car, would that be acceptable?
philosophical thinga-ma-gigs are not my jam… it works. I’m glad I don’t have to do it manually, and it does what I want, and closing and restarting DT takes 2 seconds… I’ll eat that for the correct output… What I am having trouble with is Markdown import with metadata - #17 by BLUEFROG
I just realized this one croaks with a large number of files… say over 500…
Ps. not your version, my last one… it’s fine on a small number of files, but not larger sets
TRUE!
Now, I have tried learning to code, and i’ve spent weeks on it, but I just need to work on what I do. I’m not a programmer… I just need the results…
programming is an intermediate step in what I’m working on that I would rather outsource
While it doesn’t check for existing documents, @meowky’s solution is interesting. JXA is terse and not easy to read at a glance, but it does have some advantages in array/list processing and regular expressions that are built into the language.
Yes, I tried his code, but it doesn’t output how I want… I’ve processed 2000 old notes today, and I’m not in the mood to do anything manually when I can just “gpt-it”… Old notes which I had forgotten about, but now, since I’m adding metadata (through gpt), and then using Elephas to index, and then I can ask questions to, now at least they’re not going to be “dead” notes… I do, however, add checks to verify everything is going according to plan… cause I don’t trust LLM much, though I use them everyday.
I’m using AI because it works for a lot of things I need. If humans were so good at programming, then there wouldn’t be buggy code. I’m not a luddite, things change. AI is changing things, and I don’t think there need be an apology for it. If you can get me a human that produces 100% bug free code, and is selling what I need, I’m all in… but, is my work programming or something else? I’m not a programmer, I just need to work on my own things which are valuable to me. I found a folder with old notes that took me a long time to extract from books, but I never used them because I couldn’t find them, or reference them… are they more valuable because they were “human written” than notes I can use that are ai written? Kasparov lost… Lee Sedol lost… get over it…
Just be aware that GenAI is trained with the work produced by humans. So, if humans are bad programmers, it logically follows that AI will be bad at it, too. It’s well-known that GenAI struggles to depict the human hand correctly because that has always been a challenge for human artists, too.
Is it changing things? Not changing enough to produce code that does not freeze an application, apparently.