imdone-core 1.54.0 → 1.54.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.
@@ -0,0 +1,37 @@
1
+ ---
2
+ name: Bug report
3
+ about: Create a report to help us improve imdone
4
+ title: ''
5
+ labels: 'bug'
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ **Describe the bug**
11
+ A clear and concise description of what the bug is.
12
+
13
+ **To Reproduce**
14
+ Steps to reproduce the behavior:
15
+ 1. Go to '...'
16
+ 2. Click on '....'
17
+ 3. Scroll down to '....'
18
+ 4. See error
19
+
20
+ **Expected behavior**
21
+ A clear and concise description of what you expected to happen.
22
+
23
+ **Screenshots**
24
+ If applicable, add screenshots to help explain your problem.
25
+
26
+ **Desktop (please complete the following information):**
27
+ - OS: [e.g. iOS]
28
+ - Browser (If imdone web site) [e.g. chrome, safari]
29
+ - Imdone Version [e.g. 22]
30
+
31
+ **Additional context**
32
+ Add any other context about the problem here.
33
+
34
+ **DoD**
35
+ - [ ] Issue is understood
36
+ - [ ] Fix is implemented and tested
37
+ - [ ] Fix is deployed in the current release
package/cli.js ADDED
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { program } = require('commander');
4
+ const ora = require('ora')
5
+ const chalk = require('chalk')
6
+ const package = require('./package.json')
7
+ const { createFileSystemProject } = require('./lib/project-factory.js')
8
+ // const { log } = hideLogs()
9
+ const spinner = ora('Loading unicorns')
10
+
11
+ setTimeout(() => {
12
+ spinner.color = 'yellow';
13
+ spinner.text = 'Loading rainbows';
14
+ }, 1000);
15
+
16
+ function actionCancelled() {
17
+ log(chalk.bgRed('Action canceled'))
18
+ }
19
+
20
+ const mainCmd = program
21
+ .version(package.version, '-v, --version', 'output the current version')
22
+
23
+ mainCmd
24
+ .command('action <pluginName> <actionTitle>')
25
+ .option('-t, --task <task>', 'Task filePath:line')
26
+ .description('Run a board action')
27
+ .action(async function (plugin, title, options) {
28
+ spinner.start()
29
+ const action = {plugin, title}
30
+ const [filePath, line] = options.task ? options.task.split(':') : []
31
+ try {
32
+ const project = createFileSystemProject({path: process.env.PWD})
33
+ await project.init()
34
+ await project.toImdoneJSON()
35
+ chalk.bgGreen('Project loaded')
36
+ const file = filePath && project.getFile(filePath)
37
+ const task = file && line > -1 && file.getTaskAtLine(line)
38
+ await project.performBoardAction(action, task)
39
+ } catch (e) {
40
+ console.error(e)
41
+ actionCancelled()
42
+ } finally {
43
+ spinner.stop()
44
+ process.exit(0)
45
+ }
46
+ })
47
+
48
+
49
+ program.parse();
50
+
51
+ function hideLogs() {
52
+ const log = console.log
53
+ const info = console.info
54
+ const warn = console.warn
55
+ const logQueue = {warn: [], info: [], log: []}
56
+ if (!process.env.DEBUG) {
57
+ Object.keys(logQueue).forEach((key) => {
58
+ console[key] = function(...args) {
59
+ logQueue[key].push(args)
60
+ }
61
+ })
62
+ }
63
+ return {log, info, warn, logQueue}
64
+ }
65
+
66
+ function actionCancelled() {
67
+ log(chalk.bgRed('Action canceled'))
68
+ }
@@ -38,6 +38,15 @@ const gateway = {
38
38
  }
39
39
  },
40
40
 
