Wiki Links vs Regular Document Links

Welcome @Perfido_Pelato

Not anytime soon. This requires underlying technical changes as well as new text editing frameworks to work properly.

Will DevonThink on the Mac retroactively recognize links? That is, if I type [[Make a Link]] in a document in DTTG, then sync the document, will DT make the link for me?

1 Like

Thank you @BLUEFROG, I’m an Evernote user (since 2010) and I’m currently evaluating DevonThink: I’ve played with it just half day and I found it really remarkable.

I share some notes and documents with my wife, who’s a Windows / Android user. Now I’m trying to find a solution to use DT while keep sharing with her, by taking advantage of the indexing feature of DT and using Obsidian as a middleman.

You’re welcome and thanks for the kind words!

Indeed that will work provided you’ve enabled the square bracket syntax in DEVONthink, per your example.

I have also just come across this issue and was looking for a workaround. I found a script for converting WikiLinks to Markdown links based on x-devonthink-link syntax in the forum, perhaps that’s helpful for anyone stumbling across this thread in the future.

2 Likes

And if you haven’t seen it you can control/right click any wiki link (in edit mode if using markdown) and select > convert to item link. If you hold the option key this can convert all wiki - links to item links. It’s pretty cool…

1 Like

Didn’t know about this :exploding_head: I’ve been agonising about the best way to link notes together, but this feature solves it for me. Thanks for pointing this out.

That feature is very handy, and if you haven’t seen Bluefrogs suggestion

This helps create wiki-links on the fly, and then you can use the contextual menu to convert if you need. But this includes important a backlink.

