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