mirror of
https://github.com/taskgenius/taskgenius-plugin.git
synced 2026-07-22 06:40:25 +00:00
refactor(components): improve view management and ICS integration
- Optimize ViewComponentManager with better lifecycle management - Improve calendar view rendering and layout optimization - Fix task filter date range handling for better performance - Clean up FileSource for better dataflow integration - Enhance task details editor with optimized debouncing (800ms) - Remove deprecated configurable-task-parser - Improve IcsSource with retry mechanism for manager availability - Add 30-second fallback timeout for ICS initialization - Pass plugin instance to IcsManager for proper context access - Update tests to match new IcsManager constructor signature This improves overall component performance and reliability, particularly for calendar views and ICS event synchronization.
This commit is contained in:
parent
0c6db2537b
commit
d3a850b029
15 changed files with 465 additions and 334 deletions
|
|
@ -1285,7 +1285,7 @@ describe("TaskParsingService Integration", () => {
|
|||
|
||||
// Clear cache before test
|
||||
const { MarkdownTaskParser } = await import(
|
||||
"../parsers/configurable-task-parser"
|
||||
"../dataflow/core/ConfigurableTaskParser"
|
||||
);
|
||||
MarkdownTaskParser.clearDateCache();
|
||||
|
||||
|
|
@ -1335,7 +1335,7 @@ describe("TaskParsingService Integration", () => {
|
|||
|
||||
it("should handle date cache size limit correctly", async () => {
|
||||
const { MarkdownTaskParser } = await import(
|
||||
"../parsers/configurable-task-parser"
|
||||
"../dataflow/core/ConfigurableTaskParser"
|
||||
);
|
||||
|
||||
// Clear cache before test
|
||||
|
|
|
|||
|
|
@ -225,7 +225,7 @@ END:VCALENDAR`;
|
|||
|
||||
beforeEach(async () => {
|
||||
mockComponent = new MockComponent();
|
||||
icsManager = new IcsManager(testConfig, mockPluginSettings);
|
||||
icsManager = new IcsManager(testConfig, mockPluginSettings, {} as any);
|
||||
await icsManager.initialize();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ describe("ICS Manager", () => {
|
|||
|
||||
beforeEach(async () => {
|
||||
mockComponent = new MockComponent();
|
||||
icsManager = new IcsManager(testConfig, mockSettings);
|
||||
icsManager = new IcsManager(testConfig, mockSettings, {} as any);
|
||||
await icsManager.initialize();
|
||||
});
|
||||
|
||||
|
|
@ -265,7 +265,8 @@ describe("ICS Manager", () => {
|
|||
...testConfig,
|
||||
sources: [sourceWithReplacements],
|
||||
},
|
||||
mockSettings
|
||||
mockSettings,
|
||||
{} as any
|
||||
);
|
||||
|
||||
// Convert event to task (this will apply text replacements)
|
||||
|
|
@ -323,7 +324,8 @@ describe("ICS Manager", () => {
|
|||
...testConfig,
|
||||
sources: [sourceWithMultipleReplacements],
|
||||
},
|
||||
mockSettings
|
||||
mockSettings,
|
||||
{} as any
|
||||
);
|
||||
|
||||
const task = testManager.convertEventsToTasks([mockEvent])[0];
|
||||
|
|
@ -370,7 +372,8 @@ describe("ICS Manager", () => {
|
|||
...testConfig,
|
||||
sources: [sourceWithAllTarget],
|
||||
},
|
||||
mockSettings
|
||||
mockSettings,
|
||||
{} as any
|
||||
);
|
||||
|
||||
const task = testManager.convertEventsToTasks([mockEvent])[0];
|
||||
|
|
@ -428,7 +431,8 @@ describe("ICS Manager", () => {
|
|||
...testConfig,
|
||||
sources: [sourceWithDisabledRule],
|
||||
},
|
||||
mockSettings
|
||||
mockSettings,
|
||||
{} as any
|
||||
);
|
||||
|
||||
const task = testManager.convertEventsToTasks([mockEvent])[0];
|
||||
|
|
@ -473,7 +477,8 @@ describe("ICS Manager", () => {
|
|||
...testConfig,
|
||||
sources: [sourceWithInvalidRegex],
|
||||
},
|
||||
mockSettings
|
||||
mockSettings,
|
||||
{} as any
|
||||
);
|
||||
|
||||
// Should not throw an error, and text should remain unchanged
|
||||
|
|
@ -519,7 +524,8 @@ describe("ICS Manager", () => {
|
|||
...testConfig,
|
||||
sources: [sourceWithCaptureGroups],
|
||||
},
|
||||
mockSettings
|
||||
mockSettings,
|
||||
{} as any
|
||||
);
|
||||
|
||||
const task = testManager.convertEventsToTasks([mockEvent])[0];
|
||||
|
|
@ -556,7 +562,8 @@ describe("ICS Manager", () => {
|
|||
...testConfig,
|
||||
sources: [sourceWithoutReplacements],
|
||||
},
|
||||
mockSettings
|
||||
mockSettings,
|
||||
{} as any
|
||||
);
|
||||
|
||||
const task = testManager.convertEventsToTasks([mockEvent])[0];
|
||||
|
|
@ -628,7 +635,7 @@ describe("ICS Manager Integration", () => {
|
|||
defaultEventColor: "#3b82f6",
|
||||
};
|
||||
|
||||
const manager = new IcsManager(config, mockSettings);
|
||||
const manager = new IcsManager(config, mockSettings, {} as any);
|
||||
await manager.initialize();
|
||||
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ describe("ICS Timeout Fix", () => {
|
|||
|
||||
beforeEach(async () => {
|
||||
mockComponent = new MockComponent();
|
||||
icsManager = new IcsManager(testConfig, mockSettings);
|
||||
icsManager = new IcsManager(testConfig, mockSettings, {} as any);
|
||||
await icsManager.initialize();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ interface ViewEventHandlers {
|
|||
onTaskContextMenu?: (event: MouseEvent, task: Task) => void;
|
||||
onTaskStatusUpdate?: (
|
||||
taskId: string,
|
||||
newStatusMark: string
|
||||
newStatusMark: string,
|
||||
) => Promise<void>;
|
||||
onEventContextMenu?: (ev: MouseEvent, event: CalendarEvent) => void;
|
||||
onTaskUpdate?: (originalTask: Task, updatedTask: Task) => Promise<void>;
|
||||
|
|
@ -44,7 +44,7 @@ class ViewComponentFactory {
|
|||
app: App,
|
||||
plugin: TaskProgressBarPlugin,
|
||||
parentEl: HTMLElement,
|
||||
handlers: ViewEventHandlers
|
||||
handlers: ViewEventHandlers,
|
||||
): ViewComponentInterface | null {
|
||||
const viewConfig = getViewSettingOrDefault(plugin, viewId);
|
||||
|
||||
|
|
@ -61,7 +61,7 @@ class ViewComponentFactory {
|
|||
onTaskCompleted: handlers.onTaskCompleted,
|
||||
onTaskContextMenu: handlers.onTaskContextMenu,
|
||||
},
|
||||
viewId
|
||||
viewId,
|
||||
);
|
||||
|
||||
case "calendar":
|
||||
|
|
@ -75,7 +75,7 @@ class ViewComponentFactory {
|
|||
onTaskCompleted: handlers.onTaskCompleted,
|
||||
onEventContextMenu: handlers.onEventContextMenu,
|
||||
},
|
||||
viewId
|
||||
viewId,
|
||||
);
|
||||
|
||||
case "gantt":
|
||||
|
|
@ -87,7 +87,7 @@ class ViewComponentFactory {
|
|||
onTaskCompleted: handlers.onTaskCompleted,
|
||||
onTaskContextMenu: handlers.onTaskContextMenu,
|
||||
},
|
||||
viewId
|
||||
viewId,
|
||||
);
|
||||
|
||||
case "twocolumn":
|
||||
|
|
@ -97,7 +97,7 @@ class ViewComponentFactory {
|
|||
app,
|
||||
plugin,
|
||||
viewConfig.specificConfig,
|
||||
viewId
|
||||
viewId,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
|
@ -123,19 +123,27 @@ class ViewComponentFactory {
|
|||
onTaskContextMenu: handlers.onTaskContextMenu,
|
||||
onTaskUpdated: async (task: Task) => {
|
||||
// Handle task updates through WriteAPI if dataflow is enabled
|
||||
if (plugin.settings?.experimental?.dataflowEnabled && plugin.writeAPI) {
|
||||
const result = await plugin.writeAPI.updateTask({
|
||||
taskId: task.id,
|
||||
updates: task
|
||||
});
|
||||
if (
|
||||
plugin.settings?.experimental
|
||||
?.dataflowEnabled &&
|
||||
plugin.writeAPI
|
||||
) {
|
||||
const result =
|
||||
await plugin.writeAPI.updateTask({
|
||||
taskId: task.id,
|
||||
updates: task,
|
||||
});
|
||||
if (!result.success) {
|
||||
console.error("Failed to update task:", result.error);
|
||||
console.error(
|
||||
"Failed to update task:",
|
||||
result.error,
|
||||
);
|
||||
}
|
||||
} else if (plugin.taskManager) {
|
||||
await plugin.taskManager.updateTask(task);
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
|
@ -153,20 +161,29 @@ class ViewComponentFactory {
|
|||
onTaskContextMenu: handlers.onTaskContextMenu,
|
||||
onTaskUpdated: async (task: Task) => {
|
||||
// Handle task updates through WriteAPI if dataflow is enabled
|
||||
if (plugin.settings?.experimental?.dataflowEnabled && plugin.writeAPI) {
|
||||
const result = await plugin.writeAPI.updateTask({
|
||||
taskId: task.id,
|
||||
updates: task
|
||||
});
|
||||
if (
|
||||
plugin.settings?.experimental
|
||||
?.dataflowEnabled &&
|
||||
plugin.writeAPI
|
||||
) {
|
||||
const result = await plugin.writeAPI.updateTask(
|
||||
{
|
||||
taskId: task.id,
|
||||
updates: task,
|
||||
},
|
||||
);
|
||||
if (!result.success) {
|
||||
console.error("Failed to update task:", result.error);
|
||||
console.error(
|
||||
"Failed to update task:",
|
||||
result.error,
|
||||
);
|
||||
}
|
||||
} else if (plugin.taskManager) {
|
||||
await plugin.taskManager.updateTask(task);
|
||||
}
|
||||
},
|
||||
},
|
||||
viewId
|
||||
viewId,
|
||||
);
|
||||
|
||||
default:
|
||||
|
|
@ -189,7 +206,7 @@ export class ViewComponentManager extends Component {
|
|||
app: App,
|
||||
plugin: TaskProgressBarPlugin,
|
||||
parentEl: HTMLElement,
|
||||
handlers: ViewEventHandlers
|
||||
handlers: ViewEventHandlers,
|
||||
) {
|
||||
super();
|
||||
this.parentComponent = parentComponent;
|
||||
|
|
@ -240,7 +257,7 @@ export class ViewComponentManager extends Component {
|
|||
this.app,
|
||||
this.plugin,
|
||||
this.parentEl,
|
||||
this.handlers
|
||||
this.handlers,
|
||||
);
|
||||
|
||||
if (component) {
|
||||
|
|
@ -296,14 +313,14 @@ export class ViewComponentManager extends Component {
|
|||
viewId,
|
||||
specificViewType,
|
||||
["calendar", "kanban", "gantt", "forecast", "table"].includes(
|
||||
viewId
|
||||
)
|
||||
viewId,
|
||||
),
|
||||
);
|
||||
|
||||
return !!(
|
||||
specificViewType ||
|
||||
["calendar", "kanban", "gantt", "forecast", "table"].includes(
|
||||
viewId
|
||||
viewId,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -461,8 +461,13 @@ export class CalendarComponent extends Component {
|
|||
const icsTask = isIcsTask ? (task as IcsTask) : null; // Type assertion for IcsTask
|
||||
const showAsBadge = icsTask?.icsEvent?.source?.showType === "badge";
|
||||
|
||||
// If ICS is configured as badge, do NOT add a full event; badges will be
|
||||
// provided via getBadgeEventsForDate from the raw tasks list to avoid duplication.
|
||||
if (isIcsTask && showAsBadge) {
|
||||
return; // skip adding to this.events
|
||||
}
|
||||
|
||||
// Determine the date to use based on priority (dueDate > scheduledDate > startDate)
|
||||
// This logic might need refinement based on exact requirements in PRD 4.2
|
||||
let eventDate: number | null = null;
|
||||
let isAllDay = true; // Assume tasks are all-day unless time info exists
|
||||
|
||||
|
|
@ -471,9 +476,6 @@ export class CalendarComponent extends Component {
|
|||
eventDate = icsTask.icsEvent.dtstart.getTime();
|
||||
isAllDay = icsTask.icsEvent.allDay;
|
||||
} else {
|
||||
// Use the first available date field based on preference.
|
||||
// The PRD mentions using dueDate primarily, with an option for scheduled/start.
|
||||
// Let's stick to dueDate for now as primary.
|
||||
if (task.metadata[primaryDateField]) {
|
||||
eventDate = task.metadata[primaryDateField];
|
||||
} else if (task.metadata.scheduledDate) {
|
||||
|
|
@ -482,44 +484,31 @@ export class CalendarComponent extends Component {
|
|||
eventDate = task.metadata.startDate;
|
||||
}
|
||||
}
|
||||
// We could add completedDate here if we want to show completed tasks based on completion time
|
||||
|
||||
if (eventDate) {
|
||||
const startMoment = moment(eventDate);
|
||||
// For ICS events, preserve the original time if not all-day
|
||||
const start = isAllDay
|
||||
? startMoment.startOf("day").toDate()
|
||||
: startMoment.toDate();
|
||||
|
||||
// Handle multi-day? PRD mentions if startDate and dueDate are available.
|
||||
let end: Date | undefined = undefined;
|
||||
let effectiveStart = start; // Use the primary date as start by default
|
||||
|
||||
if (isIcsTask && icsTask?.icsEvent?.dtend) {
|
||||
// For ICS events, use the end date from the event
|
||||
end = icsTask.icsEvent.dtend;
|
||||
} else if (
|
||||
task.metadata.startDate &&
|
||||
task.metadata.dueDate &&
|
||||
task.metadata.startDate !== task.metadata.dueDate
|
||||
) {
|
||||
// Ensure start is actually before due date
|
||||
const sMoment = moment(task.metadata.startDate).startOf(
|
||||
"day"
|
||||
);
|
||||
const dMoment = moment(task.metadata.dueDate).startOf(
|
||||
"day"
|
||||
);
|
||||
const sMoment = moment(task.metadata.startDate).startOf("day");
|
||||
const dMoment = moment(task.metadata.dueDate).startOf("day");
|
||||
if (sMoment.isBefore(dMoment)) {
|
||||
// FullCalendar and similar often expect the 'end' date to be exclusive
|
||||
// for all-day events. So an event ending on the 15th would have end=16th.
|
||||
end = dMoment.add(1, "day").toDate();
|
||||
// The 'start' should likely be the startDate in this case
|
||||
effectiveStart = sMoment.toDate(); // Re-assign start if using date range
|
||||
effectiveStart = sMoment.toDate();
|
||||
}
|
||||
}
|
||||
|
||||
// Determine color for the event
|
||||
let eventColor: string | undefined;
|
||||
if (isIcsTask && icsTask?.icsEvent?.source?.color) {
|
||||
eventColor = icsTask.icsEvent.source.color;
|
||||
|
|
@ -528,16 +517,14 @@ export class CalendarComponent extends Component {
|
|||
}
|
||||
|
||||
this.events.push({
|
||||
...task, // Spread all properties from the original task
|
||||
title: task.content, // Use task content as title by default
|
||||
...task,
|
||||
title: task.content,
|
||||
start: effectiveStart,
|
||||
end: end, // Add end date if calculated
|
||||
end: end,
|
||||
allDay: isAllDay,
|
||||
color: eventColor,
|
||||
badge: showAsBadge, // Mark badge events for special handling
|
||||
});
|
||||
}
|
||||
// Else: Task has no relevant date, ignore for now (PRD: maybe "unscheduled" panel)
|
||||
});
|
||||
|
||||
// Sort events for potentially easier rendering later (e.g., agenda)
|
||||
|
|
|
|||
|
|
@ -440,11 +440,11 @@ export class TaskDetailsComponent extends Component {
|
|||
cls: "date-input",
|
||||
});
|
||||
if (task.metadata.dueDate) {
|
||||
// Use UTC methods to avoid timezone issues
|
||||
// Use local date components to avoid off-by-one due to timezone
|
||||
const date = new Date(task.metadata.dueDate);
|
||||
const year = date.getUTCFullYear();
|
||||
const month = String(date.getUTCMonth() + 1).padStart(2, "0");
|
||||
const day = String(date.getUTCDate()).padStart(2, "0");
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(date.getDate()).padStart(2, "0");
|
||||
dueDateInput.value = `${year}-${month}-${day}`;
|
||||
} // Start date
|
||||
const startDateField = this.createFormField(
|
||||
|
|
@ -514,10 +514,6 @@ export class TaskDetailsComponent extends Component {
|
|||
// Update task properties
|
||||
const newContent = contentInput.getValue();
|
||||
updatedTask.content = newContent;
|
||||
// Also update originalMarkdown if content has changed
|
||||
if (task.content !== newContent) {
|
||||
updatedTask.originalMarkdown = newContent;
|
||||
}
|
||||
|
||||
// Update metadata properties
|
||||
const metadata = { ...updatedTask.metadata };
|
||||
|
|
@ -671,14 +667,10 @@ export class TaskDetailsComponent extends Component {
|
|||
try {
|
||||
await this.onTaskUpdate(task, updatedTask);
|
||||
|
||||
// Update the current task reference but don't redraw the UI
|
||||
// 更新本地引用并立即重绘详情,避免显示“上一次”的值
|
||||
this.currentTask = updatedTask;
|
||||
// Reset editing state after successful update to allow view refreshes
|
||||
// Use setTimeout to ensure the reset happens after any immediate WriteAPI events
|
||||
setTimeout(() => {
|
||||
this.isEditing = false;
|
||||
}, 50);
|
||||
console.log("updatedTask", updatedTask);
|
||||
this.isEditing = false;
|
||||
this.showTaskDetails(updatedTask);
|
||||
} catch (error) {
|
||||
console.error("Failed to update task:", error);
|
||||
// TODO: Show error message to user
|
||||
|
|
|
|||
|
|
@ -414,19 +414,20 @@ export class FileSource {
|
|||
|
||||
// Generate task content based on configuration
|
||||
const content = this.generateTaskContent(filePath, fileContent, fileCache);
|
||||
|
||||
const safeContent = (typeof content === "string") ? content : (filePath.split('/').pop() || filePath);
|
||||
|
||||
// Extract metadata from frontmatter
|
||||
const metadata = this.extractTaskMetadata(filePath, fileContent, fileCache, strategy);
|
||||
|
||||
|
||||
// Create the file task
|
||||
const fileTask: Task<FileSourceTaskMetadata> = {
|
||||
id: `file-source:${filePath}`,
|
||||
content,
|
||||
content: safeContent,
|
||||
filePath,
|
||||
line: 0, // File tasks are at line 0
|
||||
completed: metadata.status === 'x' || metadata.status === 'X',
|
||||
status: metadata.status || config.fileTaskProperties.defaultStatus,
|
||||
originalMarkdown: `[${content}](${filePath})`,
|
||||
originalMarkdown: `[${safeContent}](${filePath})`,
|
||||
metadata: {
|
||||
...metadata,
|
||||
source: "file-source",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* IcsSource - Event source for ICS calendar data
|
||||
*
|
||||
*
|
||||
* This source integrates external calendar events into the dataflow architecture.
|
||||
* It listens to IcsManager updates and emits standardized dataflow events.
|
||||
*/
|
||||
|
|
@ -27,15 +27,33 @@ export class IcsSource {
|
|||
if (this.isInitialized) return;
|
||||
|
||||
console.log("[IcsSource] Initializing ICS event source...");
|
||||
|
||||
// Initial load of ICS events
|
||||
this.loadAndEmitIcsEvents();
|
||||
|
||||
// Subscribe to ICS manager updates
|
||||
|
||||
// Subscribe to ICS manager updates first so we don't miss early signals
|
||||
this.subscribeToIcsUpdates();
|
||||
|
||||
|
||||
// Initial load of ICS events (may be no-op if manager not ready yet)
|
||||
this.loadAndEmitIcsEvents();
|
||||
|
||||
// Fallback: retry until ICS manager becomes available (up to ~30s)
|
||||
this.ensureManagerAndLoad(0);
|
||||
|
||||
this.isInitialized = true;
|
||||
}
|
||||
/**
|
||||
* Ensure ICS manager becomes available shortly after startup and then load
|
||||
*/
|
||||
private ensureManagerAndLoad(attempt: number): void {
|
||||
const maxAttempts = 30; // ~30s with 1s interval
|
||||
if (this.getIcsManager()) {
|
||||
this.loadAndEmitIcsEvents();
|
||||
return;
|
||||
}
|
||||
if (attempt >= maxAttempts) {
|
||||
console.warn("[IcsSource] ICS manager not available after retries");
|
||||
return;
|
||||
}
|
||||
setTimeout(() => this.ensureManagerAndLoad(attempt + 1), 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to ICS manager update events
|
||||
|
|
@ -67,19 +85,9 @@ export class IcsSource {
|
|||
try {
|
||||
// Get all ICS events with sync
|
||||
const icsEvents = await icsManager.getAllEventsWithSync();
|
||||
|
||||
// Convert ICS events to Task format with proper source marking
|
||||
const icsTasks: Task[] = icsEvents.map((event: any) => ({
|
||||
...event,
|
||||
metadata: {
|
||||
...event.metadata,
|
||||
source: {
|
||||
type: 'ics',
|
||||
id: event.source?.id || 'unknown',
|
||||
name: event.source?.name || 'ICS Calendar'
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// Convert ICS events to IcsTask format via manager to ensure proper shape
|
||||
const icsTasks: Task[] = icsManager.convertEventsToTasks(icsEvents);
|
||||
|
||||
console.log(`[IcsSource] Loaded ${icsTasks.length} ICS events`);
|
||||
|
||||
|
|
@ -99,7 +107,7 @@ export class IcsSource {
|
|||
|
||||
} catch (error) {
|
||||
console.error("[IcsSource] Error loading ICS events:", error);
|
||||
|
||||
|
||||
// Emit empty update on error to clear stale data
|
||||
emit(this.app, Events.ICS_EVENTS_UPDATED, {
|
||||
events: [],
|
||||
|
|
@ -115,12 +123,12 @@ export class IcsSource {
|
|||
*/
|
||||
private getSourceStats(events: Task[]): Record<string, number> {
|
||||
const stats: Record<string, number> = {};
|
||||
|
||||
|
||||
for (const event of events) {
|
||||
const sourceId = event.metadata?.source?.id || 'unknown';
|
||||
stats[sourceId] = (stats[sourceId] || 0) + 1;
|
||||
}
|
||||
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
|
|
@ -150,13 +158,13 @@ export class IcsSource {
|
|||
*/
|
||||
destroy(): void {
|
||||
console.log("[IcsSource] Destroying ICS source...");
|
||||
|
||||
|
||||
// Clear event listeners
|
||||
for (const ref of this.eventRefs) {
|
||||
this.app.vault.offref(ref);
|
||||
}
|
||||
this.eventRefs = [];
|
||||
|
||||
|
||||
// Emit clear event
|
||||
emit(this.app, Events.ICS_EVENTS_UPDATED, {
|
||||
events: [],
|
||||
|
|
@ -164,7 +172,7 @@ export class IcsSource {
|
|||
seq: Seq.next(),
|
||||
destroyed: true
|
||||
});
|
||||
|
||||
|
||||
this.isInitialized = false;
|
||||
}
|
||||
}
|
||||
383
src/index.ts
383
src/index.ts
|
|
@ -104,7 +104,10 @@ import { VersionManager } from "./managers/version-manager";
|
|||
import { RebuildProgressManager } from "./managers/rebuild-progress-manager";
|
||||
import { OnboardingConfigManager } from "./managers/onboarding-manager";
|
||||
import { SettingsChangeDetector } from "./services/settings-change-detector";
|
||||
import { OnboardingView, ONBOARDING_VIEW_TYPE } from "./components/onboarding/OnboardingView";
|
||||
import {
|
||||
OnboardingView,
|
||||
ONBOARDING_VIEW_TYPE,
|
||||
} from "./components/onboarding/OnboardingView";
|
||||
import { TaskTimerExporter } from "./services/timer-export-service";
|
||||
import { TaskTimerManager } from "./managers/timer-manager";
|
||||
import { McpServerManager } from "./mcp/McpServerManager";
|
||||
|
|
@ -135,7 +138,7 @@ class TaskProgressBarPopover extends HoverPopover {
|
|||
},
|
||||
parent: HoverParent,
|
||||
targetEl: HTMLElement,
|
||||
waitTime: number = 1000
|
||||
waitTime: number = 1000,
|
||||
) {
|
||||
super(parent, targetEl, waitTime);
|
||||
|
||||
|
|
@ -159,7 +162,7 @@ class TaskProgressBarPopover extends HoverPopover {
|
|||
`,
|
||||
this.hoverEl,
|
||||
"",
|
||||
this.plugin
|
||||
this.plugin,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -181,7 +184,7 @@ export const showPopoverWithProgressBar = (
|
|||
planned: string;
|
||||
};
|
||||
view: EditorView;
|
||||
}
|
||||
},
|
||||
) => {
|
||||
const editor = view.state.field(editorInfoField);
|
||||
if (!editor) return;
|
||||
|
|
@ -195,7 +198,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
|
||||
// Dataflow orchestrator instance (experimental)
|
||||
dataflowOrchestrator?: DataflowOrchestrator;
|
||||
|
||||
|
||||
// Write API for dataflow architecture
|
||||
writeAPI?: WriteAPI;
|
||||
|
||||
|
|
@ -283,7 +286,9 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
// Initialize dataflow orchestrator if enabled (experimental)
|
||||
if (isDataflowEnabled(this)) {
|
||||
try {
|
||||
console.log("[Plugin] Dataflow architecture enabled - initializing...");
|
||||
console.log(
|
||||
"[Plugin] Dataflow architecture enabled - initializing...",
|
||||
);
|
||||
// Wait for dataflow initialization to complete before proceeding
|
||||
this.dataflowOrchestrator = await createDataflow(
|
||||
this.app,
|
||||
|
|
@ -292,11 +297,16 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
this,
|
||||
{
|
||||
// ProjectConfigManagerOptions is narrower; pass only known properties
|
||||
}
|
||||
},
|
||||
);
|
||||
console.log(
|
||||
"[Plugin] Dataflow orchestrator initialized successfully",
|
||||
);
|
||||
console.log("[Plugin] Dataflow orchestrator initialized successfully");
|
||||
} catch (error) {
|
||||
console.error("[Plugin] Failed to initialize dataflow orchestrator:", error);
|
||||
console.error(
|
||||
"[Plugin] Failed to initialize dataflow orchestrator:",
|
||||
error,
|
||||
);
|
||||
// Continue without dataflow, fallback to TaskManager
|
||||
}
|
||||
}
|
||||
|
|
@ -311,37 +321,43 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
{
|
||||
useWorkers: true,
|
||||
debug: true, // Set to true for debugging
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
this.addChild(this.taskManager);
|
||||
console.log("[Plugin] TaskManager initialized");
|
||||
|
||||
|
||||
// Initialize WriteAPI if dataflow is enabled
|
||||
if (this.settings?.experimental?.dataflowEnabled) {
|
||||
const getTaskById = async (id: string): Promise<Task | null> => {
|
||||
const getTaskById = async (
|
||||
id: string,
|
||||
): Promise<Task | null> => {
|
||||
// Try dataflow first, fallback to taskManager
|
||||
if (this.dataflowOrchestrator) {
|
||||
try {
|
||||
const repository = this.dataflowOrchestrator.getRepository();
|
||||
const repository =
|
||||
this.dataflowOrchestrator.getRepository();
|
||||
const task = await repository.getTaskById(id);
|
||||
if (task) {
|
||||
return task;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Failed to get task from dataflow, falling back to taskManager", e);
|
||||
console.warn(
|
||||
"Failed to get task from dataflow, falling back to taskManager",
|
||||
e,
|
||||
);
|
||||
}
|
||||
}
|
||||
const taskManagerResult = this.taskManager.getTaskById(id);
|
||||
return taskManagerResult || null;
|
||||
};
|
||||
|
||||
|
||||
this.writeAPI = new WriteAPI(
|
||||
this.app,
|
||||
this.app.vault,
|
||||
this.app.metadataCache,
|
||||
this,
|
||||
getTaskById
|
||||
getTaskById,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -377,18 +393,18 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
item.setTitle(
|
||||
`${t("Set priority")}: ${
|
||||
priority.text
|
||||
}`
|
||||
}`,
|
||||
);
|
||||
item.setIcon("arrow-big-up-dash");
|
||||
item.onClick(() => {
|
||||
setPriorityAtCursor(
|
||||
editor,
|
||||
priority.emoji
|
||||
priority.emoji,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
submenu.addSeparator();
|
||||
|
|
@ -398,17 +414,17 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
([key, priority]) => {
|
||||
submenu.addItem((item) => {
|
||||
item.setTitle(
|
||||
`${t("Set priority")}: ${key}`
|
||||
`${t("Set priority")}: ${key}`,
|
||||
);
|
||||
item.setIcon("a-arrow-up");
|
||||
item.onClick(() => {
|
||||
setPriorityAtCursor(
|
||||
editor,
|
||||
`[#${key}]`
|
||||
`[#${key}]`,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Remove priority command
|
||||
|
|
@ -428,7 +444,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
if (this.settings.workflow.enableWorkflow) {
|
||||
updateWorkflowContextMenu(menu, editor, this);
|
||||
}
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
|
|
@ -485,35 +501,36 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
this.initializeTaskManagerWithVersionCheck().catch((error) => {
|
||||
console.error(
|
||||
"Failed to initialize task manager with version check:",
|
||||
error
|
||||
error,
|
||||
);
|
||||
});
|
||||
|
||||
// Register the TaskView
|
||||
this.registerView(
|
||||
TASK_VIEW_TYPE,
|
||||
(leaf) => new TaskView(leaf, this)
|
||||
(leaf) => new TaskView(leaf, this),
|
||||
);
|
||||
|
||||
this.registerView(
|
||||
TASK_SPECIFIC_VIEW_TYPE,
|
||||
(leaf) => new TaskSpecificView(leaf, this)
|
||||
(leaf) => new TaskSpecificView(leaf, this),
|
||||
);
|
||||
|
||||
// Register the Timeline Sidebar View
|
||||
this.registerView(
|
||||
TIMELINE_SIDEBAR_VIEW_TYPE,
|
||||
(leaf) => new TimelineSidebarView(leaf, this)
|
||||
(leaf) => new TimelineSidebarView(leaf, this),
|
||||
);
|
||||
|
||||
// Register the Onboarding View
|
||||
this.registerView(
|
||||
ONBOARDING_VIEW_TYPE,
|
||||
(leaf) => new OnboardingView(leaf, this, () => {
|
||||
console.log("Onboarding completed successfully");
|
||||
// Close the onboarding view and refresh views
|
||||
leaf.detach();
|
||||
})
|
||||
(leaf) =>
|
||||
new OnboardingView(leaf, this, () => {
|
||||
console.log("Onboarding completed successfully");
|
||||
// Close the onboarding view and refresh views
|
||||
leaf.detach();
|
||||
}),
|
||||
);
|
||||
|
||||
// Add a ribbon icon for opening the TaskView
|
||||
|
|
@ -522,7 +539,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
t("Open Task Genius view"),
|
||||
() => {
|
||||
this.activateTaskView();
|
||||
}
|
||||
},
|
||||
);
|
||||
// Add a command to open the TaskView
|
||||
this.addCommand({
|
||||
|
|
@ -561,7 +578,8 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
if (this.settings.icsIntegration.sources.length > 0) {
|
||||
this.icsManager = new IcsManager(
|
||||
this.settings.icsIntegration,
|
||||
this.settings
|
||||
this.settings,
|
||||
this,
|
||||
);
|
||||
this.addChild(this.icsManager);
|
||||
|
||||
|
|
@ -581,7 +599,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
this.activateTimelineSidebarView().catch((error) => {
|
||||
console.error(
|
||||
"Failed to auto-open timeline sidebar:",
|
||||
error
|
||||
error,
|
||||
);
|
||||
});
|
||||
}, 1000);
|
||||
|
|
@ -598,11 +616,11 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
(preset: any) => {
|
||||
if (preset.options) {
|
||||
preset.options = migrateOldFilterOptions(
|
||||
preset.options
|
||||
preset.options,
|
||||
);
|
||||
}
|
||||
return preset;
|
||||
}
|
||||
},
|
||||
);
|
||||
await this.saveSettings();
|
||||
}
|
||||
|
|
@ -637,7 +655,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
if (view) {
|
||||
view.dispatch({
|
||||
effects: toggleTaskFilter.of(
|
||||
!view.state.field(taskFilterState)
|
||||
!view.state.field(taskFilterState),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
|
@ -657,14 +675,14 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
const changes = sortTasksInDocument(
|
||||
editorView,
|
||||
this,
|
||||
false
|
||||
false,
|
||||
);
|
||||
|
||||
if (changes) {
|
||||
new Notice(
|
||||
t(
|
||||
"Tasks sorted (using settings). Change application needs refinement."
|
||||
)
|
||||
"Tasks sorted (using settings). Change application needs refinement.",
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// Notice is already handled within sortTasksInDocument if no changes or sorting disabled
|
||||
|
|
@ -688,11 +706,11 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
return changes;
|
||||
});
|
||||
new Notice(
|
||||
t("Entire document sorted (using settings).")
|
||||
t("Entire document sorted (using settings)."),
|
||||
);
|
||||
} else {
|
||||
new Notice(
|
||||
t("Tasks already sorted or no tasks found.")
|
||||
t("Tasks already sorted or no tasks found."),
|
||||
);
|
||||
}
|
||||
},
|
||||
|
|
@ -725,28 +743,44 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
callback: async () => {
|
||||
try {
|
||||
new Notice(t("Refreshing task index..."));
|
||||
|
||||
|
||||
// Check if dataflow is enabled
|
||||
if (this.settings?.experimental?.dataflowEnabled && this.dataflowOrchestrator) {
|
||||
if (
|
||||
this.settings?.experimental?.dataflowEnabled &&
|
||||
this.dataflowOrchestrator
|
||||
) {
|
||||
// Use dataflow orchestrator for refresh
|
||||
console.log("[Command] Refreshing task index via dataflow");
|
||||
|
||||
console.log(
|
||||
"[Command] Refreshing task index via dataflow",
|
||||
);
|
||||
|
||||
// Re-scan all files to refresh the index
|
||||
const files = this.app.vault.getMarkdownFiles();
|
||||
const canvasFiles = this.app.vault.getFiles().filter(f => f.extension === "canvas");
|
||||
const canvasFiles = this.app.vault
|
||||
.getFiles()
|
||||
.filter((f) => f.extension === "canvas");
|
||||
const allFiles = [...files, ...canvasFiles];
|
||||
|
||||
|
||||
// Process files in batches
|
||||
const batchSize = 50;
|
||||
for (let i = 0; i < allFiles.length; i += batchSize) {
|
||||
for (
|
||||
let i = 0;
|
||||
i < allFiles.length;
|
||||
i += batchSize
|
||||
) {
|
||||
const batch = allFiles.slice(i, i + batchSize);
|
||||
await Promise.all(batch.map(file =>
|
||||
(this.dataflowOrchestrator as any).processFileImmediate(file)
|
||||
));
|
||||
await Promise.all(
|
||||
batch.map((file) =>
|
||||
(
|
||||
this.dataflowOrchestrator as any
|
||||
).processFileImmediate(file),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Refresh ICS events if available
|
||||
const icsSource = (this.dataflowOrchestrator as any).icsSource;
|
||||
const icsSource = (this.dataflowOrchestrator as any)
|
||||
.icsSource;
|
||||
if (icsSource) {
|
||||
await icsSource.refresh();
|
||||
}
|
||||
|
|
@ -754,7 +788,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
// Use legacy task manager
|
||||
await this.taskManager.initialize();
|
||||
}
|
||||
|
||||
|
||||
new Notice(t("Task index refreshed"));
|
||||
} catch (error) {
|
||||
console.error("Failed to refresh task index:", error);
|
||||
|
|
@ -770,20 +804,30 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
callback: async () => {
|
||||
try {
|
||||
// Check if dataflow is enabled
|
||||
if (this.settings?.experimental?.dataflowEnabled && this.dataflowOrchestrator) {
|
||||
if (
|
||||
this.settings?.experimental?.dataflowEnabled &&
|
||||
this.dataflowOrchestrator
|
||||
) {
|
||||
// Use dataflow orchestrator for force reindex
|
||||
console.log("[Command] Force reindexing via dataflow");
|
||||
new Notice(t("Clearing task cache and rebuilding index..."));
|
||||
|
||||
console.log(
|
||||
"[Command] Force reindexing via dataflow",
|
||||
);
|
||||
new Notice(
|
||||
t(
|
||||
"Clearing task cache and rebuilding index...",
|
||||
),
|
||||
);
|
||||
|
||||
// Clear all caches and rebuild from scratch
|
||||
await this.dataflowOrchestrator.rebuild();
|
||||
|
||||
|
||||
// Refresh ICS events after rebuild
|
||||
const icsSource = (this.dataflowOrchestrator as any).icsSource;
|
||||
const icsSource = (this.dataflowOrchestrator as any)
|
||||
.icsSource;
|
||||
if (icsSource) {
|
||||
await icsSource.refresh();
|
||||
}
|
||||
|
||||
|
||||
new Notice(t("Task index completely rebuilt"));
|
||||
} else {
|
||||
// Use legacy task manager
|
||||
|
|
@ -854,7 +898,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
editor,
|
||||
ctx,
|
||||
this,
|
||||
"allCompleted"
|
||||
"allCompleted",
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -869,7 +913,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
editor,
|
||||
ctx,
|
||||
this,
|
||||
"directChildren"
|
||||
"directChildren",
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -884,7 +928,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
editor,
|
||||
ctx,
|
||||
this,
|
||||
"all"
|
||||
"all",
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -900,7 +944,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
editor,
|
||||
ctx,
|
||||
this,
|
||||
"allCompleted"
|
||||
"allCompleted",
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -908,7 +952,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
this.addCommand({
|
||||
id: "auto-move-direct-completed-subtasks",
|
||||
name: t(
|
||||
"Auto-move direct completed subtasks to default file"
|
||||
"Auto-move direct completed subtasks to default file",
|
||||
),
|
||||
editorCheckCallback: (checking, editor, ctx) => {
|
||||
return autoMoveCompletedTasksCommand(
|
||||
|
|
@ -916,7 +960,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
editor,
|
||||
ctx,
|
||||
this,
|
||||
"directChildren"
|
||||
"directChildren",
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -930,7 +974,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
editor,
|
||||
ctx,
|
||||
this,
|
||||
"all"
|
||||
"all",
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -949,7 +993,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
editor,
|
||||
ctx,
|
||||
this,
|
||||
"allIncompleted"
|
||||
"allIncompleted",
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -964,7 +1008,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
editor,
|
||||
ctx,
|
||||
this,
|
||||
"directIncompletedChildren"
|
||||
"directIncompletedChildren",
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -980,7 +1024,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
editor,
|
||||
ctx,
|
||||
this,
|
||||
"allIncompleted"
|
||||
"allIncompleted",
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -988,7 +1032,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
this.addCommand({
|
||||
id: "auto-move-direct-incomplete-subtasks",
|
||||
name: t(
|
||||
"Auto-move direct incomplete subtasks to default file"
|
||||
"Auto-move direct incomplete subtasks to default file",
|
||||
),
|
||||
editorCheckCallback: (checking, editor, ctx) => {
|
||||
return autoMoveCompletedTasksCommand(
|
||||
|
|
@ -996,7 +1040,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
editor,
|
||||
ctx,
|
||||
this,
|
||||
"directIncompletedChildren"
|
||||
"directIncompletedChildren",
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -1062,8 +1106,8 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
} catch (e) {
|
||||
new Notice(
|
||||
t(
|
||||
"Could not open quick capture panel in the current editor"
|
||||
)
|
||||
"Could not open quick capture panel in the current editor",
|
||||
),
|
||||
);
|
||||
}
|
||||
}, 100);
|
||||
|
|
@ -1082,7 +1126,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
checking,
|
||||
editor,
|
||||
ctx,
|
||||
this
|
||||
this,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -1095,7 +1139,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
checking,
|
||||
editor,
|
||||
ctx,
|
||||
this
|
||||
this,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -1108,7 +1152,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
checking,
|
||||
editor,
|
||||
ctx,
|
||||
this
|
||||
this,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -1121,7 +1165,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
checking,
|
||||
editor,
|
||||
ctx,
|
||||
this
|
||||
this,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -1134,7 +1178,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
checking,
|
||||
editor,
|
||||
ctx,
|
||||
this
|
||||
this,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -1147,7 +1191,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
checking,
|
||||
editor,
|
||||
ctx,
|
||||
this
|
||||
this,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -1166,20 +1210,25 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
return;
|
||||
}
|
||||
|
||||
const jsonData = this.taskTimerExporter.exportToJSON(true);
|
||||
const jsonData =
|
||||
this.taskTimerExporter.exportToJSON(true);
|
||||
|
||||
// Create a blob and download link
|
||||
const blob = new Blob([jsonData], {type: 'application/json'});
|
||||
const blob = new Blob([jsonData], {
|
||||
type: "application/json",
|
||||
});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = `task-timer-data-${new Date().toISOString().split('T')[0]}.json`;
|
||||
a.download = `task-timer-data-${new Date().toISOString().split("T")[0]}.json`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
new Notice(`Exported ${stats.activeTimers} timer records`);
|
||||
new Notice(
|
||||
`Exported ${stats.activeTimers} timer records`,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error exporting timer data:", error);
|
||||
new Notice("Failed to export timer data");
|
||||
|
|
@ -1193,25 +1242,34 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
callback: async () => {
|
||||
try {
|
||||
// Create file input for JSON import
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = '.json';
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.accept = ".json";
|
||||
|
||||
input.onchange = async (e) => {
|
||||
const file = (e.target as HTMLInputElement).files?.[0];
|
||||
const file = (e.target as HTMLInputElement)
|
||||
.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
try {
|
||||
const text = await file.text();
|
||||
const success = this.taskTimerExporter.importFromJSON(text);
|
||||
const success =
|
||||
this.taskTimerExporter.importFromJSON(text);
|
||||
|
||||
if (success) {
|
||||
new Notice("Timer data imported successfully");
|
||||
new Notice(
|
||||
"Timer data imported successfully",
|
||||
);
|
||||
} else {
|
||||
new Notice("Failed to import timer data - invalid format");
|
||||
new Notice(
|
||||
"Failed to import timer data - invalid format",
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error importing timer data:", error);
|
||||
console.error(
|
||||
"Error importing timer data:",
|
||||
error,
|
||||
);
|
||||
new Notice("Failed to import timer data");
|
||||
}
|
||||
};
|
||||
|
|
@ -1235,22 +1293,30 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
return;
|
||||
}
|
||||
|
||||
const yamlData = this.taskTimerExporter.exportToYAML(true);
|
||||
const yamlData =
|
||||
this.taskTimerExporter.exportToYAML(true);
|
||||
|
||||
// Create a blob and download link
|
||||
const blob = new Blob([yamlData], {type: 'text/yaml'});
|
||||
const blob = new Blob([yamlData], {
|
||||
type: "text/yaml",
|
||||
});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = `task-timer-data-${new Date().toISOString().split('T')[0]}.yaml`;
|
||||
a.download = `task-timer-data-${new Date().toISOString().split("T")[0]}.yaml`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
new Notice(`Exported ${stats.activeTimers} timer records to YAML`);
|
||||
new Notice(
|
||||
`Exported ${stats.activeTimers} timer records to YAML`,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error exporting timer data to YAML:", error);
|
||||
console.error(
|
||||
"Error exporting timer data to YAML:",
|
||||
error,
|
||||
);
|
||||
new Notice("Failed to export timer data to YAML");
|
||||
}
|
||||
},
|
||||
|
|
@ -1261,14 +1327,17 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
name: "Create task timer backup",
|
||||
callback: async () => {
|
||||
try {
|
||||
const backupData = this.taskTimerExporter.createBackup();
|
||||
const backupData =
|
||||
this.taskTimerExporter.createBackup();
|
||||
|
||||
// Create a blob and download link
|
||||
const blob = new Blob([backupData], {type: 'application/json'});
|
||||
const blob = new Blob([backupData], {
|
||||
type: "application/json",
|
||||
});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = `task-timer-backup-${new Date().toISOString().replace(/[:.]/g, '-')}.json`;
|
||||
a.download = `task-timer-backup-${new Date().toISOString().replace(/[:.]/g, "-")}.json`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
|
|
@ -1319,24 +1388,26 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
if (this.settings.taskTimer?.enabled) {
|
||||
// Initialize task timer manager and exporter
|
||||
if (!this.taskTimerManager) {
|
||||
this.taskTimerManager = new TaskTimerManager(this.settings.taskTimer);
|
||||
this.taskTimerManager = new TaskTimerManager(
|
||||
this.settings.taskTimer,
|
||||
);
|
||||
}
|
||||
if (!this.taskTimerExporter) {
|
||||
this.taskTimerExporter = new TaskTimerExporter(this.taskTimerManager);
|
||||
this.taskTimerExporter = new TaskTimerExporter(
|
||||
this.taskTimerManager,
|
||||
);
|
||||
}
|
||||
|
||||
this.registerEditorExtension([
|
||||
taskTimerExtension(this),
|
||||
]);
|
||||
this.registerEditorExtension([taskTimerExtension(this)]);
|
||||
}
|
||||
|
||||
this.settings.taskGutter.enableTaskGutter &&
|
||||
this.registerEditorExtension([taskGutterExtension(this.app, this)]);
|
||||
this.registerEditorExtension([taskGutterExtension(this.app, this)]);
|
||||
this.settings.enableTaskStatusSwitcher &&
|
||||
this.settings.enableCustomTaskMarks &&
|
||||
this.registerEditorExtension([
|
||||
taskStatusSwitcherExtension(this.app, this),
|
||||
]);
|
||||
this.settings.enableCustomTaskMarks &&
|
||||
this.registerEditorExtension([
|
||||
taskStatusSwitcherExtension(this.app, this),
|
||||
]);
|
||||
|
||||
// Add priority picker extension
|
||||
if (this.settings.enablePriorityPicker) {
|
||||
|
|
@ -1372,7 +1443,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
if (this.settings.quickCapture.enableMinimalMode) {
|
||||
this.minimalQuickCaptureSuggest = new MinimalQuickCaptureSuggest(
|
||||
this.app,
|
||||
this
|
||||
this,
|
||||
);
|
||||
this.registerEditorSuggest(this.minimalQuickCaptureSuggest);
|
||||
}
|
||||
|
|
@ -1402,7 +1473,10 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
// Clean up dataflow orchestrator (experimental)
|
||||
if (this.dataflowOrchestrator) {
|
||||
this.dataflowOrchestrator.cleanup().catch((error) => {
|
||||
console.error("Error cleaning up dataflow orchestrator:", error);
|
||||
console.error(
|
||||
"Error cleaning up dataflow orchestrator:",
|
||||
error,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -1425,15 +1499,20 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
private async checkAndShowOnboarding(): Promise<void> {
|
||||
try {
|
||||
// Check if this is the first install and onboarding hasn't been completed
|
||||
const versionResult = await this.versionManager.checkVersionChange();
|
||||
const versionResult =
|
||||
await this.versionManager.checkVersionChange();
|
||||
const isFirstInstall = versionResult.versionInfo.isFirstInstall;
|
||||
const shouldShowOnboarding = this.onboardingConfigManager.shouldShowOnboarding();
|
||||
const shouldShowOnboarding =
|
||||
this.onboardingConfigManager.shouldShowOnboarding();
|
||||
|
||||
// For existing users with changes, let the view handle the async detection
|
||||
// For new users, show onboarding directly
|
||||
if ((isFirstInstall && shouldShowOnboarding) ||
|
||||
(!isFirstInstall && shouldShowOnboarding && this.settingsChangeDetector.hasUserMadeChanges())) {
|
||||
|
||||
if (
|
||||
(isFirstInstall && shouldShowOnboarding) ||
|
||||
(!isFirstInstall &&
|
||||
shouldShowOnboarding &&
|
||||
this.settingsChangeDetector.hasUserMadeChanges())
|
||||
) {
|
||||
// Small delay to ensure UI is ready
|
||||
setTimeout(() => {
|
||||
this.openOnboardingView();
|
||||
|
|
@ -1448,7 +1527,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
* Open the onboarding view in a new leaf
|
||||
*/
|
||||
async openOnboardingView(): Promise<void> {
|
||||
const {workspace} = this.app;
|
||||
const { workspace } = this.app;
|
||||
|
||||
// Check if onboarding view is already open
|
||||
const existingLeaf = workspace.getLeavesOfType(ONBOARDING_VIEW_TYPE)[0];
|
||||
|
|
@ -1460,7 +1539,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
|
||||
// Create a new leaf in the main area and open the onboarding view
|
||||
const leaf = workspace.getLeaf("tab");
|
||||
await leaf.setViewState({type: ONBOARDING_VIEW_TYPE});
|
||||
await leaf.setViewState({ type: ONBOARDING_VIEW_TYPE });
|
||||
workspace.revealLeaf(leaf);
|
||||
}
|
||||
|
||||
|
|
@ -1517,10 +1596,10 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
// Add any missing default views to user settings
|
||||
defaultViews.forEach((defaultView) => {
|
||||
const existingView = this.settings.viewConfiguration.find(
|
||||
(v) => v.id === defaultView.id
|
||||
(v) => v.id === defaultView.id,
|
||||
);
|
||||
if (!existingView) {
|
||||
this.settings.viewConfiguration.push({...defaultView});
|
||||
this.settings.viewConfiguration.push({ ...defaultView });
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -1530,7 +1609,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
// Helper method to set priority at cursor position
|
||||
|
||||
async activateTaskView() {
|
||||
const {workspace} = this.app;
|
||||
const { workspace } = this.app;
|
||||
|
||||
// Check if view is already open
|
||||
const existingLeaf = workspace.getLeavesOfType(TASK_VIEW_TYPE)[0];
|
||||
|
|
@ -1543,16 +1622,16 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
|
||||
// Otherwise, create a new leaf in the right split and open the view
|
||||
const leaf = workspace.getLeaf("tab");
|
||||
await leaf.setViewState({type: TASK_VIEW_TYPE});
|
||||
await leaf.setViewState({ type: TASK_VIEW_TYPE });
|
||||
workspace.revealLeaf(leaf);
|
||||
}
|
||||
|
||||
async activateTimelineSidebarView() {
|
||||
const {workspace} = this.app;
|
||||
const { workspace } = this.app;
|
||||
|
||||
// Check if view is already open
|
||||
const existingLeaf = workspace.getLeavesOfType(
|
||||
TIMELINE_SIDEBAR_VIEW_TYPE
|
||||
TIMELINE_SIDEBAR_VIEW_TYPE,
|
||||
)[0];
|
||||
|
||||
if (existingLeaf) {
|
||||
|
|
@ -1564,7 +1643,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
// Open in the right sidebar
|
||||
const leaf = workspace.getRightLeaf(false);
|
||||
if (leaf) {
|
||||
await leaf.setViewState({type: TIMELINE_SIDEBAR_VIEW_TYPE});
|
||||
await leaf.setViewState({ type: TIMELINE_SIDEBAR_VIEW_TYPE });
|
||||
workspace.revealLeaf(leaf);
|
||||
}
|
||||
}
|
||||
|
|
@ -1584,7 +1663,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
|
||||
// Update Timeline Sidebar Views
|
||||
const timelineViewLeaves = this.app.workspace.getLeavesOfType(
|
||||
TIMELINE_SIDEBAR_VIEW_TYPE
|
||||
TIMELINE_SIDEBAR_VIEW_TYPE,
|
||||
);
|
||||
if (timelineViewLeaves.length > 0) {
|
||||
for (const leaf of timelineViewLeaves) {
|
||||
|
|
@ -1617,7 +1696,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
|
||||
if (!diagnosticInfo.canWrite) {
|
||||
throw new Error(
|
||||
"Cannot write to version storage - storage may be corrupted"
|
||||
"Cannot write to version storage - storage may be corrupted",
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -1626,7 +1705,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
diagnosticInfo.previousVersion
|
||||
) {
|
||||
console.warn(
|
||||
"Invalid version data detected, attempting recovery"
|
||||
"Invalid version data detected, attempting recovery",
|
||||
);
|
||||
await this.versionManager.recoverFromCorruptedVersion();
|
||||
}
|
||||
|
|
@ -1644,13 +1723,13 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
.filter(
|
||||
(file) =>
|
||||
file.extension === "md" ||
|
||||
file.extension === "canvas"
|
||||
file.extension === "canvas",
|
||||
);
|
||||
|
||||
// Start rebuild progress tracking
|
||||
this.rebuildProgressManager.startRebuild(
|
||||
allFiles.length,
|
||||
versionResult.rebuildReason
|
||||
versionResult.rebuildReason,
|
||||
);
|
||||
|
||||
// Force clear all caches before rebuild
|
||||
|
|
@ -1660,7 +1739,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
} catch (clearError) {
|
||||
console.warn(
|
||||
"Error clearing cache, attempting to recreate storage:",
|
||||
clearError
|
||||
clearError,
|
||||
);
|
||||
await this.taskManager.persister.recreate();
|
||||
}
|
||||
|
|
@ -1668,7 +1747,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
|
||||
// Set progress manager for the task manager
|
||||
this.taskManager.setProgressManager(
|
||||
this.rebuildProgressManager
|
||||
this.rebuildProgressManager,
|
||||
);
|
||||
|
||||
// Initialize task manager (this will trigger the rebuild)
|
||||
|
|
@ -1692,19 +1771,19 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
retryCount++;
|
||||
console.error(
|
||||
`Error during task manager initialization (attempt ${retryCount}/${maxRetries}):`,
|
||||
error
|
||||
error,
|
||||
);
|
||||
|
||||
if (retryCount >= maxRetries) {
|
||||
// Final attempt failed, trigger emergency rebuild
|
||||
console.error(
|
||||
"All initialization attempts failed, triggering emergency rebuild"
|
||||
"All initialization attempts failed, triggering emergency rebuild",
|
||||
);
|
||||
|
||||
try {
|
||||
const emergencyResult =
|
||||
await this.versionManager.handleEmergencyRebuild(
|
||||
`Initialization failed after ${maxRetries} attempts: ${error.message}`
|
||||
`Initialization failed after ${maxRetries} attempts: ${error.message}`,
|
||||
);
|
||||
|
||||
// Get all supported files for progress tracking
|
||||
|
|
@ -1713,13 +1792,13 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
.filter(
|
||||
(file) =>
|
||||
file.extension === "md" ||
|
||||
file.extension === "canvas"
|
||||
file.extension === "canvas",
|
||||
);
|
||||
|
||||
// Start emergency rebuild
|
||||
this.rebuildProgressManager.startRebuild(
|
||||
allFiles.length,
|
||||
emergencyResult.rebuildReason
|
||||
emergencyResult.rebuildReason,
|
||||
);
|
||||
|
||||
// Force recreate storage
|
||||
|
|
@ -1729,7 +1808,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
|
||||
// Set progress manager for the task manager
|
||||
this.taskManager.setProgressManager(
|
||||
this.rebuildProgressManager
|
||||
this.rebuildProgressManager,
|
||||
);
|
||||
|
||||
// Initialize with minimal error handling
|
||||
|
|
@ -1739,7 +1818,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
const finalTaskCount =
|
||||
this.taskManager.getAllTasks().length;
|
||||
this.rebuildProgressManager.completeRebuild(
|
||||
finalTaskCount
|
||||
finalTaskCount,
|
||||
);
|
||||
|
||||
// Store current version
|
||||
|
|
@ -1750,19 +1829,19 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
} catch (emergencyError) {
|
||||
console.error(
|
||||
"Emergency rebuild also failed:",
|
||||
emergencyError
|
||||
emergencyError,
|
||||
);
|
||||
this.rebuildProgressManager.failRebuild(
|
||||
`Emergency rebuild failed: ${emergencyError.message}`
|
||||
`Emergency rebuild failed: ${emergencyError.message}`,
|
||||
);
|
||||
throw new Error(
|
||||
`Task manager initialization failed completely: ${emergencyError.message}`
|
||||
`Task manager initialization failed completely: ${emergencyError.message}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Wait before retry
|
||||
await new Promise((resolve) =>
|
||||
setTimeout(resolve, 1000 * retryCount)
|
||||
setTimeout(resolve, 1000 * retryCount),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1773,7 +1852,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
function setPriorityAtCursor(editor: Editor, priority: string) {
|
||||
const cursor = editor.getCursor();
|
||||
const line = editor.getLine(cursor.line);
|
||||
const lineStart = editor.posToOffset({line: cursor.line, ch: 0});
|
||||
const lineStart = editor.posToOffset({ line: cursor.line, ch: 0 });
|
||||
|
||||
// Check if this line has a task
|
||||
const taskRegex =
|
||||
|
|
@ -1820,7 +1899,7 @@ function setPriorityAtCursor(editor: Editor, priority: string) {
|
|||
function removePriorityAtCursor(editor: Editor) {
|
||||
const cursor = editor.getCursor();
|
||||
const line = editor.getLine(cursor.line);
|
||||
const lineStart = editor.posToOffset({line: cursor.line, ch: 0});
|
||||
const lineStart = editor.posToOffset({ line: cursor.line, ch: 0 });
|
||||
|
||||
// Check if this line has a task with priority
|
||||
const priorityRegex = /(?:🔺|⏫|🔼|🔽|⏬️|\[#[A-C]\])/;
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import { HolidayDetector } from "../parsers/holiday-detector";
|
|||
import { StatusMapper } from "../parsers/ics-status-mapper";
|
||||
import { WebcalUrlConverter } from "../parsers/webcal-converter";
|
||||
import { TaskProgressBarSettings } from "../common/setting-definition";
|
||||
import TaskProgressBarPlugin from "src";
|
||||
|
||||
export class IcsManager extends Component {
|
||||
private config: IcsManagerConfig;
|
||||
|
|
@ -30,13 +31,17 @@ export class IcsManager extends Component {
|
|||
private onEventsUpdated?: (sourceId: string, events: IcsEvent[]) => void;
|
||||
private pluginSettings: TaskProgressBarSettings;
|
||||
|
||||
private plugin: TaskProgressBarPlugin;
|
||||
|
||||
constructor(
|
||||
config: IcsManagerConfig,
|
||||
pluginSettings: TaskProgressBarSettings
|
||||
pluginSettings: TaskProgressBarSettings,
|
||||
plugin: TaskProgressBarPlugin,
|
||||
) {
|
||||
super();
|
||||
this.config = config;
|
||||
this.pluginSettings = pluginSettings;
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -57,6 +62,18 @@ export class IcsManager extends Component {
|
|||
}
|
||||
|
||||
console.log("ICS Manager initialized");
|
||||
|
||||
// Notify listeners (e.g., IcsSource) that ICS is ready/config updated
|
||||
// try {
|
||||
// this.plugin.app?.workspace?.trigger?.(
|
||||
// "task-genius:ics-config-changed",
|
||||
// );
|
||||
// } catch (e) {
|
||||
// console.warn(
|
||||
// "[IcsManager] Failed to trigger ics-config-changed on initialize",
|
||||
// e,
|
||||
// );
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -92,13 +109,24 @@ export class IcsManager extends Component {
|
|||
} else {
|
||||
this.stopBackgroundRefresh();
|
||||
}
|
||||
|
||||
// try {
|
||||
// this.plugin.app?.workspace?.trigger?.(
|
||||
// "task-genius:ics-config-changed",
|
||||
// );
|
||||
// } catch (e) {
|
||||
// console.warn(
|
||||
// "[IcsManager] Failed to trigger ics-config-changed",
|
||||
// e,
|
||||
// );
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Set event update callback
|
||||
*/
|
||||
setOnEventsUpdated(
|
||||
callback: (sourceId: string, events: IcsEvent[]) => void
|
||||
callback: (sourceId: string, events: IcsEvent[]) => void,
|
||||
): void {
|
||||
this.onEventsUpdated = callback;
|
||||
}
|
||||
|
|
@ -129,7 +157,7 @@ export class IcsManager extends Component {
|
|||
// Apply filters if configured
|
||||
const filteredEvents = this.applyFilters(
|
||||
cacheEntry.events,
|
||||
source
|
||||
source,
|
||||
);
|
||||
console.log("filteredEvents count", filteredEvents.length);
|
||||
allEvents.push(...filteredEvents);
|
||||
|
|
@ -150,11 +178,11 @@ export class IcsManager extends Component {
|
|||
|
||||
console.log(
|
||||
"getAllEventsWithHolidayDetection: cache size",
|
||||
this.cache.size
|
||||
this.cache.size,
|
||||
);
|
||||
console.log(
|
||||
"getAllEventsWithHolidayDetection: config sources",
|
||||
this.config.sources
|
||||
this.config.sources,
|
||||
);
|
||||
|
||||
for (const [sourceId, cacheEntry] of this.cache) {
|
||||
|
|
@ -164,7 +192,7 @@ export class IcsManager extends Component {
|
|||
"Processing source:",
|
||||
sourceId,
|
||||
"enabled:",
|
||||
source?.enabled
|
||||
source?.enabled,
|
||||
);
|
||||
console.log("Cache entry events count:", cacheEntry.events.length);
|
||||
|
||||
|
|
@ -172,7 +200,7 @@ export class IcsManager extends Component {
|
|||
// Apply filters first
|
||||
const filteredEvents = this.applyFilters(
|
||||
cacheEntry.events,
|
||||
source
|
||||
source,
|
||||
);
|
||||
|
||||
console.log("Filtered events count:", filteredEvents.length);
|
||||
|
|
@ -183,7 +211,7 @@ export class IcsManager extends Component {
|
|||
processedEvents =
|
||||
HolidayDetector.processEventsWithHolidayDetection(
|
||||
filteredEvents,
|
||||
source.holidayConfig
|
||||
source.holidayConfig,
|
||||
);
|
||||
} else {
|
||||
// Convert to IcsEventWithHoliday format without holiday detection
|
||||
|
|
@ -201,7 +229,7 @@ export class IcsManager extends Component {
|
|||
|
||||
console.log(
|
||||
"getAllEventsWithHolidayDetection: total events",
|
||||
allEvents.length
|
||||
allEvents.length,
|
||||
);
|
||||
return allEvents;
|
||||
}
|
||||
|
|
@ -324,7 +352,7 @@ export class IcsManager extends Component {
|
|||
const mappedStatus = StatusMapper.applyStatusMapping(
|
||||
event,
|
||||
event.source.statusMapping,
|
||||
this.pluginSettings
|
||||
this.pluginSettings,
|
||||
);
|
||||
|
||||
const task: IcsTask = {
|
||||
|
|
@ -371,7 +399,7 @@ export class IcsManager extends Component {
|
|||
* Convert single ICS event with holiday detection to Task format
|
||||
*/
|
||||
private convertEventWithHolidayToTask(
|
||||
event: IcsEventWithHoliday
|
||||
event: IcsEventWithHoliday,
|
||||
): Task<ExtendedMetadata> & {
|
||||
icsEvent: IcsEvent;
|
||||
readonly: true;
|
||||
|
|
@ -394,7 +422,7 @@ export class IcsManager extends Component {
|
|||
const mappedStatus = StatusMapper.applyStatusMapping(
|
||||
event,
|
||||
event.source.statusMapping,
|
||||
this.pluginSettings
|
||||
this.pluginSettings,
|
||||
);
|
||||
|
||||
const task: IcsTask = {
|
||||
|
|
@ -458,7 +486,7 @@ export class IcsManager extends Component {
|
|||
* Map ICS priority to task priority
|
||||
*/
|
||||
private mapIcsPriorityToTaskPriority(
|
||||
icsPriority?: number
|
||||
icsPriority?: number,
|
||||
): number | undefined {
|
||||
if (icsPriority === undefined) return undefined;
|
||||
|
||||
|
|
@ -507,12 +535,24 @@ export class IcsManager extends Component {
|
|||
|
||||
// Notify listeners
|
||||
this.onEventsUpdated?.(sourceId, result.data.events);
|
||||
|
||||
// Broadcast workspace event so IcsSource can reload
|
||||
// try {
|
||||
// this.plugin.app?.workspace?.trigger?.(
|
||||
// "task-genius:ics-cache-updated",
|
||||
// );
|
||||
// } catch (e) {
|
||||
// console.warn(
|
||||
// "[IcsManager] Failed to trigger ics-cache-updated",
|
||||
// e,
|
||||
// );
|
||||
// }
|
||||
} else {
|
||||
// Handle different types of errors with appropriate logging
|
||||
const errorType = this.categorizeError(result.error);
|
||||
console.warn(
|
||||
`ICS sync failed for source ${sourceId} (${errorType}):`,
|
||||
result.error
|
||||
result.error,
|
||||
);
|
||||
|
||||
this.updateSyncStatus(sourceId, {
|
||||
|
|
@ -529,7 +569,7 @@ export class IcsManager extends Component {
|
|||
|
||||
console.warn(
|
||||
`ICS sync exception for source ${sourceId} (${errorType}):`,
|
||||
error
|
||||
error,
|
||||
);
|
||||
|
||||
this.updateSyncStatus(sourceId, {
|
||||
|
|
@ -598,7 +638,7 @@ export class IcsManager extends Component {
|
|||
try {
|
||||
// Convert webcal URL if needed
|
||||
const conversionResult = WebcalUrlConverter.convertWebcalUrl(
|
||||
source.url
|
||||
source.url,
|
||||
);
|
||||
|
||||
if (!conversionResult.success) {
|
||||
|
|
@ -626,18 +666,16 @@ export class IcsManager extends Component {
|
|||
case "basic":
|
||||
if (source.auth.username && source.auth.password) {
|
||||
const credentials = btoa(
|
||||
`${source.auth.username}:${source.auth.password}`
|
||||
`${source.auth.username}:${source.auth.password}`,
|
||||
);
|
||||
requestParams.headers![
|
||||
"Authorization"
|
||||
] = `Basic ${credentials}`;
|
||||
requestParams.headers!["Authorization"] =
|
||||
`Basic ${credentials}`;
|
||||
}
|
||||
break;
|
||||
case "bearer":
|
||||
if (source.auth.token) {
|
||||
requestParams.headers![
|
||||
"Authorization"
|
||||
] = `Bearer ${source.auth.token}`;
|
||||
requestParams.headers!["Authorization"] =
|
||||
`Bearer ${source.auth.token}`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -659,8 +697,8 @@ export class IcsManager extends Component {
|
|||
setTimeout(() => {
|
||||
reject(
|
||||
new Error(
|
||||
`Request timeout after ${this.config.networkTimeout} seconds`
|
||||
)
|
||||
`Request timeout after ${this.config.networkTimeout} seconds`,
|
||||
),
|
||||
);
|
||||
}, timeoutMs);
|
||||
});
|
||||
|
|
@ -735,14 +773,14 @@ export class IcsManager extends Component {
|
|||
const beforeFilter = filteredEvents.length;
|
||||
filteredEvents = filteredEvents.filter((event) => !event.allDay);
|
||||
console.log(
|
||||
`Filtered out all-day events: ${beforeFilter} -> ${filteredEvents.length}`
|
||||
`Filtered out all-day events: ${beforeFilter} -> ${filteredEvents.length}`,
|
||||
);
|
||||
}
|
||||
if (!source.showTimedEvents) {
|
||||
const beforeFilter = filteredEvents.length;
|
||||
filteredEvents = filteredEvents.filter((event) => event.allDay);
|
||||
console.log(
|
||||
`Filtered out timed events: ${beforeFilter} -> ${filteredEvents.length}`
|
||||
`Filtered out timed events: ${beforeFilter} -> ${filteredEvents.length}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -758,28 +796,31 @@ export class IcsManager extends Component {
|
|||
shouldInclude =
|
||||
shouldInclude &&
|
||||
include.summary.some((pattern) =>
|
||||
this.matchesPattern(event.summary, pattern)
|
||||
this.matchesPattern(event.summary, pattern),
|
||||
);
|
||||
}
|
||||
if (include.description?.length && event.description) {
|
||||
shouldInclude =
|
||||
shouldInclude &&
|
||||
include.description.some((pattern) =>
|
||||
this.matchesPattern(event.description!, pattern)
|
||||
this.matchesPattern(
|
||||
event.description!,
|
||||
pattern,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (include.location?.length && event.location) {
|
||||
shouldInclude =
|
||||
shouldInclude &&
|
||||
include.location.some((pattern) =>
|
||||
this.matchesPattern(event.location!, pattern)
|
||||
this.matchesPattern(event.location!, pattern),
|
||||
);
|
||||
}
|
||||
if (include.categories?.length && event.categories) {
|
||||
shouldInclude =
|
||||
shouldInclude &&
|
||||
include.categories.some((category) =>
|
||||
event.categories!.includes(category)
|
||||
event.categories!.includes(category),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -793,7 +834,7 @@ export class IcsManager extends Component {
|
|||
if (exclude.summary?.length) {
|
||||
if (
|
||||
exclude.summary.some((pattern) =>
|
||||
this.matchesPattern(event.summary, pattern)
|
||||
this.matchesPattern(event.summary, pattern),
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
|
|
@ -802,7 +843,10 @@ export class IcsManager extends Component {
|
|||
if (exclude.description?.length && event.description) {
|
||||
if (
|
||||
exclude.description.some((pattern) =>
|
||||
this.matchesPattern(event.description!, pattern)
|
||||
this.matchesPattern(
|
||||
event.description!,
|
||||
pattern,
|
||||
),
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
|
|
@ -811,7 +855,7 @@ export class IcsManager extends Component {
|
|||
if (exclude.location?.length && event.location) {
|
||||
if (
|
||||
exclude.location.some((pattern) =>
|
||||
this.matchesPattern(event.location!, pattern)
|
||||
this.matchesPattern(event.location!, pattern),
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
|
|
@ -820,7 +864,7 @@ export class IcsManager extends Component {
|
|||
if (exclude.categories?.length && event.categories) {
|
||||
if (
|
||||
exclude.categories.some((category) =>
|
||||
event.categories!.includes(category)
|
||||
event.categories!.includes(category),
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
|
|
@ -839,7 +883,7 @@ export class IcsManager extends Component {
|
|||
.sort((a, b) => b.dtstart.getTime() - a.dtstart.getTime()) // 倒序:最新的事件在前
|
||||
.slice(0, this.config.maxEventsPerSource);
|
||||
console.log(
|
||||
`Limited events: ${beforeLimit} -> ${filteredEvents.length} (max: ${this.config.maxEventsPerSource}) - keeping newest events`
|
||||
`Limited events: ${beforeLimit} -> ${filteredEvents.length} (max: ${this.config.maxEventsPerSource}) - keeping newest events`,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -899,14 +943,14 @@ export class IcsManager extends Component {
|
|||
case "summary":
|
||||
processedSummary = processedSummary.replace(
|
||||
regex,
|
||||
rule.replacement
|
||||
rule.replacement,
|
||||
);
|
||||
break;
|
||||
case "description":
|
||||
if (processedDescription) {
|
||||
processedDescription = processedDescription.replace(
|
||||
regex,
|
||||
rule.replacement
|
||||
rule.replacement,
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
|
@ -914,25 +958,25 @@ export class IcsManager extends Component {
|
|||
if (processedLocation) {
|
||||
processedLocation = processedLocation.replace(
|
||||
regex,
|
||||
rule.replacement
|
||||
rule.replacement,
|
||||
);
|
||||
}
|
||||
break;
|
||||
case "all":
|
||||
processedSummary = processedSummary.replace(
|
||||
regex,
|
||||
rule.replacement
|
||||
rule.replacement,
|
||||
);
|
||||
if (processedDescription) {
|
||||
processedDescription = processedDescription.replace(
|
||||
regex,
|
||||
rule.replacement
|
||||
rule.replacement,
|
||||
);
|
||||
}
|
||||
if (processedLocation) {
|
||||
processedLocation = processedLocation.replace(
|
||||
regex,
|
||||
rule.replacement
|
||||
rule.replacement,
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
|
@ -940,7 +984,7 @@ export class IcsManager extends Component {
|
|||
} catch (error) {
|
||||
console.warn(
|
||||
`Invalid regex pattern in text replacement rule "${rule.name}": ${rule.pattern}`,
|
||||
error
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -957,7 +1001,7 @@ export class IcsManager extends Component {
|
|||
*/
|
||||
private updateSyncStatus(
|
||||
sourceId: string,
|
||||
updates: Partial<IcsSyncStatus>
|
||||
updates: Partial<IcsSyncStatus>,
|
||||
): void {
|
||||
const current = this.syncStatuses.get(sourceId) || {
|
||||
sourceId,
|
||||
|
|
@ -1021,14 +1065,17 @@ export class IcsManager extends Component {
|
|||
if (source.enabled) {
|
||||
const interval =
|
||||
source.refreshInterval || this.config.globalRefreshInterval;
|
||||
const intervalId = setInterval(() => {
|
||||
this.syncSource(source.id).catch((error) => {
|
||||
console.error(
|
||||
`Background sync failed for source ${source.id}:`,
|
||||
error
|
||||
);
|
||||
});
|
||||
}, interval * 60 * 1000); // Convert minutes to milliseconds
|
||||
const intervalId = setInterval(
|
||||
() => {
|
||||
this.syncSource(source.id).catch((error) => {
|
||||
console.error(
|
||||
`Background sync failed for source ${source.id}:`,
|
||||
error,
|
||||
);
|
||||
});
|
||||
},
|
||||
interval * 60 * 1000,
|
||||
); // Convert minutes to milliseconds
|
||||
|
||||
this.refreshIntervals.set(source.id, intervalId as any);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ export class TaskManager extends Component {
|
|||
/** Options for the task manager */
|
||||
private options: TaskManagerOptions;
|
||||
/** Whether the manager has been initialized */
|
||||
private initialized: boolean = false;
|
||||
private initialized: boolean = false;
|
||||
/** Whether initialization is currently in progress */
|
||||
private isInitializing: boolean = false;
|
||||
/** Whether we should trigger update events after initialization */
|
||||
|
|
|
|||
|
|
@ -144,8 +144,9 @@ export class TaskView extends ItemView {
|
|||
|
||||
// Add debounced view update to prevent rapid successive refreshes
|
||||
const debouncedViewUpdate = debounce(async () => {
|
||||
// Don't skip view updates - the detailsComponent will handle edit state properly
|
||||
await this.loadTasks(false, false);
|
||||
// For external/editor updates, force a view refresh to avoid false "unchanged" skips
|
||||
await this.loadTasks(false, true); // skip internal triggerViewUpdate
|
||||
this.switchView(this.currentViewId, undefined, true); // forceRefresh
|
||||
}, 150); // 150ms debounce delay
|
||||
|
||||
// 1. 首先注册事件监听器,确保不会错过任何更新
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
// Legacy re-export shim for tests and old imports
|
||||
export { MarkdownTaskParser } from "../dataflow/core/ConfigurableTaskParser";
|
||||
|
||||
|
|
@ -471,27 +471,22 @@ export function filterTasks(
|
|||
const filterRules = viewConfig.filterRules || {};
|
||||
const globalFilterRules = plugin.settings.globalFilterRules || {};
|
||||
|
||||
// --- 过滤 badge 类型的 ICS 任务(仅在非日历视图中) ---
|
||||
// Badge 任务只应该在日历视图中显示,其他视图应该过滤掉
|
||||
// 检查是否为日历相关的视图ID
|
||||
// --- 过滤 ICS 事件在不应展示的视图中(例如 inbox)---
|
||||
// ICS 事件应仅在日历/日程类视图(calendar/forecast)中展示
|
||||
const isCalendarView =
|
||||
viewId === "calendar" ||
|
||||
(typeof viewId === "string" && viewId.startsWith("calendar"));
|
||||
const isForecastView =
|
||||
viewId === "forecast" ||
|
||||
(typeof viewId === "string" && viewId.startsWith("forecast"));
|
||||
|
||||
if (!isCalendarView) {
|
||||
if (!isCalendarView && !isForecastView) {
|
||||
filtered = filtered.filter((task) => {
|
||||
// 检查是否为 ICS 任务
|
||||
const isIcsTask = (task as any).source?.type === "ics";
|
||||
if (!isIcsTask) {
|
||||
return true; // 非 ICS 任务保留
|
||||
}
|
||||
|
||||
// 检查是否为 badge 类型
|
||||
const icsTask = task as any; // 类型断言为包含 icsEvent 的任务
|
||||
const showAsBadge = icsTask?.icsEvent?.source?.showType === "badge";
|
||||
|
||||
// 如果是 badge 类型的 ICS 任务,则过滤掉(返回 false)
|
||||
return !showAsBadge;
|
||||
// 识别 ICS 事件任务(优先从 metadata.source 读取,兼容 legacy source 字段)
|
||||
const metaSourceType = (task as any).metadata?.source?.type ?? (task as any).source?.type;
|
||||
const isIcsTask = metaSourceType === "ics";
|
||||
// 非 ICS 保留;ICS 在此类视图中过滤掉
|
||||
return !isIcsTask;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue