// https://npm.io/package/supermemo import { DataType } from '@/data/DataType.enum' import { data } from '@/data/data' import { useFile } from '@/hooks/useFile.hook' import { useLinks } from '@/hooks/useLinks.hook' import { useMarkdown } from '@/hooks/useMarkdown.hook' import { Card } from '@/modules/card/models/Card' import { RepetitionCard } from '@/modules/card/models/RepetitionCard' import { useUserRepoStore } from '@/modules/repo/store/userRepo.store' import { decodeBase64ToUTF8 } from '@/utils/decodeBase64ToUTF8' import { useAsyncState } from '@vueuse/core' import { addDays, isAfter } from 'date-fns' import { computed, nextTick, watch } from 'vue' const MAX_LEVEL = 8 export interface Repetition { repetition: RepetitionCard card: Card } export const useSpacedRepetitionCards = () => { const { toHTML } = useMarkdown() const store = useUserRepoStore() const { listenToClick } = useLinks('flip-card') const cardFiles = computed(() => store.files.filter( (file) => file.path !== undefined && file.path.startsWith('_cards') && file.path.endsWith('.md') ) ) const { state: cards, isReady, execute } = useAsyncState( async () => { const cards: Repetition[] = [] for (const cardFile of cardFiles.value) { if (!cardFile.sha) { continue } const repetition = await data.getOrCreate< DataType.RepetitionCard, RepetitionCard >(data.generateId(DataType.RepetitionCard, cardFile.path), { $type: DataType.RepetitionCard, level: 1, repeatDate: new Date() }) if ( isAfter(new Date(repetition.repeatDate), new Date()) || repetition.level === MAX_LEVEL ) { continue } const { getRawContent } = useFile(cardFile.sha, false) const content = (await getRawContent()) ?? '' const [front, back, references] = decodeBase64ToUTF8(content).split('___') ?? [] cards.push({ repetition, card: { front: toHTML(front), back: toHTML(back), references: toHTML(references) } }) } return cards }, [], { immediate: false } ) const successRepetition = async (cardId: string) => { const repetition = await data.get( cardId ) if (!repetition) { return } await data.update({ ...repetition, level: Math.min(repetition.level + 1, MAX_LEVEL), repeatDate: addDays(new Date(), 2 ** repetition.level) }) } const failRepetition = async (cardId: string) => { const repetition = await data.get( cardId ) if (!repetition) { return } const level = 1 await data.update({ ...repetition, level, repeatDate: addDays(new Date(), level) }) await execute() } watch( cards, () => nextTick(() => { listenToClick() }), { immediate: true } ) watch(cardFiles, () => execute()) return { cards, successRepetition, failRepetition, isLoading: !isReady } }