imdone-core 1.37.1 → 1.40.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.
@@ -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
@@ -117,6 +117,7 @@ module.exports = function newCard(task, _project, dontParse) {
117
117
  return {
118
118
  ..._pick(
119
119
  this,
120
+ ...['beforeText',
120
121
  'rawTask',
121
122
  'id',
122
123
  'text',
@@ -141,7 +142,7 @@ module.exports = function newCard(task, _project, dontParse) {
141
142
  'projectPath',
142
143
  'relPath',
143
144
  'fullPath',
144
- 'totals'
145
+ 'totals'].sort()
145
146
  ),
146
147
  }
147
148
  }
@@ -149,11 +150,11 @@ module.exports = function newCard(task, _project, dontParse) {
149
150
  initData() {
150
151
  const props = { ...this.props, content: CONTENT_TOKEN }
151
152
  const computed = { ...this.computed }
152
- const cardData = this.defaultData
153
+ const defaultData = this.defaultData
153
154
  try {
154
155
  for (let [key, value] of Object.entries(computed)) {
155
156
  let computedValue = value.toString()
156
- const computedProps = { ...props, ...computed, ...cardData }
157
+ const computedProps = { ...props, ...computed, ...defaultData }
157
158
  if (_isFunction(value) && !_isEmpty(props)) {
158
159
  try {
159
160
  // Call the computed function for a value
@@ -182,13 +183,13 @@ module.exports = function newCard(task, _project, dontParse) {
182
183
  }
183
184
 
184
185
  const pluginData = pluginManager
185
- ? { ...pluginManager.getCardProperties(cardData), ...cardData }
186
- : cardData
186
+ ? { ...pluginManager.getCardProperties(defaultData) }
187
+ : {}
187
188
  const data = {
189
+ ...defaultData,
188
190
  ...pluginData,
189
191
  ...props,
190
- ...computed,
191
- ...cardData,
192
+ ...computed
192
193
  }
193
194
 
194
195
  this.data = { ...data }
@@ -28,6 +28,7 @@ cards:
28
28
  orderMeta: true
29
29
  maxLines: 6 #override
30
30
  addNewCardsToTop: true #override
31
+ showTagsAndMeta: true #override
31
32
  journalTemplate: #override
32
33
  theme: 'dark'
33
34
  markdownOnly: false #override
package/lib/file.js CHANGED
@@ -19,7 +19,15 @@ 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
+ {
24
+ FILE_TYPES,
25
+ getTaskContent,
26
+ LIST_NAME_PATTERN,
27
+ HASH_STYLE_REGEX,
28
+ HASH_STYLE_META_ORDER_REGEX,
29
+ LINK_STYLE_REGEX,
30
+ } = require('./adapters/parsers/task/CardContentParser')
23
31
 
24
32
  const appContext = require('./context/ApplicationContext')
25
33
  const { isNumber } = require('./task')
@@ -68,8 +76,6 @@ function File(opts) {
68
76
  }
69
77
  }
70
78
  util.inherits(File, events.EventEmitter)
71
- const START_TAG = (File.START_TAG = '<card>')
72
- const END_TAG = (File.END_TAG = '</card>')
73
79
  const CODE_BLOCK_REGEX = (File.CODE_BLOCK_REGEX = /`{3}[\s\S]*?`{3}/gm)
74
80
  const INLINE_CODE_REGEX = (File.INLINE_CODE_REGEX = /`[\s\S]*?`/g)
75
81
  const CODE_STYLE_END = '((:)(-?[\\d.]+(?:e-?\\d+)?)?)?[ \\t]+(.+)$'
@@ -77,28 +83,12 @@ const CODE_STYLE_END = '((:)(-?[\\d.]+(?:e-?\\d+)?)?)?[ \\t]+(.+)$'
77
83
  const CODE_STYLE_PATTERN = (File.CODE_STYLE_PATTERN =
78
84
  '([A-Z]+[A-Z-_]+?)' + CODE_STYLE_END)
79
85
 
80
- const LIST_NAME_PATTERN = '[\\p{L}0-9-_]{2,}' // [a-zA-Z-_]+?
81
- File.LIST_NAME_PATTERN = new XRegExp(LIST_NAME_PATTERN)
82
86
  File.isListNameValid = function(listName) {
83
87
  return new XRegExp(LIST_NAME_PATTERN).test(listName)
84
88
  }
85
89
 
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
90
  const CHECK_STYLE_PATTERN = (File.CHECK_STYLE_PATTERN =
100
91
  /^(\s*- \[([x ])\]\s)(.+$)/gm)
101
- // var LINK_STYLE_PATTERN_META_ORDER = File.LINK_STYLE_PATTERN_META_ORDER = /(\[.*\]\s?)?(\[(.+)\]\(#([a-zA-Z-_]+?)\))/gm;
102
92
 
103
93
  var getTaskId = (File.getTaskId = function (path, line, text) {
104
94
  var shasum = crypto.createHash('sha1')
@@ -292,9 +282,9 @@ File.prototype.extractCodeStyleTasks = function (config, pos, content) {
292
282
  var self = this
293
283
  var codeStyleRegex = new RegExp(CODE_STYLE_PATTERN, 'gm')
294
284
  var result
295
- const inCodeBlock = this.isInBlockComment(content)
285
+ const inBlockComment = this.isInBlockComment(content)
296
286
  const singleLineBlockComment =
297
- inCodeBlock && this.isSingleLineBlockComment(content)
287
+ inBlockComment && this.isSingleLineBlockComment(content)
298
288
  while ((result = codeStyleRegex.exec(content)) !== null) {
299
289
  var posInContent = pos + result.index
300
290
  var line = this.getLineNumber(posInContent)
@@ -315,10 +305,10 @@ File.prototype.extractCodeStyleTasks = function (config, pos, content) {
315
305
  const charsInFront = matchCharsInFront ? matchCharsInFront[0].length : 0
316
306
  var hasColon =
317
307
  result[3] !== undefined
318
- const taskStartOnLine = inCodeBlock
308
+ const taskStartOnLine = inBlockComment
319
309
  ? posInContent - charsInFront - linePos
320
310
  : pos - linePos
321
- var task = self.newTask({
311
+ var task = self.extractTaskWithDescription({
322
312
  taskStartOnLine,
323
313
  rawTask,
324
314
  config,
@@ -328,7 +318,7 @@ File.prototype.extractCodeStyleTasks = function (config, pos, content) {
328
318
  type: Task.Types.CODE,
329
319
  hasColon,
330
320
  content: self.getContent().substring(linePos),
331
- inCodeBlock,
321
+ inBlockComment,
332
322
  singleLineBlockComment,
333
323
  })
334
324
  self.addTask(task)
@@ -338,7 +328,7 @@ File.prototype.extractCodeStyleTasks = function (config, pos, content) {
338
328
  }
339
329
 
340
330
  File.prototype.extractHashStyleTasks = function (config, pos, content) {
341
- var hashStyleRegex = new XRegExp(HASH_STYLE_PATTERN)
331
+ var hashStyleRegex = new XRegExp(HASH_STYLE_REGEX)
342
332
  var lang = this.getLang()
343
333
  var result
344
334
  while ((result = hashStyleRegex.exec(content)) !== null) {
@@ -355,7 +345,7 @@ File.prototype.extractHashStyleTasks = function (config, pos, content) {
355
345
  const blockEndPos = text.indexOf(lang.block.end)
356
346
  if (blockEndPos > -1) text = text.substring(0, blockEndPos)
357
347
  }
358
- var task = this.newTask({
348
+ var task = this.extractTaskWithDescription({
359
349
  config,
360
350
  list,
361
351
  text,
@@ -375,7 +365,7 @@ File.prototype.extractHashStyleTasks = function (config, pos, content) {
375
365
 
376
366
  File.prototype.extractLinkStyleTasks = function (config, pos, content) {
377
367
  var self = this
378
- var linkStyleRegex = new XRegExp(LINK_STYLE_PATTERN)
368
+ var linkStyleRegex = new XRegExp(LINK_STYLE_REGEX)
379
369
  var result
380
370
  while ((result = linkStyleRegex.exec(content)) !== null) {
381
371
  let [match, before, rawTask, text, list, colon, order] = result
@@ -390,7 +380,7 @@ File.prototype.extractLinkStyleTasks = function (config, pos, content) {
390
380
  log(result)
391
381
  log('list:%s text:%s order:%d line:%d', list, text, order, line)
392
382
 
393
- var task = self.newTask({
383
+ var task = self.extractTaskWithDescription({
394
384
  config,
395
385
  list,
396
386
  text,
@@ -431,7 +421,7 @@ File.prototype.extractCheckboxTasks = function (config, pos, content) {
431
421
  const posInContent = result.index + pos + beforeLength
432
422
  var line = this.getLineNumber(posInContent)
433
423
  if (hasTaskAtLine(line)) continue
434
- var task = this.newTask({
424
+ var task = this.extractTaskWithDescription({
435
425
  config,
436
426
  list,
437
427
  text,
@@ -620,7 +610,7 @@ File.prototype.deleteTask = function (task, config) {
620
610
  config
621
611
  )
622
612
  } else if (task.type === Task.Types.HASHTAG) {
623
- this.deleteCodeOrHashTask(HASH_STYLE_PATTERN, task, config)
613
+ this.deleteCodeOrHashTask(HASH_STYLE_REGEX, task, config)
624
614
  }
625
615
  } else {
626
616
  const fileContent = this.getContentLines()
@@ -661,22 +651,7 @@ File.prototype.modifyTask = function (task, config, noEmit) {
661
651
  } else {
662
652
  task.updateContent()
663
653
  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
654
  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
655
  const linesBefore = fileContent.slice(0, task.line - 1)
681
656
  if (this.isCodeFile()) {
682
657
  const linesBeforeLength = linesBefore.join(lineEnd).length
@@ -686,19 +661,19 @@ File.prototype.modifyTask = function (task, config, noEmit) {
686
661
  }
687
662
  }
688
663
 
689
- const linesAfter = fileContent.slice(task.lastLine);
690
-
664
+ const linesAfter = fileContent.slice(task.line)
691
665
  const newContent = [
692
666
  ...linesBefore,
693
667
  text,
694
- ...description,
695
668
  ...linesAfter,
696
669
  ].join(lineEnd)
697
670
 
698
- if (this.getContent().trim() === newContent.trim()) return
671
+ const contentBeforeUpdate = this.getContent()
699
672
  this.setContent(newContent)
673
+ this.modifyDescription(task, config)
674
+ if (contentBeforeUpdate.trim() == this.getContent().trim()) return
675
+
700
676
  this.setModified(true)
701
- task.lastLine = task.line + description.length
702
677
  if (!noEmit) {
703
678
  this.emit('task.modified', task)
704
679
  this.emit('file.modified', this)
@@ -737,7 +712,7 @@ File.prototype.modifyCodeOrHashTask = function (re, task, config, noEmit) {
737
712
  if (index < 0) index = 0
738
713
  var beforeContent = this.getContent().substring(0, pos)
739
714
  var afterContent = this.getContent().substring(re.lastIndex)
740
- if (task.inCodeBlock) {
715
+ if (task.inBlockComment) {
741
716
  var blockEnd = text.indexOf(lang.block.end)
742
717
  if (blockEnd > -1) {
743
718
  const desc =
@@ -797,25 +772,44 @@ File.prototype.modifyTaskFromHtml = function (task, html) {
797
772
  }
798
773
 
799
774
  File.prototype.modifyTaskFromContent = function (task, content, config) {
775
+ content = trimBlankLines(content, this.isCodeFile())
800
776
  task.updateFromContent(content)
801
777
  this.modifyTask(task, config)
802
778
  }
803
779
 
780
+ const trimBlankLines = File.trimBlankLines = function (content, isCodeFile) {
781
+ if (isCodeFile) {
782
+ // replace all occurrences of two empty lines in a row with a single empty line
783
+ return content.replace(/\n\s*\n/g, '\n')
784
+ }
785
+ return content.replace(/\n\s*\n\s*\n/g, '\n\n')
786
+ }
787
+
804
788
  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)
789
+ // DOING This should be split out by type (code, markdown, etc)
790
+ // #urgent
791
+ // <!-- order:-10 -->
807
792
  const taskStart = this.getLinePos(task.line)
793
+ const descStart = this.getLinePos(task.line + 1)
808
794
  const contentWithTask = this.getContent().substring(taskStart)
809
- let { rawDescription, preserveBlankLines, hasCardEndTag } =
810
- this.getRawDescription({
795
+ let {
796
+ rawTaskContentLines,
797
+ taskContentLines,
798
+ isWrappedWithCardTag,
799
+ trailingBlankLines
800
+ } =
801
+ this.getTaskContent({
811
802
  config,
812
803
  content: contentWithTask,
813
- inCodeBlock: task.inCodeBlock,
804
+ inBlockComment: task.inBlockComment,
814
805
  beforeText: task.beforeText,
815
806
  })
807
+
816
808
  let beforeDescContent = this.getContent().substring(0, descStart)
809
+
817
810
  if (descStart === this.getContent().length && !beforeDescContent.endsWith(lf))
818
811
  beforeDescContent += lineEnd
812
+
819
813
  let content = this.getContent().substring(descStart)
820
814
  let spaces = ''
821
815
  if (this.isCodeFile() && !this.isMarkDownFile()) {
@@ -830,32 +824,34 @@ File.prototype.modifyDescription = function (task, config) {
830
824
  )
831
825
  // BACKLOG:-30 Allow this to pad for code files as well
832
826
  description = File.padDescription(description, task.beforeText).join(lineEnd)
833
- if (rawDescription.length === 0 && description.length > 0) {
827
+ if (taskContentLines.length === 0 && description.length > 0) {
834
828
  beforeDescContent += description + lineEnd
835
829
  } else {
836
- if (preserveBlankLines) {
837
- rawDescription.unshift(START_TAG)
838
- if (hasCardEndTag) rawDescription.push(END_TAG)
839
- }
840
- rawDescription = rawDescription.join(lineEnd)
830
+ let rawTaskContent = rawTaskContentLines.join(lineEnd)
831
+ // Add card terminator to description
841
832
  if (
842
833
  this.isMarkDownFile() &&
843
834
  description.length > 0 &&
844
- (tools.hasBlankLines(description) || hasCardEndTag)
835
+ (tools.hasBlankLines(description) || isWrappedWithCardTag || trailingBlankLines)
845
836
  ) {
846
- task.preserveBlankLines = true
847
- description = `${START_TAG}${lineEnd}${description}${lineEnd}${END_TAG}`
837
+ task.isWrappedWithCardTag = isWrappedWithCardTag // DOING Find out where this is used and stop using it
838
+ // <!-- order:-30 -->
839
+ const blankLinesToAdd = isWrappedWithCardTag ? 2 : trailingBlankLines
840
+ description = `${description}${lineEnd.repeat(blankLinesToAdd)}`
848
841
  }
842
+
843
+ // Handle code file tasks
849
844
  if (
850
845
  task.singleLineBlockComment &&
851
- rawDescription.length > 0 &&
846
+ rawTaskContent.length > 0 &&
852
847
  description.length === 0
853
848
  ) {
854
- rawDescription += lineEnd
849
+ description = lineEnd + description
855
850
  }
856
- content = content.replace(rawDescription, description)
851
+ content = content.replace(rawTaskContent, description)
857
852
  }
858
853
 
854
+ task.lastLine = task.line + description.split(lineEnd).length
859
855
  this.setContent(beforeDescContent + content)
860
856
  }
861
857
 
@@ -863,7 +859,7 @@ File.padDescription = function (description = [], beforeText = '') {
863
859
  return Task.padDescription(description, beforeText)
864
860
  }
865
861
 
866
- File.prototype.newTask = function ({
862
+ File.prototype.extractTaskWithDescription = function ({
867
863
  taskStartOnLine,
868
864
  rawTask,
869
865
  config,
@@ -874,7 +870,7 @@ File.prototype.newTask = function ({
874
870
  type,
875
871
  hasColon,
876
872
  content,
877
- inCodeBlock,
873
+ inBlockComment,
878
874
  singleLineBlockComment,
879
875
  pos,
880
876
  }) {
@@ -883,20 +879,20 @@ File.prototype.newTask = function ({
883
879
  let description = []
884
880
  // BACKLOG:-230 ## add beforeText for code files
885
881
  const beforeText = this.getBeforeText(line, pos)
886
- let { rawDescription, preserveBlankLines } = this.getRawDescription({
882
+ let { rawTaskContentLines, taskContentLines, isWrappedWithCardTag } = this.getTaskContent({
887
883
  config,
888
884
  content,
889
- inCodeBlock,
885
+ inBlockComment,
890
886
  beforeText,
891
887
  })
892
888
 
893
889
  if (!singleLineBlockComment) {
894
890
  description = this.isCodeFile()
895
- ? rawDescription.map((line) => this.trimCommentChars(line))
896
- : _clone(rawDescription)
891
+ ? taskContentLines.map((line) => this.trimCommentChars(line))
892
+ : _clone(taskContentLines)
897
893
  }
898
894
  description = Task.trimDescription(description, beforeText)
899
- const descriptionStartsWith = this.getDescriptionChars(inCodeBlock)
895
+ const descriptionStartsWith = this.getDescriptionChars(inBlockComment)
900
896
  const frontMatter = this.frontMatter
901
897
  text = this.trimCommentBlockEnd(text)
902
898
  let commentStartOnLine =
@@ -908,12 +904,12 @@ File.prototype.newTask = function ({
908
904
  {
909
905
  pos,
910
906
  frontMatter,
911
- inCodeBlock,
907
+ inBlockComment,
912
908
  singleLineBlockComment,
913
909
  rawTask,
914
910
  text,
915
911
  list,
916
- rawDescription,
912
+ rawTaskContentLines,
917
913
  description,
918
914
  descriptionStartsWith,
919
915
  taskStartOnLine,
@@ -926,7 +922,7 @@ File.prototype.newTask = function ({
926
922
  source: self.getSource(),
927
923
  type,
928
924
  beforeText,
929
- preserveBlankLines,
925
+ isWrappedWithCardTag,
930
926
  },
931
927
  this.project
932
928
  )
@@ -1098,8 +1094,8 @@ File.prototype.ignoreCodeBlocks = function () {
1098
1094
  // BACKLOG:-100 This is not used, but we need a way to ignore tasks in code blocks. Maybe save position ranges
1099
1095
  var cleanContent = this.content
1100
1096
  var replacer = function (block) {
1101
- block = block.replace(new XRegExp(LINK_STYLE_PATTERN), '**TASK**')
1102
- block = block.replace(new XRegExp(HASH_STYLE_PATTERN), '**TASK**')
1097
+ block = block.replace(new XRegExp(LINK_STYLE_REGEX), '**TASK**')
1098
+ block = block.replace(new XRegExp(HASH_STYLE_REGEX), '**TASK**')
1103
1099
  return block
1104
1100
  }
1105
1101
 
@@ -1135,105 +1131,31 @@ File.prototype.getBeforeText = function (line, pos) {
1135
1131
  : this.content.substring(this.getLinePos(line), pos)
1136
1132
  }
1137
1133
 
1138
- File.prototype.getDescriptionChars = function (inCodeBlock) {
1134
+ File.prototype.getDescriptionChars = function (inBlockComment) {
1139
1135
  if (!this.isCodeFile()) return ''
1140
1136
  const lang = this.getLang()
1141
- if (inCodeBlock && lang.block && lang.block.ignore) return lang.block.ignore
1137
+ if (inBlockComment && lang.block && lang.block.ignore) return lang.block.ignore
1142
1138
  return lang.symbol
1143
1139
  }
1144
1140
 
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
1141
  File.prototype.getTaskContent = function ({
1174
1142
  config,
1175
1143
  content,
1176
- inCodeBlock,
1144
+ inBlockComment,
1177
1145
  beforeText,
1178
1146
  }) {
1179
- const commentLines = []
1180
- let lines = File.getContentLines(content)
1181
- const preserveBlankLines =
1182
- this.isMarkDownFile() && lines.length > 1 && lines[1].trim() === START_TAG
1147
+ const isMarkDownFile = this.isMarkDownFile()
1148
+ const isCodeFile = this.isCodeFile()
1183
1149
  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 }
1150
+ const fileType = isMarkDownFile ? FILE_TYPES.MARKDOWN : isCodeFile ? FILE_TYPES.CODE : undefined
1151
+ return getTaskContent({
1152
+ config,
1153
+ content,
1154
+ inBlockComment,
1155
+ beforeText,
1156
+ lang,
1157
+ fileType,
1158
+ })
1237
1159
  }
1238
1160
 
1239
1161
  File.prototype.isCheckBoxTask = function (config, line, beforeText) {
@@ -1322,9 +1244,9 @@ File.prototype.hasCodeStyleTask = function (config, text) {
1322
1244
  File.prototype.hasTaskInText = function (config, text) {
1323
1245
  return (
1324
1246
  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)) &&
1247
+ ((new XRegExp(HASH_STYLE_REGEX).test(text) ||
1248
+ new XRegExp(HASH_STYLE_META_ORDER_REGEX).test(text) ||
1249
+ new XRegExp(LINK_STYLE_REGEX).test(text)) &&
1328
1250
  config.lists.find((list) => text.includes(`#${list.name}`)))
1329
1251
  )
1330
1252
  }
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.37.1",
3
+ "version": "1.40.0",
4
4
  "description": "imdone-core",
5
5
  "main": "index.js",
6
6
  "bin": {