imdone-core 1.38.0 → 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.
- package/lib/adapters/parsers/task/CardContentParser.js +256 -0
- package/lib/file.js +93 -171
- package/lib/repository.js +2 -5
- package/lib/task.js +9 -8
- package/package.json +1 -1
|
@@ -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/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
|
|
285
|
+
const inBlockComment = this.isInBlockComment(content)
|
|
296
286
|
const singleLineBlockComment =
|
|
297
|
-
|
|
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 =
|
|
308
|
+
const taskStartOnLine = inBlockComment
|
|
319
309
|
? posInContent - charsInFront - linePos
|
|
320
310
|
: pos - linePos
|
|
321
|
-
var task = self.
|
|
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
|
-
|
|
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(
|
|
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.
|
|
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(
|
|
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.
|
|
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.
|
|
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(
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
//
|
|
806
|
-
|
|
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 {
|
|
810
|
-
|
|
795
|
+
let {
|
|
796
|
+
rawTaskContentLines,
|
|
797
|
+
taskContentLines,
|
|
798
|
+
isWrappedWithCardTag,
|
|
799
|
+
trailingBlankLines
|
|
800
|
+
} =
|
|
801
|
+
this.getTaskContent({
|
|
811
802
|
config,
|
|
812
803
|
content: contentWithTask,
|
|
813
|
-
|
|
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 (
|
|
827
|
+
if (taskContentLines.length === 0 && description.length > 0) {
|
|
834
828
|
beforeDescContent += description + lineEnd
|
|
835
829
|
} else {
|
|
836
|
-
|
|
837
|
-
|
|
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) ||
|
|
835
|
+
(tools.hasBlankLines(description) || isWrappedWithCardTag || trailingBlankLines)
|
|
845
836
|
) {
|
|
846
|
-
task.
|
|
847
|
-
|
|
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
|
-
|
|
846
|
+
rawTaskContent.length > 0 &&
|
|
852
847
|
description.length === 0
|
|
853
848
|
) {
|
|
854
|
-
|
|
849
|
+
description = lineEnd + description
|
|
855
850
|
}
|
|
856
|
-
content = content.replace(
|
|
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.
|
|
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
|
-
|
|
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 {
|
|
882
|
+
let { rawTaskContentLines, taskContentLines, isWrappedWithCardTag } = this.getTaskContent({
|
|
887
883
|
config,
|
|
888
884
|
content,
|
|
889
|
-
|
|
885
|
+
inBlockComment,
|
|
890
886
|
beforeText,
|
|
891
887
|
})
|
|
892
888
|
|
|
893
889
|
if (!singleLineBlockComment) {
|
|
894
890
|
description = this.isCodeFile()
|
|
895
|
-
?
|
|
896
|
-
: _clone(
|
|
891
|
+
? taskContentLines.map((line) => this.trimCommentChars(line))
|
|
892
|
+
: _clone(taskContentLines)
|
|
897
893
|
}
|
|
898
894
|
description = Task.trimDescription(description, beforeText)
|
|
899
|
-
const descriptionStartsWith = this.getDescriptionChars(
|
|
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
|
-
|
|
907
|
+
inBlockComment,
|
|
912
908
|
singleLineBlockComment,
|
|
913
909
|
rawTask,
|
|
914
910
|
text,
|
|
915
911
|
list,
|
|
916
|
-
|
|
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
|
-
|
|
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(
|
|
1102
|
-
block = block.replace(new XRegExp(
|
|
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 (
|
|
1134
|
+
File.prototype.getDescriptionChars = function (inBlockComment) {
|
|
1139
1135
|
if (!this.isCodeFile()) return ''
|
|
1140
1136
|
const lang = this.getLang()
|
|
1141
|
-
if (
|
|
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
|
-
|
|
1144
|
+
inBlockComment,
|
|
1177
1145
|
beforeText,
|
|
1178
1146
|
}) {
|
|
1179
|
-
const
|
|
1180
|
-
|
|
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
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
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(
|
|
1326
|
-
new XRegExp(
|
|
1327
|
-
new XRegExp(
|
|
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
|
-
|
|
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.
|
|
48
|
+
this.inBlockComment = obj.inBlockComment
|
|
49
49
|
this.singleLineBlockComment = obj.singleLineBlockComment
|
|
50
50
|
this.description = obj.description || []
|
|
51
|
-
this.
|
|
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.
|
|
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.
|
|
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 (
|
|
227
|
+
Task.trimDescription = function (rawTaskContentLines, beforeText) {
|
|
227
228
|
const checkedData = Task.getCheckedData(beforeText)
|
|
228
229
|
if (checkedData) {
|
|
229
|
-
|
|
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
|
|
236
|
+
return rawTaskContentLines
|
|
236
237
|
}
|
|
237
238
|
|
|
238
239
|
Task.prototype.getCheckedData = function () {
|