builder.cljs 28 KB

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