41
+ async stat(path, opts) {
42
+ try {
43
+ return await fs.promises.stat(path, opts)
44
+ }
45
+ catch {
46
+ return false
47
+ }
48
+ },
49
+
41
50
  readFileSync(...args) {
42
51
  console.warn('sync call')
43
52
  return fs.readFileSync.apply({}, args)
@@ -3,6 +3,7 @@ const uniqid = require('uniqid');
3
3
 
4
4
  module.exports = {
5
5
  format,
6
+ encodeMarkdownLinks
6
7
  }
7
8
 
8
9
  function removeDisplayComments(description) {
@@ -59,6 +60,16 @@ function interpolate(content, data, opts) {
59
60
  return { content, hasInterpolation, errorTokens };
60
61
  }
61
62
 
63
+ function encodeMarkdownLinks(content) {
64
+ return content.replace(/(?<!!)\[(.*?)\]\((.*?)( ".*?")?\)/g, (match, p1, p2, p3) => {
65
+ // URI encode file links
66
+ const title = p3 || '';
67
+ const encodedUrl = p2.replace(/\s/g, '%20').replace(/\)/g, '%29').replace(/\(/g, '%28');
68
+ const link = `[${p1}](${encodedUrl}${title})`;
69
+ return link.replace(/%20(".*?"\))$/, ' $1');
70
+ });
71
+ }
72
+
62
73
  function format(content, data, mustache) {
63
74
  const opts = { interpolate: /(?<![`\\])\${([\s\S]+?)}/g };
64
75
  const errorTokens = {};
package/lib/card.js CHANGED
@@ -9,7 +9,7 @@ const _get = require('lodash.get')
9
9
  const eol = require('eol')
10
10
  const allEmoji = require('markdown-it-emoji/lib/data/full.json')
11
11
  const { getFunctionSignature } = require('./adapters/parsers/function-parser')
12
- const { format } = require('./adapters/parsers/content-transformer')
12
+ const { format, encodeMarkdownLinks } = require('./adapters/parsers/content-transformer')
13
13
  const { removeMD } = require('./adapters/markdown')
14
14
 
15
15
  const CONTENT_TOKEN = '__CONTENT__'
@@ -218,14 +218,8 @@ module.exports = function newCard(task, _project, dontParse) {
218
218
  })
219
219
  const encodedText = encodeURIComponent(plainDescription)
220
220
  const encodedMD = encodeURIComponent(content)
221
- content = content
222
- .replace(CONTENT_TOKEN, encodedText)
223
- .replace(/(?<!!)\[(.*?)\]\((.*?)( ".*?")?\)/g, (match, p1, p2, p3) => {
224
- // URI encode file links
225
- const title = p3 || ''
226
- const link = `[${p1}](${p2.replace(/\s/g, '%20')}${title})`
227
- return link.replace(/%20(".*?"\))$/, ' $1')
228
- })
221
+ content = content.replace(CONTENT_TOKEN, encodedText)
222
+ content = encodeMarkdownLinks(content)
229
223
  return { encodedMD, encodedText, content }
230
224
  }
231
225
 
package/lib/file.js CHANGED
@@ -324,7 +324,7 @@ File.prototype.transformTasks = function (config, modify) {
324
324
 
325
325
  // TODO Blank lines should be allowed in code tasks
326
326
  // <!--
327
- // #imdone-1.49.0
327
+ // #imdone-1.54.0
328
328
  // #feature
329
329
  // order:-105
330
330
  // -->
@@ -338,7 +338,7 @@ File.prototype.extractCodeStyleTasks = function (config, pos, content) {
338
338
  while ((result = codeStyleRegex.exec(content)) !== null) {
339
339
  var posInContent = pos + result.index
340
340
  var line = this.getLineNumber(posInContent)
341
- if (self.hasTaskAtLine(line)) continue
341
+ if (self.getTaskAtLine(line)) continue
342
342
  var charBeforeList = this.getContent().substring(
343
343
  posInContent - 1,
344
344
  posInContent
@@ -391,7 +391,7 @@ File.prototype.extractHashStyleTasks = function (config, pos, content) {
391
391
  if (this.isWithinCodeSpanOrBlock(result.index, content)) continue
392
392
 
393
393
  const line = this.getLineNumber(posInContent)
394
- if (this.hasTaskAtLine(line)) continue
394
+ if (this.getTaskAtLine(line)) continue
395
395
 
396
396
  if (lang.block) {
397
397
  const blockEndPos = text.indexOf(lang.block.end)
@@ -427,7 +427,7 @@ File.prototype.extractLinkStyleTasks = function (config, pos, content) {
427
427
  const posInContent = result.index + pos + beforeLength
428
428
  if (this.isWithinCodeSpanOrBlock(result.index, content)) continue
429
429
  var line = this.getLineNumber(posInContent)
430
- if (self.hasTaskAtLine(line)) continue
430
+ if (self.getTaskAtLine(line)) continue
431
431
  if (!config.listExists(list)) continue
432
432
  log = require('debug')('extractLinkStyleTasks')
433
433
  log('*********************************************************')
@@ -462,7 +462,7 @@ File.prototype.extractCheckboxTasks = function (config, pos, content) {
462
462
  let tasks = []
463
463
  const hasTaskAtLine = (line) => {
464
464
  return (
465
- this.hasTaskAtLine(line) ||
465
+ this.getTaskAtLine(line) ||
466
466
  tasks.find((task) => {
467
467
  return line >= task.line && line <= task.lastLine
468
468
  })
@@ -493,7 +493,7 @@ File.prototype.extractCheckboxTasks = function (config, pos, content) {
493
493
  if (tasks) {
494
494
  const hasTaskAtLine = (line) => {
495
495
  return (
496
- this.hasTaskAtLine(line) ||
496
+ this.getTaskAtLine(line) ||
497
497
  tasks.find((task) => {
498
498
  return line > task.line && line <= task.lastLine
499
499
  })
@@ -541,6 +541,9 @@ File.prototype.extractAndTransformTasks = function (config) {
541
541
  this.transformTasks(config)
542
542
  }
543
543
 
544
+ // TODO Extract functions should be moved to another file
545
+ // Only pass the needed config values to the functions
546
+ // #imdone-1.54.0
544
547
  File.prototype.extractTasksInCodeFile = function (config) {
545
548
  var self = this
546
549
  var commentRegex = this.getCodeCommentRegex()
@@ -1002,7 +1005,7 @@ File.prototype.tasksMatch = function (config, task, line, taskText) {
1002
1005
  )
1003
1006
  }
1004
1007
 
1005
- File.prototype.hasTaskAtLine = function (line) {
1008
+ File.prototype.getTaskAtLine = function (line) {
1006
1009
  return this.getTasks().find((task) => {
1007
1010
  return line >= task.line && line <= task.lastLine
1008
1011
  })
@@ -8,6 +8,7 @@ module.exports = class DefaultBoardActionsPlugin extends Plugin {
8
8
  getBoardActions() {
9
9
  const project = this.project
10
10
  const isWindows = process.platform === 'win32'
11
+ const openIn = isWindows ? 'Explorer' : 'Finder'
11
12
 
12
13
  return [
13
14
  {
@@ -42,7 +43,7 @@ module.exports = class DefaultBoardActionsPlugin extends Plugin {
42
43
  },
43
44
  },
44
45
  {
45
- title: `Open in ${isWindows ? 'Explorer' : 'Finder'}`,
46
+ title: `Open in ${openIn}`,
46
47
  keys: ['alt+shift+o'],
47
48
  icon: "folder",
48
49
  async action () {
@@ -28,11 +28,12 @@ module.exports = class DefaultBoardPropertiesPlugin extends Plugin {
28
28
  const clearFilterURL = this.getFilterURL('');
29
29
  const getFilterURL = (filter) => this.getFilterURL(filter);
30
30
  const getFilterLink = (filter) => `[Filter By: ${filter}](${this.getFilterURL(filter)})`;
31
- // DOING Make board properties self documenting
31
+ // TODO Make board properties self documenting
32
32
  // <!--
33
33
  // #imdone-1.53.16
34
34
  // #feature
35
35
  // created:2025-02-14T16:54:22-05:00
36
+ // order:-185
36
37
  // -->
37
38
  // [Feature Description]
38
39
  // ## :ballot_box_with_check: Tasks
@@ -59,28 +60,43 @@ module.exports = class DefaultBoardPropertiesPlugin extends Plugin {
59
60
  }
60
61
 
61
62
  getFilterURL(filter) {
62
- return `imdone://${this.project.path}?filter=${filter}`
63
+ filter = filter.replace(/\(/g, '%28').replace(/\)/g, '%29');
64
+ return encodeURI(`imdone://${this.project.path}?filter=${filter}`)
63
65
  }
64
66
 
67
+ // READY Also include templates (md files) in .github and it's subdirs
68
+ // <!--
69
+ // order:0
70
+ // -->
65
71
  async getTemplates() {
66
- const { preparePathForWriting, readdir, readFile } = this.project.fileGateway
72
+ const { preparePathForWriting, readdir, readFile, stat } = this.project.fileGateway;
67
73
  const templateDir = path.join(this.project.configDir, 'templates');
74
+ const githubDir = path.join(this.project.path, '.github');
75
+
68
76
  preparePathForWriting(templateDir);
69
- const files = await readdir(templateDir);
70
- const readFilePromises = files.map((file) => {
71
- return new Promise(async (resolve) => {
72
- const data = await readFile(path.join(templateDir, file), 'utf-8');
73
- const templateName = file.replace('.md', '').replace(/[^a-zA-Z0-9]/g, '_');
74
- resolve({ [templateName]: data });
77
+
78
+ const readMarkdownFiles = async (dir, prefix = '') => {
79
+ const files = await readdir(dir);
80
+ const readFilePromises = files.map(async (file) => {
81
+ const filePath = path.join(dir, file);
82
+ const fileStat = await stat(filePath);
83
+ if (fileStat && fileStat.isDirectory()) {
84
+ return readMarkdownFiles(filePath, `${prefix}${file}_`);
85
+ } else if (file.endsWith('.md')) {
86
+ const data = await readFile(filePath, 'utf-8');
87
+ const templateName = `${prefix}${file.replace('.md', '').replace(/[^a-zA-Z0-9]/g, '_')}`;
88
+ return { [templateName]: data };
89
+ }
90
+ return {};
75
91
  });
76
- });
77
92
 
78
- const allTemplates = await Promise.all(readFilePromises);
93
+ const allTemplates = await Promise.all(readFilePromises);
94
+ return allTemplates.reduce((acc, template) => ({ ...acc, ...template }), {});
95
+ };
79
96
 
80
- const templates = allTemplates.reduce((acc, template) => {
81
- return { ...acc, ...template };
82
- }, {});
97
+ const templatesFromTemplateDir = await readMarkdownFiles(templateDir);
98
+ const templatesFromGithubDir = await readMarkdownFiles(githubDir, 'github_');
83
99
 
84
- return { simple: simpleTemplate, ...templates };
100
+ return { simple: simpleTemplate, ...templatesFromTemplateDir, ...templatesFromGithubDir };
85
101
  }
86
102
  }
@@ -216,7 +216,9 @@ module.exports = class PluginManager extends Emitter {
216
216
  }
217
217
 
218
218
  getPluginInstance(name) {
219
- return this.getPlugin(name).pluginInstance
219
+ const plugin = this.getPlugin(name)
220
+ if (!plugin) throw new Error(`Plugin ${name} not found`)
221
+ return plugin.pluginInstance
220
222
  }
221
223
 
222
224
  getPlugin(name) {
@@ -392,12 +394,12 @@ module.exports = class PluginManager extends Emitter {
392
394
  }
393
395
  }
394
396
 
395
- performBoardAction(action, task) {
397
+ async performBoardAction(action, task) {
396
398
  const plugin = this.getPluginInstance(action.plugin)
397
399
  try {
398
400
  return action.index
399
- ? plugin.getBoardActions()[action.index].action(task)
400
- : plugin.getBoardActions().find(a => a.title === action.title).action(task)
401
+ ? await plugin.getBoardActions()[action.index].action(task)
402
+ : await plugin.getBoardActions().find(a => a.title === action.title).action(task)
401
403
  } catch (e) {
402
404
  this.pluginError('getBoardActions', plugin, e)
403
405
  }
package/lib/project.js CHANGED
@@ -174,10 +174,9 @@ module.exports = class WorkerProject extends Project {
174
174
  console.timeEnd('getTags time')
175
175
 
176
176
  console.time('getLists time')
177
- const lists = this.filter ? this.getLists({
178
- tasks: this.getCards(this.filter, cards),
179
- populateFiltered: true,
180
- }) : allLists
177
+ const lists = this.filter
178
+ ? this.getRequestedLists(cards)
179
+ : allLists
181
180
  console.timeEnd('getLists time')
182
181
 
183
182
  console.timeEnd('toJSON time')
@@ -202,6 +201,20 @@ module.exports = class WorkerProject extends Project {
202
201
  }
203
202
  }
204
203
 
204
+ getRequestedLists(cards) {
205
+ return this.getLists({
206
+ tasks: this.getCards(this.filter, cards),
207
+ populateFiltered: true,
208
+ }).map(list => {
209
+ const configList = this.config.lists.find(l => l.name === list.name)
210
+ list.hidden = configList && configList.hidden
211
+ if (this.hideLists.includes(list.name)) {
212
+ list.hidden = true
213
+ }
214
+ return list
215
+ })
216
+ }
217
+
205
218
  emit() {} // This is implemented in imdone UI worker
206
219
 
207
220
  emitUpdate() {
@@ -352,7 +365,9 @@ module.exports = class WorkerProject extends Project {
352
365
  filterCards(cards, filter) {
353
366
  const {
354
367
  result,
368
+ hideLists
355
369
  } = Repository.filterCards(cards, filter)
370
+ this.hideLists = hideLists
356
371
  return result
357
372
  }
358
373
 
@@ -400,6 +415,7 @@ module.exports = class WorkerProject extends Project {
400
415
 
401
416
  set filter(filter) {
402
417
  this.innerFilter = filter
418
+ this.hideLists = []
403
419
  }
404
420
 
405
421
  async addMetadata(task, key, value) {
@@ -457,6 +473,10 @@ module.exports = class WorkerProject extends Project {
457
473
  })
458
474
  }
459
475
 
476
+ getFile(filePath) {
477
+ return this.repo.getFile(filePath)
478
+ }
479
+
460
480
  getFileForTask(task) {
461
481
  return this.repo.getFileForTask(task)
462
482
  }
@@ -503,6 +523,12 @@ module.exports = class WorkerProject extends Project {
503
523
  this.emit('project.openUrl', url)
504
524
  }
505
525
 
526
+ // TODO For methods that emit, they should still do the thing without imdone UI
527
+ // <!--
528
+ // #urgent
529
+ // #important
530
+ // order:-195
531
+ // -->
506
532
  async openPath(path) {
507
533
  this.emit('project.openPath', path)
508
534
  }
@@ -760,6 +786,11 @@ module.exports = class WorkerProject extends Project {
760
786
  if (action.plugin) return this.pluginManager.performCardAction(action, task)
761
787
 
762
788
  const actionFunction = task.getCardActions()[action.index].action
789
+ // TODO These card actions should be removed
790
+ // #imdone-1.54.0
791
+ // <!--
792
+ // order:-175
793
+ // -->
763
794
  const actions = {
764
795
  filter: (filter) => {
765
796
  this.setFilter(filter)
package/lib/repository.js CHANGED
@@ -33,7 +33,9 @@ var _isFunction = require('lodash.isfunction'),
33
33
  Task = require('./task'),
34
34
  newCard = require('./card'),
35
35
  { replaceDateLanguage } = require('./adapters/parsers/DateLanguageParser'),
36
- { getRawTask, isNumber } = require('./adapters/parsers/task/CardContentParser')
36
+ { getRawTask, isNumber, LIST_NAME_PATTERN } = require('./adapters/parsers/task/CardContentParser'),
37
+ XRegExp = require('xregexp')
38
+
37
39
 
38
40
 
39
41
  const appContext = require('./context/ApplicationContext')
@@ -503,6 +505,7 @@ Repository.prototype.removeFile = function (file) {
503
505
  * @return CallExpression
504
506
  */
505
507
  Repository.prototype.getFile = function (path) {
508
+ path = this.getRelativePath(path)
506
509
  return this.files.find((file) => file.path === path)
507
510
  }
508
511
 
@@ -1627,6 +1630,25 @@ Repository.regexQuery = function (tasks, queryString) {
1627
1630
  };
1628
1631
 
1629
1632
 
1633
+ Repository.parseHideListsFromQueryString = function (queryString) {
1634
+ const hideLists = []
1635
+ const regex = `hide:(\\s*((${LIST_NAME_PATTERN},?\\s?)+))?`
1636
+ queryString = queryString.replaceAll(
1637
+ // hide lists example `hide: list1, list2`
1638
+ new XRegExp(regex, 'g'),
1639
+ (match, allLists, lists) => {
1640
+ lists && lists.split(',').forEach((list) => {
1641
+ hideLists.push(list.trim())
1642
+ })
1643
+ return ''
1644
+ }
1645
+ ).trim()
1646
+ return {
1647
+ hideLists,
1648
+ queryString,
1649
+ }
1650
+ }
1651
+
1630
1652
  Repository.parseSortFromQueryString = function (queryString) {
1631
1653
  const sort = []
1632
1654
  queryString = queryString.replace(
@@ -1663,7 +1685,10 @@ Repository.sortByQuery = function (tasks, queryString = '') {
1663
1685
  Repository.filterCards = function (tasks, _queryString = '') {
1664
1686
  let query
1665
1687
  _queryString = replaceDateLanguage(_queryString)
1666
- let { sort, queryString } = Repository.parseSortFromQueryString(_queryString)
1688
+ let { sort, queryString } = Repository.parseSortFromQueryString(_queryString);
1689
+ const hideListsQuery = Repository.parseHideListsFromQueryString(queryString);
1690
+ queryString = hideListsQuery.queryString;
1691
+ const hideLists = hideListsQuery.hideLists;
1667
1692
  try {
1668
1693
  query = monquery(queryString)
1669
1694
  } catch (e) {
@@ -1679,7 +1704,8 @@ Repository.filterCards = function (tasks, _queryString = '') {
1679
1704
  return {
1680
1705
  result,
1681
1706
  query,
1682
- sort
1707
+ sort,
1708
+ hideLists
1683
1709
  }
1684
1710
  }
1685
1711
 
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "imdone-core",
3
- "version": "1.54.0",
3
+ "version": "1.54.2",
4
4
  "description": "imdone-core",
5
5
  "main": "index.js",
6
+ "bin": {
7
+ "imdone-cli": "./cli.js"
8
+ },
6
9
  "scripts": {
7
10
  "build": "doctoc --github --notitle README.md && npm test",
8
11
  "coverage": "nyc --reporter=lcov --reporter=text --reporter=html mocha --exit",
@@ -48,7 +51,7 @@
48
51
  "cheerio": "^1.0.0-rc.10",
49
52
  "chrono-node": "^2.6.4",
50
53
  "command-exists": "^1.2.9",
51
- "commander": "^11.0.0",
54
+ "commander": "^11.1.0",
52
55
  "debug": ">=4.3.4",
53
56
  "download": "^8.0.0",
54
57
  "eol": "^0.9.1",