Action

Process QuickNotes

Posted by iCooper, Last update 5 months ago

Outlook Mail Tasks for Drafts (aka QuickNotes)

What is QuickNotes?

Use Drafts to quickly note down tasks and notes. And if you do a task quickly, mark it done. Do you want to clean up the draft and receive any pending tasks per mail? Invoke QuickNotes action.

QuickNotes is a Drafts action which:

  1. parses current draft looking for tasks and notes
  2. sends each pending task individually per mail, e.g., to allow Outlook users to flag them as a task
  3. archives current draft by appending it into the monthly QuickNotes archive draft
  4. optionally moves current draft to trash

Repo: https://github.com/vecepet/quick-notes

QuickNotes Input Formatting

QuickNotes action processes current draft. It recognizes the following elements:
- draft title
- task - a task can be pending or done
- note

Draft Title

A draft title is the first line of a draft, in case the line starts with #. A draft can be with or without a draft title.

A draft having draft title starting with # QuickNotes is a QuickNotes draft and it has special processing.

Task

A task starts with a task subject - a line starting with
- - [ ] or [ ] in case of a pending task or
- - [x] or [x] in case of a done task

A task ends if
- next line is empty or
- next line is a start of another task.

A task can have a task body - one or more task comment lines. A task can be with or without a task body.

Note

A note consist of subsequent non-empty lines, which are not part of any task.

Example Draft

have met Martin

- [ ] buy cola
preferably sugar free

[ ] buy potatoes
- organic please
- not imported
[x] buy newspaper
English or German
- [ ] call John
- [x] call mother

watched a good movie
- The Shawshank Redemption   

QuickNotes Processing

QuickNotes action processes current draft. The processing consists of three steps:
- mailing pending tasks
- archiving current draft
- optionally moving current draft to trash

Mailing Pending Tasks

Processing the example draft above will cause 3 mails to be sent, each containing one pending task.

Note that the task prefix - [ ] or [ ] will be removed.

Mail 1

Subject: buy cola

         preferably sugar free

Mail 2

Subject: buy potatoes

         - organic please
         - not imported

Mail 3

Subject: call John

         -

See also [Configuring QuickNotes Processing] below.

Note, QuickNotes action uses Send in background option. It is used to send the email via web service without the requirement of opening a preview of the message. Because these messages come “From” a generic address, it’s best for action which email to your own email address as a reminder, or similar.

Archiving Current Draft

QuickNotes action creates one archive draft per month. This archive draft is located in the Archive and it has draft title # QuickNotes YYYY/MM. All drafts processed within the month will be appended into it.

Prefix of the pending tasks [ ] will be modified to [>], signalling that the task was forwarded.

Processing the example draft above will append the following text to the archive draft, with the header containing current timestamp.

# QuickNotes 2020/11

## QuickNotes - 2020/11/02 22:32:14

have met Martin

- [>] buy cola             
preferably sugar free      

[>] buy potatoes           
- organic please           
- not imported             
[x] buy newspaper          
English or German          
- [>] call John            
- [x] call mother          

watched a good movie       
- The Shawshank Redemption 

Moving Current Draft to Trash

As the last step, the processed draft will usually be moved to trash. There are three options:

  • A QuickNotes draft will be processed and moved to trash, and afterwards a new QuickNotes draft will be created.

  • Any draft without a draft title will be processed and moved to trash, and afterwards a new blank draft will be created.

  • Any draft with a draft title will be processed, but not moved to trash.

Configuring QuickNotes Processing

When running QuickNotes action for the first time, the user must enter a mail address to receive pending tasks per mail. This information is stored using Credential object. See https://scripting.getdrafts.com/classes/credential.

