In the tradition of plastic kitchen tomatoes and focused ‘sprints’ of c. 25 mins on component sub-projects:
I personally use Due.app for these timed sprints, mainly because I find that its classical guitar sound makes a familiar and fairly gentle recall from narrow focus to broader context.
Due.app reminders can
- be set for N mins hence
- contain a text which contains a hyperlink (launched with the → key)
- be initiated from outside through a url scheme
The link in one reminder can be a url for a following reminder, so we can automatically set up a chain of 3 or 4 sprints if we want.
This script (osascript JS - JavaScript for Automation - JXA) which I personally launch from and customise with Keyboard Maestro:
- Captures the names of any notes that are selected in the Tinderbox 8 GUI
- Optionally captures any integers from a custom attribute, perhaps with a name like mins, which you can specify, as an indication of the sprint length wanted.
- Constructs a nested Due.app url
Once the first sprint has been launched by the url, you can tap → in Due to start the next one. And so forth for each note-sprint encoded in the nest.
So, for example, from a three-note selection like:
The script would first launch Due.app with this sprint, which itself contains a further url-encoded sprint:
From which the next sprint-link could later be launched (at or after the sound of the first reminder sound) with the → key, or from a gear-wheel menu:
leading to:
The default sprint length, and the name of any custom duration attribute to use in Tinderbox, can be adjusted in the Keyboard Maestro macro, or by editing the script directly.
Keyboard Maestro macro at:
JavaScript source
(() => {
'use strict';
// Rob Trew 2019
// Ver 0.01
// SETTINGS -------------------------------------------
const
kme = Application('Keyboard Maestro Engine'),
// The name of an (optional) attribute, with an
// integer value representing a number of minutes.
k = kme.getvariable('minsAttributeName') || 'mins',
// A default number of minutes for a plastic tomato
// dash or sprint of attention to a note or issue.
defaultMins = kme.getvariable('minsDefault') || 25;
// ----------------------------------------------------
// Due.app URL, with text and duration in minutes,
// for any selected Tinderbox 8 items
// main :: IO ()
const main = () => {
const
tbx8 = Application('Tinderbox 8'),
ds = tbx8.documents;
return bindLR(
bindLR(
0 < ds.length ? (
Right(ds.at(0))
) : Left('No documents open in Tinderbox 8'),
d => {
const xs = d.selections();
return 0 < xs.length ? Right(
concatMap(
x => {
const
attr = x.attributes.byName(k),
txt = x.name().trim();
return 0 < txt.length ? [Tuple(
txt,
attr.exists() ? (
parseInt(attr.value())
) : (
console.log(
'No "' + k +
'" attribute for: "' + txt +
'". (Using default: ' +
defaultMins.toString() +
' mins).'
),
defaultMins
)
)] : []
},
xs
)
) : Left('Nothing selected in ' + d.name());
}
),
nestedDueURL
);
};
// DUE URL --------------------------------------------
// nestedDueURL :: [(String, Int)] -> URL String
const nestedDueURL = tms => {
const go = xs => {
const
t = xs.slice(1),
[s, n] = Array.from(xs[0]);
return 'due://x-callback-url/add?title=' +
encodeURIComponent(
s + (0 < t.length ? ' '.repeat(12) +
go(t) : '')
) + '&minslater=' + n.toString();
};
return 0 < tms.length ? go(tms) : '';
};
// 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
});
// Tuple (,) :: a -> b -> (a, b)
const Tuple = (a, b) => ({
type: 'Tuple',
'0': a,
'1': b,
length: 2
});
// bindLR (>>=) :: Either a -> (a -> Either b) -> Either b
const bindLR = (m, mf) =>
undefined !== m.Left ? (
m
) : mf(m.Right);
// concatMap :: (a -> [b]) -> [a] -> [b]
const concatMap = (f, xs) =>
xs.reduce((a, x) => a.concat(f(x)), []);
// 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;
// id :: a -> a
const id = x => x;
// MAIN -----------------------------------------------
return main();
})();