New "selected text" AppleScript property

Not sure I’m using the new property correctly as I don’t get a ‘selected text’ object, but simply text.

Here is my code:

tell application id "DNtp"
	set fWindow to front window
	set theText to selected text of fWindow
	set theURL to URL of theText
end tell

and get the error

Can’t make “Insulin resistance is the MOST common cause of…” into type selected text

Rick

Why would selected text have an URL property?

This one runs without an error, but it’s probably badly written.

tell application id "DNtp"
	set fWindow to first item of (viewer windows as list)
	set theText to (selected text of fWindow) as string
end tell

Am I misreading/misunderstanding what DT 3.9 has added?

Rick

The selected text will consist of one or more attribute runs, zero or more of which may have a URL.

(for attribute runs containing no link, URL will return missing value)

If we have a selection like this, for example:

This value returned by this code reveals that there are three distinct attribute runs. Only the second has a URL.

tell application "DEVONthink 3"
    tell front think window
        set refText to a reference to its selected text
        
        URL of attribute runs of refText
    end tell
end tell

-- > {missing value, "https://discourse.devontechnologies.com/c/devonthink/scripting/18", missing value}
1 Like

Can you clarify the error message the OP quoted? To me it looks as if the selected text message failed.

We need to test that there is a selection for the given tab or think window.

If there isn’t, then attempts to get properties of a missing or null value will fail.

One approach is to use exists.

In a JS idiom, for example, perhaps something like:

(() => {
    "use strict";

    const main = () => {
        const
            devon = Application("DEVONthink 3"),
            thinkWindow = devon.thinkWindows.at(0);

        return thinkWindow.exists() ? (() => {
            const selectedText = thinkWindow.selectedText;

            return selectedText.exists()
                ? (() => {
                    const runs = selectedText.attributeRuns;

                    return zip(
                        runs.text(),
                        runs.url().map(x => x || "")
                    )
                    .flatMap(
                        ([label, link]) => Boolean(link)
                            ? [`[${label}](${link})`]
                            : []
                    )
                    .join("\n");
                })()
                : `(Nothing selected in ${thinkWindow.name()})`;
        })() : "No think window found.";
    };


    const zip = (xs, ys) => Array.from({
        length: Math.min(xs.length, ys.length)
    }, (_, i) => [xs[i], ys[i]]);


    return main();
})();
1 Like

Thanks. Looks Haskellish :wink:

And yet if I right-click on the selection it gives me the option to grab a link for the selection. Any idea why that works, but calling it via AppleScript doesn’t?

Ah, I think I am conflating two separate entities. If the selected text contains and embedded link, that is what appelscript is returning.

Is there a way for me to access the “selection link” URL that is now available in 3.9?

Rick

What would that URL be for a selected text? BTW: as it’s apparently part of text suite, I doubt that there’s anything new about it.

That’s right, these features exist for quite a while (as far as I remember since DEVONthink Pro 1.x, probably 1.0)

For an AppleScript example:

on run
    set mdLinks to mdFromLabelledLinks(selectedTextLabelLinkPairs())
    
    set the clipboard to mdLinks
    return mdLinks
end run


on selectedTextLabelLinkPairs()
    tell application "DEVONthink 3"
        set thinkWindow to its front think window
        
        if exists thinkWindow then
            tell thinkWindow to ¬
                set refText to a reference to (its selected text)
            
            if exists refText then
                set runs to a reference to (attribute runs of refText)
                
                my zip(text of runs, URL of runs)
            else
                {}
            end if
        else
            {}
        end if
    end tell
end selectedTextLabelLinkPairs


on mdFromLabelledLinks(kvs)
    script f
        on |λ|(kv)
            set {k, v} to kv
            
            if missing value is not v then
                {"[" & k & "](" & v & ")"}
            else
                {}
            end if
        end |λ|
    end script
    
    my unlines(my concatMap(f, kvs))
end mdFromLabelledLinks


on concatMap(f, xs)
    set lng to length of xs
    set acc to {}
    
    tell f
        repeat with i from 1 to lng
            set acc to acc & (|λ|(item i of xs, i, xs))
        end repeat
    end tell
    acc
end concatMap


on min(x, y)
    if y < x then
        y
    else
        x
    end if
end min


on unlines(xs)
    set {dlm, my text item delimiters} to ¬
        {my text item delimiters, linefeed}
    set s to xs as text
    set my text item delimiters to dlm
    s
end unlines


on zip(xs, ys)
    set n to min(length of xs, length of ys)
    
    set vs to {}
    repeat with i from 1 to n
        set end of vs to {item i of xs, item i of ys}
    end repeat
    return vs
end zip
1 Like

Functional programming in AppleScript – impressive!

It can, of course, be expressed more parsimoniously, but perhaps not without straining a reader’s patience with the overloading of it and its.

Extracting just the main function:

on selectedTextLabelLinkPairs()
    tell application "DEVONthink 3"
        
        tell front think window
            if exists then
                tell (a reference to (its selected text))
                    if exists then
                        tell attribute runs
                            my zip(text of it, URL of it)
                        end tell
                    else
                        {}
                    end if
                end tell
            else
                {}
            end if
        end tell
    end tell
end selectedTextLabelLinkPairs
1 Like

I realized my error after dissecting winter’s solution.

When I saw the feature in 3.9 allowing a direct link to a selection in pdfs, I dove into my automation scripts to make things in my workflow better. Because I know you all love automation as much as I do, I assumed that there would be a new AppleScript link to that feature so when I saw “selected text” as an option in the AS dictionary my self-delusion was complete. :slight_smile:

Thank you for a great product and for loving making it better and AppleScript-able for those of us geeks who love to use it!

Rick

2 Likes

Wow. Thanks for this impressive script. I will dive into it when I can!

Rick

Currently there is no AppleScript property for a record’s selection link, page link, paragraph link, or annotation link. There is only the reference url and item link parameters that potentially can be strung together to create such entities.

Thanks, Jim. I will look at creating this myself.

The page reference looks pretty easy to find, and the length parameter is obviously easy. Finding the start parameter though doesn’t seem straight forward other than "find"ing the selection within the total text and then hoping it’s the only text like that in the document…

I’ll keep looking and think on it some more.

Thanks!

Rick

this code creates a link into devonthink equivalent to a “selection link”. Let me know any improvements you see that need to be done.

-- Return md link to selected text

tell application id "DNtp"
	set fWindow to its front window
	if exists fWindow then
		set refText to a reference to selected text of fWindow
		
		if exists refText then
			set attr to a reference to (attribute runs of refText)
			
			set theText to text of attr
			set theText to first item of theText
			set theURL to URL of attr
			set theURL to first item of theURL
			
			-- build link
			set theRecord to content record of fWindow
			set allText to plain text of theRecord
			set theStart to offset of theText in allText
			set theStart to theStart - 1
			set theLength to count of theText
			set theLink to reference URL of theRecord & ¬
				"?page=" & current page of fWindow & ¬
				"&start=" & (theStart as string) & ¬
				"&length=" & (length of theText) as string
			
			if missing value is not theURL then
				set theText to "[" & theText & "](" & theURL & ")"
			end if
			
			set returnText to ((theText & "[^" & theStart as string) & "]}" & return & return & ¬
				"[^" & theStart as string) & "]: " & theLink
			return returnText
		end if
	end if
	
	return ""
end tell

Rick

This parameter is actually relative to beginning of the page. However, it’s possible to create selection links without having to use start/length parameters.

The page and search parameters are sufficient if the search parameter contains the escaped content of the selected text.