mirror of
https://github.com/dralkh/spaceforge.git
synced 2026-07-22 06:45:03 +00:00
release: v1.0.5
JS/TS: - Replace builtin-modules with node:module (esbuild.config.mjs) - Replace fs-extra with native fs (install.js), remove from deps - Fix createEl deprecation: use createDiv/createSpan - Remove unnecessary type assertions, fix unsafe casts - Bind event handlers with arrow functions CSS: - Remove all !important declarations (~30 instances) — replace with chained selectors (.class.class) for equal specificity - Merge all duplicate selectors across 7 CSS files (~50+ instances) - Convert 3-digit hex to 6-digit format (_variables.css) - Fix duplicate CSS properties (overflow-y in mcq.css) - Delete leftover display:none suppression rule in calendar.css Meta: - Bump version to 1.0.5 - Update minAppVersion to 1.8.7 - Add versions.json entry for 1.0.5
This commit is contained in:
parent
241ec16101
commit
e80dfda20d
37 changed files with 390 additions and 572 deletions
|
|
@ -3,7 +3,16 @@ import { CLAUDE, MCQS, MCQ, API } from '../ui/constants';
|
|||
import SpaceforgePlugin from '../main';
|
||||
import { MCQQuestion, MCQSet } from '../models/mcq';
|
||||
import { IMCQGenerationService } from './mcq-generation-service';
|
||||
import { SpaceforgeSettings, MCQQuestionAmountMode, MCQDifficulty } from '../models/settings'; // Import MCQQuestionAmountMode
|
||||
import { SpaceforgeSettings, MCQQuestionAmountMode, MCQDifficulty } from '../models/settings';
|
||||
|
||||
interface ClaudeResponse {
|
||||
content: Array<{ text: string }>;
|
||||
}
|
||||
|
||||
interface ClaudeErrorResponse {
|
||||
error?: { message?: string };
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export class ClaudeService implements IMCQGenerationService {
|
||||
plugin: SpaceforgePlugin;
|
||||
|
|
@ -105,17 +114,18 @@ export class ClaudeService implements IMCQGenerationService {
|
|||
});
|
||||
|
||||
if (response.status !== 200) {
|
||||
const errorData = response.json || { message: response.text };
|
||||
throw new Error(`API request failed (${response.status}): ${errorData.error?.message || errorData.message || 'Unknown error'}`);
|
||||
const errorData: ClaudeErrorResponse = response.json ?? { message: response.text };
|
||||
throw new Error(`API request failed (${response.status}): ${errorData.error?.message ?? errorData.message ?? 'Unknown error'}`);
|
||||
}
|
||||
|
||||
const data = response.json;
|
||||
if (!data.content || !data.content.length || !data.content[0].text) {
|
||||
const data = response.json as ClaudeResponse;
|
||||
if (!data.content?.length || !data.content[0]?.text) {
|
||||
throw new Error('Invalid API response format from Claude - missing content');
|
||||
}
|
||||
return data.content[0].text;
|
||||
} catch (error) {
|
||||
new Notice(`Claude API error: ${error.message}`);
|
||||
const msg = error instanceof Error ? error.message : String(error);
|
||||
new Notice(`Claude API error: ${msg}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,16 @@ import { GEMINI, MCQS, MCQ, API } from '../ui/constants';
|
|||
import SpaceforgePlugin from '../main';
|
||||
import { MCQQuestion, MCQSet } from '../models/mcq';
|
||||
import { IMCQGenerationService } from './mcq-generation-service';
|
||||
import { SpaceforgeSettings, MCQQuestionAmountMode, MCQDifficulty } from '../models/settings'; // Import MCQQuestionAmountMode
|
||||
import { SpaceforgeSettings, MCQQuestionAmountMode, MCQDifficulty } from '../models/settings';
|
||||
|
||||
interface GeminiResponse {
|
||||
candidates: Array<{ content: { parts: Array<{ text: string }> } }>;
|
||||
}
|
||||
|
||||
interface GeminiErrorResponse {
|
||||
error?: { message?: string };
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export class GeminiService implements IMCQGenerationService {
|
||||
plugin: SpaceforgePlugin;
|
||||
|
|
@ -104,24 +111,24 @@ export class GeminiService implements IMCQGenerationService {
|
|||
});
|
||||
|
||||
if (response.status !== 200) {
|
||||
const errorData = response.json;
|
||||
const errorMessage = errorData?.error?.message || errorData?.message || 'Unknown error';
|
||||
const errorData = response.json as GeminiErrorResponse;
|
||||
const errorMessage = errorData?.error?.message ?? errorData?.message ?? 'Unknown error';
|
||||
throw new Error(`API request failed (${response.status}): ${errorMessage}`);
|
||||
}
|
||||
|
||||
const data = response.json;
|
||||
// Gemini response structure: data.candidates[0].content.parts[0].text
|
||||
if (!data.candidates || !data.candidates.length || !data.candidates[0].content || !data.candidates[0].content.parts || !data.candidates[0].content.parts.length || !data.candidates[0].content.parts[0].text) {
|
||||
const data = response.json as GeminiResponse;
|
||||
if (!data.candidates?.length || !data.candidates[0]?.content?.parts?.length || !data.candidates[0]?.content?.parts[0]?.text) {
|
||||
throw new Error('Invalid API response format from Gemini - missing content');
|
||||
}
|
||||
return data.candidates[0].content.parts[0].text;
|
||||
} catch (error) {
|
||||
new Notice(`Gemini API error: ${error.message}`);
|
||||
const msg = error instanceof Error ? error.message : String(error);
|
||||
new Notice(`Gemini API error: ${msg}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private parseResponse(response: string, settings: SpaceforgeSettings, numQuestionsToGenerate: number): MCQQuestion[] {
|
||||
private parseResponse(response: string, _settings: SpaceforgeSettings, numQuestionsToGenerate: number): MCQQuestion[] {
|
||||
const questions: MCQQuestion[] = [];
|
||||
try {
|
||||
const questionBlocks: string[] = response.split(/\d+\.\s+/).filter(block => block.trim().length > 0);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,11 @@ import { OLLAMA, API } from '../ui/constants';
|
|||
import SpaceforgePlugin from '../main';
|
||||
import { MCQQuestion, MCQSet } from '../models/mcq';
|
||||
import { IMCQGenerationService } from './mcq-generation-service';
|
||||
import { SpaceforgeSettings, MCQQuestionAmountMode, MCQDifficulty } from '../models/settings'; // Import MCQQuestionAmountMode
|
||||
import { SpaceforgeSettings, MCQQuestionAmountMode, MCQDifficulty } from '../models/settings';
|
||||
|
||||
interface OllamaResponse {
|
||||
message: { role: string; content: string };
|
||||
}
|
||||
|
||||
export class OllamaService implements IMCQGenerationService {
|
||||
plugin: SpaceforgePlugin;
|
||||
|
|
@ -14,7 +18,7 @@ export class OllamaService implements IMCQGenerationService {
|
|||
|
||||
async generateMCQs(notePath: string, noteContent: string, settings: SpaceforgeSettings): Promise<MCQSet | null> {
|
||||
if (!settings.ollamaApiUrl) {
|
||||
new Notice(`${OLLAMA} ${API} ${URL} not set in settings.`);
|
||||
new Notice(`${OLLAMA} ${API} URL not set in settings.`);
|
||||
return null;
|
||||
}
|
||||
if (!settings.ollamaModel) {
|
||||
|
|
@ -106,19 +110,19 @@ export class OllamaService implements IMCQGenerationService {
|
|||
throw new Error(`API request failed (${response.status}): ${errorText}`);
|
||||
}
|
||||
|
||||
const data = response.json;
|
||||
// Ollama's non-streaming chat response structure is typically { model, created_at, message: { role, content }, done }
|
||||
if (!data.message || !data.message.content) {
|
||||
const data = response.json as OllamaResponse;
|
||||
if (!data.message?.content) {
|
||||
throw new Error('Invalid API response format from Ollama - missing message content');
|
||||
}
|
||||
return data.message.content;
|
||||
} catch (error) {
|
||||
new Notice(`Ollama API error: ${error.message}`);
|
||||
const msg = error instanceof Error ? error.message : String(error);
|
||||
new Notice(`Ollama API error: ${msg}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private parseResponse(response: string, settings: SpaceforgeSettings, numQuestionsToGenerate: number): MCQQuestion[] {
|
||||
private parseResponse(response: string, _settings: SpaceforgeSettings, numQuestionsToGenerate: number): MCQQuestion[] {
|
||||
const questions: MCQQuestion[] = [];
|
||||
try {
|
||||
const questionBlocks: string[] = response.split(/\d+\.\s+/).filter(block => block.trim().length > 0);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,16 @@ import { OPENAI, API } from '../ui/constants';
|
|||
import SpaceforgePlugin from '../main';
|
||||
import { MCQQuestion, MCQSet } from '../models/mcq';
|
||||
import { IMCQGenerationService } from './mcq-generation-service';
|
||||
import { SpaceforgeSettings, MCQQuestionAmountMode, MCQDifficulty } from '../models/settings'; // Import MCQQuestionAmountMode
|
||||
import { SpaceforgeSettings, MCQQuestionAmountMode, MCQDifficulty } from '../models/settings';
|
||||
|
||||
interface OpenAIResponse {
|
||||
choices: Array<{ message: { content: string } }>;
|
||||
}
|
||||
|
||||
interface OpenAIErrorResponse {
|
||||
error?: { message?: string };
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export class OpenAIService implements IMCQGenerationService {
|
||||
plugin: SpaceforgePlugin;
|
||||
|
|
@ -102,22 +111,23 @@ export class OpenAIService implements IMCQGenerationService {
|
|||
});
|
||||
|
||||
if (response.status !== 200) {
|
||||
const errorData = response.json || { message: response.text };
|
||||
throw new Error(`API request failed (${response.status}): ${errorData.error?.message || errorData.message || 'Unknown error'}`);
|
||||
const errorData: OpenAIErrorResponse = response.json ?? { message: response.text };
|
||||
throw new Error(`API request failed (${response.status}): ${errorData.error?.message ?? errorData.message ?? 'Unknown error'}`);
|
||||
}
|
||||
|
||||
const data = response.json;
|
||||
if (!data.choices || !data.choices.length || !data.choices[0].message || !data.choices[0].message.content) {
|
||||
const data = response.json as OpenAIResponse;
|
||||
if (!data.choices?.length || !data.choices[0]?.message?.content) {
|
||||
throw new Error('Invalid API response format from OpenAI - missing content');
|
||||
}
|
||||
return data.choices[0].message.content;
|
||||
} catch (error) {
|
||||
new Notice(`OpenAI API error: ${error.message}`);
|
||||
const msg = error instanceof Error ? error.message : String(error);
|
||||
new Notice(`OpenAI API error: ${msg}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private parseResponse(response: string, settings: SpaceforgeSettings, numQuestionsToGenerate: number): MCQQuestion[] {
|
||||
private parseResponse(response: string, _settings: SpaceforgeSettings, numQuestionsToGenerate: number): MCQQuestion[] {
|
||||
const questions: MCQQuestion[] = [];
|
||||
try {
|
||||
const questionBlocks: string[] = response.split(/\d+\.\s+/).filter(block => block.trim().length > 0);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,12 @@ import { OPENROUTER, API } from '../ui/constants';
|
|||
import SpaceforgePlugin from '../main';
|
||||
import { MCQQuestion, MCQSet } from '../models/mcq';
|
||||
import { IMCQGenerationService } from './mcq-generation-service';
|
||||
import { SpaceforgeSettings, MCQQuestionAmountMode, MCQDifficulty } from '../models/settings'; // Import MCQQuestionAmountMode
|
||||
import { SpaceforgeSettings, MCQQuestionAmountMode, MCQDifficulty } from '../models/settings';
|
||||
|
||||
interface OpenRouterResponse {
|
||||
choices: Array<{ message: { content: string } }>;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Service for generating MCQs using the OpenRouter API
|
||||
|
|
@ -171,15 +176,16 @@ For example:
|
|||
throw new Error(`API request failed (${response.status}): ${response.text}`);
|
||||
}
|
||||
|
||||
const data = response.json;
|
||||
const data = response.json as OpenRouterResponse;
|
||||
|
||||
if (!data.choices || !data.choices.length || !data.choices[0].message) {
|
||||
if (!data.choices?.length || !data.choices[0]?.message) {
|
||||
throw new Error('Invalid API response format from OpenRouter - missing choices');
|
||||
}
|
||||
|
||||
return data.choices[0].message.content;
|
||||
} catch (error) {
|
||||
new Notice(`OpenRouter API error: ${error.message}`);
|
||||
const msg = error instanceof Error ? error.message : String(error);
|
||||
new Notice(`OpenRouter API error: ${msg}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
|
@ -192,7 +198,7 @@ For example:
|
|||
* @param numQuestionsToGenerate The target number of questions expected
|
||||
* @returns Array of parsed MCQ questions
|
||||
*/
|
||||
private parseResponse(response: string, settings: SpaceforgeSettings, numQuestionsToGenerate: number): MCQQuestion[] {
|
||||
private parseResponse(response: string, _settings: SpaceforgeSettings, numQuestionsToGenerate: number): MCQQuestion[] {
|
||||
const questions: MCQQuestion[] = [];
|
||||
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,16 @@ import { TOGETHER_AI, API } from '../ui/constants';
|
|||
import SpaceforgePlugin from '../main';
|
||||
import { MCQQuestion, MCQSet } from '../models/mcq';
|
||||
import { IMCQGenerationService } from './mcq-generation-service';
|
||||
import { SpaceforgeSettings, MCQQuestionAmountMode, MCQDifficulty } from '../models/settings'; // Import MCQQuestionAmountMode
|
||||
import { SpaceforgeSettings, MCQQuestionAmountMode, MCQDifficulty } from '../models/settings';
|
||||
|
||||
interface TogetherResponse {
|
||||
choices: Array<{ message: { content: string } }>;
|
||||
}
|
||||
|
||||
interface TogetherErrorResponse {
|
||||
error?: { message?: string };
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export class TogetherService implements IMCQGenerationService {
|
||||
plugin: SpaceforgePlugin;
|
||||
|
|
@ -103,17 +112,18 @@ export class TogetherService implements IMCQGenerationService {
|
|||
});
|
||||
|
||||
if (response.status !== 200) {
|
||||
const errorData = response.json || { message: response.text };
|
||||
throw new Error(`API request failed (${response.status}): ${errorData.error?.message || errorData.message || 'Unknown error'}`);
|
||||
const errorData: TogetherErrorResponse = response.json ?? { message: response.text };
|
||||
throw new Error(`API request failed (${response.status}): ${errorData.error?.message ?? errorData.message ?? 'Unknown error'}`);
|
||||
}
|
||||
|
||||
const data = response.json;
|
||||
if (!data.choices || !data.choices.length || !data.choices[0].message || !data.choices[0].message.content) {
|
||||
const data = response.json as TogetherResponse;
|
||||
if (!data.choices?.length || !data.choices[0]?.message?.content) {
|
||||
throw new Error('Invalid API response format from Together AI - missing content');
|
||||
}
|
||||
return data.choices[0].message.content;
|
||||
} catch (error) {
|
||||
new Notice(`Together AI API error: ${error.message}`);
|
||||
const msg = error instanceof Error ? error.message : String(error);
|
||||
new Notice(`Together AI API error: ${msg}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -427,12 +427,12 @@ export class ReviewControllerCore implements IReviewController {
|
|||
if (schedule && this.plugin.settings.enableQuestionRegenerationOnRating && this.plugin.mcqService && typeof response === 'number') {
|
||||
if (schedule.schedulingAlgorithm === 'fsrs') {
|
||||
// FSRS: response is FsrsRating (1-4), setting is minFsrsRatingForQuestionRegeneration (1-4)
|
||||
if (response >= this.plugin.settings.minFsrsRatingForQuestionRegeneration) {
|
||||
if ((response as number) >= this.plugin.settings.minFsrsRatingForQuestionRegeneration) {
|
||||
triggerRegeneration = true;
|
||||
}
|
||||
} else { // SM-2
|
||||
// SM-2: response is ReviewResponse (0-5), setting is minSm2RatingForQuestionRegeneration (0-5)
|
||||
if (response >= this.plugin.settings.minSm2RatingForQuestionRegeneration) {
|
||||
if ((response as number) >= this.plugin.settings.minSm2RatingForQuestionRegeneration) {
|
||||
triggerRegeneration = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import esbuild from "esbuild";
|
||||
import process from "node:process";
|
||||
import builtins from "builtin-modules";
|
||||
import { builtinModules } from "node:module";
|
||||
|
||||
const banner =
|
||||
`/*
|
||||
|
|
@ -32,7 +32,7 @@ const buildOptions = {
|
|||
'@lezer/highlight',
|
||||
'@lezer/lr',
|
||||
'tslib',
|
||||
...builtins],
|
||||
...builtinModules],
|
||||
format: 'cjs',
|
||||
target: 'es2018',
|
||||
logLevel: "info",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
const fs = require('fs-extra');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { exec } = require('child_process');
|
||||
const readline = require('readline');
|
||||
|
|
@ -40,7 +40,7 @@ function installPlugin(obsidianPluginsDir) {
|
|||
const pluginDir = path.join(obsidianPluginsDir, pluginName);
|
||||
|
||||
// Ensure plugin directory exists
|
||||
fs.ensureDirSync(pluginDir);
|
||||
fs.mkdirSync(pluginDir, { recursive: true });
|
||||
|
||||
console.log('Installing plugin files...');
|
||||
|
||||
|
|
@ -52,7 +52,7 @@ function installPlugin(obsidianPluginsDir) {
|
|||
const destPath = path.join(pluginDir, file);
|
||||
|
||||
if (fs.existsSync(sourcePath)) {
|
||||
fs.copySync(sourcePath, destPath, { overwrite: true });
|
||||
fs.copyFileSync(sourcePath, destPath);
|
||||
console.log(`✓ Copied ${file}`);
|
||||
} else {
|
||||
console.log(`⚠ Warning: ${file} not found in project directory`);
|
||||
|
|
|
|||
19
main.ts
19
main.ts
|
|
@ -258,7 +258,7 @@ export default class SpaceforgePlugin extends Plugin {
|
|||
try {
|
||||
this.loadData().then(loaded => {
|
||||
if (loaded) {
|
||||
existingData = loaded;
|
||||
existingData = loaded as Partial<SpaceforgePluginData>;
|
||||
}
|
||||
}).catch(() => { /* handle error */ });
|
||||
}
|
||||
|
|
@ -346,7 +346,7 @@ export default class SpaceforgePlugin extends Plugin {
|
|||
const file = this.app.vault.getAbstractFileByPath(effectivePath);
|
||||
if (file instanceof TFile) {
|
||||
const jsonData = await this.app.vault.read(file);
|
||||
if (jsonData) rawLoadedData = JSON.parse(jsonData);
|
||||
if (jsonData) rawLoadedData = JSON.parse(jsonData) as SpaceforgePluginData;
|
||||
new Notice(`Spaceforge: Loaded data from custom path: ${effectivePath}`, 3000);
|
||||
} else {
|
||||
// Custom path specified but file doesn't exist.
|
||||
|
|
@ -357,7 +357,7 @@ export default class SpaceforgePlugin extends Plugin {
|
|||
try {
|
||||
const oldJsonData = await this.app.vault.read(oldFile);
|
||||
if (oldJsonData) {
|
||||
rawLoadedData = JSON.parse(oldJsonData);
|
||||
rawLoadedData = JSON.parse(oldJsonData) as SpaceforgePluginData;
|
||||
// Data will be saved to new path by savePluginData later
|
||||
}
|
||||
} catch { /* handle error */ }
|
||||
|
|
@ -368,7 +368,7 @@ export default class SpaceforgePlugin extends Plugin {
|
|||
}
|
||||
} else {
|
||||
// Using default plugin storage path
|
||||
rawLoadedData = await this.loadData(); // This is the plugin's internal save/load
|
||||
rawLoadedData = await this.loadData() as SpaceforgePluginData | undefined;
|
||||
}
|
||||
|
||||
let loadedSettings = {}; // Start with empty object
|
||||
|
|
@ -444,9 +444,9 @@ export default class SpaceforgePlugin extends Plugin {
|
|||
|
||||
// SIMPLE RECOVERY: Try localStorage backup once, then use defaults
|
||||
try {
|
||||
const backupData = await this.app.loadLocalStorage('spaceforge-backup');
|
||||
const backupData: unknown = await this.app.loadLocalStorage('spaceforge-backup');
|
||||
if (backupData && typeof backupData === 'string') {
|
||||
const parsedBackup = JSON.parse(backupData);
|
||||
const parsedBackup = JSON.parse(backupData) as { reviewData?: Partial<PluginStateData>; settings?: Partial<SpaceforgeSettings> };
|
||||
if (parsedBackup.reviewData) {
|
||||
this.settings = { ...DEFAULT_SETTINGS, ...parsedBackup.settings };
|
||||
this.pluginState = { ...DEFAULT_PLUGIN_STATE_DATA, ...parsedBackup.reviewData };
|
||||
|
|
@ -483,7 +483,7 @@ export default class SpaceforgePlugin extends Plugin {
|
|||
try {
|
||||
// Ensure settings object exists and is valid, applying defaults if necessary
|
||||
if (!this.settings || typeof this.settings !== 'object') {
|
||||
this.settings = JSON.parse(JSON.stringify(DEFAULT_SETTINGS));
|
||||
this.settings = JSON.parse(JSON.stringify(DEFAULT_SETTINGS)) as SpaceforgeSettings;
|
||||
} else {
|
||||
// Ensure all default keys are present
|
||||
this.settings = { ...DEFAULT_SETTINGS, ...this.settings };
|
||||
|
|
@ -554,7 +554,8 @@ export default class SpaceforgePlugin extends Plugin {
|
|||
new Notice(`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);
|
||||
const msg = writeError instanceof Error ? writeError.message : String(writeError);
|
||||
new Notice(`Error saving data to custom path ${effectiveSavePath}: ${msg}. 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
|
||||
|
|
@ -717,7 +718,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 as any}`);
|
||||
new Notice(`Unsupported MCQ API provider selected: ${String(this.settings.mcqApiProvider)}`);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"id": "spaceforge",
|
||||
"name": "Spaceforge",
|
||||
"version": "1.0.4",
|
||||
"minAppVersion": "0.15.0",
|
||||
"version": "1.0.5",
|
||||
"minAppVersion": "1.8.7",
|
||||
"description": "Enhance knowledge retention with spaced repetition using FSRS & SM-2 algorithms, AI-powered MCQ generation, integrated Pomodoro timer, and calendar event manager.",
|
||||
"author": "dralkh",
|
||||
"authorUrl": "https://github.com/dralkh",
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ export enum ReviewResponse {
|
|||
*/
|
||||
export function toSM2Quality(response: ReviewResponse): number {
|
||||
// Ensure response is within 0-5 range
|
||||
if (response >= 0 && response <= 5) {
|
||||
if (response as number >= 0 && response as number <= 5) {
|
||||
return response;
|
||||
}
|
||||
// Default fallback
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "spaceforge",
|
||||
"version": "1.0.4",
|
||||
"version": "1.0.5",
|
||||
"description": "A spaced repetition plugin for efficient knowledge review in Obsidian",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
|
@ -20,11 +20,9 @@
|
|||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/node": "^16.11.6",
|
||||
"@typescript-eslint/eslint-plugin": "^8.48.1",
|
||||
"@typescript-eslint/parser": "^8.48.1",
|
||||
"builtin-modules": "3.3.0",
|
||||
"esbuild": "^0.17.19",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-plugin-obsidianmd": "^0.1.9",
|
||||
|
|
@ -35,7 +33,6 @@
|
|||
"typescript-eslint": "^8.48.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"fs-extra": "^11.1.1",
|
||||
"ts-fsrs": "^4.7.1"
|
||||
}
|
||||
}
|
||||
|
|
@ -292,7 +292,7 @@ export class CalendarEventService {
|
|||
*/
|
||||
importEvents(json: string): number {
|
||||
try {
|
||||
const events: CalendarEvent[] = JSON.parse(json);
|
||||
const events = JSON.parse(json) as CalendarEvent[];
|
||||
let importedCount = 0;
|
||||
|
||||
events.forEach(event => {
|
||||
|
|
|
|||
|
|
@ -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 as any}`);
|
||||
default: throw new Error(`Unknown FsrsRating: ${String(rating)}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -319,7 +319,7 @@ export class ReviewScheduleService {
|
|||
newEase = Math.max(1.3, newEase);
|
||||
schedule.ease = Math.round(newEase * 100);
|
||||
|
||||
if (qualityRating >= ReviewResponse.CorrectWithDifficulty) {
|
||||
if (qualityRating >= (ReviewResponse.CorrectWithDifficulty as number)) {
|
||||
schedule.consecutive += 1;
|
||||
if (qualityRating >= 3) {
|
||||
// repetitionCount for initial phase should increment if successful, reset if not.
|
||||
|
|
@ -342,7 +342,7 @@ export class ReviewScheduleService {
|
|||
schedule.ease = ease;
|
||||
schedule.repetitionCount = repetitionCount;
|
||||
|
||||
if (qualityRating >= ReviewResponse.CorrectWithDifficulty) {
|
||||
if (qualityRating >= (ReviewResponse.CorrectWithDifficulty as number)) {
|
||||
schedule.consecutive += 1;
|
||||
} else {
|
||||
schedule.consecutive = 0;
|
||||
|
|
|
|||
|
|
@ -161,35 +161,22 @@
|
|||
}
|
||||
|
||||
/* ====== Utility Classes ====== */
|
||||
.sf-hidden {
|
||||
display: none !important;
|
||||
.sf-hidden.sf-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sf-visible {
|
||||
display: block !important;
|
||||
.sf-visible.sf-visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sf-flex {
|
||||
display: flex !important;
|
||||
.sf-flex.sf-flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.sf-invisible {
|
||||
opacity: 0 !important;
|
||||
.sf-invisible.sf-invisible {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.sf-visible-opacity {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
/* ====== Display Utilities ====== */
|
||||
.sf-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.sf-visible {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.sf-flex {
|
||||
display: flex !important;
|
||||
.sf-visible-opacity.sf-visible-opacity {
|
||||
opacity: 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,8 +27,8 @@
|
|||
--sf-recurring-bg: rgba(94, 129, 172, 0.05); /* Very light blue tint to match accent */
|
||||
|
||||
/* Text colors */
|
||||
--sf-text: var(--text-normal, #333);
|
||||
--sf-text-muted: var(--text-muted, #888);
|
||||
--sf-text: var(--text-normal, #333333);
|
||||
--sf-text-muted: var(--text-muted, #888888);
|
||||
--sf-text-on-primary: var(--text-on-accent, white);
|
||||
|
||||
/* Background colors */
|
||||
|
|
|
|||
|
|
@ -48,35 +48,6 @@
|
|||
padding: 2px 0; /* Further reduced padding */
|
||||
}
|
||||
|
||||
.is-collapsed .calendar-day {
|
||||
min-height: 40px; /* Further reduced minimum height */
|
||||
padding: 2px; /* Further reduced padding */
|
||||
font-size: 10px; /* Further smaller font size */
|
||||
border-radius: 3px; /* Further smaller border radius */
|
||||
}
|
||||
|
||||
.is-collapsed .calendar-day-number {
|
||||
font-size: 11px; /* Further smaller font size */
|
||||
margin-bottom: 1px; /* Further reduced margin */
|
||||
}
|
||||
|
||||
.is-collapsed .calendar-day.today .calendar-day-number::after {
|
||||
width: 10px; /* Further reduced underline width */
|
||||
}
|
||||
|
||||
.is-collapsed .calendar-review-count {
|
||||
width: 16px; /* Further smaller size */
|
||||
height: 16px; /* Further smaller size */
|
||||
font-size: 8px; /* Further smaller font size */
|
||||
top: 2px; /* Adjusted position */
|
||||
right: 2px; /* Adjusted position */
|
||||
}
|
||||
|
||||
.is-collapsed .calendar-time-estimate {
|
||||
font-size: 8px; /* Further smaller font size */
|
||||
padding: 0 2px; /* Further reduced padding */
|
||||
}
|
||||
|
||||
.is-collapsed .calendar-day {
|
||||
min-height: 40px; /* Reduced minimum height */
|
||||
padding: 4px; /* Reduced padding */
|
||||
|
|
@ -866,11 +837,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
/* Day hover add event button */
|
||||
.calendar-day {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.calendar-day-add-event {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
|
|
@ -925,6 +891,7 @@
|
|||
max-width: 600px;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
animation: modalSlideIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
.event-modal .modal-content {
|
||||
|
|
@ -1014,6 +981,7 @@
|
|||
outline: none;
|
||||
border-color: var(--interactive-accent);
|
||||
box-shadow: 0 0 0 3px rgba(var(--interactive-accent-rgb), 0.1);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.event-modal-input:hover,
|
||||
|
|
@ -1225,10 +1193,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
.event-modal {
|
||||
animation: modalSlideIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes modalSlideIn {
|
||||
from {
|
||||
|
|
@ -1241,13 +1205,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
/* Focus States */
|
||||
.event-modal-input:focus,
|
||||
.event-modal-textarea:focus,
|
||||
.event-modal-select:focus {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* Success/Error States */
|
||||
.event-modal-input.error,
|
||||
.event-modal-textarea.error,
|
||||
|
|
@ -1281,54 +1238,7 @@
|
|||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Compact Event Modal Styles */
|
||||
.event-modal {
|
||||
max-width: 500px;
|
||||
max-height: 85vh;
|
||||
}
|
||||
|
||||
.event-modal .modal-content {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* Compact Header */
|
||||
.event-modal-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--sf-space-sm);
|
||||
padding: var(--sf-space-md) var(--sf-space-lg);
|
||||
background: var(--interactive-accent);
|
||||
color: var(--text-on-accent);
|
||||
border-radius: 8px 8px 0 0;
|
||||
}
|
||||
|
||||
.event-modal-header-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 50%;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.event-modal-header-text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.event-modal-title {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--text-on-accent);
|
||||
}
|
||||
|
||||
/* Compact Form Layout */
|
||||
.event-modal-form {
|
||||
padding: var(--sf-space-md);
|
||||
}
|
||||
|
||||
.event-modal-row {
|
||||
display: flex;
|
||||
gap: var(--sf-space-sm);
|
||||
|
|
@ -1346,63 +1256,6 @@
|
|||
gap: 2px;
|
||||
}
|
||||
|
||||
.event-modal-label {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--text-normal);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.event-modal-label.required::after {
|
||||
content: " *";
|
||||
color: var(--text-error);
|
||||
}
|
||||
|
||||
.event-modal-input,
|
||||
.event-modal-textarea,
|
||||
.event-modal-select {
|
||||
width: 100%;
|
||||
padding: 6px 8px;
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
background-color: var(--background-primary);
|
||||
color: var(--text-normal);
|
||||
transition: border-color 0.2s ease;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.event-modal-input:focus,
|
||||
.event-modal-textarea:focus,
|
||||
.event-modal-select:focus {
|
||||
outline: none;
|
||||
border-color: var(--interactive-accent);
|
||||
}
|
||||
|
||||
.event-modal-textarea {
|
||||
resize: vertical;
|
||||
min-height: 40px;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
/* Compact Checkbox */
|
||||
.event-modal-checkbox {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
accent-color: var(--interactive-accent);
|
||||
cursor: pointer;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.event-modal-checkbox-label {
|
||||
font-size: 13px;
|
||||
color: var(--text-normal);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Compact Color Picker */
|
||||
.event-modal-color-picker-compact {
|
||||
display: flex;
|
||||
|
|
@ -1410,15 +1263,6 @@
|
|||
gap: 6px;
|
||||
}
|
||||
|
||||
.event-modal-color-input {
|
||||
width: 32px;
|
||||
height: 28px;
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
/* Recurrence End Date Row */
|
||||
.event-modal-recurrence-end-row {
|
||||
display: none;
|
||||
|
|
@ -1428,47 +1272,7 @@
|
|||
display: flex;
|
||||
}
|
||||
|
||||
/* Compact Actions */
|
||||
.event-modal-actions {
|
||||
display: flex;
|
||||
gap: var(--sf-space-sm);
|
||||
justify-content: flex-end;
|
||||
padding: var(--sf-space-md) var(--sf-space-lg);
|
||||
border-top: 1px solid var(--background-modifier-border);
|
||||
background-color: var(--background-secondary);
|
||||
margin: 0 -var(--sf-space-md) -var(--sf-space-md);
|
||||
border-radius: 0 0 8px 8px;
|
||||
}
|
||||
|
||||
.event-modal-btn {
|
||||
padding: 6px 16px;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.event-modal-btn-primary {
|
||||
background-color: var(--interactive-accent);
|
||||
color: var(--text-on-accent);
|
||||
}
|
||||
|
||||
.event-modal-btn-primary:hover:not(:disabled) {
|
||||
background-color: var(--interactive-accent-hover);
|
||||
}
|
||||
|
||||
.event-modal-btn-secondary {
|
||||
background-color: var(--background-modifier-border);
|
||||
color: var(--text-normal);
|
||||
}
|
||||
|
||||
.event-modal-btn-secondary:hover {
|
||||
background-color: var(--background-modifier-border-hover);
|
||||
}
|
||||
|
||||
/* Danger Button */
|
||||
.event-modal-btn-danger {
|
||||
background-color: var(--color-red, #dc3545);
|
||||
color: white;
|
||||
|
|
@ -1479,36 +1283,31 @@
|
|||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.event-modal-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Responsive Compact Design */
|
||||
@media (max-width: 600px) {
|
||||
.event-modal {
|
||||
max-width: 95vw;
|
||||
margin: var(--sf-space-sm);
|
||||
}
|
||||
|
||||
|
||||
.event-modal-row {
|
||||
flex-direction: column;
|
||||
gap: var(--sf-space-xs);
|
||||
}
|
||||
|
||||
|
||||
.event-modal-header {
|
||||
padding: var(--sf-space-sm) var(--sf-space-md);
|
||||
}
|
||||
|
||||
|
||||
.event-modal-form {
|
||||
padding: var(--sf-space-sm);
|
||||
}
|
||||
|
||||
|
||||
.event-modal-actions {
|
||||
padding: var(--sf-space-sm) var(--sf-space-md);
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
|
||||
.event-modal-btn {
|
||||
flex: 1;
|
||||
min-width: auto;
|
||||
|
|
@ -1569,17 +1368,3 @@
|
|||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
/* Remove some of the previous enhanced styles to avoid conflicts */
|
||||
.event-modal-datetime-section,
|
||||
.event-modal-datetime-grid,
|
||||
.event-modal-input-group,
|
||||
.event-modal-input-icon,
|
||||
.event-modal-all-day-container,
|
||||
.event-modal-category-color-grid,
|
||||
.event-modal-category-container,
|
||||
.event-modal-color-container,
|
||||
.event-modal-color-label,
|
||||
.event-modal-recurrence-container,
|
||||
.event-modal-recurrence-end-container {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
|||
278
styles/mcq.css
278
styles/mcq.css
|
|
@ -116,7 +116,6 @@
|
|||
flex-direction: column;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
flex-grow: 1; /* Allow to grow and take available space */
|
||||
overflow-y: auto; /* Add scrolling if content overflows */
|
||||
}
|
||||
|
||||
/* Improve question text visibility */
|
||||
|
|
@ -131,37 +130,37 @@
|
|||
}
|
||||
|
||||
/* Ensure the MCQ modal takes more space */
|
||||
.spaceforge-mcq-modal.modal-content {
|
||||
.spaceforge-mcq-modal.modal-content.spaceforge-mcq-modal {
|
||||
min-width: 500px; /* Ensure modal has minimum width */
|
||||
max-width: 90vw; /* Allow up to 90% of viewport width */
|
||||
min-height: 300px; /* Ensure minimum height */
|
||||
padding: 28px !important;
|
||||
padding: 28px;
|
||||
display: flex; /* Make modal content a flex container */
|
||||
flex-direction: column; /* Stack children vertically */
|
||||
}
|
||||
|
||||
/* Enhanced progress indicator with more granular steps */
|
||||
[data-progress="0"] .mcq-progress::before { width: 0% !important; }
|
||||
[data-progress="5"] .mcq-progress::before { width: 5% !important; }
|
||||
[data-progress="10"] .mcq-progress::before { width: 10% !important; }
|
||||
[data-progress="15"] .mcq-progress::before { width: 15% !important; }
|
||||
[data-progress="20"] .mcq-progress::before { width: 20% !important; }
|
||||
[data-progress="25"] .mcq-progress::before { width: 25% !important; }
|
||||
[data-progress="30"] .mcq-progress::before { width: 30% !important; }
|
||||
[data-progress="35"] .mcq-progress::before { width: 35% !important; }
|
||||
[data-progress="40"] .mcq-progress::before { width: 40% !important; }
|
||||
[data-progress="45"] .mcq-progress::before { width: 45% !important; }
|
||||
[data-progress="50"] .mcq-progress::before { width: 50% !important; }
|
||||
[data-progress="55"] .mcq-progress::before { width: 55% !important; }
|
||||
[data-progress="60"] .mcq-progress::before { width: 60% !important; }
|
||||
[data-progress="65"] .mcq-progress::before { width: 65% !important; }
|
||||
[data-progress="70"] .mcq-progress::before { width: 70% !important; }
|
||||
[data-progress="75"] .mcq-progress::before { width: 75% !important; }
|
||||
[data-progress="80"] .mcq-progress::before { width: 80% !important; }
|
||||
[data-progress="85"] .mcq-progress::before { width: 85% !important; }
|
||||
[data-progress="90"] .mcq-progress::before { width: 90% !important; }
|
||||
[data-progress="95"] .mcq-progress::before { width: 95% !important; }
|
||||
[data-progress="100"] .mcq-progress::before { width: 100% !important; }
|
||||
[data-progress="0"] .mcq-progress::before { width: 0%; }
|
||||
[data-progress="5"] .mcq-progress::before { width: 5%; }
|
||||
[data-progress="10"] .mcq-progress::before { width: 10%; }
|
||||
[data-progress="15"] .mcq-progress::before { width: 15%; }
|
||||
[data-progress="20"] .mcq-progress::before { width: 20%; }
|
||||
[data-progress="25"] .mcq-progress::before { width: 25%; }
|
||||
[data-progress="30"] .mcq-progress::before { width: 30%; }
|
||||
[data-progress="35"] .mcq-progress::before { width: 35%; }
|
||||
[data-progress="40"] .mcq-progress::before { width: 40%; }
|
||||
[data-progress="45"] .mcq-progress::before { width: 45%; }
|
||||
[data-progress="50"] .mcq-progress::before { width: 50%; }
|
||||
[data-progress="55"] .mcq-progress::before { width: 55%; }
|
||||
[data-progress="60"] .mcq-progress::before { width: 60%; }
|
||||
[data-progress="65"] .mcq-progress::before { width: 65%; }
|
||||
[data-progress="70"] .mcq-progress::before { width: 70%; }
|
||||
[data-progress="75"] .mcq-progress::before { width: 75%; }
|
||||
[data-progress="80"] .mcq-progress::before { width: 80%; }
|
||||
[data-progress="85"] .mcq-progress::before { width: 85%; }
|
||||
[data-progress="90"] .mcq-progress::before { width: 90%; }
|
||||
[data-progress="95"] .mcq-progress::before { width: 95%; }
|
||||
[data-progress="100"] .mcq-progress::before { width: 100%; }
|
||||
|
||||
/* ====== MCQ Warning Message ====== */
|
||||
.mcq-attempt-warning {
|
||||
|
|
@ -211,7 +210,8 @@
|
|||
line-height: 1.4;
|
||||
color: var(--text-normal);
|
||||
position: relative;
|
||||
/* overflow: hidden; Removed to prevent clipping */
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
min-height: auto; /* Allow button to grow with content */
|
||||
height: auto; /* Allow button to grow with content */
|
||||
margin-bottom: 6px;
|
||||
|
|
@ -305,17 +305,17 @@
|
|||
}
|
||||
|
||||
/* MCQ Choice Correct/Incorrect Styling - fixed dimensions with modern colors */
|
||||
.mcq-choice-correct {
|
||||
background-color: var(--sf-success-light) !important; /* Modern green with lower opacity */
|
||||
border-color: var(--sf-success) !important; /* Modern green */
|
||||
color: var(--text-normal) !important;
|
||||
box-shadow: 0 2px 8px rgba(56, 161, 105, 0.1) !important;
|
||||
.mcq-choice-correct.mcq-choice-correct {
|
||||
background-color: var(--sf-success-light); /* Modern green with lower opacity */
|
||||
border-color: var(--sf-success); /* Modern green */
|
||||
color: var(--text-normal);
|
||||
box-shadow: 0 2px 8px rgba(56, 161, 105, 0.1);
|
||||
animation: correctPulse 0.5s ease-out;
|
||||
transform: none !important; /* Prevent layout shifts */
|
||||
transform: none; /* Prevent layout shifts */
|
||||
}
|
||||
|
||||
.mcq-choice-correct::before {
|
||||
background-color: var(--sf-success) !important; /* Modern green */
|
||||
.mcq-choice-correct.mcq-choice-correct::before {
|
||||
background-color: var(--sf-success); /* Modern green */
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
|
|
@ -323,16 +323,16 @@
|
|||
color: var(--sf-success); /* Modern green */
|
||||
}
|
||||
|
||||
.mcq-choice-incorrect {
|
||||
background-color: var(--sf-danger-light) !important; /* Modern red with lower opacity */
|
||||
border-color: var(--sf-danger) !important; /* Modern red */
|
||||
color: var(--text-normal) !important;
|
||||
.mcq-choice-incorrect.mcq-choice-incorrect {
|
||||
background-color: var(--sf-danger-light); /* Modern red with lower opacity */
|
||||
border-color: var(--sf-danger); /* Modern red */
|
||||
color: var(--text-normal);
|
||||
animation: none; /* Remove the shake animation to prevent layout shifts */
|
||||
box-shadow: 0 2px 8px rgba(229, 62, 62, 0.1) !important;
|
||||
box-shadow: 0 2px 8px rgba(229, 62, 62, 0.1);
|
||||
}
|
||||
|
||||
.mcq-choice-incorrect::before {
|
||||
background-color: var(--sf-danger) !important; /* Modern red */
|
||||
.mcq-choice-incorrect.mcq-choice-incorrect::before {
|
||||
background-color: var(--sf-danger); /* Modern red */
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
|
|
@ -681,10 +681,10 @@
|
|||
color: white;
|
||||
}
|
||||
|
||||
.batch-review-cancel-button {
|
||||
.batch-review-cancel-button.batch-review-cancel-button {
|
||||
background-color: var(--sf-bg-secondary);
|
||||
color: var(--sf-text);
|
||||
border: 1px solid var(--background-modifier-border) !important;
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
}
|
||||
|
||||
.batch-review-progress {
|
||||
|
|
@ -748,15 +748,106 @@
|
|||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.batch-review-complete-blackout,
|
||||
.batch-review-incorrect,
|
||||
.batch-review-incorrect-familiar,
|
||||
.batch-review-correct-difficulty,
|
||||
.batch-review-correct-hesitation,
|
||||
.batch-review-perfect-recall,
|
||||
.batch-review-hard,
|
||||
.batch-review-fair,
|
||||
.batch-review-good,
|
||||
.batch-review-complete-blackout {
|
||||
padding: 5px 10px;
|
||||
border-radius: 100px;
|
||||
min-width: 100px;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
background-color: var(--sf-danger-light);
|
||||
color: var(--sf-danger);
|
||||
}
|
||||
|
||||
.batch-review-incorrect {
|
||||
padding: 5px 10px;
|
||||
border-radius: 100px;
|
||||
min-width: 100px;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
background-color: var(--sf-danger-light);
|
||||
color: var(--sf-danger);
|
||||
}
|
||||
|
||||
.batch-review-incorrect-familiar {
|
||||
padding: 5px 10px;
|
||||
border-radius: 100px;
|
||||
min-width: 100px;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
background-color: var(--sf-danger-light);
|
||||
color: var(--sf-danger);
|
||||
}
|
||||
|
||||
.batch-review-correct-difficulty {
|
||||
padding: 5px 10px;
|
||||
border-radius: 100px;
|
||||
min-width: 100px;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
background-color: var(--sf-success-light);
|
||||
color: var(--sf-success);
|
||||
}
|
||||
|
||||
.batch-review-correct-hesitation {
|
||||
padding: 5px 10px;
|
||||
border-radius: 100px;
|
||||
min-width: 100px;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
background-color: var(--sf-success-light);
|
||||
color: var(--sf-success);
|
||||
}
|
||||
|
||||
.batch-review-perfect-recall {
|
||||
padding: 5px 10px;
|
||||
border-radius: 100px;
|
||||
min-width: 100px;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
background-color: var(--sf-success-light);
|
||||
color: var(--sf-success);
|
||||
}
|
||||
|
||||
/* Legacy result styles */
|
||||
.batch-review-hard {
|
||||
padding: 5px 10px;
|
||||
border-radius: 100px;
|
||||
min-width: 100px;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
background-color: var(--sf-danger-light);
|
||||
color: var(--sf-danger);
|
||||
}
|
||||
|
||||
.batch-review-fair {
|
||||
padding: 5px 10px;
|
||||
border-radius: 100px;
|
||||
min-width: 100px;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
background-color: var(--sf-warning-light);
|
||||
color: var(--sf-warning);
|
||||
}
|
||||
|
||||
.batch-review-good {
|
||||
padding: 5px 10px;
|
||||
border-radius: 100px;
|
||||
min-width: 100px;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
background-color: var(--sf-success-light);
|
||||
color: var(--sf-success);
|
||||
}
|
||||
|
||||
.batch-review-perfect {
|
||||
padding: 5px 10px;
|
||||
border-radius: 100px;
|
||||
|
|
@ -764,55 +855,6 @@
|
|||
text-align: center;
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.batch-review-complete-blackout {
|
||||
background-color: var(--sf-danger-light);
|
||||
color: var(--sf-danger);
|
||||
}
|
||||
|
||||
.batch-review-incorrect {
|
||||
background-color: var(--sf-danger-light);
|
||||
color: var(--sf-danger);
|
||||
}
|
||||
|
||||
.batch-review-incorrect-familiar {
|
||||
background-color: var(--sf-danger-light); /* Using light danger variable */
|
||||
color: var(--sf-danger); /* Using danger variable */
|
||||
}
|
||||
|
||||
.batch-review-correct-hesitation {
|
||||
background-color: var(--sf-success-light); /* Using light success variable */
|
||||
color: var(--sf-success); /* Using success variable */
|
||||
}
|
||||
|
||||
/* Legacy result styles */
|
||||
.batch-review-hard {
|
||||
background-color: var(--sf-danger-light);
|
||||
color: var(--sf-danger);
|
||||
}
|
||||
|
||||
.batch-review-fair {
|
||||
background-color: var(--sf-warning-light);
|
||||
color: var(--sf-warning);
|
||||
}
|
||||
|
||||
.batch-review-good {
|
||||
background-color: var(--sf-success-light); /* Using light success variable */
|
||||
color: var(--sf-success); /* Using success variable */
|
||||
}
|
||||
|
||||
.batch-review-perfect {
|
||||
background-color: var(--sf-success-light);
|
||||
color: var(--sf-success);
|
||||
}
|
||||
|
||||
.batch-review-good {
|
||||
background-color: var(--sf-success-light);
|
||||
color: var(--sf-success);
|
||||
}
|
||||
|
||||
.batch-review-perfect {
|
||||
background-color: var(--sf-success-light);
|
||||
color: var(--sf-success);
|
||||
}
|
||||
|
|
@ -837,12 +879,6 @@
|
|||
}
|
||||
|
||||
/* Additional MCQ enhancements */
|
||||
.mcq-choice-btn {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.mcq-choice-btn::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
|
|
@ -888,19 +924,19 @@
|
|||
}
|
||||
|
||||
/* Fix for letter indicators in MCQ UI */
|
||||
[class*=" or "], [class^="or "] {
|
||||
background-color: var(--sf-bg-primary) !important;
|
||||
color: var(--sf-text) !important;
|
||||
border: 1px solid var(--background-modifier-border) !important;
|
||||
padding: 2px 6px !important;
|
||||
border-radius: 4px !important;
|
||||
[class*=" or "][class*=" or "], [class^="or "][class^="or "] {
|
||||
background-color: var(--sf-bg-primary);
|
||||
color: var(--sf-text);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Preserve legacy class function while improving appearance */
|
||||
|
||||
/* Style for the selected answer using keyboard navigation */
|
||||
.mcq-choice-selected {
|
||||
border-color: var(--interactive-accent) !important;
|
||||
.mcq-choice-selected.mcq-choice-selected {
|
||||
border-color: var(--interactive-accent);
|
||||
box-shadow: 0 0 0 2px rgba(var(--interactive-accent-rgb), 0.5), 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
transform: translateX(4px); /* Consistent with hover */
|
||||
}
|
||||
|
|
@ -912,16 +948,14 @@
|
|||
}
|
||||
|
||||
/* Dark theme specific overrides for text visibility - MORE SPECIFIC */
|
||||
.theme-dark .spaceforge-mcq-modal .mcq-choice-btn {
|
||||
color: var(--text-normal) !important; /* Attempt with !important if necessary */
|
||||
.theme-dark .spaceforge-mcq-modal .mcq-choice-btn.mcq-choice-btn {
|
||||
color: var(--text-normal);
|
||||
}
|
||||
.theme-dark .spaceforge-mcq-modal .mcq-choice-btn .mcq-choice-text {
|
||||
color: var(--text-normal) !important; /* Attempt with !important if necessary */
|
||||
.theme-dark .spaceforge-mcq-modal .mcq-choice-btn .mcq-choice-text.mcq-choice-text {
|
||||
color: var(--text-normal);
|
||||
}
|
||||
.theme-dark .spaceforge-mcq-modal .mcq-choice-btn .mcq-shortcut-hint {
|
||||
color: var(--text-muted) !important; /* Attempt with !important if necessary */
|
||||
/* Consider a lighter background for the hint if still problematic */
|
||||
/* background-color: var(--background-modifier-hover) !important; */
|
||||
.theme-dark .spaceforge-mcq-modal .mcq-choice-btn .mcq-shortcut-hint.mcq-shortcut-hint {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Styles for Algorithm Details in Results */
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
/* Pomodoro Styles */
|
||||
.pomodoro-visibility-toggle-container {
|
||||
display: none !important; /* Hide the old toggle button */
|
||||
.pomodoro-visibility-toggle-container.pomodoro-visibility-toggle-container {
|
||||
display: none; /* Hide the old toggle button */
|
||||
margin-bottom: 8px; /* Space between toggle and timer content */
|
||||
}
|
||||
|
||||
.sidebar-pomodoro-section {
|
||||
|
|
@ -25,10 +26,6 @@
|
|||
box-sizing: border-box; /* Ensure padding and border are within width */
|
||||
}
|
||||
|
||||
.pomodoro-visibility-toggle-container {
|
||||
margin-bottom: 8px; /* Space between toggle and timer content */
|
||||
}
|
||||
|
||||
.pomodoro-visibility-toggle-container .pomodoro-visibility-toggle-btn {
|
||||
width: 100%;
|
||||
padding: 6px 12px;
|
||||
|
|
|
|||
|
|
@ -158,9 +158,9 @@
|
|||
.review-note-button:disabled, /* General class for individual note buttons */
|
||||
.review-bulk-button:disabled, /* General class for bulk action buttons */
|
||||
.review-date-action-button:disabled, /* General class for date section buttons */
|
||||
button.disabled { /* Fallback for any button with .disabled class */
|
||||
button.disabled.disabled { /* Fallback for any button with .disabled class */
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed !important; /* Ensure cursor changes */
|
||||
cursor: not-allowed; /* Ensure cursor changes */
|
||||
filter: grayscale(60%);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -164,9 +164,9 @@
|
|||
}
|
||||
|
||||
/* Enhance setting items */
|
||||
.setting-item {
|
||||
.setting-item.setting-item {
|
||||
padding: var(--sf-space-sm) 0;
|
||||
border-top: none !important;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.setting-item-info {
|
||||
|
|
|
|||
|
|
@ -11,21 +11,38 @@
|
|||
|
||||
/* Upcoming Reviews Section */
|
||||
.review-upcoming-section {
|
||||
margin-top: 15px;
|
||||
margin-top: var(--sf-space-lg);
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid var(--background-modifier-border);
|
||||
}
|
||||
|
||||
.review-upcoming-section h3 {
|
||||
font-size: 16px;
|
||||
margin-bottom: var(--sf-space-sm);
|
||||
color: var(--sf-text);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.review-upcoming-list {
|
||||
margin-top: var(--sf-space-xs);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px; /* Space between day items */
|
||||
gap: var(--sf-space-xs);
|
||||
}
|
||||
|
||||
.review-upcoming-day {
|
||||
padding: 5px 8px;
|
||||
border-radius: var(--radius-s);
|
||||
transition: background-color 0.2s ease;
|
||||
padding: var(--sf-space-xs) var(--sf-space-sm);
|
||||
border-radius: var(--sf-radius-sm);
|
||||
background-color: var(--sf-bg-secondary);
|
||||
transition: var(--sf-transition);
|
||||
border: 1px solid transparent;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.review-upcoming-day:hover {
|
||||
border-color: var(--interactive-accent);
|
||||
transform: translateX(3px);
|
||||
}
|
||||
|
||||
.review-upcoming-day.clickable {
|
||||
|
|
@ -48,8 +65,11 @@
|
|||
}
|
||||
|
||||
.review-upcoming-day-count {
|
||||
font-size: 0.9em;
|
||||
color: var(--text-muted);
|
||||
color: var(--sf-text-muted);
|
||||
font-size: 12px;
|
||||
background-color: var(--sf-bg-primary);
|
||||
padding: 1px 6px;
|
||||
border-radius: 100px;
|
||||
}
|
||||
|
||||
/* Style for expanded day */
|
||||
|
|
@ -136,8 +156,8 @@
|
|||
background-color: var(--text-accent); /* Using theme variable */
|
||||
}
|
||||
|
||||
.review-stats-custom-order {
|
||||
color: var(--interactive-accent) !important; /* Using theme variable */
|
||||
.review-stats-custom-order.review-stats-custom-order {
|
||||
color: var(--interactive-accent); /* Using theme variable */
|
||||
font-style: italic;
|
||||
font-size: 0.85em;
|
||||
display: flex;
|
||||
|
|
@ -159,16 +179,16 @@
|
|||
}
|
||||
|
||||
/* Force ALL direct children to have zero margins to let gap control spacing */
|
||||
.review-buttons-container > * {
|
||||
margin: 0 !important; /* Override any potential conflicting margins */
|
||||
.review-buttons-container.review-buttons-container > * {
|
||||
margin: 0; /* Override any potential conflicting margins */
|
||||
}
|
||||
|
||||
|
||||
.review-all-button,
|
||||
.review-all-mcq-button {
|
||||
.review-all-button.review-all-button,
|
||||
.review-all-mcq-button.review-all-mcq-button {
|
||||
width: 100%;
|
||||
padding: var(--sf-space-sm) var(--sf-space-md);
|
||||
margin: 0 !important; /* Ensure no margin */
|
||||
margin: 0; /* Ensure no margin */
|
||||
border-radius: var(--sf-radius-md);
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
|
|
@ -662,63 +682,18 @@
|
|||
box-shadow: var(--sf-shadow-lg); /* Using defined shadow variable */
|
||||
}
|
||||
|
||||
.review-upcoming-section {
|
||||
margin-top: var(--sf-space-lg);
|
||||
}
|
||||
|
||||
.review-upcoming-section h3 {
|
||||
font-size: 16px;
|
||||
margin-bottom: var(--sf-space-sm);
|
||||
color: var(--sf-text);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.review-upcoming-list {
|
||||
margin-top: var(--sf-space-xs);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sf-space-xs);
|
||||
}
|
||||
|
||||
.review-upcoming-day {
|
||||
padding: var(--sf-space-xs) var(--sf-space-sm);
|
||||
border-radius: var(--sf-radius-sm);
|
||||
background-color: var(--sf-bg-secondary);
|
||||
transition: var(--sf-transition);
|
||||
border: 1px solid transparent;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.review-upcoming-day:hover {
|
||||
border-color: var(--interactive-accent); /* Using theme variable */
|
||||
transform: translateX(3px);
|
||||
}
|
||||
|
||||
.review-upcoming-day-name {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.review-upcoming-day-count {
|
||||
color: var(--sf-text-muted);
|
||||
font-size: 12px;
|
||||
background-color: var(--sf-bg-primary);
|
||||
padding: 1px 6px;
|
||||
border-radius: 100px;
|
||||
}
|
||||
|
||||
|
||||
/* --- Pomodoro Timer Styles --- */
|
||||
/* Styles for Pomodoro section when inside review-buttons-container */
|
||||
.review-buttons-container .pomodoro-toggle-container {
|
||||
width: 100%; /* Make toggle container take full width */
|
||||
margin: 0 !important; /* Ensure no margin */
|
||||
margin: 0; /* Ensure no margin */
|
||||
}
|
||||
|
||||
.review-buttons-container .pomodoro-visibility-toggle-btn {
|
||||
width: 100%;
|
||||
padding: calc(var(--sf-space-xs) / 1.5) var(--sf-space-sm); /* Reduced vertical padding */
|
||||
margin: 0 !important; /* Ensure no margin */
|
||||
margin: 0; /* Ensure no margin */
|
||||
border-radius: var(--sf-radius-md);
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
|
|
@ -735,13 +710,13 @@
|
|||
|
||||
.review-buttons-container .pomodoro-section-content {
|
||||
width: 100%; /* Make content take full width */
|
||||
margin: 0 !important; /* Ensure no margin */
|
||||
margin: 0; /* Ensure no margin */
|
||||
}
|
||||
|
||||
.pomodoro-container {
|
||||
.pomodoro-container.pomodoro-container {
|
||||
/* margin-top is removed as it's handled by pomodoro-toggle-container or pomodoro-section-content */
|
||||
padding: 2px var(--sf-space-xs); /* Minimal vertical padding, keep horizontal */
|
||||
margin: 0 !important; /* Ensure no margin */
|
||||
margin: 0; /* Ensure no margin */
|
||||
background-color: var(--background-secondary); /* Slightly different background */
|
||||
border-radius: var(--radius-m);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
|
|
@ -769,7 +744,8 @@
|
|||
text-align: center;
|
||||
order: 2; /* Order for flex: Start(1) Timer(2) Skip(3) Cog(4) */
|
||||
flex-grow: 1; /* Allow timer to take some space */
|
||||
border: none; /* Base border is none */
|
||||
border-left: 3px solid transparent;
|
||||
border-right: 3px solid transparent;
|
||||
transition: border-color 0.3s ease; /* Transition border color */
|
||||
}
|
||||
|
||||
|
|
@ -781,16 +757,8 @@
|
|||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
/* .timer-hidden is no longer used to hide, opacity is handled by .is-paused or .is-idle */
|
||||
|
||||
|
||||
/* Mode-specific styling (using border-left and border-right) */
|
||||
.pomodoro-timer-display {
|
||||
border-left: 3px solid transparent; /* Default transparent border */
|
||||
border-right: 3px solid transparent; /* Default transparent border */
|
||||
background-color: var(--background-primary); /* Reset background */
|
||||
color: var(--text-normal); /* Reset text color */
|
||||
}
|
||||
.pomodoro-timer-display.mode-work {
|
||||
border-left-color: var(--interactive-accent);
|
||||
border-right-color: var(--interactive-accent);
|
||||
|
|
|
|||
|
|
@ -335,7 +335,7 @@ export class BatchReviewModal extends Modal {
|
|||
const resultItemEl = resultsListEl.createDiv("batch-review-result-item");
|
||||
const file = this.plugin.app.vault.getAbstractFileByPath(result.path);
|
||||
const fileName = file instanceof TFile ? file.basename : result.path;
|
||||
resultItemEl.createEl("div", { text: fileName, cls: "batch-review-result-filename" });
|
||||
resultItemEl.createDiv({ text: fileName, cls: "batch-review-result-filename" });
|
||||
let responseText: string; let responseClass: string;
|
||||
switch (result.response) {
|
||||
case ReviewResponse.CompleteBlackout: responseText = "Complete Blackout (0)"; responseClass = "batch-review-complete-blackout"; break;
|
||||
|
|
@ -346,9 +346,9 @@ export class BatchReviewModal extends Modal {
|
|||
case ReviewResponse.PerfectRecall: responseText = "Perfect Recall (5)"; responseClass = "batch-review-perfect-recall"; break;
|
||||
default: responseText = "Unknown"; responseClass = "";
|
||||
}
|
||||
resultItemEl.createEl("div", { text: responseText, cls: `batch-review-result-response ${responseClass}` });
|
||||
resultItemEl.createDiv({ text: responseText, cls: `batch-review-result-response ${responseClass}` });
|
||||
if (result.score !== undefined) {
|
||||
resultItemEl.createEl("div", { text: `MCQ Score: ${Math.round(result.score * 100)}%`, cls: "batch-review-result-mcq-score" });
|
||||
resultItemEl.createDiv({ text: `MCQ Score: ${Math.round(result.score * 100)}%`, cls: "batch-review-result-mcq-score" });
|
||||
}
|
||||
}
|
||||
const closeButton = contentEl.createEl("button", { text: "Close", cls: "batch-review-close-button" });
|
||||
|
|
|
|||
|
|
@ -250,7 +250,7 @@ export class CalendarView {
|
|||
|
||||
const { year, month, firstDay, daysInMonth } = this.getCalendarData();
|
||||
const totalCells = 42; // Standard 6 weeks * 7 days grid
|
||||
let dayCells = Array.from(gridEl.querySelectorAll(".calendar-day")) as HTMLElement[];
|
||||
let dayCells = Array.from(gridEl.querySelectorAll<HTMLElement>(".calendar-day"));
|
||||
|
||||
// Adjust number of day cell elements if necessary
|
||||
if (dayCells.length < totalCells) {
|
||||
|
|
@ -571,7 +571,7 @@ export class CalendarView {
|
|||
this.hideEventTooltip();
|
||||
|
||||
// Create tooltip element
|
||||
this.tooltipEl = document.body.createEl("div");
|
||||
this.tooltipEl = document.body.createDiv();
|
||||
this.tooltipEl.className = "calendar-event-tooltip";
|
||||
|
||||
// Format event details
|
||||
|
|
|
|||
|
|
@ -568,13 +568,13 @@ export class ConsolidatedMCQModal extends Modal {
|
|||
|
||||
const noteScoreEl = noteScoresEl.createDiv('mcq-note-score');
|
||||
|
||||
noteScoreEl.createEl('div', {
|
||||
noteScoreEl.createDiv({
|
||||
text: noteScore.fileName,
|
||||
cls: 'mcq-note-score-title'
|
||||
});
|
||||
|
||||
const scorePercent = Math.round(noteScore.score * 100);
|
||||
const scoreTextValueEl = noteScoreEl.createEl('div', {
|
||||
const scoreTextValueEl = noteScoreEl.createDiv({
|
||||
text: `Score: ${scorePercent}% (${noteScore.correctAnswers}/${noteScore.totalQuestions})`,
|
||||
cls: 'mcq-note-score-value'
|
||||
});
|
||||
|
|
|
|||
|
|
@ -26,7 +26,9 @@ export class ContextMenuHandler {
|
|||
register(): void {
|
||||
// Register for the 'file-menu' event, which handles both files and folders.
|
||||
this.plugin.registerEvent(
|
||||
this.plugin.app.workspace.on("file-menu", this.handleFileMenuEvent.bind(this))
|
||||
this.plugin.app.workspace.on("file-menu", (menu: Menu, abstractFile: TAbstractFile) => {
|
||||
this.handleFileMenuEvent(menu, abstractFile);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ export class EventModal extends Modal {
|
|||
this.isAllDayToggle = allDayGroup.createEl("input", {
|
||||
type: "checkbox",
|
||||
cls: "event-modal-checkbox"
|
||||
}) as HTMLInputElement;
|
||||
});
|
||||
this.isAllDayToggle.checked = this.event?.isAllDay ?? false;
|
||||
this.isAllDayToggle.addEventListener("change", () => this.toggleTimeInput());
|
||||
|
||||
|
|
@ -147,7 +147,7 @@ export class EventModal extends Modal {
|
|||
this.colorInput = colorPickerContainer.createEl("input", {
|
||||
type: "color",
|
||||
cls: "event-modal-color-input"
|
||||
}) as HTMLInputElement;
|
||||
});
|
||||
|
||||
if (this.event?.color) {
|
||||
this.colorInput.value = this.event.color;
|
||||
|
|
|
|||
|
|
@ -177,9 +177,9 @@ export class MCQModal extends Modal {
|
|||
|
||||
// Cleanup handled by Modal's onClose automatically if using this.scope.register
|
||||
// Original onClose logic for saving partial progress:
|
||||
const originalOnClose = this.onClose;
|
||||
const originalOnClose = this.onClose.bind(this);
|
||||
this.onClose = () => {
|
||||
originalOnClose.call(this);
|
||||
originalOnClose();
|
||||
if (!this.session.completed && this.session.answers.length > 0) {
|
||||
// Session was closed prematurely but some answers were given
|
||||
this.session.completedAt = Date.now();
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ export class ReviewModal extends Modal {
|
|||
createSm2Button("5: Perfect recall", "review-button review-button-perfect-recall", ReviewResponse.PerfectRecall);
|
||||
}
|
||||
|
||||
buttonsContainer.createEl("div", { cls: "review-button-separator" });
|
||||
buttonsContainer.createDiv({ cls: "review-button-separator" });
|
||||
|
||||
// Postpone Button
|
||||
const postponeButton = buttonsContainer.createEl("button", { text: "Postpone to tomorrow", cls: "review-button review-button-postpone" });
|
||||
|
|
@ -75,7 +75,7 @@ export class ReviewModal extends Modal {
|
|||
|
||||
// MCQ Buttons (if enabled)
|
||||
if (this.plugin.settings.enableMCQ) {
|
||||
buttonsContainer.createEl("div", { cls: "review-button-separator" });
|
||||
buttonsContainer.createDiv({ cls: "review-button-separator" });
|
||||
|
||||
|
||||
const mcqButton = buttonsContainer.createEl("button", { cls: "review-button review-button-mcq" });
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { App, Notice, PluginSettingTab, Setting, setIcon } from 'obsidian';
|
||||
import { ConfirmationModal } from './confirmation-modal';
|
||||
import SpaceforgePlugin from '../main';
|
||||
import { ApiProvider, DEFAULT_SETTINGS, MCQQuestionAmountMode, MCQDifficulty } from '../models/settings';
|
||||
import { SpaceforgePluginData, DEFAULT_PLUGIN_STATE_DATA } from '../models/plugin-data';
|
||||
import { ApiProvider, DEFAULT_SETTINGS, MCQQuestionAmountMode, MCQDifficulty, SpaceforgeSettings } from '../models/settings';
|
||||
import { SpaceforgePluginData, DEFAULT_PLUGIN_STATE_DATA, PluginStateData } from '../models/plugin-data';
|
||||
import { SM2, FSRS, POMODORO, MCQS, API, SPACEFORGE, CLAUDE, GEMINI, OLLAMA, TOGETHER_AI, WPM } from './constants';
|
||||
|
||||
/**
|
||||
|
|
@ -37,7 +37,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
// Add a utility for creating collapsible sections
|
||||
const createCollapsible = (title: string, iconName: string, defaultOpen = true) => {
|
||||
// Create section container
|
||||
const sectionContainer = containerEl.createEl('div', { cls: 'sf-settings-section' });
|
||||
const sectionContainer = containerEl.createDiv({ cls: 'sf-settings-section' });
|
||||
|
||||
// Create heading using Setting.setHeading()
|
||||
const headingSetting = new Setting(sectionContainer)
|
||||
|
|
@ -46,21 +46,21 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
|
||||
// Add icon if provided
|
||||
if (iconName) {
|
||||
const iconEl = headingSetting.settingEl.createEl('span', { cls: 'sf-settings-icon' });
|
||||
const iconEl = headingSetting.settingEl.createSpan({ cls: 'sf-settings-icon' });
|
||||
setIcon(iconEl, iconName);
|
||||
// Insert icon before the name
|
||||
headingSetting.nameEl.prepend(iconEl);
|
||||
}
|
||||
|
||||
// Add collapse indicator
|
||||
const collapseIndicator = headingSetting.settingEl.createEl('span', {
|
||||
const collapseIndicator = headingSetting.settingEl.createSpan({
|
||||
cls: 'sf-settings-collapse-indicator',
|
||||
text: defaultOpen ? '▾' : '▸'
|
||||
});
|
||||
headingSetting.nameEl.appendChild(collapseIndicator);
|
||||
|
||||
// Create content container
|
||||
const contentContainer = sectionContainer.createEl('div', {
|
||||
const contentContainer = sectionContainer.createDiv({
|
||||
cls: 'sf-settings-section-content'
|
||||
});
|
||||
|
||||
|
|
@ -84,7 +84,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
|
||||
// Create action buttons for global data operations
|
||||
const createActionButtons = () => {
|
||||
const actionsContainer = containerEl.createEl('div', { cls: 'sf-settings-actions' });
|
||||
const actionsContainer = containerEl.createDiv({ cls: 'sf-settings-actions' });
|
||||
|
||||
// Export all data button
|
||||
const exportBtn = actionsContainer.createEl('button', { text: 'Export all data', cls: 'sf-btn sf-btn-primary' });
|
||||
|
|
@ -178,7 +178,8 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
|
||||
new Notice('All data imported successfully. Plugin may require a reload for all changes to take effect.');
|
||||
} catch (error) {
|
||||
new Notice(`Failed to import data: ${error.message} `);
|
||||
const msg = error instanceof Error ? error.message : String(error);
|
||||
new Notice(`Failed to import data: ${msg} `);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -194,8 +195,8 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
'Are you sure you want to reset all plugin data (settings and state) to defaults? This cannot be undone.',
|
||||
async () => {
|
||||
// Apply default settings and default plugin state
|
||||
this.plugin.settings = JSON.parse(JSON.stringify(DEFAULT_SETTINGS));
|
||||
this.plugin.pluginState = JSON.parse(JSON.stringify(DEFAULT_PLUGIN_STATE_DATA));
|
||||
this.plugin.settings = JSON.parse(JSON.stringify(DEFAULT_SETTINGS)) as SpaceforgeSettings;
|
||||
this.plugin.pluginState = JSON.parse(JSON.stringify(DEFAULT_PLUGIN_STATE_DATA)) as PluginStateData;
|
||||
|
||||
// Repopulate services with defaults
|
||||
this.plugin.reviewScheduleService.schedules = this.plugin.pluginState.schedules;
|
||||
|
|
@ -321,7 +322,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
async () => {
|
||||
try {
|
||||
// Reset all plugin state to defaults
|
||||
this.plugin.pluginState = JSON.parse(JSON.stringify(DEFAULT_PLUGIN_STATE_DATA));
|
||||
this.plugin.pluginState = JSON.parse(JSON.stringify(DEFAULT_PLUGIN_STATE_DATA)) as PluginStateData;
|
||||
|
||||
// Update all services
|
||||
this.plugin.reviewScheduleService.schedules = this.plugin.pluginState.schedules;
|
||||
|
|
@ -377,7 +378,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
// Changed to h3 and removed sf-settings-subsection class for potentially better contrast
|
||||
new Setting(spacedRepSection).setName('Algorithm configuration').setHeading();
|
||||
|
||||
const algorithmContainer = spacedRepSection.createEl('div', { cls: 'sf-settings-subsection' }); // Define algorithmContainer
|
||||
const algorithmContainer = spacedRepSection.createDiv({ cls: 'sf-settings-subsection' }); // Define algorithmContainer
|
||||
|
||||
new Setting(algorithmContainer)
|
||||
.setName('Default scheduling algorithm')
|
||||
|
|
@ -393,7 +394,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
}));
|
||||
|
||||
// --- Dynamic "About Algorithm" Section ---
|
||||
const aboutAlgoContainer = spacedRepSection.createEl('div', { cls: 'sf-info-box sf-algo-about-box' });
|
||||
const aboutAlgoContainer = spacedRepSection.createDiv({ cls: 'sf-info-box sf-algo-about-box' });
|
||||
this.renderAboutAlgorithmSection(aboutAlgoContainer, this.plugin.settings.defaultSchedulingAlgorithm);
|
||||
|
||||
// --- SM-2 Parameters ---
|
||||
|
|
@ -685,10 +686,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
await this.plugin.savePluginData();
|
||||
}));
|
||||
|
||||
interfaceSection.createEl('div', {
|
||||
cls: 'sf-setting-explain',
|
||||
text: `Average adults read 200-250 ${WPM} for regular content, 100-150 ${WPM} for technical content`
|
||||
});
|
||||
interfaceSection.createDiv({ cls: 'sf-setting-explain', text: `Average adults read 200-250 ${WPM} for regular content, 100-150 ${WPM} for technical content` });
|
||||
|
||||
new Setting(interfaceSection)
|
||||
.setName("Navigation command")
|
||||
|
|
@ -819,7 +817,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
.setName("OpenRouter configuration")
|
||||
.setHeading()
|
||||
.setClass("sf-settings-subsection-provider-header");
|
||||
const apiKeyContainer = mcqSection.createEl('div', { cls: 'sf-setting-highlight' });
|
||||
const apiKeyContainer = mcqSection.createDiv({ cls: 'sf-setting-highlight' });
|
||||
new Setting(apiKeyContainer)
|
||||
.setName('API key')
|
||||
.setDesc('Required for generating MCQs via OpenRouter.')
|
||||
|
|
@ -831,7 +829,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
await this.plugin.savePluginData();
|
||||
}));
|
||||
// Removed sf-setting-explain class
|
||||
apiKeyContainer.createEl('div').setText('Get your API key at https://openrouter.ai/keys');
|
||||
apiKeyContainer.createDiv().setText('Get your API key at https://openrouter.ai/keys');
|
||||
|
||||
new Setting(mcqSection)
|
||||
.setName('Model')
|
||||
|
|
@ -1050,10 +1048,10 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
}));
|
||||
|
||||
// Use CSS grid for the two dropdowns side by side
|
||||
const mcqFormattingGrid = mcqSection.createEl('div', { cls: 'sf-setting-grid' });
|
||||
const mcqFormattingGrid = mcqSection.createDiv({ cls: 'sf-setting-grid' });
|
||||
|
||||
// First item in grid
|
||||
const promptTypeContainer = mcqFormattingGrid.createEl('div');
|
||||
const promptTypeContainer = mcqFormattingGrid.createDiv();
|
||||
new Setting(promptTypeContainer)
|
||||
.setName('Prompt type')
|
||||
.setDesc('Format for question generation')
|
||||
|
|
@ -1067,7 +1065,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
}));
|
||||
|
||||
// Second item in grid
|
||||
const difficultyContainer = mcqFormattingGrid.createEl('div');
|
||||
const difficultyContainer = mcqFormattingGrid.createDiv();
|
||||
new Setting(difficultyContainer)
|
||||
.setName('Difficulty')
|
||||
.setDesc('Complexity level')
|
||||
|
|
@ -1124,7 +1122,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
systemPromptsContainer.createEl('summary', { text: 'System prompts (advanced)', cls: 'sf-settings-subsection' });
|
||||
|
||||
// Basic prompt textarea
|
||||
systemPromptsContainer.createEl('div', { text: 'Basic difficulty prompt', cls: 'sf-prompt-label' });
|
||||
systemPromptsContainer.createDiv({ text: 'Basic difficulty prompt', cls: 'sf-prompt-label' });
|
||||
const basicTextarea = systemPromptsContainer.createEl('textarea', {
|
||||
attr: {
|
||||
placeholder: 'Enter system prompt for basic difficulty',
|
||||
|
|
@ -1141,7 +1139,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
});
|
||||
|
||||
// Advanced prompt textarea
|
||||
systemPromptsContainer.createEl('div', { text: 'Advanced difficulty prompt', cls: 'sf-prompt-label' });
|
||||
systemPromptsContainer.createDiv({ text: 'Advanced difficulty prompt', cls: 'sf-prompt-label' });
|
||||
const advancedTextarea = systemPromptsContainer.createEl('textarea', {
|
||||
attr: {
|
||||
placeholder: 'Enter system prompt for advanced difficulty',
|
||||
|
|
@ -1203,7 +1201,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
|
||||
} else {
|
||||
// If MCQ is disabled, show a message about enabling it
|
||||
const mcqDisabledMessage = mcqSection.createEl('div', { cls: 'sf-info-box' });
|
||||
const mcqDisabledMessage = mcqSection.createDiv({ cls: 'sf-info-box' });
|
||||
mcqDisabledMessage.createEl('p', {
|
||||
text: `${MCQS} are currently disabled. Enable it to configure durations and notifications.`
|
||||
});
|
||||
|
|
@ -1308,7 +1306,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
await this.plugin.savePluginData();
|
||||
}));
|
||||
} else {
|
||||
const pomodoroDisabledMessage = pomodoroSection.createEl('div', { cls: 'sf-info-box' });
|
||||
const pomodoroDisabledMessage = pomodoroSection.createDiv({ cls: 'sf-info-box' });
|
||||
pomodoroDisabledMessage.createEl('p', {
|
||||
text: `${POMODORO} timer is currently disabled. Enable it to configure durations and notifications.`
|
||||
});
|
||||
|
|
@ -1369,7 +1367,8 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
|
|||
new Notice('Legacy data file deleted successfully.', 3000);
|
||||
this.display(); // Refresh settings
|
||||
} catch (error) {
|
||||
new Notice('Failed to delete legacy file: ' + error.message, 5000);
|
||||
const msg = error instanceof Error ? error.message : String(error);
|
||||
new Notice('Failed to delete legacy file: ' + msg, 5000);
|
||||
}
|
||||
}
|
||||
).open();
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ export class ReviewSidebarView extends ItemView {
|
|||
getIcon(): string { return "calendar-clock"; }
|
||||
|
||||
async onOpen(): Promise<void> {
|
||||
this.plugin.events.on('sidebar-update', this.refresh.bind(this));
|
||||
this.plugin.events.on('sidebar-update', () => { void this.refresh(); });
|
||||
this.plugin.events.on('pomodoro-update', () => {
|
||||
if (this.pomodoroUIManager) {
|
||||
this.pomodoroUIManager.updatePomodoroUI();
|
||||
|
|
@ -71,7 +71,7 @@ export class ReviewSidebarView extends ItemView {
|
|||
if (this.resizeObserver) {
|
||||
this.resizeObserver.disconnect();
|
||||
}
|
||||
this.plugin.events.off('sidebar-update', this.refresh.bind(this));
|
||||
this.plugin.events.off('sidebar-update', () => { void this.refresh(); });
|
||||
// No need to explicitly unregister pomodoro-update as the manager handles its own logic
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
|
@ -131,7 +131,7 @@ export class ReviewSidebarView extends ItemView {
|
|||
setExpandedUpcomingDayKey: (key) => { this.expandedUpcomingDayKey = key; },
|
||||
getLastSelectedNotePath: () => this.lastSelectedNotePath,
|
||||
setLastSelectedNotePath: (path) => { this.lastSelectedNotePath = path; },
|
||||
refreshSidebarView: this.refresh.bind(this)
|
||||
refreshSidebarView: async () => { await this.refresh(); }
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -551,7 +551,7 @@ export class ListViewRenderer {
|
|||
dayItemEl.addClass("clickable");
|
||||
dayItemEl.dataset.dayKey = dayKey;
|
||||
const daySummary = dayItemEl.createDiv("review-upcoming-day-summary");
|
||||
daySummary.createEl("span", { cls: "review-upcoming-day-name" }); // Placeholder for name
|
||||
daySummary.createSpan({ cls: "review-upcoming-day-name" }); // Placeholder for name
|
||||
|
||||
dayItemEl.addEventListener("click", (e) => { // Attach listener once
|
||||
const currentDayKey = (e.currentTarget as HTMLElement).dataset.dayKey;
|
||||
|
|
@ -634,7 +634,7 @@ export class ListViewRenderer {
|
|||
this.getSelectedNotes(),
|
||||
lastSelectedNotePathRef,
|
||||
() => this.handleSelectionChange(parentContainerForBulkActions),
|
||||
this.handleNoteAction.bind(this)
|
||||
async () => { await this.handleNoteAction(); }
|
||||
);
|
||||
}
|
||||
if (noteEl) notesInOrder.push(noteEl);
|
||||
|
|
@ -680,7 +680,7 @@ export class ListViewRenderer {
|
|||
*/
|
||||
private updateSelectionClasses(container: HTMLElement): void {
|
||||
const selectedNotes = this.getSelectedNotes();
|
||||
const allNoteElements = container.querySelectorAll('.review-note-item[data-note-path]') as NodeListOf<HTMLElement>;
|
||||
const allNoteElements = container.querySelectorAll<HTMLElement>('.review-note-item[data-note-path]');
|
||||
allNoteElements.forEach(el => {
|
||||
const path = el.dataset.notePath;
|
||||
if (path && selectedNotes.includes(path)) {
|
||||
|
|
@ -778,7 +778,7 @@ export class ListViewRenderer {
|
|||
|
||||
// Find the drop target note item
|
||||
const target = e.target as HTMLElement;
|
||||
const noteItem = target.closest('.review-note-item') as HTMLElement | null;
|
||||
const noteItem = target.closest<HTMLElement>('.review-note-item');
|
||||
|
||||
if (!noteItem) return;
|
||||
|
||||
|
|
@ -966,7 +966,7 @@ export class ListViewRenderer {
|
|||
}
|
||||
headerTextEl.textContent = displayHeader;
|
||||
|
||||
let overdueBadge = headerTextEl.querySelector(".review-overdue-badge") as HTMLElement | null;
|
||||
let overdueBadge = headerTextEl.querySelector(".review-overdue-badge");
|
||||
if (dateStr === "Due notes") {
|
||||
const daysDiff = notesInSection.map(noteEl => {
|
||||
const path = (noteEl as HTMLElement).dataset.notePath;
|
||||
|
|
@ -1053,7 +1053,7 @@ export class ListViewRenderer {
|
|||
const aIsSpecial = a in dateOrder;
|
||||
const bIsSpecial = b in dateOrder;
|
||||
|
||||
if (aIsSpecial && bIsSpecial) return dateOrder[a as keyof typeof dateOrder] - dateOrder[b as keyof typeof dateOrder];
|
||||
if (aIsSpecial && bIsSpecial) return dateOrder[a] - dateOrder[b];
|
||||
if (aIsSpecial) return -1;
|
||||
if (bIsSpecial) return 1;
|
||||
|
||||
|
|
|
|||
|
|
@ -321,7 +321,7 @@ export class NoteItemRenderer {
|
|||
// Logic for selection (ctrl/meta/shift click)
|
||||
// This part uses selectedNotesArray and lastSelectedNotePathRef directly, which are passed in.
|
||||
// This is fine as they are managed by the calling ListViewRenderer.
|
||||
const allVisibleNoteElements = Array.from(parentContainerForBulkActions.querySelectorAll('.review-note-item[data-note-path]')) as HTMLElement[];
|
||||
const allVisibleNoteElements = Array.from(parentContainerForBulkActions.querySelectorAll<HTMLElement>('.review-note-item[data-note-path]'));
|
||||
const allVisibleNotePaths = allVisibleNoteElements.map(el => el.dataset.notePath).filter(p => p) as string[];
|
||||
const currentIndex = allVisibleNotePaths.indexOf(currentPath);
|
||||
|
||||
|
|
@ -335,7 +335,7 @@ export class NoteItemRenderer {
|
|||
notesToSelectInRange.forEach(p => { if (p && !selectedNotesArray.includes(p)) selectedNotesArray.push(p); });
|
||||
} else {
|
||||
selectedNotesArray.length = 0;
|
||||
selectedNotesArray.push(...notesToSelectInRange.filter(p => p) as string[]);
|
||||
selectedNotesArray.push(...notesToSelectInRange.filter(p => p));
|
||||
}
|
||||
} else {
|
||||
selectedNotesArray.length = 0;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*/
|
||||
export type EventMap = Record<string, unknown[]>;
|
||||
|
||||
export class EventEmitter<Events extends { [K in keyof Events]: any[] } = Record<string, unknown[]>> {
|
||||
export class EventEmitter<Events extends { [K in keyof Events]: unknown[] } = Record<string, unknown[]>> {
|
||||
/**
|
||||
* Event listeners by event name
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -3,5 +3,6 @@
|
|||
"1.0.1": "0.15.0",
|
||||
"1.0.2": "0.15.0",
|
||||
"1.0.3": "0.15.0",
|
||||
"1.0.5": "1.8.7",
|
||||
"1.0.4": "0.15.0"
|
||||
}
|
||||
Loading…
Reference in a new issue