refactor(dataflow): consolidate time parsing types and remove debug files

- Remove temporary debug files (debug-actual-parsing.js, debug-task-parsing.js, debug-time-parsing.js)
- Move time parsing type definitions from task.d.ts to dedicated time-parsing.ts
- Update all tests and services to import from new type location
- Simplify BaseTask interface by extracting time-related properties
- Minor CSS adjustments for timeline sidebar and main styles
This commit is contained in:
Quorafind 2025-08-24 21:04:28 +08:00
parent dc364df963
commit 13bd8f3b05
18 changed files with 185 additions and 427 deletions

View file

@ -1,195 +0,0 @@
// Debug script to simulate the actual task parsing process
console.log("=== Simulating Task Parsing Process ===");
// Simulate the task content
const taskContent = "12:00-13:00 New 任务 #project/任务 🔼 📅 2025-06-19";
console.log("Task content:", taskContent);
// Simulate the time parsing service configuration (from DEFAULT_SETTINGS)
const timeParsingConfig = {
enabled: true,
supportedLanguages: ["en", "zh"],
dateKeywords: {
start: ["start", "begin", "from", "starting", "begins", "开始", "从", "起始", "起", "始于", "自"],
due: ["due", "deadline", "by", "until", "before", "expires", "ends", "截止", "到期", "之前", "期限", "最晚", "结束", "终止", "完成于"],
scheduled: ["scheduled", "on", "at", "planned", "set for", "arranged", "安排", "计划", "在", "定于", "预定", "约定", "设定"]
},
removeOriginalText: true,
perLineProcessing: true,
realTimeReplacement: true,
timePatterns: {
singleTime: [
/\b([01]?\d|2[0-3]):([0-5]\d)(?::([0-5]\d))?\b/g,
/\b(1[0-2]|0?[1-9]):([0-5]\d)(?::([0-5]\d))?\s*(AM|PM|am|pm)\b/g
],
timeRange: [
/\b([01]?\d|2[0-3]):([0-5]\d)(?::([0-5]\d))?\s*[-~]\s*([01]?\d|2[0-3]):([0-5]\d)(?::([0-5]\d))?\b/g,
/\b(1[0-2]|0?[1-9]):([0-5]\d)(?::([0-5]\d))?\s*(AM|PM|am|pm)?\s*[-~]\s*(1[0-2]|0?[1-9]):([0-5]\d)(?::([0-5]\d))?\s*(AM|PM|am|pm)\b/g
],
rangeSeparators: ["-", "~", ""]
},
timeDefaults: {
preferredFormat: "24h",
defaultPeriod: "AM",
midnightCrossing: "next-day"
}
};
console.log("\n=== Time Parsing Configuration ===");
console.log("Enabled:", timeParsingConfig.enabled);
console.log("Range separators:", timeParsingConfig.timePatterns.rangeSeparators);
// Test the time range regex from the config
console.log("\n=== Testing Time Range Regex from Config ===");
const timeRangeRegex = timeParsingConfig.timePatterns.timeRange[0];
console.log("Regex:", timeRangeRegex);
const matches = [...taskContent.matchAll(timeRangeRegex)];
console.log("Matches found:", matches.length);
matches.forEach((match, index) => {
console.log(`Match ${index}:`, {
fullMatch: match[0],
position: match.index,
groups: match.slice(1)
});
});
// Simulate the parseTimeComponent function
function parseTimeComponent(timeText) {
const cleanedText = timeText.trim();
// Try 24-hour format
const match24h = cleanedText.match(/^([01]?\d|2[0-3]):([0-5]\d)(?::([0-5]\d))?$/);
if (match24h) {
const hour = parseInt(match24h[1], 10);
const minute = parseInt(match24h[2], 10);
const second = match24h[3] ? parseInt(match24h[3], 10) : undefined;
return {
hour,
minute,
second,
originalText: cleanedText,
isRange: false,
};
}
return null;
}
// Simulate the extractTimeComponents function
function extractTimeComponents(text) {
const timeComponents = {};
const timeExpressions = [];
// Check for time ranges first
const rangeMatches = [...text.matchAll(timeRangeRegex)];
for (const match of rangeMatches) {
const fullMatch = match[0];
const index = match.index || 0;
// Parse start and end times
const parts = fullMatch.split(/\s*[-~\uff5e]\s*/);
if (parts.length === 2) {
const startTime = parseTimeComponent(parts[0]);
const endTime = parseTimeComponent(parts[1]);
if (startTime && endTime) {
startTime.isRange = true;
endTime.isRange = true;
startTime.rangePartner = endTime;
endTime.rangePartner = startTime;
timeExpressions.push({
text: fullMatch,
index,
isRange: true,
rangeStart: startTime,
rangeEnd: endTime,
});
// Determine context for time range
const context = determineTimeContext(text, fullMatch, index);
console.log("Determined context:", context);
if (context === "start" || !timeComponents.startTime) {
timeComponents.startTime = startTime;
timeComponents.endTime = endTime;
} else if (context === "due") {
timeComponents.dueTime = startTime;
// For due time ranges, we might want to use the end time as the actual due time
// But for now, let's keep it as start time for consistency
} else if (context === "scheduled") {
timeComponents.scheduledTime = startTime;
}
}
}
}
return { timeComponents, timeExpressions };
}
// Simulate the determineTimeContext function
function determineTimeContext(text, expression, index) {
const beforeText = text.substring(Math.max(0, index - 20), index).toLowerCase();
const afterText = text.substring(index + expression.length, Math.min(text.length, index + expression.length + 20)).toLowerCase();
const context = beforeText + " " + afterText;
// Check for start keywords first
for (const keyword of timeParsingConfig.dateKeywords.start) {
if (context.includes(keyword.toLowerCase())) {
return "start";
}
}
// Check for scheduled keywords
for (const keyword of timeParsingConfig.dateKeywords.scheduled) {
if (context.includes(keyword.toLowerCase())) {
return "scheduled";
}
}
// Check for due keywords
for (const keyword of timeParsingConfig.dateKeywords.due) {
if (context.includes(keyword.toLowerCase())) {
return "due";
}
}
// Default based on common patterns
if (context.includes("at") || context.includes("@")) {
return "scheduled";
}
// Default to due if no specific context found
return "due";
}
console.log("\n=== Simulating extractTimeComponents ===");
const result = extractTimeComponents(taskContent);
// Handle circular references by creating a safe representation
function safeStringify(obj) {
const seen = new WeakSet();
return JSON.stringify(obj, (key, val) => {
if (val != null && typeof val == "object") {
if (seen.has(val)) {
return "[Circular]";
}
seen.add(val);
}
return val;
}, 2);
}
console.log("Time components:", safeStringify(result.timeComponents));
console.log("Time expressions:", safeStringify(result.timeExpressions));
// Check what should be in the task metadata
console.log("\n=== Expected Task Metadata ===");
if (Object.keys(result.timeComponents).length > 0) {
console.log("Should have timeComponents:", result.timeComponents);
console.log("Should have enhancedDates (if combined with date)");
} else {
console.log("No time components found - this is the problem!");
}

View file

@ -1,87 +0,0 @@
// Debug script to test task parsing with time components
const taskContent = "12:00-13:00 New 任务 #project/任务 🔼 📅 2025-06-19";
console.log("Testing task content:", taskContent);
// Test the time parsing service patterns
const TIME_RANGE = /\b([01]?\d|2[0-3]):([0-5]\d)(?::([0-5]\d))?\s*[-~]\s*([01]?\d|2[0-3]):([0-5]\d)(?::([0-5]\d))?\b/g;
console.log("\n=== Time Range Pattern Test ===");
const rangeMatches = [...taskContent.matchAll(TIME_RANGE)];
console.log("Range matches found:", rangeMatches.length);
rangeMatches.forEach((match, index) => {
console.log(`Range Match ${index}:`, {
fullMatch: match[0],
position: match.index,
startHour: match[1],
startMinute: match[2],
endHour: match[4],
endMinute: match[5]
});
});
// Test context determination
const beforeText = taskContent.substring(Math.max(0, 0 - 20), 0).toLowerCase();
const afterText = taskContent.substring(0 + "12:00-13:00".length, Math.min(taskContent.length, 0 + "12:00-13:00".length + 20)).toLowerCase();
const context = beforeText + " " + afterText;
console.log("\n=== Context Analysis ===");
console.log("Before text:", beforeText);
console.log("After text:", afterText);
console.log("Combined context:", context);
// Test date keywords
const dateKeywords = {
start: ["start", "from", "begin", "开始"],
due: ["due", "deadline", "by", "截止", "到期"],
scheduled: ["at", "on", "scheduled", "安排", "@"]
};
console.log("\n=== Keyword Detection ===");
for (const [type, keywords] of Object.entries(dateKeywords)) {
const found = keywords.some(keyword => context.includes(keyword.toLowerCase()));
console.log(`${type}: ${found} (keywords: ${keywords.join(', ')})`);
}
// Test if the time range would be detected as "due" (default)
console.log("\nExpected context type: due (default)");
// Test individual time parsing
function parseTimeComponent(timeText) {
const cleanedText = timeText.trim();
// Try 24-hour format
const match24h = cleanedText.match(/^([01]?\d|2[0-3]):([0-5]\d)(?::([0-5]\d))?$/);
if (match24h) {
const hour = parseInt(match24h[1], 10);
const minute = parseInt(match24h[2], 10);
const second = match24h[3] ? parseInt(match24h[3], 10) : undefined;
return {
hour,
minute,
second,
originalText: cleanedText,
isRange: false,
};
}
return null;
}
console.log("\n=== Individual Time Component Parsing ===");
const startTime = parseTimeComponent("12:00");
const endTime = parseTimeComponent("13:00");
console.log("Start time component:", startTime);
console.log("End time component:", endTime);
if (startTime && endTime) {
startTime.isRange = true;
endTime.isRange = true;
startTime.rangePartner = endTime;
endTime.rangePartner = startTime;
console.log("Range setup complete:");
console.log("Start time with range:", startTime);
console.log("End time with range:", endTime);
}

View file

@ -1,22 +0,0 @@
// Debug script to test time parsing
const testText = "12:00-13:00 New 任务 #project/任务 🔼 📅 2025-06-19";
// Test the regex patterns
const TIME_RANGE = /\b([01]?\d|2[0-3]):([0-5]\d)(?::([0-5]\d))?\s*[-~]\s*([01]?\d|2[0-3]):([0-5]\d)(?::([0-5]\d))?\b/g;
console.log("Testing time range regex:");
console.log("Text:", testText);
console.log("Regex:", TIME_RANGE);
const matches = [...testText.matchAll(TIME_RANGE)];
console.log("Matches found:", matches.length);
matches.forEach((match, index) => {
console.log(`Match ${index}:`, match[0], "at position", match.index);
});
// Test individual time parsing
const timeText = "12:00";
const TIME_24H = /^([01]?\d|2[0-3]):([0-5]\d)(?::([0-5]\d))?$/;
console.log("\nTesting individual time parsing:");
console.log("Time text:", timeText);
console.log("24h regex match:", timeText.match(TIME_24H));

