Mail to TP3 script?

Has anyone managed to convert and old TP2 script or produce a new script that sends the currently selected email in Mail to the current TP3 file?

Where would you like it to end up in the active TaskPaper file ?

An Inbox: project, for example ?

(or a project chosen from a menu, or even derived from the sender address ?)

I currently send them into an “Inbox:” project (in TP2) but it is interesting to consider your suggestions.

I typically have about 6 or 7 unchanging project titles so a dropdown or similar menu with a default of “Inbox:” would probably be an improvement. I think a rule based on sender would have less applications for me but maybe for others it would be useful.

I can twiddle with an existing AppleScript file to do some limited things but I’m a complete JavaScript novice!

I’ll take a look in a day or two, and sketch something up.

Great, really appreciated.

Very preliminary sketch here, just writing things straight to an Inbox: project. Not sure if the sequence of parts is right yet (sender, received, link, etc)

  1. My guess is that if you select several things from the same correspondent, mixed in with other stuff, the Inbox should probably show things grouped by sender ?
  2. On help with which projects they go to next, I think it’ll probably be more sensible for me to write a separate script for generally selecting TaskPaper items, throwing up a menu of target projects, and letting you choose which project to move them to.

(on grouping and sorting of multiple messages - I’ve thrown in a couple of functions for that at the top the script, but they are not in use yet - let me know if grouping and/or sorting sounds more useful than confusing. Sometimes magic just makes a mess, like ‘auto-correction’ ;- ).

Test in Script Editor with:

  1. dummy data (very rough and early pre-draft)
  2. the language drop-down at top left set to JavaScript
// SEND EMAILS SELECTED IN APPLE MAIL OR MS OUTLOOK 2016
// TO INBOX: PROJECT OF FRONT TASKPAPER DOCUMENT

// NB to use with Outlook 2016, edit option at end of script

// Ver 0.5 Generalised the version-specific application reference
// Ver 0.4 Allows for MS Outlook as well as Apple Mail
// Ver 0.31  Keeps whole inbox sorted when new material arrrives,
//          fixes issue when only 1 email was selected
//          checks that new Inbox is created and used if none found
// Ver 0.2  Sorts incoming mail by sender A-Z and datetime received New-Old

