mirror of
https://github.com/taskgenius/taskgenius-plugin.git
synced 2026-07-22 06:40:25 +00:00
chore: update tasks
This commit is contained in:
parent
96fd09ee8e
commit
a71f26e6eb
9 changed files with 2073 additions and 1742 deletions
|
|
@ -34,18 +34,40 @@ interface TaskMetadata {
|
|||
|
||||
/**
|
||||
* Sanitize filename by replacing unsafe characters with safe alternatives
|
||||
* This function only sanitizes the filename part, not directory separators
|
||||
* @param filename - The filename to sanitize
|
||||
* @returns The sanitized filename
|
||||
*/
|
||||
function sanitizeFilename(filename: string): string {
|
||||
// Replace unsafe characters with safe alternatives
|
||||
// Replace unsafe characters with safe alternatives, but keep forward slashes for paths
|
||||
return filename
|
||||
.replace(/[<>:"|*?\\]/g, "-") // Replace unsafe chars with dash
|
||||
.replace(/\//g, "-") // Replace forward slash with dash
|
||||
.replace(/\s+/g, " ") // Normalize whitespace
|
||||
.trim(); // Remove leading/trailing whitespace
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize a file path by sanitizing only the filename part while preserving directory structure
|
||||
* @param filePath - The file path to sanitize
|
||||
* @returns The sanitized file path
|
||||
*/
|
||||
function sanitizeFilePath(filePath: string): string {
|
||||
const pathParts = filePath.split("/");
|
||||
// Sanitize each part of the path except preserve the directory structure
|
||||
const sanitizedParts = pathParts.map((part, index) => {
|
||||
// For the last part (filename), we can be more restrictive
|
||||
if (index === pathParts.length - 1) {
|
||||
return sanitizeFilename(part);
|
||||
}
|
||||
// For directory names, we still need to avoid problematic characters but can be less restrictive
|
||||
return part
|
||||
.replace(/[<>:"|*?\\]/g, "-")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
});
|
||||
return sanitizedParts.join("/");
|
||||
}
|
||||
|
||||
export class QuickCaptureModal extends Modal {
|
||||
plugin: TaskProgressBarPlugin;
|
||||
markdownEditor: EmbeddableMarkdownEditor | null = null;
|
||||
|
|
@ -74,12 +96,13 @@ export class QuickCaptureModal extends Modal {
|
|||
const dateStr = moment().format(
|
||||
this.plugin.settings.quickCapture.dailyNoteSettings.format
|
||||
);
|
||||
const sanitizedDateStr = sanitizeFilename(dateStr);
|
||||
const fileName = `${sanitizedDateStr}.md`;
|
||||
this.tempTargetFilePath = this.plugin.settings.quickCapture
|
||||
// For daily notes, the format might include path separators (e.g., YYYY-MM/YYYY-MM-DD)
|
||||
// We need to preserve the path structure and only sanitize the final filename
|
||||
const pathWithDate = this.plugin.settings.quickCapture
|
||||
.dailyNoteSettings.folder
|
||||
? `${this.plugin.settings.quickCapture.dailyNoteSettings.folder}/${fileName}`
|
||||
: fileName;
|
||||
? `${this.plugin.settings.quickCapture.dailyNoteSettings.folder}/${dateStr}.md`
|
||||
: `${dateStr}.md`;
|
||||
this.tempTargetFilePath = sanitizeFilePath(pathWithDate);
|
||||
} else {
|
||||
this.tempTargetFilePath =
|
||||
this.plugin.settings.quickCapture.targetFile;
|
||||
|
|
|
|||
|
|
@ -24,18 +24,40 @@ import "../styles/quick-capture.css";
|
|||
|
||||
/**
|
||||
* Sanitize filename by replacing unsafe characters with safe alternatives
|
||||
* This function only sanitizes the filename part, not directory separators
|
||||
* @param filename - The filename to sanitize
|
||||
* @returns The sanitized filename
|
||||
*/
|
||||
function sanitizeFilename(filename: string): string {
|
||||
// Replace unsafe characters with safe alternatives
|
||||
// Replace unsafe characters with safe alternatives, but keep forward slashes for paths
|
||||
return filename
|
||||
.replace(/[<>:"|*?\\]/g, "-") // Replace unsafe chars with dash
|
||||
.replace(/\//g, "-") // Replace forward slash with dash
|
||||
.replace(/\s+/g, " ") // Normalize whitespace
|
||||
.trim(); // Remove leading/trailing whitespace
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize a file path by sanitizing only the filename part while preserving directory structure
|
||||
* @param filePath - The file path to sanitize
|
||||
* @returns The sanitized file path
|
||||
*/
|
||||
function sanitizeFilePath(filePath: string): string {
|
||||
const pathParts = filePath.split("/");
|
||||
// Sanitize each part of the path except preserve the directory structure
|
||||
const sanitizedParts = pathParts.map((part, index) => {
|
||||
// For the last part (filename), we can be more restrictive
|
||||
if (index === pathParts.length - 1) {
|
||||
return sanitizeFilename(part);
|
||||
}
|
||||
// For directory names, we still need to avoid problematic characters but can be less restrictive
|
||||
return part
|
||||
.replace(/[<>:"|*?\\]/g, "-")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
});
|
||||
return sanitizedParts.join("/");
|
||||
}
|
||||
|
||||
// Effect to toggle the quick capture panel
|
||||
export const toggleQuickCapture = StateEffect.define<boolean>();
|
||||
|
||||
|
|
@ -200,11 +222,12 @@ const handleSubmit = async (
|
|||
let displayPath = selectedTargetPath;
|
||||
if (options.targetType === "daily-note" && options.dailyNoteSettings) {
|
||||
const dateStr = moment().format(options.dailyNoteSettings.format);
|
||||
const sanitizedDateStr = sanitizeFilename(dateStr);
|
||||
const fileName = `${sanitizedDateStr}.md`;
|
||||
displayPath = options.dailyNoteSettings.folder
|
||||
? `${options.dailyNoteSettings.folder}/${fileName}`
|
||||
: fileName;
|
||||
// For daily notes, the format might include path separators (e.g., YYYY-MM/YYYY-MM-DD)
|
||||
// We need to preserve the path structure and only sanitize the final filename
|
||||
const pathWithDate = options.dailyNoteSettings.folder
|
||||
? `${options.dailyNoteSettings.folder}/${dateStr}.md`
|
||||
: `${dateStr}.md`;
|
||||
displayPath = sanitizeFilePath(pathWithDate);
|
||||
}
|
||||
|
||||
new Notice(`${t("Captured successfully to")} ${displayPath}`);
|
||||
|
|
@ -255,11 +278,12 @@ function createQuickCapturePanel(view: EditorView): Panel {
|
|||
let selectedTargetPath: string;
|
||||
if (options.targetType === "daily-note" && options.dailyNoteSettings) {
|
||||
const dateStr = moment().format(options.dailyNoteSettings.format);
|
||||
const sanitizedDateStr = sanitizeFilename(dateStr);
|
||||
const fileName = `${sanitizedDateStr}.md`;
|
||||
selectedTargetPath = options.dailyNoteSettings.folder
|
||||
? `${options.dailyNoteSettings.folder}/${fileName}`
|
||||
: fileName;
|
||||
// For daily notes, the format might include path separators (e.g., YYYY-MM/YYYY-MM-DD)
|
||||
// We need to preserve the path structure and only sanitize the final filename
|
||||
const pathWithDate = options.dailyNoteSettings.folder
|
||||
? `${options.dailyNoteSettings.folder}/${dateStr}.md`
|
||||
: `${dateStr}.md`;
|
||||
selectedTargetPath = sanitizeFilePath(pathWithDate);
|
||||
} else {
|
||||
selectedTargetPath = options.targetFile || "Quick Capture.md";
|
||||
}
|
||||
|
|
|
|||
25
src/index.ts
25
src/index.ts
|
|
@ -210,11 +210,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
this.addChild(this.versionManager);
|
||||
|
||||
// Initialize rebuild progress manager
|
||||
this.rebuildProgressManager = new RebuildProgressManager(
|
||||
this.app,
|
||||
this
|
||||
);
|
||||
this.addChild(this.rebuildProgressManager);
|
||||
this.rebuildProgressManager = new RebuildProgressManager();
|
||||
|
||||
// Initialize task manager
|
||||
if (this.settings.enableView) {
|
||||
|
|
@ -643,11 +639,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
name: t("Force reindex all tasks"),
|
||||
callback: async () => {
|
||||
try {
|
||||
new Notice(
|
||||
t("Clearing task cache and rebuilding index...")
|
||||
);
|
||||
await this.taskManager.forceReindex();
|
||||
new Notice(t("Task index completely rebuilt"));
|
||||
} catch (error) {
|
||||
console.error("Failed to force reindex tasks:", error);
|
||||
new Notice(t("Failed to force reindex tasks"));
|
||||
|
|
@ -1203,12 +1195,10 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
// Add progress callback to track rebuild
|
||||
this.rebuildProgressManager.addCallback((progress) => {
|
||||
console.log(
|
||||
`Rebuild progress: ${progress.processedFiles}/${progress.totalFiles} files processed`
|
||||
);
|
||||
});
|
||||
// Set progress manager for the task manager
|
||||
this.taskManager.setProgressManager(
|
||||
this.rebuildProgressManager
|
||||
);
|
||||
|
||||
// Initialize task manager (this will trigger the rebuild)
|
||||
await this.taskManager.initialize();
|
||||
|
|
@ -1266,6 +1256,11 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
await this.taskManager.persister.recreate();
|
||||
}
|
||||
|
||||
// Set progress manager for the task manager
|
||||
this.taskManager.setProgressManager(
|
||||
this.rebuildProgressManager
|
||||
);
|
||||
|
||||
// Initialize with minimal error handling
|
||||
await this.taskManager.initialize();
|
||||
|
||||
|
|
|
|||
|
|
@ -741,13 +741,7 @@ export class TaskView extends ItemView {
|
|||
onConfirm: async (confirmed) => {
|
||||
if (!confirmed) return;
|
||||
try {
|
||||
new Notice(
|
||||
t(
|
||||
"Clearing task cache and rebuilding index..."
|
||||
)
|
||||
);
|
||||
await this.plugin.taskManager.forceReindex();
|
||||
new Notice(t("Task index completely rebuilt"));
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"Failed to force reindex tasks:",
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ const translations = {
|
|||
"Failed to sync daily notes settings",
|
||||
"Daily note format": "Daily note format",
|
||||
"Date format for daily notes (e.g., YYYY-MM-DD)":
|
||||
"Date format for daily notes (e.g., YYYY-MM-DD)",
|
||||
"Date format for daily notes (e.g., YYYY-MM-DD, supports nested formats like YYYY-MM/YYYY-MM-DD)",
|
||||
"Daily note folder": "Daily note folder",
|
||||
"Folder path for daily notes (leave empty for root)":
|
||||
"Folder path for daily notes (leave empty for root)",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,261 +1,132 @@
|
|||
/**
|
||||
* Progress Manager for handling index rebuild progress notifications
|
||||
* Simple rebuild progress tracker using Obsidian's Notice component
|
||||
*/
|
||||
|
||||
import { App, Component, Notice } from "obsidian";
|
||||
import TaskProgressBarPlugin from "../index";
|
||||
|
||||
export interface RebuildProgress {
|
||||
/** Current step being processed */
|
||||
currentStep: string;
|
||||
/** Current file being processed */
|
||||
currentFile?: string;
|
||||
/** Number of files processed */
|
||||
processedFiles: number;
|
||||
/** Total number of files to process */
|
||||
totalFiles: number;
|
||||
/** Number of tasks found so far */
|
||||
tasksFound: number;
|
||||
/** Whether the rebuild is complete */
|
||||
isComplete: boolean;
|
||||
/** Any error that occurred */
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface RebuildProgressCallback {
|
||||
(progress: RebuildProgress): void;
|
||||
}
|
||||
import { Notice } from "obsidian";
|
||||
|
||||
/**
|
||||
* Manages progress notifications during index rebuild operations
|
||||
* Manages rebuild progress notifications using a single persistent Notice
|
||||
*/
|
||||
export class RebuildProgressManager extends Component {
|
||||
private currentProgress: RebuildProgress;
|
||||
private callbacks: Set<RebuildProgressCallback> = new Set();
|
||||
private currentNotice: Notice | null = null;
|
||||
export class RebuildProgressManager {
|
||||
private notice: Notice | null = null;
|
||||
private startTime: number = 0;
|
||||
|
||||
constructor(
|
||||
private app: App,
|
||||
private plugin: TaskProgressBarPlugin
|
||||
) {
|
||||
super();
|
||||
this.currentProgress = this.createInitialProgress();
|
||||
}
|
||||
private processedFiles: number = 0;
|
||||
private totalFiles: number = 0;
|
||||
private tasksFound: number = 0;
|
||||
|
||||
/**
|
||||
* Create initial progress state
|
||||
*/
|
||||
private createInitialProgress(): RebuildProgress {
|
||||
return {
|
||||
currentStep: "Initializing",
|
||||
processedFiles: 0,
|
||||
totalFiles: 0,
|
||||
tasksFound: 0,
|
||||
isComplete: false
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a new rebuild progress session
|
||||
* Start tracking rebuild progress
|
||||
*/
|
||||
public startRebuild(totalFiles: number, reason?: string): void {
|
||||
this.startTime = Date.now();
|
||||
this.currentProgress = {
|
||||
currentStep: "Starting rebuild",
|
||||
processedFiles: 0,
|
||||
totalFiles,
|
||||
tasksFound: 0,
|
||||
isComplete: false
|
||||
};
|
||||
this.processedFiles = 0;
|
||||
this.totalFiles = totalFiles;
|
||||
this.tasksFound = 0;
|
||||
|
||||
// Show initial notice
|
||||
// Create persistent notice (duration: 0 means it won't auto-hide)
|
||||
const reasonText = reason ? ` (${reason})` : "";
|
||||
this.showNotice(`Task Genius: Rebuilding index${reasonText}...`, 0);
|
||||
|
||||
this.notifyCallbacks();
|
||||
this.notice = new Notice(
|
||||
`Task Genius: Starting rebuild${reasonText}...`,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update progress for current step
|
||||
*/
|
||||
public updateProgress(updates: Partial<RebuildProgress>): void {
|
||||
this.currentProgress = {
|
||||
...this.currentProgress,
|
||||
...updates
|
||||
};
|
||||
|
||||
// Update notice if it exists
|
||||
if (this.currentNotice && !this.currentProgress.isComplete) {
|
||||
const percentage = this.currentProgress.totalFiles > 0
|
||||
? Math.round((this.currentProgress.processedFiles / this.currentProgress.totalFiles) * 100)
|
||||
: 0;
|
||||
|
||||
const progressText = this.formatProgressText(percentage);
|
||||
this.updateNotice(progressText);
|
||||
}
|
||||
|
||||
this.notifyCallbacks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update step information
|
||||
* Update progress with current step information
|
||||
*/
|
||||
public updateStep(step: string, currentFile?: string): void {
|
||||
this.updateProgress({
|
||||
currentStep: step,
|
||||
currentFile
|
||||
});
|
||||
if (!this.notice) return;
|
||||
|
||||
let message = `Task Genius: ${step}`;
|
||||
|
||||
if (this.totalFiles > 0) {
|
||||
const percentage = Math.round(
|
||||
(this.processedFiles / this.totalFiles) * 100
|
||||
);
|
||||
message += ` (${this.processedFiles}/${this.totalFiles} - ${percentage}%)`;
|
||||
}
|
||||
|
||||
if (this.tasksFound > 0) {
|
||||
message += ` - ${this.tasksFound} tasks found`;
|
||||
}
|
||||
|
||||
if (currentFile) {
|
||||
const fileName = currentFile.split("/").pop() || currentFile;
|
||||
message += ` - ${fileName}`;
|
||||
}
|
||||
|
||||
this.notice.setMessage(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment processed files count
|
||||
* Increment processed files count and update progress
|
||||
*/
|
||||
public incrementProcessedFiles(tasksFound: number = 0): void {
|
||||
this.updateProgress({
|
||||
processedFiles: this.currentProgress.processedFiles + 1,
|
||||
tasksFound: this.currentProgress.tasksFound + tasksFound
|
||||
});
|
||||
this.processedFiles++;
|
||||
this.tasksFound += tasksFound;
|
||||
|
||||
if (!this.notice) return;
|
||||
|
||||
const percentage =
|
||||
this.totalFiles > 0
|
||||
? Math.round((this.processedFiles / this.totalFiles) * 100)
|
||||
: 0;
|
||||
|
||||
const message = `Task Genius: Processing files (${this.processedFiles}/${this.totalFiles} - ${percentage}%) - ${this.tasksFound} tasks found`;
|
||||
this.notice.setMessage(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark rebuild as complete
|
||||
* Mark rebuild as complete and show final statistics
|
||||
*/
|
||||
public completeRebuild(tasksFound?: number): void {
|
||||
const duration = Date.now() - this.startTime;
|
||||
const finalTasksFound = tasksFound ?? this.currentProgress.tasksFound;
|
||||
|
||||
this.currentProgress = {
|
||||
...this.currentProgress,
|
||||
currentStep: "Complete",
|
||||
isComplete: true,
|
||||
tasksFound: finalTasksFound
|
||||
};
|
||||
public completeRebuild(finalTaskCount?: number): void {
|
||||
if (!this.notice) return;
|
||||
|
||||
// Show completion notice
|
||||
this.showCompletionNotice(finalTasksFound, duration);
|
||||
|
||||
this.notifyCallbacks();
|
||||
const duration = Date.now() - this.startTime;
|
||||
const durationText =
|
||||
duration > 1000
|
||||
? `${Math.round(duration / 1000)}s`
|
||||
: `${duration}ms`;
|
||||
|
||||
const taskCount = finalTaskCount ?? this.tasksFound;
|
||||
const message = `Task Genius: Rebuild complete! Found ${taskCount} tasks in ${durationText}`;
|
||||
|
||||
this.notice.setMessage(message);
|
||||
|
||||
// Auto-hide the completion notice after 3 seconds
|
||||
setTimeout(() => {
|
||||
if (this.notice) {
|
||||
this.notice.hide();
|
||||
this.notice = null;
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark rebuild as failed
|
||||
* Mark rebuild as failed and show error
|
||||
*/
|
||||
public failRebuild(error: string): void {
|
||||
this.currentProgress = {
|
||||
...this.currentProgress,
|
||||
currentStep: "Failed",
|
||||
isComplete: true,
|
||||
error
|
||||
};
|
||||
if (!this.notice) return;
|
||||
|
||||
// Show error notice
|
||||
this.showNotice(`Task Genius: Index rebuild failed - ${error}`, 5000);
|
||||
|
||||
this.notifyCallbacks();
|
||||
}
|
||||
const message = `Task Genius: Rebuild failed - ${error}`;
|
||||
this.notice.setMessage(message);
|
||||
|
||||
/**
|
||||
* Add a progress callback
|
||||
*/
|
||||
public addCallback(callback: RebuildProgressCallback): void {
|
||||
this.callbacks.add(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a progress callback
|
||||
*/
|
||||
public removeCallback(callback: RebuildProgressCallback): void {
|
||||
this.callbacks.delete(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current progress
|
||||
*/
|
||||
public getProgress(): RebuildProgress {
|
||||
return { ...this.currentProgress };
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify all registered callbacks
|
||||
*/
|
||||
private notifyCallbacks(): void {
|
||||
for (const callback of this.callbacks) {
|
||||
try {
|
||||
callback(this.currentProgress);
|
||||
} catch (error) {
|
||||
console.error("Error in rebuild progress callback:", error);
|
||||
// Auto-hide the error notice after 5 seconds
|
||||
setTimeout(() => {
|
||||
if (this.notice) {
|
||||
this.notice.hide();
|
||||
this.notice = null;
|
||||
}
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format progress text for display
|
||||
* Clean up and hide any active notice
|
||||
*/
|
||||
private formatProgressText(percentage: number): string {
|
||||
const { currentStep, processedFiles, totalFiles, tasksFound, currentFile } = this.currentProgress;
|
||||
|
||||
let text = `Task Genius: ${currentStep}`;
|
||||
|
||||
if (totalFiles > 0) {
|
||||
text += ` (${processedFiles}/${totalFiles} - ${percentage}%)`;
|
||||
public cleanup(): void {
|
||||
if (this.notice) {
|
||||
this.notice.hide();
|
||||
this.notice = null;
|
||||
}
|
||||
|
||||
if (tasksFound > 0) {
|
||||
text += ` - ${tasksFound} tasks found`;
|
||||
}
|
||||
|
||||
if (currentFile) {
|
||||
const fileName = currentFile.split('/').pop() || currentFile;
|
||||
text += ` - ${fileName}`;
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show or update a notice
|
||||
*/
|
||||
private showNotice(message: string, timeout: number = 0): void {
|
||||
// Hide existing notice
|
||||
if (this.currentNotice) {
|
||||
this.currentNotice.hide();
|
||||
}
|
||||
|
||||
// Show new notice
|
||||
this.currentNotice = new Notice(message, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update existing notice text
|
||||
*/
|
||||
private updateNotice(message: string): void {
|
||||
if (this.currentNotice && this.currentNotice.noticeEl) {
|
||||
this.currentNotice.setMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show completion notice
|
||||
*/
|
||||
private showCompletionNotice(tasksFound: number, duration: number): void {
|
||||
const durationText = duration > 1000
|
||||
? `${Math.round(duration / 1000)}s`
|
||||
: `${duration}ms`;
|
||||
|
||||
const message = `Task Genius: Index rebuilt successfully! Found ${tasksFound} tasks in ${durationText}`;
|
||||
this.showNotice(message, 3000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up resources
|
||||
*/
|
||||
public onunload(): void {
|
||||
if (this.currentNotice) {
|
||||
this.currentNotice.hide();
|
||||
this.currentNotice = null;
|
||||
}
|
||||
this.callbacks.clear();
|
||||
super.onunload();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import {
|
|||
import { CanvasParser } from "./parsing/CanvasParser";
|
||||
import { CanvasTaskUpdater } from "./parsing/CanvasTaskUpdater";
|
||||
import { FileMetadataTaskUpdater } from "./workers/FileMetadataTaskUpdater";
|
||||
import { RebuildProgressManager } from "./RebuildProgressManager";
|
||||
|
||||
/**
|
||||
* TaskManager options
|
||||
|
|
@ -726,6 +727,14 @@ export class TaskManager extends Component {
|
|||
)} (${batch.length} files)`
|
||||
);
|
||||
|
||||
// Update progress
|
||||
if (this.progressManager) {
|
||||
this.progressManager.updateStep(
|
||||
"Processing files",
|
||||
batch[0]?.path
|
||||
);
|
||||
}
|
||||
|
||||
// Process each file in the batch
|
||||
for (const file of batch) {
|
||||
// Try to load from cache
|
||||
|
|
@ -747,6 +756,13 @@ export class TaskManager extends Component {
|
|||
`Loaded ${cached.data.length} tasks from cache for ${file.path}`
|
||||
);
|
||||
cachedCount++;
|
||||
|
||||
// Report progress
|
||||
if (this.progressManager) {
|
||||
this.progressManager.incrementProcessedFiles(
|
||||
cached.data.length
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Cache doesn't exist, is outdated, or version incompatible - process with worker
|
||||
if (
|
||||
|
|
@ -763,11 +779,19 @@ export class TaskManager extends Component {
|
|||
);
|
||||
}
|
||||
// Don't trigger events - we'll trigger once when initialization is complete
|
||||
await this.processFileWithoutEvents(
|
||||
file,
|
||||
enhancedProjectData
|
||||
);
|
||||
const processedTasks =
|
||||
await this.processFileWithoutEvents(
|
||||
file,
|
||||
enhancedProjectData
|
||||
);
|
||||
importedCount++;
|
||||
|
||||
// Report progress
|
||||
if (this.progressManager) {
|
||||
this.progressManager.incrementProcessedFiles(
|
||||
processedTasks.length
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
|
|
@ -777,6 +801,13 @@ export class TaskManager extends Component {
|
|||
// Fall back to main thread processing
|
||||
await this.indexer.indexFile(file);
|
||||
importedCount++;
|
||||
|
||||
// Report progress
|
||||
if (this.progressManager) {
|
||||
this.progressManager.incrementProcessedFiles(
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -807,6 +838,9 @@ export class TaskManager extends Component {
|
|||
const totalTasks = this.indexer.getCache().tasks.size;
|
||||
this.log(`Task manager initialized with ${totalTasks} tasks`);
|
||||
|
||||
// Clear progress manager reference after initialization
|
||||
this.progressManager = undefined;
|
||||
|
||||
// Store the consolidated cache after we've finished processing everything
|
||||
await this.storeConsolidatedCache();
|
||||
|
||||
|
|
@ -832,7 +866,7 @@ export class TaskManager extends Component {
|
|||
private async processFileWithoutEvents(
|
||||
file: TFile,
|
||||
enhancedProjectData?: import("./workers/TaskIndexWorkerMessage").EnhancedProjectData
|
||||
): Promise<void> {
|
||||
): Promise<Task[]> {
|
||||
if (!this.workerManager) {
|
||||
// If worker manager is not available, use main thread processing
|
||||
await this.indexer.indexFile(file);
|
||||
|
|
@ -841,7 +875,7 @@ export class TaskManager extends Component {
|
|||
if (tasks.length > 0) {
|
||||
await this.persister.storeFile(file.path, tasks);
|
||||
}
|
||||
return;
|
||||
return tasks;
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -863,6 +897,7 @@ export class TaskManager extends Component {
|
|||
}
|
||||
|
||||
// No event triggering in this version
|
||||
return tasks;
|
||||
} catch (error) {
|
||||
console.error(`Worker error processing ${file.path}:`, error);
|
||||
// Fall back to main thread indexing
|
||||
|
|
@ -874,6 +909,7 @@ export class TaskManager extends Component {
|
|||
}
|
||||
|
||||
// No event triggering in this version
|
||||
return tasks;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -961,6 +997,14 @@ export class TaskManager extends Component {
|
|||
for (let i = 0; i < files.length; i += batchSize) {
|
||||
const batch = files.slice(i, i + batchSize);
|
||||
|
||||
// Update progress
|
||||
if (this.progressManager) {
|
||||
this.progressManager.updateStep(
|
||||
"Processing files (main thread)",
|
||||
batch[0]?.path
|
||||
);
|
||||
}
|
||||
|
||||
// Process each file in the batch
|
||||
for (const file of batch) {
|
||||
// Try to load from cache
|
||||
|
|
@ -982,6 +1026,13 @@ export class TaskManager extends Component {
|
|||
`Loaded ${cached.data.length} tasks from cache for ${file.path}`
|
||||
);
|
||||
cachedCount++;
|
||||
|
||||
// Report progress
|
||||
if (this.progressManager) {
|
||||
this.progressManager.incrementProcessedFiles(
|
||||
cached.data.length
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Remove incompatible cache if it exists
|
||||
if (
|
||||
|
|
@ -1016,6 +1067,13 @@ export class TaskManager extends Component {
|
|||
}
|
||||
}
|
||||
importedCount++;
|
||||
|
||||
// Report progress
|
||||
if (this.progressManager) {
|
||||
this.progressManager.incrementProcessedFiles(
|
||||
tasks.length
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error processing file ${file.path}:`, error);
|
||||
|
|
@ -1031,11 +1089,22 @@ export class TaskManager extends Component {
|
|||
if (tasks.length > 0) {
|
||||
await this.persister.storeFile(file.path, tasks);
|
||||
}
|
||||
|
||||
// Report progress
|
||||
if (this.progressManager) {
|
||||
this.progressManager.incrementProcessedFiles(
|
||||
tasks.length
|
||||
);
|
||||
}
|
||||
} catch (fallbackError) {
|
||||
console.error(
|
||||
`Fallback parsing also failed for ${file.path}:`,
|
||||
fallbackError
|
||||
);
|
||||
// Report progress even on failure
|
||||
if (this.progressManager) {
|
||||
this.progressManager.incrementProcessedFiles(0);
|
||||
}
|
||||
}
|
||||
importedCount++;
|
||||
}
|
||||
|
|
@ -1216,6 +1285,16 @@ export class TaskManager extends Component {
|
|||
// 添加初始化节流标志
|
||||
private initializationPending: boolean = false;
|
||||
|
||||
/** Optional progress manager for rebuild operations */
|
||||
private progressManager?: RebuildProgressManager;
|
||||
|
||||
/**
|
||||
* Set the progress manager for rebuild operations
|
||||
*/
|
||||
public setProgressManager(progressManager: RebuildProgressManager): void {
|
||||
this.progressManager = progressManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query tasks based on filters and sorting criteria
|
||||
*/
|
||||
|
|
@ -2618,9 +2697,30 @@ export class TaskManager extends Component {
|
|||
console.error("Error clearing cache:", error);
|
||||
}
|
||||
|
||||
// Get all supported files for progress tracking
|
||||
const allFiles = this.app.vault
|
||||
.getFiles()
|
||||
.filter(
|
||||
(file) => file.extension === "md" || file.extension === "canvas"
|
||||
);
|
||||
|
||||
// Create and start progress manager for force reindex
|
||||
const progressManager = new RebuildProgressManager();
|
||||
progressManager.startRebuild(
|
||||
allFiles.length,
|
||||
"Force reindex requested"
|
||||
);
|
||||
|
||||
// Set progress manager for the task manager
|
||||
this.setProgressManager(progressManager);
|
||||
|
||||
// Re-initialize everything
|
||||
await this.initialize();
|
||||
|
||||
// Mark rebuild as complete
|
||||
const finalTaskCount = this.getAllTasks().length;
|
||||
progressManager.completeRebuild(finalTaskCount);
|
||||
|
||||
// Trigger an update event
|
||||
this.app.workspace.trigger(
|
||||
"task-genius:task-cache-updated",
|
||||
|
|
|
|||
|
|
@ -4,18 +4,40 @@ import { moment } from "obsidian";
|
|||
|
||||
/**
|
||||
* Sanitize filename by replacing unsafe characters with safe alternatives
|
||||
* This function only sanitizes the filename part, not directory separators
|
||||
* @param filename - The filename to sanitize
|
||||
* @returns The sanitized filename
|
||||
*/
|
||||
function sanitizeFilename(filename: string): string {
|
||||
// Replace unsafe characters with safe alternatives
|
||||
// Replace unsafe characters with safe alternatives, but keep forward slashes for paths
|
||||
return filename
|
||||
.replace(/[<>:"|*?\\]/g, "-") // Replace unsafe chars with dash
|
||||
.replace(/\//g, "-") // Replace forward slash with dash
|
||||
.replace(/\s+/g, " ") // Normalize whitespace
|
||||
.trim(); // Remove leading/trailing whitespace
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize a file path by sanitizing only the filename part while preserving directory structure
|
||||
* @param filePath - The file path to sanitize
|
||||
* @returns The sanitized file path
|
||||
*/
|
||||
function sanitizeFilePath(filePath: string): string {
|
||||
const pathParts = filePath.split("/");
|
||||
// Sanitize each part of the path except preserve the directory structure
|
||||
const sanitizedParts = pathParts.map((part, index) => {
|
||||
// For the last part (filename), we can be more restrictive
|
||||
if (index === pathParts.length - 1) {
|
||||
return sanitizeFilename(part);
|
||||
}
|
||||
// For directory names, we still need to avoid problematic characters but can be less restrictive
|
||||
return part
|
||||
.replace(/[<>:"|*?\\]/g, "-")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
});
|
||||
return sanitizedParts.join("/");
|
||||
}
|
||||
|
||||
/**
|
||||
* Process file path with date templates
|
||||
* Replaces {{DATE:format}} patterns with current date formatted using moment.js
|
||||
|
|
@ -27,23 +49,32 @@ export function processDateTemplates(filePath: string): string {
|
|||
// Match patterns like {{DATE:YYYY-MM-DD}} or {{date:YYYY-MM-DD-HHmm}}
|
||||
const dateTemplateRegex = /\{\{DATE?:([^}]+)\}\}/gi;
|
||||
|
||||
return filePath.replace(dateTemplateRegex, (match, format) => {
|
||||
try {
|
||||
// Check if format is empty or only whitespace
|
||||
if (!format || format.trim() === "") {
|
||||
return match; // Return original match for empty formats
|
||||
}
|
||||
const processedPath = filePath.replace(
|
||||
dateTemplateRegex,
|
||||
(match, format) => {
|
||||
try {
|
||||
// Check if format is empty or only whitespace
|
||||
if (!format || format.trim() === "") {
|
||||
return match; // Return original match for empty formats
|
||||
}
|
||||
|
||||
// Use moment to format the current date with the specified format
|
||||
const formattedDate = moment().format(format);
|
||||
// Sanitize the result to ensure it's safe for file systems
|
||||
return sanitizeFilename(formattedDate);
|
||||
} catch (error) {
|
||||
console.warn(`Invalid date format in template: ${format}`, error);
|
||||
// Return the original match if formatting fails
|
||||
return match;
|
||||
// Use moment to format the current date with the specified format
|
||||
const formattedDate = moment().format(format);
|
||||
// Return the formatted date without sanitizing here to preserve path structure
|
||||
return formattedDate;
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
`Invalid date format in template: ${format}`,
|
||||
error
|
||||
);
|
||||
// Return the original match if formatting fails
|
||||
return match;
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
// Sanitize the entire path while preserving directory structure
|
||||
return sanitizeFilePath(processedPath);
|
||||
}
|
||||
|
||||
// Save the captured content to the target file
|
||||
|
|
@ -66,11 +97,12 @@ export async function saveCapture(
|
|||
if (targetType === "daily-note" && dailyNoteSettings) {
|
||||
// Generate daily note file path
|
||||
const dateStr = moment().format(dailyNoteSettings.format);
|
||||
const sanitizedDateStr = sanitizeFilename(dateStr);
|
||||
const fileName = `${sanitizedDateStr}.md`;
|
||||
filePath = dailyNoteSettings.folder
|
||||
? `${dailyNoteSettings.folder}/${fileName}`
|
||||
: fileName;
|
||||
// For daily notes, the format might include path separators (e.g., YYYY-MM/YYYY-MM-DD)
|
||||
// We need to preserve the path structure and only sanitize the final filename
|
||||
const pathWithDate = dailyNoteSettings.folder
|
||||
? `${dailyNoteSettings.folder}/${dateStr}.md`
|
||||
: `${dateStr}.md`;
|
||||
filePath = sanitizeFilePath(pathWithDate);
|
||||
} else {
|
||||
// Use fixed file path
|
||||
const rawFilePath = targetFile || "Quick Capture.md";
|
||||
|
|
|
|||
Loading…
Reference in a new issue