Steps

  • script

    /* *********************************************************************
    
        Initialize QuickNotes settings variables
    
    ********************************************************************* */
    
    // get mail recipient to receive notes and pending tasks
    // using Credential object https://scripting.getdrafts.com/classes/credential
    const credential = Credential.create("QuickNotes Mail Address", " Enter a mail address to receive pending tasks");
    credential.addTextField("recipientMail", "QuickNotes Recipient Mail Address");
    credential.authorize();
    
    // create QuickNotes title
    const quickNotesTitle = "# QuickNotes"
    // create tag to be assigned to quick notes drafts
    const quickNotesTag = "quick-notes";
    
    /* *********************************************************************
    
        QuickNotes processing classes and functions
    
    ********************************************************************* */
    
    // class to create current date and time strings
    class DateTimeString {
        constructor() {
            this._now = new Date();
        }
    
        _pad2(n) {
            return n < 10 ? '0' + n : n
        }
    
        get ym() {
            return this._now.getFullYear().toString() + "/"
                + this._pad2(this._now.getMonth() + 1)
        }
    
        get ymd() {
            return this.ym + "/"
                + this._pad2(this._now.getDate())
        }
    
        get ymdHM() {
            return this.ymd + " "
                + this._pad2(this._now.getHours()) + ":"
                + this._pad2(this._now.getMinutes())
        }
    
        get ymdHMS() {
            return this.ymdHM + ":"
                + this._pad2(this._now.getSeconds())
        }
    }
    
    // class to collect all notes and tasks
    class QuickNotesCollection {
        constructor() {
            // initialize property to distinguish "note" mode, "task-done" mode and "task-pending" mode
            this._mode = "";
            // initialize property to collect all lines of the current note
            this._currentNote = "";
            // initialize property to collect all lines of the current task
            this._currentTask = "";
            // initialize property to collect all notes - CURRENTLY NOT USED AFTER PARSING
            this._notes = "";
            // initialize property to collect all pending tasks
            this._tasksPending = Array();
            // initialize property to collect all done tasks - CURRENTLY NOT USED AFTER PARSING
            this._tasksDone = Array();
            // initialize property to collect all lines of the archive entry
            this._archiveEntry = "";
        }
    
        // method to add line to current item (note or task)
        addLine(line) {
            switch (this._mode) {
                case "note":
                    this._currentNote += line.trim() + "\n";
                    break;
                case "task-pending":
                case "task-done":
                    this._currentTask += line.trim() + "\n";
                    break;
            }
        }
    
        //method to add line to archive entry
        addLineArchive(line) {
            this._archiveEntry += line.trim() + "\n";
        }
    
        // method to push current item (note or task)
        push() {
            switch (this._mode) {
                case "note":
                    // add '_currentNote' to '_notes' and empty '_currentNote'
                    if (this._currentNote !== "") {
                        this._notes += this._currentNote + "\n";
                        this._currentNote = "";
                    }
                    break;
                case "task-pending":
                    // add '_currentTask' to '_tasksPending' and empty '_currentTask'
                    if (this._currentTask !== "") {
                        this._tasksPending.push(this._currentTask);
                        this._currentTask = "";
                    }
                    break;
                case "task-done":
                    // add '_currentTask' to '_tasksDone' and empty '_currentTask'
                    if (this._currentTask !== "") {
                        this._tasksDone.push(this._currentTask);
                        this._currentTask = "";
                    }
                    break;
            }
        }
    
        // setter to set _mode
        set mode(modeId) {
            this._mode = modeId;
        }
    
        // getter to get all notes
        get notes() {
            return this._notes.trim();
        }
    
        // getter to get all pending tasks
        get tasksPending() {
            return this._tasksPending;
        }
    
        // getter to get all done tasks
        get tasksDone() {
            return this._tasksDone;
        }
    
        // getter to get archive entry
        get archiveEntry() {
            return this._archiveEntry.trim();
        }
    }
    
    // function to find or create draft starting with 'queryString'
    function get_draft(queryString, filter) {
        // query for drafts
        let drafts = Draft.query(queryString, filter, [quickNotesTag], [], "modified", true);
        // loop over found drafts looking for a matching draft
        let d;
        for (let draft of drafts) {
            if (draft.content.startsWith(queryString)) {
                d = draft;
            }
        }
        // if we didn't find the draft, create it
        if (!d) {
            d = Draft.create();
            d.content = queryString;
            d.addTag(quickNotesTag);
            if (filter === "archive") d.isArchived = true;
        }
        // add one empty line
        d.content = d.content.trim() + "\n\n";
        d.update();
        return d;
    }
    
    // function to mail a single task
    function mail_task(task) {
        let mail = Mail.create();
        mail.toRecipients = [credential.getValue("recipientMail")];
        mail.sendInBackground = true;
    
        // get lines
        let lines = task.trim().split("\n");
        // first line is the actual task - use as mail subject
        mail.subject = lines[0];
    
        // get task comments - use as mail body
        if (lines.length > 1) {
            lines.shift();
            mail.body = lines.join("\n");
        } else {
            mail.body = "-";
        }
    
        // send mail
        let success = mail.send();
        if (!success) {
            console.log(mail.status);
            context.fail();
        }
    }
    
    /* *********************************************************************
    
        Initialize QuickNotes other variables
    
    ********************************************************************* */
    
    // create current date and time strings
    const now = new DateTimeString;
    // create quick notes collection
    let quickNotes = new QuickNotesCollection();
    
    /* *********************************************************************
    
        Process current draft
    
    ********************************************************************* */
    
    // get current draft
    const currentDraft = draft;
    
    // get current draft's content
    const currentContent = currentDraft.content.trim();
    
    // split draft to loop over paragraphs
    let paragraphs = currentContent.split("\n\n");
    
    // loop over paragraphs
    for (let paragraph of paragraphs) {
        // skip QuickNotes title
        if (paragraph === quickNotesTitle) continue;
        // always assume a paragraph is a note
        quickNotes.mode = "note";
        // split each paragraph to loop over lines
        let lines = paragraph.split("\n");
        for (let line of lines) {
            // check if a new task starts on the current line
            if (line.startsWith("[ ]") || line.startsWith("- [ ]")) {
                // current line is a pending task, push previous task and change mode
                quickNotes.push();
                quickNotes.mode = "task-pending";
                // adding to task array without task prefix
                quickNotes.addLine(line.replace(/^(- |)\[[ x]]/, ""))
                quickNotes.addLineArchive(line.replace("[ ]", "[>]"));
            } else if (line.startsWith("[x]") || line.startsWith("- [x]")) {
                // current line is a done task, push previous task and change mode
                quickNotes.push();
                quickNotes.mode = "task-done";
                 // adding to task array without task prefix
               quickNotes.addLine(line.replace(/^(- |)\[[ x]]/, ""));
                quickNotes.addLineArchive(line);
            } else {
                // current line still belongs to the current item
                quickNotes.addLine(line);
                // add current line to archive
                if (line.startsWith("#")) {
                    // current line is a header line - in archive we will replace '#' at the beginning with '*** #'
                    quickNotes.addLineArchive(line.replace("#", "*** #"));
                } else {
                    // current line is not a header line
                    quickNotes.addLineArchive(line);
                }
            }
        }
        // add empty line to archive
        quickNotes.addLineArchive("");
        // before processing next paragraph push current task
        quickNotes.push();
        // before processing next paragraph push current note
        quickNotes.mode = "note";
        quickNotes.push();
    }
    
    /* *********************************************************************
    
        Archive content of the current draft and mail pending tasks
    
    ********************************************************************* */
    
    // get QuickNotes archive draft for the current month
    let archivedQuickNotes = get_draft(quickNotesTitle + " " + now.ym, "archive")
    
    // if archiveEntry is not empty, update archived QuickNotes draft
    if (quickNotes.archiveEntry.length !== 0) {
        archivedQuickNotes.content += "## QuickNotes - " + now.ymdHMS + "\n\n";
        archivedQuickNotes.content += quickNotes.archiveEntry + "\n\n";
        archivedQuickNotes.update();
    }
    
    // mail pending tasks one by one, if any
    if (quickNotes.tasksPending.length !== 0) {
        for (let task of quickNotes.tasksPending) {
            mail_task(task);
        }
    }
    
    /* *********************************************************************
    
        Trash and Open QuickNotes
    
    ********************************************************************* */
    
    if ((currentContent.startsWith(quickNotesTitle)) && (currentContent !== quickNotesTitle)) {
        // if current draft is QuickNotes and it is not empty, move it to trash
        currentDraft.isTrashed = true;
        currentDraft.update();
    
        // get new empty QuickNotes draft
        let quickNotes = get_draft(quickNotesTitle, "inbox")
    
        // open it in the editor and go to the end
        editor.activate();
        editor.load(quickNotes);
        editor.setSelectedRange(quickNotes.content.length, 0);
    } else if (!currentContent.startsWith("#") && currentContent !== "") {
        // if current draft does not start with heading and it is not empty, move it to trash
        currentDraft.isTrashed = true;
        currentDraft.update();
        // activate editor and create a new blank draft
        editor.activate();
        editor.new();
    } else {
        // if current draft starts with heading or it is empty, show editor to hide actions
        editor.activate();
        editor.deactivate();
    }
    

Options

  • After Success Nothing
    Notification Info
    Log Level Info
Items available in the Drafts Directory are uploaded by community members. Use appropriate caution reviewing downloaded items before use.