rcmerci 1 год назад
Родитель
Сommit
1808586bb7
2 измененных файлов с 92 добавлено и 15 удалено
  1. 91 14
      src/main/frontend/extensions/fsrs.cljs
  2. 1 1
      src/main/frontend/handler/events.cljs

+ 91 - 14
src/main/frontend/extensions/fsrs.cljs

@@ -46,7 +46,9 @@
   "Return nil if block is not #card.
   Return default card-map if `:logseq.property.fsrs/state` or `:logseq.property.fsrs/due` is nil"
   [block-entity]
-  (when (some (fn [tag] (= :logseq.class/Card (:db/ident tag))) ;block should contains #Card
+  (when (some (fn [tag]
+                (assert (some? (:db/ident tag)) tag)
+                (= :logseq.class/Card (:db/ident tag))) ;block should contains #Card
               (:block/tags block-entity))
     (let [fsrs-state (:logseq.property.fsrs/state block-entity)
           fsrs-due (:logseq.property.fsrs/due block-entity)
@@ -66,7 +68,9 @@
     (when-let [card-map (get-card-map block-entity)]
       (let [next-card-map (fsrs.core/repeat-card! card-map rating)
             prop-card-map (fsrs-card-map->property-fsrs-state next-card-map)
-            prop-fsrs-state (dissoc prop-card-map :due)
+            prop-fsrs-state (-> prop-card-map
+                                (dissoc :due)
+                                (assoc :logseq/last-rating rating))
             prop-fsrs-due (:due prop-card-map)]
         (property-handler/set-block-properties!
          repo (:block/uuid block-entity)
@@ -175,7 +179,7 @@
                                    {:align "start"}))}
     (ui/icon "info-circle"))])
 
-(rum/defcs ^:private card < rum/reactive db-mixins/query
+(rum/defcs ^:private card-view < rum/reactive db-mixins/query
   {:will-mount (fn [state]
                  (when-let [[repo block-id _] (:rum/args state)]
                    (db-async/<get-block repo block-id))
@@ -212,7 +216,7 @@
           (rating-btns repo (:db/id block-entity) *card-index *phase))]])))
 
 (declare update-due-cards-count)
-(rum/defcs cards < rum/reactive
+(rum/defcs cards-view < rum/reactive
   (rum/local 0 ::card-index)
   (shortcut/mixin :shortcut.handler/cards false)
   {:init (fn [state]
@@ -266,22 +270,22 @@
         [:span.text-sm.opacity-50 (str (min (inc @*card-index) (count @*block-ids)) "/" (count @*block-ids))]]
        (if-let [block-id (nth block-ids @*card-index nil)]
          [:div.flex.flex-col
-          (card repo block-id *card-index *phase)]
+          (card-view repo block-id *card-index *phase)]
          [:p (t :flashcards/modal-finished)])])))
 
 (defonce ^:private *last-update-due-cards-count-canceler (atom nil))
 (def ^:private new-task--update-due-cards-count
   "Return a task that update `:srs/cards-due-count` periodically."
   (m/sp
-   (let [repo (state/get-current-repo)]
-     (if (config/db-based-graph? repo)
-       (m/?
-        (m/reduce
-         (fn [_ _]
-           (p/let [due-cards (<get-due-card-block-ids repo nil)]
-             (state/set-state! :srs/cards-due-count (count due-cards))))
-         (c.m/clock (* 3600 1000))))
-       (srs/update-cards-due-count!)))))
+    (let [repo (state/get-current-repo)]
+      (if (config/db-based-graph? repo)
+        (m/?
+         (m/reduce
+          (fn [_ _]
+            (p/let [due-cards (<get-due-card-block-ids repo nil)]
+              (state/set-state! :srs/cards-due-count (count due-cards))))
+          (c.m/clock (* 3600 1000))))
+        (srs/update-cards-due-count!)))))
 
 (defn update-due-cards-count
   []
@@ -311,3 +315,76 @@
         block-ids
         :block/tags
         (:db/id (db/entity :logseq.class/Card)))))))
