property.cljs 38 KB

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