imdone-core 1.54.11 → 1.54.13

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,8 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(npm test:*)",
5
+ "Bash(node test-debug.js:*)"
6
+ ]
7
+ }
8
+ }
package/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## 1.54.13
2
+
3
+ - **Enhancement**: `customCardTerminator` now accepts numeric values to specify the number of consecutive blank lines needed to terminate a card (e.g., `customCardTerminator: 3` requires 3 blank lines)
4
+ - **Enhancement**: `repo.resume()` now automatically calls `refresh()` to pick up file changes that occurred while the repository was paused
5
+ - **Enhancement**: `File.trimBlankLines()` now respects the `customCardTerminator` config setting, preserving the appropriate number of blank lines in card content
6
+ - **Fix**: `Task.updateFromContent()` now respects the `customCardTerminator` config setting when collapsing consecutive blank lines, ensuring consistency across all content operations
7
+ - **Tests**: Added comprehensive test coverage for numeric `customCardTerminator` values, `trimBlankLines` behavior, `updateFromContent` behavior, and `resume()` refresh functionality
8
+ - **Refactor**: Improved code readability by replacing comments with self-documenting function names and descriptive constants
9
+
1
10
  ## 1.28.8
2
11
  - Allow Card and Board actions to return a value
3
12
 
@@ -55,10 +55,28 @@ function getContentLines(content) {
55
55
  }
56
56
 
57
57
  function isCustomCardTerminator(config, line) {
58
- return config.customCardTerminator
59
- && config.customCardTerminator.trim()
60
- && line
61
- && line.trim() === config.customCardTerminator
58
+ if (!config.customCardTerminator) return false
59
+
60
+ const isStringTerminator = typeof config.customCardTerminator === 'string'
61
+ && config.customCardTerminator.trim()
62
+
63
+ if (isStringTerminator) {
64
+ const terminatorMatches = line && line.trim() === config.customCardTerminator.trim()
65
+ return terminatorMatches
66
+ }
67
+
68
+ return false
69
+ }
70
+
71
+ const DEFAULT_BLANK_LINE_TERMINATOR_COUNT = 2
72
+
73
+ function getBlankLineTerminatorCount(config) {
74
+ const hasNumericTerminator = typeof config.customCardTerminator === 'number'
75
+ && config.customCardTerminator > 0
76
+
77
+ return hasNumericTerminator
78
+ ? config.customCardTerminator
79
+ : DEFAULT_BLANK_LINE_TERMINATOR_COUNT
62
80
  }
63
81
 
