refactor: consolidate styles and improve UI components

- Split styles.css into modular component files
- Update UI components to use new modular CSS structure
- Remove deprecated main.js file
- Add sleep utility function
- Enhance calendar and modal components
- Update navigation controller and event handling
This commit is contained in:
dralkh 2025-10-13 10:45:31 +03:00
parent b85c65a841
commit 4cbc58b5fa
27 changed files with 193 additions and 15995 deletions

5
.gitignore vendored
View file

@ -3,3 +3,8 @@ node_modules/.bin/acorn
node_modules
.kilocode
opencode.json
# Compiled files
main.js
styles.css
docs

View file

@ -110,7 +110,7 @@ export class ReviewNavigationController implements IReviewNavigationController {
}
if (this.plugin.settings.enableNavigationCommands && this.plugin.settings.navigationCommand.key) {
setTimeout(() => this.executeCommand(), this.plugin.settings.navigationCommandDelay);
window.setTimeout(() => this.executeCommand(), this.plugin.settings.navigationCommandDelay);
}
}
@ -180,7 +180,7 @@ export class ReviewNavigationController implements IReviewNavigationController {
}
if (this.plugin.settings.enableNavigationCommands && this.plugin.settings.navigationCommand.key) {
setTimeout(() => this.executeCommand(), this.plugin.settings.navigationCommandDelay);
window.setTimeout(() => this.executeCommand(), this.plugin.settings.navigationCommandDelay);
}
}

View file

