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:
dralkh 2026-05-20 11:01:45 +03:00
parent 241ec16101
commit e80dfda20d
37 changed files with 390 additions and 572 deletions

View file

@ -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;
}
}

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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 {

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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",

View file

@ -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
View file

@ -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;
}
}

View file

@ -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",

View file

@ -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

View file

@ -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"
}
}

View file

@ -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 => {

View file

@ -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)}`);
}
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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 */

View file

@ -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;
}

View file

@ -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 */

View file

@ -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;

View file

@ -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;
}

View file

@ -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 {

View file

@ -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);

View file

@ -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" });

View file

@ -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

View file

@ -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'
});

View file

@ -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);
})
);
}

View file

@ -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;

View file

@ -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();

View file

@ -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" });

View file

@ -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();

View file

@ -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(); }
});
}

View file

@ -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;

View file

@ -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;

View file

@ -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
*/

View file

@ -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"
}