Răsfoiți Sursa

feat: add #Cards and :logseq.property/query

Some enhancements:
1. DSL query is a property value (:default type) for better UX.
2. Cards can be selected on the flashcards dialog.
Tienson Qin 1 an în urmă
părinte
comite
263a2bf219

+ 14 - 7
deps/common/src/logseq/common/util.cljs

@@ -82,9 +82,18 @@
   [v]
   (string/trim (subs v 1 (dec (count v)))))
 
+(defn wrapped-by
+  [v start end]
+  (and (string? v) (>= (count v) 2)
+       (= start (first v)) (= end (last v))))
+
 (defn wrapped-by-quotes?
   [v]
-  (and (string? v) (>= (count v) 2) (= "\"" (first v) (last v))))
+  (wrapped-by v "\"" "\""))
+
+(defn wrapped-by-parens?
+  [v]
+  (wrapped-by v "(" ")"))
 
 (defn url?
   "Test if it is a `protocol://`-style URL.
@@ -161,7 +170,6 @@
            (map string/capitalize)
            (string/join " ")))
 
-
 (defn distinct-by
   "Copy from medley"
   [f coll]
@@ -177,9 +185,9 @@
     (step (seq coll) #{})))
 
 (defn distinct-by-last-wins
-     [f col]
-     {:pre [(sequential? col)]}
-     (reverse (distinct-by f (reverse col))))
+  [f col]
+  {:pre [(sequential? col)]}
+  (reverse (distinct-by f (reverse col))))
 
 (defn normalize-format
   [format]
@@ -315,7 +323,6 @@
   [s old-value new-value]
   (string/replace-first s (re-pattern (str "(?i)" (escape-regex-chars old-value))) new-value))
 
-
 (defn sort-coll-by-dependency
   "Sort the elements in the collection based on dependencies.
 coll:  [{:id 1 :depend-on 2} {:id 2 :depend-on 3} {:id 3}]
@@ -347,4 +354,4 @@ return: [{:id 3} {:id 2 :depend-on 3} {:id 1 :depend-on 2}]"
               (let [rest-ids* (disj rest-ids id)]
                 (vreset! seen-ids #{})
                 (recur (conj r id) rest-ids* (first rest-ids*))))))]
-    (mapv id->elem sorted-ids)))
+    (mapv id->elem sorted-ids)))

+ 20 - 3
deps/db/src/logseq/db.cljs

@@ -1,7 +1,8 @@
 (ns logseq.db
   "Main namespace for public db fns. For DB and file graphs.
    For shared file graph only fns, use logseq.graph-parser.db"
-  (:require [clojure.string :as string]
+  (:require [clojure.set :as set]
+            [clojure.string :as string]
             [datascript.core :as d]
             [datascript.impl.entity :as de]
             [logseq.common.config :as common-config]
@@ -189,7 +190,6 @@
   (assert (or (de/entity? block) (nil? block)))
   (first (sort-by-order (:block/_parent block))))
 
-
 (defn get-pages
   [db]
   (->> (d/q
@@ -450,7 +450,6 @@
     (when (seq refs)
       (d/pull-many db '[*] (map :db/id refs)))))
 
-
 (defn get-block-refs-count
   [db id]
   (some-> (d/entity db id)
@@ -620,6 +619,24 @@
           (recur (:logseq.property/parent current-parent)))))
     @*classes))
 
+(defn get-classes-parents
+  [db tags]
+  (let [tags' (filter class? tags)
+        result (map
+                (fn [id] (d/entity db id))
+                (mapcat get-class-parents tags'))]
+    (set result)))
+
+(defn class-instance?
+  "Whether `object` is an instance of `class`"
+  [class object]
+  (let [tags (:block/tags object)
+        tags-ids (set (map :db/id tags))]
+    (or
+     (contains? tags-ids (:db/id class))
+     (let [parents (get-classes-parents (.-db object) tags)]
+       (contains? (set/union parents tags-ids) (:db/id class))))))
+
 (defn get-all-pages-views
   [db]
   (when (db-based-graph? db)

+ 18 - 6
deps/db/src/logseq/db/frontend/class.cljs

@@ -7,20 +7,32 @@
   "Map of built-in classes for db graphs with their :db/ident as keys"
   {:logseq.class/Root {:title "Root Tag"}
 
-   :logseq.class/Query
-   {:title "Query"
-    :properties {:logseq.property/icon {:type :tabler-icon :id "search"}}}
-
    :logseq.class/Task
    {:title "Task"
     :schema {:properties [:logseq.task/status :logseq.task/priority :logseq.task/deadline]}}
 
+   :logseq.class/Journal
+   {:title "Journal"
+    :properties {:logseq.property.journal/title-format "MMM do, yyyy"}}
+
+   :logseq.class/Query
+   {:title "Query"
+    :properties {:logseq.property/icon {:type :tabler-icon :id "search"}}
+    :schema {:properties [:logseq.property/query]}}
+
+   :logseq.class/Advanced-query
+   {:title "Advanced query"
+    :properties {:logseq.property/icon {:type :tabler-icon :id "search"}
+                 :logseq.property/parent :logseq.class/Query}}
+
    :logseq.class/Card
    {:title "Card"
     :schema {:properties [:logseq.property.fsrs/state :logseq.property.fsrs/due]}}
 
-   :logseq.class/Journal {:title "Journal"
-                          :properties {:logseq.property.journal/title-format "MMM do, yyyy"}}
+   :logseq.class/Cards
+   {:title "Cards"
+    :properties {:logseq.property/icon {:type :tabler-icon :id "search"}
+                 :logseq.property/parent :logseq.class/Query}}
 
    ;; TODO: Add more classes such as :book, :paper, :movie, :music, :project
    })

+ 8 - 4
deps/db/src/logseq/db/frontend/property.cljs

@@ -51,6 +51,10 @@
                                           :schema {:type :checkbox
                                                    :public? true
                                                    :view-context :class}}
+   :logseq.property/query {:title "Query"
+                           :schema {:type :default
+                                    :public? true
+                                    :view-context :block}}
    :logseq.property/page-tags {:title "Page Tags"
                                :schema {:type :page
                                         :public? true
@@ -210,10 +214,10 @@
                                             :hide? true
                                             :public? false}}
 
-    :logseq.property.table/sized-columns {:schema
-                                          {:type :map
-                                           :hide? true
-                                           :public? false}}
+   :logseq.property.table/sized-columns {:schema
+                                         {:type :map
+                                          :hide? true
+                                          :public? false}}
 
    :logseq.property/view-for {:schema
                               {:type :node

+ 2 - 3
deps/db/src/logseq/db/frontend/schema.cljs

@@ -2,7 +2,7 @@
   "Main datascript schemas for the Logseq app"
   (:require [clojure.set :as set]))
 
-(def version 23)
+(def version 25)
 ;; A page is a special block, a page can corresponds to multiple files with the same ":block/name".
 (def ^:large-vars/data-var schema
   {:db/ident        {:db/unique :db.unique/identity}
@@ -103,8 +103,7 @@
    :file/content {}
    :file/created-at {}
    :file/last-modified-at {}
-   :file/size {}
-   })
+   :file/size {}})
 
 (def schema-for-db-based-graph
   (merge

+ 4 - 9
deps/outliner/src/logseq/outliner/property.cljs

@@ -43,7 +43,6 @@
           [:db/retract (:db/id block) property-id :logseq.property/empty-placeholder])
         block-tx-data]))))
 
-
 (defn- get-property-value-schema
   "Gets a malli schema to validate the property value for the given property type and builds
    it with additional args like datascript db"
@@ -354,17 +353,13 @@
                            [[:db/retract (:db/id block) property-id property-value]]
                            {:outliner-op :save-block})))))))
 