64
82
  function isCheckBoxTask (config, line, beforeText) {
@@ -256,8 +274,8 @@ function getTextFileTaskContent ({
256
274
  const isTaskInList = isMarkDownFile && isBeforeTextMarkdownList(beforeText)
257
275
  let openBlockCommentTokens = 0
258
276
  let closeBlockCommentTokens = 0
259
- let lastLineBlank = false
260
- let endsWithDoubleBlank = false
277
+ const blankLineTerminatorCount = getBlankLineTerminatorCount(config)
278
+ let consecutiveBlankLines = 0
261
279
  let trailingBlankLines = 0
262
280
  const rawTaskContentLines = []
263
281
  const taskContentLines = []
@@ -277,7 +295,7 @@ function getTextFileTaskContent ({
277
295
  if (isCheckBoxTask(config, line, beforeText)) break
278
296
  if (isTaskInList && isLineOutsideMarkdownList(line, beforeText)) break
279
297
  if (isCustomCardTerminator(config, line)) break
280
-
298
+
281
299
  rawTaskContentLines.push(line)
282
300
  taskContentLines.push(`${line}`)
283
301
 
@@ -285,20 +303,28 @@ function getTextFileTaskContent ({
285
303
  if (isMarkDownFile && line.trim().indexOf('<!--') > -1)
286
304
  openBlockCommentTokens++
287
305
  if (isMarkDownFile && line.trim().indexOf('-->') > -1)
288
- closeBlockCommentTokens++
306
+ closeBlockCommentTokens++
289
307
 
290
308
  const thisLineBlank = line.trim() === ''
291
309
 
292
- if (thisLineBlank && lastLineBlank) {
293
- endsWithDoubleBlank = true
294
- break
310
+ if (thisLineBlank) {
311
+ consecutiveBlankLines++
312
+ if (consecutiveBlankLines >= blankLineTerminatorCount) {
313
+ trailingBlankLines = blankLineTerminatorCount
314
+ break
315
+ }
316
+ } else {
317
+ consecutiveBlankLines = 0
295
318
  }
296
-
297
- lastLineBlank = thisLineBlank
298
319
  }
299
320
 
300
- if (endsWithDoubleBlank) trailingBlankLines = 2
301
- else if (lastLineBlank) trailingBlankLines = 1
321
+ const reachedEndNaturally = trailingBlankLines === 0
322
+ const hasTrailingBlankLines = consecutiveBlankLines > 0
323
+ const isUsingDefaultTerminator = blankLineTerminatorCount === DEFAULT_BLANK_LINE_TERMINATOR_COUNT
324
+
325
+ if (reachedEndNaturally && hasTrailingBlankLines && isUsingDefaultTerminator) {
326
+ trailingBlankLines = consecutiveBlankLines
327
+ }
302
328
 
303
329
  // Remove extra block comment tokens
304
330
  if (
package/lib/constants.js CHANGED
@@ -50,13 +50,14 @@ module.exports = {
50
50
  SORT_FILE: path.join(CONFIG_DIR, 'sort.json'),
51
51
  TEMPLATES_DIR: path.join(CONFIG_DIR, 'templates'),
52
52
  IGNORE_FILE: '.imdoneignore',
53
+ PAUSE_FILE: '.imdonepause',
53
54
  DEFAULT_FILE_PATTERN: '^(readme\\.md|home\\.md|readme\\.w+|home\\.w+)$',
54
55
  DEFAULT_IGNORE,
55
56
  DEFAULT_IGNORE_DIRS,
56
57
  DEFAULT_IGNORE_EXTS,
57
58
  ERRORS: {
58
59
  NOT_A_FILE: 'file must be of type File',
59
- CALLBACK_REQUIRED: 'Last paramter must be a callback function',
60
+ CALLBACK_REQUIRED: 'Last parameter must be a callback function',
60
61
  NO_CONTENT: 'File has no content',
61
62
  LIST_NOT_FOUND: 'List not found',
62
63
  TASK_NOT_FOUND: 'Task not found',
package/lib/file.js CHANGED
@@ -820,17 +820,51 @@ File.prototype.modifyTaskFromHtml = function (task, html) {
820
820
  }
821
821
 
822
822
  File.prototype.modifyTaskFromContent = function (task, content, config) {
823
- content = trimBlankLines(content, this.isCodeFile())
823
+ content = trimBlankLines(content, this.isCodeFile(), config)
824
824
  task.updateFromContent(content)
825
825
  this.modifyTask(task, config)
826
826
  }
827
827
 
828
- const trimBlankLines = File.trimBlankLines = function (content, isCodeFile) {
828
+ const DEFAULT_BLANK_LINE_TERMINATOR_COUNT = 2
829
+ const CODE_FILE_BLANK_LINE_PATTERN = /\n\s*\n/g
830
+ const CODE_FILE_BLANK_LINE_REPLACEMENT = '\n'
831
+
832
+ function getBlankLineTerminatorCountFromConfig(config) {
833
+ const hasNumericTerminator = config
834
+ && typeof config.customCardTerminator === 'number'
835
+ && config.customCardTerminator > 0
836
+
837
+ return hasNumericTerminator
838
+ ? config.customCardTerminator
839
+ : DEFAULT_BLANK_LINE_TERMINATOR_COUNT
840
+ }
841
+
842
+ function buildExcessiveBlankLinePattern(terminatorCount) {
843
+ const minNewlinesToTrim = terminatorCount + 1
844
+ const patternParts = Array(minNewlinesToTrim).fill('\\n\\s*')
845
+ return new RegExp(patternParts.join(''), 'g')
846
+ }
847
+
848
+ function calculateMaxPreservedBlankLines(terminatorCount) {
849
+ return terminatorCount - 1
850
+ }
851
+
852
+ function buildBlankLineReplacement(maxBlankLines) {
853
+ const newlineCount = maxBlankLines + 1
854
+ return '\n'.repeat(newlineCount)
855
+ }
856
+
857
+ const trimBlankLines = File.trimBlankLines = function (content, isCodeFile, config) {
829
858
  if (isCodeFile) {
830
- // replace all occurrences of two empty lines in a row with a single empty line
831
- return content.replace(/\n\s*\n/g, '\n')
832
- }
833
- return content.replace(/\n\s*\n\s*\n/g, '\n\n')
859
+ return content.replace(CODE_FILE_BLANK_LINE_PATTERN, CODE_FILE_BLANK_LINE_REPLACEMENT)
860
+ }
861
+
862
+ const terminatorCount = getBlankLineTerminatorCountFromConfig(config)
863
+ const excessiveBlankLinePattern = buildExcessiveBlankLinePattern(terminatorCount)
864
+ const maxBlankLines = calculateMaxPreservedBlankLines(terminatorCount)
865
+ const replacement = buildBlankLineReplacement(maxBlankLines)
866
+
867
+ return content.replace(excessiveBlankLinePattern, replacement)
834
868
  }
835
869
 
836
870
  File.prototype.modifyDescription = function (task, config) {
@@ -4,7 +4,8 @@ var fsStore = require('./repo-fs-store'),
4
4
  log = require('debug')('imdone-mixins:repo-watched-fs-store'),
5
5
  File = require('../file'),
6
6
  constants = require('../constants'),
7
- sane = require('sane')
7
+ sane = require('sane'),
8
+ path = require('path')
8
9
 
9
10
  module.exports = mixin
10
11
 
@@ -18,6 +19,7 @@ function mixin(repo, fs = require('fs')) {
18
19
  var _destroy = repo.destroy
19
20
  var _init = repo.init
20
21
  var initializing = false
22
+ var paused = false
21
23
 
22
24
  repo.init = function (cb) {
23
25
  if (initializing) {
@@ -33,9 +35,17 @@ function mixin(repo, fs = require('fs')) {
33
35
  initializing = false
34
36
  return cb ? cb(err) : null
35
37
  }
36
- repo.initWatcher(() => {
37
- initializing = false
38
- if (cb) cb(err, files)
38
+ // Check if pause file exists on init
39
+ const pauseFilePath = path.join(repo.path, constants.PAUSE_FILE)
40
+ fs.access(pauseFilePath, fs.constants.F_OK, (accessErr) => {
41
+ if (!accessErr) {
42
+ log('Pause file detected on init. Pausing repository.')
43
+ paused = true
44
+ }
45
+ repo.initWatcher(() => {
46
+ initializing = false
47
+ if (cb) cb(err, files)
48
+ })
39
49
  })
40
50
  })
41
51
  }
@@ -57,9 +67,19 @@ function mixin(repo, fs = require('fs')) {
57
67
  initializing = false
58
68
  return cb ? cb(err) : null
59
69
  }
60
- repo.initWatcher(() => {
61
- initializing = false
62
- return cb ? cb(err, files) : null
70
+ // Check if pause file exists on refresh
71
+ const pauseFilePath = path.join(repo.path, constants.PAUSE_FILE)
72
+ fs.access(pauseFilePath, fs.constants.F_OK, (accessErr) => {
73
+ if (!accessErr) {
74
+ log('Pause file detected on refresh. Pausing repository.')
75
+ paused = true
76
+ } else {
77
+ paused = false
78
+ }
79
+ repo.initWatcher(() => {
80
+ initializing = false
81
+ return cb ? cb(err, files) : null
82
+ })
63
83
  })
64
84
  })
65
85
  }
@@ -72,6 +92,10 @@ function mixin(repo, fs = require('fs')) {
72
92
  return path === constants.IGNORE_FILE
73
93
  }
74
94
 
95
+ var _isImdonePause = function (path) {
96
+ return path === constants.PAUSE_FILE
97
+ }
98
+
75
99
  const onConfigChange = function (file) {
76
100
  console.info('Loading config. Observed a change to:', file)
77
101
  const label = `Refresh repo: ${repo.path}`
@@ -85,6 +109,30 @@ function mixin(repo, fs = require('fs')) {
85
109
 
86
110
  const onIgnoreChange = onConfigChange
87
111
 
112
+ repo.pause = async function () {
113
+ log('Pausing watcher for:', repo.path)
114
+ paused = true
115
+ return Promise.resolve()
116
+ }
117
+
118
+ repo.resume = async function () {
119
+ log('Resuming watcher for:', repo.path)
120
+ paused = false
121
+ return new Promise((resolve, reject) => {
122
+ repo.refresh((err, files) => {
123
+ if (err) {
124
+ console.error('Error refreshing repo on resume', err)
125
+ return reject(err)
126
+ }
127
+ resolve(files)
128
+ })
129
+ })
130
+ }
131
+
132
+ repo.isPaused = function () {
133
+ return paused
134
+ }
135
+
88
136
  repo.initWatcher = function (cb) {
89
137
  console.log('initializing watcher for:', repo.path)
90
138
  repo.watcher = sane(repo.path, {
@@ -98,6 +146,11 @@ function mixin(repo, fs = require('fs')) {
98
146
  onIgnoreChange(file)
99
147
  }
100
148
 
149
+ // Don't ignore the pause file so we can detect it
150
+ if (_isImdonePause(file)) {
151
+ return false
152
+ }
153
+
101
154
  const ignore = !repo.shouldInclude(file)
102
155
  if (!ignore || /^.*\.(jpg|gif|pdf|png)$/i.test(file)) {
103
156
  repo.addFilePath(file)
@@ -119,6 +172,11 @@ function mixin(repo, fs = require('fs')) {
119
172
  })
120
173
  .on('add', function (path, root, stat) {
121
174
  console.log('Watcher received add event for file: ' + path)
175
+ if (_isImdonePause(path)) {
176
+ console.log('Pause file detected. Pausing repository.')
177
+ return repo.pause()
178
+ }
179
+ if (paused) return log('Watcher is paused. Ignoring add event for: ' + path)
122
180
  if (stat.isDirectory()) return
123
181
  var file = repo.getFile(path)
124
182
  if (file === undefined) {
@@ -148,6 +206,8 @@ function mixin(repo, fs = require('fs')) {
148
206
  `Watcher received change event for file: ${path} repoPath: ${repo.path}`,
149
207
  stat.mtime
150
208
  )
209
+ if (_isImdonePause(path)) return log('Ignoring change to pause file')
210
+ if (paused) return log('Watcher is paused. Ignoring change event for: ' + path)
151
211
  if (!path || stat.isDirectory()) return
152
212
  var file = repo.getFile(path) || path
153
213
  const isFile = File.isFile(file)
@@ -178,6 +238,11 @@ function mixin(repo, fs = require('fs')) {
178
238
  })
179
239
  .on('delete', function (path) {
180
240
  log('Watcher received unlink event for file: ' + path)
241
+ if (_isImdonePause(path)) {
242
+ console.log('Pause file deleted. Resuming repository.')
243
+ return repo.resume()
244
+ }
245
+ if (paused) return log('Watcher is paused. Ignoring delete event for: ' + path)
181
246
  repo.removeFilePath(path)
182
247
  var file = new File({
183
248
  repoId: repo.getId(),
package/lib/repository.js CHANGED
@@ -624,6 +624,33 @@ Repository.prototype.readFileContent = function (file, cb) {
624
624
  inMixinsNoop(cb)
625
625
  }
626
626
 
627
+ /**
628
+ * Pause file watching (Implemented in mixins)
629
+ * @method pause
630
+ * @return Promise
631
+ */
632
+ Repository.prototype.pause = async function () {
633
+ return Promise.resolve()
634
+ }
635
+
636
+ /**
637
+ * Resume file watching (Implemented in mixins)
638
+ * @method resume
639
+ * @return Promise
640
+ */
641
+ Repository.prototype.resume = async function () {
642
+ return Promise.resolve()
643
+ }
644
+
645
+ /**
646
+ * Check if file watching is paused (Implemented in mixins)
647
+ * @method isPaused
648
+ * @return Boolean
649
+ */
650
+ Repository.prototype.isPaused = function () {
651
+ return false
652
+ }
653
+
627
654
  /**
628
655
  * Description
629
656
  * @method readFile
@@ -1129,7 +1156,9 @@ Repository.prototype.appendTask = function({file, content, list}, cb) {
1129
1156
  : description.join(eol.lf)
1130
1157
 
1131
1158
  let appendContent = File.trimBlankLines(
1132
- `${taskPrefix}${rawTask}${eol.lf}${taskContent}`
1159
+ `${taskPrefix}${rawTask}${eol.lf}${taskContent}`,
1160
+ file.isCodeFile(),
1161
+ config
1133
1162
  )
1134
1163
 
1135
1164
  fileContent = `${fileContent}${sep}`
package/lib/task.js CHANGED
@@ -14,7 +14,7 @@ var _clone = require('lodash.clone'),
14
14
  { escapeRegExp } = require('./tools'),
15
15
  { extractWikilinkTopics } = require('./adapters/markdown');
16
16
 
17
- const {
17
+ const {
18
18
  CHECK_REGEX,
19
19
  TASK_TYPES,
20
20
  getCheckedData,
@@ -22,6 +22,33 @@ const {
22
22
  isNumber
23
23
  } = require('./adapters/parsers/task/CardContentParser.js')
24
24
 
25
+ const DEFAULT_BLANK_LINE_TERMINATOR_COUNT = 2
26
+
27
+ function getBlankLineTerminatorCountFromConfig(config) {
28
+ const hasNumericTerminator = config
29
+ && typeof config.customCardTerminator === 'number'
30
+ && config.customCardTerminator > 0
31
+
32
+ return hasNumericTerminator
33
+ ? config.customCardTerminator
34
+ : DEFAULT_BLANK_LINE_TERMINATOR_COUNT
35
+ }
36
+
37
+ function buildExcessiveBlankLinePattern(terminatorCount) {
38
+ const minNewlinesToTrim = terminatorCount + 1
39
+ const patternParts = Array(minNewlinesToTrim).fill('\\n\\s*')
40
+ return new RegExp(patternParts.join(''), 'g')
41
+ }
42
+
43
+ function calculateMaxPreservedBlankLines(terminatorCount) {
44
+ return terminatorCount - 1
45
+ }
46
+
47
+ function buildBlankLineReplacement(maxBlankLines) {
48
+ const newlineCount = maxBlankLines + 1
49
+ return '\n'.repeat(newlineCount)
50
+ }
51
+
25
52
  /**
26
53
  * Description
27
54
  * @method Task
@@ -177,8 +204,12 @@ class Task {
177
204
  }
178
205
 
179
206
  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')
207
+ const terminatorCount = getBlankLineTerminatorCountFromConfig(this.config)
208
+ const excessiveBlankLinePattern = buildExcessiveBlankLinePattern(terminatorCount)
209
+ const maxBlankLines = calculateMaxPreservedBlankLines(terminatorCount)
210
+ const replacement = buildBlankLineReplacement(maxBlankLines)
211
+
212
+ content = content.replace(excessiveBlankLinePattern, replacement)
182
213
  let lines = eol.split(content)
183
214
  this.text = lines.shift().trimStart()
184
215
  if (lines && lines.length > 1 && lines[lines.length - 1].trim() === '') lines.pop()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imdone-core",
3
- "version": "1.54.11",
3
+ "version": "1.54.13",
4
4
  "description": "imdone-core",
5
5
  "main": "index.js",
6
6
  "bin": {