value.cljs 63 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305
  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. (property-handler/set-block-property! repo (:block/uuid block)
  481. (:db/ident property)
  482. (if datetime?
  483. value
  484. (:db/id value))))
  485. :del-btn? (some? value)
  486. :on-delete (fn []
  487. (property-handler/set-block-property! repo (:block/uuid block)
  488. (:db/ident property) nil)
  489. (shui/popup-hide!))}))))
  490. (defn- <create-page-if-not-exists!
  491. [block property classes page]
  492. (let [page* (string/trim page)
  493. ;; inline-class is only for input from :transform-fn
  494. [page inline-class] (if (and (seq classes) (not (contains? db-property/db-attribute-properties (:db/ident property))))
  495. (or (seq (map string/trim (rest (re-find #"(.*)#(.*)$" page*))))
  496. [page* nil])
  497. [page* nil])
  498. page-entity (ldb/get-case-page (db/get-db) page)
  499. id (:db/id page-entity)
  500. class? (or (= :block/tags (:db/ident property))
  501. (and (= :logseq.property/parent (:db/ident property))
  502. (ldb/class? block)))
  503. ;; Note: property and other types shouldn't be converted to class
  504. page? (ldb/internal-page? page-entity)]
  505. (cond
  506. ;; page not exists or page exists but not a page type
  507. (or (nil? id) (and class? (not page?)))
  508. (let [inline-class-uuid
  509. (when inline-class
  510. (or (:block/uuid (ldb/get-case-page (db/get-db) inline-class))
  511. (do (log/error :msg "Given inline class does not exist" :inline-class inline-class)
  512. nil)))
  513. create-options {:redirect? false
  514. :create-first-block? false
  515. :tags (if inline-class-uuid
  516. [inline-class-uuid]
  517. ;; Only 1st class b/c page normally has
  518. ;; one of and not all these classes
  519. (mapv :block/uuid (take 1 classes)))}]
  520. (p/let [page (if class?
  521. (db-page-handler/<create-class! page create-options)
  522. (page-handler/<create! page create-options))]
  523. (:db/id page)))
  524. (and class? page? id)
  525. (p/let [_ (db-page-handler/convert-to-tag! page-entity)]
  526. id)
  527. :else
  528. id)))
  529. (defn- select-aux
  530. [block property {:keys [items selected-choices multiple-choices?] :as opts}]
  531. (let [selected-choices (->> selected-choices
  532. (remove nil?)
  533. (remove #(= :logseq.property/empty-placeholder %)))
  534. clear-value (str "No " (:block/title property))
  535. clear-value-label [:div.flex.flex-row.items-center.gap-2
  536. (ui/icon "x")
  537. [:div clear-value]]
  538. items' (->>
  539. (if (and (seq selected-choices)
  540. (not multiple-choices?)
  541. (not (and (ldb/class? block) (= (:db/ident property) :logseq.property/parent)))
  542. (not= (:db/ident property) :logseq.property.view/type))
  543. (concat items
  544. [{:value clear-value
  545. :label clear-value-label
  546. :clear? true}])
  547. items)
  548. (remove #(= :logseq.property/empty-placeholder (:value %))))
  549. k :on-chosen
  550. f (get opts k)
  551. f' (fn [chosen selected?]
  552. (if (or (and (not multiple-choices?) (= chosen clear-value))
  553. (and multiple-choices? (= chosen [clear-value])))
  554. (p/do!
  555. (let [blocks (get-operating-blocks block)
  556. block-ids (map :block/uuid blocks)]
  557. (property-handler/batch-remove-block-property!
  558. (state/get-current-repo)
  559. block-ids
  560. (:db/ident property)))
  561. (when-not (false? (:exit-edit? opts))
  562. (shui/popup-hide!)))
  563. (f chosen selected?)))]
  564. (select/select (assoc opts
  565. :selected-choices selected-choices
  566. :items items'
  567. k f'))))
  568. (defn- get-node-icon
  569. [node]
  570. (cond
  571. (ldb/class? node)
  572. "hash"
  573. (ldb/property? node)
  574. "letter-p"
  575. (entity-util/page? node)
  576. "page"
  577. :else
  578. "letter-n"))
  579. (rum/defc ^:large-vars/cleanup-todo select-node < rum/static
  580. [property
  581. {:keys [block multiple-choices? dropdown? input-opts on-input] :as opts}
  582. result]
  583. (let [[refresh-count set-refresh-count!] (rum/use-state 0)
  584. repo (state/get-current-repo)
  585. classes (:logseq.property/classes property)
  586. tags? (= :block/tags (:db/ident property))
  587. alias? (= :block/alias (:db/ident property))
  588. tags-or-alias? (or tags? alias?)
  589. block (db/entity (:db/id block))
  590. selected-choices (when block
  591. (when-let [v (get block (:db/ident property))]
  592. (if (every? de/entity? v)
  593. (map :db/id v)
  594. [(:db/id v)])))
  595. parent-property? (= (:db/ident property) :logseq.property/parent)
  596. children-pages (when parent-property? (model/get-structured-children repo (:db/id block)))
  597. nodes
  598. (->>
  599. (cond
  600. parent-property?
  601. (let [;; Disallows cyclic hierarchies
  602. exclude-ids (-> (set (map (fn [id] (:block/uuid (db/entity id))) children-pages))
  603. (conj (:block/uuid block))) ; break cycle
  604. options (if (ldb/class? block)
  605. (model/get-all-classes repo)
  606. (when (ldb/internal-page? block)
  607. (cond->>
  608. (->> (model/get-all-pages repo)
  609. (filter ldb/internal-page?)
  610. (remove ldb/built-in?)))))
  611. excluded-options (remove (fn [e] (contains? exclude-ids (:block/uuid e))) options)]
  612. excluded-options)
  613. (seq classes)
  614. (->>
  615. (mapcat
  616. (fn [class]
  617. (if (= :logseq.class/Root (:db/ident class))
  618. (model/get-all-classes repo {:except-root-class? true})
  619. (model/get-class-objects repo (:db/id class))))
  620. classes)
  621. distinct)
  622. :else
  623. (let [property-type (:logseq.property/type property)]
  624. (if (empty? result)
  625. (let [v (get block (:db/ident property))]
  626. (remove #(= :logseq.property/empty-placeholder (:db/ident %))
  627. (if (every? de/entity? v) v [v])))
  628. (remove (fn [node]
  629. (or (= (:db/id block) (:db/id node))
  630. ;; A page's alias can't be itself
  631. (and alias? (= (or (:db/id (:block/page block))
  632. (:db/id block))
  633. (:db/id node)))
  634. (when (and property-type (not= property-type :node))
  635. (if (= property-type :page)
  636. (not (db/page? node))
  637. (not (contains? (ldb/get-entity-types node) property-type))))))
  638. result)))))
  639. options (map (fn [node]
  640. (let [id (or (:value node) (:db/id node))
  641. [header label] (if (integer? id)
  642. (let [node-title (if (seq (:logseq.property/classes property))
  643. (:block/title node)
  644. (block-handler/block-unique-title node))
  645. title (subs node-title 0 256)
  646. node (or (db/entity id) node)
  647. icon (get-node-icon node)
  648. header (when-not (db/page? node)
  649. (when-let [breadcrumb (state/get-component :block/breadcrumb)]
  650. [:div.text-xs.opacity-70
  651. (breadcrumb {:search? true} (state/get-current-repo) (:block/uuid block) {})]))
  652. label [:div.flex.flex-row.items-center.gap-1
  653. (when-not (:logseq.property/classes property)
  654. (ui/icon icon {:size 14}))
  655. [:div title]]]
  656. [header label])
  657. [nil (or (:label node) (:block/title node))])]
  658. (assoc node
  659. :header header
  660. :label-value (:block/title node)
  661. :label label
  662. :value id
  663. :disabled? (and tags? (contains?
  664. (set/union #{:logseq.class/Journal :logseq.class/Whiteboard} ldb/internal-tags)
  665. (:db/ident node)))))) nodes)
  666. classes' (remove (fn [class] (= :logseq.class/Root (:db/ident class))) classes)
  667. opts' (cond->
  668. (merge
  669. opts
  670. {:multiple-choices? multiple-choices?
  671. :items options
  672. :selected-choices selected-choices
  673. :dropdown? dropdown?
  674. :input-default-placeholder (cond
  675. tags?
  676. "Set tags"
  677. alias?
  678. "Set alias"
  679. multiple-choices?
  680. "Choose nodes"
  681. :else
  682. "Choose node")
  683. :show-new-when-not-exact-match? (if (or (and parent-property? (contains? (set children-pages) (:db/id block)))
  684. ;; Don't allow creating private tags
  685. (seq (set/intersection (set (map :db/ident classes))
  686. ldb/private-tags)))
  687. false
  688. true)
  689. :extract-chosen-fn :value
  690. :extract-fn (fn [x] (or (:label-value x) (:label x)))
  691. :input-opts input-opts
  692. :on-input (debounce on-input 50)
  693. :on-chosen (fn [chosen selected?]
  694. (p/let [[id new?] (if (integer? chosen)
  695. [chosen false]
  696. (when-not (string/blank? (string/trim chosen))
  697. (p/let [result (<create-page-if-not-exists! block property classes' chosen)]
  698. [result true])))
  699. _ (when (and (integer? id) (not (entity-util/page? (db/entity id))))
  700. (db-async/<get-block repo id))]
  701. (p/do!
  702. (if id
  703. (add-or-remove-property-value block property id selected? {})
  704. (log/error :msg "No :db/id found or created for chosen" :chosen chosen))
  705. (when new? (set-refresh-count! (inc refresh-count))))))})
  706. (and (seq classes') (not tags-or-alias?))
  707. (assoc
  708. ;; Provides additional completion for inline classes on new pages or objects
  709. :transform-fn (fn [results input]
  710. (if-let [[_ new-page class-input] (and (empty? results) (re-find #"(.*)#(.*)$" input))]
  711. (let [repo (state/get-current-repo)
  712. descendent-classes (->> classes'
  713. (mapcat #(model/get-structured-children repo (:db/id %)))
  714. (map #(db/entity repo %)))]
  715. (->> (concat classes' descendent-classes)
  716. (filter #(string/includes? (:block/title %) class-input))
  717. (mapv (fn [p]
  718. {:value (str new-page "#" (:block/title p))
  719. :label (str new-page "#" (:block/title p))}))))
  720. results))))]
  721. (select-aux block property opts')))
  722. (rum/defc property-value-select-node < rum/static
  723. [block property opts
  724. {:keys [*show-new-property-config?]}]
  725. (let [[result set-result!] (rum/use-state nil)
  726. input-opts (fn [_]
  727. {:on-click (fn []
  728. (when *show-new-property-config?
  729. (reset! *show-new-property-config? false)))
  730. :on-key-down
  731. (fn [e]
  732. (case (util/ekey e)
  733. "Escape"
  734. (when-let [f (:on-chosen opts)] (f))
  735. nil))})
  736. opts' (assoc opts
  737. :block block
  738. :input-opts input-opts
  739. :on-input (fn [v]
  740. (if (string/blank? v)
  741. (set-result! nil)
  742. (p/let [result (search/block-search (state/get-current-repo) v {:enable-snippet? false
  743. :built-in? false})]
  744. (set-result! result)))))
  745. repo (state/get-current-repo)
  746. classes (:logseq.property/classes property)
  747. non-root-classes (remove (fn [c] (= (:db/ident c) :logseq.class/Root)) classes)
  748. parent-property? (= (:db/ident property) :logseq.property/parent)]
  749. (when (and (not parent-property?) (seq non-root-classes))
  750. ;; effect runs once
  751. (hooks/use-effect!
  752. (fn []
  753. (p/let [result (p/all (map (fn [class] (db-async/<get-tag-objects repo (:db/id class))) non-root-classes))
  754. result' (distinct (apply concat result))]
  755. (set-result! result')))
  756. []))
  757. (select-node property opts' result)))
  758. (rum/defcs select < rum/reactive db-mixins/query
  759. {:init (fn [state]
  760. (let [*values (atom :loading)
  761. refresh-result-f (fn []
  762. (let [[block property _] (:rum/args state)]
  763. (p/let [property-ident (if (= :logseq.property/default-value (:db/ident property))
  764. (:db/ident block)
  765. (:db/ident property))
  766. result (db-async/<get-block-property-values (state/get-current-repo)
  767. property-ident)]
  768. (reset! *values result))))]
  769. (refresh-result-f)
  770. (assoc state
  771. ::values *values
  772. ::refresh-result-f refresh-result-f)))}
  773. [state block property
  774. {:keys [multiple-choices? dropdown? content-props] :as select-opts}
  775. {:keys [*show-new-property-config? exit-edit?] :as opts}]
  776. (let [*values (::values state)
  777. refresh-result-f (::refresh-result-f state)
  778. values (rum/react *values)
  779. block (db/sub-block (:db/id block))]
  780. (when-not (= :loading values)
  781. (let [type (:logseq.property/type property)
  782. closed-values? (seq (:property/closed-values property))
  783. ref-type? (db-property-type/all-ref-property-types type)
  784. items (if closed-values?
  785. (let [date? (and
  786. (= (:db/ident property) :logseq.task/recur-unit)
  787. (= :date (:logseq.property/type (:property opts))))
  788. values (cond->> (:property/closed-values property)
  789. date?
  790. (remove (fn [b] (contains? #{:logseq.task/recur-unit.minute :logseq.task/recur-unit.hour} (:db/ident b)))))]
  791. (keep (fn [block]
  792. (let [icon (pu/get-block-property-value block :logseq.property/icon)
  793. value (db-property/closed-value-content block)]
  794. {:label (if icon
  795. [:div.flex.flex-row.gap-1.items-center
  796. (icon-component/icon icon {:color? true})
  797. value]
  798. value)
  799. :value (:db/id block)
  800. :label-value value}))
  801. values))
  802. (->> values
  803. (mapcat (fn [value]
  804. (if (coll? value)
  805. (map (fn [v] {:value v}) value)
  806. [{:value value}])))
  807. (map (fn [{:keys [value]}]
  808. (if (and ref-type? (number? value))
  809. (when-let [e (db/entity value)]
  810. {:label (db-property/property-value-content e)
  811. :value value})
  812. {:label value
  813. :value value})))
  814. (distinct)))
  815. items (->> (if (= :date type)
  816. (map (fn [m] (let [label (:block/title (db/entity (:value m)))]
  817. (when label
  818. (assoc m :label label)))) items)
  819. items)
  820. (remove nil?))
  821. on-chosen (fn [chosen selected?]
  822. (let [value (if (map? chosen) (:value chosen) chosen)]
  823. (add-or-remove-property-value block property value selected?
  824. {:exit-edit? exit-edit?
  825. :refresh-result-f refresh-result-f})))
  826. selected-choices' (get block (:db/ident property))
  827. selected-choices (if (every? de/entity? selected-choices')
  828. (map :db/id selected-choices')
  829. [selected-choices'])]
  830. (select-aux block property
  831. {:multiple-choices? multiple-choices?
  832. :items items
  833. :selected-choices selected-choices
  834. :dropdown? dropdown?
  835. :show-new-when-not-exact-match? (not (or closed-values? (= :date type)))
  836. :input-default-placeholder "Select"
  837. :extract-chosen-fn :value
  838. :extract-fn (fn [x] (or (:label-value x) (:label x)))
  839. :content-props content-props
  840. :on-chosen on-chosen
  841. :input-opts (fn [_]
  842. {:on-blur (fn []
  843. (when-let [f (:on-chosen select-opts)] (f)))
  844. :on-click (fn []
  845. (when *show-new-property-config?
  846. (reset! *show-new-property-config? false)))
  847. :on-key-down
  848. (fn [e]
  849. (case (util/ekey e)
  850. "Escape"
  851. (when-let [f (:on-chosen select-opts)] (f))
  852. nil))})})))))
  853. (rum/defcs property-normal-block-value <
  854. {:init (fn [state]
  855. (assoc state :container-id (state/get-next-container-id)))}
  856. [state block property value-block]
  857. (let [container-id (:container-id state)
  858. multiple-values? (db-property/many? property)
  859. block-container (state/get-component :block/container)
  860. blocks-container (state/get-component :block/blocks-container)
  861. value-block (if (and (coll? value-block) (every? de/entity? value-block))
  862. (set (remove #(= (:db/ident %) :logseq.property/empty-placeholder) value-block))
  863. value-block)
  864. default-value (:logseq.property/default-value property)
  865. default-value? (and
  866. (:db/id default-value)
  867. (= (:db/id value-block) (:db/id default-value))
  868. (not= (:db/ident property) :logseq.property/default-value))]
  869. (if (seq value-block)
  870. [:div.property-block-container.content.w-full
  871. (let [config {:id (str (if multiple-values?
  872. (:block/uuid block)
  873. (:block/uuid value-block)))
  874. :container-id container-id
  875. :editor-box (state/get-component :editor/box)
  876. :property-block? true
  877. :on-block-content-pointer-down (when default-value?
  878. (fn [_e]
  879. (<create-new-block! block property (or (:block/title default-value) ""))))}]
  880. (if (set? value-block)
  881. (blocks-container config (ldb/sort-by-order value-block))
  882. (rum/with-key
  883. (block-container (assoc config :property-default-value? default-value?) value-block)
  884. (str (:db/id property) "-" (:block/uuid value-block)))))]
  885. [:div
  886. {:tabIndex 0
  887. :on-click (fn [] (<create-new-block! block property ""))}
  888. (property-empty-btn-value property)])))
  889. (rum/defcs property-block-value < rum/reactive db-mixins/query
  890. {:init (fn [state]
  891. (let [block (first (:rum/args state))]
  892. (when-let [block-id (or (:db/id block) (:block/uuid block))]
  893. (db-async/<get-block (state/get-current-repo) block-id :children? true)))
  894. state)}
  895. [state value block property page-cp]
  896. (when value
  897. (if (state/sub-async-query-loading (:block/uuid value))
  898. [:div.text-sm.opacity-70 "loading"]
  899. (if-let [v-block (db/sub-block (:db/id value))]
  900. (let [class? (ldb/class? v-block)
  901. invalid-warning [:div.warning.text-sm
  902. "Invalid block value, please delete the current property."]]
  903. (when v-block
  904. (cond
  905. (:block/page v-block)
  906. (property-normal-block-value block property v-block)
  907. ;; page/class/etc.
  908. (entity-util/page? v-block)
  909. (rum/with-key
  910. (page-cp {:disable-preview? true
  911. :tag? class?} v-block)
  912. (:db/id v-block))
  913. :else
  914. invalid-warning)))
  915. (property-empty-btn-value property)))))
  916. (rum/defc closed-value-item < rum/reactive db-mixins/query
  917. [value {:keys [inline-text icon?]}]
  918. (when value
  919. (let [eid (if (de/entity? value) (:db/id value) [:block/uuid value])]
  920. (when-let [block (db/sub-block (:db/id (db/entity eid)))]
  921. (let [property-block? (db-property/property-created-block? block)
  922. value' (db-property/closed-value-content block)
  923. icon (pu/get-block-property-value block :logseq.property/icon)]
  924. (cond
  925. icon
  926. (if icon?
  927. (icon-component/icon icon {:color? true})
  928. [:div.flex.flex-row.items-center.gap-1.h-6
  929. (icon-component/icon icon {:color? true})
  930. (when value'
  931. [:span value'])])
  932. property-block?
  933. value'
  934. (= type :number)
  935. [:span.number (str value')]
  936. :else
  937. (inline-text {} :markdown (str value'))))))))
  938. (rum/defc select-item
  939. [property type value {:keys [page-cp inline-text other-position? property-position _icon?] :as opts}]
  940. (let [closed-values? (seq (:property/closed-values property))
  941. tag? (or (:tag? opts) (= (:db/ident property) :block/tags))
  942. inline-text-cp (fn [content]
  943. [:div.flex.flex-row.items-center
  944. (inline-text {} :markdown (macro-util/expand-value-if-macro content (state/get-macros)))])]
  945. [:div.select-item.cursor-pointer
  946. (cond
  947. (= value :logseq.property/empty-placeholder)
  948. (property-empty-btn-value property)
  949. closed-values?
  950. (closed-value-item value opts)
  951. (or (entity-util/page? value)
  952. (and (seq (:block/tags value))
  953. ;; FIXME: page-cp should be renamed to node-cp and
  954. ;; support this case and maybe other complex cases.
  955. (not (string/includes? (:block/title value) "[["))))
  956. (when value
  957. (rum/with-key
  958. (page-cp {:disable-preview? true
  959. :tag? tag?
  960. :property-position property-position
  961. :meta-click? other-position?} value)
  962. (:db/id value)))
  963. (contains? #{:node :class :property :page} type)
  964. (when-let [reference (state/get-component :block/reference)]
  965. (reference {} (:block/uuid value)))
  966. (de/entity? value)
  967. (when-some [content (str (db-property/property-value-content value))]
  968. (inline-text-cp content))
  969. :else
  970. (inline-text-cp (str value)))]))
  971. (rum/defc single-value-select
  972. [block property value value-f select-opts opts]
  973. (let [*el (rum/use-ref nil)]
  974. ;; Open popover initially when editing a property
  975. (hooks/use-effect!
  976. (fn []
  977. (when (:editing? opts)
  978. (.click (rum/deref *el))))
  979. [(:editing? opts)])
  980. (let [type (:logseq.property/type property)
  981. select-opts' (assoc select-opts :multiple-choices? false)
  982. popup-content (fn content-fn [_]
  983. [:div.property-select
  984. (case type
  985. (:entity :number :default :url)
  986. (select block property select-opts' opts)
  987. (:node :class :property :page :date)
  988. (property-value-select-node block property select-opts' opts))])
  989. trigger-id (str "trigger-" (:container-id opts) "-" (:db/id block) "-" (:db/id property))
  990. show! (fn [e]
  991. (let [target (.-target e)]
  992. (when-not (or config/publishing?
  993. (util/shift-key? e)
  994. (util/meta-key? e)
  995. (util/link? target)
  996. (when-let [node (.closest target "a")]
  997. (not (or (d/has-class? node "page-ref")
  998. (d/has-class? node "tag")))))
  999. (shui/popup-show! target popup-content
  1000. {:align "start"
  1001. :as-dropdown? true
  1002. :auto-focus? true
  1003. :trigger-id trigger-id}))))]
  1004. (shui/trigger-as
  1005. (if (:other-position? opts) :div.jtrigger :div.jtrigger.flex.flex-1.w-full)
  1006. {:ref *el
  1007. :id trigger-id
  1008. :tabIndex 0
  1009. :on-click show!}
  1010. (if (string/blank? value)
  1011. (property-empty-text-value property opts)
  1012. (value-f))))))
  1013. (defn- property-value-inner
  1014. [block property value {:keys [inline-text page-cp
  1015. dom-id row?]}]
  1016. (let [multiple-values? (db-property/many? property)
  1017. class (str (when-not row? "flex flex-1 ")
  1018. (when multiple-values? "property-value-content"))
  1019. type (:logseq.property/type property)
  1020. text-ref-type? (db-property-type/text-ref-property-types type)]
  1021. [:div.cursor-text
  1022. {:id (or dom-id (random-uuid))
  1023. :tabIndex 0
  1024. :class (str class " " (when-not text-ref-type? "jtrigger"))
  1025. :style {:min-height 24}
  1026. :on-click (fn []
  1027. (when (and text-ref-type? (nil? value))
  1028. (<create-new-block! block property "")))}
  1029. (cond
  1030. (and (= :logseq.property/default-value (:db/ident property)) (nil? (:block/title value)))
  1031. [:div.jtrigger.cursor-pointer.text-sm.px-2 "Set default value"]
  1032. (and text-ref-type? (nil? (:block/title value)))
  1033. [:div.jtrigger (property-empty-btn-value property)]
  1034. text-ref-type?
  1035. (property-block-value value block property page-cp)
  1036. :else
  1037. (inline-text {} :markdown (macro-util/expand-value-if-macro (str value) (state/get-macros))))]))
  1038. (rum/defcs property-scalar-value < rum/static rum/reactive
  1039. [state block property value* {:keys [container-id editing? on-chosen]
  1040. :as opts}]
  1041. (let [property (model/sub-block (:db/id property))
  1042. type (:logseq.property/type property)
  1043. editing? (or editing?
  1044. (and (state/sub-editing? [container-id (:block/uuid block)])
  1045. (= (:db/id property) (:db/id (:property (state/get-editor-action-data))))))
  1046. select-type?' (select-type? block property type)
  1047. closed-values? (seq (:property/closed-values property))
  1048. select-opts {:on-chosen on-chosen}
  1049. value (if (and (de/entity? value*) (= (:db/ident value*) :logseq.property/empty-placeholder))
  1050. nil
  1051. value*)]
  1052. (if (= :logseq.property/icon (:db/ident property))
  1053. (icon-row block editing?)
  1054. (if (and select-type?'
  1055. (not (and (not closed-values?) (= type :date))))
  1056. (let [classes (outliner-property/get-block-classes (db/get-db) (:db/id block))
  1057. display-as-checkbox? (and (some
  1058. (fn [block]
  1059. (-> (set (map :db/id (:logseq.property/checkbox-display-properties block)))
  1060. (contains? (:db/id property))))
  1061. (conj classes block))
  1062. (seq (:property/closed-values property))
  1063. (boolean? (:logseq.property/choice-checkbox-state value*)))]
  1064. (if display-as-checkbox?
  1065. (let [checked? (:logseq.property/choice-checkbox-state value*)]
  1066. (shui/checkbox {:checked checked?
  1067. :class "mt-1"
  1068. :on-checked-change (fn [value]
  1069. (let [choices (:property/closed-values property)
  1070. choice (some (fn [choice] (when (= value (:logseq.property/choice-checkbox-state choice))
  1071. choice)) choices)]
  1072. (when choice
  1073. (db-property-handler/set-block-property! (:db/id block) (:db/ident property) (:db/id choice)))))}))
  1074. (single-value-select block property value
  1075. (fn [] (select-item property type value opts))
  1076. select-opts
  1077. (assoc opts :editing? editing?))))
  1078. (case type
  1079. (:date :datetime)
  1080. (property-value-date-picker block property value (merge opts {:editing? editing?}))
  1081. :checkbox
  1082. (let [add-property! (fn []
  1083. (let [value' (boolean (not value))]
  1084. (<add-property! block (:db/ident property) value' opts)
  1085. (when-let [on-checked-change (:on-checked-change opts)]
  1086. (on-checked-change value'))))]
  1087. [:label.flex.w-full.as-scalar-value-wrap.cursor-pointer
  1088. (shui/checkbox {:class "jtrigger flex flex-row items-center"
  1089. :disabled config/publishing?
  1090. :auto-focus editing?
  1091. :checked value
  1092. :on-checked-change add-property!
  1093. :on-key-down (fn [e]
  1094. (when (= (util/ekey e) "Enter")
  1095. (add-property!)))})])
  1096. ;; :others
  1097. [:div.flex.flex-1
  1098. (property-value-inner block property value opts)])))))
  1099. (rum/defc multiple-values-inner
  1100. [block property v {:keys [on-chosen editing?] :as opts}]
  1101. (let [type (:logseq.property/type property)
  1102. date? (= type :date)
  1103. *el (rum/use-ref nil)
  1104. items (cond->> (if (de/entity? v) #{v} v)
  1105. (= (:db/ident property) :block/tags)
  1106. (remove (fn [v] (contains? ldb/hidden-tags (:db/ident v)))))]
  1107. (hooks/use-effect!
  1108. (fn []
  1109. (when editing?
  1110. (.click (rum/deref *el))))
  1111. [editing?])
  1112. (let [select-cp (fn [select-opts]
  1113. (let [select-opts (merge {:multiple-choices? true
  1114. :on-chosen (fn []
  1115. (when on-chosen (on-chosen)))}
  1116. select-opts
  1117. {:dropdown? false})]
  1118. [:div.property-select
  1119. (if (contains? #{:node :page :class :property} type)
  1120. (property-value-select-node block property
  1121. select-opts
  1122. opts)
  1123. (select block property select-opts opts))]))]
  1124. (let [toggle-fn shui/popup-hide!
  1125. content-fn (fn [{:keys [_id content-props]}]
  1126. (select-cp {:content-props content-props}))]
  1127. [:div.multi-values.jtrigger
  1128. {:tab-index "0"
  1129. :ref *el
  1130. :on-click (fn [^js e]
  1131. (let [target (.-target e)]
  1132. (when-not (or (util/link? target) (.closest target "a") config/publishing?)
  1133. (shui/popup-show! (rum/deref *el) content-fn
  1134. {:as-dropdown? true :as-content? false
  1135. :align "start" :auto-focus? true}))))
  1136. :on-key-down (fn [^js e]
  1137. (case (.-key e)
  1138. (" " "Enter")
  1139. (do (some-> (rum/deref *el) (.click))
  1140. (util/stop e))
  1141. :dune))
  1142. :class "flex flex-1 flex-row items-center flex-wrap gap-x-2 gap-y-2"}
  1143. (let [not-empty-value? (not= (map :db/ident items) [:logseq.property/empty-placeholder])]
  1144. (if (and (seq items) not-empty-value?)
  1145. (concat
  1146. (->> (for [item items]
  1147. (rum/with-key (select-item property type item opts) (or (:block/uuid item) (str item))))
  1148. (interpose [:span.opacity-50.-ml-2 ","]))
  1149. (when date?
  1150. [(property-value-date-picker block property nil {:toggle-fn toggle-fn})]))
  1151. (if date?
  1152. (property-value-date-picker block property nil {:toggle-fn toggle-fn})
  1153. (property-empty-text-value property opts))))]))))
  1154. (rum/defc multiple-values < rum/reactive db-mixins/query
  1155. [block property opts]
  1156. (let [block (db/sub-block (:db/id block))
  1157. value (get block (:db/ident property))
  1158. value' (if (coll? value) value
  1159. (when (some? value) #{value}))]
  1160. (multiple-values-inner block property value' opts)))
  1161. (rum/defcs property-value < rum/reactive db-mixins/query
  1162. [state block property {:keys [show-tooltip?]
  1163. :as opts}]
  1164. (ui/catch-error
  1165. (ui/block-error "Something wrong" {})
  1166. (let [block (db/sub-block (:db/id block))
  1167. block-cp (state/get-component :block/blocks-container)
  1168. properties-cp (state/get-component :block/properties-cp)
  1169. opts (merge opts
  1170. {:page-cp (state/get-component :block/page-cp)
  1171. :inline-text (state/get-component :block/inline-text)
  1172. :editor-box (state/get-component :editor/box)
  1173. :block-cp block-cp
  1174. :properties-cp :properties-cp})
  1175. dom-id (str "ls-property-" (:db/id block) "-" (:db/id property))
  1176. editor-id (str dom-id "-editor")
  1177. type (:logseq.property/type property)
  1178. multiple-values? (db-property/many? property)
  1179. v (get block (:db/ident property))
  1180. v (cond
  1181. (and multiple-values? (or (set? v) (and (coll? v) (empty? v)) (nil? v)))
  1182. v
  1183. multiple-values?
  1184. #{v}
  1185. (set? v)
  1186. (first v)
  1187. :else
  1188. v)
  1189. empty-value? (when (coll? v) (= :logseq.property/empty-placeholder (:db/ident (first v))))
  1190. closed-values? (seq (:property/closed-values property))
  1191. property-ident (:db/ident property)
  1192. value-cp [:div.property-value-inner
  1193. {:data-type type
  1194. :class (str (when empty-value? "empty-value")
  1195. (when-not (:other-position? opts) " w-full"))}
  1196. (cond
  1197. (= property-ident :logseq.property.class/properties)
  1198. (properties-cp {} block {:selected? false
  1199. :class-schema? true})
  1200. (and multiple-values? (contains? #{:default :url} type) (not closed-values?))
  1201. (property-normal-block-value block property v)
  1202. multiple-values?
  1203. (multiple-values block property opts)
  1204. :else
  1205. (let [parent? (= property-ident :logseq.property/parent)
  1206. value-cp (property-scalar-value block property v
  1207. (merge
  1208. opts
  1209. {:editor-id editor-id
  1210. :dom-id dom-id}))
  1211. page-ancestors (when parent?
  1212. (let [ancestor-pages (loop [parents [block]]
  1213. (if-let [parent (:logseq.property/parent (last parents))]
  1214. (when-not (contains? (set parents) parent)
  1215. (recur (conj parents parent)))
  1216. parents))]
  1217. (->> (reverse ancestor-pages)
  1218. (remove (fn [e] (= (:db/id block) (:db/id e))))
  1219. butlast)))]
  1220. (if (seq page-ancestors)
  1221. [:div.flex.flex-1.items-center.gap-1
  1222. (interpose [:span.opacity-50.text-sm " > "]
  1223. (concat
  1224. (map (fn [{title :block/title :as ancestor}]
  1225. [:a.whitespace-nowrap {:on-click #(route-handler/redirect-to-page! (:block/uuid ancestor))} title])
  1226. page-ancestors)
  1227. [value-cp]))]
  1228. value-cp)))]]
  1229. (if show-tooltip?
  1230. (shui/tooltip-provider
  1231. (shui/tooltip
  1232. {:delayDuration 1200}
  1233. (shui/tooltip-trigger
  1234. {:onFocusCapture #(util/stop-propagation %)} value-cp)
  1235. (shui/tooltip-content
  1236. (str "Change " (:block/title property)))))
  1237. value-cp))))