mirror of
https://github.com/taskgenius/taskgenius-plugin.git
synced 2026-07-22 06:40:25 +00:00
feat(quick-capture): add datetime-local support for task dates
This commit is contained in:
parent
e89e3e59db
commit
359ea6dc15
4 changed files with 124 additions and 49 deletions
|
|
@ -13,6 +13,7 @@ import {
|
|||
processDateTemplates,
|
||||
} from "@/utils/file/file-operations";
|
||||
import { t } from "@/translations/helper";
|
||||
import { formatDateSmart, isDateOnly } from "@/utils/date/date-utils";
|
||||
import { SuggestManager } from "@/components/ui/suggest";
|
||||
import { EmbeddableMarkdownEditor } from "@/editor-extensions/core/markdown-editor";
|
||||
|
||||
|
|
@ -138,6 +139,12 @@ export abstract class BaseQuickCaptureModal extends Modal {
|
|||
if (typeof value === "string") {
|
||||
const isoParsed = moment(value, moment.ISO_8601, true);
|
||||
if (isoParsed.isValid()) return isoParsed.toDate();
|
||||
const dateTimeParsed = moment(
|
||||
value,
|
||||
["YYYY-MM-DD HH:mm", "YYYY-MM-DDTHH:mm"],
|
||||
true,
|
||||
);
|
||||
if (dateTimeParsed.isValid()) return dateTimeParsed.toDate();
|
||||
const strictDate = moment(value, "YYYY-MM-DD", true);
|
||||
if (strictDate.isValid()) return strictDate.toDate();
|
||||
}
|
||||
|
|
@ -809,18 +816,39 @@ export abstract class BaseQuickCaptureModal extends Modal {
|
|||
* Format date
|
||||
*/
|
||||
protected formatDate(date: Date): string {
|
||||
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(
|
||||
2,
|
||||
"0",
|
||||
)}-${String(date.getDate()).padStart(2, "0")}`;
|
||||
return formatDateSmart(date, { includeSeconds: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse date
|
||||
*/
|
||||
protected parseDate(dateString: string): Date {
|
||||
const [year, month, day] = dateString.split("-").map(Number);
|
||||
return new Date(year, month - 1, day);
|
||||
const trimmed = dateString?.trim();
|
||||
if (!trimmed) return new Date("");
|
||||
|
||||
const parsed = moment(
|
||||
trimmed,
|
||||
["YYYY-MM-DD HH:mm", "YYYY-MM-DDTHH:mm", "YYYY-MM-DD"],
|
||||
true,
|
||||
);
|
||||
if (parsed.isValid()) {
|
||||
const date = parsed.toDate();
|
||||
// Preserve date-only timestamps as midnight to keep smart formatting consistent
|
||||
if (parsed.creationData().format === "YYYY-MM-DD") {
|
||||
date.setHours(0, 0, 0, 0);
|
||||
}
|
||||
return date;
|
||||
}
|
||||
|
||||
const fallback = new Date(trimmed);
|
||||
if (isNaN(fallback.getTime())) {
|
||||
return new Date("");
|
||||
}
|
||||
|
||||
if (isDateOnly(fallback)) {
|
||||
fallback.setHours(0, 0, 0, 0);
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -387,10 +387,10 @@ export class QuickCaptureModal extends BaseQuickCaptureModal {
|
|||
private createDateInputs(container: HTMLElement): void {
|
||||
// Start Date
|
||||
new Setting(container).setName(t("Start Date")).addText((text) => {
|
||||
text.setPlaceholder("YYYY-MM-DD")
|
||||
text.setPlaceholder("YYYY-MM-DD HH:mm")
|
||||
.setValue(
|
||||
this.taskMetadata.startDate
|
||||
? this.formatDate(this.taskMetadata.startDate)
|
||||
? this.formatDateInputValue(this.taskMetadata.startDate)
|
||||
: "",
|
||||
)
|
||||
.onChange((value) => {
|
||||
|
|
@ -405,16 +405,16 @@ export class QuickCaptureModal extends BaseQuickCaptureModal {
|
|||
}
|
||||
this.updatePreview();
|
||||
});
|
||||
text.inputEl.type = "date";
|
||||
text.inputEl.type = "datetime-local";
|
||||
this.startDateInput = text.inputEl;
|
||||
});
|
||||
|
||||
// Due Date
|
||||
new Setting(container).setName(t("Due Date")).addText((text) => {
|
||||
text.setPlaceholder("YYYY-MM-DD")
|
||||
text.setPlaceholder("YYYY-MM-DD HH:mm")
|
||||
.setValue(
|
||||
this.taskMetadata.dueDate
|
||||
? this.formatDate(this.taskMetadata.dueDate)
|
||||
? this.formatDateInputValue(this.taskMetadata.dueDate)
|
||||
: "",
|
||||
)
|
||||
.onChange((value) => {
|
||||
|
|
@ -429,16 +429,18 @@ export class QuickCaptureModal extends BaseQuickCaptureModal {
|
|||
}
|
||||
this.updatePreview();
|
||||
});
|
||||
text.inputEl.type = "date";
|
||||
text.inputEl.type = "datetime-local";
|
||||
this.dueDateInput = text.inputEl;
|
||||
});
|
||||
|
||||
// Scheduled Date
|
||||
new Setting(container).setName(t("Scheduled Date")).addText((text) => {
|
||||
text.setPlaceholder("YYYY-MM-DD")
|
||||
text.setPlaceholder("YYYY-MM-DD HH:mm")
|
||||
.setValue(
|
||||
this.taskMetadata.scheduledDate
|
||||
? this.formatDate(this.taskMetadata.scheduledDate)
|
||||
? this.formatDateInputValue(
|
||||
this.taskMetadata.scheduledDate,
|
||||
)
|
||||
: "",
|
||||
)
|
||||
.onChange((value) => {
|
||||
|
|
@ -453,7 +455,7 @@ export class QuickCaptureModal extends BaseQuickCaptureModal {
|
|||
}
|
||||
this.updatePreview();
|
||||
});
|
||||
text.inputEl.type = "date";
|
||||
text.inputEl.type = "datetime-local";
|
||||
this.scheduledDateInput = text.inputEl;
|
||||
});
|
||||
}
|
||||
|
|
@ -863,24 +865,18 @@ export class QuickCaptureModal extends BaseQuickCaptureModal {
|
|||
// Update metadata (only if not manually set)
|
||||
if (aggregatedStartDate && !this.isManuallySet("startDate")) {
|
||||
this.taskMetadata.startDate = aggregatedStartDate;
|
||||
if (this.startDateInput) {
|
||||
this.startDateInput.value =
|
||||
this.formatDate(aggregatedStartDate);
|
||||
}
|
||||
this.setDateInputValue(this.startDateInput, aggregatedStartDate);
|
||||
}
|
||||
if (aggregatedDueDate && !this.isManuallySet("dueDate")) {
|
||||
this.taskMetadata.dueDate = aggregatedDueDate;
|
||||
if (this.dueDateInput) {
|
||||
this.dueDateInput.value = this.formatDate(aggregatedDueDate);
|
||||
}
|
||||
this.setDateInputValue(this.dueDateInput, aggregatedDueDate);
|
||||
}
|
||||
if (aggregatedScheduledDate && !this.isManuallySet("scheduledDate")) {
|
||||
this.taskMetadata.scheduledDate = aggregatedScheduledDate;
|
||||
if (this.scheduledDateInput) {
|
||||
this.scheduledDateInput.value = this.formatDate(
|
||||
aggregatedScheduledDate,
|
||||
);
|
||||
}
|
||||
this.setDateInputValue(
|
||||
this.scheduledDateInput,
|
||||
aggregatedScheduledDate,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -905,6 +901,19 @@ export class QuickCaptureModal extends BaseQuickCaptureModal {
|
|||
this.taskMetadata.manuallySet[field] = true;
|
||||
}
|
||||
|
||||
private setDateInputValue(
|
||||
input: HTMLInputElement | undefined,
|
||||
value?: Date,
|
||||
): void {
|
||||
if (!input) return;
|
||||
input.value = this.formatDateInputValue(value);
|
||||
}
|
||||
|
||||
private formatDateInputValue(value?: Date): string {
|
||||
if (!value) return "";
|
||||
return moment(value).format("YYYY-MM-DD[T]HH:mm");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset UI elements
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { App, Notice } from "obsidian";
|
||||
import { App, Notice, moment } from "obsidian";
|
||||
import TaskProgressBarPlugin from "../index";
|
||||
import { t } from "../translations/helper";
|
||||
import { moment } from "obsidian";
|
||||
import { formatDateSmart } from "@/utils/date/date-utils";
|
||||
|
||||
export class ElectronQuickCapture {
|
||||
private captureWindow: any = null;
|
||||
|
|
@ -396,6 +396,12 @@ export class ElectronQuickCapture {
|
|||
private parseDueDate(dateStr: string): string | undefined {
|
||||
if (!dateStr) return undefined;
|
||||
|
||||
const trimmed = dateStr.trim();
|
||||
const normalize = (value: moment.Moment): string | undefined => {
|
||||
if (!value || !value.isValid()) return undefined;
|
||||
return formatDateSmart(value.toDate(), { includeSeconds: false });
|
||||
};
|
||||
|
||||
try {
|
||||
// Try natural language parsing first
|
||||
const naturalParsers = [
|
||||
|
|
@ -406,26 +412,33 @@ export class ElectronQuickCapture {
|
|||
];
|
||||
|
||||
for (const parser of naturalParsers) {
|
||||
const match = dateStr.match(parser.pattern);
|
||||
const match = trimmed.match(parser.pattern);
|
||||
if (match) {
|
||||
const offset = parser.offsetMatch
|
||||
? parseInt(match[parser.offsetMatch])
|
||||
const offset = parser.offsetMatch
|
||||
? parseInt(match[parser.offsetMatch])
|
||||
: parser.offset;
|
||||
const date = moment().add(offset, 'days');
|
||||
return date.format('YYYY-MM-DD');
|
||||
const date = moment().add(offset, "days");
|
||||
const formatted = normalize(date);
|
||||
if (formatted) return formatted;
|
||||
}
|
||||
}
|
||||
|
||||
// Try parsing as date
|
||||
const parsed = moment(dateStr);
|
||||
if (parsed.isValid()) {
|
||||
return parsed.format('YYYY-MM-DD');
|
||||
}
|
||||
// Try parsing with strict formats (supporting date and datetime)
|
||||
const parsed = moment(
|
||||
trimmed,
|
||||
[moment.ISO_8601, "YYYY-MM-DD HH:mm", "YYYY-MM-DDTHH:mm", "YYYY-MM-DD"],
|
||||
true,
|
||||
);
|
||||
const strictFormatted = normalize(parsed);
|
||||
if (strictFormatted) return strictFormatted;
|
||||
|
||||
// Fallback to loose parsing
|
||||
const fallback = moment(trimmed);
|
||||
return normalize(fallback);
|
||||
} catch {
|
||||
// Ignore parsing errors
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private async getDataForWindow(type: string): Promise<any> {
|
||||
|
|
@ -810,7 +823,7 @@ export class ElectronQuickCapture {
|
|||
<div class="metadata-item">
|
||||
<label>Start Date</label>
|
||||
<div class="date-input-wrapper">
|
||||
<input type="date" id="start-date-picker" class="date-picker">
|
||||
<input type="datetime-local" id="start-date-picker" class="date-picker">
|
||||
<input type="text" id="start-date-text" placeholder="today, tomorrow" class="date-text" style="display:none">
|
||||
<button type="button" class="date-toggle" data-date-type="start" title="Toggle input type">🛫</button>
|
||||
</div>
|
||||
|
|
@ -819,7 +832,7 @@ export class ElectronQuickCapture {
|
|||
<div class="metadata-item">
|
||||
<label>Due Date</label>
|
||||
<div class="date-input-wrapper">
|
||||
<input type="date" id="due-date-picker" class="date-picker">
|
||||
<input type="datetime-local" id="due-date-picker" class="date-picker">
|
||||
<input type="text" id="due-date-text" placeholder="tomorrow, next week" class="date-text" style="display:none">
|
||||
<button type="button" class="date-toggle" data-date-type="due" title="Toggle input type">📅</button>
|
||||
</div>
|
||||
|
|
@ -828,7 +841,7 @@ export class ElectronQuickCapture {
|
|||
<div class="metadata-item">
|
||||
<label>Scheduled Date</label>
|
||||
<div class="date-input-wrapper">
|
||||
<input type="date" id="scheduled-date-picker" class="date-picker">
|
||||
<input type="datetime-local" id="scheduled-date-picker" class="date-picker">
|
||||
<input type="text" id="scheduled-date-text" placeholder="next monday" class="date-text" style="display:none">
|
||||
<button type="button" class="date-toggle" data-date-type="scheduled" title="Toggle input type">⏳</button>
|
||||
</div>
|
||||
|
|
@ -1233,7 +1246,8 @@ export class ElectronQuickCapture {
|
|||
function parseNaturalDate(input) {
|
||||
if (!input) return '';
|
||||
|
||||
const lower = input.toLowerCase().trim();
|
||||
const trimmed = input.trim();
|
||||
const lower = trimmed.toLowerCase();
|
||||
const today = new Date();
|
||||
|
||||
// Natural language patterns
|
||||
|
|
@ -1309,21 +1323,41 @@ export class ElectronQuickCapture {
|
|||
|
||||
// Try to parse as a regular date
|
||||
try {
|
||||
const parsed = new Date(input);
|
||||
const parsed = new Date(trimmed);
|
||||
if (!isNaN(parsed.getTime())) {
|
||||
return formatDate(parsed);
|
||||
}
|
||||
} catch {}
|
||||
|
||||
// If not a natural language pattern, return original
|
||||
return input;
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
function formatDate(date) {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
return year + '-' + month + '-' + day;
|
||||
const hasTime =
|
||||
date.getHours() !== 0 ||
|
||||
date.getMinutes() !== 0 ||
|
||||
date.getSeconds() !== 0 ||
|
||||
date.getMilliseconds() !== 0;
|
||||
if (!hasTime) {
|
||||
return year + '-' + month + '-' + day;
|
||||
}
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
return (
|
||||
year +
|
||||
'-' +
|
||||
month +
|
||||
'-' +
|
||||
day +
|
||||
' ' +
|
||||
hours +
|
||||
':' +
|
||||
minutes
|
||||
);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
|
@ -1334,4 +1368,4 @@ export class ElectronQuickCapture {
|
|||
this.closeCaptureWindow();
|
||||
this.cleanupIPCHandlers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -632,3 +632,7 @@
|
|||
|
||||
gap: var(--size-4-3);
|
||||
}
|
||||
|
||||
.quick-capture-modal .setting-item-control input[type="datetime-local"] {
|
||||
width: 10rem;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue