diff.cljs 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. (ns frontend.components.diff
  2. (:require [rum.core :as rum]
  3. [frontend.util :as util]
  4. [frontend.config :as config]
  5. [frontend.handler.git :as git-handler]
  6. [frontend.handler.file :as file]
  7. [frontend.handler.notification :as notification]
  8. [frontend.handler.common :as common-handler]
  9. [frontend.state :as state]
  10. [clojure.string :as string]
  11. [frontend.db :as db]
  12. [frontend.components.svg :as svg]
  13. [frontend.ui :as ui]
  14. [frontend.db :as db]
  15. [frontend.git :as git]
  16. [frontend.helper :as helper]
  17. [goog.object :as gobj]
  18. [promesa.core :as p]
  19. [frontend.github :as github]
  20. [frontend.diff :as diff]
  21. [medley.core :as medley]))
  22. (defonce remote-hash-id (atom nil))
  23. (defonce diff-state (atom {}))
  24. (defonce commit-message (atom ""))
  25. ;; TODO: use db :git/status
  26. (defonce *pushing? (atom nil))
  27. (defonce *edit? (atom false))
  28. (defonce *edit-content (atom ""))
  29. (defn- toggle-collapse?
  30. [path]
  31. (swap! diff-state update-in [path :collapse?] not))
  32. (defn- mark-as-resolved
  33. [path]
  34. (swap! diff-state assoc-in [path :resolved?] true)
  35. (swap! diff-state assoc-in [path :collapse?] true))
  36. (rum/defc diff-cp
  37. [diff]
  38. [:div
  39. (for [[idx {:keys [added removed value]}] diff]
  40. (let [bg-color (cond
  41. added "#057a55"
  42. removed "#d61f69"
  43. :else
  44. "initial")]
  45. [:span.diff {:key idx
  46. :style {:background-color bg-color}}
  47. value]))])
  48. (rum/defc file < rum/reactive
  49. [repo type path contents remote-oid component]
  50. (let [{:keys [collapse? resolved?]} (util/react (rum/cursor diff-state path))
  51. edit? (util/react *edit?)]
  52. [:div.cp__diff-file
  53. [:div.cp__diff-file-header
  54. [:a.mr-2 {:on-click (fn [] (toggle-collapse? path))}
  55. (if collapse?
  56. (svg/arrow-right)
  57. (svg/arrow-down))]
  58. [:span.cp__diff-file-header-content path]
  59. (when resolved?
  60. [:span.text-green-600
  61. {:dangerouslySetInnerHTML
  62. {:__html "&#10003;"}}])]
  63. (let [content (get contents path)
  64. local-content (db/get-file path)]
  65. (if (not= content local-content)
  66. (let [local-content (or local-content "")
  67. content (or content "")
  68. diff (medley/indexed (diff/diff local-content content))
  69. diff? (some (fn [[_idx {:keys [added removed]}]]
  70. (or added removed))
  71. diff)]
  72. [:div.pre-line-white-space.p-2 {:class (if collapse? "hidden")
  73. :style {:overflow "auto"}}
  74. (if edit?
  75. [:div.grid.grid-cols-2.gap-1
  76. (diff-cp diff)
  77. (ui/textarea
  78. {:default-value local-content
  79. :on-change (fn [e]
  80. (reset! *edit-content (util/evalue e)))})]
  81. (diff-cp diff))
  82. (cond
  83. edit?
  84. [:div.mt-2
  85. (ui/button "Save"
  86. :on-click
  87. (fn []
  88. (reset! *edit? false)
  89. (let [new-content @*edit-content]
  90. (file/alter-file repo path new-content
  91. {:commit? false
  92. :re-render-root? true})
  93. (swap! state/state
  94. assoc-in [:github/contents repo remote-oid path] new-content)
  95. (mark-as-resolved path))))]
  96. diff?
  97. [:div.mt-2
  98. (ui/button "Use remote"
  99. :on-click
  100. (fn []
  101. ;; overwrite the file
  102. (file/alter-file repo path content
  103. {:commit? false
  104. :re-render-root? true})
  105. (mark-as-resolved path))
  106. :background "green")
  107. [:span.pl-2.pr-2 "or"]
  108. (ui/button "Keep local"
  109. :on-click
  110. (fn []
  111. ;; overwrite the file
  112. (swap! state/state
  113. assoc-in [:github/contents repo remote-oid path] local-content)
  114. (mark-as-resolved path))
  115. :background "pink")
  116. [:span.pl-2.pr-2 "or"]
  117. (ui/button "Edit"
  118. :on-click
  119. (fn []
  120. (reset! *edit? true)))]
  121. :else
  122. nil)])))]))
  123. ;; TODO: `n` shortcut for next diff, `p` for previous diff
  124. (rum/defcc diff <
  125. rum/reactive
  126. {:will-mount
  127. (fn [state]
  128. (when-let [repo (state/get-current-repo)]
  129. (p/let [remote-latest-commit (common-handler/get-remote-ref repo)
  130. local-latest-commit (common-handler/get-ref repo)
  131. result (git/get-diffs repo local-latest-commit remote-latest-commit)
  132. token (helper/get-github-token repo)]
  133. (reset! state/diffs result)
  134. (reset! remote-hash-id remote-latest-commit)
  135. (doseq [{:keys [type path]} result]
  136. (when (contains? #{"add" "modify"}
  137. type)
  138. (github/get-content
  139. token
  140. repo
  141. path
  142. remote-latest-commit
  143. (fn [{:keys [repo-url path ref content]}]
  144. (swap! state/state
  145. assoc-in [:github/contents repo-url remote-latest-commit path] (or content "")))
  146. (fn [response]
  147. (when (= (gobj/get response "status") 401)
  148. (notification/show!
  149. [:span.mr-2
  150. (util/format
  151. "Please make sure that you've installed the logseq app for the repo %s on GitHub. "
  152. repo)
  153. (ui/button
  154. "Install Logseq on GitHub"
  155. :href (str "https://github.com/apps/" config/github-app-name "/installations/new"))]
  156. :error
  157. false))))))))
  158. state)
  159. :will-unmount
  160. (fn [state]
  161. (reset! state/diffs nil)
  162. (reset! remote-hash-id nil)
  163. (reset! diff-state {})
  164. (reset! commit-message "")
  165. (reset! *pushing? nil)
  166. (reset! *edit? false)
  167. (reset! *edit-content "")
  168. state)}
  169. [component]
  170. (let [diffs (util/react state/diffs)
  171. remote-oid (util/react remote-hash-id)
  172. repo (state/get-current-repo)
  173. contents (if remote-oid (state/sub [:github/contents repo remote-oid]))
  174. pushing? (util/react *pushing?)]
  175. [:div#diffs {:style {:margin-bottom 200}}
  176. [:h1.title "Diff"]
  177. (cond
  178. (false? pushing?)
  179. [:div "No diffs"]
  180. (seq diffs)
  181. [:div#diffs-body
  182. (for [{:keys [type path]} diffs]
  183. (rum/with-key (file repo type path contents remote-oid component)
  184. path))
  185. [:div
  186. (ui/textarea
  187. {:placeholder "Commit message (optional)"
  188. :on-change (fn [e]
  189. (reset! commit-message (util/evalue e)))})
  190. (if pushing?
  191. [:span (ui/loading "Pushing")]
  192. (ui/button "Commit and push"
  193. :on-click
  194. (fn []
  195. (let [commit-message (if (string/blank? @commit-message)
  196. "Merge"
  197. @commit-message)]
  198. (reset! *pushing? true)
  199. (git-handler/commit-and-force-push! commit-message *pushing?)))))]]
  200. :else
  201. [:div "No diffs"])]))