sidebar.cljs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. (ns frontend.components.sidebar
  2. (:require [rum.core :as rum]
  3. [frontend.ui :as ui]
  4. [frontend.mixins :as mixins]
  5. [frontend.db-mixins :as db-mixins]
  6. [frontend.db :as db]
  7. [frontend.components.widgets :as widgets]
  8. [frontend.components.journal :as journal]
  9. [frontend.components.page :as page]
  10. [frontend.components.settings :as settings]
  11. [frontend.components.svg :as svg]
  12. [frontend.components.repo :as repo]
  13. [frontend.components.commit :as commit]
  14. [frontend.components.header :as header]
  15. [frontend.components.right-sidebar :as right-sidebar]
  16. [frontend.storage :as storage]
  17. [frontend.util :as util]
  18. [frontend.state :as state]
  19. [frontend.handler.user :as user-handler]
  20. [frontend.handler.editor :as editor-handler]
  21. [frontend.handler.route :as route-handler]
  22. [frontend.handler.export :as export]
  23. [frontend.config :as config]
  24. [frontend.keyboards :as keyboards]
  25. [dommy.core :as d]
  26. [clojure.string :as string]
  27. [goog.object :as gobj]
  28. [frontend.context.i18n :as i18n]
  29. [reitit.frontend.easy :as rfe]
  30. [goog.dom :as gdom]))
  31. (defn nav-item
  32. [title href svg-d active? close-modal-fn]
  33. [:a.mb-1.group.flex.items-center.pl-4.py-2.text-base.leading-6.font-medium.hover:text-gray-200.transition.ease-in-out.duration-150.nav-item
  34. {:href href
  35. :on-click close-modal-fn}
  36. [:svg.mr-4.h-6.w-6.group-hover:text-gray-200.group-focus:text-gray-200.transition.ease-in-out.duration-150
  37. {:viewBox "0 0 24 24", :fill "none", :stroke "currentColor"}
  38. [:path
  39. {:d svg-d
  40. :stroke-width "2",
  41. :stroke-linejoin "round",
  42. :stroke-linecap "round"}]]
  43. title])
  44. (rum/defc sidebar-nav < rum/reactive
  45. [route-match close-modal-fn]
  46. (let [white? (= "white" (state/sub :ui/theme))
  47. active? (fn [route] (= route (get-in route-match [:data :name])))
  48. page-active? (fn [page]
  49. (= page (get-in route-match [:parameters :path :name])))
  50. right-sidebar? (state/sub :ui/sidebar-open?)
  51. left-sidebar? (state/sub :ui/left-sidebar-open?)]
  52. (when left-sidebar?
  53. [:nav.flex-1
  54. (nav-item "Journals" "/"
  55. "M3 12l9-9 9 9M5 10v10a1 1 0 001 1h3a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1h3a1 1 0 001-1V10M9 21h6"
  56. (active? :home)
  57. close-modal-fn)
  58. (nav-item "All Pages" "/all-pages"
  59. "M6 2h9a1 1 0 0 1 .7.3l4 4a1 1 0 0 1 .3.7v13a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4c0-1.1.9-2 2-2zm9 2.41V7h2.59L15 4.41zM18 9h-3a2 2 0 0 1-2-2V4H6v16h12V9zm-2 7a1 1 0 0 1-1 1H9a1 1 0 0 1 0-2h6a1 1 0 0 1 1 1zm0-4a1 1 0 0 1-1 1H9a1 1 0 0 1 0-2h6a1 1 0 0 1 1 1zm-5-4a1 1 0 0 1-1 1H9a1 1 0 1 1 0-2h1a1 1 0 0 1 1 1z"
  60. (active? :all-pages)
  61. close-modal-fn)
  62. (when-not config/publishing?
  63. (nav-item "All Files" "/all-files"
  64. "M3 7V17C3 18.1046 3.89543 19 5 19H19C20.1046 19 21 18.1046 21 17V9C21 7.89543 20.1046 7 19 7H13L11 5H5C3.89543 5 3 5.89543 3 7Z"
  65. (active? :all-files)
  66. close-modal-fn))
  67. (when-not right-sidebar?
  68. [:div.pl-4.pr-4 {:style {:height 1
  69. :background-color (if white? "#f0f8ff" "#073642")
  70. :margin 12}}])
  71. (right-sidebar/contents)])))
  72. (rum/defc sidebar-mobile-sidebar < rum/reactive
  73. [{:keys [open? close-fn route-match]}]
  74. [:div.md:hidden
  75. [:div.fixed.inset-0.z-30.bg-gray-600.opacity-0.pointer-events-none.transition-opacity.ease-linear.duration-300
  76. {:class (if @open?
  77. "opacity-75 pointer-events-auto"
  78. "opacity-0 pointer-events-none")
  79. :on-click close-fn}]
  80. [:div#left-bar.fixed.inset-y-0.left-0.flex.flex-col.z-40.max-w-xs.w-full.transform.ease-in-out.duration-300
  81. {:class (if @open?
  82. "translate-x-0"
  83. "-translate-x-full")
  84. :style {:background-color "#002b36"}}
  85. (if @open?
  86. [:div.absolute.top-0.right-0.-mr-14.p-1
  87. [:button#close-left-bar.flex.items-center.justify-center.h-12.w-12.rounded-full.focus:outline-none.focus:bg-gray-600
  88. {:on-click close-fn}
  89. [:svg.h-6.w-6.text-white
  90. {:viewBox "0 0 24 24", :fill "none", :stroke "currentColor"}
  91. [:path
  92. {:d "M6 18L18 6M6 6l12 12"
  93. :stroke-width "2"
  94. :stroke-linejoin "round"
  95. :stroke-linecap "round"}]]]])
  96. [:div.flex-shrink-0.flex.items-center.px-4.h-16 {:style {:background-color "#002b36"}}
  97. (repo/repos-dropdown false)]
  98. [:div.flex-1.h-0.overflow-y-auto
  99. (sidebar-nav route-match close-fn)]]])
  100. (rum/defc sidebar-main
  101. [{:keys [route-match global-graph-pages? logged? home? route-name indexeddb-support? white? db-restoring? main-content]}]
  102. (rum/with-context [[t] i18n/*tongue-context*]
  103. [:div#main-content.cp__sidebar-main-layout
  104. (when-not config/mobile?
  105. [:div#sidebar-nav-wrapper.flex-col.pt-4.hidden.sm:block
  106. {:style {:flex (if (state/get-left-sidebar-open?)
  107. "0 1 20%"
  108. "0 0 0px")
  109. :border-right (str "1px solid "
  110. (if white? "#f0f8ff" "#073642"))}}
  111. (when (state/sub :ui/left-sidebar-open?)
  112. (sidebar-nav route-match nil))])
  113. [:div#main-content-container.cp__sidebar-main-content-container
  114. [:div.cp__sidebar-main-content
  115. {:data-is-global-graph-pages global-graph-pages?
  116. :data-is-full-width (or global-graph-pages?
  117. (and (not logged?)
  118. home?)
  119. (contains? #{:all-files :all-pages} route-name))}
  120. (cond
  121. (not indexeddb-support?)
  122. nil
  123. db-restoring?
  124. [:div.mt-20
  125. [:div.ls-center
  126. (ui/loading (t :loading))]]
  127. :else
  128. [:div {:style {:margin-bottom (if global-graph-pages? 0 120)}}
  129. main-content])]]
  130. (when-not config/mobile?
  131. (right-sidebar/sidebar))]))
  132. (defn get-default-home-if-valid
  133. []
  134. (when-let [default-home (state/get-default-home)]
  135. (when-let [page (:page default-home)]
  136. (when (db/entity [:page/name (string/lower-case page)])
  137. default-home))))
  138. (defonce sidebar-inited? (atom false))
  139. ;; TODO: simplify logic
  140. (rum/defc main-content < rum/reactive db-mixins/query
  141. {:init (fn [state]
  142. (when-not @sidebar-inited?
  143. (let [current-repo (state/sub :git/current-repo)
  144. default-home (get-default-home-if-valid)
  145. sidebar (:sidebar default-home)
  146. sidebar (if (string? sidebar) [sidebar] sidebar)]
  147. (when-let [pages (->> (seq sidebar)
  148. (remove nil?))]
  149. (let [blocks (remove nil? pages)]
  150. (doseq [page pages]
  151. (let [page (string/lower-case page)
  152. [db-id block-type] (if (= page "contents")
  153. ["contents" :contents]
  154. [page :page])]
  155. (state/sidebar-add-block! current-repo db-id block-type nil))))
  156. (reset! sidebar-inited? true))))
  157. state)}
  158. []
  159. (let [today (state/sub :today)
  160. cloning? (state/sub :repo/cloning?)
  161. default-home (get-default-home-if-valid)
  162. importing-to-db? (state/sub :repo/importing-to-db?)
  163. loading-files? (state/sub :repo/loading-files?)
  164. me (state/sub :me)
  165. journals-length (state/sub :journals-length)
  166. current-repo (state/sub :git/current-repo)
  167. latest-journals (db/get-latest-journals (state/get-current-repo) journals-length)
  168. preferred-format (state/sub [:me :preferred_format])
  169. logged? (:name me)
  170. token (state/sub :encrypt/token)
  171. ;; TODO: remove this
  172. daily-migrating? (state/sub [:daily/migrating?])]
  173. (rum/with-context [[t] i18n/*tongue-context*]
  174. [:div.max-w-7xl.mx-auto
  175. (cond
  176. daily-migrating?
  177. (ui/loading "Migrating to daily notes")
  178. (and default-home
  179. (= :home (state/get-current-route))
  180. (not (state/route-has-p?)))
  181. (route-handler/redirect! {:to :page
  182. :path-params {:name (:page default-home)}})
  183. (and (not logged?) (seq latest-journals))
  184. (journal/journals latest-journals)
  185. (and logged? (not preferred-format))
  186. (widgets/choose-preferred-format)
  187. ;; TODO: delay this
  188. (and logged? (nil? (:email me)))
  189. (settings/set-email)
  190. cloning?
  191. (ui/loading (t :cloning))
  192. (seq latest-journals)
  193. (journal/journals latest-journals)
  194. importing-to-db?
  195. (ui/loading (t :parsing-files))
  196. loading-files?
  197. (ui/loading (t :loading-files))
  198. (and logged? (empty? (:repos me)))
  199. (widgets/add-repo)
  200. ;; FIXME: why will this happen?
  201. :else
  202. [:div])])))
  203. (rum/defc custom-context-menu < rum/reactive
  204. []
  205. (when (state/sub :custom-context-menu/show?)
  206. (when-let [links (state/sub :custom-context-menu/links)]
  207. (ui/css-transition
  208. {:class-names "fade"
  209. :timeout {:enter 500
  210. :exit 300}}
  211. links
  212. ;; (custom-context-menu-content)
  213. ))))
  214. (rum/defc new-block-mode < rum/reactive
  215. []
  216. (when-let [alt-enter? (= "alt+enter" (state/sub [:shortcuts :editor/new-block]))]
  217. [:a.px-1.text-sm.font-medium.bg-base-2.mr-4.rounded-md
  218. {:title "Click to switch to \"Enter\" for creating new block"
  219. :on-click state/toggle-new-block-shortcut!}
  220. "A"]))
  221. (rum/defc help-button < rum/reactive
  222. []
  223. (when-not (state/sub :ui/sidebar-open?)
  224. ;; TODO: remove with-context usage
  225. (rum/with-context [[t] i18n/*tongue-context*]
  226. [:div#help.font-bold.absolute.bottom-4.bg-base-2.rounded-full.h-8.w-8.flex.items-center.justify-center.font-bold.cursor.opacity-70.hover:opacity-100
  227. {:style {:right 24}
  228. :title (t :help-shortcut-title)
  229. :on-click (fn []
  230. (state/sidebar-add-block! (state/get-current-repo) "help" :help nil))}
  231. "?"])))
  232. (rum/defcs sidebar <
  233. (mixins/modal :modal/show?)
  234. rum/reactive
  235. ;; TODO: move this to keyboards
  236. (mixins/event-mixin
  237. (fn [state]
  238. (mixins/listen state js/window "click"
  239. (fn [e]
  240. ;; hide context menu
  241. (state/hide-custom-context-menu!)
  242. (if-not (state/get-selection-start-block)
  243. (editor-handler/clear-selection! e)
  244. (state/set-selection-start-block! nil))))
  245. (mixins/on-key-down
  246. state
  247. {;; esc
  248. 27 (fn [_state e]
  249. (editor-handler/clear-selection! e))
  250. ;; shift+up
  251. 38 (fn [state e]
  252. (editor-handler/on-select-block state e true))
  253. ;; shift+down
  254. 40 (fn [state e]
  255. (editor-handler/on-select-block state e false))
  256. ;; ?
  257. 191 (fn [state e]
  258. (when-not (util/input? (gobj/get e "target"))
  259. (state/sidebar-add-block! (state/get-current-repo) "help" :help nil)))
  260. ;; c
  261. 67 (fn [state e]
  262. (when (and (not (util/input? (gobj/get e "target")))
  263. (not (gobj/get e "shiftKey"))
  264. (not (gobj/get e "ctrlKey"))
  265. (not (gobj/get e "altKey"))
  266. (not (gobj/get e "metaKey")))
  267. (when-let [repo-url (state/get-current-repo)]
  268. (when-not (state/get-edit-input-id)
  269. (util/stop e)
  270. (state/set-modal! commit/add-commit-message)))))})))
  271. {:did-mount (fn [state]
  272. (keyboards/bind-shortcuts!)
  273. state)}
  274. (mixins/keyboards-mixin keyboards/keyboards)
  275. [state route-match main-content]
  276. (let [{:keys [open? close-fn open-fn]} state
  277. close-fn (fn []
  278. (close-fn)
  279. (state/set-left-sidebar-open! false))
  280. me (state/sub :me)
  281. current-repo (state/sub :git/current-repo)
  282. theme (state/sub :ui/theme)
  283. white? (= "white" (state/sub :ui/theme))
  284. route-name (get-in route-match [:data :name])
  285. global-graph-pages? (= :graph route-name)
  286. logged? (:name me)
  287. db-restoring? (state/sub :db/restoring?)
  288. indexeddb-support? (state/sub :indexeddb/support?)
  289. page? (= :page route-name)
  290. home? (= :home route-name)
  291. default-home (get-default-home-if-valid)]
  292. (rum/with-context [[t] i18n/*tongue-context*]
  293. [:div {:class (if white? "white-theme" "dark-theme")
  294. :on-click editor-handler/unhighlight-block!}
  295. (sidebar-mobile-sidebar {:open? open?
  296. :close-fn close-fn
  297. :route-match route-match})
  298. [:div.cp__sidebar-layout.h-screen
  299. (header/header {:open-fn open-fn
  300. :white? white?
  301. :current-repo current-repo
  302. :logged? logged?
  303. :page? page?
  304. :route-match route-match
  305. :me me
  306. :default-home default-home
  307. :new-block-mode new-block-mode})
  308. (sidebar-main {:route-match route-match
  309. :global-graph-pages? global-graph-pages?
  310. :logged? logged?
  311. :home? home?
  312. :route-name route-name
  313. :indexeddb-support? indexeddb-support?
  314. :white? white?
  315. :db-restoring? db-restoring?
  316. :main-content main-content})]
  317. (ui/notification)
  318. (ui/modal)
  319. (custom-context-menu)
  320. [:a#download.hidden]
  321. (when (and (not config/mobile?)
  322. (not config/publishing?))
  323. (help-button)
  324. ;; [:div.font-bold.absolute.bottom-4.bg-base-2.rounded-full.h-8.w-8.flex.items-center.justify-center.font-bold.cursor.opacity-70.hover:opacity-100
  325. ;; {:style {:left 24}
  326. ;; :title "Click to show/hide sidebar"
  327. ;; :on-click (fn []
  328. ;; (state/set-left-sidebar-open! (not (state/get-left-sidebar-open?))))}
  329. ;; (if (state/sub :ui/left-sidebar-open?) "<" ">")]
  330. )])))