diff --git a/Notes_for_Logseq.md b/Notes_for_Logseq.md index c6827a3a..8aa88e47 100644 --- a/Notes_for_Logseq.md +++ b/Notes_for_Logseq.md @@ -26,6 +26,7 @@ Please use settings below "replaceSpaceWith": "-" }, "outputFormat": "StandardMD", + "taskOutputFormat: "StandardMD", "urlEncodeFileNamesAndLinks": false, "sanitizeResourceNameSpaces": false, "replacementChar": "_", diff --git a/README.md b/README.md index 4f60424e..224ebb82 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,7 @@ To configure Yarle, you must create a config file. By default it looks like this "skipWebClips": false, "useHashTags": true, "outputFormat": "StandardMD", + "taskOutputFormat": "StandardMD", "urlEncodeFileNamesAndLinks": false, "skipEnexFileNameFromOutputPath": false, "monospaceIsCodeBlock": false, @@ -166,6 +167,8 @@ The following configurational properties are available: | ```turndownOptions``` | `{...}` | additional configuration options for [turndown](https://github.com/mixmark-io/turndown#options), e.g., `{ "bulletListMarker": "-" }` (only in Yarle config file, not desktop app) | ```obsidianSettings``` | `{...}` | settings for Obsidian output. Currently, ```omitLinkDisplayName``` is supported. If set to `true` links will be of the form `[[foo]]`. Conversely they will be of the form `[[foo|bar]]`. Defaults to `false`. | ```logseqSettings``` | `{...}` | settings for Logseq output, currently ```journalNotes``` property is supported, if it is set to `true`, then the notes will be converted to be recognizable by Logseq as Journal notes, the notes will be named by their creation date and they will be collected under `journal` folder. If it is `false`, then they will be converted to be `Pages` (e.g. simple notes, collected in `pages` folder). +| ```taskOutputFormat``` | `ObsidianMD` or `StandardMD` | Output format of Evernote v10+ tasks. ObsidianMD will connvert tasks to match with Obsidian Tasks plugin's requirements. StandardMD will create plain tasks, loosing many features like reminders or due dates. +| ```obsidianTaskTag``` | string | a tag to put to a task converted from Evernote v10+ tasks. Optional by Obsidian Tasks plugin, pls check the details there. Metadata settings can be set via the template. diff --git a/config.json b/config.json index 9bd715a1..9cc123be 100644 --- a/config.json +++ b/config.json @@ -17,6 +17,7 @@ "skipTags": false, "useHashTags": true, "outputFormat": "ObsidianMD", + "taskOutputFormat": "ObsidianMD", "skipEnexFileNameFromOutputPath": false, "keepOriginalAmountOfNewlines": false, "urlEncodeFileNamesAndLinks": false, diff --git a/config.logseq.json b/config.logseq.json index 5156a63c..3ae03fe8 100644 --- a/config.logseq.json +++ b/config.logseq.json @@ -13,6 +13,7 @@ "replaceSpaceWith": "-" }, "outputFormat": "StandardMD", + "taskOutputFormat": "StandardMD", "urlEncodeFileNamesAndLinks": false, "sanitizeResourceNameSpaces": false, "replacementChar": "_", diff --git a/src/YarleOptions.ts b/src/YarleOptions.ts index 1d793d3d..f8021b11 100644 --- a/src/YarleOptions.ts +++ b/src/YarleOptions.ts @@ -1,5 +1,6 @@ import { OutputFormat } from './output-format'; import { TagSeparatorReplaceOptions } from './models'; +import { TaskOutputFormat } from './task-output-format'; export interface YarleOptions { enexDir?: string; // used by command line @@ -47,4 +48,6 @@ export interface YarleOptions { pathSeparator?: string; resourcesDir?: string; turndownOptions?: Record; + taskOutputFormat?: TaskOutputFormat; + obsidianTaskTag?: string; } diff --git a/src/index.ts b/src/index.ts index a3f31868..2c566421 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1,2 @@ export * from './runtime-properties'; +export * from './task-output-format'; diff --git a/src/models/EvernoteTask.ts b/src/models/EvernoteTask.ts new file mode 100644 index 00000000..32e6c3b5 --- /dev/null +++ b/src/models/EvernoteTask.ts @@ -0,0 +1,43 @@ +import moment from 'moment'; + +export enum EvernoteTaskStatus { + Open = 'open', + Closed= 'closed', +} +export interface EvernoteTask { + $name: string; + created: Date; + creator: string; + lasteditor: string; + notelevelid: string; + sortweight: string; + statusupdated: Date; + taskflag: boolean; + taskgroupnotelevelid: string; + taskstatus: EvernoteTaskStatus; + title: string; + duedate: Date; + duedateoption: string; + reminderdate: Date; + reminderdateoption: string; + updated: Date; +} + +export const mapEvernoteTask = (pureTask: any): EvernoteTask => { + return { + ...pureTask, + created: getDateFromProperty(pureTask.created), + statusupdated: getDateFromProperty(pureTask.statusupdated), + updated: getDateFromProperty(pureTask.updated), + duedate: getDateFromProperty(pureTask.duedate), + taskflag: pureTask.taskflag === 'true', + reminderdate: pureTask.reminder ? getDateFromProperty(pureTask.reminder.reminderdate) : undefined, + + }; +}; + +const getDateFromProperty = (property: string) => { + return property + ? moment(property, 'YYYYMMDDThhmmssZ').toDate() + : undefined; +}; diff --git a/src/models/index.ts b/src/models/index.ts index a575bbeb..239dfdd6 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -4,3 +4,4 @@ export * from './ResourceFileProperties'; export * from './ResourceHash'; export * from './TagSeparatorReplaceOptions'; export * from './InternalLink'; +export * from './EvernoteTask'; diff --git a/src/process-tasks.ts b/src/process-tasks.ts new file mode 100644 index 00000000..ab364075 --- /dev/null +++ b/src/process-tasks.ts @@ -0,0 +1,49 @@ +import { cloneDeep } from 'lodash'; +import moment from 'moment'; + +import { yarleOptions } from './yarle'; +import { TaskOutputFormat } from './task-output-format'; +import { EvernoteTask, EvernoteTaskStatus } from './models/EvernoteTask'; + +const MEDIUM_PRIORITY_ICON = '🔼'; +const LOW_PRIORITY_ICON = '🔽'; +const DUE_DATE_ICON = '📅'; +const SCHEDULE_DATE_ICON = '⏳'; + +export const processTaskFactory = (outputFormat: TaskOutputFormat): Function => { + switch (outputFormat) { + case TaskOutputFormat.ObsidianMD: + return convertTasktoMd; + default : + return convertTasktoPlainMdTask; + } +}; + +const convertTasktoPlainMdTask = (task: EvernoteTask, notebookName: string): string => { + const taskStatusMd = (task.taskstatus === EvernoteTaskStatus.Open) + ? '- [ ]' + : '- [x]'; + const title = task.title ? ` ${task.title}` : ''; + + return `${taskStatusMd}${title}`; +}; + +export const convertTasktoMd = (task: EvernoteTask, notebookName: string): string => { + const taskStatusMd = (task.taskstatus === EvernoteTaskStatus.Open) + ? '- [ ]' + : '- [x]'; + const title = task.title ? ` ${task.title}` : ''; + const tag = yarleOptions.obsidianTaskTag !== '' ? ` ${yarleOptions.obsidianTaskTag}` : ''; + const duedate = task.duedate + ? ` ${DUE_DATE_ICON} ${convertDateFormat(task.duedate)}` + : ''; + const reminder = task.reminderdate ? ` ${SCHEDULE_DATE_ICON} ${convertDateFormat(task.reminderdate)}` : ''; + + const priority = task.taskflag ? ` ${MEDIUM_PRIORITY_ICON}` : ` ${LOW_PRIORITY_ICON}`; + + return `${taskStatusMd}${tag}${title}${duedate}${reminder}${priority}`; + }; + +const convertDateFormat = (dateProp: Date): string => { + return moment(dateProp).format('YYYY-MM-DD').toString(); +}; diff --git a/src/runtime-properties.ts b/src/runtime-properties.ts index 2be06ed4..88d7ff22 100644 --- a/src/runtime-properties.ts +++ b/src/runtime-properties.ts @@ -8,6 +8,7 @@ export class RuntimePropertiesSingleton { noteIdNameTOCMap: any; // Table of Contents map - the trusted source currentNoteName: string; currentNotebookName: string; + currentNotePath: string; private constructor() { this.noteIdNameMap = {}; @@ -65,4 +66,12 @@ export class RuntimePropertiesSingleton { getCurrentNoteName(): string { return this.currentNoteName; } + getCurrentNotePath(): string { + return this.currentNotePath; + } + + setCurrentNotePath(value: string): void { + this.currentNotePath = value; + } + } diff --git a/src/task-output-format.ts b/src/task-output-format.ts new file mode 100644 index 00000000..e855f78d --- /dev/null +++ b/src/task-output-format.ts @@ -0,0 +1,4 @@ +export enum TaskOutputFormat { + ObsidianMD= 'ObsidianMD', + StandardMD= 'StandardMD', +} diff --git a/src/ui/sections/config.html b/src/ui/sections/config.html index fe5ef7de..1695366b 100644 --- a/src/ui/sections/config.html +++ b/src/ui/sections/config.html @@ -17,7 +17,8 @@

