imdone-core 1.54.9 → 2.0.0

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 (71) hide show
  1. package/README.md +2 -2
  2. package/lib/cli.js +114 -0
  3. package/lib/index.js +23 -0
  4. package/package.json +48 -66
  5. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -37
  6. package/.github/workflows/ci.yml +0 -24
  7. package/.imdoneignore +0 -12
  8. package/.mocharc.json +0 -10
  9. package/.prettierrc +0 -6
  10. package/CHANGELOG.md +0 -160
  11. package/_navbar.md +0 -1
  12. package/cli.js +0 -74
  13. package/index-markdown.js +0 -55
  14. package/index.html +0 -184
  15. package/index.js +0 -1
  16. package/lib/ProjectContext.js +0 -42
  17. package/lib/adapters/date-time.js +0 -20
  18. package/lib/adapters/file-gateway.js +0 -127
  19. package/lib/adapters/git-download.js +0 -35
  20. package/lib/adapters/markdown.js +0 -168
  21. package/lib/adapters/parsers/DateLanguageParser.js +0 -10
  22. package/lib/adapters/parsers/content-transformer.js +0 -111
  23. package/lib/adapters/parsers/file/LocalFileParserFactory.js +0 -37
  24. package/lib/adapters/parsers/file/ReadStreamCodeParser.js +0 -126
  25. package/lib/adapters/parsers/file/ReadStreamMarkdownParser.js +0 -17
  26. package/lib/adapters/parsers/file/ReadStreamTaskParser.js +0 -104
  27. package/lib/adapters/parsers/function-parser.js +0 -29
  28. package/lib/adapters/parsers/task/CardContentParser.js +0 -376
  29. package/lib/adapters/parsers/task/CheckStyleTaskParser.js +0 -28
  30. package/lib/adapters/parsers/task/CodeStyleTaskParser.js +0 -24
  31. package/lib/adapters/parsers/task/HashStyleTaskParser.js +0 -27
  32. package/lib/adapters/parsers/task/MarkdownStyleTaskParser.js +0 -24
  33. package/lib/adapters/parsers/task/TaskParser.js +0 -38
  34. package/lib/adapters/readers/FileSystemProjectReader.js +0 -45
  35. package/lib/adapters/storage/config.js +0 -49
  36. package/lib/adapters/storage/tags.js +0 -30
  37. package/lib/adapters/yaml.js +0 -6
  38. package/lib/card.js +0 -300
  39. package/lib/config.js +0 -214
  40. package/lib/constants.js +0 -97
  41. package/lib/context/ApplicationContext.js +0 -32
  42. package/lib/context/StreamingApplicationContext.js +0 -31
  43. package/lib/controlers/SaveViewController.js +0 -10
  44. package/lib/default-settings.js +0 -37
  45. package/lib/domain/entities/FileProjectContext.js +0 -10
  46. package/lib/domain/entities/View.js +0 -30
  47. package/lib/domain/usecases/GetViewsUsecase.js +0 -50
  48. package/lib/domain/usecases/OpenProjectUsecase.js +0 -13
  49. package/lib/domain/usecases/SaveViewUsecase.js +0 -57
  50. package/lib/file.js +0 -1395
  51. package/lib/languages.js +0 -114
  52. package/lib/list.js +0 -105
  53. package/lib/migrate-config.js +0 -165
  54. package/lib/mixins/repo-fs-store.js +0 -424
  55. package/lib/mixins/repo-watched-fs-store.js +0 -195
  56. package/lib/plugins/archive-plugin.js +0 -70
  57. package/lib/plugins/default-board-actions-plugin.js +0 -56
  58. package/lib/plugins/default-board-properties-plugin.js +0 -101
  59. package/lib/plugins/epic-plugin.js +0 -176
  60. package/lib/plugins/extension-plugin.js +0 -87
  61. package/lib/plugins/persist-tags-plugin.js +0 -13
  62. package/lib/plugins/plugin-manager.js +0 -421
  63. package/lib/plugins/plugin-registry.js +0 -61
  64. package/lib/project-factory.js +0 -45
  65. package/lib/project.js +0 -911
  66. package/lib/repository.js +0 -1746
  67. package/lib/task.js +0 -798
  68. package/lib/tools.js +0 -250
  69. package/lib/usecases/get-imdone-url.js +0 -6
  70. package/lib/usecases/get-project-tags.js +0 -16
  71. package/lib/usecases/get-tasks-in-file.js +0 -29