(function (dctOptions) {
    'use strict';

    // TASKPAPER CONTEXT

    function TaskPaperContext(editor, options) {

        // FIND OR MAKE A PROJECT TO HOLD INCOMING MAIL
        // String -> String -> {project: tpItem, new: Bool}
        function projectAtPath(strProjectPath, strDefaultName) {
            var strDefault = strDefaultName || 'UnNamed project:',
                outline = editor.outline,
                lstMatch = outline.evaluateItemPath(strProjectPath),
                blnFound = lstMatch.length > 0;

            return {
                project: blnFound ? lstMatch[0] : (function () {
                    var defaultProject = outline.createItem(
                        strDefault
                    );

                    return (
                        outline.groupUndoAndChanges(function () {
                            outline.root.appendChildren(
                                defaultProject
                            );
                        }),
                        defaultProject
                    );

                })(),
                new: !blnFound
            };
        }

        // tpItem -> (tpiItem -> tpItem -> (-1|0|1)) -> tpItem
        function sortChildren(item, fnSort) {
            var lstSort = item.children.sort(fnSort);

            return (
                item.removeChildren(item.children),
                item.insertChildrenBefore(
                    lstSort
                ),
                true
            );
        }
        // FIND OR CREATE AN INBOX

        var outline = editor.outline,
            mInbox = projectAtPath(options.inboxPath, 'Inbox:'),
            itemInbox = mInbox.project;

        // Doesn't seem to be undoable 
        // Perhaps only mutations of existing material undo ?
        outline.groupUndoAndChanges(function () {
            options.messages
                .forEach(function (msg) {
                    var item = outline.createItem(
                        '- ' + [msg.sender, msg.subject]
                        .join(' ') +
                        ' @received(' + msg.received + ')'
                    );

                    itemInbox.appendChildren(item);
                    item.appendChildren(
                        outline.createItem(msg.link)
                    );
                });

        });


        if (!mInbox.new && itemInbox.hasChildren) {
            return sortChildren(itemInbox, function (a, b) {
                var strRA = a.getAttribute('trailingMatch') || '', // date
                    strRB = a.getAttribute('trailingMatch') || '',

                    strTA = a.bodyString, // all
                    strTB = b.bodyString,

                    iFromA = strTA.charAt(2) === '"' ? 3 : 2,
                    iFromB = strTB.charAt(2) === '"' ? 3 : 2,

                    // Lowercase content, ignoring "- " +  any opening doubleQuote
                    strCA = strTA.slice(
                        iFromA, iFromA - strRA.length
                    )
                    .toLowerCase(),
                    strCB = strTB.slice(
                        iFromB, iFromB - strRB.length
                    )
                    .toLowerCase();

                // By A-Z sender and New to Old date
                return strCA === strCB ? (
                    strRA < strRB ? 1 : (strRA > strRB ? -1 : 0)
                ) : (strCA < strCB ? -1 : 1);
            });
        }
    }


    // JAVASCRIPT FOR AUTOMATION CONTEXT

    // Date -> String
    function fmtTP(dte) {
        var s = dte.toISOString(),
            d = s.substring(0, 10);

        return dte.getMinutes() ? d + ' ' + s.substring(11, 16) : d;
    }

    // String -> String
    function mailURL(strMessageID) {
        return "message://%3C" + strMessageID + "%3E";
    }

    // concatMap :: (a -> [b]) -> [a] -> [b]
    function concatMap(f, xs) {
        return [].concat.apply([], xs.map(f));
    }


    // READ MAIL or OUTLOOK SELECTIONS

    // Key message fields  
    if (dctOptions.mailApp.toLowerCase()
        .indexOf('outlook') === -1) {

        // APPLE MAIL
        var m = Application("Mail"),
            lstSeln = m.selection();

        dctOptions.messages = lstSeln.length > 0 ? lstSeln
            .map(function (msg) {
                return {
                    'sender': msg.sender(),
                    'subject': msg.subject(),
                    'received': fmtTP(msg.dateReceived()),
                    'link': mailURL(msg.messageId()),
                };
            }) : [];
    } else {
    
        // MS OUTLOOK 2016
        var ol = Application("com.microsoft.Outlook"),
            lstSeln = ol.selectedObjects();

        dctOptions.messages = concatMap(function (x) {
            var strClass = x.class();

            if (strClass
                .endsWith('Message')) {
                var dctSender = x.sender();

                return [{
                    'sender': dctSender.name + ' ' +
                        dctSender.address,
                    'subject': x.subject(),
                    'received': strClass.indexOf('incoming') ===
                        0 ? fmtTP(x.timeReceived()) : '',
                    'link': 'outlook://' + x.id()
                }]
            } else return [];

        }, lstSeln);
    }

    //return dctOptions.messages

    var ds = Application("TaskPaper")
        .documents,
        d = (ds.length ? ds[0] : undefined);

    if (d) {
        if (d.file()) {
            return d.evaluate({
                script: TaskPaperContext.toString(),
                withOptions: dctOptions
            });
        } else {
            var a = Application.currentApplication(),
                sa = (a.includeStandardAdditions = true, a),
                strMsg =
                "Script: (Email to TaskPaper)\n\nFirst save TaskPaper file ...",
                strAppFolder = sa.pathTo("applications folder", {
                    from: 'user domain'
                })
                .toString();

            sa.displayDialog(strMsg, {
                withTitle: "TaskPaper file not saved"
            });
        }
    }
})({
    inboxPath: '//Inbox and @type=project[-1]',
    mailApp: 'com.apple.mail' // or edit to 'com.microsoft.Outlook'
});
5 Likes

This is fantastic!

The sequence of message elements is just right in my opinion. Separating the processes of moving messages to the Inbox: and then placing them into projects would probably work just as well as combining them. In some cases tasks/ messages would just stay in the Inbox: until completed, as they may not need to be assigned to a project, or a suitable project may not yet exist and need to be created first.

I think grouping by sender is the behaviour I (and other users?) would “expect” to see and I think it would be a useful addition. I agree with you about auto-correction; it’s like trying to shovel smoke with a pitchfork in the wind!

Thanks again, I owe you a Guinness.

2 Likes

Here’s a first draft of a script for:

  1. Selecting one or more items
  2. Choosing a new destination project for them from a menu