Target Dialect

- + +

@@ -252,8 +253,34 @@

Tags

- - + + +

+
+
+

Evernote v10+ Tasks

+
+ +
+
+ + + + +
+
+
+
diff --git a/src/ui/settingsMapper.ts b/src/ui/settingsMapper.ts index 77be7369..545d93c4 100644 --- a/src/ui/settingsMapper.ts +++ b/src/ui/settingsMapper.ts @@ -1,6 +1,7 @@ import { YarleOptions } from './../YarleOptions'; import { store } from './store'; import { OutputFormat } from './../output-format'; +import { TaskOutputFormat } from './../task-output-format'; export const mapSettingsToYarleOptions = (): YarleOptions => { return { @@ -18,6 +19,8 @@ export const mapSettingsToYarleOptions = (): YarleOptions => { skipTags: !(store.get('addTags') as boolean), useHashTags: store.get('useHashTags') as boolean, outputFormat: store.get('outputFormat') as OutputFormat, + obsidianTaskTag: store.get('obsidianTaskTag') as string, + taskOutputFormat: store.get('taskOutputFormat') as TaskOutputFormat, skipEnexFileNameFromOutputPath: store.get('skipEnexFileNameFromOutputPath') as boolean, keepMDCharactersOfENNotes: store.get('keepMDCharactersOfENNotes') as boolean, monospaceIsCodeBlock: store.get('monospaceIsCodeBlock') as boolean, diff --git a/src/ui/store.ts b/src/ui/store.ts index 80bae702..a0f81871 100644 --- a/src/ui/store.ts +++ b/src/ui/store.ts @@ -22,6 +22,8 @@ const schema: any = { addTags: { type: 'boolean', default: false }, useHashTags: { type: 'boolean', default: false }, outputFormat: {type: 'string', default: OutputFormat.ObsidianMD}, + obsidianTaskTag: { type: 'string' }, + taskOutputFormat: {type: 'string', default: OutputFormat.ObsidianMD}, skipEnexFileNameFromOutputPath: { type: 'boolean', default: false }, keepMDCharactersOfENNotes: { type: 'boolean', default: false }, monospaceIsCodeBlock: { type: 'boolean', default: false }, diff --git a/src/utils/save-md-file.ts b/src/utils/save-md-file.ts index b8fa5a67..5f4c3c12 100644 --- a/src/utils/save-md-file.ts +++ b/src/utils/save-md-file.ts @@ -1,3 +1,4 @@ +import { RuntimePropertiesSingleton } from './../runtime-properties'; import { writeFile } from './file-utils'; import { getMdFilePath } from './folder-utils'; import { loggerInfo } from './loggerInfo'; @@ -5,6 +6,8 @@ import { loggerInfo } from './loggerInfo'; export const saveMdFile = (data: any, note: any) => { const absMdFilePath = getMdFilePath(note); + const runtimeProps = RuntimePropertiesSingleton.getInstance(); + runtimeProps.setCurrentNotePath(absMdFilePath); writeFile(absMdFilePath, data, note); loggerInfo(`Note saved to ${absMdFilePath}`); }; diff --git a/src/utils/turndown-rules/code-block-rule.ts b/src/utils/turndown-rules/code-block-rule.ts deleted file mode 100644 index 3cb8781a..00000000 --- a/src/utils/turndown-rules/code-block-rule.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { filterByNodeName } from './filter-by-nodename'; -import { getAttributeProxy } from './get-attribute-proxy'; - -const markdownBlock = '\n```\n'; - -const isCodeBlock = (node: any) => { - const nodeProxy = getAttributeProxy(node); - const codeBlockFlag = '-en-codeblock:true'; - - return nodeProxy.style && nodeProxy.style.value.indexOf(codeBlockFlag) >= 0; -}; - -const getIntendNumber = (node: any): number => { - const nodeProxy = getAttributeProxy(node); - const paddingAttr = 'padding-left:'; - let intendNumber = 0; - if (nodeProxy.style && nodeProxy.style.value.indexOf(paddingAttr) >= 0) { - intendNumber = Math.floor(nodeProxy.style.value.split(paddingAttr)[1].split('px')[0] / 20); - } - - return intendNumber; -}; - -export const unescapeMarkdown = (s: string): string => s.replace(/\\(.)/g, '$1'); - -export const codeBlockRule = { - filter: filterByNodeName('DIV'), - replacement: (content: string, node: any) => { - const nodeProxy = getAttributeProxy(node); - const intend = getIntendNumber(node); - content = `${'\t'.repeat(intend)}${content}`; - if (isCodeBlock(node)) { - // turndown has already escaped markdown chars (and all '\') in content; - // reverse that to avoid extraneous backslashes in code block. - content = unescapeMarkdown(content); - - return `${markdownBlock}${content}${markdownBlock}`; - } - - if (node.parentElement && isCodeBlock(node.parentElement) && node.parentElement.firstElementChild === node) { - return `${content}`; - } - - if (node.parentElement && isCodeBlock(node.parentElement)) { - return `\n${content}`; - } - const childHtml = node.innerHTML; - /*return node.isBlock - ? childHtml !== '
' - ? `\n${content}\n` - : `${content}` - : `${content}`; - */ - - return node.isBlock ? `\n${content}\n` : content; - }, -}; diff --git a/src/utils/turndown-rules/div-rule.ts b/src/utils/turndown-rules/div-rule.ts new file mode 100644 index 00000000..e92dd20c --- /dev/null +++ b/src/utils/turndown-rules/div-rule.ts @@ -0,0 +1,33 @@ +import { yarleOptions } from './../../yarle'; +import { replaceCodeBlock } from './replace-code-block'; +import { filterByNodeName } from './filter-by-nodename'; +import { getAttributeProxy } from './get-attribute-proxy'; +import { replaceMonospaceCodeBlock } from './replace-monospace-code-block'; + +const markdownBlock = '\n```\n'; + +const isTaskBlock = (node: any) => { + const nodeProxy = getAttributeProxy(node); + const taskFlag = '--en-task-group:true'; + + return nodeProxy.style && nodeProxy.style.value.indexOf(taskFlag) >= 0; +}; +const getTaskGroupId = (node: any) => { + const nodeProxy = getAttributeProxy(node); + const idAttr = '--en-id:'; + + return nodeProxy.style.value.split(idAttr)[1].split(';')[0]; +}; + +export const divRule = { + filter: filterByNodeName('DIV'), + replacement: (content: string, node: any) => { + const nodeProxy = getAttributeProxy(node); + + return (isTaskBlock(node)) + ? `${getTaskGroupId(node)}` + : (yarleOptions.monospaceIsCodeBlock) + ? replaceMonospaceCodeBlock(content, node) + : replaceCodeBlock(content, node); + }, +}; diff --git a/src/utils/turndown-rules/index.ts b/src/utils/turndown-rules/index.ts index 6a44531d..a5bdafc0 100644 --- a/src/utils/turndown-rules/index.ts +++ b/src/utils/turndown-rules/index.ts @@ -1,8 +1,9 @@ -export * from './code-block-rule'; -export * from './monospace-code-block-rule'; +export * from './replace-code-block'; +export * from './replace-monospace-code-block'; export * from './images-rule'; export * from './internal-links-rule'; export * from './span-rule'; export * from './strikethrough-rule'; export * from './task-items-rule'; export * from './newline-rule'; +export * from './div-rule'; diff --git a/src/utils/turndown-rules/replace-code-block.ts b/src/utils/turndown-rules/replace-code-block.ts new file mode 100644 index 00000000..4efa4353 --- /dev/null +++ b/src/utils/turndown-rules/replace-code-block.ts @@ -0,0 +1,61 @@ +import { filterByNodeName } from './filter-by-nodename'; +import { getAttributeProxy } from './get-attribute-proxy'; + +const markdownBlock = '\n```\n'; + +const isCodeBlock = (node: any) => { + const nodeProxy = getAttributeProxy(node); + const codeBlockFlag = '-en-codeblock:true'; + + return nodeProxy.style && nodeProxy.style.value.indexOf(codeBlockFlag) >= 0; +}; + +const getIntendNumber = (node: any): number => { + const nodeProxy = getAttributeProxy(node); + const paddingAttr = 'padding-left:'; + let intendNumber = 0; + if (nodeProxy.style && nodeProxy.style.value.indexOf(paddingAttr) >= 0) { + intendNumber = Math.floor(nodeProxy.style.value.split(paddingAttr)[1].split('px')[0] / 20); + } + + return intendNumber; +}; + +export const unescapeMarkdown = (s: string): string => s.replace(/\\(.)/g, '$1'); + +export const codeBlockRule = { + filter: filterByNodeName('DIV'), + replacement: (content: string, node: any) => { + return replaceCodeBlock(content, node); + }, +}; + +export const replaceCodeBlock = (content: string, node: any): any => { + const nodeProxy = getAttributeProxy(node); + const intend = getIntendNumber(node); + content = `${'\t'.repeat(intend)}${content}`; + if (isCodeBlock(node)) { + // turndown has already escaped markdown chars (and all '\') in content; + // reverse that to avoid extraneous backslashes in code block. + content = unescapeMarkdown(content); + + return `${markdownBlock}${content}${markdownBlock}`; + } + + if (node.parentElement && isCodeBlock(node.parentElement) && node.parentElement.firstElementChild === node) { + return `${content}`; + } + + if (node.parentElement && isCodeBlock(node.parentElement)) { + return `\n${content}`; + } + const childHtml = node.innerHTML; + /*return node.isBlock + ? childHtml !== '
' + ? `\n${content}\n` + : `${content}` + : `${content}`; + */ + + return node.isBlock ? `\n${content}\n` : content; +}; diff --git a/src/utils/turndown-rules/monospace-code-block-rule.ts b/src/utils/turndown-rules/replace-monospace-code-block.ts similarity index 51% rename from src/utils/turndown-rules/monospace-code-block-rule.ts rename to src/utils/turndown-rules/replace-monospace-code-block.ts index 81dc7dd6..13e009ce 100644 --- a/src/utils/turndown-rules/monospace-code-block-rule.ts +++ b/src/utils/turndown-rules/replace-monospace-code-block.ts @@ -1,6 +1,6 @@ import { filterByNodeName } from './filter-by-nodename'; import { getAttributeProxy } from './get-attribute-proxy'; -import { unescapeMarkdown } from './code-block-rule'; +import { unescapeMarkdown } from './replace-code-block'; const markdownBlock = '\n```\n'; @@ -38,7 +38,7 @@ const deepestFont: (node: any) => string = node => { return null; }; -const isCodeBlock: (node: any) => boolean = node => { +const isMonospaceCodeBlock: (node: any) => boolean = node => { const nodeProxy = getAttributeProxy(node); const style = nodeProxy.style?.value; if (style && style.includes(codeBlockFlag)) { @@ -49,35 +49,41 @@ const isCodeBlock: (node: any) => boolean = node => { return font && reMonospaceFont.test(font); }; - +/* export const monospaceCodeBlockRule = { filter: filterByNodeName('DIV'), replacement: (content: string, node: any) => { - if (isCodeBlock(node)) { - const previous = node.previousSibling; - const previousIsBlock = previous && previous.tagName === node.tagName && isCodeBlock(previous); - const next = node.nextSibling; - const nextIsBlock = next && next.tagName === node.tagName && isCodeBlock(next); - if (previousIsBlock || nextIsBlock) { - content = previousIsBlock ? `\n${content}` : `${markdownBlock}${content}`; - content = nextIsBlock ? `${content}\n` : `${content}${markdownBlock}`; - - return content; - } - - content = unescapeMarkdown(content); - - return content.trim() ? `${markdownBlock}${content}${markdownBlock}` : content; + if (yarleOptions.monospaceIsCodeBlock && isMonospaceCodeBlock(node)) { + return replaceMonospaceCodeBlock(content, node); } + }, +}; +*/ +export const replaceMonospaceCodeBlock = (content: string, node: any): any => { + if (isMonospaceCodeBlock(node)) { + const previous = node.previousSibling; + const previousIsBlock = previous && previous.tagName === node.tagName && isMonospaceCodeBlock(previous); + const next = node.nextSibling; + const nextIsBlock = next && next.tagName === node.tagName && isMonospaceCodeBlock(next); + if (previousIsBlock || nextIsBlock) { + content = previousIsBlock ? `\n${content}` : `${markdownBlock}${content}`; + content = nextIsBlock ? `${content}\n` : `${content}${markdownBlock}`; - if (node.parentElement && isCodeBlock(node.parentElement) && node.parentElement.firstElementChild === node) { return content; } - if (node.parentElement && isCodeBlock(node.parentElement)) { - return `\n${content}`; - } + content = unescapeMarkdown(content); - return node.isBlock ? `\n${content}\n` : content; - }, + return content.trim() ? `${markdownBlock}${content}${markdownBlock}` : content; + } + + if (node.parentElement && isMonospaceCodeBlock(node.parentElement) && node.parentElement.firstElementChild === node) { + return content; + } + + if (node.parentElement && isMonospaceCodeBlock(node.parentElement)) { + return `\n${content}`; + } + + return node.isBlock ? `\n${content}\n` : content; }; diff --git a/src/utils/turndown-service.ts b/src/utils/turndown-service.ts index 04a1a6c1..4cdcf3f8 100644 --- a/src/utils/turndown-service.ts +++ b/src/utils/turndown-service.ts @@ -3,9 +3,8 @@ import { gfm } from 'joplin-turndown-plugin-gfm'; import { YarleOptions } from './../YarleOptions'; import { - codeBlockRule, + divRule, imagesRule, - monospaceCodeBlockRule, newLineRule, spanRule, strikethroughRule, @@ -51,12 +50,16 @@ export const getTurndownService = (yarleOptions: YarleOptions) => { turndownService.escape = ((str: string) => str); } + turndownService.addRule('divBlock', divRule); + /* + turndownService.addRule('v10Tasks', v10TaskBlockRule); + if (yarleOptions.monospaceIsCodeBlock) { turndownService.addRule('codeblocks', monospaceCodeBlockRule); } else { turndownService.addRule('codeblocks', codeBlockRule); } - + */ if (yarleOptions.keepOriginalAmountOfNewlines) { turndownService.addRule('newline', newLineRule); } diff --git a/src/yarle.ts b/src/yarle.ts index 52d2789b..2c46d7b6 100644 --- a/src/yarle.ts +++ b/src/yarle.ts @@ -20,6 +20,9 @@ import { defaultTemplate } from './utils/templates/default-template'; import { OutputFormat } from './output-format'; import { clearLogFile } from './utils/clearLogFile'; import { RuntimePropertiesSingleton } from './runtime-properties'; +import { processTaskFactory } from './process-tasks'; +import { mapEvernoteTask } from './models/EvernoteTask'; +import { TaskOutputFormat } from './task-output-format'; export const defaultYarleOptions: YarleOptions = { enexSources: ['notebook.enex'], @@ -37,6 +40,8 @@ export const defaultYarleOptions: YarleOptions = { replaceSpaceWith: '-', }, outputFormat: OutputFormat.StandardMD, + taskOutputFormat: TaskOutputFormat.StandardMD, + obsidianTaskTag: '', urlEncodeFileNamesAndLinks: false, sanitizeResourceNameSpaces: false, replacementChar: '_', @@ -79,8 +84,9 @@ export const parseStream = async (options: YarleOptions, enexSource: string): Pr let noteNumber = 0; let failed = 0; let skipped = 0; - + const tasks: any = {}; // key: taskId value: generated md text const notebookName = utils.getNotebookName(enexSource); + const processTaskFn = processTaskFactory(yarleOptions.taskOutputFormat); return new Promise((resolve, reject) => { @@ -118,7 +124,27 @@ export const parseStream = async (options: YarleOptions, enexSource: string): Pr noteAttributes = null; }); + xml.on('tag:task', (pureTask: any) => { + const task = mapEvernoteTask(pureTask); + if (!tasks[task.taskgroupnotelevelid]) { + tasks[task.taskgroupnotelevelid] = []; + } + + tasks[task.taskgroupnotelevelid].push(processTaskFn(task, notebookName)); + + }); + xml.on('end', () => { + const runtimeProps = RuntimePropertiesSingleton.getInstance(); + const currentNotePath = runtimeProps.getCurrentNotePath(); + if (currentNotePath) { + for (const task of Object.keys(tasks)) { + const fileContent = fs.readFileSync(currentNotePath, 'UTF-8'); + const updatedContent = fileContent.replace(`${task}`, tasks[task].join('\n')); + fs.writeFileSync(currentNotePath, updatedContent); + } + } + const success = noteNumber - failed; const totalNotes = noteNumber + skipped; loggerInfo('=========================='); diff --git a/test/data/test-things-to-do-global-filter.md b/test/data/test-things-to-do-global-filter.md new file mode 100644 index 00000000..34ce8147 --- /dev/null +++ b/test/data/test-things-to-do-global-filter.md @@ -0,0 +1,13 @@ +# Things to do + + + +- [ ] #globalTaskTag Simple task 🔽 +- [x] #globalTaskTag Done 🔽 +- [ ] #globalTaskTag Task with due date 📅 2022-05-28 🔽 +- [ ] #globalTaskTag Task with a flag 🔼 +- [ ] #globalTaskTag Task with a reminder ⏳ 2022-05-22 🔽 + + Created at: 2021-07-22T09:59:28+01:00 + Updated at: 2022-05-20T23:20:12+01:00 + Notebook: test-things-to-do diff --git a/test/data/test-things-to-do-standard.md b/test/data/test-things-to-do-standard.md new file mode 100644 index 00000000..92ada43a --- /dev/null +++ b/test/data/test-things-to-do-standard.md @@ -0,0 +1,13 @@ +# Things to do + + + +- [ ] Simple task +- [x] Done +- [ ] Task with due date +- [ ] Task with a flag +- [ ] Task with a reminder + + Created at: 2021-07-22T09:59:28+01:00 + Updated at: 2022-05-20T23:20:12+01:00 + Notebook: test-things-to-do diff --git a/test/data/test-things-to-do.enex b/test/data/test-things-to-do.enex new file mode 100644 index 00000000..ece3b736 --- /dev/null +++ b/test/data/test-things-to-do.enex @@ -0,0 +1,95 @@ + + + + + Things to do + 20210722T085928Z + 20220520T222012Z + + akos + task + + + +
Content not supported
This block is a placeholder for Tasks, which has been officially released on the newest version of Evernote and is no longer supported on this version. Deleting or moving this block may cause unexpected behavior in newer versions of Evernote.

]]> +
+ + Simple task + 20220520T221209Z + 20220520T222015Z + open + false + C + c78e74a2-8984-41b7-8ce8-052da41204c0 + 0bd3793f-2bd2-4723-b8ec-054c590b4d11 + 20220520T221209Z + 16093948 + 16093948 + + + Done + 20220520T221216Z + 20220520T221522Z + completed + false + Q + 8cfb3940-5abe-4a07-9582-6b6aca6898a3 + 0bd3793f-2bd2-4723-b8ec-054c590b4d11 + 20220520T221221Z + 16093948 + 16093948 + + + Task with due date + 20220520T221227Z + 20220520T221610Z + open + false + U + 7e89a048-4675-47d8-888e-575bdbcb88aa + 0bd3793f-2bd2-4723-b8ec-054c590b4d11 + 20220528T215959Z + date_only + Europe/Budapest + false + 20220520T221227Z + 16093948 + 16093948 + + + Task with a flag + 20220520T221528Z + 20220520T221614Z + open + true + W + c8afdd7e-ae58-4443-8944-8c698e0d6231 + 0bd3793f-2bd2-4723-b8ec-054c590b4d11 + 20220520T221528Z + 16093948 + 16093948 + + + Task with a reminder + 20220520T221602Z + 20220520T221729Z + open + false + E + 68d2dee2-1607-4b5f-9d34-b85da6320176 + 0bd3793f-2bd2-4723-b8ec-054c590b4d11 + 20220520T221602Z + 16093948 + 16093948 + + 20220520T221603Z + 20220520T221603Z + 2d6f6efa-4a22-49e0-8247-f3f19ae765ac + 20220522T070000Z + date_time + Europe/Budapest + active + + +
+
diff --git a/test/data/test-things-to-do.md b/test/data/test-things-to-do.md new file mode 100644 index 00000000..078eeac9 --- /dev/null +++ b/test/data/test-things-to-do.md @@ -0,0 +1,13 @@ +# Things to do + + + +- [ ] Simple task 🔽 +- [x] Done 🔽 +- [ ] Task with due date 📅 2022-05-28 🔽 +- [ ] Task with a flag 🔼 +- [ ] Task with a reminder ⏳ 2022-05-22 🔽 + + Created at: 2021-07-22T09:59:28+01:00 + Updated at: 2022-05-20T23:20:12+01:00 + Notebook: test-things-to-do diff --git a/test/yarle-special-cases.spec.ts b/test/yarle-special-cases.spec.ts index b8ce4eff..4aed244f 100644 --- a/test/yarle-special-cases.spec.ts +++ b/test/yarle-special-cases.spec.ts @@ -9,6 +9,7 @@ import * as yarle from './../src/yarle'; import * as dropTheRopeRunner from './../src/dropTheRopeRunner'; import { YarleOptions } from './../src/YarleOptions'; import { LOGFILE } from './../src/utils'; +import { TaskOutputFormat } from '../src/task-output-format'; const testDataFolder = `.${path.sep}test${path.sep}data${path.sep}`; @@ -773,6 +774,88 @@ describe('Yarle special cases', async () => { ); }); + it('tasks from Evernote v10+ - no global filter', async () => { + const options: YarleOptions = { + enexSources: [ `${testDataFolder}test-things-to-do.enex` ], + outputDir: 'out', + templateFile: `${testDataFolder}full_template.templ`, + isMetadataNeeded: true, + outputFormat: OutputFormat.ObsidianMD, + taskOutputFormat: TaskOutputFormat.ObsidianMD, + skipEnexFileNameFromOutputPath: false, + + }; + await yarle.dropTheRope(options); + assert.equal( + fs.existsSync( + `${__dirname}/../out/notes/test-things-to-do/Things to do.md`, + ), + true, + ); + assert.equal( + eol.auto(fs.readFileSync( + `${__dirname}/../out/notes/test-things-to-do/Things to do.md`, + 'utf8', + )), + fs.readFileSync(`${__dirname}/data/test-things-to-do.md`, 'utf8'), + ); + }); + + it('tasks from Evernote v10+ with global filter', async () => { + const options: YarleOptions = { + enexSources: [ `${testDataFolder}test-things-to-do.enex` ], + outputDir: 'out', + templateFile: `${testDataFolder}full_template.templ`, + isMetadataNeeded: true, + outputFormat: OutputFormat.ObsidianMD, + taskOutputFormat: TaskOutputFormat.ObsidianMD, + obsidianTaskTag: '#globalTaskTag', + skipEnexFileNameFromOutputPath: false, + + }; + await yarle.dropTheRope(options); + assert.equal( + fs.existsSync( + `${__dirname}/../out/notes/test-things-to-do/Things to do.md`, + ), + true, + ); + assert.equal( + eol.auto(fs.readFileSync( + `${__dirname}/../out/notes/test-things-to-do/Things to do.md`, + 'utf8', + )), + fs.readFileSync(`${__dirname}/data/test-things-to-do-global-filter.md`, 'utf8'), + ); + }); + + it('tasks from Evernote v10+ standard task', async () => { + const options: YarleOptions = { + enexSources: [ `${testDataFolder}test-things-to-do.enex` ], + outputDir: 'out', + templateFile: `${testDataFolder}full_template.templ`, + isMetadataNeeded: true, + outputFormat: OutputFormat.ObsidianMD, + taskOutputFormat: TaskOutputFormat.StandardMD, + skipEnexFileNameFromOutputPath: false, + + }; + await yarle.dropTheRope(options); + assert.equal( + fs.existsSync( + `${__dirname}/../out/notes/test-things-to-do/Things to do.md`, + ), + true, + ); + assert.equal( + eol.auto(fs.readFileSync( + `${__dirname}/../out/notes/test-things-to-do/Things to do.md`, + 'utf8', + )), + fs.readFileSync(`${__dirname}/data/test-things-to-do-standard.md`, 'utf8'), + ); + }); + it('really old creation time', async () => { const options: YarleOptions = { enexSources: [ `${testDataFolder}test-old-note.enex` ], @@ -798,4 +881,5 @@ describe('Yarle special cases', async () => { fs.readFileSync(`${__dirname}/data/test-old-note.md`, 'utf8'), ); }); + });