imdone-core 1.54.10 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/.dependency-cruiser.cjs +412 -0
  2. package/.github/workflows/ci.yml +2 -2
  3. package/.imdoneignore +2 -1
  4. package/README.md +2 -2
  5. package/_index.md +7 -0
  6. package/build.js +57 -0
  7. package/esm-migration.md +446 -0
  8. package/lib/FileProjectContext.js +13 -0
  9. package/lib/ProjectContext.js +19 -18
  10. package/lib/adapters/date-time.js +3 -8
  11. package/lib/adapters/environment.js +3 -0
  12. package/lib/adapters/file-gateway.js +133 -106
  13. package/lib/adapters/git-download.js +36 -16
  14. package/lib/adapters/index.js +9 -0
  15. package/lib/adapters/logger.js +136 -0
  16. package/lib/adapters/markdown.js +31 -19
  17. package/lib/adapters/parsers/DateLanguageParser.js +11 -9
  18. package/lib/adapters/parsers/content-transformer.js +35 -30
  19. package/lib/adapters/parsers/file/LocalFileParserFactory.js +8 -9
  20. package/lib/adapters/parsers/file/ReadStreamCodeParser.js +8 -8
  21. package/lib/adapters/parsers/file/ReadStreamMarkdownParser.js +4 -4
  22. package/lib/adapters/parsers/file/ReadStreamTaskParser.js +5 -3
  23. package/lib/adapters/parsers/file/index.js +4 -0
  24. package/lib/adapters/parsers/function-parser.js +1 -5
  25. package/lib/adapters/parsers/index.js +5 -0
  26. package/lib/adapters/parsers/task/CardContentParser.js +23 -32
  27. package/lib/adapters/parsers/task/CheckStyleTaskParser.js +2 -2
  28. package/lib/adapters/parsers/task/CodeStyleTaskParser.js +2 -2
  29. package/lib/adapters/parsers/task/HashStyleTaskParser.js +3 -3
  30. package/lib/adapters/parsers/task/MarkdownStyleTaskParser.js +2 -2
  31. package/lib/adapters/parsers/task/TaskParser.js +1 -1
  32. package/lib/adapters/parsers/task/index.js +6 -0
  33. package/lib/adapters/storage/config.js +15 -20
  34. package/lib/adapters/storage/index.js +2 -0
  35. package/lib/adapters/storage/tags.js +11 -12
  36. package/lib/adapters/yaml.js +8 -5
  37. package/lib/card.js +25 -26
  38. package/lib/checksum.js +22 -0
  39. package/lib/cli.js +114 -0
  40. package/lib/config.js +106 -71
  41. package/lib/constants.js +21 -50
  42. package/lib/context/ApplicationContext.js +7 -4
  43. package/lib/context/index.js +1 -0
  44. package/lib/default-settings.js +3 -3
  45. package/lib/domain/entities/View.js +4 -11
  46. package/lib/domain/entities/index.js +1 -0
  47. package/lib/domain/index.js +2 -0
  48. package/lib/domain/usecases/GetViewsUsecase.js +6 -12
  49. package/lib/domain/usecases/SaveViewUsecase.js +8 -14
  50. package/lib/domain/usecases/index.js +2 -0
  51. package/lib/file.js +1158 -1162
  52. package/lib/get-tasks-to-modify.js +117 -0
  53. package/lib/index.js +23 -0
  54. package/lib/languages.js +4 -5
  55. package/lib/list.js +46 -95
  56. package/lib/migrate-config.js +5 -5
  57. package/lib/mixins/index.js +2 -0
  58. package/lib/mixins/repo-fs-store.js +224 -289
  59. package/lib/mixins/repo-watched-fs-store.js +158 -147
  60. package/lib/plugins/archive-plugin.js +10 -8
  61. package/lib/plugins/default-board-actions-plugin.js +2 -2
  62. package/lib/plugins/default-board-properties-plugin.js +7 -7
  63. package/lib/plugins/default-plugins.js +7 -0
  64. package/lib/plugins/epic-plugin.js +6 -5
  65. package/lib/plugins/extension-plugin.js +24 -17
  66. package/lib/plugins/index.js +9 -0
  67. package/lib/plugins/persist-tags-plugin.js +4 -3
  68. package/lib/plugins/plugin-manager.js +89 -52
  69. package/lib/plugins/plugin-registry.js +12 -16
  70. package/lib/project-factory.js +21 -24
  71. package/lib/project.js +97 -144
  72. package/lib/repository.js +1323 -1555
  73. package/lib/task.js +562 -580
  74. package/lib/tools.js +39 -113
  75. package/lib/usecases/actions.js +32 -0
  76. package/lib/usecases/get-project-tags.js +3 -12
  77. package/lib/usecases/get-tasks-in-file.js +4 -7
  78. package/lib/usecases/index.js +4 -0
  79. package/package.json +44 -66
  80. package/scripts/delete-markdown-indexes.js +87 -0
  81. package/scripts/generate-index.js +35 -0
  82. package/{index-markdown.js → scripts/index-markdown.js} +7 -7
  83. package/tsconfig.json +15 -0
  84. package/types/FileProjectContext.d.ts +4 -0
  85. package/types/ProjectContext.d.ts +9 -0
  86. package/types/adapters/date-time.d.ts +2 -0
  87. package/types/adapters/environment.d.ts +1 -0
  88. package/types/adapters/file-gateway.d.ts +51 -0
  89. package/types/adapters/git-download.d.ts +1 -0
  90. package/types/adapters/index.d.ts +9 -0
  91. package/types/adapters/logger.d.ts +24 -0
  92. package/types/adapters/markdown.d.ts +13 -0
  93. package/types/adapters/parsers/DateLanguageParser.d.ts +1 -0
  94. package/types/adapters/parsers/content-transformer.d.ts +10 -0
  95. package/types/adapters/parsers/file/LocalFileParserFactory.d.ts +5 -0
  96. package/types/adapters/parsers/file/ReadStreamCodeParser.d.ts +13 -0
  97. package/types/adapters/parsers/file/ReadStreamMarkdownParser.d.ts +7 -0
  98. package/types/adapters/parsers/file/ReadStreamTaskParser.d.ts +19 -0
  99. package/types/adapters/parsers/file/index.d.ts +4 -0
  100. package/types/adapters/parsers/function-parser.d.ts +1 -0
  101. package/types/adapters/parsers/index.d.ts +5 -0
  102. package/types/adapters/parsers/task/CardContentParser.d.ts +45 -0
  103. package/types/adapters/parsers/task/CheckStyleTaskParser.d.ts +12 -0
  104. package/types/adapters/parsers/task/CodeStyleTaskParser.d.ts +14 -0
  105. package/types/adapters/parsers/task/HashStyleTaskParser.d.ts +13 -0
  106. package/types/adapters/parsers/task/MarkdownStyleTaskParser.d.ts +14 -0
  107. package/types/adapters/parsers/task/TaskParser.d.ts +7 -0
  108. package/types/adapters/parsers/task/index.d.ts +6 -0
  109. package/types/adapters/storage/config.d.ts +6 -0
  110. package/types/adapters/storage/index.d.ts +2 -0
  111. package/types/adapters/storage/tags.d.ts +3 -0
  112. package/types/adapters/yaml.d.ts +2 -0
  113. package/types/card.d.ts +166 -0
  114. package/types/checksum.d.ts +8 -0
  115. package/types/config.d.ts +61 -0
  116. package/types/constants.d.ts +46 -0
  117. package/types/context/ApplicationContext.d.ts +24 -0
  118. package/types/context/index.d.ts +1 -0
  119. package/types/default-settings.d.ts +2 -0
  120. package/types/domain/entities/FileProjectContext.d.ts +5 -0
  121. package/types/domain/entities/View.d.ts +17 -0
  122. package/types/domain/entities/index.d.ts +1 -0
  123. package/types/domain/index.d.ts +2 -0
  124. package/types/domain/usecases/GetViewsUsecase.d.ts +4 -0
  125. package/types/domain/usecases/SaveViewUsecase.d.ts +8 -0
  126. package/types/domain/usecases/index.d.ts +2 -0
  127. package/types/file.d.ts +344 -0
  128. package/types/get-tasks-to-modify.d.ts +3 -0
  129. package/types/index.d.ts +23 -0
  130. package/types/languages.d.ts +649 -0
  131. package/types/list.d.ts +30 -0
  132. package/types/migrate-config.d.ts +1 -0
  133. package/types/mixins/index.d.ts +2 -0
  134. package/types/mixins/repo-fs-store.d.ts +2 -0
  135. package/types/mixins/repo-watched-fs-store.d.ts +2 -0
  136. package/types/plugins/archive-plugin.d.ts +8 -0
  137. package/types/plugins/default-board-actions-plugin.d.ts +9 -0
  138. package/types/plugins/default-board-properties-plugin.d.ts +16 -0
  139. package/types/plugins/default-plugins.d.ts +6 -0
  140. package/types/plugins/epic-plugin.d.ts +19 -0
  141. package/types/plugins/extension-plugin.d.ts +15 -0
  142. package/types/plugins/index.d.ts +9 -0
  143. package/types/plugins/persist-tags-plugin.d.ts +4 -0
  144. package/types/plugins/plugin-manager.d.ts +68 -0
  145. package/types/plugins/plugin-registry.d.ts +1 -0
  146. package/types/project-factory.d.ts +14 -0
  147. package/types/project.d.ts +158 -0
  148. package/types/repository.d.ts +996 -0
  149. package/types/task.d.ts +202 -0
  150. package/types/tools.d.ts +29 -0
  151. package/types/usecases/actions.d.ts +7 -0
  152. package/types/usecases/get-imdone-url.d.ts +1 -0
  153. package/types/usecases/get-project-tags.d.ts +1 -0
  154. package/types/usecases/get-tasks-in-file.d.ts +4 -0
  155. package/types/usecases/index.d.ts +4 -0
  156. package/vitest.config.js +13 -0
  157. package/cli.js +0 -74
  158. package/index.js +0 -1
  159. package/lib/adapters/readers/FileSystemProjectReader.js +0 -45
  160. package/lib/context/StreamingApplicationContext.js +0 -31
  161. package/lib/controlers/SaveViewController.js +0 -10
  162. package/lib/domain/entities/FileProjectContext.js +0 -10
  163. package/lib/domain/usecases/OpenProjectUsecase.js +0 -13
