property.cljs 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746
  1. (ns frontend.handler.db-based.property
  2. "Properties handler for db graphs"
  3. (:require [clojure.string :as string]
  4. [frontend.db :as db]
  5. [frontend.db.model :as model]
  6. [frontend.format.block :as block]
  7. [frontend.handler.notification :as notification]
  8. [frontend.handler.db-based.property.util :as db-pu]
  9. [logseq.outliner.core :as outliner-core]
  10. [frontend.util :as util]
  11. [frontend.state :as state]
  12. [logseq.common.util :as common-util]
  13. [logseq.db.sqlite.util :as sqlite-util]
  14. [logseq.db.frontend.property.type :as db-property-type]
  15. [logseq.db.frontend.property.util :as db-property-util]
  16. [malli.util :as mu]
  17. [malli.error :as me]
  18. [logseq.common.util.page-ref :as page-ref]
  19. [datascript.impl.entity :as e]
  20. [logseq.db.frontend.property :as db-property]
  21. [frontend.handler.property.util :as pu]
  22. [promesa.core :as p]
  23. [frontend.db.async :as db-async]
  24. [logseq.db :as ldb]))
  25. ;; schema -> type, cardinality, object's class
  26. ;; min, max -> string length, number range, cardinality size limit
  27. (defn built-in-validation-schemas
  28. "A frontend version of built-in-validation-schemas that adds the current database to
  29. schema fns"
  30. [property & {:keys [new-closed-value?]
  31. :or {new-closed-value? false}}]
  32. (into {}
  33. (map (fn [[property-type property-val-schema]]
  34. (cond
  35. (db-property-type/closed-value-property-types property-type)
  36. (let [[_ schema-opts schema-fn] property-val-schema
  37. schema-fn' (if (db-property-type/property-types-with-db property-type) #(schema-fn (db/get-db) %) schema-fn)]
  38. [property-type [:fn
  39. schema-opts
  40. #((db-property-type/type-or-closed-value? schema-fn') (db/get-db) property % new-closed-value?)]])
  41. (db-property-type/property-types-with-db property-type)
  42. (let [[_ schema-opts schema-fn] property-val-schema]
  43. [property-type [:fn schema-opts #(schema-fn (db/get-db) %)]])
  44. :else
  45. [property-type property-val-schema]))
  46. db-property-type/built-in-validation-schemas)))
  47. (defn- fail-parse-long
  48. [v-str]
  49. (let [result (parse-long v-str)]
  50. (or result
  51. (throw (js/Error. (str "Can't convert \"" v-str "\" to a number"))))))
  52. (defn- fail-parse-double
  53. [v-str]
  54. (let [result (parse-double v-str)]
  55. (or result
  56. (throw (js/Error. (str "Can't convert \"" v-str "\" to a number"))))))
  57. (defn- infer-schema-from-input-string
  58. [v-str]
  59. (try
  60. (cond
  61. (fail-parse-long v-str) :number
  62. (fail-parse-double v-str) :number
  63. (util/uuid-string? v-str) :page
  64. (common-util/url? v-str) :url
  65. (contains? #{"true" "false"} (string/lower-case v-str)) :checkbox
  66. :else :default)
  67. (catch :default _e
  68. :default)))
  69. (defn convert-property-input-string
  70. [schema-type v-str]
  71. (if (and (not (string? v-str)) (not (object? v-str)))
  72. v-str
  73. (case schema-type
  74. :number
  75. (fail-parse-double v-str)
  76. :page
  77. (uuid v-str)
  78. ;; these types don't need to be translated. :date expects uuid and other
  79. ;; types usually expect text
  80. (:url :date :any)
  81. v-str
  82. ;; :default
  83. (if (util/uuid-string? v-str) (uuid v-str) v-str))))
  84. (defn upsert-property!
  85. [repo property-id schema {:keys [property-name]}]
  86. (let [db-ident (or property-id (db-property/get-db-ident-from-name property-name))
  87. property (db/entity db-ident)
  88. k-name (or (:block/original-name property) (name property-name))]
  89. (if property
  90. (db/transact! repo [(cond->
  91. (outliner-core/block-with-updated-at
  92. {:db/ident db-ident
  93. :block/schema schema})
  94. (= :many (:cardinality schema))
  95. (assoc :db/cardinality :db.cardinality/many))]
  96. {:outliner-op :save-block})
  97. (db/transact! repo [(sqlite-util/build-new-property k-name schema {:db-ident db-ident})]
  98. {:outliner-op :new-property}))))
  99. (defn validate-property-value
  100. [schema value]
  101. (me/humanize (mu/explain-data schema value)))
  102. (defn- reset-block-property-multiple-values!
  103. [repo block-id property-id values _opts]
  104. (let [block (db/entity repo [:block/uuid block-id])
  105. property (db/entity property-id)
  106. property-name (:block/original-name property)
  107. values (remove nil? values)
  108. property-schema (:block/schema property)
  109. {:keys [type cardinality]} property-schema
  110. multiple-values? (= cardinality :many)]
  111. (when (and multiple-values? (seq values))
  112. (let [infer-schema (when-not type (infer-schema-from-input-string (first values)))
  113. property-type (or type infer-schema :default)
  114. schema (get (built-in-validation-schemas property) property-type)
  115. values' (try
  116. (set (map #(convert-property-input-string property-type %) values))
  117. (catch :default e
  118. (notification/show! (str e) :error false)
  119. nil))
  120. tags-or-alias? (contains? #{:block/tags :block/alias} property-id)
  121. old-values (if tags-or-alias?
  122. (->> (get block property-id)
  123. (map (fn [e] (:db/id e))))
  124. (get block (:db/ident property)))]
  125. (when (not= old-values values')
  126. (if tags-or-alias?
  127. (db/transact! repo
  128. [[:db/retract (:db/id block) property-id]
  129. {:block/uuid block-id
  130. property-id values'}]
  131. {:outliner-op :save-block})
  132. (if-let [msg (some #(validate-property-value schema %) values')]
  133. (let [msg' (str "\"" property-name "\"" " " (if (coll? msg) (first msg) msg))]
  134. (notification/show! msg' :warning))
  135. (do
  136. (upsert-property! repo property-id (assoc property-schema :type property-type) {})
  137. (let [block {:block/uuid (:block/uuid block)
  138. property-id values'}]
  139. (db/transact! repo [block] {:outliner-op :save-block}))))))))))
  140. (defn- resolve-tag
  141. "Change `v` to a tag's db id if v is a string tag, e.g. `#book`"
  142. [v]
  143. (when (and (string? v)
  144. (util/tag? (string/trim v)))
  145. (let [tag-without-hash (common-util/safe-subs (string/trim v) 1)
  146. tag (or (page-ref/get-page-name tag-without-hash) tag-without-hash)]
  147. (when-not (string/blank? tag)
  148. (let [e (db/entity [:block/name (util/page-name-sanity-lc tag)])
  149. e' (if e
  150. (do
  151. (when-not (contains? (:block/type e) "tag")
  152. (db/transact! [{:db/id (:db/id e)
  153. :block/type (set (conj (:block/type e) "class"))}]))
  154. e)
  155. (let [m (assoc (block/page-name->map tag true)
  156. :block/type #{"class"})]
  157. (db/transact! [m])
  158. m))]
  159. (:db/id e'))))))
  160. (defn set-block-property!
  161. [repo block-eid property-id v {:keys [old-value] :as opts}]
  162. (let [property-id (if (string? property-id)
  163. (db-property/get-db-ident-from-name property-id)
  164. property-id)
  165. _ (assert (keyword? property-id) "property-id should be a keyword")
  166. block (db/entity repo block-eid)
  167. property (db/entity property-id)
  168. k-name (:block/original-name property)
  169. property-schema (:block/schema property)
  170. {:keys [type cardinality]} property-schema
  171. multiple-values? (= cardinality :many)
  172. v (or (resolve-tag v) v)]
  173. (if (and multiple-values? (coll? v))
  174. (reset-block-property-multiple-values! repo block-eid property-id v opts)
  175. (let [v (if property v (or v ""))]
  176. (when (some? v)
  177. (let [infer-schema (when-not type (infer-schema-from-input-string v))
  178. property-type (or type infer-schema :default)
  179. schema (get (built-in-validation-schemas property) property-type)
  180. value (when-let [id (:db/ident property)]
  181. (get block id))
  182. v* (if (= v :property/empty-placeholder)
  183. v
  184. (try
  185. (convert-property-input-string property-type v)
  186. (catch :default e
  187. (js/console.error e)
  188. (notification/show! (str e) :error false)
  189. nil)))
  190. tags-or-alias? (and (contains? #{:block/tags :block/alias} property-id) (uuid? v*))]
  191. (if tags-or-alias?
  192. (let [property-value-id v*]
  193. (db/transact! repo
  194. [[:db/add (:db/id block) property-id property-value-id]]
  195. {:outliner-op :save-block}))
  196. (when-not (contains? (if (set? value) value #{value}) v*)
  197. (if-let [msg (when-not (= v* :property/empty-placeholder) (validate-property-value schema v*))]
  198. (let [msg' (str "\"" k-name "\"" " " (if (coll? msg) (first msg) msg))]
  199. (notification/show! msg' :warning))
  200. (let [_ (upsert-property! repo property-id (assoc property-schema :type property-type) {})
  201. status? (= :logseq.property/status (:db/ident property))
  202. value (if (= value :property/empty-placeholder) [] value)
  203. new-value (cond
  204. (and multiple-values? old-value
  205. (not= old-value :frontend.components.property/new-value-placeholder))
  206. (if (coll? v*)
  207. (vec (distinct (concat value v*)))
  208. (let [v (mapv (fn [x] (if (= x old-value) v* x)) value)]
  209. (if (contains? (set v) v*)
  210. v
  211. (conj v v*))))
  212. multiple-values?
  213. (let [f (if (coll? v*) concat conj)]
  214. (f value v*))
  215. :else
  216. v*)
  217. ;; don't modify maps
  218. new-value (if (or (sequential? new-value) (set? new-value))
  219. (if (= :coll property-type)
  220. (vec (remove string/blank? new-value))
  221. (set (remove string/blank? new-value)))
  222. new-value)
  223. block (cond->
  224. {:block/uuid (:block/uuid block)
  225. property-id new-value}
  226. status?
  227. (assoc :block/tags [:logseq.class/task]))]
  228. (db/transact! repo [block] {:outliner-op :save-block})))))))))))
  229. (defn <update-property!
  230. [repo property-id {:keys [property-name property-schema properties]}]
  231. (assert (keyword? property-id) (str "property-id " property-id " is not a keyword"))
  232. (when-let [property (db/entity property-id)]
  233. (p/let [type (get-in property [:block/schema :type])
  234. type-changed? (and type (:type property-schema) (not= type (:type property-schema)))
  235. property-values (db-async/<get-block-property-values repo property-id)]
  236. (when (or (not type-changed?)
  237. ;; only change type if property hasn't been used yet
  238. (and (not (ldb/built-in? property)) (empty? property-values)))
  239. (let [tx-data (cond-> (merge {:db/ident property-id} properties)
  240. property-name (merge
  241. {:block/original-name property-name})
  242. property-schema (assoc :block/schema
  243. ;; a property must have a :type when making schema changes
  244. (merge {:type :default}
  245. property-schema))
  246. true outliner-core/block-with-updated-at)]
  247. (db/transact! repo [tx-data]
  248. {:outliner-op :save-block}))))))
  249. (defn class-add-property!
  250. [repo class-uuid property-id]
  251. (when-let [class (db/entity repo [:block/uuid class-uuid])]
  252. (when (contains? (:block/type class) "class")
  253. (let [property (db/entity property-id)
  254. property-type (get-in property [:block/schema :type])
  255. _ (upsert-property! repo property-id
  256. (cond-> (:block/schema property)
  257. (some? property-type)
  258. (assoc :type property-type))
  259. {})]
  260. (db/transact! repo
  261. [[:db/add (:db/id class) :class/schema.properties property-id]]
  262. {:outliner-op :save-block})))))
  263. (defn class-remove-property!
  264. [repo class-uuid property-id]
  265. (when-let [class (db/entity repo [:block/uuid class-uuid])]
  266. (when (contains? (:block/type class) "class")
  267. (when-let [property (db/entity repo property-id)]
  268. (when-not (ldb/built-in-class-property? class property)
  269. (db/transact! repo [[:db/retract (:db/id class) :class/schema.properties property-id]]
  270. {:outliner-op :save-block}))))))
  271. (defn class-set-schema!
  272. [repo class-uuid schema]
  273. (when-let [class (db/entity repo [:block/uuid class-uuid])]
  274. (when (contains? (:block/type class) "class")
  275. (db/transact! repo [{:db/id (:db/id class)
  276. :block/schema schema}]
  277. {:outliner-op :save-block}))))
  278. (defn- ->eid
  279. [id]
  280. (if (uuid? id) [:block/uuid id] id))
  281. (defn batch-set-property!
  282. "Notice that this works only for properties with cardinality equals to `one`."
  283. [repo block-ids property-id v]
  284. (assert property-id "property-id is nil")
  285. (let [block-eids (map ->eid block-ids)
  286. property (db/entity property-id)
  287. type (:type (:block/schema property))
  288. infer-schema (when-not type (infer-schema-from-input-string v))
  289. property-type (or type infer-schema :default)
  290. {:keys [cardinality]} (:block/schema property)
  291. status? (= :logseq.property/status (:db/ident property))
  292. txs (mapcat
  293. (fn [eid]
  294. (when-let [block (db/entity eid)]
  295. (when (and (some? v) (not= cardinality :many))
  296. (when-let [v* (try
  297. (convert-property-input-string property-type v)
  298. (catch :default e
  299. (notification/show! (str e) :error false)
  300. nil))]
  301. [{:block/uuid (:block/uuid block)
  302. property-id v*}
  303. (when status?
  304. [:db/add (:db/id block) :block/tags :logseq.class/task])]))))
  305. block-eids)]
  306. (when (seq txs)
  307. (db/transact! repo txs {:outliner-op :save-block}))))
  308. (defn batch-remove-property!
  309. [repo block-ids property-id]
  310. (let [block-eids (map ->eid block-ids)]
  311. (when-let [property (db/entity property-id)]
  312. (let [txs (mapcat
  313. (fn [eid]
  314. (when-let [block (db/entity eid)]
  315. (when (get block property-id)
  316. (let [value (get block property-id)
  317. block-value? (and (= :default (get-in property [:block/schema :type] :default))
  318. (uuid? value))
  319. property-block (when block-value? (db/entity [:block/uuid value]))
  320. retract-blocks-tx (when (and property-block
  321. (some? (get property-block :logseq.property/created-from-block))
  322. (some? (get property-block :logseq.property/created-from-property)))
  323. (let [txs-state (atom [])]
  324. (outliner-core/delete-block repo
  325. (db/get-db false)
  326. txs-state
  327. (outliner-core/->Block property-block)
  328. {:children? true})
  329. @txs-state))]
  330. (concat
  331. [[:db/retract (:db/id block) property-id]]
  332. retract-blocks-tx)))))
  333. block-eids)]
  334. (when (seq txs)
  335. (db/transact! repo txs {:outliner-op :save-block}))))))
  336. (defn remove-block-property!
  337. [repo eid property-id]
  338. (prn :debug :eid eid :property-id property-id)
  339. (if (contains? #{:block/alias :block/tags} property-id)
  340. (when-let [block (db/entity eid)]
  341. (db/transact! repo
  342. [[:db/retract (:db/id block) property-id]]
  343. {:outliner-op :save-block}))
  344. (batch-remove-property! repo [eid] property-id)))
  345. (defn delete-property-value!
  346. "Delete value if a property has multiple values"
  347. [repo block property-id property-value]
  348. (when block
  349. (when (not= property-id (:db/ident block))
  350. (when-let [property (db/entity property-id)]
  351. (let [schema (:block/schema property)
  352. db-ident (:db/ident property)
  353. tags-or-alias? (contains? #{:block/tags :block/alias} db-ident)]
  354. (if tags-or-alias?
  355. (db/transact! repo
  356. [[:db/retract (:db/id block) db-ident property-value]]
  357. {:outliner-op :save-block})
  358. (if (= :many (:cardinality schema))
  359. (db/transact! repo
  360. [[:db/retract (:db/id block) property-id]]
  361. {:outliner-op :save-block})
  362. (if (= :default (get-in property [:block/schema :type]))
  363. (set-block-property! repo (:db/id block)
  364. (:db/ident property)
  365. ""
  366. {})
  367. (remove-block-property! repo (:db/id block) property-id)))))))))
  368. (defn replace-key-with-id
  369. "Notice: properties need to be created first"
  370. [m]
  371. (zipmap
  372. (map (fn [k]
  373. (if (uuid? k)
  374. k
  375. (let [property-id (db-pu/get-user-property-uuid k)]
  376. (when-not property-id
  377. (throw (ex-info "Property not exists yet"
  378. {:key k})))
  379. property-id)))
  380. (keys m))
  381. (vals m)))
  382. (defn collapse-expand-property!
  383. "Notice this works only if the value itself if a block (property type should be either :default or :template)"
  384. [repo block property collapse?]
  385. (let [f (if collapse? :db/add :db/retract)]
  386. (db/transact! repo
  387. [[f (:db/id block) :block/collapsed-properties (:db/id property)]]
  388. {:outliner-op :save-block})))
  389. (defn- get-namespace-parents
  390. [tags]
  391. (let [tags' (filter (fn [tag] (contains? (:block/type tag) "class")) tags)
  392. *namespaces (atom #{})]
  393. (doseq [tag tags']
  394. (when-let [ns (:block/namespace tag)]
  395. (loop [current-ns ns]
  396. (when (and
  397. current-ns
  398. (contains? (:block/type ns) "class")
  399. (not (contains? @*namespaces (:db/id ns))))
  400. (swap! *namespaces conj current-ns)
  401. (recur (:block/namespace current-ns))))))
  402. @*namespaces))
  403. (defn get-block-classes-properties
  404. [eid]
  405. (let [block (db/entity eid)
  406. classes (->> (:block/tags block)
  407. (sort-by :block/name)
  408. (filter (fn [tag] (contains? (:block/type tag) "class"))))
  409. namespace-parents (get-namespace-parents classes)
  410. all-classes (->> (concat classes namespace-parents)
  411. (filter (fn [class]
  412. (seq (:class/schema.properties class)))))
  413. all-properties (-> (mapcat (fn [class]
  414. (map :db/ident (:class/schema.properties class))) all-classes)
  415. distinct)]
  416. {:classes classes
  417. :all-classes all-classes ; block own classes + parent classes
  418. :classes-properties all-properties}))
  419. (defn- closed-value-other-position?
  420. [property-id block]
  421. (and
  422. (some? (get block property-id))
  423. (let [schema (:block/schema (db/entity property-id))]
  424. (= (:position schema) "block-beginning"))))
  425. (defn get-block-other-position-properties
  426. [eid]
  427. (let [block (db/entity eid)
  428. own-properties (filter db-property/property? (keys block))]
  429. (->> (:classes-properties (get-block-classes-properties eid))
  430. (concat own-properties)
  431. (filter (fn [id] (closed-value-other-position? id block)))
  432. (distinct))))
  433. (defn block-has-viewable-properties?
  434. [block-entity]
  435. (let [properties (->> (keys block-entity) (filter db-property/property?))]
  436. (or
  437. (seq (:block/alias block-entity))
  438. (and (seq properties)
  439. (not= properties [:logseq.property/icon])))))
  440. (defn property-create-new-block
  441. [block property value parse-block]
  442. (let [current-page-id (:block/uuid (or (:block/page block) block))
  443. page-name (str "$$$" current-page-id)
  444. page-entity (db/entity [:block/name page-name])
  445. page (or page-entity
  446. (-> (block/page-name->map page-name true)
  447. (assoc :block/type #{"hidden"}
  448. :block/format :markdown
  449. :logseq.property/source-page-id current-page-id)))
  450. page-tx (when-not page-entity page)
  451. page-id [:block/uuid (:block/uuid page)]
  452. parent-id (db/new-block-id)
  453. parent (-> {:block/uuid parent-id
  454. :block/format :markdown
  455. :block/content ""
  456. :block/page page-id
  457. :block/parent page-id
  458. :block/left (or (when page-entity (model/get-block-last-direct-child-id (db/get-db) (:db/id page-entity)))
  459. page-id)
  460. :logseq.property/created-from-block [:block/uuid (:block/uuid block)]
  461. :logseq.property/created-from-property [:block/uuid (:block/uuid property)]}
  462. sqlite-util/block-with-timestamps)
  463. child-1-id (db/new-block-id)
  464. child-1 (-> {:block/uuid child-1-id
  465. :block/format :markdown
  466. :block/content value
  467. :block/page page-id
  468. :block/parent [:block/uuid parent-id]
  469. :block/left [:block/uuid parent-id]}
  470. sqlite-util/block-with-timestamps
  471. parse-block)]
  472. {:page page-tx
  473. :blocks [parent child-1]}))
  474. (defn create-property-text-block!
  475. [block property value parse-block {:keys [class-schema?]}]
  476. (let [repo (state/get-current-repo)
  477. {:keys [page blocks]} (property-create-new-block block property value parse-block)
  478. first-block (first blocks)
  479. last-block-id (:block/uuid (last blocks))
  480. class? (contains? (:block/type block) "class")
  481. property-id (:db/ident property)]
  482. (db/transact! repo (if page (cons page blocks) blocks) {:outliner-op :insert-blocks})
  483. (let [result (when property-id
  484. (if (and class? class-schema?)
  485. (class-add-property! repo (:db/id block) property-id)
  486. (set-block-property! repo (:db/id block) property-id (:block/uuid first-block) {})))]
  487. {:last-block-id last-block-id
  488. :result result})))
  489. (defn property-create-new-block-from-template
  490. [block property template]
  491. (let [current-page-id (:block/uuid (or (:block/page block) block))
  492. page-name (str "$$$" current-page-id)
  493. page-entity (db/entity [:block/name page-name])
  494. page (or page-entity
  495. (-> (block/page-name->map page-name true)
  496. (assoc :block/type #{"hidden"}
  497. :block/format :markdown
  498. :logseq.property/source-page-id current-page-id)))
  499. page-tx (when-not page-entity page)
  500. page-id [:block/uuid (:block/uuid page)]
  501. block-id (db/new-block-id)
  502. new-block (-> {:block/uuid block-id
  503. :block/format :markdown
  504. :block/content ""
  505. :block/tags #{(:db/id template)}
  506. :block/page page-id
  507. :block/parent page-id
  508. :block/left (or (when page-entity (model/get-block-last-direct-child-id (db/get-db) (:db/id page-entity)))
  509. page-id)
  510. :logseq.property/created-from-block [:block/uuid (:block/uuid block)]
  511. :logseq.property/created-from-property [:block/uuid (:block/uuid property)]
  512. :logseq.property/created-from-template [:block/uuid (:block/uuid template)]}
  513. sqlite-util/block-with-timestamps)]
  514. {:page page-tx
  515. :blocks [new-block]}))
  516. (defn- get-property-hidden-page
  517. [property]
  518. (let [page-name (str db-property-util/hidden-page-name-prefix (:block/uuid property))]
  519. (or (db/entity [:block/name page-name])
  520. (db-property-util/build-property-hidden-page property))))
  521. (defn re-init-commands!
  522. "Update commands after task status and priority's closed values has been changed"
  523. [property]
  524. (when (contains? #{:logseq.property/status :logseq.property/priority} (:db/ident property))
  525. (state/pub-event! [:init/commands])))
  526. (defn replace-closed-value
  527. [property new-id old-id]
  528. (assert (and (uuid? new-id) (uuid? old-id)))
  529. (let [schema (-> (:block/schema property)
  530. (update :values (fn [values]
  531. (vec (conj (remove #{old-id} values) new-id)))))]
  532. (db/transact! (state/get-current-repo)
  533. [{:db/id (:db/id property)
  534. :block/schema schema}]
  535. {:outliner-op :save-block})))
  536. (defn upsert-closed-value
  537. "id should be a block UUID or nil"
  538. [property {:keys [id value icon description]
  539. :or {description ""}}]
  540. (assert (or (nil? id) (uuid? id)))
  541. (let [property-type (get-in property [:block/schema :type] :default)]
  542. (when (contains? db-property-type/closed-value-property-types property-type)
  543. (let [property (db/entity (:db/id property))
  544. value (if (string? value) (string/trim value) value)
  545. property-schema (:block/schema property)
  546. closed-values (:values property-schema)
  547. block-values (map (fn [id] (db/entity [:block/uuid id])) closed-values)
  548. resolved-value (try
  549. (convert-property-input-string (:type property-schema) value)
  550. (catch :default e
  551. (js/console.error e)
  552. (notification/show! (str e) :error false)
  553. nil))
  554. block (when id (db/entity [:block/uuid id]))
  555. value-block (when (uuid? value) (db/entity [:block/uuid value]))
  556. validate-message (validate-property-value
  557. (get (built-in-validation-schemas property {:new-closed-value? true}) property-type)
  558. resolved-value)]
  559. (cond
  560. (some (fn [b] (and (= resolved-value (or (db-pu/property-value-when-closed b)
  561. (:block/uuid b)))
  562. (not= id (:block/uuid b)))) block-values)
  563. (do
  564. (notification/show! "Choice already exists" :warning)
  565. :value-exists)
  566. validate-message
  567. (do
  568. (notification/show! validate-message :warning)
  569. :value-invalid)
  570. (nil? resolved-value)
  571. nil
  572. (:block/name value-block) ; page
  573. (let [new-values (vec (conj closed-values value))]
  574. {:block-id value
  575. :tx-data [{:db/id (:db/id property)
  576. :block/schema (assoc property-schema :values new-values)}]})
  577. :else
  578. (let [block-id (or id (db/new-block-id))
  579. icon (when-not (and (string? icon) (string/blank? icon)) icon)
  580. description (string/trim description)
  581. description (when-not (string/blank? description) description)
  582. tx-data (if block
  583. [(let [schema (assoc (:block/schema block)
  584. :value resolved-value)]
  585. (cond->
  586. {:block/uuid id
  587. :block/schema (if description
  588. (assoc schema :description description)
  589. (dissoc schema :description))}
  590. icon
  591. (assoc :logseq.property/icon icon)))]
  592. (let [page (get-property-hidden-page property)
  593. page-tx (when-not (e/entity? page) page)
  594. page-id [:block/uuid (:block/uuid page)]
  595. new-block (db-property-util/build-closed-value-block block-id resolved-value page-id property {:icon icon
  596. :description description})
  597. new-values (vec (conj closed-values block-id))]
  598. (->> (cons page-tx [new-block
  599. {:db/id (:db/id property)
  600. :block/schema (merge {:type property-type}
  601. (assoc property-schema :values new-values))}])
  602. (remove nil?))))]
  603. {:block-id block-id
  604. :tx-data tx-data}))))))
  605. (defn <add-existing-values-to-closed-values!
  606. "Adds existing values as closed values and returns their new block uuids"
  607. [property values]
  608. (when (seq values)
  609. (let [values' (remove string/blank? values)
  610. property-schema (:block/schema property)]
  611. (if (every? uuid? values')
  612. (p/let [new-value-ids (vec (remove #(nil? (db/entity [:block/uuid %])) values'))]
  613. (when (seq new-value-ids)
  614. (let [property-tx {:db/id (:db/id property)
  615. :block/schema (assoc property-schema :values new-value-ids)}]
  616. (db/transact! (state/get-current-repo) [property-tx]
  617. {:outliner-op :insert-blocks})
  618. new-value-ids)))
  619. (p/let [property-id (:db/ident property)
  620. page (get-property-hidden-page property)
  621. page-tx (when-not (e/entity? page) page)
  622. page-id (:block/uuid page)
  623. closed-value-blocks (map (fn [value]
  624. (db-property-util/build-closed-value-block
  625. (db/new-block-id)
  626. value
  627. [:block/uuid page-id]
  628. property
  629. {}))
  630. values')
  631. value->block-id (zipmap
  632. (map #(get-in % [:block/schema :value]) closed-value-blocks)
  633. (map :block/uuid closed-value-blocks))
  634. new-value-ids (mapv :block/uuid closed-value-blocks)
  635. property-tx {:db/id (:db/id property)
  636. :block/schema (assoc property-schema :values new-value-ids)}
  637. property-values (db-async/<get-block-property-values (state/get-current-repo) (:db/ident property))
  638. block-values (->> property-values
  639. (remove #(uuid? (first %))))
  640. tx-data (concat
  641. (when page-tx [page-tx])
  642. closed-value-blocks
  643. [property-tx]
  644. (mapcat (fn [[id value]]
  645. [[:db/retract id property-id]
  646. {:db/id id
  647. property-id (if (set? value)
  648. (set (map value->block-id value))
  649. (get value->block-id value))}])
  650. (filter second block-values)))]
  651. (db/transact! (state/get-current-repo) tx-data
  652. {:outliner-op :insert-blocks})
  653. new-value-ids)))))
  654. (defn delete-closed-value!
  655. "Returns true when deleted or if not deleted displays warning and returns false"
  656. [db property value-block]
  657. (cond
  658. (ldb/built-in? value-block)
  659. (do (notification/show! "The choice can't be deleted because it's built-in." :warning)
  660. false)
  661. (seq (:block/_refs value-block))
  662. (do (notification/show! "The choice can't be deleted because it's still used." :warning)
  663. false)
  664. :else
  665. (let [property (db/entity (:db/id property))
  666. schema (:block/schema property)
  667. tx-data [[:db/retractEntity (:db/id value-block)]
  668. {:db/id (:db/id property)
  669. :block/schema (update schema :values
  670. (fn [values]
  671. (vec (remove #{(:block/uuid value-block)} values))))}]]
  672. (p/do!
  673. (db/transact! tx-data)
  674. (re-init-commands! property)
  675. true))))
  676. (defn get-property-block-created-block
  677. "Get the root block and property that created this property block."
  678. [eid]
  679. (let [b (db/entity eid)
  680. parents (model/get-block-parents (state/get-current-repo) (:block/uuid b) {})
  681. [created-from-block created-from-property]
  682. (some (fn [block]
  683. (let [from-block (:logseq.property/created-from-block block)
  684. from-property (:logseq.property/created-from-property block)]
  685. (when (and from-block from-property)
  686. [from-block from-property]))) (reverse parents))]
  687. {:from-block-id (or (:db/id created-from-block) (:db/id b))
  688. :from-property-id (:db/id created-from-property)}))
  689. (defn batch-set-property-closed-value!
  690. [block-ids db-ident closed-value]
  691. (let [repo (state/get-current-repo)
  692. closed-value-id (:block/uuid (pu/get-closed-value-entity-by-name db-ident closed-value))]
  693. (when closed-value-id
  694. (batch-set-property! repo
  695. block-ids
  696. db-ident
  697. closed-value-id))))