Return links/Back links

This script will look for markdown text files liking to the currently open one (first using the name and then the aliases) and add the results to the “Return links” custom metadata field.

You’ll need the script RegexAndStuffLib for it to compile/work (just google it and you’ll find it for download) although this is not strictly necessary and you can easily remove the only line containing regex from the script.

Thanks to @ngan for the scripts he has been posting since I took the list method and handler from one of his scripts. Other parts I might have taken from other people, but I can’t recall for sure now.

Version A - RTF Links in special metadata field

A.1 Stand-alone script

use AppleScript version "2.4" -- Yosemite (10.10) or later
use script "RegexAndStuffLib"
use scripting additions


property MainUUID : "C02BC195-1E85-4364-B9E0-1CEF96CE7E96"

tell application id "DNtp"
	
	set theRecords to the selection
	repeat with theRecord in theRecords
		

		set theName to name of theRecord
		set theShortName to regex search once theName search pattern "^(.{4})" replace template "$1"
		set theShortName to my replaceText(theShortName, " ", "%20")
		set theGroup to get record with uuid MainUUID
		set theAliases to aliases of theRecord
		set theAliases to my trimtext(theAliases, ", ", "end")
		set theAliases to my replaceText(theAliases, ", ", "\") OR (\"")
		set theName to "(\"" & theName & "\")"
		set theSearchList to theName & " OR " & "(\"" & theAliases & "\")"
		
		set theRecs to search "content:" & theSearchList & space & "kind:markdown" in theGroup
		
		
		-- Prepare the list
		set theList to {}
		repeat with each in theRecs
			
			
			set the end of theList to return & quoted form of (name of each & "<a href=\"" & reference URL of each & "?search=" & theShortName & "&reveal=1" & "\">" & "**" & "</a></br>")
			
			
		end repeat
		
		
		-- Sort the list
		set theList to my sortlist(theList)
		
		set theFinalList to "'<font size=\"5\" color=\"#8080BB\"><font face=\"Menlo\">'" & theList & "'</font></font>'"
		
		-- Convert to RTF
		set theRTF to (do shell script "echo " & theFinalList & " | textutil -stdin -stdout -inputencoding utf-8 -format html -convert rtf | pbcopy")
		set theRTF to the clipboard
		set theRTF to «class RTF » of theRTF
		
		add custom meta data theRTF for "return links" to theRecord
		
		display notification "Hooray! Success!"
		
	end repeat
end tell



-- Handlers section

on replaceText(theString, old, new)
	set {TID, text item delimiters} to {text item delimiters, old}
	set theStringItems to text items of theString
	set text item delimiters to new
	set theString to theStringItems as text
	set text item delimiters to TID
	return theString
end replaceText

on trimtext(theText, theCharactersToTrim, theTrimDirection)
	set theTrimLength to length of theCharactersToTrim
	if theTrimDirection is in {"beginning", "both"} then
		repeat while theText begins with theCharactersToTrim
			try
				set theText to characters (theTrimLength + 1) thru -1 of theText as string
			on error
				-- text contains nothing but trim characters
				return ""
			end try
		end repeat
	end if
	if theTrimDirection is in {"end", "both"} then
		repeat while theText ends with theCharactersToTrim
			try
				set theText to characters 1 thru -(theTrimLength + 1) of theText as string
			on error
				-- text contains nothing but trim characters
				return ""
			end try
		end repeat
	end if
	return theText
end trimtext

on sortlist(theList)
	set theIndexList to {}
	set theSortedList to {}
	repeat (length of theList) times
		set theLowItem to ""
		repeat with a from 1 to (length of theList)
			if a is not in theIndexList then
				set theCurrentItem to item a of theList as text
				if theLowItem is "" then
					set theLowItem to theCurrentItem
					set theLowItemIndex to a
				else if theCurrentItem comes before theLowItem then
					set theLowItem to theCurrentItem
					set theLowItemIndex to a
				end if
			end if
		end repeat
		set end of theSortedList to theLowItem
		set end of theIndexList to theLowItemIndex
	end repeat
	return theSortedList