package/lib/task.js CHANGED
@@ -1,26 +1,20 @@
1
- 'use strict'
2
-
3
- var _clone = require('lodash.clone'),
4
- _isObject = require('lodash.isobject'),
5
- _isEqual = require('lodash.isequal'),
6
- _assign = require('lodash.assign'),
7
- _union = require('lodash.union'),
8
- _get = require('lodash.get'),
9
- util = require('util'),
10
- eol = require('eol'),
11
- lf = String(eol.lf),
12
- lineEnd = String(eol.auto),
13
- fastSort = require('fast-sort/dist/sort.js'),
14
- { escapeRegExp } = require('./tools'),
15
- { extractWikilinkTopics } = require('./adapters/markdown');
16
-
17
- const {
1
+ import util from 'util';
2
+ import eol from 'eol';
3
+ import { sort } from 'fast-sort';
4
+ import { extractWikilinkTopics } from './adapters/markdown.js';
5
+ import {
18
6
  CHECK_REGEX,
19
7
  TASK_TYPES,
20
8
  getCheckedData,
21
9
  isBeforeTextMarkdownList,
22
- isNumber
23
- } = require('./adapters/parsers/task/CardContentParser.js')
10
+ isNumber,
11
+ toNumber,
12
+ } from './adapters/parsers/task/CardContentParser.js';
13
+ import { tools } from './tools.js';
14
+
15
+ const { deepEqual, union, escapeRegExp } = tools
16
+ const lf = String(eol.lf);
17
+ const lineEnd = String(eol.auto);
24
18
 
25
19
  /**
26
20
  * Description
@@ -28,9 +22,9 @@ const {
28
22
  * @param {} obj
29
23
  * @return
30
24
  */
