Action
Process QuickNotes
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:
- parses current draft looking for tasks and notes
- sends each pending task individually per mail, e.g., to allow Outlook users to flag them as a task
- archives current draft by appending it into the monthly QuickNotes archive draft
- 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