value.cljs 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853
  1. (ns frontend.components.property.value
  2. (:require [clojure.string :as string]
  3. [frontend.components.select :as select]
  4. [frontend.components.icon :as icon-component]
  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.model :as model]
  10. [frontend.handler.editor :as editor-handler]
  11. [frontend.handler.page :as page-handler]
  12. [frontend.handler.property :as property-handler]
  13. [frontend.handler.db-based.property :as db-property-handler]
  14. [frontend.state :as state]
  15. [frontend.ui :as ui]
  16. [logseq.shui.ui :as shui]
  17. [frontend.util :as util]
  18. [lambdaisland.glogi :as log]
  19. [rum.core :as rum]
  20. [promesa.core :as p]
  21. [frontend.db.async :as db-async]
  22. [logseq.common.util.macro :as macro-util]
  23. [logseq.db :as ldb]
  24. [logseq.db.frontend.property :as db-property]
  25. [datascript.impl.entity :as de]
  26. [frontend.handler.property.util :as pu]
  27. [logseq.db.frontend.property.type :as db-property-type]
  28. [dommy.core :as d]))
  29. (rum/defc property-empty-btn-value
  30. [& {:as opts}]
  31. (shui/button (merge {:class "empty-btn" :variant :text} opts) "Empty"))
  32. (rum/defc property-empty-text-value
  33. [& {:as opts}]
  34. [:span.inline-flex.items-center.cursor-pointer
  35. (merge {:class "empty-text-btn" :variant :text} opts) "Empty"])
  36. (rum/defc icon-row < rum/reactive
  37. [block]
  38. (let [icon-value (:logseq.property/icon block)]
  39. [:div.col-span-3.flex.flex-row.items-center.gap-2
  40. (icon-component/icon-picker icon-value
  41. {:disabled? config/publishing?
  42. :on-chosen (fn [_e icon]
  43. (db-property-handler/set-block-property!
  44. (:db/id block)
  45. :logseq.property/icon
  46. (select-keys icon [:type :id])))})
  47. (when (and icon-value (not config/publishing?))
  48. [:a.fade-link.flex {:on-click (fn [_e]
  49. (db-property-handler/remove-block-property!
  50. (:db/id block)
  51. :logseq.property/icon))
  52. :title "Delete this icon"}
  53. (ui/icon "X")])]))
  54. (defn- select-type?
  55. [property type]
  56. (or (contains? #{:page :object :number :url :date} type)
  57. ;; closed values
  58. (seq (:property/closed-values property))))
  59. (defn <create-new-block!
  60. [block property value & {:keys [edit-block?]
  61. :or {edit-block? true}}]
  62. (p/let [existing-value (get block (:db/ident property))
  63. new-block-id (when-not existing-value (db/new-block-id))
  64. _ (when-not existing-value
  65. (db-property-handler/create-property-text-block!
  66. (:db/id block)
  67. (:db/id property)
  68. value
  69. {:new-block-id new-block-id}))]
  70. (p/do!
  71. (let [block (or existing-value (db/entity [:block/uuid new-block-id]))]
  72. (when edit-block?
  73. (editor-handler/edit-block! block :max {:container-id :unknown-container})))
  74. (shui/dialog-close!))))
  75. (defn <add-property!
  76. "If a class and in a class schema context, add the property to its schema.
  77. Otherwise, add a block's property and its value"
  78. ([block property-key property-value] (<add-property! block property-key property-value {}))
  79. ([block property-id property-value' {:keys [exit-edit? class-schema?]
  80. :or {exit-edit? true}}]
  81. (let [repo (state/get-current-repo)
  82. class? (contains? (:block/type block) "class")]
  83. (assert (qualified-keyword? property-id) "property to add must be a keyword")
  84. (p/do!
  85. (if (and class? class-schema?)
  86. (db-property-handler/class-add-property! (:db/id block) property-id)
  87. (p/let [property (db/entity property-id)]
  88. (if (and (db-property-type/ref-property-types (get-in property [:block/schema :type]))
  89. (not (int? property-value')))
  90. (<create-new-block! block (db/entity property-id) property-value' {:edit-block? false})
  91. (property-handler/set-block-property! repo (:block/uuid block) property-id property-value'))))
  92. (when exit-edit?
  93. (shui/popup-hide!)
  94. (shui/dialog-close!))))))
  95. (defn- add-or-remove-property-value
  96. [block property value selected?]
  97. (let [many? (db-property/many? property)]
  98. (if selected?
  99. (<add-property! block (:db/ident property) value {:exit-edit? (not many?)})
  100. (p/do!
  101. (db-property-handler/delete-property-value! (:db/id block) (:db/ident property) value)
  102. (when (or (not many?)
  103. ;; values will be cleared
  104. (and many? (<= (count (get block (:db/ident property))) 1)))
  105. (shui/popup-hide!))))))
  106. (rum/defc calendar-inner <
  107. {:will-unmount (fn [state]
  108. (shui/dialog-close!)
  109. state)}
  110. [id on-change value]
  111. (let [initial-day (some-> value (.getTime) (js/Date.))
  112. initial-month (when value
  113. (js/Date. (.getFullYear value) (.getMonth value)))
  114. select-handler!
  115. (fn [^js d]
  116. ;; force local to UTC
  117. (when d
  118. (let [gd (goog.date.Date. (.getFullYear d) (.getMonth d) (.getDate d))]
  119. (let [journal (date/js-date->journal-title gd)]
  120. (p/do!
  121. (shui/popup-hide! id)
  122. (when-not (db/get-case-page journal)
  123. (page-handler/<create! journal {:redirect? false
  124. :create-first-block? false}))
  125. (when (fn? on-change)
  126. (on-change (db/get-case-page journal)))
  127. (shui/dialog-close!))))))]
  128. (shui/calendar
  129. (cond->
  130. {:mode "single"
  131. :initial-focus true
  132. :selected initial-day
  133. :class-names {:months ""}
  134. :on-day-key-down (fn [^js d _ ^js e]
  135. (when (= "Enter" (.-key e))
  136. (select-handler! d)))
  137. :on-select select-handler!}
  138. initial-month
  139. (assoc :default-month initial-month)))))
  140. (rum/defc date-picker
  141. [value {:keys [on-change editing? multiple-values? other-position?]}]
  142. (let [*trigger-ref (rum/use-ref nil)
  143. page value
  144. title (when page (:block/original-name page))
  145. value' (when title
  146. (js/Date. (date/journal-title->long title)))
  147. content-fn (fn [{:keys [id]}] (calendar-inner id on-change value'))
  148. open-popup! (fn [e]
  149. (when-not (or (util/meta-key? e) (util/shift-key? e))
  150. (util/stop e)
  151. (when-not config/publishing?
  152. (shui/popup-show! (.-target e) content-fn
  153. {:align "start" :auto-focus? true}))))]
  154. (rum/use-effect!
  155. (fn []
  156. (when editing?
  157. (js/setTimeout
  158. #(some-> (rum/deref *trigger-ref)
  159. (.click)) 32)))
  160. [editing?])
  161. (if multiple-values?
  162. (shui/button
  163. {:class "jtrigger h-6 empty-btn"
  164. :ref *trigger-ref
  165. :variant :text
  166. :size :sm
  167. :on-click open-popup!}
  168. (ui/icon "calendar-plus" {:size 16}))
  169. (shui/trigger-as
  170. :div.flex.flex-1.flex-row.gap-1.items-center.flex-wrap
  171. {:tabIndex 0
  172. :class "jtrigger min-h-[24px]" ; FIXME: min-h-6 not works
  173. :ref *trigger-ref
  174. :on-click open-popup!}
  175. (if page
  176. (when-let [page-cp (state/get-component :block/page-cp)]
  177. (rum/with-key
  178. (page-cp {:disable-preview? true
  179. :hide-close-button? true
  180. :meta-click? other-position?} page)
  181. (:db/id page)))
  182. (when-not multiple-values?
  183. (property-empty-btn-value)))))))
  184. (rum/defc property-value-date-picker
  185. [block property value opts]
  186. (let [multiple-values? (db-property/many? property)]
  187. (date-picker value
  188. (merge opts
  189. {:multiple-values? multiple-values?
  190. :on-change (fn [page]
  191. (let [repo (state/get-current-repo)]
  192. (property-handler/set-block-property! repo (:block/uuid block)
  193. (:db/ident property)
  194. (:db/id page))))}))))
  195. (defn- <create-page-if-not-exists!
  196. [property classes page]
  197. (let [page* (string/trim page)
  198. ;; inline-class is only for input from :transform-fn
  199. [page inline-class] (if (and (seq classes) (not (contains? db-property/db-attribute-properties (:db/ident property))))
  200. (or (seq (map string/trim (rest (re-find #"(.*)#(.*)$" page*))))
  201. [page* nil])
  202. [page* nil])
  203. id (:db/id (ldb/get-case-page (db/get-db) page))]
  204. (if (nil? id)
  205. (let [inline-class-uuid
  206. (when inline-class
  207. (or (:block/uuid (ldb/get-case-page (db/get-db) inline-class))
  208. (do (log/error :msg "Given inline class does not exist" :inline-class inline-class)
  209. nil)))
  210. class? (= :block/tags (:db/ident property))]
  211. (p/let [page (page-handler/<create! page {:redirect? false
  212. :create-first-block? false
  213. :tags (if inline-class-uuid
  214. [inline-class-uuid]
  215. ;; Only 1st class b/c page normally has
  216. ;; one of and not all these classes
  217. (mapv :block/uuid (take 1 classes)))
  218. :class? class?})]
  219. (:db/id page)))
  220. id)))
  221. (defn- select-aux
  222. [block property {:keys [items selected-choices multiple-choices?] :as opts}]
  223. (let [selected-choices (->> selected-choices
  224. (remove nil?)
  225. (remove #(= :logseq.property/empty-placeholder %)))
  226. clear-value (str "No " (:block/original-name property))
  227. clear-value-label [:div.flex.flex-row.items-center.gap-2
  228. (ui/icon "x")
  229. [:div clear-value]]
  230. items' (->>
  231. (if (and (seq selected-choices) (not multiple-choices?))
  232. (concat items
  233. [{:value clear-value
  234. :label clear-value-label
  235. :clear? true}])
  236. items)
  237. (remove #(= :logseq.property/empty-placeholder (:value %))))
  238. k :on-chosen
  239. f (get opts k)
  240. f' (fn [chosen selected?]
  241. (if (or (and (not multiple-choices?) (= chosen clear-value))
  242. (and multiple-choices? (= chosen [clear-value])))
  243. (p/do!
  244. (property-handler/remove-block-property! (state/get-current-repo) (:block/uuid block)
  245. (:db/ident property))
  246. (shui/popup-hide!))
  247. (f chosen selected?)))]
  248. (select/select (assoc opts
  249. :selected-choices selected-choices
  250. :items items'
  251. k f'))
  252. ;(shui/multi-select-content
  253. ; (map #(let [{:keys [value label]} %]
  254. ; {:id value :value label}) items') nil opts)
  255. ))
  256. (defn- get-title
  257. [e]
  258. (or (:block/original-name e)
  259. (:block/content e)))
  260. (defn select-page
  261. [property
  262. {:keys [block multiple-choices? dropdown? input-opts] :as opts}]
  263. (let [repo (state/get-current-repo)
  264. object? (= :object (get-in property [:block/schema :type]))
  265. classes (:property/schema.classes property)
  266. tags? (= :block/tags (:db/ident property))
  267. alias? (= :block/alias (:db/ident property))
  268. tags-or-alias? (or tags? alias?)
  269. selected-choices (when block
  270. (if tags-or-alias?
  271. (->> (if tags?
  272. (:block/tags block)
  273. (:block/alias block))
  274. (map (fn [e] (:db/id e))))
  275. (when-let [v (get block (:db/ident property))]
  276. (if (every? de/entity? v)
  277. (map :db/id v)
  278. [(:db/id v)]))))
  279. objects-or-pages
  280. (->>
  281. (cond
  282. (seq classes)
  283. (mapcat
  284. (fn [class]
  285. (if (= :logseq.class/Root (:db/ident class))
  286. (->> (model/get-all-classes repo)
  287. (keep (fn [[_ id]]
  288. (let [e (db/entity [:block/uuid id])]
  289. (when-not (= :logseq.class/Root (:db/ident e))
  290. e)))))
  291. (->> (model/get-class-objects repo (:db/id class))
  292. (keep db/entity))))
  293. classes)
  294. :else
  295. (remove ldb/built-in? (model/get-all-pages repo))))
  296. options (map (fn [object] {:label (get-title object)
  297. :value (:db/id object)}) objects-or-pages)
  298. classes' (remove (fn [class] (= :logseq.class/Root (:db/ident class))) classes)
  299. opts' (cond->
  300. (merge
  301. opts
  302. {:multiple-choices? multiple-choices?
  303. :items options
  304. :selected-choices selected-choices
  305. :dropdown? dropdown?
  306. :input-default-placeholder (cond
  307. tags?
  308. "Set tags"
  309. alias?
  310. "Set alias"
  311. multiple-choices?
  312. (str "Choose " (if object? "objects" "pages"))
  313. :else
  314. (str "Choose " (if object? "object" "page")))
  315. :show-new-when-not-exact-match? true
  316. :extract-chosen-fn :value
  317. :extract-fn :label
  318. :input-opts input-opts
  319. :on-chosen (fn [chosen selected?]
  320. (p/let [id (if (integer? chosen) chosen
  321. (when-not (string/blank? (string/trim chosen))
  322. (<create-page-if-not-exists! property classes' chosen)))]
  323. (if id
  324. (add-or-remove-property-value block property id selected?)
  325. (log/error :msg "No :db/id found or created for chosen" :chosen chosen))))})
  326. (and (seq classes') (not tags-or-alias?))
  327. (assoc
  328. ;; Provides additional completion for inline classes on new pages or objects
  329. :transform-fn (fn [results input]
  330. (if-let [[_ new-page class-input] (and (empty? results) (re-find #"(.*)#(.*)$" input))]
  331. (let [repo (state/get-current-repo)
  332. descendent-classes (->> classes'
  333. (mapcat #(model/get-class-children repo (:db/id %)))
  334. (map #(db/entity repo %)))]
  335. (->> (concat classes' descendent-classes)
  336. (filter #(string/includes? (:block/original-name %) class-input))
  337. (mapv (fn [p]
  338. {:value (str new-page "#" (:block/original-name p))
  339. :label (str new-page "#" (:block/original-name p))}))))
  340. results))))]
  341. (select-aux block property opts')))
  342. (defn property-value-select-page
  343. [block property opts
  344. {:keys [*show-new-property-config?]}]
  345. (let [input-opts (fn [_]
  346. {:on-click (fn []
  347. (when *show-new-property-config?
  348. (reset! *show-new-property-config? false)))
  349. :on-key-down
  350. (fn [e]
  351. (case (util/ekey e)
  352. "Escape"
  353. (do
  354. (shui/dialog-close!)
  355. (when-let [f (:on-chosen opts)] (f)))
  356. nil))})
  357. opts' (assoc opts
  358. :block block
  359. :input-opts input-opts)]
  360. (select-page property opts')))
  361. (defn <create-new-block-from-template!
  362. "`template`: tag block"
  363. [block property template]
  364. (p/let [new-block-id (db/new-block-id)
  365. _ (db-property-handler/create-property-text-block!
  366. (:db/id block)
  367. (:db/id property)
  368. ""
  369. {:new-block-id new-block-id
  370. :template-id (:db/id template)})
  371. new-block (db/entity [:block/uuid new-block-id])]
  372. (shui/popup-hide!)
  373. new-block))
  374. (rum/defcs select < rum/reactive
  375. {:init (fn [state]
  376. (let [*values (atom :loading)]
  377. (p/let [result (db-async/<get-block-property-values (state/get-current-repo)
  378. (:db/ident (nth (:rum/args state) 1)))]
  379. (reset! *values result))
  380. (assoc state ::values *values)))}
  381. [state block property
  382. {:keys [multiple-choices? dropdown? content-props] :as select-opts}
  383. {:keys [*show-new-property-config?]}]
  384. (let [values (rum/react (::values state))]
  385. (when-not (= :loading values)
  386. (let [schema (:block/schema property)
  387. property (db/sub-block (:db/id property))
  388. type (:type schema)
  389. closed-values? (seq (:property/closed-values property))
  390. ref-type? (db-property-type/ref-property-types type)
  391. items (if closed-values?
  392. (keep (fn [block]
  393. (let [icon (pu/get-block-property-value block :logseq.property/icon)
  394. value (db-property/closed-value-name block)]
  395. {:label (if icon
  396. [:div.flex.flex-row.gap-2
  397. (icon-component/icon icon)
  398. value]
  399. value)
  400. :value (:db/id block)})) (:property/closed-values property))
  401. (->> values
  402. (mapcat (fn [value]
  403. (if (coll? value)
  404. (map (fn [v] {:value v}) value)
  405. [{:value value}])))
  406. (map (fn [{:keys [value]}]
  407. (if (and ref-type? (number? value))
  408. (when-let [e (db/entity value)]
  409. {:label (or (:block/content e)
  410. (:block/original-name e))
  411. :value value})
  412. {:label value
  413. :value value})))
  414. (distinct)))
  415. items (->> (if (= :date type)
  416. (map (fn [m] (let [label (:block/original-name (db/entity (:value m)))]
  417. (when label
  418. (assoc m :label label)))) items)
  419. items)
  420. (remove nil?))
  421. on-chosen (fn [chosen selected?]
  422. (let [value (if (map? chosen) (:value chosen) chosen)]
  423. (add-or-remove-property-value block property value selected?)))
  424. selected-choices' (get block (:db/ident property))
  425. selected-choices (if (every? de/entity? selected-choices')
  426. (map :db/id selected-choices')
  427. [selected-choices'])]
  428. (select-aux block property
  429. {:multiple-choices? multiple-choices?
  430. :items items
  431. :selected-choices selected-choices
  432. :dropdown? dropdown?
  433. :show-new-when-not-exact-match? (not (or closed-values? (= :date type)))
  434. :input-default-placeholder "Select"
  435. :extract-chosen-fn :value
  436. :extract-fn :label
  437. :content-props content-props
  438. :on-chosen on-chosen
  439. :input-opts (fn [_]
  440. {:on-blur (fn []
  441. (when-let [f (:on-chosen select-opts)] (f)))
  442. :on-click (fn []
  443. (when *show-new-property-config?
  444. (reset! *show-new-property-config? false)))
  445. :on-key-down
  446. (fn [e]
  447. (case (util/ekey e)
  448. "Escape"
  449. (do
  450. (shui/dialog-close!)
  451. (when-let [f (:on-chosen select-opts)] (f)))
  452. nil))})})))))
  453. (rum/defc property-normal-block-value
  454. [block property value-block block-cp editor-box opts]
  455. (let [multiple-values? (db-property/many? property)]
  456. (if value-block
  457. [:div.property-block-container.content
  458. (let [config {:id (str (if multiple-values?
  459. (:block/uuid block)
  460. (:block/uuid value-block)))
  461. :container-id (:container-id opts)
  462. :editor-box editor-box
  463. :property-block? true}]
  464. (block-cp config [value-block]))]
  465. (property-empty-btn-value))))
  466. (rum/defc property-template-value < rum/reactive
  467. {:init (fn [state]
  468. (when-let [block-id (second (:rum/args state))]
  469. (db-async/<get-block (state/get-current-repo) block-id :children? false))
  470. state)}
  471. [config value opts]
  472. (when value
  473. (if (state/sub-async-query-loading value)
  474. [:div.text-sm.opacity-70 "loading"]
  475. (when-let [entity (db/sub-block (:db/id (db/entity [:block/uuid value])))]
  476. (let [properties-cp (:properties-cp opts)]
  477. (when (and entity properties-cp)
  478. [:div.property-block-container.content.property-template
  479. (properties-cp config entity (:editor-id config) opts)]))))))
  480. (defn- create-template-block!
  481. [block property v-block *template-instance]
  482. (when-not @*template-instance
  483. (p/let [result (<create-new-block-from-template! block property v-block)]
  484. (reset! *template-instance result))))
  485. (rum/defcs property-block-value < rum/reactive
  486. (rum/local nil ::template-instance)
  487. {:init (fn [state]
  488. (let [block (first (:rum/args state))]
  489. (when-let [block-id (or (:db/id block) (:block/uuid block))]
  490. (db-async/<get-block (state/get-current-repo) block-id :children? true)))
  491. state)}
  492. [state value block property block-cp editor-box opts page-cp editor-id]
  493. (let [*template-instance (::template-instance state)
  494. template-instance @*template-instance]
  495. (when value
  496. (if (state/sub-async-query-loading value)
  497. [:div.text-sm.opacity-70 "loading"]
  498. (if-let [v-block (db/sub-block (:db/id value))]
  499. (let [class? (contains? (:block/type v-block) "class")
  500. invalid-warning [:div.warning.text-sm
  501. "Invalid block value, please delete the current property."]]
  502. (when v-block
  503. (cond
  504. (:block/page v-block)
  505. (property-normal-block-value block property v-block block-cp editor-box opts)
  506. (and class? (seq (:class/schema.properties v-block)))
  507. (if template-instance
  508. (property-template-value {:editor-id editor-id}
  509. (:block/uuid template-instance)
  510. opts)
  511. (create-template-block! block property v-block *template-instance))
  512. ;; page/class/etc.
  513. (:block/name v-block)
  514. (rum/with-key
  515. (page-cp {:disable-preview? true
  516. :hide-close-button? true
  517. :tag? class?} v-block)
  518. (:db/id v-block))
  519. :else
  520. invalid-warning)))
  521. (property-empty-btn-value))))))
  522. (rum/defc closed-value-item < rum/reactive
  523. [value {:keys [inline-text icon?]}]
  524. (when value
  525. (let [eid (if (de/entity? value) (:db/id value) [:block/uuid value])]
  526. (when-let [block (db/sub-block (:db/id (db/entity eid)))]
  527. (let [property-block? (db-property/property-created-block? block)
  528. value' (db-property/closed-value-name block)
  529. icon (pu/get-block-property-value block :logseq.property/icon)]
  530. (cond
  531. icon
  532. (if icon?
  533. (icon-component/icon icon)
  534. [:div.flex.flex-row.items-center.gap-2
  535. (icon-component/icon icon)
  536. (when value'
  537. [:span value'])])
  538. property-block?
  539. value'
  540. (= type :number)
  541. [:span.number (str value')]
  542. :else
  543. (inline-text {} :markdown (str value'))))))))
  544. (rum/defc select-item
  545. [property type value {:keys [page-cp inline-text other-position? _icon?] :as opts}]
  546. (let [closed-values? (seq (:property/closed-values property))
  547. tag? (or (:tag? opts) (= (:db/ident property) :block/tags))
  548. inline-text-cp (fn [content]
  549. [:div.flex.flex-row.items-center
  550. (inline-text {} :markdown (macro-util/expand-value-if-macro content (state/get-macros)))
  551. (when (and (= type :url) other-position?)
  552. (shui/button {:variant :ghost
  553. :size :sm
  554. :class "px-0 py-0 h-4"}
  555. (ui/icon "edit" {:size 14})))])]
  556. [:div.select-item
  557. (cond
  558. (= value :logseq.property/empty-placeholder)
  559. (property-empty-btn-value)
  560. (ldb/page? value)
  561. (when value
  562. (rum/with-key
  563. (page-cp {:disable-preview? true
  564. :tag? tag?
  565. :hide-close-button? true
  566. :meta-click? other-position?} value)
  567. (:db/id value)))
  568. (= type :object)
  569. (inline-text {} :markdown (:block/content value))
  570. closed-values?
  571. (closed-value-item value opts)
  572. (de/entity? value)
  573. (when-let [content (:block/content value)]
  574. (inline-text-cp content))
  575. :else
  576. (inline-text-cp (str value)))]))
  577. (rum/defc single-value-select
  578. [block property value value-f select-opts opts]
  579. (let [*el (rum/use-ref nil)]
  580. ;; Open popover initially when editing a property
  581. (rum/use-effect!
  582. (fn []
  583. (when (:editing? opts)
  584. (.click (rum/deref *el))))
  585. [(:editing? opts)])
  586. (let [schema (:block/schema property)
  587. type (get schema :type :default)
  588. select-opts' (assoc select-opts :multiple-choices? false)
  589. popup-content (fn content-fn [_]
  590. [:div.property-select
  591. (case type
  592. (:number :url :default)
  593. (select block property select-opts' opts)
  594. (:object :page :date)
  595. (property-value-select-page block property select-opts' opts))])
  596. trigger-id (str "trigger-" (:container-id opts) "-" (:db/id block) "-" (:db/id property))
  597. show! (fn [e]
  598. (let [target (.-target e)]
  599. (when-not (or config/publishing?
  600. (util/shift-key? e)
  601. (util/meta-key? e)
  602. (util/link? target)
  603. (when-let [node (.closest target "a")]
  604. (not (or (d/has-class? node "page-ref")
  605. (d/has-class? node "tag")))))
  606. (shui/popup-show! target popup-content
  607. {:align "start"
  608. :as-dropdown? true
  609. :auto-focus? true
  610. :trigger-id trigger-id}))))]
  611. (shui/trigger-as
  612. (if (:other-position? opts) :div :div.jtrigger.flex.flex-1.w-full)
  613. {:ref *el
  614. :id trigger-id
  615. :tabIndex 0
  616. :on-click show!}
  617. (if (string/blank? value)
  618. (property-empty-text-value)
  619. (value-f))))))
  620. (defn- property-editing
  621. [block property schema]
  622. [:div.flex.flex-1
  623. (case (:type schema)
  624. :template
  625. (when-let [template (first (:property/schema.classes property))]
  626. (<create-new-block-from-template! block property template))
  627. nil)])
  628. (defn- property-value-inner
  629. [block property value {:keys [inline-text block-cp page-cp
  630. editor-id dom-id row?
  631. editor-box]
  632. :as opts}]
  633. (let [schema (:block/schema property)
  634. multiple-values? (db-property/many? property)
  635. class (str (when-not row? "flex flex-1 ")
  636. (when multiple-values? "property-value-content"))
  637. type (:type schema)
  638. type (if (= :default type)
  639. (or
  640. (let [v-block (db/entity value)]
  641. (when (get v-block (pu/get-pid :logseq.property/created-from-template))
  642. :template))
  643. type)
  644. type)
  645. template? (= :template type)]
  646. [:div.cursor-text
  647. {:id (or dom-id (random-uuid))
  648. :tabIndex 0
  649. :class (str class " " (when-not (= type :default) "jtrigger"))
  650. :style {:min-height 24}
  651. :on-click (fn []
  652. (when (and (= type :default) (nil? value))
  653. (<create-new-block! block property "")))}
  654. (if (and (string/blank? value) template?)
  655. (when-let [template (first (:property/schema.classes schema))]
  656. [:a.fade-link.pointer.text-sm.jtrigger
  657. {:on-click (fn [e]
  658. (util/stop e)
  659. (<create-new-block-from-template! block property template))}
  660. (str "Use template #" (:block/original-name template))])
  661. (cond
  662. (= type :template)
  663. (property-template-value {:editor-id editor-id}
  664. value
  665. opts)
  666. (and (= type :default) (nil? (:block/content value)))
  667. [:div.jtrigger (property-empty-btn-value)]
  668. (= type :default)
  669. (property-block-value value block property block-cp editor-box opts page-cp editor-id)
  670. :else
  671. (inline-text {} :markdown (macro-util/expand-value-if-macro (str value) (state/get-macros)))))]))
  672. (rum/defcs property-scalar-value < rum/reactive db-mixins/query rum/static
  673. [state block property value {:keys [container-id editing? on-chosen]
  674. :as opts}]
  675. (let [property (model/sub-block (:db/id property))
  676. schema (:block/schema property)
  677. type (get schema :type :default)
  678. editing? (or editing?
  679. (state/sub-editing? [container-id (:block/uuid block) (:block/uuid property)]))
  680. select-type? (select-type? property type)
  681. closed-values? (seq (:property/closed-values property))
  682. select-opts {:on-chosen on-chosen}
  683. value (if (and (de/entity? value) (= (:db/ident value) :logseq.property/empty-placeholder))
  684. nil
  685. value)]
  686. (if (= :logseq.property/icon (:db/ident property))
  687. (icon-row block)
  688. (if (and select-type?
  689. (not (and (not closed-values?) (= type :date))))
  690. (single-value-select block property value
  691. (fn [] (select-item property type value opts))
  692. select-opts
  693. (assoc opts :editing? editing?))
  694. (case type
  695. :date
  696. (property-value-date-picker block property value (merge opts {:editing? editing?}))
  697. :checkbox
  698. (let [add-property! (fn []
  699. (<add-property! block (:db/ident property) (boolean (not value))))]
  700. [:label.flex.w-full.as-scalar-value-wrap.cursor-pointer
  701. (shui/checkbox {:class "jtrigger flex flex-row items-center"
  702. :disabled config/publishing?
  703. :checked value
  704. :on-checked-change add-property!
  705. :on-key-down (fn [e]
  706. (when (= (util/ekey e) "Enter")
  707. (add-property!)))})])
  708. ;; :others
  709. [:div.flex.flex-1
  710. (if editing?
  711. (property-editing block property schema)
  712. (property-value-inner block property value opts))])))))
  713. (rum/defc multiple-values < rum/static
  714. [block property v {:keys [on-chosen editing?] :as opts} schema]
  715. (let [type (get schema :type :default)
  716. date? (= type :date)
  717. *el (rum/use-ref nil)
  718. items (if (coll? v) v (when v [v]))]
  719. (rum/use-effect!
  720. (fn []
  721. (when editing?
  722. (.click (rum/deref *el))))
  723. [editing?])
  724. (let [values-cp (fn [toggle-fn]
  725. (let [not-empty-value? (not= (map :db/ident items) [:logseq.property/empty-placeholder])]
  726. (if (and (seq items) not-empty-value?)
  727. (concat
  728. (for [item items]
  729. (rum/with-key (select-item property type item opts) (or (:block/uuid item) (str item))))
  730. (when date?
  731. [(property-value-date-picker block property nil {:toggle-fn toggle-fn})]))
  732. (when-not editing?
  733. (property-empty-text-value)))))
  734. select-cp (fn [select-opts]
  735. (let [select-opts (merge {:multiple-choices? true
  736. :on-chosen (fn []
  737. (when on-chosen (on-chosen)))}
  738. select-opts
  739. {:dropdown? false})]
  740. [:div.property-select
  741. (if (contains? #{:page :object} type)
  742. (property-value-select-page block property
  743. select-opts
  744. opts)
  745. (select block property select-opts opts))]))]
  746. (let [toggle-fn shui/popup-hide!
  747. content-fn (fn [{:keys [_id content-props]}]
  748. (select-cp {:content-props content-props}))]
  749. [:div.multi-values.jtrigger
  750. {:tab-index "0"
  751. :ref *el
  752. :on-click (fn [^js e]
  753. (let [target (.-target e)]
  754. (when-not (or (util/link? target) (.closest target "a") config/publishing?)
  755. (shui/popup-show! (rum/deref *el) content-fn
  756. {:as-dropdown? true :as-content? false
  757. :align "start" :auto-focus? true}))))
  758. :on-key-down (fn [^js e]
  759. (case (.-key e)
  760. (" " "Enter")
  761. (do (some-> (rum/deref *el) (.click))
  762. (util/stop e))
  763. :dune))
  764. :class "flex flex-1 flex-row items-center flex-wrap gap-x-2 gap-y-2 pr-4"}
  765. (values-cp toggle-fn)]))))
  766. (rum/defcs property-value < rum/reactive
  767. [state block property v opts]
  768. (ui/catch-error
  769. (ui/block-error "Something wrong" {})
  770. (let [opts (merge opts
  771. {:page-cp (state/get-component :block/page-cp)
  772. :inline-text (state/get-component :block/inline-text)
  773. :editor-box (state/get-component :editor/box)
  774. :block-cp (state/get-component :block/blocks-container)
  775. :properties-cp (state/get-component :block/properties-cp)})
  776. dom-id (str "ls-property-" (:db/id block) "-" (:db/id property))
  777. editor-id (str dom-id "-editor")
  778. schema (:block/schema property)
  779. type (some-> schema (get :type :default))
  780. multiple-values? (db-property/many? property)
  781. empty-value? (= :logseq.property/empty-placeholder v)
  782. v (cond
  783. (and multiple-values? (or (set? v) (and (coll? v) (empty? v)) (nil? v)))
  784. v
  785. multiple-values?
  786. #{v}
  787. (set? v)
  788. (first v)
  789. :else
  790. v)]
  791. [:div.property-value-inner
  792. {:data-type type
  793. :class (str (when empty-value? "empty-value")
  794. (when-not (:other-position? opts) " w-full"))}
  795. (cond
  796. multiple-values?
  797. (multiple-values block property v opts schema)
  798. :else
  799. (property-scalar-value block property v
  800. (merge
  801. opts
  802. {:editor-id editor-id
  803. :dom-id dom-id})))])))