mirror of
https://github.com/dralkh/spaceforge.git
synced 2026-07-22 06:45:03 +00:00
refactor: remove unnecessary await calls, clean up ESLint comments, and refine UI element selection.
This commit is contained in:
parent
341c222231
commit
77b214b936
25 changed files with 253 additions and 271 deletions
|
|
@ -13,18 +13,15 @@ export class ClaudeService implements IMCQGenerationService {
|
|||
|
||||
async generateMCQs(notePath: string, noteContent: string, settings: SpaceforgeSettings): Promise<MCQSet | null> {
|
||||
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 [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,18 +15,15 @@ export class GeminiService implements IMCQGenerationService {
|
|||
|
||||
async generateMCQs(notePath: string, noteContent: string, settings: SpaceforgeSettings): Promise<MCQSet | null> {
|
||||
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 [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,18 +13,15 @@ export class OllamaService implements IMCQGenerationService {
|
|||
|
||||
async generateMCQs(notePath: string, noteContent: string, settings: SpaceforgeSettings): Promise<MCQSet | null> {
|
||||
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 [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,13 +13,11 @@ export class OpenAIService implements IMCQGenerationService {
|
|||
|
||||
async generateMCQs(notePath: string, noteContent: string, settings: SpaceforgeSettings): Promise<MCQSet | null> {
|
||||
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 [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,14 +33,12 @@ export class OpenRouterService implements IMCQGenerationService {
|
|||
async generateMCQs(notePath: string, noteContent: string, settings: SpaceforgeSettings): Promise<MCQSet | null> {
|
||||
// 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 [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,18 +13,15 @@ export class TogetherService implements IMCQGenerationService {
|
|||
|
||||
async generateMCQs(notePath: string, noteContent: string, settings: SpaceforgeSettings): Promise<MCQSet | null> {
|
||||
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 [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -68,12 +68,12 @@ export class MCQController {
|
|||
externalOnCompleteCallback?: (path: string, success: boolean) => void
|
||||
): Promise<void> {
|
||||
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<MCQSet | null> {
|
||||
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<void> {
|
||||
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");
|
||||
}
|
||||
})();
|
||||
|
|
|
|||
59
main.ts
59
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<SpaceforgeEvents>;
|
||||
|
||||
async onload(): Promise<void> {
|
||||
this.events = new EventEmitter();
|
||||
this.events = new EventEmitter<SpaceforgeEvents>();
|
||||
|
||||
// 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<void> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ export class BatchReviewModal extends Modal {
|
|||
async collectAllMCQs(): Promise<void> {
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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<HTMLElement>(".calendar-container");
|
||||
if (!calendarContainer) {
|
||||
calendarContainer = this.containerEl.createDiv("calendar-container");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<HTMLButtonElement>(".event-modal-btn-primary");
|
||||
const isValid = this.titleInput.value.trim() !== "" && this.dateInput.value !== "";
|
||||
|
||||
if (saveButton) {
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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' });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<void> {
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<HTMLElement>(".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<HTMLElement>(".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<HTMLElement>(".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<HTMLElement>(".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<HTMLElement>(".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<HTMLElement>(".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<HTMLElement>(".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<string, ReviewSchedule[]>): Promise<void> {
|
||||
const existingSectionElements = Array.from(container.querySelectorAll(".review-date-section")) as HTMLElement[];
|
||||
const existingSectionElements = Array.from(container.querySelectorAll<HTMLElement>(".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<HTMLElement>(`.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<HTMLElement>(".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<HTMLElement>(".review-date-header-container");
|
||||
const dateHeading = headerContainer?.querySelector<HTMLElement>("h3");
|
||||
const reviewTimeEl = dateSectionEl.querySelector<HTMLElement>(".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<HTMLElement>(".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<HTMLElement>(".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<HTMLElement>(".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<HTMLElement>(".review-session-name");
|
||||
if (nameEl) nameEl.setText(activeSession.name);
|
||||
|
||||
const progressEl = sessionSection.querySelector<HTMLElement>(".review-session-progress");
|
||||
if (progressEl) progressEl.setText(`Progress: ${activeSession.currentIndex}/${activeSession.hierarchy.traversalOrder.length}`);
|
||||
|
||||
const progressBar = sessionSection.querySelector<HTMLElement>(".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<HTMLElement>(".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<HTMLElement>(".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<HTMLElement>(".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<HTMLElement>(`.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<HTMLElement>(".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<HTMLElement>(".review-upcoming-notes-container");
|
||||
|
||||
if (isExpanded) {
|
||||
if (!notesContainerEl) {
|
||||
|
|
@ -591,7 +599,7 @@ export class ListViewRenderer {
|
|||
dateStr: string,
|
||||
parentContainerForBulkActions: HTMLElement
|
||||
): Promise<void> {
|
||||
const existingNoteElements = Array.from(notesContainer.querySelectorAll('.review-note-item[data-note-path]')) as HTMLElement[];
|
||||
const existingNoteElements = Array.from(notesContainer.querySelectorAll<HTMLElement>('.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<HTMLElement>('.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<HTMLButtonElement>('.review-bulk-advance');
|
||||
if (advanceSelectedBtn) {
|
||||
if (selectedNotesPaths.length > 1) {
|
||||
const todayStart = DateUtils.startOfDay(new Date()); // Returns timestamp
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ export class NoteItemRenderer {
|
|||
}
|
||||
|
||||
// Title
|
||||
const titleEl = noteEl.querySelector(".review-note-title") as HTMLElement;
|
||||
const titleEl = noteEl.querySelector<HTMLElement>(".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<HTMLElement>(".review-note-phase");
|
||||
const timeElOld = noteEl.querySelector<HTMLElement>(".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<HTMLElement>(".review-note-buttons");
|
||||
const dragHandleEl = buttonsEl?.querySelector<HTMLElement>(".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<HTMLButtonElement>(".review-note-advance");
|
||||
if (advanceBtn) {
|
||||
const todayStartTs = DateUtils.startOfDay(new Date()); // Returns timestamp
|
||||
const noteReviewDayStartTs = DateUtils.startOfDay(new Date(note.nextReviewDate)); // Returns timestamp
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
/**
|
||||
* Simple event emitter implementation
|
||||
*/
|
||||
export class EventEmitter {
|
||||
export type EventMap = Record<string, any[]>;
|
||||
|
||||
export class EventEmitter<Events extends { [K in keyof Events]: any[] } = Record<string, any[]>> {
|
||||
/**
|
||||
* Event listeners by event name
|
||||
*/
|
||||
private listeners: Record<string, ((...args: any[]) => 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<K extends keyof Events>(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<K extends keyof Events>(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<K extends keyof Events>(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<K extends keyof Events>(event?: K): void {
|
||||
if (event) {
|
||||
delete this.listeners[event];
|
||||
delete this.listeners[event as keyof Events];
|
||||
} else {
|
||||
this.listeners = {};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 []; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue