diff --git a/src/components/features/quick-capture/modals/BaseQuickCaptureModal.ts b/src/components/features/quick-capture/modals/BaseQuickCaptureModal.ts index af3b8ecf..c8537f87 100644 --- a/src/components/features/quick-capture/modals/BaseQuickCaptureModal.ts +++ b/src/components/features/quick-capture/modals/BaseQuickCaptureModal.ts @@ -13,7 +13,10 @@ import { processDateTemplates, } from "@/utils/file/file-operations"; import { t } from "@/translations/helper"; -import { formatDateSmart, isDateOnly } from "@/utils/date/date-utils"; +import { + formatDate as formatDateSmart, + isDateOnly, +} from "@/utils/date/date-utils"; import { SuggestManager } from "@/components/ui/suggest"; import { EmbeddableMarkdownEditor } from "@/editor-extensions/core/markdown-editor"; diff --git a/src/components/features/quick-capture/modals/QuickCaptureModalWithSwitch.ts b/src/components/features/quick-capture/modals/QuickCaptureModalWithSwitch.ts index 49b14698..9aab4b04 100644 --- a/src/components/features/quick-capture/modals/QuickCaptureModalWithSwitch.ts +++ b/src/components/features/quick-capture/modals/QuickCaptureModalWithSwitch.ts @@ -33,6 +33,7 @@ import { TaskMetadata, } from "./BaseQuickCaptureModal"; import { FileNameInput } from "../components/FileNameInput"; +import { formatDate as formatDateSmart } from "@/utils/date/date-utils"; const LAST_USED_MODE_KEY = "task-genius.lastUsedQuickCaptureMode"; @@ -52,6 +53,7 @@ export class QuickCaptureModal extends BaseQuickCaptureModal { private startDateInput?: HTMLInputElement; private dueDateInput?: HTMLInputElement; private scheduledDateInput?: HTMLInputElement; + private includeTime: boolean = true; // File name input for file creation mode private fileNameInput: FileNameInput | null = null; @@ -385,9 +387,25 @@ export class QuickCaptureModal extends BaseQuickCaptureModal { * Create date inputs */ private createDateInputs(container: HTMLElement): void { + const getInputType = () => + this.includeTime ? "datetime-local" : "date"; + const getPlaceholder = () => + this.includeTime ? "YYYY-MM-DD HH:mm" : "YYYY-MM-DD"; + + // Toggle for including time in date inputs + new Setting(container) + .setName(t("Include time")) + .setDesc(t("Toggle between date-only and date+time input")) + .addToggle((toggle) => { + toggle.setValue(this.includeTime).onChange((value) => { + this.includeTime = value; + this.updateDateInputsMode(); + }); + }); + // Start Date new Setting(container).setName(t("Start Date")).addText((text) => { - text.setPlaceholder("YYYY-MM-DD HH:mm") + text.setPlaceholder(getPlaceholder()) .setValue( this.taskMetadata.startDate ? this.formatDateInputValue(this.taskMetadata.startDate) @@ -405,13 +423,13 @@ export class QuickCaptureModal extends BaseQuickCaptureModal { } this.updatePreview(); }); - text.inputEl.type = "datetime-local"; + text.inputEl.type = getInputType(); this.startDateInput = text.inputEl; }); // Due Date new Setting(container).setName(t("Due Date")).addText((text) => { - text.setPlaceholder("YYYY-MM-DD HH:mm") + text.setPlaceholder(getPlaceholder()) .setValue( this.taskMetadata.dueDate ? this.formatDateInputValue(this.taskMetadata.dueDate) @@ -429,13 +447,13 @@ export class QuickCaptureModal extends BaseQuickCaptureModal { } this.updatePreview(); }); - text.inputEl.type = "datetime-local"; + text.inputEl.type = getInputType(); this.dueDateInput = text.inputEl; }); // Scheduled Date new Setting(container).setName(t("Scheduled Date")).addText((text) => { - text.setPlaceholder("YYYY-MM-DD HH:mm") + text.setPlaceholder(getPlaceholder()) .setValue( this.taskMetadata.scheduledDate ? this.formatDateInputValue( @@ -455,7 +473,7 @@ export class QuickCaptureModal extends BaseQuickCaptureModal { } this.updatePreview(); }); - text.inputEl.type = "datetime-local"; + text.inputEl.type = getInputType(); this.scheduledDateInput = text.inputEl; }); } @@ -901,6 +919,29 @@ export class QuickCaptureModal extends BaseQuickCaptureModal { this.taskMetadata.manuallySet[field] = true; } + /** + * Update all date input elements when the includeTime mode changes + */ + private updateDateInputsMode(): void { + const inputType = this.includeTime ? "datetime-local" : "date"; + const placeholder = this.includeTime + ? "YYYY-MM-DD HH:mm" + : "YYYY-MM-DD"; + + const updateInput = (input?: HTMLInputElement, value?: Date) => { + if (!input) return; + input.type = inputType; + input.placeholder = placeholder; + input.value = this.formatDateInputValue(value); + }; + + updateInput(this.startDateInput, this.taskMetadata.startDate); + updateInput(this.dueDateInput, this.taskMetadata.dueDate); + updateInput(this.scheduledDateInput, this.taskMetadata.scheduledDate); + + this.updatePreview(); + } + private setDateInputValue( input: HTMLInputElement | undefined, value?: Date, @@ -911,7 +952,19 @@ export class QuickCaptureModal extends BaseQuickCaptureModal { private formatDateInputValue(value?: Date): string { if (!value) return ""; - return moment(value).format("YYYY-MM-DD[T]HH:mm"); + return moment(value).format( + this.includeTime ? "YYYY-MM-DD[T]HH:mm" : "YYYY-MM-DD", + ); + } + + /** + * Override formatDate to respect includeTime setting for task metadata output + */ + protected formatDate(date: Date): string { + return formatDateSmart(date, { + forceFormat: this.includeTime ? undefined : "date-only", + includeSeconds: false, + }); } /** diff --git a/src/dataflow/api/WriteAPI.ts b/src/dataflow/api/WriteAPI.ts index 74514f65..2edabe42 100644 --- a/src/dataflow/api/WriteAPI.ts +++ b/src/dataflow/api/WriteAPI.ts @@ -25,7 +25,7 @@ import { CanvasTaskUpdater } from "@/parsers/canvas-task-updater"; import { rrulestr } from "rrule"; import { EMOJI_TAG_REGEX, TOKEN_CONTEXT_REGEX } from "@/common/regex-define"; import { BulkOperationResult } from "@/types/selection"; -import { formatDateSmart } from "@/utils/date/date-utils"; +import { formatDate as formatDateSmart } from "@/utils/date/date-utils"; /** * Arguments for creating a task @@ -956,8 +956,9 @@ export class WriteAPI { if (metadata.dueDate !== undefined) { const dueDateKey = getFrontmatterKey("dueDate"); if (metadata.dueDate) { - const formatted = - formatFrontmatterDate(metadata.dueDate); + const formatted = formatFrontmatterDate( + metadata.dueDate, + ); if (formatted) { (fm as any)[dueDateKey] = formatted; } @@ -969,8 +970,9 @@ export class WriteAPI { if (metadata.startDate !== undefined) { const startDateKey = getFrontmatterKey("startDate"); if (metadata.startDate) { - const formatted = - formatFrontmatterDate(metadata.startDate); + const formatted = formatFrontmatterDate( + metadata.startDate, + ); if (formatted) { (fm as any)[startDateKey] = formatted; } diff --git a/src/managers/electron-quick-capture.ts b/src/managers/electron-quick-capture.ts index 7179535d..265edec4 100644 --- a/src/managers/electron-quick-capture.ts +++ b/src/managers/electron-quick-capture.ts @@ -1,7 +1,7 @@ import { App, Notice, moment } from "obsidian"; import TaskProgressBarPlugin from "../index"; import { t } from "../translations/helper"; -import { formatDateSmart } from "@/utils/date/date-utils"; +import { formatDate as formatDateSmart } from "@/utils/date/date-utils"; export class ElectronQuickCapture { private captureWindow: any = null; @@ -95,7 +95,7 @@ export class ElectronQuickCapture { // Generate and load the HTML content const html = this.generateCaptureHTML(); this.captureWindow.loadURL( - `data:text/html;charset=utf-8,${encodeURIComponent(html)}` + `data:text/html;charset=utf-8,${encodeURIComponent(html)}`, ); // Setup IPC handlers @@ -104,7 +104,10 @@ export class ElectronQuickCapture { // Handle window events this.captureWindow.once("ready-to-show", () => { // Use showInactive on macOS to avoid bringing main window to front - if (process.platform === "darwin" && this.captureWindow?.showInactive) { + if ( + process.platform === "darwin" && + this.captureWindow?.showInactive + ) { this.captureWindow.showInactive(); } else { this.captureWindow?.show(); @@ -115,30 +118,41 @@ export class ElectronQuickCapture { }, 100); // Send initial data to window this.captureWindow.webContents.executeJavaScript(` - window.postMessage({ - type: 'init', + window.postMessage({ + type: 'init', settings: ${JSON.stringify(this.getQuickCaptureSettings())} }, '*'); `); }); // Auto-adjust window size to content after loading - this.captureWindow.webContents.once("did-finish-load", async () => { - try { - const size = await this.captureWindow?.webContents.executeJavaScript( - `({w:document.documentElement.scrollWidth,h:document.documentElement.scrollHeight})` - ); - if (size && this.captureWindow && !this.captureWindow.isDestroyed()) { - this.captureWindow.setContentSize( - Math.max(500, Math.min(800, size.w)), - Math.max(320, Math.min(600, size.h)), - true + this.captureWindow.webContents.once( + "did-finish-load", + async () => { + try { + const size = + await this.captureWindow?.webContents.executeJavaScript( + `({w:document.documentElement.scrollWidth,h:document.documentElement.scrollHeight})`, + ); + if ( + size && + this.captureWindow && + !this.captureWindow.isDestroyed() + ) { + this.captureWindow.setContentSize( + Math.max(500, Math.min(800, size.w)), + Math.max(320, Math.min(600, size.h)), + true, + ); + } + } catch (e) { + console.log( + "Could not auto-adjust window size:", + e, ); } - } catch (e) { - console.log('Could not auto-adjust window size:', e); - } - }); + }, + ); this.captureWindow.on("closed", () => { this.captureWindow = null; @@ -155,7 +169,6 @@ export class ElectronQuickCapture { this.captureReject = null; this.isClosingNormally = false; }); - } catch (error) { console.error("Failed to create capture window:", error); new Notice(t("Failed to open quick capture window")); @@ -177,68 +190,103 @@ export class ElectronQuickCapture { // Use ipcMain handlers (preferred method) try { // Remove existing handlers if any - ipcMain.removeHandler('quick-capture-save'); - ipcMain.removeHandler('quick-capture-cancel'); - ipcMain.removeHandler('quick-capture-request-data'); + ipcMain.removeHandler("quick-capture-save"); + ipcMain.removeHandler("quick-capture-cancel"); + ipcMain.removeHandler("quick-capture-request-data"); // Handle save - ipcMain.handle('quick-capture-save', async (event: any, data: any) => { - console.log("[ElectronQuickCapture] IPC received save:", data); - await this.handleSaveTask(data); - }); + ipcMain.handle( + "quick-capture-save", + async (event: any, data: any) => { + console.log( + "[ElectronQuickCapture] IPC received save:", + data, + ); + await this.handleSaveTask(data); + }, + ); - // Handle cancel - ipcMain.handle('quick-capture-cancel', async () => { + // Handle cancel + ipcMain.handle("quick-capture-cancel", async () => { console.log("[ElectronQuickCapture] IPC received cancel"); this.closeCaptureWindow(); }); // Handle data requests - ipcMain.handle('quick-capture-request-data', async (event: any, type: string) => { - console.log("[ElectronQuickCapture] IPC requesting data:", type); - return await this.getDataForWindow(type); - }); + ipcMain.handle( + "quick-capture-request-data", + async (event: any, type: string) => { + console.log( + "[ElectronQuickCapture] IPC requesting data:", + type, + ); + return await this.getDataForWindow(type); + }, + ); (this as any)._ipcHandlers = { ipcMain, registered: true }; - console.log("[ElectronQuickCapture] IPC handlers registered with ipcMain.handle"); + console.log( + "[ElectronQuickCapture] IPC handlers registered with ipcMain.handle", + ); } catch (e) { - console.warn("[ElectronQuickCapture] Failed to set up ipcMain handlers:", e); + console.warn( + "[ElectronQuickCapture] Failed to set up ipcMain handlers:", + e, + ); } } // Fallback: Listen for regular IPC messages - this.captureWindow.webContents.on('ipc-message', async (_event: any, channel: string, ...args: any[]) => { - console.log("[ElectronQuickCapture] Received ipc-message:", channel, args); - if (channel === 'quick-capture-save') { - await this.handleSaveTask(args[0]); - } else if (channel === 'quick-capture-cancel') { - this.closeCaptureWindow(); - } else if (channel === 'quick-capture-request-data') { - const data = await this.getDataForWindow(args[0]); - // Send data back to window - this.captureWindow?.webContents?.executeJavaScript(` + this.captureWindow.webContents.on( + "ipc-message", + async (_event: any, channel: string, ...args: any[]) => { + console.log( + "[ElectronQuickCapture] Received ipc-message:", + channel, + args, + ); + if (channel === "quick-capture-save") { + await this.handleSaveTask(args[0]); + } else if (channel === "quick-capture-cancel") { + this.closeCaptureWindow(); + } else if (channel === "quick-capture-request-data") { + const data = await this.getDataForWindow(args[0]); + // Send data back to window + this.captureWindow?.webContents?.executeJavaScript(` window.receiveSuggestions('${args[0]}', ${JSON.stringify(data)}); `); - } - }); + } + }, + ); // Also listen for direct channel messages (for newer Electron versions) if (ipcMain) { - ipcMain.on('quick-capture-save', async (event: any, data: any) => { - console.log("[ElectronQuickCapture] Direct IPC received save:", data); + ipcMain.on("quick-capture-save", async (event: any, data: any) => { + console.log( + "[ElectronQuickCapture] Direct IPC received save:", + data, + ); await this.handleSaveTask(data); }); - ipcMain.on('quick-capture-cancel', () => { - console.log("[ElectronQuickCapture] Direct IPC received cancel"); + ipcMain.on("quick-capture-cancel", () => { + console.log( + "[ElectronQuickCapture] Direct IPC received cancel", + ); this.closeCaptureWindow(); }); - ipcMain.on('quick-capture-request-data', async (event: any, type: string) => { - console.log("[ElectronQuickCapture] Direct IPC requesting data:", type); - const data = await this.getDataForWindow(type); - event.reply('quick-capture-data-response', type, data); - }); + ipcMain.on( + "quick-capture-request-data", + async (event: any, type: string) => { + console.log( + "[ElectronQuickCapture] Direct IPC requesting data:", + type, + ); + const data = await this.getDataForWindow(type); + event.reply("quick-capture-data-response", type, data); + }, + ); (this as any)._ipcHandlers = { ipcMain, registered: true }; } @@ -251,7 +299,7 @@ export class ElectronQuickCapture { const handleSave = ` window.handleQuickCaptureSave = async (data) => { - return ${JSON.stringify({ handler: 'save' })}; + return ${JSON.stringify({ handler: "save" })}; }; `; @@ -265,13 +313,16 @@ export class ElectronQuickCapture { this.captureWindow.webContents.executeJavaScript(handleCancel); // Set up message passing through window.postMessage - this.captureWindow.webContents.on('ipc-message', async (_event: any, channel: string, ...args: any[]) => { - if (channel === 'quick-capture-save') { - await this.handleSaveTask(args[0]); - } else if (channel === 'quick-capture-cancel') { - this.closeCaptureWindow(); - } - }); + this.captureWindow.webContents.on( + "ipc-message", + async (_event: any, channel: string, ...args: any[]) => { + if (channel === "quick-capture-save") { + await this.handleSaveTask(args[0]); + } else if (channel === "quick-capture-cancel") { + this.closeCaptureWindow(); + } + }, + ); } private cleanupIPCHandlers(): void { @@ -285,7 +336,7 @@ export class ElectronQuickCapture { ipcMain.removeHandler("quick-capture-save"); ipcMain.removeHandler("quick-capture-cancel"); ipcMain.removeHandler("quick-capture-request-data"); - + // Remove event-based listeners ipcMain.removeAllListeners("quick-capture-save"); ipcMain.removeAllListeners("quick-capture-cancel"); @@ -300,7 +351,10 @@ export class ElectronQuickCapture { private async handleSaveTask(data: any): Promise { try { - console.log("[ElectronQuickCapture] handleSaveTask called with data:", data); + console.log( + "[ElectronQuickCapture] handleSaveTask called with data:", + data, + ); // Parse the task content and metadata const { content, project, context, dueDate, priority, tags } = data; @@ -331,7 +385,10 @@ export class ElectronQuickCapture { } // Create the task using the write API - console.log("[ElectronQuickCapture] Calling createTask with args:", taskArgs); + console.log( + "[ElectronQuickCapture] Calling createTask with args:", + taskArgs, + ); const result = await this.createTask(taskArgs); if (result.success) { @@ -373,23 +430,33 @@ export class ElectronQuickCapture { let result; if (targetType === "daily-note") { // Create in daily note - console.log("[ElectronQuickCapture] Creating task in daily note"); + console.log( + "[ElectronQuickCapture] Creating task in daily note", + ); result = await this.plugin.writeAPI.createTaskInDailyNote(args); } else if (targetType === "fixed" && qc?.targetFile) { // Create in fixed file - console.log("[ElectronQuickCapture] Creating task in fixed file:", qc.targetFile); + console.log( + "[ElectronQuickCapture] Creating task in fixed file:", + qc.targetFile, + ); args.filePath = qc.targetFile; result = await this.plugin.writeAPI.createTask(args); } else { // Default to daily note - console.log("[ElectronQuickCapture] Creating task in daily note (default)"); + console.log( + "[ElectronQuickCapture] Creating task in daily note (default)", + ); result = await this.plugin.writeAPI.createTaskInDailyNote(args); } console.log("[ElectronQuickCapture] Task creation result:", result); return result; } catch (error) { console.error("[ElectronQuickCapture] Error creating task:", error); - return { success: false, error: error.message || "Failed to create task" }; + return { + success: false, + error: error.message || "Failed to create task", + }; } } @@ -426,7 +493,12 @@ export class ElectronQuickCapture { // 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"], + [ + moment.ISO_8601, + "YYYY-MM-DD HH:mm", + "YYYY-MM-DDTHH:mm", + "YYYY-MM-DD", + ], true, ); const strictFormatted = normalize(parsed); @@ -456,7 +528,9 @@ export class ElectronQuickCapture { private async getProjects(): Promise { try { - const queryAPI = (this.plugin as any).dataflowOrchestrator?.getQueryAPI?.(); + const queryAPI = ( + this.plugin as any + ).dataflowOrchestrator?.getQueryAPI?.(); if (!queryAPI) return []; const allTasks = await queryAPI.getAllTasks(); const projects = new Set(); @@ -473,7 +547,9 @@ export class ElectronQuickCapture { private async getContexts(): Promise { try { - const queryAPI = (this.plugin as any).dataflowOrchestrator?.getQueryAPI?.(); + const queryAPI = ( + this.plugin as any + ).dataflowOrchestrator?.getQueryAPI?.(); if (!queryAPI) return []; const allTasks = await queryAPI.getAllTasks(); const contexts = new Set(); @@ -490,7 +566,9 @@ export class ElectronQuickCapture { private async getTags(): Promise { try { - const queryAPI = (this.plugin as any).dataflowOrchestrator?.getQueryAPI?.(); + const queryAPI = ( + this.plugin as any + ).dataflowOrchestrator?.getQueryAPI?.(); if (!queryAPI) return []; const allTasks = await queryAPI.getAllTasks(); const tags = new Set(); @@ -530,7 +608,10 @@ export class ElectronQuickCapture { const electron = this.getElectron(); const nativeTheme = electron?.nativeTheme || electron?.remote?.nativeTheme; - if (nativeTheme && typeof nativeTheme.shouldUseDarkColors === "boolean") { + if ( + nativeTheme && + typeof nativeTheme.shouldUseDarkColors === "boolean" + ) { return nativeTheme.shouldUseDarkColors; } return window.matchMedia("(prefers-color-scheme: dark)").matches; @@ -555,7 +636,7 @@ export class ElectronQuickCapture { private generateCaptureHTML(): string { const isDark = this.isDarkTheme(); - + // Define Obsidian-like CSS variables for consistent styling const cssVars = ` :root { @@ -585,7 +666,7 @@ export class ElectronQuickCapture { Quick Capture - Task Genius