The problem is just one of timing – the Hook GUI doesn’t give MarginNote 3 enough time for both Address and Name to be collected.
MarginNote turns out to be slow (in a two-punch Copy Address + Copy Name
) because of the way it gives leisurely user feedback after each separate copy event. By the time it has done that twice, Hook has already moved on and left poor MarginNote behind.
The JS script below, for example, if launched from something more patient, like Keyboard Maestro, does copy both Address and Name, and returns a Markdown link.
To do that, however, it needs a 0.5s pause for the second stage (copying the Name, after the Address) to allow for the MarginNote GUI to settle down.
(You can reproduce the the problem arising from Hook’s slight impatience, and MarginNote’s leisurely pace, by reducing the second of the two delay
entries (see the comment in the source code)).
Hook just needs to adjust its approach to waiting for the other application.
//JavaScript
// Version 2 - copies both Address and Name,
// but needs more time than Hook allows.
(() => {
'use strict';
ObjC.import('AppKit');
// main :: IO ()
const main = () =>
either(alert('Hook for MarginNote 3'))(x => x)(
bindLR(
menuItemClickedLR('MarginNote 3')([
'Edit', 'Copy note URL'
])
)(_ => {
const strAddr = (
delay(0.2),
clipboardText()
);
return bindLR(
strAddr.startsWith('marginnote') ? (
Right(strAddr)
) : Left('No address copied')
)(address => bindLR(
menuItemClickedLR('MarginNote 3')([
'Edit', 'Copy'
])
)(_ => {
const strName = (
delay(0.5), // ADJUST HERE ...
clipboardText()
);
return !strName.startsWith('marginnote') ? (
Right('[' +
strName.split(/\n/)[0] +
'](' + address + ')')
) : Left('No Name copied - try increasing delay ...')
}))
})
);
// alert :: String -> String -> IO String
const alert = title => s => {
const
sa = Object.assign(Application('System Events'), {
includeStandardAdditions: true
});
return (
sa.activate(),
sa.displayDialog(s, {
withTitle: title,
buttons: ['OK'],
defaultButton: 'OK'
}),
s
);
};
// clipboardText :: IO () -> String
const clipboardText = () =>
// Any plain text in the clipboard.
ObjC.unwrap(
$.NSString.alloc.initWithDataEncoding(
$.NSPasteboard.generalPasteboard
.dataForType($.NSPasteboardTypeString),
$.NSUTF8StringEncoding
)
);
// menuItemClickedLR :: String -> [String] -> Either String IO String
const menuItemClickedLR = strAppName => menuParts => {
const intMenuPath = menuParts.length;
return 1 < intMenuPath ? (() => {
const
appProcs = Application('System Events')
.processes.where({
name: strAppName
});
return 0 < appProcs.length ? (() => {
Application(strAppName).activate();
delay(0.1);
return bindLR(
menuParts.slice(1, -1)
.reduce(
(lra, x) => bindLR(lra)(a => {
const menuItem = a.menuItems[x];
return menuItem.exists() ? (
Right(menuItem.menus[x])
) : Left('Menu item not found: ' + x);
})(),
(() => {
const
k = menuParts[0],
menu = appProcs[0].menuBars[0]
.menus.byName(k);
return menu.exists() ? (
Right(menu)
) : Left('Menu not found: ' + k);
})()
)
)(xs => {
const
k = menuParts[intMenuPath - 1],
items = xs.menuItems,
strPath = [strAppName]
.concat(menuParts).join(' > ');
return bindLR(
items[k].exists() ? (
Right(items[k])
) : Left('Menu item not found: ' + k)
)(x => x.enabled() ? (
x.click(),
Right('Clicked: ' + strPath)
) : Left(
'Menu item disabled : ' + strPath
))
})
})() : Left(strAppName + ' not running.');
})() : Left(
'MenuItemClickedLR needs a menu path of 2+ items.'
);
};
// GENERIC FUNCTIONS ----------------------------
// https://github.com/RobTrew/prelude-jxa
// Left :: a -> Either a b
const Left = x => ({
type: 'Either',
Left: x
});
// Right :: b -> Either a b
const Right = x => ({
type: 'Either',
Right: x
});
// bindLR (>>=) :: Either a -> (a -> Either b) -> Either b
const bindLR = m => mf =>
undefined !== m.Left ? (
m
) : mf(m.Right);
// either :: (a -> c) -> (b -> c) -> Either a b -> c
const either = fl => fr => e =>
'Either' === e.type ? (
undefined !== e.Left ? (
fl(e.Left)
) : fr(e.Right)
) : undefined;
// MAIN ---
return main();
})();