The move sections are wrapped in the ‘Undo’ bracketing which Jesse has included in the scripting interface, so ⌘Z should extricate from the unexpected, but do experiment a bit with dummy data until the behaviour seems to make sense, or we have got itinto a more finished state:

// Select item(s) in TaskPaper 3 (Build 170+)
// and run this script to:
//  1. Choose a target project from a user menu which pops up
//  2. Move the selected item(s) (with any children) to the start of 
//     the chosen project
//  3. See a notification of destination and number moved

// TO TEST: Paste into Script Editor

//ver 0.41 Ignore request to move tasks to themselves
//ver 0.4 activate TaskPaper 3 after move
//ver 0.4 fixed visibility of moved items (clearing hoisting + filters)
//ver 0.31 updated item-moving code
//         selects moved items in their new position
//ver 0.3  (tweaked notification text)

(function () {
    'use strict';

    // TASKPAPER CONTEXT FUNCTIONS

    // PROJECTS IN ACTIVE DOCUMENT
    function tpProjectList(editor, options) {

        return (editor.selection.selectedItems.length > 0) ?
            editor.outline.evaluateItemPath(
                '//@type=project')
                .map(function (p) {
                var strName = p.bodyString;

                return {
                    id: p.id,
                    name: strName.slice(
                        0, strName.indexOf(':')
                    )
                }
            }) : [];
    }

    // MOVEMENT OF SELECTED ITEMS TO START OF CHOSEN PROJECT
    function tpMoveSelected(editor, options) {

        var outline = editor.outline,
            lstSelns = editor.selection.selectedItems,
            lngSelns = lstSelns.length;

        if (lngSelns > 0) {
            var oFirst = lstSelns[0],
                oLast = lngSelns > 1 ? lstSelns[lngSelns - 1] : oFirst,

                oProject = outline.getItemForID(
                    options.projectID
                ),
                oFirstChild = oProject.firstChild || (function () {
                    outline.groupUndoAndChanges(function () {
                        oProject.appendChildren(outline.createItem());
                    });
                    return oProject.firstChild;
                })();

            // If we have a non-circular destination, make the move ...
            if (oFirstChild && (lstSelns.indexOf(oFirstChild) === -1)) {
                outline.groupUndoAndChanges(function () {
                    outline.insertItemsBefore(
                        lstSelns,
                        oFirstChild
                    );
                });

                // make sure that source and target are both visible
                editor.hoistedItem = null;
                editor.itemPathFilter = '';

                // Select the newly moved items, for more visibility
                editor.moveSelectionToItems(
                    oFirst, 0,
                    oLast, oLast.bodyString.length
                );

                return lstSelns.length;
            } else return 0
        } else return 0;
    }


    // JAVASCRIPT FOR AUTOMATION CONTEXT

    // PROJECTS IN ACTIVE DOCUMENT ?
    var tp3 = Application("com.hogbaysoftware.TaskPaper3"),
        ds = tp3.documents,
        d = ds.length ? ds[0] : undefined,
        lstProjects = d ? d.evaluate({
            script: tpProjectList.toString(),
        }) : [];

    // USER CHOICE OF TARGET PROJECT ?
    if (lstProjects.length > 0) {
        var a = Application.currentApplication(),
            sa = (a.includeStandardAdditions = true, a),
            e = Application("SystemUIServer"),
            se = (e.includeStandardAdditions = true, e);

        var lstMenu = lstProjects.map(function (x) {
                return x.name;
            }),

            varChoice = (se.activate(),
                se.chooseFromList(lstMenu, {
                    withTitle: "Move items to Project",
                    withPrompt: 'Choose:',
                    defaultItems: lstMenu[0],
                    okButtonName: 'OK',
                    cancelButtonName: 'Cancel',
                    multipleSelectionsAllowed: false,
                    emptySelectionAllowed: true
                })),

            lstChoice = varChoice ? varChoice : [];

        if (lstChoice.length > 0) {
            // MAKE THE MOVE IN TASKPAPER 3
            var dctChoice = lstProjects[lstMenu.indexOf(lstChoice[0])],
                lngMoved = d.evaluate({
                    script: tpMoveSelected.toString(),
                    withOptions: {
                        projectID: dctChoice.id
                    }
                });

            // AND REPORT ON ANY RESULT
            if (lngMoved > 0) {
                sa.displayNotification('in TaskPaper 3 Preview', {
                    withTitle: lngMoved + ' selected item' + (
                        lngMoved > 1 ? 's' : ''
                    ) + ' moved',
                    subtitle: "to start of '" + dctChoice.name +
                        "'",
                    sound: 'glass'
                })
            }
            tp3.activate();
            return lngMoved;
        }
    }
})();
1 Like

