imdone-core 2.0.0 → 2.0.2

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 (160) hide show
  1. package/.dependency-cruiser.cjs +412 -0
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +37 -0
  3. package/.github/workflows/ci.yml +24 -0
  4. package/.imdoneignore +13 -0
  5. package/.mocharc.json +10 -0
  6. package/.prettierrc +6 -0
  7. package/CHANGELOG.md +160 -0
  8. package/_index.md +7 -0
  9. package/_navbar.md +1 -0
  10. package/build.js +57 -0
  11. package/esm-migration.md +446 -0
  12. package/index.html +184 -0
  13. package/lib/FileProjectContext.js +13 -0
  14. package/lib/ProjectContext.js +43 -0
  15. package/lib/adapters/date-time.js +15 -0
  16. package/lib/adapters/environment.js +3 -0
  17. package/lib/adapters/file-gateway.js +154 -0
  18. package/lib/adapters/git-download.js +55 -0
  19. package/lib/adapters/index.js +9 -0
  20. package/lib/adapters/logger.js +136 -0
  21. package/lib/adapters/markdown.js +178 -0
  22. package/lib/adapters/parsers/DateLanguageParser.js +12 -0
  23. package/lib/adapters/parsers/content-transformer.js +116 -0
  24. package/lib/adapters/parsers/file/LocalFileParserFactory.js +36 -0
  25. package/lib/adapters/parsers/file/ReadStreamCodeParser.js +126 -0
  26. package/lib/adapters/parsers/file/ReadStreamMarkdownParser.js +17 -0
  27. package/lib/adapters/parsers/file/ReadStreamTaskParser.js +106 -0
  28. package/lib/adapters/parsers/file/index.js +4 -0
  29. package/lib/adapters/parsers/function-parser.js +25 -0
  30. package/lib/adapters/parsers/index.js +5 -0
  31. package/lib/adapters/parsers/task/CardContentParser.js +367 -0
  32. package/lib/adapters/parsers/task/CheckStyleTaskParser.js +28 -0
  33. package/lib/adapters/parsers/task/CodeStyleTaskParser.js +24 -0
  34. package/lib/adapters/parsers/task/HashStyleTaskParser.js +27 -0
  35. package/lib/adapters/parsers/task/MarkdownStyleTaskParser.js +24 -0
  36. package/lib/adapters/parsers/task/TaskParser.js +38 -0
  37. package/lib/adapters/parsers/task/index.js +6 -0
  38. package/lib/adapters/storage/config.js +44 -0
  39. package/lib/adapters/storage/index.js +2 -0
  40. package/lib/adapters/storage/tags.js +29 -0
  41. package/lib/adapters/yaml.js +9 -0
  42. package/lib/card.js +299 -0
  43. package/lib/checksum.js +22 -0
  44. package/lib/config.js +249 -0
  45. package/lib/constants.js +68 -0
  46. package/lib/context/ApplicationContext.js +35 -0
  47. package/lib/context/index.js +1 -0
  48. package/lib/default-settings.js +37 -0
  49. package/lib/domain/entities/View.js +23 -0
  50. package/lib/domain/entities/index.js +1 -0
  51. package/lib/domain/index.js +2 -0
  52. package/lib/domain/usecases/GetViewsUsecase.js +44 -0
  53. package/lib/domain/usecases/SaveViewUsecase.js +51 -0
  54. package/lib/domain/usecases/index.js +2 -0
  55. package/lib/file.js +1391 -0
  56. package/lib/get-tasks-to-modify.js +117 -0
  57. package/lib/languages.js +113 -0
  58. package/lib/list.js +56 -0
  59. package/lib/migrate-config.js +165 -0
  60. package/lib/mixins/index.js +2 -0
  61. package/lib/mixins/repo-fs-store.js +359 -0
  62. package/lib/mixins/repo-watched-fs-store.js +206 -0
  63. package/lib/plugins/archive-plugin.js +72 -0
  64. package/lib/plugins/default-board-actions-plugin.js +56 -0
  65. package/lib/plugins/default-board-properties-plugin.js +101 -0
  66. package/lib/plugins/default-plugins.js +7 -0
  67. package/lib/plugins/epic-plugin.js +177 -0
  68. package/lib/plugins/extension-plugin.js +94 -0
  69. package/lib/plugins/index.js +9 -0
  70. package/lib/plugins/persist-tags-plugin.js +14 -0
  71. package/lib/plugins/plugin-manager.js +458 -0
  72. package/lib/plugins/plugin-registry.js +57 -0
  73. package/lib/project-factory.js +42 -0
  74. package/lib/project.js +864 -0
  75. package/lib/repository.js +1514 -0
  76. package/lib/task.js +780 -0
  77. package/lib/tools.js +176 -0
  78. package/lib/usecases/actions.js +32 -0
  79. package/lib/usecases/get-imdone-url.js +6 -0
  80. package/lib/usecases/get-project-tags.js +7 -0
  81. package/lib/usecases/get-tasks-in-file.js +26 -0
  82. package/lib/usecases/index.js +4 -0
  83. package/package.json +2 -7
  84. package/scripts/delete-markdown-indexes.js +87 -0
  85. package/scripts/generate-index.js +35 -0
  86. package/scripts/index-markdown.js +55 -0
  87. package/tsconfig.json +15 -0
  88. package/types/FileProjectContext.d.ts +4 -0
  89. package/types/ProjectContext.d.ts +9 -0
  90. package/types/adapters/date-time.d.ts +2 -0
  91. package/types/adapters/environment.d.ts +1 -0
  92. package/types/adapters/file-gateway.d.ts +51 -0
  93. package/types/adapters/git-download.d.ts +1 -0
  94. package/types/adapters/index.d.ts +9 -0
  95. package/types/adapters/logger.d.ts +24 -0
  96. package/types/adapters/markdown.d.ts +13 -0
  97. package/types/adapters/parsers/DateLanguageParser.d.ts +1 -0
  98. package/types/adapters/parsers/content-transformer.d.ts +10 -0
  99. package/types/adapters/parsers/file/LocalFileParserFactory.d.ts +5 -0
  100. package/types/adapters/parsers/file/ReadStreamCodeParser.d.ts +13 -0
  101. package/types/adapters/parsers/file/ReadStreamMarkdownParser.d.ts +7 -0
  102. package/types/adapters/parsers/file/ReadStreamTaskParser.d.ts +19 -0
  103. package/types/adapters/parsers/file/index.d.ts +4 -0
  104. package/types/adapters/parsers/function-parser.d.ts +1 -0
  105. package/types/adapters/parsers/index.d.ts +5 -0
  106. package/types/adapters/parsers/task/CardContentParser.d.ts +45 -0
  107. package/types/adapters/parsers/task/CheckStyleTaskParser.d.ts +12 -0
  108. package/types/adapters/parsers/task/CodeStyleTaskParser.d.ts +14 -0
  109. package/types/adapters/parsers/task/HashStyleTaskParser.d.ts +13 -0
  110. package/types/adapters/parsers/task/MarkdownStyleTaskParser.d.ts +14 -0
  111. package/types/adapters/parsers/task/TaskParser.d.ts +7 -0
  112. package/types/adapters/parsers/task/index.d.ts +6 -0
  113. package/types/adapters/storage/config.d.ts +6 -0
  114. package/types/adapters/storage/index.d.ts +2 -0
  115. package/types/adapters/storage/tags.d.ts +3 -0
  116. package/types/adapters/yaml.d.ts +2 -0
  117. package/types/card.d.ts +166 -0
  118. package/types/checksum.d.ts +8 -0
  119. package/types/config.d.ts +61 -0
  120. package/types/constants.d.ts +46 -0
  121. package/types/context/ApplicationContext.d.ts +24 -0
  122. package/types/context/index.d.ts +1 -0
  123. package/types/default-settings.d.ts +2 -0
  124. package/types/domain/entities/FileProjectContext.d.ts +5 -0
  125. package/types/domain/entities/View.d.ts +17 -0
  126. package/types/domain/entities/index.d.ts +1 -0
  127. package/types/domain/index.d.ts +2 -0
  128. package/types/domain/usecases/GetViewsUsecase.d.ts +4 -0
  129. package/types/domain/usecases/SaveViewUsecase.d.ts +8 -0
  130. package/types/domain/usecases/index.d.ts +2 -0
  131. package/types/file.d.ts +344 -0
  132. package/types/get-tasks-to-modify.d.ts +3 -0
  133. package/types/index.d.ts +23 -0
  134. package/types/languages.d.ts +649 -0
  135. package/types/list.d.ts +30 -0
  136. package/types/migrate-config.d.ts +1 -0
  137. package/types/mixins/index.d.ts +2 -0
  138. package/types/mixins/repo-fs-store.d.ts +2 -0
  139. package/types/mixins/repo-watched-fs-store.d.ts +2 -0
  140. package/types/plugins/archive-plugin.d.ts +8 -0
  141. package/types/plugins/default-board-actions-plugin.d.ts +9 -0
  142. package/types/plugins/default-board-properties-plugin.d.ts +16 -0
  143. package/types/plugins/default-plugins.d.ts +6 -0
  144. package/types/plugins/epic-plugin.d.ts +19 -0
  145. package/types/plugins/extension-plugin.d.ts +15 -0
  146. package/types/plugins/index.d.ts +9 -0
  147. package/types/plugins/persist-tags-plugin.d.ts +4 -0
  148. package/types/plugins/plugin-manager.d.ts +68 -0
  149. package/types/plugins/plugin-registry.d.ts +1 -0
  150. package/types/project-factory.d.ts +14 -0
  151. package/types/project.d.ts +158 -0
  152. package/types/repository.d.ts +996 -0
  153. package/types/task.d.ts +202 -0
  154. package/types/tools.d.ts +29 -0
  155. package/types/usecases/actions.d.ts +7 -0
  156. package/types/usecases/get-imdone-url.d.ts +1 -0
  157. package/types/usecases/get-project-tags.d.ts +1 -0
  158. package/types/usecases/get-tasks-in-file.d.ts +4 -0
  159. package/types/usecases/index.d.ts +4 -0
  160. package/vitest.config.js +13 -0