-(defn ^:api get-class-parents
+(defn ^:api get-classes-parents
   [db tags]
-  (let [tags' (filter ldb/class? tags)
-        result (map
-                (fn [id] (d/entity db id))
-                (mapcat ldb/get-class-parents tags'))]
-    (set result)))
+  (ldb/get-classes-parents db tags))
 
 (defn ^:api get-class-properties
   [db class]
-  (let [class-parents (get-class-parents db [class])]
+  (let [class-parents (get-classes-parents db [class])]
     (->> (mapcat (fn [class]
                    (:logseq.property.class/properties class)) (concat [class] class-parents))
          (common-util/distinct-by :db/id)
@@ -376,7 +371,7 @@
         classes (->> (:block/tags block)
                      (sort-by :block/name)
                      (filter ldb/class?))
-        class-parents (get-class-parents db classes)
+        class-parents (get-classes-parents db classes)
         all-classes (->> (concat classes class-parents)
                          (filter (fn [class]
                                    (seq (:logseq.property.class/properties class)))))

+ 108 - 109
deps/shui/src/logseq/shui/dialog/core.cljs

@@ -38,7 +38,7 @@
             (let [v (get config k)
                   v (if (fn? v) (apply v args) v)]
               (if (vector? v) (assoc config k (interpret v)) config)))
-    config ks))
+          config ks))
 
 ;; {:id :title :description :content :footer :open? :on-close ...}
 (def ^:private *modals (atom []))
@@ -49,7 +49,7 @@
   [id]
   (when id
     (some->> (medley/indexed @*modals)
-      (filter #(= id (:id (second %)))) (first))))
+             (filter #(= id (:id (second %)))) (first))))
 
 (defn update-modal!
   [id ks val]
@@ -90,13 +90,14 @@
         config (cond-> config
                  (fn? content)
                  (assoc :content (content config)))]
-    (upsert-modal! config)))
+    (upsert-modal! (assoc-in config [:content-props :onOpenAutoFocus]
+                             #(.preventDefault %)))))
 
 (defn alert!
   [content-or-config & config']
   (let [deferred (p/deferred)]
     (open! content-or-config
-      (merge {:alert? :default :deferred deferred} (first config')))
+           (merge {:alert? :default :deferred deferred} (first config')))
     (p/promise deferred)))
 
 (defn confirm!
@@ -121,45 +122,45 @@
   (let [{:keys [id title description content footer on-open-change align open?
                 auto-width? close-btn? root-props content-props]} config
         props (dissoc config
-                :id :title :description :content :footer :auto-width? :close-btn?
-                :align :on-open-change :open? :root-props :content-props)
+                      :id :title :description :content :footer :auto-width? :close-btn?
+                      :align :on-open-change :open? :root-props :content-props)
         props (assoc-in props [:overlay-props :data-align] (name (or align :center)))]
 
     (rum/use-effect!
-      (fn []
-        (when (false? open?)
-          (js/setTimeout #(detach-modal! id) 128)))
-      [open?])
+     (fn []
+       (when (false? open?)
+         (js/setTimeout #(detach-modal! id) 128)))
+     [open?])
 
     (dialog
-      (merge root-props
-        {:key (str "modal-" id)
-         :open open?
-         :on-open-change (fn [v]
-                           (let [set-open! #(update-modal! id :open? %)]
-                             (if (fn? on-open-change)
-                               (on-open-change {:value v :set-open! set-open!})
-                               (set-open! v))))})
-      (let [onPointerDownOutside (:onPointerDownOutside content-props)
-            content-props (assoc content-props
-                            :onPointerDownOutside
-                            (fn [^js e]
-                              (when (fn? onPointerDownOutside)
-                                (onPointerDownOutside e))
-                              (when-not (some-> (.-target e) (.closest ".ui__dialog-overlay"))
-                                (.preventDefault e))))]
-        (dialog-content
-          (cond-> (merge props content-props)
-            auto-width? (assoc :data-auto-width true)
-            (false? close-btn?) (assoc :data-close-btn false))
-          (when title
-            (dialog-header
-              (when title (dialog-title title))
-              (when description (dialog-description description))))
-          (when content
-            [:div.ui__dialog-main-content content])
-          (when footer
-            (dialog-footer footer)))))))
+     (merge root-props
+            {:key (str "modal-" id)
+             :open open?
+             :on-open-change (fn [v]
+                               (let [set-open! #(update-modal! id :open? %)]
+                                 (if (fn? on-open-change)
+                                   (on-open-change {:value v :set-open! set-open!})
+                                   (set-open! v))))})
+     (let [onPointerDownOutside (:onPointerDownOutside content-props)
+           content-props (assoc content-props
+                                :onPointerDownOutside
+                                (fn [^js e]
+                                  (when (fn? onPointerDownOutside)
+                                    (onPointerDownOutside e))
+                                  (when-not (some-> (.-target e) (.closest ".ui__dialog-overlay"))
+                                    (.preventDefault e))))]
+       (dialog-content
+        (cond-> (merge props content-props)
+          auto-width? (assoc :data-auto-width true)
+          (false? close-btn?) (assoc :data-close-btn false))
+        (when title
+          (dialog-header
+           (when title (dialog-title title))
+           (when description (dialog-description description))))
+        (when content
+          [:div.ui__dialog-main-content content])
+        (when footer
+          (dialog-footer footer)))))))
 
 (rum/defc alert-inner
   [config]
@@ -167,35 +168,34 @@
         props (dissoc config :id :title :description :content :footer :deferred :open? :alert?)]
 
     (rum/use-effect!
-      (fn []
-        (when (false? open?)
-          (js/setTimeout #(detach-modal! id) 128)))
-      [open?])
+     (fn []
+       (when (false? open?)
+         (js/setTimeout #(detach-modal! id) 128)))
+     [open?])
 
     (alert-dialog
-      {:key (str "alert-" id)
-       :open open?
-       :on-open-change #(update-modal! id :open? %)}
-      (alert-dialog-content props
-        (when (or title description)
-          (alert-dialog-header
-            {:class "ui__alert-dialog-header"}
-            (when title (alert-dialog-title title))
-            (when description (alert-dialog-description description))))
+     {:key (str "alert-" id)
+      :open open?
+      :on-open-change #(update-modal! id :open? %)}
+     (alert-dialog-content props
+                           (when (or title description)
+                             (alert-dialog-header
+                              {:class "ui__alert-dialog-header"}
+                              (when title (alert-dialog-title title))
+                              (when description (alert-dialog-description description))))
 
-        (when content
-          [:div.ui__alert-dialog-main-content content])
+                           (when content
+                             [:div.ui__alert-dialog-main-content content])
 
-        (alert-dialog-footer
-          {:class "ui__alert-dialog-footer"}
-          (if footer
-            footer
-            [:<>
-             (base/button
-               {:key "ok"
-                :on-click #(do (close!) (p/resolve! deferred true))
-                :size :sm
-                } "OK")]))))))
+                           (alert-dialog-footer
+                            {:class "ui__alert-dialog-footer"}
+                            (if footer
+                              footer
+                              [:<>
+                               (base/button
+                                {:key "ok"
+                                 :on-click #(do (close!) (p/resolve! deferred true))
+                                 :size :sm} "OK")]))))))
 
 (rum/defc confirm-inner
   [config]
@@ -206,56 +206,55 @@
         *reminder-ref (rum/use-ref nil)]
 
     (rum/use-effect!
-      (fn []
-        (when ready?
-          (js/setTimeout
-            #(some-> (rum/deref *ok-ref) (.focus)) 128)))
-      [ready?])
+     (fn []
+       (when ready?
+         (js/setTimeout
+          #(some-> (rum/deref *ok-ref) (.focus)) 128)))
+     [ready?])
 
     (rum/use-effect!
-      (fn []
-        (try
-          (if-let [reminder-v (and reminder? (js/localStorage.getItem (str id)))]
-            (if (< (- (js/Date.now) reminder-v) (* 1000 60 10))
-              (do (detach-modal! id) (p/resolve! deferred true))
-              (set-ready! true))
-            (set-ready! true))
-          (catch js/Error _e
-            (set-ready! true))))
-      [])
+     (fn []
+       (try
+         (if-let [reminder-v (and reminder? (js/localStorage.getItem (str id)))]
+           (if (< (- (js/Date.now) reminder-v) (* 1000 60 10))
+             (do (detach-modal! id) (p/resolve! deferred true))
+             (set-ready! true))
+           (set-ready! true))
+         (catch js/Error _e
+           (set-ready! true))))
+     [])
 
     (when ready?
       (alert-inner
-        (assoc config
-          :data-mode :confirm
-          :overlay-props
-          {:on-click #(when outside-cancel? (close!) (p/reject! deferred nil))}
+       (assoc config
+              :data-mode :confirm
+              :overlay-props
+              {:on-click #(when outside-cancel? (close!) (p/reject! deferred nil))}
 
-          :footer
-          [:<>
-           [:span.flex.items-center.pt-1
-            (when (and id data-reminder)
-              [:label.flex.items-center.gap-1.text-sm
-               (form/checkbox {:ref *reminder-ref})
-               [:span.opacity-50 "Don't remind me again"]])]
-           [:span.flex.gap-2
-            (base/button
-              {:key "cancel"
-               :on-click #(do (close!) (p/reject! deferred false))
-               :variant :outline
-               :size :sm}
-              "Cancel")
-            (base/button
-              {:key "ok"
-               :ref *ok-ref
-               :on-click (fn []
-                           (when-let [^js reminder (and id data-reminder (rum/deref *reminder-ref))]
-                             (when (= "checked" (.-state (.-dataset reminder)))
-                               (js/localStorage.setItem (str id) (js/Date.now))))
-                           (close!)
-                           (p/resolve! deferred true))
-               :size :sm
-               } "OK")]])))))
+              :footer
+              [:<>
+               [:span.flex.items-center.pt-1
+                (when (and id data-reminder)
+                  [:label.flex.items-center.gap-1.text-sm
+                   (form/checkbox {:ref *reminder-ref})
+                   [:span.opacity-50 "Don't remind me again"]])]
+               [:span.flex.gap-2
+                (base/button
+                 {:key "cancel"
+                  :on-click #(do (close!) (p/reject! deferred false))
+                  :variant :outline
+                  :size :sm}
+                 "Cancel")
+                (base/button
+                 {:key "ok"
+                  :ref *ok-ref
+                  :on-click (fn []
+                              (when-let [^js reminder (and id data-reminder (rum/deref *reminder-ref))]
+                                (when (= "checked" (.-state (.-dataset reminder)))
+                                  (js/localStorage.setItem (str id) (js/Date.now))))
+                              (close!)
+                              (p/resolve! deferred true))
+                  :size :sm} "OK")]])))))
 
 (rum/defc install-modals
   < rum/static
@@ -266,8 +265,8 @@
       (let [id (:id config)
             alert? (:alert? config)
             config (interpret-vals config
-                     [:title :description :content :footer]
-                     {:id id})]
+                                   [:title :description :content :footer]
+                                   {:id id})]
         (case alert?
           :default
           (alert-inner config)

+ 315 - 298
src/main/frontend/components/block.cljs

@@ -280,106 +280,104 @@
   (let [size (get state ::size)
         breadcrumb? (:breadcrumb? config)]
     (ui/resize-provider
-      (ui/resize-consumer
-        (if (and (not (mobile-util/native-platform?))
-              (not breadcrumb?))
-          (cond->
-            {:className "resize image-resize"
-             :onSizeChanged (fn [value]
-                              (when (and (not @*resizing-image?)
+     (ui/resize-consumer
+      (if (and (not (mobile-util/native-platform?))
+               (not breadcrumb?))
+        (cond->
+         {:className "resize image-resize"
+          :onSizeChanged (fn [value]
+                           (when (and (not @*resizing-image?)
                                       (some? @size)
                                       (not= value @size))
-                                (reset! *resizing-image? true))
-                              (reset! size value))
-             :onPointerUp (fn []
-                            (when (and @size @*resizing-image?)
-                              (when-let [block-id (:block/uuid config)]
-                                (let [size (bean/->clj @size)]
-                                  (editor-handler/resize-image! block-id metadata full-text size))))
-                            (when @*resizing-image?
+                             (reset! *resizing-image? true))
+                           (reset! size value))
+          :onPointerUp (fn []
+                         (when (and @size @*resizing-image?)
+                           (when-let [block-id (:block/uuid config)]
+                             (let [size (bean/->clj @size)]
+                               (editor-handler/resize-image! block-id metadata full-text size))))
+                         (when @*resizing-image?
                               ;; TODO: need a better way to prevent the clicking to edit current block
-                              (js/setTimeout #(reset! *resizing-image? false) 200)))
-             :onClick (fn [e]
-                        (when @*resizing-image? (util/stop e)))}
-            (and (:width metadata) (not (util/mobile?)))
-            (assoc :style {:width (:width metadata)}))
-          {})
-        [:div.asset-container {:key "resize-asset-container"}
-         [:img.rounded-sm.relative
-          (merge
-            {:loading "lazy"
-             :referrerPolicy "no-referrer"
-             :src src
-             :title title}
-            metadata)]
-         (when-not breadcrumb?
-           [:<>
-            (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 (:block/uuid config)]
-                        (let [*local-selected? (atom local?)]
-                          (-> (shui/dialog-confirm!
-                                [:div.text-xs.opacity-60.-my-2
-                                 (when local?
-                                   [: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})
+                           (js/setTimeout #(reset! *resizing-image? false) 200)))
+          :onClick (fn [e]
+                     (when @*resizing-image? (util/stop e)))}
+          (and (:width metadata) (not (util/mobile?)))
+          (assoc :style {:width (:width metadata)}))
+        {})
+      [:div.asset-container {:key "resize-asset-container"}
+       [:img.rounded-sm.relative
+        (merge
+         {:loading "lazy"
+          :referrerPolicy "no-referrer"
+          :src src
+          :title title}
+         metadata)]
+       (when-not breadcrumb?
+         [:<>
+          (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 (:block/uuid config)]
+                      (let [*local-selected? (atom local?)]
+                        (-> (shui/dialog-confirm!
+                             [:div.text-xs.opacity-60.-my-2
+                              (when local?
+                                [: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
-                                         :local? local?
-                                         :delete-local? @*local-selected?
-                                         :repo (state/get-current-repo)
-                                         :href src
-                                         :title title
-                                         :full-text full-text})))))
-                        ))}
-                   (ui/icon "trash")])
+                                       {:block-id block-id
+                                        :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 :asset/copy)
+                 {: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)
-                              (-> (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")])
-                ]])])]))))
+                              (if local?
+                                (ipc/ipc "openFileInFolder" image-src)
+                                (js/window.apis.openExternal image-src)))}
+                 (shui/tabler-icon "folder-pin")])]])])]))))
 
 (rum/defc audio-cp [src]
   ;; Change protocol to allow media fragment uris to play
@@ -388,16 +386,16 @@
            :on-touch-start #(util/stop %)}])
 
 (rum/defcs asset-link < rum/reactive
-                        (rum/local nil ::src)
+  (rum/local nil ::src)
   [state config title href metadata full_text]
   (let [src (::src state)
         granted? (state/sub [:nfs/user-granted? (state/get-current-repo)])
         href (config/get-local-asset-absolute-path href)]
     (when (and (or granted?
-                 (util/electron?)
-                 (mobile-util/native-platform?)
-                 (config/db-based-graph? (state/get-current-repo)))
-            (nil? @src))
+                   (util/electron?)
+                   (mobile-util/native-platform?)
+                   (config/db-based-graph? (state/get-current-repo)))
+               (nil? @src))
       (p/then (assets-handler/make-asset-url href) #(reset! src %)))
 
     (when @src
@@ -673,31 +671,31 @@
   [children {:keys [*timer *timer1 visible? set-visible! render *el-popup]}]
   (let [*el-trigger (rum/use-ref nil)]
     (rum/use-effect!
-      (fn []
-        (when (true? visible?)
-          (shui/popup-show!
-            (rum/deref *el-trigger) render
-            {:root-props {:onOpenChange (fn [v] (set-visible! v))
-                          :modal false}
-             :content-props {:class "ls-preview-popup"
-                             :onInteractOutside (fn [^js e] (.preventDefault e))
-                             :onEscapeKeyDown (fn [^js e]
-                                                (when (state/editing?)
-                                                  (.preventDefault e)
-                                                  (some-> (rum/deref *el-popup) (.focus))))}
-             :as-dropdown? false}))
-
-        (when (false? visible?)
-          (shui/popup-hide!)
-          (when (state/get-edit-block)
-            (state/clear-edit!)))
-        (rum/set-ref! *timer nil)
-        (rum/set-ref! *timer1 nil)
+     (fn []
+       (when (true? visible?)
+         (shui/popup-show!
+          (rum/deref *el-trigger) render
+          {:root-props {:onOpenChange (fn [v] (set-visible! v))
+                        :modal false}
+           :content-props {:class "ls-preview-popup"
+                           :onInteractOutside (fn [^js e] (.preventDefault e))
+                           :onEscapeKeyDown (fn [^js e]
+                                              (when (state/editing?)
+                                                (.preventDefault e)
+                                                (some-> (rum/deref *el-popup) (.focus))))}
+           :as-dropdown? false}))
+
+       (when (false? visible?)
+         (shui/popup-hide!)
+         (when (state/get-edit-block)
+           (state/clear-edit!)))
+       (rum/set-ref! *timer nil)
+       (rum/set-ref! *timer1 nil)
         ;; teardown
-        (fn []
-          (when visible?
-            (shui/popup-hide!))))
-      [visible?])
+       (fn []
+         (when visible?
+           (shui/popup-hide!))))
+     [visible?])
 
     [:span.preview-ref-link
      {:ref *el-trigger
@@ -706,7 +704,7 @@
                               timer1 (rum/deref *timer1)]
                           (when-not timer
                             (rum/set-ref! *timer
-                              (js/setTimeout #(set-visible! true) 1000)))
+                                          (js/setTimeout #(set-visible! true) 1000)))
                           (when timer1
                             (js/clearTimeout timer1)
                             (rum/set-ref! *timer1 nil))))
@@ -718,7 +716,7 @@
                             (rum/set-ref! *timer nil))
                           (when-not timer1
                             (rum/set-ref! *timer1
-                              (js/setTimeout #(set-visible! false) 300)))))}
+                                          (js/setTimeout #(set-visible! false) 300)))))}
      children]))
 
 (rum/defc page-preview-trigger
@@ -738,27 +736,27 @@
                                   [])
 
                                  (when-let [source (or (db/get-alias-source-page (state/get-current-repo) (:db/id page-entity))
-                                                  page-entity)]
+                                                       page-entity)]
                                    [:div.tippy-wrapper.as-page
-                                      {:ref *el-popup
-                                       :tab-index -1
-                                       :style {:width 600
-                                               :text-align "left"
-                                               :font-weight 500
-                                               :padding-bottom 64}
-                                       :on-mouse-enter (fn []
-                                                         (when-let [timer1 (rum/deref *timer1)]
-                                                           (js/clearTimeout timer1)))
-                                       :on-mouse-leave (fn []
+                                    {:ref *el-popup
+                                     :tab-index -1
+                                     :style {:width 600
+                                             :text-align "left"
+                                             :font-weight 500
+                                             :padding-bottom 64}
+                                     :on-mouse-enter (fn []
+                                                       (when-let [timer1 (rum/deref *timer1)]
+                                                         (js/clearTimeout timer1)))
+                                     :on-mouse-leave (fn []
                                                          ;; check the top popup whether is the preview popup
-                                                         (when (ui/last-shui-preview-popup?)
-                                                           (rum/set-ref! *timer1
-                                                                         (js/setTimeout #(set-visible! false) 500))))}
-                                      (let [page-cp (state/get-page-blocks-cp)]
-                                        (page-cp {:repo (state/get-current-repo)
-                                                  :page-name (str (:block/uuid source))
-                                                  :sidebar? sidebar?
-                                                  :preview? true}))]))]
+                                                       (when (ui/last-shui-preview-popup?)
+                                                         (rum/set-ref! *timer1
+                                                                       (js/setTimeout #(set-visible! false) 500))))}
+                                    (let [page-cp (state/get-page-blocks-cp)]
+                                      (page-cp {:repo (state/get-current-repo)
+                                                :page-name (str (:block/uuid source))
+                                                :sidebar? sidebar?
+                                                :preview? true}))]))]
 
     (if (and (not (:preview? config))
              (or (not manual?) open?))
@@ -776,7 +774,7 @@
   (let [db-based? (config/db-based-graph? (state/get-current-repo))
         ->ref (if db-based? page-ref/->page-ref block-ref/->block-ref)]
     [:span.warning.mr-1 {:title "Node ref invalid"}
-   (->ref id)]))
+     (->ref id)]))
 
 (rum/defcs page-cp-inner < db-mixins/query rum/reactive
   "Component for a page. `page` argument contains :block/name which can be (un)sanitized page name.
@@ -1020,15 +1018,15 @@
 
                                   :on-mouse-leave (fn []
                                                     (rum/set-ref! *timer1
-                                                      (js/setTimeout #(set-visible! false) 500)))}
+                                                                  (js/setTimeout #(set-visible! false) 500)))}
                                  [(breadcrumb config repo id {:indent? true})
                                   (blocks-container
-                                    (assoc config :id (str id) :preview? true)
-                                    [(db/entity [:block/uuid id])])]])]
+                                   (assoc config :id (str id) :preview? true)
+                                   [(db/entity [:block/uuid id])])]])]
     (popup-preview-impl children
-      {:visible? visible? :set-visible! set-visible!
-       :*timer *timer :*timer1 *timer1
-       :render render})))
+                        {:visible? visible? :set-visible! set-visible!
+                         :*timer *timer :*timer1 *timer1
+                         :render render})))
 
 (rum/defc block-reference < rum/reactive db-mixins/query
   {:init (fn [state]
@@ -1225,24 +1223,24 @@
                                         (state/set-current-pdf! current)
                                         (util/stop e))))]
                         (-> (load$)
-                          (p/catch
-                            (fn [^js _e]
+                            (p/catch
+                             (fn [^js _e]
                               ;; load pdf asset to indexed db
-                              (p/let [[handle] (js/window.showOpenFilePicker
+                               (p/let [[handle] (js/window.showOpenFilePicker
                                                  (bean/->js {:multiple false :startIn "documents" :types [{:accept {"application/pdf" [".pdf"]}}]}))
-                                      file (.getFile handle)
-                                      buffer (.arrayBuffer file)]
-                                (when-let [content (some-> buffer (js/Uint8Array.))]
-                                  (let [repo (state/get-current-repo)
-                                        file-rpath (string/replace s #"^[.\/\\]*assets[\/\\]+" "assets/")
-                                        dir (config/get-repo-dir repo)]
-                                    (-> (fs/write-file! repo dir file-rpath content nil)
-                                      (p/then load$)))))
-                              (js/console.error _e)))))))
+                                       file (.getFile handle)
+                                       buffer (.arrayBuffer file)]
+                                 (when-let [content (some-> buffer (js/Uint8Array.))]
+                                   (let [repo (state/get-current-repo)
+                                         file-rpath (string/replace s #"^[.\/\\]*assets[\/\\]+" "assets/")
+                                         dir (config/get-repo-dir repo)]
+                                     (-> (fs/write-file! repo dir file-rpath content nil)
+                                         (p/then load$)))))
+                               (js/console.error _e)))))))
         :draggable true
         :on-drag-start #(.setData (gobj/get % "dataTransfer") "file" s)}
        (or label-text
-         (->elem :span (map-inline config label)))]
+           (->elem :span (map-inline config label)))]
 
       (contains? config/doc-formats ext)
       (asset-link config label-text s metadata full_text)
@@ -1264,7 +1262,7 @@
 
     ;; FIXME: same headline, see more https://orgmode.org/manual/Internal-Links.html
     (and (= \* (first s))
-      (not= \* (last s)))
+         (not= \* (last s)))
     (->elem :a {:on-click #(route-handler/jump-to-anchor! (mldoc/anchorLink (subs s 1)))} (subs s 1))
 
     (block-ref/block-ref? s)
@@ -1278,7 +1276,7 @@
     (->elem :a {:href s
                 :data-href s
                 :target "_blank"}
-      (map-inline config label))
+            (map-inline config label))
 
     (show-link? config metadata s full_text)
     (media-link config url s label metadata full_text)
@@ -1294,9 +1292,9 @@
                  :else
                  (relative-assets-path->absolute-path s))]
       (->elem
-        :a
-        (cond->
-          {:href (path/path-join "file://" path)
+       :a
+       (cond->
+        {:href (path/path-join "file://" path)
          :data-href path
          :target    "_blank"}
          title
@@ -1880,7 +1878,7 @@
     :else
     (when uuid
       (-> (or on-redirect-to-page route-handler/redirect-to-page!)
-        (apply [(str uuid)])))))
+          (apply [(str uuid)])))))
 
 (declare block-list)
 (rum/defc block-children < rum/reactive
@@ -1975,7 +1973,8 @@
                           (and (some? icon)
                                (or (db/page? block)
                                    (:logseq.property/icon block)
-                                   link?))
+                                   link?
+                                   (seq (:block/tags block))))
                           icon
 
                           :else
