Skip to content

Commit 519d1d5

Browse files
committed
feat: support better date handler
1 parent 1821ac0 commit 519d1d5

1 file changed

Lines changed: 128 additions & 43 deletions

File tree

src/editor-ext/autoDateManager.ts

Lines changed: 128 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ function handleAutoDateManagerTransaction(
7878
const dateOperations = determineDateOperations(
7979
oldStatus,
8080
newStatus,
81-
plugin
81+
plugin,
82+
doc.line(lineNumber).text
8283
);
8384

8485
if (dateOperations.length === 0) {
@@ -165,12 +166,14 @@ function findTaskStatusChange(tr: Transaction): {
165166
* @param oldStatus The old task status
166167
* @param newStatus The new task status
167168
* @param plugin The plugin instance
169+
* @param lineText The current line text to check for existing dates
168170
* @returns Array of date operations to perform
169171
*/
170172
function determineDateOperations(
171173
oldStatus: string,
172174
newStatus: string,
173-
plugin: TaskProgressBarPlugin
175+
plugin: TaskProgressBarPlugin,
176+
lineText: string
174177
): DateOperation[] {
175178
const operations: DateOperation[] = [];
176179
const settings = plugin.settings.autoDateManager;
@@ -185,27 +188,21 @@ function determineDateOperations(
185188
return operations;
186189
}
187190

188-
// Remove old status date if it exists and is managed
191+
// Remove old status date if it exists and is managed (but never remove start date)
189192
if (settings.manageCompletedDate && oldStatusType === "completed") {
190193
operations.push({
191194
type: "remove",
192195
dateType: "completed",
193196
});
194197
}
195-
if (settings.manageStartDate && oldStatusType === "inProgress") {
196-
operations.push({
197-
type: "remove",
198-
dateType: "start",
199-
});
200-
}
201198
if (settings.manageCancelledDate && oldStatusType === "abandoned") {
202199
operations.push({
203200
type: "remove",
204201
dateType: "cancelled",
205202
});
206203
}
207204

208-
// Add new status date if it should be managed
205+
// Add new status date if it should be managed and doesn't already exist
209206
if (settings.manageCompletedDate && newStatusType === "completed") {
210207
operations.push({
211208
type: "add",
@@ -214,11 +211,14 @@ function determineDateOperations(
214211
});
215212
}
216213
if (settings.manageStartDate && newStatusType === "inProgress") {
217-
operations.push({
218-
type: "add",
219-
dateType: "start",
220-
format: settings.startDateFormat || "YYYY-MM-DD",
221-
});
214+
// Only add start date if it doesn't already exist
215+
if (!hasExistingDate(lineText, "start", plugin)) {
216+
operations.push({
217+
type: "add",
218+
dateType: "start",
219+
format: settings.startDateFormat || "YYYY-MM-DD",
220+
});
221+
}
222222
}
223223
if (settings.manageCancelledDate && newStatusType === "abandoned") {
224224
operations.push({
@@ -231,6 +231,38 @@ function determineDateOperations(
231231
return operations;
232232
}
233233

234+
/**
235+
* Checks if a specific date type already exists in the line
236+
* @param lineText The task line text
237+
* @param dateType The type of date to check for
238+
* @param plugin The plugin instance
239+
* @returns True if the date already exists
240+
*/
241+
function hasExistingDate(
242+
lineText: string,
243+
dateType: string,
244+
plugin: TaskProgressBarPlugin
245+
): boolean {
246+
const useDataviewFormat =
247+
plugin.settings.preferMetadataFormat === "dataview";
248+
249+
if (useDataviewFormat) {
250+
const fieldName = dateType === "start" ? "start" : dateType;
251+
const pattern = new RegExp(
252+
`\\[${fieldName}::\\s*\\d{4}-\\d{2}-\\d{2}(?:\\s+\\d{2}:\\d{2}(?::\\d{2})?)?\\]`
253+
);
254+
return pattern.test(lineText);
255+
} else {
256+
const dateMarker = getDateMarker(dateType, plugin);
257+
const pattern = new RegExp(
258+
`${escapeRegex(
259+
dateMarker
260+
)}\\s*\\d{4}-\\d{2}-\\d{2}(?:\\s+\\d{2}:\\d{2}(?::\\d{2})?)?`
261+
);
262+
return pattern.test(lineText);
263+
}
264+
}
265+
234266
/**
235267
* Gets the status type (completed, inProgress, etc.) for a given status character
236268
* @param status The status character
@@ -294,8 +326,23 @@ function applyDateOperations(
294326
dateText = ` ${dateMarker} ${dateString}`;
295327
}
296328

297-
// Find the end of the task content (before any existing dates)
298-
const insertPosition = findDateInsertPosition(lineText, plugin);
329+
// Find the appropriate insert position based on date type
330+
let insertPosition: number;
331+
if (operation.dateType === "completed") {
332+
// Completed date goes at the end (before block reference ID)
333+
insertPosition = findCompletedDateInsertPosition(
334+
lineText,
335+
plugin
336+
);
337+
} else {
338+
// Start date and cancelled date go after existing metadata but before completed date
339+
insertPosition = findMetadataInsertPosition(
340+
lineText,
341+
plugin,
342+
operation.dateType
343+
);
344+
}
345+
299346
const absolutePosition = line.from + insertPosition;
300347

301348
changes.push({
@@ -316,12 +363,10 @@ function applyDateOperations(
316363
let datePattern: RegExp;
317364

318365
if (useDataviewFormat) {
319-
// For dataview format: [completion::2024-01-01] or [start::2024-01-01]
366+
// For dataview format: [completion::2024-01-01] or [cancelled::2024-01-01]
320367
const fieldName =
321368
operation.dateType === "completed"
322369
? "completion"
323-
: operation.dateType === "start"
324-
? "start"
325370
: operation.dateType === "cancelled"
326371
? "cancelled"
327372
: "unknown";
@@ -330,7 +375,7 @@ function applyDateOperations(
330375
"g"
331376
);
332377
} else {
333-
// For emoji format: ✅ 2024-01-01 or 🚀 2024-01-01
378+
// For emoji format: ✅ 2024-01-01 or 2024-01-01
334379
const dateMarker = getDateMarker(operation.dateType, plugin);
335380
datePattern = new RegExp(
336381
`\\s*${escapeRegex(
@@ -442,45 +487,85 @@ function getDateMarker(
442487
}
443488

444489
/**
445-
* Finds the position where a new date should be inserted
490+
* Finds the position where metadata (start date, cancelled date, etc.) should be inserted
446491
* @param lineText The task line text
447492
* @param plugin The plugin instance
448-
* @returns The position index where the date should be inserted
493+
* @param dateType The type of date being inserted
494+
* @returns The position index where the metadata should be inserted
449495
*/
450-
function findDateInsertPosition(
496+
function findMetadataInsertPosition(
451497
lineText: string,
452-
plugin: TaskProgressBarPlugin
498+
plugin: TaskProgressBarPlugin,
499+
dateType: string
453500
): number {
454-
// Find the end of the task content, before any existing dates or metadata
501+
// Find the end of the task content, right after the task description
455502
const taskMatch = lineText.match(/^[\s|\t]*([-*+]|\d+\.)\s\[.\]\s*/);
456503
if (!taskMatch) return lineText.length;
457504

458505
let position = taskMatch[0].length;
459506

460-
const useDataviewFormat =
461-
plugin.settings.preferMetadataFormat === "dataview";
507+
// Find the main task content (description) before any metadata
508+
const contentMatch = lineText
509+
.slice(position)
510+
.match(/^[^\[#@📅🚀]*(?=\s*[\[#@📅🚀]|$)/);
511+
if (contentMatch) {
512+
position += contentMatch[0].trimEnd().length;
513+
}
462514

463-
if (useDataviewFormat) {
464-
// For dataview format, find the main task content before any dataview fields, tags, or metadata
465-
const contentMatch = lineText
466-
.slice(position)
467-
.match(/^[^\[#@]*(?=\s*[\[#@]|$)/);
468-
if (contentMatch) {
469-
position += contentMatch[0].trimEnd().length;
470-
}
471-
} else {
472-
// For emoji format, find the main task content before any emoji dates, tags, or metadata
473-
const contentMatch = lineText
474-
.slice(position)
475-
.match(/^[^📅🚀#@\[]*(?=\s*[📅🚀#@\[]|$)/);
476-
if (contentMatch) {
477-
position += contentMatch[0].trimEnd().length;
515+
// If we're inserting a cancelled date, we need to find the position after existing start dates
516+
if (dateType === "cancelled") {
517+
const useDataviewFormat =
518+
plugin.settings.preferMetadataFormat === "dataview";
519+
520+
// Look for existing start dates and position after them
521+
const remainingText = lineText.slice(position);
522+
let startDateEnd = 0;
523+
524+
if (useDataviewFormat) {
525+
const startDateMatch = remainingText.match(/^\s*\[start::[^\]]*\]/);
526+
if (startDateMatch) {
527+
startDateEnd = startDateMatch[0].length;
528+
}
529+
} else {
530+
const startMarker = getDateMarker("start", plugin);
531+
const startDatePattern = new RegExp(
532+
`^\\s*${escapeRegex(
533+
startMarker
534+
)}\\s*\\d{4}-\\d{2}-\\d{2}(?:\\s+\\d{2}:\\d{2}(?::\\d{2})?)?`
535+
);
536+
const startDateMatch = remainingText.match(startDatePattern);
537+
if (startDateMatch) {
538+
startDateEnd = startDateMatch[0].length;
539+
}
478540
}
541+
542+
position += startDateEnd;
479543
}
480544

481545
return position;
482546
}
483547

548+
/**
549+
* Finds the position where completed date should be inserted (at the end, before block reference ID)
550+
* @param lineText The task line text
551+
* @param plugin The plugin instance
552+
* @returns The position index where the completed date should be inserted
553+
*/
554+
function findCompletedDateInsertPosition(
555+
lineText: string,
556+
plugin: TaskProgressBarPlugin
557+
): number {
558+
// Look for block reference ID pattern (^block-id) at the end
559+
const blockRefMatch = lineText.match(/\s*\^[\w-]+\s*$/);
560+
if (blockRefMatch) {
561+
// Insert before the block reference ID
562+
return lineText.length - blockRefMatch[0].length;
563+
}
564+
565+
// If no block reference, insert at the very end
566+
return lineText.length;
567+
}
568+
484569
/**
485570
* Escapes special regex characters
486571
* @param string The string to escape

0 commit comments

Comments
 (0)