imdone-core 1.38.0 → 1.40.1

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,256 @@
1
+ const eol = require('eol')
2
+ const Task = require('../../../task')
3
+ const XRegExp = require('xregexp')
4
+
5
+ const FILE_TYPES = {
6
+ MARKDOWN: 'markdown',
7
+ CODE: 'code',
8
+ }
9
+
10
+ const START_TAG = '<card>'
11
+ const END_TAG = '</card>'
12
+ const CODE_STYLE_PATTERN = '([A-Z]+[A-Z-_]+?)((:)(-?[\\d.]+(?:e-?\\d+)?)?)?[ \\t]+(.+)$'
13
+ const LIST_NAME_PATTERN = '[\\p{L}0-9-_]{2,}' // [a-zA-Z-_]+?
14
+ const HASH_STYLE_REGEX = new XRegExp(
15
+ `#(${LIST_NAME_PATTERN})(:(-?[\\d.]+(?:e-?\\d+)?)?)?[ \\t]+(.+)$`,
16
+ 'gm'
17
+ )
18
+ const HASH_STYLE_META_ORDER_REGEX = new XRegExp(
19
+ `#(${LIST_NAME_PATTERN})[ \\t]+(.+)$`, 'gm'
20
+ )
21
+ const LINK_STYLE_REGEX = new XRegExp(
22
+ `(\\[.*\\]\\s?)?(\\[(.+)\\]\\(#(${LIST_NAME_PATTERN})(:)(-?[\\d.]+(?:e-?\\d+)?)?\\))`,
23
+ 'gm'
24
+ )
25
+
26
+ function getContentLines(content) {
27
+ let foundContent = false
28
+ return eol.split(content).filter((line) => {
29
+ const hasContent = line.trim()
30
+ if (!foundContent && hasContent) foundContent = true
31
+ return foundContent || hasContent
32
+ })
33
+ }
34
+
35
+ function isCheckBoxTask (config, line, beforeText) {
36
+ if (!config.isAddCheckBoxTasks()) return
37
+
38
+ const beforeTextCheckData = Task.getCheckedData(beforeText)
39
+ if (!beforeTextCheckData) return
40
+
41
+ const lineCheckData = Task.getCheckedData(line)
42
+ if (!lineCheckData) return
43
+
44
+ return lineCheckData.pad <= beforeTextCheckData.pad
45
+ }
46
+
47
+ function hasTaskInText(config, text, isCodeFile) {
48
+ return (
49
+ (isCodeFile && hasCodeStyleTask(config, text)) ||
50
+ ((new XRegExp(HASH_STYLE_REGEX).test(text) ||
51
+ new XRegExp(HASH_STYLE_META_ORDER_REGEX).test(text) ||
52
+ new XRegExp(LINK_STYLE_REGEX).test(text)) &&
53
+ config.lists.find((list) => text.includes(`#${list.name}`)))
54
+ )
55
+ }
56
+
57
+ function hasCodeStyleTask (config, text) {
58
+ let result = new RegExp(CODE_STYLE_PATTERN).exec(text)
59
+ if (!result) return false
60
+ return config.includeList(result[1])
61
+ }
62
+
63
+ function getCardTagsTaskContent ({
64
+ content,
65
+ fileType
66
+ }) {
67
+ const isCodeFile = fileType === FILE_TYPES.CODE
68
+ if (isCodeFile) return
69
+
70
+ const isMarkDownFile = fileType === FILE_TYPES.MARKDOWN
71
+ let lines = getContentLines(content)
72
+ const hasCardStartTag =
73
+ isMarkDownFile && lines.length > 1 && lines[1].trim() === START_TAG
74
+ if (!hasCardStartTag) return
75
+
76
+ let openBlockCommentTokens = 0
77
+ let closeBlockCommentTokens = 0
78
+ let hasCardEndTag = false
79
+ const rawTaskContentLines = []
80
+ const taskContentLines = []
81
+ for (let i = 1; i < lines.length; i++) {
82
+ let line = lines[i]
83
+ // Check for matching block comments in markdown files
84
+ if (isMarkDownFile && line.trim().indexOf('<!--') > -1)
85
+ openBlockCommentTokens++
86
+ if (isMarkDownFile && line.trim().indexOf('-->') > -1)
87
+ closeBlockCommentTokens++
88
+
89
+ rawTaskContentLines.push(line)
90
+
91
+ if (line.trim() === START_TAG) continue
92
+ if (line.trim() === END_TAG) {
93
+ hasCardEndTag = true
94
+ break
95
+ }
96
+ taskContentLines.push(line)
97
+ }
98
+
99
+ // Remove extra block comment tokens
100
+ if (
101
+ isMarkDownFile &&
102
+ rawTaskContentLines.length &&
103
+ rawTaskContentLines[rawTaskContentLines.length - 1].trim() === '-->' &&
104
+ openBlockCommentTokens !== closeBlockCommentTokens
105
+ )
106
+ rawTaskContentLines.pop()
107
+
108
+ const isWrappedWithCardTag = hasCardStartTag && hasCardEndTag
109
+
110
+ return {
111
+ rawTaskContentLines,
112
+ taskContentLines,
113
+ isWrappedWithCardTag,
114
+ }
115
+ }
116
+
117
+ function getCodeFileTaskContent ({
118
+ config,
119
+ content,
120
+ inBlockComment,
121
+ fileType,
122
+ lang
123
+ }) {
124
+ const isCodeFile = fileType === FILE_TYPES.CODE
125
+ if (!isCodeFile) return
126
+
127
+ const rawTaskContentLines = []
128
+ let lines = getContentLines(content)
129
+ for (let i = 1; i < lines.length; i++) {
130
+ let line = lines[i]
131
+ // Another task ends a task
132
+ if (hasTaskInText(config, line, isCodeFile)) break
133
+ if (
134
+ !inBlockComment &&
135
+ (line.indexOf(lang.symbol) < 0 ||
136
+ line.trim().indexOf(lang.symbol) > 0 ||
137
+ line.trim() === lang.symbol)
138
+ )
139
+ break
140
+ if (lang.block && line.trim() === lang.block.end)
141
+ break
142
+ if (lang.block && line.trim() === lang.block.ignore)
143
+ break
144
+ if (line && lang && lang.block) line = line.replace(lang.block.end, '')
145
+
146
+ rawTaskContentLines.push(line)
147
+ }
148
+ return {
149
+ rawTaskContentLines,
150
+ taskContentLines: rawTaskContentLines,
151
+ }
152
+ }
153
+
154
+ function getTextFileTaskContent ({
155
+ config,
156
+ content,
157
+ beforeText,
158
+ fileType,
159
+ }) {
160
+ const isCodeFile = fileType === FILE_TYPES.CODE
161
+ if (isCodeFile) return
162
+
163
+ let lines = getContentLines(content)
164
+ const hasCardStartTag = lines.length > 1 && lines[1].trim() === START_TAG
165
+ if (hasCardStartTag) return
166
+
167
+ const isMarkDownFile = fileType === FILE_TYPES.MARKDOWN
168
+ let openBlockCommentTokens = 0
169
+ let closeBlockCommentTokens = 0
170
+ let lastLineBlank = false
171
+ let endsWithDoubleBlank = false
172
+ let trailingBlankLines = 0
173
+ const rawTaskContentLines = []
174
+
175
+ for (let i = 1; i < lines.length; i++) {
176
+ let line = lines[i]
177
+ // Check for end of task
178
+ // Another task ends a task
179
+ if (hasTaskInText(config, line, isCodeFile)) break
180
+ if (isCheckBoxTask(config, line, beforeText)) break
181
+
182
+ // TODO Break this out into a function with an accurate name
183
+ // Check if task is a list item in markdown and end it if content is not part of a nested list
184
+ // #urgent
185
+ // <!-- order:-65 -->
186
+ if (
187
+ isMarkDownFile &&
188
+ beforeText &&
189
+ /^\s*(\*|-|\d+\.|[a-zA-Z]\.)\s$/.test(beforeText) &&
190
+ line.search(/\S/) <= beforeText.search(/\S/)
191
+ ) break
192
+
193
+ rawTaskContentLines.push(line)
194
+
195
+ // Check for matching block comments in markdown files
196
+ if (isMarkDownFile && line.trim().indexOf('<!--') > -1)
197
+ openBlockCommentTokens++
198
+ if (isMarkDownFile && line.trim().indexOf('-->') > -1)
199
+ closeBlockCommentTokens++
200
+
201
+ const thisLineBlank = line.trim() === ''
202
+
203
+ if (thisLineBlank && lastLineBlank) {
204
+ endsWithDoubleBlank = true
205
+ break
206
+ }
207
+
208
+ lastLineBlank = thisLineBlank
209
+ }
210
+
211
+ if (endsWithDoubleBlank) trailingBlankLines = 2
212
+ else if (lastLineBlank) trailingBlankLines = 1
213
+
214
+ // Remove extra block comment tokens
215
+ if (
216
+ isMarkDownFile &&
217
+ rawTaskContentLines.length &&
218
+ rawTaskContentLines[rawTaskContentLines.length - 1].trim() === '-->' &&
219
+ openBlockCommentTokens !== closeBlockCommentTokens
220
+ )
221
+ rawTaskContentLines.pop()
222
+
223
+ const taskContentLines = trailingBlankLines
224
+ ? rawTaskContentLines.slice(0, -trailingBlankLines)
225
+ : rawTaskContentLines;
226
+
227
+ return {
228
+ rawTaskContentLines,
229
+ taskContentLines,
230
+ trailingBlankLines
231
+ }
232
+ }
233
+
234
+ function getTaskContent ({
235
+ config,
236
+ content,
237
+ inBlockComment,
238
+ beforeText,
239
+ fileType,
240
+ lang
241
+ }) {
242
+ return getCardTagsTaskContent({ content, fileType }) ||
243
+ getCodeFileTaskContent({ config, content, inBlockComment, fileType, lang }) ||
244
+ getTextFileTaskContent({ config, content, beforeText, fileType })
245
+ }
246
+
247
+ module.exports = {
248
+ FILE_TYPES,
249
+ getTaskContent,
250
+ START_TAG,
251
+ END_TAG,
252
+ LIST_NAME_PATTERN,
253
+ HASH_STYLE_REGEX,
254
+ HASH_STYLE_META_ORDER_REGEX,
255
+ LINK_STYLE_REGEX
256
+ }
package/lib/card.js CHANGED
@@ -6,6 +6,7 @@ const _isFunction = require('lodash.isfunction')
6
6
  const _isEmpty = require('lodash.isempty')