@@ -2035,9 +2034,9 @@
 (declare block-content)
 
 (defn build-block-title
-  [config {:block/keys [marker pre-block? properties] :as t}]
-  (let [block-title (:block.temp/ast-title t)
-        config (assoc config :block t)
+  [config {:block/keys [marker pre-block? properties] :as block}]
+  (let [block-title (:block.temp/ast-title block)
+        config (assoc config :block block)
         level (:level config)
         slide? (boolean (:slide? config))
         block-ref? (:block-ref? config)
@@ -2045,7 +2044,7 @@
         html-export? (:html-export? config)
         bg-color (pu/lookup properties :logseq.property/background-color)
         ;; `heading-level` is for backward compatibility, will remove it in later releases
-        heading-level (:block/heading-level t)
+        heading-level (:block/heading-level block)
         heading (or
                  (and heading-level
                       (<= heading-level 6)
@@ -2085,7 +2084,7 @@
                              (if (and area? (.contains (.-classList target) "blank"))
                                :actions
                                (do
-                                 (pdf-assets/open-block-ref! t)
+                                 (pdf-assets/open-block-ref! block)
                                  (util/stop e)))
 
                              :dune)))}
@@ -2098,20 +2097,20 @@
 
                       (when (and area?
                                  (pu/lookup properties :logseq.property.pdf/hl-stamp))
-                        (pdf-assets/area-display t))])]
+                        (pdf-assets/area-display block))])]
        (remove-nils
         (concat
          (when (config/local-file-based-graph? (state/get-current-repo))
-          [(when (and (not pre-block?)
-                      (not html-export?)
-                      (not slide?))
-             (file-block/block-checkbox t (str "mr-1 cursor")))
-           (when (and (not pre-block?)
-                      (not html-export?)
-                      (not slide?))
-             (file-block/marker-switch t))
-           (file-block/marker-cp t)
-           (file-block/priority-cp t)])
+           [(when (and (not pre-block?)
+                       (not html-export?)
+                       (not slide?))
+              (file-block/block-checkbox block (str "mr-1 cursor")))
+            (when (and (not pre-block?)
+                       (not html-export?)
+                       (not slide?))
+              (file-block/marker-switch block))
+            (file-block/marker-cp block)
+            (file-block/priority-cp block)])
 
          ;; highlight ref block (inline)
          (when-not area? [(hl-ref)])
@@ -2121,7 +2120,16 @@
           (when (= block-type :whiteboard-shape) [:span.mr-1 (ui/icon "whiteboard-element" {:extension? true})]))
 
          ;; highlight ref block (area)
-         (when area? [(hl-ref)])))))))
+         (when area? [(hl-ref)])
+
+         (when (and (seq block-title) (ldb/class-instance? (db/entity :logseq.class/Cards) block))
+           [(shui/button
+             {:variant :outline
+              :size :sm
+              :class "ml-2"
+              :on-click (fn [_]
+                          (state/pub-event! [:modal/show-cards (:db/id block)]))}
+             "Practice")])))))))
 
 (rum/defc span-comma
   []
@@ -2231,17 +2239,17 @@
 
   (let [*trigger-ref (rum/use-ref nil)]
     (rum/use-effect!
-      (fn []
-        (let [pid (shui/popup-show!
-                    (.closest (rum/deref *trigger-ref) "a")
-                    (datetime-comp/date-picker nil nil (repeated/timestamp->map ast))
-                    {:id :timestamp-editor
-                     :align :start
-                     :root-props {:onOpenChange #(reset! *show-datapicker? %)}
-                     :content-props {:onEscapeKeyDown #(reset! *show-datapicker? false)}})]
-          #(do (shui/popup-hide! pid)
-               (reset! *show-datapicker? false))))
-      [])
+     (fn []
+       (let [pid (shui/popup-show!
+                  (.closest (rum/deref *trigger-ref) "a")
+                  (datetime-comp/date-picker nil nil (repeated/timestamp->map ast))
+                  {:id :timestamp-editor
+                   :align :start
+                   :root-props {:onOpenChange #(reset! *show-datapicker? %)}
+                   :content-props {:onEscapeKeyDown #(reset! *show-datapicker? false)}})]
+         #(do (shui/popup-hide! pid)
+              (reset! *show-datapicker? false))))
+     [])
     [:i {:ref *trigger-ref}]))
 
 (rum/defcs timestamp-cp
@@ -2385,19 +2393,19 @@
       (-> block (dissoc :block/children :block/page)))]
 
     (let [title-collapse-enabled? (:outliner/block-title-collapse-enabled? (state/get-config))]
-        (when (and (not block-ref-with-title?)
-                   (seq body)
-                   (or (not title-collapse-enabled?)
-                       (and title-collapse-enabled?
-                            (or (not collapsed?)
-                                (some? (mldoc/extract-first-query-from-ast body))))))
-          [:div.block-body
-           (let [body (block/trim-break-lines! (:block.temp/ast-body block))
-                 uuid (:block/uuid block)]
-             (for [[idx child] (medley/indexed body)]
-               (when-let [block (markup-element-cp config child)]
-                 (rum/with-key (block-child block)
-                   (str uuid "-" idx)))))]))))
+      (when (and (not block-ref-with-title?)
+                 (seq body)
+                 (or (not title-collapse-enabled?)
+                     (and title-collapse-enabled?
+                          (or (not collapsed?)
+                              (some? (mldoc/extract-first-query-from-ast body))))))
+        [:div.block-body
+         (let [body (block/trim-break-lines! (:block.temp/ast-body block))
+               uuid (:block/uuid block)]
+           (for [[idx child] (medley/indexed body)]
+             (when-let [block (markup-element-cp config child)]
+               (rum/with-key (block-child block)
+                 (str uuid "-" idx)))))]))))
 
 (rum/defcs block-tag <
   (rum/local false ::hover?)
@@ -2500,21 +2508,21 @@
                      :other-position? true})]
     (when (seq properties)
       (case position
-         :block-below
-         [:div.positioned-properties.block-below.flex.flex-row.gap-1.item-center.flex-wrap.text-sm.overflow-x-hidden.max-h-6
-          (for [pid properties]
-            (let [property (db/entity pid)
-                  v (get block pid)]
-              [:div.flex.flex-row.items-center.gap-2.hover:bg-secondary.rounded
-               [:div.flex.flex-row.opacity-50.hover:opacity-100
-                (property-component/property-key-cp block property opts)
-                [:div.select-none ":"]]
-               (pv/property-value block property v opts)]))]
-         [:div.positioned-properties.flex.flex-row.gap-1.select-none.h-6
-          {:class (name position)}
-          (for [pid properties]
-            (when-let [property (db/entity pid)]
-              (pv/property-value block property (get block pid) (assoc opts :show-tooltip? true))))]))))
+        :block-below
+        [:div.positioned-properties.block-below.flex.flex-row.gap-1.item-center.flex-wrap.text-sm.overflow-x-hidden.max-h-6
+         (for [pid properties]
+           (let [property (db/entity pid)
+                 v (get block pid)]
+             [:div.flex.flex-row.items-center.gap-2.hover:bg-secondary.rounded
+              [:div.flex.flex-row.opacity-50.hover:opacity-100
+               (property-component/property-key-cp block property opts)
+               [:div.select-none ":"]]
+              (pv/property-value block property v opts)]))]
+        [:div.positioned-properties.flex.flex-row.gap-1.select-none.h-6
+         {:class (name position)}
+         (for [pid properties]
+           (when-let [property (db/entity pid)]
+             (pv/property-value block property (get block pid) (assoc opts :show-tooltip? true))))]))))
 
 (rum/defc ^:large-vars/cleanup-todo block-content < rum/reactive
   [config {:block/keys [uuid properties scheduled deadline format pre-block?] :as block} edit-input-id block-id slide?]
@@ -2641,7 +2649,7 @@
                                  (:db/id block)
                                  :block-ref)
                                 (swap! *hide-block-refs? not)))}
-      [:span.text-sm block-refs-count'])))
+                 [:span.text-sm block-refs-count'])))
 
 (rum/defc block-left-menu < rum/reactive
   [_config {:block/keys [uuid] :as _block}]
@@ -2811,7 +2819,7 @@
            :else
            (when-let [uuid (:block/uuid block)]
              (-> (or (:on-redirect-to-page config) route-handler/redirect-to-page!)
-               (apply [(str uuid)])))))}
+                 (apply [(str uuid)])))))}
    label])
 
 (rum/defc breadcrumb-separator
@@ -2861,10 +2869,10 @@
                                (if name
                                  [block (page-cp {} block)]
                                  (let [result (block/parse-title-and-body
-                                                                   uuid
-                                                                   (:block/format block)
-                                                                   (:block/pre-block? block)
-                                                                   title)
+                                               uuid
+                                               (:block/format block)
+                                               (:block/pre-block? block)
+                                               title)
                                        ast-body (:block.temp/ast-body result)
                                        ast-title (:block.temp/ast-title result)
                                        config (assoc config :block/uuid uuid)]
@@ -2874,14 +2882,14 @@
                                         (->elem :span.inline-wrap (map-inline config ast-title))
                                         (->elem :div (markup-elements-cp config ast-body))))]))))
               breadcrumbs (->> (into [] parents-props)
-                              (concat [page-name-props] (when more? [:more]))
-                              (filterv identity)
-                              (map (fn [x]
-                                     (if (and (vector? x) (second x))
-                                       (let [[block label] x]
-                                         (rum/with-key (breadcrumb-fragment config block label opts) (:block/uuid block)))
-                                       [:span.opacity-70 "⋯"])))
-                              (interpose (breadcrumb-separator)))]
+                               (concat [page-name-props] (when more? [:more]))
+                               (filterv identity)
+                               (map (fn [x]
+                                      (if (and (vector? x) (second x))
+                                        (let [[block label] x]
+                                          (rum/with-key (breadcrumb-fragment config block label opts) (:block/uuid block)))
+                                        [:span.opacity-70 "⋯"])))
+                               (interpose (breadcrumb-separator)))]
           (when (seq breadcrumbs)
             [:div.breadcrumb.block-parents
              {:class (when (seq breadcrumbs)
@@ -3269,6 +3277,15 @@
        [:div (when-not (:page-title? config) {:style {:padding-left 45}})
         (db-properties-cp config block {:in-block-container? true})])
 
+     (when (and db-based? (not collapsed?) (not (or table? property?)) (not (string/blank? (:block/title (:logseq.property/query block)))))
+       (let [query (:block/title (:logseq.property/query block))]
+         [:div.dsl-query {:style {:padding-left 42}}
+          (query/custom-query (wrap-query-components (assoc config
+                                                            :dsl-query? true
+                                                            :cards? (ldb/class-instance? (db/entity :logseq.class/Cards) block)))
+                              {:builder nil
+                               :query (query-builder-component/sanitize-q query)})]))
+
      (when-not (or (:hide-children? config) in-whiteboard? (or table? property?))
        (let [config' (-> (update config :level inc)
                          (dissoc :original-block :data))]
@@ -3321,14 +3338,14 @@
                   config)]
     (when (:block/uuid block)
       (ui/catch-error
-        (fn [^js error]
-          [:div.flex.flex-col.pl-6.my-1
-           [:code (str "#uuid\"" (:block/uuid block) "\"")]
-           [:code.flex.p-1.text-red-rx-09 "Block render error: " (.-message error)]])
-        (rum/with-key
-          (block-container-inner state repo config' block
-            {:navigating-block navigating-block :navigated? navigated?})
-          (str "block-inner" (:block/uuid block)))))))
+       (fn [^js error]
+         [:div.flex.flex-col.pl-6.my-1
+          [:code (str "#uuid\"" (:block/uuid block) "\"")]
+          [:code.flex.p-1.text-red-rx-09 "Block render error: " (.-message error)]])
+       (rum/with-key
+         (block-container-inner state repo config' block
+                                {:navigating-block navigating-block :navigated? navigated?})
+         (str "block-inner" (:block/uuid block)))))))
 
 (defn divide-lists
   [[f & l]]
@@ -3416,42 +3433,42 @@
 
   (let [tr (fn [elm cols]
              (->elem
-               :tr
-               (mapv (fn [col]
-                       (->elem
-                         elm
-                         {:scope "col"
-                          :class "org-left"}
-                         (map-inline config col)))
-                 cols)))
+              :tr
+              (mapv (fn [col]
+                      (->elem
+                       elm
+                       {:scope "col"
+                        :class "org-left"}
+                       (map-inline config col)))
+                    cols)))
         tb-col-groups (try
                         (mapv (fn [number]
                                 (let [col-elem [:col {:class "org-left"}]]
                                   (->elem
-                                    :colgroup
-                                    (repeat number col-elem))))
-                          col_groups)
+                                   :colgroup
+                                   (repeat number col-elem))))
+                              col_groups)
                         (catch :default _e
                           []))
         head (when header
                [:thead (tr :th header)])
         groups (mapv (fn [group]
                        (->elem
-                         :tbody
-                         (mapv #(tr :td %) group)))
-                 groups)]
+                        :tbody
+                        (mapv #(tr :td %) group)))
+                     groups)]
     [:div.table-wrapper.classic-table.force-visible-scrollbar.markdown-table
      (->elem
-       :table
-       {:class "table-auto"
-        :border 2
-        :cell-spacing 0
-        :cell-padding 6
-        :rules "groups"
-        :frame "hsides"}
-       (vec-cat
-         tb-col-groups
-         (cons head groups)))]))
+      :table
+      {:class "table-auto"
+       :border 2
+       :cell-spacing 0
+       :cell-padding 6
+       :rules "groups"
+       :frame "hsides"}
+      (vec-cat
+       tb-col-groups
+       (cons head groups)))]))
 
 (defn logbook-cp
   [log]
@@ -3763,26 +3780,26 @@
         *wrap-ref (rum/use-ref nil)]
 
     (rum/use-effect!
-      (fn []
+     (fn []
         ;; Try to fix virtuoso scrollable container blink for the block insertion at bottom
-        (when virtualized?
-          (let [^js *ob (volatile! nil)]
-            (js/setTimeout
-              (fn []
-                (when-let [_inst (rum/deref *virtualized-ref)]
-                  (when-let [^js target (.-firstElementChild (rum/deref *wrap-ref))]
-                    (let [set-wrap-h! #(when-let [ref (rum/deref *wrap-ref)] (set! (.-height (.-style ref)) %))
-                          set-wrap-h! (debounce set-wrap-h! 16)
-                          ob (js/ResizeObserver.
-                               (fn []
-                                 (when-let [h (and (rum/deref *wrap-ref)
+       (when virtualized?
+         (let [^js *ob (volatile! nil)]
+           (js/setTimeout
+            (fn []
+              (when-let [_inst (rum/deref *virtualized-ref)]
+                (when-let [^js target (.-firstElementChild (rum/deref *wrap-ref))]
+                  (let [set-wrap-h! #(when-let [ref (rum/deref *wrap-ref)] (set! (.-height (.-style ref)) %))
+                        set-wrap-h! (debounce set-wrap-h! 16)
+                        ob (js/ResizeObserver.
+                            (fn []
+                              (when-let [h (and (rum/deref *wrap-ref)
                                                 (.-height (.-style target)))]
                                    ;(prn "==>> debug: " h)
-                                   (set-wrap-h! h))))]
-                      (.observe ob target)
-                      (vreset! *ob ob))))))
-            #(some-> @*ob (.disconnect)))))
-      [])
+                                (set-wrap-h! h))))]
+                    (.observe ob target)
+                    (vreset! *ob ob))))))
+           #(some-> @*ob (.disconnect)))))
+     [])
 
     [:div.blocks-list-wrap
      {:data-level (or (:level config) 0)
@@ -3842,22 +3859,22 @@
         parent-blocks (group-by :block/parent page-blocks)]
     [:div.my-2.references-blocks-item {:key (str "page-" (:db/id page))}
      (let [items (for [[parent blocks] parent-blocks]
-                     (let [blocks' (map (fn [b]
-                                          (if (e/entity? b)
-                                            b
-                                            (update b :block/children
-                                              (fn [col]
-                                                (tree/non-consecutive-blocks->vec-tree col))))) blocks)]
-                       (rum/with-key
-                         (breadcrumb-with-container blocks' config)
-                         (:db/id parent))))]
+                   (let [blocks' (map (fn [b]
+                                        (if (e/entity? b)
+                                          b
+                                          (update b :block/children
+                                                  (fn [col]
+                                                    (tree/non-consecutive-blocks->vec-tree col))))) blocks)]
+                     (rum/with-key
+                       (breadcrumb-with-container blocks' config)
+                       (:db/id parent))))]
        (if page
          (ui/foldable
-           [:div.with-foldable-page
-            (page-cp config page)
-            (when alias? [:span.text-sm.font-medium.opacity-50 " Alias"])]
-           items
-           {:debug-id page})
+          [:div.with-foldable-page
+           (page-cp config page)
+           (when alias? [:span.text-sm.font-medium.opacity-50 " Alias"])]
+          items
+          {:debug-id page})
          [:div.only-page-blocks items]))]))
 
 ;; headers to hiccup

+ 12 - 13
src/main/frontend/components/property.cljs

@@ -33,7 +33,6 @@
             [promesa.core :as p]
             [rum.core :as rum]))
 
