Przeglądaj źródła

feat: add /function command for queries

Tienson Qin 4 lat temu
rodzic
commit
d05c39b534

+ 2 - 1
src/main/frontend/commands.cljs

@@ -244,6 +244,7 @@
     ;; advanced
 
     [["Query" [[:editor/input "{{query }}" {:backward-pos 2}]] "Create a DataScript query"]
+     ["Query table function" [[:editor/input "{{function }}" {:backward-pos 2}]] "Create a query table function"]
      ["Calculator" [[:editor/input "```calc\n\n```" {:backward-pos 4}]
                     [:codemirror/focus]] "Insert a calculator"]
      ["Draw" (fn []
@@ -562,4 +563,4 @@
   [pid {:keys [key label block-id] :as cmd} action]
   (let [format (and block-id (:block/format (db-util/pull [:block/uuid block-id])))
         inputs (vector (conj action (assoc cmd :pid pid)))]
-    (handle-steps inputs format)))
+    (handle-steps inputs format)))

+ 28 - 1
src/main/frontend/components/block.cljs

@@ -30,6 +30,8 @@
             [frontend.handler.repeated :as repeated]
             [frontend.handler.route :as route-handler]
             [frontend.handler.ui :as ui-handler]
+            [frontend.handler.query :as query-handler]
+            [frontend.handler.common :as common-handler]
             [frontend.modules.outliner.tree :as tree]
             [frontend.search :as search]
             [frontend.security :as security]
@@ -990,9 +992,26 @@
         [:div.dsl-query
          (let [query (string/join ", " arguments)]
            (custom-query (assoc config :dsl-query? true)
-                         {:title [:span.font-medium.p-1 (str "Query: " query)]
+                         {:title [:span.font-medium.p-1.query-title
+                                  (str "Query: " query)]
                           :query query}))]
 
+        (= name "function")
+        (or
+         (when (:query-result config)
+           (when-let [query-result (rum/react (:query-result config))]
+             (let [fn-string (-> (util/format "(fn [result] %s)" (first arguments))
+                                 (common-handler/safe-read-string "failed to parse function")
+                                 (query-handler/normalize-query-function query-result)
+                                 (str))
+                   f (sci/eval-string fn-string)]
+               (when (fn? f)
+                 (try (f query-result)
+                      (catch js/Error e
+                        (js/console.error e)))))))
+         [:span.warning
+          (util/format "{{function %s}}" (first arguments))])
+
         (= name "youtube")
         (when-let [url (first arguments)]
           (let [YouTube-regex #"^((?:https?:)?//)?((?:www|m).)?((?:youtube.com|youtu.be))(/(?:[\w-]+\?v=|embed/|v/)?)([\w-]+)(\S+)?$"]
@@ -1866,6 +1885,11 @@
   [state config {:block/keys [uuid title body meta content page format repo children pre-block? top? properties refs path-refs heading-level level type] :as block}]
   (let [blocks-container-id (:blocks-container-id config)
         config (update config :block merge block)
+
+        ;; Each block might have multiple queries, but we store only the first query's result
+        config (if (nil? (:query-result config))
+                 (assoc config :query-result (atom nil))
+                 config)
         heading? (and (= type :heading) heading-level (<= heading-level 6))
         *control-show? (get state ::control-show?)
         *ref-collapsed? (get state ::ref-collapsed?)
@@ -2122,6 +2146,9 @@
                                     (and (string? query) (string/includes? query "(by-page false)")))
            result (when query-result
                     (db/custom-query-result-transform query-result remove-blocks q not-grouped-by-page?))
+           _ (when-let [query-result (:query-result config)]
+               (let [result (remove (fn [b] (some? (get-in b [:block/properties :template]))) result)]
+                     (reset! query-result result)))
            view-f (and view (sci/eval-string (pr-str view)))
            only-blocks? (:block/uuid (first result))
            blocks-grouped-by-page? (and (seq result)

+ 4 - 0
src/main/frontend/components/block.css

@@ -431,3 +431,7 @@ a.filter svg {
 .table-query-properties svg {
     display: inline;
 }
+
+.query-title {
+    background: var(--ls-page-properties-background-color);
+}

+ 5 - 0
src/main/frontend/components/query_table.cljs

@@ -57,6 +57,11 @@
           keys (if (seq query-properties)
                  query-properties
                  (get-keys result page?))
+          keys (if (some #{:created-at :updated-at} keys)
+                 (concat
+                  (remove #{:created-at :updated-at} keys)
+                  (filter #{:created-at :updated-at} keys))
+                 keys)
           sort-by-fn (fn [item]
                        (let [key @*sort-by-item]
                          (case key

+ 9 - 1
src/main/frontend/extensions/sci.cljs

@@ -1,10 +1,18 @@
 (ns frontend.extensions.sci
   (:require [sci.core :as sci]))
 
+;; Some helpers
+(def sum (partial apply +))
+
+(defn average [coll]
+  (def coll coll)
+  (/ (reduce + coll) (count coll)))
+
 (defn eval-string
   [s]
   (try
-    (sci/eval-string s)
+    (sci/eval-string s {:bindings {'sum sum
+                                   'average average}})
     (catch js/Error e
       (println "Query: sci eval failed:")
       (js/console.error e))))

+ 46 - 0
src/main/frontend/handler/query.cljs

@@ -0,0 +1,46 @@
+(ns frontend.handler.query
+  (:require [clojure.walk :as walk]))
+
+(defn normalize-query-function
+  [ast result]
+  (let [ast (walk/prewalk
+             (fn [f]
+               (if (and (list? f)
+                        (keyword? (second f))
+                        (contains? #{'sum 'average 'count 'min 'max} (first f)))
+                 (if (contains? #{'min 'max} (first f))
+                   (list
+                    'apply
+                    (first f)
+                    (list 'map (second f) 'result))
+                   (list
+                    (first f)
+                    (list 'map (second f) 'result)))
+                 f))
+             ast)]
+    (walk/postwalk
+     (fn [f]
+       (cond
+         (keyword? f)
+         (case f
+           :block
+           :block/content
+
+           :page
+           :block/name
+
+           :created-at
+           :block/created-at
+
+           :updated-at
+           :block/updated-at
+
+           (let [vals (map #(get-in % [:block/properties f]) result)
+                 int? (some integer? vals)]
+             `(~'fn [~'b]
+               (~'let [~'result (~'get-in ~'b [:block/properties ~f])]
+                (~'or ~'result (~'when ~int? 0))))))
+
+         :else
+         f))
+     ast)))