end sortlist

A.2 Smart-rule version

use AppleScript version "2.4" -- Yosemite (10.10) or later
use script "RegexAndStuffLib"
use scripting additions
property MainUUID : "C02BC195-1E85-4364-B9E0-1CEF96CE7E96"

on performSmartRule(theRecords)
	tell application id "DNtp"
		repeat with theRecord in theRecords
			
			set theName to name of theRecord
			set theShortName to regex search once theName search pattern "^(.{4})" replace template "$1"
			set theShortName to my replaceText(theShortName, " ", "%20")
			set theGroup to get record with uuid MainUUID
			set theAliases to aliases of theRecord
			set theAliases to my trimtext(theAliases, ", ", "end")
			set theAliases to my replaceText(theAliases, ", ", "\") OR (\"")
			set theName to "(\"" & theName & "\")"
			set theSearchList to theName & " OR " & "(\"" & theAliases & "\")"
			
			set theRecs to search "content:" & theSearchList & space & "kind:markdown" in theGroup
			
			
			-- Prepare the list
			set theList to {}
			repeat with each in theRecs
				
				
				set the end of theList to return & quoted form of ("- " & name of each & "<a href=\"" & reference URL of each & "?search=" & theShortName & "&reveal=1" & "\">" & "**" & "</a></br>")
				
				
			end repeat
			
			
			-- Sort the list
			set theList to my sortlist(theList)
			
			set theFinalList to "'<font size=\"4\" color=\"#8080BB\"><font face=\"Monaco\">'" & theList & "'</font></font>'"
			
			-- Convert to RTF
			set theRTF to (do shell script "echo " & theFinalList & " | textutil -stdin -stdout -inputencoding utf-8 -format html -convert rtf | pbcopy" as rich text)
			set theRTF to the clipboard
			set theRTF to «class RTF » of theRTF
			
			add custom meta data theRTF for "return links" to theRecord
			
			display notification "Hooray! Success!"
			
		end repeat
	end tell
end performSmartRule

-- Handlers section

on replaceText(theString, old, new)
	set {TID, text item delimiters} to {text item delimiters, old}
	set theStringItems to text items of theString
	set text item delimiters to new
	set theString to theStringItems as text
	set text item delimiters to TID
	return theString
end replaceText

on trimtext(theText, theCharactersToTrim, theTrimDirection)
	set theTrimLength to length of theCharactersToTrim
	if theTrimDirection is in {"beginning", "both"} then
		repeat while theText begins with theCharactersToTrim
			try
				set theText to characters (theTrimLength + 1) thru -1 of theText as string
			on error
				-- text contains nothing but trim characters
				return ""
			end try
		end repeat
	end if
	if theTrimDirection is in {"end", "both"} then
		repeat while theText ends with theCharactersToTrim
			try
				set theText to characters 1 thru -(theTrimLength + 1) of theText as string
			on error
				-- text contains nothing but trim characters
				return ""
			end try
		end repeat
	end if
	return theText
end trimtext

on sortlist(theList)
	set theIndexList to {}
	set theSortedList to {}
	repeat (length of theList) times
		set theLowItem to ""
		repeat with a from 1 to (length of theList)
			if a is not in theIndexList then
				set theCurrentItem to item a of theList as text
				if theLowItem is "" then
					set theLowItem to theCurrentItem
					set theLowItemIndex to a
				else if theCurrentItem comes before theLowItem then
					set theLowItem to theCurrentItem
					set theLowItemIndex to a
				end if
			end if
		end repeat
		set end of theSortedList to theLowItem
		set end of theIndexList to theLowItemIndex
	end repeat
	return theSortedList
end sortlist

1 Like

Version B - Stand-alone with options (RTF, MD Links or Auto wiki links)

