value.cljs 37 KB

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