-
 (defn- property-type-label
   [property-type]
   (case property-type
@@ -265,17 +264,17 @@
      (when-not other-position?
        (let [content-fn (fn [{:keys [id]}]
                           (icon-component/icon-search
-                            {:on-chosen
-                             (fn [_e icon]
-                               (if icon
-                                 (db-property-handler/upsert-property! (:db/ident property)
-                                   (:block/schema property)
-                                   {:properties {:logseq.property/icon icon}})
-                                 (db-property-handler/remove-block-property! (:db/id property)
-                                   (pu/get-pid :logseq.property/icon)))
-                               (shui/popup-hide! id))
-                             :icon-value icon
-                             :del-btn? (boolean icon)}))]
+                           {:on-chosen
+                            (fn [_e icon]
+                              (if icon
+                                (db-property-handler/upsert-property! (:db/ident property)
+                                                                      (:block/schema property)
+                                                                      {:properties {:logseq.property/icon icon}})
+                                (db-property-handler/remove-block-property! (:db/id property)
+                                                                            (pu/get-pid :logseq.property/icon)))
+                              (shui/popup-hide! id))
+                            :icon-value icon
+                            :del-btn? (boolean icon)}))]
 
          (shui/trigger-as
           :button.property-m
@@ -530,7 +529,7 @@
   [block]
   (let [repo (state/get-current-repo)
         db (db/get-db repo)
-        classes (concat (:block/tags block) (outliner-property/get-class-parents db (:block/tags block)))]
+        classes (concat (:block/tags block) (outliner-property/get-classes-parents db (:block/tags block)))]
     (doseq [class classes]
       (db-async/<get-block repo (:db/id class) :children? false))
     (when (ldb/class? block)

+ 87 - 81
src/main/frontend/components/property/value.cljs

@@ -34,7 +34,8 @@
             [frontend.handler.route :as route-handler]
             [frontend.components.title :as title]
             [cljs-time.coerce :as tc]
-            [frontend.modules.outliner.ui :as ui-outliner-tx]))
+            [frontend.modules.outliner.ui :as ui-outliner-tx]
+            [frontend.components.query.builder :as query-builder-component]))
 
 (rum/defc property-empty-btn-value
   [property & opts]
@@ -63,45 +64,45 @@
         on-chosen! (fn [_e icon]
                      (if icon
                        (db-property-handler/set-block-property!
-                         (:db/id block)
-                         :logseq.property/icon
-                         (select-keys icon [:type :id :color]))
+                        (:db/id block)
+                        :logseq.property/icon
+                        (select-keys icon [:type :id :color]))
                        (db-property-handler/remove-block-property!
-                         (:db/id block)
-                         :logseq.property/icon))
+                        (:db/id block)
+                        :logseq.property/icon))
                      (clear-overlay!))]
 
     (rum/use-effect!
-      (fn []
-        (when editing?
-          (clear-overlay!)
-          (let [^js container (or (some-> js/document.activeElement (.closest ".page"))
-                                (gdom/getElement "main-content-container"))
-                icon (get block (pu/get-pid :logseq.property/icon))]
-            (util/schedule
-              (fn []
-                (when-let [^js target (some-> (.querySelector container (str "#ls-block-" (str (:block/uuid block))))
-                                        (.querySelector ".block-main-container"))]
-                  (shui/popup-show! target
-                    #(icon-component/icon-search
-                       {:on-chosen on-chosen!
-                        :icon-value icon
-                        :del-btn? (some? icon)})
-                    {:id :ls-icon-picker
-                     :align :start})))))))
-      [editing?])
+     (fn []
+       (when editing?
+         (clear-overlay!)
+         (let [^js container (or (some-> js/document.activeElement (.closest ".page"))
+                                 (gdom/getElement "main-content-container"))
+               icon (get block (pu/get-pid :logseq.property/icon))]
+           (util/schedule
+            (fn []
+              (when-let [^js target (some-> (.querySelector container (str "#ls-block-" (str (:block/uuid block))))
+                                            (.querySelector ".block-main-container"))]
+                (shui/popup-show! target
+                                  #(icon-component/icon-search
+                                    {:on-chosen on-chosen!
+                                     :icon-value icon
+                                     :del-btn? (some? icon)})
+                                  {:id :ls-icon-picker
+                                   :align :start})))))))
+     [editing?])
 
     [:div.col-span-3.flex.flex-row.items-center.gap-2
      (icon-component/icon-picker icon-value
-       {:disabled? config/publishing?
-        :del-btn? (some? icon-value)
-        :on-chosen on-chosen!})]))
+                                 {:disabled? config/publishing?
+                                  :del-btn? (some? icon-value)
+                                  :on-chosen on-chosen!})]))
 
 (defn- select-type?
   [property type]
   (or (contains? #{:node :number :url :date :page :class :property} type)
     ;; closed values
-    (seq (:property/closed-values property))))
+      (seq (:property/closed-values property))))
 
 (defn <create-new-block!
   [block property value & {:keys [edit-block?]
@@ -402,8 +403,8 @@
              (if (or (and (not multiple-choices?) (= chosen clear-value))
                      (and multiple-choices? (= chosen [clear-value])))
                (p/do!
-                 (let [blocks (get-operating-blocks block)
-                       block-ids (map :block/uuid blocks)]
+                (let [blocks (get-operating-blocks block)
+                      block-ids (map :block/uuid blocks)]
                   (property-handler/batch-remove-block-property!
                    (state/get-current-repo)
                    block-ids
@@ -594,16 +595,16 @@
     (select-node property opts' *result)))
 
 (rum/defcs select < rum/reactive db-mixins/query
-                    {:init (fn [state]
-                             (let [*values (atom :loading)
-                                   refresh-result-f (fn []
-                                                      (p/let [result (db-async/<get-block-property-values (state/get-current-repo)
-                                                                       (:db/ident (nth (:rum/args state) 1)))]
-                                                        (reset! *values result)))]
-                               (refresh-result-f)
-                               (assoc state
-                                 ::values *values
-                                 ::refresh-result-f refresh-result-f)))}
+  {:init (fn [state]
+           (let [*values (atom :loading)
+                 refresh-result-f (fn []
+                                    (p/let [result (db-async/<get-block-property-values (state/get-current-repo)
+                                                                                        (:db/ident (nth (:rum/args state) 1)))]
+                                      (reset! *values result)))]
+             (refresh-result-f)
+             (assoc state
+                    ::values *values
+                    ::refresh-result-f refresh-result-f)))}
   [state block property
    {:keys [multiple-choices? dropdown? content-props] :as select-opts}
    {:keys [*show-new-property-config?]}]
@@ -628,55 +629,55 @@
                                :value (:db/id block)
                                :label-value value})) (:property/closed-values property))
                     (->> values
-                      (mapcat (fn [value]
-                                (if (coll? value)
-                                  (map (fn [v] {:value v}) value)
-                                  [{:value value}])))
-                      (map (fn [{:keys [value]}]
-                             (if (and ref-type? (number? value))
-                               (when-let [e (db/entity value)]
-                                 {:label (db-property/property-value-content e)
-                                  :value value})
-                               {:label value
-                                :value value})))
-                      (distinct)))
+                         (mapcat (fn [value]
+                                   (if (coll? value)
+                                     (map (fn [v] {:value v}) value)
+                                     [{:value value}])))
+                         (map (fn [{:keys [value]}]
+                                (if (and ref-type? (number? value))
+                                  (when-let [e (db/entity value)]
+                                    {:label (db-property/property-value-content e)
+                                     :value value})
+                                  {:label value
+                                   :value value})))
+                         (distinct)))
             items (->> (if (= :date type)
                          (map (fn [m] (let [label (:block/title (db/entity (:value m)))]
                                         (when label
                                           (assoc m :label label)))) items)
                          items)
-                    (remove nil?))
+                       (remove nil?))
             on-chosen (fn [chosen selected?]
                         (let [value (if (map? chosen) (:value chosen) chosen)]
                           (add-or-remove-property-value block property value selected?
-                            {:refresh-result-f refresh-result-f})))
+                                                        {:refresh-result-f refresh-result-f})))
             selected-choices' (get block (:db/ident property))
             selected-choices (if (every? de/entity? selected-choices')
                                (map :db/id selected-choices')
                                [selected-choices'])]
         (select-aux block property
-          {:multiple-choices? multiple-choices?
-           :items items
-           :selected-choices selected-choices
-           :dropdown? dropdown?
-           :show-new-when-not-exact-match? (not (or closed-values? (= :date type)))
-           :input-default-placeholder "Select"
-           :extract-chosen-fn :value
-           :extract-fn (fn [x] (or (:label-value x) (:label x)))
-           :content-props content-props
-           :on-chosen on-chosen
-           :input-opts (fn [_]
-                         {:on-blur (fn []
-                                     (when-let [f (:on-chosen select-opts)] (f)))
-                          :on-click (fn []
-                                      (when *show-new-property-config?
-                                        (reset! *show-new-property-config? false)))
-                          :on-key-down
-                          (fn [e]
-                            (case (util/ekey e)
-                              "Escape"
-                              (when-let [f (:on-chosen select-opts)] (f))
-                              nil))})})))))
+                    {:multiple-choices? multiple-choices?
+                     :items items
+                     :selected-choices selected-choices
+                     :dropdown? dropdown?
+                     :show-new-when-not-exact-match? (not (or closed-values? (= :date type)))
+                     :input-default-placeholder "Select"
+                     :extract-chosen-fn :value
+                     :extract-fn (fn [x] (or (:label-value x) (:label x)))
+                     :content-props content-props
+                     :on-chosen on-chosen
+                     :input-opts (fn [_]
+                                   {:on-blur (fn []
+                                               (when-let [f (:on-chosen select-opts)] (f)))
+                                    :on-click (fn []
+                                                (when *show-new-property-config?
+                                                  (reset! *show-new-property-config? false)))
+                                    :on-key-down
+                                    (fn [e]
+                                      (case (util/ekey e)
+                                        "Escape"
+                                        (when-let [f (:on-chosen select-opts)] (f))
+                                        nil))})})))))
 
 (rum/defcs property-normal-block-value <
   {:init (fn [state]
@@ -723,6 +724,11 @@
                                "Invalid block value, please delete the current property."]]
           (when v-block
             (cond
+              (= (:db/ident property) :logseq.property/query)
+              (let [query (:block/title v-block)]
+                (query-builder-component/builder query {:property property
+                                                        :block v-block}))
+
               (:block/page v-block)
               (property-normal-block-value block property v-block)
 
@@ -988,7 +994,7 @@
   (let [block (db/sub-block (:db/id block))
         value (get block (:db/ident property))
         value' (if (coll? value) value
-                 (when (some? value) #{value}))]
+                   (when (some? value) #{value}))]
     (multiple-values-inner block property value' opts schema)))
 
 (rum/defcs property-value < rum/reactive
@@ -1038,10 +1044,10 @@
                      :else
                      (let [parent? (= (:db/ident property) :logseq.property/parent)
                            value-cp (property-scalar-value block property v
-                                      (merge
-                                        opts
-                                        {:editor-id editor-id
-                                         :dom-id dom-id}))
+                                                           (merge
+                                                            opts
+                                                            {:editor-id editor-id
+                                                             :dom-id dom-id}))
                            page-ancestors (when parent?
                                             (let [ancestor-pages (loop [parents [block]]
                                                                    (if-let [parent (:logseq.property/parent (last parents))]

+ 1 - 1
src/main/frontend/components/query.cljs

@@ -185,7 +185,7 @@
                                             :result result
                                             :collapsed? collapsed?'}))
 
-         (when dsl-query? builder)
+         (when (and dsl-query? builder) builder)
 
          (if built-in?
            [:div {:style {:margin-left 2}}

+ 64 - 52
src/main/frontend/components/query/builder.cljs

@@ -33,10 +33,10 @@
                {:label "Pages"
                 :value "page"
                 :selected (= @*find :page)}]
-     (fn [e v]
+              (fn [e v]
        ;; Prevent opening the current block's editor
-       (util/stop e)
-       (reset! *find (keyword v))))])
+                (util/stop e)
+                (reset! *find (keyword v))))])
 
 (defn- select
   ([items on-chosen]
@@ -83,10 +83,10 @@
 
 (defonce *between-dates (atom {}))
 (rum/defcs datepicker < rum/reactive
-                        (rum/local nil ::input-value)
-                        {:will-unmount (fn [state]
-                                         (swap! *between-dates dissoc (first (:rum/args state)))
-                                         state)}
+  (rum/local nil ::input-value)
+  {:will-unmount (fn [state]
+                   (swap! *between-dates dissoc (first (:rum/args state)))
+                   state)}
   [state id placeholder {:keys [auto-focus]}]
   (let [*input-value (::input-value state)]
     [:div.ml-4
@@ -97,24 +97,24 @@
        :value (some-> @*input-value (first))
        :on-focus (fn [^js e]
                    (js/setTimeout
-                     #(shui/popup-show! (.-target e)
-                        (let [select-handle! (fn [^js d]
-                                               (let [gd (date/js-date->goog-date d)
-                                                     journal-date (date/js-date->journal-title gd)]
-                                                 (reset! *input-value [journal-date d])
-                                                 (swap! *between-dates assoc id journal-date))
-                                               (shui/popup-hide!))]
-                          (shui/calendar
-                            {:mode "single"
-                             :initial-focus true
-                             :selected (some-> @*input-value (second))
-                             :on-select select-handle!
-                             :on-day-key-down (fn [^js d _ ^js e]
-                                                (when (= "Enter" (.-key e))
-                                                  (select-handle! d)
-                                                  (util/stop e)))}))
-                        {:id :query-datepicker
-                         :align :start}) 16))}]]))
+                    #(shui/popup-show! (.-target e)
+                                       (let [select-handle! (fn [^js d]
+                                                              (let [gd (date/js-date->goog-date d)
+                                                                    journal-date (date/js-date->journal-title gd)]
+                                                                (reset! *input-value [journal-date d])
+                                                                (swap! *between-dates assoc id journal-date))
+                                                              (shui/popup-hide!))]
+                                         (shui/calendar
+                                          {:mode "single"
+                                           :initial-focus true
+                                           :selected (some-> @*input-value (second))
+                                           :on-select select-handle!
+                                           :on-day-key-down (fn [^js d _ ^js e]
+                                                              (when (= "Enter" (.-key e))
+                                                                (select-handle! d)
+                                                                (util/stop e)))}))
+                                       {:id :query-datepicker
+                                        :align :start}) 16))}]]))
 
 (rum/defcs between <
   (rum/local nil ::start)
@@ -127,12 +127,12 @@
     (datepicker :end "End date" opts)]
    [:p.pt-2
     (ui/button "Submit"
-      :on-click (fn []
-                  (let [{:keys [start end]} @*between-dates]
-                    (when (and start end)
-                      (let [clause [:between [:page-ref start] [:page-ref end]]]
-                        (append-tree! tree opts loc clause)
-                        (reset! *between-dates {}))))))]])
+               :on-click (fn []
+                           (let [{:keys [start end]} @*between-dates]
+                             (when (and start end)
+                               (let [clause [:between [:page-ref start] [:page-ref end]]]
+                                 (append-tree! tree opts loc clause)
+                                 (reset! *between-dates {}))))))]])
 
 (rum/defc property-select
   [*mode *property]
@@ -276,7 +276,7 @@
 
        "full text search"
        (search (fn [v] (append-tree! *tree opts loc v))
-         (:toggle-fn opts))
+               (:toggle-fn opts))
 
        "between"
        (between (merge opts
@@ -330,7 +330,7 @@
 (rum/defc add-filter
   [*find *tree loc clause]
   (shui/button
-   {:class "!px-1 h-6 add-filter text-muted-foreground"
+   {:class "jtrigger !px-1 h-6 add-filter text-muted-foreground"
     :size :sm
     :variant :ghost
     :title "Add clause"
@@ -439,13 +439,13 @@
       [:div.flex.flex-row.gap-2
        (for [op query-builder/operators]
          (ui/button (string/upper-case (name op))
-           :intent "logseq"
-           :small? true
-           :on-click (fn []
-                       (swap! *tree (fn [q]
-                                      (let [loc' (if operator? (vec (butlast loc)) loc)]
-                                        (query-builder/wrap-operator q loc' op))))
-                       (toggle-fn))))]
+                    :intent "logseq"
+                    :small? true
+                    :on-click (fn []
+                                (swap! *tree (fn [q]
+                                               (let [loc' (if operator? (vec (butlast loc)) loc)]
+                                                 (query-builder/wrap-operator q loc' op))))
+                                (toggle-fn))))]
 
       (when operator?
         [:div
@@ -453,12 +453,12 @@
          [:div.flex.flex-row.gap-2
           (for [op (remove #{(keyword (string/lower-case clause))} query-builder/operators)]
             (ui/button (string/upper-case (name op))
-              :intent "logseq"
-              :small? true
-              :on-click (fn []
-                          (swap! *tree (fn [q]
-                                         (query-builder/replace-element q loc op)))
-                          (toggle-fn))))]])])
+                       :intent "logseq"
+                       :small? true
+                       :on-click (fn []
+                                   (swap! *tree (fn [q]
+                                                  (query-builder/replace-element q loc op)))
+                                   (toggle-fn))))]])])
    {:modal-class (util/hiccup->class
                   "origin-top-right.absolute.left-0.mt-2.ml-2.rounded-md.shadow-lg.w-64")}))
 
@@ -505,10 +505,20 @@
                           [:and [@tree]])]
     (clauses-group *tree *find [0] kind' clauses)))
 
+(defn sanitize-q
+  [q-str]
+  (if (string/blank? q-str)
+    ""
+    (if (or (common-util/wrapped-by-parens? q-str)
+            (common-util/wrapped-by-quotes? q-str)
+            (page-ref/page-ref? q-str))
+      q-str
+      (str "\"" q-str "\""))))
+
 (rum/defcs builder <
   (rum/local nil ::find)
   {:init (fn [state]
-           (let [q-str (first (:rum/args state))
+           (let [q-str (sanitize-q (first (:rum/args state)))
                  query (common-util/safe-read-string
                         query-dsl/custom-readers
                         (query-dsl/pre-transform-query q-str))
@@ -535,13 +545,15 @@
                                                  repo (state/get-current-repo)
                                                  block (db/pull [:block/uuid (:block/uuid block)])]
                                              (when block
-                                               (let [content (string/replace (:block/title block)
-                                                                             #"\{\{query[^}]+\}\}"
-                                                                             (util/format "{{query %s}}" q))]
-                                                 (editor-handler/save-block! repo (:block/uuid block) content)))))))
+                                               (if (:property config)
+                                                 (editor-handler/save-block! repo (:block/uuid block) q)
+                                                 (let [content (string/replace (:block/title block)
+                                                                               #"\{\{query[^}]+\}\}"
+                                                                               (util/format "{{query %s}}" q))]
+                                                   (editor-handler/save-block! repo (:block/uuid block) content))))))))
              (assoc state ::tree *tree)))
    :will-mount (fn [state]
-                 (let [q-str (first (:rum/args state))
+                 (let [q-str (sanitize-q (first (:rum/args state)))
                        blocks-query? (:blocks? (query-dsl/parse-query q-str))
                        find-mode (cond
                                    blocks-query?

+ 1 - 1
src/main/frontend/components/query/result.cljs

@@ -47,7 +47,7 @@
                              *fulltext-query-result)
 
                            :else
-                           (query-dsl/query (state/get-current-repo) q)))
+                           (query-dsl/query (state/get-current-repo) q {:cards? (:cards? config)})))
 
                        :else
                        (query-custom/custom-query query {:current-block-uuid current-block-uuid}))