The edit > inert > item link is also pretty handy for directly creating markdown links, or use control-cmd E, but not quite as smooth as the [[ method.

I would love to see an option of all links being created as item links as they are more robust and iOS friendly.

Bear does this exact thing, so it’s definitely possible (but of course Bear is not DT nor has any of it’s superpowers).

2 Likes

The script was written before the built-in command (see below) was introduced.

To convert wikilinks of more that one record at once the script is still useful.

I agree Pete31, you’re script is still going to come in handy, and would be a good one to hard bake into one of DT3 official script offerings.

1 Like

In fact Pete31, you could probably run your script as a smart rule, e.g. when save a markdown file, it automatically runs your script, which would be quite an elegant solution, ensuring the user always has their links functional on iOS if created on Mac.
Would you know how that smart rule might be configured?

First off, I’m not sure it’s a good idea to convert WikiLinks automatically to Markdown links. The initial script wasn’t written to do that. I’ll show how it maybe could be possible to use it in a Smart Rule but I didn’t test whether problems could arise from such usage.

If you really want to do that then the linked script won’t work as we can’t use AppleScriptObjC directly in Smart Rules.

So you need to create a Script Library that contains everything that uses AppleScriptObjC. To do so:

  • In Finder:

    • press ⇧⌘G
    • paste ~/Library/Script Libraries
    • if no window is opened
      • paste ~/Library/
      • create folder Script Libraries
  • In Script Editor.app

    • create new document
    • paste this script:
-- Script Library - Convert WikiLinks to Markdown links (Smart Rule version)

use AppleScript version "2.4"
use framework "Foundation"
use scripting additions

on regexFind(theText, thePattern)
	try
		set aString to current application's NSString's stringWithString:theText
		set {theExpr, theError} to current application's NSRegularExpression's regularExpressionWithPattern:(thePattern) options:0 |error|:(reference)
		set theMatches to theExpr's matchesInString:aString options:0 range:{0, aString's |length|()}
		set theResults to {}
		repeat with aMatch in theMatches
			set theRange to (aMatch's rangeAtIndex:0)
			set theString to (aString's substringWithRange:theRange) as text
			if theString is not in theResults then
				set end of theResults to theString
			end if
		end repeat
		return theResults
	on error error_message number error_number
		activate
		display alert "Error: Handler \"regexFind\"" message error_message as warning
		error number -128
	end try
end regexFind

on regexReplace(theText, thePattern, theRepacement)
	try
		set theString to current application's NSString's stringWithString:theText
		set newString to theString's stringByReplacingOccurrencesOfString:(thePattern) withString:(theRepacement) options:(current application's NSRegularExpressionSearch) range:{location:0, |length|:length of theText}
		set newText to newString as string
	on error error_message number error_number
		activate
		display alert "Error: Handler \"regexReplace\"" message error_message as warning
		error number -128
	end try
end regexReplace

on decodeHTML(theText)
	try
		-- https://macscripter.net/viewtopic.php?pid=190404#p190404
		set ca to current application
		set str to ca's class "NSMutableString"'s stringWithString:(theText)
		set HTMLData to str's dataUsingEncoding:(ca's NSUTF8StringEncoding)
		set attributedStr to ca's class "NSAttributedString"'s alloc()'s initWithHTML:(HTMLData) documentAttributes:(missing value)
		set decodedString to attributedStr's |string|()
		return decodedString as text
	on error error_message number error_number
		activate
		display alert "Error: Handler \"decodeHTML\"" message error_message as warning
		error number -128
	end try
end decodeHTML

  • press ⌘S
  • press ⇧⌘G
  • paste ~/Script Libraries/Script Library - Convert WikiLinks to Markdown links (Smart Rule version).scpt
  • save

Then create a Smart Rule with this script:

-- Convert WikiLinks to Markdown links (Smart Rule version)

property useSuffix : true -- include suffix in markdown link name

on performSmartRule(theRecords)
	tell application id "DNtp"
		try
			set theEscapePattern to "\\,|\\!|\\?|\\.|\\(|\\)|\\[|\\]|\\{|\\}|\\*|\\\\|\\^|\\+|\\<|\\>|\\||\\$|\\="
			
			repeat with thisRecord in theRecords
				set theType to (type of thisRecord) as string
				if theType is in {"markdown", "«constant ****mkdn»"} then
					
					set the_record to {}
					
					set theWikiLinkRecords to outgoing Wiki references of thisRecord
					if theWikiLinkRecords ≠ {} then
						repeat with thisWikiRecord in theWikiLinkRecords
							set thisWikiRecord_RefURL to reference URL of thisWikiRecord
							set thisWikiRecord_Type to (type of thisWikiRecord) as string
							set thisWikiRecord_Kind to kind of thisWikiRecord
							set {thisWikiRecord_NameWithoutSuffix, thisWikiRecord_Suffix} to {item 1, item 2} of my recordName(name of thisWikiRecord, filename of thisWikiRecord)
							set end of the_record to {namewithoutsuffix_:thisWikiRecord_NameWithoutSuffix, suffix_:thisWikiRecord_Suffix, rurl_:thisWikiRecord_RefURL, neglookbehind_:{"\\t", "\\["}, neglookahead_:{("(\\." & thisWikiRecord_Suffix & ")?" & "\\]\\(") as string}, pattern_:"", type_:thisWikiRecord_Type, kind_:thisWikiRecord_Kind}
						end repeat
						
						set theLinkTexts to {}
						
						set theSource to source of thisRecord
						set theSource_Body to item 2 of my tid(theSource, ("</head>" & linefeed & "<body>") as string)
						set theURLs to get links of theSource_Body
						if theURLs ≠ {} then
							repeat with thisURL in theURLs
								set thisURL to thisURL as string
								set thisURL_escaped to script "Script Library - Convert WikiLinks to Markdown links (Smart Rule version)"'s regexReplace(thisURL, theEscapePattern, "\\\\$0")
								set thisURL_LinkTexts to script "Script Library - Convert WikiLinks to Markdown links (Smart Rule version)"'s regexFind(theSource_Body, (("(?<=\\<a href=\"" & thisURL_escaped & "\">)(.*?)(?=\\</a\\>)") as string))
								repeat with thisLinkText in thisURL_LinkTexts
									set thisLinkText to thisLinkText as string
									if thisLinkText starts with "[[" and thisLinkText ends with "]]" then set thisLinkText to (characters 3 thru -3 in thisLinkText) as string
									if theLinkTexts does not contain thisLinkText then set end of theLinkTexts to thisLinkText
									set thisLinkText_decoded to script "Script Library - Convert WikiLinks to Markdown links (Smart Rule version)"'s decodeHTML(thisLinkText)
									if thisLinkText_decoded ≠ thisLinkText then set end of theLinkTexts to thisLinkText_decoded
								end repeat
							end repeat
						end if
						
						repeat with this_record in the_record
							set thisWikiRecord_NameWithoutSuffix to namewithoutsuffix_ of this_record
							set thisWikiRecord_NameWithoutSuffix_Length to length of thisWikiRecord_NameWithoutSuffix
							set thisSuffix to ("." & (suffix_ of this_record)) as string
							considering case
								repeat with thisLinkText in theLinkTexts
									set thisLinkText to thisLinkText as string
									if thisWikiRecord_NameWithoutSuffix is in thisLinkText then
										if thisWikiRecord_NameWithoutSuffix ≠ thisLinkText then
											set thisSubStringOffsets to my getSubStringOffsets(thisWikiRecord_NameWithoutSuffix, thisLinkText)
											set thisLinkText_Length to length of thisLinkText
											repeat with thisOffset in thisSubStringOffsets
												if thisOffset > 1 then
													set thisNegLookbehind to characters 1 thru (thisOffset - 1) in thisLinkText as string
													set thisNegLookbehind_escaped to ("(\\[)?" & script "Script Library - Convert WikiLinks to Markdown links (Smart Rule version)"'s regexReplace(thisNegLookbehind, theEscapePattern, "\\\\$0")) as string
													if neglookbehind_ of this_record does not contain thisNegLookbehind_escaped then
														set end of neglookbehind_ of this_record to thisNegLookbehind_escaped
													end if
												end if
												if ((thisOffset + thisWikiRecord_NameWithoutSuffix_Length)) < thisLinkText_Length then
													set thisNegLookahead to characters (thisOffset + thisWikiRecord_NameWithoutSuffix_Length) thru -1 in thisLinkText as string
													if thisNegLookahead ≠ thisSuffix then
														set thisNegLookahead_escaped to (script "Script Library - Convert WikiLinks to Markdown links (Smart Rule version)"'s regexReplace(thisNegLookahead, theEscapePattern, "\\\\$0") & "(\\]\\()?") as string
														if neglookahead_ of this_record does not contain thisNegLookahead_escaped then
															set end of neglookahead_ of this_record to thisNegLookahead_escaped
														end if
													end if
												end if
											end repeat
										end if
									end if
								end repeat
							end considering
						end repeat
						
						set newText to plain text of thisRecord
						
						repeat with this_record in the_record
							set thisWikiRecord_NameWithoutSuffix to namewithoutsuffix_ of this_record
							set thisWikiRecord_NameWithoutSuffix_escaped to script "Script Library - Convert WikiLinks to Markdown links (Smart Rule version)"'s regexReplace(thisWikiRecord_NameWithoutSuffix, theEscapePattern, "\\\\$0")
							set thisWikiRecord_RefURL to rurl_ of this_record
							set thisWikiRecord_Suffix to suffix_ of this_record
							if useSuffix = true then
								set thisWikiRecord_Type to type_ of this_record
								set thisWikiRecord_Kind to kind_ of this_record
								if thisWikiRecord_Type is in {"group", "«constant ****DTgr»", "smart group", "«constant ****DTsg»"} or thisWikiRecord_Kind = "Tag" then
									set thisMarkdownLink to ("[" & thisWikiRecord_NameWithoutSuffix_escaped & "](" & thisWikiRecord_RefURL & ")") as string
								else
									set thisMarkdownLink to ("[" & thisWikiRecord_NameWithoutSuffix_escaped & "." & thisWikiRecord_Suffix & "](" & thisWikiRecord_RefURL & ")") as string
								end if
							else
								set thisMarkdownLink to ("[" & thisWikiRecord_NameWithoutSuffix_escaped & "](" & thisWikiRecord_RefURL & ")") as string
							end if
							set thisPattern to ("(?<!" & my tid((neglookbehind_ of this_record), "|") & ")" & "(\\[\\[)?" & thisWikiRecord_NameWithoutSuffix_escaped) as string
							set thisPattern to (thisPattern & "(?!" & my tid((neglookahead_ of this_record), "|") & ")") as string
							set thisPattern to (thisPattern & "(\\." & thisWikiRecord_Suffix & ")?" & "(\\]\\])?" & "(\\." & thisWikiRecord_Suffix & ")?") as string
							set pattern_ of this_record to thisPattern
							set newText to script "Script Library - Convert WikiLinks to Markdown links (Smart Rule version)"'s regexReplace(newText, thisPattern, thisMarkdownLink)
						end repeat
						
						set plain text of thisRecord to newText
					end if
				end if
			end repeat
			
		on error error_message number error_number
			script "error"'s defaultError(error_message, error_number, path to me)
		end try
	end tell
end performSmartRule

on recordName(theName, theFilename)
	set theSuffix to my getSuffix(theFilename)
	if theName ends with theSuffix and theName ≠ theSuffix then set theName to characters 1 thru -((length of theSuffix) + 2) in theName as string
	return {theName, theSuffix}
end recordName

on getSuffix(thePath)
	set revPath to reverse of characters in thePath as string
	set theSuffix to reverse of characters 1 thru ((offset of "." in revPath) - 1) in revPath as string
end getSuffix

on tid(theInput, theDelimiter)
	set d to AppleScript's text item delimiters
	set AppleScript's text item delimiters to theDelimiter
	if class of theInput = text then
		set theOutput to text items of theInput
	else if class of theInput = list then
		set theOutput to theInput as text
	end if
	set AppleScript's text item delimiters to d
	return theOutput
end tid

on getSubStringOffsets(theSubstring, theText)
	try
		set theSubstringOffsets to {}
		set x to 1
		repeat (count (characters in theText)) times
			set thisOffset to (offset of theSubstring in (characters x thru -1 in theText) as string)
			if thisOffset > 0 then
				set end of theSubstringOffsets to (thisOffset + x - 1)
				set x to x + thisOffset
			else
				exit repeat
			end if
		end repeat
		return theSubstringOffsets
	on error error_message number error_number
		activate
		display alert "Error: Handler \"getSubStringOffsets\"" message error_message as warning
		error number -128
	end try
end getSubStringOffsets

Again, it’s not tested.

Thanks Pete31, you might be right, it may not be a good idea, I did try the original suggestion prior to posting, but of course it doesn’t work, I personally would love DT3 links to work like [[ wiki links but use item links, I hope that is possible in the future. In the. mean time will try the method you mention carefully.

+1 for native wiki link support in DTTG.

6 Likes

Yet another request for [[link]] style wiki link support in DTTG.

My use case is using DEVONThink to index an Obsidian vault. Wikilinks in Obsidian are easy to create and use and navigate. I want to be able to effectively “click” on a wikilink in a note in DTTG and have it open up the linked note.

Adding my eagerness for a square bracket or other linking support in future DTTG. I read a ton of research material on my ipad and annotate as I go. I often notice unexpected connections between names, organizations and other subjects connected to the book I’m writing. As I annotate while reading I’d like to be able to quickly pull up these connections or otherwise related subjects on the fly. If such a feature existed I could see myself quickly referring to other mentions of these topics, my own discussions of them, their relevance to a different focus, or which chapter I’m referencing them (might also be useful when citing sources in my writing). I’m eager to try some of the workarounds via synching with DT3.

I appreciate the warnings that this is would be a complicated feature addition and that other improvements might be higher priority, but please include my interest in the tally of feature requests.

1 Like

While we wait for wikilinks on DTTG (and I’d note that as Zettelkasten-based note management has surged in popularity, maybe reconsider bumping wikilinks up on the DTTG roadmap as it’s practically useless without them for that sort of thing), I have a suggestion.

When you double tap on a word to select it, iOS brings up a pop up menu. You already customize that menu by adding a “Capture” item. Might I request another customization and add “Search”? That would allow us to at least quickly search the database for the wikilinked term and jump to the document that way. Right now I have to copy, then back all the way out to the application Home Screen and paste in the search box from there. The juice is rarely worth the squeeze.

9 Likes

Hard agree, here. The lack of wiki links is the most painful thing about DTTG right now. With Zettelkasten gaining so many adherents it feels like the right time to make wiki links a priority. I’m all for limiting it to double square brackets, and trashing the other types. Even limiting the link to the same database would be fine.

Link conversion is just ok. That means clicking the link in any other piece of software opens DEVONthink instead of staying inside the app you’re currently using.

5 Likes

The lack of even a response on this has led me to consider other options. I’m using Craft for my Zettelkasten, which is really fantastic for that use. I’m still keeping reference material in DEVONthink, but it’s not my go to for everything anymore.

1 Like