2025-12-08 13:58:14 +00:00
|
|
|
import { TFile } from 'obsidian';
|
2025-07-31 04:03:20 +00:00
|
|
|
import SpaceforgePlugin from './main';
|
2025-12-08 13:58:14 +00:00
|
|
|
import { ReviewHistoryItem, ReviewSchedule, ReviewResponse } from './models/review-schedule';
|
|
|
|
|
import { ReviewSessionStore, ReviewSession } from './models/review-session';
|
2025-07-31 04:03:20 +00:00
|
|
|
import { MCQSet, MCQSession } from './models/mcq';
|
2025-12-08 13:58:14 +00:00
|
|
|
import { EstimationUtils } from './utils/estimation';
|
2025-07-31 04:03:20 +00:00
|
|
|
import { ReviewScheduleService } from './services/review-schedule-service';
|
|
|
|
|
import { ReviewHistoryService } from './services/review-history-service';
|
|
|
|
|
import { ReviewSessionService } from './services/review-session-service';
|
|
|
|
|
import { MCQService } from './services/mcq-service';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Data storage interface for plugin data
|
|
|
|
|
*/
|
|
|
|
|
export interface SpaceforgeData {
|
|
|
|
|
/**
|
|
|
|
|
* Review schedules for all notes
|
|
|
|
|
*/
|
|
|
|
|
schedules: Record<string, ReviewSchedule>;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Review history items
|
|
|
|
|
*/
|
|
|
|
|
history: ReviewHistoryItem[];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Review sessions
|
|
|
|
|
*/
|
|
|
|
|
reviewSessions: ReviewSessionStore;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Multiple-choice question sets
|
|
|
|
|
*/
|
|
|
|
|
mcqSets: Record<string, MCQSet>;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Multiple-choice question session history
|
|
|
|
|
*/
|
|
|
|
|
mcqSessions: Record<string, MCQSession[]>;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Custom order for notes (user-defined ordering)
|
|
|
|
|
*/
|
|
|
|
|
customNoteOrder: string[];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Last used version (for migrations)
|
|
|
|
|
*/
|
|
|
|
|
version: string;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Timestamp of the last time link analysis was performed for ordering
|
|
|
|
|
*/
|
|
|
|
|
lastLinkAnalysisTimestamp?: number | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handles storage and retrieval of spaced repetition data
|
|
|
|
|
*/
|
|
|
|
|
export class DataStorage {
|
|
|
|
|
/**
|
|
|
|
|
* Reference to the main plugin
|
|
|
|
|
*/
|
|
|
|
|
plugin: SpaceforgePlugin;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Service for managing review schedules
|
|
|
|
|
*/
|
|
|
|
|
reviewScheduleService: ReviewScheduleService;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Service for managing review history
|
|
|
|
|
*/
|
|
|
|
|
reviewHistoryService: ReviewHistoryService;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Service for managing review sessions
|
|
|
|
|
*/
|
|
|
|
|
reviewSessionService: ReviewSessionService;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Service for managing MCQ data
|
|
|
|
|
*/
|
|
|
|
|
mcqService: MCQService;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Initialize data storage
|
|
|
|
|
*
|
|
|
|
|
* @param plugin Reference to the main plugin
|
|
|
|
|
* @param reviewScheduleService Instance of ReviewScheduleService
|
|
|
|
|
* @param reviewHistoryService Instance of ReviewHistoryService
|
|
|
|
|
* @param reviewSessionService Instance of ReviewSessionService
|
|
|
|
|
* @param mcqService Instance of MCQService
|
|
|
|
|
*/
|
|
|
|
|
constructor(
|
|
|
|
|
plugin: SpaceforgePlugin,
|
|
|
|
|
reviewScheduleService: ReviewScheduleService,
|
|
|
|
|
reviewHistoryService: ReviewHistoryService,
|
|
|
|
|
reviewSessionService: ReviewSessionService,
|
|
|
|
|
mcqService: MCQService
|
|
|
|
|
) {
|
|
|
|
|
this.plugin = plugin;
|
|
|
|
|
this.reviewScheduleService = reviewScheduleService;
|
|
|
|
|
this.reviewHistoryService = reviewHistoryService;
|
|
|
|
|
this.reviewSessionService = reviewSessionService;
|
|
|
|
|
this.mcqService = mcqService;
|
|
|
|
|
|
|
|
|
|
// Constructor no longer calls ensureDataLoaded()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Removed ensureDataLoaded() method
|
|
|
|
|
// Removed loadData() method (and all localStorage logic within it)
|
|
|
|
|
// Removed saveData() method (and all localStorage logic within it)
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Initialize default data when no data is available
|
|
|
|
|
* This is now primarily for internal use during integrity checks,
|
|
|
|
|
* as main.ts handles initial default loading.
|
|
|
|
|
*/
|
|
|
|
|
private initializeDefaultData(): void {
|
|
|
|
|
this.reviewScheduleService.schedules = {};
|
|
|
|
|
this.reviewHistoryService.history = [];
|
|
|
|
|
this.reviewSessionService.reviewSessions = {
|
|
|
|
|
sessions: {},
|
|
|
|
|
activeSessionId: null
|
|
|
|
|
};
|
|
|
|
|
this.mcqService.mcqSets = {};
|
|
|
|
|
this.mcqService.mcqSessions = {};
|
|
|
|
|
this.reviewScheduleService.customNoteOrder = [];
|
|
|
|
|
this.reviewScheduleService.lastLinkAnalysisTimestamp = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Verify data integrity and fix any issues
|
|
|
|
|
* @returns true if data is valid, false if it needed to be fixed
|
|
|
|
|
*/
|
|
|
|
|
public verifyDataIntegrity(): boolean {
|
|
|
|
|
let isValid = true;
|
|
|
|
|
if (!this.reviewScheduleService.schedules || typeof this.reviewScheduleService.schedules !== 'object') {
|
|
|
|
|
this.reviewScheduleService.schedules = {};
|
|
|
|
|
isValid = false;
|
|
|
|
|
} else {
|
|
|
|
|
for (const path in this.reviewScheduleService.schedules) {
|
|
|
|
|
const schedule = this.reviewScheduleService.schedules[path];
|
|
|
|
|
if (!schedule || typeof schedule !== 'object') {
|
|
|
|
|
delete this.reviewScheduleService.schedules[path];
|
|
|
|
|
isValid = false;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2025-10-13 07:45:31 +00:00
|
|
|
const s = schedule as unknown as Record<string, unknown>;
|
2025-07-31 04:03:20 +00:00
|
|
|
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' ||
|
|
|
|
|
!('ease' in s) || typeof s.ease !== 'number') {
|
|
|
|
|
|
|
|
|
|
delete this.reviewScheduleService.schedules[path];
|
|
|
|
|
isValid = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!Array.isArray(this.reviewHistoryService.history)) {
|
|
|
|
|
this.reviewHistoryService.history = [];
|
|
|
|
|
isValid = false;
|
|
|
|
|
}
|
|
|
|
|
if (!this.reviewSessionService.reviewSessions ||
|
|
|
|
|
typeof this.reviewSessionService.reviewSessions !== 'object' ||
|
|
|
|
|
!this.reviewSessionService.reviewSessions.sessions ||
|
|
|
|
|
typeof this.reviewSessionService.reviewSessions.sessions !== 'object') {
|
|
|
|
|
|
|
|
|
|
this.reviewSessionService.reviewSessions = {
|
|
|
|
|
sessions: {},
|
|
|
|
|
activeSessionId: null
|
|
|
|
|
};
|
|
|
|
|
isValid = false;
|
|
|
|
|
}
|
|
|
|
|
if (!this.mcqService.mcqSets || typeof this.mcqService.mcqSets !== 'object') {
|
|
|
|
|
this.mcqService.mcqSets = {};
|
|
|
|
|
isValid = false;
|
|
|
|
|
}
|
|
|
|
|
if (!this.mcqService.mcqSessions || typeof this.mcqService.mcqSessions !== 'object') {
|
|
|
|
|
this.mcqService.mcqSessions = {};
|
|
|
|
|
isValid = false;
|
|
|
|
|
}
|
|
|
|
|
if (!Array.isArray(this.reviewScheduleService.customNoteOrder)) {
|
|
|
|
|
this.reviewScheduleService.customNoteOrder = [];
|
|
|
|
|
isValid = false;
|
|
|
|
|
}
|
|
|
|
|
if (this.reviewScheduleService.lastLinkAnalysisTimestamp !== null && typeof this.reviewScheduleService.lastLinkAnalysisTimestamp !== 'number') {
|
|
|
|
|
this.reviewScheduleService.lastLinkAnalysisTimestamp = null;
|
|
|
|
|
isValid = false;
|
|
|
|
|
}
|
|
|
|
|
const noSchedules = Object.keys(this.reviewScheduleService.schedules).length === 0;
|
|
|
|
|
const hasMCQs = Object.keys(this.mcqService.mcqSets).length > 0;
|
|
|
|
|
|
|
|
|
|
if (noSchedules && hasMCQs) {
|
2025-12-08 13:58:14 +00:00
|
|
|
// no-op
|
2025-07-31 04:03:20 +00:00
|
|
|
}
|
2025-09-03 15:10:33 +00:00
|
|
|
return isValid;
|
2025-07-31 04:03:20 +00:00
|
|
|
}
|
|
|
|
|
|
2025-12-08 13:58:14 +00:00
|
|
|
public cleanupNonExistentFiles(): boolean {
|
2025-09-03 15:10:33 +00:00
|
|
|
let changesMade = false;
|
2025-07-31 04:03:20 +00:00
|
|
|
if (!this.reviewScheduleService.schedules || typeof this.reviewScheduleService.schedules !== 'object') {
|
|
|
|
|
this.reviewScheduleService.schedules = {};
|
2025-09-03 15:10:33 +00:00
|
|
|
changesMade = true;
|
2025-07-31 04:03:20 +00:00
|
|
|
}
|
|
|
|
|
if (!Array.isArray(this.reviewHistoryService.history)) {
|
|
|
|
|
this.reviewHistoryService.history = [];
|
|
|
|
|
changesMade = true;
|
|
|
|
|
}
|
|
|
|
|
if (!this.reviewSessionService.reviewSessions || typeof this.reviewSessionService.reviewSessions !== 'object' || !this.reviewSessionService.reviewSessions.sessions) {
|
|
|
|
|
this.reviewSessionService.reviewSessions = {
|
|
|
|
|
sessions: {},
|
|
|
|
|
activeSessionId: null
|
|
|
|
|
};
|
|
|
|
|
changesMade = true;
|
|
|
|
|
}
|
|
|
|
|
let cleanupCount = 0;
|
|
|
|
|
const beforeCount = Object.keys(this.reviewScheduleService.schedules).length;
|
|
|
|
|
const safetyCheck = {
|
|
|
|
|
totalSchedules: beforeCount,
|
|
|
|
|
checkedSchedules: 0,
|
|
|
|
|
missingSchedules: 0,
|
|
|
|
|
preserved: false
|
|
|
|
|
};
|
|
|
|
|
try {
|
2025-12-08 13:58:14 +00:00
|
|
|
const allSchedules = { ...this.reviewScheduleService.schedules };
|
2025-07-31 04:03:20 +00:00
|
|
|
const allFiles = new Set<string>();
|
|
|
|
|
try {
|
2025-12-08 13:58:14 +00:00
|
|
|
this.plugin.app.vault.getMarkdownFiles().forEach(file => allFiles.add(file.path));
|
|
|
|
|
} catch {
|
2025-09-03 15:10:33 +00:00
|
|
|
return changesMade;
|
2025-07-31 04:03:20 +00:00
|
|
|
}
|
|
|
|
|
if (allFiles.size === 0 && beforeCount > 0) {
|
|
|
|
|
safetyCheck.preserved = true;
|
2025-09-03 15:10:33 +00:00
|
|
|
return changesMade;
|
2025-07-31 04:03:20 +00:00
|
|
|
}
|
|
|
|
|
for (const path in allSchedules) {
|
|
|
|
|
try {
|
|
|
|
|
safetyCheck.checkedSchedules++;
|
2025-09-03 15:10:33 +00:00
|
|
|
if (allFiles.has(path)) continue;
|
2025-07-31 04:03:20 +00:00
|
|
|
const file = this.plugin.app.vault.getAbstractFileByPath(path);
|
|
|
|
|
if (!file) {
|
|
|
|
|
safetyCheck.missingSchedules++;
|
2025-09-03 15:10:33 +00:00
|
|
|
delete this.reviewScheduleService.schedules[path];
|
2025-07-31 04:03:20 +00:00
|
|
|
cleanupCount++;
|
2025-09-03 15:10:33 +00:00
|
|
|
changesMade = true;
|
2025-07-31 04:03:20 +00:00
|
|
|
}
|
2025-12-08 13:58:14 +00:00
|
|
|
} catch {
|
|
|
|
|
// no-op
|
2025-07-31 04:03:20 +00:00
|
|
|
}
|
|
|
|
|
if (safetyCheck.missingSchedules > 0 &&
|
|
|
|
|
safetyCheck.missingSchedules === safetyCheck.checkedSchedules &&
|
|
|
|
|
safetyCheck.checkedSchedules >= 5) {
|
|
|
|
|
|
2025-09-03 15:10:33 +00:00
|
|
|
this.reviewScheduleService.schedules = allSchedules;
|
2025-07-31 04:03:20 +00:00
|
|
|
cleanupCount = 0;
|
2025-09-03 15:10:33 +00:00
|
|
|
changesMade = false;
|
2025-07-31 04:03:20 +00:00
|
|
|
safetyCheck.preserved = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (cleanupCount > 0 && !safetyCheck.preserved) {
|
2025-12-08 13:58:14 +00:00
|
|
|
// no-op
|
2025-07-31 04:03:20 +00:00
|
|
|
}
|
|
|
|
|
const dataState = {
|
|
|
|
|
schedules: Object.keys(this.reviewScheduleService.schedules).length,
|
|
|
|
|
history: this.reviewHistoryService.history.length,
|
|
|
|
|
reviewSessions: Object.keys(this.reviewSessionService.reviewSessions.sessions || {}).length,
|
|
|
|
|
mcqSets: Object.keys(this.mcqService.mcqSets || {}).length,
|
|
|
|
|
mcqSessions: Object.keys(this.mcqService.mcqSessions || {}).length
|
|
|
|
|
};
|
|
|
|
|
if (dataState.schedules === 0 && (dataState.history > 0 || dataState.reviewSessions > 0 || dataState.mcqSets > 0)) {
|
2025-12-08 13:58:14 +00:00
|
|
|
// no-op
|
2025-07-31 04:03:20 +00:00
|
|
|
}
|
2025-12-08 13:58:14 +00:00
|
|
|
} catch {
|
|
|
|
|
// no-op
|
2025-07-31 04:03:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return changesMade; // Return whether cleanup occurred
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// The following methods are now delegated to the respective service classes.
|
|
|
|
|
// They are kept here as public methods to maintain the public API of DataStorage,
|
|
|
|
|
// but they now simply call the corresponding method on the service instance.
|
|
|
|
|
// REMOVED await this.saveData() from all these methods.
|
|
|
|
|
|
2025-12-08 13:58:14 +00:00
|
|
|
async scheduleNoteForReview(path: string, daysFromNow = 0): Promise<void> {
|
2025-07-31 04:03:20 +00:00
|
|
|
await this.reviewScheduleService.scheduleNoteForReview(path, daysFromNow);
|
|
|
|
|
// await this.saveData(); // Removed
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-08 13:58:14 +00:00
|
|
|
recordReview(path: string, response: ReviewResponse, isSkipped = false): boolean {
|
|
|
|
|
return this.reviewScheduleService.recordReview(path, response, isSkipped);
|
2025-07-31 04:03:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
calculateNewSchedule(
|
|
|
|
|
currentInterval: number,
|
|
|
|
|
currentEase: number,
|
|
|
|
|
response: ReviewResponse
|
|
|
|
|
): { interval: number, ease: number } {
|
|
|
|
|
// This method is likely redundant now that recordReview uses calculateSM2Schedule directly,
|
|
|
|
|
// but keeping for potential external use or backward compatibility if needed.
|
|
|
|
|
// It should probably be moved to ReviewScheduleService if kept.
|
|
|
|
|
// For now, just calling the service method.
|
2025-12-08 13:58:14 +00:00
|
|
|
const result = this.reviewScheduleService.calculateNewSchedule(currentInterval, currentEase, response);
|
|
|
|
|
return { interval: result.interval, ease: result.ease };
|
2025-07-31 04:03:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async skipNote(path: string, response: ReviewResponse = ReviewResponse.CorrectWithDifficulty): Promise<void> {
|
|
|
|
|
await this.reviewScheduleService.skipNote(path, response);
|
|
|
|
|
// await this.saveData(); // Removed
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-08 13:58:14 +00:00
|
|
|
async postponeNote(path: string, days = 1): Promise<void> {
|
2025-07-31 04:03:20 +00:00
|
|
|
await this.reviewScheduleService.postponeNote(path, days);
|
|
|
|
|
// await this.saveData(); // Removed
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async removeFromReview(path: string): Promise<void> {
|
|
|
|
|
await this.reviewScheduleService.removeFromReview(path);
|
|
|
|
|
// await this.saveData(); // Removed
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async clearAllSchedules(): Promise<void> {
|
|
|
|
|
await this.reviewScheduleService.clearAllSchedules();
|
|
|
|
|
// await this.saveData(); // Removed
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async estimateReviewTime(path: string): Promise<number> {
|
|
|
|
|
// This method doesn't directly modify data, so it can stay or be moved to a utility
|
|
|
|
|
// Keeping it here for now, but it doesn't use service data directly.
|
|
|
|
|
const file = this.plugin.app.vault.getAbstractFileByPath(path);
|
|
|
|
|
if (!(file instanceof TFile)) return 60; // Default 1 minute
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const content = await this.plugin.app.vault.read(file);
|
|
|
|
|
return EstimationUtils.estimateReviewTime(file, content);
|
2025-12-08 13:58:14 +00:00
|
|
|
} catch {
|
2025-07-31 04:03:20 +00:00
|
|
|
return 60; // Default 1 minute
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async createReviewSession(folderPath: string, name: string): Promise<ReviewSession | null> {
|
|
|
|
|
const session = await this.reviewSessionService.createReviewSession(folderPath, name);
|
|
|
|
|
// if (session) { // Removed save call
|
|
|
|
|
// await this.saveData();
|
|
|
|
|
// }
|
|
|
|
|
return session;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async setActiveSession(sessionId: string | null): Promise<boolean> {
|
|
|
|
|
const success = await this.reviewSessionService.setActiveSession(sessionId);
|
|
|
|
|
// if (success) { // Removed save call
|
|
|
|
|
// await this.saveData();
|
|
|
|
|
// }
|
|
|
|
|
return success;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getActiveSession(): ReviewSession | null {
|
|
|
|
|
return this.reviewSessionService.getActiveSession();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getNextSessionFile(): string | null {
|
|
|
|
|
return this.reviewSessionService.getNextSessionFile();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async advanceActiveSession(): Promise<boolean> {
|
|
|
|
|
const moreFiles = await this.reviewSessionService.advanceActiveSession();
|
|
|
|
|
// await this.saveData(); // Removed
|
|
|
|
|
return moreFiles;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-08 13:58:14 +00:00
|
|
|
async scheduleNotesInOrder(paths: string[], daysFromNow = 0): Promise<number> {
|
2025-07-31 04:03:20 +00:00
|
|
|
const count = await this.reviewScheduleService.scheduleNotesInOrder(paths, daysFromNow);
|
|
|
|
|
// if (count > 0) { // Removed save call
|
|
|
|
|
// await this.saveData();
|
|
|
|
|
// }
|
|
|
|
|
return count;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-08 13:58:14 +00:00
|
|
|
scheduleSessionForReview(sessionId: string): number {
|
2025-07-31 04:03:20 +00:00
|
|
|
// This method calls scheduleNotesInOrder internally, which saves data.
|
|
|
|
|
// No need to save again here.
|
2025-12-08 13:58:14 +00:00
|
|
|
return this.reviewSessionService.scheduleSessionForReview(sessionId);
|
2025-07-31 04:03:20 +00:00
|
|
|
}
|
|
|
|
|
|
2025-12-08 13:58:14 +00:00
|
|
|
saveMCQSet(mcqSet: MCQSet): string {
|
2025-07-31 04:03:20 +00:00
|
|
|
const id = this.mcqService.saveMCQSet(mcqSet);
|
|
|
|
|
// await this.saveData(); // Removed
|
|
|
|
|
return id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getMCQSetForNote(notePath: string): MCQSet | null {
|
|
|
|
|
return this.mcqService.getMCQSetForNote(notePath);
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-08 13:58:14 +00:00
|
|
|
saveMCQSession(session: MCQSession): void {
|
2025-07-31 04:03:20 +00:00
|
|
|
this.mcqService.saveMCQSession(session);
|
|
|
|
|
// await this.saveData(); // Removed
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getMCQSessionsForNote(notePath: string): MCQSession[] {
|
|
|
|
|
return this.mcqService.getMCQSessionsForNote(notePath);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getLatestMCQSessionForNote(notePath: string): MCQSession | null {
|
|
|
|
|
return this.mcqService.getLatestMCQSessionForNote(notePath);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async updateCustomNoteOrder(order: string[]): Promise<void> {
|
|
|
|
|
await this.reviewScheduleService.updateCustomNoteOrder(order);
|
|
|
|
|
// await this.saveData(); // Removed
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-08 13:58:14 +00:00
|
|
|
getDueNotesWithCustomOrder(date: number = Date.now(), useCustomOrder = true): ReviewSchedule[] {
|
2025-07-31 04:03:20 +00:00
|
|
|
return this.reviewScheduleService.getDueNotesWithCustomOrder(date, useCustomOrder);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Removed internal helper methods that were moved to services
|
|
|
|
|
}
|