@ -151,7 +151,7 @@ export class DataStorage {
isValid = false;
continue;
}
const s = schedule as Record<string, any>;
const s = schedule as unknown as Record<string, unknown>;
if (!('path' in s) || typeof s.path !== 'string' ||
!('lastReviewDate' in s) || (s.lastReviewDate !== null && typeof s.lastReviewDate !== 'number') ||
!('nextReviewDate' in s) || typeof s.nextReviewDate !== 'number' ||

11778
main.js

File diff suppressed because it is too large Load diff

View file

@ -213,7 +213,7 @@ export default class SpaceforgePlugin extends Plugin {
};
const combinedData = { ...existingData, reviewData };
let backupStr = JSON.stringify(combinedData);
window.localStorage.setItem('spaceforge-backup', backupStr);
this.app.saveLocalStorage('spaceforge-backup', backupStr);
(async () => {
try {
await this.savePluginData();
@ -222,7 +222,7 @@ export default class SpaceforgePlugin extends Plugin {
} catch (error) {
try {
const minimalBackup = JSON.stringify({ settings: this.settings, reviewData: { schedules: this.reviewScheduleService.schedules || {}, customNoteOrder: this.reviewScheduleService.customNoteOrder || [], lastLinkAnalysisTimestamp: this.reviewScheduleService.lastLinkAnalysisTimestamp, version: this.manifest.version }});
window.localStorage.setItem('spaceforge-minimal-backup', minimalBackup);
this.app.saveLocalStorage('spaceforge-minimal-backup', minimalBackup);
} catch (minimalError) { /* handle error */ }
}
}; // Removed the extra closing parenthesis and semicolon, and moved the declaration.
@ -264,7 +264,7 @@ export default class SpaceforgePlugin extends Plugin {
};
const combinedData = { ...existingData, reviewData };
const backupStr = JSON.stringify(combinedData);
window.localStorage.setItem('spaceforge-backup', backupStr);
this.app.saveLocalStorage('spaceforge-backup', backupStr);
} catch (backupError) { /* handle error */ }
try {

View file

@ -3,7 +3,7 @@
"name": "Spaceforge",
"version": "1.0.3",
"minAppVersion": "0.15.0",
"description": "A spaced repetition FSRS & SM-2 Algorithm powered by AI plugin for efficient knowledge review with pomodoro timer",
"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",
"isDesktopOnly": false

View file

@ -1,5 +1,6 @@
import SpaceforgePlugin from "../main";
import { PluginStateData } from "../models/plugin-data";
import { ReviewSchedule } from "../models/review-schedule";
import { SpaceforgeSettings } from "../models/settings";
export type PomodoroMode = 'work' | 'shortBreak' | 'longBreak' | 'idle';
@ -127,7 +128,7 @@ export class PomodoroService {
/**
* Calculate and set estimation based on reading time
*/
public async calculateEstimationFromNotes(notes: any[]): Promise<{
public async calculateEstimationFromNotes(notes: ReviewSchedule[]): Promise<{
totalReadingTimeInSeconds: number;
totalReadingTimeInMinutes: number;
pomodorosNeeded: number;
@ -398,7 +399,8 @@ export class PomodoroService {
private playSoundNotification(): void {
// Simple beep sound using AudioContext
try {
const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();
const AudioContextClass = window.AudioContext || (window as unknown as { webkitAudioContext: typeof AudioContext }).webkitAudioContext;
const audioContext = new AudioContextClass();
if (!audioContext) return;
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();

View file

@ -710,7 +710,7 @@ export class ReviewScheduleService {
// Refresh the sidebar view if available with a slight delay to allow data to settle
if (this.plugin.events) {
setTimeout(() => {
window.setTimeout(() => {
this.plugin.events.emit('sidebar-update');
}, 50); // Small delay (e.g., 50ms)
}
@ -758,7 +758,7 @@ export class ReviewScheduleService {
if (this.plugin.events) {
// Use a timeout to allow other operations to complete before UI refresh
setTimeout(() => {
window.setTimeout(() => {
this.plugin.events.emit('sidebar-update');
}, 50);
}

4119
styles.css

File diff suppressed because it is too large Load diff

View file

@ -159,3 +159,37 @@
color: var(--sf-text-muted);
margin-top: 2px;
}
/* ====== Utility Classes ====== */
.sf-hidden {
display: none !important;
}
.sf-visible {
display: block !important;
}
.sf-flex {
display: flex !important;
}
.sf-invisible {
opacity: 0 !important;
}
.sf-visible-opacity {
opacity: 1 !important;
}
/* ====== Display Utilities ====== */
.sf-hidden {
display: none !important;
}
.sf-visible {
display: block !important;
}
.sf-flex {
display: flex !important;
}

View file

@ -533,6 +533,7 @@
text-overflow: ellipsis;
white-space: nowrap;
font-weight: 500;
background-color: var(--event-color, #95A5A6);
line-height: 1.2;
}
@ -713,6 +714,7 @@
flex-shrink: 0;
margin-top: 2px;
margin-bottom: 2px;
background-color: var(--event-color, #95A5A6);
}
.upcoming-events-event-content {
@ -1531,6 +1533,11 @@
transition: all 0.2s ease;
}
.calendar-event-tooltip.visible {
opacity: 1;
transform: translateY(0);
}
.calendar-event-tooltip .tooltip-title {
font-weight: 600;
font-size: 14px;

View file

@ -20,6 +20,10 @@
letter-spacing: -0.02em;
}
.mcq-user-answer {
font-style: italic;
color: var(--text-muted);
}
.mcq-refresh-btn {
display: flex;
align-items: center;

View file

@ -1,8 +1,9 @@
import { Modal, Notice, TFile, Setting } from 'obsidian';
import { App, Modal, Notice, TFile, Setting } from 'obsidian';
import SpaceforgePlugin from '../main';
import { ReviewResponse, ReviewSchedule } from '../models/review-schedule';
import { MCQSet } from '../models/mcq';
import { EstimationUtils } from '../utils/estimation';
import { sleep } from '../utils/sleep';
import { ConsolidatedMCQModal } from './consolidated-mcq-modal'; // Import ConsolidatedMCQModal
/**
@ -28,7 +29,7 @@ export class BatchReviewModal extends Modal {
collectingMCQs: boolean = false;
constructor(
app: any,
app: App,
plugin: SpaceforgePlugin,
notes: ReviewSchedule[],
useMCQ: boolean = false
@ -124,7 +125,7 @@ export class BatchReviewModal extends Modal {
const fileName = file instanceof TFile ? file.basename : note.path;
progressFill.style.width = `${((i + 1) / this.notes.length) * 100}%`;
statusEl.setText(`Processing ${i + 1}/${this.notes.length}: ${fileName}`);
await new Promise(resolve => setTimeout(resolve, 10));
await sleep(10);
let mcqSet = this.plugin.dataStorage.getMCQSetForNote(note.path);
// Use mcqGenerationService instead of openRouterService
@ -135,7 +136,7 @@ export class BatchReviewModal extends Modal {
mcqSet = await this.plugin.mcqController.generateMCQs(note.path);
} catch (error) {
statusEl.setText(`Error generating MCQs for ${fileName}`);
await new Promise(resolve => setTimeout(resolve, 1000));
await sleep(1000);
}
}
if (mcqSet) {
@ -143,7 +144,7 @@ export class BatchReviewModal extends Modal {
}
}
statusEl.setText(`Collected MCQs for ${this.allMCQSets.length}/${this.notes.length} notes`);
await new Promise(resolve => setTimeout(resolve, 1000));
await sleep(1000);
this.collectingMCQs = false;
}
@ -325,7 +326,7 @@ export class BatchReviewModal extends Modal {
statsEl.createEl("p", { text: `Completed: ${totalNotes}/${this.notes.length} notes` });
statsEl.createEl("p", { text: `Success rate: ${successRate}% (${successfulNotes}/${totalNotes})`, cls: successRate >= 70 ? "batch-review-success" : "batch-review-needs-improvement" });
const resultsEl = contentEl.createDiv("batch-review-results");
resultsEl.createEl("h3", { text: "Individual Results" });
new Setting(resultsEl).setHeading().setName("Individual Results");
const resultsListEl = resultsEl.createDiv("batch-review-results-list");
for (const result of this.results) {

View file

@ -1,7 +1,7 @@
import { Notice, setIcon } from "obsidian";
import SpaceforgePlugin from "../main";
import { ReviewSchedule } from "../models/review-schedule";
import { CalendarEvent } from "../models/calendar-event";
import { CalendarEvent, EventCategory, EventRecurrence } from "../models/calendar-event";
import { DateUtils } from "../utils/dates";
import { EstimationUtils } from "../utils/estimation";
import { UpcomingEvents } from "./upcoming-events";
@ -62,7 +62,7 @@ export class CalendarView {
// Tooltip elements
private tooltipEl: HTMLElement | null = null;
private tooltipTimeout: NodeJS.Timeout | null = null;
private tooltipTimeout: number | null = null;
/**
* Initialize calendar view
@ -331,9 +331,9 @@ export class CalendarView {
const eventTab = eventsContainer.createDiv("calendar-event-tab");
eventTab.setText(event.title.substring(0, 8) + (event.title.length > 8 ? "..." : ""));
// Set color based on event category or custom color
// Set color based on event category or custom color using CSS custom property
const eventColor = this.plugin.calendarEventService?.getEventColor(event) || '#95A5A6';
eventTab.style.backgroundColor = eventColor;
eventTab.style.setProperty('--event-color', eventColor);
// Add hover handler for tooltip
eventTab.addEventListener("mouseenter", (e) => {
@ -475,8 +475,8 @@ export class CalendarView {
title: "",
date: date.getTime(),
isAllDay: true,
category: this.plugin.settings.defaultEventCategory as any || 'personal',
recurrence: 'none' as any
category: (this.plugin.settings.defaultEventCategory as EventCategory) || EventCategory.Personal,
recurrence: EventRecurrence.None
};
const modal = new EventModal(
@ -491,7 +491,7 @@ export class CalendarView {
modal.open();
// Pre-fill the date in the modal after it opens
setTimeout(() => {
window.setTimeout(() => {
const dateInput = modal.contentEl.querySelector('input[type="date"]') as HTMLInputElement;
if (dateInput) {
dateInput.value = date.toISOString().split('T')[0];
@ -540,11 +540,11 @@ export class CalendarView {
showEventTooltip(targetEl: HTMLElement, event: CalendarEvent): void {
// Clear any existing timeout
if (this.tooltipTimeout) {
clearTimeout(this.tooltipTimeout);
window.clearTimeout(this.tooltipTimeout);
}
// Small delay before showing tooltip to avoid flickering
this.tooltipTimeout = setTimeout(() => {
this.tooltipTimeout = window.setTimeout(() => {
this.createEventTooltip(targetEl, event);
}, 300);
}
@ -555,7 +555,7 @@ export class CalendarView {
hideEventTooltip(): void {
// Clear any pending tooltip
if (this.tooltipTimeout) {
clearTimeout(this.tooltipTimeout);
window.clearTimeout(this.tooltipTimeout);
this.tooltipTimeout = null;
}
@ -577,7 +577,7 @@ export class CalendarView {
this.hideEventTooltip();
// Create tooltip element
this.tooltipEl = document.createElement("div");
this.tooltipEl = document.body.createEl("div");
this.tooltipEl.className = "calendar-event-tooltip";
// Format event details
@ -588,24 +588,33 @@ export class CalendarView {
day: 'numeric'
});
let tooltipContent = `<div class="tooltip-title">${event.title}</div>`;
tooltipContent += `<div class="tooltip-date">📅 ${dateStr}</div>`;
// Clear existing content
this.tooltipEl.empty();
// Create tooltip content safely
const titleEl = this.tooltipEl.createDiv('tooltip-title');
titleEl.textContent = event.title;
const dateEl = this.tooltipEl.createDiv('tooltip-date');
dateEl.textContent = `📅 ${dateStr}`;
if (event.time) {
tooltipContent += `<div class="tooltip-time">🕐 ${event.time}</div>`;
const timeEl = this.tooltipEl.createDiv('tooltip-time');
timeEl.textContent = `🕐 ${event.time}`;
}
if (event.description) {
tooltipContent += `<div class="tooltip-description">📝 ${event.description}</div>`;
const descEl = this.tooltipEl.createDiv('tooltip-description');
descEl.textContent = `📝 ${event.description}`;
}
if (event.location) {
tooltipContent += `<div class="tooltip-location">📍 ${event.location}</div>`;
const locationEl = this.tooltipEl.createDiv('tooltip-location');
locationEl.textContent = `📍 ${event.location}`;
}
tooltipContent += `<div class="tooltip-category">🏷️ ${event.category}</div>`;
this.tooltipEl.innerHTML = tooltipContent;
const categoryEl = this.tooltipEl.createDiv('tooltip-category');
categoryEl.textContent = `🏷️ ${event.category}`;
// Position tooltip
const rect = targetEl.getBoundingClientRect();
@ -630,14 +639,12 @@ export class CalendarView {
this.tooltipEl.style.left = `${left}px`;
this.tooltipEl.style.top = `${top}px`;
// Add fade-in animation
this.tooltipEl.style.opacity = '0';
this.tooltipEl.style.transform = 'translateY(-4px)';
// Add fade-in animation using CSS class
this.tooltipEl.classList.remove('visible');
requestAnimationFrame(() => {
if (this.tooltipEl) {
this.tooltipEl.style.opacity = '1';
this.tooltipEl.style.transform = 'translateY(0)';
this.tooltipEl.classList.add('visible');
}
});
}

View file

@ -326,7 +326,7 @@ export class ConsolidatedMCQModal extends Modal {
this.highlightAnswer(selectedIndex, isCorrect);
// Wait a moment before proceeding
setTimeout(() => {
window.setTimeout(() => {
if (isCorrect) {
// Move to next question if correct
this.currentQuestionIndex++;
@ -355,7 +355,7 @@ export class ConsolidatedMCQModal extends Modal {
new Notice("Incorrect answer. Try again to proceed to the next question.");
// Remove the highlight after a short delay
setTimeout(() => {
window.setTimeout(() => {
const choiceButtons = document.querySelectorAll('.mcq-choice-btn');
if (choiceButtons.length <= selectedIndex) return;
@ -557,7 +557,7 @@ export class ConsolidatedMCQModal extends Modal {
// Display note scores with enhanced styling
const noteScoresEl = contentEl.createDiv('mcq-note-scores');
const scoreHeading = noteScoresEl.createEl('h3', { text: 'Scores by Note', cls: 'mcq-note-scores-heading' });
const scoreHeading = new Setting(noteScoresEl).setHeading().setName('Scores by Note').settingEl;
// Sort notes by score for better visualization (highest first)
const sortedNotes = Object.keys(noteScores).sort((a, b) => noteScores[b].score - noteScores[a].score);
@ -604,7 +604,7 @@ export class ConsolidatedMCQModal extends Modal {
// --- Detailed Question Breakdown ---
const breakdownContainer = contentEl.createDiv('mcq-detailed-breakdown');
breakdownContainer.createEl('h3', { text: 'Detailed Question Breakdown' });
new Setting(breakdownContainer).setHeading().setName('Detailed Question Breakdown');
this.allQuestions.forEach((question, index) => {
const questionEl = breakdownContainer.createDiv('mcq-breakdown-item');
@ -640,7 +640,7 @@ export class ConsolidatedMCQModal extends Modal {
}
} else {
userAnswerTextEl.createSpan({ text: 'Your answer: ' + userAnswerDisplay });
userAnswerTextEl.style.fontStyle = 'italic';
userAnswerTextEl.addClass('mcq-user-answer');
}
const correctAnswerEl = questionEl.createDiv('mcq-correct-answer');

View file

@ -40,10 +40,10 @@ export class EventModal extends Modal {
setIcon(headerIcon, this.event ? "edit" : "calendar-plus");
const headerText = header.createDiv("event-modal-header-text");
headerText.createEl("h2", {
text: this.event ? "Edit Event" : "New Event",
cls: "event-modal-title"
});
const titleSetting = new Setting(headerText)
.setHeading()
.setName(this.event ? "Edit Event" : "New Event");
titleSetting.settingEl.addClass("event-modal-title");
// Compact form container
const formContainer = contentEl.createDiv("event-modal-form");

View file

@ -149,7 +149,7 @@ export class MCQModal extends Modal {
if (this.selectedAnswerIndex < choiceButtons.length) {
const button = choiceButtons[this.selectedAnswerIndex] as HTMLElement;
button.classList.add('mcq-key-pressed');
setTimeout(() => {
window.setTimeout(() => {
button.classList.remove('mcq-key-pressed');
this.handleAnswer(this.selectedAnswerIndex);
this.selectedAnswerIndex = -1;
@ -262,8 +262,8 @@ export class MCQModal extends Modal {
}
});
const choicesContainer = this.contentEl.querySelector('.mcq-choices-container'); // Use this.contentEl
if (choicesContainer instanceof HTMLElement) choicesContainer.style.display = 'none'; // Keep display:none for dynamic hiding
skipButton.style.display = 'none'; // Keep display:none for dynamic hiding
if (choicesContainer instanceof HTMLElement) choicesContainer.classList.add('sf-hidden'); // Use CSS class for hiding
skipButton.classList.add('sf-hidden'); // Use CSS class for hiding
});
}
@ -301,7 +301,7 @@ export class MCQModal extends Modal {
}
this.highlightAnswer(selectedIndex, isCorrect);
setTimeout(() => {
window.setTimeout(() => {
if (isCorrect) {
this.session.currentQuestionIndex++;
this.questionStartTime = Date.now();
@ -391,7 +391,7 @@ export class MCQModal extends Modal {
// Removed the old score indicator text (🎯 Excellent, etc.)
const resultsEl = contentEl.createDiv('mcq-results');
resultsEl.createEl('h3', { text: 'Question Results' });
new Setting(resultsEl).setHeading().setName('Question Results');
if (this.session.answers.length === 0) {
resultsEl.createDiv({ cls: 'mcq-no-answers', text: 'No questions were answered in this session.' });
} else {

View file

@ -1,4 +1,4 @@
import { Modal, Notice, TFile, setIcon, Setting } from 'obsidian';
import { App, Modal, Notice, TFile, setIcon, Setting } from 'obsidian';
import SpaceforgePlugin from '../main';
import { ReviewResponse, ReviewSchedule, FsrsRating } from '../models/review-schedule'; // Added FsrsRating
import { State as FsrsState } from 'ts-fsrs'; // For displaying FSRS state name
@ -11,7 +11,7 @@ export class ReviewModal extends Modal {
plugin: SpaceforgePlugin;
path: string;
constructor(app: any, plugin: SpaceforgePlugin, path: string) {
constructor(app: App, plugin: SpaceforgePlugin, path: string) {
super(app);
this.plugin = plugin;
this.path = path;

View file

@ -39,21 +39,25 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
// Create section container
const sectionContainer = containerEl.createEl('div', { cls: 'sf-settings-section' });
// Create header
const header = sectionContainer.createEl('div', { cls: 'sf-settings-section-header' });
// Create heading using Setting.setHeading()
const headingSetting = new Setting(sectionContainer)
.setName(title)
.setHeading();
// Add icon
// Add icon if provided
if (iconName) {
const iconEl = header.createEl('span', { cls: 'sf-settings-icon' });
const iconEl = headingSetting.settingEl.createEl('span', { cls: 'sf-settings-icon' });
setIcon(iconEl, iconName);
// Insert icon before the name
headingSetting.nameEl.prepend(iconEl);
}
// Add title and collapse indicator
header.createEl('h3', { text: title });
const collapseIndicator = header.createEl('span', {
// Add collapse indicator
const collapseIndicator = headingSetting.settingEl.createEl('span', {
cls: 'sf-settings-collapse-indicator',
text: defaultOpen ? '▾' : '▸'
});
headingSetting.nameEl.appendChild(collapseIndicator);
// Create content container
const contentContainer = sectionContainer.createEl('div', {
@ -62,13 +66,14 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
// Initially hide if not defaultOpen
if (!defaultOpen) {
contentContainer.style.display = 'none';
contentContainer.classList.add('sf-hidden');
}
// Add click handler for toggling
header.addEventListener('click', () => {
const isVisible = contentContainer.style.display !== 'none';
contentContainer.style.display = isVisible ? 'none' : 'block';
headingSetting.settingEl.addEventListener('click', () => {
const isVisible = contentContainer.classList.contains('sf-visible');
contentContainer.classList.toggle('sf-hidden');
contentContainer.classList.toggle('sf-visible');
collapseIndicator.textContent = isVisible ? '▸' : '▾';
});
@ -116,7 +121,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
const dataJson = JSON.stringify(dataToExport, null, 2);
const blob = new Blob([dataJson], { type: 'application/json' });
const a = document.createElement('a');
const a = document.body.createEl('a');
a.href = URL.createObjectURL(blob);
a.download = 'spaceforge-data.json'; // Changed filename
document.body.appendChild(a);
@ -129,7 +134,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
// Import all data button
const importBtn = actionsContainer.createEl('button', { text: 'Import all data', cls: 'sf-btn sf-btn-primary' });
importBtn.addEventListener('click', () => {
const input = document.createElement('input');
const input = document.body.createEl('input');
input.type = 'file';
input.accept = 'application/json';
@ -743,7 +748,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab {
enableMCQ: value,
openRouterModel: this.plugin.settings.openRouterModel
};
window.localStorage.setItem('spaceforge-api-settings', JSON.stringify(apiSettings));
this.plugin.app.saveLocalStorage('spaceforge-api-settings', JSON.stringify(apiSettings));
} catch (e) {
}

View file

@ -1,4 +1,4 @@
import { ItemView, Menu, Notice, TFile, WorkspaceLeaf, setIcon } from "obsidian";
import { ItemView, Menu, Notice, TFile, WorkspaceLeaf, setIcon, Setting } from "obsidian";
import SpaceforgePlugin from "../main";
import { PomodoroUIManager } from "./sidebar/pomodoro-ui-manager";
import { NoteItemRenderer } from "./sidebar/note-item-renderer";
@ -86,7 +86,7 @@ export class ReviewSidebarView extends ItemView {
if (!this.persistentHeaderEl) {
this.persistentHeaderEl = this.mainContainer.createDiv("review-header");
this.persistentHeaderEl.createEl("h2", { text: "Review Schedule" });
new Setting(this.persistentHeaderEl).setHeading().setName("Review Schedule");
const viewToggle = this.persistentHeaderEl.createDiv("review-view-toggle");
const listViewBtn = viewToggle.createDiv("review-view-btn");

View file

@ -1,4 +1,4 @@
import { Notice, TFile } from "obsidian";
import { App, TFile, Notice, Setting } from "obsidian";
import SpaceforgePlugin from "../../main";
import { ReviewSchedule } from "../../models/review-schedule";
import { DateUtils } from "../../utils/dates";
@ -138,14 +138,14 @@ export class ListViewRenderer {
if (pomodoroSectionContainerEl) {
this.pomodoroUIManager.attachAndRender(pomodoroSectionContainerEl);
if (this.plugin.settings.pomodoroEnabled) {
pomodoroContainer.style.display = '';
pomodoroContainer.classList.remove('sf-hidden');
this.pomodoroUIManager.showPomodoroSection(true);
this.pomodoroUIManager.updatePomodoroUI();
// Automatically calculate estimation for current notes
this.pomodoroUIManager.calculateAndDisplayEstimation();
} else {
pomodoroContainer.style.display = 'none';
pomodoroContainer.classList.add('sf-hidden');
this.pomodoroUIManager.showPomodoroSection(false);
}
}
@ -175,7 +175,7 @@ export class ListViewRenderer {
reviewAllMCQBtn.addEventListener("click", () => { this.plugin.reviewController.reviewAllNotesWithMCQ(true); });
}
}
reviewButtonsContainer.style.display = '';
reviewButtonsContainer.classList.remove('sf-hidden');
// Bulk action buttons
let bulkActionButtons = container.querySelector(".review-bulk-actions") as HTMLElement;
@ -232,7 +232,7 @@ export class ListViewRenderer {
this.updateBulkActionButtonsVisibility(container); // Update visibility based on current selection
} else if (reviewButtonsContainer) {
reviewButtonsContainer.style.display = 'none';
reviewButtonsContainer.classList.add('sf-hidden');
const bulkActionButtons = container.querySelector(".review-bulk-actions") as HTMLElement;
if (bulkActionButtons) bulkActionButtons.style.display = 'none';
}
@ -434,7 +434,7 @@ export class ListViewRenderer {
if (activeSession) {
if (!sessionSection) {
sessionSection = container.createDiv("review-session-section");
sessionSection.createEl("h3", { text: "Active Review Session" });
new Setting(sessionSection).setHeading().setName("Active Review Session");
const sessionInfo = sessionSection.createDiv("review-session-info");
sessionInfo.createDiv({ cls: "review-session-name" });
sessionInfo.createDiv({ cls: "review-session-progress" });
@ -476,7 +476,7 @@ export class ListViewRenderer {
if (upcomingKeys.length > 0) {
if (!upcomingSection) {
upcomingSection = container.createDiv("review-upcoming-section");
upcomingSection.createEl("h3", { text: "Upcoming Reviews" });
new Setting(upcomingSection).setHeading().setName("Upcoming Reviews");
upcomingSection.createDiv("review-upcoming-list"); // List container
}
upcomingSection.style.display = '';

View file

@ -93,7 +93,13 @@ export class NoteItemRenderer {
const noteReviewDayStartTs = DateUtils.startOfDay(new Date(note.nextReviewDate)); // Returns timestamp
const isEligibleForAdvance = noteReviewDayStartTs > todayStartTs;
advanceBtn.disabled = !isEligibleForAdvance;
advanceBtn.style.display = isEligibleForAdvance ? '' : 'none';
if (isEligibleForAdvance) {
advanceBtn.classList.remove('sf-hidden');
advanceBtn.classList.add('sf-visible');
} else {
advanceBtn.classList.remove('sf-visible');
advanceBtn.classList.add('sf-hidden');
}
}
}

View file

@ -90,7 +90,8 @@ export class PomodoroUIManager {
// Pomodoro section is always "open" now, so pomodoroRootEl should always be visible.
if (this.pomodoroRootEl) {
this.pomodoroRootEl.style.display = '';
this.pomodoroRootEl.classList.remove('sf-hidden');
this.pomodoroRootEl.classList.add('sf-visible');
}
// If pomodoroRootEl was just created or its children are missing, render its internal timer structure
@ -108,7 +109,13 @@ export class PomodoroUIManager {
/** Controls the visibility of the entire attached Pomodoro UI section (e.g. for global plugin enable/disable) */
public showPomodoroSection(show: boolean): void {
if (this.attachedContainer) {
this.attachedContainer.style.display = show ? '' : 'none';
if (show) {
this.attachedContainer.classList.remove('sf-hidden');
this.attachedContainer.classList.add('sf-visible');
} else {
this.attachedContainer.classList.remove('sf-visible');
this.attachedContainer.classList.add('sf-hidden');
}
// If the section is hidden externally, we might want to ensure controls are visible when it's re-shown.
// For now, we'll let areMainControlsVisible persist.
}
@ -176,8 +183,8 @@ export class PomodoroUIManager {
});
const handlePressEnd = () => {
if (this.veryLongPressTimer) clearTimeout(this.veryLongPressTimer);
if (this.longPressTimer) clearTimeout(this.longPressTimer);
if (this.veryLongPressTimer) window.clearTimeout(this.veryLongPressTimer);
if (this.longPressTimer) window.clearTimeout(this.longPressTimer);
this.veryLongPressTimer = null;
this.longPressTimer = null;
@ -205,8 +212,8 @@ export class PomodoroUIManager {
this.pomodoroTimerDisplayEl.addEventListener("touchend", handlePressEnd);
const cancelPress = () => {
if (this.veryLongPressTimer) clearTimeout(this.veryLongPressTimer);
if (this.longPressTimer) clearTimeout(this.longPressTimer);
if (this.veryLongPressTimer) window.clearTimeout(this.veryLongPressTimer);
if (this.longPressTimer) window.clearTimeout(this.longPressTimer);
this.veryLongPressTimer = null;
this.longPressTimer = null;
this.didLongPress = false;
@ -282,7 +289,7 @@ export class PomodoroUIManager {
if (!this.pomodoroQuickSettingsPanelEl || this.pomodoroQuickSettingsPanelEl.parentElement !== settingsPanelContainer) {
this.pomodoroQuickSettingsPanelEl?.remove();
this.pomodoroQuickSettingsPanelEl = settingsPanelContainer.createDiv("pomodoro-quick-settings-panel");
this.pomodoroQuickSettingsPanelEl.style.display = 'none'; // Initial state
this.pomodoroQuickSettingsPanelEl.classList.add('sf-hidden'); // Initial state
// Recreate inputs and buttons if panel is new
const createQuickSetting = (labelText: string, inputType: string = 'number'): HTMLInputElement => {
@ -354,7 +361,7 @@ export class PomodoroUIManager {
});
this.pomodoroCalculationResultEl = this.pomodoroQuickSettingsPanelEl.createDiv({ cls: "pomodoro-calculation-result" });
this.pomodoroCalculationResultEl.style.display = 'none';
this.pomodoroCalculationResultEl.classList.add('sf-hidden');
}
this.updatePomodoroUI(); // Ensure UI reflects current state after potential recreation
@ -383,7 +390,8 @@ export class PomodoroUIManager {
? `No notes scheduled for ${new Date(activeDate).toLocaleDateString()} to calculate.`
: "No notes currently due to calculate.";
this.pomodoroCalculationResultEl.setText(message);
this.pomodoroCalculationResultEl.style.display = 'block';
this.pomodoroCalculationResultEl.classList.remove('sf-hidden');
this.pomodoroCalculationResultEl.classList.add('sf-visible');
return;
}
@ -457,8 +465,14 @@ export class PomodoroUIManager {
private toggleSettingsPanel(): void {
const panel = this.pomodoroQuickSettingsPanelEl;
if (!panel) return;
const isCurrentlyHidden = panel.style.display === 'none' || !panel.style.display;
panel.style.display = isCurrentlyHidden ? 'flex' : 'none';
const isCurrentlyHidden = panel.classList.contains('sf-hidden') || !panel.classList.contains('sf-visible');
if (isCurrentlyHidden) {
panel.classList.remove('sf-hidden');
panel.classList.add('sf-visible');
} else {
panel.classList.remove('sf-visible');
panel.classList.add('sf-hidden');
}
if (isCurrentlyHidden) { // Populate inputs when opening
if(this.pomodoroQuickWorkInput) this.pomodoroQuickWorkInput.value = String(this.plugin.settings.pomodoroWorkDuration);

View file

@ -1,6 +1,6 @@
import { Notice, setIcon } from "obsidian";
import SpaceforgePlugin from "../main";
import { UpcomingEvent } from "../models/calendar-event";
import { CalendarEvent, UpcomingEvent } from "../models/calendar-event";
import { DateUtils } from "../utils/dates";
/**
@ -180,7 +180,7 @@ export class UpcomingEvents {
// Event color indicator
const colorIndicator = eventItem.createDiv("upcoming-events-event-color");
const eventColor = this.plugin.calendarEventService?.getEventColor(event) || '#95A5A6';
colorIndicator.style.backgroundColor = eventColor;
colorIndicator.style.setProperty('--event-color', eventColor);
// Event content
const eventContent = eventItem.createDiv("upcoming-events-event-content");
@ -239,7 +239,7 @@ export class UpcomingEvents {
*
* @param event Event to show details for
*/
private showEventDetails(event: any): void {
private showEventDetails(event: CalendarEvent): void {
const eventDate = new Date(event.date);
const dateStr = eventDate.toLocaleDateString(undefined, {
weekday: 'long',

View file

@ -27,7 +27,7 @@ export class EventEmitter {
* @param event Event name
* @param args Arguments to pass to listeners
*/
emit(event: string, ...args: any[]): void {
emit(event: string, ...args: unknown[]): void {
if (!this.listeners[event]) {
return;
}

8
utils/sleep.ts Normal file
View file

@ -0,0 +1,8 @@
/**
* Utility function for sleeping/delaying execution
* @param ms Number of milliseconds to sleep
* @returns Promise that resolves after the specified delay
*/
export function sleep(ms: number): Promise<void> {
return new Promise(resolve => window.setTimeout(resolve, ms));
}

View file

@ -1,4 +1,6 @@
{
"1.0.0": "Initial release",
"1.0.2": "Enhanced Pomodoro timer with estimation overrides and detailed cycle tracking"
"1.0.0": "0.15.0",
"1.0.1": "0.15.0",
"1.0.2": "0.15.0",
"1.0.3": "0.15.0"
}