Skip to content

Commit

Permalink
refactor: Separate business logic from user interface code (#751)
Browse files Browse the repository at this point in the history
* First commit

* This doesn't build yet

* Something

* Something

* Something

* Something

* Something

* Something

* Something

* Something

* Something

* Something

* Something

* Something

* Something

* Something

* Something

* Something

* Something

* Something

* Something

* Something

* Something

* Something

* Something

* Something

* Something

* Something

* Something

* Something

* Something

* Something

* Something

* Something

* Something

* Something

* Something

* Something

* Something

* Something

* Something

* Something

* Something

* Something

* Added support for burySiblingCards

* Fixed the card count displayed on the status bar

* Minor

* Minor

* Refactoring in preparation for random DeckTreeIter

* Support random sequencing of cards during review

* Fixed statistics charts

* after npm run format

* Code for updating question text made resilient to whitespace differences

* Added more test cases

* Added test case

* Fixes for certain cases of whitespace following a question's topic tag

* npm run format; update version in manifest.json

* Support the Enter key on the numeric keypad in the shortcuts #706

* In flashcard modal, allow user to click across entire tree item rectangle #709

* npm run format; temporary filename changes

* Updated formatting for lint, file rename

* fix issues reported by pnpm lint

* Restored version number to "1.10.1"

* Restored pnpm-lock.yaml from version 1.10.1

* Updated change log to reference #706, and #709

* Fixing capitalization of "Note.ts" (step 1)

* Fixing capitalization of "Note.ts" step #2

* Reduced collectCoverageFrom to subset of files with 100% code coverage
  • Loading branch information
ronzulu authored Oct 4, 2023
1 parent 388dec9 commit 5e8f988
Show file tree
Hide file tree
Showing 53 changed files with 5,969 additions and 1,498 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
17 changes: 17 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file. Dates are d

Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).

### [Unreleased]

