commands.cljs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. (ns frontend.worker.commands
  2. "Invoke commands based on user settings"
  3. (:require [cljs-time.coerce :as tc]
  4. [cljs-time.core :as t]
  5. [datascript.core :as d]
  6. [logseq.common.util.date-time :as date-time-util]
  7. [logseq.db :as ldb]
  8. [logseq.db.frontend.property :as db-property]
  9. [logseq.db.frontend.property.build :as db-property-build]
  10. [logseq.db.frontend.property.type :as db-property-type]
  11. [logseq.db.sqlite.util :as sqlite-util]
  12. [logseq.outliner.page :as outliner-page]
  13. [logseq.outliner.pipeline :as outliner-pipeline]))
  14. ;; TODO: allow users to add command or configure it through #Command (which parent should be #Code)
  15. (def *commands
  16. (atom
  17. [[:repeated-task
  18. {:title "Repeated task"
  19. :entity-conditions [{:property :logseq.property.repeat/repeated?
  20. :value true}]
  21. :tx-conditions [{:property :status
  22. :value :done}]
  23. :actions [[:reschedule]
  24. [:set-property :status :todo]]}]
  25. [:property-history
  26. {:title "Record property history"
  27. :tx-conditions [{:kind :datom-attribute-check?
  28. :property :logseq.property/enable-history?
  29. :value true}]
  30. :actions [[:record-property-history]]}]]))
  31. (defn- get-property
  32. [entity property]
  33. (if (= property :status)
  34. (or
  35. (:db/ident (:logseq.property.repeat/checked-property entity))
  36. :logseq.property/status)
  37. property))
  38. (defn- get-value
  39. [entity property value]
  40. (cond
  41. (and (= property :status) (= value :done))
  42. (or
  43. (let [p (:logseq.property.repeat/checked-property entity)
  44. choices (:property/closed-values p)
  45. checkbox? (= :checkbox (:logseq.property/type p))]
  46. (if checkbox?
  47. true
  48. (some (fn [choice]
  49. (when (:logseq.property/choice-checkbox-state choice)
  50. (:db/id choice))) choices)))
  51. :logseq.property/status.done)
  52. (and (= property :status) (= value :todo))
  53. (or
  54. (let [p (:logseq.property.repeat/checked-property entity)
  55. choices (:property/closed-values p)
  56. checkbox? (= :checkbox (:logseq.property/type p))]
  57. (if checkbox?
  58. false
  59. (some (fn [choice]
  60. (when (false? (:logseq.property/choice-checkbox-state choice))
  61. (:db/id choice))) choices)))
  62. :logseq.property/status.todo)
  63. :else
  64. value))
  65. (defn satisfy-condition?
  66. "Whether entity or updated datoms satisfy the `condition`"
  67. [db entity {:keys [kind property value]} datoms]
  68. (let [property' (get-property entity property)
  69. value' (get-value entity property value)]
  70. (when-let [property-entity (d/entity db property')]
  71. (let [value-matches? (fn [datom-value]
  72. (let [ref? (contains? db-property-type/all-ref-property-types (:logseq.property/type property-entity))
  73. db-value (cond
  74. ;; entity-conditions
  75. (nil? datom-value)
  76. (get entity property')
  77. ;; tx-conditions
  78. ref?
  79. (d/entity db datom-value)
  80. :else
  81. datom-value)]
  82. (cond
  83. (qualified-keyword? value')
  84. (and (map? db-value) (= value' (:db/ident db-value)))
  85. ref?
  86. (or
  87. (and (uuid? value') (= (:block/uuid db-value) value'))
  88. (= value' (db-property/property-value-content db-value))
  89. (= value' (:db/id db-value)))
  90. :else
  91. (= db-value value'))))]
  92. (if (seq datoms)
  93. (case kind
  94. :datom-attribute-check?
  95. (some (fn [d]
  96. (= value' (get (d/entity db (:a d)) property)))
  97. datoms)
  98. (some (fn [d] (and (value-matches? (:v d)) (:added d)))
  99. (filter (fn [d] (= property' (:a d))) datoms)))
  100. (value-matches? nil))))))
  101. (defmulti handle-command (fn [action-id & _others] action-id))
  102. (defn- repeat-until-future-timestamp
  103. [datetime recur-unit frequency period-f keep-week?]
  104. (let [now (t/now)
  105. v (max
  106. 1
  107. (if (t/after? datetime now)
  108. 1
  109. (period-f (t/interval datetime now))))
  110. delta (->> (Math/ceil (/ v frequency))
  111. (* frequency)
  112. recur-unit)
  113. result* (t/plus datetime delta)
  114. result (if (t/after? result* now)
  115. result*
  116. (t/plus result* (recur-unit frequency)))
  117. w1 (t/day-of-week datetime)
  118. w2 (t/day-of-week result)]
  119. (if (and keep-week? (not= w1 w2))
  120. ;; next week
  121. (if (> w2 w1)
  122. (t/plus result (t/days (- 7 (- w2 w1))))
  123. (t/plus result (t/days (- w1 w2))))
  124. result)))
  125. (defn- get-next-time
  126. [current-value unit frequency]
  127. (let [current-date-time (tc/to-date-time current-value)
  128. [recur-unit period-f] (case (:db/ident unit)
  129. :logseq.property.repeat/recur-unit.minute [t/minutes t/in-minutes]
  130. :logseq.property.repeat/recur-unit.hour [t/hours t/in-hours]
  131. :logseq.property.repeat/recur-unit.day [t/days t/in-days]
  132. :logseq.property.repeat/recur-unit.week [t/weeks t/in-weeks]
  133. :logseq.property.repeat/recur-unit.month [t/months t/in-months]
  134. :logseq.property.repeat/recur-unit.year [t/years t/in-years]
  135. nil)]
  136. (when recur-unit
  137. (let [week? (= (:db/ident unit) :logseq.property.repeat/recur-unit.week)
  138. next-time (repeat-until-future-timestamp current-date-time recur-unit frequency period-f week?)]
  139. (tc/to-long next-time)))))
  140. (defn- compute-reschedule-property-tx
  141. [conn db entity property-ident]
  142. (let [frequency (or (db-property/property-value-content (:logseq.property.repeat/recur-frequency entity))
  143. (let [property (d/entity db :logseq.property.repeat/recur-frequency)
  144. default-value-block (db-property-build/build-property-value-block property property 1)
  145. default-value-tx-data [default-value-block
  146. {:db/id (:db/id property)
  147. :logseq.property/default-value [:block/uuid (:block/uuid default-value-block)]}]]
  148. (d/transact! conn default-value-tx-data)
  149. 1))
  150. unit (:logseq.property.repeat/recur-unit entity)
  151. property (d/entity db property-ident)
  152. date? (= :date (:logseq.property/type property))
  153. current-value (cond->
  154. (get entity property-ident)
  155. date?
  156. (#(date-time-util/journal-day->ms (:block/journal-day %))))]
  157. (when (and frequency unit)
  158. (when-let [next-time-long (get-next-time current-value unit frequency)]
  159. (let [journal-day (outliner-pipeline/get-journal-day-from-long db next-time-long)
  160. {:keys [tx-data page-uuid]} (if journal-day
  161. {:page-uuid (:block/uuid (d/entity db journal-day))}
  162. (let [formatter (:logseq.property.journal/title-format (d/entity db :logseq.class/Journal))
  163. title (date-time-util/format (t/to-default-time-zone (tc/to-date-time next-time-long)) formatter)]
  164. (outliner-page/create db title {})))
  165. value (if date? [:block/uuid page-uuid] next-time-long)]
  166. (concat
  167. tx-data
  168. (when value
  169. [[:db/add (:db/id entity) property-ident value]])))))))
  170. (defmethod handle-command :reschedule [_ conn db entity _datoms]
  171. (let [property-ident (or (:db/ident (:logseq.property.repeat/temporal-property entity))
  172. :logseq.property/scheduled)
  173. other-property-idents (cond
  174. (and (= property-ident :logseq.property/scheduled)
  175. (:logseq.property/deadline entity))
  176. [:logseq.property/deadline]
  177. (and (= property-ident :logseq.property/deadline)
  178. (:logseq.property/scheduled entity))
  179. [:logseq.property/scheduled]
  180. :else
  181. (filter (fn [p] (get entity p)) [:logseq.property/deadline :logseq.property/scheduled]))]
  182. (mapcat #(compute-reschedule-property-tx conn db entity %) (distinct (cons property-ident other-property-idents)))))
  183. (defmethod handle-command :set-property [_ _db _conn entity _datoms property value]
  184. (let [property' (get-property entity property)
  185. value' (get-value entity property value)]
  186. [[:db/add (:db/id entity) property' value']]))
  187. (defmethod handle-command :record-property-history [_ _conn db entity datoms]
  188. (let [changes (keep (fn [d]
  189. (let [property (d/entity db (:a d))]
  190. (when (and (true? (get property :logseq.property/enable-history?))
  191. (:added d))
  192. {:property property
  193. :value (:v d)}))) datoms)]
  194. (map
  195. (fn [{:keys [property value]}]
  196. (let [ref? (= :db.type/ref (:db/valueType property))
  197. value-key (if ref? :logseq.property.history/ref-value :logseq.property.history/scalar-value)]
  198. (sqlite-util/block-with-timestamps
  199. {:block/uuid (ldb/new-block-id)
  200. value-key value
  201. :logseq.property.history/block (:db/id entity)
  202. :logseq.property.history/property (:db/id property)})))
  203. changes)))
  204. (defmethod handle-command :default [command _conn _db entity datoms]
  205. (throw (ex-info "Unhandled command"
  206. {:command command
  207. :entity entity
  208. :datoms datoms})))
  209. (defn execute-command
  210. "Build tx-data"
  211. [conn db entity datoms [_command {:keys [actions]}]]
  212. (mapcat (fn [action]
  213. (apply handle-command (first action) conn db entity datoms (rest action))) actions))
  214. (defn run-commands
  215. [conn {:keys [tx-data db-after]}]
  216. (let [db db-after]
  217. (mapcat (fn [[e datoms]]
  218. (let [entity (d/entity db e)
  219. commands (filter (fn [[_command {:keys [entity-conditions tx-conditions]}]]
  220. (and
  221. (if (seq entity-conditions)
  222. (every? #(satisfy-condition? db entity % nil) entity-conditions)
  223. true)
  224. (every? #(satisfy-condition? db entity % datoms) tx-conditions))) @*commands)]
  225. (mapcat
  226. (fn [command]
  227. (execute-command conn db entity datoms command))
  228. commands)))
  229. (group-by :e tx-data))))