package/lib/task.js ADDED
@@ -0,0 +1,780 @@
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 {
6
+ CHECK_REGEX,
7
+ TASK_TYPES,
8
+ getCheckedData,
9
+ isBeforeTextMarkdownList,
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);
18
+
19
+ /**
20
+ * Description
21
+ * @method Task
22
+ * @param {} obj
23
+ * @return
24
+ */
25
+ export class Task {
26
+ constructor(config, obj, dontParse) {
27
+ if (obj == null || typeof obj !== 'object') throw new Error('obj must be an Object')
28
+ this.frontMatter = obj.frontMatter || {
29
+ tags: [],
30
+ context: [],
31
+ meta: {},
32
+ }
33
+ this.pos = obj.pos
34
+ this.rawTask = obj.rawTask
35
+ this.beforeText = obj.beforeText
36
+ this.text = obj.text
37
+ this.list = obj.list
38
+ this.order = obj.order
39
+ this.hasColon = obj.hasColon
40
+ this.line = obj.line
41
+ this.id = obj.id
42
+ this.repoId = obj.repoId
43
+ this.source = obj.source
44
+ this.type = obj.type
45
+ this.tags = obj.tags || []
46
+ this.context = obj.context || []
47
+ this.meta = obj.meta || {}
48
+ this.metaIndex = obj.metaIndex || {}
49
+ this.inBlockComment = obj.inBlockComment
50
+ this.singleLineBlockComment = obj.singleLineBlockComment
51
+ this.description = obj.description || []
52
+ this.rawTaskContentLines = obj.rawTaskContentLines || []
53
+ this.taskStartOnLine = obj.taskStartOnLine || 0
54
+ this.commentStartOnLine = obj.commentStartOnLine || 0
55
+ this.descriptionStartsWith = obj.descriptionStartsWith
56
+ this.filteredListName = obj.filteredListName
57
+ this.isWrappedWithCardTag = obj.isWrappedWithCardTag
58
+ this.config = config
59
+ this.allMeta = obj.allMeta || {}
60
+ this.allContext = obj.allContext || []
61
+ this.allTags = obj.allTags || []
62
+ this.progress = obj.progress
63
+ this.content = obj.content
64
+ this.lastLine = this.getLastLine()
65
+ this.orderModified = obj.orderModified
66
+ this.index = obj.index
67
+ if (dontParse || !this.text) return
68
+ this.parse()
69
+ }
70
+
71
+ set order(val) {
72
+ this.innerOrder = toNumber(val)
73
+ }
74
+
75
+ get order() {
76
+ return toNumber(this.innerOrder)
77
+ }
78
+
79
+ get descriptionString() {
80
+ return this.description.join(lineEnd)
81
+ }
82
+
83
+ updateLastLine() {
84
+ this.lastLine = this.getLastLine()
85
+ }
86
+
87
+ getLastLine() {
88
+ let lastLine = this.line + this.rawTaskContentLines.length
89
+ return lastLine
90
+ }
91
+
92
+ parse() {
93
+ const checkedData = this.getCheckedData()
94
+ this.paddingLength = checkedData ? checkedData.pad : 0
95
+ this.parseTodoTxt()
96
+ if (this.meta.order) {
97
+ this.order = this.meta.order[0]
98
+ }
99
+ this.allMeta = this.getMetaData()
100
+ this.allContext = this.getContext()
101
+ this.allTags = this.getTags()
102
+ this.metaKeys = Object.keys(this.allMeta)
103
+ this.progress = this.getProgress()
104
+ this.content = this.getContent()
105
+ this.topics = extractWikilinkTopics(this.content)
106
+ }
107
+
108
+ issueNumberRegExp() {
109
+ return new RegExp(/:\/\/github\.com.*?\/issues\/\d+/)
110
+ }
111
+
112
+ get listId() {
113
+ return this.filteredListName || this.list
114
+ }
115
+
116
+ get path() {
117
+ return this.source.path
118
+ }
119
+
120
+ get markdownBeforeText() {
121
+ if (!this.beforeText) return ''
122
+ const beforeText = this.beforeText
123
+ .replace('<!--', '')
124
+ .trim()
125
+ .replace(/[^#]+/g, '')
126
+ return beforeText.length > 0 ? `${beforeText} ` : ''
127
+ }
128
+
129
+ getTextAndDescription() {
130
+ const taskContent = `${this.markdownBeforeText}${this.text}${lineEnd}${this.descriptionString}`
131
+ .replace(/<!--\s*\[\s*([\s\S]*?)\s*\]\s*-->/gm, '$1')
132
+ return this.getText(
133
+ {
134
+ stripMeta: true,
135
+ sanitize: true,
136
+ stripTags: true,
137
+ stripContext: true,
138
+ },
139
+ taskContent);
140
+ }
141
+
142
+ getRawTextAndDescription() {
143
+ const text = this.text
144
+ if (this.description.length < 1) return text
145
+ return `${text}${lineEnd}${this.descriptionString}${lineEnd}`
146
+ }
147
+
148
+ getProgress() {
149
+ const progress = { completed: 0, total: 0 }
150
+ const text = this.interpretedContent
151
+ if (!text) return progress
152
+ const checks = text.match(/- \[[x| ]\]/g)
153
+ if (!checks) return progress
154
+ return {
155
+ completed: checks.filter((match) => match.includes('x')).length,
156
+ total: checks.length,
157
+ }
158
+ }
159
+
160
+ getContent() {
161
+ return this.getRawTextAndDescription()
162
+ }
163
+
164
+ updateContent() {
165
+ this.content = this.text + lineEnd + this.descriptionString
166
+ }
167
+
168
+ updateFromContent(content) {
169
+ // replace all occurrences of two empty lines in a row with a single empty line
170
+ content = content.replace(/\n\s*\n\s*\n/g, '\n\n')
171
+ let lines = eol.split(content)
172
+ this.text = lines.shift().trimStart()
173
+ if (lines && lines.length > 1 && lines[lines.length - 1].trim() === '') lines.pop()
174
+ this.description = lines
175
+ this.parse()
176
+ }
177
+
178
+ toJSON() {
179
+ return Object.assign({}, this, {
180
+ allTags: this.allTags,
181
+ allContext: this.allContext,
182
+ allMeta: this.allMeta,
183
+ config: undefined,
184
+ expand: !!(this?.allMeta?.expand && this.allMeta.expand[0]),
185
+ })
186
+ }
187
+
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
+ }
219
+
220
+ addToLastCommentInContent (content, toAdd, newLine) {
221
+ return Task.addToLastCommentInContent(content, toAdd, newLine)
222
+ }
223
+
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
+ })
274
+ })
275
+ }
276
+
277
+ static DATE_META_KEYS = ['due', 'defer', 'created', 'started', 'remind', 'completed', 'archivedAt']
278
+
279
+ static hasCheckPrefix (content) {
280
+ return new RegExp(CHECK_REGEX).test(content)
281
+ }
282
+
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
+ }
293
+
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
+ })
302
+ }
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);
331
+ }
332
+
333
+ return tags;
334
+ };
335
+
336
+ static getMarkdownLinkLabelPositions (text) {
337
+ const positions = [];
338
+ const re = /\[([^\]]+)\]\([^\)]+\)/g;
339
+ let match;
340
+
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'
355
+ )
356
+
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)
367
+ }
368
+
369
+ return context
370
+ }
371
+
372
+ static MetaRegExp = new RegExp(
373
+ `(^|\\s)([a-zA-Z-_]+?):(?!\\/\\/)([${Task.AnyLanguageGroup}${Task.EmojiGroup}]+)`,
374
+ 'gu'
375
+ )
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) {}
415
+ }
416
+ })
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)
429
+ }
430
+ }
431
+
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
+ )
441
+ }
442
+
443
+ static hasWindowsEOL (content) {
444
+ return /\r\n|\r/.test(content)
445
+ }
446
+
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
+ }
463
+
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 });
496
+ }
497
+ return positions;
498
+ };
499
+
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
+ }
507
+
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
+ )
528
+ }
529
+
530
+ static removeMetaDataFromText (config, text) {
531
+ const re = Task.getMetaRegExp(config)
532
+ const re2 = Task.getMetaRegExpQuotes(config)
533
+
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
+ })
546
+ }
547
+
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
+ }
568
+
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
+ }
574
+
575
+ getTags () {
576
+ if (!this.tags) this.tags = []
577
+ return union(this.tags, this.frontMatter.tags)
578
+ }
579
+
580
+ getContext () {
581
+ if (!this.context) this.context = []
582
+ return union(this.context, this.frontMatter.context)
583
+ }
584
+
585
+ getDueMeta () {
586
+ const dueMeta = this.allMeta.due
587
+ if (dueMeta) return dueMeta[0]
588
+ }
589
+
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
599
+ }
600
+
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
+ }
651
+
652
+ return meta
653
+ }
654
+
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
+ }
664
+
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
+ }
739
+
740
+ getChecksFromHtml (html) {
741
+ const result = [];
742
+ const checkboxRegex = /<input[^>]*type=['"]checkbox['"][^>]*>/gi;
743
+ const checkedRegex = /checked(?:=['"]?checked['"]?)?/i;
744
+
745
+ let match;
746
+ while ((match = checkboxRegex.exec(html)) !== null) {
747
+ result.push(checkedRegex.test(match[0]));
748
+ }
749
+
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
+ }