+
+(defn- cards-in-time-range
+  [cards start-instant end-instant]
+  (assert (and (tick/instant? start-instant)
+               (tick/instant? end-instant))
+          [start-instant end-instant])
+  (->> cards
+       (filter (fn [card] (tick/<= start-instant (:last-repeat card) end-instant)))))
+
+(defn- cards-today
+  [cards]
+  (let [date-today (tick/new-date)
+        start-instant (tick/instant (tick/at date-today (tick/new-time 0 0)))
+        end-instant (tick/instant)]
+    (cards-in-time-range cards start-instant end-instant)))
+
+(defn- cards-recent-7-days
+  [cards]
+  (let [now-instant (tick/instant)
+        date-7-days-ago (tick/date (tick/<< now-instant (tick/new-duration 7 :days)))
+        start-instant (tick/instant (tick/at date-7-days-ago (tick/new-time 0 0)))
+        end-instant now-instant]
+    (cards-in-time-range cards start-instant end-instant)))
+
+(defn- cards-recent-30-days
+  [cards]
+  (let [now-instant (tick/instant)
+        date-30-days-ago (tick/date (tick/<< now-instant (tick/new-duration 30 :days)))
+        start-instant (tick/instant (tick/at date-30-days-ago (tick/new-time 0 0)))
+        end-instant now-instant]
+    (cards-in-time-range cards start-instant end-instant)))
+
+(defn- cards-stat
+  [cards]
+  (let [state-grouped-cards (group-by :state cards)
+        state-cards-count (update-vals state-grouped-cards count)
+        {new-count :new
+         learning-count :learning
+         review-count :review
+         relearning-count :relearning} state-cards-count
+        passed-repeat-count (count (filter #(contains? #{:good :easy} (:logseq/last-rating %)) cards))
+        lapsed-repeat-count (count (filter #(contains? #{:again :hard} (:logseq/last-rating %)) cards))
+        true-retention-percent (when (seq cards) (/ review-count (count cards)))]
+    {:true-retention true-retention-percent
+     :passed-repeats passed-repeat-count
+     :lapsed-repeats lapsed-repeat-count
+     :new-state-cards (or new-count 0)
+     :learning-state-cards (or learning-count 0)
+     :review-state-cards (or review-count 0)
+     :relearning-state-cards (or relearning-count 0)}))
+
+(defn <cards-stat
+  "Some explanations on return value:
+  :true-retention, cards in review-state / all-cards-count
+  :passed-repeats, last rating is :good or :easy
+  :lapsed-repeats, last rating is :again or :hard
+  :XXX-state-cards, cards' state is XXX"
+  []
+  (p/let [repo (state/get-current-repo)
+          all-card-blocks
+          (db-async/<q repo {:transact-db? false}
+                       '[:find [(pull ?b [* {:block/tags [:db/ident]}]) ...]
+                         :where
+                         [?b :block/tags :logseq.class/Card]
+                         [?b :block/uuid]])
+          all-cards (map get-card-map all-card-blocks)
+          [today-stat
+           recent-7-days-stat
+           recent-30-days-stat]
+          (map cards-stat ((juxt cards-today cards-recent-7-days cards-recent-30-days) all-cards))]
+    {:today-stat today-stat
+     :recent-7-days-stat recent-7-days-stat
+     :recent-30-days-stat recent-30-days-stat}))

+ 1 - 1
src/main/frontend/handler/events.cljs

@@ -291,7 +291,7 @@
 (defmethod handle :modal/show-cards [[_ cards-id]]
   (let [db-based? (config/db-based-graph? (state/get-current-repo))]
     (shui/dialog-open!
-     (if db-based? (fn [] (fsrs/cards cards-id)) srs/global-cards)
+     (if db-based? (fn [] (fsrs/cards-view cards-id)) srs/global-cards)
      {:id :srs
       :label "flashcards__cp"})))