mirror of
https://github.com/dralkh/spaceforge.git
synced 2026-07-22 06:45:03 +00:00
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:
parent
b85c65a841
commit
4cbc58b5fa
27 changed files with 193 additions and 15995 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -3,3 +3,8 @@ node_modules/.bin/acorn
|
|||
node_modules
|
||||
.kilocode
|
||||
opencode.json
|
||||
|
||||
# Compiled files
|
||||
main.js
|
||||
styles.css
|
||||
docs
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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' ||
|
||||
|
|
|
|||
6
main.ts
6
main.ts
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
4119
styles.css
File diff suppressed because it is too large
Load diff
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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 = '';
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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
8
utils/sleep.ts
Normal 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));
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
Loading…
Reference in a new issue