diff --git a/src/algorithms/base/isrs-algorithm.ts b/src/algorithms/base/isrs-algorithm.ts index 4d0339b0..927d8182 100644 --- a/src/algorithms/base/isrs-algorithm.ts +++ b/src/algorithms/base/isrs-algorithm.ts @@ -7,6 +7,7 @@ import { INoteEaseList } from "src/note-ease-list"; export enum Algorithm { SM_2_OSR = "SM-2-OSR", + CUSTOM_INTERVALS = "Custom-Intervals", } export interface ISrsAlgorithm { diff --git a/src/algorithms/custom/srs-algorithm-custom.ts b/src/algorithms/custom/srs-algorithm-custom.ts new file mode 100644 index 00000000..52e2acf6 --- /dev/null +++ b/src/algorithms/custom/srs-algorithm-custom.ts @@ -0,0 +1,101 @@ +import { ISrsAlgorithm } from "src/algorithms/base/isrs-algorithm"; +import { RepItemScheduleInfo } from "src/algorithms/base/rep-item-schedule-info"; +import { ReviewResponse } from "src/algorithms/base/repetition-item"; +import { OsrNoteGraph } from "src/algorithms/osr/osr-note-graph"; +import { RepItemScheduleInfoOsr } from "src/algorithms/osr/rep-item-schedule-info-osr"; +// TICKS_PER_DAY not needed for custom intervals +// import { TICKS_PER_DAY } from "src/constants"; +import { DueDateHistogram } from "src/due-date-histogram"; +import { Note } from "src/note"; +import { INoteEaseList } from "src/note-ease-list"; +import { SRSettings } from "src/settings"; +import { globalDateProvider } from "src/utils/dates"; + +export class SrsAlgorithmCustom implements ISrsAlgorithm { + private settings: SRSettings; + + constructor(settings: SRSettings) { + this.settings = settings; + } + + noteOnLoadedNote(_path: string, _note: Note, _noteEase: number): void { + // For custom intervals, we don't need to track note ease like SM-2 + // This is a no-op for the custom intervals algorithm + } + + noteCalcNewSchedule( + notePath: string, + osrNoteGraph: OsrNoteGraph, + response: ReviewResponse, + _dueDateNoteHistogram: DueDateHistogram, + ): RepItemScheduleInfo { + const interval = this.getCustomInterval(response); + const dueDate = globalDateProvider.today.add(interval, "d"); + + return new RepItemScheduleInfoOsr(dueDate, interval, 250); // Use default ease for custom intervals + } + + noteCalcUpdatedSchedule( + notePath: string, + noteSchedule: RepItemScheduleInfo, + response: ReviewResponse, + _dueDateNoteHistogram: DueDateHistogram, + ): RepItemScheduleInfo { + const interval = this.getCustomInterval(response); + const dueDate = globalDateProvider.today.add(interval, "d"); + + return new RepItemScheduleInfoOsr(dueDate, interval, 250); // Use default ease for custom intervals + } + + noteStats(): INoteEaseList { + // Return empty note stats for custom intervals + return { + dict: {}, + hasEaseForPath: () => false, + getEaseByPath: () => 250, + baseEase: () => 250, + }; + } + + cardGetResetSchedule(): RepItemScheduleInfo { + // Reset to new card state + return null; + } + + cardGetNewSchedule( + response: ReviewResponse, + _notePath: string, + _dueDateFlashcardHistogram: DueDateHistogram, + ): RepItemScheduleInfo { + const interval = this.getCustomInterval(response); + const dueDate = globalDateProvider.today.add(interval, "d"); + + return new RepItemScheduleInfoOsr(dueDate, interval, 250); // Use default ease for custom intervals + } + + cardCalcUpdatedSchedule( + response: ReviewResponse, + _schedule: RepItemScheduleInfo, + _dueDateFlashcardHistogram: DueDateHistogram, + ): RepItemScheduleInfo { + const interval = this.getCustomInterval(response); + const dueDate = globalDateProvider.today.add(interval, "d"); + + return new RepItemScheduleInfoOsr(dueDate, interval, 250); // Use default ease for custom intervals + } + + private getCustomInterval(response: ReviewResponse): number { + switch (response) { + case ReviewResponse.Easy: + return this.settings.customIntervalEasy; + case ReviewResponse.Good: + return this.settings.customIntervalGood; + case ReviewResponse.Hard: + return this.settings.customIntervalHard; + case ReviewResponse.Reset: + return 0; // Reset means new card + default: + return this.settings.customIntervalGood; // Default to Good interval + } + } +} diff --git a/src/data-store-algorithm/data-store-in-note-algorithm-osr.ts b/src/data-store-algorithm/data-store-in-note-algorithm-osr.ts index 6fe4b67b..29ca6bbc 100644 --- a/src/data-store-algorithm/data-store-in-note-algorithm-osr.ts +++ b/src/data-store-algorithm/data-store-in-note-algorithm-osr.ts @@ -1,6 +1,7 @@ import { Moment } from "moment"; import moment from "moment"; +import { Algorithm } from "src/algorithms/base/isrs-algorithm"; import { RepItemScheduleInfo } from "src/algorithms/base/rep-item-schedule-info"; import { RepItemScheduleInfoOsr } from "src/algorithms/osr/rep-item-schedule-info-osr"; import { Card } from "src/card"; @@ -32,15 +33,22 @@ export class DataStoreInNoteAlgorithmOsr implements IDataStoreAlgorithm { let result: RepItemScheduleInfo = null; const frontmatter: Map = await note.getFrontmatter(); - if ( - frontmatter && - frontmatter.has("sr-due") && - frontmatter.has("sr-interval") && - frontmatter.has("sr-ease") - ) { + if (frontmatter && frontmatter.has("sr-due")) { const dueDate: Moment = moment(frontmatter.get("sr-due"), ALLOWED_DATE_FORMATS); - const interval: number = parseFloat(frontmatter.get("sr-interval")); - const ease: number = parseFloat(frontmatter.get("sr-ease")); + + // For Custom Intervals, interval might not be stored, so use a default value + // The actual interval will be determined by the algorithm when scheduling + let interval: number = 1; // Default interval + if (frontmatter.has("sr-interval")) { + interval = parseFloat(frontmatter.get("sr-interval")); + } + + // For Custom Intervals, ease might not be stored, so use default value + let ease: number = 250; // Default ease + if (frontmatter.has("sr-ease")) { + ease = parseFloat(frontmatter.get("sr-ease")); + } + result = new RepItemScheduleInfoOsr(dueDate, interval, ease); } return result; @@ -54,13 +62,18 @@ export class DataStoreInNoteAlgorithmOsr implements IDataStoreAlgorithm { const interval: number = schedInfo.interval; const ease: number = schedInfo.latestEase; + // Determine what to include based on algorithm + const includeSM2Data = this.settings.algorithm === Algorithm.SM_2_OSR; + const intervalString = includeSM2Data ? `sr-interval: ${interval}\n` : ""; + const easeString = includeSM2Data ? `sr-ease: ${ease}\n` : ""; + // check if scheduling info exists if (SCHEDULING_INFO_REGEX.test(fileText)) { const schedulingInfo = SCHEDULING_INFO_REGEX.exec(fileText); fileText = fileText.replace( SCHEDULING_INFO_REGEX, `---\n${schedulingInfo[1]}sr-due: ${dueString}\n` + - `sr-interval: ${interval}\nsr-ease: ${ease}\n` + + `${intervalString}${easeString}` + `${schedulingInfo[5]}---`, ); } else if (YAML_FRONT_MATTER_REGEX.test(fileText)) { @@ -69,12 +82,11 @@ export class DataStoreInNoteAlgorithmOsr implements IDataStoreAlgorithm { fileText = fileText.replace( YAML_FRONT_MATTER_REGEX, `---\n${existingYaml[1]}sr-due: ${dueString}\n` + - `sr-interval: ${interval}\nsr-ease: ${ease}\n---`, + `${intervalString}${easeString}---`, ); } else { fileText = - `---\nsr-due: ${dueString}\nsr-interval: ${interval}\n` + - `sr-ease: ${ease}\n---\n\n${fileText}`; + `---\nsr-due: ${dueString}\n` + `${intervalString}${easeString}---\n\n${fileText}`; } await note.write(fileText); diff --git a/src/gui/settings.tsx b/src/gui/settings.tsx index 20dc2092..9d5de3d2 100644 --- a/src/gui/settings.tsx +++ b/src/gui/settings.tsx @@ -833,185 +833,293 @@ export class SRSettingTab extends PluginSettingTab { dropdown .addOptions({ "SM-2-OSR": t("SM2_OSR_VARIANT"), + "Custom-Intervals": t("CUSTOM_INTERVALS"), }) .setValue(this.plugin.data.settings.algorithm) .onChange(async (value) => { this.plugin.data.settings.algorithm = value; await this.plugin.savePluginData(); + // Reinitialize algorithm instance with new setting + this.plugin.setupDataStoreAndAlgorithmInstances(this.plugin.data.settings); + // Refresh the settings display to show/hide custom interval settings + this.display(); }), ); - new Setting(containerEl) - .setName(t("BASE_EASE")) - .setDesc(t("BASE_EASE_DESC")) - .addText((text) => - text.setValue(this.plugin.data.settings.baseEase.toString()).onChange((value) => { - applySettingsUpdate(async () => { - const numValue: number = Number.parseInt(value); - if (!isNaN(numValue)) { - if (numValue < 130) { - new Notice(t("BASE_EASE_MIN_WARNING")); - text.setValue(this.plugin.data.settings.baseEase.toString()); - return; - } - - this.plugin.data.settings.baseEase = numValue; + // Custom interval settings (only show when Custom Intervals algorithm is selected) + if (this.plugin.data.settings.algorithm === "Custom-Intervals") { + new Setting(containerEl) + .setName(t("CUSTOM_INTERVAL_EASY")) + .setDesc(t("CUSTOM_INTERVAL_EASY_DESC")) + .addText((text) => + text + .setValue(this.plugin.data.settings.customIntervalEasy.toString()) + .onChange((value) => { + applySettingsUpdate(async () => { + const numValue: number = Number.parseInt(value); + if (!isNaN(numValue) && numValue > 0) { + this.plugin.data.settings.customIntervalEasy = numValue; + await this.plugin.savePluginData(); + } else { + new Notice(t("VALID_NUMBER_WARNING")); + } + }); + }), + ) + .addExtraButton((button) => { + button + .setIcon("reset") + .setTooltip(t("RESET_DEFAULT")) + .onClick(async () => { + this.plugin.data.settings.customIntervalEasy = + DEFAULT_SETTINGS.customIntervalEasy; await this.plugin.savePluginData(); - } else { - new Notice(t("VALID_NUMBER_WARNING")); - } - }); - }), - ) - .addExtraButton((button) => { - button - .setIcon("reset") - .setTooltip(t("RESET_DEFAULT")) - .onClick(async () => { - this.plugin.data.settings.baseEase = DEFAULT_SETTINGS.baseEase; - await this.plugin.savePluginData(); - - this.display(); - }); - }); - - new Setting(containerEl) - .setName(t("LAPSE_INTERVAL_CHANGE")) - .setDesc(t("LAPSE_INTERVAL_CHANGE_DESC")) - .addSlider((slider) => - slider - .setLimits(1, 99, 1) - .setValue(this.plugin.data.settings.lapsesIntervalChange * 100) - .setDynamicTooltip() - .onChange(async (value: number) => { - this.plugin.data.settings.lapsesIntervalChange = value / 100; - await this.plugin.savePluginData(); - }), - ) - .addExtraButton((button) => { - button - .setIcon("reset") - .setTooltip(t("RESET_DEFAULT")) - .onClick(async () => { - this.plugin.data.settings.lapsesIntervalChange = - DEFAULT_SETTINGS.lapsesIntervalChange; - await this.plugin.savePluginData(); - - this.display(); - }); - }); - - new Setting(containerEl) - .setName(t("EASY_BONUS")) - .setDesc(t("EASY_BONUS_DESC")) - .addText((text) => - text - .setValue((this.plugin.data.settings.easyBonus * 100).toString()) - .onChange((value) => { - applySettingsUpdate(async () => { - const numValue: number = Number.parseInt(value) / 100; - if (!isNaN(numValue)) { - if (numValue < 1.0) { - new Notice(t("EASY_BONUS_MIN_WARNING")); - text.setValue( - (this.plugin.data.settings.easyBonus * 100).toString(), - ); - return; + this.display(); + }); + }); + + new Setting(containerEl) + .setName(t("CUSTOM_INTERVAL_GOOD")) + .setDesc(t("CUSTOM_INTERVAL_GOOD_DESC")) + .addText((text) => + text + .setValue(this.plugin.data.settings.customIntervalGood.toString()) + .onChange((value) => { + applySettingsUpdate(async () => { + const numValue: number = Number.parseInt(value); + if (!isNaN(numValue) && numValue > 0) { + this.plugin.data.settings.customIntervalGood = numValue; + await this.plugin.savePluginData(); + } else { + new Notice(t("VALID_NUMBER_WARNING")); } - - this.plugin.data.settings.easyBonus = numValue; - await this.plugin.savePluginData(); - } else { - new Notice(t("VALID_NUMBER_WARNING")); - } + }); + }), + ) + .addExtraButton((button) => { + button + .setIcon("reset") + .setTooltip(t("RESET_DEFAULT")) + .onClick(async () => { + this.plugin.data.settings.customIntervalGood = + DEFAULT_SETTINGS.customIntervalGood; + await this.plugin.savePluginData(); + this.display(); }); - }), - ) - .addExtraButton((button) => { - button - .setIcon("reset") - .setTooltip(t("RESET_DEFAULT")) - .onClick(async () => { - this.plugin.data.settings.easyBonus = DEFAULT_SETTINGS.easyBonus; - await this.plugin.savePluginData(); + }); + + new Setting(containerEl) + .setName(t("CUSTOM_INTERVAL_HARD")) + .setDesc(t("CUSTOM_INTERVAL_HARD_DESC")) + .addText((text) => + text + .setValue(this.plugin.data.settings.customIntervalHard.toString()) + .onChange((value) => { + applySettingsUpdate(async () => { + const numValue: number = Number.parseInt(value); + if (!isNaN(numValue) && numValue > 0) { + this.plugin.data.settings.customIntervalHard = numValue; + await this.plugin.savePluginData(); + } else { + new Notice(t("VALID_NUMBER_WARNING")); + } + }); + }), + ) + .addExtraButton((button) => { + button + .setIcon("reset") + .setTooltip(t("RESET_DEFAULT")) + .onClick(async () => { + this.plugin.data.settings.customIntervalHard = + DEFAULT_SETTINGS.customIntervalHard; + await this.plugin.savePluginData(); + this.display(); + }); + }); + } - this.display(); - }); - }); + // SM-2 OSR specific settings (only show when SM-2 OSR algorithm is selected) + if (this.plugin.data.settings.algorithm === "SM-2-OSR") { + new Setting(containerEl) + .setName(t("BASE_EASE")) + .setDesc(t("BASE_EASE_DESC")) + .addText((text) => + text + .setValue(this.plugin.data.settings.baseEase.toString()) + .onChange((value) => { + applySettingsUpdate(async () => { + const numValue: number = Number.parseInt(value); + if (!isNaN(numValue)) { + if (numValue < 130) { + new Notice(t("BASE_EASE_MIN_WARNING")); + text.setValue( + this.plugin.data.settings.baseEase.toString(), + ); + return; + } + + this.plugin.data.settings.baseEase = numValue; + await this.plugin.savePluginData(); + } else { + new Notice(t("VALID_NUMBER_WARNING")); + } + }); + }), + ) + .addExtraButton((button) => { + button + .setIcon("reset") + .setTooltip(t("RESET_DEFAULT")) + .onClick(async () => { + this.plugin.data.settings.baseEase = DEFAULT_SETTINGS.baseEase; + await this.plugin.savePluginData(); - new Setting(containerEl) - .setName(t("LOAD_BALANCE")) - .setDesc(t("LOAD_BALANCE_DESC")) - .addToggle((toggle) => - toggle.setValue(this.plugin.data.settings.loadBalance).onChange(async (value) => { - this.plugin.data.settings.loadBalance = value; - await this.plugin.savePluginData(); - }), - ); + this.display(); + }); + }); + + new Setting(containerEl) + .setName(t("LAPSE_INTERVAL_CHANGE")) + .setDesc(t("LAPSE_INTERVAL_CHANGE_DESC")) + .addSlider((slider) => + slider + .setLimits(1, 99, 1) + .setValue(this.plugin.data.settings.lapsesIntervalChange * 100) + .setDynamicTooltip() + .onChange(async (value: number) => { + this.plugin.data.settings.lapsesIntervalChange = value / 100; + await this.plugin.savePluginData(); + }), + ) + .addExtraButton((button) => { + button + .setIcon("reset") + .setTooltip(t("RESET_DEFAULT")) + .onClick(async () => { + this.plugin.data.settings.lapsesIntervalChange = + DEFAULT_SETTINGS.lapsesIntervalChange; + await this.plugin.savePluginData(); - new Setting(containerEl) - .setName(t("MAX_INTERVAL")) - .setDesc(t("MAX_INTERVAL_DESC")) - .addText((text) => - text - .setValue(this.plugin.data.settings.maximumInterval.toString()) - .onChange((value) => { - applySettingsUpdate(async () => { - const numValue: number = Number.parseInt(value); - if (!isNaN(numValue)) { - if (numValue < 1) { - new Notice(t("MAX_INTERVAL_MIN_WARNING")); - text.setValue( - this.plugin.data.settings.maximumInterval.toString(), - ); - return; + this.display(); + }); + }); + + new Setting(containerEl) + .setName(t("EASY_BONUS")) + .setDesc(t("EASY_BONUS_DESC")) + .addText((text) => + text + .setValue((this.plugin.data.settings.easyBonus * 100).toString()) + .onChange((value) => { + applySettingsUpdate(async () => { + const numValue: number = Number.parseInt(value) / 100; + if (!isNaN(numValue)) { + if (numValue < 1.0) { + new Notice(t("EASY_BONUS_MIN_WARNING")); + text.setValue( + (this.plugin.data.settings.easyBonus * 100).toString(), + ); + return; + } + + this.plugin.data.settings.easyBonus = numValue; + await this.plugin.savePluginData(); + } else { + new Notice(t("VALID_NUMBER_WARNING")); } + }); + }), + ) + .addExtraButton((button) => { + button + .setIcon("reset") + .setTooltip(t("RESET_DEFAULT")) + .onClick(async () => { + this.plugin.data.settings.easyBonus = DEFAULT_SETTINGS.easyBonus; + await this.plugin.savePluginData(); - this.plugin.data.settings.maximumInterval = numValue; - await this.plugin.savePluginData(); - } else { - new Notice(t("VALID_NUMBER_WARNING")); - } + this.display(); }); - }), - ) - .addExtraButton((button) => { - button - .setIcon("reset") - .setTooltip(t("RESET_DEFAULT")) - .onClick(async () => { - this.plugin.data.settings.maximumInterval = - DEFAULT_SETTINGS.maximumInterval; - await this.plugin.savePluginData(); - - this.display(); - }); - }); + }); + + new Setting(containerEl) + .setName(t("LOAD_BALANCE")) + .setDesc(t("LOAD_BALANCE_DESC")) + .addToggle((toggle) => + toggle + .setValue(this.plugin.data.settings.loadBalance) + .onChange(async (value) => { + this.plugin.data.settings.loadBalance = value; + await this.plugin.savePluginData(); + }), + ); + + new Setting(containerEl) + .setName(t("MAX_INTERVAL")) + .setDesc(t("MAX_INTERVAL_DESC")) + .addText((text) => + text + .setValue(this.plugin.data.settings.maximumInterval.toString()) + .onChange((value) => { + applySettingsUpdate(async () => { + const numValue: number = Number.parseInt(value); + if (!isNaN(numValue)) { + if (numValue < 1) { + new Notice(t("MAX_INTERVAL_MIN_WARNING")); + text.setValue( + this.plugin.data.settings.maximumInterval.toString(), + ); + return; + } + + this.plugin.data.settings.maximumInterval = numValue; + await this.plugin.savePluginData(); + } else { + new Notice(t("VALID_NUMBER_WARNING")); + } + }); + }), + ) + .addExtraButton((button) => { + button + .setIcon("reset") + .setTooltip(t("RESET_DEFAULT")) + .onClick(async () => { + this.plugin.data.settings.maximumInterval = + DEFAULT_SETTINGS.maximumInterval; + await this.plugin.savePluginData(); - new Setting(containerEl) - .setName(t("MAX_LINK_CONTRIB")) - .setDesc(t("MAX_LINK_CONTRIB_DESC")) - .addSlider((slider) => - slider - .setLimits(0, 100, 1) - .setValue(this.plugin.data.settings.maxLinkFactor * 100) - .setDynamicTooltip() - .onChange(async (value: number) => { - this.plugin.data.settings.maxLinkFactor = value / 100; - await this.plugin.savePluginData(); - }), - ) - .addExtraButton((button) => { - button - .setIcon("reset") - .setTooltip(t("RESET_DEFAULT")) - .onClick(async () => { - this.plugin.data.settings.maxLinkFactor = DEFAULT_SETTINGS.maxLinkFactor; - await this.plugin.savePluginData(); + this.display(); + }); + }); + + new Setting(containerEl) + .setName(t("MAX_LINK_CONTRIB")) + .setDesc(t("MAX_LINK_CONTRIB_DESC")) + .addSlider((slider) => + slider + .setLimits(0, 100, 1) + .setValue(this.plugin.data.settings.maxLinkFactor * 100) + .setDynamicTooltip() + .onChange(async (value: number) => { + this.plugin.data.settings.maxLinkFactor = value / 100; + await this.plugin.savePluginData(); + }), + ) + .addExtraButton((button) => { + button + .setIcon("reset") + .setTooltip(t("RESET_DEFAULT")) + .onClick(async () => { + this.plugin.data.settings.maxLinkFactor = + DEFAULT_SETTINGS.maxLinkFactor; + await this.plugin.savePluginData(); - this.display(); - }); - }); + this.display(); + }); + }); + } containerEl.createEl("h3", { text: t("GROUP_DATA_STORAGE") }); new Setting(containerEl) diff --git a/src/lang/locale/en.ts b/src/lang/locale/en.ts index 5f9f0aed..9834ad4f 100644 --- a/src/lang/locale/en.ts +++ b/src/lang/locale/en.ts @@ -167,6 +167,7 @@ export default { CHECK_ALGORITHM_WIKI: 'For more information, check the algorithm details.', SM2_OSR_VARIANT: "OSR's variant of SM-2", + CUSTOM_INTERVALS: "Custom Intervals", BASE_EASE: "Base ease", BASE_EASE_DESC: "minimum = 130, preferrably approximately 250.", BASE_EASE_MIN_WARNING: "The base ease must be at least 130.", @@ -176,6 +177,12 @@ export default { EASY_BONUS_DESC: "The easy bonus allows you to set the difference in intervals between answering Good and Easy on a flashcard/note (minimum = 100%).", EASY_BONUS_MIN_WARNING: "The easy bonus must be at least 100.", + CUSTOM_INTERVAL_EASY: "Easy Interval (days)", + CUSTOM_INTERVAL_EASY_DESC: "Number of days before the next review when answering Easy.", + CUSTOM_INTERVAL_GOOD: "Good Interval (days)", + CUSTOM_INTERVAL_GOOD_DESC: "Number of days before the next review when answering Good.", + CUSTOM_INTERVAL_HARD: "Hard Interval (days)", + CUSTOM_INTERVAL_HARD_DESC: "Number of days before the next review when answering Hard.", LOAD_BALANCE: "Enable load balancer", LOAD_BALANCE_DESC: `Slightly tweaks the interval so that the number of reviews per day is more consistent. It's like Anki's fuzz but instead of being random, it picks the day with the least amount of reviews. diff --git a/src/main.ts b/src/main.ts index c92c9152..c71cdaa4 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,7 +1,9 @@ import { Menu, Notice, Plugin, TAbstractFile, TFile, WorkspaceLeaf } from "obsidian"; +import { Algorithm } from "src/algorithms/base/isrs-algorithm"; import { ReviewResponse } from "src/algorithms/base/repetition-item"; import { SrsAlgorithm } from "src/algorithms/base/srs-algorithm"; +import { SrsAlgorithmCustom } from "src/algorithms/custom/srs-algorithm-custom"; import { ObsidianVaultNoteLinkInfoFinder } from "src/algorithms/osr/obsidian-vault-notelink-info-finder"; import { SrsAlgorithmOsr } from "src/algorithms/osr/srs-algorithm-osr"; import { OsrAppCore } from "src/core"; @@ -523,9 +525,20 @@ export default class SRPlugin extends Plugin { } setupDataStoreAndAlgorithmInstances(settings: SRSettings) { - // For now we can hardcode as we only support the one data store and one algorithm + // Set up data store (only one supported for now) DataStore.instance = new StoreInNotes(settings); - SrsAlgorithm.instance = new SrsAlgorithmOsr(settings); + + // Select algorithm based on settings + switch (settings.algorithm) { + case Algorithm.CUSTOM_INTERVALS: + SrsAlgorithm.instance = new SrsAlgorithmCustom(settings); + break; + case Algorithm.SM_2_OSR: + default: + SrsAlgorithm.instance = new SrsAlgorithmOsr(settings); + break; + } + DataStoreAlgorithm.instance = new DataStoreInNoteAlgorithmOsr(settings); } async savePluginData(): Promise { diff --git a/src/settings.ts b/src/settings.ts index d2c8a3da..473c6008 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -56,6 +56,11 @@ export interface SRSettings { maximumInterval: number; maxLinkFactor: number; + // custom intervals (for notes and flashcards) + customIntervalEasy: number; + customIntervalGood: number; + customIntervalHard: number; + // storage dataStore: string; cardCommentOnSameLine: boolean; @@ -116,6 +121,11 @@ export const DEFAULT_SETTINGS: SRSettings = { maximumInterval: 36525, maxLinkFactor: 1.0, + // custom intervals (for notes and flashcards) - in days + customIntervalEasy: 4, + customIntervalGood: 2, + customIntervalHard: 1, + // storage dataStore: DataStoreName.NOTES, cardCommentOnSameLine: false, @@ -152,6 +162,17 @@ export function upgradeSettings(settings: SRSettings) { if (settings.convertCurlyBracketsToClozes) settings.clozePatterns.push("{{[123;;]answer[;;hint]}}"); } + + // Add custom interval settings if they don't exist (for existing users) + if (settings.customIntervalEasy == null) { + settings.customIntervalEasy = DEFAULT_SETTINGS.customIntervalEasy; + } + if (settings.customIntervalGood == null) { + settings.customIntervalGood = DEFAULT_SETTINGS.customIntervalGood; + } + if (settings.customIntervalHard == null) { + settings.customIntervalHard = DEFAULT_SETTINGS.customIntervalHard; + } } export class SettingsUtil {