31
- class Task {
25
+ export class Task {
32
26
  constructor(config, obj, dontParse) {
33
- if (!_isObject(obj)) throw new Error('obj must be an Object')
27
+ if (obj == null || typeof obj !== 'object') throw new Error('obj must be an Object')
34
28
  this.frontMatter = obj.frontMatter || {
35
29
  tags: [],
36
30
  context: [],
@@ -75,12 +69,11 @@ class Task {
75
69
  }
76
70
 
77
71
  set order(val) {
78
- const order = parseFloat(val)
79
- this.innerOrder = isNaN(order) ? null : order
72
+ this.innerOrder = toNumber(val)
80
73
  }
81
74
 
82
75
  get order() {
83
- return this.innerOrder
76
+ return toNumber(this.innerOrder)
84
77
  }
85
78
 
86
79
  get descriptionString() {
@@ -133,10 +126,6 @@ class Task {
133
126
  return beforeText.length > 0 ? `${beforeText} ` : ''
134
127
  }
135
128
 
136
- getOrder() {
137
- return this.order
138
- }
139
-
140
129
  getTextAndDescription() {
141
130
  const taskContent = `${this.markdownBeforeText}${this.text}${lineEnd}${this.descriptionString}`
142
131
  .replace(/<!--\s*\[\s*([\s\S]*?)\s*\]\s*-->/gm, '$1')
@@ -187,612 +176,605 @@ class Task {
187
176
  }
188
177
 
189
178
  toJSON() {
190
- return _assign({}, this, {
179
+ return Object.assign({}, this, {
191
180
  allTags: this.allTags,
192
181
  allContext: this.allContext,
193
182
  allMeta: this.allMeta,
194
183
  config: undefined,
195
- expand: !!_get(this, 'allMeta.expand[0]'),
184
+ expand: !!(this?.allMeta?.expand && this.allMeta.expand[0]),
196
185
  })
197
186
  }
198
- }
199
- const DATE_META_KEYS = Task.DATE_META_KEYS = ['due', 'defer', 'created', 'started', 'remind', 'completed', 'archivedAt']
200
187
 
201
- Task.hasCheckPrefix = function (content) {
202
- return new RegExp(CHECK_REGEX).test(content)
203
- }
188
+ getTagPrefix () {
189
+ return this.config?.getTagPrefix()
190
+ }
191
+
192
+ static addToLastCommentInContent (content, toAdd, newLine) {
193
+ // BACKLOG respect padding of task prefix for list items
194
+ // <!--
195
+ // order:-680
196
+ // -->
197
+ let prefix = newLine ? lineEnd : ' '
198
+ const commentRegex = /<!--([\s\S]*?)-->/gm
199
+ let result, lastIndex, index
200
+ while ((result = commentRegex.exec(content))) {
201
+ lastIndex = commentRegex.lastIndex
202
+ index = result.index
203
+ }
204
+
205
+ if (lastIndex > 0) {
206
+ let before = content.slice(0, lastIndex - 3).trimEnd()
207
+ const after = content.slice(lastIndex - 3)
208
+ return `${before}${prefix}${toAdd}${prefix}${after}`
209
+ }
210
+ if (!content.endsWith(lf) && content !== '') content = content + lineEnd
211
+ if (isBeforeTextMarkdownList(this.beforeText)) {
212
+ const spaces = this.beforeText.search(/\S|$/)
213
+ content = content.padEnd(content.length + spaces)
214
+ prefix = prefix.padEnd(prefix.length + spaces)
215
+ }
216
+
217
+ return `${content}<!--${prefix}${toAdd}${prefix}-->`
218
+ }
204
219
 
205
- Task.padDescription = function (description = [], beforeText = '') {
206
- const checkedData = getCheckedData(beforeText)
207
- if (checkedData) {
208
- return description.map((line) => {
209
- const paddedLine = line.padStart(line.length + checkedData.pad)
210
- return paddedLine.trim() ? paddedLine : ''
211
- })
220
+ addToLastCommentInContent (content, toAdd, newLine) {
221
+ return Task.addToLastCommentInContent(content, toAdd, newLine)
212
222
  }
213
- return description
214
- }
215
223
 
216
- Task.trimDescription = function (rawTaskContentLines, beforeText) {
217
- const checkedData = getCheckedData(beforeText)
218
- if (checkedData) {
219
- rawTaskContentLines = rawTaskContentLines.map((line) => {
220
- const lineLength = line.length
221
- line = line.trimStart()
222
- return line.padStart(lineLength - checkedData.pad)
224
+ getCheckedData () {
225
+ return getCheckedData(this.beforeText)
226
+ }
227
+
228
+ updateOrderMeta (config, descContent = this.descriptionString) {
229
+ if (config.orderMeta) {
230
+ if (!isNumber(this.order)) return this.order = undefined
231
+ const metaSep = config.getMetaSep()
232
+ if (this.meta.order) {
233
+ const regex = Task.getMetaOrderRegex(config)
234
+ descContent = descContent.replace(regex, `order${metaSep}${this.order}`)
235
+ } else {
236
+ const addNewLine = config.isMetaNewLine()
237
+ descContent = this.addToLastCommentInContent(
238
+ descContent,
239
+ `order${metaSep}${this.order}`,
240
+ addNewLine
241
+ )
242
+ }
243
+ this.meta.order = isNumber(this.order) ? [this.order] : undefined
244
+ } else {
245
+ descContent = Task.removeMetaData({
246
+ config,
247
+ content: descContent,
248
+ key: 'order',
249
+ value: Task.parseMetaData(config, descContent).order,
250
+ })
251
+ }
252
+ this.description = descContent.split(lineEnd)
253
+ this.updateContent()
254
+ }
255
+
256
+ replaceContent (regex, replacement) {
257
+ this.text = this.text.replace(regex, replacement)
258
+ this.description = this.description.map((line) =>
259
+ line.replace(regex, replacement)
260
+ )
261
+ }
262
+
263
+ replaceMetaSep (oldMetaSep, newMetaSep) {
264
+ Object.keys(this.meta).forEach((key) => {
265
+ const metaValues = this.meta[key]
266
+ metaValues.forEach((metaValue) => {
267
+ let regex = new RegExp(escapeRegExp(`${key}${oldMetaSep}"${metaValue}"`))
268
+ this.replaceContent(regex, `${key}${newMetaSep}"${metaValue}"`)
269
+ if (metaValue && !metaValue.toString().includes(' ')) {
270
+ regex = new RegExp(escapeRegExp(`${key}${oldMetaSep}${metaValue}`))
271
+ this.replaceContent(regex, `${key}${newMetaSep}${metaValue}`)
272
+ }
273
+ })
223
274
  })
224
275
  }
225
- return rawTaskContentLines
226
- }
227
276
 
228
- Task.prototype.getCheckedData = function () {
229
- return getCheckedData(this.beforeText)
230
- }
277
+ static DATE_META_KEYS = ['due', 'defer', 'created', 'started', 'remind', 'completed', 'archivedAt']
231
278
 
232
- Task.Types = TASK_TYPES
279
+ static hasCheckPrefix (content) {
280
+ return new RegExp(CHECK_REGEX).test(content)
281
+ }
233
282
 
234
- Task.AnyLanguageGroup =
235
- '#\\w:%/.$-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u209C\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2183\\u2184\\u2C00-\\u2C2E\\u2C30-\\u2C5E\\u2C60-\\u2CE4\\u2CEB-\\u2CEE\\u2CF2\\u2CF3\\u2D00-\\u2D25\\u2D27\\u2D2D\\u2D30-\\u2D67\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005\\u3006\\u3031-\\u3035\\u303B\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312D\\u3131-\\u318E\\u31A0-\\u31BA\\u31F0-\\u31FF\\u3400-\\u4DB5\\u4E00-\\u9FCC\\uA000-\\uA48C\\uA4D0-\\uA4FD\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A\\uA62B\\uA640-\\uA66E\\uA67F-\\uA697\\uA6A0-\\uA6E5\\uA717-\\uA71F\\uA722-\\uA788\\uA78B-\\uA78E\\uA790-\\uA793\\uA7A0-\\uA7AA\\uA7F8-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA8F2-\\uA8F7\\uA8FB\\uA90A-\\uA925\\uA930-\\uA946\\uA960-\\uA97C\\uA984-\\uA9B2\\uA9CF\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAA60-\\uAA76\\uAA7A\\uAA80-\\uAAAF\\uAAB1\\uAAB5\\uAAB6\\uAAB9-\\uAABD\\uAAC0\\uAAC2\\uAADB-\\uAADD\\uAAE0-\\uAAEA\\uAAF2-\\uAAF4\\uAB01-\\uAB06\\uAB09-\\uAB0E\\uAB11-\\uAB16\\uAB20-\\uAB26\\uAB28-\\uAB2E\\uABC0-\\uABE2\\uAC00-\\uD7A3\\uD7B0-\\uD7C6\\uD7CB-\\uD7FB\\uF900-\\uFA6D\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40\\uFB41\\uFB43\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC'
236
- Task.EmojiGroup = '\\p{Emoji_Presentation}'
283
+ static padDescription (description = [], beforeText = '') {
284
+ const checkedData = getCheckedData(beforeText)
285
+ if (checkedData) {
286
+ return description.map((line) => {
287
+ const paddedLine = line.padStart(line.length + checkedData.pad)
288
+ return paddedLine.trim() ? paddedLine : ''
289
+ })
290
+ }
291
+ return description
292
+ }
237
293
 
238
- Task.getTagRegexp = function (prefix = '+') {
239
- return new RegExp(
240
- `(^|\\s)[${prefix}](?![${Task.AnyLanguageGroup}${Task.EmojiGroup}]*##)([${Task.AnyLanguageGroup}${Task.EmojiGroup}]+)`,
241
- 'gmiu'
242
- )
243
- }
244
- Task.getTags = function (text, prefix = '+') {
245
- const tags = [];
246
- const codePositions = Task.getMarkdownCodePositions(text);
247
- const linkLabelPositions = Task.getMarkdownLinkLabelPositions(text);
248
- let result;
249
- const re = Task.getTagRegexp(prefix);
250
-
251
- while ((result = re.exec(text)) !== null) {
252
- if (Task.isResultInMarkdownCode(codePositions, result.index)) continue;
253
- if (Task.isResultInMarkdownLinkLabel(linkLabelPositions, result.index)) continue;
254
- const tag = result[2];
255
- if (tags.includes(tag) || /^[#]+$/.test(tag)) continue;
256
- tags.push(tag);
257
- }
258
-
259
- return tags;
260
- };
261
-
262
- Task.getMarkdownLinkLabelPositions = function (text) {
263
- const positions = [];
264
- const re = /\[([^\]]+)\]\([^\)]+\)/g;
265
- let match;
266
-
267
- while ((match = re.exec(text)) !== null) {
268
- positions.push({ start: match.index, end: re.lastIndex });
269
- }
270
-
271
- return positions;
272
- };
273
-
274
- Task.isResultInMarkdownLinkLabel = function (linkLabelPositions, index) {
275
- return linkLabelPositions.some(pos => index >= pos.start && index < pos.end);
276
- };
277
-
278
- Task.ContextRegExp = new RegExp(
279
- `(^|\\s)@([${Task.AnyLanguageGroup}${Task.EmojiGroup}]+)`,
280
- 'gmiu'
281
- )
282
-
283
- Task.getContext = function (text) {
284
- const context = []
285
- var codePositions = Task.getMarkdownCodePositions(text)
286
- var result,
287
- re = new RegExp(Task.ContextRegExp)
288
-
289
- while ((result = re.exec(text)) !== null) {
290
- if (Task.isResultInMarkdownCode(codePositions, result.index)) continue
291
- const con = result[2]
292
- if (!context.includes(con)) context.push(con)
293
- }
294
-
295
- return context
296
- }
297
-
298
- Task.MetaRegExp = new RegExp(
299
- `(^|\\s)([a-zA-Z-_]+?):(?!\\/\\/)([${Task.AnyLanguageGroup}${Task.EmojiGroup}]+)`,
300
- 'gu'
301
- )
302
- Task.getMetaRegExp = function (config) {
303
- return new RegExp(
304
- `(^|\\s)([a-zA-Z-_]+?)${config.getMetaSep()}(?![*_]+)(?!\\/\\/)([${
305
- Task.AnyLanguageGroup
306
- }${Task.EmojiGroup}]+)`,
307
- 'gu'
308
- )
309
- }
310
- Task.getMetaRegExpQuotes = function (config) {
311
- return new RegExp(
312
- `(^|\\s)([a-zA-Z-_]+?)${config.getMetaSep()}(?![*_]+)"((?${config.getMetaSep()}""|[^"]|[${
313
- Task.AnyLanguageGroup
314
- }${Task.EmojiGroup}])*)"`,
315
- 'gu'
316
- )
317
- }
318
-
319
- Task.parseMetaData = function (config, content) {
320
- var meta = {}
321
- Task.eachMetaInContent(config, content, (result) => {
322
- var key = result[2]
323
- var val = result[3]
324
- if (!meta[key]) meta[key] = []
325
- if (!meta[key].push) return
326
- meta[key].push(val)
327
- })
328
- return meta
329
- }
330
-
331
- Task.getMetaData = function (config, task) {
332
- var text = Task.isTask(task) ? task.getRawTextAndDescription() : task
333
- var meta = Task.parseMetaData(config, text)
334
- Object.entries(meta).forEach(([key, val]) => {
335
- if (DATE_META_KEYS.includes(key)) {
336
- try {
337
- const date = new Date(val)
338
- task[key] = date.toISOString()
339
- task[`${key}Date`] = date.toISOString()
340
- } catch (e) {}
294
+ static trimDescription (rawTaskContentLines, beforeText) {
295
+ const checkedData = getCheckedData(beforeText)
296
+ if (checkedData) {
297
+ rawTaskContentLines = rawTaskContentLines.map((line) => {
298
+ const lineLength = line.length
299
+ line = line.trimStart()
300
+ return line.padStart(lineLength - checkedData.pad)
301
+ })
341
302
  }
342
- })
343
- return meta
344
- }
345
-
346
- Task.execMetaRegex = function (re, content, codePositions, cb) {
347
- let result
348
- while ((result = re.exec(content)) !== null) {
349
- const index = result.index
350
- if (Task.isResultInMarkdownCode(codePositions, index)) continue
351
- var key = result[2]
352
- if (/[\(<]{1}(http|ssh|ftp|git)/.test(key)) continue
353
- if (!/^[^$][^\s\.]+$/.test(key)) continue
354
- cb(result)
355
- }
356
- }
357
-
358
- Task.eachMetaInContent = function (config, content, cb) {
359
- var codePositions = Task.getMarkdownCodePositions(content)
360
- Task.execMetaRegex(Task.getMetaRegExp(config), content, codePositions, cb)
361
- Task.execMetaRegex(
362
- Task.getMetaRegExpQuotes(config),
363
- content,
364
- codePositions,
365
- cb
366
- )
367
- }
368
-
369
- Task.hasWindowsEOL = function (content) {
370
- return /\r\n|\r/.test(content)
371
- }
372
-
373
- Task.getLinePos = function (content, lineNo) {
374
- var re = /^/gm
375
- var line = 1
376
- var result
377
- const normalizedContent = eol.lf(content)
378
- while ((result = re.exec(normalizedContent)) !== null) {
379
- if (line == lineNo) {
380
- return Task.hasWindowsEOL(content)
381
- ? result.index + line - 1
382
- : result.index
303
+ return rawTaskContentLines
304
+ }
305
+
306
+ static Types = TASK_TYPES
307
+
308
+ static AnyLanguageGroup =
309
+ '#\\w:%/.$-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u209C\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2183\\u2184\\u2C00-\\u2C2E\\u2C30-\\u2C5E\\u2C60-\\u2CE4\\u2CEB-\\u2CEE\\u2CF2\\u2CF3\\u2D00-\\u2D25\\u2D27\\u2D2D\\u2D30-\\u2D67\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005\\u3006\\u3031-\\u3035\\u303B\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312D\\u3131-\\u318E\\u31A0-\\u31BA\\u31F0-\\u31FF\\u3400-\\u4DB5\\u4E00-\\u9FCC\\uA000-\\uA48C\\uA4D0-\\uA4FD\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A\\uA62B\\uA640-\\uA66E\\uA67F-\\uA697\\uA6A0-\\uA6E5\\uA717-\\uA71F\\uA722-\\uA788\\uA78B-\\uA78E\\uA790-\\uA793\\uA7A0-\\uA7AA\\uA7F8-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA8F2-\\uA8F7\\uA8FB\\uA90A-\\uA925\\uA930-\\uA946\\uA960-\\uA97C\\uA984-\\uA9B2\\uA9CF\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAA60-\\uAA76\\uAA7A\\uAA80-\\uAAAF\\uAAB1\\uAAB5\\uAAB6\\uAAB9-\\uAABD\\uAAC0\\uAAC2\\uAADB-\\uAADD\\uAAE0-\\uAAEA\\uAAF2-\\uAAF4\\uAB01-\\uAB06\\uAB09-\\uAB0E\\uAB11-\\uAB16\\uAB20-\\uAB26\\uAB28-\\uAB2E\\uABC0-\\uABE2\\uAC00-\\uD7A3\\uD7B0-\\uD7C6\\uD7CB-\\uD7FB\\uF900-\\uFA6D\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40\\uFB41\\uFB43\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC'
310
+ static EmojiGroup = '\\p{Emoji_Presentation}'
311
+
312
+ static getTagRegexp (prefix = '+') {
313
+ return new RegExp(
314
+ `(^|\\s)[${prefix}](?![${Task.AnyLanguageGroup}${Task.EmojiGroup}]*##)([${Task.AnyLanguageGroup}${Task.EmojiGroup}]+)`,
315
+ 'gmiu'
316
+ )
317
+ }
318
+ static getTags (text, prefix = '+') {
319
+ const tags = [];
320
+ const codePositions = Task.getMarkdownCodePositions(text);
321
+ const linkLabelPositions = Task.getMarkdownLinkLabelPositions(text);
322
+ let result;
323
+ const re = Task.getTagRegexp(prefix);
324
+
325
+ while ((result = re.exec(text)) !== null) {
326
+ if (Task.isResultInMarkdownCode(codePositions, result.index)) continue;
327
+ if (Task.isResultInMarkdownLinkLabel(linkLabelPositions, result.index)) continue;
328
+ const tag = result[2];
329
+ if (tags.includes(tag) || /^[#]+$/.test(tag)) continue;
330
+ tags.push(tag);
383
331
  }
384
- if (result.index === re.lastIndex) re.lastIndex++
385
- line++
386
- }
387
- return content.length
388
- }
389
332
 
390
- Task.getLineNumber = function (content, pos) {
391
- const contentBeforePos = content.substring(0, pos)
392
- const lines = eol.split(contentBeforePos)
393
- return lines.length
394
- }
333
+ return tags;
334
+ };
395
335
 
396
- Task.prototype.getTagPrefix = function () {
397
- return this.config.getTagPrefix()
398
- }
336
+ static getMarkdownLinkLabelPositions (text) {
337
+ const positions = [];
338
+ const re = /\[([^\]]+)\]\([^\)]+\)/g;
339
+ let match;
399
340
 
400
- Task.prototype.addToLastCommentInContent = Task.addToLastCommentInContent = function (content, toAdd, newLine) {
401
- // BACKLOG respect padding of task prefix for list items
402
- // <!--
403
- // order:-680
404
- // -->
405
- let prefix = newLine ? lineEnd : ' '
406
- const commentRegex = /<!--([\s\S]*?)-->/gm
407
- let result, lastIndex, index
408
- while ((result = commentRegex.exec(content))) {
409
- lastIndex = commentRegex.lastIndex
410
- index = result.index
411
- }
412
-
413
- if (lastIndex > 0) {
414
- let before = content.slice(0, lastIndex - 3).trimEnd()
415
- const after = content.slice(lastIndex - 3)
416
- return `${before}${prefix}${toAdd}${prefix}${after}`
417
- }
418
- if (!content.endsWith(lf) && content !== '') content = content + lineEnd
419
- if (isBeforeTextMarkdownList(this.beforeText)) {
420
- const spaces = this.beforeText.search(/\S|$/)
421
- content = content.padEnd(content.length + spaces)
422
- prefix = prefix.padEnd(prefix.length + spaces)
423
- }
424
-
425
- return `${content}<!--${prefix}${toAdd}${prefix}-->`
426
- }
427
-
428
- Task.getMetaOrderRegex = function (config) {
429
- const metaSep = config.getMetaSep()
430
- return new RegExp(
431
- `order${metaSep}("(-?[\\d.]+(?:e-?\\d+)?)"|(-?[\\d.]+(?:e-?\\d+)?))`,
432
- 'gm'
341
+ while ((match = re.exec(text)) !== null) {
342
+ positions.push({ start: match.index, end: re.lastIndex });
343
+ }
344
+
345
+ return positions;
346
+ };
347
+
348
+ static isResultInMarkdownLinkLabel (linkLabelPositions, index) {
349
+ return linkLabelPositions.some(pos => index >= pos.start && index < pos.end);
350
+ };
351
+
352
+ static ContextRegExp = new RegExp(
353
+ `(^|\\s)@([${Task.AnyLanguageGroup}${Task.EmojiGroup}]+)`,
354
+ 'gmiu'
433
355
  )
434
- }
435
356
 
436
- Task.prototype.updateOrderMeta = function (config, descContent = this.descriptionString) {
437
- if (config.orderMeta) {
438
- if (!isNumber(this.order)) return this.order = ''
439
- const addNewLine = config.isMetaNewLine()
440
- const metaSep = config.getMetaSep()
441
- if (this.meta.order) {
442
- const regex = Task.getMetaOrderRegex(config)
443
- descContent = descContent.replace(regex, `order${metaSep}${this.order}`)
444
- } else {
445
- descContent = this.addToLastCommentInContent(
446
- descContent,
447
- `order${metaSep}${this.order}`,
448
- addNewLine
449
- )
357
+ static getContext (text) {
358
+ const context = []
359
+ var codePositions = Task.getMarkdownCodePositions(text)
360
+ var result,
361
+ re = new RegExp(Task.ContextRegExp)
362
+
363
+ while ((result = re.exec(text)) !== null) {
364
+ if (Task.isResultInMarkdownCode(codePositions, result.index)) continue
365
+ const con = result[2]
366
+ if (!context.includes(con)) context.push(con)
450
367
  }
451
- this.meta.order = [this.order]
452
- } else {
453
- descContent = Task.removeMetaData({
454
- config,
455
- content: descContent,
456
- key: 'order',
457
- value: Task.parseMetaData(config, descContent).order,
458
- })
368
+
369
+ return context
459
370
  }
460
- this.description = descContent.split(lineEnd)
461
- this.updateContent()
462
- }
463
371
 
464
- Task.prototype.replaceContent = function (regex, replacement) {
465
- this.text = this.text.replace(regex, replacement)
466
- this.description = this.description.map((line) =>
467
- line.replace(regex, replacement)
372
+ static MetaRegExp = new RegExp(
373
+ `(^|\\s)([a-zA-Z-_]+?):(?!\\/\\/)([${Task.AnyLanguageGroup}${Task.EmojiGroup}]+)`,
374
+ 'gu'
468
375
  )
469
- }
470
-
471
- Task.prototype.replaceMetaSep = function (oldMetaSep, newMetaSep) {
472
- Object.keys(this.meta).forEach((key) => {
473
- const metaValues = this.meta[key]
474
- metaValues.forEach((metaValue) => {
475
- let regex = new RegExp(escapeRegExp(`${key}${oldMetaSep}"${metaValue}"`))
476
- this.replaceContent(regex, `${key}${newMetaSep}"${metaValue}"`)
477
- if (metaValue && !metaValue.toString().includes(' ')) {
478
- regex = new RegExp(escapeRegExp(`${key}${oldMetaSep}${metaValue}`))
479
- this.replaceContent(regex, `${key}${newMetaSep}${metaValue}`)
376
+ static getMetaRegExp (config) {
377
+ return new RegExp(
378
+ `(^|\\s)([a-zA-Z-_]+?)${config.getMetaSep()}(?![*_]+)(?!\\/\\/)([${
379
+ Task.AnyLanguageGroup
380
+ }${Task.EmojiGroup}]+)`,
381
+ 'gu'
382
+ )
383
+ }
384
+ static getMetaRegExpQuotes (config) {
385
+ return new RegExp(
386
+ `(^|\\s)([a-zA-Z-_]+?)${config.getMetaSep()}(?![*_]+)"((?${config.getMetaSep()}""|[^"]|[${
387
+ Task.AnyLanguageGroup
388
+ }${Task.EmojiGroup}])*)"`,
389
+ 'gu'
390
+ )
391
+ }
392
+
393
+ static parseMetaData (config, content) {
394
+ var meta = {}
395
+ Task.eachMetaInContent(config, content, (result) => {
396
+ var key = result[2]
397
+ var val = result[3]
398
+ if (!meta[key]) meta[key] = []
399
+ if (!meta[key].push) return
400
+ meta[key].push(val)
401
+ })
402
+ return meta
403
+ }
404
+
405
+ static getMetaData (config, task) {
406
+ var text = Task.isTask(task) ? task.getRawTextAndDescription() : task
407
+ var meta = Task.parseMetaData(config, text)
408
+ Object.entries(meta).forEach(([key, val]) => {
409
+ if (Task.DATE_META_KEYS.includes(key)) {
410
+ try {
411
+ const date = new Date(val)
412
+ task[key] = date.toISOString()
413
+ task[`${key}Date`] = date.toISOString()
414
+ } catch (e) {}
480
415
  }
481
416
  })
482
- })
483
- }
484
-
485
- /**
486
- * Description
487
- * @method isTask
488
- * @param {} task
489
- * @return BinaryExpression
490
- */
491
- Task.isTask = function (task) {
492
- return task instanceof Task
493
- }
494
-
495
- Task.getMarkdownCodePositions = function (text) {
496
- const re = /(```[\s\S]*?```|`[^`]+`)/g;
497
- let result;
498
- let positions = [];
499
- while ((result = re.exec(text)) !== null) {
500
- const start = result.index;
501
- const end = start + result[0].length;
502
- positions.push({ start, end });
503
- }
504
- return positions;
505
- };
506
-
507
- Task.isResultInMarkdownCode = function (positions, index) {
508
- for (let i = 0; i < positions.length; i++) {
509
- const { start, end } = positions[i]
510
- if (index > start && index < end) return true
511
- }
512
- return false
513
- }
514
-
515
- Task.prototype.parseTodoTxt = function () {
516
- this.tags = Task.getTags(this.getRawTextAndDescription(), this.getTagPrefix())
517
- this.context = Task.getContext(this.getRawTextAndDescription())
518
- this.meta = Task.getMetaData(this.config, this)
519
- }
520
-
521
- Task.removeTags = function (text, prefix = '+') {
522
- const codePositions = Task.getMarkdownCodePositions(text);
523
- const linkLabelPositions = Task.getMarkdownLinkLabelPositions(text);
524
- return text.replace(Task.getTagRegexp(prefix), (match, p1, p2, offset) => {
525
- if (Task.isResultInMarkdownCode(codePositions, offset) || Task.isResultInMarkdownLinkLabel(linkLabelPositions, offset) || match.includes('##')) {
526
- return match;
417
+ return meta
418
+ }
419
+
420
+ static execMetaRegex (re, content, codePositions, cb) {
421
+ let result
422
+ while ((result = re.exec(content)) !== null) {
423
+ const index = result.index
424
+ if (Task.isResultInMarkdownCode(codePositions, index)) continue
425
+ var key = result[2]
426
+ if (/[\(<]{1}(http|ssh|ftp|git)/.test(key)) continue
427
+ if (!/^[^$][^\s\.]+$/.test(key)) continue
428
+ cb(result)
527
429
  }
528
- return '';
529
- });
530
- };
531
-
532
- Task.removeContext = function (text) {
533
- const positions = Task.getMarkdownCodePositions(text)
534
- return text.replace(
535
- new RegExp(Task.ContextRegExp),
536
- (match, p1, p2, offset) => {
537
- if (Task.isResultInMarkdownCode(positions, offset)) return match
538
- return ''
539
- }
540
- )
541
- }
430
+ }
542
431
 
543
- Task.prototype.updateContext = function () {
544
- // BACKLOG implement updateContext id:1 gh:124
545
- // <!--
546
- // order:-690
547
- // -->
548
- var self = this
549
- if (this.context) {
550
- let text = Task.removeContext(this.text)
551
- this.context.forEach((context) => {
552
- text += ` @${context}`
553
- })
554
- this.text = text
432
+ static eachMetaInContent (config, content, cb) {
433
+ var codePositions = Task.getMarkdownCodePositions(content)
434
+ Task.execMetaRegex(Task.getMetaRegExp(config), content, codePositions, cb)
435
+ Task.execMetaRegex(
436
+ Task.getMetaRegExpQuotes(config),
437
+ content,
438
+ codePositions,
439
+ cb
440
+ )
555
441
  }
556
- }
557
442
 
558
- Task.removeMetaDataFromText = function (config, text) {
559
- const re = Task.getMetaRegExp(config)
560
- const re2 = Task.getMetaRegExpQuotes(config)
443
+ static hasWindowsEOL (content) {
444
+ return /\r\n|\r/.test(content)
445
+ }
561
446
 
562
- const positions = Task.getMarkdownCodePositions(text)
563
- return text
564
- .replace(re, (match, p1, p2, p3, offset) => {
565
- if (/[\(<]{1}(http|ssh|ftp|git)/.test(p1)) return match
566
- if (Task.isResultInMarkdownCode(positions, offset)) return match
567
- return ''
568
- })
569
- .replace(re2, (match, p1, p2, p3, offset) => {
570
- if (/[\(<]{1}(http|ssh|ftp|git)/.test(p1)) return match
571
- if (Task.isResultInMarkdownCode(positions, offset)) return match
572
- return ''
573
- })
574
- }
447
+ static getLinePos (content, lineNo) {
448
+ var re = /^/gm
449
+ var line = 1
450
+ var result
451
+ const normalizedContent = eol.lf(content)
452
+ while ((result = re.exec(normalizedContent)) !== null) {
453
+ if (line == lineNo) {
454
+ return Task.hasWindowsEOL(content)
455
+ ? result.index + line - 1
456
+ : result.index
457
+ }
458
+ if (result.index === re.lastIndex) re.lastIndex++
459
+ line++
460
+ }
461
+ return content.length
462
+ }
575
463
 
576
- Task.removeMetaData = function ({ config, content, key, value }) {
577
- return content.replace(
578
- new RegExp(`${key}${config.getMetaSep()}"?${value}"?\\s`, 'g'),
579
- ''
580
- )
581
- }
582
-
583
- Task.prototype.getTags = function () {
584
- if (!this.tags) this.tags = []
585
- return _union(this.tags, this.frontMatter.tags)
586
- }
587
-
588
- Task.prototype.getContext = function () {
589
- if (!this.context) this.context = []
590
- return _union(this.context, this.frontMatter.context)
591
- }
592
-
593
- Task.prototype.getDueMeta = function () {
594
- const dueMeta = this.allMeta.due
595
- if (dueMeta) return dueMeta[0]
596
- }
597
-
598
- Task.prototype.getMetaData = function () {
599
- const meta = _clone(this.frontMatter.meta) || []
600
- Object.entries(meta).forEach(([key, val]) => {
601
- if (this.meta[key]) meta[key] = _union(meta[key], this.meta[key])
602
- })
603
- Object.entries(this.meta).forEach(([key, val]) => {
604
- if (!meta[key]) meta[key] = this.meta[key]
605
- })
606
- return meta
607
- }
608
-
609
- Task.prototype.getListTrackingMeta = function (lists) {
610
- const taskMeta = this.meta
611
- let listTrackingMeta = []
612
- lists.forEach((list) => {
613
- if (taskMeta[list]) {
614
- const listMeta = taskMeta[list].map((timestamp) => ({ list, timestamp }))
615
- listTrackingMeta = listTrackingMeta.concat(listMeta)
464
+ static getLineNumber (content, pos) {
465
+ const contentBeforePos = content.substring(0, pos)
466
+ const lines = eol.split(contentBeforePos)
467
+ return lines.length
468
+ }
469
+
470
+ static getMetaOrderRegex (config) {
471
+ const metaSep = config.getMetaSep()
472
+ return new RegExp(
473
+ `order${metaSep}("(-?[\\d.]+(?:e-?\\d+)?)"|(-?[\\d.]+(?:e-?\\d+)?))`,
474
+ 'gm'
475
+ )
476
+ }
477
+
478
+ /**
479
+ * Description
480
+ * @method isTask
481
+ * @param {} task
482
+ * @return BinaryExpression
483
+ */
484
+ static isTask (task) {
485
+ return task instanceof Task
486
+ }
487
+
488
+ static getMarkdownCodePositions (text) {
489
+ const re = /(```[\s\S]*?```|`[^`]+`)/g;
490
+ let result;
491
+ let positions = [];
492
+ while ((result = re.exec(text)) !== null) {
493
+ const start = result.index;
494
+ const end = start + result[0].length;
495
+ positions.push({ start, end });
616
496
  }
617
- })
618
- return fastSort(listTrackingMeta).asc(u => u.timestamp)
619
- }
620
-
621
- Task.prototype.hasListChanged = function (lists) {
622
- const listTrackingMeta = this.getListTrackingMeta(lists)
623
- return (
624
- listTrackingMeta.length === 0 || listTrackingMeta.pop().list !== this.list
625
- )
626
- }
497
+ return positions;
498
+ };
627
499
 
628
- Task.prototype.hasMetaData = function (key, value) {
629
- var meta = this.meta[key]
630
- if (!meta) return false
631
- if (Array.isArray(value)) return _isEqual(value.sort(), meta.sort())
632
- return meta.includes(value)
633
- }
500
+ static isResultInMarkdownCode (positions, index) {
501
+ for (let i = 0; i < positions.length; i++) {
502
+ const { start, end } = positions[i]
503
+ if (index > start && index < end) return true
504
+ }
505
+ return false
506
+ }
634
507
 
635
- /**
636
- * Returns metadata as list with links
637
- * @method getMetaDataWithLinks
638
- * @param {} repository config
639
- * @return Array
640
- */
641
- Task.prototype.getMetaDataWithLinks = function (config) {
642
- var self = this
643
- var meta = []
644
- var metaData = this.allMeta
645
- if (metaData) {
646
- Object.getOwnPropertyNames(metaData)
647
- .sort()
648
- .forEach(function (metaKey) {
649
- var valList = metaData[metaKey]
650
- valList.forEach(function (value) {
651
- meta.push({
652
- key: metaKey,
653
- value: value,
654
- link: self.getMetaLink(config, metaKey, value),
655
- })
656
- })
657
- })
508
+ static removeTags (text, prefix = '+') {
509
+ const codePositions = Task.getMarkdownCodePositions(text);
510
+ const linkLabelPositions = Task.getMarkdownLinkLabelPositions(text);
511
+ return text.replace(Task.getTagRegexp(prefix), (match, p1, p2, offset) => {
512
+ if (Task.isResultInMarkdownCode(codePositions, offset) || Task.isResultInMarkdownLinkLabel(linkLabelPositions, offset) || match.includes('##')) {
513
+ return match;
514
+ }
515
+ return '';
516
+ });
517
+ };
518
+
519
+ static removeContext (text) {
520
+ const positions = Task.getMarkdownCodePositions(text)
521
+ return text.replace(
522
+ new RegExp(Task.ContextRegExp),
523
+ (match, p1, p2, offset) => {
524
+ if (Task.isResultInMarkdownCode(positions, offset)) return match
525
+ return ''
526
+ }
527
+ )
658
528
  }
659
529
 
660
- return meta
661
- }
530
+ static removeMetaDataFromText (config, text) {
531
+ const re = Task.getMetaRegExp(config)
532
+ const re2 = Task.getMetaRegExpQuotes(config)
662
533
 
663
- Task.prototype.getMetaLink = function (config, metaKey, metaValue) {
664
- var metaConfig = config.meta && config.meta[metaKey]
665
- if (!metaConfig || !metaValue) return
666
- return {
667
- title: util.format(metaConfig.titleTemplate, metaValue),
668
- url: util.format(metaConfig.urlTemplate, metaValue),
669
- icon: metaConfig.icon,
534
+ const positions = Task.getMarkdownCodePositions(text)
535
+ return text
536
+ .replace(re, (match, p1, p2, p3, offset) => {
537
+ if (/[\(<]{1}(http|ssh|ftp|git)/.test(p1)) return match
538
+ if (Task.isResultInMarkdownCode(positions, offset)) return match
539
+ return ''
540
+ })
541
+ .replace(re2, (match, p1, p2, p3, offset) => {
542
+ if (/[\(<]{1}(http|ssh|ftp|git)/.test(p1)) return match
543
+ if (Task.isResultInMarkdownCode(positions, offset)) return match
544
+ return ''
545
+ })
670
546
  }
671
- }
672
547
 
673
- Task.prototype.toString = function () {
674
- return util.format(
675
- 'file:%s, line:%d, text:%s, id:%s',
676
- this.source.path,
677
- this.line,
678
- this.text,
679
- this.id
680
- )
681
- }
548
+ static removeMetaData ({ config, content, key, value }) {
549
+ return content.replace(
550
+ new RegExp(`${key}${config.getMetaSep()}"?${value}"?\\s`, 'g'),
551
+ ''
552
+ )
553
+ }
554
+ updateContext () {
555
+ // BACKLOG implement updateContext id:1 gh:124
556
+ // <!--
557
+ // order:-690
558
+ // -->
559
+ var self = this
560
+ if (this.context) {
561
+ let text = Task.removeContext(this.text)
562
+ this.context.forEach((context) => {
563
+ text += ` @${context}`
564
+ })
565
+ this.text = text
566
+ }
567
+ }
682
568
 
683
- /**
684
- * Description
685
- * @method getRepoId
686
- * @return MemberExpression
687
- */
688
- Task.prototype.getRepoId = function () {
689
- return this.repoId
690
- }
569
+ parseTodoTxt () {
570
+ this.tags = Task.getTags(this.getRawTextAndDescription(), this.getTagPrefix())
571
+ this.context = Task.getContext(this.getRawTextAndDescription())
572
+ this.meta = Task.getMetaData(this.config, this)
573
+ }
691
574
 
692
- /**
693
- * Description
694
- * @method getSource
695
- * @return MemberExpression
696
- */
697
- Task.prototype.getSource = function () {
698
- return this.source
699
- }
575
+ getTags () {
576
+ if (!this.tags) this.tags = []
577
+ return union(this.tags, this.frontMatter.tags)
578
+ }
700
579
 
701
- /**
702
- * Description
703
- * @method getId
704
- * @return MemberExpression
705
- */
706
- Task.prototype.getId = function () {
707
- return this.id
708
- }
580
+ getContext () {
581
+ if (!this.context) this.context = []
582
+ return union(this.context, this.frontMatter.context)
583
+ }
709
584
 
710
- /**
711
- * Description
712
- * @method getList
713
- * @return MemberExpression
714
- */
715
- Task.prototype.getList = function () {
716
- return this.list
717
- }
585
+ getDueMeta () {
586
+ const dueMeta = this.allMeta.due
587
+ if (dueMeta) return dueMeta[0]
588
+ }
718
589
 
719
- /**
720
- * Description
721
- * @method getText
722
- * @return text
723
- */
724
- // BACKLOG make this use config.getMetaSep
725
- // <!--
726
- // order:-700
727
- // -->
728
- Task.prototype.getText = function (opts, text) {
729
- if (text === undefined) return
730
- if (opts) {
731
- if (opts.stripMeta) {
732
- text = Task.removeMetaDataFromText(this.config || {}, text)
733
- }
734
- if (opts.stripTags) {
735
- text = Task.removeTags(text, this.getTagPrefix())
736
- } else if (this.fontMatter && this.frontMatter.tags) {
737
- this.frontMatter.tags.forEach((tag) => (text += ` +${tag}`))
738
- }
739
- if (opts.stripContext) {
740
- text = Task.removeContext(text)
741
- } else if (this.fontMatter && this.frontMatter.context) {
742
- this.frontMatter.context.forEach((context) => (text += ` @${context}`))
743
- }
590
+ getMetaData () {
591
+ const meta = structuredClone(this.frontMatter.meta) || []
592
+ Object.entries(meta).forEach(([key, val]) => {
593
+ if (this.meta[key]) meta[key] = union(meta[key], this.meta[key])
594
+ })
595
+ Object.entries(this.meta).forEach(([key, val]) => {
596
+ if (!meta[key]) meta[key] = this.meta[key]
597
+ })
598
+ return meta
744
599
  }
745
- return text
746
- }
747
600
 
748
- Task.prototype.getChecksFromHtml = function (html) {
749
- const result = [];
750
- const checkboxRegex = /<input[^>]*type=['"]checkbox['"][^>]*>/gi;
751
- const checkedRegex = /checked(?:=['"]?checked['"]?)?/i;
601
+ getListTrackingMeta (lists) {
602
+ const taskMeta = this.meta
603
+ let listTrackingMeta = []
604
+ lists.forEach((list) => {
605
+ if (taskMeta[list]) {
606
+ const listMeta = taskMeta[list].map((timestamp) => ({ list, timestamp }))
607
+ listTrackingMeta = listTrackingMeta.concat(listMeta)
608
+ }
609
+ })
610
+ return sort(listTrackingMeta).asc(u => u.timestamp)
611
+ }
612
+
613
+ hasListChanged (lists) {
614
+ const listTrackingMeta = this.getListTrackingMeta(lists)
615
+ return (
616
+ listTrackingMeta.length === 0 || listTrackingMeta.pop().list !== this.list
617
+ )
618
+ }
619
+
620
+ hasMetaData (key, value) {
621
+ var meta = this.meta[key]
622
+ if (!meta) return false
623
+ if (Array.isArray(value)) return deepEqual(value.sort(), meta.sort())
624
+ return meta.includes(value)
625
+ }
626
+
627
+ /**
628
+ * Returns metadata as list with links
629
+ * @method getMetaDataWithLinks
630
+ * @param {} repository config
631
+ * @return Array
632
+ */
633
+ getMetaDataWithLinks (config) {
634
+ var self = this
635
+ var meta = []
636
+ var metaData = this.allMeta
637
+ if (metaData) {
638
+ Object.getOwnPropertyNames(metaData)
639
+ .sort()
640
+ .forEach(function (metaKey) {
641
+ var valList = metaData[metaKey]
642
+ valList.forEach(function (value) {
643
+ meta.push({
644
+ key: metaKey,
645
+ value: value,
646
+ link: self.getMetaLink(config, metaKey, value),
647
+ })
648
+ })
649
+ })
650
+ }
752
651
 
753
- let match;
754
- while ((match = checkboxRegex.exec(html)) !== null) {
755
- result.push(checkedRegex.test(match[0]));
652
+ return meta
756
653
  }
757
654
 
758
- return result;
759
- };
760
-
761
- /**
762
- * Description
763
- * @method order
764
- * @return MemberExpression
765
- */
766
- Task.prototype.order = function () {
767
- return this.order
768
- }
655
+ getMetaLink (config, metaKey, metaValue) {
656
+ var metaConfig = config.meta && config.meta[metaKey]
657
+ if (!metaConfig || !metaValue) return
658
+ return {
659
+ title: util.format(metaConfig.titleTemplate, metaValue),
660
+ url: util.format(metaConfig.urlTemplate, metaValue),
661
+ icon: metaConfig.icon,
662
+ }
663
+ }
769
664
 
770
- /**
771
- * Description
772
- * @method getLine
773
- * @return MemberExpression
774
- */
775
- Task.prototype.getLine = function () {
776
- return this.line
777
- }
665
+ toString () {
666
+ return util.format(
667
+ 'file:%s, line:%d, text:%s, id:%s',
668
+ this.source.path,
669
+ this.line,
670
+ this.text,
671
+ this.id
672
+ )
673
+ }
674
+
675
+ /**
676
+ * Description
677
+ * @method getRepoId
678
+ * @return MemberExpression
679
+ */
680
+ getRepoId () {
681
+ return this.repoId
682
+ }
683
+
684
+ /**
685
+ * Description
686
+ * @method getSource
687
+ * @return MemberExpression
688
+ */
689
+ getSource () {
690
+ return this.source
691
+ }
692
+
693
+ /**
694
+ * Description
695
+ * @method getId
696
+ * @return MemberExpression
697
+ */
698
+ getId () {
699
+ return this.id
700
+ }
701
+
702
+ /**
703
+ * Description
704
+ * @method getList
705
+ * @return MemberExpression
706
+ */
707
+ getList () {
708
+ return this.list
709
+ }
710
+
711
+ /**
712
+ * Description
713
+ * @method getText
714
+ * @return text
715
+ */
716
+ // BACKLOG make this use config.getMetaSep
717
+ // <!--
718
+ // order:-700
719
+ // -->
720
+ getText (opts, text) {
721
+ if (text === undefined) return
722
+ if (opts) {
723
+ if (opts.stripMeta) {
724
+ text = Task.removeMetaDataFromText(this.config || {}, text)
725
+ }
726
+ if (opts.stripTags) {
727
+ text = Task.removeTags(text, this.getTagPrefix())
728
+ } else if (this.fontMatter && this.frontMatter.tags) {
729
+ this.frontMatter.tags.forEach((tag) => (text += ` +${tag}`))
730
+ }
731
+ if (opts.stripContext) {
732
+ text = Task.removeContext(text)
733
+ } else if (this.fontMatter && this.frontMatter.context) {
734
+ this.frontMatter.context.forEach((context) => (text += ` @${context}`))
735
+ }
736
+ }
737
+ return text
738
+ }
778
739
 
779
- Task.prototype.getType = function () {
780
- return this.type
781
- }
740
+ getChecksFromHtml (html) {
741
+ const result = [];
742
+ const checkboxRegex = /<input[^>]*type=['"]checkbox['"][^>]*>/gi;
743
+ const checkedRegex = /checked(?:=['"]?checked['"]?)?/i;
782
744
 
783
- /**
784
- * Description
785
- * @method equals
786
- * @param {} task
787
- * @return LogicalExpression
788
- */
789
- Task.prototype.equals = function (task) {
790
- if (!task) return
791
- return (
792
- task.getRepoId() == this.getRepoId() &&
793
- task.getSource().path == this.getSource().path &&
794
- task.getId() == this.getId()
795
- )
796
- }
745
+ let match;
746
+ while ((match = checkboxRegex.exec(html)) !== null) {
747
+ result.push(checkedRegex.test(match[0]));
748
+ }
797
749
 
798
- module.exports = Task
750
+ return result;
751
+ };
752
+
753
+ /**
754
+ * Description
755
+ * @method getLine
756
+ * @return MemberExpression
757
+ */
758
+ getLine () {
759
+ return this.line
760
+ }
761
+
762
+ getType () {
763
+ return this.type
764
+ }
765
+
766
+ /**
767
+ * Description
768
+ * @method equals
769
+ * @param {} task
770
+ * @return LogicalExpression
771
+ */
772
+ equals (task) {
773
+ if (!task) return
774
+ return (
775
+ task.getRepoId() == this.getRepoId() &&
776
+ task.getSource().path == this.getSource().path &&
777
+ task.getId() == this.getId()
778
+ )
779
+ }
780
+ }