imdone-core 1.53.6 → 1.53.8

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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  [![npm version](https://badge.fury.io/js/imdone-core.svg)](https://badge.fury.io/js/imdone-core)
2
2
  [![Downloads](https://img.shields.io/npm/dm/imdone-core.svg)](https://npmjs.org/package/imdone-core)
3
- [![Build Status](https://github.com/imdone/imdone-core/actions/workflows/ci.yml/badge.svg)
3
+ ![Build Status](https://github.com/imdone/imdone-core/actions/workflows/ci.yml/badge.svg)
4
4
 
5
5
  Imdone is text based kanban processor with a simple syntax that uses [comment tags like TODO and FIXME](https://en.wikipedia.org/wiki/Comment_%28computer_programming%29#Tags) and [todo.txt format](https://github.com/todotxt/todo.txt#todotxt-format). This model allows the user to create and modify tasks using the keyboard and automatically establishes a link between their tasks and work. Get [imdone](https://imdone.io) or use the cli to see your projects board and this library in action.
6
6
 
@@ -4,7 +4,7 @@ let fs = require('fs')
4
4
  const sanitize = require('sanitize-filename')
5
5
 
6
6
 
7
- module.exports = {
7
+ module.exports = gateway = {
8
8
  init(fileSystem = fs) {
9
9
  fs = fileSystem
10
10
  },
@@ -94,14 +94,14 @@ module.exports = {
94
94
  },
95
95
 
96
96
  preparePathForWriting(path) {
97
- let stat = this.statSync(path)
97
+ let stat = gateway.statSync(path)
98
98
  if (!stat) {
99
99
  let { dir, ext } = _path.parse(path)
100
100
  if (!ext) {
101
101
  dir = path
102
102
  }
103
- this.mkdirpSync(dir)
104
- stat = this.statSync(path)
103
+ gateway.mkdirpSync(dir)
104
+ stat = gateway.statSync(path)
105
105
  }
106
106
  return {
107
107
  isFile: stat && stat.isFile(),
@@ -0,0 +1,29 @@
1
+ module.exports = {
2
+ getFunctionSignature
3
+ };
4
+
5
+ function getFunctionSignature(fn) {
6
+ if (typeof fn !== "function") {
7
+ throw new TypeError("Expected a function");
8
+ }
9
+
10
+ const fnString = fn.toString().trim();
11
+ let name = fn.name || "anonymous";
12
+ let params = "";
13
+
14
+ // Match function parameters
15
+ const paramMatch = fnString.match(/\(([^)]*)\)/);
16
+ if (paramMatch) {
17
+ params = paramMatch[1].trim();
18
+ } else {
19
+ // Handle arrow functions without parentheses
20
+ const arrowMatch = fnString.match(/^(?:\S+\s+)?(\w+)?\s*=?\s*(?:async\s*)?\(?([^)=]*)\)?\s*=>/);
21
+ if (arrowMatch) {
22
+ name = name || arrowMatch[1] || "anonymous";
23
+ params = arrowMatch[2].trim();
24
+ }
25
+ }
26
+
27
+ return `${name}(${params})`;
28
+ }
29
+
@@ -225,9 +225,27 @@ function getTextFileTaskContent ({
225
225
  const rawTaskContentLines = []
226
226
  const taskContentLines = []
227
227
 
228
+ let inCodeBlock = false
229
+ let inCodeSpan = false
230
+
228
231
  for (let i = 1; i < lines.length; i++) {
229
232
  let line = lines[i]
230
- if (hasTaskInText(config, line)) break
233
+
234
+ // Check for markdown code blocks
235
+ if (isMarkDownFile && line.trim().startsWith('```')) {
236
+ inCodeBlock = !inCodeBlock
237
+ }
238
+
239
+ // Check for markdown code spans
240
+ if (isMarkDownFile && line.includes('`')) {
241
+ const codeSpanMatches = line.match(/`+/g)
242
+ if (codeSpanMatches && codeSpanMatches.length % 2 !== 0) {
243
+ inCodeSpan = !inCodeSpan
244
+ }
245
+ }
246
+
247
+ // Only break if the task is not in a markdown code block or code span
248
+ if (!inCodeBlock && !inCodeSpan && hasTaskInText(config, line)) break
231
249
  if (isCheckBoxTask(config, line, beforeText)) break
232
250
  if (isTaskInList && isLineOutsideMarkdownList(line, beforeText)) break
233
251
  if (isCustomCardTerminator(config, line)) break
package/lib/card.js CHANGED
@@ -8,6 +8,7 @@ const _isEmpty = require('lodash.isempty')
8
8
  const _get = require('lodash.get')
9
9
  const eol = require('eol')
10
10
  const allEmoji = require('markdown-it-emoji/lib/data/full.json')
11
+ const { getFunctionSignature } = require('./adapters/parsers/function-parser')
11
12
 
12
13
  const { removeMD } = require('./adapters/markdown')
13
14
 
@@ -190,7 +191,7 @@ module.exports = function newCard(task, _project, dontParse) {
190
191
  const keys = Object.keys(data).map((key) => {
191
192
  const value = data[key]
192
193
  if (_isFunction(value)) {
193
- key = key + '()'
194
+ key = getFunctionSignature(value)
194
195
  }
195
196
  return key
196
197
  })
@@ -237,7 +238,8 @@ module.exports = function newCard(task, _project, dontParse) {
237
238
  }
238
239
 
239
240
  format(content, mustache) {
240
- const data = this.data
241
+ const projectData = project ? { ...project.data } : {}
242
+ const data = { ...this.data, ...projectData }
241
243
 
242
244
  const opts = { interpolate: /(?<![`\\])\${([\s\S]+?)}/g }
243
245
 
package/lib/config.js CHANGED
@@ -71,8 +71,10 @@ class Config {
71
71
  }
72
72
 
73
73
  getDoingList() {
74
- const lists = _cloneDeep(this.lists).reverse().filter((list) => !list.filter)
75
- const defaultValue = lists && lists.length ? lists[1].name : undefined
74
+ const lists = _cloneDeep(this.lists).filter((list) => !list.filter)
75
+ .reverse().map(list => list.name)
76
+ const defaultValue = lists ? lists.find(list => /doing|progress/i.test(list.name)) :
77
+ lists[1].name || '';
76
78
  return _get(this, 'settings.cards.doingList', defaultValue)
77
79
  }
78
80
 
package/lib/constants.js CHANGED
@@ -14,6 +14,7 @@ flow-typed
14
14
  .hs
15
15
  .npmignore
16
16
  .obsidian
17
+ .imdone/templates
17
18
  `
18
19
  var DEFAULT_IGNORE_EXTS = `
19
20
  *.jpg
package/lib/file.js CHANGED
@@ -381,7 +381,7 @@ File.prototype.extractHashStyleTasks = function (config, pos, content) {
381
381
  if (!config.listExists(list)) continue
382
382
 
383
383
  const posInContent = result.index + pos
384
- if (this.isWithinCodeSpanOrBlock(posInContent, content)) continue
384
+ if (this.isWithinCodeSpanOrBlock(result.index, content)) continue
385
385
 
386
386
  const line = this.getLineNumber(posInContent)
387
387
  if (this.hasTaskAtLine(line)) continue
@@ -416,7 +416,7 @@ File.prototype.extractLinkStyleTasks = function (config, pos, content) {
416
416
  let [match, before, rawTask, text, list, colon, order] = result
417
417
  const beforeLength = before ? before.length : 0
418
418
  const posInContent = result.index + pos + beforeLength
419
- if (this.isWithinCodeSpanOrBlock(posInContent, content)) continue
419
+ if (this.isWithinCodeSpanOrBlock(result.index, content)) continue
420
420
  var line = this.getLineNumber(posInContent)
421
421
  if (self.hasTaskAtLine(line)) continue
422
422
  if (!config.listExists(list)) continue
@@ -1192,13 +1192,13 @@ File.prototype.getCodeCommentRegex = function () {
1192
1192
  // #BACKLOG:-460 Allow languages to have multiple block comment styles, like html gh:13 id:5
1193
1193
  var lang = this.getLang()
1194
1194
  var symbol = lang.symbol
1195
- var reString = escapeRegExp(symbol) + '[^{].*$'
1195
+ var reString = '(?<!["\'`])' + escapeRegExp(symbol) + '[^{].*$'
1196
1196
 
1197
1197
  if (lang.block) {
1198
1198
  var start = escapeRegExp(lang.block.start)
1199
1199
  var end = escapeRegExp(lang.block.end)
1200
1200
  //
1201
- reString = reString + '|' + start + '(.|[\\r\\n])*?' + end
1201
+ reString = reString + '|(?<!["\'`])' + start + '(.|[\\r\\n])*?' + end
1202
1202
  }
1203
1203
 
1204
1204
  return new RegExp(reString, 'gmi')
package/lib/list.js CHANGED
@@ -13,7 +13,7 @@ const uniqid = require('uniqid')
13
13
  * @param {} hidden
14
14
  * @return
15
15
  */
16
- function List({name, hidden, ignore, filter, id = null}) {
16
+ function List({ name, hidden, ignore, filter, id }) {
17
17
  this.name = name;
18
18
  this.hidden = hidden || false
19
19
  this.ignore = ignore || false
@@ -51,7 +51,8 @@ module.exports = function (repo, defaultSettings = defaultSettingsObject) {
51
51
  }
52
52
 
53
53
  if (config.settings.cards.defaultList === undefined) {
54
- config.settings.cards.defaultList = lists ? lists[0] : ''
54
+ config.settings.cards.defaultList = lists ? lists.find(list => /todo/i.test(list)) :
55
+ lists[0] || '';
55
56
  }
56
57
 
57
58
  // archiveCompleted
@@ -107,11 +108,13 @@ module.exports = function (repo, defaultSettings = defaultSettingsObject) {
107
108
  }
108
109
  // Set up default doneList
109
110
  if (config.settings.cards.doneList === undefined) {
110
- config.settings.cards.doneList = lists ? lists[lists.length - 1] : ''
111
+ config.settings.cards.doneList = lists ? lists.find(list => /done/i.test(list)) :
112
+ lists[lists.length - 1] || '';
111
113
  }
112
114
  // Set up default doingList
113
115
  if (config.settings.cards.doingList === undefined) {
114
- config.settings.cards.doingList = lists ? lists[lists.length - 1] : ''
116
+ config.settings.cards.doingList = lists ? lists.find(list => /doing|progress/i.test(list)) :
117
+ lists[lists.length - 2] || '';
115
118
  }
116
119
  // Add name
117
120
  if (config.settings.name === undefined) {
@@ -0,0 +1,54 @@
1
+ const Plugin = require('imdone-api')
2
+ const {getIsoDateWithOffset} = require('../adapters/date-time')
3
+ const path = require('path')
4
+ const simpleTemplate = '\n<!--\ncreated:${timestamp}\n-->\n'
5
+
6
+ module.exports = class DefaultBoardPropertiesPlugin extends Plugin {
7
+ constructor(project) {
8
+ super(project)
9
+ this.templates = {}
10
+ }
11
+
12
+ async onBeforeBoardUpdate() {
13
+ this.templates = await this.getTemplates()
14
+ }
15
+
16
+ getBoardProperties() {
17
+ const self = this;
18
+
19
+ return {
20
+ date: `${getIsoDateWithOffset(new Date()).substring(0, 10)}`,
21
+ timestamp: getIsoDateWithOffset(new Date()),
22
+ clearFilterURL: self.getFilterURL(''),
23
+ templates: this.templates,
24
+ getFilterURL: (filter) => self.getFilterURL(filter),
25
+ getFilterLink: (filter) => `[Filter By: ${filter}](${self.getFilterURL(filter)})`
26
+ }
27
+ }
28
+
29
+ getFilterURL(filter) {
30
+ return `imdone://${this.project.path}?filter=${filter}`
31
+ }
32
+
33
+ async getTemplates() {
34
+ const { preparePathForWriting, readdir, readFile } = this.project.fileGateway
35
+ const templateDir = path.join(this.project.configDir, 'templates');
36
+ preparePathForWriting(templateDir);
37
+ const files = await readdir(templateDir);
38
+ const readFilePromises = files.map((file) => {
39
+ return new Promise(async (resolve) => {
40
+ const data = await readFile(path.join(templateDir, file), 'utf-8');
41
+ const templateName = file.replace('.md', '').replace(/[^a-zA-Z0-9]/g, '_');
42
+ resolve({ [templateName]: data });
43
+ });
44
+ });
45
+
46
+ const allTemplates = await Promise.all(readFilePromises);
47
+
48
+ const templates = allTemplates.reduce((acc, template) => {
49
+ return { ...acc, ...template };
50
+ }, {});
51
+
52
+ return { simple: simpleTemplate, ...templates };
53
+ }
54
+ }
@@ -14,7 +14,7 @@ module.exports = class EpicPlugin extends Plugin {
14
14
  this.epics = {}
15
15
  }
16
16
 
17
- onBeforeBoardUpdate() {
17
+ async onBeforeBoardUpdate() {
18
18
  this.epics = {}
19
19
  }
20
20
 
@@ -25,10 +25,21 @@ module.exports = class ExtensionPlugin extends Plugin {
25
25
  'properties',
26
26
  'card'
27
27
  )
28
+ this.boardPropertiesFunction = this.loadExtensionModule(
29
+ () => {
30
+ return {}
31
+ },
32
+ 'properties',
33
+ 'board'
34
+ )
28
35
  }
29
36
 
30
37
  getCardProperties(task) {
31
- return this.cardPropertiesFunction(task, this.project)
38
+ return this.cardPropertiesFunction(task)
39
+ }
40
+
41
+ getBoardProperties() {
42
+ return this.boardPropertiesFunction()
32
43
  }
33
44
 
34
45
  getCardActions(task) {
@@ -13,7 +13,7 @@ module.exports = class PluginManager extends Emitter {
13
13
  this.project = project
14
14
  this.defaultPlugins = [
15
15
  './persist-tags-plugin',
16
- './default-card-properties-plugin',
16
+ './default-board-properties-plugin',
17
17
  './default-board-actions-plugin',
18
18
  './archive-plugin',
19
19
  './epic-plugin',
@@ -229,7 +229,7 @@ module.exports = class PluginManager extends Emitter {
229
229
  }
230
230
 
231
231
  pluginError(method, pluginInstance, error) {
232
- console.error(
232
+ console.warn(
233
233
  `Plugin: ${this.getPluginName(
234
234
  pluginInstance
235
235
  )} threw an error on ${method}: `,
@@ -239,7 +239,7 @@ module.exports = class PluginManager extends Emitter {
239
239
 
240
240
  async onBoardUpdate(lists) {
241
241
  if (!lists || lists.length == 0) return
242
- this.eachPluginAsync(async ({ pluginInstance }) => {
242
+ await this.eachPluginAsync(async ({ pluginInstance }) => {
243
243
  // const timeLabel = `${this.getPluginName(pluginInstance)} onBoardUpdate time`
244
244
  // console.time(timeLabel)
245
245
  try {
@@ -252,10 +252,10 @@ module.exports = class PluginManager extends Emitter {
252
252
  return lists
253
253
  }
254
254
 
255
- onBeforeBoardUpdate() {
256
- this.eachPlugin(({ pluginInstance }) => {
255
+ async onBeforeBoardUpdate() {
256
+ await this.eachPluginAsync(async ({ pluginInstance }) => {
257
257
  try {
258
- pluginInstance.onBeforeBoardUpdate()
258
+ await pluginInstance.onBeforeBoardUpdate()
259
259
  } catch (e) {
260
260
  this.pluginError('onBeforeBoardUpdate', pluginInstance, e)
261
261
  }
@@ -320,6 +320,19 @@ module.exports = class PluginManager extends Emitter {
320
320
  return cardProps
321
321
  }
322
322
 
323
+ getBoardProperties() {
324
+ let boardProps = {}
325
+ this.eachPlugin(({ pluginInstance }) => {
326
+ try {
327
+ const pluginProps = pluginInstance.getBoardProperties ? pluginInstance.getBoardProperties() : {}
328
+ boardProps = { ...boardProps, ...pluginProps }
329
+ } catch (e) {
330
+ this.pluginError('getBoardProperties', pluginInstance, e)
331
+ }
332
+ })
333
+ return boardProps
334
+ }
335
+
323
336
  getCardActions(task) {
324
337
  let cardLinks = []
325
338
  this.eachPlugin(({ pluginInstance }) => {
package/lib/project.js CHANGED
@@ -3,7 +3,7 @@ const PluginManager = require('./plugins/plugin-manager')
3
3
  const Repository = require('./repository')
4
4
  const _cloneDeep = require('lodash.clonedeep')
5
5
  const newCard = require('./card')
6
- const { JOURNAL_TYPE } = require('./constants')
6
+ const { JOURNAL_TYPE, CONFIG_DIR } = require('./constants')
7
7
  const _path = require('path')
8
8
  const moment = require('moment')
9
9
  const { getIsoDateWithOffset } = require('./adapters/date-time')
@@ -11,11 +11,13 @@ const fileGateway = require('./adapters/file-gateway')
11
11
  const matter = require('gray-matter')
12
12
  const _isObject = require('lodash.isobject')
13
13
  const _isString = require('lodash.isstring')
14
+ const _isFunction = require('lodash.isfunction')
14
15
  const exec = require('child_process').exec
15
16
  const fastSort = require('fast-sort/dist/sort.js')
16
17
  const Task = require('./task')
17
18
  const eol = require('eol')
18
19
  const { renderMarkdown, extractWikilinkTopics } = require('./adapters/markdown')
20
+ const { getFunctionSignature } = require('./adapters/parsers/function-parser')
19
21
 
20
22
  function calculateTotals(lists) {
21
23
  const totals = {}
@@ -97,6 +99,28 @@ module.exports = class WorkerProject extends Project {
97
99
  return { path: this.path }
98
100
  }
99
101
 
102
+ getListsForImdoneJSON(totals) {
103
+ return this.allLists.map((list) => {
104
+ list = { ...list }
105
+ list.tasks = list.tasks.map((card, index) => {
106
+ card.interpretedContent = ''
107
+ card.totals = totals
108
+ card.index = index
109
+ return card
110
+ })
111
+ return list
112
+ })
113
+ }
114
+
115
+ getInitializedCards(allLists, totals) {
116
+ return allLists
117
+ .map((list) => list.tasks)
118
+ .flat()
119
+ .map(
120
+ (card) => card.init(totals)
121
+ )
122
+ }
123
+
100
124
  async toImdoneJSON() {
101
125
  this.pluginManager.initDevMode()
102
126
  console.time('toJSON time')
@@ -106,26 +130,17 @@ module.exports = class WorkerProject extends Project {
106
130
  populateFiltered: true,
107
131
  })
108
132
  )
109
- let allLists = this.allLists
110
- allLists = allLists.map((list) => {
111
- list.tasks = list.tasks.map((card, index) => {
112
- card.interpretedContent = ''
113
- card.totals = totals
114
- card.index = index
115
- return card
116
- })
117
- return list
118
- })
133
+ const allLists = this.getListsForImdoneJSON(totals)
119
134
 
120
135
  console.time('plugin onBeforeBoardUpdate')
121
- this.pluginManager.onBeforeBoardUpdate()
136
+ await this.pluginManager.onBeforeBoardUpdate()
122
137
  console.timeEnd('plugin onBeforeBoardUpdate')
123
138
 
139
+ const data = this.data = this.pluginManager.getBoardProperties()
140
+ const dataKeys = this.dataKays = this.getDataKeys(data)
141
+
124
142
  console.time('card init')
125
- const cards = allLists
126
- .map((list) => list.tasks)
127
- .flat()
128
- .map((card) => card.init(totals))
143
+ const cards = this.getInitializedCards(allLists, totals)
129
144
  console.timeEnd('card init')
130
145
 
131
146
  console.time('plugin onBoardUpdate')
@@ -140,6 +155,7 @@ module.exports = class WorkerProject extends Project {
140
155
  tasks: this.getCards(this.filter, cards),
141
156
  populateFiltered: true,
142
157
  })
158
+
143
159
  console.timeEnd('toJSON time')
144
160
  return {
145
161
  path: this.path,
@@ -156,6 +172,8 @@ module.exports = class WorkerProject extends Project {
156
172
  defaultFilter: this.defaultFilter,
157
173
  actions: this.boardActions,
158
174
  plugins: this.pluginManager.getPlugins(),
175
+ data,
176
+ dataKeys,
159
177
  queryProps: this.getQueryProps(cards),
160
178
  }
161
179
  }
@@ -171,6 +189,10 @@ module.exports = class WorkerProject extends Project {
171
189
  if (this.pluginManager) this.pluginManager.destroyPlugins()
172
190
  }
173
191
 
192
+ get configDir() {
193
+ return _path.join(this.path, CONFIG_DIR)
194
+ }
195
+
174
196
  get allTopics() {
175
197
  return [...this.repo.allTopics]
176
198
  }
@@ -209,6 +231,23 @@ module.exports = class WorkerProject extends Project {
209
231
  return this._updatedAt
210
232
  }
211
233
 
234
+ getDataKeys(data) {
235
+ const keys = Object.keys(data).map((key) => {
236
+ const value = data[key]
237
+ if (_isFunction(value)) {
238
+ key = getFunctionSignature(value)
239
+ }
240
+ return key
241
+ })
242
+ keys.forEach((key) => {
243
+ const value = data[key]
244
+ if (_isObject(value) && !Array.isArray(value)) {
245
+ keys.push(...Object.keys(value).map((k) => `${key}.${k}`))
246
+ }
247
+ })
248
+ return keys
249
+ }
250
+
212
251
  removeList(list) {
213
252
  this.repo.removeList(list)
214
253
  }
@@ -218,18 +257,18 @@ module.exports = class WorkerProject extends Project {
218
257
  return Repository.getTasksByList(this.repo, tasks, true, populateFiltered)
219
258
  }
220
259
 
221
- getTaskQueryProps(task) {
260
+ getTaskQueryProps(props) {
222
261
  const queryProps = []
223
- if (task) {
262
+ if (props) {
224
263
  // add all properties of the task to the queryProps array
225
- Object.keys(task).forEach((key) => {
264
+ Object.keys(props).forEach((key) => {
226
265
  if (key === 'data') return
227
266
  if (queryProps.includes(key)) return
228
267
 
229
268
  queryProps.push(key)
230
269
 
231
- if (_isObject(task[key])) {
232
- Object.keys(task[key]).forEach((subKey) => {
270
+ if (_isObject(props[key])) {
271
+ Object.keys(props[key]).forEach((subKey) => {
233
272
  if (!queryProps.includes(`${key}.${subKey}`))
234
273
  queryProps.push(`${key}.${subKey}`)
235
274
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imdone-core",
3
- "version": "1.53.6",
3
+ "version": "1.53.8",
4
4
  "description": "imdone-core",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -57,7 +57,7 @@
57
57
  "find-up": "^5.0.0",
58
58
  "gray-matter": "^4.0.2",
59
59
  "ignore": "^5.2.0",
60
- "imdone-api": "^1.52.2",
60
+ "imdone-api": "^1.53.0",
61
61
  "isbinaryfile": "^3.0.2",
62
62
  "js-yaml": "^3.14.1",
63
63
  "json-fns": "^1.0.0",
@@ -1,25 +0,0 @@
1
- const Plugin = require('imdone-api')
2
- const {getIsoDateWithOffset} = require('../adapters/date-time')
3
-
4
- module.exports = class DefaultCardPropertiesPlugin extends Plugin {
5
- constructor(project) {
6
- super(project)
7
- }
8
-
9
- getCardProperties() {
10
- const self = this;
11
-
12
- return {
13
- date: `${getIsoDateWithOffset(new Date()).substring(0, 10)}`,
14
- timestamp: getIsoDateWithOffset(new Date()),
15
- clearFilterURL: self.getFilterURL(''),
16
- getFilterURL: (filter) => self.getFilterURL(filter),
17
- getFilterLink: (filter) => `[Filter By: ${filter}](${self.getFilterURL(filter)})`
18
- }
19
- }
20
-
21
- getFilterURL(filter) {
22
- return `imdone://${this.project.path}?filter=${filter}`
23
- }
24
-
25
- }