Not sure if this will be of use to anyone else - I’m find it useful for exploring what’s there.
(Can be run, for example, from Script Editor, with the language tab at top left set to JavaScript - I personally run scripts more often from Atom or Keyboard Maestro etc)
(() => {
'use strict';
// Copy to clipboard a list of all the non-empty (attribute: value)
// pairs for the selected Tinderbox 8 note .
// ver 0.03
const main = () =>
either(
x => x,
x => (
standardAdditions().setTheClipboardTo(x),
console.log(x),
x
),
bindLR(
frontDocLR(),
d => bindLR(
selectedNoteLR(d),
note => Right(unlines(sort(concat(zipWith(
(k, v) => 0 < v.length ? [
k + ': ' + v
] : [],
note.attributes.name(),
note.attributes.value()
)))))
)
)
);
// TINDERBOX FUNCTIONS --------------------------------
// frontDocLR :: Tbx IO () -> Either String Tbx Doc
const frontDocLR = () => {
// Either the front document in Tinderbox 8, or an
// explanatory message if no documents are open,
// or Tinderbox 8 is not running.
const tbx = Application('Tinderbox 8');
return tbx.running() ? (() => {
const ds = tbx.documents;
return 0 < ds.length ? (
Right(ds.at(0))
) : Left('No documents open in Tinderbox');
})() : Left('Tinderbox 8 is not running.');
};
// selectedNoteLR :: Tbx Doc -> Either String Tbx Note
const selectedNoteLR = doc => {
// Either the first selected note, or an
// explanatory message if nothing is selected.
const note = doc.selectedNote();
return note !== null ? (
Right(note)
) : Left('No note selected in ' + doc.name());
};
// JXA ------------------------------------------------
// standardAdditions :: () -> Application
const standardAdditions = () =>
Object.assign(Application.currentApplication(), {
includeStandardAdditions: true
});
// 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);
// concat :: [[a]] -> [a]
// concat :: [String] -> String
const concat = xs =>
0 < xs.length ? (() => {
const unit = 'string' !== typeof xs[0] ? (
[]
) : '';
return unit.concat.apply(unit, xs);
})() : [];
// 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;
// Returns Infinity over objects without finite length.
// This enables zip and zipWith to choose the shorter
// argument when one is non-finite, like cycle, repeat etc
// length :: [a] -> Int
const length = xs =>
(Array.isArray(xs) || 'string' === typeof xs) ? (
xs.length
) : Infinity;
// sort :: Ord a => [a] -> [a]
const sort = xs => xs.slice()
.sort((a, b) => a < b ? -1 : (a > b ? 1 : 0));
// take :: Int -> [a] -> [a]
// take :: Int -> String -> String
const take = (n, xs) =>
'GeneratorFunction' !== xs.constructor.constructor.name ? (
xs.slice(0, n)
) : [].concat.apply([], Array.from({
length: n
}, () => {
const x = xs.next();
return x.done ? [] : [x.value];
}));
// unlines :: [String] -> String
const unlines = xs => xs.join('\n');
// Use of `take` and `length` here allows zipping with non-finite lists
// i.e. generators like cycle, repeat, iterate.
// zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
const zipWith = (f, xs, ys) => {
const
lng = Math.min(length(xs), length(ys)),
as = take(lng, xs),
bs = take(lng, ys);
return Array.from({
length: lng
}, (_, i) => f(as[i], bs[i], i));
};
// MAIN ---
return main();
})();