imdone-core 1.45.0 → 1.46.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.
@@ -30,6 +30,11 @@ module.exports = {
30
30
  return fs.readFileSync.apply({}, args)
31
31
  },
32
32
 
33
+ appendFileSync(...args) {
34
+ console.warn('sync call')
35
+ return fs.appendFileSync.apply({}, args)
36
+ },
37
+
33
38
  writeFileSync(...args) {
34
39
  console.warn('sync call')
35
40
  return fs.writeFileSync.apply({}, args)
@@ -78,6 +83,7 @@ module.exports = {
78
83
  console.warn('sync call')
79
84
  return fs.readdirSync(path, { withFileTypes: true })
80
85
  },
86
+
81
87
  sanitizeFileName(fileName, replaceSpacesWith) {
82
88
  // Remove markdown emoji
83
89
  fileName = fileName.replace(/:\w+:/g, '').trim()
@@ -85,5 +91,21 @@ module.exports = {
85
91
  if (replaceSpacesWith)
86
92
  fileName = fileName.replace(/ /g, replaceSpacesWith)
87
93
  return fileName
94
+ },
95
+
96
+ preparePathForWriting(path) {
97
+ let stat = this.statSync(path)
98
+ if (!stat) {
99
+ let { dir, ext } = _path.parse(path)
100
+ if (!ext) {
101
+ dir = path
102
+ }
103
+ this.mkdirpSync(dir)
104
+ stat = this.statSync(path)
105
+ }
106
+ return {
107
+ isFile: stat && stat.isFile(),
108
+ isDirectory: stat && stat.isDirectory()
109
+ }
88
110
  }
89
111
  }
package/lib/card.js CHANGED
@@ -327,7 +327,9 @@ module.exports = function newCard(task, _project, dontParse) {
327
327
  )
328
328
  const formattedRawMarkdown = this.formatContent(rawMarkdown, true).content
329
329
  const { html, truncHtml } = this.getHtml(content)
330
+ const title = this.format(this.text, true)
330
331
  return {
332
+ title,
331
333
  html,
332
334
  truncHtml,
333
335
  encodedText,
package/lib/config.js CHANGED
@@ -179,6 +179,18 @@ class Config {
179
179
  return _get(this, 'settings.cards.maxLines', 1)
180
180
  }
181
181
 
182
+ get archiveFolder() {
183
+ return _get(this, 'settings.cards.archiveFolder', '')
184
+ }
185
+
186
+ get archiveCompleted() {
187
+ return _get(this, 'settings.cards.archiveCompleted', false)
188
+ }
189
+
190
+ get doneList() {
191
+ return this.getDoneList()
192
+ }
193
+
182
194
  get blankLinesToEndTask() {
183
195
  let blankLinesToEndTask = Math.round(
184
196
  _get(this, 'settings.cards.blankLinesToEndTask', 1)
package/lib/file.js CHANGED
@@ -74,6 +74,7 @@ function File(opts) {
74
74
  this.tasks = []
75
75
  this.isDir = false
76
76
  this.lineCount = 0
77
+ this.deleted = false
77
78
  }
78
79
  }
79
80
  util.inherits(File, events.EventEmitter)
@@ -36,6 +36,9 @@ module.exports = function (repo, defaultSettings = defaultSettingsObject) {
36
36
  config.code.include_lists.push(list.name)
37
37
  })
38
38
 
39
+ if (!config.settings.cards) {
40
+ config.settings.cards = {}
41
+ }
39
42
  // metaSep
40
43
  if (config.settings.metaSep) {
41
44
  config.settings.cards.metaSep = config.settings.metaSep
@@ -47,14 +50,20 @@ module.exports = function (repo, defaultSettings = defaultSettingsObject) {
47
50
  delete config.settings.defaultList
48
51
  }
49
52
 
50
- if (!config.settings.cards) {
51
- config.settings.cards = {}
52
- }
53
-
54
53
  if (!config.settings.cards.defaultList) {
55
54
  config.settings.cards.defaultList = lists ? lists[0] : ''
56
55
  }
57
56
 
57
+ // archiveCompleted
58
+ if (config.settings.cards.archiveCompleted === undefined) {
59
+ config.settings.cards.archiveCompleted = false
60
+ }
61
+
62
+ // archiveFolder
63
+ if (!config.settings.cards.archiveFolder) {
64
+ config.settings.cards.archiveFolder = config.settings.journalPath ? _path.join(config.settings.journalPath, 'archive') : 'archive'
65
+ }
66
+
58
67
  // showTagsAndMeta
59
68
  if (config.settings.cards.showTagsAndMeta === undefined) {
60
69
  config.settings.cards.showTagsAndMeta = true
@@ -126,7 +135,12 @@ module.exports = function (repo, defaultSettings = defaultSettingsObject) {
126
135
  // make sure all lists have an id
127
136
  config.lists.forEach((list) => {
128
137
  if (!list.id || (list.id + "").length < 4) list.id = uniqid()
129
- })
138
+ })
139
+
140
+ // Make sure kudosProbability is set
141
+ if (!config.settings.kudosProbability && config.settings.kudosProbability !== 0) {
142
+ config.settings.kudosProbability = 0.33
143
+ }
130
144
 
131
145
  const defaultCardsSettings = _cloneDeep(defaultSettings.cards)
132
146
  const cardsSettings = _cloneDeep(config.settings.cards)
@@ -311,7 +311,8 @@ function mixin(repo, fs = require('fs')) {
311
311
  cb = tools.cb(cb)
312
312
 
313
313
  if (!File.isFile(file)) return cb(new Error(ERRORS.NOT_A_FILE))
314
-
314
+ if (file.deleted) return cb(null, file)
315
+
315
316
  var filePath = repo.getFullPath(file)
316
317
 
317
318
  if (!/\.\.(\/|\\)/.test(file.path)) {
@@ -0,0 +1,58 @@
1
+ const Plugin = require('imdone-api')
2
+ const path = require('path')
3
+
4
+ module.exports = class ArchivePlugin extends Plugin {
5
+ constructor(project) {
6
+ super(project)
7
+ }
8
+
9
+ get config() {
10
+ return this.project.config
11
+ }
12
+
13
+ get fileGateway() {
14
+ return this.project.fileGateway
15
+ }
16
+
17
+ onBeforeBoardUpdate() {
18
+ const config = this.config
19
+ if (!config || !config.archiveCompleted || this.archivingTasks) return
20
+ this.archivingTasks = true
21
+ const cards = this.project.getAllCards(`meta.archived != "true" and list = ${config.doneList}`)
22
+ const messageUser = cards.length > 1
23
+ if (!cards.length) {
24
+ this.archivingTasks = false
25
+ return
26
+ } else {
27
+ if (messageUser) this.project.snackBar({message: `Archiving ${cards.length} card(s)...`})
28
+ }
29
+ cards.forEach(card => this.archiveTask(card))
30
+ if (messageUser) this.project.snackBar({message: ` Done archiving ${cards.length} card(s). Deleting original(s)...`})
31
+ this.project.deleteTasks(cards).then(() => {
32
+ this.archivingTasks = false
33
+ if (messageUser) this.project.snackBar({message: `Done deleting original card(s).`})
34
+ })
35
+ }
36
+
37
+ archiveTask (task, config = this.config) {
38
+ const archiveFolder = config.archiveFolder
39
+ const fileDir = path.dirname(task.relPath)
40
+
41
+ if (fileDir.startsWith(archiveFolder)) return
42
+
43
+ const fileName = this.fileGateway.sanitizeFileName(`${task.text}.md`, config.replaceSpacesWith)
44
+ const newPath = path.join(this.project.path, archiveFolder, fileDir, fileName)
45
+
46
+ const content = this.project.addMetaToContent(
47
+ [
48
+ { key: 'archived', value: 'true'}
49
+ ],
50
+ task.content
51
+ )
52
+ const taskContent = `${task.beforeText}#${task.list} ${content}`
53
+
54
+ this.fileGateway.preparePathForWriting(newPath)
55
+ this.fileGateway.appendFileSync(newPath, taskContent)
56
+ }
57
+
58
+ }
@@ -11,7 +11,11 @@ module.exports = class PluginManager extends Emitter {
11
11
  constructor(project) {
12
12
  super()
13
13
  this.project = project
14
- this.defaultPlugins = ['./epic-plugin', './extension-plugin', './cli-backlog-plugin']
14
+ this.defaultPlugins = [
15
+ './archive-plugin',
16
+ './epic-plugin',
17
+ './extension-plugin'
18
+ ]
15
19
  this.pluginsMap = {}
16
20
  this.pluginPath = _path.join(project.path, '.imdone', 'plugins')
17
21
  this.onDevChange = debounce(this.onDevChange.bind(this), 1000)
@@ -19,7 +23,7 @@ module.exports = class PluginManager extends Emitter {
19
23
  }
20
24
 
21
25
  async startDevMode() {
22
- if (this.project.config.devMode && !this.watcher) {
26
+ if (this.project && this.project.config.devMode && !this.watcher) {
23
27
  if (!(await this.exists(this.pluginPath)))
24
28
  await fs.promises.mkdir(this.pluginPath)
25
29
  this.watcher = sane(this.pluginPath, {
package/lib/project.js CHANGED
@@ -47,8 +47,45 @@ module.exports = class WorkerProject extends Project {
47
47
  super()
48
48
  this.repo = repo
49
49
  this.innerFilter = ''
50
+ this.fileGateway = fileGateway
50
51
  }
51
52
 
53
+ init(cb) {
54
+ this.repo.project = this
55
+
56
+ this.pluginManager = new PluginManager(this)
57
+ this.pluginManager.on('plugin-installed', () => this.emitUpdate())
58
+ this.pluginManager.on('plugin-uninstalled', () => this.emitUpdate())
59
+ this.pluginManager.on('plugins-reloaded', () => this.emitUpdate())
60
+
61
+ EVENTS.forEach((event) => {
62
+ this.repo.on(event, (data) => onChange(this, event, data))
63
+ })
64
+
65
+ const promise = new Promise((resolve, reject) => {
66
+ this.repo.init((err, files) => {
67
+ if (err) {
68
+ if (cb) cb(err)
69
+ else reject(err)
70
+ return
71
+ }
72
+ this.pluginManager
73
+ .startDevMode()
74
+ .then(() => {
75
+ if (cb) cb(null, files)
76
+ else resolve(files)
77
+ })
78
+ .catch(err => {
79
+ console.log('Error on starting dev mode', err)
80
+ if (cb) cb(err)
81
+ else reject(err)
82
+ })
83
+ })
84
+ })
85
+
86
+ if (!cb) return promise
87
+ }
88
+
52
89
  // HACK:-50 To handle circular dependency issue with file
53
90
  toJSON() {
54
91
  return { path: this.path }
@@ -114,42 +151,6 @@ module.exports = class WorkerProject extends Project {
114
151
  }
115
152
  }
116
153
 
117
- init(cb) {
118
- this.repo.project = this
119
- this.pluginManager = new PluginManager(this)
120
- this.pluginManager.on('plugin-installed', () => this.emitUpdate())
121
- this.pluginManager.on('plugin-uninstalled', () => this.emitUpdate())
122
- this.pluginManager.on('plugins-reloaded', () => this.emitUpdate())
123
-
124
- EVENTS.forEach((event) => {
125
- this.repo.on(event, (data) => onChange(this, event, data))
126
- })
127
-
128
-
129
- const promise = new Promise((resolve, reject) => {
130
- this.repo.init((err, files) => {
131
- if (err) {
132
- if (cb) cb(err)
133
- else reject(err)
134
- return
135
- }
136
- this.pluginManager
137
- .startDevMode()
138
- .then(() => {
139
- if (cb) cb(null, files)
140
- else resolve(files)
141
- })
142
- .catch(err => {
143
- console.log('Error on starting dev mode', err)
144
- if (cb) cb(err)
145
- else reject(err)
146
- })
147
- })
148
- })
149
-
150
- if (!cb) return promise
151
- }
152
-
153
154
  emit() {}
154
155
 
155
156
  emitUpdate() {
@@ -371,13 +372,13 @@ module.exports = class WorkerProject extends Project {
371
372
 
372
373
  async updateCardContent(task, content) {
373
374
  return new Promise((resolve, reject) => {
374
- this.repo.modifyTaskFromContent(task, content, (err) => {
375
+ this.repo.modifyTaskFromContent(task, content, (err, file) => {
375
376
  if (err) {
376
377
  console.error(err)
377
378
  reject(err)
378
379
  }
379
380
  this.emitUpdate()
380
- resolve()
381
+ resolve(file)
381
382
  })
382
383
  })
383
384
  }
@@ -416,22 +417,6 @@ module.exports = class WorkerProject extends Project {
416
417
  this.emit('project.saveFile', { file: filePath, content })
417
418
  }
418
419
 
419
- preparePathForWriting(path) {
420
- let stat = fileGateway.statSync(path)
421
- if (!stat) {
422
- let { dir, ext } = _path.parse(path)
423
- if (!ext) {
424
- dir = path
425
- }
426
- fileGateway.mkdirpSync(dir)
427
- stat = fileGateway.statSync(path)
428
- }
429
- return {
430
- isFile: stat && stat.isFile(),
431
- isDirectory: stat && stat.isDirectory()
432
- }
433
- }
434
-
435
420
  /*
436
421
  * @param {Object} opts - The options object
437
422
  * @param {String} opts.list - The list name to add the card to
@@ -442,9 +427,10 @@ module.exports = class WorkerProject extends Project {
442
427
  * @param {Boolean} opts.emit - Whether to emit the new card event
443
428
  */
444
429
  newCard({ list, path, template, title, comments, emit = true }) {
445
- if (!path || !_path.parse(path).ext) path = this.getFullPath(this.getNewCardsFile({title}))
430
+ if (!path || !_path.parse(path).ext) path = this.getNewCardsFile({title})
431
+ path = this.getFullPath(path)
446
432
 
447
- const { isFile, isDirectory } = this.preparePathForWriting(path)
433
+ const { isFile, isDirectory } = fileGateway.preparePathForWriting(path)
448
434
 
449
435
  if (!template) template = this.getNewCardTemplate(path, isFile)
450
436
 
@@ -559,6 +545,16 @@ module.exports = class WorkerProject extends Project {
559
545
  })
560
546
  }
561
547
 
548
+ async deleteTasks(tasks) {
549
+ return new Promise((resolve, reject) => {
550
+ this.repo.deleteTasks(tasks, async (err) => {
551
+ if (err) return reject(err)
552
+ resolve()
553
+ })
554
+ })
555
+
556
+ }
557
+
562
558
  setFilter(filter) {
563
559
  this.emit('project.filter', { filter })
564
560
  }
package/lib/repository.js CHANGED
@@ -622,12 +622,14 @@ Repository.prototype.readFile = function (file, cb) {
622
622
  const checksum = this.checksum || function () {}
623
623
  cb = tools.cb(cb)
624
624
  if (!File.isFile(file)) return cb(new Error(ERRORS.NOT_A_FILE))
625
+ if (file.deleted) return cb(null, file)
625
626
  var self = this
626
627
 
627
628
  var currentChecksum = file.checksum
628
- if (!/\.\.(\/|\\)/.test(file.path)) {
629
+ const filePath = file.path
630
+ if (!/\.\.(\/|\\)/.test(filePath)) {
629
631
  this.readFileContent(file, (err, file) => {
630
- if (err) return cb(new Error('Unable to read file:' + file))
632
+ if (err) return cb(new Error('Unable to read file:' + filePath))
631
633
  // BACKLOG:-40 Allow user to assign a module for content transformation
632
634
  file.checksum = checksum(file.getContent())
633
635
  file.updated = currentChecksum !== file.checksum
@@ -966,13 +968,15 @@ Repository.prototype.deleteTask = function (task, cb) {
966
968
  if (!cb) throw new Error('task, callback required')
967
969
  var self = this
968
970
  var file = self.getFileForTask(task)
971
+ if (!file) return cb(null)
969
972
  const execute = function (file) {
970
973
  file.deleteTask(task, self.getConfig())
971
- if (
972
- file.getContentForFile().trim() === '' &&
973
- self.config.journalType === constants.JOURNAL_TYPE.NEW_FILE
974
- ) {
975
- return self.deleteFile(file.path, cb)
974
+ if (file.getContentForFile().trim() === '') {
975
+ if (file.deleted) return
976
+ console.log('Deleting empty file:', file.path)
977
+ self.deleteFile(file.path, cb)
978
+ file.deleted = true
979
+ return
976
980
  }
977
981
  self.writeAndExtract(file, true, cb)
978
982
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imdone-core",
3
- "version": "1.45.0",
3
+ "version": "1.46.0",
4
4
  "description": "imdone-core",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/cli.js DELETED
@@ -1,225 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- const { program } = require('commander');
4
- const ora = require('ora')
5
- const chalk = require('chalk')
6
- const { ChangesExistError } = require('./lib/cli/adapters/Errors')
7
- const {
8
- imdoneInit,
9
- planStory,
10
- startTask,
11
- addTask,
12
- listTasks ,
13
- completeTask,
14
- openBoard,
15
- openTaskFile,
16
- exportProject
17
- } = require('./lib/cli/CliControler')
18
- const {
19
- STORY_ID
20
- } = require('./lib/cli/domain/BacklogProject').constants
21
- const package = require('./package.json')
22
-
23
- const { log } = hideLogs()
24
- const spinner = ora('Loading unicorns')
25
-
26
- setTimeout(() => {
27
- spinner.color = 'yellow';
28
- spinner.text = 'Loading rainbows';
29
- }, 1000);
30
-
31
- function actionCancelled() {
32
- log(chalk.bgRed('Action canceled'))
33
- }
34
-
35
- const STORY_OPTION = `-s, --${STORY_ID} `
36
- const STORY_OPTION_OPTIONAL = `${STORY_OPTION}[${STORY_ID}]`
37
- const STORY_OPTION_REQUIRED = `${STORY_OPTION}<${STORY_ID}>`
38
-
39
- const mainCmd = program
40
- .version(package.version, '-v, --version', 'output the current version')
41
-
42
- mainCmd
43
- .command('export')
44
- .description('Export the current project')
45
- .action(async function () {
46
- try {
47
- await exportProject(log)
48
- } catch (e) {
49
- console.error(e)
50
- actionCancelled()
51
- } finally {
52
- process.exit(0)
53
- }
54
- })
55
-
56
- // addDevCmd(mainCmd)
57
-
58
- program.parse();
59
-
60
- function hideLogs() {
61
- const log = console.log
62
- const info = console.info
63
- const warn = console.warn
64
- const logQueue = {warn: [], info: [], log: []}
65
- if (!process.env.DEBUG) {
66
- Object.keys(logQueue).forEach((key) => {
67
- console[key] = function(...args) {
68
- logQueue[key].push(args)
69
- }
70
- })
71
- }
72
- return {log, info, warn, logQueue}
73
- }
74
-
75
-
76
- async function planAStory(markdown, storyId, log) {
77
- try {
78
- await planStory(markdown, storyId, log)
79
- } catch (e) {
80
- log(chalk.yellowBright(e.message))
81
- }
82
- }
83
-
84
- function addDevCmd(mainCmd) {
85
- const devCmd = mainCmd
86
- .command('dev')
87
- .description('Plan and track your storys and tasks')
88
-
89
- devCmd
90
- .command('init')
91
- .description('initialize backlog')
92
- .action(async function () {
93
- try {
94
- await imdoneInit()
95
- } catch (e) {
96
- log(chalk.yellowBright(e.message))
97
- } finally {
98
- process.exit(0)
99
- }
100
- })
101
-
102
- devCmd
103
- .command('plan')
104
- .option(STORY_OPTION_OPTIONAL, 'Update an existing story\'s tasks')
105
- .description('Plan a story with tasks and DoD')
106
- .action(async function () {
107
- let markdown
108
- let { storyId } = this.opts()
109
- if (process.stdin.isTTY) {
110
- await planAStory(markdown, storyId, log)
111
- process.exit(0)
112
- } else {
113
-
114
- const stdin = process.stdin;
115
- spinner.start()
116
-
117
- markdown = ''
118
-
119
- stdin.on('readable', function() {
120
- var chunk = stdin.read();
121
- if(chunk !== null){
122
- markdown += chunk;
123
- }
124
- });
125
- stdin.on('end', async function() {
126
- await planAStory(markdown, storyId, log)
127
- spinner.stop()
128
- process.exit(0)
129
- });
130
- }
131
- })
132
-
133
- devCmd
134
- .command('open')
135
- .description('Open the current or selected task in the default markdown editor')
136
- .action(async function () {
137
- try {
138
- await openTaskFile(log)
139
- } catch (e) {
140
- log(chalk.yellowBright(e.message))
141
- } finally {
142
- process.exit(0)
143
- }
144
- })
145
-
146
- devCmd
147
- .command('board')
148
- .description('Open the current task in imdone')
149
- .action(async function () {
150
- try {
151
- spinner.start()
152
- await openBoard(log)
153
- spinner.stop()
154
- } catch (e) {
155
- log(chalk.yellowBright(e.message))
156
- } finally {
157
- process.exit(0)
158
- }
159
- })
160
-
161
- devCmd
162
- .command('start [task-id]')
163
- .description('Start a task by id')
164
- .action(async function () {
165
- const taskId = this.args.length > 0 ? this.args[0]: null
166
- try {
167
- await startTask(taskId, log)
168
- } catch (e) {
169
- if (e instanceof ChangesExistError) {
170
- log(chalk.yellowBright(e.message))
171
- } else {
172
- actionCancelled()
173
- }
174
- } finally {
175
- process.exit(0)
176
- }
177
- })
178
-
179
- devCmd
180
- .command('done')
181
- .description('Mark the current task as done')
182
- .action(async function () {
183
- try {
184
- await completeTask(log)
185
- } catch (e) {
186
- actionCancelled()
187
- } finally {
188
- process.exit(0)
189
- }
190
- })
191
-
192
- devCmd
193
- .command('add-task <task-content>')
194
- .description('Add a task to a story')
195
- .option(STORY_OPTION_REQUIRED, 'The story to add this task to')
196
- .option('-g, --group <group>', 'The group to add this task to')
197
- .action(async function () {
198
- let { storyId, group } = this.opts()
199
- try {
200
- await addTask({content: this.args[0], storyId, group, log})
201
- } catch (e) {
202
- actionCancelled()
203
- } finally {
204
- process.exit(0)
205
- }
206
- })
207
-
208
- devCmd
209
- .command('ls')
210
- .description('list tasks')
211
- .option(STORY_OPTION_REQUIRED, 'The story to list tasks for')
212
- .option('-f, --filter <filter>', 'The filter to use')
213
- .option('-j, --json', 'Output as json')
214
- .action(async function () {
215
- let {storyId, filter, json } = this.opts()
216
- try {
217
- await listTasks({storyId, filter, json, log})
218
- } catch (e) {
219
-
220
- actionCancelled()
221
- } finally {
222
- process.exit(0)
223
- }
224
- })
225
- }