value.cljs 64 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309
  1. (ns frontend.components.property.value
  2. (:require [cljs-time.coerce :as tc]
  3. [cljs-time.core :as t]
  4. [cljs-time.local :as local]
  5. [clojure.set :as set]
  6. [clojure.string :as string]
  7. [datascript.impl.entity :as de]
  8. [dommy.core :as d]
  9. [frontend.components.icon :as icon-component]
  10. [frontend.components.select :as select]
  11. [frontend.config :as config]
  12. [frontend.date :as date]
  13. [frontend.db :as db]
  14. [frontend.db-mixins :as db-mixins]
  15. [frontend.db.async :as db-async]
  16. [frontend.db.model :as model]
  17. [frontend.handler.block :as block-handler]
  18. [frontend.handler.db-based.page :as db-page-handler]
  19. [frontend.handler.db-based.property :as db-property-handler]
  20. [frontend.handler.editor :as editor-handler]
  21. [frontend.handler.page :as page-handler]
  22. [frontend.handler.property :as property-handler]
  23. [frontend.handler.property.util :as pu]
  24. [frontend.handler.route :as route-handler]
  25. [frontend.hooks :as hooks]
  26. [frontend.modules.outliner.ui :as ui-outliner-tx]
  27. [frontend.search :as search]
  28. [frontend.state :as state]
  29. [frontend.ui :as ui]
  30. [frontend.util :as util]
  31. [goog.dom :as gdom]
  32. [goog.functions :refer [debounce]]
  33. [lambdaisland.glogi :as log]
  34. [logseq.common.util.macro :as macro-util]
  35. [logseq.db :as ldb]
  36. [logseq.db.frontend.entity-util :as entity-util]
  37. [logseq.db.frontend.property :as db-property]
  38. [logseq.db.frontend.property.type :as db-property-type]
  39. [logseq.outliner.property :as outliner-property]
  40. [logseq.shui.ui :as shui]
  41. [promesa.core :as p]
  42. [rum.core :as rum]))
  43. (rum/defc property-empty-btn-value
  44. [property & opts]
  45. (let [text (cond
  46. (= (:db/ident property) :logseq.property/description)
  47. "Add description"
  48. :else
  49. "Empty")]
  50. (if (= text "Empty")
  51. (shui/button (merge {:class "empty-btn" :variant :text} opts)
  52. text)
  53. (shui/button (merge {:class "empty-btn !text-base" :variant :text} opts)
  54. text))))
  55. (rum/defc property-empty-text-value
  56. [property {:keys [property-position]}]
  57. [:span.inline-flex.items-center.cursor-pointer
  58. (merge {:class "empty-text-btn" :variant :text})
  59. (if property-position
  60. (if-let [icon (:logseq.property/icon property)]
  61. (icon-component/icon icon {:color? true})
  62. (ui/icon "line-dashed"))
  63. "Empty")])
  64. (rum/defc icon-row
  65. [block editing?]
  66. (let [icon-value (:logseq.property/icon block)
  67. clear-overlay! (fn []
  68. (shui/dialog-close!)
  69. (shui/popup-hide-all!))
  70. on-chosen! (fn [_e icon]
  71. (if icon
  72. (db-property-handler/set-block-property!
  73. (:db/id block)
  74. :logseq.property/icon
  75. (select-keys icon [:type :id :color]))
  76. (db-property-handler/remove-block-property!
  77. (:db/id block)
  78. :logseq.property/icon))
  79. (clear-overlay!))]
  80. (hooks/use-effect!
  81. (fn []
  82. (when editing?
  83. (clear-overlay!)
  84. (let [^js container (or (some-> js/document.activeElement (.closest ".page"))
  85. (gdom/getElement "main-content-container"))
  86. icon (get block (pu/get-pid :logseq.property/icon))]
  87. (util/schedule
  88. (fn []
  89. (when-let [^js target (some-> (.querySelector container (str "#ls-block-" (str (:block/uuid block))))
  90. (.querySelector ".block-main-container"))]
  91. (shui/popup-show! target
  92. #(icon-component/icon-search
  93. {:on-chosen on-chosen!
  94. :icon-value icon
  95. :del-btn? (some? icon)})
  96. {:id :ls-icon-picker
  97. :align :start})))))))
  98. [editing?])
  99. [:div.col-span-3.flex.flex-row.items-center.gap-2
  100. (icon-component/icon-picker icon-value
  101. {:disabled? config/publishing?
  102. :del-btn? (some? icon-value)
  103. :on-chosen on-chosen!})]))
  104. (defn- select-type?
  105. [block property type]
  106. (or (contains? #{:node :number :date :page :class :property} type)
  107. ;; closed values
  108. (seq (:property/closed-values property))
  109. (and (= (:db/ident property) :logseq.property/default-value)
  110. (= (:logseq.property/type block) :number))))
  111. (defn <create-new-block!
  112. [block property value & {:keys [edit-block?]
  113. :or {edit-block? true}}]
  114. (when-not (or (:logseq.property/hide? property)
  115. (= (:db/ident property) :logseq.property/default-value))
  116. (ui/hide-popups-until-preview-popup!)
  117. (shui/dialog-close!))
  118. (p/let [block
  119. (if (and (contains? #{:default :url} (:logseq.property/type property))
  120. (not (db-property/many? property)))
  121. (p/let [existing-value (get block (:db/ident property))
  122. default-value (:logseq.property/default-value property)
  123. existing-value? (and (some? existing-value)
  124. (not= (:db/ident existing-value) :logseq.property/empty-placeholder)
  125. (not= (:db/id existing-value) (:db/id default-value)))
  126. new-block-id (when-not existing-value? (db/new-block-id))
  127. _ (when-not existing-value?
  128. (let [value' (if (and default-value (string? value) (string/blank? value))
  129. (db-property/property-value-content default-value)
  130. value)]
  131. (db-property-handler/create-property-text-block!
  132. (:db/id block)
  133. (:db/id property)
  134. value'
  135. {:new-block-id new-block-id})))]
  136. (if existing-value? existing-value (db/entity [:block/uuid new-block-id])))
  137. (p/let [new-block-id (db/new-block-id)
  138. _ (db-property-handler/create-property-text-block!
  139. (:db/id block)
  140. (:db/id property)
  141. value
  142. {:new-block-id new-block-id})]
  143. (db/entity [:block/uuid new-block-id])))]
  144. (when edit-block?
  145. (editor-handler/edit-block! block :max {:container-id :unknown-container}))
  146. block))
  147. (defn- get-operating-blocks
  148. [block]
  149. (let [selected-blocks (some->> (state/get-selection-block-ids)
  150. (map (fn [id] (db/entity [:block/uuid id])))
  151. (seq)
  152. block-handler/get-top-level-blocks
  153. (remove ldb/property?))]
  154. (or (seq selected-blocks) [block])))
  155. (defn <set-class-as-property!
  156. [repo property]
  157. (db/transact! repo
  158. [{:db/id (:db/id property)
  159. :db/ident (:db/ident property)
  160. :db/cardinality :db.cardinality/one
  161. :db/valueType :db.type/ref
  162. :db/index true
  163. :block/tags :logseq.class/Property
  164. :logseq.property/type :node
  165. :logseq.property/classes (:db/id property)}]
  166. {:outliner-op :save-block}))
  167. (defn <add-property!
  168. "If a class and in a class schema context, add the property to its schema.
  169. Otherwise, add a block's property and its value"
  170. ([block property-key property-value] (<add-property! block property-key property-value {}))
  171. ([block property-id property-value {:keys [selected? exit-edit? class-schema?]
  172. :or {exit-edit? true}}]
  173. (let [repo (state/get-current-repo)
  174. class? (ldb/class? block)
  175. property (db/entity property-id)
  176. many? (db-property/many? property)
  177. checkbox? (= :checkbox (:logseq.property/type property))
  178. blocks (get-operating-blocks block)]
  179. (assert (qualified-keyword? property-id) "property to add must be a keyword")
  180. (p/do!
  181. (if (and class? class-schema?)
  182. (p/do!
  183. (when (ldb/class? property)
  184. (<set-class-as-property! repo property))
  185. (db-property-handler/class-add-property! (:db/id block) property-id))
  186. (let [block-ids (map :block/uuid blocks)]
  187. (if (and (db-property-type/all-ref-property-types (:logseq.property/type property))
  188. (string? property-value))
  189. (p/let [new-block (<create-new-block! block (db/entity property-id) property-value {:edit-block? false})]
  190. (when (seq (remove #{(:db/id block)} (map :db/id block)))
  191. (property-handler/batch-set-block-property! repo block-ids property-id (:db/id new-block)))
  192. new-block)
  193. (property-handler/batch-set-block-property! repo block-ids property-id property-value))))
  194. (cond
  195. exit-edit?
  196. (do
  197. (ui/hide-popups-until-preview-popup!)
  198. (shui/dialog-close!))
  199. selected?
  200. (shui/popup-hide!))
  201. (when-not (or many? checkbox?)
  202. (when-let [input (state/get-input)]
  203. (.focus input)))
  204. (when checkbox?
  205. (state/set-editor-action-data! {:type :focus-property-value
  206. :property property}))))))
  207. (defn- add-or-remove-property-value
  208. [block property value selected? {:keys [refresh-result-f] :as opts}]
  209. (let [many? (db-property/many? property)
  210. blocks (get-operating-blocks block)]
  211. (p/do!
  212. (if selected?
  213. (<add-property! block (:db/ident property) value
  214. {:selected? selected?
  215. :exit-edit? (if (some? (:exit-edit? opts)) (:exit-edit? opts) (not many?))})
  216. (p/do!
  217. (ui-outliner-tx/transact!
  218. {:outliner-op :save-block}
  219. (doseq [block blocks]
  220. (db-property-handler/delete-property-value! (:db/id block) (:db/ident property) value)))
  221. (when (or (not many?)
  222. ;; values will be cleared
  223. (and many? (<= (count (get block (:db/ident property))) 1)))
  224. (shui/popup-hide!))))
  225. (when (fn? refresh-result-f) (refresh-result-f)))))
  226. (declare property-value)
  227. (rum/defc repeat-setting < rum/reactive db-mixins/query
  228. [block property]
  229. (let [opts {:exit-edit? false}
  230. block (db/sub-block (:db/id block))]
  231. [:div.p-4.flex.flex-col.gap-4.w-64
  232. [:div.mb-4
  233. [:div.flex.flex-row.items-center.gap-1
  234. [:div.w-4
  235. (property-value block (db/entity :logseq.task/repeated?)
  236. (assoc opts
  237. :on-checked-change (fn [value]
  238. (if value
  239. (db-property-handler/set-block-property! (:db/id block)
  240. :logseq.task/scheduled-on-property
  241. (:db/id property))
  242. (db-property-handler/remove-block-property! (:db/id block)
  243. :logseq.task/scheduled-on-property)))))]
  244. (if (#{:logseq.task/deadline :logseq.task/scheduled} (:db/ident property))
  245. [:div "Repeat task"]
  246. [:div "Repeat " (if (= :date (:logseq.property/type property)) "date" "datetime")])]]
  247. [:div.flex.flex-row.gap-2
  248. [:div.flex.text-muted-foreground.mr-4
  249. "Every"]
  250. ;; recur frequency
  251. [:div.w-6
  252. (property-value block (db/entity :logseq.task/recur-frequency) opts)]
  253. ;; recur unit
  254. [:div.w-20
  255. (property-value block (db/entity :logseq.task/recur-unit) (assoc opts :property property))]]
  256. (let [properties (->>
  257. (outliner-property/get-block-full-properties (db/get-db) (:db/id block))
  258. (filter (fn [property]
  259. (and (not (ldb/built-in? property))
  260. (>= (count (:property/closed-values property)) 2))))
  261. (concat [(db/entity :logseq.task/status)])
  262. (util/distinct-by :db/id))
  263. status-property (or (:logseq.task/recur-status-property block)
  264. (db/entity :logseq.task/status))
  265. property-id (:db/id status-property)
  266. done-choice (or
  267. (some (fn [choice] (when (true? (:logseq.property/choice-checkbox-state choice)) choice)) (:property/closed-values status-property))
  268. (db/entity :logseq.task/status.done))]
  269. [:div.flex.flex-col.gap-2
  270. [:div.text-muted-foreground
  271. "When"]
  272. (shui/select
  273. (cond->
  274. {:on-value-change (fn [v]
  275. (db-property-handler/set-block-property! (:db/id block)
  276. :logseq.task/recur-status-property
  277. v))}
  278. property-id
  279. (assoc :default-value property-id))
  280. (shui/select-trigger
  281. (shui/select-value {:placeholder "Select a property"}))
  282. (shui/select-content
  283. (map (fn [choice]
  284. (shui/select-item {:key (str (:db/id choice))
  285. :value (:db/id choice)} (:block/title choice))) properties)))
  286. [:div.flex.flex-row.gap-1
  287. [:div.text-muted-foreground
  288. "is:"]
  289. (when done-choice
  290. (db-property/property-value-content done-choice))]])]))
  291. (rum/defcs calendar-inner < rum/reactive db-mixins/query
  292. (rum/local (str "calendar-inner-" (js/Date.now)) ::identity)
  293. {:init (fn [state]
  294. (state/set-editor-action! :property-set-date)
  295. state)
  296. :will-mount (fn [state]
  297. (js/setTimeout
  298. #(some-> @(::identity state)
  299. (js/document.getElementById)
  300. (.querySelector "[aria-selected=true]")
  301. (.focus)) 16)
  302. state)
  303. :will-unmount (fn [state]
  304. (shui/popup-hide!)
  305. (shui/dialog-close!)
  306. (state/set-editor-action! nil)
  307. state)}
  308. [state id {:keys [block property datetime? on-change del-btn? on-delete]}]
  309. (let [block (db/sub-block (:db/id block))
  310. value (get block (:db/ident property))
  311. value (cond
  312. (map? value)
  313. (when-let [day (:block/journal-day value)]
  314. (let [t (date/journal-day->utc-ms day)]
  315. (js/Date. t)))
  316. (number? value)
  317. (js/Date. value)
  318. :else
  319. (let [d (js/Date.)]
  320. (.setHours d 0 0 0)
  321. d))
  322. *ident (::identity state)
  323. initial-day value
  324. initial-month (when value
  325. (let [d (tc/to-date-time value)]
  326. (js/Date. (t/last-day-of-the-month (t/date-time (t/year d) (t/month d))))))
  327. select-handler!
  328. (fn [^js d]
  329. (when d
  330. (let [journal (date/js-date->journal-title d)]
  331. (p/do!
  332. (when-not (db/get-page journal)
  333. (page-handler/<create! journal {:redirect? false
  334. :create-first-block? false}))
  335. (when (fn? on-change)
  336. (let [value (if datetime? (tc/to-long d) (db/get-page journal))]
  337. (on-change value)))
  338. (when-not datetime?
  339. (shui/popup-hide! id)
  340. (ui/hide-popups-until-preview-popup!)
  341. (shui/dialog-close!))))))]
  342. [:div.flex.flex-row.gap-2
  343. [:div.flex.flex-col
  344. (ui/nlp-calendar
  345. (cond->
  346. {:initial-focus true
  347. :datetime? datetime?
  348. :selected initial-day
  349. :id @*ident
  350. :del-btn? del-btn?
  351. :on-delete on-delete
  352. :on-day-click select-handler!}
  353. initial-month
  354. (assoc :default-month initial-month)))]
  355. (shui/separator {:orientation "vertical"})
  356. (repeat-setting block property)]))
  357. (rum/defc overdue
  358. [date content]
  359. (let [[current-time set-current-time!] (rum/use-state (t/now))]
  360. (hooks/use-effect!
  361. (fn []
  362. (let [timer (js/setInterval (fn [] (set-current-time! (t/now))) (* 1000 60 3))]
  363. #(js/clearInterval timer)))
  364. [])
  365. (let [overdue? (when date (t/after? current-time (t/plus date (t/seconds 59))))]
  366. [:div
  367. (cond-> {} overdue? (assoc :class "overdue"
  368. :title "Overdue"))
  369. content])))
  370. (defn- human-date-label [date]
  371. (let [given-date (date/start-of-day date)
  372. now (local/local-now)
  373. today (date/start-of-day now)
  374. tomorrow (t/plus today (t/days 1))
  375. yesterday (t/minus today (t/days 1))]
  376. (cond
  377. (and (t/before? given-date today) (not (t/before? given-date yesterday)))
  378. "Yesterday"
  379. (and (not (t/before? given-date today)) (t/before? given-date tomorrow))
  380. "Today"
  381. (and (not (t/before? given-date tomorrow)) (t/before? given-date (t/plus tomorrow (t/days 1))))
  382. "Tomorrow"
  383. :else nil)))
  384. (rum/defc datetime-value
  385. [value property-id repeated-task?]
  386. (when-let [date (t/to-default-time-zone (tc/from-long value))]
  387. (let [content [:div.ls-datetime.flex.flex-row.gap-1.items-center
  388. (when-let [page-cp (state/get-component :block/page-cp)]
  389. (let [page-title (date/journal-name date)]
  390. (rum/with-key
  391. (page-cp {:disable-preview? true
  392. :show-non-exists-page? true
  393. :label (human-date-label date)}
  394. {:block/name page-title})
  395. page-title)))
  396. (let [date (js/Date. value)
  397. hours (.getHours date)
  398. minutes (.getMinutes date)]
  399. [:span.select-none
  400. (str (util/zero-pad hours)
  401. ":"
  402. (util/zero-pad minutes))])]]
  403. (if (or repeated-task? (contains? #{:logseq.task/deadline :logseq.task/scheduled} property-id))
  404. (overdue date content)
  405. content))))
  406. (rum/defc date-picker
  407. [value {:keys [block property datetime? on-change on-delete del-btn? editing? multiple-values? other-position?]}]
  408. (let [*trigger-ref (rum/use-ref nil)
  409. content-fn (fn [{:keys [id]}] (calendar-inner id
  410. {:block block
  411. :property property
  412. :on-change on-change
  413. :value value
  414. :del-btn? del-btn?
  415. :on-delete on-delete
  416. :datetime? datetime?}))
  417. open-popup! (fn [e]
  418. (when-not (or (util/meta-key? e) (util/shift-key? e))
  419. (util/stop e)
  420. (editor-handler/save-current-block!)
  421. (when-not config/publishing?
  422. (shui/popup-show! (.-target e) content-fn
  423. {:align "start" :auto-focus? true}))))
  424. repeated-task? (:logseq.task/repeated? block)]
  425. (hooks/use-effect!
  426. (fn []
  427. (when editing?
  428. (js/setTimeout
  429. #(some-> (rum/deref *trigger-ref)
  430. (.click)) 32)))
  431. [editing?])
  432. (if multiple-values?
  433. (shui/button
  434. {:class "jtrigger h-6 empty-btn"
  435. :ref *trigger-ref
  436. :variant :text
  437. :size :sm
  438. :on-click open-popup!}
  439. (ui/icon "calendar-plus" {:size 16}))
  440. (shui/trigger-as
  441. :div.flex.flex-1.flex-row.gap-1.items-center.flex-wrap
  442. {:tabIndex 0
  443. :class "jtrigger min-h-[24px]" ; FIXME: min-h-6 not works
  444. :ref *trigger-ref
  445. :on-click open-popup!}
  446. [:div.flex.flex-row.gap-1.items-center
  447. (when repeated-task?
  448. (ui/icon "repeat" {:size 14 :class "opacity-40"}))
  449. (cond
  450. (map? value)
  451. (let [date (tc/to-date-time (date/journal-day->utc-ms (:block/journal-day value)))
  452. compare-value (some-> date
  453. (t/plus (t/days 1))
  454. (t/minus (t/seconds 1)))
  455. content (when-let [page-cp (state/get-component :block/page-cp)]
  456. (rum/with-key
  457. (page-cp {:disable-preview? true
  458. :meta-click? other-position?
  459. :label (human-date-label (t/to-default-time-zone date))} value)
  460. (:db/id value)))]
  461. (if (or repeated-task? (contains? #{:logseq.task/deadline :logseq.task/scheduled} (:db/id property)))
  462. (overdue compare-value content)
  463. content))
  464. (number? value)
  465. (datetime-value value (:db/ident property) repeated-task?)
  466. :else
  467. (property-empty-btn-value nil))]))))
  468. (rum/defc property-value-date-picker
  469. [block property value opts]
  470. (let [multiple-values? (db-property/many? property)
  471. repo (state/get-current-repo)
  472. datetime? (= :datetime (:logseq.property/type property))]
  473. (date-picker value
  474. (merge opts
  475. {:block block
  476. :property property
  477. :datetime? datetime?
  478. :multiple-values? multiple-values?
  479. :on-change (fn [value]
  480. (let [blocks (get-operating-blocks block)]
  481. (property-handler/batch-set-block-property! repo (map :block/uuid blocks)
  482. (:db/ident property)
  483. (if datetime?
  484. value
  485. (:db/id value)))))
  486. :del-btn? (some? value)
  487. :on-delete (fn [e]
  488. (util/stop-propagation e)
  489. (let [blocks (get-operating-blocks block)]
  490. (property-handler/batch-set-block-property! repo (map :block/uuid blocks)
  491. (:db/ident property)
  492. nil))
  493. (shui/popup-hide!))}))))
  494. (defn- <create-page-if-not-exists!
  495. [block property classes page]
  496. (let [page* (string/trim page)
  497. ;; inline-class is only for input from :transform-fn
  498. [page inline-class] (if (and (seq classes) (not (contains? db-property/db-attribute-properties (:db/ident property))))
  499. (or (seq (map string/trim (rest (re-find #"(.*)#(.*)$" page*))))
  500. [page* nil])
  501. [page* nil])
  502. page-entity (ldb/get-case-page (db/get-db) page)
  503. id (:db/id page-entity)
  504. class? (or (= :block/tags (:db/ident property))
  505. (and (= :logseq.property/parent (:db/ident property))
  506. (ldb/class? block)))
  507. ;; Note: property and other types shouldn't be converted to class
  508. page? (ldb/internal-page? page-entity)]
  509. (cond
  510. ;; page not exists or page exists but not a page type
  511. (or (nil? id) (and class? (not page?)))
  512. (let [inline-class-uuid
  513. (when inline-class
  514. (or (:block/uuid (ldb/get-case-page (db/get-db) inline-class))
  515. (do (log/error :msg "Given inline class does not exist" :inline-class inline-class)
  516. nil)))
  517. create-options {:redirect? false
  518. :create-first-block? false
  519. :tags (if inline-class-uuid
  520. [inline-class-uuid]
  521. ;; Only 1st class b/c page normally has
  522. ;; one of and not all these classes
  523. (mapv :block/uuid (take 1 classes)))}]
  524. (p/let [page (if class?
  525. (db-page-handler/<create-class! page create-options)
  526. (page-handler/<create! page create-options))]
  527. (:db/id page)))
  528. (and class? page? id)
  529. (p/let [_ (db-page-handler/convert-to-tag! page-entity)]
  530. id)
  531. :else
  532. id)))
  533. (defn- select-aux
  534. [block property {:keys [items selected-choices multiple-choices?] :as opts}]
  535. (let [selected-choices (->> selected-choices
  536. (remove nil?)
  537. (remove #(= :logseq.property/empty-placeholder %)))
  538. clear-value (str "No " (:block/title property))
  539. clear-value-label [:div.flex.flex-row.items-center.gap-2
  540. (ui/icon "x")
  541. [:div clear-value]]
  542. items' (->>
  543. (if (and (seq selected-choices)
  544. (not multiple-choices?)
  545. (not (and (ldb/class? block) (= (:db/ident property) :logseq.property/parent)))
  546. (not= (:db/ident property) :logseq.property.view/type))
  547. (concat items
  548. [{:value clear-value
  549. :label clear-value-label
  550. :clear? true}])
  551. items)
  552. (remove #(= :logseq.property/empty-placeholder (:value %))))
  553. k :on-chosen
  554. f (get opts k)
  555. f' (fn [chosen selected?]
  556. (if (or (and (not multiple-choices?) (= chosen clear-value))
  557. (and multiple-choices? (= chosen [clear-value])))
  558. (p/do!
  559. (let [blocks (get-operating-blocks block)
  560. block-ids (map :block/uuid blocks)]
  561. (property-handler/batch-remove-block-property!
  562. (state/get-current-repo)
  563. block-ids
  564. (:db/ident property)))
  565. (when-not (false? (:exit-edit? opts))
  566. (shui/popup-hide!)))
  567. (f chosen selected?)))]
  568. (select/select (assoc opts
  569. :selected-choices selected-choices
  570. :items items'
  571. k f'))))
  572. (defn- get-node-icon
  573. [node]
  574. (cond
  575. (ldb/class? node)
  576. "hash"
  577. (ldb/property? node)
  578. "letter-p"
  579. (entity-util/page? node)
  580. "page"
  581. :else
  582. "letter-n"))
  583. (rum/defc ^:large-vars/cleanup-todo select-node < rum/static
  584. [property
  585. {:keys [block multiple-choices? dropdown? input-opts on-input] :as opts}
  586. result]
  587. (let [[refresh-count set-refresh-count!] (rum/use-state 0)
  588. repo (state/get-current-repo)
  589. classes (:logseq.property/classes property)
  590. tags? (= :block/tags (:db/ident property))
  591. alias? (= :block/alias (:db/ident property))
  592. tags-or-alias? (or tags? alias?)
  593. block (db/entity (:db/id block))
  594. selected-choices (when block
  595. (when-let [v (get block (:db/ident property))]
  596. (if (every? de/entity? v)
  597. (map :db/id v)
  598. [(:db/id v)])))
  599. parent-property? (= (:db/ident property) :logseq.property/parent)
  600. children-pages (when parent-property? (model/get-structured-children repo (:db/id block)))
  601. nodes
  602. (->>
  603. (cond
  604. parent-property?
  605. (let [;; Disallows cyclic hierarchies
  606. exclude-ids (-> (set (map (fn [id] (:block/uuid (db/entity id))) children-pages))
  607. (conj (:block/uuid block))) ; break cycle
  608. options (if (ldb/class? block)
  609. (model/get-all-classes repo)
  610. (when (ldb/internal-page? block)
  611. (cond->>
  612. (->> (model/get-all-pages repo)
  613. (filter ldb/internal-page?)
  614. (remove ldb/built-in?)))))
  615. excluded-options (remove (fn [e] (contains? exclude-ids (:block/uuid e))) options)]
  616. excluded-options)
  617. (seq classes)
  618. (->>
  619. (mapcat
  620. (fn [class]
  621. (if (= :logseq.class/Root (:db/ident class))
  622. (model/get-all-classes repo {:except-root-class? true})
  623. (model/get-class-objects repo (:db/id class))))
  624. classes)
  625. distinct)
  626. :else
  627. (let [property-type (:logseq.property/type property)]
  628. (if (empty? result)
  629. (let [v (get block (:db/ident property))]
  630. (remove #(= :logseq.property/empty-placeholder (:db/ident %))
  631. (if (every? de/entity? v) v [v])))
  632. (remove (fn [node]
  633. (or (= (:db/id block) (:db/id node))
  634. ;; A page's alias can't be itself
  635. (and alias? (= (or (:db/id (:block/page block))
  636. (:db/id block))
  637. (:db/id node)))
  638. (when (and property-type (not= property-type :node))
  639. (if (= property-type :page)
  640. (not (db/page? node))
  641. (not (contains? (ldb/get-entity-types node) property-type))))))
  642. result)))))
  643. options (map (fn [node]
  644. (let [id (or (:value node) (:db/id node))
  645. [header label] (if (integer? id)
  646. (let [node-title (if (seq (:logseq.property/classes property))
  647. (:block/title node)
  648. (block-handler/block-unique-title node))
  649. title (subs node-title 0 256)
  650. node (or (db/entity id) node)
  651. icon (get-node-icon node)
  652. header (when-not (db/page? node)
  653. (when-let [breadcrumb (state/get-component :block/breadcrumb)]
  654. [:div.text-xs.opacity-70
  655. (breadcrumb {:search? true} (state/get-current-repo) (:block/uuid block) {})]))
  656. label [:div.flex.flex-row.items-center.gap-1
  657. (when-not (:logseq.property/classes property)
  658. (ui/icon icon {:size 14}))
  659. [:div title]]]
  660. [header label])
  661. [nil (or (:label node) (:block/title node))])]
  662. (assoc node
  663. :header header
  664. :label-value (:block/title node)
  665. :label label
  666. :value id
  667. :disabled? (and tags? (contains?
  668. (set/union #{:logseq.class/Journal :logseq.class/Whiteboard} ldb/internal-tags)
  669. (:db/ident node)))))) nodes)
  670. classes' (remove (fn [class] (= :logseq.class/Root (:db/ident class))) classes)
  671. opts' (cond->
  672. (merge
  673. opts
  674. {:multiple-choices? multiple-choices?
  675. :items options
  676. :selected-choices selected-choices
  677. :dropdown? dropdown?
  678. :input-default-placeholder (cond
  679. tags?
  680. "Set tags"
  681. alias?
  682. "Set alias"
  683. multiple-choices?
  684. "Choose nodes"
  685. :else
  686. "Choose node")
  687. :show-new-when-not-exact-match? (if (or (and parent-property? (contains? (set children-pages) (:db/id block)))
  688. ;; Don't allow creating private tags
  689. (seq (set/intersection (set (map :db/ident classes))
  690. ldb/private-tags)))
  691. false
  692. true)
  693. :extract-chosen-fn :value
  694. :extract-fn (fn [x] (or (:label-value x) (:label x)))
  695. :input-opts input-opts
  696. :on-input (debounce on-input 50)
  697. :on-chosen (fn [chosen selected?]
  698. (p/let [[id new?] (if (integer? chosen)
  699. [chosen false]
  700. (when-not (string/blank? (string/trim chosen))
  701. (p/let [result (<create-page-if-not-exists! block property classes' chosen)]
  702. [result true])))
  703. _ (when (and (integer? id) (not (entity-util/page? (db/entity id))))
  704. (db-async/<get-block repo id))]
  705. (p/do!
  706. (if id
  707. (add-or-remove-property-value block property id selected? {})
  708. (log/error :msg "No :db/id found or created for chosen" :chosen chosen))
  709. (when new? (set-refresh-count! (inc refresh-count))))))})
  710. (and (seq classes') (not tags-or-alias?))
  711. (assoc
  712. ;; Provides additional completion for inline classes on new pages or objects
  713. :transform-fn (fn [results input]
  714. (if-let [[_ new-page class-input] (and (empty? results) (re-find #"(.*)#(.*)$" input))]
  715. (let [repo (state/get-current-repo)
  716. descendent-classes (->> classes'
  717. (mapcat #(model/get-structured-children repo (:db/id %)))
  718. (map #(db/entity repo %)))]
  719. (->> (concat classes' descendent-classes)
  720. (filter #(string/includes? (:block/title %) class-input))
  721. (mapv (fn [p]
  722. {:value (str new-page "#" (:block/title p))
  723. :label (str new-page "#" (:block/title p))}))))
  724. results))))]
  725. (select-aux block property opts')))
  726. (rum/defc property-value-select-node < rum/static
  727. [block property opts
  728. {:keys [*show-new-property-config?]}]
  729. (let [[result set-result!] (rum/use-state nil)
  730. input-opts (fn [_]
  731. {:on-click (fn []
  732. (when *show-new-property-config?
  733. (reset! *show-new-property-config? false)))
  734. :on-key-down
  735. (fn [e]
  736. (case (util/ekey e)
  737. "Escape"
  738. (when-let [f (:on-chosen opts)] (f))
  739. nil))})
  740. opts' (assoc opts
  741. :block block
  742. :input-opts input-opts
  743. :on-input (fn [v]
  744. (if (string/blank? v)
  745. (set-result! nil)
  746. (p/let [result (search/block-search (state/get-current-repo) v {:enable-snippet? false
  747. :built-in? false})]
  748. (set-result! result)))))
  749. repo (state/get-current-repo)
  750. classes (:logseq.property/classes property)
  751. non-root-classes (remove (fn [c] (= (:db/ident c) :logseq.class/Root)) classes)
  752. parent-property? (= (:db/ident property) :logseq.property/parent)]
  753. (when (and (not parent-property?) (seq non-root-classes))
  754. ;; effect runs once
  755. (hooks/use-effect!
  756. (fn []
  757. (p/let [result (p/all (map (fn [class] (db-async/<get-tag-objects repo (:db/id class))) non-root-classes))
  758. result' (distinct (apply concat result))]
  759. (set-result! result')))
  760. []))
  761. (select-node property opts' result)))
  762. (rum/defcs select < rum/reactive db-mixins/query
  763. {:init (fn [state]
  764. (let [*values (atom :loading)
  765. refresh-result-f (fn []
  766. (let [[block property _] (:rum/args state)]
  767. (p/let [property-ident (if (= :logseq.property/default-value (:db/ident property))
  768. (:db/ident block)
  769. (:db/ident property))
  770. result (db-async/<get-block-property-values (state/get-current-repo)
  771. property-ident)]
  772. (reset! *values result))))]
  773. (refresh-result-f)
  774. (assoc state
  775. ::values *values
  776. ::refresh-result-f refresh-result-f)))}
  777. [state block property
  778. {:keys [multiple-choices? dropdown? content-props] :as select-opts}
  779. {:keys [*show-new-property-config? exit-edit?] :as opts}]
  780. (let [*values (::values state)
  781. refresh-result-f (::refresh-result-f state)
  782. values (rum/react *values)
  783. block (db/sub-block (:db/id block))]
  784. (when-not (= :loading values)
  785. (let [type (:logseq.property/type property)
  786. closed-values? (seq (:property/closed-values property))
  787. ref-type? (db-property-type/all-ref-property-types type)
  788. items (if closed-values?
  789. (let [date? (and
  790. (= (:db/ident property) :logseq.task/recur-unit)
  791. (= :date (:logseq.property/type (:property opts))))
  792. values (cond->> (:property/closed-values property)
  793. date?
  794. (remove (fn [b] (contains? #{:logseq.task/recur-unit.minute :logseq.task/recur-unit.hour} (:db/ident b)))))]
  795. (keep (fn [block]
  796. (let [icon (pu/get-block-property-value block :logseq.property/icon)
  797. value (db-property/closed-value-content block)]
  798. {:label (if icon
  799. [:div.flex.flex-row.gap-1.items-center
  800. (icon-component/icon icon {:color? true})
  801. value]
  802. value)
  803. :value (:db/id block)
  804. :label-value value}))
  805. values))
  806. (->> values
  807. (mapcat (fn [value]
  808. (if (coll? value)
  809. (map (fn [v] {:value v}) value)
  810. [{:value value}])))
  811. (map (fn [{:keys [value]}]
  812. (if (and ref-type? (number? value))
  813. (when-let [e (db/entity value)]
  814. {:label (db-property/property-value-content e)
  815. :value value})
  816. {:label value
  817. :value value})))
  818. (distinct)))
  819. items (->> (if (= :date type)
  820. (map (fn [m] (let [label (:block/title (db/entity (:value m)))]
  821. (when label
  822. (assoc m :label label)))) items)
  823. items)
  824. (remove nil?))
  825. on-chosen (fn [chosen selected?]
  826. (let [value (if (map? chosen) (:value chosen) chosen)]
  827. (add-or-remove-property-value block property value selected?
  828. {:exit-edit? exit-edit?
  829. :refresh-result-f refresh-result-f})))
  830. selected-choices' (get block (:db/ident property))
  831. selected-choices (if (every? de/entity? selected-choices')
  832. (map :db/id selected-choices')
  833. [selected-choices'])]
  834. (select-aux block property
  835. {:multiple-choices? multiple-choices?
  836. :items items
  837. :selected-choices selected-choices
  838. :dropdown? dropdown?
  839. :show-new-when-not-exact-match? (not (or closed-values? (= :date type)))
  840. :input-default-placeholder "Select"
  841. :extract-chosen-fn :value
  842. :extract-fn (fn [x] (or (:label-value x) (:label x)))
  843. :content-props content-props
  844. :on-chosen on-chosen
  845. :input-opts (fn [_]
  846. {:on-blur (fn []
  847. (when-let [f (:on-chosen select-opts)] (f)))
  848. :on-click (fn []
  849. (when *show-new-property-config?
  850. (reset! *show-new-property-config? false)))
  851. :on-key-down
  852. (fn [e]
  853. (case (util/ekey e)
  854. "Escape"
  855. (when-let [f (:on-chosen select-opts)] (f))
  856. nil))})})))))
  857. (rum/defcs property-normal-block-value <
  858. {:init (fn [state]
  859. (assoc state :container-id (state/get-next-container-id)))}
  860. [state block property value-block]
  861. (let [container-id (:container-id state)
  862. multiple-values? (db-property/many? property)
  863. block-container (state/get-component :block/container)
  864. blocks-container (state/get-component :block/blocks-container)
  865. value-block (if (and (coll? value-block) (every? de/entity? value-block))
  866. (set (remove #(= (:db/ident %) :logseq.property/empty-placeholder) value-block))
  867. value-block)
  868. default-value (:logseq.property/default-value property)
  869. default-value? (and
  870. (:db/id default-value)
  871. (= (:db/id value-block) (:db/id default-value))
  872. (not= (:db/ident property) :logseq.property/default-value))]
  873. (if (seq value-block)
  874. [:div.property-block-container.content.w-full
  875. (let [config {:id (str (if multiple-values?
  876. (:block/uuid block)
  877. (:block/uuid value-block)))
  878. :container-id container-id
  879. :editor-box (state/get-component :editor/box)
  880. :property-block? true
  881. :on-block-content-pointer-down (when default-value?
  882. (fn [_e]
  883. (<create-new-block! block property (or (:block/title default-value) ""))))}]
  884. (if (set? value-block)
  885. (blocks-container config (ldb/sort-by-order value-block))
  886. (rum/with-key
  887. (block-container (assoc config :property-default-value? default-value?) value-block)
  888. (str (:db/id property) "-" (:block/uuid value-block)))))]
  889. [:div
  890. {:tabIndex 0
  891. :on-click (fn [] (<create-new-block! block property ""))}
  892. (property-empty-btn-value property)])))
  893. (rum/defcs property-block-value < rum/reactive db-mixins/query
  894. {:init (fn [state]
  895. (let [block (first (:rum/args state))]
  896. (when-let [block-id (or (:db/id block) (:block/uuid block))]
  897. (db-async/<get-block (state/get-current-repo) block-id :children? true)))
  898. state)}
  899. [state value block property page-cp]
  900. (when value
  901. (if (state/sub-async-query-loading (:block/uuid value))
  902. [:div.text-sm.opacity-70 "loading"]
  903. (if-let [v-block (db/sub-block (:db/id value))]
  904. (let [class? (ldb/class? v-block)
  905. invalid-warning [:div.warning.text-sm
  906. "Invalid block value, please delete the current property."]]
  907. (when v-block
  908. (cond
  909. (:block/page v-block)
  910. (property-normal-block-value block property v-block)
  911. ;; page/class/etc.
  912. (entity-util/page? v-block)
  913. (rum/with-key
  914. (page-cp {:disable-preview? true
  915. :tag? class?} v-block)
  916. (:db/id v-block))
  917. :else
  918. invalid-warning)))
  919. (property-empty-btn-value property)))))
  920. (rum/defc closed-value-item < rum/reactive db-mixins/query
  921. [value {:keys [inline-text icon?]}]
  922. (when value
  923. (let [eid (if (de/entity? value) (:db/id value) [:block/uuid value])]
  924. (when-let [block (db/sub-block (:db/id (db/entity eid)))]
  925. (let [property-block? (db-property/property-created-block? block)
  926. value' (db-property/closed-value-content block)
  927. icon (pu/get-block-property-value block :logseq.property/icon)]
  928. (cond
  929. icon
  930. (if icon?
  931. (icon-component/icon icon {:color? true})
  932. [:div.flex.flex-row.items-center.gap-1.h-6
  933. (icon-component/icon icon {:color? true})
  934. (when value'
  935. [:span value'])])
  936. property-block?
  937. value'
  938. (= type :number)
  939. [:span.number (str value')]
  940. :else
  941. (inline-text {} :markdown (str value'))))))))
  942. (rum/defc select-item
  943. [property type value {:keys [page-cp inline-text other-position? property-position _icon?] :as opts}]
  944. (let [closed-values? (seq (:property/closed-values property))
  945. tag? (or (:tag? opts) (= (:db/ident property) :block/tags))
  946. inline-text-cp (fn [content]
  947. [:div.flex.flex-row.items-center
  948. (inline-text {} :markdown (macro-util/expand-value-if-macro content (state/get-macros)))])]
  949. [:div.select-item.cursor-pointer
  950. (cond
  951. (= value :logseq.property/empty-placeholder)
  952. (property-empty-btn-value property)
  953. closed-values?
  954. (closed-value-item value opts)
  955. (or (entity-util/page? value)
  956. (and (seq (:block/tags value))
  957. ;; FIXME: page-cp should be renamed to node-cp and
  958. ;; support this case and maybe other complex cases.
  959. (not (string/includes? (:block/title value) "[["))))
  960. (when value
  961. (rum/with-key
  962. (page-cp {:disable-preview? true
  963. :tag? tag?
  964. :property-position property-position
  965. :meta-click? other-position?} value)
  966. (:db/id value)))
  967. (contains? #{:node :class :property :page} type)
  968. (when-let [reference (state/get-component :block/reference)]
  969. (reference {} (:block/uuid value)))
  970. (de/entity? value)
  971. (when-some [content (str (db-property/property-value-content value))]
  972. (inline-text-cp content))
  973. :else
  974. (inline-text-cp (str value)))]))
  975. (rum/defc single-value-select
  976. [block property value value-f select-opts opts]
  977. (let [*el (rum/use-ref nil)]
  978. ;; Open popover initially when editing a property
  979. (hooks/use-effect!
  980. (fn []
  981. (when (:editing? opts)
  982. (.click (rum/deref *el))))
  983. [(:editing? opts)])
  984. (let [type (:logseq.property/type property)
  985. select-opts' (assoc select-opts :multiple-choices? false)
  986. popup-content (fn content-fn [_]
  987. [:div.property-select
  988. (case type
  989. (:entity :number :default :url)
  990. (select block property select-opts' opts)
  991. (:node :class :property :page :date)
  992. (property-value-select-node block property select-opts' opts))])
  993. trigger-id (str "trigger-" (:container-id opts) "-" (:db/id block) "-" (:db/id property))
  994. show! (fn [e]
  995. (let [target (.-target e)]
  996. (when-not (or config/publishing?
  997. (util/shift-key? e)
  998. (util/meta-key? e)
  999. (util/link? target)
  1000. (when-let [node (.closest target "a")]
  1001. (not (or (d/has-class? node "page-ref")
  1002. (d/has-class? node "tag")))))
  1003. (shui/popup-show! target popup-content
  1004. {:align "start"
  1005. :as-dropdown? true
  1006. :auto-focus? true
  1007. :trigger-id trigger-id}))))]
  1008. (shui/trigger-as
  1009. (if (:other-position? opts) :div.jtrigger :div.jtrigger.flex.flex-1.w-full)
  1010. {:ref *el
  1011. :id trigger-id
  1012. :tabIndex 0
  1013. :on-click show!}
  1014. (if (string/blank? value)
  1015. (property-empty-text-value property opts)
  1016. (value-f))))))
  1017. (defn- property-value-inner
  1018. [block property value {:keys [inline-text page-cp
  1019. dom-id row?]}]
  1020. (let [multiple-values? (db-property/many? property)
  1021. class (str (when-not row? "flex flex-1 ")
  1022. (when multiple-values? "property-value-content"))
  1023. type (:logseq.property/type property)
  1024. text-ref-type? (db-property-type/text-ref-property-types type)]
  1025. [:div.cursor-text
  1026. {:id (or dom-id (random-uuid))
  1027. :tabIndex 0
  1028. :class (str class " " (when-not text-ref-type? "jtrigger"))
  1029. :style {:min-height 24}
  1030. :on-click (fn []
  1031. (when (and text-ref-type? (nil? value))
  1032. (<create-new-block! block property "")))}
  1033. (cond
  1034. (and (= :logseq.property/default-value (:db/ident property)) (nil? (:block/title value)))
  1035. [:div.jtrigger.cursor-pointer.text-sm.px-2 "Set default value"]
  1036. (and text-ref-type? (nil? (:block/title value)))
  1037. [:div.jtrigger (property-empty-btn-value property)]
  1038. text-ref-type?
  1039. (property-block-value value block property page-cp)
  1040. :else
  1041. (inline-text {} :markdown (macro-util/expand-value-if-macro (str value) (state/get-macros))))]))
  1042. (rum/defcs property-scalar-value < rum/static rum/reactive
  1043. [state block property value* {:keys [container-id editing? on-chosen]
  1044. :as opts}]
  1045. (let [property (model/sub-block (:db/id property))
  1046. type (:logseq.property/type property)
  1047. editing? (or editing?
  1048. (and (state/sub-editing? [container-id (:block/uuid block)])
  1049. (= (:db/id property) (:db/id (:property (state/get-editor-action-data))))))
  1050. select-type?' (select-type? block property type)
  1051. closed-values? (seq (:property/closed-values property))
  1052. select-opts {:on-chosen on-chosen}
  1053. value (if (and (de/entity? value*) (= (:db/ident value*) :logseq.property/empty-placeholder))
  1054. nil
  1055. value*)]
  1056. (if (= :logseq.property/icon (:db/ident property))
  1057. (icon-row block editing?)
  1058. (if (and select-type?'
  1059. (not (and (not closed-values?) (= type :date))))
  1060. (let [classes (outliner-property/get-block-classes (db/get-db) (:db/id block))
  1061. display-as-checkbox? (and (some
  1062. (fn [block]
  1063. (-> (set (map :db/id (:logseq.property/checkbox-display-properties block)))
  1064. (contains? (:db/id property))))
  1065. (conj classes block))
  1066. (seq (:property/closed-values property))
  1067. (boolean? (:logseq.property/choice-checkbox-state value*)))]
  1068. (if display-as-checkbox?
  1069. (let [checked? (:logseq.property/choice-checkbox-state value*)]
  1070. (shui/checkbox {:checked checked?
  1071. :class "mt-1"
  1072. :on-checked-change (fn [value]
  1073. (let [choices (:property/closed-values property)
  1074. choice (some (fn [choice] (when (= value (:logseq.property/choice-checkbox-state choice))
  1075. choice)) choices)]
  1076. (when choice
  1077. (db-property-handler/set-block-property! (:db/id block) (:db/ident property) (:db/id choice)))))}))
  1078. (single-value-select block property value
  1079. (fn [] (select-item property type value opts))
  1080. select-opts
  1081. (assoc opts :editing? editing?))))
  1082. (case type
  1083. (:date :datetime)
  1084. (property-value-date-picker block property value (merge opts {:editing? editing?}))
  1085. :checkbox
  1086. (let [add-property! (fn []
  1087. (let [value' (boolean (not value))]
  1088. (<add-property! block (:db/ident property) value' opts)
  1089. (when-let [on-checked-change (:on-checked-change opts)]
  1090. (on-checked-change value'))))]
  1091. [:label.flex.w-full.as-scalar-value-wrap.cursor-pointer
  1092. (shui/checkbox {:class "jtrigger flex flex-row items-center"
  1093. :disabled config/publishing?
  1094. :auto-focus editing?
  1095. :checked value
  1096. :on-checked-change add-property!
  1097. :on-key-down (fn [e]
  1098. (when (= (util/ekey e) "Enter")
  1099. (add-property!)))})])
  1100. ;; :others
  1101. [:div.flex.flex-1
  1102. (property-value-inner block property value opts)])))))
  1103. (rum/defc multiple-values-inner
  1104. [block property v {:keys [on-chosen editing?] :as opts}]
  1105. (let [type (:logseq.property/type property)
  1106. date? (= type :date)
  1107. *el (rum/use-ref nil)
  1108. items (cond->> (if (de/entity? v) #{v} v)
  1109. (= (:db/ident property) :block/tags)
  1110. (remove (fn [v] (contains? ldb/hidden-tags (:db/ident v)))))]
  1111. (hooks/use-effect!
  1112. (fn []
  1113. (when editing?
  1114. (.click (rum/deref *el))))
  1115. [editing?])
  1116. (let [select-cp (fn [select-opts]
  1117. (let [select-opts (merge {:multiple-choices? true
  1118. :on-chosen (fn []
  1119. (when on-chosen (on-chosen)))}
  1120. select-opts
  1121. {:dropdown? false})]
  1122. [:div.property-select
  1123. (if (contains? #{:node :page :class :property} type)
  1124. (property-value-select-node block property
  1125. select-opts
  1126. opts)
  1127. (select block property select-opts opts))]))]
  1128. (let [toggle-fn shui/popup-hide!
  1129. content-fn (fn [{:keys [_id content-props]}]
  1130. (select-cp {:content-props content-props}))]
  1131. [:div.multi-values.jtrigger
  1132. {:tab-index "0"
  1133. :ref *el
  1134. :on-click (fn [^js e]
  1135. (let [target (.-target e)]
  1136. (when-not (or (util/link? target) (.closest target "a") config/publishing?)
  1137. (shui/popup-show! (rum/deref *el) content-fn
  1138. {:as-dropdown? true :as-content? false
  1139. :align "start" :auto-focus? true}))))
  1140. :on-key-down (fn [^js e]
  1141. (case (.-key e)
  1142. (" " "Enter")
  1143. (do (some-> (rum/deref *el) (.click))
  1144. (util/stop e))
  1145. :dune))
  1146. :class "flex flex-1 flex-row items-center flex-wrap gap-x-2 gap-y-2"}
  1147. (let [not-empty-value? (not= (map :db/ident items) [:logseq.property/empty-placeholder])]
  1148. (if (and (seq items) not-empty-value?)
  1149. (concat
  1150. (->> (for [item items]
  1151. (rum/with-key (select-item property type item opts) (or (:block/uuid item) (str item))))
  1152. (interpose [:span.opacity-50.-ml-2 ","]))
  1153. (when date?
  1154. [(property-value-date-picker block property nil {:toggle-fn toggle-fn})]))
  1155. (if date?
  1156. (property-value-date-picker block property nil {:toggle-fn toggle-fn})
  1157. (property-empty-text-value property opts))))]))))
  1158. (rum/defc multiple-values < rum/reactive db-mixins/query
  1159. [block property opts]
  1160. (let [block (db/sub-block (:db/id block))
  1161. value (get block (:db/ident property))
  1162. value' (if (coll? value) value
  1163. (when (some? value) #{value}))]
  1164. (multiple-values-inner block property value' opts)))
  1165. (rum/defcs property-value < rum/reactive db-mixins/query
  1166. [state block property {:keys [show-tooltip?]
  1167. :as opts}]
  1168. (ui/catch-error
  1169. (ui/block-error "Something wrong" {})
  1170. (let [block (db/sub-block (:db/id block))
  1171. block-cp (state/get-component :block/blocks-container)
  1172. properties-cp (state/get-component :block/properties-cp)
  1173. opts (merge opts
  1174. {:page-cp (state/get-component :block/page-cp)
  1175. :inline-text (state/get-component :block/inline-text)
  1176. :editor-box (state/get-component :editor/box)
  1177. :block-cp block-cp
  1178. :properties-cp :properties-cp})
  1179. dom-id (str "ls-property-" (:db/id block) "-" (:db/id property))
  1180. editor-id (str dom-id "-editor")
  1181. type (:logseq.property/type property)
  1182. multiple-values? (db-property/many? property)
  1183. v (get block (:db/ident property))
  1184. v (cond
  1185. (and multiple-values? (or (set? v) (and (coll? v) (empty? v)) (nil? v)))
  1186. v
  1187. multiple-values?
  1188. #{v}
  1189. (set? v)
  1190. (first v)
  1191. :else
  1192. v)
  1193. empty-value? (when (coll? v) (= :logseq.property/empty-placeholder (:db/ident (first v))))
  1194. closed-values? (seq (:property/closed-values property))
  1195. property-ident (:db/ident property)
  1196. value-cp [:div.property-value-inner
  1197. {:data-type type
  1198. :class (str (when empty-value? "empty-value")
  1199. (when-not (:other-position? opts) " w-full"))}
  1200. (cond
  1201. (= property-ident :logseq.property.class/properties)
  1202. (properties-cp {} block {:selected? false
  1203. :class-schema? true})
  1204. (and multiple-values? (contains? #{:default :url} type) (not closed-values?))
  1205. (property-normal-block-value block property v)
  1206. multiple-values?
  1207. (multiple-values block property opts)
  1208. :else
  1209. (let [parent? (= property-ident :logseq.property/parent)
  1210. value-cp (property-scalar-value block property v
  1211. (merge
  1212. opts
  1213. {:editor-id editor-id
  1214. :dom-id dom-id}))
  1215. page-ancestors (when parent?
  1216. (let [ancestor-pages (loop [parents [block]]
  1217. (if-let [parent (:logseq.property/parent (last parents))]
  1218. (when-not (contains? (set parents) parent)
  1219. (recur (conj parents parent)))
  1220. parents))]
  1221. (->> (reverse ancestor-pages)
  1222. (remove (fn [e] (= (:db/id block) (:db/id e))))
  1223. butlast)))]
  1224. (if (seq page-ancestors)
  1225. [:div.flex.flex-1.items-center.gap-1
  1226. (interpose [:span.opacity-50.text-sm " > "]
  1227. (concat
  1228. (map (fn [{title :block/title :as ancestor}]
  1229. [:a.whitespace-nowrap {:on-click #(route-handler/redirect-to-page! (:block/uuid ancestor))} title])
  1230. page-ancestors)
  1231. [value-cp]))]
  1232. value-cp)))]]
  1233. (if show-tooltip?
  1234. (shui/tooltip-provider
  1235. (shui/tooltip
  1236. {:delayDuration 1200}
  1237. (shui/tooltip-trigger
  1238. {:onFocusCapture #(util/stop-propagation %)} value-cp)
  1239. (shui/tooltip-content
  1240. (str "Change " (:block/title property)))))
  1241. value-cp))))