Action
Save to Evernote (Full Formatting)
Convert draft Markdown to ENML and save to a note in Evernote with the title as note name.
This action uses some scripting to make the draft output be compatible with Evernote (ENML) and preserves the following when saving to Evernote:
- All heading formatting up to h3 (max supported by new Evernote)
- Line breaks and makes them Evernote compatable
- All list indentations
- Code blocks and make them Evernote compatible
- Inline
code block
and formats as monospace font in Evernote - Checklists and todo lists with full functionality in Evernote to interact with them
- All other standard markdown other than Evernote specific stuff.
- Tables with formatting improvements
Steps
-
script
const CONFIG_LIST_INDENT_SPACES = 4; const CONFIG_MONOSPACE_INLINE_CODEBLOCKS = true; const CONFIG_RETAIN_CODE_BLOCK_LANG_AS_COMMENT = true; // Code block langage is not supported by ENML const DEBUG_STRING = ``; const DEBUG = false; const TYPE_CHECK_UNCHECKED = "TYPE_CHECKLIST_UNCHECKED"; const TYPE_CHECK_CHECKED = "TYPE_CHECK_CHECKED"; const TYPE_UL = "TYPE_UL"; const TYPE_OL = "TYPE_OL"; let mmd; if (!DEBUG) { mmd = MultiMarkdown.create(); mmd.format = "html"; mmd.noMetadata = true; mmd.footnotesEnabled = false; mmd.criticMarkup = true; } function getListIndent(listItemType, listItem) { function indentPosition(RegexTest) { const indexLocationToFirstChar = listItem.match(RegexTest); if (indexLocationToFirstChar) { const indent = indexLocationToFirstChar[2]; let spacesCount = 0; if (indent) { spacesCount = indent.length } return Math.ceil(spacesCount/CONFIG_LIST_INDENT_SPACES) } else { return 0 } }; switch(listItemType) { case TYPE_CHECK_UNCHECKED: return indentPosition(new RegExp(/^((\s+)?- \[\s\])/)); case TYPE_CHECK_CHECKED: return indentPosition(new RegExp(/^((\s+)?- \[x\])/)); case TYPE_UL: return indentPosition(new RegExp(/^((\s+)?-\s)/)) || indentPosition(new RegExp(/^((\s+)?\*\s)/)); case TYPE_OL: return indentPosition(new RegExp(/^((\s+)?\d+\.\s)/)); } } function getListItemType(line) { if (!line) return null; const trimmedLine = line.trim(); const firstFiveChars = trimmedLine.substring(0, 5); const firstTwoChars = trimmedLine.substring(0, 2); if (firstFiveChars === "- [ ]" ) { return TYPE_CHECK_UNCHECKED; } else if(firstFiveChars === "- [x]") { return TYPE_CHECK_CHECKED; } else if (firstTwoChars === "- " || firstTwoChars === "* ") { return TYPE_UL; } else if (firstFiveChars.match(/^\d+\.\s/)) { return TYPE_OL; } else { return null } } let draftBody = ''; if (DEBUG) { draftBody = DEBUG_STRING; } else { draftBody = draft.content; } // Fix <hr /> converted incorrectly by some apps in correctly by some apps draftBody = draftBody .replace(/</g, "<") .replace(/>/g, ">") .replace(/^-\s-\s-\s-$/gm, "---") .replace(/\t/g," "); const originalContent = draftBody.split("\n"); const originalContentWorkingCopy = draftBody.split("\n"); originalContent.shift(); originalContentWorkingCopy.shift(); let listGroups = [[]]; let processingGroup = false; // Create groupings of lists to be converted to ENML originalContent.forEach((line, index) => { const lineRecord = {}; const listItemType = getListItemType(line); let listItemFound = Boolean(listItemType); if (listItemType) { lineRecord.type = listItemType; lineRecord.indentPosition = getListIndent(listItemType, line); } const groupStartCriteriaMatch = (!originalContent[index-1] || !Boolean(getListItemType(originalContent[index-1]))) && listItemFound; const groupEndCriteriaMatch = (!originalContent[index+1] || originalContent[index+1].trim() === "") || !Boolean(getListItemType(originalContent[index+1])); if (processingGroup || groupStartCriteriaMatch) { if (!processingGroup) { processingGroup = true; originalContentWorkingCopy[index] = `{{REPLACE_LIST-${listGroups.length}}}`; } else { originalContentWorkingCopy[index] = null; } lineRecord.content = line.trim(); listGroupsLastIndex = listGroups.length-1; listGroups[listGroupsLastIndex].push(lineRecord); if (groupEndCriteriaMatch) { listGroups.push([]); processingGroup = false; } } }); // Clean up list groups listGroups = listGroups.filter((listGroup) => Boolean(listGroup.length)); let processedLists = []; if (listGroups.length) { listGroups.forEach((listGroup, groupIndex) => { processedLists.push([]); let currentIndentPosition = 0; let endTagStack = []; listGroup.forEach((listItem, itemIndex) => { if (itemIndex === 0) { if (listItem.type === TYPE_OL) { processedLists[groupIndex].push("<ol>"); endTagStack.push("</ol>"); } else { processedLists[groupIndex].push("<ul>"); endTagStack.push("</ul>"); } } if(currentIndentPosition != listItem.indentPosition) { if (currentIndentPosition < listItem.indentPosition) { if (listItem.type === TYPE_OL) { processedLists[groupIndex].push("<ol>"); endTagStack.push("</ol>"); } else { processedLists[groupIndex].push("<ul>"); endTagStack.push("</ul>"); } } else { (new Array(currentIndentPosition - listItem.indentPosition)).fill().forEach(() => { processedLists[groupIndex].push(endTagStack.pop()) }); } currentIndentPosition = listItem.indentPosition; } // Prepare list item for insertion let preparedListItem = listItem.content.trim(); function markdownRenderList(content) { if (!DEBUG) { return mmd.render(content).replace("<p>", "").replace("</p>", ""); } else { return content; }; } switch (listItem.type) { case TYPE_CHECK_UNCHECKED: preparedListItem = preparedListItem.replace("- [ ] ", ''); preparedListItem = markdownRenderList(preparedListItem); preparedListItem = '<en-todo checked="false" />' + preparedListItem; break; case TYPE_CHECK_CHECKED: preparedListItem = preparedListItem.replace("- [x] ", ''); preparedListItem = markdownRenderList(preparedListItem); preparedListItem = '<en-todo checked="true" />' + preparedListItem; break; case TYPE_UL: preparedListItem = preparedListItem.replace(/^-\s|^\*\s/, ''); preparedListItem = markdownRenderList(preparedListItem); break; case TYPE_OL: preparedListItem = preparedListItem.replace(/^\d+\.\s/, ''); preparedListItem = markdownRenderList(preparedListItem); break; } processedLists[groupIndex].push(`<li>${preparedListItem}</li>`); if(listGroups[groupIndex].length-1 === itemIndex) { new Array(endTagStack.length).fill().forEach(() => { processedLists[groupIndex].push(endTagStack.pop()) }); } }) }) } // Final result let reassembledBody = originalContentWorkingCopy .filter((line) => line !== null ).join("\n"); let markdownOutputBody = ''; if (CONFIG_RETAIN_CODE_BLOCK_LANG_AS_COMMENT) { reassembledBody = reassembledBody.replace(/^```([A-Za-z]+)$/mg, (_, c1) => "```\n//" + c1 + "\n") } else { reassembledBody = reassembledBody.replace(/^```([A-Za-z]+)/mg, "```"); } // New Evernote does not support more than 3 heading levels reassembledBody = reassembledBody .replace(/^####\s/mg, "### ") .replace(/^#####\s/mg, "### ") .replace(/^######\s/mg, "### "); if (DEBUG) { markdownOutputBody = reassembledBody; } else { markdownOutputBody = mmd.render(reassembledBody); } processedLists.forEach((list, index) => { markdownOutputBody = markdownOutputBody.replace(`{{REPLACE_LIST-${index + 1}}}`, list.join("\n")); }) // solution to malformed p multiline let pMatcher = /<p>((.|\n)*?)<\/p>/g; let pmarkdownOutputBodyCopy = markdownOutputBody; while (matched = pMatcher.exec(pmarkdownOutputBodyCopy)) { const splitedLines = matched[1].split("\n"); if ( splitedLines.length > 1 && !matched[0].includes("<ul>") && !matched[0].includes("<ol>") ) { const output = splitedLines.map((split) => { return `<div>${split}</div>`; }); markdownOutputBody = markdownOutputBody.replace(matched[0], output.join("\n")) } } // Improve table formatting markdownOutputBody = markdownOutputBody .replace(/\n<table>((.|\n)*?)<thead>/g, "<table><thead>"); // Take code blocks out of base flow before formatting line breaks let preCodeMatcher = /<pre><code>((.|\n)*?)<\/code><\/pre>/g; let preCodeMarkdownOutputBodyCopy = markdownOutputBody; while (matched = preCodeMatcher.exec(preCodeMarkdownOutputBodyCopy)) { markdownOutputBody = markdownOutputBody.replace(matched[0], '<div style="--en-codeblock:true;box-sizing: border-box; padding: 8px; font-family: Monaco, Menlo, Consolas, "Courier New", monospace; font-size: 12px; color: rgb(51, 51, 51); border-top-left-radius: 4px; border-top-right-radius: 4px; border-bottom-right-radius: 4px; border-bottom-left-radius: 4px; background-color: rgb(251, 250, 248); border: 1px solid rgba(0, 0, 0, 0.14902); background-position: initial initial; background-repeat: initial initial;"><pre>' +(matched[1].split("\n").map((codeLine, index, content) => { if (index === (content.length-1)) { return ''; } if (codeLine.trim() === '') { return `<div><br /></div>`; } return `<div>${codeLine}</div>`; })).join("") +"</pre></div>" ); } // ENML compatable line breaks let enmlOutput = markdownOutputBody .replace(/<figure>/g, '') .replace(/<\/figure>/g, '') .replace(/<figcaption>/g,'') .replace(/<\/figcaption>/g, '') .replace(/<p>/g, "<div>") .replace(/<\/p>/g, "</div>") .replace(/\n(\s)*?\n/g,"\n<div><br \/><\/div>\n"); if (CONFIG_MONOSPACE_INLINE_CODEBLOCKS) { enmlOutput = enmlOutput.replace(/<code>(.*?)<\/code>/g, (_, c1) => `<span style="--en-fontfamily: monospace; font-family: "Source Code Pro",monospace">\`${c1}\`</span>`) } if (DEBUG) { console.log(enmlOutput); } else { draft.setTemplateTag("enmlOutput", enmlOutput); }
-
evernote
nameTemplate [[safe_title]]
notebookTemplate tagTemplate [[tags]]
template [[enmlOutput]]
format enml
writeType create
Options
-
After Success Default Notification Info Log Level Info
Items available in the Drafts Directory are uploaded by community members. Use appropriate caution reviewing downloaded items before use.