imdone-core 2.5.3 → 2.6.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.
- package/CHANGELOG.md +8 -0
- package/README.md +12 -0
- package/lib/adapters/parsers/file/ReadStreamMarkdownParser.js +7 -0
- package/lib/adapters/parsers/file/ReadStreamTaskParser.js +4 -3
- package/lib/adapters/parsers/task/CardContentParser.js +7 -5
- package/lib/config.js +4 -0
- package/lib/file.js +8 -2
- package/lib/task.js +4 -5
- package/package.json +1 -1
- package/types/adapters/parsers/file/ReadStreamMarkdownParser.d.ts +0 -1
- package/types/adapters/parsers/file/ReadStreamTaskParser.d.ts +2 -1
- package/types/card.d.ts +3 -1
- package/types/config.d.ts +1 -0
- package/types/file.d.ts +3 -1
- package/types/task.d.ts +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
## 2.6.1
|
|
2
|
+
|
|
3
|
+
- Preserve repeated and whitespace-only blank lines during Markdown/text task write-back when EOF task ending is enabled, including file-watcher transformations after an editor save. Default cleanup and code-file behavior are unchanged.
|
|
4
|
+
|
|
5
|
+
## 2.6.0
|
|
6
|
+
|
|
7
|
+
- Add opt-in `settings.cards.endTaskAtEndOfFile` for Markdown/text tasks to retain blank lines through EOF, while preserving next-task and explicit termination boundaries. Existing blank-line behavior remains the default; code-file parsing is unchanged.
|
|
8
|
+
|
|
1
9
|
## 2.5.3
|
|
2
10
|
|
|
3
11
|
- Reorder configured lists atomically with `repository.moveList`, preserving every list field and unrelated configuration, rolling back failed saves, and emitting `list.modified` only after persistence succeeds.
|
package/README.md
CHANGED
|
@@ -83,6 +83,18 @@ Task descriptions are captured from the lines that follow a task. In code files
|
|
|
83
83
|
-->
|
|
84
84
|
|
|
85
85
|
### todo.txt syntax examples
|
|
86
|
+
For Markdown and text tasks that should retain all blank lines through the end of their file, opt into EOF task ending in `.imdone/config.yml`:
|
|
87
|
+
|
|
88
|
+
```yaml
|
|
89
|
+
settings:
|
|
90
|
+
cards:
|
|
91
|
+
endTaskAtEndOfFile: true
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
This ignores blank-line termination regardless of blank-line counts. A following task, custom text terminator, list boundary or explicit `</card>` still ends the task. Code-file parsing is unchanged. The setting defaults to `false`, preserving the existing two-blank-line behavior.
|
|
95
|
+
|
|
96
|
+
In 2.6.1 and newer, EOF mode also preserves repeated and whitespace-only blank lines during task write-back and file-watcher transformations.
|
|
97
|
+
|
|
86
98
|
Imdone uses todo.txt +project/tag @context and meta:data
|
|
87
99
|
|
|
88
100
|
#### Create date
|
|
@@ -32,6 +32,13 @@ export class ReadStreamMarkdownParser extends ReadStreamTaskParser {
|
|
|
32
32
|
this.updateCodeFenceState(line)
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
isEndOfTask(line) {
|
|
36
|
+
const terminator = this.config?.customCardTerminator
|
|
37
|
+
if (this.task && this.config?.endTaskAtEndOfFile &&
|
|
38
|
+
typeof terminator === 'string' && terminator.trim() && line.trim() === terminator) return true
|
|
39
|
+
return super.isEndOfTask(line)
|
|
40
|
+
}
|
|
41
|
+
|
|
35
42
|
shouldParseTaskLine() {
|
|
36
43
|
return !this.ignoredByFrontmatter &&
|
|
37
44
|
!this.currentLineIsFrontmatter &&
|
|
@@ -4,8 +4,9 @@ import { logger } from '../../logger.js';
|
|
|
4
4
|
const blankLinesToEndTask = 2
|
|
5
5
|
|
|
6
6
|
export class ReadStreamTaskParser {
|
|
7
|
-
constructor(readStream) {
|
|
7
|
+
constructor(readStream, config) {
|
|
8
8
|
this.readStream = readStream
|
|
9
|
+
this.config = config
|
|
9
10
|
this.task = null
|
|
10
11
|
this.line = null
|
|
11
12
|
this.taskParsers = []
|
|
@@ -78,7 +79,7 @@ export class ReadStreamTaskParser {
|
|
|
78
79
|
|
|
79
80
|
this.nextTask = this.getTaskFromLine(line)
|
|
80
81
|
|
|
81
|
-
if (this.blankLines > blankLinesToEndTask - 1) {
|
|
82
|
+
if (!this.config?.endTaskAtEndOfFile && this.blankLines > blankLinesToEndTask - 1) {
|
|
82
83
|
this.nextTask = undefined
|
|
83
84
|
return true
|
|
84
85
|
}
|
|
@@ -111,7 +112,7 @@ export class ReadStreamTaskParser {
|
|
|
111
112
|
if (!this.task) return
|
|
112
113
|
|
|
113
114
|
// Remove the empty strings from the end of the task description
|
|
114
|
-
while (this.task.description[this.task.description.length - 1] === '') {
|
|
115
|
+
while (!this.config?.endTaskAtEndOfFile && this.task.description[this.task.description.length - 1] === '') {
|
|
115
116
|
this.task.description.pop()
|
|
116
117
|
}
|
|
117
118
|
this.task.lastLine = this.task.line + this.task.description.length
|
|
@@ -56,7 +56,7 @@ function getContentLines(content) {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
function isCustomCardTerminator(config, line) {
|
|
59
|
-
return config.customCardTerminator
|
|
59
|
+
return typeof config.customCardTerminator === 'string'
|
|
60
60
|
&& config.customCardTerminator.trim()
|
|
61
61
|
&& line
|
|
62
62
|
&& line.trim() === config.customCardTerminator
|
|
@@ -290,7 +290,7 @@ function getTextFileTaskContent ({
|
|
|
290
290
|
|
|
291
291
|
const thisLineBlank = line.trim() === ''
|
|
292
292
|
|
|
293
|
-
if (thisLineBlank && lastLineBlank) {
|
|
293
|
+
if (!config.endTaskAtEndOfFile && thisLineBlank && lastLineBlank) {
|
|
294
294
|
endsWithDoubleBlank = true
|
|
295
295
|
break
|
|
296
296
|
}
|
|
@@ -298,8 +298,10 @@ function getTextFileTaskContent ({
|
|
|
298
298
|
lastLineBlank = thisLineBlank
|
|
299
299
|
}
|
|
300
300
|
|
|
301
|
-
if (
|
|
302
|
-
|
|
301
|
+
if (!config.endTaskAtEndOfFile) {
|
|
302
|
+
if (endsWithDoubleBlank) trailingBlankLines = 2
|
|
303
|
+
else if (lastLineBlank) trailingBlankLines = 1
|
|
304
|
+
}
|
|
303
305
|
|
|
304
306
|
// Remove extra block comment tokens
|
|
305
307
|
if (
|
|
@@ -364,4 +366,4 @@ export function toNumber(value) {
|
|
|
364
366
|
if (value == null || value == undefined) return undefined; // Convert null or undefined to undefined
|
|
365
367
|
const num = Number(value);
|
|
366
368
|
return isNaN(num) ? undefined : num; // Return 0 if conversion fails, otherwise return the number
|
|
367
|
-
}
|
|
369
|
+
}
|
package/lib/config.js
CHANGED
|
@@ -174,6 +174,10 @@ export class Config {
|
|
|
174
174
|
return this?.cards?.customCardTerminator
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
+
get endTaskAtEndOfFile() {
|
|
178
|
+
return this?.cards?.endTaskAtEndOfFile === true
|
|
179
|
+
}
|
|
180
|
+
|
|
177
181
|
set customCardTerminator(terminator) {
|
|
178
182
|
this.settings.cards.customCardTerminator = terminator
|
|
179
183
|
}
|
package/lib/file.js
CHANGED
|
@@ -899,8 +899,9 @@ export class File extends Emitter {
|
|
|
899
899
|
}
|
|
900
900
|
|
|
901
901
|
modifyTaskFromContent (task, content, config) {
|
|
902
|
-
|
|
903
|
-
|
|
902
|
+
const preserveBlankLines = config.endTaskAtEndOfFile && !this.isCodeFile()
|
|
903
|
+
if (!preserveBlankLines) content = trimBlankLines(content, this.isCodeFile())
|
|
904
|
+
task.updateFromContent(content, { preserveBlankLines })
|
|
904
905
|
this.modifyTask(task, config)
|
|
905
906
|
}
|
|
906
907
|
|
|
@@ -950,6 +951,11 @@ export class File extends Emitter {
|
|
|
950
951
|
beforeDescContent += description + lineEnd
|
|
951
952
|
} else {
|
|
952
953
|
let rawTaskContent = rawTaskContentLines.join(lineEnd)
|
|
954
|
+
if (config.endTaskAtEndOfFile && !this.isCodeFile() && !isWrappedWithCardTag) {
|
|
955
|
+
// Blank lines belong to the task in EOF mode; retain its existing boundary.
|
|
956
|
+
const boundary = rawTaskContent.match(/(?:\n[\t ]*)+$/)?.[0] || ''
|
|
957
|
+
if (!description.endsWith(lineEnd)) description += boundary
|
|
958
|
+
}
|
|
953
959
|
// Add card terminator to description
|
|
954
960
|
if (
|
|
955
961
|
this.isMarkDownFile() &&
|
package/lib/task.js
CHANGED
|
@@ -165,12 +165,11 @@ export class Task {
|
|
|
165
165
|
this.content = this.text + lineEnd + this.descriptionString
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
-
updateFromContent(content) {
|
|
169
|
-
|
|
170
|
-
content = content.replace(/\n\s*\n\s*\n/g, '\n\n')
|
|
168
|
+
updateFromContent(content, { preserveBlankLines = false } = {}) {
|
|
169
|
+
if (!preserveBlankLines) content = content.replace(/\n\s*\n\s*\n/g, '\n\n')
|
|
171
170
|
let lines = eol.split(content)
|
|
172
171
|
this.text = lines.shift().trimStart()
|
|
173
|
-
if (
|
|
172
|
+
if (!preserveBlankLines && lines.length > 1 && lines[lines.length - 1].trim() === '') lines.pop()
|
|
174
173
|
this.description = lines
|
|
175
174
|
this.parse()
|
|
176
175
|
}
|
|
@@ -913,4 +912,4 @@ export class Task {
|
|
|
913
912
|
task.getId() == this.getId()
|
|
914
913
|
)
|
|
915
914
|
}
|
|
916
|
-
}
|
|
915
|
+
}
|
package/package.json
CHANGED
package/types/card.d.ts
CHANGED
|
@@ -151,7 +151,9 @@ export function newCard(task: any, _project: any, dontParse: any): {
|
|
|
151
151
|
};
|
|
152
152
|
getContent(): any;
|
|
153
153
|
updateContent(): void;
|
|
154
|
-
updateFromContent(content: any
|
|
154
|
+
updateFromContent(content: any, { preserveBlankLines }?: {
|
|
155
|
+
preserveBlankLines?: boolean | undefined;
|
|
156
|
+
}): void;
|
|
155
157
|
toJSON(): /*elided*/ any & {
|
|
156
158
|
allTags: any;
|
|
157
159
|
allContext: any;
|
package/types/config.d.ts
CHANGED
package/types/file.d.ts
CHANGED
|
@@ -249,7 +249,9 @@ export class File extends Emitter<[never]> {
|
|
|
249
249
|
};
|
|
250
250
|
getContent(): any;
|
|
251
251
|
updateContent(): void;
|
|
252
|
-
updateFromContent(content: any
|
|
252
|
+
updateFromContent(content: any, { preserveBlankLines }?: {
|
|
253
|
+
preserveBlankLines?: boolean | undefined;
|
|
254
|
+
}): void;
|
|
253
255
|
toJSON(): /*elided*/ any & {
|
|
254
256
|
allTags: any;
|
|
255
257
|
allContext: any;
|
package/types/task.d.ts
CHANGED
|
@@ -128,7 +128,9 @@ export class Task {
|
|
|
128
128
|
};
|
|
129
129
|
getContent(): any;
|
|
130
130
|
updateContent(): void;
|
|
131
|
-
updateFromContent(content: any
|
|
131
|
+
updateFromContent(content: any, { preserveBlankLines }?: {
|
|
132
|
+
preserveBlankLines?: boolean | undefined;
|
|
133
|
+
}): void;
|
|
132
134
|
toJSON(): this & {
|
|
133
135
|
allTags: any;
|
|
134
136
|
allContext: any;
|