Browse Source

Merge branch 'master' into feat/capacitor-new

charlie 5 months ago
parent
commit
018a18253d

+ 1 - 1
deps/common/nbb.edn

@@ -1,4 +1,4 @@
 {:paths ["src" "resources"]
  :deps
  {io.github.nextjournal/nbb-test-runner
-  {:git/sha "012017a8a8983d05f905f38f631f5222f25b9ed9"}}}
+  {:git/sha "b379325cfa5a3306180649da5de3bf5166414e71"}}}

+ 2 - 5
deps/db/nbb.edn

@@ -8,9 +8,6 @@
   borkdude/rewrite-edn {:mvn/version "0.4.9"}
   logseq/clj-fractional-indexing        {:git/url "https://github.com/logseq/clj-fractional-indexing"
                                          :sha     "1087f0fb18aa8e25ee3bbbb0db983b7a29bce270"}
-  ;; TODO: Remove fork once https://github.com/nextjournal/nbb-test-runner/pull/3 is merged
-  io.github.logseq-cldwalker/nbb-test-runner
-  {:git/sha "581c88fdedd928c595ec0288a4d1b7c2ed36095d"}
-;;   io.github.nextjournal/nbb-test-runner
-;;   {:git/sha "012017a8a8983d05f905f38f631f5222f25b9ed9"}
+  io.github.nextjournal/nbb-test-runner
+  {:git/sha "b379325cfa5a3306180649da5de3bf5166414e71"}
   io.github.pez/baldr {:mvn/version "1.0.9"}}}

+ 1 - 1
deps/db/src/logseq/db/common/entity_plus.cljc

