OmniPlan links to specific task(s)

Out of the box, Hook can already create and support links to particular OmniPlan files.

The following pair of scripts (Open Item and Get Address) enable Hook to link to one or more specific tasks in OmniPlan:

  • The Hook name and link created is based on the task(s) selected in the OmniPlan GUI,
  • and the link created opens the specified file, and also restores selection of one or more particular tasks.

Get Address and Open Item scripts below

(Note that Get Name can be left blank - its function is covered within the Get Address script)

Get Address

(Copy the whole script, all the way down to "end map")

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

tell application "OmniPlan"
    set ds to documents as list
    if 0 < length of ds then
        set fp to "omniplan://localhost" & my encodedPath(POSIX path of ((file of (item 1 of ds)) as alias))
        set ws to (windows as list)
        if 0 < length of ws then
            set seln to (selected tasks of (item 1 of ws))
            if missing value is not seln and 0 < length of seln then
                script go
                    on |λ|(x)
                        id of x
                    end |λ|
                end script
                set taskIDs to "task/" & my intercalate(",%2520", my map(go, seln))
                script titles
                    on |λ|(x)
                        name of x
                    end |λ|
                end script
                "[" & my intercalate(", ", my map(titles, seln)) & ¬
                    "](" & fp & taskIDs & ")"
            else
                "[" & name of item 1 of ds & "](" & fp & ")"
            end if
        else
            "[" & name of item 1 of ds & "](" & fp & ")"
        end if
    else
        ""
    end if
end tell


-- encodedPath :: FilePath -> Percent Encoded String
on encodedPath(fp)
    tell current application
        set charSet to URLPathAllowedCharacterSet of its NSCharacterSet
        (stringByAddingPercentEncodingWithAllowedCharacters_(charSet) of ¬
            stringWithString_(fp) of its NSString) as string
    end tell
end encodedPath

-- intercalate :: String -> [String] -> String
on intercalate(delim, xs)
    set {dlm, my text item delimiters} to ¬
        {my text item delimiters, delim}
    set str to xs as text
    set my text item delimiters to dlm
    str
end intercalate

-- mReturn :: First-class m => (a -> b) -> m (a -> b)
on mReturn(f)
    -- 2nd class handler function lifted into 1st class script wrapper. 
    if script is class of f then
        f
    else
        script
            property |λ| : f
        end script
    end if
end mReturn

-- map :: (a -> b) -> [a] -> [b]
on map(f, xs)
    -- The list obtained by applying f
    -- to each element of xs.
    tell mReturn(f)
        set lng to length of xs
        set lst to {}
        repeat with i from 1 to lng
            set end of lst to |λ|(item i of xs, i, xs)
        end repeat
        return lst
    end tell
end map

Open Item

tell application "OmniPlan"
    activate
    GetURL "$0"
end tell
3 Likes

It’s great to see fine-grained linking! That’s a very important direction. And I’m sure omniplan users will love that ability.

(I personally don’t use OmniPlan yet, but it’s been on my wish list for a long time, and I use the other OmniOutliner and OmniFocus on a daily basis, and OmniGraffle for most of my diagramming needs.)

Thanks, Rob!!

You mention OmniOutliner Luc; any chance a similar script could be developed to link to individual OO topics? (And being able to target Nisus bookmarks would truly be the holy grail!)

Fine grained linking to OmniOutliner was one of the very first use cases I had in mind for this product, along with PDFs and web pages. And I for one would find it handy. (I may have mentioned elsewhere on the forum that we built a couple of products at SFU that did this for HTML and videos. One could link to/from specific time points and x,y coordinates of quicktime in “gStudy”. ) The target apps would need to have automation to auto-scroll, so it requires some discussion with other apps’ devs. Fine-grained linking between PDFs/HTML and editable note-type documents is a natural direction that one would expect Hook to go in :slight_smile: .

In Cognitive Productivity for macOS , and on this forum,I think, I mention a work-around which is to generate unique IDs (from gear menu or any other means) and to paste them in respective locations of documents. I append # and ^ personally to link from #… to ID after Hook links.

1 Like

In the macOS OmniOutliner menu:

Edit > Copy As Link

yields an id which could, I think, be combined with a file-open.

1 Like

OmniPlan 4 update:

The new OmniPlan changes the menu path to task-specific URLs:

com.omnigroup.OmniPlan4 :: (Edit > Copy Link to Task)

vs

com.omnigroup.OmniPlan3 :: (Edit > Copy As Link)

1 Like

With these scripts, I was able to link a task in OmniPlan to another file, e.g., an Obsidian note. After this, when I bring up Hook on the Obsidian note, I see that the OmniPlan task is linked. However, it does not work the other way around. When I bring up Hook by selecting the OmniPlan task, it says that no linked items are found. Is this the desired result?

Hookmark now supports deep linking to specific rows of an OmniOutliner document. See Using Hookmark with OmniOutliner – Hookmark

Thanks @RobTrew, I 've used your script and it still works!

But need to add a “/” before “task/” in line "set taskIDs to “task/”

Here is the modified Get Address function for it to work. Hope this help

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

tell application "OmniPlan"
    set ds to documents as list
    if 0 < length of ds then
        set fp to "omniplan://localhost" & my encodedPath(POSIX path of ((file of (item 1 of ds)) as alias))
        set ws to (windows as list)
        if 0 < length of ws then
            set seln to (selected tasks of (item 1 of ws))
            if missing value is not seln and 0 < length of seln then
                script go
                    on |λ|(x)
                        id of x
                    end |λ|
                end script
                set taskIDs to "/task/" & my intercalate(",%2520", my map(go, seln))
                script titles
                    on |λ|(x)
                        name of x
                    end |λ|
                end script
                "[" & my intercalate(", ", my map(titles, seln)) & ¬
                    "](" & fp & taskIDs & ")"
            else
                "[" & name of item 1 of ds & "](" & fp & ")"
            end if
        else
            "[" & name of item 1 of ds & "](" & fp & ")"
        end if
    else
        ""
    end if
end tell


-- encodedPath :: FilePath -> Percent Encoded String
on encodedPath(fp)
    tell current application
        set charSet to URLPathAllowedCharacterSet of its NSCharacterSet
        (stringByAddingPercentEncodingWithAllowedCharacters_(charSet) of ¬
            stringWithString_(fp) of its NSString) as string
    end tell
end encodedPath

-- intercalate :: String -> [String] -> String
on intercalate(delim, xs)
    set {dlm, my text item delimiters} to ¬
        {my text item delimiters, delim}
    set str to xs as text
    set my text item delimiters to dlm
    str
end intercalate

-- mReturn :: First-class m => (a -> b) -> m (a -> b)
on mReturn(f)
    -- 2nd class handler function lifted into 1st class script wrapper. 
    if script is class of f then
        f
    else
        script
            property |λ| : f
        end script
    end if
end mReturn

-- map :: (a -> b) -> [a] -> [b]
on map(f, xs)
    -- The list obtained by applying f
    -- to each element of xs.
    tell mReturn(f)
        set lng to length of xs
        set lst to {}
        repeat with i from 1 to lng
            set end of lst to |λ|(item i of xs, i, xs)
        end repeat
        return lst
    end tell
	end map
1 Like