7
7
  const _get = require('lodash.get')
8
8
  const eol = require('eol')
9
+ const moment = require('moment')
9
10
  const allEmoji = require('markdown-it-emoji/lib/data/full.json')
10
11
 
11
12
  const { removeMD, renderMarkdown } = require('./adapters/markdown')
@@ -115,6 +116,7 @@ module.exports = function newCard(task, _project, dontParse) {
115
116
 
116
117
  get defaultData() {
117
118
  return {
119
+ timestamp: moment().format(),
118
120
  ..._pick(
119
121
  this,
120
122
  ...['beforeText',
@@ -186,10 +188,10 @@ module.exports = function newCard(task, _project, dontParse) {
186
188
  ? { ...pluginManager.getCardProperties(defaultData) }
187
189
  : {}
188
190
  const data = {
189
- ...defaultData,
190
191
  ...pluginData,
191
192
  ...props,
192
- ...computed
193
+ ...computed,
194
+ ...defaultData
193
195
  }
194
196
 
195
197
  this.data = { ...data }
package/lib/file.js CHANGED
@@ -19,7 +19,16 @@ var _clone = require('lodash.clone'),
19
19
  Task = require('./task'),
20
20
  newCard = require('./card'),
21
21
  fastSort = require('fast-sort/dist/sort'),
22
- XRegExp = require('xregexp')
22
+ XRegExp = require('xregexp'),
23
+ moment = require('moment'),
24
+ {
25
+ FILE_TYPES,
26
+ getTaskContent,
27
+ LIST_NAME_PATTERN,
28
+ HASH_STYLE_REGEX,
29
+ HASH_STYLE_META_ORDER_REGEX,
30
+ LINK_STYLE_REGEX,
31
+ } = require('./adapters/parsers/task/CardContentParser')
23
32
 
24
33
  const appContext = require('./context/ApplicationContext')
25
34
  const { isNumber } = require('./task')
@@ -68,8 +77,6 @@ function File(opts) {
68
77
  }
69
78
  }
70
79
  util.inherits(File, events.EventEmitter)
71
- const START_TAG = (File.START_TAG = '<card>')
72
- const END_TAG = (File.END_TAG = '</card>')
73
80
  const CODE_BLOCK_REGEX = (File.CODE_BLOCK_REGEX = /`{3}[\s\S]*?`{3}/gm)
74
81
  const INLINE_CODE_REGEX = (File.INLINE_CODE_REGEX = /`[\s\S]*?`/g)
75
82
  const CODE_STYLE_END = '((:)(-?[\\d.]+(?:e-?\\d+)?)?)?[ \\t]+(.+)$'
@@ -77,28 +84,12 @@ const CODE_STYLE_END = '((:)(-?[\\d.]+(?:e-?\\d+)?)?)?[ \\t]+(.+)$'
77
84
  const CODE_STYLE_PATTERN = (File.CODE_STYLE_PATTERN =
78
85
  '([A-Z]+[A-Z-_]+?)' + CODE_STYLE_END)
79
86
 
80
- const LIST_NAME_PATTERN = '[\\p{L}0-9-_]{2,}' // [a-zA-Z-_]+?
81
- File.LIST_NAME_PATTERN = new XRegExp(LIST_NAME_PATTERN)
82
87
  File.isListNameValid = function(listName) {
83
88
  return new XRegExp(LIST_NAME_PATTERN).test(listName)
84
89
  }
85
90
 
86
- const HASH_STYLE_PATTERN = (File.HASH_STYLE_PATTERN = new XRegExp(
87
- `#(${LIST_NAME_PATTERN})(:(-?[\\d.]+(?:e-?\\d+)?)?)?[ \\t]+(.+)$`,
88
- 'gm'
89
- ))
90
-
91
- const HASH_STYLE_META_ORDER_PATTERN = (File.HASH_STYLE_META_ORDER_PATTERN =
92
- new XRegExp(`#(${LIST_NAME_PATTERN})[ \\t]+(.+)$`, 'gm'))
93
-
94
- const LINK_STYLE_PATTERN = (File.LINK_STYLE_PATTERN = new XRegExp(
95
- `(\\[.*\\]\\s?)?(\\[(.+)\\]\\(#(${LIST_NAME_PATTERN})(:)(-?[\\d.]+(?:e-?\\d+)?)?\\))`,
96
- 'gm'
97
- ))
98
-
99
91
  const CHECK_STYLE_PATTERN = (File.CHECK_STYLE_PATTERN =
100
92
  /^(\s*- \[([x ])\]\s)(.+$)/gm)
101
- // var LINK_STYLE_PATTERN_META_ORDER = File.LINK_STYLE_PATTERN_META_ORDER = /(\[.*\]\s?)?(\[(.+)\]\(#([a-zA-Z-_]+?)\))/gm;
102
93
 
103
94
  var getTaskId = (File.getTaskId = function (path, line, text) {
104
95
  var shasum = crypto.createHash('sha1')
@@ -158,7 +149,7 @@ File.addCompleted = function ({ task, content, config }) {
158
149
  if (task.list !== doneList) return content
159
150
  if (task.completed || task.meta.completed) return content
160
151
  if (content.includes(`completed${config.getMetaSep()}`)) return content
161
- const now = new Date().toISOString()
152
+ const now = moment().format()
162
153
  content = task.addToLastCommentInContent(
163
154
  content,
164
155
  `completed${config.getMetaSep()}${now}`,
@@ -175,7 +166,7 @@ File.parseDueDate = function (config, text) {
175
166
  if (dueString) return dueMatch
176
167
  try {
177
168
  const due = chrono.parseDate(dueMatch, new Date(), {forwardDate: true})
178
- dueString = due.toISOString()
169
+ dueString = moment(due).format()
179
170
  return `${p1.toLowerCase()}${config.getMetaSep()}${dueString}`
180
171
  } catch (e) {
181
172
  // console.log(`Unable to parse due date for ${text}`)
@@ -292,9 +283,9 @@ File.prototype.extractCodeStyleTasks = function (config, pos, content) {
292
283
  var self = this
293
284
  var codeStyleRegex = new RegExp(CODE_STYLE_PATTERN, 'gm')
294
285
  var result
295
- const inCodeBlock = this.isInBlockComment(content)
286
+ const inBlockComment = this.isInBlockComment(content)
296
287
  const singleLineBlockComment =
297
- inCodeBlock && this.isSingleLineBlockComment(content)
288
+ inBlockComment && this.isSingleLineBlockComment(content)
298
289
  while ((result = codeStyleRegex.exec(content)) !== null) {
299
290
  var posInContent = pos + result.index
300
291
  var line = this.getLineNumber(posInContent)
@@ -315,10 +306,10 @@ File.prototype.extractCodeStyleTasks = function (config, pos, content) {
315
306
  const charsInFront = matchCharsInFront ? matchCharsInFront[0].length : 0
316
307
  var hasColon =
317
308
  result[3] !== undefined
318
- const taskStartOnLine = inCodeBlock
309
+ const taskStartOnLine = inBlockComment
319
310
  ? posInContent - charsInFront - linePos
320
311
  : pos - linePos
321
- var task = self.newTask({
312
+ var task = self.extractTaskWithDescription({
322
313
  taskStartOnLine,
323
314
  rawTask,
324
315
  config,
@@ -328,7 +319,7 @@ File.prototype.extractCodeStyleTasks = function (config, pos, content) {
328
319
  type: Task.Types.CODE,
329
320
  hasColon,
330
321
  content: self.getContent().substring(linePos),
331
- inCodeBlock,
322
+ inBlockComment,
332
323
  singleLineBlockComment,
333
324
  })
334
325
  self.addTask(task)
@@ -338,7 +329,7 @@ File.prototype.extractCodeStyleTasks = function (config, pos, content) {
338
329
  }
339
330
 
340
331
  File.prototype.extractHashStyleTasks = function (config, pos, content) {
341
- var hashStyleRegex = new XRegExp(HASH_STYLE_PATTERN)
332
+ var hashStyleRegex = new XRegExp(HASH_STYLE_REGEX)
342
333
  var lang = this.getLang()
343
334
  var result
344
335
  while ((result = hashStyleRegex.exec(content)) !== null) {
@@ -355,7 +346,7 @@ File.prototype.extractHashStyleTasks = function (config, pos, content) {
355
346
  const blockEndPos = text.indexOf(lang.block.end)
356
347
  if (blockEndPos > -1) text = text.substring(0, blockEndPos)
357
348
  }
358
- var task = this.newTask({
349
+ var task = this.extractTaskWithDescription({
359
350
  config,
360
351
  list,
361
352
  text,
@@ -375,7 +366,7 @@ File.prototype.extractHashStyleTasks = function (config, pos, content) {
375
366
 
376
367
  File.prototype.extractLinkStyleTasks = function (config, pos, content) {
377
368
  var self = this
378
- var linkStyleRegex = new XRegExp(LINK_STYLE_PATTERN)
369
+ var linkStyleRegex = new XRegExp(LINK_STYLE_REGEX)
379
370
  var result
380
371
  while ((result = linkStyleRegex.exec(content)) !== null) {
381
372
  let [match, before, rawTask, text, list, colon, order] = result
@@ -390,7 +381,7 @@ File.prototype.extractLinkStyleTasks = function (config, pos, content) {
390
381
  log(result)
391
382
  log('list:%s text:%s order:%d line:%d', list, text, order, line)
392
383
 
393
- var task = self.newTask({
384
+ var task = self.extractTaskWithDescription({
394
385
  config,
395
386
  list,
396
387
  text,
@@ -431,7 +422,7 @@ File.prototype.extractCheckboxTasks = function (config, pos, content) {
431
422
  const posInContent = result.index + pos + beforeLength
432
423
  var line = this.getLineNumber(posInContent)
433
424
  if (hasTaskAtLine(line)) continue
434
- var task = this.newTask({
425
+ var task = this.extractTaskWithDescription({
435
426
  config,
436
427
  list,
437
428
  text,
@@ -620,7 +611,7 @@ File.prototype.deleteTask = function (task, config) {
620
611
  config
621
612
  )
622
613
  } else if (task.type === Task.Types.HASHTAG) {
623
- this.deleteCodeOrHashTask(HASH_STYLE_PATTERN, task, config)
614
+ this.deleteCodeOrHashTask(HASH_STYLE_REGEX, task, config)
624
615
  }
625
616
  } else {
626
617
  const fileContent = this.getContentLines()
@@ -661,22 +652,7 @@ File.prototype.modifyTask = function (task, config, noEmit) {
661
652
  } else {
662
653
  task.updateContent()
663
654
  const fileContent = this.getContentLines()
664
- let description = task.description.slice()
665
- task.preserveBlankLines = hasBlankLines(description.join(eol.auto))
666
- if (task.preserveBlankLines) {
667
- description.unshift('<card>')
668
- description.push('</card>')
669
- }
670
655
  let text = File.TASK_TYPE_TEMPLATES[task.type](task, config)
671
- description = Task.padDescription(description, task.beforeText)
672
- if (
673
- description &&
674
- description.length > 0 &&
675
- description[description.length - 1].trim() === ''
676
- ) {
677
- description.pop()
678
- }
679
-
680
656
  const linesBefore = fileContent.slice(0, task.line - 1)
681
657
  if (this.isCodeFile()) {
682
658
  const linesBeforeLength = linesBefore.join(lineEnd).length
@@ -686,19 +662,19 @@ File.prototype.modifyTask = function (task, config, noEmit) {
686
662
  }
687
663
  }
688
664
 
689
- const linesAfter = fileContent.slice(task.lastLine);
690
-
665
+ const linesAfter = fileContent.slice(task.line)
691
666
  const newContent = [
692
667
  ...linesBefore,
693
668
  text,
694
- ...description,
695
669
  ...linesAfter,
696
670
  ].join(lineEnd)
697
671
 
698
- if (this.getContent().trim() === newContent.trim()) return
672
+ const contentBeforeUpdate = this.getContent()
699
673
  this.setContent(newContent)
674
+ this.modifyDescription(task, config)
675
+ if (contentBeforeUpdate.trim() == this.getContent().trim()) return
676
+
700
677
  this.setModified(true)
701
- task.lastLine = task.line + description.length
702
678
  if (!noEmit) {
703
679
  this.emit('task.modified', task)
704
680
  this.emit('file.modified', this)
@@ -737,7 +713,7 @@ File.prototype.modifyCodeOrHashTask = function (re, task, config, noEmit) {
737
713
  if (index < 0) index = 0
738
714
  var beforeContent = this.getContent().substring(0, pos)
739
715
  var afterContent = this.getContent().substring(re.lastIndex)
740
- if (task.inCodeBlock) {
716
+ if (task.inBlockComment) {
741
717
  var blockEnd = text.indexOf(lang.block.end)
742
718
  if (blockEnd > -1) {
743
719
  const desc =
@@ -797,25 +773,44 @@ File.prototype.modifyTaskFromHtml = function (task, html) {
797
773
  }
798
774
 
799
775
  File.prototype.modifyTaskFromContent = function (task, content, config) {
776
+ content = trimBlankLines(content, this.isCodeFile())
800
777
  task.updateFromContent(content)
801
778
  this.modifyTask(task, config)
802
779
  }
803
780
 
781
+ const trimBlankLines = File.trimBlankLines = function (content, isCodeFile) {
782
+ if (isCodeFile) {
783
+ // replace all occurrences of two empty lines in a row with a single empty line
784
+ return content.replace(/\n\s*\n/g, '\n')
785
+ }
786
+ return content.replace(/\n\s*\n\s*\n/g, '\n\n')
787
+ }
788
+
804
789
  File.prototype.modifyDescription = function (task, config) {
805
- // BACKLOG:-170 respect indent of preFix when task is a list item
806
- const descStart = this.getLinePos(task.line + 1)
790
+ // DOING This should be split out by type (code, markdown, etc)
791
+ // #urgent
792
+ // <!-- order:-10 -->
807
793
  const taskStart = this.getLinePos(task.line)
794
+ const descStart = this.getLinePos(task.line + 1)
808
795
  const contentWithTask = this.getContent().substring(taskStart)
809
- let { rawDescription, preserveBlankLines, hasCardEndTag } =
810
- this.getRawDescription({
796
+ let {
797
+ rawTaskContentLines,
798
+ taskContentLines,
799
+ isWrappedWithCardTag,
800
+ trailingBlankLines
801
+ } =
802
+ this.getTaskContent({
811
803
  config,
812
804
  content: contentWithTask,
813
- inCodeBlock: task.inCodeBlock,
805
+ inBlockComment: task.inBlockComment,
814
806
  beforeText: task.beforeText,
815
807
  })
808
+
816
809
  let beforeDescContent = this.getContent().substring(0, descStart)
810
+
817
811
  if (descStart === this.getContent().length && !beforeDescContent.endsWith(lf))
818
812
  beforeDescContent += lineEnd
813
+
819
814
  let content = this.getContent().substring(descStart)
820
815
  let spaces = ''
821
816
  if (this.isCodeFile() && !this.isMarkDownFile()) {
@@ -830,32 +825,34 @@ File.prototype.modifyDescription = function (task, config) {
830
825
  )
831
826
  // BACKLOG:-30 Allow this to pad for code files as well
832
827
  description = File.padDescription(description, task.beforeText).join(lineEnd)
833
- if (rawDescription.length === 0 && description.length > 0) {
828
+ if (taskContentLines.length === 0 && description.length > 0) {
834
829
  beforeDescContent += description + lineEnd
835
830
  } else {
836
- if (preserveBlankLines) {
837
- rawDescription.unshift(START_TAG)
838
- if (hasCardEndTag) rawDescription.push(END_TAG)
839
- }
840
- rawDescription = rawDescription.join(lineEnd)
831
+ let rawTaskContent = rawTaskContentLines.join(lineEnd)
832
+ // Add card terminator to description
841
833
  if (
842
834
  this.isMarkDownFile() &&
843
835
  description.length > 0 &&
844
- (tools.hasBlankLines(description) || hasCardEndTag)
836
+ (hasBlankLines(description) || isWrappedWithCardTag || trailingBlankLines)
845
837
  ) {
846
- task.preserveBlankLines = true
847
- description = `${START_TAG}${lineEnd}${description}${lineEnd}${END_TAG}`
838
+ task.isWrappedWithCardTag = isWrappedWithCardTag // DOING Find out where this is used and stop using it
839
+ // <!-- order:-30 -->
840
+ const blankLinesToAdd = isWrappedWithCardTag ? 2 : trailingBlankLines
841
+ description = `${description}${lineEnd.repeat(blankLinesToAdd)}`
848
842
  }
843
+
844
+ // Handle code file tasks
849
845
  if (
850
846
  task.singleLineBlockComment &&
851
- rawDescription.length > 0 &&
847
+ rawTaskContent.length > 0 &&
852
848
  description.length === 0
853
849
  ) {
854
- rawDescription += lineEnd
850
+ description = lineEnd + description
855
851
  }
856
- content = content.replace(rawDescription, description)
852
+ content = content.replace(rawTaskContent, description)
857
853
  }
858
854
 
855
+ task.lastLine = task.line + description.split(lineEnd).length
859
856
  this.setContent(beforeDescContent + content)
860
857
  }
861
858
 
@@ -863,7 +860,7 @@ File.padDescription = function (description = [], beforeText = '') {
863
860
  return Task.padDescription(description, beforeText)
864
861
  }
865
862
 
866
- File.prototype.newTask = function ({
863
+ File.prototype.extractTaskWithDescription = function ({
867
864
  taskStartOnLine,
868
865
  rawTask,
869
866
  config,
@@ -874,7 +871,7 @@ File.prototype.newTask = function ({
874
871
  type,
875
872
  hasColon,
876
873
  content,
877
- inCodeBlock,
874
+ inBlockComment,
878
875
  singleLineBlockComment,
879
876
  pos,
880
877
  }) {
@@ -883,20 +880,20 @@ File.prototype.newTask = function ({
883
880
  let description = []
884
881
  // BACKLOG:-230 ## add beforeText for code files
885
882
  const beforeText = this.getBeforeText(line, pos)
886
- let { rawDescription, preserveBlankLines } = this.getRawDescription({
883
+ let { rawTaskContentLines, taskContentLines, isWrappedWithCardTag } = this.getTaskContent({
887
884
  config,
888
885
  content,
889
- inCodeBlock,
886
+ inBlockComment,
890
887
  beforeText,
891
888
  })
892
889
 
893
890
  if (!singleLineBlockComment) {
894
891
  description = this.isCodeFile()
895
- ? rawDescription.map((line) => this.trimCommentChars(line))
896
- : _clone(rawDescription)
892
+ ? taskContentLines.map((line) => this.trimCommentChars(line))
893
+ : _clone(taskContentLines)
897
894
  }
898
895
  description = Task.trimDescription(description, beforeText)
899
- const descriptionStartsWith = this.getDescriptionChars(inCodeBlock)
896
+ const descriptionStartsWith = this.getDescriptionChars(inBlockComment)
900
897
  const frontMatter = this.frontMatter
901
898
  text = this.trimCommentBlockEnd(text)
902
899
  let commentStartOnLine =
@@ -908,12 +905,12 @@ File.prototype.newTask = function ({
908
905
  {
909
906
  pos,
910
907
  frontMatter,
911
- inCodeBlock,
908
+ inBlockComment,
912
909
  singleLineBlockComment,
913
910
  rawTask,
914
911
  text,
915
912
  list,
916
- rawDescription,
913
+ rawTaskContentLines,
917
914
  description,
918
915
  descriptionStartsWith,
919
916
  taskStartOnLine,
@@ -926,7 +923,7 @@ File.prototype.newTask = function ({
926
923
  source: self.getSource(),
927
924
  type,
928
925
  beforeText,
929
- preserveBlankLines,
926
+ isWrappedWithCardTag,
930
927
  },
931
928
  this.project
932
929
  )
@@ -1098,8 +1095,8 @@ File.prototype.ignoreCodeBlocks = function () {
1098
1095
  // BACKLOG:-100 This is not used, but we need a way to ignore tasks in code blocks. Maybe save position ranges
1099
1096
  var cleanContent = this.content
1100
1097
  var replacer = function (block) {
1101
- block = block.replace(new XRegExp(LINK_STYLE_PATTERN), '**TASK**')
1102
- block = block.replace(new XRegExp(HASH_STYLE_PATTERN), '**TASK**')
1098
+ block = block.replace(new XRegExp(LINK_STYLE_REGEX), '**TASK**')
1099
+ block = block.replace(new XRegExp(HASH_STYLE_REGEX), '**TASK**')
1103
1100
  return block
1104
1101
  }
1105
1102
 
@@ -1135,105 +1132,31 @@ File.prototype.getBeforeText = function (line, pos) {
1135
1132
  : this.content.substring(this.getLinePos(line), pos)
1136
1133
  }
1137
1134
 
1138
- File.prototype.getDescriptionChars = function (inCodeBlock) {
1135
+ File.prototype.getDescriptionChars = function (inBlockComment) {
1139
1136
  if (!this.isCodeFile()) return ''
1140
1137
  const lang = this.getLang()
1141
- if (inCodeBlock && lang.block && lang.block.ignore) return lang.block.ignore
1138
+ if (inBlockComment && lang.block && lang.block.ignore) return lang.block.ignore
1142
1139
  return lang.symbol
1143
1140
  }
1144
1141
 
1145
- File.prototype.getRawDescription = function ({
1146
- config,
1147
- content,
1148
- inCodeBlock,
1149
- beforeText,
1150
- }) {
1151
- const { rawDescription, preserveBlankLines, hasCardEndTag } =
1152
- this.getTaskContent({ config, content, inCodeBlock, beforeText })
1153
- const blockEnd = this.getLang().block && this.getLang().block.end
1154
- if (
1155
- this.isCodeFile() &&
1156
- blockEnd &&
1157
- rawDescription.length > 0 &&
1158
- rawDescription[rawDescription.length - 1].indexOf(blockEnd) > -1
1159
- )
1160
- rawDescription.pop()
1161
- return { rawDescription, preserveBlankLines, hasCardEndTag }
1162
- }
1163
-
1164
- File.getContentLines = function (content) {
1165
- let foundContent = false
1166
- return eol.split(content).filter((line) => {
1167
- const hasContent = line.trim()
1168
- if (!foundContent && hasContent) foundContent = true
1169
- return foundContent || hasContent
1170
- })
1171
- }
1172
-
1173
1142
  File.prototype.getTaskContent = function ({
1174
1143
  config,
1175
1144
  content,
1176
- inCodeBlock,
1145
+ inBlockComment,
1177
1146
  beforeText,
1178
1147
  }) {
1179
- const commentLines = []
1180
- let lines = File.getContentLines(content)
1181
- const preserveBlankLines =
1182
- this.isMarkDownFile() && lines.length > 1 && lines[1].trim() === START_TAG
1148
+ const isMarkDownFile = this.isMarkDownFile()
1149
+ const isCodeFile = this.isCodeFile()
1183
1150
  const lang = this.getLang()
1184
- let openBlockTokens = 0
1185
- let closeBlockTokens = 0
1186
- let hasCardEndTag = false
1187
- for (let i = preserveBlankLines ? 2 : 1; i < lines.length; i++) {
1188
- let line = lines[i]
1189
- if (this.isCheckBoxTask(config, line, beforeText) && !preserveBlankLines)
1190
- break
1191
- if (this.isMarkDownFile() && line.trim().indexOf('<!--') > -1)
1192
- openBlockTokens++
1193
- if (this.isMarkDownFile() && line.trim().indexOf('-->') > -1)
1194
- closeBlockTokens++
1195
-
1196
- if (preserveBlankLines) {
1197
- if (line.trim() === END_TAG) {
1198
- hasCardEndTag = true
1199
- break
1200
- }
1201
- } else {
1202
- if (
1203
- this.isMarkDownFile() &&
1204
- beforeText &&
1205
- /^\s*(\*|-|\d+\.|[a-zA-Z]\.)\s$/.test(beforeText) &&
1206
- line.search(/\S/) <= beforeText.search(/\S/)
1207
- )
1208
- break
1209
- if (
1210
- (this.isCodeFile() &&
1211
- !inCodeBlock &&
1212
- (line.indexOf(lang.symbol) < 0 ||
1213
- line.trim().indexOf(lang.symbol) > 0)) ||
1214
- line.trim() === lang.symbol
1215
- )
1216
- break
1217
- if (this.isCodeFile() && lang.block && line.trim() === lang.block.end)
1218
- break
1219
- if (this.isCodeFile() && lang.block && line.trim() === lang.block.ignore)
1220
- break
1221
- if (!this.isCodeFile() && line.trim() === '') break
1222
- }
1223
- if (this.hasTaskInText(config, line)) break
1224
-
1225
- if (line && lang.block) line = line.replace(lang.block.end, '')
1226
-
1227
- if (preserveBlankLines || line.trim() !== '') commentLines.push(line)
1228
- }
1229
- if (
1230
- this.isMarkDownFile() &&
1231
- commentLines.length &&
1232
- commentLines[commentLines.length - 1].trim() === '-->' &&
1233
- openBlockTokens !== closeBlockTokens
1234
- )
1235
- commentLines.pop()
1236
- return { rawDescription: commentLines, preserveBlankLines, hasCardEndTag }
1151
+ const fileType = isMarkDownFile ? FILE_TYPES.MARKDOWN : isCodeFile ? FILE_TYPES.CODE : undefined
1152
+ return getTaskContent({
1153
+ config,
1154
+ content,
1155
+ inBlockComment,
1156
+ beforeText,
1157
+ lang,
1158
+ fileType,
1159
+ })
1237
1160
  }
1238
1161
 
1239
1162
  File.prototype.isCheckBoxTask = function (config, line, beforeText) {
@@ -1322,9 +1245,9 @@ File.prototype.hasCodeStyleTask = function (config, text) {
1322
1245
  File.prototype.hasTaskInText = function (config, text) {
1323
1246
  return (
1324
1247
  this.hasCodeStyleTask(config, text) ||
1325
- ((new XRegExp(HASH_STYLE_PATTERN).test(text) ||
1326
- new XRegExp(HASH_STYLE_META_ORDER_PATTERN).test(text) ||
1327
- new XRegExp(LINK_STYLE_PATTERN).test(text)) &&
1248
+ ((new XRegExp(HASH_STYLE_REGEX).test(text) ||
1249
+ new XRegExp(HASH_STYLE_META_ORDER_REGEX).test(text) ||
1250
+ new XRegExp(LINK_STYLE_REGEX).test(text)) &&
1328
1251
  config.lists.find((list) => text.includes(`#${list.name}`)))
1329
1252
  )
1330
1253
  }
package/lib/repository.js CHANGED
@@ -1048,10 +1048,6 @@ Repository.prototype.appendTask = function({file, content, list}, cb) {
1048
1048
  }).trimEnd()
1049
1049
  const lines = eol.split(content)
1050
1050
  const text = lines[0]
1051
- if (tools.hasBlankLines(lines.join(eol.auto))) {
1052
- lines.splice(1, 0, File.START_TAG)
1053
- lines.push(File.END_TAG)
1054
- }
1055
1051
  const taskSyntax = config.getNewCardSyntax()
1056
1052
  const taskPrefix = interpretedTaskPrefix ? `${interpretedTaskPrefix} ` : ''
1057
1053
  let fileContent = file.getContent()
@@ -1092,7 +1088,8 @@ Repository.prototype.appendTask = function({file, content, list}, cb) {
1092
1088
  const inLineOrder = config.orderMeta ? '' : `:${orderString}`
1093
1089
  appendContent = `${taskPrefix}#${list}${inLineOrder} ${text}${eol.lf}${taskContent}`
1094
1090
  }
1095
- file.setContent(`${fileContent}${sep}${appendContent}`)
1091
+ appendContent = File.trimBlankLines(appendContent)
1092
+ file.setContent(`${fileContent}${sep}${appendContent}${"\n".repeat(2)}`)
1096
1093
  this.writeAndExtract(file, true, cb)
1097
1094
  }
1098
1095
 
package/lib/task.js CHANGED
@@ -45,15 +45,15 @@ class Task {
45
45
  this.context = obj.context || []
46
46
  this.meta = obj.meta || {}
47
47
  this.metaIndex = obj.metaIndex || {}
48
- this.inCodeBlock = obj.inCodeBlock
48
+ this.inBlockComment = obj.inBlockComment
49
49
  this.singleLineBlockComment = obj.singleLineBlockComment
50
50
  this.description = obj.description || []
51
- this.rawDescription = obj.rawDescription || []
51
+ this.rawTaskContentLines = obj.rawTaskContentLines || []
52
52
  this.taskStartOnLine = obj.taskStartOnLine || 0
53
53
  this.commentStartOnLine = obj.commentStartOnLine || 0
54
54
  this.descriptionStartsWith = obj.descriptionStartsWith
55
55
  this.filteredListName = obj.filteredListName
56
- this.preserveBlankLines = obj.preserveBlankLines
56
+ this.isWrappedWithCardTag = obj.isWrappedWithCardTag
57
57
  this.config = config
58
58
  this.allMeta = obj.allMeta || {}
59
59
  this.allContext = obj.allContext || []
@@ -80,8 +80,7 @@ class Task {
80
80
  }
81
81
 
82
82
  getLastLine() {
83
- let lastLine = this.line + this.description.length
84
- if (this.preserveBlankLines) lastLine += 2
83
+ let lastLine = this.line + this.rawTaskContentLines.length
85
84
  return lastLine
86
85
  }
87
86
 
@@ -178,6 +177,8 @@ class Task {
178
177
  }
179
178
 
180
179
  updateFromContent(content) {
180
+ // replace all occurrences of two empty lines in a row with a single empty line
181
+ content = content.replace(/\n\s*\n\s*\n/g, '\n\n')
181
182
  let lines = eol.split(content)
182
183
  this.text = lines.shift().trim()
183
184
  if (lines && lines.length > 1 && lines[lines.length - 1].trim() === '') lines.pop()
@@ -223,16 +224,16 @@ Task.padDescription = function (description = [], beforeText = '') {
223
224
  return description
224
225
  }
225
226
 
226
- Task.trimDescription = function (rawDescription, beforeText) {
227
+ Task.trimDescription = function (rawTaskContentLines, beforeText) {
227
228
  const checkedData = Task.getCheckedData(beforeText)
228
229
  if (checkedData) {
229
- rawDescription = rawDescription.map((line) => {
230
+ rawTaskContentLines = rawTaskContentLines.map((line) => {
230
231
  const lineLength = line.length
231
232
  line = line.trimStart()
232
233
  return line.padStart(lineLength - checkedData.pad)
233
234
  })
234
235
  }
235
- return rawDescription
236
+ return rawTaskContentLines
236
237
  }
237
238
 
238
239
  Task.prototype.getCheckedData = function () {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imdone-core",
3
- "version": "1.38.0",
3
+ "version": "1.40.1",
4
4
  "description": "imdone-core",
5
5
  "main": "index.js",
6
6
  "bin": {