- [Fixed] Enable clicking anywhere within deck item (not just deck name) in flashcard modal's deck tree [`#709`](https://github.com/st3v3nmw/obsidian-spaced-repetition/issues/709)
- [Added] Support the Enter key on the numeric keypad [`#706`](https://github.com/st3v3nmw/obsidian-spaced-repetition/issues/706)

### [Unreleased]

- This has not yet been approved by the project owner (https://github.com/st3v3nmw/obsidian-spaced-repetition)
- It is available at (https://github.com/ronzulu/obsidian-spaced-repetition)
- I want to make it available prior to the code being merged into the master branch by the project owner

- [Changed] Separated out "business logic" from the user interface code for flashcards, as well as other refactoring
- Intention to keep existing functionality without change
- Development: Thoughts about implementation redesign? [`#736`](https://github.com/st3v3nmw/obsidian-spaced-repetition/discussions/736)
- [Changed] Added many unit test cases - now around 170 (up from 20)
- [Fixed] Revisons cannot be saved properly [`#745`](https://github.com/st3v3nmw/obsidian-spaced-repetition/issues/745)

#### [1.10.1](https://github.com/st3v3nmw/obsidian-spaced-repetition/compare/1.10.0...1.10.1)

- style: Fix formatting [`#678`](https://github.com/st3v3nmw/obsidian-spaced-repetition/pull/678)
Expand Down
12 changes: 11 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,17 @@ module.exports = {
},
moduleFileExtensions: ["js", "jsx", "ts", "tsx", "json", "node", "d.ts"],
roots: ["<rootDir>/src/", "<rootDir>/tests/unit/"],
collectCoverageFrom: ["src/**/lang/*.ts", "src/parser.ts", "src/scheduling.ts", "utils.ts"],
collectCoverageFrom: [
"src/**/lang/*.ts",
"src/NoteEaseList.ts",
"src/NoteFileLoader.ts",
"src/NoteParser.ts",
"src/NoteQuestionParser.ts",
"src/TopicParser.ts",
"src/parser.ts",
"src/scheduling.ts",
"utils.ts",
],
coveragePathIgnorePatterns: [
"/node_modules/",
"src/lang/locale/",
Expand Down
41 changes: 41 additions & 0 deletions src/Card.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Question } from "./Question";
import { CardScheduleInfo } from "./CardSchedule";
import { CardListType } from "./Deck";

export class Card {
question: Question;
cardIdx: number;

// scheduling
get hasSchedule(): boolean {
return this.scheduleInfo != null;
}
scheduleInfo?: CardScheduleInfo;

// visuals
front: string;
back: string;

constructor(init?: Partial<Card>) {
Object.assign(this, init);
}

get cardListType(): CardListType {
return this.hasSchedule ? CardListType.DueCard : CardListType.NewCard;
}

get isNew(): boolean {
return !this.hasSchedule;
}

get isDue(): boolean {
return this.hasSchedule && this.scheduleInfo.isDue();
}

formatSchedule(): string {
let result: string = "";
if (this.hasSchedule) result = this.scheduleInfo.formatSchedule();
else result = "New";
return result;
}
}
168 changes: 168 additions & 0 deletions src/CardSchedule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import { Moment } from "moment";
import {
LEGACY_SCHEDULING_EXTRACTOR,
MULTI_SCHEDULING_EXTRACTOR,
TICKS_PER_DAY,
} from "./constants";
import { INoteEaseList } from "./NoteEaseList";
import { ReviewResponse, schedule } from "./scheduling";
import { SRSettings } from "./settings";
import { formatDate_YYYY_MM_DD } from "./util/utils";
import { DateUtil, globalDateProvider } from "./util/DateProvider";

export class CardScheduleInfo {
dueDate: Moment;
interval: number;
ease: number;
delayBeforeReviewTicks: number;

constructor(dueDate: Moment, interval: number, ease: number, delayBeforeReviewTicks: number) {
this.dueDate = dueDate;
this.interval = interval;
this.ease = ease;
this.delayBeforeReviewTicks = delayBeforeReviewTicks;
}

get delayBeforeReviewDaysInt(): number {
return Math.ceil(this.delayBeforeReviewTicks / TICKS_PER_DAY);
}

isDue(): boolean {
return this.dueDate.isSameOrBefore(globalDateProvider.today);
}

static getDummySchedule(settings: SRSettings): CardScheduleInfo {
return CardScheduleInfo.fromDueDateStr(
"2000-01-01",
CardScheduleInfo.initialInterval,
settings.baseEase,
0,
);
}

static fromDueDateStr(
dueDateStr: string,
interval: number,
ease: number,
delayBeforeReviewTicks: number,
) {
const dueDateTicks: Moment = DateUtil.dateStrToMoment(dueDateStr);
return new CardScheduleInfo(dueDateTicks, interval, ease, delayBeforeReviewTicks);
}

static fromDueDateMoment(
dueDateTicks: Moment,
interval: number,
ease: number,
delayBeforeReviewTicks: number,
) {
return new CardScheduleInfo(dueDateTicks, interval, ease, delayBeforeReviewTicks);
}

static get initialInterval(): number {
return 1.0;
}

formatDueDate(): string {
return formatDate_YYYY_MM_DD(this.dueDate);
}

formatSchedule() {
return `!${this.formatDueDate()},${this.interval},${this.ease}`;
}
}

export interface ICardScheduleCalculator {
getResetCardSchedule(): CardScheduleInfo;
getNewCardSchedule(response: ReviewResponse, notePath: string): CardScheduleInfo;
calcUpdatedSchedule(response: ReviewResponse, schedule: CardScheduleInfo): CardScheduleInfo;
}

export class CardScheduleCalculator {
settings: SRSettings;
noteEaseList: INoteEaseList;
dueDatesFlashcards: Record<number, number> = {}; // Record<# of days in future, due count>

constructor(settings: SRSettings, noteEaseList: INoteEaseList) {
this.settings = settings;
this.noteEaseList = noteEaseList;
}

getResetCardSchedule(): CardScheduleInfo {
const interval = CardScheduleInfo.initialInterval;
const ease = this.settings.baseEase;
const dueDate = globalDateProvider.today.add(interval, "d");
const delayBeforeReview = 0;
return CardScheduleInfo.fromDueDateMoment(dueDate, interval, ease, delayBeforeReview);
}

getNewCardSchedule(response: ReviewResponse, notePath: string): CardScheduleInfo {
const initial_ease: number = this.noteEaseList.getEaseByPath(notePath);
const delayBeforeReview = 0;

const schedObj: Record<string, number> = schedule(
response,
CardScheduleInfo.initialInterval,
initial_ease,
delayBeforeReview,
this.settings,
this.dueDatesFlashcards,
);

const interval = schedObj.interval;
const ease = schedObj.ease;
const dueDate = globalDateProvider.today.add(interval, "d");
return CardScheduleInfo.fromDueDateMoment(dueDate, interval, ease, delayBeforeReview);
}

calcUpdatedSchedule(
response: ReviewResponse,
cardSchedule: CardScheduleInfo,
): CardScheduleInfo {
const schedObj: Record<string, number> = schedule(
response,
cardSchedule.interval,
cardSchedule.ease,
cardSchedule.delayBeforeReviewTicks,
this.settings,
this.dueDatesFlashcards,
);
const interval = schedObj.interval;
const ease = schedObj.ease;
const dueDate = globalDateProvider.today.add(interval, "d");
const delayBeforeReview = 0;
return CardScheduleInfo.fromDueDateMoment(dueDate, interval, ease, delayBeforeReview);
}
}

export class NoteCardScheduleParser {
static createCardScheduleInfoList(questionText: string): CardScheduleInfo[] {
let scheduling: RegExpMatchArray[] = [...questionText.matchAll(MULTI_SCHEDULING_EXTRACTOR)];
if (scheduling.length === 0)
scheduling = [...questionText.matchAll(LEGACY_SCHEDULING_EXTRACTOR)];

const result: CardScheduleInfo[] = [];
for (let i = 0; i < scheduling.length; i++) {
const match: RegExpMatchArray = scheduling[i];
const dueDateStr = match[1];
const interval = parseInt(match[2]);
const ease = parseInt(match[3]);
const dueDate: Moment = DateUtil.dateStrToMoment(dueDateStr);
const delayBeforeReviewTicks: number =
dueDate.valueOf() - globalDateProvider.today.valueOf();

const info: CardScheduleInfo = new CardScheduleInfo(
dueDate,
interval,
ease,
delayBeforeReviewTicks,
);
result.push(info);
}
return result;
}

static removeCardScheduleInfo(questionText: string): string {
return questionText.replace(/<!--SR:.+-->/gm, "");
}
}
Loading

0 comments on commit 5e8f988

Please sign in to comment.