feat(quick-capture): add date-only input mode toggle

This commit is contained in:
Quorafind 2025-11-29 22:58:47 +08:00
parent b1e3c22082
commit 25fe82d7bd
10 changed files with 2588 additions and 2054 deletions

View file

@ -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";

View file

@ -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,
});
}
/**

View file

@ -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;
}

View file

@ -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<void> {
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<string[]> {
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<string>();
@ -473,7 +547,9 @@ export class ElectronQuickCapture {
private async getContexts(): Promise<string[]> {
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<string>();
@ -490,7 +566,9 @@ export class ElectronQuickCapture {
private async getTags(): Promise<string[]> {
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<string>();
@ -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 {
<title>Quick Capture - Task Genius</title>
<style>
${cssVars}
* {
margin: 0;
padding: 0;
@ -797,11 +878,11 @@ export class ElectronQuickCapture {
<body>
<div class="container">
<div class="title">Quick Capture</div>
<div class="input-group">
<textarea
id="task-content"
placeholder="Enter your task..."
<textarea
id="task-content"
placeholder="Enter your task..."
autofocus
></textarea>
<div class="error-message" id="content-error"></div>
@ -891,7 +972,7 @@ export class ElectronQuickCapture {
// Try to get ipcRenderer from various sources
const electron = window.require ? window.require('electron') : null;
const ipcRenderer = electron?.ipcRenderer;
if (ipcRenderer) {
console.log('IPC bridge established');
bridge = {
@ -931,14 +1012,14 @@ export class ElectronQuickCapture {
// Auto-resize textarea
function autoResizeTextarea(textarea) {
if (!textarea) return;
// Reset height to measure content
textarea.style.height = 'auto';
// Calculate new height based on scroll height
const newHeight = Math.min(textarea.scrollHeight, 300);
textarea.style.height = newHeight + 'px';
// Don't resize the window - let the container handle overflow
// The window size should remain stable
}
@ -946,16 +1027,16 @@ export class ElectronQuickCapture {
// Initialize
document.addEventListener('DOMContentLoaded', () => {
const taskContent = document.getElementById('task-content');
// Focus on task content
if (taskContent) {
taskContent.focus();
// Auto-resize on input
taskContent.addEventListener('input', () => {
autoResizeTextarea(taskContent);
});
// Initial resize
setTimeout(() => autoResizeTextarea(taskContent), 0);
}
@ -977,14 +1058,14 @@ export class ElectronQuickCapture {
['start', 'due', 'scheduled'].forEach(dateType => {
const picker = document.getElementById(dateType + '-date-picker');
const text = document.getElementById(dateType + '-date-text');
if (picker) {
picker.addEventListener('change', (e) => {
// Sync to text field
if (text) text.value = e.target.value;
});
}
// Handle natural language date input
if (text) {
text.addEventListener('blur', (e) => {
@ -1000,7 +1081,7 @@ export class ElectronQuickCapture {
// Handle priority buttons
document.querySelectorAll('.priority-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.priority-btn').forEach(b =>
document.querySelectorAll('.priority-btn').forEach(b =>
b.classList.remove('selected')
);
btn.classList.add('selected');
@ -1054,9 +1135,9 @@ export class ElectronQuickCapture {
function toggleDateInput(dateType) {
const picker = document.getElementById(dateType + '-date-picker');
const text = document.getElementById(dateType + '-date-text');
if (!picker || !text) return;
if (dateInputModes[dateType] === 'picker') {
picker.style.display = 'none';
text.style.display = 'block';
@ -1077,11 +1158,11 @@ export class ElectronQuickCapture {
// Request and populate projects
const projects = await bridge.requestData('projects');
populateDatalist('project-list', projects || []);
// Request and populate contexts
const contexts = await bridge.requestData('contexts');
populateDatalist('context-list', contexts || []);
// Request and populate tags
const tags = await bridge.requestData('tags');
populateDatalist('tags-list', tags || []);
@ -1095,7 +1176,7 @@ export class ElectronQuickCapture {
function populateDatalist(listId, items) {
const datalist = document.getElementById(listId);
if (!datalist || !items) return;
datalist.innerHTML = '';
items.forEach(item => {
const option = document.createElement('option');
@ -1107,7 +1188,7 @@ export class ElectronQuickCapture {
function applySettings(settings) {
// Show/hide metadata fields based on settings
const metadata = document.getElementById('metadata-section');
if (!settings.showProject && !settings.showContext &&
if (!settings.showProject && !settings.showContext &&
!settings.showDueDate && !settings.showPriority) {
metadata.style.display = 'none';
} else {
@ -1141,7 +1222,7 @@ export class ElectronQuickCapture {
async function save() {
const content = document.getElementById('task-content').value;
if (!content.trim()) {
showError('Task content cannot be empty');
return;
@ -1162,7 +1243,7 @@ export class ElectronQuickCapture {
// Get tags and split by comma
const tagsInput = document.getElementById('tags').value.trim();
const tags = tagsInput ? tagsInput.split(',').map(t => t.trim()).filter(t => t) : [];
// Process content to convert to task format if needed
const processedContent = processTaskContent(content.trim());
@ -1214,20 +1295,20 @@ export class ElectronQuickCapture {
errorEl.classList.remove('show');
}, 3000);
}
// Process content to ensure it's in task format
function processTaskContent(content) {
if (!content) return '';
const lines = content.split('\\n');
const processedLines = [];
for (let line of lines) {
if (!line.trim()) {
processedLines.push(line);
continue;
}
// Check if line starts with a task marker
const taskRegex = /^[\\s]*[-*+\\d+.]\\s*(\\[[^\\]]*\\])?/;
if (taskRegex.test(line)) {
@ -1238,18 +1319,18 @@ export class ElectronQuickCapture {
processedLines.push('- [ ] ' + line.trim());
}
}
return processedLines.join('\\n');
}
// Parse natural language dates
function parseNaturalDate(input) {
if (!input) return '';
const trimmed = input.trim();
const lower = trimmed.toLowerCase();
const today = new Date();
// Natural language patterns
if (lower === 'today' || lower === 'tod') {
return formatDate(today);
@ -1274,7 +1355,7 @@ export class ElectronQuickCapture {
nextMonth.setMonth(nextMonth.getMonth() + 1);
return formatDate(nextMonth);
}
// Weekday names
const weekdays = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
const weekdayIndex = weekdays.indexOf(lower);
@ -1286,7 +1367,7 @@ export class ElectronQuickCapture {
targetDate.setDate(targetDate.getDate() + daysUntil);
return formatDate(targetDate);
}
// "next" + weekday
const nextWeekdayMatch = lower.match(/^next (\\w+)$/);
if (nextWeekdayMatch) {
@ -1302,7 +1383,7 @@ export class ElectronQuickCapture {
return formatDate(targetDate);
}
}
// Match patterns like "in X days"
const inDaysMatch = lower.match(/^in (\\d+) days?$/);
if (inDaysMatch) {
@ -1311,7 +1392,7 @@ export class ElectronQuickCapture {
future.setDate(future.getDate() + days);
return formatDate(future);
}
// Match patterns like "X days"
const daysMatch = lower.match(/^(\\d+) days?$/);
if (daysMatch) {
@ -1320,7 +1401,7 @@ export class ElectronQuickCapture {
future.setDate(future.getDate() + days);
return formatDate(future);
}
// Try to parse as a regular date
try {
const parsed = new Date(trimmed);
@ -1328,11 +1409,11 @@ export class ElectronQuickCapture {
return formatDate(parsed);
}
} catch {}
// If not a natural language pattern, return original
return trimmed;
}
function formatDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');

View file

@ -8,7 +8,7 @@ import { CanvasData, CanvasTextData } from "../types/canvas";
import type TaskProgressBarPlugin from "../index";
import { Events, emit } from "../dataflow/events/Events";
import { CanvasParser } from "../dataflow/core/CanvasParser";
import { formatDateSmart } from "@/utils/date/date-utils";
import { formatDate as formatDateSmart } from "@/utils/date/date-utils";
/**
* Result of a Canvas task update operation

View file

@ -777,6 +777,9 @@ const translations = {
"Support expression in format, like using data.percentages to get the percentage of completed tasks. And using math or even repeat functions to get the result.",
"Target File:": "Target File:",
"Task Properties": "Task Properties",
"Include time": "Include time",
"Toggle between date-only and date+time input":
"Toggle between date-only and date+time input",
"Start Date": "Start Date",
"Due Date": "Due Date",
"Scheduled Date": "Scheduled Date",

View file

@ -498,6 +498,9 @@ const translations = {
"フォーマットで式をサポートする、例えばdata.percentagesを使用して完了したタスクのパーセンテージを取得する。また、数学や繰り返し関数を使用して結果を取得する。",
"Target File:": "対象ファイル:",
"Task Properties": "タスクのプロパティ",
"Include time": "時刻を含める",
"Toggle between date-only and date+time input":
"日付のみと日付+時刻入力を切り替え",
"Start Date": "開始日",
"Due Date": "期限日",
"Scheduled Date": "予定日",

View file

@ -558,6 +558,8 @@ const translations = {
"支持在格式中使用表达式,例如使用 data.percentages 获取已完成任务的百分比。使用 Math 或 Repeat 函数来获取结果。",
"Target File:": "目标文件:",
"Task Properties": "任务属性",
"Include time": "包含时间",
"Toggle between date-only and date+time input": "切换仅日期或日期+时间输入",
"Start Date": "开始日期",
"Due Date": "截止日期",
"Scheduled Date": "计划日期",

File diff suppressed because it is too large Load diff

View file

@ -29,7 +29,7 @@ export function isDateOnly(timestamp: DateInput): boolean {
* - 00:00:00.000 -> "YYYY-MM-DD"
* - Otherwise -> "YYYY-MM-DD HH:mm" (seconds optional)
*/
export function formatDateSmart(
export function formatDate(
timestamp: DateInput,
options?: {
forceFormat?: "date-only" | "date-time";