diff --git a/api/claude-service.ts b/api/claude-service.ts index c5639a8..0d82204 100644 --- a/api/claude-service.ts +++ b/api/claude-service.ts @@ -13,18 +13,15 @@ export class ClaudeService implements IMCQGenerationService { async generateMCQs(notePath: string, noteContent: string, settings: SpaceforgeSettings): Promise { if (!settings.claudeApiKey) { - // eslint-disable-next-line obsidianmd/ui/sentence-case - new Notice('Claude API key is not set, please add it in the Spaceforge settings'); + new Notice('Claude API key is not set. Please add it in the Spaceforge settings'); return null; } if (!settings.claudeModel) { - // eslint-disable-next-line obsidianmd/ui/sentence-case - new Notice('Claude model is not set, please add it in the Spaceforge settings'); + new Notice('Claude model is not set. Please add it in the Spaceforge settings'); return null; } try { - // eslint-disable-next-line obsidianmd/ui/sentence-case new Notice('Generating MCQs using Claude...'); // Determine the number of questions to generate @@ -41,8 +38,7 @@ export class ClaudeService implements IMCQGenerationService { const questions = this.parseResponse(response, settings, numQuestionsToGenerate); if (questions.length === 0) { - // eslint-disable-next-line obsidianmd/ui/sentence-case - new Notice('Failed to generate valid MCQs from Claude, please try again'); + new Notice('Failed to generate valid MCQs from Claude. Please try again'); return null; } @@ -52,8 +48,7 @@ export class ClaudeService implements IMCQGenerationService { generatedAt: Date.now() }; } catch { - // eslint-disable-next-line obsidianmd/ui/sentence-case - new Notice('Failed to generate MCQs with Claude, please check console for details'); + new Notice('Failed to generate MCQs with Claude. Please check console for details'); return null; } } @@ -227,8 +222,7 @@ export class ClaudeService implements IMCQGenerationService { } return questions.slice(0, numQuestionsToGenerate); // Use calculated number } catch { - // eslint-disable-next-line obsidianmd/ui/sentence-case - new Notice('Error parsing MCQ response from Claude, please try again'); + new Notice('Error parsing MCQ response from Claude. Please try again'); return []; } } diff --git a/api/gemini-service.ts b/api/gemini-service.ts index eb4365c..71dca99 100644 --- a/api/gemini-service.ts +++ b/api/gemini-service.ts @@ -15,18 +15,15 @@ export class GeminiService implements IMCQGenerationService { async generateMCQs(notePath: string, noteContent: string, settings: SpaceforgeSettings): Promise { if (!settings.geminiApiKey) { - // eslint-disable-next-line obsidianmd/ui/sentence-case - new Notice('Gemini API key is not set, please add it in the Spaceforge settings'); + new Notice('Gemini API key is not set. Please add it in the Spaceforge settings'); return null; } if (!settings.geminiModel) { - // eslint-disable-next-line obsidianmd/ui/sentence-case - new Notice('Gemini model is not set, please add it in the Spaceforge settings'); + new Notice('Gemini model is not set. Please add it in the Spaceforge settings'); return null; } try { - // eslint-disable-next-line obsidianmd/ui/sentence-case new Notice('Generating MCQs using Gemini...'); // Determine the number of questions to generate @@ -43,8 +40,7 @@ export class GeminiService implements IMCQGenerationService { const questions = this.parseResponse(response, settings, numQuestionsToGenerate); if (questions.length === 0) { - // eslint-disable-next-line obsidianmd/ui/sentence-case - new Notice('Failed to generate valid MCQs from Gemini, please try again'); + new Notice('Failed to generate valid MCQs from Gemini. Please try again'); return null; } @@ -54,8 +50,7 @@ export class GeminiService implements IMCQGenerationService { generatedAt: Date.now() }; } catch { - // eslint-disable-next-line obsidianmd/ui/sentence-case - new Notice('Failed to generate MCQs with Gemini, please check console for details'); + new Notice('Failed to generate MCQs with Gemini. Please check console for details'); return null; } } @@ -179,8 +174,7 @@ export class GeminiService implements IMCQGenerationService { } return questions.slice(0, numQuestionsToGenerate); // Use calculated number } catch { - // eslint-disable-next-line obsidianmd/ui/sentence-case - new Notice('Error parsing MCQ response from Gemini, please try again'); + new Notice('Error parsing MCQ response from Gemini. Please try again'); return []; } } diff --git a/api/ollama-service.ts b/api/ollama-service.ts index d65d83d..40a3afc 100644 --- a/api/ollama-service.ts +++ b/api/ollama-service.ts @@ -13,18 +13,15 @@ export class OllamaService implements IMCQGenerationService { async generateMCQs(notePath: string, noteContent: string, settings: SpaceforgeSettings): Promise { if (!settings.ollamaApiUrl) { - // eslint-disable-next-line obsidianmd/ui/sentence-case - new Notice('Ollama API URL is not set, please add it in the Spaceforge settings'); + new Notice('Ollama API URL is not set. Please add it in the Spaceforge settings'); return null; } if (!settings.ollamaModel) { - // eslint-disable-next-line obsidianmd/ui/sentence-case - new Notice('Ollama model is not set, please add it in the Spaceforge settings'); + new Notice('Ollama model is not set. Please add it in the Spaceforge settings'); return null; } try { - // eslint-disable-next-line obsidianmd/ui/sentence-case new Notice('Generating MCQs using Ollama...'); // Determine the number of questions to generate @@ -41,8 +38,7 @@ export class OllamaService implements IMCQGenerationService { const questions = this.parseResponse(response, settings, numQuestionsToGenerate); if (questions.length === 0) { - // eslint-disable-next-line obsidianmd/ui/sentence-case - new Notice('Failed to generate valid MCQs from Ollama, please try again'); + new Notice('Failed to generate valid MCQs from Ollama. Please try again'); return null; } @@ -52,8 +48,7 @@ export class OllamaService implements IMCQGenerationService { generatedAt: Date.now() }; } catch { - // eslint-disable-next-line obsidianmd/ui/sentence-case - new Notice('Failed to generate MCQs with Ollama, please check console for details'); + new Notice('Failed to generate MCQs with Ollama. Please check console for details'); return null; } } @@ -176,8 +171,7 @@ export class OllamaService implements IMCQGenerationService { } return questions.slice(0, numQuestionsToGenerate); // Use calculated number } catch { - // eslint-disable-next-line obsidianmd/ui/sentence-case - new Notice('Error parsing MCQ response from Ollama, please try again'); + new Notice('Error parsing MCQ response from Ollama. Please try again'); return []; } } diff --git a/api/openai-service.ts b/api/openai-service.ts index d1b5622..d6c1462 100644 --- a/api/openai-service.ts +++ b/api/openai-service.ts @@ -13,13 +13,11 @@ export class OpenAIService implements IMCQGenerationService { async generateMCQs(notePath: string, noteContent: string, settings: SpaceforgeSettings): Promise { if (!settings.openaiApiKey) { - // eslint-disable-next-line obsidianmd/ui/sentence-case - new Notice('OpenAI API key is not set, please add it in the Spaceforge settings'); + new Notice('OpenAI API key is not set. Please add it in the Spaceforge settings'); return null; } if (!settings.openaiModel) { - // eslint-disable-next-line obsidianmd/ui/sentence-case - new Notice('OpenAI model is not set, please add it in the Spaceforge settings'); + new Notice('OpenAI model is not set. Please add it in the Spaceforge settings'); return null; } @@ -172,8 +170,7 @@ export class OpenAIService implements IMCQGenerationService { } return questions.slice(0, numQuestionsToGenerate); // Use calculated number } catch { - // eslint-disable-next-line obsidianmd/ui/sentence-case - new Notice('Error parsing MCQ response from OpenAI, please try again'); + new Notice('Error parsing MCQ response from OpenAI. Please try again'); return []; } } diff --git a/api/openrouter-service.ts b/api/openrouter-service.ts index c5fd445..8380ea3 100644 --- a/api/openrouter-service.ts +++ b/api/openrouter-service.ts @@ -33,14 +33,12 @@ export class OpenRouterService implements IMCQGenerationService { async generateMCQs(notePath: string, noteContent: string, settings: SpaceforgeSettings): Promise { // Check if API key is set if (!settings.openRouterApiKey) { - // eslint-disable-next-line obsidianmd/ui/sentence-case - new Notice('OpenRouter API key is not set, please add it in the settings'); + new Notice('OpenRouter API key is not set. Please add it in the settings'); return null; } try { // Show loading notice - // eslint-disable-next-line obsidianmd/ui/sentence-case new Notice('Generating MCQs using OpenRouter...'); // Determine the number of questions to generate @@ -65,8 +63,7 @@ export class OpenRouterService implements IMCQGenerationService { // Ensure we have at least one question if requested if (questions.length === 0) { - // eslint-disable-next-line obsidianmd/ui/sentence-case - new Notice('Failed to generate valid MCQs from OpenRouter, please try again'); + new Notice('Failed to generate valid MCQs from OpenRouter. Please try again'); return null; } @@ -79,8 +76,7 @@ export class OpenRouterService implements IMCQGenerationService { return mcqSet; } catch { - // eslint-disable-next-line obsidianmd/ui/sentence-case - new Notice('Failed to generate MCQs with OpenRouter, please check console for details'); + new Notice('Failed to generate MCQs with OpenRouter. Please check console for details'); return null; } } @@ -298,8 +294,7 @@ For example: // Use numQuestionsToGenerate instead of settings.mcqQuestionsPerNote return questions.slice(0, numQuestionsToGenerate); } catch { - // eslint-disable-next-line obsidianmd/ui/sentence-case - new Notice('Error parsing MCQ response from OpenRouter, please try again'); + new Notice('Error parsing MCQ response from OpenRouter. Please try again'); return []; } } diff --git a/api/together-service.ts b/api/together-service.ts index a1d32ee..cc215fa 100644 --- a/api/together-service.ts +++ b/api/together-service.ts @@ -13,18 +13,15 @@ export class TogetherService implements IMCQGenerationService { async generateMCQs(notePath: string, noteContent: string, settings: SpaceforgeSettings): Promise { if (!settings.togetherApiKey) { - // eslint-disable-next-line obsidianmd/ui/sentence-case - new Notice('Together AI API key is not set, please add it in the Spaceforge settings'); + new Notice('Together AI API key is not set. Please add it in the Spaceforge settings'); return null; } if (!settings.togetherModel) { - // eslint-disable-next-line obsidianmd/ui/sentence-case - new Notice('Together AI model is not set, please add it in the Spaceforge settings'); + new Notice('Together AI model is not set. Please add it in the Spaceforge settings'); return null; } try { - // eslint-disable-next-line obsidianmd/ui/sentence-case new Notice('Generating MCQs using Together AI...'); // Determine the number of questions to generate @@ -41,8 +38,7 @@ export class TogetherService implements IMCQGenerationService { const questions = this.parseResponse(response, settings, numQuestionsToGenerate); if (questions.length === 0) { - // eslint-disable-next-line obsidianmd/ui/sentence-case - new Notice('Failed to generate valid MCQs from Together AI, please try again'); + new Notice('Failed to generate valid MCQs from Together AI. Please try again'); return null; } @@ -52,8 +48,7 @@ export class TogetherService implements IMCQGenerationService { generatedAt: Date.now() }; } catch { - // eslint-disable-next-line obsidianmd/ui/sentence-case - new Notice('Failed to generate MCQs with Together AI, please check console for details'); + new Notice('Failed to generate MCQs with Together AI. Please check console for details'); return null; } } @@ -205,8 +200,7 @@ export class TogetherService implements IMCQGenerationService { } return questions.slice(0, numQuestionsToGenerate); // Use calculated number } catch { - // eslint-disable-next-line obsidianmd/ui/sentence-case - new Notice('Error parsing MCQ response from Together AI, please try again'); + new Notice('Error parsing MCQ response from Together AI. Please try again'); return []; } } diff --git a/controllers/review-batch-controller.ts b/controllers/review-batch-controller.ts index 78ab620..25074f5 100644 --- a/controllers/review-batch-controller.ts +++ b/controllers/review-batch-controller.ts @@ -134,8 +134,7 @@ export class ReviewBatchController implements IReviewBatchController { } if (!this.plugin.mcqController) { - // eslint-disable-next-line obsidianmd/ui/sentence-case - new Notice("MCQ controller not initialized, please check MCQ settings"); + new Notice("MCQ controller not initialized. Please check MCQ settings"); return; } diff --git a/controllers/review-controller-core.ts b/controllers/review-controller-core.ts index a83a2f2..bd1f224 100644 --- a/controllers/review-controller-core.ts +++ b/controllers/review-controller-core.ts @@ -259,7 +259,7 @@ export class ReviewControllerCore implements IReviewController { if (advanced) { await this.plugin.savePluginData(); - await this.handleNoteAdvanced(path); // New handler for advancing + this.handleNoteAdvanced(path); // New handler for advancing void this.plugin.getSidebarView()?.refresh(); new Notice(`Note advanced.`); // Controller handles notice for advance @@ -320,7 +320,7 @@ export class ReviewControllerCore implements IReviewController { } // Update custom order in storage - await this.plugin.dataStorage.reviewScheduleService.updateCustomNoteOrder(this.traversalOrder); + this.plugin.dataStorage.reviewScheduleService.updateCustomNoteOrder(this.traversalOrder); // Removed redundant savePluginData() here; postponeNote already saves. // If the postponed note was the current one, select a new note diff --git a/controllers/review-controller-mcq.ts b/controllers/review-controller-mcq.ts index 51a8e78..25d27c0 100644 --- a/controllers/review-controller-mcq.ts +++ b/controllers/review-controller-mcq.ts @@ -68,12 +68,12 @@ export class MCQController { externalOnCompleteCallback?: (path: string, success: boolean) => void ): Promise { if (!this.plugin.settings.enableMCQ) { - new Notice('MCQ feature is disabled in settings.'); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice('MCQ feature is disabled in settings.'); if (externalOnCompleteCallback) externalOnCompleteCallback(notePath, false); return; } if (!this.mcqGenerationService) { - new Notice('MCQ generation service is not available. Check API provider settings.'); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice('MCQ generation service is not available. Check API provider settings.'); return; } @@ -135,7 +135,7 @@ export class MCQController { // Pass the new internal callback to the modal constructor new MCQModal(this.plugin, notePath, mcqSet, internalOnComplete).open(); } catch { - new Notice('Error starting MCQ review. Please check console for details.'); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice('Error starting MCQ review. Please check console for details.'); if (externalOnCompleteCallback) externalOnCompleteCallback(notePath, false); } } @@ -149,7 +149,7 @@ export class MCQController { */ async generateMCQs(notePath: string, forceRegeneration = false): Promise { if (!this.plugin.settings.enableMCQ || !this.mcqGenerationService) { - new Notice('MCQ feature is disabled or the generation service is not available. Check API provider settings.'); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice('MCQ feature is disabled or the generation service is not available. Check API provider settings.'); return null; } @@ -202,11 +202,11 @@ export class MCQController { */ async startConsolidatedMCQReviewForSelectedDate(): Promise { if (!this.plugin.settings.enableMCQ) { - new Notice('MCQ feature is disabled in settings.'); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice('MCQ feature is disabled in settings.'); return; } if (!this.mcqGenerationService) { - new Notice('MCQ generation service is not available. Check API provider settings.'); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice('MCQ generation service is not available. Check API provider settings.'); return; } @@ -295,7 +295,6 @@ export class MCQController { if (reviewsProcessed > 0) { new Notice(`${reviewsProcessed} note review(s) updated based on consolidated MCQ session.`); } else { - // eslint-disable-next-line obsidianmd/ui/sentence-case new Notice("No note reviews were updated from the MCQ session"); } })(); diff --git a/main.ts b/main.ts index 1ebaa04..fed4086 100644 --- a/main.ts +++ b/main.ts @@ -29,6 +29,11 @@ import { CalendarEventService } from './services/calendar-event-service'; /** * Spaceforge: Spaced Repetition Plugin for Obsidian */ +export interface SpaceforgeEvents { + 'sidebar-update': []; + 'pomodoro-update': []; +} + export default class SpaceforgePlugin extends Plugin { settings: SpaceforgeSettings; pluginState: PluginStateData; @@ -56,10 +61,10 @@ export default class SpaceforgePlugin extends Plugin { contextMenuHandler: ContextMenuHandler; // sidebarView: ReviewSidebarView; // Avoid storing direct references to views public clickedDateFromCalendar: Date | null = null; - events: EventEmitter; + events: EventEmitter; async onload(): Promise { - this.events = new EventEmitter(); + this.events = new EventEmitter(); // Initialize settings with defaults FIRST this.settings = { ...DEFAULT_SETTINGS }; @@ -121,7 +126,6 @@ export default class SpaceforgePlugin extends Plugin { EstimationUtils.setPlugin(this); this.addIcons(); this.contextMenuHandler.register(); - // eslint-disable-next-line obsidianmd/ui/sentence-case this.addRibbonIcon('calendar-clock', 'Spaceforge review', async () => { await this.activateSidebarView(); }); @@ -130,7 +134,6 @@ export default class SpaceforgePlugin extends Plugin { this.addCommand({ id: 'add-selected-file-to-review', - // eslint-disable-next-line obsidianmd/ui/sentence-case name: 'Add selected file to review schedule (file explorer)', callback: async () => { const fileExplorerLeaf = this.app.workspace.getLeavesOfType('file-explorer')[0]; @@ -142,7 +145,7 @@ export default class SpaceforgePlugin extends Plugin { await this.savePluginData(); new Notice(`Added "${selectedFile.path}" to review schedule.`); } else { - new Notice("Selected item is not a markdown file."); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice("Selected item is not a markdown file."); } } else { new Notice("No file selected in file explorer"); @@ -222,7 +225,7 @@ export default class SpaceforgePlugin extends Plugin { await this.savePluginData(); } catch { /* handle error */ } })(); - } catch (_error) { + } catch { try { const minimalBackup = JSON.stringify({ settings: this.settings, reviewData: { schedules: this.reviewScheduleService.schedules || {}, customNoteOrder: this.reviewScheduleService.customNoteOrder || [], lastLinkAnalysisTimestamp: this.reviewScheduleService.lastLinkAnalysisTimestamp, version: this.manifest.version } }); this.app.saveLocalStorage('spaceforge-minimal-backup', minimalBackup); @@ -232,7 +235,7 @@ export default class SpaceforgePlugin extends Plugin { window.addEventListener('beforeunload', this.beforeUnloadHandler); } - // eslint-disable-next-line @typescript-eslint/no-misused-promises + // eslint-disable-next-line @typescript-eslint/no-misused-promises -- onunload can be async async onunload(): Promise { if (this.cssHotReloadIntervalId !== null) { window.clearInterval(this.cssHotReloadIntervalId); @@ -351,7 +354,7 @@ export default class SpaceforgePlugin extends Plugin { rawLoadedData = JSON.parse(oldJsonData); // Data will be saved to new path by savePluginData later } - } catch (_migrationReadError) { /* handle error */ } + } catch { /* handle error */ } } if (!rawLoadedData) { new Notice(`Spaceforge: No data file found at custom path ${effectivePath}. New data file will be created on save.`, 3000); @@ -441,7 +444,7 @@ export default class SpaceforgePlugin extends Plugin { if (parsedBackup.reviewData) { this.settings = { ...DEFAULT_SETTINGS, ...parsedBackup.settings }; this.pluginState = { ...DEFAULT_PLUGIN_STATE_DATA, ...parsedBackup.reviewData }; - new Notice("Spaceforge: Recovered data from backup", 5000); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice("Spaceforge: Recovered data from backup", 5000); } else { throw new Error("Invalid backup format"); } @@ -452,7 +455,7 @@ export default class SpaceforgePlugin extends Plugin { console.warn("Spaceforge: Backup recovery failed, using defaults:", backupError); this.settings = { ...DEFAULT_SETTINGS }; this.pluginState = { ...DEFAULT_PLUGIN_STATE_DATA }; - new Notice("Spaceforge: Error loading data, using defaults to prevent crash", 5000); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice("Spaceforge: Error loading data, using defaults to prevent crash", 5000); } // Repopulate services @@ -542,16 +545,16 @@ export default class SpaceforgePlugin extends Plugin { if (oldFile instanceof TFile) { // Instead of deleting, we'll keep the old file as a backup // Users can manually clean it up later if needed - new Notice(`Spaceforge: Successfully saved to custom path, original data file kept as backup for safety`, 5000); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice(`Spaceforge: Successfully saved to custom path, original data file kept as backup for safety`, 5000); } } catch (writeError) { new Notice(`Error saving data to custom path ${effectiveSavePath}: ${writeError.message}. Falling back to default path for this save.`, 10000); // Fallback save to default location if custom path write fails try { await this.saveData(dataToSave); // Plugin's internal save - new Notice(`Spaceforge: Data saved to default plugin folder due to error with custom path`, 5000); // eslint-disable-line obsidianmd/ui/sentence-case - } catch (_error) { - new Notice(`CRITICAL: Spaceforge failed to save data to both custom and default locations`, 10000); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice(`Spaceforge: Data saved to default plugin folder due to error with custom path`, 5000); + } catch { + new Notice(`CRITICAL: Spaceforge failed to save data to both custom and default locations`, 10000); } } } else { @@ -563,8 +566,8 @@ export default class SpaceforgePlugin extends Plugin { // Removed localStorage backup for settings as it's not the source of truth for the path anymore. // If user wants to backup settings, they can use the export feature. - } catch (_error) { - new Notice("Error saving Spaceforge data, check console for details", 5000); // eslint-disable-line obsidianmd/ui/sentence-case + } catch { + new Notice("Error saving Spaceforge data. Check console for details", 5000); } } @@ -583,7 +586,7 @@ export default class SpaceforgePlugin extends Plugin { }); void this.app.workspace.revealLeaf(leaf); // Reveal the newly created leaf } else { - new Notice("Spaceforge: Could not open sidebar view"); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice("Spaceforge: Could not open sidebar view"); } } } @@ -613,7 +616,7 @@ export default class SpaceforgePlugin extends Plugin { if (activeFile && activeFile instanceof TFile && activeFile.extension === 'md') { void this.reviewController.reviewNote(activeFile.path); } else { - new Notice('No active markdown file to review'); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice('No active markdown file to review'); } }, }); @@ -628,7 +631,7 @@ export default class SpaceforgePlugin extends Plugin { await this.savePluginData(); new Notice(`Added "${activeFile.path}" to review schedule`); } else { - new Notice('No active markdown file to add to review'); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice('No active markdown file to add to review'); } }, }); @@ -643,7 +646,7 @@ export default class SpaceforgePlugin extends Plugin { // Use the same logic as the context menu for consistency await this.contextMenuHandler.addFolderToReview(folder); } else { - new Notice("Could not determine the current note's folder, no active file, or parent is not a folder"); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice("Could not determine the current note's folder, no active file, or parent is not a folder"); } }, }); @@ -661,7 +664,7 @@ export default class SpaceforgePlugin extends Plugin { // Pass the mcqService for data management, and the new mcqGenerationService for API calls this.mcqController = new MCQController(this, this.mcqService, this.mcqGenerationService); } else { - new Notice('MCQ generation service could not be initialized, check API provider settings in Spaceforge settings'); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice('MCQ generation service could not be initialized. Check API provider settings in Spaceforge settings'); } } } @@ -670,37 +673,37 @@ export default class SpaceforgePlugin extends Plugin { switch (this.settings.mcqApiProvider) { case ApiProvider.OpenRouter: if (!this.settings.openRouterApiKey) { - new Notice('OpenRouter API key is not set in Spaceforge settings'); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice('OpenRouter API key is not set in Spaceforge settings'); return undefined; } return new OpenRouterService(this); case ApiProvider.OpenAI: if (!this.settings.openaiApiKey) { - new Notice('OpenAI API key is not set in Spaceforge settings'); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice('OpenAI API key is not set in Spaceforge settings'); return undefined; } return new OpenAIService(this); case ApiProvider.Ollama: if (!this.settings.ollamaApiUrl || !this.settings.ollamaModel) { - new Notice('Ollama API URL or model is not set in Spaceforge settings'); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice('Ollama API URL or model is not set in Spaceforge settings'); return undefined; } return new OllamaService(this); case ApiProvider.Gemini: if (!this.settings.geminiApiKey) { - new Notice('Gemini API key is not set in Spaceforge settings'); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice('Gemini API key is not set in Spaceforge settings'); return undefined; } return new GeminiService(this); case ApiProvider.Claude: if (!this.settings.claudeApiKey || !this.settings.claudeModel) { - new Notice('Claude API key or model is not set in Spaceforge settings'); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice('Claude API key or model is not set in Spaceforge settings'); return undefined; } return new ClaudeService(this); case ApiProvider.Together: if (!this.settings.togetherApiKey || !this.settings.togetherModel) { - new Notice('Together AI API key or model is not set in Spaceforge settings'); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice('Together AI API key or model is not set in Spaceforge settings'); return undefined; } return new TogetherService(this); @@ -708,7 +711,7 @@ export default class SpaceforgePlugin extends Plugin { // It's good practice to handle unexpected enum values, even if TypeScript provides some safety. // This could happen if settings data is corrupted or from an older version. // const exhaustiveCheck: never = this.settings.mcqApiProvider; // This will cause a type error now, which is good! - new Notice(`Unsupported MCQ API provider selected: ${this.settings.mcqApiProvider}`); + new Notice(`Unsupported MCQ API provider selected: ${this.settings.mcqApiProvider as any}`); return undefined; } } diff --git a/services/fsrs-schedule-service.ts b/services/fsrs-schedule-service.ts index 873317b..0e6d6ba 100644 --- a/services/fsrs-schedule-service.ts +++ b/services/fsrs-schedule-service.ts @@ -63,7 +63,7 @@ export class FsrsScheduleService { case FsrsRating.Hard: return TsFsrsRating.Hard; case FsrsRating.Good: return TsFsrsRating.Good; case FsrsRating.Easy: return TsFsrsRating.Easy; - default: throw new Error(`Unknown FsrsRating: ${rating}`); + default: throw new Error(`Unknown FsrsRating: ${rating as any}`); } } diff --git a/services/review-schedule-service.ts b/services/review-schedule-service.ts index f7e76d5..b3958be 100644 --- a/services/review-schedule-service.ts +++ b/services/review-schedule-service.ts @@ -76,7 +76,7 @@ export class ReviewScheduleService { // Check if file exists and is a markdown file const file = this.plugin.app.vault.getAbstractFileByPath(path); if (!file || !(file instanceof TFile) || file.extension !== "md") { - new Notice("Only markdown files can be added to the review schedule."); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice("Only markdown files can be added to the review schedule."); return; } diff --git a/ui/batch-review-modal.ts b/ui/batch-review-modal.ts index caf2d17..fda3d4a 100644 --- a/ui/batch-review-modal.ts +++ b/ui/batch-review-modal.ts @@ -113,7 +113,7 @@ export class BatchReviewModal extends Modal { async collectAllMCQs(): Promise { const { contentEl } = this; contentEl.empty(); - new Setting(contentEl).setName("Collecting MCQs").setHeading(); // eslint-disable-line obsidianmd/ui/sentence-case + new Setting(contentEl).setName("Collecting MCQs").setHeading(); const progressEl = contentEl.createDiv("batch-review-progress"); progressEl.createEl("p", { text: `Preparing MCQs for ${this.notes.length} notes...` }); const progressBar = contentEl.createDiv("mcq-collection-progress sf-progress-bar-container-batch"); @@ -172,12 +172,12 @@ export class BatchReviewModal extends Modal { ); consolidatedModal.open(); } catch (_error) { - new Notice("Error showing MCQ review. Falling back to manual review."); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice("Error showing MCQ review. Falling back to manual review."); this.open(); void this.processNextManual(); } } else { - new Notice("MCQ controller not available. Falling back to manual review."); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice("MCQ controller not available. Falling back to manual review."); this.open(); void this.processNextManual(); } @@ -198,7 +198,7 @@ export class BatchReviewModal extends Modal { const note = this.notes[this.currentIndex]; const { contentEl } = this; contentEl.empty(); - new Setting(contentEl).setName("MCQ review in progress").setHeading(); // eslint-disable-line obsidianmd/ui/sentence-case + new Setting(contentEl).setName("MCQ review in progress").setHeading(); const progressEl = contentEl.createDiv("batch-review-progress"); progressEl.createEl("p", { text: `Processing note ${this.currentIndex + 1}/${this.notes.length}` }); const file = this.plugin.app.vault.getAbstractFileByPath(note.path); @@ -254,7 +254,7 @@ export class BatchReviewModal extends Modal { void this.processNextManual(); } } else { - new Notice("MCQ controller not available, falling back to manual review."); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice("MCQ controller not available, falling back to manual review."); this.open(); void this.processNextManual(); } @@ -285,17 +285,17 @@ export class BatchReviewModal extends Modal { } const buttonsContainer = contentEl.createDiv("batch-review-buttons"); - const blackoutButton = buttonsContainer.createEl("button", { text: "0: Complete blackout", cls: "review-button review-button-complete-blackout" }); // eslint-disable-line obsidianmd/ui/sentence-case + const blackoutButton = buttonsContainer.createEl("button", { text: "0: Complete blackout", cls: "review-button review-button-complete-blackout" }); blackoutButton.addEventListener("click", () => void this.recordManualResult(note.path, ReviewResponse.CompleteBlackout)); - const incorrectButton = buttonsContainer.createEl("button", { text: "1: Incorrect response", cls: "review-button review-button-incorrect" }); // eslint-disable-line obsidianmd/ui/sentence-case + const incorrectButton = buttonsContainer.createEl("button", { text: "1: Incorrect response", cls: "review-button review-button-incorrect" }); incorrectButton.addEventListener("click", () => void this.recordManualResult(note.path, ReviewResponse.IncorrectResponse)); - const incorrectFamiliarButton = buttonsContainer.createEl("button", { text: "2: Incorrect but familiar", cls: "review-button review-button-incorrect-familiar" }); // eslint-disable-line obsidianmd/ui/sentence-case + const incorrectFamiliarButton = buttonsContainer.createEl("button", { text: "2: Incorrect but familiar", cls: "review-button review-button-incorrect-familiar" }); incorrectFamiliarButton.addEventListener("click", () => void this.recordManualResult(note.path, ReviewResponse.IncorrectButFamiliar)); - const correctDifficultyButton = buttonsContainer.createEl("button", { text: "3: Correct with difficulty", cls: "review-button review-button-correct-difficulty" }); // eslint-disable-line obsidianmd/ui/sentence-case + const correctDifficultyButton = buttonsContainer.createEl("button", { text: "3: Correct with difficulty", cls: "review-button review-button-correct-difficulty" }); correctDifficultyButton.addEventListener("click", () => void this.recordManualResult(note.path, ReviewResponse.CorrectWithDifficulty)); - const correctHesitationButton = buttonsContainer.createEl("button", { text: "4: Correct with hesitation", cls: "review-button review-button-correct-hesitation" }); // eslint-disable-line obsidianmd/ui/sentence-case + const correctHesitationButton = buttonsContainer.createEl("button", { text: "4: Correct with hesitation", cls: "review-button review-button-correct-hesitation" }); correctHesitationButton.addEventListener("click", () => void this.recordManualResult(note.path, ReviewResponse.CorrectWithHesitation)); - const perfectRecallButton = buttonsContainer.createEl("button", { text: "5: Perfect recall", cls: "review-button review-button-perfect-recall" }); // eslint-disable-line obsidianmd/ui/sentence-case + const perfectRecallButton = buttonsContainer.createEl("button", { text: "5: Perfect recall", cls: "review-button review-button-perfect-recall" }); perfectRecallButton.addEventListener("click", () => void this.recordManualResult(note.path, ReviewResponse.PerfectRecall)); const skipButton = buttonsContainer.createEl("button", { text: "Skip", cls: "review-button review-button-skip" }); skipButton.addEventListener("click", () => { @@ -322,7 +322,7 @@ export class BatchReviewModal extends Modal { showSummary(): void { const { contentEl } = this; contentEl.empty(); - new Setting(contentEl).setName('MCQ review complete').setHeading().setClass('mcq-review-complete-header'); // eslint-disable-line obsidianmd/ui/sentence-case + new Setting(contentEl).setName('MCQ review complete').setHeading().setClass('mcq-review-complete-header'); const statsEl = contentEl.createDiv("batch-review-summary-stats"); const totalNotes = this.results.length; const successfulNotes = this.results.filter(r => r.success).length; diff --git a/ui/calendar-view.ts b/ui/calendar-view.ts index d1b61d6..839a878 100644 --- a/ui/calendar-view.ts +++ b/ui/calendar-view.ts @@ -101,7 +101,7 @@ export class CalendarView { private ensureCalendarBaseStructure(): void { if (!this.containerEl) return; - let calendarContainer = this.containerEl.querySelector(".calendar-container") as HTMLElement; + let calendarContainer = this.containerEl.children[1].querySelector(".calendar-container"); if (!calendarContainer) { calendarContainer = this.containerEl.createDiv("calendar-container"); } diff --git a/ui/consolidated-mcq-modal.ts b/ui/consolidated-mcq-modal.ts index 11b2634..744b7ef 100644 --- a/ui/consolidated-mcq-modal.ts +++ b/ui/consolidated-mcq-modal.ts @@ -213,7 +213,7 @@ export class ConsolidatedMCQModal extends Modal { // Verify we have a valid question if (!question || !question.choices || question.choices.length < 2) { - new Notice('Error: Invalid question data. Moving to next question.'); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice('Error: Invalid question data. Moving to next question.'); // Skip to next question this.currentQuestionIndex++; @@ -522,7 +522,7 @@ export class ConsolidatedMCQModal extends Modal { contentEl.empty(); // Display results header with stylized heading - new Setting(contentEl).setName('MCQ review complete').setHeading().setClass('mcq-review-complete-header'); // eslint-disable-line obsidianmd/ui/sentence-case + new Setting(contentEl).setName('MCQ review complete').setHeading().setClass('mcq-review-complete-header'); // Display overall score with enhanced styling const totalCorrectOverall = this.answers.filter(a => a.correct && a.attempts <= 1).length; @@ -538,16 +538,16 @@ export class ConsolidatedMCQModal extends Modal { const performanceIndicator = scoreEl.createDiv('mcq-performance-indicator'); if (scorePercentOverall >= 90) { - performanceIndicator.setText('🎓 Excellent performance!'); // eslint-disable-line obsidianmd/ui/sentence-case + performanceIndicator.setText('🎓 Excellent performance!'); performanceIndicator.addClass('excellent'); } else if (scorePercentOverall >= 70) { - performanceIndicator.setText('👍 Good work!'); // eslint-disable-line obsidianmd/ui/sentence-case + performanceIndicator.setText('👍 Good work!'); performanceIndicator.addClass('good'); } else if (scorePercentOverall >= 50) { - performanceIndicator.setText('🔄 Keep practicing'); // eslint-disable-line obsidianmd/ui/sentence-case + performanceIndicator.setText('🔄 Keep practicing'); performanceIndicator.addClass('needs-improvement'); } else { - performanceIndicator.setText('📚 More review recommended'); // eslint-disable-line obsidianmd/ui/sentence-case + performanceIndicator.setText('📚 More review recommended'); performanceIndicator.addClass('review-recommended'); } diff --git a/ui/event-modal.ts b/ui/event-modal.ts index 2c1ad08..cdd0d65 100644 --- a/ui/event-modal.ts +++ b/ui/event-modal.ts @@ -246,7 +246,7 @@ export class EventModal extends Modal { * Validate form and enable/disable save button */ private validateForm(): void { - const saveButton = this.contentEl.querySelector(".event-modal-btn-primary") as HTMLButtonElement; + const saveButton = this.contentEl.querySelector(".event-modal-btn-primary"); const isValid = this.titleInput.value.trim() !== "" && this.dateInput.value !== ""; if (saveButton) { diff --git a/ui/mcq-modal.ts b/ui/mcq-modal.ts index 044c53a..9688496 100644 --- a/ui/mcq-modal.ts +++ b/ui/mcq-modal.ts @@ -75,7 +75,7 @@ export class MCQModal extends Modal { new Notice('Could not find note file to regenerate MCQs.'); } } else { - new Notice('MCQ generation service not available.'); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice('MCQ generation service not available.'); } })(); }); @@ -205,7 +205,7 @@ export class MCQModal extends Modal { } const question = this.mcqSet.questions[questionIndex]; if (!question || !question.choices || question.choices.length < 2) { - new Notice('Error: Invalid question data. Moving to next question.'); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice('Error: Invalid question data. Moving to next question.'); this.session.currentQuestionIndex++; if (this.session.currentQuestionIndex < this.mcqSet.questions.length) { this.displayCurrentQuestion(containerEl); @@ -430,7 +430,7 @@ export class MCQModal extends Modal { resultItem.createDiv({ cls: 'mcq-result-time', text: `Time: ${Math.round(answer.timeToAnswer)} seconds` }); // FSRS/SM-2 data per question removed from here - } catch (_error) { /* Error displaying answer result: ${error} */ } + } catch { /* Error displaying answer result: ${error} */ } }); } const closeBtn = contentEl.createEl('button', { cls: 'mcq-close-btn', text: 'Close' }); @@ -441,9 +441,9 @@ export class MCQModal extends Modal { } this.close(); }); - } catch (_error) { + } catch { new Setting(contentEl).setName('Error completing session').setHeading(); - contentEl.createEl('p', { text: 'There was an error completing the MCQ session. Please try again.' }); // eslint-disable-line obsidianmd/ui/sentence-case + contentEl.createEl('p', { text: 'There was an error completing the MCQ session. Please try again.' }); const errorCloseBtn = contentEl.createEl('button', { cls: 'mcq-close-btn', text: 'Close' }); errorCloseBtn.addEventListener('click', () => this.close()); } @@ -514,7 +514,7 @@ function getSm2RatingText(rating: number): string { case 1: return "Incorrect response"; case 2: return "Incorrect but familiar"; case 3: return "Correct with difficulty"; - case 4: return "Correct with hHesitation"; + case 4: return "Correct with hesitation"; case 5: return "Perfect recall"; default: return "Unknown"; } diff --git a/ui/review-modal.ts b/ui/review-modal.ts index 61904dd..953635d 100644 --- a/ui/review-modal.ts +++ b/ui/review-modal.ts @@ -79,7 +79,7 @@ export class ReviewModal extends Modal { const mcqButton = buttonsContainer.createEl("button", { cls: "review-button review-button-mcq" }); const mcqIconSpan = mcqButton.createSpan("mcq-button-icon"); setIcon(mcqIconSpan, "mcq-quiz"); - const textSpan = mcqButton.createSpan("mcq-button-text"); textSpan.setText("Test with MCQs"); // eslint-disable-line obsidianmd/ui/sentence-case + const textSpan = mcqButton.createSpan("mcq-button-text"); textSpan.setText("Test with MCQs"); mcqButton.addEventListener("click", () => { const mcqController = this.plugin.mcqController; @@ -100,7 +100,7 @@ export class ReviewModal extends Modal { void initializedMcqController.startMCQReview(this.path); this.close(); } else { - new Notice("MCQ feature could not be initialized. Please check settings."); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice("MCQ feature could not be initialized. Please check settings."); } } }); @@ -110,7 +110,7 @@ export class ReviewModal extends Modal { if (mcqController && this.plugin.mcqService.getMCQSetForNote(this.path)) { const refreshMcqButton = buttonsContainer.createEl("button", { cls: "review-button review-button-mcq-refresh" }); const refreshIconSpan = refreshMcqButton.createSpan("mcq-button-icon"); setIcon(refreshIconSpan, "refresh-cw"); - const refreshTextSpan = refreshMcqButton.createSpan("mcq-button-text"); refreshTextSpan.setText("Generate new MCQs"); // eslint-disable-line obsidianmd/ui/sentence-case + const refreshTextSpan = refreshMcqButton.createSpan("mcq-button-text"); refreshTextSpan.setText("Generate new MCQs"); refreshMcqButton.addEventListener("click", () => { if (mcqController) { @@ -150,13 +150,13 @@ export class ReviewModal extends Modal { if (schedule.lastReviewDate) infoText.createEl("p", { text: `Last reviewed: ${new Date(schedule.lastReviewDate).toLocaleDateString()}` }); if (schedule.schedulingAlgorithm === 'fsrs' && schedule.fsrsData) { - infoText.createEl("p", { text: `Algorithm: FSRS`, cls: "review-phase-fsrs" }); // eslint-disable-line obsidianmd/ui/sentence-case + infoText.createEl("p", { text: `Algorithm: FSRS`, cls: "review-phase-fsrs" }); infoText.createEl("p", { text: `Stability: ${schedule.fsrsData.stability.toFixed(2)}` }); infoText.createEl("p", { text: `Difficulty: ${schedule.fsrsData.difficulty.toFixed(2)}` }); infoText.createEl("p", { text: `State: ${FsrsState[schedule.fsrsData.state]}` }); // Display FSRS state name infoText.createEl("p", { text: `Interval: ${schedule.fsrsData.scheduled_days} days` }); } else { // SM-2 or fallback - infoText.createEl("p", { text: `Algorithm: SM-2`, cls: "review-phase-sm2" }); // eslint-disable-line obsidianmd/ui/sentence-case + infoText.createEl("p", { text: `Algorithm: SM-2`, cls: "review-phase-sm2" }); let phaseText: string; let phaseClass: string; if (schedule.scheduleCategory === 'initial') { const totalInitialSteps = this.plugin.settings.initialScheduleCustomIntervals.length; diff --git a/ui/settings-tab.ts b/ui/settings-tab.ts index 2c8fd3c..2960c99 100644 --- a/ui/settings-tab.ts +++ b/ui/settings-tab.ts @@ -265,7 +265,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { this.display(); // Refresh settings UI void this.plugin.getSidebarView()?.refresh(); - } catch (_error) { + } catch { new Notice('Failed to clear review data. Check console for details.'); } } @@ -301,7 +301,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { this.display(); // Refresh settings UI void this.plugin.getSidebarView()?.refresh(); - } catch (_error) { + } catch { new Notice('Failed to clear calendar events. Check console for details.'); } } @@ -360,7 +360,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { this.display(); // Refresh settings UI void this.plugin.getSidebarView()?.refresh(); - } catch (_error) { + } catch { new Notice('Failed to clear all data. Check console for details.'); } } @@ -383,8 +383,8 @@ export class SpaceforgeSettingTab extends PluginSettingTab { .setName('Default scheduling algorithm') .setDesc('Choose the default algorithm for newly created notes.') .addDropdown(dropdown => dropdown - .addOption('sm2', 'SM-2') // eslint-disable-line obsidianmd/ui/sentence-case - .addOption('fsrs', 'FSRS') // eslint-disable-line obsidianmd/ui/sentence-case + .addOption('sm2', 'SM-2') + .addOption('fsrs', 'FSRS') .setValue(this.plugin.settings.defaultSchedulingAlgorithm) .onChange(async (value: 'sm2' | 'fsrs') => { this.plugin.settings.defaultSchedulingAlgorithm = value; @@ -400,13 +400,13 @@ export class SpaceforgeSettingTab extends PluginSettingTab { const sm2ParamsContainer = spacedRepSection.createEl('details', { cls: 'sf-settings-collapsible-subsection' }); const sm2Summary = sm2ParamsContainer.createEl('summary'); // Changed to h3 - sm2Summary.setText('SM-2 parameters'); // eslint-disable-line obsidianmd/ui/sentence-case + sm2Summary.setText('SM-2 parameters'); sm2ParamsContainer.open = false; // Initially closed new Setting(sm2ParamsContainer) - .setName('SM-2: base ease factor') // eslint-disable-line obsidianmd/ui/sentence-case - .setDesc('Initial ease factor for new SM-2 notes (2.5 is SM-2 default). Higher ease increases interval growth. Value shown is internal format (250 = 2.5).') // eslint-disable-line obsidianmd/ui/sentence-case + .setName('SM-2: base ease factor') + .setDesc('Initial ease factor for new SM-2 notes (2.5 is SM-2 default). Higher ease increases interval growth. Value shown is internal format (250 = 2.5).') .addSlider(slider => slider .setLimits(130, 500, 10) .setValue(this.plugin.settings.baseEase) @@ -417,8 +417,8 @@ export class SpaceforgeSettingTab extends PluginSettingTab { })); new Setting(sm2ParamsContainer) - .setName('SM-2: use initial learning schedule') // eslint-disable-line obsidianmd/ui/sentence-case - .setDesc('For new SM-2 notes, use a fixed set of initial intervals before applying the full algorithm.') // eslint-disable-line obsidianmd/ui/sentence-case + .setName('SM-2: use initial learning schedule') + .setDesc('For new SM-2 notes, use a fixed set of initial intervals before applying the full algorithm.') .addToggle(toggle => toggle .setValue(this.plugin.settings.useInitialSchedule) .onChange(async (value: boolean) => { @@ -429,8 +429,8 @@ export class SpaceforgeSettingTab extends PluginSettingTab { if (this.plugin.settings.useInitialSchedule) { new Setting(sm2ParamsContainer) - .setName('SM-2: custom initial intervals (days)') // eslint-disable-line obsidianmd/ui/sentence-case - .setDesc('Comma-separated list for initial SM-2 reviews (e.g., 0,1,3,7). Must start with 0.') // eslint-disable-line obsidianmd/ui/sentence-case + .setName('SM-2: custom initial intervals (days)') + .setDesc('Comma-separated list for initial SM-2 reviews (e.g., 0,1,3,7). Must start with 0.') .addText(text => text .setValue(this.plugin.settings.initialScheduleCustomIntervals.join(', ')) .onChange(async (value: string) => { @@ -439,15 +439,15 @@ export class SpaceforgeSettingTab extends PluginSettingTab { this.plugin.settings.initialScheduleCustomIntervals = intervals; await this.plugin.savePluginData(); } else { - new Notice("Custom initial SM-2 intervals must start with 0 and be valid numbers.", 5000); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice("Custom initial SM-2 intervals must start with 0 and be valid numbers.", 5000); text.setValue(this.plugin.settings.initialScheduleCustomIntervals.join(', ')); } })); } new Setting(sm2ParamsContainer) - .setName('SM-2: maximum interval (days)') // eslint-disable-line obsidianmd/ui/sentence-case - .setDesc('Longest possible interval between SM-2 reviews.') // eslint-disable-line obsidianmd/ui/sentence-case + .setName('SM-2: maximum interval (days)') + .setDesc('Longest possible interval between SM-2 reviews.') .addText(text => text .setValue(this.plugin.settings.maximumInterval.toString()) .onChange(async (value: string) => { @@ -459,8 +459,8 @@ export class SpaceforgeSettingTab extends PluginSettingTab { })); new Setting(sm2ParamsContainer) - .setName('SM-2: load balancing') // eslint-disable-line obsidianmd/ui/sentence-case - .setDesc('Add slight randomness to SM-2 intervals to prevent reviews clumping on the same day.') // eslint-disable-line obsidianmd/ui/sentence-case + .setName('SM-2: load balancing') + .setDesc('Add slight randomness to SM-2 intervals to prevent reviews clumping on the same day.') .addToggle(toggle => toggle .setValue(this.plugin.settings.loadBalance) .onChange(async (value: boolean) => { @@ -472,7 +472,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { const fsrsParamsContainer = spacedRepSection.createEl('details', { cls: 'sf-settings-collapsible-subsection' }); const fsrsSummary = fsrsParamsContainer.createEl('summary'); // Changed to h3 - fsrsSummary.setText('FSRS parameters'); // eslint-disable-line obsidianmd/ui/sentence-case + fsrsSummary.setText('FSRS parameters'); fsrsParamsContainer.open = false; // Initially closed new Setting(fsrsParamsContainer) @@ -487,13 +487,13 @@ export class SpaceforgeSettingTab extends PluginSettingTab { await this.plugin.savePluginData(); this.plugin.reviewScheduleService.updateAlgorithmServicesForSettingsChange(); } else { - new Notice("FSRS request retention must be between 0.7 and 0.99."); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice("FSRS request retention must be between 0.7 and 0.99."); } })); new Setting(fsrsParamsContainer) .setName('Maximum interval (days)') - .setDesc('Longest possible interval FSRS will schedule.') // eslint-disable-line obsidianmd/ui/sentence-case + .setDesc('Longest possible interval FSRS will schedule.') .addText(text => text .setValue(this.plugin.settings.fsrsParameters?.maximum_interval?.toString() ?? DEFAULT_SETTINGS.fsrsParameters.maximum_interval?.toString() ?? '36500') .onChange(async (value) => { @@ -503,7 +503,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { await this.plugin.savePluginData(); this.plugin.reviewScheduleService.updateAlgorithmServicesForSettingsChange(); } else { - new Notice("FSRS maximum interval must be a positive number."); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice("FSRS maximum interval must be a positive number."); } })); @@ -523,12 +523,12 @@ export class SpaceforgeSettingTab extends PluginSettingTab { await this.plugin.savePluginData(); this.plugin.reviewScheduleService.updateAlgorithmServicesForSettingsChange(); } else { - new Notice("FSRS learning steps must be valid comma-separated numbers > 0, or empty."); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice("FSRS learning steps must be valid comma-separated numbers > 0, or empty."); } })); new Setting(fsrsParamsContainer) .setName('Enable fuzz') - .setDesc('Add slight randomness to FSRS intervals (recommended).') // eslint-disable-line obsidianmd/ui/sentence-case + .setDesc('Add slight randomness to FSRS intervals (recommended).') .addToggle(toggle => toggle .setValue(this.plugin.settings.fsrsParameters?.enable_fuzz ?? DEFAULT_SETTINGS.fsrsParameters.enable_fuzz ?? false) .onChange(async (value) => { @@ -538,7 +538,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { })); new Setting(fsrsParamsContainer) .setName('Enable short term scheduling') - .setDesc('Use FSRS short-term memory model (affects initial learning steps).') // eslint-disable-line obsidianmd/ui/sentence-case + .setDesc('Use FSRS short-term memory model (affects initial learning steps).') .addToggle(toggle => toggle .setValue(this.plugin.settings.fsrsParameters?.enable_short_term ?? DEFAULT_SETTINGS.fsrsParameters.enable_short_term ?? false) .onChange(async (value) => { @@ -548,7 +548,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { })); new Setting(fsrsParamsContainer) .setName('Weights (w)') - .setDesc('FSRS algorithm parameters (17 numbers). Edit with caution. Default weights are generally good.') // eslint-disable-line obsidianmd/ui/sentence-case + .setDesc('FSRS algorithm parameters (17 numbers). Edit with caution. Default weights are generally good.') .addTextArea(text => { text.inputEl.rows = 3; text.inputEl.addClass('sf-full-width-textarea'); @@ -560,7 +560,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { await this.plugin.savePluginData(); this.plugin.reviewScheduleService.updateAlgorithmServicesForSettingsChange(); } else { - new Notice("FSRS weights must be a comma-separated list of 17 valid numbers."); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice("FSRS weights must be a comma-separated list of 17 valid numbers."); } }); }); @@ -573,31 +573,31 @@ export class SpaceforgeSettingTab extends PluginSettingTab { conversionContainer.open = false; // Initially closed new Setting(conversionContainer) - .setName('Convert all SM-2 cards to FSRS') // eslint-disable-line obsidianmd/ui/sentence-case - .setDesc('Migrate all existing SM-2 cards to use the FSRS algorithm. This will reset their learning state for FSRS.') // eslint-disable-line obsidianmd/ui/sentence-case + .setName('Convert all SM-2 cards to FSRS') + .setDesc('Migrate all existing SM-2 cards to use the FSRS algorithm. This will reset their learning state for FSRS.') .addButton(button => button - .setButtonText('Convert SM-2 to FSRS') // eslint-disable-line obsidianmd/ui/sentence-case + .setButtonText('Convert SM-2 to FSRS') .setCta() // Call to action style .onClick(() => { new ConfirmationModal( this.app, - 'Convert SM-2 to FSRS', // eslint-disable-line obsidianmd/ui/sentence-case - 'Are you sure you want to convert ALL SM-2 cards to FSRS? Their FSRS learning state will be reset. This action cannot be easily undone.', // eslint-disable-line obsidianmd/ui/sentence-case + 'Convert SM-2 to FSRS', + 'Are you sure you want to convert ALL SM-2 cards to FSRS? Their FSRS learning state will be reset. This action cannot be easily undone.', async () => { - new Notice('Converting SM-2 cards to FSRS... This may take a moment.'); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice('Converting SM-2 cards to FSRS... This may take a moment.'); this.plugin.reviewScheduleService.convertAllSm2ToFsrs(); await this.plugin.savePluginData(); - new Notice('All SM-2 cards have been converted to FSRS.'); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice('All SM-2 cards have been converted to FSRS.'); this.display(); // Refresh settings tab } ).open(); })); new Setting(conversionContainer) - .setName('Convert all FSRS cards to SM-2') // eslint-disable-line obsidianmd/ui/sentence-case - .setDesc('Migrate all existing FSRS cards to use the SM-2 algorithm. Their SM-2 learning state will be initialized with defaults.') // eslint-disable-line obsidianmd/ui/sentence-case + .setName('Convert all FSRS cards to SM-2') + .setDesc('Migrate all existing FSRS cards to use the SM-2 algorithm. Their SM-2 learning state will be initialized with defaults.') .addButton(button => button - .setButtonText('Convert FSRS to SM-2') // eslint-disable-line obsidianmd/ui/sentence-case + .setButtonText('Convert FSRS to SM-2') .setCta() .onClick(() => { new ConfirmationModal( @@ -605,10 +605,10 @@ export class SpaceforgeSettingTab extends PluginSettingTab { 'Convert FSRS to SM-2', 'Are you sure you want to convert ALL FSRS cards to SM-2? Their SM-2 learning state will be reset to defaults. This action cannot be easily undone.', async () => { - new Notice('Converting FSRS cards to SM-2... This may take a moment.'); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice('Converting FSRS cards to SM-2... This may take a moment.'); this.plugin.reviewScheduleService.convertAllFsrsToSm2(); await this.plugin.savePluginData(); - new Notice('All FSRS cards have been converted to SM-2.'); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice('All FSRS cards have been converted to SM-2.'); this.display(); // Refresh settings tab } ).open(); @@ -674,7 +674,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { })); new Setting(interfaceSection) - .setName('Reading speed (WPM)') // eslint-disable-line obsidianmd/ui/sentence-case + .setName('Reading speed (WPM)') .setDesc('Words per minute for estimating review time') .addSlider(slider => slider .setLimits(100, 500, 10) @@ -687,7 +687,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { interfaceSection.createEl('div', { cls: 'sf-setting-explain', - text: 'Average adults read 200-250 WPM for regular content, 100-150 WPM for technical content' // eslint-disable-line obsidianmd/ui/sentence-case + text: 'Average adults read 200-250 WPM for regular content, 100-150 WPM for technical content' }); new Setting(interfaceSection) @@ -756,7 +756,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { const mcqSection = createCollapsible('Multiple choice questions', 'newspaper', false); // Closed by default new Setting(mcqSection) - .setName('Enable MCQ feature') // eslint-disable-line obsidianmd/ui/sentence-case + .setName('Enable MCQ feature') .setDesc('Use AI-generated multiple-choice questions to test your knowledge') .addToggle(toggle => toggle .setValue(this.plugin.settings.enableMCQ) @@ -878,17 +878,17 @@ export class SpaceforgeSettingTab extends PluginSettingTab { .setName('Ollama API URL') .setDesc('URL of your running Ollama instance (e.g., http://localhost:11434)') .addText(text => text - .setPlaceholder('http://localhost:11434') // eslint-disable-line obsidianmd/ui/sentence-case + .setPlaceholder('http://localhost:11434') .setValue(this.plugin.settings.ollamaApiUrl) .onChange(async (value: string) => { this.plugin.settings.ollamaApiUrl = value; await this.plugin.savePluginData(); })); new Setting(mcqSection) - .setName('Ollama model') // eslint-disable-line obsidianmd/ui/sentence-case - .setDesc('Name of the Ollama model to use (e.g., llama3, mistral)') // eslint-disable-line obsidianmd/ui/sentence-case + .setName('Ollama model') + .setDesc('Name of the Ollama model to use (e.g., llama3, mistral)') .addText(text => text - .setPlaceholder('Enter Ollama model name') // eslint-disable-line obsidianmd/ui/sentence-case + .setPlaceholder('Enter Ollama model name') .setValue(this.plugin.settings.ollamaModel) .onChange(async (value: string) => { this.plugin.settings.ollamaModel = value; @@ -901,9 +901,9 @@ export class SpaceforgeSettingTab extends PluginSettingTab { .setClass("sf-settings-subsection-provider-header"); new Setting(mcqSection) .setName('Gemini API key') - .setDesc('Your Google AI Gemini API key.') // eslint-disable-line obsidianmd/ui/sentence-case + .setDesc('Your Google AI Gemini API key.') .addText(text => text - .setPlaceholder('Enter your Gemini API key') // eslint-disable-line obsidianmd/ui/sentence-case + .setPlaceholder('Enter your Gemini API key') .setValue(this.plugin.settings.geminiApiKey) .onChange(async (value: string) => { this.plugin.settings.geminiApiKey = value; @@ -913,7 +913,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { .setName('Gemini model') .setDesc('Model name (e.g., gemini-pro)') .addText(text => text - .setPlaceholder('Enter Gemini model name') // eslint-disable-line obsidianmd/ui/sentence-case + .setPlaceholder('Enter Gemini model name') .setValue(this.plugin.settings.geminiModel) .onChange(async (value: string) => { this.plugin.settings.geminiModel = value; @@ -926,9 +926,9 @@ export class SpaceforgeSettingTab extends PluginSettingTab { .setClass("sf-settings-subsection-provider-header"); new Setting(mcqSection) .setName('Claude API key') - .setDesc('Your Anthropic Claude API key.') // eslint-disable-line obsidianmd/ui/sentence-case + .setDesc('Your Anthropic Claude API key.') .addText(text => text - .setPlaceholder('Enter your Claude API key') // eslint-disable-line obsidianmd/ui/sentence-case + .setPlaceholder('Enter your Claude API key') .setValue(this.plugin.settings.claudeApiKey) .onChange(async (value: string) => { this.plugin.settings.claudeApiKey = value; @@ -938,7 +938,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { .setName('Claude model') .setDesc('Model name (e.g., claude-3-opus-20240229, claude-3-sonnet-20240229)') .addText(text => text - .setPlaceholder('Enter Claude model name') // eslint-disable-line obsidianmd/ui/sentence-case + .setPlaceholder('Enter Claude model name') .setValue(this.plugin.settings.claudeModel) .onChange(async (value: string) => { this.plugin.settings.claudeModel = value; @@ -951,9 +951,9 @@ export class SpaceforgeSettingTab extends PluginSettingTab { .setClass("sf-settings-subsection-provider-header"); new Setting(mcqSection) .setName('Together AI API key') - .setDesc('Your Together AI API key.') // eslint-disable-line obsidianmd/ui/sentence-case + .setDesc('Your Together AI API key.') .addText(text => text - .setPlaceholder('Enter your Together AI API key') // eslint-disable-line obsidianmd/ui/sentence-case + .setPlaceholder('Enter your Together AI API key') .setValue(this.plugin.settings.togetherApiKey) .onChange(async (value: string) => { this.plugin.settings.togetherApiKey = value; @@ -963,7 +963,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { .setName('Together AI model') .setDesc('Model identifier from Together AI (e.g., meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8)') .addText(text => text - .setPlaceholder('Enter Together AI model identifier') // eslint-disable-line obsidianmd/ui/sentence-case + .setPlaceholder('Enter Together AI model identifier') .setValue(this.plugin.settings.togetherModel) .onChange(async (value: string) => { this.plugin.settings.togetherModel = value; @@ -1056,7 +1056,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { const promptTypeContainer = mcqFormattingGrid.createEl('div'); new Setting(promptTypeContainer) .setName('Prompt type') - .setDesc('Format for MCQ generation') // eslint-disable-line obsidianmd/ui/sentence-case + .setDesc('Format for MCQ generation') .addDropdown(dropdown => dropdown .addOption('basic', 'Basic') .addOption('detailed', 'Detailed') @@ -1069,7 +1069,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { // Second item in grid const difficultyContainer = mcqFormattingGrid.createEl('div'); new Setting(difficultyContainer) - .setName('MCQ difficulty') // eslint-disable-line obsidianmd/ui/sentence-case + .setName('MCQ difficulty') .setDesc('Complexity level') .addDropdown(dropdown => dropdown .addOption(MCQDifficulty.Basic, 'Basic recall') @@ -1177,8 +1177,8 @@ export class SpaceforgeSettingTab extends PluginSettingTab { if (this.plugin.settings.enableQuestionRegenerationOnRating) { new Setting(mcqSection) - .setName('Min SM-2 rating for MCQ regeneration') // eslint-disable-line obsidianmd/ui/sentence-case - .setDesc('For SM-2: Regenerate MCQs if review rating (0-5) is this value or higher. (0:Blackout, 5:Perfect)') // eslint-disable-line obsidianmd/ui/sentence-case + .setName('Min SM-2 rating for MCQ regeneration') + .setDesc('For SM-2: Regenerate MCQs if review rating (0-5) is this value or higher. (0:Blackout, 5:Perfect)') .addSlider(slider => slider .setLimits(0, 5, 1) .setValue(this.plugin.settings.minSm2RatingForQuestionRegeneration) @@ -1189,8 +1189,8 @@ export class SpaceforgeSettingTab extends PluginSettingTab { })); new Setting(mcqSection) - .setName('Min FSRS rating for MCQ regeneration') // eslint-disable-line obsidianmd/ui/sentence-case - .setDesc('For FSRS: Regenerate MCQs if review rating (1-4) is this value or higher. (1:Again, 4:Easy)') // eslint-disable-line obsidianmd/ui/sentence-case + .setName('Min FSRS rating for MCQ regeneration') + .setDesc('For FSRS: Regenerate MCQs if review rating (1-4) is this value or higher. (1:Again, 4:Easy)') .addSlider(slider => slider .setLimits(1, 4, 1) // FSRS ratings are 1-4 .setValue(this.plugin.settings.minFsrsRatingForQuestionRegeneration) @@ -1205,7 +1205,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { // If MCQ is disabled, show a message about enabling it const mcqDisabledMessage = mcqSection.createEl('div', { cls: 'sf-info-box' }); mcqDisabledMessage.createEl('p', { - text: 'Multiple Choice Questions are currently disabled. Enable it to configure durations and notifications.' // eslint-disable-line obsidianmd/ui/sentence-case + text: 'Multiple choice questions are currently disabled. Enable it to configure durations and notifications.' }); } @@ -1214,7 +1214,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { new Setting(pomodoroSection) .setName('Enable pomodoro timer') - .setDesc('Show the Pomodoro timer in the sidebar.') // eslint-disable-line obsidianmd/ui/sentence-case + .setDesc('Show the Pomodoro timer in the sidebar.') .addToggle(toggle => toggle .setValue(this.plugin.settings.pomodoroEnabled) .onChange(async (value: boolean) => { @@ -1310,7 +1310,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { } else { const pomodoroDisabledMessage = pomodoroSection.createEl('div', { cls: 'sf-info-box' }); pomodoroDisabledMessage.createEl('p', { - text: 'Pomodoro Timer is currently disabled. Enable it to configure durations and notifications.' // eslint-disable-line obsidianmd/ui/sentence-case + text: 'Pomodoro timer is currently disabled. Enable it to configure durations and notifications.' }); } @@ -1331,7 +1331,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { .setDesc(locationDesc) .addExtraButton(button => button .setIcon('info') - .setTooltip('Current location of your Spaceforge data') // eslint-disable-line obsidianmd/ui/sentence-case + .setTooltip('Current location of your Spaceforge data') .onClick(() => { const message = this.plugin.settings.useCustomDataPath && this.plugin.settings.customDataPath ? `Your data is stored at: ${this.plugin.settings.customDataPath.endsWith('/data.json') ? this.plugin.settings.customDataPath : `${this.plugin.settings.customDataPath}/data.json`}` @@ -1385,7 +1385,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { container.empty(); // Clear previous content if (algorithm === 'sm2') { - new Setting(container).setName('About the modified SM-2 algorithm').setHeading(); // eslint-disable-line obsidianmd/ui/sentence-case + new Setting(container).setName('About the modified SM-2 algorithm').setHeading(); container.createEl('p', { text: 'Spaceforge uses a modified version of the SuperMemo SM-2 algorithm (1991) which schedules reviews based on how well you recall information. ' + 'When you rate your recall quality from 0-5, the algorithm adjusts the interval and difficulty (ease factor) accordingly.' @@ -1394,8 +1394,8 @@ export class SpaceforgeSettingTab extends PluginSettingTab { text: 'Our implementation includes specific handling for overdue or skipped items to prevent them from accumulating in a backlog:' }); const sm2List = container.createEl('ul'); - sm2List.createEl('li', { text: 'Overdue items: Automatically set to review tomorrow with a quality rating of 0.' }); // eslint-disable-line obsidianmd/ui/sentence-case - sm2List.createEl('li', { text: 'Postponed items: Explicitly moved to tomorrow with a one-step quality penalty.' }); // eslint-disable-line obsidianmd/ui/sentence-case + sm2List.createEl('li', { text: 'Overdue items: Automatically set to review tomorrow with a quality rating of 0.' }); + sm2List.createEl('li', { text: 'Postponed items: Explicitly moved to tomorrow with a one-step quality penalty.' }); sm2List.createEl('li', { text: 'Both cases reset the repetition count to 1 and update the ease factor.' }); const ratingsTable = container.createEl('table', { cls: 'sf-ratings-table' }); // Added a class for potential styling @@ -1426,19 +1426,19 @@ export class SpaceforgeSettingTab extends PluginSettingTab { row4.createEl('td', { text: 'Perfect recall' }); row4.createEl('td', { text: 'Largest increase' }); } else if (algorithm === 'fsrs') { - new Setting(container).setName('About the FSRS algorithm').setHeading(); // eslint-disable-line obsidianmd/ui/sentence-case + new Setting(container).setName('About the FSRS algorithm').setHeading(); container.createEl('p', { text: 'FSRS (Free Spaced Repetition Scheduler) is a modern, evidence-based algorithm that models memory retention to optimize review schedules. ' + 'It calculates card difficulty and stability dynamically based on your review history and aims for a target retention rate.' - }); // eslint-disable-line obsidianmd/ui/sentence-case + }); container.createEl('p', { - text: 'Key concepts in FSRS:' // eslint-disable-line obsidianmd/ui/sentence-case + text: 'Key concepts in FSRS:' }); const fsrsList = container.createEl('ul'); - fsrsList.createEl('li', { text: 'Difficulty: How hard a card is to remember.' }); // eslint-disable-line obsidianmd/ui/sentence-case - fsrsList.createEl('li', { text: 'Stability: How long a card is likely to be remembered.' }); // eslint-disable-line obsidianmd/ui/sentence-case - fsrsList.createEl('li', { text: 'Retention: The probability of recalling a card at the time of review.' }); // eslint-disable-line obsidianmd/ui/sentence-case - fsrsList.createEl('li', { text: 'Learning Steps: Initial short intervals for new cards (configurable).' }); // eslint-disable-line obsidianmd/ui/sentence-case + fsrsList.createEl('li', { text: 'Difficulty: How hard a card is to remember.' }); + fsrsList.createEl('li', { text: 'Stability: How long a card is likely to be remembered.' }); + fsrsList.createEl('li', { text: 'Retention: The probability of recalling a card at the time of review.' }); + fsrsList.createEl('li', { text: 'Learning steps: Initial short intervals for new cards (configurable).' }); const ratingsTable = container.createEl('table', { cls: 'sf-ratings-table' }); const thead = ratingsTable.createTHead(); @@ -1449,22 +1449,22 @@ export class SpaceforgeSettingTab extends PluginSettingTab { headerRow.createEl('th', { text: 'Effect on stability/difficulty' }); const row1 = tbody.insertRow(); - row1.createEl('td', { text: '1 (Again)' }); // eslint-disable-line obsidianmd/ui/sentence-case + row1.createEl('td', { text: '1 (Again)' }); row1.createEl('td', { text: 'Forgot the card' }); row1.createEl('td', { text: 'Decreases stability, may increase difficulty' }); const row2 = tbody.insertRow(); - row2.createEl('td', { text: '2 (Hard)' }); // eslint-disable-line obsidianmd/ui/sentence-case + row2.createEl('td', { text: '2 (Hard)' }); row2.createEl('td', { text: 'Recalled with significant difficulty' }); row2.createEl('td', { text: 'Smaller increase in stability' }); const row3 = tbody.insertRow(); - row3.createEl('td', { text: '3 (Good)' }); // eslint-disable-line obsidianmd/ui/sentence-case + row3.createEl('td', { text: '3 (Good)' }); row3.createEl('td', { text: 'Recalled correctly' }); row3.createEl('td', { text: 'Standard increase in stability' }); const row4 = tbody.insertRow(); - row4.createEl('td', { text: '4 (Easy)' }); // eslint-disable-line obsidianmd/ui/sentence-case + row4.createEl('td', { text: '4 (Easy)' }); row4.createEl('td', { text: 'Recalled easily' }); row4.createEl('td', { text: 'Largest increase in stability' }); } diff --git a/ui/sidebar-view.ts b/ui/sidebar-view.ts index 540be4d..93b16c5 100644 --- a/ui/sidebar-view.ts +++ b/ui/sidebar-view.ts @@ -52,7 +52,7 @@ export class ReviewSidebarView extends ItemView { } getViewType(): string { return "spaceforge-review-schedule"; } - getDisplayText(): string { return "Spaceforge Review"; } // eslint-disable-line obsidianmd/ui/sentence-case + getDisplayText(): string { return "Spaceforge review"; } getIcon(): string { return "calendar-clock"; } async onOpen(): Promise { @@ -248,7 +248,7 @@ export class ReviewSidebarView extends ItemView { if (this.listViewRenderer) { // Check if renderer is initialized await this.listViewRenderer.render(container); } else { - container.setText("Error: Could not render list view. Renderer not ready."); // eslint-disable-line obsidianmd/ui/sentence-case + container.setText("Error: Could not render list view. Renderer not ready."); } } @@ -267,7 +267,7 @@ export class ReviewSidebarView extends ItemView { if (this.calendarView) { // Should always exist due to ensureBaseStructure await this.calendarView.render(); } else { - container.setText("Error: Could not render calendar view. CalendarView not ready."); // eslint-disable-line obsidianmd/ui/sentence-case + container.setText("Error: Could not render calendar view. CalendarView not ready."); return; // Avoid further errors if calendarView is somehow null } diff --git a/ui/sidebar/list-view-renderer.ts b/ui/sidebar/list-view-renderer.ts index 194bc66..9b1b8b5 100644 --- a/ui/sidebar/list-view-renderer.ts +++ b/ui/sidebar/list-view-renderer.ts @@ -127,15 +127,15 @@ export class ListViewRenderer { private _ensureAndUpdateReviewButtonsSection(container: HTMLElement, notesForDisplay: ReviewSchedule[], _selectedNotes: string[]): void { // --- Pomodoro Section (Always visible when enabled, independent of notes) --- - let pomodoroContainer = container.querySelector(".sidebar-pomodoro-section") as HTMLElement; + let pomodoroContainer = container.querySelector(".sidebar-pomodoro-section"); if (!pomodoroContainer) { pomodoroContainer = container.createDiv("sidebar-pomodoro-section"); } - let pomodoroSectionContainerEl = pomodoroContainer.querySelector(".sidebar-pomodoro-section-container") as HTMLElement; + let pomodoroSectionContainerEl = pomodoroContainer.querySelector(".sidebar-pomodoro-section-container"); if (!pomodoroSectionContainerEl) { // Check for legacy class names and migrate if found - const legacyEl = pomodoroContainer.querySelector(".pomodoro-section-container, .sidebar-pomodoro-button-container") as HTMLElement; + const legacyEl = pomodoroContainer.querySelector(".pomodoro-section-container, .sidebar-pomodoro-button-container"); if (legacyEl) { legacyEl.className = "sidebar-pomodoro-section-container"; pomodoroSectionContainerEl = legacyEl; @@ -161,7 +161,7 @@ export class ListViewRenderer { } // --- Review Buttons Section (Only visible when there are notes) --- - let reviewButtonsContainer = container.querySelector(".review-buttons-container") as HTMLElement; + let reviewButtonsContainer = container.querySelector(".review-buttons-container"); // Visibility of review buttons should depend on whether there are notes in the current context (notesForDisplay) if (notesForDisplay.length > 0) { @@ -187,7 +187,7 @@ export class ListViewRenderer { reviewButtonsContainer.classList.remove('sf-hidden'); // Bulk action buttons - let bulkActionButtons = container.querySelector(".review-bulk-actions") as HTMLElement; + let bulkActionButtons = container.querySelector(".review-bulk-actions"); if (!bulkActionButtons) { bulkActionButtons = container.createDiv("review-bulk-actions"); const reviewSelectedBtn = bulkActionButtons.createEl("button", { text: "Review selected", cls: "review-bulk-button" }); @@ -253,13 +253,13 @@ export class ListViewRenderer { } else if (reviewButtonsContainer) { reviewButtonsContainer.classList.add('sf-hidden'); - const bulkActionButtons = container.querySelector(".review-bulk-actions") as HTMLElement; + const bulkActionButtons = container.querySelector(".review-bulk-actions"); if (bulkActionButtons) bulkActionButtons.toggleClass('sf-hidden', true); } } private _ensureAndUpdateAllCaughtUpMessage(container: HTMLElement, dueNotesForStats: ReviewSchedule[], activeListBaseDate: Date | null): void { - let caughtUpEl = container.querySelector(".review-all-caught-up") as HTMLElement; + let caughtUpEl = container.querySelector(".review-all-caught-up"); if (dueNotesForStats.length === 0 && !activeListBaseDate) { if (!caughtUpEl) { caughtUpEl = container.createDiv("review-all-caught-up"); @@ -283,7 +283,7 @@ export class ListViewRenderer { } private async _ensureAndUpdateDateSections(container: HTMLElement, sortedDateKeys: string[], groupedNotes: Record): Promise { - const existingSectionElements = Array.from(container.querySelectorAll(".review-date-section")) as HTMLElement[]; + const existingSectionElements = Array.from(container.querySelectorAll(".review-date-section")); const dataKeysFromData = new Set(sortedDateKeys); let notesDisplayed = false; @@ -301,8 +301,8 @@ export class ListViewRenderer { if (!notesForSection || notesForSection.length === 0) continue; notesDisplayed = true; - let dateSectionEl = container.querySelector(`.review-date-section[data-date-key="${dateStr}"]`) as HTMLElement; - let notesContainerEl: HTMLElement; + let dateSectionEl = container.querySelector(`.review-date-section[data-date-key="${dateStr}"]`); + let notesContainerEl: HTMLElement | null; if (!dateSectionEl) { dateSectionEl = container.createDiv("review-date-section"); @@ -355,7 +355,7 @@ export class ListViewRenderer { headerRow.createSpan("review-date-time"); // Placeholder for time notesContainerEl = dateSectionEl.createDiv("review-notes-container"); } else { - notesContainerEl = dateSectionEl.querySelector(".review-notes-container") as HTMLElement; + notesContainerEl = dateSectionEl.querySelector(".review-notes-container"); if (!notesContainerEl) { // Should not happen if structure is consistent notesContainerEl = dateSectionEl.createDiv("review-notes-container"); } @@ -372,9 +372,11 @@ export class ListViewRenderer { dateSectionEl.addClass("review-date-section-overdue"); } - const headerContainer = dateSectionEl.querySelector(".review-date-header-container") as HTMLElement; - const dateHeading = headerContainer.querySelector("h3") as HTMLElement; - const reviewTimeEl = dateSectionEl.querySelector(".review-date-time") as HTMLElement; + const headerContainer = dateSectionEl.querySelector(".review-date-header-container"); + const dateHeading = headerContainer?.querySelector("h3"); + const reviewTimeEl = dateSectionEl.querySelector(".review-date-time"); + + if (!headerContainer || !dateHeading) continue; let displayHeader = dateStr; const noteCountText = `${notesForSection.length} ${notesForSection.length === 1 ? 'note' : 'notes'}`; @@ -390,7 +392,7 @@ export class ListViewRenderer { } dateHeading.setText(displayHeader); - let overdueBadge = dateHeading.querySelector(".review-overdue-badge") as HTMLElement | null; + let overdueBadge = dateHeading.querySelector(".review-overdue-badge"); const todayActualStart = DateUtils.startOfDay(); // Timestamp for actual today's midnight // Condition for showing overdue badge: @@ -432,13 +434,13 @@ export class ListViewRenderer { let sectionTime = 0; for (const note of notesForSection) { sectionTime += await this.plugin.reviewScheduleService.estimateReviewTime(note.path); } - reviewTimeEl.setText(`(${EstimationUtils.formatTime(sectionTime)})`); + if (reviewTimeEl) reviewTimeEl.setText(`(${EstimationUtils.formatTime(sectionTime)})`); await this._updateOrRenderNoteList(notesContainerEl, notesForSection, dateStr, container); } // Message for activeListBaseDate with no notes - let noNotesForDateMsg = container.querySelector(".review-no-notes-for-date") as HTMLElement; + let noNotesForDateMsg = container.querySelector(".review-no-notes-for-date"); const activeListBaseDate = this.getActiveListBaseDate(); if (activeListBaseDate && !notesDisplayed) { if (!noNotesForDateMsg) { @@ -453,7 +455,7 @@ export class ListViewRenderer { private _ensureAndUpdateActiveSessionSection(container: HTMLElement): void { const activeSession = this.plugin.reviewSessionService.getActiveSession(); - let sessionSection = container.querySelector(".review-session-section") as HTMLElement; + let sessionSection = container.querySelector(".review-session-section"); if (activeSession) { if (!sessionSection) { @@ -473,11 +475,17 @@ export class ListViewRenderer { }); } sessionSection.toggleClass('sf-hidden', false); - (sessionSection.querySelector(".review-session-name") as HTMLElement).setText(activeSession.name); - (sessionSection.querySelector(".review-session-progress") as HTMLElement).setText(`Progress: ${activeSession.currentIndex}/${activeSession.hierarchy.traversalOrder.length}`); - const progressBar = sessionSection.querySelector(".review-session-progress-bar") as HTMLElement; - const progressPercent = Math.min(100, Math.round((activeSession.currentIndex / activeSession.hierarchy.traversalOrder.length) * 100)); - progressBar.style.width = `${progressPercent}%`; + const nameEl = sessionSection.querySelector(".review-session-name"); + if (nameEl) nameEl.setText(activeSession.name); + + const progressEl = sessionSection.querySelector(".review-session-progress"); + if (progressEl) progressEl.setText(`Progress: ${activeSession.currentIndex}/${activeSession.hierarchy.traversalOrder.length}`); + + const progressBar = sessionSection.querySelector(".review-session-progress-bar"); + if (progressBar) { + const progressPercent = Math.min(100, Math.round((activeSession.currentIndex / activeSession.hierarchy.traversalOrder.length) * 100)); + progressBar.style.width = `${progressPercent}%`; + } } else if (sessionSection) { sessionSection.toggleClass('sf-hidden', true); } @@ -495,7 +503,7 @@ export class ListViewRenderer { return true; }); - let upcomingSection = container.querySelector(".review-upcoming-section") as HTMLElement; + let upcomingSection = container.querySelector(".review-upcoming-section"); if (upcomingKeys.length > 0) { if (!upcomingSection) { @@ -504,10 +512,10 @@ export class ListViewRenderer { upcomingSection.createDiv("review-upcoming-list"); // List container } upcomingSection.toggleClass('sf-hidden', false); - const upcomingListEl = upcomingSection.querySelector(".review-upcoming-list") as HTMLElement; + const upcomingListEl = upcomingSection.querySelector(".review-upcoming-list"); if (!upcomingListEl) return; // Should not happen - const existingDayItemElements = Array.from(upcomingListEl.querySelectorAll(".review-upcoming-day")) as HTMLElement[]; + const existingDayItemElements = Array.from(upcomingListEl.querySelectorAll(".review-upcoming-day")); // Remove stale day items @@ -528,7 +536,7 @@ export class ListViewRenderer { continue; } - let dayItemEl = upcomingListEl.querySelector(`.review-upcoming-day[data-day-key="${dayKey}"]`) as HTMLElement; + let dayItemEl = upcomingListEl.querySelector(`.review-upcoming-day[data-day-key="${dayKey}"]`); if (!dayItemEl) { dayItemEl = upcomingListEl.createDiv("review-upcoming-day"); dayItemEl.addClass("clickable"); @@ -536,8 +544,8 @@ export class ListViewRenderer { const daySummary = dayItemEl.createDiv("review-upcoming-day-summary"); daySummary.createEl("span", { cls: "review-upcoming-day-name" }); // Placeholder for name - dayItemEl.addEventListener("click", () => { // Attach listener once - const currentDayKey = dayItemEl.dataset.dayKey; + dayItemEl.addEventListener("click", (e) => { // Attach listener once + const currentDayKey = (e.currentTarget as HTMLElement).dataset.dayKey; if (!currentDayKey) return; const expandedUpcomingDayKey = this.getExpandedUpcomingDayKey(); const isCurrentlyExpanded = expandedUpcomingDayKey === currentDayKey; @@ -548,7 +556,7 @@ export class ListViewRenderer { } // Update summary - const daySummaryNameEl = dayItemEl.querySelector(".review-upcoming-day-summary .review-upcoming-day-name") as HTMLElement; + const daySummaryNameEl = dayItemEl.querySelector(".review-upcoming-day-summary .review-upcoming-day-name"); let upcomingDisplayHeader = dayKey; if (notesForDay.length > 0) { // Should always be true here const sampleUpcomingDate = new Date(notesForDay[0].nextReviewDate); @@ -564,7 +572,7 @@ export class ListViewRenderer { // Handle expanded state const isExpanded = this.getExpandedUpcomingDayKey() === dayKey; dayItemEl.classList.toggle("is-expanded", isExpanded); - let notesContainerEl = dayItemEl.querySelector(".review-upcoming-notes-container") as HTMLElement; + let notesContainerEl = dayItemEl.querySelector(".review-upcoming-notes-container"); if (isExpanded) { if (!notesContainerEl) { @@ -591,7 +599,7 @@ export class ListViewRenderer { dateStr: string, parentContainerForBulkActions: HTMLElement ): Promise { - const existingNoteElements = Array.from(notesContainer.querySelectorAll('.review-note-item[data-note-path]')) as HTMLElement[]; + const existingNoteElements = Array.from(notesContainer.querySelectorAll('.review-note-item[data-note-path]')); const existingNotesMap = new Map(existingNoteElements.map(el => [el.dataset.notePath, el])); const notesInOrder: HTMLElement[] = []; @@ -677,13 +685,13 @@ export class ListViewRenderer { */ private updateBulkActionButtonsVisibility(container: HTMLElement): void { const selectedNotesPaths = this.getSelectedNotes(); - const bulkActionsContainer = container.querySelector('.review-bulk-actions') as HTMLElement; + const bulkActionsContainer = container.querySelector('.review-bulk-actions'); if (bulkActionsContainer) { bulkActionsContainer.toggleClass('sf-hidden', selectedNotesPaths.length <= 1); // Handle visibility/disabled state of "Advance Selected" button - const advanceSelectedBtn = bulkActionsContainer.querySelector('.review-bulk-advance') as HTMLButtonElement | null; + const advanceSelectedBtn = bulkActionsContainer.querySelector('.review-bulk-advance'); if (advanceSelectedBtn) { if (selectedNotesPaths.length > 1) { const todayStart = DateUtils.startOfDay(new Date()); // Returns timestamp diff --git a/ui/sidebar/note-item-renderer.ts b/ui/sidebar/note-item-renderer.ts index f9d1663..8dd4488 100644 --- a/ui/sidebar/note-item-renderer.ts +++ b/ui/sidebar/note-item-renderer.ts @@ -38,7 +38,7 @@ export class NoteItemRenderer { } // Title - const titleEl = noteEl.querySelector(".review-note-title") as HTMLElement; + const titleEl = noteEl.querySelector(".review-note-title"); if (titleEl) { const file = this.plugin.app.vault.getAbstractFileByPath(note.path); titleEl.setText(file instanceof TFile ? file.basename : note.path); @@ -48,8 +48,8 @@ export class NoteItemRenderer { const formattedTime = EstimationUtils.formatTime(estimatedTime); // Phase and Time - const phaseEl = noteEl.querySelector(".review-note-phase") as HTMLElement; - const timeElOld = noteEl.querySelector(".review-note-time") as HTMLElement; // For non-initial + const phaseEl = noteEl.querySelector(".review-note-phase"); + const timeElOld = noteEl.querySelector(".review-note-time"); // For non-initial if (timeElOld) timeElOld.remove(); // Remove old time element if it exists from a previous state if (phaseEl) { @@ -74,8 +74,8 @@ export class NoteItemRenderer { } // Drag handle visibility (buttons are static, drag handle might change) - const buttonsEl = noteEl.querySelector(".review-note-buttons") as HTMLElement; - const dragHandleEl = buttonsEl?.querySelector(".review-note-drag-handle") as HTMLElement; + const buttonsEl = noteEl.querySelector(".review-note-buttons"); + const dragHandleEl = buttonsEl?.querySelector(".review-note-drag-handle"); if (dragHandleEl) { // If it exists, update its state or recreate if logic is complex const isDraggable = (dateStr === 'Due notes' || dateStr === 'Today'); dragHandleEl.classList.toggle("is-disabled", !isDraggable); @@ -88,7 +88,7 @@ export class NoteItemRenderer { } // Advance button state - const advanceBtn = noteEl.querySelector(".review-note-advance") as HTMLButtonElement | null; + const advanceBtn = noteEl.querySelector(".review-note-advance"); if (advanceBtn) { const todayStartTs = DateUtils.startOfDay(new Date()); // Returns timestamp const noteReviewDayStartTs = DateUtils.startOfDay(new Date(note.nextReviewDate)); // Returns timestamp diff --git a/ui/sidebar/pomodoro-ui-manager.ts b/ui/sidebar/pomodoro-ui-manager.ts index b3d6de6..5c980b0 100644 --- a/ui/sidebar/pomodoro-ui-manager.ts +++ b/ui/sidebar/pomodoro-ui-manager.ts @@ -55,7 +55,7 @@ export class PomodoroUIManager { // new Notice("Pomodoro durations updated."); // Removed notification return true; } else { - new Notice("Invalid Pomodoro durations. Settings not saved. Please enter positive numbers."); // eslint-disable-line obsidianmd/ui/sentence-case + new Notice("Invalid Pomodoro durations. Settings not saved. Please enter positive numbers."); // Re-populate with current valid settings to prevent saving invalid on next close if not corrected if (this.pomodoroQuickWorkInput) this.pomodoroQuickWorkInput.value = String(this.plugin.settings.pomodoroWorkDuration); if (this.pomodoroQuickShortInput) this.pomodoroQuickShortInput.value = String(this.plugin.settings.pomodoroShortBreakDuration); @@ -339,7 +339,7 @@ export class PomodoroUIManager { this.pomodoroUserOverrideHoursInput.value = String(this.plugin.pluginState.pomodoroUserOverrideHours); const hoursLabel = overrideInputsContainer.createSpan("pomodoro-override-label-small"); - hoursLabel.setText("h"); // eslint-disable-line obsidianmd/ui/sentence-case + hoursLabel.setText("h"); this.pomodoroUserOverrideMinutesInput = overrideInputsContainer.createEl("input", { type: "number", cls: "pomodoro-override-minutes" }); this.pomodoroUserOverrideMinutesInput.setAttr("min", "0"); @@ -347,7 +347,7 @@ export class PomodoroUIManager { this.pomodoroUserOverrideMinutesInput.value = String(this.plugin.pluginState.pomodoroUserOverrideMinutes); const minutesLabel = overrideInputsContainer.createSpan("pomodoro-override-label-small"); - minutesLabel.setText("m"); // eslint-disable-line obsidianmd/ui/sentence-case + minutesLabel.setText("m"); // Add to estimation toggle const toggleContainer = overrideContainer.createDiv("pomodoro-override-toggle-container"); diff --git a/utils/event-emitter.ts b/utils/event-emitter.ts index c324fe5..4a7515a 100644 --- a/utils/event-emitter.ts +++ b/utils/event-emitter.ts @@ -1,11 +1,13 @@ /** * Simple event emitter implementation */ -export class EventEmitter { +export type EventMap = Record; + +export class EventEmitter> { /** * Event listeners by event name */ - private listeners: Record void)[]> = {}; + private listeners: { [K in keyof Events]?: ((...args: Events[K]) => void)[] } = {}; /** * Register a listener for an event @@ -13,12 +15,13 @@ export class EventEmitter { * @param event Event name * @param callback Function to call when event is emitted */ - on(event: string, callback: (...args: any[]) => void): void { - if (!this.listeners[event]) { - this.listeners[event] = []; + on(event: K, callback: (...args: Events[K]) => void): void { + const key = event as string; + if (!this.listeners[key as keyof Events]) { + this.listeners[key as keyof Events] = []; } - this.listeners[event].push(callback); + this.listeners[key as keyof Events]!.push(callback); } /** @@ -27,12 +30,13 @@ export class EventEmitter { * @param event Event name * @param args Arguments to pass to listeners */ - emit(event: string, ...args: unknown[]): void { - if (!this.listeners[event]) { + emit(event: K, ...args: Events[K]): void { + const key = event as string; + if (!this.listeners[key as keyof Events]) { return; } - for (const callback of this.listeners[event]) { + for (const callback of this.listeners[key as keyof Events]!) { callback(...args); } } @@ -43,12 +47,13 @@ export class EventEmitter { * @param event Event name * @param callback Function to remove */ - off(event: string, callback: (...args: any[]) => void): void { - if (!this.listeners[event]) { + off(event: K, callback: (...args: Events[K]) => void): void { + const key = event as string; + if (!this.listeners[key as keyof Events]) { return; } - this.listeners[event] = this.listeners[event].filter(cb => cb !== callback); + this.listeners[key as keyof Events] = this.listeners[key as keyof Events]!.filter(cb => cb !== callback); } /** @@ -56,9 +61,9 @@ export class EventEmitter { * * @param event Event name */ - removeAllListeners(event?: string): void { + removeAllListeners(event?: K): void { if (event) { - delete this.listeners[event]; + delete this.listeners[event as keyof Events]; } else { this.listeners = {}; } diff --git a/utils/link-analyzer.ts b/utils/link-analyzer.ts index 465800f..37f49b4 100644 --- a/utils/link-analyzer.ts +++ b/utils/link-analyzer.ts @@ -161,7 +161,7 @@ export class LinkAnalyzer { } } } - } catch (_error) { /* handle error */ } + } catch { /* handle error */ } } // Find the node with the most outgoing links as the starting point @@ -506,6 +506,6 @@ export class LinkAnalyzer { return resolvedLinks; - } catch (_error) { /* handle error */ return []; } + } catch { /* handle error */ return []; } } }