浏览代码

:art: Flashcard support review mode https://github.com/siyuan-note/siyuan/issues/10303

Daniel 1 年之前
父节点
当前提交
882466e10d

+ 5 - 0
app/appearance/langs/en_US.json

@@ -1,4 +1,9 @@
 {
+  "reviewMode": "Review mode",
+  "reviewModeTip": "Select the order in which new and old cards appear during review",
+  "reviewMode0": "New and old mix",
+  "reviewMode1": "New first",
+  "reviewMode2": "Old first",
   "fileAnnoRefPlaceholder": "Please enter block ID",
   "addToDatabase": "Add to Database",
   "clearContext": "Clear context",

+ 5 - 0
app/appearance/langs/es_ES.json

@@ -1,4 +1,9 @@
 {
+  "reviewMode": "Modo de revisión",
+  "reviewModeTip": "Seleccione el orden en que aparecen las tarjetas nuevas y antiguas durante la revisión",
+  "reviewMode0": "Nuevo y viejo mezclado",
+  "reviewMode1": "Nuevo primero",
+  "reviewMode2": "Viejo primero",
   "fileAnnoRefPlaceholder": "Ingrese el ID del bloque",
   "addToDatabase": "Agregar a la base de datos",
   "clearContext": "Borrar contexto",

+ 5 - 0
app/appearance/langs/fr_FR.json

@@ -1,4 +1,9 @@
 {
+  "reviewMode": "Mode de révision",
+  "reviewModeTip": "Sélectionnez l'ordre dans lequel les nouvelles et anciennes cartes apparaissent lors de la révision",
+  "reviewMode0": "Nouveau et ancien mélange",
+  "reviewMode1": "Nouveau d'abord",
+  "reviewMode2": "Ancien d'abord",
   "fileAnnoRefPlaceholder": "Veuillez saisir l'ID de bloc",
   "addToDatabase": "Ajouter à la base de données",
   "clearContext": "Effacer le contexte",

+ 5 - 0
app/appearance/langs/zh_CHT.json

@@ -1,4 +1,9 @@
 {
+  "reviewMode": "複習模式",
+  "reviewModeTip": "選擇複習時新卡和舊卡出現的順序",
+  "reviewMode0": "新舊混合",
+  "reviewMode1": "新卡優先",
+  "reviewMode2": "舊卡優先",
   "fileAnnoRefPlaceholder": "請輸入區塊 ID",
   "addToDatabase": "新增至資料庫",
   "clearContext": "清空上下文",

+ 5 - 0
app/appearance/langs/zh_CN.json

@@ -1,4 +1,9 @@
 {
+  "reviewMode": "复习模式",
+  "reviewModeTip": "选择复习时新卡和旧卡出现的顺序",
+  "reviewMode0": "新旧混合",
+  "reviewMode1": "新卡优先",
+  "reviewMode2": "旧卡优先",
   "fileAnnoRefPlaceholder": "请输入块 ID",
   "addToDatabase": "添加到数据库",
   "clearContext": "清空上下文",

+ 15 - 2
app/src/config/flashcard.ts

@@ -42,7 +42,19 @@ export const flashcard = {
     </div>
     <span class="fn__space"></span>
     <input class="b3-switch fn__flex-center" id="deck" type="checkbox"${window.siyuan.config.flashcard.deck ? " checked" : ""}/>
-</label>`;
+</label>
+<div class="fn__flex b3-label config__item">
+    <div class="fn__flex-1">
+        ${window.siyuan.languages.reviewMode}
+        <div class="b3-label__text">${window.siyuan.languages.reviewModeTip}</div>
+    </div>
+    <span class="fn__space"></span>
+    <select class="b3-select fn__flex-center fn__size200" id="reviewMode">
+      <option value="0" ${window.siyuan.config.flashcard.reviewMode === 0 ? "selected" : ""}>${window.siyuan.languages.reviewMode0}</option>
+      <option value="1" ${window.siyuan.config.flashcard.reviewMode === 1 ? "selected" : ""}>${window.siyuan.languages.reviewMode1}</option>
+      <option value="2" ${window.siyuan.config.flashcard.reviewMode === 2 ? "selected" : ""}>${window.siyuan.languages.reviewMode2}</option>
+    </select>    
+</div>`;
         /// #if MOBILE
         responsiveHTML = `${responsiveHTML}<div class="b3-label">
     ${window.siyuan.languages.flashcardNewCardLimit}
@@ -119,9 +131,10 @@ export const flashcard = {
         return responsiveHTML;
     },
     bindEvent: () => {
-        flashcard.element.querySelectorAll("input").forEach((item) => {
+        flashcard.element.querySelectorAll("input, select.b3-select").forEach((item) => {
             item.addEventListener("change", () => {
                 fetchPost("/api/setting/setFlashcard", {
+                    reviewMode: parseInt((flashcard.element.querySelector("#reviewMode") as HTMLSelectElement).value),
                     newCardLimit: parseInt((flashcard.element.querySelector("#newCardLimit") as HTMLInputElement).value),
                     reviewCardLimit: parseInt((flashcard.element.querySelector("#reviewCardLimit") as HTMLInputElement).value),
                     mark: (flashcard.element.querySelector("#mark") as HTMLInputElement).checked,

+ 1 - 0
app/src/types/index.d.ts

@@ -716,6 +716,7 @@ interface IConfig {
         superBlock: boolean
         heading: boolean
         deck: boolean
+        reviewMode: number
         requestRetention: number
         maximumInterval: number
         weights: string

+ 2 - 0
kernel/conf/flashcard.go

@@ -30,6 +30,7 @@ type Flashcard struct {
 	SuperBlock      bool `json:"superBlock"`      // 是否启用超级块制卡 https://github.com/siyuan-note/siyuan/issues/7702
 	Heading         bool `json:"heading"`         // 是否启用标题块制卡 https://github.com/siyuan-note/siyuan/issues/9005
 	Deck            bool `json:"deck"`            // 是否启用卡包制卡 https://github.com/siyuan-note/siyuan/issues/7724
+	ReviewMode      int  `json:"reviewMode"`      // 复习模式,0:新旧混合,1:新卡优先,2:旧卡优先 https://github.com/siyuan-note/siyuan/issues/10303
 
 	// Apply result optimized by FSRS optimizer https://github.com/siyuan-note/siyuan/issues/9309
 	RequestRetention float64 `json:"requestRetention"`
@@ -55,6 +56,7 @@ func NewFlashcard() *Flashcard {
 		SuperBlock:       true,
 		Heading:          true,
 		Deck:             false,
+		ReviewMode:       0,
 		RequestRetention: param.RequestRetention,
 		MaximumInterval:  int(param.MaximumInterval),
 		Weights:          weightsBuilder.String(),

+ 20 - 5
kernel/model/flashcard.go

@@ -490,7 +490,7 @@ func GetNotebookDueFlashcards(boxID string, reviewedCardIDs []string) (ret []*Fl
 		return
 	}
 
-	cards, unreviewedCnt, unreviewedNewCardCnt, unreviewedOldCardCnt := getDeckDueCards(deck, reviewedCardIDs, treeBlockIDs, Conf.Flashcard.NewCardLimit, Conf.Flashcard.ReviewCardLimit)
+	cards, unreviewedCnt, unreviewedNewCardCnt, unreviewedOldCardCnt := getDeckDueCards(deck, reviewedCardIDs, treeBlockIDs, Conf.Flashcard.NewCardLimit, Conf.Flashcard.ReviewCardLimit, Conf.Flashcard.ReviewMode)
 	now := time.Now()
 	for _, card := range cards {
 		ret = append(ret, newFlashcard(card, card.BlockID(), builtinDeckID, now))
@@ -535,7 +535,7 @@ func GetTreeDueFlashcards(rootID string, reviewedCardIDs []string) (ret []*Flash
 		}
 	}
 
-	cards, unreviewedCnt, unreviewedNewCardCnt, unreviewedOldCardCnt := getDeckDueCards(deck, reviewedCardIDs, treeBlockIDs, newCardLimit, reviewCardLimit)
+	cards, unreviewedCnt, unreviewedNewCardCnt, unreviewedOldCardCnt := getDeckDueCards(deck, reviewedCardIDs, treeBlockIDs, newCardLimit, reviewCardLimit, Conf.Flashcard.ReviewMode)
 	now := time.Now()
 	for _, card := range cards {
 		ret = append(ret, newFlashcard(card, card.BlockID(), builtinDeckID, now))
@@ -606,7 +606,7 @@ func getDueFlashcards(deckID string, reviewedCardIDs []string) (ret []*Flashcard
 		return
 	}
 
-	cards, unreviewedCnt, unreviewedNewCardCnt, unreviewedOldCardCnt := getDeckDueCards(deck, reviewedCardIDs, nil, Conf.Flashcard.NewCardLimit, Conf.Flashcard.ReviewCardLimit)
+	cards, unreviewedCnt, unreviewedNewCardCnt, unreviewedOldCardCnt := getDeckDueCards(deck, reviewedCardIDs, nil, Conf.Flashcard.NewCardLimit, Conf.Flashcard.ReviewCardLimit, Conf.Flashcard.ReviewMode)
 	now := time.Now()
 	for _, card := range cards {
 		ret = append(ret, newFlashcard(card, card.BlockID(), deckID, now))
@@ -623,7 +623,7 @@ func getDueFlashcards(deckID string, reviewedCardIDs []string) (ret []*Flashcard
 func getAllDueFlashcards(reviewedCardIDs []string) (ret []*Flashcard, unreviewedCount, unreviewedNewCardCount, unreviewedOldCardCount int) {
 	now := time.Now()
 	for _, deck := range Decks {
-		cards, unreviewedCnt, unreviewedNewCardCnt, unreviewedOldCardCnt := getDeckDueCards(deck, reviewedCardIDs, nil, Conf.Flashcard.NewCardLimit, Conf.Flashcard.ReviewCardLimit)
+		cards, unreviewedCnt, unreviewedNewCardCnt, unreviewedOldCardCnt := getDeckDueCards(deck, reviewedCardIDs, nil, Conf.Flashcard.NewCardLimit, Conf.Flashcard.ReviewCardLimit, Conf.Flashcard.ReviewMode)
 		unreviewedCount += unreviewedCnt
 		unreviewedNewCardCount += unreviewedNewCardCnt
 		unreviewedOldCardCount += unreviewedOldCardCnt
@@ -988,8 +988,10 @@ func getDeckIDs() (deckIDs []string) {
 	return
 }
 
-func getDeckDueCards(deck *riff.Deck, reviewedCardIDs, blockIDs []string, newCardLimit, reviewCardLimit int) (ret []riff.Card, unreviewedCount, unreviewedNewCardCountInRound, unreviewedOldCardCountInRound int) {
+func getDeckDueCards(deck *riff.Deck, reviewedCardIDs, blockIDs []string, newCardLimit, reviewCardLimit, reviewMode int) (ret []riff.Card, unreviewedCount, unreviewedNewCardCountInRound, unreviewedOldCardCountInRound int) {
 	ret = []riff.Card{}
+	var retNew, retOld []riff.Card
+
 	dues := deck.Dues()
 
 	var tmp []riff.Card
@@ -1060,15 +1062,28 @@ func getDeckDueCards(deck *riff.Deck, reviewedCardIDs, blockIDs []string, newCar
 			}
 
 			newCount++
+			retNew = append(retNew, c)
 		} else {
 			if reviewCount >= reviewCardLimit {
 				continue
 			}
 
 			reviewCount++
+			retOld = append(retOld, c)
 		}
 
 		ret = append(ret, c)
 	}
+
+	switch reviewMode {
+	case 1: // 优先复习新卡
+		ret = nil
+		ret = append(ret, retNew...)
+		ret = append(ret, retOld...)
+	case 2: // 优先复习旧卡
+		ret = nil
+		ret = append(ret, retOld...)
+		ret = append(ret, retNew...)
+	}
 	return
 }