package/lib/repository.js DELETED
@@ -1,1746 +0,0 @@
1
- 'use strict'
2
-
3
- var _isFunction = require('lodash.isfunction'),
4
- _isUndefined = require('lodash.isundefined'),
5
- _isString = require('lodash.isstring'),
6
- _omit = require('lodash.omit'),
7
- _reject = require('lodash.reject'),
8
- _noop = require('lodash.noop'),
9
- _some = require('lodash.some'),
10
- _remove = require('lodash.remove'),
11
- _groupBy = require('lodash.groupby'),
12
- _where = require('lodash.where'),
13
- _template = require('lodash.template'),
14
- _union = require('lodash.union'),
15
- events = require('events'),
16
- languages = require('./languages'),
17
- util = require('util'),
18
- async = require('async'),
19
- path = require('path'),
20
- ignore = require('ignore'),
21
- File = require('./file'),
22
- Config = require('./config'),
23
- eol = require('eol'),
24
- tools = require('./tools'),
25
- inMixinsNoop = tools.inMixinsNoop,
26
- constants = require('./constants'),
27
- log = require('debug')('imdone-core:Repository'),
28
- List = require('./list'),
29
- monquery = require('monquery'),
30
- sift = require('sift'),
31
- fastSort = require('fast-sort/dist/sort.js'),
32
- JSONfns = require('json-fns'),
33
- Task = require('./task'),
34
- newCard = require('./card'),
35
- { replaceDateLanguage } = require('./adapters/parsers/DateLanguageParser'),
36
- { getRawTask, isNumber, LIST_NAME_PATTERN } = require('./adapters/parsers/task/CardContentParser'),
37
- XRegExp = require('xregexp'),
38
- promisify = require('util').promisify
39
-
40
- const appContext = require('./context/ApplicationContext')
41
-
42
- var ERRORS = constants.ERRORS,
43
- DEFAULT_FILE_PATTERN = constants.DEFAULT_FILE_PATTERN,
44
- ASYNC_LIMIT = constants.ASYNC_LIMIT,
45
- DEFAULT_SORT = [{ asc: u => u.order }, { asc: u => u.text }]
46
-
47
- // Emits task.found, list.found, file.update and file.delete, file.processed, files.saved
48
-
49
- /**
50
- * A Repository is a file system directory in which to look for tasks in files. The Repository manages all the files
51
- * and lists in it's path.
52
- *
53
- * @constructor
54
- * @param {} path
55
- * @param {} config
56
- * @return
57
- */
58
-
59
- function Repository(_path, config) {
60
- var self = this
61
-
62
- events.EventEmitter.call(this)
63
- // path is the root path of the repository
64
- this.config = config
65
- this.path = _path
66
- this.files = []
67
- this.languages = languages
68
- this.allMeta = {}
69
- this.metaKeys = new Set()
70
- this.allTags = new Set()
71
- this.allTopics = new Set()
72
- this.allContexts = new Set()
73
- }
74
-
75
- util.inherits(Repository, events.EventEmitter)
76
-
77
- Repository.prototype.init = function (cb) {
78
- cb(null)
79
- }
80
-
81
- Repository.prototype.refresh = function (cb) {
82
- cb = tools.cb(cb)
83
- this.config.dirty = true
84
- this.loadConfig((err, config) => {
85
- if (err) {
86
- return cb(err)
87
- }
88
- this.config = config
89
- this.files = []
90
- this.allMeta = {}
91
- this.metaKeys = new Set()
92
- this.allTags = new Set()
93
- this.allTopics = new Set()
94
- this.allContexts = new Set()
95
- this.readFiles((err, files) => {
96
- if (err) {
97
- this.emit('initialized', { ok: false })
98
- return cb(err)
99
- }
100
- this.emit('initialized', { ok: true, lists: this.getTasksByList() })
101
- if (cb) cb(err, files)
102
- })
103
- })
104
- }
105
-
106
- /**
107
- * Description
108
- * @method destroy
109
- * @return
110
- */
111
- Repository.prototype.destroy = function () {
112
- this.destroyed = true
113
- this.removeAllListeners()
114
- }
115
-
116
- /**
117
- * Description
118
- * @method getId
119
- * @return CallExpression
120
- */
121
- Repository.prototype.getId = function () {
122
- return this.getPath()
123
- }
124
-
125
- Repository.prototype.getProject = function () {
126
- return this.project
127
- }
128
-
129
- Repository.prototype.getDisplayName = function () {
130
- return path.basename(this.path)
131
- }
132
-
133
- Repository.prototype.emitFileUpdate = function (file, force) {
134
- if (force || this.shouldEmitFileUpdate(file)) this.emit('file.update', file)
135
- }
136
-
137
- Repository.prototype.shouldEmitFileUpdate = function (file) {
138
- if (this.moving) return
139
- if (this.lastMovedFiles) {
140
- var index = this.lastMovedFiles.indexOf(file)
141
- if (index > -1) {
142
- this.lastMovedFiles.splice(index, 1)
143
- } else {
144
- if (file && file.updated) return true
145
- }
146
- } else {
147
- if (file && file.updated) return true
148
- }
149
- }
150
-
151
- Repository.prototype.emitConfigUpdate = function (data) {
152
- if (this.savingConfig) return
153
- process.nextTick(() => {
154
- this.emit('config.update', data)
155
- })
156
- }
157
-
158
- /**
159
- * Description
160
- * @method createListeners
161
- * @return
162
- */
163
- Repository.prototype.createListeners = function () {
164
- if (this.taskListener) return
165
- var self = this
166
- /**
167
- * Description
168
- * @method taskFoundListener
169
- * @param {} task
170
- * @return
171
- */
172
- this.taskListener = function (event, task) {
173
- if (!self.listExists(task.list) && self.config.includeList(task.list)) {
174
- const list = new List({ name: task.list })
175
- self.addList(list)
176
- self.emit('list.found', list)
177
- self.saveConfig()
178
- }
179
- Object.keys(task.allMeta).forEach(key => self.metaKeys.add(key))
180
- task.topics.forEach(topic => self.allTopics.add(topic))
181
- task.allTags.forEach(tag => self.allTags.add(tag))
182
- task.allContext.forEach(context => self.allContexts.add(context))
183
- self.allMeta = self.addAllMeta(task.allMeta)
184
- self.emit(event, task)
185
- }
186
-
187
- this.taskFoundListener = function (task) {
188
- self.taskListener('task.found', task)
189
- }
190
-
191
- this.taskModifiedListener = function (task) {
192
- self.taskListener('task.modified', task)
193
- }
194
- }
195
-
196
- Repository.prototype.addAllMeta = function (meta) {
197
- const allMeta = Object.assign({}, this.allMeta)
198
- Object.keys(meta).forEach((key) => {
199
- if (!allMeta[key]) {
200
- allMeta[key] = meta[key]
201
- return
202
- }
203
- allMeta[key] = _union(allMeta[key], meta[key])
204
- })
205
- return allMeta
206
- }
207
-
208
- /**
209
- * Description
210
- * @method addList
211
- * @param {} list
212
- * @return
213
- */
214
- Repository.prototype.addList = async function (list, cb) {
215
- cb = tools.cb(cb);
216
- if (this.listExists(list.name)) return cb();
217
-
218
- try {
219
- list = _omit(list, 'tasks');
220
- this.config.lists.push(new List(list));
221
-
222
- if (!list.filter && !/[a-z]/.test(list.name)) {
223
- const codeList = list.name.replace(/\s+/g, '-').toUpperCase();
224
- if (!this.config.code.include_lists.find((name) => name === codeList)) {
225
- this.config.code.include_lists.push(codeList);
226
- }
227
- }
228
-
229
- await promisify(this.saveConfig).bind(this)();
230
- this.emit('list.modified', list);
231
- cb();
232
- } catch (err) {
233
- cb(err);
234
- }
235
- };
236
-
237
- Repository.prototype.removeList = function (list, cb) {
238
- return new Promise((resolve, reject) => {
239
- var fn = (err) => {
240
- if (err) return cb && cb(err) || reject(err)
241
- this.emit('list.modified', list)
242
- resolve()
243
- }
244
- if (!this.listExists(list)) return fn()
245
-
246
- var lists = _reject(this.getLists(), { name: list })
247
- if (this.config.code && this.config.code.include_lists) {
248
- this.config.code.include_lists = _reject(this.config.code.include_lists, list)
249
- }
250
- this.setLists(lists)
251
- this.saveConfig(fn)
252
- })
253
- }
254
-
255
- /**
256
- * Description
257
- * @method getPath
258
- * @return MemberExpression
259
- */
260
- Repository.prototype.getPath = function () {
261
- return this.path
262
- }
263
-
264
- /**
265
- * Description
266
- * @method getConfig
267
- * @return MemberExpression
268
- */
269
- Repository.prototype.getConfig = function () {
270
- return this.config
271
- }
272
-
273
- /**
274
- * Description
275
- * @method getLists
276
- * @return MemberExpression
277
- */
278
- Repository.prototype.getLists = function () {
279
- const config = this.getConfig()
280
- return config ? config.lists.map(obj => ({...obj})) : []
281
- }
282
-
283
- Repository.prototype.getVisibleLists = function () {
284
- return _reject(this.getLists(), 'hidden')
285
- }
286
-
287
- Repository.prototype.isListVisible = function (name) {
288
- return this.getVisibleLists.find((list) => list.name === name)
289
- }
290
-
291
- /**
292
- * Description
293
- * @method setLists
294
- * @param {} lists
295
- * @return ThisExpression
296
- */
297
- Repository.prototype.setLists = function (lists) {
298
- this.config.lists = lists.map((list) => {
299
- return new List(list)
300
- })
301
- return this
302
- }
303
-
304
- /**
305
- * Description
306
- * @method listExists
307
- * @param {} name
308
- * @return BinaryExpression
309
- */
310
- Repository.prototype.listExists = function (name) {
311
- return this.getConfig().listExists(name)
312
- }
313
-
314
- /**
315
- * Save the config file (Implemented in mixins)
316
- *
317
- * @method saveConfig
318
- * @param {} cb
319
- * @return
320
- */
321
- Repository.prototype.saveConfig = function (cb) {
322
- inMixinsNoop(cb)
323
- }
324
-
325
- /**
326
- * Load the config file (Implemented in mixins)
327
- *
328
- * @method loadConfig
329
- * @return MemberExpression
330
- */
331
- Repository.prototype.loadConfig = function (cb) {
332
- inMixinsNoop(cb)
333
- }
334
-
335
- Repository.prototype.migrateTasksByConfig = function (
336
- oldConfig,
337
- newConfig,
338
- cb
339
- ) {
340
- if (!oldConfig || !newConfig) return cb()
341
- const oldMetaSep = oldConfig.getMetaSep()
342
- const newMetaSep = newConfig.getMetaSep()
343
- if (oldMetaSep === newMetaSep) return cb()
344
- async.eachLimit(
345
- this.getFiles(),
346
- ASYNC_LIMIT,
347
- (file, cb) => {
348
- const tasks = fastSort(file.tasks).desc(u => u.line)
349
- async.eachSeries(
350
- tasks,
351
- (task, cb) => {
352
- if (!Task.isTask(task)) return cb()
353
- task.replaceMetaSep(oldMetaSep, newMetaSep)
354
- this.modifyTask(task, false, cb)
355
- },
356
- (err) => {
357
- if (err) return cb(err)
358
- if (!file.isModified() || file.getContent().trim() === '') return cb()
359
- this.writeFile(file, (err, file) => {
360
- this.resetFile(file)
361
- if (err) return cb(err)
362
- cb(null, file)
363
- })
364
- }
365
- )
366
- },
367
- cb
368
- )
369
- }
370
-
371
- /**
372
- * Get the full path from a relative path
373
- *
374
- * @method getFullPath
375
- * @param {} file
376
- * @return String
377
- */
378
- Repository.prototype.getFullPath = function (file) {
379
- if (File.isFile(file)) file = file.path
380
- if (file.indexOf(this.path) === 0) return file
381
- try {
382
- var fullPath = path.join(this.path, file)
383
- return fullPath
384
- } catch (e) {
385
- throw new Error(
386
- util.format(
387
- 'Error getting full path for file:%s and repo path:%s',
388
- file,
389
- this.path
390
- )
391
- )
392
- }
393
- }
394
-
395
- /**
396
- * Get the relative path from repository root
397
- *
398
- * @method getRelativePath
399
- * @param {} fullPath
400
- * @return String
401
- */
402
- Repository.prototype.getRelativePath = function (fullPath) {
403
- if (path.sep === '\\') {
404
- fullPath = fullPath.replace(/\//g, path.sep)
405
- }
406
- if (!fullPath.startsWith(this.path)) return fullPath
407
- try {
408
- var relPath = path.relative(this.path, fullPath)
409
- return relPath
410
- } catch (e) {
411
- throw new Error(
412
- util.format(
413
- 'Error getting relative path for file:%s and repo path:%s',
414
- fullPath,
415
- this.path
416
- )
417
- )
418
- }
419
- }
420
-
421
- /**
422
- * Is this file OK? Implemented in mixins
423
- *
424
- * @method fileOK
425
- * @param {} file
426
- * @param {} includeDirs
427
- * @return stat
428
- */
429
- Repository.prototype.fileOK = function (file, includeDirs, cb) {
430
- if (_isFunction(includeDirs)) cb = includeDirs
431
- cb(null, true)
432
- }
433
-
434
- Repository.prototype.setIgnores = function (ignores) {
435
- // console.info('ignore patterns:', ignores)
436
- this.ignorePatterns = ignores
437
- this.ignore = ignore().add(ignores)
438
- }
439
-
440
- /**
441
- * Should the relative path be included.
442
- *
443
- * @method shouldInclude
444
- * @param {} relPath
445
- * @return exclude
446
- */
447
- Repository.prototype.shouldInclude = function (relPath) {
448
- relPath = this.getRelativePath(relPath)
449
- let include = true
450
- if (this.config.markdownOnly && relPath) {
451
- include = File.isMarkdown(relPath)
452
- }
453
- if (this.ignore) {
454
- try {
455
- include = include && (relPath === '' || !this.ignore.ignores(relPath))
456
- } catch (e) {
457
- console.error(
458
- `Failed to check ignore status for dir: [${relPath}] in: [${this.path}]. It will be included.`,
459
- e
460
- )
461
- }
462
- }
463
-
464
- return include
465
- }
466
-
467
- /**
468
- * Add or replace a file in the files reference array
469
- *
470
- * @method addFile
471
- * @param {} file
472
- * @return MemberExpression
473
- */
474
- Repository.prototype.addFile = function (file, cb) {
475
- if (this.destroyed) return cb(new Error('destroyed'))
476
- var self = this
477
- this.fileOK(file, function (err, ok) {
478
- if (err) return cb(err)
479
- if (ok) {
480
- var index = self.files.findIndex(({ path }) => path === file.path)
481
- if (index > -1) {
482
- self.files[index] = file
483
- } else {
484
- self.files.push(file)
485
- }
486
- }
487
- cb(err, self.files)
488
- })
489
- }
490
-
491
- /**
492
- * Remove a file from the files refrence array
493
- *
494
- * @method removeFile
495
- * @param {} file
496
- * @return MemberExpression
497
- */
498
- Repository.prototype.removeFile = function (file) {
499
- if (!File.isFile(file)) throw new Error(ERRORS.NOT_A_FILE)
500
- _remove(this.files, f => f.path === file.path)
501
-
502
- return this.files
503
- }
504
-
505
- /**
506
- * Description
507
- * @method getFile
508
- * @param {} path
509
- * @return CallExpression
510
- */
511
- Repository.prototype.getFile = function (path) {
512
- path = this.getRelativePath(path)
513
- return this.files.find((file) => file.path === path)
514
- }
515
-
516
- Repository.prototype.getTask = function (id) {
517
- if (Task.isTask(id)) {
518
- let task = id
519
- if (!task.meta.id) return
520
- return this.getTasks().find((existingTask) => {
521
- return existingTask.meta.id && existingTask.meta.id[0] == task.meta.id[0]
522
- })
523
- } else {
524
- let task = this.getTasks().find((task) => task.id === id)
525
- if (!task) return
526
- return this.getFile(task.source.path)
527
- .getTasks()
528
- .find((task) => task.id === id)
529
- }
530
- }
531
-
532
- /**
533
- * Description
534
- * @method getFileForTask
535
- * @param {} task
536
- * @return CallExpression
537
- */
538
- Repository.prototype.getFileForTask = function (task) {
539
- return this.getFile(task.getSource().path)
540
- }
541
-
542
- /**
543
- * Descriptione
544
- * @method getFiles
545
- * @param {} paths
546
- * @return CallExpression
547
- */
548
- Repository.prototype.getFiles = function (paths) {
549
- if (_isUndefined(paths)) return fastSort(this.files).asc(u => u.path)
550
- return this.files.filter((file) => {
551
- return paths.includes(file.path)
552
- })
553
- }
554
-
555
- Repository.prototype.getFilesWithTasks = function () {
556
- const files = this.files.filter((file) => file.getTasks().length > 0)
557
- return fastSort(files).asc(u => u.path)
558
- }
559
-
560
- Repository.prototype.resetFile = function (file) {
561
- file.reset()
562
- file.removeListener('task.found', this.taskFoundListener)
563
- file.removeListener('task.modified', this.taskModifiedListener)
564
- }
565
-
566
- Repository.prototype.extractTasks = function (file, cb) {
567
- cb = tools.cb(cb)
568
- var self = this
569
- var extract = (file) => {
570
- file.on('task.found', self.taskFoundListener)
571
- file.on('task.modified', self.taskModifiedListener)
572
- const fileContent = file.content
573
- file.extractAndTransformTasks(self.getConfig())
574
- if (!file.isModified() || fileContent === file.content) {
575
- this.resetFile(file)
576
- return cb(null, file)
577
- }
578
- file.extractTasks(self.getConfig())
579
- self.writeFile(file, (err, file) => {
580
- this.resetFile(file)
581
- if (err) return cb(err)
582
- cb(null, file)
583
- })
584
- }
585
-
586
- if (file.content === null) {
587
- this.readFileContent(file, function (err, file) {
588
- if (err) return cb(err)
589
- extract(file)
590
- })
591
- } else extract(file)
592
- }
593
-
594
- /**
595
- * Implemented in mixins
596
- * @method writeFile
597
- * @param {} file
598
- * @param {} cb
599
- * @return
600
- */
601
- Repository.prototype.writeFile = function (file, cb) {
602
- inMixinsNoop(cb)
603
- }
604
-
605
- /**
606
- * Implemented in mixins
607
- * @method getFilesInPath
608
- * @param {} includeDirs
609
- * @return CallExpression
610
- */
611
-
612
- Repository.prototype.getFilesInPath = function (includeDirs, cb) {
613
- inMixinsNoop(cb)
614
- }
615
-
616
- /**
617
- * Implemented in mixins
618
- * @method readFileContent
619
- * @param {} file
620
- * @param {} cb
621
- * @return
622
- */
623
- Repository.prototype.readFileContent = function (file, cb) {
624
- inMixinsNoop(cb)
625
- }
626
-
627
- /**
628
- * Description
629
- * @method readFile
630
- * @param {} file
631
- * @param {} cb
632
- * @return
633
- */
634
- Repository.prototype.readFile = function (file, cb) {
635
- const checksum = this.checksum || function () {}
636
- cb = tools.cb(cb)
637
- if (!File.isFile(file)) return cb(new Error(ERRORS.NOT_A_FILE))
638
- if (file.deleted) return cb(null, file)
639
- var self = this
640
-
641
- var currentChecksum = file.checksum
642
- const filePath = file.path
643
- if (!/\.\.(\/|\\)/.test(filePath)) {
644
- this.readFileContent(file, (err, file) => {
645
- if (err) return cb(new Error('Unable to read file:' + filePath))
646
- // BACKLOG Allow user to assign a module for content transformation
647
- // <!--
648
- // order:-800
649
- // -->
650
- file.checksum = checksum(file.getContent())
651
- file.updated = currentChecksum !== file.checksum
652
- if (file.updated)
653
- self.extractTasks(file, (err) => {
654
- if (err) return cb(err)
655
- self.addFile(file, (err) => {
656
- if (err) log('error:', err)
657
- log(`File added. repo: ${self.getPath()} file-path: ${file.path}`)
658
- if (err) return cb(err)
659
- cb(null, file)
660
- })
661
- })
662
- else cb(null, file)
663
- })
664
- } else return cb(new Error('Unable to read file:' + file))
665
- }
666
-
667
- /**
668
- * Description
669
- * @method readFiles
670
- * @param {} files
671
- * @param {} cb
672
- * @return
673
- */
674
- Repository.prototype.readFiles = function (files, cb) {
675
- if (arguments.length === 0) {
676
- cb = _noop
677
- files = undefined
678
- } else if (_isFunction(files)) {
679
- cb = files
680
- files = undefined
681
- } else if (files && !Array.isArray(files))
682
- return cb(new Error('files must be an array of files or undefined'))
683
-
684
- cb = tools.cb(cb)
685
-
686
- var self = this
687
-
688
- this.allMeta = {}
689
- this.metaKeys = new Set()
690
- this.allTags = new Set()
691
- this.allContexts = new Set()
692
-
693
- var exec = function (files) {
694
- var completed = 0
695
- if (files.length < 1) return cb(null, [])
696
- async.eachLimit(
697
- files,
698
- ASYNC_LIMIT,
699
- function (file, cb) {
700
- self.emit('file.reading', { path: file.path })
701
- self.readFile(file, function (err, file) {
702
- if (err) return cb(err)
703
- completed++
704
- self.emit('file.read', {
705
- path: file.path,
706
- completed: completed,
707
- })
708
- cb()
709
- })
710
- },
711
- function (err) {
712
- cb(err, files)
713
- }
714
- )
715
- }
716
-
717
- if (files === undefined) {
718
- if (this.files && this.files.length > 0) {
719
- exec(this.files)
720
- } else {
721
- this.getFilesInPath(false, (err, files) => {
722
- if (err) return cb(err)
723
- self.files = files
724
- const filesToInclude = files.map((file) => file.path)
725
- const fileStats = {
726
- count: filesToInclude.length,
727
- files: filesToInclude,
728
- }
729
- this.emit('files.found', fileStats)
730
- exec(files)
731
- })
732
- }
733
- } else exec(files)
734
- }
735
-
736
- /**
737
- * Implemented in mixins
738
- * @method deleteFile
739
- * @param {} path
740
- * @param {} cb
741
- * @return
742
- */
743
- Repository.prototype.deleteFile = function (path, cb) {
744
- inMixinsNoop(cb)
745
- }
746
-
747
- /**
748
- * Description
749
- * @method hasDefaultFile
750
- * @return CallExpression
751
- */
752
- Repository.prototype.hasDefaultFile = function () {
753
- return _some(this.getFiles(), function (file) {
754
- var regex = new RegExp(DEFAULT_FILE_PATTERN, 'i')
755
- return regex.test(file.path)
756
- })
757
- }
758
-
759
- /**
760
- * Description
761
- * @method getDefaultFile
762
- * @return file
763
- */
764
- Repository.prototype.getDefaultFile = function () {
765
- var files = fastSort(this.getFiles()).asc(u => u.path)
766
- var file = files.reverse().find((file) => {
767
- var regex = new RegExp(DEFAULT_FILE_PATTERN, 'i')
768
- return regex.test(file.path)
769
- })
770
- return file
771
- }
772
-
773
- Repository.prototype.getList = function (name) {
774
- return this.getLists().find((list) => list.name === name)
775
- }
776
-
777
-
778
- Repository.prototype.getListById = function(id, lists = this.getLists()) {
779
- return lists.find((list) => list.id === id)
780
- }
781
-
782
- Repository.prototype.hideList = function (name, cb) {
783
- var self = this
784
- cb = tools.cb(cb)
785
- var fn = function (err) {
786
- if (!err) self.emit('list.modified', name)
787
- cb(err)
788
- }
789
-
790
- var list = this.getList(name)
791
- if (list) {
792
- list.hidden = true
793
- this.saveConfig(fn)
794
- } else cb()
795
- }
796
-
797
- Repository.prototype.showList = function (name, cb) {
798
- var self = this
799
- cb = tools.cb(cb)
800
- var fn = function (err) {
801
- if (!err) self.emit('list.modified', name)
802
- cb(err)
803
- }
804
-
805
- var list = this.getList(name)
806
- if (list) {
807
- list.hidden = false
808
- this.saveConfig(fn)
809
- } else cb()
810
- }
811
-
812
- /**
813
- * Description
814
- * @method moveList
815
- * @param {} name
816
- * @param {} pos
817
- * @param {} cb
818
- * @return
819
- */
820
- Repository.prototype.moveList = function (name, pos, cb) {
821
- var self = this
822
- cb = tools.cb(cb)
823
- var list = this.getList(name)
824
- // Only modify the lists if the list name exists
825
- var fn = function (err) {
826
- if (!err) self.emit('list.modified', name)
827
- cb(err)
828
- }
829
-
830
- if (list) {
831
- _remove(this.getLists(), { name: name })
832
- this.getLists().splice(pos, 0, list)
833
- this.saveConfig(fn)
834
- } else cb()
835
- }
836
-
837
- Repository.prototype.toggleListIgnore = async function (name) {
838
- return new Promise(async (resolve, reject) => {
839
- var self = this
840
- var list = this.getList(name)
841
- if (!list) return reject(new Error('List not found'))
842
- list.ignore = !list.ignore
843
- await this.updateList(list.id, list)
844
- this.saveConfig((err) => {
845
- if (err) reject(err)
846
- self.emit('list.modified', name)
847
- resolve()
848
- })
849
- });
850
- }
851
-
852
- Repository.prototype.toggleList = async function (name) {
853
- return new Promise(async (resolve, reject) => {
854
- var self = this
855
- var list = this.getList(name)
856
- if (!list) return reject(new Error('List not found'))
857
- list.hidden = !list.hidden
858
- await this.updateList(list.id, list)
859
- this.saveConfig((err) => {
860
- if (err) reject(err)
861
- self.emit('list.modified', name)
862
- resolve()
863
- })
864
- });
865
- }
866
-
867
- Repository.prototype.updateList = function(id, {name, hidden, ignore, filter}) {
868
- return new Promise((resolve, reject) => {
869
- const lists = this.getLists()
870
- const list = this.getListById(id, lists)
871
- const oldName = list.name
872
- const hasFilter = list.filter
873
- list.name = name
874
- list.hidden = hidden
875
- list.ignore = ignore
876
- if (hasFilter !== undefined) list.filter = filter
877
- this.setLists(lists)
878
-
879
- if (oldName === name || hasFilter) {
880
- this.saveConfig(err => {
881
- if (err) return reject(err)
882
- resolve()
883
- })
884
- } else {
885
- this.moveTasksBetweenLists(oldName, name, err => {
886
- if (err) return reject(err)
887
- resolve()
888
- })
889
- }
890
- })
891
- }
892
-
893
- Repository.prototype.moveTasksBetweenLists = function(oldName, newName, cb) {
894
- cb = tools.cb(cb)
895
- // Modify the tasks
896
- const tasksToModify = this.getTasksInList(oldName)
897
-
898
- const cbfn = (err) => {
899
- this.moving = false
900
- if (err) return cb(err)
901
- this.saveConfig((err) => {
902
- if (err) return cb(err)
903
- cb(null, { oldName, newName })
904
- })
905
- }
906
-
907
- const tasksByFile = {} // path: [files]
908
- tasksToModify.forEach((task) => {
909
- const filePath = task.path
910
- if (!tasksByFile[filePath]) {
911
- tasksByFile[filePath] = { file: this.getFileForTask(task), tasks: [] }
912
- }
913
- tasksByFile[filePath].tasks.push(task)
914
- })
915
- const modifyTasks = Object.values(tasksByFile).map(({ file, tasks }) => {
916
- return (cb) => {
917
- this.readFileContent(file, (err) => {
918
- if (err) return cb(err)
919
- try {
920
- tasks.forEach((task) => {
921
- task.list = newName
922
- file.modifyTask(task, this.getConfig(), true)
923
- })
924
- } catch (err) {
925
- return cb(err)
926
- }
927
- cb()
928
- })
929
- }
930
- })
931
-
932
- // BACKLOG Let's use saveModifiedFiles here id:15 gh:100 +fix
933
- // <!--
934
- // order:-810
935
- // -->
936
-
937
- this.moving = true
938
- if (modifyTasks.length > 0) {
939
- async.parallel(modifyTasks, (err) => {
940
- if (err) return cb(err)
941
-
942
- var fns = Object.values(tasksByFile).map(({ file }) => {
943
- return (cb) => {
944
- this.writeFile(file, cb)
945
- }
946
- })
947
-
948
- if (fns.length > 0) {
949
- async.parallel(fns, cbfn)
950
- } else {
951
- cbfn()
952
- }
953
- })
954
- } else cbfn()
955
- }
956
-
957
- Repository.prototype.writeAndExtract = function (file, emit, cb) {
958
- this.writeFile(file, emit, (err, file) => {
959
- if (err) return cb(new Error('Unable to write file:' + file.path))
960
- this.extractTasks(file, (err, file) => {
961
- if (err) {
962
- return cb(new Error('Unable to extract tasks for file:' + file.path))
963
- }
964
-
965
- this.addFile(file, (err) => {
966
- if (err) {
967
- err.message = `Unable to add file after extracting tasks: #{file.path}\n${err.message}`
968
- return cb(err)
969
- }
970
- cb(null, file)
971
- })
972
- })
973
- })
974
- }
975
-
976
- Repository.prototype.writeAndAdd = function (file, emit, cb) {
977
- this.writeFile(file, emit, (err, file) => {
978
- if (err) return cb(new Error('Unable to write file:' + file.path))
979
- this.addFile(file, (err) => {
980
- if (err) return cb(new Error('Unable to add file:' + file.path))
981
- cb(null, file)
982
- })
983
- })
984
- }
985
-
986
- Repository.prototype.deleteTask = function (task, cb) {
987
- if (!cb) throw new Error('task, callback required')
988
- var self = this
989
- var file = self.getFileForTask(task)
990
- if (!file) return cb(null)
991
- const execute = function (file) {
992
- file.deleteTask(task, self.getConfig())
993
- if (file.getContentForFile().trim() === '' && file.isMarkDownFile()) {
994
- if (file.deleted) return
995
- console.log('Deleting empty file:', file.path)
996
- self.deleteFile(file.path, cb)
997
- file.deleted = true
998
- return
999
- }
1000
- self.writeAndExtract(file, true, cb)
1001
- }
1002
- if (!file.getContent()) {
1003
- self.readFileContent(file, function (err, file) {
1004
- if (err) return cb(err)
1005
- execute(file)
1006
- })
1007
- } else {
1008
- execute(file)
1009
- }
1010
- }
1011
- Repository.prototype.deleteTasks = function (tasks, cb) {
1012
- if (!cb) throw new Error('tasks, callback required')
1013
- var self = this
1014
-
1015
- let files = _groupBy(tasks, (task) => task.source.path) // {path:tasks, path2:tasks}
1016
- for (let [path, tasks] of Object.entries(files)) {
1017
- files[path] = fastSort(tasks).desc(u => u.line)
1018
- }
1019
- async.eachSeries(
1020
- Object.entries(files),
1021
- ([path, tasks], cb) => {
1022
- const file = this.getFile(path)
1023
- async.eachSeries(
1024
- tasks,
1025
- (task, cb) => {
1026
- task = newCard(task, this.project, true)
1027
- this.deleteTask(task, (err) => {
1028
- cb(err || null, task)
1029
- })
1030
- },
1031
- (err) => {
1032
- cb(err)
1033
- }
1034
- )
1035
- },
1036
- (err) => {
1037
- cb(err)
1038
- }
1039
- )
1040
- }
1041
-
1042
- Repository.prototype.modifyTaskFromHtml = function (task, html, cb) {
1043
- cb = tools.cb(cb)
1044
- var file = this.getFileForTask(task)
1045
- if (!file.getContent()) {
1046
- this.readFileContent(file, (err, file) => {
1047
- if (err) return cb(err)
1048
- file.modifyTaskFromHtml(task, html)
1049
- this.writeAndExtract(file, true, cb)
1050
- })
1051
- } else {
1052
- file.modifyTaskFromHtml(task, html)
1053
- this.writeAndExtract(file, true, cb)
1054
- }
1055
- }
1056
-
1057
- Repository.prototype.modifyTaskFromContent = function (task, content, cb) {
1058
- cb = tools.cb(cb)
1059
- var file = this.getFileForTask(task)
1060
- if (!file.getContent()) {
1061
- this.readFileContent(file, (err, file) => {
1062
- if (err) return cb(err)
1063
- file.modifyTaskFromContent(task, content, this.getConfig())
1064
- this.writeAndExtract(file, true, cb)
1065
- })
1066
- } else {
1067
- file.modifyTaskFromContent(task, content, this.getConfig())
1068
- this.writeAndExtract(file, true, cb)
1069
- }
1070
- }
1071
-
1072
- Repository.prototype.getTaskContent = function({
1073
- description,
1074
- order,
1075
- text,
1076
- taskPrefix,
1077
- taskSyntax,
1078
- }) {
1079
- const task = newCard(
1080
- {
1081
- description,
1082
- meta: {},
1083
- order,
1084
- text,
1085
- beforeText: taskPrefix,
1086
- type: taskSyntax,
1087
- },
1088
- this.project
1089
- )
1090
- Task.prototype.updateOrderMeta.apply(task, [this.config])
1091
- return task.description.join(eol.lf)
1092
- }
1093
-
1094
- Repository.prototype.appendTask = function({file, content, list}, cb) {
1095
- const config = this.getConfig()
1096
- const interpretedTaskPrefix = _template(config.getTaskPrefix())({
1097
- date: new Date(),
1098
- }).trimEnd()
1099
- const lines = eol.split(content)
1100
- const text = lines[0]
1101
- const taskSyntax = config.getNewCardSyntax()
1102
- const taskPrefix = interpretedTaskPrefix ? `${interpretedTaskPrefix} ` : ''
1103
- let fileContent = file.getContent()
1104
- const journalTemplate = this.getConfig().journalTemplate
1105
- fileContent = fileContent.trim()
1106
- ? fileContent
1107
- : journalTemplate
1108
- && journalTemplate.trim()
1109
- ? journalTemplate + String(eol.lf)
1110
- : '';
1111
-
1112
- const length = fileContent.length
1113
- const fileIsEmpty = length < 1
1114
- const crlf = String(eol.crlf)
1115
- let sep = fileContent.indexOf(crlf) > -1 ? crlf : String(eol.lf)
1116
- if (fileContent.endsWith(sep) || fileIsEmpty) sep = ''
1117
- const order = appContext().projectContext.getOrder(list)
1118
- const description = lines.length > 1 ? Task.padDescription(lines.slice(1), taskPrefix) : []
1119
- const { orderMeta, tokenPrefix } = config
1120
- const rawTask = getRawTask({tokenPrefix, orderMeta, list, order, text, type: taskSyntax})
1121
- const taskContent = config.orderMeta
1122
- ? this.getTaskContent({
1123
- description,
1124
- order,
1125
- text,
1126
- taskPrefix,
1127
- taskSyntax,
1128
- })
1129
- : description.join(eol.lf)
1130
-
1131
- let appendContent = File.trimBlankLines(
1132
- `${taskPrefix}${rawTask}${eol.lf}${taskContent}`
1133
- )
1134
-
1135
- fileContent = `${fileContent}${sep}`
1136
- const cardTerminator = "\n".repeat(2)
1137
- file.setContent(`${fileContent}${appendContent}${cardTerminator}`)
1138
-
1139
- this.writeAndExtract(file, true, (err, file) => {
1140
- if (err) return cb(err)
1141
- const task = file.getTasks().find(task => task.text === text && task.list === list)
1142
- cb(null, file, task)
1143
- })
1144
- }
1145
-
1146
- Repository.prototype.addTaskToFile = function (filePath, list, content, cb) {
1147
- cb = tools.cb(cb)
1148
- const relPath = this.getRelativePath(filePath)
1149
- let file = this.getFile(relPath)
1150
-
1151
- if (file) {
1152
- this.readFileContent(file, (err, file) => {
1153
- if (err) return cb(err)
1154
- this.appendTask({file, content, list}, cb)
1155
- })
1156
- } else {
1157
- const modifiedTime = new Date()
1158
- const createdTime = new Date()
1159
- file = new File({
1160
- repoId: this.path,
1161
- filePath: relPath,
1162
- content: '',
1163
- modifiedTime,
1164
- createdTime,
1165
- project: this.project,
1166
- })
1167
- this.appendTask({file, content, list}, cb)
1168
- }
1169
- }
1170
-
1171
- /**
1172
- * Description
1173
- * @method modifyTask
1174
- * @param {} task
1175
- * @return CallExpression
1176
- */
1177
- Repository.prototype.modifyTask = function (task, writeFile, cb) {
1178
- if (!Task.isTask(task)) return cb()
1179
- if (_isFunction(writeFile)) {
1180
- cb = writeFile
1181
- writeFile = false
1182
- }
1183
- cb = tools.cb(cb)
1184
- const config = this.getConfig()
1185
- log(
1186
- 'Modifying Task... text:%s list:%s order:%d path:%s id:%s line:%d',
1187
- task.text,
1188
- task.list,
1189
- task.order,
1190
- task.source.path,
1191
- task.id,
1192
- task.line
1193
- )
1194
- var self = this
1195
- let beforeModifyContent
1196
- var file = this.getFileForTask(task)
1197
- try {
1198
- beforeModifyContent = file.getContent()
1199
- } catch (e) {
1200
- console.error(
1201
- `Can't get file for task: {text:'${task.text}', path:'${task.source.path}', line:${task.line}}`
1202
- )
1203
- }
1204
-
1205
- const modifyTransformExtract = (cb) => {
1206
- file.modifyTask(task, config, true)
1207
- file.extractTasks(config)
1208
- file.transformTask({config, modify:true, task})
1209
- file.extractTasks(config)
1210
-
1211
- if (!writeFile || beforeModifyContent === file.getContent()) return cb(null, task)
1212
-
1213
- this.writeAndAdd(file, cb)
1214
- }
1215
-
1216
- if (!beforeModifyContent) {
1217
- this.readFileContent(file, (err) => {
1218
- if (err) return cb(err)
1219
- modifyTransformExtract(cb)
1220
- })
1221
- } else {
1222
- modifyTransformExtract(cb)
1223
- }
1224
- }
1225
-
1226
- Repository.prototype.setTaskPriority = function (_task, index, cb) {
1227
- if (_task.order === '') return cb()
1228
- _task.order = index * 10
1229
- cb()
1230
- }
1231
-
1232
- function getPreviousIndexWithDifferentOrder(tasks, pos) {
1233
- let closestIndexWithOrder = -1
1234
-
1235
- if (pos === 0) return closestIndexWithOrder
1236
-
1237
- let lastTask = tasks[pos] || tasks [pos - 1]
1238
-
1239
- for (
1240
- let index = pos - 1;
1241
- index >= 0 && closestIndexWithOrder < 0;
1242
- index--
1243
- ) {
1244
- const t = tasks[index]
1245
- if (!tasksHaveSameOrder(lastTask, t)) closestIndexWithOrder = index
1246
- lastTask = t
1247
- }
1248
- return closestIndexWithOrder
1249
- }
1250
-
1251
- Repository.getPreviousIndexWithDifferentOrder = getPreviousIndexWithDifferentOrder
1252
-
1253
- function getNextIndexWithDifferentOrder(tasks, pos) {
1254
- let closestIndexWithOrder = -1
1255
-
1256
- let lastTask = tasks[pos]
1257
-
1258
- for (
1259
- let index = pos;
1260
- index < tasks.length && closestIndexWithOrder < 0;
1261
- index++
1262
- ) {
1263
- const t = tasks[index]
1264
- if (!tasksHaveSameOrder(lastTask, t)) closestIndexWithOrder = index
1265
- lastTask = t
1266
- }
1267
- return closestIndexWithOrder
1268
- }
1269
-
1270
- Repository.getNextIndexWithDifferentOrder = getNextIndexWithDifferentOrder
1271
-
1272
- function taskBeforeAndAfterHaveDifferentOrder(taskList, newPos) {
1273
- const taskBefore = taskList[newPos - 1]
1274
- const taskAfter = taskList[newPos]
1275
- return taskBeforeHasOrder(taskList, newPos)
1276
- && taskAfterHasOrder(taskList, newPos)
1277
- && parseFloat(taskBefore.order) !== parseFloat(taskAfter.order)
1278
- }
1279
-
1280
- function taskBeforeHasOrder(taskList, newPos) {
1281
- const taskBefore = taskList[newPos - 1]
1282
- return taskBefore && isNumber(taskBefore.order)
1283
- }
1284
-
1285
- function taskAfterHasOrder(taskList, newPos) {
1286
- const taskAfter = taskList[newPos]
1287
- return taskAfter && isNumber(taskAfter.order)
1288
- }
1289
- function lastTaskHasOrder(taskList) {
1290
- const lastTask = taskList[taskList.length - 1]
1291
- return lastTask && isNumber(lastTask.order)
1292
- }
1293
-
1294
- function getTasksToModify(task, taskList, newPos) {
1295
-
1296
- const previousIndexWithDifferentOrder = getPreviousIndexWithDifferentOrder(taskList, newPos)
1297
- const nextIndexWithDifferentOrder = getNextIndexWithDifferentOrder(taskList, newPos)
1298
- const previousTaskWithOrder = taskList[previousIndexWithDifferentOrder]
1299
- const nextTaskWithOrder = taskList[nextIndexWithDifferentOrder]
1300
-
1301
- const startingOrder = previousTaskWithOrder
1302
- ? isNumber(previousTaskWithOrder.order) && previousTaskWithOrder.order || 0
1303
- : 0;
1304
- const endingOrder = nextTaskWithOrder
1305
- ? nextTaskWithOrder.order
1306
- : null;
1307
-
1308
- const start = previousIndexWithDifferentOrder > -1 ? previousIndexWithDifferentOrder + 1 : 0;
1309
- const end = nextIndexWithDifferentOrder > -1 ? nextIndexWithDifferentOrder : newPos + 1;
1310
- const changes = nextIndexWithDifferentOrder - previousIndexWithDifferentOrder + 1
1311
- const increment = isNumber(endingOrder) ? (endingOrder - startingOrder) / changes : 10
1312
- const tasksToModify = []
1313
- let newOrder = startingOrder
1314
- let order = 0
1315
- if (taskList.length === 0) {
1316
- order = 0
1317
- } else if (newPos === 0) {
1318
- order = taskList[0].order - 10
1319
- } else if (newPos === taskList.length && lastTaskHasOrder(taskList)) {
1320
- order = taskList[taskList.length - 1].order + 10
1321
- } else if (taskBeforeAndAfterHaveDifferentOrder(taskList, newPos)) {
1322
- order = (taskList[newPos].order - taskList[newPos - 1].order) / 2 + taskList[newPos - 1].order
1323
- } else if (taskBeforeHasOrder(taskList, newPos) && !taskAfterHasOrder(taskList, newPos)) {
1324
- order = taskList[newPos - 1].order + 10
1325
- } else {
1326
- for (let index = start; index < end; index++) {
1327
- newOrder += increment
1328
- if (index === newPos) {
1329
- task.order = newOrder
1330
- tasksToModify.push(task)
1331
- newOrder += increment
1332
- }
1333
-
1334
- if (!taskList[index]) continue
1335
- taskList[index].order = newOrder
1336
- tasksToModify.push(taskList[index])
1337
- }
1338
- }
1339
-
1340
- if (tasksToModify.length === 0) {
1341
- task.order = order
1342
- tasksToModify.push(task)
1343
- }
1344
-
1345
- return tasksToModify
1346
- }
1347
-
1348
- Repository.getTasksToModify = getTasksToModify
1349
-
1350
- function tasksHaveSameOrder(taskA, taskB) {
1351
- return taskA && taskB && taskA.order + "" === taskB.order + ""
1352
- }
1353
-
1354
- Repository.prototype.moveTask = function ({ task, newList = task.list, newPos }, cb) {
1355
- if (!Task.isTask(task)) {
1356
- task = newCard(task, this.project, true)
1357
- }
1358
- task = this.getTask(task.id)
1359
-
1360
- var toListTasks = this.getTasksInList(newList)
1361
- if (toListTasks === undefined)
1362
- return cb(new Error(ERRORS.LIST_NOT_FOUND, newList))
1363
-
1364
- var fromListTasks = this.getTasksInList(task.list)
1365
- if (fromListTasks === undefined)
1366
- return cb(new Error(ERRORS.LIST_NOT_FOUND, task.list))
1367
-
1368
- var sameList = newList == task.list
1369
- if (!sameList) task.oldList = task.list
1370
- task.list = newList
1371
-
1372
- // Move the task to the correct position in the list
1373
- fromListTasks = _reject(fromListTasks, function (_task) {
1374
- return _task.equals(task)
1375
- })
1376
- toListTasks = _reject(toListTasks, function (_task) {
1377
- return _task.equals(task)
1378
- })
1379
-
1380
- const tasksToModify = getTasksToModify(task, toListTasks, newPos)
1381
- task.updateOrderMeta(this.config)
1382
- toListTasks.splice(newPos, 0, task)
1383
-
1384
- const tasksToModifySorted = fastSort(tasksToModify).by([{ desc: u => u.line }, { asc: u => u.path }])
1385
- async.eachSeries(
1386
- tasksToModifySorted,
1387
- (task, cb) => {
1388
- this.modifyTask(task, true, cb)
1389
- },
1390
- (err) => {
1391
- if (err)
1392
- return cb([
1393
- new Error(
1394
- 'moveTasks: Error while modifying tasks in the current list'
1395
- ),
1396
- err,
1397
- ])
1398
- cb(null, task)
1399
- }
1400
- )
1401
- }
1402
-
1403
- Repository.prototype.moveTasks = function (tasks, newList, newPos = 0, noEmit, cb) {
1404
- var log = require('debug')('moveTasks')
1405
- var self = this
1406
- if (this.getList(newList).filter)
1407
- return cb(new Error(`Tasks can\'t be moved to a filtered list ${newList}.`))
1408
- var listsModified = [newList]
1409
- if (_isFunction(noEmit)) {
1410
- cb = noEmit
1411
- noEmit = false
1412
- }
1413
- this.moving = true
1414
- cb = tools.cb(cb)
1415
-
1416
- log('Move tasks to list:%s at position:%d : %j', newList, newPos, tasks)
1417
- log(
1418
- 'newList before mods:',
1419
- JSON.stringify(self.getTasksInList(newList), null, 3)
1420
- )
1421
- fastSort(tasks).by({desc: t => t.line})
1422
- async.series(
1423
- tasks.map((task, i) => {
1424
- return function (cb) {
1425
- const foundTask = self.getTasks().find(({source, line}) => task.source.path === source.path && task.line === line)
1426
- if (foundTask) {
1427
- if (listsModified.indexOf(foundTask.list) < 0) listsModified.push(foundTask.list)
1428
- self.moveTask({ task: foundTask, newList, newPos: newPos + i, noEmit: true }, cb)
1429
- } else {
1430
- cb()
1431
- }
1432
- }
1433
- }),
1434
- function (err) {
1435
- if (err) {
1436
- console.error('Error occurred while moving tasks:', err)
1437
- }
1438
- self.saveModifiedFiles(function (err, results) {
1439
- if (err)
1440
- console.error('Error occurred while saving modified files:', err)
1441
- self.lastMovedFiles = results
1442
- var tasksByList = listsModified.map((list) => {
1443
- return {
1444
- list: list,
1445
- tasks: self.getTasksInList(list),
1446
- }
1447
- })
1448
- self.moving = false
1449
- cb(err, tasksByList)
1450
- if (!err && !noEmit) self.emit('tasks.moved', tasks)
1451
- })
1452
- }
1453
- )
1454
- }
1455
-
1456
- Repository.prototype.moveTasksAsync = function (tasks, newList, newPos, noEmit = false) {
1457
- return new Promise((resolve, reject) => {
1458
- this.moveTasks(tasks, newList, newPos, noEmit, (err, tasksByList) => {
1459
- if (err) return reject(err)
1460
- resolve(tasksByList)
1461
- })
1462
- })
1463
- }
1464
-
1465
- Repository.prototype.getModifiedFiles = function () {
1466
- var filesToSave = []
1467
- this.getFiles().forEach((file) => {
1468
- if (file.isModified()) filesToSave.push(file)
1469
- })
1470
- return filesToSave
1471
- }
1472
-
1473
- Repository.prototype.saveModifiedFiles = function (cb) {
1474
- const checksum = this.checksum || function () {}
1475
- var self = this
1476
- var filesToSave = self.getModifiedFiles()
1477
- var funcs = filesToSave.map((file) => {
1478
- return function (cb) {
1479
- file.checksum = checksum(file.getContent())
1480
- self.writeAndExtract(file, false, cb)
1481
- }
1482
- })
1483
-
1484
- if (funcs.length < 1) return cb()
1485
- this.savingFiles = true
1486
- async.parallel(funcs, (err) => {
1487
- this.savingFiles = false
1488
- if (err) return cb(err)
1489
- self.emit('files.saved', filesToSave)
1490
- cb()
1491
- })
1492
- }
1493
-
1494
- /**
1495
- * Description
1496
- * @method getTasks
1497
- * @return tasks
1498
- */
1499
- Repository.prototype.getTasks = function () {
1500
- var tasks = [],
1501
- self = this
1502
- this.getFiles().forEach((file) => {
1503
- Array.prototype.push.apply(tasks, file.getTasks())
1504
- })
1505
-
1506
- return tasks
1507
- }
1508
-
1509
- Repository.prototype.getTasksByList = function (noSort) {
1510
- return Repository.getTasksByList(this, this.getTasks(), noSort, true)
1511
- }
1512
-
1513
- Repository.getTasksByList = function (
1514
- repo,
1515
- tasksAry,
1516
- noSort,
1517
- populateFiltered
1518
- ) {
1519
- if (!repo) return []
1520
- var tasks = {}
1521
- var allTasks = noSort ? tasksAry : fastSort(tasksAry).by(DEFAULT_SORT)
1522
- allTasks.forEach(function (task) {
1523
- if (!tasks[task.list]) tasks[task.list] = []
1524
- tasks[task.list].push(task)
1525
- })
1526
- var lists = repo.getLists()
1527
- lists.forEach((list) => {
1528
- if (list.filter && populateFiltered)
1529
- return Repository.populateFilteredList(list, tasksAry)
1530
- list.tasks = tasks[list.name] || []
1531
- })
1532
- return lists
1533
- }
1534
-
1535
- Repository.populateFilteredList = function (list, tasks) {
1536
- try {
1537
- list.tasks = Repository.query(tasks, list.filter).map((task) => {
1538
- task.filteredListName = list.name
1539
- return task
1540
- })
1541
- } catch (e) {
1542
- list.tasks = []
1543
- }
1544
- }
1545
-
1546
- /**
1547
- * Description
1548
- * @method getTasksInList
1549
- * @param {} name
1550
- * @return ConditionalExpression
1551
- */
1552
- Repository.prototype.getTasksInList = function (name, offset, limit) {
1553
- if (!_isString(name)) return []
1554
- var tasks = _where(this.getTasks(), { list: name })
1555
- if (tasks.length === 0) return []
1556
- var allTasks = fastSort(tasks).by(DEFAULT_SORT)
1557
- if (isNumber(offset) && isNumber(limit))
1558
- return allTasks.slice(offset, offset + limit)
1559
- return allTasks
1560
- }
1561
-
1562
- Repository.prototype.getTaskIndex = function (task) {
1563
- const tasks = this.getTasksInList(task.list)
1564
- let index = 0
1565
- tasks.forEach((t, i) => {
1566
- if (t.id === task.id) index = i
1567
- })
1568
- return index
1569
- }
1570
-
1571
- Repository.prototype.serialize = function () {
1572
- return JSON.stringify(this, null, 3)
1573
- }
1574
-
1575
- // WARN: deprecated
1576
- Repository.deserialize = function (repo, cb) {
1577
- cb = tools.cb(cb)
1578
- repo = JSON.parse(repo)
1579
- const config = new Config(repo.config)
1580
- var newRepo = new Repository(repo.path, config)
1581
- var count = 0
1582
- repo.files.forEach((file) => {
1583
- log('file:%j', file)
1584
- newRepo.addFile(new File({ file, config, languages }), function (err) {
1585
- if (err) return cb(err)
1586
- count++
1587
- log('count:%d, repo.files.length:%d', count, repo.files.length)
1588
- if (count == repo.files.length) cb(null, newRepo)
1589
- })
1590
- })
1591
- }
1592
-
1593
- Repository.regexQuery = function (tasks, queryString) {
1594
- return tasks
1595
- .map((task) => {
1596
- const escapedQueryString = tools.escapeRegExp(queryString.trim());
1597
-
1598
- // Regex to match occurrences not part of a URL and not already surrounded by `==`
1599
- const regex = new RegExp(
1600
- `(?<!https?:\\/\\/[^\\s]*?)\\b${escapedQueryString}\\b(?![^\\s]*\\/)(?<!==)(?!==)`,
1601
- 'gi'
1602
- );
1603
-
1604
- // Regex to detect Markdown links `[title](url)` where `queryString` is in title or URL
1605
- const markdownLinkRegex = new RegExp(
1606
- `\\[([^\\]]*${escapedQueryString}[^\\]]*)\\]\\(([^)]+${escapedQueryString}[^)]*)\\)`,
1607
- 'gi'
1608
- );
1609
-
1610
- let match = task.content.match(regex) || task.content.match(markdownLinkRegex);
1611
- task.match = match;
1612
-
1613
- if (match && queryString.trim() && task.interpretedContent) {
1614
- // Replace standalone occurrences
1615
- task.interpretedContent = task.interpretedContent.replace(
1616
- regex,
1617
- '==$&=='
1618
- );
1619
-
1620
- // Replace Markdown links where queryString is in title or URL
1621
- task.interpretedContent = task.interpretedContent.replace(
1622
- markdownLinkRegex,
1623
- (match) => `==${match}==`
1624
- );
1625
-
1626
- task.updateDescriptionData();
1627
- }
1628
-
1629
- return task;
1630
- })
1631
- .filter(({ match }) => match);
1632
- };
1633
-
1634
-
1635
- Repository.parseHideListsFromQueryString = function (queryString) {
1636
- const hideLists = []
1637
- const regex = `hide:(\\s*((${LIST_NAME_PATTERN},?\\s?)+))?`
1638
- queryString = queryString.replaceAll(
1639
- // hide lists example `hide: list1, list2`
1640
- new XRegExp(regex, 'g'),
1641
- (match, allLists, lists) => {
1642
- lists && lists.split(',').forEach((list) => {
1643
- hideLists.push(list.trim())
1644
- })
1645
- return ''
1646
- }
1647
- ).trim()
1648
- return {
1649
- hideLists,
1650
- queryString,
1651
- }
1652
- }
1653
-
1654
- Repository.parseSortFromQueryString = function (queryString) {
1655
- const sort = []
1656
- queryString = queryString.replace(
1657
- /\s([+-])([A-Za-z.]+)/g,
1658
- (match, order, attr) => {
1659
- const direction = order === '+' ? 'asc' : 'desc'
1660
- const sortString = `{ "${direction}": "function (o) { return o.${attr}; }" }`
1661
- sort.push(JSONfns.parse(sortString))
1662
- return ''
1663
- }
1664
- )
1665
- return {
1666
- sort,
1667
- queryString,
1668
- }
1669
- }
1670
-
1671
- Repository.parseSortFromMongoQuery = function (mongoQuery) {
1672
- const sort = []
1673
- for (const [key, value] of Object.entries(mongoQuery.sort)) {
1674
- if (value > 0) sort.push({ asc: key })
1675
- else sort.push({ desc: key })
1676
- }
1677
- return sort
1678
- }
1679
-
1680
- Repository.sortByQuery = function (tasks, queryString = '') {
1681
- queryString = replaceDateLanguage(queryString)
1682
- let { sort } = Repository.parseSortFromQueryString(queryString)
1683
- if (!sort || sort.length === 0) sort = DEFAULT_SORT
1684
- fastSort(tasks).by(sort)
1685
- }
1686
-
1687
- Repository.filterCards = function (tasks, _queryString = '') {
1688
- let query
1689
- _queryString = replaceDateLanguage(_queryString)
1690
- let { sort, queryString } = Repository.parseSortFromQueryString(_queryString);
1691
- const hideListsQuery = Repository.parseHideListsFromQueryString(queryString);
1692
- queryString = hideListsQuery.queryString;
1693
- const hideLists = hideListsQuery.hideLists;
1694
- try {
1695
- query = monquery(queryString)
1696
- } catch (e) {
1697
- log(`Unable to parse ${_queryString} using monquery`)
1698
- }
1699
- let result = []
1700
- if (query) result = tasks.filter(sift.default(query))
1701
- if (!query || result.length === 0) {
1702
- result = Repository.regexQuery(tasks, queryString)
1703
- }
1704
- if (!sort || sort.length === 0) sort = DEFAULT_SORT
1705
- fastSort(result).by(sort)
1706
- return {
1707
- result,
1708
- query,
1709
- sort,
1710
- hideLists
1711
- }
1712
- }
1713
-
1714
- Repository.query = function (tasks, queryString) {
1715
- return Repository.filterCards(tasks, queryString).result
1716
- }
1717
-
1718
- Repository.replaceDatesInQuery = function (query) {
1719
- return Repository.filterObjectValues(query, (key, value) => {
1720
- let date = Date.parse(value)
1721
- if (date && /(\d{4})-(\d{2})-(\d{2})/.test(value)) {
1722
- return date
1723
- }
1724
- return value
1725
- })
1726
- }
1727
-
1728
- Repository.filterObjectValues = function (o, cb) {
1729
- if (o && typeof o === 'object') {
1730
- for (const [key, value] of Object.entries(o)) {
1731
- if (value && typeof value === 'object') {
1732
- Repository.filterObjectValues(value, cb)
1733
- } else {
1734
- o[key] = cb(key, value)
1735
- }
1736
- }
1737
- }
1738
- return o
1739
- }
1740
-
1741
- Repository.prototype.query = function (queryString) {
1742
- const result = Repository.query(this.getTasks(), queryString)
1743
- return Repository.getTasksByList(this, result, true)
1744
- }
1745
-
1746
- module.exports = Repository