imdone-core 2.5.3 → 2.6.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/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 2.6.0
2
+
3
+ - 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.
4
+
1
5
  ## 2.5.3
2
6
 
3
7
  - 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,16 @@ 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
+
86
96
  Imdone uses todo.txt +project/tag @context and meta:data
87
97
 
88
98
  #### 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 (endsWithDoubleBlank) trailingBlankLines = 2
302
- else if (lastLineBlank) trailingBlankLines = 1
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
@@ -950,6 +950,11 @@ export class File extends Emitter {
950
950
  beforeDescContent += description + lineEnd
951
951
  } else {
952
952
  let rawTaskContent = rawTaskContentLines.join(lineEnd)
953
+ if (config.endTaskAtEndOfFile && !this.isCodeFile() && !isWrappedWithCardTag) {
954
+ // Blank lines belong to the task in EOF mode; retain its existing boundary.
955
+ const boundary = rawTaskContent.match(/(?:\n[\t ]*)+$/)?.[0] || ''
956
+ if (!description.endsWith(lineEnd)) description += boundary
957
+ }
953
958
  // Add card terminator to description
954
959
  if (
955
960
  this.isMarkDownFile() &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imdone-core",
3
- "version": "2.5.3",
3
+ "version": "2.6.0",
4
4
  "description": "imdone-core",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,5 +1,4 @@
1
1
  export class ReadStreamMarkdownParser extends ReadStreamTaskParser {
2
- constructor(readStream: any, config: any);
3
2
  frontmatterLines: any[];
4
3
  collectingFrontmatter: boolean;
5
4
  frontmatterClosed: boolean;
@@ -1,6 +1,7 @@
1
1
  export class ReadStreamTaskParser {
2
- constructor(readStream: any);
2
+ constructor(readStream: any, config: any);
3
3
  readStream: any;
4
+ config: any;
4
5
  task: any;
5
6
  line: IteratorResult<string, any> | null;
6
7
  taskParsers: any[];
package/types/config.d.ts CHANGED
@@ -41,6 +41,7 @@ export class Config {
41
41
  isAddCompletedMeta(): any;
42
42
  set customCardTerminator(terminator: any);
43
43
  get customCardTerminator(): any;
44
+ get endTaskAtEndOfFile(): boolean;
44
45
  get markdownOnly(): any;
45
46
  get views(): any;
46
47
  get appendNewCardsTo(): any;