value.cljs 76 KB

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