and then a fuller JavaScript for Automation Script, also for use either from Script Editor (with the language tab at top left set to JavaScript) or from Keyboard Maestro etc.
This version provides alerts and richer explanatory messages if it is run for example, when any of the following turn out to be the case:
- Tinderbox 8 is not running
- No document is open in TB8
- Nothing is selected in TB8
- The named attribute doesn’t exist (as spelled) in the front Tinderbox document
- The named attribute value doesn’t contain something which Tinderbox can parse as a date
- Reminders doesn’t contain a list matching the list name provided
When a reminder with an alert is created, it shows a dialog of this kind:
and creates an alert in the specified Reminders list like:
(() => {
'use strict';
// Rob Trew 2019
// Ver 0.06
// Added explanatory messages for case where the string
// given for alertDateAttribName doesn't match an
// attribute in the front Tinderbox 8 document.
// alertDateAttribName, remindersListName :: String
const
alertDateAttribName = 'StartDate',
remindersListName = 'General';
// main :: IO ()
const main = () =>
either(
alert('Could not create alert'), // + explanatory message.
x => (
Application('Reminders').activate(),
alert('Reminder alert created')(x)
),
bindLR(
tbxTitle_URL_DateFromSelectedNoteLR(
alertDateAttribName
),
reminderAlertFromNameAndDateLR(
remindersListName
)
)
);
// TINDERBOX FUNCTIONS --------------------------------
// tbxAttribValLR :: TBX Note -> String -> String
const tbxAttribValLR = note => k => {
// Either an explanatory message (Left) or
// an attribute value (Right).
const mb = note.attributes.byName(k);
return mb.exists() ? (
Right(mb.value())
) : Left('Attribute not found: ' + k);
};
// tbxFrontDocLR :: Tbx IO () -> Either String Tbx Doc
const tbxFrontDocLR = () => {
// Either an explanatory message if no documents are open,
// or or Tinderbox 8 is not running,
// or a reference to the front Tinderbox 8 document.
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.');
};
// tbxSelectedNoteLR :: Tbx Doc -> Either String Tbx Note
const tbxSelectedNoteLR = doc => {
// Either an explanatory message if nothing is selected,
// or a reference to the first selected note.
const note = doc.selectedNote();
return note !== null ? (
Right(note)
) : Left('No note selected in ' + doc.name());
};
// tbxTitle_URL_DateFromSelectedNoteLR ::
// String -> Either String (String, URL, Date)
const tbxTitle_URL_DateFromSelectedNoteLR = attributeName =>
// Either an explanatory message if no valid date is found,
// or a (name, url, date) triple.
bindLR(
bindLR(
tbxFrontDocLR(),
tbxSelectedNoteLR
),
note => {
// attribValLR :: String -> String
const attribValLR = tbxAttribValLR(note);
return bindLR(
attribValLR(attributeName),
s => {
const maybeDate = new Date(s);
return isNaN(maybeDate) ? (
Left('Not a valid date: "' + s + '"')
) : Right(Tuple3(
attribValLR('Name').Right,
attribValLR('NoteURL').Right,
maybeDate
))
}
);
}
);
// JXA AND REMINDERS ----------------------------------
// 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
);
};
// reminderAlertFromNameAndDate :: String -> (String, URL, Date) ->
// -> Either String (IO macOS reminder creation)
const reminderAlertFromNameAndDateLR = remindersListName =>
tupleNameURLDate => {
const
appRems = Application('Reminders'),
listsWithMatchingName = appRems.lists.where({
name: remindersListName
});
return 0 < listsWithMatchingName.length ? (() => {
const [title, url, alertDate] = Array.from(
tupleNameURLDate
);
return (
appRems.lists.byName(remindersListName)
.reminders.push(appRems.Reminder({
name: title,
body: url,
remindMeDate: alertDate
})),
Right(
'New alert created in ' +
remindersListName + ' list:\n\n"' + title +
'"\n\n' + alertDate
)
);
})() : Left(
'Reminders list not found: "' +
remindersListName + '".\n\n' + 'Lists found:\n' +
bulleted(' ', unlines(appRems.lists.name()))
);
};
// 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
});
// Tuple3 (,,) :: a -> b -> c -> (a, b, c)
const Tuple3 = (a, b, c) => ({
type: 'Tuple3',
'0': a,
'1': b,
'2': c,
length: 3
});
// bindLR (>>=) :: Either a -> (a -> Either b) -> Either b
const bindLR = (m, mf) =>
undefined !== m.Left ? (
m
) : mf(m.Right);
// bulleted :: String -> String -> String
const bulleted = (strIndent, s) =>
s.split(/[\r\n]/).map(
x => '' !== x ? strIndent + '- ' + x : x
).join('\n')
// 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;
// showLog :: a -> IO ()
const showLog = (...args) =>
console.log(
args
.map(JSON.stringify)
.join(' -> ')
);
// unlines :: [String] -> String
const unlines = xs => xs.join('\n');
// MAIN ---
return main();
})();