+ 101 - 104
src/main/frontend/components/views.cljs

@@ -133,7 +133,7 @@
          (fn [property]
            (let [ident (or (:db/ident property) (:id property))]
              (when-not (or (contains? #{:logseq.property/built-in?} ident)
-                           (contains? #{:map} (get-in property [:block/schema :type])))
+                           (contains? #{:map :entity} (get-in property [:block/schema :type])))
                (let [property (if (de/entity? property)
                                 property
                                 (or (db/entity ident) property))
@@ -269,76 +269,74 @@
         remove-resizing-class #(dom/remove-class! js/document.documentElement "is-resizing-buf")]
 
     (rum/use-effect!
-      (fn []
-        (when (number? dx)
-          (some-> (rum/deref *el)
-            (dom/set-style! :transform (str "translate3D(" dx "px , 0, 0)")))))
-      [dx])
+     (fn []
+       (when (number? dx)
+         (some-> (rum/deref *el)
+                 (dom/set-style! :transform (str "translate3D(" dx "px , 0, 0)")))))
+     [dx])
 
     (rum/use-effect!
-      (fn []
-        (when-let [el (and (fn? js/window.interact) (rum/deref *el))]
-          (let [*field-rect (atom nil)
-                min-width 120
-                max-width 500]
-            (-> (js/interact el)
-              (.draggable
+     (fn []
+       (when-let [el (and (fn? js/window.interact) (rum/deref *el))]
+         (let [*field-rect (atom nil)
+               min-width 120
+               max-width 500]
+           (-> (js/interact el)
+               (.draggable
                 (bean/->js
-                  {:listeners
-                   {:start (fn []
-                             (let [{:keys [width right] :as rect} (bean/->clj (.toJSON (.getBoundingClientRect (.closest el ".ls-table-header-cell"))))
-                                   left-dx (if (>= width min-width) (- min-width width) 0)
-                                   right-dx (if (<= width max-width) (- max-width width) 0)]
-                               (reset! *field-rect rect)
-                               (swap! *field-rect assoc
+                 {:listeners
+                  {:start (fn []
+                            (let [{:keys [width right] :as rect} (bean/->clj (.toJSON (.getBoundingClientRect (.closest el ".ls-table-header-cell"))))
+                                  left-dx (if (>= width min-width) (- min-width width) 0)
+                                  right-dx (if (<= width max-width) (- max-width width) 0)]
+                              (reset! *field-rect rect)
+                              (swap! *field-rect assoc
                                  ;; calculate left/right boundary
-                                 :left-dx left-dx
-                                 :right-dx right-dx
-                                 :left-b (inc (+ left-dx right))
-                                 :right-b (inc (+ right-dx right)))
-                               (dom/add-class! el "is-active")))
-                    :move (fn [^js e]
-                            (let [dx (.-dx e)
-                                  pointer-x (js/Math.floor (.-clientX e))
-                                  {:keys [left-b right-b]} @*field-rect
-                                  left-b (js/Math.floor left-b)
-                                  right-b (js/Math.floor right-b)]
-                              (when (and (> pointer-x left-b)
-                                      (< pointer-x right-b))
-                                (set-dx! (fn [dx']
-                                           (if (contains? #{min-width max-width} (abs dx'))
-                                             dx'
-                                             (let [to-dx (+ (or dx' 0) dx)
-                                                   {:keys [left-dx right-dx]} @*field-rect]
-                                               (cond
+                                     :left-dx left-dx
+                                     :right-dx right-dx
+                                     :left-b (inc (+ left-dx right))
+                                     :right-b (inc (+ right-dx right)))
+                              (dom/add-class! el "is-active")))
+                   :move (fn [^js e]
+                           (let [dx (.-dx e)
+                                 pointer-x (js/Math.floor (.-clientX e))
+                                 {:keys [left-b right-b]} @*field-rect
+                                 left-b (js/Math.floor left-b)
+                                 right-b (js/Math.floor right-b)]
+                             (when (and (> pointer-x left-b)
+                                        (< pointer-x right-b))
+                               (set-dx! (fn [dx']
+                                          (if (contains? #{min-width max-width} (abs dx'))
+                                            dx'
+                                            (let [to-dx (+ (or dx' 0) dx)
+                                                  {:keys [left-dx right-dx]} @*field-rect]
+                                              (cond
                                                  ;; left
-                                                 (neg? to-dx) (if (> (abs left-dx) (abs to-dx)) to-dx left-dx)
+                                                (neg? to-dx) (if (> (abs left-dx) (abs to-dx)) to-dx left-dx)
                                                  ;; right
-                                                 (pos? to-dx) (if (> right-dx to-dx) to-dx right-dx)
-                                                 ))))))))
-                    :end (fn []
-                           (set-dx!
-                             (fn [dx]
-                               (let [w (js/Math.round (+ dx (:width @*field-rect)))]
-                                 (set-width! (cond
-                                               (< w min-width) min-width
-                                               (> w max-width) max-width
-                                               :else w)))
-                               (reset! *field-rect nil)
-                               (dom/remove-class! el "is-active")
-                               0)))}}))
-              (.styleCursor false)
-              (.on "dragstart" add-resizing-class)
-              (.on "dragend" remove-resizing-class)
-              (.on "mousedown" util/stop-propagation)
-              ))))
-      [])
+                                                (pos? to-dx) (if (> right-dx to-dx) to-dx right-dx)))))))))
+                   :end (fn []
+                          (set-dx!
+                           (fn [dx]
+                             (let [w (js/Math.round (+ dx (:width @*field-rect)))]
+                               (set-width! (cond
+                                             (< w min-width) min-width
+                                             (> w max-width) max-width
+                                             :else w)))
+                             (reset! *field-rect nil)
+                             (dom/remove-class! el "is-active")
+                             0)))}}))
+               (.styleCursor false)
+               (.on "dragstart" add-resizing-class)
+               (.on "dragend" remove-resizing-class)
+               (.on "mousedown" util/stop-propagation)))))
+     [])
 
     (rum/use-effect!
-      (fn []
-        (when (number? width)
-          (on-sized! width)))
-      [width])
+     (fn []
+       (when (number? width)
+         (on-sized! width)))
+     [width])
 
     [:a.ls-table-resize-handle
      {:data-no-dnd true
@@ -365,27 +363,27 @@
                                    ;; resize handle
                                    (when-not (false? (:resizable? column))
                                      (column-resizer column
-                                       (fn [size]
-                                         (set-sized-columns! (assoc sized-columns (:id column) size)))))])
+                                                     (fn [size]
+                                                       (set-sized-columns! (assoc sized-columns (:id column) size)))))])
                        :disabled? (= (:id column) :select)}) columns)
         items (if show-add-property?
                 (conj items
-                  {:id "add property"
-                   :prop {:style {:width "-webkit-fill-available"
-                                  :min-width 160}
-                          :on-click (fn [] (when (fn? add-property!) (add-property!)))}
-                   :value :add-new-property
-                   :content (add-property-button)
-                   :disabled? true})
+                      {:id "add property"
+                       :prop {:style {:width "-webkit-fill-available"
+                                      :min-width 160}
+                              :on-click (fn [] (when (fn? add-property!) (add-property!)))}
+                       :value :add-new-property
+                       :content (add-property-button)
+                       :disabled? true})
                 items)
         selection-rows-count (count selected-rows)]
     (shui/table-header
-      (dnd/items items {:vertical? false
-                        :on-drag-end (fn [ordered-columns _m]
-                                       (set-ordered-columns! ordered-columns))})
-      (when (pos? selection-rows-count)
-        [:div.absolute.top-0.left-8
-         (action-bar table selected-rows option)]))))
+     (dnd/items items {:vertical? false
+                       :on-drag-end (fn [ordered-columns _m]
+                                      (set-ordered-columns! ordered-columns))})
+     (when (pos? selection-rows-count)
+       [:div.absolute.top-0.left-8
+        (action-bar table selected-rows option)]))))
 
 (rum/defc table-row < rum/reactive
   [{:keys [row-selected?] :as table} row columns props {:keys [show-add-property?]}]
@@ -453,10 +451,10 @@
 
 (comment
   (defn- property-ref-type?
-   [property]
-   (let [schema (:block/schema property)
-         type (:type schema)]
-     (db-property-type/all-ref-property-types type))))
+    [property]
+    (let [schema (:block/schema property)
+          type (:type schema)]
+      (db-property-type/all-ref-property-types type))))
 
 (defn- get-property-values
   [rows property]
@@ -808,7 +806,6 @@
                         (set-filters! new-filters))))
         :class "w-24 !h-6 !py-0 border-none focus-visible:ring-0 focus-visible:ring-offset-0"})
 