use AppleScript version "2.4" -- Yosemite (10.10) or later
use script "RegexAndStuffLib"
use scripting additions


property MainUUID : ""

tell application id "DNtp"
	
	try
		set invalid to false
		set options to {"RTF", "MD", "Cancel"}
		set options2 to {"Links", "Links and aliases", "Cancel"}
		set options3 to {"Auto Wiki Links", "Without Auto Wiki Links", "Cancel"}
		set answer to the button returned of (display dialog "Should the links be in RTF or MD?" buttons the options default button 2)
		
		if answer is "RTF" then
			set RTFLinks to true
			set answer2 to the button returned of (display dialog "Should the search include aliases?

NOTE: without aliases, the search command (?search=name) will be added to the link" buttons the options2 default button 1)
			set answer3 to ""
			
		else
			set RTFLinks to false
			set answer2 to the button returned of (display dialog "Should the search include aliases?

NOTE: without aliases, the search command (?search=name) will be added to the link" buttons the options2 default button 1)
			set answer3 to the button returned of (display dialog "Are you using Auto Wiki Links?

NOTE: with auto wiki links ON, there is no need for x-devonthink URLs" buttons the options3 default button 1)
			
		end if
		
		
		if answer2 is "Links and aliases" then
			set UseAliases to true
		else
			set UseAliases to false
		end if
		
		
		if answer3 is "Auto Wiki Links" then
			set AutoWikiLinks to true
		else
			set AutoWikiLinks to false
		end if
		
	on error error_message number error_number
		set invalid to true
		if invalid then error "Error"
	end try
	
	
	set theRecord to (content record of think window 1)
	set theText to plain text of theRecord
	set theName to name of theRecord
	
	-- Prepare the search string
	if UseAliases then
		set theName2 to "(\"" & theName & "\")"
		set theAliases to aliases of theRecord
		set theAliases to my trimtext(theAliases, ", ", "end")
		set theAliases to my replaceText(theAliases, ", ", "\") OR (\"")
		set theSearchList to "(\"" & theAliases & "\")"
		set theSearchList to theName2 & " OR " & "(\"" & theAliases & "\")"
	else
		
		set theSearchList to "\"" & theName & "\""
		
	end if
	
	
	-- Perform the search
	set theGroup to get record with uuid MainUUID
	set theRecords to search "content:" & theSearchList & space & "kind:markdown" in theGroup
	
	if RTFLinks is true then
		
		set theList2 to {}
		repeat with each in theRecords
			
			if UseAliases then
				set the end of theList2 to return & quoted form of ("<font size=\"5\"><font face=\"Menlo\"><a href=\"" & reference URL of each & "\">" & name of each & "</a></font></font></br>")
				
			else
				set theName to my replaceText(theName, " ", "%20")
				set the end of theList2 to return & quoted form of ("<font size=\"5\"><font face=\"Menlo\"><a href=\"" & reference URL of each & "?search=" & theName & "\">" & name of each & " " & "</a></font></font> </br>")
			end if
		end repeat
		
		set theList2 to regex change theList2 search pattern "^.+" & name of theRecord & ".+" replace template "$1"
		
		set theList2 to my sortlist(theList2)
		
		set theRTF to (do shell script "echo " & theList2 & " | textutil -stdin -stdout -inputencoding utf-8 -format html -convert rtf | pbcopy")
		set theRTF to the clipboard
		
		if UseAliases then
			add custom meta data «class RTF » of theRTF for "return links with aliases" to theRecord
			
		else
			add custom meta data «class RTF » of theRTF for "return links" to theRecord
		end if
		
		
		
	else
		
		
		set theList to {}
		repeat with each in theRecords
			
			-- if aliases are being used, we can't add further commands to the URL (such "?search")
			if UseAliases then
				
				-- if AutoWikiLinks is active, there is no need to add the x-devonthink address	
				if AutoWikiLinks then
					set the end of theList to name of each & " | "
				else
					-- Without AutoWikiLinks, we need the full address
					set the end of theList to "[" & name of each & "]" & "(" & reference URL of each & ")" & " | "
					--set the end of theList to return & "* [" & name of each & "]" & "(" & reference URL of each & ")"
				end if
				
			else
				
				if AutoWikiLinks then
					set the end of theList to name of each & " | "
				else
					-- Without AutoWikiLinks, we need the full address
					set the end of theList to "[" & name of each & "]" & "(" & reference URL of each & ")" & " | "
					--set the end of theList to return & "* [" & name of each & "]" & "(" & reference URL of each & ")"
				end if
				
			end if
			
		end repeat
		set theList to my sortlist(theList)
		
		
		-- Remove old Returnlinks section
		try
			set oldDelims to AppleScript's text item delimiters -- salvar o delimitador padrão
			set AppleScript's text item delimiters to {"# Return links"} -- declarar delimitador novo
			set delimitedList to every text item of theText
			set AppleScript's text item delimiters to oldDelims -- restaurar delimitador padrão
		on error
			set AppleScript's text item delimiters to oldDelims -- restaurar delimitador padrão em caso de erro
		end try
		
		-- Add new ReturnLinks section
		try
			set theText to item 1 of delimitedList
			set theText to my trimtext(theText, "
", "end")
			
			set the plain text of theRecord to theText & "

# Return links" & return & theList as text
		end try
		
		
	end if
	
	
	set answer to ""
	set answer2 to ""
	set answer3 to ""
	
	display notification "Hooray! Success!"
	
	-- end repeat
end tell



-- Handlers section

on replaceText(theString, old, new)
	set {TID, text item delimiters} to {text item delimiters, old}
	set theStringItems to text items of theString
	set text item delimiters to new
	set theString to theStringItems as text
	set text item delimiters to TID
	return theString
end replaceText

on trimtext(theText, theCharactersToTrim, theTrimDirection)
	set theTrimLength to length of theCharactersToTrim
	if theTrimDirection is in {"beginning", "both"} then
		repeat while theText begins with theCharactersToTrim
			try
				set theText to characters (theTrimLength + 1) thru -1 of theText as string
			on error
				-- text contains nothing but trim characters
				return ""
			end try
		end repeat
	end if
	if theTrimDirection is in {"end", "both"} then
		repeat while theText ends with theCharactersToTrim
			try
				set theText to characters 1 thru -(theTrimLength + 1) of theText as string
			on error
				-- text contains nothing but trim characters
				return ""
			end try
		end repeat
	end if
	return theText
end trimtext

on sortlist(theList)
	set theIndexList to {}
	set theSortedList to {}
	repeat (length of theList) times
		set theLowItem to ""
		repeat with a from 1 to (length of theList)
			if a is not in theIndexList then
				set theCurrentItem to item a of theList as text
				if theLowItem is "" then
					set theLowItem to theCurrentItem
					set theLowItemIndex to a
				else if theCurrentItem comes before theLowItem then
					set theLowItem to theCurrentItem
					set theLowItemIndex to a
				end if
			end if
		end repeat
		set end of theSortedList to theLowItem
		set end of theIndexList to theLowItemIndex
	end repeat
	return theSortedList
end sortlist

Version C - Smart rule for [[Wiki links]]

use AppleScript version "2.4" -- Yosemite (10.10) or later
use script "RegexAndStuffLib"
use scripting additions
property MainUUID : ""

on performSmartRule(theRecords)
	tell application id "DNtp"
		repeat with theRecord in theRecords
			
			set theName to name of theRecord
			set theText to plain text of theRecord
			set theGroup to get record with uuid MainUUID
			set theSearchList to "\"" & theName & "\""
			
			set theRecs to search "content:" & theSearchList & space & "kind:markdown" in theGroup
			
			
			-- Prepare the list
			set theList to {}
			repeat with each in theRecs
				
				
				set the end of theList to "[[" & name of each & " ]] | "
				
				
			end repeat
			
			
			-- Sort the list
			set theList to my sortlist(theList)
			
			try
			set oldDelims to AppleScript's text item delimiters -- salvar o delimitador padrão
			set AppleScript's text item delimiters to {"# Return links"} -- declarar delimitador novo
			set delimitedList to every text item of theText
			set AppleScript's text item delimiters to oldDelims -- restaurar delimitador padrão
		on error
			set AppleScript's text item delimiters to oldDelims -- restaurar delimitador padrão em caso de erro
		end try
		
		-- Add new ReturnLinks section
		try
			set theText to item 1 of delimitedList
			set theText to my trimtext(theText, "
", "end")
			
			set the plain text of theRecord to theText & "

# Return links" & return & theList as text
		end try
			
			display notification "Hooray! Success!"
			
		end repeat
	end tell
end performSmartRule

-- Handlers section

on replaceText(theString, old, new)
	set {TID, text item delimiters} to {text item delimiters, old}
	set theStringItems to text items of theString
	set text item delimiters to new
	set theString to theStringItems as text
	set text item delimiters to TID
	return theString
end replaceText

on trimtext(theText, theCharactersToTrim, theTrimDirection)
	set theTrimLength to length of theCharactersToTrim
	if theTrimDirection is in {"beginning", "both"} then
		repeat while theText begins with theCharactersToTrim
			try
				set theText to characters (theTrimLength + 1) thru -1 of theText as string
			on error
				-- text contains nothing but trim characters
				return ""
			end try
		end repeat
	end if
	if theTrimDirection is in {"end", "both"} then
		repeat while theText ends with theCharactersToTrim
			try
				set theText to characters 1 thru -(theTrimLength + 1) of theText as string
			on error
				-- text contains nothing but trim characters
				return ""
			end try
		end repeat
	end if
	return theText
end trimtext

on sortlist(theList)
	set theIndexList to {}
	set theSortedList to {}
	repeat (length of theList) times
		set theLowItem to ""
		repeat with a from 1 to (length of theList)
			if a is not in theIndexList then
				set theCurrentItem to item a of theList as text
				if theLowItem is "" then
					set theLowItem to theCurrentItem
					set theLowItemIndex to a
				else if theCurrentItem comes before theLowItem then
					set theLowItem to theCurrentItem
					set theLowItemIndex to a
				end if
			end if
		end repeat
		set end of theSortedList to theLowItem
		set end of theIndexList to theLowItemIndex
	end repeat
	return theSortedList
end sortlist
1 Like

In Catalina, there is:

~Library/Scripts
~Library/ScriptingAdditions
~Library/Application Scripts

Doesn’t compile from any of them. Different location?

As noted in Help > Documentation > Automation > AppleScript

Ah, thank you. I did not see that–obviously.

You need the RegexAndStuff script in ~/Library/Script Libraries.

I turned it into a smart rule. Now all my notes have backlinks based on the name and the aliases. This is a feature I have always wanted :slight_smile:

1 Like

Stupid questions: do those links work? What metadata type is it?

It is just rich text. Since the links appear in a blue color that is hard to see, I set the script to print the result in the usual DT3 dark mode link color and then placed the URL in the two asterisks right after the name of the record.

Like this:

  • COVID-19 Cases**
1 Like

Very interesting script!

So, you have long notes and the field will give you a glimpse on which references (BTW what language is that?) are included?

Just a thought and only if you have too much spare time and you like scripting for fun …
If another cmd “forward link” is created, another script can search whatever backlinks (say there are two links from documents B & document C respectively ) are in the “Return Links” of a focal document (A) and the script can insert the item link of A into the cmd “forward link” of B & C. A user will be able to know where/how his/her references are cited. I have no idea on the true value of this “forward links” function, but it could be an interesting intellectual exercise.

I wish in the next major update of DT, markdown formatted field can be another option in cmd. IMHO, it’s easier to manipulate link by Applescript in a markdown format.

1 Like

I wish in the next major update of DT, markdown formatted field can be another option in cmd. IMHO, it’s easier to manipulate link by Applescript in a markdown format.

Can you clarify what you mean by this?

Assuming that I want to create a link in clipboard. To use Applescript to insert a link in markdown format is using plain text.
e.g.

	set theLink to ">" & theCitedText & " [" & theBLName & "](" & theBL & ")"
	set the clipboard to theFullLink

But for RTF, we need to format the link in HTML (?) and then using quoted form and may need to consider the font face etc? Perhaps it’s just me, I found that if I do not specify the font face in the script, I always end up with the link that is pasted in Times font?

--basically, just modifying what Korm posted many years ago...
set fontOfLink to "<font face=\"SFProText-Light\" color=#333333>"
set theFullLink to "<meta charset=\"UTF-8\">" & fontOfLink & "\" " & theCitedText & " \"<a href=\"" & theBL & "\" style=\"" & linkColor & "\"> " & theBLName & "</a> <br><br>"
do shell script "echo " & quoted form of theFullLink & " | textutil -format html -convert rtf -stdin -stdout | pbcopy -Prefer rtf"

Oh, when I said markdown format, I was referring to the datatype of the cmd.

Oh, when I said markdown format, I was referring to the datatype of the cmd.

Ahh. Okay.

Thanks! As you can see, I made use of parts from other scripts you posted.

The field will tell me all the notes that link to the note I am currently viewing. So if I am in note A and note B contains a reference to A, then a link to note B should appear in the Return Links of note A.

I keep all my research data in MD files inside a database. I guess this is a mere convenience for browsing notes quickly. Eventually, this could help to notice unexpected connections, but since I use auto wiki links with huge amounts of aliases this already happens naturally in the body of the text.

In the screencap, there is portuguese and greek :upside_down_face:

Agreed.

I am not sure I understood the concept.

If note B links to note A, then note A gets a link to note B (in the special field).
So in note A, I have a link to note B, and in note B I already had a link to note A.

What I am missing here? What should appear in the forward field? Note C?

I’ve just downloaded and tried this Bernado and I have to say – thanks! It’s a really well thought out addition to DT3’s capabilities and I’ll definitely use it.

The only minor amendments I’ve made is that I’ve changed the ‘Wikilinks’ output (because I use [[wikilink]] not names and aliases.

(In case anyone else wants to do this, just comment out the lines

set the end of theList to name of each & " | "

and replace them with

set the end of theList to "[[" & name of each & "]] | "

At least, that has worked so far for me…)

Sorry to be a pain, but you mention that you’ve set it to be a smart rule. Could I ask you how you’ve set it up – I’ve copied the script to the smart rules folder and set up a basic rule, but nothing happens when I run it.

Thanks again!

2 Likes

No pain at all, @brookter.
You are using version 2, right?

The backlinks field of A shows B and C (ur script). The forward links field of B and C will show A (the other script). So we can know which documents are cited in A from A’s backlinks field ( i.e. link of B and link of C), and we can also know which documents have cited B (link of A is in B’s forward links field) and C (link of A is in C’s forward links field).

I am not certain on how much value this function has. But my guts feeling is that the two fields working together may lay out the foundation for many-to-many linkages relationship. No need to bother, just a crazy thought!

I am using v2, yes. Sorry, should have said. I’ve just created the smart rule, pointed it at docs in the relevant group, and then used execute script on my (minimally amended) version of your v2. There’s no error, it just doesn’t fire, so I must be doing something wrong, or misunderstanding what you’re using it for.

Thanks!

Are we both considering that auto wiki links is on? (That is the way I am thinking about this). Because, if document A mentions B and C, and B and C both have return links to A, then the forward links to B and C are already in the body of text of A and in the links section (⌃7) while on preview mode.

Or is this not the same thing? I am just trying to understand because this interests me :slight_smile: