We received some requests for Tinderbox support quite a while ago: Hook with Tinderbox 8.
None of us here at CogSci Apps at the time were Tinderbox users. However, Graham has been delving into it, and should have some scripts to share next month.
We received some requests for Tinderbox support quite a while ago: Hook with Tinderbox 8.
None of us here at CogSci Apps at the time were Tinderbox users. However, Graham has been delving into it, and should have some scripts to share next month.
The native tinderbox://
url-scheme does most of the work for free,
but it doesnāt record the filepath of the active document.
(It was first conceived for linking within open documents, and any file not open or in the MRU list is hidden to it.).
SO
tinderbox://
url itself (possible through osascript in Tinderbox 8), but?filepath=<encodedurl>
option to it, where encodedurl
is derived from the file path of the active Tinderbox document.?filepath
addition, which is outside the scope of the tinderbox://
scheme itself, we also need a Hook Open Item
script, to handle the document finding, giving it a suitable custom scheme name, perhaps tbx://
For Name and Address, Hook now allows us to write a single āGet Addressā script which returns both Name and Addresss in a markdown [Name](Address)
format.
As the Get Address
script needs to percent-encode the file path, and the Open Item
script needs to decode that url-encoding, JavaScript may be a little more straightforward than AppleScript (JS has built in encode and decode functions).
For example, we can, first for the Open Item script:
tbx
in the Hook://
ā¦ document-identifier
field at the bottom of the Open Script panel in Hook preferences.//JavaScript
comment line at the top, which Hook requires to identify the script language, down to })();
at the end.//JavaScript
(() => {
'use strict';
const
parts = "$0".split(/\?filepath=/),
tbx = Application('Tinderbox 8');
return (
tbx.activate(),
tbx.open(Path(
decodeURIComponent(
parts.slice(-1)[0] // Document filepath.
)
)),
Object.assign(
Application.currentApplication(), {
includeStandardAdditions: true
}
).openLocation(
`tinderbox://${parts[0].split('//')[1]}`
)
);
})();
and then for the Get Address script.
Paste the the whole of the following (again, including \\JavaScript
at the top, down to })();
at the end.
//JavaScript
(() => {
'use strict';
const ds = Application('Tinderbox 8').documents;
return 0 < ds.length ? (() => {
const
doc = ds.at(0),
note = doc.selectedNote();
return null !== note ? (
`[${note.name()}](${
'tbx' + note.attributes.byName('NoteURL')
.value().slice('tinderbox'.length) +
'?filepath=' + encodeURIComponent(doc.file().toString())
})`
) : '';
})() : '';
})();
An additional subtlety is the question of whether we want Hook links to restore the Tinderbox view type (map
vs outline
etc) in addition to the note selection.
In the script above, the TBX NoteURL is read from the corresponding attribute of the selected note. The attribute version of the url always opens a note in outline view.
Alternatively, we can obtain a link which remembers view type through a GUI sub-menu in the Tinderbox app: Note > Copy Note URL
, or with the corresponding key-stroke ^ā„āU.
I personally donāt do this, for two reasons:
Nevertheless, restoring another view would be perfectly possible, in contexts where it might seem useful, and for earlier versions of Tinderbox (the osacript interface was introduced in ver 8.0), GUI scripting of Note > Copy Note URL
would in any case be the only option.
Thanks Rob.
Question:
As I understand it, the scripts above are for the āOpen Itemā and āGet Addressā tinderbox panels in hook under preferences
What about the āGet Nameā panel? Does this remain blank?
Thanks in advance
Tom
Thatās right ā we donāt need a Get Name script if the Get Address script returns a Markdown link pattern which includes a name.
Many Thanks Rob for your assistance.
Tom
For reference, AppleScript equivalents (using the Foundation classes for url encoding and decoding) might look something like:
Get Address
(copy whole script, down to end encodedPath
)
use AppleScript version "2.4"
use framework "Foundation"
use scripting additions
on run
tell application "Tinderbox 8"
set ds to documents
if 0 < length of ds then
set doc to item 1 of ds
set oFile to file of doc
if missing value is not oFile then
set fp to my encodedPath(POSIX path of (oFile as alias)) as string
set oNote to selected note of doc
if missing value is not oNote then
set noteURL to (value of (attribute "NoteURL" of oNote)) as string
set strURL to (text (1 + (length of "tinderbox")) thru -1 of noteURL)
"[" & name of oNote & "](" & "tbx" & strURL & "?filepath=" & fp & ")"
else
""
end if
else
"[Hook can't link an unsaved file]()"
end if
else
""
end if
end tell
end run
-- GENERIC
-- https://github.com/RobTrew/prelude-applescript
-- encodedPath :: FilePath -> Percent Encoded String
on encodedPath(fp)
tell current application
(its ((NSString's stringWithString:fp)'s Ā¬
stringByAddingPercentEncodingWithAllowedCharacters:(its NSCharacterSet's Ā¬
URLPathAllowedCharacterSet))) as string
end tell
end encodedPath
Open Item
(Copy whole script, down to end splitOn
)
use framework "Foundation"
use scripting additions
on run
set xs to splitOn("?filePath=", "$0")
set ys to splitOn("//", item 1 of xs)
tell application "Tinderbox 8"
activate
open (my decodedPath(item -1 of xs))
open location ("tinderbox://" & (item 2 of ys))
end tell
end run
-- GENERIC
-- https://github.com/RobTrew/prelude-applescript
-- decodedPath :: Percent Encoded String -> FilePath
on decodedPath(fp)
tell current application
(its ((NSString's stringWithString:fp)'s Ā¬
stringByRemovingPercentEncoding)) as string
end tell
end decodedPath
-- splitOn :: String -> String -> [String]
on splitOn(pat, src)
set {dlm, my text item delimiters} to Ā¬
{my text item delimiters, pat}
set xs to text items of src
set my text item delimiters to dlm
return xs
end splitOn
Those are great scripts, thanks a lot for putting them together @RobTrew
Iām going to add them to Hook as a built in integrations, but there are a few changes Iām making to the URL structure
e.g. from
hook://tbx/file/note?view=outline+select=1575069337;?filepath=/path/file.tbx
to
hook://tbx/file/note?view=outline+select=1575069337&filepath=/path/file.tbx
I also changed the script to link to the file if no note is selected
The Tinderbox GUI Note > Copy Note URL
uses that trailing semicolon to get an intercalating separator when several notes are selected:
e.g.
tinderbox://treeBook-003/?view=outline+select=1557888055;1558033595;1558033590;
Perhaps worth going beyond my first sketch to support that (multi-selection) case too ?
On a related note, we are working on Hook working with multi-selection in Finder for early 2020 (which was one of the early enhancement requests for Hook).
For example, updating both to enable multiple selections and being agnostic about use of either & or ? in the additional string (to avoid breaking any links that people have already made), perhaps sth like:
use AppleScript version "2.4"
use framework "Foundation"
use scripting additions
-- Rob Trew ver 0.2
-- (Enabling restoration of multiple selections - also needs Open Item ver 0.2)
on run
tell application "Tinderbox 8"
set ds to documents
if 0 < length of ds then
set doc to item 1 of ds
set oFile to file of doc
if missing value is not oFile then
set fp to my encodedPath(POSIX path of (oFile as alias)) as string
set selns to selections of doc
set intSelns to length of selns
if 0 < intSelns then
set firstNote to item 1 of selns
if 1 < intSelns then
set strName to name of firstNote & " ETC"
script go
on |Ī»|(x)
value of attribute "ID" of x
end |Ī»|
end script
set strOtherIDs to ";" & my intercalate(";", my map(go, rest of selns))
else
set strName to name of firstNote
set strOtherIDs to ""
end if
set selnIDs to (value of (attribute "NoteURL" of firstNote)) as string
set strURL to (text (1 + (length of "tinderbox")) thru -2 of selnIDs)
"[" & strName & "](" & "tbx" & strURL & strOtherIDs & "&filepath=" & fp & ")"
else
""
end if
else
"[Hook can't link an unsaved file]()"
end if
else
""
end if
end tell
end run
-- GENERIC
-- https://github.com/RobTrew/prelude-applescript
-- encodedPath :: FilePath -> Percent Encoded String
on encodedPath(fp)
tell current application
(its ((NSString's stringWithString:fp)'s Ā¬
stringByAddingPercentEncodingWithAllowedCharacters:(its NSCharacterSet's Ā¬
URLPathAllowedCharacterSet))) 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
-- 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
-- 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
use AppleScript version "2.4"
use framework "Foundation"
use scripting additions
on run
set xs to splitOn("filePath=", "$0")
set ys to splitOn("//", item 1 of xs)
tell application "Tinderbox 8"
activate
open (my decodedPath(item -1 of xs))
open location "tinderbox://" & (item 2 of ys)
end tell
end run
-- GENERIC
-- https://github.com/RobTrew/prelude-applescript
-- decodedPath :: Percent Encoded String -> FilePath
on decodedPath(fp)
tell current application
(its ((NSString's stringWithString:fp)'s Ā¬
stringByRemovingPercentEncoding)) as string
end tell
end decodedPath
-- quoted :: Char -> String -> String
on quoted(c, s)
-- string flanked on both sides
-- by a specified quote character.
c & s & c
end quoted
-- splitOn :: String -> String -> [String]
on splitOn(pat, src)
set {dlm, my text item delimiters} to Ā¬
{my text item delimiters, pat}
set xs to text items of src
set my text item delimiters to dlm
return xs
end splitOn
As Luc mentioned, there are plans to support multi-selection in the near future. Weāre going to hold off on supporting any kind of multi-selection in Tinderbox until we can do it in a way which is in line with Hookās model for it.
Obviously users are welcome and encouraged to use custom scripts such as the ones youāve written, but Tinderboxās model of one URL to multiple selected notes is at odds with Hookās (planned) model which is multiple URLs to multiple selected notes. So it wonāt be included in the default integration.
After thinking about it Iāve decided to use the same URL structure as you initially designed, with identifier;?filepath
for Hookās default integration with Tinderbox
So the only functional changes to the scripts will be
These are the scripts that we plan to make the Hook default integration
Aside from the functional changes mentioned above, this script has been flattened to fail first instead of using nested conditionals. This is an arbitrary stylistic preference.
use framework "Foundation"
tell application "Tinderbox 8"
if (count of documents) is equal to 0 then
return "No document is available"
end if
set openFile to file of first document
if openFile is missing value then
return "Hook can't link unsaved files"
end if
set filePath to my encodedPath(POSIX path of (openFile as alias)) as string
set openNote to selected note of first document
if openNote is missing value then
-- link to file if no note is selected
return "file://" & filePath
end if
set tinderboxURL to value of attribute "NoteURL" of openNote
set tinderboxURL to (text (1 + (length of "tinderbox://")) thru -1 of tinderboxURL)
set tinderboxURL to "hook://tbx/" & tinderboxURL
return tinderboxURL & "?filepath=" & filePath
end tell
-- encodedPath :: FilePath -> Percent Encoded String
on encodedPath(fp)
tell current application
(its ((NSString's stringWithString:fp)'s Ā¬
stringByAddingPercentEncodingWithAllowedCharacters:(its NSCharacterSet's Ā¬
URLPathAllowedCharacterSet))) as string
end tell
end encodedPath
I personally favour splitting Get Name and Get Address apart, instead of returning a markdown link, unless there are performance reasons to do them both in one pass, e.g. if scripts rely on simulated menu items or button presses.
It is āsafeā to directly access the selected note which may not exist because the only case this script needs to handle is if it does exist.
tell application "Tinderbox 8"
get name of selected note of document 1
end tell
I believe this is unchanged from the initial script posted by RobTrew
use framework "Foundation"
use scripting additions
set xs to splitOn("?filePath=", "$0")
set filePath to my decodedPath(item -1 of xs)
set tinderboxURL to text ((length of "tbx://") + 1) thru -1 of item 1 of xs
set tinderboxURL to "tinderbox://" & tinderboxURL
tell application "Tinderbox 8"
activate
open filePath
open location tinderboxURL
end tell
-- GENERIC
-- https://github.com/RobTrew/prelude-applescript
-- decodedPath :: Percent Encoded String -> FilePath
on decodedPath(fp)
tell current application
(its ((NSString's stringWithString:fp)'s Ā¬
stringByRemovingPercentEncoding)) as string
end tell
end decodedPath
-- splitOn :: String -> String -> [String]
on splitOn(pat, src)
set {dlm, my text item delimiters} to Ā¬
{my text item delimiters, pat}
set xs to text items of src
set my text item delimiters to dlm
return xs
end splitOn
Might someone be kind enough to point me to the latest information please about how to use Hook with Eastgate Tinderbox v9? Iām basically just looking to discover how to enable this as Hook doesnāt seem, as far as I can tell so far, to have awareness of a Tinderbox note in map or outline view. Thanks in advance.
I created a copy of the Tinderbox 8 scripts and changed the āTinderbox 8ā reference to āTinderbox 9ā.
tell application "Tinderbox 9"
You can do this in the Hook preferences and you will have to change the reference in Open Item, Get Name and Get Address.
This is needed because main Tinderbox versions can co-exist. @LucB maybe this can be added in the new scripts update?
thank you very much. Weāre looking at it.
weāve updated the script server.