-
       (filter-value-select table property value operator idx))))
 
 (rum/defc filters-row < rum/static
@@ -1009,27 +1006,27 @@
   [table option row-selection add-new-object! ready?]
   (let [selected-rows (shui/table-get-selection-rows row-selection (:rows table))]
     (shui/table
-    (let [columns' (:columns table)
-          rows (:rows table)]
-      [:div.ls-table-rows.content.overflow-x-auto.force-visible-scrollbar
-       {:class (when (not ready?) "invisible")}
-       [:div.relative
-        (table-header table columns' option selected-rows)
-
-        (ui/virtualized-list
-         {:custom-scroll-parent (gdom/getElement "main-content-container")
-          :increase-viewport-by 128
-          :overscan 128
-          :compute-item-key (fn [idx]
-                              (let [block (nth rows idx)]
-                                (str "table-row-" (:db/id block))))
-          :total-count (count rows)
-          :item-content (fn [idx]
-                          (let [row (nth rows idx)]
-                            (table-row table row columns' {} option)))})
-
-        (when add-new-object!
-          (shui/table-footer (add-new-row table)))]]))))
+     (let [columns' (:columns table)
+           rows (:rows table)]
+       [:div.ls-table-rows.content.overflow-x-auto.force-visible-scrollbar
+        {:class (when (not ready?) "invisible")}
+        [:div.relative
+         (table-header table columns' option selected-rows)
+
+         (ui/virtualized-list
+          {:custom-scroll-parent (gdom/getElement "main-content-container")
+           :increase-viewport-by 128
+           :overscan 128
+           :compute-item-key (fn [idx]
+                               (let [block (nth rows idx)]
+                                 (str "table-row-" (:db/id block))))
+           :total-count (count rows)
+           :item-content (fn [idx]
+                           (let [row (nth rows idx)]
+                             (table-row table row columns' {} option)))})
+
+         (when add-new-object!
+           (shui/table-footer (add-new-row table)))]]))))
 
 (rum/defc list-view < rum/static
   [view-entity result config]

+ 20 - 15
src/main/frontend/db/query_dsl.cljs

@@ -21,7 +21,6 @@
             [frontend.config :as config]
             [frontend.state :as state]))
 
-
 ;; Query fields:
 
 ;; Operators:
@@ -241,20 +240,19 @@
     (= 4 (count e))
     (build-between-three-arg e)))
 
-
 (defn ->file-property-value
   "Parses property values for file graphs and handles non-string values or any page-ref like values"
   [v*]
   (if (some? v*)
-   (let [v (str v*)
-         result (if-some [res (text/parse-non-string-property-value v)]
-                  res
-                  (if (string/starts-with? v "#")
-                    (subs v 1)
-                    (or (page-ref/get-page-name v) v)))]
-     (if (string? result)
-       (or (parse-double result) (string/trim result))
-       result))
+    (let [v (str v*)
+          result (if-some [res (text/parse-non-string-property-value v)]
+                   res
+                   (if (string/starts-with? v "#")
+                     (subs v 1)
+                     (or (page-ref/get-page-name v) v)))]
+      (if (string? result)
+        (or (parse-double result) (string/trim result))
+        result))
     v*))
 
 (defn ->db-property-value
@@ -323,7 +321,7 @@
       (if db-graph?
         (let [markers' (set (map (comp string/capitalize name) markers))]
           {:query (list 'task '?b (set markers'))
-          :rules [:task]})
+           :rules [:task]})
         (let [markers (set (map (comp string/upper-case name) markers))]
           {:query (list 'task '?b markers)
            :rules [:task]})))))
@@ -571,7 +569,7 @@ Some bindings in this fn:
 
 (def custom-readers {:readers {'tag (fn [x] (page-ref/->page-ref x))}})
 (defn parse
-  [s {:keys [db-graph?] :as opts}]
+  [s {:keys [db-graph? cards?] :as opts}]
   (when (and (string? s)
              (not (string/blank? s)))
     (let [s (if (= \# (first s)) (page-ref/->page-ref (subs s 1)) s)
@@ -586,7 +584,8 @@ Some bindings in this fn:
           (when form (build-query form {:sort-by sort-by
                                         :blocks? blocks?
                                         :db-graph? db-graph?
-                                        :sample sample}))
+                                        :sample sample
+                                        :cards? cards?}))
           result' (when (seq result)
                     (let [key (if (coll? (first result))
                                 ;; Only queries for this branch are not's like:
@@ -652,7 +651,13 @@ Some bindings in this fn:
    (query repo query-string {}))
   ([repo query-string query-opts]
    (when (and (string? query-string) (not= "\"\"" query-string))
-     (let [{query* :query :keys [rules sort-by blocks? sample]} (parse-query query-string)]
+     (let [{query* :query :keys [rules sort-by blocks? sample]} (parse-query query-string {:cards? (:cards? query-opts)})
+           query* (if (:cards? query-opts)
+                    (let [card-id (:db/id (db-utils/entity :logseq.class/Card))]
+                      (util/concat-without-nil
+                       [['?b :block/tags card-id]]
+                       (if (coll? (first query*)) query* [query*])))
+                    query*)]
        (when-let [query' (some-> query* (query-wrapper {:blocks? blocks?
                                                         :block-attrs (when (config/db-based-graph? repo)
                                                                        db-block-attrs)}))]

+ 64 - 27
src/main/frontend/extensions/fsrs.cljs

@@ -8,6 +8,8 @@
             [frontend.db :as db]
             [frontend.db-mixins :as db-mixins]
             [frontend.db.async :as db-async]
+            [frontend.db.model :as db-model]
+            [frontend.db.query-dsl :as query-dsl]
             [frontend.extensions.srs :as srs]
             [frontend.handler.block :as block-handler]
             [frontend.handler.property :as property-handler]
@@ -72,20 +74,28 @@
           :logseq.property.fsrs/due prop-fsrs-due})))))
 
 (defn- <get-due-card-block-ids
-  [repo]
-  (let [now-inst-ms (inst-ms (js/Date.))]
-    (db-async/<q repo {:transact-db? false}
-                 '[:find [?b ...]
-                   :in $ ?now-inst-ms
-                   :where
-                   [?b :block/tags :logseq.class/Card]
-                   (or-join [?b ?now-inst-ms]
-                            (and
-                             [?b :logseq.property.fsrs/due ?due]
-                             [(>= ?now-inst-ms ?due)])
-                            [(missing? $ ?b :logseq.property.fsrs/due)])
-                   [?b :block/uuid]]
-                 now-inst-ms)))
+  [repo cards-id]
+  (let [now-inst-ms (inst-ms (js/Date.))
+        cards (when (and cards-id (not= (keyword cards-id) :global)) (db/entity cards-id))
+        query (:block/title (:logseq.property/query cards))
+        result (query-dsl/parse query {:db-graph? true})
+        q '[:find [?b ...]
+            :in $ ?now-inst-ms %
+            :where
+            [?b :block/tags :logseq.class/Card]
+            (or-join [?b ?now-inst-ms]
+                     (and
+                      [?b :logseq.property.fsrs/due ?due]
+                      [(>= ?now-inst-ms ?due)])
+                     [(missing? $ ?b :logseq.property.fsrs/due)])
+            [?b :block/uuid]]
+        q' (if query
+             (let [query* (:query result)]
+               (util/concat-without-nil
+                q
+                (if (coll? (first query*)) query* [query*])))
+             q)]
+    (db-async/<q repo {:transact-db? false} q' now-inst-ms (:rules result))))
 
 (defn- btn-with-shortcut [{:keys [shortcut id btn-text background on-click class]}]
   (shui/button
@@ -207,26 +217,53 @@
   (shortcut/mixin :shortcut.handler/cards false)
   {:init (fn [state]
            (let [*block-ids (atom nil)
-                 *loading? (atom nil)]
+                 *loading? (atom nil)
+                 cards-id (last (:rum/args state))]
              (reset! *loading? true)
-             (p/let [result (<get-due-card-block-ids (state/get-current-repo))]
+             (p/let [result (<get-due-card-block-ids (state/get-current-repo) cards-id)]
                (reset! *block-ids result)
                (reset! *loading? false))
              (assoc state
                     ::block-ids *block-ids
+                    ::cards-id (atom (or cards-id :global))
                     ::loading? *loading?)))
    :will-unmount (fn [state]
                    (update-due-cards-count)
                    state)}
-  [state]
+  [state _cards-id]
   (let [repo (state/get-current-repo)
+        *cards-id (::cards-id state)
+        cards-id (rum/react *cards-id)
+        all-cards (concat
+                   [{:db/id :global
+                     :block/title "All cards"}]
+                   (db-model/get-class-objects repo (:db/id (db/entity :logseq.class/Cards))))
         *block-ids (::block-ids state)
         block-ids (rum/react *block-ids)
         loading? (rum/react (::loading? state))
         *card-index (::card-index state)
         *phase (atom :init)]
     (when (false? loading?)
-      [:div#cards-modal.p-2
+      [:div#cards-modal.p-1.flex.flex-col.gap-8
+       [:div.flex.flex-row.items-center.gap-2.flex-wrap
+        (shui/select
+         {:on-value-change (fn [v]
+                             (reset! *cards-id v)
+                             (p/let [result (<get-due-card-block-ids repo (if (= :global v) nil v))]
+                               (reset! *card-index 0)
+                               (reset! *block-ids result)))
+          :default-value cards-id}
+         (shui/select-trigger
+          {:class "!px-2 !py-0 !h-8 w-64"}
+          (shui/select-value
+           {:placeholder "Select cards"})
+          (shui/select-content
+           (shui/select-group
+            (for [card-entity all-cards]
+              (shui/select-item {:value (:db/id card-entity)}
+                                (:block/title card-entity)))))))
+
+        [:span.text-sm.opacity-50 (str (inc @*card-index) "/" (count @*block-ids))]]
        (if-let [block-id (nth block-ids @*card-index nil)]
          [:div.flex.flex-col
           (card repo block-id *card-index *phase)]
@@ -236,15 +273,15 @@
 (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)]
-              (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
   []

+ 2 - 11
src/main/frontend/extensions/srs.cljs

@@ -29,7 +29,6 @@
             [frontend.util :as util]
             [frontend.util.file-based.drawer :as drawer]
             [frontend.util.persist-var :as persist-var]
-            [logseq.common.util.page-ref :as page-ref]
             [logseq.graph-parser.property :as gp-property]
             [logseq.shui.ui :as shui]
             [medley.core :as medley]
@@ -264,16 +263,8 @@
      (let [result (if (string/blank? query-string)
                     (:block/_refs (db/get-page card-hash-tag))
                     (let [query-string (template/resolve-dynamic-template! query-string)
-                          query-string (if-not (or (string/blank? query-string)
-                                                   (string/starts-with? query-string "(")
-                                                   (string/starts-with? query-string "["))
-                                         (page-ref/->page-ref (string/trim query-string))
-                                         query-string)
-                          {query* :query :keys [sort-by rules]} (query-dsl/parse query-string {:db-graph? (config/db-based-graph? repo)})
-                          query** (util/concat-without-nil
-                                   [['?b :block/refs '?br] ['?br :block/name card-hash-tag]]
-                                   (if (coll? (first query*)) query* [query*]))]
-                      (when-let [query' (query-dsl/query-wrapper query**
+                          {query* :query :keys [sort-by rules]} (query-dsl/parse query-string {:db-graph? (config/db-based-graph? repo)})]
+                      (when-let [query' (query-dsl/query-wrapper query*
                                                                  {:blocks? true
                                                                   :block-attrs [:db/id :block/properties]})]
                         (let [result (query-react/react-query repo

+ 115 - 115
src/main/frontend/handler/editor.cljs

@@ -344,7 +344,6 @@
                                                             :ordered-list? ordered-list?
                                                             :replace-empty-target? replace-empty-target?}))))
 
-
 (defn- block-self-alone-when-insert?
   [config uuid]
   (let [current-page (state/get-current-page)
@@ -393,7 +392,7 @@
                        (wrap-parse-block))
         sibling? (or (:block/collapsed? (:block/link block)) (when block-self? false))
         result (outliner-insert-block! config current-block next-block {:sibling? sibling?
-                                                                          :keep-uuid? true})]
+                                                                        :keep-uuid? true})]
     [result sibling? next-block]))
 
 (defn clear-when-saved!
@@ -583,20 +582,20 @@
                                    nil)]
           (when block-m
             (p/do!
-              (ui-outliner-tx/transact!
-                {:outliner-op :insert-blocks}
-                (outliner-insert-block! {} block-m new-block {:sibling? sibling?
-                                                              :keep-uuid? true
-                                                              :ordered-list? ordered-list?
-                                                              :replace-empty-target? replace-empty-target?})
-                (when (and db-base? (seq properties))
-                  (property-handler/set-block-properties! repo (:block/uuid new-block) properties)))
-              (when edit-block?
-                (if (and replace-empty-target?
-                      (string/blank? (:block/title last-block)))
-                  (edit-block! last-block :max)
-                  (edit-block! new-block :max)))
-              new-block)))))))
+             (ui-outliner-tx/transact!
+              {:outliner-op :insert-blocks}
+              (outliner-insert-block! {} block-m new-block {:sibling? sibling?
+                                                            :keep-uuid? true
+                                                            :ordered-list? ordered-list?
+                                                            :replace-empty-target? replace-empty-target?})
+              (when (and db-base? (seq properties))
+                (property-handler/set-block-properties! repo (:block/uuid new-block) properties)))
+             (when edit-block?
+               (if (and replace-empty-target?
+                        (string/blank? (:block/title last-block)))
+                 (edit-block! last-block :max)
+                 (edit-block! new-block :max)))
+             new-block)))))))
 
 (defn insert-first-page-block-if-not-exists!
   [page-uuid-or-title]
@@ -605,12 +604,12 @@
       (when-let [page (db/get-page page-title)]
         (let [class-or-property? (or (ldb/class? page) (ldb/property? page))]
           (when (or class-or-property? (db/page-empty? (state/get-current-repo) (:db/id page)))
-           (let [format (or (:block/format page) (state/get-preferred-format))
-                 new-block {:block/title ""
-                            :block/format format}]
-             (ui-outliner-tx/transact!
-              {:outliner-op :insert-blocks}
-              (outliner-op/insert-blocks! [new-block] page {:sibling? false})))))))))
+            (let [format (or (:block/format page) (state/get-preferred-format))
+                  new-block {:block/title ""
+                             :block/format format}]
+              (ui-outliner-tx/transact!
+               {:outliner-op :insert-blocks}
+               (outliner-op/insert-blocks! [new-block] page {:sibling? false})))))))))
 
 (defn update-timestamps-content!
   [{:block/keys [repeated? marker format] :as block} content]
@@ -749,9 +748,9 @@
   [{:block/keys [priority title] :as block} new-priority]
   (when-not (config/db-based-graph? (state/get-current-repo))
     (let [new-content (string/replace-first title
-                                           (util/format "[#%s]" priority)
-                                           (util/format "[#%s]" new-priority))]
-     (save-block-if-changed! block new-content))))
+                                            (util/format "[#%s]" priority)
+                                            (util/format "[#%s]" new-priority))]
+      (save-block-if-changed! block new-content))))
 
 (defn delete-block-aux!
   [{:block/keys [uuid] :as _block}]
@@ -858,18 +857,18 @@
                           (save-block! repo block new-content {}))
                          (edit-block! (assoc block :block/title new-content) (count (:block/title prev-block))))
                         (p/do!
-                          (ui-outliner-tx/transact!
-                           transact-opts
-                           (when (seq children)
-                             (outliner-op/move-blocks! children prev-block false))
-                           (delete-block-aux! block)
-                           (save-block! repo prev-block new-content {}))
-                          (when edit-block-f (edit-block-f)))))
+                         (ui-outliner-tx/transact!
+                          transact-opts
+                          (when (seq children)
+                            (outliner-op/move-blocks! children prev-block false))
+                          (delete-block-aux! block)
+                          (save-block! repo prev-block new-content {}))
+                         (when edit-block-f (edit-block-f)))))
 
                     :else
                     (p/do!
-                      (delete-block-aux! block)
-                      (when edit-block-f (edit-block-f)))))))))))))
+                     (delete-block-aux! block)
+                     (when edit-block-f (edit-block-f)))))))))))))
 
 (defn delete-block!
   [repo]
@@ -1028,10 +1027,10 @@
                                    (if (config/db-based-graph? (state/get-current-repo))
                                      (str (string/join (repeat (dec level) "\t")) "- " (page-ref/->page-ref id))
                                      (condp = (:block/format block)
-                                      :org
-                                      (str (string/join (repeat level "*")) " " (block-ref/->block-ref id))
-                                      :markdown
-                                      (str (string/join (repeat (dec level) "\t")) "- " (block-ref/->block-ref id))))))
+                                       :org
+                                       (str (string/join (repeat level "*")) " " (block-ref/->block-ref id))
+                                       :markdown
+                                       (str (string/join (repeat (dec level) "\t")) "- " (block-ref/->block-ref id))))))
                             (string/join "\n\n"))]
       (set-blocks-id! (map :id blocks))
       (util/copy-to-clipboard! copy-str))))
@@ -1704,11 +1703,11 @@
 (defn in-shui-popup?
   []
   (or (some-> js/document.activeElement
-        (.closest "[data-radix-menu-content]")
-        (nil?)
-        (not))
-    (.querySelector js/document.body
-      "div[data-radix-popper-content-wrapper]")))
+              (.closest "[data-radix-menu-content]")
+              (nil?)
+              (not))
+      (.querySelector js/document.body
+                      "div[data-radix-popper-content-wrapper]")))
 
 (defn get-current-input-char
   [input]
@@ -1919,9 +1918,9 @@
       ;; Open "Search page or New page" auto-complete
       (and (= last-input-char commands/hashtag)
              ;; Only trigger at beginning of a line, before whitespace or after a reference
-             (or (re-find #"(?m)^#" (str (.-value input)))
-                 (start-of-new-word? input pos)
-                 (and db-based? (= page-ref/right-brackets (common-util/safe-subs (str (.-value input)) (- pos 3) (dec pos))))))
+           (or (re-find #"(?m)^#" (str (.-value input)))
+               (start-of-new-word? input pos)
+               (and db-based? (= page-ref/right-brackets (common-util/safe-subs (str (.-value input)) (- pos 3) (dec pos))))))
       (do
         (state/set-editor-action-data! {:pos (cursor/get-caret-pos input)})
         (state/set-editor-last-pos! pos)
@@ -1982,10 +1981,10 @@
              :block/title new-content}
              (not db-based?)
              (assoc :block/properties (apply dissoc (:block/properties block)
-                                        (concat
-                                         (when-not keep-uuid? [:id])
-                                         [:custom_id :custom-id]
-                                         exclude-properties)))
+                                             (concat
+                                              (when-not keep-uuid? [:id])
+                                              [:custom_id :custom-id]
+                                              exclude-properties)))
              (not db-based?)
              (assoc :block/properties-text-values (apply dissoc (:block/properties-text-values block)
                                                          (concat
@@ -2020,24 +2019,24 @@
            :or {exclude-properties []}}]
   (let [editing-block (when-let [editing-block (state/get-edit-block)]
                         (some-> (db/entity [:block/uuid (:block/uuid editing-block)])
-                          (assoc :block/title (state/get-edit-content))))
+                                (assoc :block/title (state/get-edit-content))))
         has-unsaved-edits (and editing-block
-                            (not= (:block/title (db/entity (:db/id editing-block)))
-                              (state/get-edit-content)))
+                               (not= (:block/title (db/entity (:db/id editing-block)))
+                                     (state/get-edit-content)))
         target-block (or target-block editing-block)
         block (db/entity (:db/id target-block))
         page (if (:block/name block) block
-               (when target-block (:block/page (db/entity (:db/id target-block)))))
+                 (when target-block (:block/page (db/entity (:db/id target-block)))))
         empty-target? (if (true? skip-empty-target?) false
-                        (string/blank? (:block/title target-block)))
+                          (string/blank? (:block/title target-block)))
         paste-nested-blocks? (nested-blocks blocks)
         target-block-has-children? (db/has-children? (:block/uuid target-block))
         replace-empty-target? (and empty-target?
-                                (or (not target-block-has-children?)
-                                  (and target-block-has-children? (= (count blocks) 1))))
+                                   (or (not target-block-has-children?)
+                                       (and target-block-has-children? (= (count blocks) 1))))
         target-block' (if (and empty-target? target-block-has-children? paste-nested-blocks?)
                         (or (ldb/get-left-sibling target-block)
-                          (:block/parent (db/entity (:db/id target-block))))
+                            (:block/parent (db/entity (:db/id target-block))))
                         target-block)
         sibling? (cond
                    (and paste-nested-blocks? empty-target?)
@@ -2052,24 +2051,24 @@
                    :else
                    true)
         transact-blocks! #(ui-outliner-tx/transact!
-                            {:outliner-op :insert-blocks
-                             :additional-tx revert-cut-txs}
-                            (when target-block'
-                              (let [format (or (:block/format target-block') (state/get-preferred-format))
-                                    repo (state/get-current-repo)
-                                    blocks' (map (fn [block]
-                                                   (paste-block-cleanup repo block page exclude-properties format content-update-fn keep-uuid?))
-                                              blocks)]
-                                (outliner-op/insert-blocks! blocks' target-block' {:sibling? sibling?
-                                                                                   :outliner-op :paste
-                                                                                   :replace-empty-target? replace-empty-target?
-                                                                                   :keep-uuid? keep-uuid?}))))]
+                           {:outliner-op :insert-blocks
+                            :additional-tx revert-cut-txs}
+                           (when target-block'
+                             (let [format (or (:block/format target-block') (state/get-preferred-format))
+                                   repo (state/get-current-repo)
+                                   blocks' (map (fn [block]
+                                                  (paste-block-cleanup repo block page exclude-properties format content-update-fn keep-uuid?))
+                                                blocks)]
+                               (outliner-op/insert-blocks! blocks' target-block' {:sibling? sibling?
+                                                                                  :outliner-op :paste
+                                                                                  :replace-empty-target? replace-empty-target?
+                                                                                  :keep-uuid? keep-uuid?}))))]
     (if ops-only?
       (transact-blocks!)
       (p/let [_ (when has-unsaved-edits
                   (ui-outliner-tx/transact!
-                    {:outliner-op :save-block}
-                    (outliner-save-block! editing-block)))
+                   {:outliner-op :save-block}
+                   (outliner-save-block! editing-block)))
               result (transact-blocks!)]
         (state/set-block-op-type! nil)
         (when-let [result (some-> result (ldb/read-transit-str))]
@@ -2534,7 +2533,7 @@
   [direction]
   (let [f (case direction :up last :down first)
         container (if (some-> js/document.activeElement
-                        (.querySelector ".blocks-container"))
+                              (.querySelector ".blocks-container"))
                     js/document.activeElement js/document.body)
         block (->> (util/get-blocks-noncollapse container)
                    (f))]
@@ -2729,14 +2728,14 @@
             current-pos (cursor/pos input)
             value (gobj/get input "value")
             deleted (and (> current-pos 0)
-                      (util/nth-safe value (dec current-pos)))
+                         (util/nth-safe value (dec current-pos)))
             selected-start (util/get-selection-start input)
             selected-end (util/get-selection-end input)
             block (state/get-edit-block)
             block (db/entity (:db/id block))
             repo (state/get-current-repo)
             top-block? (= (:db/id (or (ldb/get-left-sibling block) (:block/parent block)))
-                         (:db/id (:block/page block)))
+                          (:db/id (:block/page block)))
             single-block? (inside-of-single-block (.-target e))
             root-block? (= (:block.temp/container block) (str (:block/uuid block)))]
         (block-handler/mark-last-input-time! repo)
