1
0

diff.cljs 7.7 KB

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