macros.cljs 3.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. (ns frontend.components.block.macros
  2. "Logseq macros that render and evaluate in blocks"
  3. (:require [clojure.walk :as walk]
  4. [frontend.extensions.sci :as sci]
  5. [frontend.handler.common :as common-handler]
  6. [goog.string :as gstring]
  7. [goog.string.format]
  8. [frontend.state :as state]
  9. [frontend.config :as config]
  10. [datascript.core :as d]
  11. [logseq.db.frontend.property :as db-property]
  12. [frontend.db.conn :as db-conn]))
  13. (defn- properties-by-name
  14. "Given a block from a query result, returns a map of its properties indexed by
  15. property idents and titles"
  16. [db block]
  17. (->> (db-property/properties block)
  18. (mapcat (fn [[k v]]
  19. ;; For now just support cardinality :one
  20. (when-not (set? v)
  21. (let [prop-val (some->> (:db/id v)
  22. (d/entity db)
  23. db-property/property-value-content)
  24. property (d/entity db k)]
  25. [[(keyword (:block/title property)) prop-val]
  26. [(:db/ident property) prop-val]]))))
  27. (into {})))
  28. (defn- normalize-query-function
  29. [ast* repo result]
  30. (let [ast (walk/prewalk
  31. (fn [f]
  32. (if (and (list? f)
  33. (keyword? (second f))
  34. (contains? #{'sum 'average 'count 'min 'max} (first f)))
  35. (if (contains? #{'min 'max} (first f))
  36. (list
  37. 'apply
  38. (first f)
  39. (list 'map (second f) 'result))
  40. (list
  41. (first f)
  42. (list 'map (second f) 'result)))
  43. f))
  44. ast*)
  45. db-based-graph? (config/db-based-graph? repo)
  46. ;; These keyword aliases should be the same as those used in the query-table for sorting
  47. special-file-graph-keywords
  48. {:block :block/title
  49. :page :block/name
  50. :created-at :block/created-at
  51. :updated-at :block/updated-at}]
  52. (walk/postwalk
  53. (fn [f]
  54. (cond
  55. (keyword? f)
  56. (if-let [kw (and (not db-based-graph?) (get special-file-graph-keywords f))]
  57. kw
  58. (let [vals (map #(get-in % [:block/properties f]) result)
  59. int? (some integer? vals)]
  60. `(~'fn [~'b]
  61. (~'let [~'result-str (~'get-in ~'b [:block/properties ~f])
  62. ~'result-num (~'parseFloat ~'result-str)
  63. ~'result (if (~'isNaN ~'result-num) ~'result-str ~'result-num)]
  64. (~'or ~'result (~'when ~int? 0))))))
  65. :else
  66. f))
  67. ast)))
  68. (defn function-macro
  69. "Provides functionality for {{function}}"
  70. [query-result* arguments]
  71. (let [query-result (if (map? query-result*)
  72. ;; Ungroup results grouped by page in page view
  73. (mapcat val query-result*)
  74. query-result*)
  75. repo (state/get-current-repo)
  76. db (db-conn/get-db repo)
  77. query-result' (if (config/db-based-graph? repo)
  78. (->> query-result
  79. (map #(d/entity db (:db/id %)))
  80. (map #(hash-map :block/properties (properties-by-name db %))))
  81. query-result)
  82. fn-string (-> (gstring/format "(fn [result] %s)" (first arguments))
  83. (common-handler/safe-read-string "failed to parse function")
  84. (normalize-query-function repo query-result')
  85. (str))
  86. f (sci/eval-string fn-string)]
  87. (when (fn? f)
  88. (try (f query-result')
  89. (catch :default e
  90. (js/console.error e))))))