View file

@ -180,6 +180,11 @@ describe("Enhanced Time Parsing Edge Cases", () => {
test("should respect configuration for ambiguous time handling", () => {
const config: EnhancedTimeParsingConfig = {
...DEFAULT_TIME_PARSING_CONFIG,
timePatterns: {
singleTime: [/\d{1,2}:\d{2}(?::\d{2})?(?:\s*(?:AM|PM|am|pm))?/],
timeRange: [/\d{1,2}:\d{2}(?:\s*(?:AM|PM|am|pm))?\s*[-–—~]\s*\d{1,2}:\d{2}(?:\s*(?:AM|PM|am|pm))?/],
rangeSeparators: ["-", "", "—", "~"],
},
timeDefaults: {
preferredFormat: "12h",
defaultPeriod: "PM",
@ -382,6 +387,11 @@ describe("Enhanced Time Parsing Edge Cases", () => {
test("should respect midnight crossing configuration", () => {
const nextDayConfig: EnhancedTimeParsingConfig = {
...DEFAULT_TIME_PARSING_CONFIG,
timePatterns: {
singleTime: [/\d{1,2}:\d{2}(?::\d{2})?(?:\s*(?:AM|PM|am|pm))?/],
timeRange: [/\d{1,2}:\d{2}(?:\s*(?:AM|PM|am|pm))?\s*[-–—~]\s*\d{1,2}:\d{2}(?:\s*(?:AM|PM|am|pm))?/],
rangeSeparators: ["-", "", "—", "~"],
},
timeDefaults: {
preferredFormat: "24h",
defaultPeriod: "PM",
@ -391,6 +401,11 @@ describe("Enhanced Time Parsing Edge Cases", () => {
const sameDayConfig: EnhancedTimeParsingConfig = {
...DEFAULT_TIME_PARSING_CONFIG,
timePatterns: {
singleTime: [/\d{1,2}:\d{2}(?::\d{2})?(?:\s*(?:AM|PM|am|pm))?/],
timeRange: [/\d{1,2}:\d{2}(?:\s*(?:AM|PM|am|pm))?\s*[-–—~]\s*\d{1,2}:\d{2}(?:\s*(?:AM|PM|am|pm))?/],
rangeSeparators: ["-", "", "—", "~"],
},
timeDefaults: {
preferredFormat: "24h",
defaultPeriod: "PM",
@ -413,9 +428,19 @@ describe("Enhanced Time Parsing Edge Cases", () => {
});
test("should handle disabled time parsing", () => {
const disabledConfig = {
const disabledConfig: EnhancedTimeParsingConfig = {
...DEFAULT_TIME_PARSING_CONFIG,
enabled: false,
timePatterns: {
singleTime: [/\d{1,2}:\d{2}(?::\d{2})?(?:\s*(?:AM|PM|am|pm))?/],
timeRange: [/\d{1,2}:\d{2}(?:\s*(?:AM|PM|am|pm))?\s*[-–—~]\s*\d{1,2}:\d{2}(?:\s*(?:AM|PM|am|pm))?/],
rangeSeparators: ["-", "", "—", "~"],
},
timeDefaults: {
preferredFormat: "24h",
defaultPeriod: "PM",
midnightCrossing: "next-day",
},
};
const disabledService = new TimeParsingService(disabledConfig);
@ -437,6 +462,11 @@ describe("Enhanced Time Parsing Edge Cases", () => {
timeRange: [/\d{1,2}h\d{2}-\d{1,2}h\d{2}/], // Custom range format
rangeSeparators: ["-"],
},
timeDefaults: {
preferredFormat: "24h",
defaultPeriod: "PM",
midnightCrossing: "next-day",
},
};
const customService = new TimeParsingService(customConfig);

View file

@ -177,7 +177,7 @@ describe("Enhanced Time Parsing Integration Tests", () => {
completed: false,
due: "2025-08-25",
},
getValue: jest.fn((prop) => {
getValue: jest.fn((prop: any) => {
if (prop.name === "title") return "Team meeting 14:00-16:00 tomorrow";
if (prop.name === "status") return " ";
if (prop.name === "completed") return false;
@ -205,7 +205,7 @@ describe("Enhanced Time Parsing Integration Tests", () => {
id: fileTask.id,
title: fileTask.content,
date: new Date(fileTask.metadata.dueDate!),
task: fileTask,
task: fileTask as any,
timeInfo: {
primaryTime: fileTask.metadata.enhancedDates!.startDateTime!,
endTime: fileTask.metadata.enhancedDates!.endDateTime,
@ -324,7 +324,7 @@ describe("Enhanced Time Parsing Integration Tests", () => {
status: " ",
completed: false,
},
getValue: jest.fn((prop) => {
getValue: jest.fn((prop: any) => {
if (prop.name === "title") return "Doctor appointment at 2:00 PM";
if (prop.name === "status") return " ";
if (prop.name === "completed") return false;
@ -588,8 +588,8 @@ END:VCALENDAR`;
// Should have original ICS time information
expect(task.icsEvent).toBeDefined();
expect(task.icsEvent?.start).toBeDefined();
expect(task.icsEvent?.end).toBeDefined();
expect(task.icsEvent?.dtstart).toBeDefined();
expect(task.icsEvent?.dtend).toBeDefined();
// Should have enhanced time components from ICS times
expect(metadata.timeComponents).toBeDefined();

View file

@ -271,7 +271,7 @@ describe("Enhanced Time Parsing Performance Tests", () => {
status: " ",
completed: false,
},
getValue: jest.fn((prop) => {
getValue: jest.fn((prop: any) => {
if (prop.name === "title") return `Task ${index} at ${9 + (index % 8)}:${(index % 4) * 15} AM`;
if (prop.name === "status") return " ";
if (prop.name === "completed") return false;
@ -331,7 +331,13 @@ describe("Enhanced Time Parsing Performance Tests", () => {
batchSizes.forEach(batchSize => {
const mockEntries = Array.from({ length: batchSize }, (_, i) => ({
ctx: {},
ctx: {
_local: {},
app: {} as any,
filter: {},
formulas: {},
localUsed: false
},
file: {
parent: null,
deleted: false,
@ -355,7 +361,7 @@ describe("Enhanced Time Parsing Performance Tests", () => {
status: " ",
completed: false,
},
getValue: jest.fn((prop) => {
getValue: jest.fn((prop: any) => {
if (prop.name === "title") return `Batch task ${i} at ${10 + (i % 6)}:${(i % 4) * 15}`;
if (prop.name === "status") return " ";
if (prop.name === "completed") return false;
@ -714,7 +720,7 @@ describe("Enhanced Time Parsing Performance Tests", () => {
// Benchmark parsing tasks without time
const startTime = performance.now();
tasksWithoutTime.forEach(taskText => {
const result = enhancedTimeParsingService.parseTimeExpressions(taskText);
const result = enhancedTimeParsingService.parseTimeExpressions(taskText) as EnhancedParsedTimeResult;
// Access the result to ensure it's computed
const hasTime = result.timeComponents && Object.keys(result.timeComponents).length > 0;
});
@ -744,7 +750,7 @@ describe("Enhanced Time Parsing Performance Tests", () => {
for (let i = 0; i < iterations; i++) {
edgeCases.forEach(edgeCase => {
try {
const result = enhancedTimeParsingService.parseTimeExpressions(edgeCase);
const result = enhancedTimeParsingService.parseTimeExpressions(edgeCase) as EnhancedParsedTimeResult;
// Access the result
const hasTime = result.timeComponents && Object.keys(result.timeComponents).length > 0;
} catch (error) {

View file

@ -100,7 +100,7 @@ describe("FileTaskManager Time Integration", () => {
status: " ",
completed: false,
},
getValue: jest.fn((prop) => {
getValue: jest.fn((prop: any) => {
if (prop.name === "title") return "Meeting at 2:30 PM";
if (prop.name === "status") return " ";
if (prop.name === "completed") return false;
@ -159,7 +159,7 @@ describe("FileTaskManager Time Integration", () => {
status: " ",
completed: false,
},
getValue: jest.fn((prop) => {
getValue: jest.fn((prop: any) => {
if (prop.name === "title") return "Workshop 9:00-17:00";
if (prop.name === "status") return " ";
if (prop.name === "completed") return false;
@ -209,7 +209,7 @@ describe("FileTaskManager Time Integration", () => {
completed: false,
due: "2025-08-25", // Date in YYYY-MM-DD format
},
getValue: jest.fn((prop) => {
getValue: jest.fn((prop: any) => {
if (prop.name === "title") return "Doctor appointment at 3:30 PM";
if (prop.name === "status") return " ";
if (prop.name === "completed") return false;
@ -269,7 +269,7 @@ describe("FileTaskManager Time Integration", () => {
status: " ",
completed: false,
},
getValue: jest.fn((prop) => {
getValue: jest.fn((prop: any) => {
if (prop.name === "title") return "Simple task without time";
if (prop.name === "status") return " ";
if (prop.name === "completed") return false;
@ -315,7 +315,7 @@ describe("FileTaskManager Time Integration", () => {
status: " ",
completed: false,
},
getValue: jest.fn((prop) => {
getValue: jest.fn((prop: any) => {
if (prop.name === "title") return "Meeting at 2:00 PM";
if (prop.name === "status") return " ";
if (prop.name === "completed") return false;
@ -375,7 +375,7 @@ describe("FileTaskManager Time Integration", () => {
status: " ",
completed: false,
},
getValue: jest.fn((prop) => {
getValue: jest.fn((prop: any) => {
if (prop.name === "title") return "Task with invalid time 25:99";
if (prop.name === "status") return " ";
if (prop.name === "completed") return false;

View file

@ -6,6 +6,7 @@ import { MarkdownTaskParser, ConfigurableTaskParser } from "../dataflow/core/Con
import { TimeParsingService } from "../services/time-parsing-service";
import { TimeParsingConfig } from "../services/time-parsing-service";
import { TaskParserConfig, MetadataParseMode } from "../types/TaskParserConfig";
import { EnhancedStandardTaskMetadata } from "../types/time-parsing";
describe("Inline Task Time Integration", () => {
let timeParsingService: TimeParsingService;
@ -49,10 +50,10 @@ describe("Inline Task Time Integration", () => {
const task = tasks[0];
expect(task.content).toBe("Meeting at 2:30 PM");
expect(task.metadata.timeComponents).toBeDefined();
expect((task.metadata as EnhancedStandardTaskMetadata).timeComponents).toBeDefined();
// Check time component (could be dueTime or scheduledTime based on context)
const timeComponent = task.metadata.timeComponents?.dueTime || task.metadata.timeComponents?.scheduledTime;
const timeComponent = (task.metadata as EnhancedStandardTaskMetadata).timeComponents?.dueTime || (task.metadata as EnhancedStandardTaskMetadata).timeComponents?.scheduledTime;
expect(timeComponent).toBeDefined();
expect(timeComponent?.hour).toBe(14);
expect(timeComponent?.minute).toBe(30);
@ -66,9 +67,9 @@ describe("Inline Task Time Integration", () => {
const task = tasks[0];
expect(task.content).toBe("Call client at 15:45");
expect(task.metadata.timeComponents).toBeDefined();
expect((task.metadata as EnhancedStandardTaskMetadata).timeComponents).toBeDefined();
const timeComponent = task.metadata.timeComponents?.dueTime || task.metadata.timeComponents?.scheduledTime;
const timeComponent = (task.metadata as EnhancedStandardTaskMetadata).timeComponents?.dueTime || (task.metadata as EnhancedStandardTaskMetadata).timeComponents?.scheduledTime;
expect(timeComponent).toBeDefined();
expect(timeComponent?.hour).toBe(15);
expect(timeComponent?.minute).toBe(45);
@ -84,13 +85,13 @@ describe("Inline Task Time Integration", () => {
const task = tasks[0];
expect(task.content).toBe("Workshop 9:00-17:00");
expect(task.metadata.timeComponents).toBeDefined();
expect(task.metadata.timeComponents?.startTime).toBeDefined();
expect(task.metadata.timeComponents?.endTime).toBeDefined();
expect(task.metadata.timeComponents?.startTime?.hour).toBe(9);
expect(task.metadata.timeComponents?.startTime?.minute).toBe(0);
expect(task.metadata.timeComponents?.endTime?.hour).toBe(17);
expect(task.metadata.timeComponents?.endTime?.minute).toBe(0);
expect((task.metadata as EnhancedStandardTaskMetadata).timeComponents).toBeDefined();
expect((task.metadata as EnhancedStandardTaskMetadata).timeComponents?.startTime).toBeDefined();
expect((task.metadata as EnhancedStandardTaskMetadata).timeComponents?.endTime).toBeDefined();
expect((task.metadata as EnhancedStandardTaskMetadata).timeComponents?.startTime?.hour).toBe(9);
expect((task.metadata as EnhancedStandardTaskMetadata).timeComponents?.startTime?.minute).toBe(0);
expect((task.metadata as EnhancedStandardTaskMetadata).timeComponents?.endTime?.hour).toBe(17);
expect((task.metadata as EnhancedStandardTaskMetadata).timeComponents?.endTime?.minute).toBe(0);
});
it("should extract 12-hour format time range", () => {
@ -101,13 +102,13 @@ describe("Inline Task Time Integration", () => {
const task = tasks[0];
expect(task.content).toBe("Meeting 2:30 PM - 4:00 PM");
expect(task.metadata.timeComponents).toBeDefined();
expect(task.metadata.timeComponents?.startTime).toBeDefined();
expect(task.metadata.timeComponents?.endTime).toBeDefined();
expect(task.metadata.timeComponents?.startTime?.hour).toBe(14);
expect(task.metadata.timeComponents?.startTime?.minute).toBe(30);
expect(task.metadata.timeComponents?.endTime?.hour).toBe(16);
expect(task.metadata.timeComponents?.endTime?.minute).toBe(0);
expect((task.metadata as EnhancedStandardTaskMetadata).timeComponents).toBeDefined();
expect((task.metadata as EnhancedStandardTaskMetadata).timeComponents?.startTime).toBeDefined();
expect((task.metadata as EnhancedStandardTaskMetadata).timeComponents?.endTime).toBeDefined();
expect((task.metadata as EnhancedStandardTaskMetadata).timeComponents?.startTime?.hour).toBe(14);
expect((task.metadata as EnhancedStandardTaskMetadata).timeComponents?.startTime?.minute).toBe(30);
expect((task.metadata as EnhancedStandardTaskMetadata).timeComponents?.endTime?.hour).toBe(16);
expect((task.metadata as EnhancedStandardTaskMetadata).timeComponents?.endTime?.minute).toBe(0);
});
});
@ -120,17 +121,17 @@ describe("Inline Task Time Integration", () => {
const task = tasks[0];
expect(task.content).toBe("Doctor appointment at 3:30 PM");
expect(task.metadata.timeComponents).toBeDefined();
expect((task.metadata as EnhancedStandardTaskMetadata).timeComponents).toBeDefined();
// Check time component
const timeComponent = task.metadata.timeComponents?.dueTime || task.metadata.timeComponents?.scheduledTime;
const timeComponent = (task.metadata as EnhancedStandardTaskMetadata).timeComponents?.dueTime || (task.metadata as EnhancedStandardTaskMetadata).timeComponents?.scheduledTime;
expect(timeComponent).toBeDefined();
expect(timeComponent?.hour).toBe(15);
expect(timeComponent?.minute).toBe(30);
// Check that enhanced datetime was created
expect(task.metadata.enhancedDates?.dueDateTime).toBeDefined();
const dueDateTime = task.metadata.enhancedDates?.dueDateTime;
expect((task.metadata as EnhancedStandardTaskMetadata).enhancedDates?.dueDateTime).toBeDefined();
const dueDateTime = (task.metadata as EnhancedStandardTaskMetadata).enhancedDates?.dueDateTime;
if (dueDateTime) {
expect(dueDateTime.getFullYear()).toBe(2025);
expect(dueDateTime.getMonth()).toBe(7); // August (0-based)
@ -148,15 +149,15 @@ describe("Inline Task Time Integration", () => {
const task = tasks[0];
expect(task.content).toBe("Project deadline at 5:00 PM");
expect(task.metadata.timeComponents).toBeDefined();
expect((task.metadata as EnhancedStandardTaskMetadata).timeComponents).toBeDefined();
const timeComponent = task.metadata.timeComponents?.dueTime || task.metadata.timeComponents?.scheduledTime;
const timeComponent = (task.metadata as EnhancedStandardTaskMetadata).timeComponents?.dueTime || (task.metadata as EnhancedStandardTaskMetadata).timeComponents?.scheduledTime;
expect(timeComponent).toBeDefined();
expect(timeComponent?.hour).toBe(17);
expect(timeComponent?.minute).toBe(0);
// Check enhanced datetime
expect(task.metadata.enhancedDates?.dueDateTime).toBeDefined();
expect((task.metadata as EnhancedStandardTaskMetadata).enhancedDates?.dueDateTime).toBeDefined();
});
});
@ -172,18 +173,18 @@ describe("Inline Task Time Integration", () => {
// First task
expect(tasks[0].content).toBe("Morning meeting at 9:00 AM");
const time1 = tasks[0].metadata.timeComponents?.dueTime || tasks[0].metadata.timeComponents?.scheduledTime;
const time1 = (tasks[0].metadata as EnhancedStandardTaskMetadata).timeComponents?.dueTime || (tasks[0].metadata as EnhancedStandardTaskMetadata).timeComponents?.scheduledTime;
expect(time1?.hour).toBe(9);
expect(time1?.minute).toBe(0);
// Second task (time range)
expect(tasks[1].content).toBe("Lunch break 12:00-13:00");
expect(tasks[1].metadata.timeComponents?.startTime?.hour).toBe(12);
expect(tasks[1].metadata.timeComponents?.endTime?.hour).toBe(13);
expect((tasks[1].metadata as EnhancedStandardTaskMetadata).timeComponents?.startTime?.hour).toBe(12);
expect((tasks[1].metadata as EnhancedStandardTaskMetadata).timeComponents?.endTime?.hour).toBe(13);
// Third task
expect(tasks[2].content).toBe("Afternoon call at 3:30 PM");
const time3 = tasks[2].metadata.timeComponents?.dueTime || tasks[2].metadata.timeComponents?.scheduledTime;
const time3 = (tasks[2].metadata as EnhancedStandardTaskMetadata).timeComponents?.dueTime || (tasks[2].metadata as EnhancedStandardTaskMetadata).timeComponents?.scheduledTime;
expect(time3?.hour).toBe(15);
expect(time3?.minute).toBe(30);
});
@ -201,17 +202,17 @@ describe("Inline Task Time Integration", () => {
// Parent task (time range)
expect(tasks[0].content).toBe("Project meeting 2:00 PM - 4:00 PM");
expect(tasks[0].metadata.timeComponents?.startTime?.hour).toBe(14);
expect(tasks[0].metadata.timeComponents?.endTime?.hour).toBe(16);
expect((tasks[0].metadata as EnhancedStandardTaskMetadata).timeComponents?.startTime?.hour).toBe(14);
expect((tasks[0].metadata as EnhancedStandardTaskMetadata).timeComponents?.endTime?.hour).toBe(16);
// Child tasks
expect(tasks[1].content).toBe("Review agenda at 1:45 PM");
const time1 = tasks[1].metadata.timeComponents?.dueTime || tasks[1].metadata.timeComponents?.scheduledTime;
const time1 = (tasks[1].metadata as EnhancedStandardTaskMetadata).timeComponents?.dueTime || (tasks[1].metadata as EnhancedStandardTaskMetadata).timeComponents?.scheduledTime;
expect(time1?.hour).toBe(13);
expect(time1?.minute).toBe(45);
expect(tasks[2].content).toBe("Prepare slides at 1:30 PM");
const time2 = tasks[2].metadata.timeComponents?.dueTime || tasks[2].metadata.timeComponents?.scheduledTime;
const time2 = (tasks[2].metadata as EnhancedStandardTaskMetadata).timeComponents?.dueTime || (tasks[2].metadata as EnhancedStandardTaskMetadata).timeComponents?.scheduledTime;
expect(time2?.hour).toBe(13);
expect(time2?.minute).toBe(30);
});
@ -226,8 +227,8 @@ describe("Inline Task Time Integration", () => {
const task = tasks[0];
expect(task.content).toBe("Simple task without time");
expect(task.metadata.timeComponents).toBeUndefined();
expect(task.metadata.enhancedDates).toBeUndefined();
expect((task.metadata as EnhancedStandardTaskMetadata).timeComponents).toBeUndefined();
expect((task.metadata as EnhancedStandardTaskMetadata).enhancedDates).toBeUndefined();
});
it("should handle invalid time formats gracefully", () => {
@ -250,7 +251,7 @@ describe("Inline Task Time Integration", () => {
const task = tasks[0];
expect(task.content).toBe("Meeting at 2:30 PM");
expect(task.metadata.timeComponents).toBeUndefined();
expect((task.metadata as EnhancedStandardTaskMetadata).timeComponents).toBeUndefined();
});
});
});

View file

@ -57,7 +57,7 @@ describe("TimeParsingSettingsIntegration", () => {
service.updateConfig(updatedConfig);
// Verify the configuration was updated
const config = service.getConfig();
const config = service.getConfig() as EnhancedTimeParsingConfig;
expect(config.timeDefaults?.preferredFormat).toBe("12h");
});
@ -74,7 +74,7 @@ describe("TimeParsingSettingsIntegration", () => {
service.updateConfig(updatedConfig);
// Verify the configuration was updated
const config = service.getConfig();
const config = service.getConfig() as EnhancedTimeParsingConfig;
expect(config.timeDefaults?.defaultPeriod).toBe("PM");
});
@ -91,7 +91,7 @@ describe("TimeParsingSettingsIntegration", () => {
service.updateConfig(updatedConfig);
// Verify the configuration was updated
const config = service.getConfig();
const config = service.getConfig() as EnhancedTimeParsingConfig;
expect(config.timeDefaults?.midnightCrossing).toBe("same-day");
});
@ -108,7 +108,7 @@ describe("TimeParsingSettingsIntegration", () => {
service.updateConfig(updatedConfig);
// Verify the configuration was updated
const config = service.getConfig();
const config = service.getConfig() as EnhancedTimeParsingConfig;
expect(config.timePatterns?.rangeSeparators).toEqual(["-", "to", "until"]);
});
@ -126,7 +126,7 @@ describe("TimeParsingSettingsIntegration", () => {
service.updateConfig(updatedConfig);
// Verify the configuration was updated
const config = service.getConfig();
const config = service.getConfig() as EnhancedTimeParsingConfig;
expect(config.dateKeywords.start).toEqual(["begin", "commence", "initiate"]);
expect(config.dateKeywords.due).toEqual(["deadline", "expires", "finish"]);
expect(config.dateKeywords.scheduled).toEqual(["planned", "arranged", "set"]);
@ -211,7 +211,7 @@ describe("TimeParsingSettingsIntegration", () => {
enabled: false,
});
const config = service.getConfig();
const config = service.getConfig() as EnhancedTimeParsingConfig;
expect(config.enabled).toBe(false);
// Other settings should remain unchanged
expect(config.supportedLanguages).toEqual(["en", "zh"]);
@ -264,7 +264,7 @@ describe("TimeParsingSettingsIntegration", () => {
service.updateConfig(config12h);
// Verify initial configuration
let config = service.getConfig();
let config = service.getConfig() as EnhancedTimeParsingConfig;
expect(config.timeDefaults?.preferredFormat).toBe("12h");
// Switch to 24h format
@ -279,7 +279,7 @@ describe("TimeParsingSettingsIntegration", () => {
service.updateConfig(config24h);
// Verify configuration was updated
config = service.getConfig();
config = service.getConfig() as EnhancedTimeParsingConfig;
expect(config.timeDefaults?.preferredFormat).toBe("24h");
});
@ -296,7 +296,7 @@ describe("TimeParsingSettingsIntegration", () => {
service.updateConfig(customConfig);
// Verify configuration was updated
const config = service.getConfig();
const config = service.getConfig() as EnhancedTimeParsingConfig;
expect(config.timePatterns?.rangeSeparators).toEqual(["-", "~", "to", "until", "through"]);
});

View file

@ -599,6 +599,11 @@ export class DataflowOrchestrator {
this.workerOrchestrator.setWorkerProcessingEnabled(enableWorkerProcessing);
}
// Update TimeParsingService configuration
if (settings.timeParsing && this.timeParsingService) {
this.timeParsingService.updateConfig(settings.timeParsing);
}
// Update FileSource if needed
if (settings?.fileSource?.enabled && !this.fileSource) {
// Initialize FileSource if enabled but not yet created
@ -1086,18 +1091,6 @@ export class DataflowOrchestrator {
this.projectResolver.updateOptions(options);
}
/**
* Update settings for all components
*/
updateSettings(settings: any): void {
// Update TimeParsingService configuration
if (settings.timeParsing) {
this.timeParsingService.updateConfig(settings.timeParsing);
}
// Update other components as needed
// This method can be extended to update other services when their settings change
}
/**
* Get the query API for external access

View file

@ -164,7 +164,7 @@ export class DateInheritanceAugmentor {
(timeComponents.dueTime && !metadata.dueDate) ||
(timeComponents.scheduledTime && !metadata.scheduledDate);
return hasTimeWithoutDate;
return hasTimeWithoutDate || false;
}
/**
@ -370,15 +370,12 @@ export class DateInheritanceAugmentor {
switch (type) {
case 'startDate':
updatedMetadata.startDate = timestamp;
updatedTask.startDate = timestamp; // Legacy field
break;
case 'dueDate':
updatedMetadata.dueDate = timestamp;
updatedTask.dueDate = timestamp; // Legacy field
break;
case 'scheduledDate':
updatedMetadata.scheduledDate = timestamp;
updatedTask.scheduledDate = timestamp; // Legacy field
break;
}

View file

@ -361,7 +361,10 @@ export class IcsManager extends Component {
);
// Extract time components from event description and preserve original ICS time information
const enhancedMetadata = this.extractTimeComponentsFromIcsEvent(event, processedEvent);
const enhancedMetadata = this.extractTimeComponentsFromIcsEvent(event, {
...event,
...processedEvent,
});
const task: IcsTask = {
id: `ics-${event.source.id}-${event.uid}`,
@ -437,7 +440,10 @@ export class IcsManager extends Component {
);
// Extract time components from event description and preserve original ICS time information
const enhancedMetadata = this.extractTimeComponentsFromIcsEvent(event, processedEvent);
const enhancedMetadata = this.extractTimeComponentsFromIcsEvent(event, {
...event,
...processedEvent,
});
const task: IcsTask = {
id: `ics-${event.source.id}-${event.uid}`,

View file

@ -264,7 +264,7 @@ export class DateInheritanceService {
// Return default info for non-existent files
return {
ctime: new Date(),
dailyNoteDate: null,
dailyNoteDate: undefined,
metadataDate: undefined,
isDailyNote: false,
cachedAt: new Date(),
@ -277,11 +277,13 @@ export class DateInheritanceService {
const mtime = fileStat?.mtime || 0;
// Extract daily note date from file path/title
const dailyNoteDate = this.extractDailyNoteDate(filePath);
const isDailyNote = dailyNoteDate !== null;
const dailyNoteDateResult = this.extractDailyNoteDate(filePath);
const dailyNoteDate = dailyNoteDateResult ?? undefined;
const isDailyNote = dailyNoteDateResult !== null;
// Extract metadata date from frontmatter
const metadataDate = await this.extractMetadataDate(file);
const metadataDateResult = await this.extractMetadataDate(file);
const metadataDate = metadataDateResult ?? undefined;
const fileDateInfo: FileDateInfo = {
ctime,

View file

@ -170,7 +170,7 @@ export class TaskMigrationService {
if (hasTimeComponents(migratedMeta)) {
const timeComponents = migratedMeta.timeComponents!;
for (const [key, component] of Object.entries(timeComponents)) {
if (component && !validateTimeComponent(component)) {
if (component && !validateTimeComponent(component as TimeComponent)) {
return false;
}
}

View file

@ -209,22 +209,24 @@ div[data-type^="tg-timeline-sidebar-view"] .timeline-event-time.timeline-event-t
font-weight: 600;
min-width: 80px;
position: relative;
animation: subtle-pulse 2s ease-in-out infinite;
}
div[data-type^="tg-timeline-sidebar-view"] .timeline-event-time.timeline-event-time-range::after {
content: "⏱";
position: absolute;
top: -2px;
right: -2px;
top: -3px;
right: -3px;
font-size: 10px;
background-color: var(--background-primary);
border-radius: 50%;
width: 14px;
height: 14px;
width: 16px;
height: 16px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid var(--interactive-accent);
border: 2px solid var(--interactive-accent);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
}
div[data-type^="tg-timeline-sidebar-view"] .timeline-event-time.timeline-event-time-default {
@ -712,6 +714,15 @@ div[data-type^="tg-timeline-sidebar-view"]
}
}
@keyframes subtle-pulse {
0%, 100% {
box-shadow: 0 0 0 0 rgba(var(--interactive-accent-rgb), 0.4);
}
50% {
box-shadow: 0 0 0 4px rgba(var(--interactive-accent-rgb), 0.1);
}
}
/* Focus Mode */
div[data-type^="tg-timeline-sidebar-view"] .timeline-content.focus-mode {
position: relative;

30
src/types/task.d.ts vendored
View file

@ -3,7 +3,6 @@
*/
import { Component, EventRef, TFile } from "obsidian";
import { TimeComponent } from "./time-parsing";
/** Base task interface with only core required fields */
export interface BaseTask {
@ -79,32 +78,6 @@ export interface StandardTaskMetadata {
[key: string]: any;
}
/** Enhanced task metadata interface with time components */
export interface EnhancedStandardTaskMetadata extends StandardTaskMetadata {
/** Time-specific metadata (separate from date timestamps) */
timeComponents?: {
/** Start time component */
startTime?: TimeComponent;
/** End time component (for time ranges) */
endTime?: TimeComponent;
/** Due time component */
dueTime?: TimeComponent;
/** Scheduled time component */
scheduledTime?: TimeComponent;
};
/** Enhanced date fields that combine date + time */
enhancedDates?: {
/** Full datetime for start (combines startDate + startTime) */
startDateTime?: Date;
/** Full datetime for end (combines date + endTime) */
endDateTime?: Date;
/** Full datetime for due (combines dueDate + dueTime) */
dueDateTime?: Date;
/** Full datetime for scheduled (combines scheduledDate + scheduledTime) */
scheduledDateTime?: Date;
};
}
export interface StandardFileTaskMetadata extends StandardTaskMetadata {
/** Task source */
@ -159,6 +132,9 @@ export interface Task<
metadata: TMetadata;
}
// Re-export EnhancedStandardTaskMetadata from time-parsing module
export { EnhancedStandardTaskMetadata } from "./time-parsing";
/** Task with enhanced time component support */
export type EnhancedTask = Task<EnhancedStandardTaskMetadata>;

View file

@ -1,4 +1,5 @@
import { ParsedTimeResult, TimeParsingConfig } from "../services/time-parsing-service";
import { StandardTaskMetadata } from "./task";
/**
* Time component structure representing parsed time information
@ -92,4 +93,33 @@ export interface EnhancedParseResult {
result?: EnhancedParsedTimeResult;
errors: TimeParsingError[];
warnings: string[];
}
/**
* Enhanced task metadata interface with time components
*/
export interface EnhancedStandardTaskMetadata extends StandardTaskMetadata {
/** Time-specific metadata (separate from date timestamps) */
timeComponents?: {
/** Start time component */
startTime?: TimeComponent;
/** End time component (for time ranges) */
endTime?: TimeComponent;
/** Due time component */
dueTime?: TimeComponent;
/** Scheduled time component */
scheduledTime?: TimeComponent;
};
/** Enhanced date fields that combine date + time */
enhancedDates?: {
/** Full datetime for start (combines startDate + startTime) */
startDateTime?: Date;
/** Full datetime for end (combines date + endTime) */
endDateTime?: Date;
/** Full datetime for due (combines dueDate + dueTime) */
dueDateTime?: Date;
/** Full datetime for scheduled (combines scheduledDate + scheduledTime) */
scheduledDateTime?: Date;
};
}

File diff suppressed because one or more lines are too long