@@ -63,7 +63,7 @@
 
 (defn entity-memoized
   [db eid]
-  (if (qualified-keyword? eid)
+  (if (and (qualified-keyword? eid) (not (exists? js/process))) ; don't memoize on node
     (when-not (contains? nil-db-ident-entities eid) ;fast return nil
       (if (and @*reset-cache-background-task-running?
                (contains? immutable-db-ident-entities eid)) ;return cache entity if possible which isn't nil

+ 6 - 3
deps/db/src/logseq/db/common/initial_data.cljs

@@ -9,6 +9,7 @@
             [logseq.db.common.entity-plus :as entity-plus]
             [logseq.db.common.entity-util :as common-entity-util]
             [logseq.db.common.order :as db-order]
+            [logseq.db.frontend.class :as db-class]
             [logseq.db.frontend.entity-util :as entity-util]
             [logseq.db.frontend.rules :as rules]))
 
@@ -153,8 +154,7 @@
   [db block-uuid]
   (let [ids (get-block-children-ids db block-uuid)]
     (when (seq ids)
-      (let [ids' (map (fn [id] [:block/uuid id]) ids)]
-        (d/pull-many db '[*] ids')))))
+      (map (fn [id] (d/entity db [:block/uuid id])) ids))))
 
 (defn- with-raw-title
   [m entity]
@@ -180,7 +180,10 @@
          (= id (:db/id (:logseq.property/view-for ref-block)))
          (entity-util/hidden? (:block/page ref-block))
          (entity-util/hidden? ref-block)
-         (contains? (set (map :db/id (:block/tags ref-block))) (:db/id entity))
+         (and (entity-util/class? entity)
+              (let [children (db-class/get-structured-children db id)
+                    class-ids (set (conj children id))]
+                (some class-ids (map :db/id (:block/tags ref-block)))))
          (some? (get ref-block (:db/ident entity)))))
       (or
        (= (:db/id ref-block) id)

+ 146 - 0
deps/db/src/logseq/db/common/reference.cljs

@@ -0,0 +1,146 @@
+(ns logseq.db.common.reference
+  "References"
+  (:require [cljs.reader :as reader]
+            [clojure.string :as string]
+            [datascript.core :as d]
+            [logseq.common.log :as log]
+            [logseq.db :as ldb]
+            [logseq.db.common.entity-plus :as entity-plus]
+            [logseq.db.common.initial-data :as common-initial-data]
+            [logseq.db.frontend.class :as db-class]
+            [logseq.db.frontend.rules :as rules]))
+
+(defn get-filters
+  [db page]
+  (let [db-based? (entity-plus/db-based-graph? db)]
+    (if db-based?
+      (let [included-pages (:logseq.property.linked-references/includes page)
+            excluded-pages (:logseq.property.linked-references/excludes page)]
+        (when (or (seq included-pages) (seq excluded-pages))
+          {:included included-pages
+           :excluded excluded-pages}))
+      (let [k :filters
+            properties (:block/properties page)
+            properties-str (or (get properties k) "{}")]
+        (try (let [result (reader/read-string properties-str)]
+               (when (seq result)
+                 (let [excluded-pages (->> (filter #(false? (second %)) result)
+                                           (keep first)
+                                           (keep #(ldb/get-page db %)))
+                       included-pages (->> (filter #(true? (second %)) result)
+                                           (keep first)
+                                           (keep #(ldb/get-page db %)))]
+                   {:included included-pages
+                    :excluded excluded-pages})))
+             (catch :default e
+               (log/error :syntax/filters e)))))))
+
+(defn- build-include-exclude-query
+  [variable includes excludes]
+  (concat
+   (for [include includes]
+     [variable :block/path-refs include])
+   (for [exclude excludes]
+     (list 'not [variable :block/path-refs exclude]))))
+
+(defn- filter-refs-children-query
+  [includes excludes class-ids]
+  (let [clauses (concat
+                 [;; find refs from refed block's children only
+                  '(block-parent ?b ?c)
+                  ;; find all levels of parents
+                  '(block-parent ?p ?c)]
+                 (build-include-exclude-query '?c includes excludes)
+                 (when class-ids
+                   (mapcat
+                    (fn [class-id]
+                      (map
+                       (fn [variable]
+                         (list 'not [variable :block/tags class-id]))
+                       ['?b '?p '?c]))
+                    class-ids)))]
+    (into [:find '?b '?p '?c
+           :in '$ '% '[?id ...]
+           :where
+           ['?b :block/refs '?id]]
+          clauses)))
+
+(defn- filter-refs-query
+  [includes excludes class-ids]
+  (let [clauses (concat
+                 (build-include-exclude-query '?b includes excludes)
+                 (for [class-id class-ids]
+                   (list 'not ['?b :block/tags class-id])))]
+    (into [:find '[?b ...]
+           :in '$ '% '[?id ...]
+           :where
+           ['?b :block/refs '?id]]
+          clauses)))
+
+(defn- remove-hidden-ref
+  [db page-id refs]
+  (remove (fn [block] (common-initial-data/hidden-ref? db block page-id)) refs))
+
+(defn- get-ref-pages-count
+  [db id ref-blocks children-ids]
+  (when (seq ref-blocks)
+    (let [children (->> children-ids
+                        (map (fn [id] (d/entity db id)))
+                        (remove-hidden-ref db id))]
+      (->> (concat (mapcat :block/path-refs ref-blocks)
+                   (mapcat :block/refs children))
+           frequencies
+           (keep (fn [[ref size]]
+                   (when (and (ldb/page? ref)
+                              (not= (:db/id ref) id)
+                              (not= :block/tags (:db/ident ref))
+                              (not (common-initial-data/hidden-ref? db ref id)))
+                     [(:block/title ref) size])))
+           (sort-by second #(> %1 %2))))))
+
+(defn get-linked-references
+  [db id]
+  (let [entity (d/entity db id)
+        ids (set (cons id (ldb/get-block-alias db id)))
+        page-filters (get-filters db entity)
+        excludes (map :db/id (:excluded page-filters))
+        includes (map :db/id (:included page-filters))
+        filter-exists? (or (seq excludes) (seq includes))
+        class-ids (when (ldb/class? entity)
+                    (let [class-children (db-class/get-structured-children db id)]
+                      (set (conj class-children id))))
+        rules (rules/extract-rules rules/db-query-dsl-rules [:block-parent] {})
+        ref-blocks-query-result (d/q (filter-refs-query includes excludes class-ids) db rules ids)
+        children-query-result (d/q (filter-refs-children-query includes excludes class-ids) db rules ids)
+        ref-blocks (->> (map first children-query-result)
+                        (concat ref-blocks-query-result)
+                        distinct
+                        (map (fn [id] (d/entity db id)))
+                        (remove-hidden-ref db id))
+        children-ids (->>
+                      (distinct (concat
+                                 (map second children-query-result)
+                                 (map last children-query-result)))
+                      (remove (set (map :db/id ref-blocks))))
+        ref-pages-count (get-ref-pages-count db id ref-blocks children-ids)]
+    {:ref-pages-count ref-pages-count
+     :ref-blocks ref-blocks
+     :ref-matched-children-ids (when filter-exists? (set children-ids))}))
+
+(defn get-unlinked-references
+  [db id]
+  (let [entity (d/entity db id)
+        title (string/lower-case (:block/title entity))]
+    (when-not (string/blank? title)
+      (let [ids (->> (d/datoms db :avet :block/title)
+                     (keep (fn [d]
+                             (when (and (not= id (:e d)) (string/includes? (string/lower-case (:v d)) title))
+                               (:e d)))))]
+        (keep
+         (fn [eid]
+           (let [e (d/entity db eid)]
+             (when-not (or (some #(= id %) (map :db/id (:block/refs e)))
+                           (:block/link e)
+                           (ldb/built-in? e))
+               e)))
+         ids)))))

+ 5 - 99
deps/db/src/logseq/db/common/view.cljs

@@ -1,15 +1,13 @@
 (ns logseq.db.common.view
   "Main namespace for view fns."
-  (:require [cljs.reader :as reader]
-            [clojure.set :as set]
+  (:require [clojure.set :as set]
             [clojure.string :as string]
             [datascript.core :as d]
             [datascript.impl.entity :as de]
-            [logseq.common.log :as log]
             [logseq.common.util :as common-util]
             [logseq.db :as ldb]
             [logseq.db.common.entity-plus :as entity-plus]
-            [logseq.db.common.initial-data :as common-initial-data]
+            [logseq.db.common.reference :as db-reference]
             [logseq.db.frontend.class :as db-class]
             [logseq.db.frontend.entity-util :as entity-util]
             [logseq.db.frontend.property :as db-property]
@@ -263,98 +261,6 @@
             result)))
       (:filters filters)))))
 
-(defn filter-blocks
-  [filters ref-blocks]
-  (let [exclude-ids (set (map :db/id (:excluded filters)))
-        include-ids (set (map :db/id (:included filters)))
-        get-ids (fn [block]
-                  (set (map :db/id (:block/path-refs block))))]
-    (cond->> ref-blocks
-      (seq exclude-ids)
-      (remove (fn [block]
-                (let [ids (get-ids block)]
-                  (seq (set/intersection exclude-ids ids)))))
-
-      (seq include-ids)
-      (filter (fn [block]
-                (let [ids (get-ids block)]
-                  (set/subset? include-ids ids)))))))
-
-(defn get-filters
-  [db page]
-  (let [db-based? (entity-plus/db-based-graph? db)]
-    (if db-based?
-      (let [included-pages (:logseq.property.linked-references/includes page)
-            excluded-pages (:logseq.property.linked-references/excludes page)]
-        (when (or (seq included-pages) (seq excluded-pages))
-          {:included included-pages
-           :excluded excluded-pages}))
-      (let [k :filters
-            properties (:block/properties page)
-            properties-str (or (get properties k) "{}")]
-        (try (let [result (reader/read-string properties-str)]
-               (when (seq result)
-                 (let [excluded-pages (->> (filter #(false? (second %)) result)
-                                           (keep first)
-                                           (keep #(ldb/get-page db %)))
-                       included-pages (->> (filter #(true? (second %)) result)
-                                           (keep first)
-                                           (keep #(ldb/get-page db %)))]
-                   {:included included-pages
-                    :excluded excluded-pages})))
-             (catch :default e
-               (log/error :syntax/filters e)))))))
-
-(defn- get-linked-references
-  [db id]
-  (let [entity (d/entity db id)
-        ids (set (cons id (ldb/get-block-alias db id)))
-        refs (mapcat (fn [id] (:block/_path-refs (d/entity db id))) ids)
-        page-filters (get-filters db entity)
-        full-ref-blocks (->> refs
-                             (remove (fn [block] (common-initial-data/hidden-ref? db block id)))
-                             (common-util/distinct-by :db/id))
-        ref-blocks (cond->> full-ref-blocks
-                     (seq page-filters)
-                     (filter-blocks page-filters))
-        ref-pages-count (->> ref-blocks
-                             (remove (fn [block]
-                                       (common-initial-data/hidden-ref? db block id)))
-                             (mapcat (fn [block]
-                                       (->>
-                                        (cons
-                                         (:block/title (:block/page block))
-                                         (map (fn [b]
-                                                (when (and (ldb/page? b)
-                                                           (not= (:db/id b) id)
-                                                           (not (contains? #{:block/tags} (:db/ident b))))
-                                                  (:block/title b)))
-                                              (:block/path-refs block)))
-                                        distinct)))
-                             (remove nil?)
-                             (frequencies)
-                             (sort-by second #(> %1 %2)))]
-    {:ref-pages-count ref-pages-count
-     :ref-blocks ref-blocks}))
-
-(defn- get-unlinked-references
-  [db id]
-  (let [entity (d/entity db id)
-        title (string/lower-case (:block/title entity))]
-    (when-not (string/blank? title)
-      (let [ids (->> (d/datoms db :avet :block/title)
-                     (keep (fn [d]
-                             (when (and (not= id (:e d)) (string/includes? (string/lower-case (:v d)) title))
-                               (:e d)))))]
-        (keep
-         (fn [eid]
-           (let [e (d/entity db eid)]
-             (when-not (or (some #(= id %) (map :db/id (:block/refs e)))
-                           (:block/link e)
-                           (ldb/built-in? e))
-               e)))
-         ids)))))
-
 (defn- get-exclude-page-ids
   [db]
   (->>
@@ -413,10 +319,10 @@
        (keep (fn [id] (non-hidden-e id))))
 
       :linked-references
-      (get-linked-references db view-for-id)
+      (db-reference/get-linked-references db view-for-id)
 
       :unlinked-references
-      (get-unlinked-references db view-for-id)
+      (db-reference/get-unlinked-references db view-for-id)
 
       :query-result
       nil
@@ -589,6 +495,6 @@
        {:count (count filtered-entities)
         :data (distinct data')}
         (= feat-type :linked-references)
-        (assoc :ref-pages-count (:ref-pages-count entities-result))
+        (merge (select-keys entities-result [:ref-pages-count :ref-matched-children-ids]))
         query?
         (assoc :properties (get-query-properties entities-result))))))

+ 7 - 1
deps/db/src/logseq/db/file_based/rules.cljc

@@ -14,7 +14,13 @@
   "Rules used by frontend.db.query-dsl for file graphs. The symbols ?b and ?p
   respectively refer to block and page. Do not alter them as they are
   programmatically built by the query-dsl ns"
-  {:page-property
+  {:block-parent
+   '[[(block-parent ?p ?c)
+      [?c :block/parent ?p]]
+     [(block-parent ?p ?c)
+      [?t :block/parent ?p]
+      (block-parent ?t ?c)]]
+   :page-property
    '[(page-property ?p ?key ?val)
      [?p :block/name]
      [?p :block/properties ?prop]

+ 1 - 1
deps/graph-parser/nbb.edn

@@ -5,6 +5,6 @@
   logseq/db
   {:local/root "../db"}
   io.github.nextjournal/nbb-test-runner
-  {:git/sha "012017a8a8983d05f905f38f631f5222f25b9ed9"}
+  {:git/sha "b379325cfa5a3306180649da5de3bf5166414e71"}
   borkdude/rewrite-edn {:mvn/version "0.4.9"}
   io.github.pez/baldr {:mvn/version "1.0.9"}}}

+ 1 - 1
deps/outliner/nbb.edn

@@ -7,4 +7,4 @@
   metosin/malli
   {:mvn/version "0.16.1"}
   io.github.nextjournal/nbb-test-runner
-  {:git/sha "012017a8a8983d05f905f38f631f5222f25b9ed9"}}}
+  {:git/sha "b379325cfa5a3306180649da5de3bf5166414e71"}}}

+ 1 - 1
deps/publishing/nbb.edn

@@ -3,4 +3,4 @@
  {logseq/db
   {:local/root "../db"}
   io.github.nextjournal/nbb-test-runner
-  {:git/sha "012017a8a8983d05f905f38f631f5222f25b9ed9"}}}
+  {:git/sha "b379325cfa5a3306180649da5de3bf5166414e71"}}}

+ 1 - 2
deps/shui/src/logseq/shui/base/core.cljs

@@ -51,8 +51,7 @@
           {:variant variant
            :data-button :icon
            :style (when size {:width size :height size})})
-   (tabler-icon/root (name icon-name) (merge {:size 20
-                                              :key "icon"} icon-props))
+   (tabler-icon/root (name icon-name) (merge {:size 20} icon-props))
    child))
 
 (def button-ghost-icon (partial button-icon :ghost))

+ 1 - 1
resources/css/shui.css

@@ -246,7 +246,7 @@ div[data-radix-popper-content-wrapper] {
 }
 
 .ui__dialog-content {
-  @apply outline-none;
+  @apply outline-none m-4;
 
   &[data-auto-width] {
     @apply max-w-[90vw] w-max sm:max-w-[960px];

+ 97 - 90
src/main/frontend/components/block.cljs

@@ -304,6 +304,86 @@
      [:span.handle-right.image-resize (assoc handle-props :ref *handle-right)]]))
 
 (defonce *resizing-image? (atom false))
+(rum/defc asset-container
+  [asset-block src title metadata {:keys [breadcrumb? positioned? local? full-text]}]
+  [:div.asset-container
+   {:key "resize-asset-container"}
+   [:img.rounded-sm.relative
+    (merge
+     {:loading "lazy"
+      :referrerPolicy "no-referrer"
+      :src src
+      :title title}
+     metadata)]
+   (when (and (not breadcrumb?)
+              (not positioned?))
+     [:<>
+      (let [image-src (fs/asset-path-normalize src)]
+        [:.asset-action-bar {:aria-hidden "true"}
+         [:.flex
+          (when-not config/publishing?
+            [:button.asset-action-btn
+             {:title (t :asset/delete)
+              :tabIndex "-1"
+              :on-pointer-down util/stop
+              :on-click
+              (fn [e]
+                (util/stop e)
+                (when-let [block-id (some-> (.-target e) (.closest "[blockid]") (.getAttribute "blockid") (uuid))]
+                  (let [*local-selected? (atom local?)]
+                    (-> (shui/dialog-confirm!
+                         [:div.text-xs.opacity-60.-my-2
+                          (when (and local? (not= (:block/uuid asset-block) block-id))
+                            [:label.flex.gap-1.items-center
+                             (shui/checkbox
+                              {:default-checked @*local-selected?
+                               :on-checked-change #(reset! *local-selected? %)})
+                             (t :asset/physical-delete)])]
+                         {:title (t :asset/confirm-delete (.toLocaleLowerCase (t :text/image)))
+                          :outside-cancel? true})
+                        (p/then (fn []
+                                  (shui/dialog-close!)
+                                  (editor-handler/delete-asset-of-block!
+                                   {:block-id block-id
+                                    :asset-block asset-block
+                                    :local? local?
+                                    :delete-local? @*local-selected?
+                                    :repo (state/get-current-repo)
+                                    :href src
+                                    :title title
+                                    :full-text full-text})))))))}
+             (ui/icon "trash")])
+
+          [:button.asset-action-btn
+           {:title (t :asset/copy)
+            :tabIndex "-1"
+            :on-pointer-down util/stop
+            :on-click (fn [e]
+                        (util/stop e)
+                        (-> (util/copy-image-to-clipboard image-src)
+                            (p/then #(notification/show! "Copied!" :success))))}
+           (ui/icon "copy")]
+
+          [:button.asset-action-btn
+           {:title (t :asset/maximize)
+            :tabIndex "-1"
+            :on-pointer-down util/stop
+            :on-click open-lightbox}
+
+           (ui/icon "maximize")]
+
+          (when (util/electron?)
+            [:button.asset-action-btn
+             {:title (t (if local? :asset/show-in-folder :asset/open-in-browser))
+              :tabIndex "-1"
+              :on-pointer-down util/stop
+              :on-click (fn [e]
+                          (util/stop e)
+                          (if local?
+                            (ipc/ipc "openFileInFolder" image-src)
+                            (js/window.apis.openExternal image-src)))}
+             (shui/tabler-icon "folder-pin")])]])])])
+
 (rum/defcs ^:large-vars/cleanup-todo resizable-image <
   (rum/local nil ::size)
   {:will-unmount (fn [state]
@@ -313,101 +393,24 @@
   (let [breadcrumb? (:breadcrumb? config)
         positioned? (:property-position config)
         asset-block (:asset-block config)
-        asset-container [:div.asset-container {:key "resize-asset-container"}
-                         [:img.rounded-sm.relative
-                          (merge
-                           {:loading "lazy"
-                            :referrerPolicy "no-referrer"
-                            :src src
-                            :title title}
-                           metadata)]
-                         (when (and (not breadcrumb?)
-                                    (not positioned?))
-                           [:<>
-                            (let [image-src (fs/asset-path-normalize src)]
-                              [:.asset-action-bar {:aria-hidden "true"}
-                               [:.flex
-                                (when-not config/publishing?
-                                  [:button.asset-action-btn
-                                   {:title (t :asset/delete)
-                                    :tabIndex "-1"
-                                    :on-pointer-down util/stop
-                                    :on-click
-                                    (fn [e]
-                                      (util/stop e)
-                                      (when-let [block-id (some-> (.-target e) (.closest "[blockid]") (.getAttribute "blockid") (uuid))]
-                                        (let [*local-selected? (atom local?)]
-                                          (-> (shui/dialog-confirm!
-                                               [:div.text-xs.opacity-60.-my-2
-                                                (when (and local? (not= (:block/uuid asset-block) block-id))
-                                                  [:label.flex.gap-1.items-center
-                                                   (shui/checkbox
-                                                    {:default-checked @*local-selected?
-                                                     :on-checked-change #(reset! *local-selected? %)})
-                                                   (t :asset/physical-delete)])]
-                                               {:title (t :asset/confirm-delete (.toLocaleLowerCase (t :text/image)))
-                                                :outside-cancel? true})
-                                              (p/then (fn []
-                                                        (shui/dialog-close!)
-                                                        (editor-handler/delete-asset-of-block!
-                                                         {:block-id block-id
-                                                          :asset-block asset-block
-                                                          :local? local?
-                                                          :delete-local? @*local-selected?
-                                                          :repo (state/get-current-repo)
-                                                          :href src
-                                                          :title title
-                                                          :full-text full-text})))))))}
-                                   (ui/icon "trash")])
-
-                                [:button.asset-action-btn
-                                 {:title (t :asset/copy)
-                                  :tabIndex "-1"
-                                  :on-pointer-down util/stop
-                                  :on-click (fn [e]
-                                              (util/stop e)
-                                              (-> (util/copy-image-to-clipboard image-src)
-                                                  (p/then #(notification/show! "Copied!" :success))))}
-                                 (ui/icon "copy")]
-
-                                [:button.asset-action-btn
-                                 {:title (t :asset/maximize)
-                                  :tabIndex "-1"
-                                  :on-pointer-down util/stop
-                                  :on-click open-lightbox}
-
-                                 (ui/icon "maximize")]
-
-                                (when (util/electron?)
-                                  [:button.asset-action-btn
-                                   {:title (t (if local? :asset/show-in-folder :asset/open-in-browser))
-                                    :tabIndex "-1"
-                                    :on-pointer-down util/stop
-                                    :on-click (fn [e]
-                                                (util/stop e)
-                                                (if local?
-                                                  (ipc/ipc "openFileInFolder" image-src)
-                                                  (js/window.apis.openExternal image-src)))}
-                                   (shui/tabler-icon "folder-pin")])]])])]
         width (or (get-in asset-block [:logseq.property.asset/resize-metadata :width])
                   (:width metadata))
         *width (get state ::size)
-        width (or @*width width)
-        style (when-not (util/mobile?)
-                (cond width
-                      {(if (:sidebar? config)
-                         :max-width :width) width}
-                      :else
-                      {}))
+        width (or @*width width 500)
+        metadata' (merge metadata (when width {:width width}))
         resizable? (and (not (mobile-util/native-platform?))
                         (not breadcrumb?)
-                        (not positioned?))]
+                        (not positioned?))
+        asset-container-cp (asset-container asset-block src title metadata'
+                                            {:breadcrumb? breadcrumb?
+                                             :positioned? positioned?
+                                             :local? local?
+                                             :full-text full-text})]
     (if (or (:disable-resize? config)
             (not resizable?))
-      asset-container
+      asset-container-cp
       [:div.ls-resize-image.rounded-md
-       (when style {:style style})
-       asset-container
+       asset-container-cp
        (resize-image-handles
         (fn [k ^js event]
           (let [dx (.-dx event)
@@ -2121,7 +2124,11 @@
   (let [ref?        (:ref? config)
         query?      (:custom-query? config)
         children    (when (coll? children)
-                      (remove nil? children))]
+                      (let [ref-matched-children-ids (:ref-matched-children-ids config)]
+                        (cond->> (remove nil? children)
+                          ref-matched-children-ids
+                          ;; Block children will not be rendered if the filters do not match them
+                          (filter (fn [b] (ref-matched-children-ids (:db/id b)))))))]
     (when (and (coll? children)
                (seq children)
                (not collapsed?))
@@ -3262,7 +3269,7 @@
                                         (rum/with-key (breadcrumb-fragment config block label opts)
                                           (str (:block/uuid block))))
                                       [:span.opacity-70 {:key "dots"} "⋯"])))
-                             (interpose (rum/with-key (breadcrumb-separator) "icon")))]
+                             (interpose (breadcrumb-separator)))]
         (when (seq breadcrumbs)
           [:div.breadcrumb.block-parents
            {:class (when (seq breadcrumbs)
@@ -3788,7 +3795,7 @@
 
 (defn- config-block-should-update?
   [old-state new-state]
-  (let [config-compare-keys [:show-cloze? :hide-children? :own-order-list-type :own-order-list-index :original-block :edit? :hide-bullet?]
+  (let [config-compare-keys [:show-cloze? :hide-children? :own-order-list-type :own-order-list-index :original-block :edit? :hide-bullet? :ref-matched-children-ids]
         b1                  (second (:rum/args old-state))
         b2                  (second (:rum/args new-state))
         result              (or

+ 10 - 8
src/main/frontend/components/block.css

@@ -9,7 +9,6 @@
   @apply min-h-[24px] max-w-full whitespace-pre-wrap break-words cursor-text;
 
   img {
-    max-width: 100%;
 
     /* FIXME: img macros */
 
@@ -44,11 +43,6 @@
         @apply opacity-100;
       }
     }
-
-    > img {
-      max-width: unset;
-      width: 100%;
-    }
   }
 
   .draw [aria-labelledby="shapes-title"] {
@@ -1078,8 +1072,16 @@ html.is-mac {
   height: 16px;
 }
 
-.ls-page-title .positioned-properties, .ls-page-title .time-spent {
-  margin-top: 15px;
+.ls-page-title {
+  .positioned-properties, .time-spent {
+    margin-top: 15px;
+  }
+}
+
+.ls-page-title .ls-properties-area {
+    .positioned-properties, .time-spent {
+        margin-top: initial;
+    }
 }
 
 .ls-page-title .ls-page-icon button {

+ 5 - 0
src/main/frontend/components/cards.css

@@ -3,3 +3,8 @@
         min-height: 60vh;
     }
 }
+
+#cards-modal {
+    height: 70vh;
+    max-height: 1024px;
+}

+ 2 - 2
src/main/frontend/components/reference.cljs

@@ -7,7 +7,7 @@
             [frontend.db-mixins :as db-mixins]
             [frontend.state :as state]
             [frontend.ui :as ui]
-            [logseq.db.common.view :as db-view]
+            [logseq.db.common.reference :as db-reference]
             [logseq.shui.hooks :as hooks]
             [logseq.shui.ui :as shui]
             [missionary.core :as m]
@@ -15,7 +15,7 @@
 
 (rum/defc references-aux
   [page-entity config]
-  (let [filters (db-view/get-filters (db/get-db) page-entity)
+  (let [filters (db-reference/get-filters (db/get-db) page-entity)
         reference-filter (fn [{:keys [ref-pages-count]}]
                            (shui/button
                             {:title "Page filter"

+ 9 - 1
src/main/frontend/components/reference.css

@@ -15,7 +15,15 @@
 }
 
 .ls-filters {
-  max-width: 704px;
+    @screen md {
+        min-width: 700px;
+        width: 700px;
+    }
+
+    @screen sm {
+        min-width: 600px;
+        width: 600px;
+    }
 }
 
 .custom-query-page-result {

+ 2 - 2
src/main/frontend/components/reference_filters.cljs

@@ -11,7 +11,7 @@
             [frontend.state :as state]
             [frontend.ui :as ui]
             [frontend.util :as util]
-            [logseq.db.common.view :as db-view]
+            [logseq.db.common.reference :as db-reference]
             [logseq.shui.hooks :as hooks]
             [promesa.core :as p]
             [rum.core :as rum]))
@@ -75,7 +75,7 @@
   [page-entity references]
   (let [[filter-search set-filter-search!] (hooks/use-state "")
         [filtered-references set-filtered-references!] (hooks/use-state references)
-        filters (db-view/get-filters (db/get-db) page-entity)
+        filters (db-reference/get-filters (db/get-db) page-entity)
         {:keys [included excluded]} filters]
     (hooks/use-effect!
      (fn []

+ 23 - 8
src/main/frontend/components/views.cljs

@@ -1525,11 +1525,17 @@
            (shui/table-footer (add-new-row (:view-entity option) table)))]]))))
 
 (rum/defc list-view < rum/static
-  [{:keys [config] :as option} _view-entity {:keys [rows]} *scroller-ref]
+  [{:keys [config ref-matched-children-ids] :as option} view-entity {:keys [rows]} *scroller-ref]
   (let [lazy-item-render (fn [rows idx]
                            (lazy-item rows idx (assoc option :list-view? true)
                                       (fn [block]
-                                        (block-container (assoc config :list-view? true :block-level 1) block))))
+                                        (let [config' (cond->
+                                                       (assoc config
+                                                              :list-view? true
+                                                              :block-level 1)
+                                                        (= :linked-references (:logseq.property.view/feature-type view-entity))
+                                                        (assoc :ref-matched-children-ids ref-matched-children-ids))]
+                                          (block-container config' block)))))
         list-cp (fn [rows]
                   (when (seq rows)
                     (ui/virtualized-list
@@ -2025,7 +2031,7 @@
 (defn- load-view-data-aux
   [view-entity view-parent {:keys [query? query-entity-ids sorting filters input
                                    view-feature-type group-by-property-ident
-                                   set-data! set-ref-pages-count! set-properties! set-loading!]}]
+                                   set-data! set-ref-pages-count! set-ref-matched-children-ids! set-properties! set-loading!]}]
   (c.m/run-task*
    (m/sp
      (let [need-query? (and query? (seq query-entity-ids) (or sorting filters (not (string/blank? input))))]
@@ -2037,7 +2043,7 @@
          :else
          (when (or (not query?) need-query?)
            (try
-             (let [{:keys [data ref-pages-count properties]}
+             (let [{:keys [data ref-pages-count ref-matched-children-ids properties]}
                    (c.m/<?
                     (<load-view-data view-entity
                                      (cond->
@@ -2052,7 +2058,8 @@
                                        (assoc :query-entity-ids query-entity-ids))))]
                (set-data! data)
                (when ref-pages-count
-                 (set-ref-pages-count! ref-pages-count))
+                 (set-ref-pages-count! ref-pages-count)
+                 (set-ref-matched-children-ids! ref-matched-children-ids))
                (set-properties! properties))
              (finally
                (set-loading! false)))))))))
@@ -2089,13 +2096,14 @@
         [loading? set-loading!] (hooks/use-state (not query?))
         [data set-data!] (hooks/use-state data)
         [ref-pages-count set-ref-pages-count!] (hooks/use-state nil)
+        [ref-matched-children-ids set-ref-matched-children-ids!] (hooks/use-state nil)
         load-view-data (fn load-view-data []
                          (load-view-data-aux view-entity view-parent
                                              {:query? query?
                                               :query-entity-ids query-entity-ids
                                               :sorting sorting :filters filters :input input
                                               :view-feature-type view-feature-type :group-by-property-ident group-by-property-ident
-                                              :set-data! set-data! :set-ref-pages-count! set-ref-pages-count!
+                                              :set-data! set-data! :set-ref-pages-count! set-ref-pages-count! :set-ref-matched-children-ids! set-ref-matched-children-ids!
                                               :set-properties! set-properties! :set-loading! set-loading!}))]
     (let [sorting-filters {:sorting sorting
                            :filters filters}]
@@ -2129,10 +2137,17 @@
                                           :items-count (if (every? number? data)
                                                          (count data)
                                                          ;; grouped
-                                                         (reduce (fn [total [_ col]]
-                                                                   (+ total (count col))) 0 data))
+                                                         (let [f (fn count-col
+                                                                   [data]
+                                                                   (reduce (fn [total [_k col]]
+                                                                             (if (and (vector? (first col))
+                                                                                      (uuid? (ffirst col)))
+                                                                               (+ total (count-col col))
+                                                                               (+ total (count col)))) 0 data))]
+                                                           (f data)))
                                           :group-by-property-ident group-by-property-ident
                                           :ref-pages-count ref-pages-count
+                                          :ref-matched-children-ids ref-matched-children-ids
                                           :display-type display-type
                                           :load-view-data load-view-data
                                           :set-view-entity! set-view-entity!))])))

+ 5 - 4
src/main/frontend/extensions/fsrs.cljs

@@ -257,10 +257,9 @@
         block-ids (rum/react *block-ids)
         loading? (rum/react (::loading? state))
         *card-index (::card-index state)
-        card-index (rum/react *card-index)
         *phase (atom :init)]
     (when (false? loading?)
-      [:div#cards-modal.flex.flex-col.gap-8.h-full.flex-1
+      [:div#cards-modal.flex.flex-col.gap-8.flex-1
        [:div.flex.flex-row.items-center.gap-2.flex-wrap
         (shui/select
          {:on-value-change (fn [v]
@@ -280,11 +279,13 @@
                                 (:block/title card-entity)))))))
 
         [:span.text-sm.opacity-50 (str (min (inc @*card-index) (count @*block-ids)) "/" (count @*block-ids))]]
-       (let [block-id (nth block-ids card-index nil)]
+       (let [block-id (nth block-ids @*card-index nil)]
          (cond
            block-id
            [:div.flex.flex-col
-            (card-view repo block-id *card-index *phase)]
+            (rum/with-key
+              (card-view repo block-id *card-index *phase)
+              (str "card-" block-id))]
 
            (empty? block-ids)
            [:div.ls-card.content.ml-2

+ 24 - 12
src/main/frontend/handler/block.cljs

@@ -263,18 +263,30 @@
                                      last)]
        (get-original-block-by-dom last-block-node)))))
 
-(defn indent-outdent-blocks!
-  [blocks indent? save-current-block]
-  (when (seq blocks)
-    (let [blocks (get-top-level-blocks blocks)]
-      (ui-outliner-tx/transact!
-       {:outliner-op :move-blocks
-        :real-outliner-op :indent-outdent}
-       (when save-current-block (save-current-block))
-       (outliner-op/indent-outdent-blocks! (get-top-level-blocks blocks)
-                                           indent?
-                                           {:parent-original (get-first-block-original)
-                                            :logical-outdenting? (state/logical-outdenting?)})))))
+(let [*timeout (atom nil)]
+  (defn indent-outdent-blocks!
+    [blocks indent? save-current-block]
+    (when-let [timeout *timeout]
+      (js/clearTimeout timeout))
+    (when (seq blocks)
+      (let [blocks-container (when-let [first-selected-node (first (state/get-selection-blocks))]
+                               (util/rec-get-blocks-container first-selected-node))
+            blocks' (get-top-level-blocks blocks)]
+        (p/do!
+         (ui-outliner-tx/transact!
+          {:outliner-op :move-blocks
+           :real-outliner-op :indent-outdent}
+          (when save-current-block (save-current-block))
+          (outliner-op/indent-outdent-blocks! (get-top-level-blocks blocks')
+                                              indent?
+                                              {:parent-original (get-first-block-original)
+                                               :logical-outdenting? (state/logical-outdenting?)}))
+         (when blocks-container
+           ;; Update selection nodes to be the new ones
+           (reset! *timeout
+                   (js/setTimeout
+                    #(state/set-selection-blocks! (dom/sel blocks-container ".ls-block.selected") :down)
+                    100))))))))
 
 (def *swipe (atom nil))
 (def *swiped? (atom false))

+ 13 - 16
src/main/frontend/worker/commands.cljs

@@ -108,16 +108,21 @@
 
 (defmulti handle-command (fn [action-id & _others] action-id))
 
-(defn- repeat-timestamp
+(defn- repeat-until-future-timestamp
   [datetime recur-unit frequency period-f keep-week?]
   (let [now (t/now)
-        v (if (t/after? datetime now)
-            1
-            (period-f (t/interval datetime now)))
-        delta (->> (Math/ceil (/ (if (zero? v) 1 v) frequency))
+        v (max
+           1
+           (if (t/after? datetime now)
+             1
+             (period-f (t/interval datetime now))))
+        delta (->> (Math/ceil (/ v frequency))
                    (* frequency)
                    recur-unit)
-        result (t/plus datetime delta)
+        result* (t/plus datetime delta)
+        result (if (t/after? result* now)
+                 result*
+                 (t/plus result* (recur-unit frequency)))
         w1 (t/day-of-week datetime)
         w2 (t/day-of-week result)]
     (if (and keep-week? (not= w1 w2))
@@ -130,7 +135,6 @@
 (defn- get-next-time
   [current-value unit frequency]
   (let [current-date-time (tc/to-date-time current-value)
-        default-timezone-time (t/to-default-time-zone current-date-time)
         [recur-unit period-f] (case (:db/ident unit)
                                 :logseq.property.repeat/recur-unit.minute [t/minutes t/in-minutes]
                                 :logseq.property.repeat/recur-unit.hour [t/hours t/in-hours]
@@ -140,15 +144,8 @@
                                 :logseq.property.repeat/recur-unit.year [t/years t/in-years]
                                 nil)]
     (when recur-unit
-      (let [delta (recur-unit frequency)
-            next-time (case (:db/ident unit)
-                        :logseq.property.repeat/recur-unit.year
-                        (repeat-timestamp default-timezone-time recur-unit frequency period-f false)
-                        :logseq.property.repeat/recur-unit.month
-                        (repeat-timestamp default-timezone-time recur-unit frequency period-f false)
-                        :logseq.property.repeat/recur-unit.week
-                        (repeat-timestamp default-timezone-time recur-unit frequency period-f true)
-                        (t/plus default-timezone-time delta))]
+      (let [week? (= (:db/ident unit) :logseq.property.repeat/recur-unit.week)
+            next-time (repeat-until-future-timestamp current-date-time recur-unit frequency period-f week?)]
         (tc/to-long next-time)))))
 
 (defn- compute-reschedule-property-tx

File diff suppressed because it is too large
+ 0 - 0
src/test/fixtures/references.transit


+ 182 - 0
src/test/frontend/db/reference_test.cljs

@@ -0,0 +1,182 @@
+(ns frontend.db.reference-test
+  (:require [cljs.test :refer [deftest is testing]]
+            [datascript.core :as d]
+            [logseq.db :as ldb]
+            [logseq.db.common.reference :as db-reference]
+            [shadow.resource :as rc]))
+
+(def test-transit (rc/inline "fixtures/references.transit"))
+;; (use-fixtures :each test-helper/db-based-start-and-destroy-db)
+
+(defn- create-conn!
+  []
+  (let [db (ldb/read-transit-str test-transit)]
+    (d/conn-from-db db)))
+
+;; FIXME: EDN import doesn't work
+(comment
+  (def test-page-blocks
+    {:pages-and-blocks
+     [{:page
+       {:build/journal 20250611},
+       :blocks
+       [{:block/title "[[68485f78-1e70-4173-a569-1ebcb2ba69e6]] 1",
+         :build/children
+         [{:block/title "[[68485f7a-d9c1-495a-a364-e7aae6ab0147]] 1",
+           :build/children
+           [{:block/title "test",
+             :build/children
+             [{:block/title "[[68485f7f-3de9-46d3-a1e5-50a7d052066e]] 1"}]}
+            {:block/title "another test",
+             :build/children
+             [{:block/title "[[68485f7f-3de9-46d3-a1e5-50a7d052066e]] 2"}]}]}
+          {:block/title "[[68485f7f-3de9-46d3-a1e5-50a7d052066e]] 3"}]}
+        {:block/title "[[68485f78-1e70-4173-a569-1ebcb2ba69e6]] 2",
+         :build/children
+         [{:block/title "[[68485f7f-3de9-46d3-a1e5-50a7d052066e]] 4",
+           :build/children
+           [{:block/title "[[68485f7a-d9c1-495a-a364-e7aae6ab0147]] 1"}]}]}]}
+      {:page
+       {:block/title "bar",
+        :block/uuid #uuid "68485f7a-d9c1-495a-a364-e7aae6ab0147",
+        :build/keep-uuid? true}}
+      {:page
+       {:block/title "baz",
+        :block/uuid #uuid "68485f7f-3de9-46d3-a1e5-50a7d052066e",
+        :build/keep-uuid? true}}
+      {:page
+       {:block/title "foo",
+        :block/uuid #uuid "68485f78-1e70-4173-a569-1ebcb2ba69e6",
+        :build/keep-uuid? true}}],
+     :logseq.db.sqlite.export/export-type :page}))
+
+(comment
+  (defn- import-edn!
+    [conn data]
+    (let [{:keys [init-tx block-props-tx misc-tx error] :as _txs} (sqlite-export/build-import data @conn {})]
+      (when error
+        (throw (ex-info "Build import failed" {:data test-page-blocks})))
+      (d/transact! conn init-tx)
+      (when (seq block-props-tx)
+        (d/transact! conn block-props-tx))
+      (when (seq misc-tx)
+        (d/transact! conn misc-tx)))))
+
+(defn- retract-filters!
+  [conn foo-id]
+  (d/transact! conn [[:db/retract foo-id :logseq.property.linked-references/includes]
+                     [:db/retract foo-id :logseq.property.linked-references/excludes]]))
+
+(deftest ^:large-vars/cleanup-todo get-linked-references
+  (let [conn (create-conn!)
+        foo-id (:db/id (ldb/get-page @conn "foo"))
+        _ (retract-filters! conn foo-id)
+        db @conn
+        [foo bar baz] (map #(ldb/get-page @conn %) ["foo" "bar" "baz"])]
+
+    (testing "Linked references without filters"
+      (let [{:keys [ref-pages-count ref-blocks ref-matched-children-ids]} (db-reference/get-linked-references db (:db/id foo))]
+        (is (= [["baz" 4] ["Journal" 3] ["Jun 11th, 2025" 2] ["bar" 2]] (vec ref-pages-count))
+            "ref-pages-count check failed")
+        (is (empty? ref-matched-children-ids)
+            "ref-matched-children-ids check failed")
+        (is (= #{"[[foo]] 1" "[[foo]] 2"} (set (map :block/title ref-blocks)))
+            "ref-blocks check failed")))
+
+    (testing "Linked references include \"bar\""
+      (d/transact! conn
+                   [{:db/id (:db/id foo)
+                     :logseq.property.linked-references/includes (:db/id bar)}])
+      (let [{:keys [ref-pages-count ref-blocks ref-matched-children-ids]} (db-reference/get-linked-references @conn (:db/id foo))]
+        (is (= [["Journal" 3] ["baz" 3] ["Jun 11th, 2025" 2] ["bar" 2]] (vec ref-pages-count))
+            "ref-pages-count check failed")
+        (is (= 8 (count ref-matched-children-ids))
+            "ref-matched-children-ids check failed")
+        (is (= #{"[[foo]] 1" "[[foo]] 2"} (set (map :block/title ref-blocks)))
+            "ref-blocks check failed")))
+
+    (testing "Linked references include \"bar\" and \"baz\""
+      (d/transact! conn
+                   [{:db/id (:db/id foo)
+                     :logseq.property.linked-references/includes (:db/id baz)}])
+      (let [{:keys [ref-pages-count ref-blocks ref-matched-children-ids]} (db-reference/get-linked-references @conn (:db/id foo))]
+        (is (= [["Journal" 3] ["baz" 3] ["Jun 11th, 2025" 2] ["bar" 2]] (vec ref-pages-count))
+            "ref-pages-count check failed")
+        (is (= 8 (count ref-matched-children-ids))
+            "ref-matched-children-ids check failed")
+        (is (= #{"[[foo]] 1" "[[foo]] 2"} (set (map :block/title ref-blocks)))
+            "ref-blocks check failed")))
+
+    (testing "Linked references exclude \"bar\""
+      (retract-filters! conn foo-id)
+      (d/transact! conn
+                   [{:db/id (:db/id foo)
+                     :logseq.property.linked-references/excludes (:db/id bar)}])
+      (let [{:keys [ref-pages-count ref-blocks ref-matched-children-ids]} (db-reference/get-linked-references @conn (:db/id foo))]
+        (is (= [["Journal" 3] ["Jun 11th, 2025" 2] ["baz" 2]] (vec ref-pages-count))
+            "ref-pages-count check failed")
+        (is (= 3 (count ref-matched-children-ids))
+            "ref-matched-children-ids check failed")
+        (is (= #{"[[foo]] 1" "[[foo]] 2"} (set (map :block/title ref-blocks)))
+            "ref-blocks check failed")))
+
+    (testing "Linked references exclude \"baz\""
+      (retract-filters! conn foo-id)
+      (d/transact! conn
+                   [{:db/id (:db/id foo)
+                     :logseq.property.linked-references/excludes (:db/id baz)}])
+      (let [{:keys [ref-pages-count ref-blocks ref-matched-children-ids]} (db-reference/get-linked-references @conn (:db/id foo))]
+        (is (= [["Journal" 3] ["Jun 11th, 2025" 2] ["bar" 1]] (vec ref-pages-count))
+            "ref-pages-count check failed")
+        (is (= 4 (count ref-matched-children-ids))
+            "ref-matched-children-ids check failed")
+        (is (= #{"[[foo]] 1" "[[foo]] 2"} (set (map :block/title ref-blocks)))
+            "ref-blocks check failed")))
+
+    (testing "Linked references exclude both \"baz\" and \"bar\""
+      (retract-filters! conn foo-id)
+      (d/transact! conn
+                   [{:db/id (:db/id foo)
+                     :logseq.property.linked-references/excludes #{(:db/id baz) (:db/id bar)}}])
+      (let [{:keys [ref-pages-count ref-blocks ref-matched-children-ids]} (db-reference/get-linked-references @conn (:db/id foo))]
+        (is (= [["Journal" 2] ["Jun 11th, 2025" 2]] (vec ref-pages-count))
+            "ref-pages-count check failed")
+        (is (zero? (count ref-matched-children-ids))
+            "ref-matched-children-ids check failed")
+        (is (= #{"[[foo]] 1" "[[foo]] 2"} (set (map :block/title ref-blocks)))
+            "ref-blocks check failed")))
+
+    (testing "Linked references includes \"bar\" and excludes \"baz\""
+      (retract-filters! conn foo-id)
+      (d/transact! conn
+                   [{:db/id (:db/id foo)
+                     :logseq.property.linked-references/includes (:db/id bar)
+                     :logseq.property.linked-references/excludes (:db/id baz)}])
+      (let [{:keys [ref-pages-count ref-blocks ref-matched-children-ids]} (db-reference/get-linked-references @conn (:db/id foo))]
+        (is (= [["Journal" 2] ["Jun 11th, 2025" 1] ["bar" 1]] (vec ref-pages-count))
+            "ref-pages-count check failed")
+        (is (= 4 (count ref-matched-children-ids))
+            "ref-matched-children-ids check failed")
+        (is (= #{"[[foo]] 1"} (set (map :block/title ref-blocks)))
+            "ref-blocks check failed")))
+
+    (testing "Linked references includes \"baz\" and excludes \"bar\""
+      (retract-filters! conn foo-id)
+      (d/transact! conn
+                   [{:db/id (:db/id foo)
+                     :logseq.property.linked-references/includes (:db/id baz)
+                     :logseq.property.linked-references/excludes (:db/id bar)}])
+      (let [{:keys [ref-pages-count ref-blocks ref-matched-children-ids]} (db-reference/get-linked-references @conn (:db/id foo))]
+        (is (= [["Journal" 3] ["Jun 11th, 2025" 2] ["baz" 2]] (vec ref-pages-count))
+            "ref-pages-count check failed")
+        (is (= 3 (count ref-matched-children-ids))
+            "ref-matched-children-ids check failed")
+        (is (= #{"[[foo]] 1" "[[foo]] 2"} (set (map :block/title ref-blocks)))
+            "ref-blocks check failed")))))
+
+(deftest get-unlinked-references
+  (let [conn (create-conn!)
+        db @conn
+        ids (map #(:db/id (ldb/get-page @conn %)) ["foo" "bar" "baz"])]
+    (is (= [3 2 3]
+           (mapv #(count (db-reference/get-unlinked-references db %)) ids)))))

+ 122 - 0
src/test/frontend/worker/commands_test.cljs

@@ -0,0 +1,122 @@
+(ns frontend.worker.commands-test
+  (:require [cljs-time.coerce :as tc]
+            [cljs-time.core :as t]
+            [cljs.test :refer [deftest is testing]]
+            [frontend.worker.commands :as commands]))
+
+(def get-next-time #'commands/get-next-time)
+(def minute-unit {:db/ident :logseq.property.repeat/recur-unit.minute})
+(def hour-unit {:db/ident :logseq.property.repeat/recur-unit.hour})
+(def day-unit {:db/ident :logseq.property.repeat/recur-unit.day})
+(def week-unit {:db/ident :logseq.property.repeat/recur-unit.week})
+(def month-unit {:db/ident :logseq.property.repeat/recur-unit.month})
+(def year-unit {:db/ident :logseq.property.repeat/recur-unit.year})
+
+(deftest ^:large-vars/cleanup-todo get-next-time-test
+  (let [now (t/now)
+        one-minute-ago (t/minus now (t/minutes 1))
+        one-hour-ago (t/minus now (t/hours 1))
+        one-day-ago (t/minus now (t/days 1))
+        one-week-ago (t/minus now (t/weeks 1))
+        one-month-ago (t/minus now (t/months 1))
+        one-year-ago (t/minus now (t/years 1))
+        in-minutes (fn [next-time] (/ (- next-time now) (* 1000 60)))
+        in-hours (fn [next-time] (/ (- next-time now) (* 1000 60 60)))
+        in-days (fn [next-time] (/ (- next-time now) (* 1000 60 60 24)))
+        in-weeks (fn [next-time] (/ (- next-time now) (* 1000 60 60 24 7)))
+        in-months (fn [next-time] (t/in-months (t/interval now (tc/from-long next-time))))
+        in-years (fn [next-time] (t/in-years (t/interval now (tc/from-long next-time))))]
+    (testing "basic test for get-next-time"
+      ;; minute
+      (let [next-time (get-next-time now minute-unit 1)]
+        (is (= 1 (in-minutes next-time))))
+      (let [next-time (get-next-time one-minute-ago minute-unit 1)]
+        (is (= 1 (in-minutes next-time))))
+      (let [next-time (get-next-time one-minute-ago minute-unit 3)]
+        (is (= 2 (in-minutes next-time))))
+      (let [next-time (get-next-time one-minute-ago minute-unit 5)]
+        (is (= 4 (in-minutes next-time))))
+
+      ;; hour
+      (let [next-time (get-next-time now hour-unit 1)]
+        (is (= 1 (in-hours next-time))))
+      (let [next-time (get-next-time one-hour-ago hour-unit 1)]
+        (is (= 1 (in-hours next-time))))
+      (let [next-time (get-next-time one-hour-ago hour-unit 3)]
+        (is (= 2 (in-hours next-time))))
+      (let [next-time (get-next-time one-hour-ago hour-unit 5)]
+        (is (= 4 (in-hours next-time))))
+
+      ;; day
+      (let [next-time (get-next-time now day-unit 1)]
+        (is (= 1 (in-days next-time))))
+      (let [next-time (get-next-time one-day-ago day-unit 1)]
+        (is (= 1 (in-days next-time))))
+      (let [next-time (get-next-time one-day-ago day-unit 3)]
+        (is (= 2 (in-days next-time))))
+      (let [next-time (get-next-time one-day-ago day-unit 5)]
+        (is (= 4 (in-days next-time))))
+
+      ;; week
+      (let [next-time (get-next-time now week-unit 1)]
+        (is (= 1 (in-weeks next-time))))
+      (let [next-time (get-next-time one-week-ago week-unit 1)]
+        (is (= 1 (in-weeks next-time))))
+      (let [next-time (get-next-time one-week-ago week-unit 3)]
+        (is (= 2 (in-weeks next-time))))
+      (let [next-time (get-next-time one-week-ago week-unit 5)]
+        (is (= 4 (in-weeks next-time))))
+
+      ;; month
+      (let [next-time (get-next-time now month-unit 1)]
+        (is (= 1 (in-months next-time))))
+      (let [next-time (get-next-time one-month-ago month-unit 1)]
+        (is (= 1 (in-months next-time))))
+      (let [next-time (get-next-time one-month-ago month-unit 3)]
+        (is (= 2 (in-months next-time))))
+      (let [next-time (get-next-time one-month-ago month-unit 5)]
+        (is (= 4 (in-months next-time))))
+
+      ;; year
+      (let [next-time (get-next-time now year-unit 1)]
+        (is (= 1 (in-years next-time))))
+      (let [next-time (get-next-time one-year-ago year-unit 1)]
+        (is (= 1 (in-years next-time))))
+      (let [next-time (get-next-time one-year-ago year-unit 3)]
+        (is (= 2 (in-years next-time))))
+      (let [next-time (get-next-time one-year-ago year-unit 5)]
+        (is (= 4 (in-years next-time)))))
+
+    (testing "preserves week day"
+      (let [next-time (get-next-time now week-unit 1)]
+        (is (= (t/day-of-week (tc/from-long next-time)) (t/day-of-week now))))
+      (let [next-time (get-next-time one-week-ago week-unit 1)]
+        (is (= (t/day-of-week (tc/from-long next-time)) (t/day-of-week now)))))
+
+    (testing "schedule on future time should move it to the next one"
+      (let [next-time (get-next-time (t/plus now (t/minutes 10)) minute-unit 1)]
+        (is (= 11 (in-minutes next-time))))
+      (let [next-time (get-next-time (t/plus now (t/hours 10)) hour-unit 1)]
+        (is (= 11 (in-hours next-time))))
+      (let [next-time (get-next-time (t/plus now (t/days 10)) day-unit 1)]
+        (is (= 11 (in-days next-time))))
+      (let [next-time (get-next-time (t/plus now (t/weeks 10)) week-unit 1)]
+        (is (= 11 (in-weeks next-time))))
+      (let [next-time (get-next-time (t/plus now (t/months 10)) month-unit 1)]
+        (is (= 11 (in-months next-time))))
+      (let [next-time (get-next-time (t/plus now (t/years 10)) year-unit 1)]
+        (is (= 11 (in-years next-time)))))
+
+    (testing "schedule on past time should move it to future"
+      (let [next-time (get-next-time (t/minus now (t/minutes 10)) minute-unit 1)]
+        (is (= 1 (in-minutes next-time))))
+      (let [next-time (get-next-time (t/minus now (t/hours 10)) hour-unit 1)]
+        (is (= 1 (in-hours next-time))))
+      (let [next-time (get-next-time (t/minus now (t/days 10)) day-unit 1)]
+        (is (= 1 (in-days next-time))))
+      (let [next-time (get-next-time (t/minus now (t/weeks 10)) week-unit 1)]
+        (is (= 1 (in-weeks next-time))))
+      (let [next-time (get-next-time (t/minus now (t/months 10)) month-unit 1)]
+        (is (= 1 (in-months next-time))))
+      (let [next-time (get-next-time (t/minus now (t/years 10)) year-unit 1)]
+        (is (= 1 (in-years next-time)))))))

+ 1 - 1
typos.toml

@@ -18,4 +18,4 @@ fom = "fom"
 tne = "tne"
 Damon = "Damon"
 [files]
-extend-exclude = ["resources/*", "src/resources/*", "scripts/resources/*", "e2e-tests/plugin/lsplugin.user.js"]
+extend-exclude = ["resources/*", "src/resources/*", "scripts/resources/*", "e2e-tests/plugin/lsplugin.user.js", "src/test/fixtures/*"]

Some files were not shown because too many files changed in this diff