@@ -2753,18 +2752,18 @@
                 custom-query? (get-in editor-state [:config :custom-query?])]
             (util/stop e)
             (when (and (not (and top-block? (not (string/blank? value))))
-                    (not root-block?)
-                    (not single-block?)
-                    (not custom-query?))
+                       (not root-block?)
+                       (not single-block?)
+                       (not custom-query?))
               (if (own-order-number-list? block)
                 (p/do!
-                  (save-current-block!)
-                  (remove-block-own-order-list-type! block))
+                 (save-current-block!)
+                 (remove-block-own-order-list-type! block))
                 (delete-block! repo))))
 
           (and (> current-pos 0)
-            (contains? #{commands/command-trigger commands/command-ask}
-              (util/nth-safe value (dec current-pos))))
+               (contains? #{commands/command-trigger commands/command-ask}
+                          (util/nth-safe value (dec current-pos))))
           (do
             (util/stop e)
             (commands/restore-state)
@@ -2772,12 +2771,12 @@
 
           ;; pair
           (and
-            deleted
-            (contains?
-              (set (keys delete-map))
-              deleted)
-            (>= (count value) (inc current-pos))
-            (= (util/nth-safe value current-pos)
+           deleted
+           (contains?
+            (set (keys delete-map))
+            deleted)
+           (>= (count value) (inc current-pos))
+           (= (util/nth-safe value current-pos)
               (get delete-map deleted)))
 
           (do
@@ -2804,7 +2803,7 @@
           (when (and input (not (mobile-util/native-ios?)))
             (util/stop e)
             (delete-and-update
-              input (util/safe-dec-current-pos-from-end (.-value input) current-pos) current-pos))))
+             input (util/safe-dec-current-pos-from-end (.-value input) current-pos) current-pos))))
       false)))
 
 (defn indent-outdent
@@ -3230,14 +3229,14 @@
 (defn- in-page-preview?
   []
   (some-> js/document.activeElement
-    (.closest ".ls-preview-popup")
-    (nil?) (not)))
+          (.closest ".ls-preview-popup")
+          (nil?) (not)))
 
 (defn shortcut-up-down [direction]
   (fn [e]
     (when (and (not (auto-complete?))
                (or (in-page-preview?)
-                 (not (in-shui-popup?)))
+                   (not (in-shui-popup?)))
                (not (slide-focused?))
                (not (state/get-timestamp-block)))
       (util/stop e)
@@ -3396,6 +3395,7 @@
            (or (if ignore-children? false (db-model/has-children? block-id))
                (valid-dsl-query-block? block repo)
                (valid-custom-query-block? block)
+               (and db-based? (ldb/class-instance? (db/entity :logseq.class/Query) block))
                (and db-based?
                     (seq property-keys)
                     (not (db-pu/all-hidden-properties? property-keys)))
@@ -3592,39 +3592,39 @@
 (defn toggle-collapse!
   ([e] (toggle-collapse! e false))
   ([e clear-selection?]
-    (when e (util/stop e))
-    (cond
-      (state/editing?)
-      (when-let [block (state/get-edit-block)]
+   (when e (util/stop e))
+   (cond
+     (state/editing?)
+     (when-let [block (state/get-edit-block)]
         ;; get-edit-block doesn't track the latest collapsed state, so we need to reload from db.
-        (let [block-id (:block/uuid block)
-              block (db/entity [:block/uuid block-id])]
-          (if (:block/collapsed? block)
-            (expand! e clear-selection?)
-            (collapse! e clear-selection?))))
+       (let [block-id (:block/uuid block)
+             block (db/entity [:block/uuid block-id])]
+         (if (:block/collapsed? block)
+           (expand! e clear-selection?)
+           (collapse! e clear-selection?))))
 
-      (state/selection?)
-      (do
-        (let [block-ids (map #(-> % (dom/attr "blockid") uuid) (get-selected-blocks))
-              first-block-id (first block-ids)]
-          (when first-block-id
+     (state/selection?)
+     (do
+       (let [block-ids (map #(-> % (dom/attr "blockid") uuid) (get-selected-blocks))
+             first-block-id (first block-ids)]
+         (when first-block-id
             ;; If multiple blocks are selected, they may not have all the same collapsed state.
             ;; For simplicity, use the first block's state to decide whether to collapse/expand all.
-            (let [first-block (db/entity [:block/uuid first-block-id])]
-              (if (:block/collapsed? first-block)
-                (doseq [block-id block-ids] (expand-block! block-id))
-                (doseq [block-id block-ids] (collapse-block! block-id))))))
-        (and clear-selection? (clear-selection!)))
+           (let [first-block (db/entity [:block/uuid first-block-id])]
+             (if (:block/collapsed? first-block)
+               (doseq [block-id block-ids] (expand-block! block-id))
+               (doseq [block-id block-ids] (collapse-block! block-id))))))
+       (and clear-selection? (clear-selection!)))
 
-      (whiteboard?)
+     (whiteboard?)
       ;; TODO: Looks like detecting the whiteboard selection's collapse state will take more work.
       ;; Leaving unimplemented for now.
-      nil
+     nil
 
-      :else
+     :else
       ;; If no block is being edited or selected, the "toggle" action doesn't make sense,
       ;; so we no-op here, unlike in the expand! & collapse! functions.
-      nil)))
+     nil)))
 
 (defn collapse-all!
   ([]

+ 165 - 168
src/main/frontend/handler/events.cljs

@@ -88,12 +88,12 @@
 
 (defn file-sync-restart! []
   (async/go (async/<! (p->c (persist-var/load-vars)))
-    (async/<! (sync/<sync-stop))
-    (some-> (sync/<sync-start) async/<!)))
+            (async/<! (sync/<sync-stop))
+            (some-> (sync/<sync-start) async/<!)))
 
 (defn- file-sync-stop! []
   (async/go (async/<! (p->c (persist-var/load-vars)))
-    (async/<! (sync/<sync-stop))))
+            (async/<! (sync/<sync-stop))))
 
 (defn- enable-beta-features!
   []
@@ -225,32 +225,32 @@
           (when-let [root (state/get-local-container-root-url)]
             (let [graph-path (graph-picker/validate-graph-dirname root graph-name)]
               (->
-                (p/let [exists? (fs/dir-exists? graph-path)]
-                  (let [overwrite? (if exists?
-                                     (js/confirm (str "There's already a directory with the name \"" graph-name "\", do you want to overwrite it? Make sure to backup it first if you're not sure about it."))
-                                     true)]
-                    (if overwrite?
-                      (p/let [_ (fs/mkdir-if-not-exists graph-path)]
-                        (nfs-handler/ls-dir-files-with-path!
-                          graph-path
-                          {:ok-handler (fn []
-                                         (file-sync-handler/init-remote-graph graph-path graph)
-                                         (js/setTimeout (fn [] (repo-handler/refresh-repos!)) 200))}))
-                      (let [graph-name (-> (js/prompt "Please specify a new directory name to download the graph:")
-                                         str
-                                         string/trim)]
-                        (when-not (string/blank? graph-name)
-                          (state/pub-event! [:graph/pull-down-remote-graph graph graph-name]))))))
-                (p/catch (fn [^js e]
-                           (notification/show! (str e) :error)
-                           (js/console.error e)))))))))
+               (p/let [exists? (fs/dir-exists? graph-path)]
+                 (let [overwrite? (if exists?
+                                    (js/confirm (str "There's already a directory with the name \"" graph-name "\", do you want to overwrite it? Make sure to backup it first if you're not sure about it."))
+                                    true)]
+                   (if overwrite?
+                     (p/let [_ (fs/mkdir-if-not-exists graph-path)]
+                       (nfs-handler/ls-dir-files-with-path!
+                        graph-path
+                        {:ok-handler (fn []
+                                       (file-sync-handler/init-remote-graph graph-path graph)
+                                       (js/setTimeout (fn [] (repo-handler/refresh-repos!)) 200))}))
+                     (let [graph-name (-> (js/prompt "Please specify a new directory name to download the graph:")
+                                          str
+                                          string/trim)]
+                       (when-not (string/blank? graph-name)
+                         (state/pub-event! [:graph/pull-down-remote-graph graph graph-name]))))))
+               (p/catch (fn [^js e]
+                          (notification/show! (str e) :error)
+                          (js/console.error e)))))))))
     (shui/dialog-open!
-      (file-sync/pick-dest-to-sync-panel graph))))
+     (file-sync/pick-dest-to-sync-panel graph))))
 
 (defmethod handle :graph/pick-page-histories [[_ graph-uuid page-name]]
   (shui/dialog-open!
-    (file-sync/pick-page-histories-panel graph-uuid page-name)
-    {:id :page-histories :label "modal-page-histories"}))
+   (file-sync/pick-page-histories-panel graph-uuid page-name)
+   {:id :page-histories :label "modal-page-histories"}))
 
 (defmethod handle :graph/open-new-window [[_ev target-repo]]
   (p/let [current-repo (state/get-current-repo)]
@@ -269,7 +269,7 @@
   [repo]
   (when
    (and (not (util/electron?))
-     (not (mobile-util/native-platform?)))
+        (not (mobile-util/native-platform?)))
     (fn [{:keys [close]}]
       [:div
        ;; TODO: fn translation with args
@@ -277,29 +277,29 @@
         "Grant native filesystem permission for directory: "
         [:b (config/get-local-dir repo)]]
        (ui/button
-         (t :settings-permission/start-granting)
-         :class "ui__modal-enter"
-         :on-click (fn []
-                     (nfs/check-directory-permission! repo)
-                     (close)))])))
+        (t :settings-permission/start-granting)
+        :class "ui__modal-enter"
+        :on-click (fn []
+                    (nfs/check-directory-permission! repo)
+                    (close)))])))
 
 (defmethod handle :modal/nfs-ask-permission []
   (when-let [repo (get-local-repo)]
     (some-> (ask-permission repo)
-      (shui/dialog-open! {:align :top}))))
+            (shui/dialog-open! {:align :top}))))
 
-(defmethod handle :modal/show-cards [_]
+(defmethod handle :modal/show-cards [[_ cards-id]]
   (let [db-based? (config/db-based-graph? (state/get-current-repo))]
     (shui/dialog-open!
-     (if db-based? fsrs/cards srs/global-cards)
+     (if db-based? (fn [] (fsrs/cards cards-id)) srs/global-cards)
      {:id :srs
       :label "flashcards__cp"})))
 
 (defmethod handle :modal/show-instruction [_]
   (shui/dialog-open!
-    capacitor-fs/instruction
-    {:id :instruction
-     :label "instruction__cp"}))
+   capacitor-fs/instruction
+   {:id :instruction
+    :label "instruction__cp"}))
 
 (defmethod handle :modal/show-themes-modal [[_ classic?]]
   (if classic?
@@ -311,10 +311,10 @@
     (if (shui/dialog-get label)
       (shui/dialog-close! label)
       (shui/dialog-open!
-        #(settings/modal-appearance-inner)
-        {:id      label
-         :overlay-props {:label label}
-         :label   label}))))
+       #(settings/modal-appearance-inner)
+       {:id      label
+        :overlay-props {:label label}
+        :label   label}))))
 
 (defmethod handle :modal/set-git-username-and-email [[_ _content]]
   (shui/dialog-open! git-component/set-git-username-and-email))
@@ -337,13 +337,12 @@
 (defmethod handle :file/not-matched-from-disk [[_ path disk-content db-content]]
   (when-let [repo (state/get-current-repo)]
     (shui/dialog-open!
-      #(diff/local-file repo path disk-content db-content)
-      {:label "diff__cp"})))
-
+     #(diff/local-file repo path disk-content db-content)
+     {:label "diff__cp"})))
 
 (defmethod handle :modal/display-file-version-selector  [[_ versions path  get-content]]
   (shui/dialog-open!
-    #(git-component/file-version-selector versions path get-content)))
+   #(git-component/file-version-selector versions path get-content)))
 
 (defmethod handle :graph/sync-context []
   (let [context {:dev? config/dev?
@@ -393,10 +392,10 @@
 
 (defmethod handle :go/search [_]
   (state/set-modal! cmdk/cmdk-modal
-    {:fullscreen? true
-     :close-btn?  false
-     :panel?      false
-     :label "ls-modal-search"}))
+                    {:fullscreen? true
+                     :close-btn?  false
+                     :panel?      false
+                     :label "ls-modal-search"}))
 
 (defmethod handle :go/plugins [_]
   (plugin/open-plugins-modal!))
@@ -415,9 +414,8 @@
 
 (defmethod handle :go/proxy-settings [[_ agent-opts]]
   (shui/dialog-open!
-    (plugin/user-proxy-settings-panel agent-opts)
-    {:id :https-proxy-panel :center? true :class "lg:max-w-2xl"}))
-
+   (plugin/user-proxy-settings-panel agent-opts)
+   {:id :https-proxy-panel :center? true :class "lg:max-w-2xl"}))
 
 (defmethod handle :redirect-to-home [_]
   (page-handler/create-today-journal!))
@@ -430,12 +428,12 @@
 (defmethod handle :capture-error [[_ {:keys [error payload]}]]
   (let [[user-uuid graph-uuid tx-id] @sync/graphs-txid
         payload (assoc payload
-                  :user-id user-uuid
-                  :graph-id graph-uuid
-                  :tx-id tx-id
-                  :db-based (config/db-based-graph? (state/get-current-repo)))]
+                       :user-id user-uuid
+                       :graph-id graph-uuid
+                       :tx-id tx-id
+                       :db-based (config/db-based-graph? (state/get-current-repo)))]
     (Sentry/captureException error
-      (bean/->js {:tags payload}))))
+                             (bean/->js {:tags payload}))))
 
 (defmethod handle :exec-plugin-cmd [[_ {:keys [pid cmd action]}]]
   (commands/exec-plugin-simple-command! pid cmd action))
@@ -469,7 +467,7 @@
       (js/setTimeout (fn []
                        (when-let [toolbar (.querySelector main-node "#mobile-editor-toolbar")]
                          (set! (.. toolbar -style -bottom) (str keyboard-height "px"))))
-        100))))
+                     100))))
 
 (defmethod handle :mobile/keyboard-will-hide [[_]]
   (let [main-node (util/app-scroll-container-node)]
@@ -497,8 +495,8 @@
   [repo-url]
   (when repo-url
     (let [app-id (-> (first (string/split repo-url "/Documents"))
-                   (string/split "/")
-                   last)]
+                     (string/split "/")
+                     last)]
       app-id)))
 
 (defmethod handle :validate-appId [[_ graph-switch-f graph]]
@@ -513,13 +511,13 @@
               current-document-url (.getUri Filesystem #js {:path ""
                                                             :directory (.-Documents Directory)})
               current-app-id (-> (js->clj current-document-url :keywordize-keys true)
-                               get-ios-app-id)]
+                                 get-ios-app-id)]
         (if (= deprecated-app-id current-app-id)
           (when graph-switch-f (graph-switch-f graph true))
           (do
             (notification/show! [:div "Migrating from previous App installation..."]
-              :warning
-              true)
+                                :warning
+                                true)
             (prn ::migrate-app-id :from deprecated-app-id :to current-app-id)
             (file-sync-stop!)
             (.unwatch mobile-util/fs-watcher)
@@ -528,11 +526,11 @@
               (try
                 ;; replace app-id part of repo url
                 (reset! conn/conns
-                  (update-keys @conn/conns
-                    (fn [key]
-                      (if (string/includes? key deprecated-app-id)
-                        (string/replace key deprecated-app-id current-app-id)
-                        key))))
+                        (update-keys @conn/conns
+                                     (fn [key]
+                                       (if (string/includes? key deprecated-app-id)
+                                         (string/replace key deprecated-app-id current-app-id)
+                                         key))))
                 (db-persist/rename-graph! deprecated-repo current-repo)
                 (search/remove-db! deprecated-repo)
                 (state/add-repo! {:url current-repo :nfs? true})
@@ -550,7 +548,7 @@
   (let [downloading?   (:plugin/updates-downloading? @state/state)
         auto-checking? (plugin-handler/get-auto-checking?)]
     (when-let [coming (and (not downloading?)
-                        (get-in @state/state [:plugin/updates-coming id]))]
+                           (get-in @state/state [:plugin/updates-coming id]))]
       (let [error-code (:error-code coming)
             error-code (if (= error-code (str :no-new-version)) nil error-code)
             title      (:title coming)]
@@ -558,14 +556,14 @@
           (if-not error-code
             (plugin/set-updates-sub-content! (str title "...") 0)
             (notification/show!
-              (str "[Checked]<" title "> " error-code) :error)))))
+             (str "[Checked]<" title "> " error-code) :error)))))
 
     (if (and updated? downloading?)
       ;; try to start consume downloading item
       (if-let [next-coming (state/get-next-selected-coming-update)]
         (plugin-handler/check-or-update-marketplace-plugin!
-          (assoc next-coming :only-check false :error-code nil)
-          (fn [^js e] (js/console.error "[Download Err]" next-coming e)))
+         (assoc next-coming :only-check false :error-code nil)
+         (fn [^js e] (js/console.error "[Download Err]" next-coming e)))
         (plugin-handler/close-updates-downloading))
 
       ;; try to start consume pending item
