right_sidebar.cljs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. (ns frontend.components.right-sidebar
  2. (:require [rum.core :as rum]
  3. [frontend.ui :as ui]
  4. [frontend.components.svg :as svg]
  5. [frontend.components.page :as page]
  6. [frontend.components.hiccup :as hiccup]
  7. [frontend.components.heading :as heading]
  8. [frontend.extensions.graph-2d :as graph-2d]
  9. [frontend.extensions.latex :as latex]
  10. [frontend.extensions.code :as code]
  11. [frontend.handler :as handler]
  12. [frontend.state :as state]
  13. [frontend.db :as db]
  14. [frontend.util :as util]
  15. [frontend.date :as date]
  16. [medley.core :as medley]
  17. [clojure.string :as string]
  18. [frontend.extensions.slide :as slide]
  19. [cljs-bean.core :as bean]
  20. [goog.object :as gobj]
  21. [frontend.graph :as graph]))
  22. (rum/defc heading-cp < rum/reactive
  23. [repo idx heading]
  24. (let [id (:heading/uuid heading)]
  25. (page/page {:parameters {:path {:name (str id)}}
  26. :sidebar? true
  27. :sidebar/idx idx
  28. :repo repo})))
  29. (defn page-graph
  30. [page]
  31. (let [theme (:ui/theme @state/state)
  32. dark? (= theme "dark")
  33. graph (db/build-page-graph page theme)]
  34. (when (seq (:nodes graph))
  35. [:div.sidebar-item.flex-col.flex-1
  36. (graph-2d/graph
  37. (graph/build-graph-opts
  38. graph dark?
  39. {:width 600
  40. :height 600}))])))
  41. (defn help
  42. []
  43. [:div.help.ml-2.mt-1
  44. [:ul
  45. [:li
  46. [:a {:href "https://github.com/logseq/logseq/issues/new"
  47. :target "_blank"}
  48. "Bug report"]]
  49. [:li
  50. [:a {:href "https://github.com/logseq/logseq/issues/new"
  51. :target "_blank"}
  52. "Feature request"]]
  53. [:li
  54. [:a {:href "/blog/changelog"
  55. :target "_blank"}
  56. "Changelog"]]
  57. [:li
  58. [:a {:href "/blog"
  59. :target "_blank"}
  60. "Logseq Blog"]]
  61. [:li
  62. [:a {:href "https://discord.gg/KpN4eHY"
  63. :target "_blank"}
  64. [:div.flex-row.flex.items-center
  65. [:span.mr-1 "Discord community"]
  66. svg/discord]]]
  67. [:li
  68. "Keyboard Shortcuts"
  69. [:table
  70. [:thead
  71. [:tr
  72. [:th [:b "Triggers"]]
  73. [:th "Shortcut"]]]
  74. [:tbody
  75. [:tr [:td "Slash Autocomplete"] [:td "/"]]
  76. [:tr [:td "Block content (Src, Quote, Query, etc) Autocomplete"] [:td "<"]]
  77. [:tr [:td "Page reference Autocomplete"] [:td "[[]]"]]
  78. [:tr [:td "Block Reference"] [:td "(())"]]]]
  79. [:table
  80. [:thead
  81. [:tr
  82. [:th [:span [:b "Key Commands"]
  83. " (working with lists)"]]
  84. [:th "Shortcut"]]]
  85. [:tbody
  86. [:tr [:td "Indent Block Tab"] [:td "Tab"]]
  87. [:tr [:td "Unindent Block"] [:td "Shift-Tab"]]
  88. [:tr [:td "Create New Block"] [:td "Enter"]]
  89. [:tr [:td "New Line in Block"] [:td "Shift-Enter"]]
  90. [:tr [:td "Undo"] [:td "Ctrl-z"]]
  91. [:tr [:td "Redo"] [:td "Ctrl-y"]]
  92. [:tr [:td "Zoom In"] [:td "Alt-Right"]]
  93. [:tr [:td "Zoom out"] [:td "Alt-left"]]
  94. [:tr [:td "Follow link under cursor"] [:td "Ctrl-o"]]
  95. [:tr [:td "Open link in Sidebar"] [:td "Ctrl-shift-o"]]
  96. [:tr [:td "Select Block Above"] [:td "Shift-Up"]]
  97. [:tr [:td "Select Block Below"] [:td "Shift-Down"]]
  98. [:tr [:td "Select All Blocks"] [:td "Ctrl-Shift-a"]]]]
  99. [:table
  100. [:thead
  101. [:tr
  102. [:th [:b "General"]]
  103. [:th "Shortcut"]]]
  104. [:tbody
  105. [:tr [:td "Toggle help"] [:td "?"]]
  106. [:tr [:td "Full Text Search"] [:td "Ctrl-u"]]
  107. [:tr [:td "Open Link in Sidebar"] [:td "Shift-Click"]]
  108. [:tr [:td "Context Menu"] [:td "Right Click"]]
  109. [:tr [:td "Fold/Unfold blocks (when not in edit mode)"] [:td "Tab"]]
  110. [:tr [:td "Toggle document mode"] [:td "Ctrl-Alt-d"]]
  111. [:tr [:td "Toggle right sidebar"] [:td "Ctrl-Alt-r"]]
  112. [:tr [:td "Jump to Journals"] [:td "Alt-j"]]]]
  113. [:table
  114. [:thead
  115. [:tr
  116. [:th [:b "Formatting"]]
  117. [:th "Shortcut"]]]
  118. [:tbody
  119. [:tr [:td "Bold"] [:td "Ctrl-b"]]
  120. [:tr [:td "Italics"] [:td "Ctrl-i"]]
  121. [:tr [:td "Html Link"] [:td "Ctrl-k"]]
  122. [:tr [:td "Highlight"] [:td "Ctrl-h"]]]]]
  123. [:li
  124. "Markdown syntax"
  125. [:table
  126. [:tbody
  127. [:tr [:td "**Bold**"] [:td.text-right [:b "Bold"]]]
  128. [:tr [:td "__Italics__"] [:td.text-right [:i "Italics"]]]
  129. [:tr [:td "~~Strikethrough~~"] [:td.text-right [:del "Strikethrough"]]]
  130. [:tr [:td "^^highlight^^"] [:td.text-right [:mark "highlight"]]]
  131. [:tr [:td "$$E = mc^2$$"] [:td.text-right (latex/latex
  132. "help-latex"
  133. "E = mc^2" true false)]]
  134. [:tr [:td "`Code`"] [:td.text-right [:code "Code"]]]
  135. [:tr [:td [:pre "```clojure
  136. (println \"Hello world!\")
  137. ```"]] [:td.text-right
  138. (code/highlight
  139. "help-highlight"
  140. {:data-lang "clojure"}
  141. "(println \"Hello world!\")")]]
  142. [:tr [:td "[label](https://www.example.com)"]
  143. [:td.text-right
  144. [:a {:href "https://www.example.com"}
  145. "label"]]]
  146. [:tr [:td "![image](https://logseq.com/static/img/logo.png)"]
  147. [:td.text-right
  148. [:img {:style {:float "right"
  149. :width 64
  150. :height 64}
  151. :src "https://logseq.com/static/img/logo.png"
  152. :alt "image"}]]]]]]
  153. [:li
  154. "Org mode syntax"
  155. [:table
  156. [:tbody
  157. [:tr [:td "*Bold*"] [:td.text-right [:b "Bold"]]]
  158. [:tr [:td "/Italics/"] [:td.text-right [:i "Italics"]]]
  159. [:tr [:td "+Strikethrough+"] [:td.text-right [:del "Strikethrough"]]]
  160. [:tr [:td "^^highlight^^"] [:td.text-right [:mark "highlight"]]]
  161. [:tr [:td "$$E = mc^2$$"] [:td.text-right (latex/latex
  162. "help-latex"
  163. "E = mc^2" true false)]]
  164. [:tr [:td "~Code~"] [:td.text-right [:code "Code"]]]
  165. [:tr [:td [:pre "#+BEGIN_SRC clojure
  166. (println \"Hello world!\")
  167. #+END_SRC"]] [:td.text-right
  168. (code/highlight
  169. "help-highlight-org"
  170. {:data-lang "clojure"}
  171. "(println \"hello world\")")]]
  172. [:tr [:td "[[https://www.example.com][label]]"]
  173. [:td.text-right
  174. [:a {:href "https://www.example.com"}
  175. "label"]]]
  176. [:tr [:td "[[https://logseq.com/static/img/logo.png][image]]"]
  177. [:td.text-right
  178. [:img {:style {:float "right"
  179. :width 64
  180. :height 64}
  181. :src "https://logseq.com/static/img/logo.png"
  182. :alt "image"}]]]]]]]])
  183. (defn recent-pages
  184. []
  185. (let [pages (db/get-key-value :recent/pages)]
  186. [:div.recent-pages.text-sm.flex-col.flex.ml-3.mt-2
  187. (if (seq pages)
  188. (for [page pages]
  189. [:a.mb-1 {:key (str "recent-page-" page)
  190. :href (str "/page/" page)}
  191. (util/capitalize-all page)]))]))
  192. (rum/defcs foldable-list <
  193. (rum/local false ::fold?)
  194. [state page l]
  195. (let [fold? (get state ::fold?)]
  196. [:div
  197. [:div.flex.flex-row.items-center.mb-1
  198. [:a.control {:on-click #(swap! fold? not)
  199. :style {:width "0.75rem"}}
  200. (when (seq l)
  201. [:svg.h-3.w-3
  202. {:version "1.1",
  203. :view-box "0 0 128 128"
  204. :fill "currentColor"
  205. :display "inline-block"
  206. :style {:margin-top -3}}
  207. [:path
  208. {:d
  209. (if @fold?
  210. "M64.177 100.069a7.889 7.889 0 01-5.6-2.316l-55.98-55.98a7.92 7.92 0 010-11.196c3.086-3.085 8.105-3.092 11.196 0l50.382 50.382 50.382-50.382a7.92 7.92 0 0111.195 0c3.086 3.086 3.092 8.104 0 11.196l-55.98 55.98a7.892 7.892 0 01-5.595 2.316z"
  211. "M99.069 64.173c0 2.027-.77 4.054-2.316 5.6l-55.98 55.98a7.92 7.92 0 01-11.196 0c-3.085-3.086-3.092-8.105 0-11.196l50.382-50.382-50.382-50.382a7.92 7.92 0 010-11.195c3.086-3.085 8.104-3.092 11.196 0l55.98 55.98a7.892 7.892 0 012.316 5.595z")}]])]
  212. [:a.ml-2 {:key (str "contents-" page)
  213. :href (str "/page/" page)}
  214. (util/capitalize-all page)]]
  215. (when (and (seq l) (not @fold?))
  216. [:div.contents-list.ml-4
  217. (for [{:keys [page list]} l]
  218. (rum/with-key
  219. (foldable-list page list)
  220. (str "toc-item-" page)))])]))
  221. (rum/defc contents < rum/reactive
  222. []
  223. (let [l (db/get-contents)]
  224. [:div.contents.text-sm.flex-col.flex.ml-3.mt-2
  225. (if (seq l)
  226. (for [{:keys [page list]} l]
  227. (rum/with-key
  228. (foldable-list page list)
  229. (str "toc-item-" page)))
  230. (let [page (db/entity [:page/name "contents"])]
  231. (if page
  232. [:div
  233. [:p.text-base "No contents yet, you can click the button below to edit it."]
  234. (ui/button
  235. "Edit the contents"
  236. :on-click (fn [e]
  237. (util/stop e)
  238. (handler/redirect! {:to :page
  239. :path-params {:name "contents"}})))]
  240. [:div
  241. [:p.text-base
  242. [:i.font-medium "Contents"] " (similar to book contents) is a way to structure your pages, please click the button below to start!"]
  243. (ui/button
  244. "Create the contents"
  245. :on-click (fn [e]
  246. (util/stop e)
  247. (handler/create-new-page! "contents")))])))]))
  248. (defn build-sidebar-item
  249. [repo idx db-id block-type block-data]
  250. (case block-type
  251. :contents
  252. [[:a {:on-click (fn [e]
  253. (util/stop e)
  254. (if-not (db/entity [:page/name "contents"])
  255. (handler/create-new-page! "contents")
  256. (handler/redirect! {:to :page
  257. :path-params {:name "contents"}})))}
  258. "Contents (edit)"]
  259. (contents)]
  260. :recent
  261. ["Recent" (recent-pages)]
  262. :help
  263. ["Help" (help)]
  264. :page-graph
  265. [(str "Graph of " (util/capitalize-all block-data))
  266. (page-graph block-data)]
  267. :heading-ref
  268. ["Block reference"
  269. (let [heading (:heading block-data)
  270. heading-id (:heading/uuid heading)
  271. format (:heading/format heading)]
  272. [[:div.ml-2.mt-1
  273. (heading/heading-parents repo heading-id format)]
  274. [:div.ml-2
  275. (heading-cp repo idx heading)]])]
  276. :heading
  277. (let [heading-id (:heading/uuid block-data)
  278. format (:heading/format block-data)]
  279. [(heading/heading-parents repo heading-id format)
  280. [:div.ml-2
  281. (heading-cp repo idx block-data)]])
  282. :page
  283. (let [page-name (get-in block-data [:page :page/name])]
  284. [[:a {:href (str "/page/" (util/url-encode page-name))}
  285. (util/capitalize-all page-name)]
  286. [:div.ml-2
  287. (page/page {:parameters {:path {:name page-name}}
  288. :sidebar? true
  289. :repo repo})]])
  290. :page-presentation
  291. (let [page-name (get-in block-data [:page :page/name])
  292. journal? (:journal? block-data)
  293. headings (db/get-page-headings repo page-name)
  294. headings (if journal?
  295. (rest headings)
  296. headings)
  297. sections (hiccup/build-slide-sections headings {:id "slide-reveal-js"
  298. :start-level 2
  299. :slide? true
  300. :sidebar? true})]
  301. [[:a {:href (str "/page/" (util/url-encode page-name))}
  302. (util/capitalize-all page-name)]
  303. [:div.ml-2.slide.mt-2
  304. (slide/slide sections)]])
  305. ["" [:span]]))
  306. (defn close
  307. ([on-close]
  308. (close nil on-close))
  309. ([class on-close]
  310. [:a.close.hover:text-gray-900.text-gray-500.flex.items-center
  311. (cond-> {:on-click on-close}
  312. class
  313. (assoc :class class))
  314. svg/close]))
  315. (rum/defc sidebar-item < rum/reactive
  316. [repo idx db-id block-type block-data]
  317. (let [collapse? (state/sub [:ui/sidebar-collapsed-blocks db-id])]
  318. [:div.sidebar-item.content
  319. (let [[title component] (build-sidebar-item repo idx db-id block-type block-data)]
  320. [:div.flex.flex-col
  321. [:div.flex.flex-row.justify-between
  322. [:div.flex.flex-row.justify-center
  323. [:a.hover:text-gray-900.text-gray-500.flex.items-center.pl-1.pr-1
  324. {:on-click #(state/sidebar-block-toggle-collapse! db-id)}
  325. (if collapse?
  326. (svg/caret-right)
  327. (svg/caret-down))]
  328. [:div.ml-1.font-medium
  329. title]]
  330. (close #(state/sidebar-remove-block! idx))]
  331. [:div {:class (if collapse? "hidden" "initial")}
  332. component]])]))
  333. (defn- get-page
  334. [match]
  335. (let [route-name (get-in match [:data :name])
  336. page (case route-name
  337. :page
  338. (get-in match [:path-params :name])
  339. :file
  340. (get-in match [:path-params :path])
  341. (date/journal-name))]
  342. (if page
  343. (util/url-decode (string/lower-case page)))))
  344. (defn get-current-page
  345. []
  346. (let [match (:route-match @state/state)
  347. theme (:ui/theme @state/state)]
  348. (get-page match)))
  349. (rum/defcs sidebar < rum/reactive
  350. [state]
  351. (let [blocks (state/sub :sidebar/blocks)
  352. repo (state/sub :git/current-repo)
  353. match (state/sub :route-match)
  354. theme (state/sub :ui/theme)
  355. dark? (= "dark" theme)]
  356. [:div#right-sidebar.flex.flex-col.p-2.shadow-xs.overflow-y-auto
  357. [:div#theme-selector.ml-3.mb-2
  358. [:div.flex.flex-row
  359. [:div.flex.flex-row {:key "right-sidebar-settings"}
  360. [:div.mr-4.text-sm
  361. [:a {:on-click (fn [e]
  362. (state/sidebar-add-block! repo "contents" :contents nil))}
  363. "Contents"]]
  364. [:div.mr-4.text-sm
  365. [:a {:on-click (fn [_e]
  366. (state/sidebar-add-block! repo "recent" :recent nil))}
  367. "Recent"]]
  368. [:div.mr-4.text-sm
  369. [:a {:on-click (fn []
  370. (when-let [page (get-current-page)]
  371. (state/sidebar-add-block!
  372. repo
  373. (str "page-graph-" page)
  374. :page-graph
  375. page)))}
  376. "Page graph"]]
  377. [:div.mr-4.text-sm
  378. (let [theme (if dark? "white" "dark")]
  379. [:a {:title (str "Switch to "
  380. theme
  381. " theme")
  382. :on-click (fn []
  383. (state/set-theme! theme))}
  384. (str (string/capitalize theme) " theme")])]
  385. [:div.mr-4.text-sm
  386. [:a {:on-click (fn [_e]
  387. (state/sidebar-add-block! repo "help" :help nil))}
  388. "Help"]]]]]
  389. (for [[idx [repo db-id block-type block-data]] (medley/indexed blocks)]
  390. (rum/with-key
  391. (sidebar-item repo idx db-id block-type block-data)
  392. (str "sidebar-block-" idx)))]))