builder.cljs 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723
  1. (ns frontend.components.query.builder
  2. "DSL query builder."
  3. (:require [clojure.string :as string]
  4. [frontend.components.select :as component-select]
  5. [frontend.config :as config]
  6. [frontend.date :as date]
  7. [frontend.db :as db]
  8. [frontend.db-mixins :as db-mixins]
  9. [frontend.db.async :as db-async]
  10. [frontend.db.model :as db-model]
  11. [frontend.db.query-dsl :as query-dsl]
  12. [frontend.handler.editor :as editor-handler]
  13. [frontend.handler.query.builder :as query-builder]
  14. [frontend.hooks :as hooks]
  15. [frontend.mixins :as mixins]
  16. [frontend.state :as state]
  17. [frontend.ui :as ui]
  18. [frontend.util :as util]
  19. [logseq.common.util :as common-util]
  20. [logseq.common.util.page-ref :as page-ref]
  21. [logseq.db :as ldb]
  22. [logseq.db.frontend.property :as db-property]
  23. [logseq.db.frontend.property.type :as db-property-type]
  24. [logseq.db.sqlite.util :as sqlite-util]
  25. [logseq.graph-parser.db :as gp-db]
  26. [logseq.shui.ui :as shui]
  27. [promesa.core :as p]
  28. [rum.core :as rum]))
  29. (rum/defc page-block-selector
  30. [*find]
  31. [:div.filter-item {:on-pointer-down (fn [e] (util/stop-propagation e))}
  32. (ui/select [{:label "Blocks"
  33. :value "block"
  34. :selected (not= @*find :page)}
  35. {:label "Pages"
  36. :value "page"
  37. :selected (= @*find :page)}]
  38. (fn [e v]
  39. ;; Prevent opening the current block's editor
  40. (util/stop e)
  41. (reset! *find (keyword v))))])
  42. (defn- select
  43. ([items on-chosen]
  44. (select items on-chosen {}))
  45. ([items on-chosen options]
  46. (component-select/select (merge
  47. ;; Allow caller to build :items
  48. {:items (if (map? (first items))
  49. items
  50. (map #(hash-map :value %) items))
  51. :on-chosen on-chosen}
  52. options))))
  53. (defn append-tree!
  54. [*tree {:keys [toggle-fn toggle?]
  55. :or {toggle? true}} loc x]
  56. (swap! *tree #(query-builder/append-element % loc x))
  57. (when toggle? (toggle-fn)))
  58. (rum/defcs search < (rum/local nil ::input-value)
  59. (mixins/event-mixin
  60. (fn [state]
  61. (mixins/on-key-down
  62. state
  63. {;; enter
  64. 13 (fn [state e]
  65. (let [input-value (get state ::input-value)]
  66. (when-not (string/blank? @input-value)
  67. (util/stop e)
  68. (let [on-submit (first (:rum/args state))]
  69. (on-submit @input-value))
  70. (reset! input-value nil))))
  71. ;; escape
  72. 27 (fn [_state _e]
  73. (let [[_on-submit on-cancel] (:rum/args state)]
  74. (on-cancel)))})))
  75. [state _on-submit _on-cancel]
  76. (let [*input-value (::input-value state)]
  77. [:input#query-builder-search.form-input.block.sm:text-sm.sm:leading-5
  78. {:auto-focus true
  79. :placeholder "Full text search"
  80. :aria-label "Full text search"
  81. :on-change #(reset! *input-value (util/evalue %))}]))
  82. (defonce *between-dates (atom {}))
  83. (rum/defcs datepicker < rum/reactive
  84. (rum/local nil ::input-value)
  85. {:will-unmount (fn [state]
  86. (swap! *between-dates dissoc (first (:rum/args state)))
  87. state)}
  88. [state id placeholder {:keys [auto-focus on-select]}]
  89. (let [*input-value (::input-value state)]
  90. [:div.ml-4
  91. [:input.query-builder-datepicker.form-input.block.sm:text-sm.sm:leading-5
  92. {:auto-focus (or auto-focus false)
  93. :data-key (name id)
  94. :placeholder placeholder
  95. :aria-label placeholder
  96. :value (some-> @*input-value (first))
  97. :on-focus (fn [^js e]
  98. (js/setTimeout
  99. #(shui/popup-show! (.-target e)
  100. (let [select-handle! (fn [^js d]
  101. (let [gd (date/js-date->goog-date d)
  102. journal-date (date/js-date->journal-title gd)]
  103. (reset! *input-value [journal-date d])
  104. (swap! *between-dates assoc id journal-date))
  105. (some-> on-select (apply []))
  106. (shui/popup-hide!))]
  107. (ui/single-calendar
  108. {:initial-focus true
  109. :selected (some-> @*input-value (second))
  110. :on-select select-handle!}))
  111. {:id :query-datepicker
  112. :content-props {:class "p-0"}
  113. :align :start}) 16))}]]))
  114. (rum/defcs between <
  115. (rum/local nil ::start)
  116. (rum/local nil ::end)
  117. [state {:keys [tree loc] :as opts}]
  118. [:div.between-date.p-4 {:on-pointer-down (fn [e] (util/stop-propagation e))}
  119. [:div.flex.flex-row
  120. [:div.font-medium.mt-2 "Between: "]
  121. (datepicker :start "Start date"
  122. (merge opts {:auto-focus true
  123. :on-select (fn []
  124. (when-let [^js end-input (js/document.querySelector ".query-builder-datepicker[data-key=end]")]
  125. (when (string/blank? (.-value end-input))
  126. (.focus end-input))))}))
  127. (datepicker :end "End date" opts)]
  128. [:p.pt-2
  129. (ui/button "Submit"
  130. :on-click (fn []
  131. (let [{:keys [start end]} @*between-dates]
  132. (when (and start end)
  133. (let [clause [:between [:page-ref start] [:page-ref end]]]
  134. (append-tree! tree opts loc clause)
  135. (reset! *between-dates {}))))))]])
  136. (rum/defc property-select
  137. [*mode *property *private-property?]
  138. (let [[properties set-properties!] (rum/use-state nil)
  139. properties (cond->> properties
  140. (not @*private-property?)
  141. (remove ldb/built-in?))]
  142. (hooks/use-effect!
  143. (fn []
  144. (p/let [properties (db-async/<get-all-properties {:remove-built-in-property? false
  145. :remove-non-queryable-built-in-property? true})]
  146. (set-properties! properties)))
  147. [])
  148. [:div.flex.flex-col.gap-1
  149. [:div.flex.flex-row.justify-between.gap-1.items-center.px-1.pb-1.border-b
  150. [:label.opacity-50.cursor.select-none.text-sm
  151. {:for "built-in"}
  152. "Show built-in properties"]
  153. (shui/checkbox
  154. {:id "built-in"
  155. :value @*private-property?
  156. :on-checked-change #(reset! *private-property? (not @*private-property?))})]
  157. (select (map #(hash-map :db/ident (:db/ident %)
  158. :value (:block/title %))
  159. properties)
  160. (fn [{value :value db-ident :db/ident}]
  161. (reset! *mode "property-value")
  162. (reset! *property (if (config/db-based-graph? (state/get-current-repo))
  163. db-ident
  164. (keyword value)))))]))
  165. (rum/defc property-value-select-inner
  166. < rum/reactive db-mixins/query
  167. [repo *property *private-property? *find *tree opts loc values {:keys [db-graph? ref-property? property-type]}]
  168. (let [;; FIXME: lazy load property values consistently on first call
  169. ;; Guard against non ref properties like :logseq.property/icon
  170. _ (when (and db-graph? ref-property?)
  171. (doseq [id values] (db/sub-block id)))
  172. values' (if db-graph?
  173. (if ref-property?
  174. (map #(db-property/property-value-content (db/entity repo %)) values)
  175. (if (contains? #{:checkbox :keyword :raw-number :string} property-type)
  176. values
  177. ;; Don't display non-ref property values as they don't have display and query support
  178. []))
  179. values)
  180. values'' (map #(hash-map :value (str %)
  181. ;; Preserve original-value as some values like boolean do not display in select
  182. :original-value %)
  183. (cons "Select all" values'))]
  184. (select values''
  185. (fn [{:keys [original-value]}]
  186. (let [k (cond
  187. db-graph? (if @*private-property? :private-property :property)
  188. (= (rum/react *find) :page) :page-property
  189. :else :property)
  190. x (if (= original-value "Select all")
  191. [k @*property]
  192. [k @*property original-value])]
  193. (reset! *property nil)
  194. (append-tree! *tree opts loc x))))))
  195. (rum/defc property-value-select
  196. [repo *property *private-property? *find *tree opts loc]
  197. (let [db-graph? (sqlite-util/db-based-graph? repo)
  198. property-type (when db-graph? (:logseq.property/type (db/entity repo @*property)))
  199. ref-property? (and db-graph? (contains? db-property-type/all-ref-property-types property-type))
  200. [values set-values!] (rum/use-state nil)]
  201. (hooks/use-effect!
  202. (fn []
  203. (p/let [result (if db-graph?
  204. (db-async/<get-block-property-values repo @*property)
  205. (db-async/<file-get-property-values repo @*property))]
  206. (when (and db-graph? ref-property?)
  207. (doseq [db-id result]
  208. (db-async/<get-block repo db-id :children? false)))
  209. (set-values! result)))
  210. [@*property])
  211. (property-value-select-inner repo *property *private-property? *find *tree opts loc values
  212. {:db-graph? db-graph?
  213. :ref-property? ref-property?
  214. :property-type property-type})))
  215. (rum/defc tags
  216. [repo *tree opts loc]
  217. (let [[values set-values!] (rum/use-state nil)
  218. db-based? (config/db-based-graph? repo)]
  219. (hooks/use-effect!
  220. (fn []
  221. (let [result (db-model/get-all-readable-classes repo {:except-root-class? true})]
  222. (set-values! result)))
  223. [])
  224. (let [items (->> values
  225. (map :block/title)
  226. sort)]
  227. (select items
  228. (fn [{:keys [value]}]
  229. (append-tree! *tree opts loc [(if db-based? :tags :page-tags) value]))))))
  230. (defn- db-based-query-filter-picker
  231. [state *find *tree loc clause opts]
  232. (let [*mode (::mode state)
  233. *property (::property state)
  234. *private-property? (::private-property? state)
  235. repo (state/get-current-repo)]
  236. [:div
  237. (case @*mode
  238. "property"
  239. (property-select *mode *property *private-property?)
  240. "property-value"
  241. (property-value-select repo *property *private-property? *find *tree opts loc)
  242. "sample"
  243. (select (range 1 101)
  244. (fn [{:keys [value]}]
  245. (append-tree! *tree opts loc [:sample (util/safe-parse-int value)])))
  246. "tags"
  247. (tags repo *tree opts loc)
  248. "task"
  249. (let [items (let [values (:property/closed-values (db/entity :logseq.task/status))]
  250. (mapv db-property/property-value-content values))]
  251. (select items
  252. (constantly nil)
  253. {:multiple-choices? true
  254. ;; Need the existing choices later to improve the UX
  255. :selected-choices #{}
  256. :extract-chosen-fn :value
  257. :prompt-key :select/default-select-multiple
  258. :close-modal? false
  259. :on-apply (fn [choices]
  260. (when (seq choices)
  261. (append-tree! *tree opts loc (vec (cons :task choices)))))}))
  262. "priority"
  263. (select (if (config/db-based-graph? repo)
  264. (let [values (:property/closed-values (db/entity :logseq.task/priority))]
  265. (mapv db-property/property-value-content values))
  266. gp-db/built-in-priorities)
  267. (constantly nil)
  268. {:multiple-choices? true
  269. :selected-choices #{}
  270. :extract-chosen-fn :value
  271. :prompt-key :select/default-select-multiple
  272. :close-modal? false
  273. :on-apply (fn [choices]
  274. (when (seq choices)
  275. (append-tree! *tree opts loc (vec (cons :priority choices)))))})
  276. "page"
  277. (let [pages (sort (db-model/get-all-page-titles repo))]
  278. (select pages
  279. (fn [{:keys [value]}]
  280. (append-tree! *tree opts loc [:page value]))))
  281. ;; TODO: replace with node reference
  282. "page reference"
  283. (let [pages (sort (db-model/get-all-page-titles repo))]
  284. (select pages
  285. (fn [{:keys [value]}]
  286. (append-tree! *tree opts loc [:page-ref value]))
  287. {}))
  288. "full text search"
  289. (search (fn [v] (append-tree! *tree opts loc v))
  290. (:toggle-fn opts))
  291. "between"
  292. (between (merge opts
  293. {:tree *tree
  294. :loc loc
  295. :clause clause}))
  296. nil)]))
  297. (defn- file-based-query-filter-picker
  298. [state *find *tree loc clause opts]
  299. (let [*mode (::mode state)
  300. *property (::property state)
  301. *private-property? (::private-property? state)
  302. repo (state/get-current-repo)]
  303. [:div
  304. (case @*mode
  305. "namespace"
  306. (let [items (sort (map :block/title (db-model/get-all-namespace-parents repo)))]
  307. (select items
  308. (fn [{:keys [value]}]
  309. (append-tree! *tree opts loc [:namespace value]))))
  310. "tags"
  311. (tags repo *tree opts loc)
  312. "property"
  313. (property-select *mode *property *private-property?)
  314. "property-value"
  315. (property-value-select repo *property *private-property? *find *tree opts loc)
  316. "sample"
  317. (select (range 1 101)
  318. (fn [{:keys [value]}]
  319. (append-tree! *tree opts loc [:sample (util/safe-parse-int value)])))
  320. "task"
  321. (select (if (config/db-based-graph? repo)
  322. (let [values (:property/closed-values (db/entity :logseq.task/status))]
  323. (mapv db-property/property-value-content values))
  324. gp-db/built-in-markers)
  325. (constantly nil)
  326. {:multiple-choices? true
  327. ;; Need the existing choices later to improve the UX
  328. :selected-choices #{}
  329. :extract-chosen-fn :value
  330. :prompt-key :select/default-select-multiple
  331. :close-modal? false
  332. :on-apply (fn [choices]
  333. (when (seq choices)
  334. (append-tree! *tree opts loc (vec (cons :task choices)))))})
  335. "priority"
  336. (select (if (config/db-based-graph? repo)
  337. (let [values (:property/closed-values (db/entity :logseq.task/priority))]
  338. (mapv db-property/property-value-content values))
  339. gp-db/built-in-priorities)
  340. (constantly nil)
  341. {:multiple-choices? true
  342. :selected-choices #{}
  343. :extract-chosen-fn :value
  344. :prompt-key :select/default-select-multiple
  345. :close-modal? false
  346. :on-apply (fn [choices]
  347. (when (seq choices)
  348. (append-tree! *tree opts loc (vec (cons :priority choices)))))})
  349. "page"
  350. (let [pages (sort (db-model/get-all-page-titles repo))]
  351. (select pages
  352. (fn [{:keys [value]}]
  353. (append-tree! *tree opts loc [:page value]))))
  354. "page reference"
  355. (let [pages (sort (db-model/get-all-page-titles repo))]
  356. (select pages
  357. (fn [{:keys [value]}]
  358. (append-tree! *tree opts loc [:page-ref value]))
  359. {}))
  360. "full text search"
  361. (search (fn [v] (append-tree! *tree opts loc v))
  362. (:toggle-fn opts))
  363. "between"
  364. (between (merge opts
  365. {:tree *tree
  366. :loc loc
  367. :clause clause}))
  368. nil)]))
  369. (rum/defcs picker < rum/reactive
  370. {:will-mount (fn [state]
  371. (state/clear-selection!)
  372. state)}
  373. (rum/local nil ::mode) ; pick mode
  374. (rum/local nil ::property)
  375. (rum/local false ::private-property?)
  376. [state *find *tree loc clause opts]
  377. (let [*mode (::mode state)
  378. db-based? (config/db-based-graph? (state/get-current-repo))
  379. filters (if db-based?
  380. query-builder/db-based-block-filters
  381. (if (= :page (rum/react *find))
  382. query-builder/page-filters
  383. query-builder/block-filters))
  384. filters-and-ops (concat filters query-builder/operators)
  385. operator? #(contains? query-builder/operators-set (keyword %))]
  386. [:div.query-builder-picker
  387. (if @*mode
  388. (when-not (operator? @*mode)
  389. (if db-based?
  390. (db-based-query-filter-picker state *find *tree loc clause opts)
  391. (file-based-query-filter-picker state *find *tree loc clause opts)))
  392. [:div
  393. (when-not db-based?
  394. [:<>
  395. (when-not @*find
  396. [:div.flex.flex-row.items-center.p-2.justify-between
  397. [:div.ml-2 "Find: "]
  398. (page-block-selector *find)])
  399. (when-not @*find
  400. [:hr.m-0])])
  401. (select
  402. (map name filters-and-ops)
  403. (fn [{:keys [value]}]
  404. (cond
  405. (= value "all page tags")
  406. (append-tree! *tree opts loc [:all-page-tags])
  407. (operator? value)
  408. (append-tree! *tree opts loc [(keyword value)])
  409. :else
  410. (reset! *mode value)))
  411. {:input-default-placeholder "Add filter/operator"})])]))
  412. (rum/defc add-filter
  413. [*find *tree loc clause]
  414. (shui/button
  415. {:class "jtrigger !px-1 h-6 add-filter text-muted-foreground"
  416. :size :sm
  417. :variant :outline
  418. :on-pointer-down util/stop-propagation
  419. :on-click (fn [^js e]
  420. (shui/popup-show! (.-target e)
  421. (fn [{:keys [id]}]
  422. (picker *find *tree loc clause {:toggle-fn #(shui/popup-hide! id)}))
  423. {:align :start}))}
  424. (ui/icon "plus" {:size 14})
  425. (when (= [0] loc) "Filter")))
  426. (declare clauses-group)
  427. (defn- dsl-human-output
  428. [clause]
  429. (let [f (first clause)]
  430. (cond
  431. (string/starts-with? (str f) "?") ; variable
  432. (str clause)
  433. (string? clause)
  434. (str "Search: " clause)
  435. (= (keyword f) :page-ref)
  436. (page-ref/->page-ref (second clause))
  437. (contains? #{:tags :page-tags} (keyword f))
  438. (cond
  439. (string? (second clause))
  440. (str "#" (second clause))
  441. (symbol? (second clause))
  442. (str "#" (str (second clause)))
  443. :else
  444. (str "#" (second (second clause))))
  445. (contains? #{:property :private-property :page-property} (keyword f))
  446. (str (if (and (config/db-based-graph? (state/get-current-repo))
  447. (qualified-keyword? (second clause)))
  448. (:block/title (db/entity (second clause)))
  449. (some-> (second clause) name))
  450. ": "
  451. (cond
  452. (and (vector? (last clause)) (= :page-ref (first (last clause))))
  453. (second (last clause))
  454. (= 2 (count clause))
  455. "ALL"
  456. :else
  457. (last clause)))
  458. ;; between timestamp start (optional end)
  459. (and (= (keyword f) :between) (query-dsl/get-timestamp-property clause))
  460. (let [k (query-dsl/get-timestamp-property clause)
  461. [_ _property start end] clause
  462. start (if (or (keyword? start)
  463. (symbol? start))
  464. (name start)
  465. (second start))
  466. end (if (or (keyword? end)
  467. (symbol? end))
  468. (name end)
  469. (second end))]
  470. (str (if (= k :block/created-at)
  471. "Created"
  472. "Updated")
  473. " " start
  474. (when end
  475. (str " ~ " end))))
  476. ;; between journal start end
  477. (= (keyword f) :between)
  478. (let [start (if (or (keyword? (second clause))
  479. (symbol? (second clause)))
  480. (name (second clause))
  481. (second (second clause)))
  482. end (if (or (keyword? (last clause))
  483. (symbol? (last clause)))
  484. (name (last clause))
  485. (second (last clause)))]
  486. (str "between: " start " ~ " end))
  487. (contains? #{:task :priority} (keyword f))
  488. (str (name f) ": "
  489. (string/join " | " (rest clause)))
  490. (contains? #{:page :task :namespace} (keyword f))
  491. (str (name f) ": " (if (vector? (second clause))
  492. (second (second clause))
  493. (second clause)))
  494. (= 2 (count clause))
  495. (str (name f) ": " (second clause))
  496. :else
  497. (str (query-builder/->dsl clause)))))
  498. (rum/defc clause-inner
  499. [*tree loc clause & {:keys [operator?]}]
  500. (ui/dropdown
  501. (fn [{:keys [toggle-fn]}]
  502. (if operator?
  503. [:a.flex.text-sm.query-clause {:on-click toggle-fn}
  504. clause]
  505. [:div.flex.flex-row.items-center.gap-2.px-1.rounded.border.query-clause-btn
  506. [:a.flex.query-clause {:on-click toggle-fn}
  507. (dsl-human-output clause)]]))
  508. (fn [{:keys [toggle-fn]}]
  509. [:div.p-4.flex.flex-col.gap-2
  510. [:a {:title "Delete"
  511. :on-click (fn []
  512. (swap! *tree (fn [q]
  513. (let [loc' (if operator? (vec (butlast loc)) loc)]
  514. (query-builder/remove-element q loc'))))
  515. (toggle-fn))}
  516. "Delete"]
  517. (when operator?
  518. [:a {:title "Unwrap this operator"
  519. :on-click (fn []
  520. (swap! *tree (fn [q]
  521. (let [loc' (vec (butlast loc))]
  522. (query-builder/unwrap-operator q loc'))))
  523. (toggle-fn))}
  524. "Unwrap"])
  525. [:div.font-medium.text-sm "Wrap this filter with: "]
  526. [:div.flex.flex-row.gap-2
  527. (for [op query-builder/operators]
  528. (ui/button (string/upper-case (name op))
  529. :intent "logseq"
  530. :small? true
  531. :on-click (fn []
  532. (swap! *tree (fn [q]
  533. (let [loc' (if operator? (vec (butlast loc)) loc)]
  534. (query-builder/wrap-operator q loc' op))))
  535. (toggle-fn))))]
  536. (when operator?
  537. [:div
  538. [:div.font-medium.text-sm "Replace with: "]
  539. [:div.flex.flex-row.gap-2
  540. (for [op (remove #{(keyword (string/lower-case clause))} query-builder/operators)]
  541. (ui/button (string/upper-case (name op))
  542. :intent "logseq"
  543. :small? true
  544. :on-click (fn []
  545. (swap! *tree (fn [q]
  546. (query-builder/replace-element q loc op)))
  547. (toggle-fn))))]])])
  548. {:modal-class (util/hiccup->class
  549. "origin-top-right.absolute.left-0.mt-2.ml-2.rounded-md.shadow-lg.w-64")}))
  550. (rum/defc clause
  551. [*tree *find loc clauses]
  552. (when (seq clauses)
  553. [:div.query-builder-clause
  554. (let [operator (first clauses)
  555. kind (keyword operator)]
  556. (if (query-builder/operators-set kind)
  557. [:div.operator-clause.flex.flex-row.items-center {:data-level (count loc)}
  558. [:div.clause-bracket "("]
  559. (clauses-group *tree *find (conj loc 0) kind (rest clauses))
  560. [:div.clause-bracket ")"]]
  561. (clause-inner *tree loc clauses)))]))
  562. (rum/defc clauses-group
  563. [*tree *find loc kind clauses]
  564. (let [parens? (and (= loc [0]) (or (not= kind :and) (> (count clauses) 1)))]
  565. [:div.clauses-group
  566. (when parens? [:div.clause-bracket "("])
  567. (when-not (and (= loc [0])
  568. (= kind :and)
  569. (<= (count clauses) 1))
  570. (clause-inner *tree loc
  571. (string/upper-case (name kind))
  572. :operator? true))
  573. (map-indexed (fn [i item]
  574. (clause *tree *find (update loc (dec (count loc)) #(+ % i 1)) item))
  575. clauses)
  576. (when parens? [:div.clause-bracket ")"])
  577. (when (not= loc [0])
  578. (add-filter *find *tree loc []))]))
  579. (rum/defc clause-tree < rum/reactive
  580. [*tree *find]
  581. (let [tree (rum/react *tree)
  582. kind ((set query-builder/operators) (first tree))
  583. [kind' clauses] (if kind
  584. [kind (rest tree)]
  585. [:and [@tree]])]
  586. (clauses-group *tree *find [0] kind' clauses)))
  587. (defn sanitize-q
  588. [q-str]
  589. (if (string/blank? q-str)
  590. ""
  591. (if (or (common-util/wrapped-by-parens? q-str)
  592. (common-util/wrapped-by-quotes? q-str)
  593. (page-ref/page-ref? q-str)
  594. (string/starts-with? q-str "[?"))
  595. q-str
  596. (str "\"" q-str "\""))))
  597. (defn- get-q
  598. [block]
  599. (sanitize-q (or (:file-version/query-macro-title block)
  600. (:block/title block)
  601. "")))
  602. (rum/defcs builder <
  603. (rum/local nil ::find)
  604. {:init (fn [state]
  605. (let [block (first (:rum/args state))
  606. q-str (get-q block)
  607. query (common-util/safe-read-string
  608. query-dsl/custom-readers
  609. (query-dsl/pre-transform-query q-str))
  610. query' (cond
  611. (contains? #{'and 'or 'not} (first query))
  612. query
  613. query
  614. [:and query]
  615. :else
  616. [:and])
  617. tree (query-builder/from-dsl query')
  618. *tree (atom tree)]
  619. (add-watch *tree :updated (fn [_ _ _old _new]
  620. (when block
  621. (let [q (if (= [:and] @*tree)
  622. ""
  623. (let [result (query-builder/->dsl @*tree)]
  624. (if (string? result)
  625. (util/format "\"%s\"" result)
  626. (str result))))
  627. repo (state/get-current-repo)
  628. block (db/entity [:block/uuid (:block/uuid block)])]
  629. (if (config/db-based-graph? (state/get-current-repo))
  630. (editor-handler/save-block! repo (:block/uuid block) q)
  631. (let [content (string/replace (:block/title block)
  632. #"\{\{query[^}]+\}\}"
  633. (util/format "{{query %s}}" q))]
  634. (editor-handler/save-block! repo (:block/uuid block) content)))))))
  635. (assoc state ::tree *tree)))
  636. :will-mount (fn [state]
  637. (let [q-str (get-q (first (:rum/args state)))
  638. blocks-query? (:blocks? (query-dsl/parse-query q-str))
  639. find-mode (cond
  640. blocks-query?
  641. :block
  642. (false? blocks-query?)
  643. :page
  644. :else
  645. nil)]
  646. (when find-mode (reset! (::find state) find-mode))
  647. state))}
  648. [state _block _option]
  649. (let [*find (::find state)
  650. *tree (::tree state)]
  651. [:div.cp__query-builder
  652. [:div.cp__query-builder-filter
  653. (when (and (seq @*tree)
  654. (not= @*tree [:and]))
  655. (clause-tree *tree *find))
  656. (add-filter *find *tree [0] [])]]))