import { Notice } from 'obsidian'; import SpaceforgePlugin from '../main'; import { IReviewBatchController } from './interfaces'; import { ReviewSchedule } from '../models/review-schedule'; import { BatchReviewModal } from '../ui/batch-review-modal'; import { ConsolidatedMCQModal } from '../ui/consolidated-mcq-modal'; /** * Controller for batch reviewing multiple notes */ export class ReviewBatchController implements IReviewBatchController { /** * Reference to the main plugin */ plugin: SpaceforgePlugin; /** * Initialize batch controller * * @param plugin Reference to the main plugin */ constructor(plugin: SpaceforgePlugin) { this.plugin = plugin; } /** * Start reviewing all of today's notes */ async reviewAllTodaysNotes(): Promise { const reviewController = this.plugin.reviewController; if (!reviewController) return; // Update notes while preserving existing order await reviewController.updateTodayNotes(true); const todayNotes = reviewController.getTodayNotes(); if (todayNotes.length === 0) { new Notice("No notes due for review today!"); return; } // Always start with first note in current order // We need to update the core controller's state if (reviewController) { // Force update to ensure we start at the first note await reviewController.updateTodayNotes(false); const note = todayNotes[0]; // Start the review with the selected note await reviewController.reviewNote(note.path); } new Notice(`Starting review of all ${todayNotes.length} notes due today`); } /** * Review a specific set of notes * * @param paths Array of note paths to review * @param useMCQ Whether to use MCQs for testing (default: false) */ async reviewNotes(paths: string[], useMCQ: boolean = false): Promise { if (paths.length === 0) { new Notice("No notes selected for review."); return; } // Get the ReviewSchedule objects for the given paths const notesToReview = this.plugin.dataStorage.getDueNotesWithCustomOrder().filter(note => paths.includes(note.path)); if (notesToReview.length === 0) { new Notice("Selected notes are not currently due for review."); return; } if (useMCQ) { new Notice(`Preparing MCQs for ${notesToReview.length} selected notes. This may take a moment...`); } else { new Notice(`Starting review of ${notesToReview.length} selected notes.`); } // Create a new batch review modal with the selected notes const modal = new BatchReviewModal(this.plugin.app, this.plugin, notesToReview, useMCQ); modal.open(); } /** * Review all notes with MCQs in a batch * * @param useMCQ Whether to use MCQs for testing */ async reviewAllNotesWithMCQ(useMCQ: boolean = true): Promise { const reviewController = this.plugin.reviewController; if (!reviewController) return; // Update today's notes first, preserving custom order if it exists const hasCustomOrder = this.plugin.reviewScheduleService.customNoteOrder.length > 0; await reviewController.updateTodayNotes(hasCustomOrder); const todayNotes = reviewController.getTodayNotes(); if (todayNotes.length === 0) { new Notice("No notes due for review today!"); return; } if (useMCQ) { new Notice("Preparing all MCQs. This may take a moment..."); } // Use notes directly from todayNotes - they're already in the correct order // Custom order is already applied by updateTodayNotes if available const orderedNotes = [...todayNotes]; // Create a new batch review modal with the ordered notes const modal = new BatchReviewModal(this.plugin.app, this.plugin, orderedNotes, useMCQ); modal.open(); } /** * Regenerate MCQs for all notes due today */ async regenerateAllMCQs(): Promise { const reviewController = this.plugin.reviewController; if (!reviewController) return; // Update notes respecting custom order if it exists const hasCustomOrder = this.plugin.reviewScheduleService.customNoteOrder.length > 0; await reviewController.updateTodayNotes(hasCustomOrder); const todayNotes = reviewController.getTodayNotes(); if (todayNotes.length === 0) { new Notice("No notes due for review today!"); return; } if (!this.plugin.mcqController) { new Notice("MCQ controller not initialized. Please check MCQ settings."); return; } new Notice(`Regenerating MCQs for ${todayNotes.length} notes...`); let generatedCount = 0; for (const note of todayNotes) { const success = await this.plugin.mcqController.generateMCQs(note.path); if (success) { generatedCount++; } } new Notice(`Generated MCQs for ${generatedCount} out of ${todayNotes.length} notes`); // Start batch review with the newly generated MCQs this.reviewAllNotesWithMCQ(true); } /** * Postpone a specific set of notes * * @param paths Array of note paths to postpone * @param days Number of days to postpone (default: 1) */ async postponeNotes(paths: string[], days: number = 1): Promise { const reviewController = this.plugin.reviewController; if (!reviewController) return; if (paths.length === 0) { new Notice("No notes selected to postpone."); return; } new Notice(`Postponing ${paths.length} notes by ${days} day(s)...`); for (const path of paths) { await this.plugin.dataStorage.postponeNote(path, days); // No need to call handleNotePostponed here for each note, // as a single refresh of the sidebar after the loop is sufficient. } await this.plugin.savePluginData(); // Add save call after loop // Refresh the sidebar view if available this.plugin.getSidebarView()?.refresh(); // Update today's notes after postponing await reviewController.updateTodayNotes(true); new Notice(`Postponed ${paths.length} notes by ${days} day(s).`); } /** * Advance a specific set of notes by one day each, if eligible. * * @param paths Array of note paths to advance */ async advanceNotes(paths: string[]): Promise { const reviewController = this.plugin.reviewController; if (!reviewController) return; if (paths.length === 0) { new Notice("No notes selected to advance."); return; } let advancedCount = 0; for (const path of paths) { // Directly call the core controller's advanceNote, which handles eligibility and notices // The service's advanceNote returns a boolean indicating success const advanced = await this.plugin.dataStorage.reviewScheduleService.advanceNote(path); if (advanced) { advancedCount++; } } if (advancedCount > 0) { await this.plugin.savePluginData(); // Save once after all operations // Refresh the sidebar view if available this.plugin.getSidebarView()?.refresh(); // Update today's notes after advancing await reviewController.updateTodayNotes(true); new Notice(`Advanced ${advancedCount} note(s).`); } else { new Notice("No eligible notes were advanced."); } } /** * Remove a specific set of notes from the review schedule * * @param paths Array of note paths to remove */ async removeNotes(paths: string[]): Promise { const reviewController = this.plugin.reviewController; if (!reviewController) return; if (paths.length === 0) { new Notice("No notes selected to remove."); return; } new Notice(`Removing ${paths.length} notes from review schedule...`); for (const path of paths) { await this.plugin.dataStorage.removeFromReview(path); // No need to call handleNotePostponed here for each note, // as a single refresh of the sidebar after the loop is sufficient. } await this.plugin.savePluginData(); // Add save call after loop // Refresh the sidebar view if available this.plugin.getSidebarView()?.refresh(); // Update today's notes after removing await reviewController.updateTodayNotes(true); new Notice(`Removed ${paths.length} notes from review schedule.`); } }