@@ -573,29 +571,29 @@
         (do
           (println "Updates: take next pending - " (:id next-pending))
           (js/setTimeout
-            #(plugin-handler/check-or-update-marketplace-plugin!
-               (assoc next-pending :only-check true :auto-check auto-checking? :error-code nil)
-               (fn [^js e]
-                 (notification/show! (.toString e) :error)
-                 (js/console.error "[Check Err]" next-pending e))) 500))
+           #(plugin-handler/check-or-update-marketplace-plugin!
+             (assoc next-pending :only-check true :auto-check auto-checking? :error-code nil)
+             (fn [^js e]
+               (notification/show! (.toString e) :error)
+               (js/console.error "[Check Err]" next-pending e))) 500))
 
         ;; try to open waiting updates list
         (do (when (and prev-pending? (not auto-checking?)
-                    (seq (state/all-available-coming-updates)))
+                       (seq (state/all-available-coming-updates)))
               (plugin/open-waiting-updates-modal!))
             (plugin-handler/set-auto-checking! false))))))
 
 (defmethod handle :plugin/hook-db-tx [[_ {:keys [blocks tx-data] :as payload}]]
   (when-let [payload (and (seq blocks)
-                       (merge payload {:tx-data (map #(into [] %) tx-data)}))]
+                          (merge payload {:tx-data (map #(into [] %) tx-data)}))]
     (plugin-handler/hook-plugin-db :changed payload)
     (plugin-handler/hook-plugin-block-changes payload)))
 
 (defmethod handle :plugin/loader-perf-tip [[_ {:keys [^js o _s _e]}]]
   (when-let [opts (.-options o)]
     (notification/show!
-      (plugin/perf-tip-content (.-id o) (.-name opts) (.-url opts))
-      :warning false (.-id o))))
+     (plugin/perf-tip-content (.-id o) (.-name opts) (.-url opts))
+     :warning false (.-id o))))
 
 (defmethod handle :mobile-file-watcher/changed [[_ ^js event]]
   (let [type (.-event event)
@@ -616,16 +614,16 @@
 
 (defmethod handle :graph/ask-for-re-fresh [_]
   (shui/dialog-open!
-    [:div {:style {:max-width 700}}
-     [:p (t :sync-from-local-changes-detected)]
-     [:div.flex.justify-end
-      (ui/button
-        (t :yes)
-        :autoFocus "on"
-        :class "ui__modal-enter"
-        :on-click (fn []
-                    (shui/dialog-close!)
-                    (nfs-handler/refresh! (state/get-current-repo) refresh-cb)))]]))
+   [:div {:style {:max-width 700}}
+    [:p (t :sync-from-local-changes-detected)]
+    [:div.flex.justify-end
+     (ui/button
+      (t :yes)
+      :autoFocus "on"
+      :class "ui__modal-enter"
+      :on-click (fn []
+                  (shui/dialog-close!)
+                  (nfs-handler/refresh! (state/get-current-repo) refresh-cb)))]]))
 
 (defmethod handle :sync/create-remote-graph [[_ current-repo]]
   (let [graph-name (js/decodeURI (util/node-path.basename current-repo))]
@@ -639,21 +637,21 @@
         (state/set-repos! (map (fn [r]
                                  (if (= (:url r) current-repo)
                                    (assoc r
-                                     :GraphUUID GraphUUID
-                                     :GraphName graph-name
-                                     :remote? true)
+                                          :GraphUUID GraphUUID
+                                          :GraphName graph-name
+                                          :remote? true)
                                    r))
-                            (state/get-repos)))))))
+                               (state/get-repos)))))))
 
 (defmethod handle :modal/remote-encryption-input-pw-dialog [[_ repo-url remote-graph-info type opts]]
   (shui/dialog-open!
-    (encryption/input-password
-      repo-url nil (merge
-                     (assoc remote-graph-info
-                       :type (or type :create-pwd-remote)
-                       :repo repo-url)
-                     opts))
-    {:center? true :close-btn? false :close-backdrop? false}))
+   (encryption/input-password
+    repo-url nil (merge
+                  (assoc remote-graph-info
+                         :type (or type :create-pwd-remote)
+                         :repo repo-url)
+                  opts))
+   {:center? true :close-btn? false :close-backdrop? false}))
 
 (defmethod handle :journal/insert-template [[_ page-name]]
   (let [page-name (util/page-name-sanity-lc page-name)]
@@ -674,32 +672,32 @@
     (let [file (:block/file page-entity)]
       (when-let [path (:file/path file)]
         (when (and (not= content (:file/content file))
-                (:file/content file))
+                   (:file/content file))
           (sync/add-new-version-file graph path (:file/content file)))
         (p/let [_ (file-handler/alter-file graph
-                    path
-                    content
-                    {:re-render-root? true
-                     :skip-compare? true})]
+                                           path
+                                           content
+                                           {:re-render-root? true
+                                            :skip-compare? true})]
           (state/close-modal!)
           (route-handler/redirect! {:to :page
                                     :path-params {:name (:block/name page-entity)}}))))))
 
 (defmethod handle :whiteboard/onboarding [[_ opts]]
   (shui/dialog-open!
-    (fn [{:keys [close]}] (whiteboard/onboarding-welcome close))
-    (merge {:close-btn?      false
-            :center?         true
-            :close-backdrop? false} opts)))
+   (fn [{:keys [close]}] (whiteboard/onboarding-welcome close))
+   (merge {:close-btn?      false
+           :center?         true
+           :close-backdrop? false} opts)))
 
 (defmethod handle :file-sync/onboarding-tip [[_ type opts]]
   (let [type (keyword type)]
     (when-not (config/db-based-graph? (state/get-current-repo))
       (shui/dialog-open!
-        (file-sync/make-onboarding-panel type)
-        (merge {:close-btn? false
-                :center? true
-                :close-backdrop? (not= type :welcome)} opts)))))
+       (file-sync/make-onboarding-panel type)
+       (merge {:close-btn? false
+               :center? true
+               :close-backdrop? (not= type :welcome)} opts)))))
 
 (defmethod handle :file-sync/maybe-onboarding-show [[_ type]]
   (file-sync/maybe-onboarding-show type))
@@ -747,24 +745,24 @@
 
 (defmethod handle :ui/notify-skipped-downloading-files [[_ paths]]
   (notification/show!
-    [:div
-     [:div.mb-4
-      [:div.font-semibold.mb-4.text-xl "It seems that some of your filenames are in the outdated format."]
-      [:p
-       "The files below that have reserved characters can't be saved on this device."]
-      [:div.overflow-y-auto.max-h-96
-       [:ol.my-2
-        (for [path paths]
-          [:li path])]]
+   [:div
+    [:div.mb-4
+     [:div.font-semibold.mb-4.text-xl "It seems that some of your filenames are in the outdated format."]
+     [:p
+      "The files below that have reserved characters can't be saved on this device."]
+     [:div.overflow-y-auto.max-h-96
+      [:ol.my-2
+       (for [path paths]
+         [:li path])]]
 
-      [:div
-       [:p
-        "Check " [:a {:href "https://docs.logseq.com/#/page/logseq%20file%20and%20folder%20naming%20rules"
-                      :target "_blank"}
-                  "Logseq file and folder naming rules"]
-        " for more details."]]]]
-    :warning
-    false))
+     [:div
+      [:p
+       "Check " [:a {:href "https://docs.logseq.com/#/page/logseq%20file%20and%20folder%20naming%20rules"
+                     :target "_blank"}
+                 "Logseq file and folder naming rules"]
+       " for more details."]]]]
+   :warning
+   false))
 
 (defmethod handle :graph/setup-a-repo [[_ opts]]
   (let [opts' (merge {:picked-root-fn #(state/close-modal!)
@@ -772,16 +770,16 @@
                       :logged?        (user-handler/logged-in?)} opts)]
     (if (mobile-util/native-ios?)
       (shui/dialog-open!
-        #(graph-picker/graph-picker-cp opts')
-        {:label "graph-setup"})
+       #(graph-picker/graph-picker-cp opts')
+       {:label "graph-setup"})
       (page-handler/ls-dir-files! st/refresh! opts'))))
 
 (defmethod handle :graph/new-db-graph [[_ _opts]]
   (shui/dialog-open!
-    repo/new-db-graph
-    {:id :new-db-graph
-     :title [:h2 "Create a new graph"]
-     :style {:max-width "500px"}}))
+   repo/new-db-graph
+   {:id :new-db-graph
+    :title [:h2 "Create a new graph"]
+    :style {:max-width "500px"}}))
 
 (defmethod handle :graph/save-db-to-disk [[_ _opts]]
   (persist-db/export-current-graph! {:succ-notification? true}))
@@ -814,21 +812,21 @@
         [:div
          [:p
           (str "It seems that another whiteboard file already has the ID \"" id
-            "\". You can fix it by changing the ID in this file with another UUID.")]
+               "\". You can fix it by changing the ID in this file with another UUID.")]
          [:p
           "Or, let me"
           (ui/button "Fix"
-            :on-click (fn []
-                        (let [dir (config/get-repo-dir repo)]
-                          (p/let [content (fs/read-file dir file)]
-                            (let [new-content (string/replace content (str id) (str (random-uuid)))]
-                              (p/let [_ (fs/write-file! repo
-                                          dir
-                                          file
-                                          new-content
-                                          {})]
-                                (reset! resolved? true))))))
-            :class "inline mx-1")
+                     :on-click (fn []
+                                 (let [dir (config/get-repo-dir repo)]
+                                   (p/let [content (fs/read-file dir file)]
+                                     (let [new-content (string/replace content (str id) (str (random-uuid)))]
+                                       (p/let [_ (fs/write-file! repo
+                                                                 dir
+                                                                 file
+                                                                 new-content
+                                                                 {})]
+                                         (reset! resolved? true))))))
+                     :class "inline mx-1")
           "it."]])]]))
 
 (defmethod handle :file/parse-and-load-error [[_ repo parse-errors]]
@@ -841,8 +839,8 @@
                           (let [data (ex-data error)]
                             (cond
                               (and (common-config/whiteboard? file)
-                                (= :transact/upsert (:error data))
-                                (uuid? (last (:assertion data))))
+                                   (= :transact/upsert (:error data))
+                                   (uuid? (last (:assertion data))))
                               (rum/with-key (file-id-conflict-item repo file data) file)
 
                               :else
@@ -945,8 +943,7 @@
                               :auto-focus? true})
            (shui/dialog-open! #(property-dialog/dialog blocks opts')
                               {:id :property-dialog
-                               :align "start"
-                               :content-props {:onOpenAutoFocus #(.preventDefault %)}})))))))
+                               :align "start"})))))))
 
 (rum/defc multi-tabs-dialog
   []
@@ -954,7 +951,7 @@
     [:div.flex.p-4.flex-col.gap-4.h-64
      [:span.warning.text-lg
       (util/format "Logseq doesn't support multiple %ss access to the same graph yet, please close this %s or switch to another graph."
-        word word)]
+                   word word)]
      [:div.text-lg
       [:p "Switch to another repo: "]
       [:div.border.rounded.bg-gray-01.overflow-hidden.w-60
@@ -976,7 +973,7 @@
 (defmethod handle :rtc/download-remote-graph [[_ graph-name graph-uuid]]
   (->
    (p/do!
-     (rtc-handler/<rtc-download-graph! graph-name graph-uuid 60000))
+    (rtc-handler/<rtc-download-graph! graph-name graph-uuid 60000))
    (p/catch (fn [e]
               (println "RTC download graph failed, error:")
               (js/console.error e)))))
@@ -996,18 +993,18 @@
     (async/go-loop []
       (let [[payload d] (async/<! chan)]
         (->
-          (try
-            (p/resolved (handle payload))
-            (catch :default error
-              (p/rejected error)))
-          (p/then (fn [result]
-                    (p/resolve! d result)))
-          (p/catch (fn [error]
-                     (let [type :handle-system-events/failed]
-                       (state/pub-event! [:capture-error {:error error
-                                                          :payload {:type type
-                                                                    :payload payload}}])
-                       (p/reject! d error))))))
+         (try
+           (p/resolved (handle payload))
+           (catch :default error
+             (p/rejected error)))
+         (p/then (fn [result]
+                   (p/resolve! d result)))
+         (p/catch (fn [error]
+                    (let [type :handle-system-events/failed]
+                      (state/pub-event! [:capture-error {:error error
+                                                         :payload {:type type
+                                                                   :payload payload}}])
+                      (p/reject! d error))))))
       (recur))
     chan))
 

+ 22 - 10
src/main/frontend/worker/db/migrate.cljs

@@ -123,7 +123,7 @@
                                          types)]
                               (when type
                                 [[:db/retract id :block/type]
-                                [:db/add id :block/type type]])))))
+                                 [:db/add id :block/type type]])))))
         schema (:schema db)]
     (ldb/transact! conn new-type-tx {:db-migrate? true})
     (d/reset-schema! conn (update schema :block/type #(assoc % :db/cardinality :db.cardinality/one)))
@@ -134,12 +134,12 @@
   (let [db @conn]
     (when (ldb/db-based-graph? db)
       (let [datoms (d/datoms db :avet :class/parent)]
-       (->> (set (map :e datoms))
-            (mapcat
-             (fn [id]
-               (let [value (:db/id (:class/parent (d/entity db id)))]
-                 [[:db/retract id :class/parent]
-                  [:db/add id :logseq.property/parent value]]))))))))
+        (->> (set (map :e datoms))
+             (mapcat
+              (fn [id]
+                (let [value (:db/id (:class/parent (d/entity db id)))]
+                  [[:db/retract id :class/parent]
+                   [:db/add id :logseq.property/parent value]]))))))))
 
 (defn- deprecate-class-schema-properties
   [conn _search-db]
@@ -184,8 +184,8 @@
             fix-data (keep
                       (fn [d]
                         (if-let [id (if (= :all-pages (:v d))
-                                   (:db/id (ldb/get-case-page db common-config/views-page-name))
-                                   (:db/id (d/entity db (:v d))))]
+                                      (:db/id (ldb/get-case-page db common-config/views-page-name))
+                                      (:db/id (d/entity db (:v d))))]
                           [:db/add (:e d) :logseq.property/view-for id]
                           [:db/retract (:e d) :logseq.property/view-for (:v d)]))
                       datoms)]
@@ -200,6 +200,14 @@
         [[:db/add card-id :logseq.property.class/properties :logseq.property.fsrs/due]
          [:db/add card-id :logseq.property.class/properties :logseq.property.fsrs/state]]))))
 
+(defn- add-query-property-to-query-tag
+  [conn _search-db]
+  (let [db @conn]
+    (when (ldb/db-based-graph? db)
+      (let [query (d/entity db :logseq.class/Query)
+            query-id (:db/id query)]
+        [[:db/add query-id :logseq.property.class/properties :logseq.property/query]]))))
+
 (defn- add-addresses-in-kvs-table
   [^Object sqlite-db]
   (let [columns (->> (.exec sqlite-db #js {:sql "SELECT NAME FROM PRAGMA_TABLE_INFO('kvs')"
@@ -263,7 +271,11 @@
    [20 {:fix fix-view-for}]
    [21 {:properties [:logseq.property.table/sized-columns]}]
    [22 {:properties [:logseq.property.fsrs/state :logseq.property.fsrs/due]}]
-   [23 {:fix add-card-properties}]])
+   [23 {:fix add-card-properties}]
+   [24 {:classes [:logseq.class/Cards]}]
+   [25 {:classes [:logseq.class/Advanced-query]
+        :properties [:logseq.property/query]
+        :fix add-query-property-to-query-tag}]])
 
 (let [max-schema-version (apply max (map first schema-version->updates))]
   (assert (<= db-schema/version max-schema-version))

+ 1 - 1
src/test/frontend/db/db_based_model_test.cljs

@@ -24,7 +24,7 @@
   (let [opts {:redirect? false :create-first-block? false :class? true}
         _ (test-helper/create-page! "class1" opts)
         _ (test-helper/create-page! "class2" opts)]
-    (is (= ["Card" "Journal" "Query" "Root Tag" "Task" "class1" "class2"] (sort (map :block/title (model/get-all-classes repo)))))))
+    (is (= ["Advanced query" "Card" "Cards" "Journal" "Query" "Root Tag" "Task" "class1" "class2"] (sort (map :block/title (model/get-all-classes repo)))))))
 
 (deftest ^:fix-me get-class-objects-test
   (let [opts {:redirect? false :create-first-block? false :class? true}