I’ll take a look at the grouping of mail by sender.

(Probably not before tomorrow)

I like that you can select multiple items to move en masse, and it works well with an Alfred hotkey. These scripts really help with managing email overload :smile:

1 Like

Ver 0.2 of the Selected Mail Items -> TaskPaper Inbox: script (above) just sorts the incoming mail by sender and time received – it doesn’t group under sender headings.

Would sender headings make sense ? If you use the script a second time before clearing the previous batch, perhaps additional mail from an existing sender should join its heading-grouped predecessors ?

(An alternative would just be to re-sort the contents of the whole inbox (by sender and received) each time the script runs).

Update: Ver 0.3 keeps the whole inbox sorted (A-Z sender, then New-Old received) when new material arrives, and fixes a glitch which ignored the script if only one email was selected.

1 Like

The chronological ordering of the message carries useful information and is a definite benefit in terms of organising items and/or deciding the order that items are actioned.

I was thinking about the helpfulness of grouping child items under a sender header. On one hand it would seem like typical behaviour for an outliner-type application. On the other it could potentially make the list items appear a bit busy. Also an original message with subsequent messages included as child items underneath its not strictly a regular sort of hierarchy, unless you had something else in mind.

I suppose the answer to the question “If I manually indented these messages to keep them all under a sending header would it help and could it be done automagicallly? (sorry ;))” would indicate the best approach. I generally try to keep task items for each project at the first level of indentation but other cases may differ.

Lets skip grouping for the moment : -)

The sort in version 0.3 above is sender A-Z then chronology recent->old

If that seems to work we can leave it, unless you would prefer to go for strict chronology.

(Another thing would be a script/button for sorting the inbox in different ways - easily done, but not sure if there’s a need for it - toggling for example, between by Sender+Time and just by Time)

1 Like

Ah yes of course A-Z first and then recent --> old, is just right. I think you have the balance right as it is. For me this is a such a useful function and for many others too I’m sure, great work!

1 Like

Newbie question: how is this script run? I can use Keyboard Maestro, but thus far, I can get the script to work. Any suggestions?

Probably worth:

  1. Installing this KM macro directly: Send selected emails (Mail / Outlook 2016) to TaskPaper 3 - Macro Library - Keyboard Maestro Discourse

  2. Experimenting with alternative hot-key keystrokes, in case there is a clash

1 Like

Thank you @complexpoint — I have it working now, using the posted macro.

Is it easy to remove the sender and date received? Basically, I just want to have the subject of the e-mail and the link to the e-mail entered in TP3.

Is it easy to remove the sender and date received ?

Absolutely. Just replace the source code in the ‘Execute JavaScript for Automation’ action with a lightly edited copy.

  • Copy-paste the whole of the source into Script Editor, check that the language selector at top left reads JavaScript rather than AppleScript, and test run.
  • then, search for @received,
  • notice that JavaScript concatenates quoted strings with the + operator, and add and remove whatever you like. (Testing that it runs with each small change).

Then just paste it all back into the KM JavaScript action, and test again.

1 Like

Thanks @complexpoint — I went with the following, after hitting my head against JavaScript formatting for the last couple of hours:

outline.groupUndoAndChanges(function () {
		lstMsg
			.forEach(function (msg) {
				var item = outline.createItem(
					'- Reply to ' + [msg.subject]
				);
				itemInbox.appendChildren(item);
				item.appendChildren(
					outline.createItem(msg.link)
				);
			});
	});

Overall, I am happy with it, although I’d like to indent the msg.link with one more tab. Thoughts?

1 Like

Nice work there, @complexpoint!
My challenge is somewhat different, but related.
I currently use OmniFocus to maintain my ‘Waiting for …’ list.
When I send an email with a request, I Bcc my personal inbox at the OmniSync server. The subject of the mail becomes the name of the task, the content ends up in the task’s Note field.
Could this be realised with Apple Mail on OS X and TaskPaper?