Browse Source

feat(ui): enhance left sidebar (#2899)

* feat(ui): enhance left sidebar

Co-authored-by: Tienson Qin <[email protected]>
Charlie 4 years ago
parent
commit
7f9a04d2c1
40 changed files with 896 additions and 469 deletions
  1. 3 1
      resources/css/common.css
  2. 3 0
      resources/css/tabler-icons.min.css
  3. 1 0
      resources/electron.html
  4. BIN
      resources/fonts/tabler-icons.eot
  5. BIN
      resources/fonts/tabler-icons.woff
  6. BIN
      resources/fonts/tabler-icons.woff2
  7. 1 1
      resources/index.html
  8. 1 1
      src/electron/electron/updater.cljs
  9. 7 1
      src/main/frontend/components/block.cljs
  10. 3 0
      src/main/frontend/components/external.cljs
  11. 46 77
      src/main/frontend/components/header.cljs
  12. 0 24
      src/main/frontend/components/header.css
  13. 38 0
      src/main/frontend/components/modals.cljs
  14. 96 87
      src/main/frontend/components/page.cljs
  15. 4 6
      src/main/frontend/components/repo.cljs
  16. 15 6
      src/main/frontend/components/right_sidebar.cljs
  17. 5 0
      src/main/frontend/components/right_sidebar.css
  18. 3 4
      src/main/frontend/components/search.cljs
  19. 7 6
      src/main/frontend/components/settings.cljs
  20. 1 0
      src/main/frontend/components/settings.css
  21. 298 161
      src/main/frontend/components/sidebar.cljs
  22. 165 19
      src/main/frontend/components/sidebar.css
  23. 1 1
      src/main/frontend/components/svg.cljs
  24. 5 0
      src/main/frontend/db/model.cljs
  25. 23 9
      src/main/frontend/dicts.cljs
  26. 2 2
      src/main/frontend/extensions/pdf/highlights.cljs
  27. 8 0
      src/main/frontend/extensions/pdf/pdf.css
  28. 10 0
      src/main/frontend/extensions/srs.cljs
  29. 4 0
      src/main/frontend/handler/events.cljs
  30. 72 34
      src/main/frontend/handler/page.cljs
  31. 20 0
      src/main/frontend/handler/repo.cljs
  32. 5 0
      src/main/frontend/handler/ui.cljs
  33. 21 13
      src/main/frontend/modules/shortcut/config.cljs
  34. 7 2
      src/main/frontend/state.cljs
  35. 10 7
      src/main/frontend/ui.cljs
  36. 7 0
      src/main/logseq/api.cljs
  37. 2 2
      templates/config.edn
  38. 1 0
      templates/favorites.md
  39. 1 0
      templates/favorites.org
  40. 0 5
      yarn.lock

+ 3 - 1
resources/css/common.css

@@ -4,10 +4,12 @@
   --ls-page-text-size: 1em;
   --ls-page-title-size: 36px;
   --ls-font-family: 'Inter';
-  --ls-main-content-max-width: 740px;
+  --ls-main-content-max-width: 820px;
   --ls-main-content-max-width-wide: 100%;
   --ls-border-radius-low: 4px;
   --ls-border-radius-medium: 8px;
+  --ls-left-sidebar-width: 240px;
+  --ls-left-sidebar-nav-btn-size: 38px;
 }
 
 @media (prefers-color-scheme: dark) {

File diff suppressed because it is too large
+ 3 - 0
resources/css/tabler-icons.min.css


+ 1 - 0
resources/electron.html

@@ -3,6 +3,7 @@
 <head>
   <meta charset="utf-8">
   <meta content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no" name="viewport">
+  <link type="text/css" rel="stylesheet" href="./css/tabler-icons.min.css">
   <link href="./css/style.css" rel="stylesheet" type="text/css">
   <link href="./img/logo.png" rel="shortcut icon" type="image/png">
   <link href="./img/logo.png" rel="shortcut icon" sizes="192x192">

BIN
resources/fonts/tabler-icons.eot


BIN
resources/fonts/tabler-icons.woff


BIN
resources/fonts/tabler-icons.woff2


+ 1 - 1
resources/index.html

@@ -3,8 +3,8 @@
 <head>
   <meta charset="utf-8">
   <meta content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no" name="viewport">
+  <link rel="stylesheet" type="text/css" href="./css/tabler-icons.min.css">
   <link href="./css/style.css" rel="stylesheet" type="text/css">
-  <link rel="stylesheet" href="https://unpkg.com/@tabler/icons@latest/iconfont/tabler-icons.min.css">
   <link href="./img/logo.png" rel="shortcut icon" type="image/png">
   <link href="./img/logo.png" rel="shortcut icon" sizes="192x192">
   <link href="./img/logo.png" rel="apple-touch-icon">

+ 1 - 1
src/electron/electron/updater.cljs

@@ -113,7 +113,7 @@
 (defn- new-version-downloaded-cb
   [_ & args]
   (.info logger "[update-downloaded]" args)
-  (when-let [web-contents (and @*win (. @*win -webContents))]
+  (when-let [web-contents (and @*win (. ^js @*win -webContents))]
     (.send web-contents "auto-updater-downloaded" (bean/->js args))))
 
 (defn init-auto-updater

+ 7 - 1
src/main/frontend/components/block.cljs

@@ -34,6 +34,7 @@
             [frontend.components.plugins :as plugins]
             [frontend.handler.plugin :as plugin-handler]
             [frontend.handler.block :as block-handler]
+            [frontend.handler.page :as page-handler]
             [frontend.handler.dnd :as dnd]
             [frontend.handler.editor :as editor-handler]
             [frontend.handler.repeated :as repeated]
@@ -373,6 +374,9 @@
       :on-mouse-down
       (fn [e]
         (util/stop e)
+        (when redirect-page-name
+          (page-handler/add-page-to-recent! (state/get-current-repo) redirect-page-name)
+          (js/setTimeout #(model/refresh-recent-pages) 300))
         (let [create-first-block! (fn []
                                     (when-not (editor-handler/add-default-title-property-if-needed! redirect-page-name)
                                       (editor-handler/insert-first-page-block-if-not-exists! redirect-page-name)))]
@@ -389,7 +393,9 @@
               (create-first-block!)
               (route-handler/redirect! {:to :page
                                         :path-params {:name redirect-page-name}}))))
-        (when (and contents-page? (state/get-left-sidebar-open?))
+        (when (and contents-page?
+                   (util/mobile?)
+                   (state/get-left-sidebar-open?))
           (ui-handler/close-left-sidebar!)))}
 
      (if (and (coll? children) (seq children))

+ 3 - 0
src/main/frontend/components/external.cljs

@@ -35,6 +35,9 @@
                             (.readAsText reader file)))
                         (notification/show! "Please choose a JSON file."
                                             :error))))}]
+
+     [:hr]
+
      [:div.mt-4
       (case roam-importing?
         true (ui/loading "Loading")

+ 46 - 77
src/main/frontend/components/header.cljs

@@ -2,6 +2,7 @@
   (:require [frontend.components.export :as export]
             [frontend.components.plugins :as plugins]
             [frontend.components.repo :as repo]
+            [frontend.components.page :as page]
             [frontend.components.right-sidebar :as sidebar]
             [frontend.components.search :as search]
             [frontend.components.svg :as svg]
@@ -22,20 +23,14 @@
             [rum.core :as rum]
             [frontend.mobile.util :as mobile-util]))
 
-(rum/defc home-btn < rum/reactive
-  [{:keys [white? electron-mac?]}]
-  [:a.cp__header-logo
-   {:class (when electron-mac? "button")
-    :href     (rfe/href :home)
+(rum/defc home-button
+  []
+  [:a.button
+   {:href     (rfe/href :home)
     :on-click (fn []
                 (util/scroll-to-top)
                 (state/set-journals-length! 2))}
-   (if electron-mac?
-     svg/home
-     (if-let [logo (and config/publishing?
-                        (get-in (state/get-config) [:project :logo]))]
-       [:img.cp__header-logo-img {:src logo}]
-       (svg/logo (not white?))))])
+   (ui/icon "home" {:style {:fontSize 20}})])
 
 (rum/defc login
   [logged?]
@@ -62,54 +57,38 @@
 
 (rum/defc left-menu-button < rum/reactive
   [{:keys [on-click]}]
-  [:button#left-menu.cp__header-left-menu
-   {:on-click on-click}
-   [:svg.h-6.w-6
-    {:viewBox "0 0 24 24", :fill "none", :stroke "currentColor"}
-    [:path
-     {:d "M4 6h16M4 12h16M4 18h7"
-      :stroke-width "2"
-      :stroke-linejoin "round"
-      :stroke-linecap "round"}]]])
+  (let [left-sidebar-open? (state/sub :ui/left-sidebar-open?)]
+
+    (ui/tippy
+      {:html [:div.text-sm.font-medium
+              "Shortcut: "
+              [:code (util/->platform-shortcut "t l")]]
+       :delay 2000
+       :hideDelay 1
+       :position "right"
+       :interactive true
+       :arrow true}
+
+      [:a#left-menu.cp__header-left-menu.button
+       {:on-click on-click
+        :style {:margin-left 10}}
+       (ui/icon "menu-2" {:style {:fontSize 20}})])))
 
 (rum/defc dropdown-menu < rum/reactive
   [{:keys [me current-repo t default-home]}]
   (let [projects (state/sub [:me :projects])
         developer-mode? (state/sub [:ui/developer-mode?])
-        logged? (state/logged?)]
+        logged? (state/logged?)
+        page-menu (page/page-menu t)
+        page-menu-and-hr (when (seq page-menu)
+                           (concat page-menu [{:hr true}]))]
     (ui/dropdown-with-links
      (fn [{:keys [toggle-fn]}]
-       [:a.cp__right-menu-button.button
+       [:a.button
         {:on-click toggle-fn}
-        (svg/horizontal-dots nil)])
+        (ui/icon "dots" {:style {:fontSize 20}})])
      (->>
-      [(when current-repo
-         {:title (t :cards-view)
-          :options {:on-click #(state/pub-event! [:modal/show-cards])}})
-
-       (when current-repo
-         {:title (t :graph-view)
-          :options {:href (rfe/href :graph)}
-          :icon svg/graph-sm})
-
-       (when current-repo
-         {:title (t :all-pages)
-          :options {:href (rfe/href :all-pages)}
-          :icon svg/pages-sm})
-
-       (when (and current-repo (not config/publishing?))
-         {:title (t :all-files)
-          :options {:href (rfe/href :all-files)}
-          :icon svg/folder-sm})
-
-       (when (and default-home current-repo)
-         {:title (t :all-journals)
-          :options {:href (rfe/href :all-journals)}
-          :icon svg/calendar-sm})
-
-       {:hr true}
-
-       (when-not (state/publishing-enable-editing?)
+      [(when-not (state/publishing-enable-editing?)
          {:title (t :settings)
           :options {:on-click state/open-settings!}
           :icon svg/settings-sm})
@@ -123,7 +102,7 @@
           :options {:on-click #(plugins/open-select-theme!)}})
 
        (when current-repo
-         {:title (t :export)
+         {:title (t :export-graph)
           :options {:on-click #(state/set-modal! export/export)}
           :icon nil})
 
@@ -142,6 +121,7 @@
          {:title (t :sign-out)
           :options {:on-click user-handler/sign-out!}
           :icon svg/logout-sm})]
+      (concat page-menu-and-hr)
       (remove nil?))
      ;; {:links-footer (when (and (util/electron?) (not logged?))
      ;;                  [:div.px-2.py-2 (login logged?)])}
@@ -202,11 +182,9 @@
                                (js/window.apis.toggleMaxOrMinActiveWindow))))}
        (left-menu-button {:on-click (fn []
                                       (open-fn)
-                                      (state/set-left-sidebar-open! true))})
-
+                                      (state/set-left-sidebar-open!
+                                        (not (:ui/left-sidebar-open? @state/state))))})
 
-       (home-btn {:white? white? :electron-mac? electron-mac?})
-       (when electron-mac? (back-and-forward true))
 
        (when current-repo
          (ui/tippy
@@ -215,21 +193,26 @@
                   ;; TODO: Pull from config so it displays custom shortcut, not just the default
                   [:code (util/->platform-shortcut "Ctrl + k")]]
            :interactive true
+           :delay [2000, 0]
            :arrow true}
           [:a.button#search-button
            {:on-click #(state/pub-event! [:go/search])}
-           svg/search]))
-
-       (when electron-not-mac? (back-and-forward))
+           (ui/icon "search" {:style {:fontSize 20}})]))
 
        [:div.flex-1.flex] ;; Spacer in the middle ------------------------------
 
+       (when (and
+              (not (mobile-util/is-native-platform?))
+              (not (util/electron?)))
+         (login logged?))
+
        (when plugin-handler/lsp-enabled?
          (plugins/hook-ui-items :toolbar))
 
-       [:a (when refreshing?
-             [:div {:class "animate-spin-reverse"}
-              svg/refresh])]
+       (when (not= (state/get-current-route) :home)
+         (home-button))
+
+       (when electron-mac? (back-and-forward electron-mac?))
 
        (new-block-mode)
 
@@ -237,17 +220,8 @@
          [:div {:class "animate-spin-reverse"}
           svg/refresh])
 
-       (when (and
-              (not (mobile-util/is-native-platform?))
-              (not (util/electron?)))
-         (login logged?))
-
        (repo/sync-status current-repo)
 
-       (when-not (util/mobile?)
-         [:div.repos
-          (repo/repos-dropdown nil nil)])
-
        (when show-open-folder?
          [:a.text-sm.font-medium.button
           {:on-click #(page-handler/ls-dir-files! shortcut/refresh!)}
@@ -261,17 +235,12 @@
          [:a.text-sm.font-medium.button {:href (rfe/href :graph)}
           (t :graph)])
 
-       ;; Go to Keyboard Shortcuts page
-       [:a.button
-        {:title "Keyboard shortcuts"
-         :on-click (fn [] (route-handler/redirect! {:to :shortcut-setting}))}
-        (svg/icon-cmd 20)]
-
        (dropdown-menu {:me me
                        :t t
                        :current-repo current-repo
                        :default-home default-home})
 
-       (when (not (state/sub :ui/sidebar-open?)) (sidebar/toggle))
+       (when (not (state/sub :ui/sidebar-open?))
+         (sidebar/toggle))
 
        (updater-tips-new-version t)])))

+ 0 - 24
src/main/frontend/components/header.css

@@ -13,7 +13,6 @@
   left: 0;
   right: 0;
   user-select: none;
-  transition: width 0.3s;
   line-height: 1;
 
   .it svg {
@@ -80,10 +79,6 @@
 
 .is-electron.is-mac .cp__header {
   padding-left: 78px;
-  -moz-transition: padding-left .3s ease-in;
-  -o-transition: padding-left .3s ease-in;
-  -webkit-transition: padding-left .3s ease-in;
-  transition: padding-left .3s ease-in;
 }
 
 .cp__header .navigation svg {
@@ -98,19 +93,6 @@
   -webkit-app-region: no-drag;
 }
 
-.cp__header-left-menu {
-  @apply px-4 mr-4;
-  border-right: 1px solid var(--ls-secondary-background-color);
-  color: var(--ls-link-text-color);
-  display: block;
-  height: 100%;
-}
-
-.cp__header-left-menu:focus {
-  @apply outline-none;
-  background: var(--ls-menu-hover-color);
-}
-
 .cp__header-logo {
   @apply p-2;
 }
@@ -133,10 +115,6 @@
     @apply shadow-none;
   }
 
-  .cp__header-left-menu {
-    display: none;
-  }
-
   .cp__header-logo {
     display: block;
   }
@@ -147,13 +125,11 @@
 }
 
 #repo-name {
-  vertical-align: middle;
   display: inline;
   white-space: nowrap;
   overflow: hidden;
   text-overflow: ellipsis;
   max-width: 7ch;
-  color: var(--ls-icon-color, #045591);
   height: 14px;
 }
 

+ 38 - 0
src/main/frontend/components/modals.cljs

@@ -0,0 +1,38 @@
+(ns frontend.components.modals
+  (:require [frontend.db.model :as db-model]
+            [frontend.handler.page :as page-handler]
+            [frontend.handler.route :as route-handler]
+            [frontend.ui :as ui]
+            [clojure.string :as string]
+            [rum.core :as rum]
+            [frontend.state :as state]
+            [frontend.mixins :as mixins]
+            [frontend.context.i18n :as i18n]
+            [goog.dom :as gdom]))
+
+(rum/defcs new-page-modal <
+           (mixins/event-mixin
+             (fn [state]
+               (mixins/on-enter
+                 state
+                 :node (gdom/getElement "input-new-page-title")
+                 :on-enter (fn [^js e]
+                             (let [^js target (.-target e)
+                                   value (.-value target)]
+                               (when-let [title (and (not (string/blank? (string/trim value))) value)]
+                                 (if (db-model/page-empty? (state/get-current-repo) title)
+                                   (page-handler/create! title {:redirect? true})
+                                   (route-handler/redirect! {:to :page
+                                                             :path-params {:name title}}))))))))
+           [state]
+           (rum/with-context
+             [[t] i18n/*tongue-context*]
+             [:div
+              [:h2.text-xl.pb-4 (t :new-page)]
+              [:input#input-new-page-title.form-input
+               {:autoFocus true
+                :placeholder "page title"}]
+              [:p.py-2.flex (ui/button (t :submit))]]))
+
+(defn show-new-page-modal! []
+  (state/set-modal! new-page-modal))

+ 96 - 87
src/main/frontend/components/page.cljs

@@ -48,9 +48,7 @@
   (when page-name
     (if block?
       (db/get-block-and-children repo block-id)
-      (do
-        (page-handler/add-page-to-recent! repo page-original-name)
-        (db/get-page-blocks repo page-name)))))
+      (db/get-page-blocks repo page-name))))
 
 (defn- open-first-block!
   [state]
@@ -270,87 +268,97 @@
          {:default-collapsed? false})]])))
 
 (defn page-menu
-  [repo t page page-name page-original-name title journal? public? developer-mode?]
-  (let [contents? (= (string/lower-case (str page-name)) "contents")
-        links (fn [] (->>
-                     [(when-not contents?
-                        {:title   (t :page/add-to-favorites)
-                         :options {:on-click (fn [] (page-handler/handle-add-page-to-contents! page-original-name))}})
-
-                      {:title "Go to presentation mode"
-                       :options {:on-click (fn []
-                                             (state/sidebar-add-block!
-                                              repo
-                                              (:db/id page)
-                                              :page-presentation
-                                              {:page page}))}}
-                      (when (and (not contents?)
-                                 (not journal?))
-                        {:title   (t :page/rename)
-                         :options {:on-click #(state/set-modal! (rename-page-dialog title page-name))}})
-
-                      (when-let [file-path (and (util/electron?) (page-handler/get-page-file-path))]
-                        [{:title   (t :page/open-in-finder)
-                          :options {:on-click #(js/window.apis.showItemInFolder file-path)}}
-                         {:title   (t :page/open-with-default-app)
-                          :options {:on-click #(js/window.apis.openPath file-path)}}])
-
-                      (when-not contents?
-                        {:title   (t :page/delete)
-                         :options {:on-click #(state/set-modal! (delete-page-dialog page-name))}})
-
-                      (when (state/get-current-page)
-                        {:title   (t :export)
-                         :options {:on-click #(state/set-modal!
-                                               (fn []
-                                                 (export/export-blocks [(:block/uuid page)])))}})
-
-                      (when (util/electron?)
-                        {:title   (t (if public? :page/make-private :page/make-public))
-                         :options {:on-click
-                                   (fn []
-                                     (page-handler/update-public-attribute!
-                                      page-name
-                                      (if public? false true))
-                                     (state/close-modal!))}})
-
-                      (when (util/electron?)
-                        {:title   (t :page/version-history)
-                         :options {:on-click
+  [t]
+  (when-let [page-name (and (state/get-current-page)
+                            (string/lower-case (state/get-current-page)))]
+    (let [repo (state/sub :git/current-repo)
+          page (and page-name (db/entity repo [:block/name page-name]))
+          page-original-name (:block/original-name page)
+          journal? (db/journal-page? page-name)
+          block? (and page (util/uuid-string? page-name))
+          contents? (= (string/lower-case (str page-name)) "contents")
+          {:keys [title] :as properties} (:block/properties page)
+          title (or title page-original-name page-name)
+          public? (true? (:public properties))
+          favorites (:favorites (state/sub-graph-config))
+          favorited? (contains? (set (map string/lower-case favorites))
+                                (string/lower-case page-name))
+          developer-mode? (state/sub [:ui/developer-mode?])]
+      (when (and page (not block?))
+        (->>
+         [{:title   (if favorited?
+                      (t :page/unfavorite)
+                      (t :page/add-to-favorites))
+           :options {:on-click
+                     (fn []
+                       (if favorited?
+                         (page-handler/unfavorite-page! page-original-name)
+                         (page-handler/favorite-page! page-original-name)))}}
+
+          {:title (t :page/presentation-mode)
+           :options {:on-click (fn []
+                                 (state/sidebar-add-block!
+                                  repo
+                                  (:db/id page)
+                                  :page-presentation
+                                  {:page page}))}}
+          (when (and (not contents?)
+                     (not journal?))
+            {:title   (t :page/rename)
+             :options {:on-click #(state/set-modal! (rename-page-dialog title page-name))}})
+
+          (when-let [file-path (and (util/electron?) (page-handler/get-page-file-path))]
+            [{:title   (t :page/open-in-finder)
+              :options {:on-click #(js/window.apis.showItemInFolder file-path)}}
+             {:title   (t :page/open-with-default-app)
+              :options {:on-click #(js/window.apis.openPath file-path)}}])
+
+          (when-not contents?
+            {:title   (t :page/delete)
+             :options {:on-click #(state/set-modal! (delete-page-dialog page-name))}})
+
+          (when (state/get-current-page)
+            {:title   (t :export-page)
+             :options {:on-click #(state/set-modal!
                                    (fn []
-                                     (shell/get-file-latest-git-log page 100))}})
-
-                      (when plugin-handler/lsp-enabled?
-                        (for [[_ {:keys [key label] :as cmd} action pid] (state/get-plugins-commands-with-type :page-menu-item)]
-                          {:title label
-                           :options {:on-click #(commands/exec-plugin-simple-command!
-                                                 pid (assoc cmd :page (state/get-current-page)) action)}}))
-
-                      (when developer-mode?
-                        {:title   "(Dev) Show page data"
-                         :options {:on-click (fn []
-                                               (let [page-data (with-out-str (pprint/pprint (db/pull (:db/id page))))]
-                                                 (println page-data)
-                                                 (notification/show!
-                                                  [:div
-                                                   [:pre.code page-data]
-                                                   [:br]
-                                                   (ui/button "Copy to clipboard"
-                                                     :on-click #(.writeText js/navigator.clipboard page-data))]
-                                                  :success
-                                                  false)))}})]
-                     (flatten)
-                     (remove nil?)))]
-    (ui/dropdown-with-links
-     (fn [{:keys [toggle-fn]}]
-       [:a.cp__vertical-menu-button
-        {:title    "More options"
-         :on-click toggle-fn}
-        (svg/vertical-dots nil)])
-     links
-     {:modal-class (util/hiccup->class
-                    "origin-top-right.absolute.right-0.top-10.mt-2.rounded-md.shadow-lg.whitespace-nowrap.dropdown-overflow-auto.page-drop-options")
-      :z-index     1})))
+                                     (export/export-blocks [(:block/uuid page)])))}})
+
+          (when (util/electron?)
+            {:title   (t (if public? :page/make-private :page/make-public))
+             :options {:on-click
+                       (fn []
+                         (page-handler/update-public-attribute!
+                          page-name
+                          (if public? false true))
+                         (state/close-modal!))}})
+
+          (when (util/electron?)
+            {:title   (t :page/version-history)
+             :options {:on-click
+                       (fn []
+                         (shell/get-file-latest-git-log page 100))}})
+
+          (when plugin-handler/lsp-enabled?
+            (for [[_ {:keys [key label] :as cmd} action pid] (state/get-plugins-commands-with-type :page-menu-item)]
+              {:title label
+               :options {:on-click #(commands/exec-plugin-simple-command!
+                                     pid (assoc cmd :page (state/get-current-page)) action)}}))
+
+          (when developer-mode?
+            {:title   "(Dev) Show page data"
+             :options {:on-click (fn []
+                                   (let [page-data (with-out-str (pprint/pprint (db/pull (:db/id page))))]
+                                     (println page-data)
+                                     (notification/show!
+                                      [:div
+                                       [:pre.code page-data]
+                                       [:br]
+                                       (ui/button "Copy to clipboard"
+                                         :on-click #(.writeText js/navigator.clipboard page-data))]
+                                      :success
+                                      false)))}})]
+         (flatten)
+         (remove nil?))))))
 
 ;; A page is just a logical block
 (rum/defcs page < rum/reactive
@@ -429,10 +437,7 @@
                  [:div.flex.flex-row
                   (when plugin-handler/lsp-enabled?
                     (plugins/hook-ui-slot :page-head-actions-slotted nil)
-                    (plugins/hook-ui-items :pagebar))
-
-                  (page-menu repo t page page-name page-original-name title
-                             journal? public? developer-mode?)])])
+                    (plugins/hook-ui-items :pagebar))])])
             [:div
              (when (and block? (not sidebar?))
                (let [config {:id "block-parent"
@@ -750,6 +755,10 @@
     (rum/with-context [[t] i18n/*tongue-context*]
       [:div.flex-1
        [:h1.title (t :all-pages)]
+       [:a.ml-1.opacity-70.hover:opacity-100 {:href (rfe/href :all-files)}
+        [:span
+         (ui/icon "files")
+         [:span.ml-1 (t :all-files)]]]
        (when current-repo
          (let [pages (->> (page-handler/get-all-pages current-repo)
                          (map (fn [page] (assoc page :block/backlinks (count (:block/_refs (db/entity (:db/id page)))))))

+ 4 - 6
src/main/frontend/components/repo.cljs

@@ -189,9 +189,7 @@
         (when (seq repos)
           (ui/dropdown-with-links
            (fn [{:keys [toggle-fn]}]
-             [:a#repo-switch.block.pr-2.whitespace-nowrap {:on-click toggle-fn
-                                                           :class (when-not (util/mobile?)
-                                                                    "fade-link")}
+             [:a#repo-switch.block.pr-2.whitespace-nowrap {:on-click toggle-fn}
               [:span
                (let [repo-name (get-repo-name current-repo)
                      repo-name (if (or (util/electron?)
@@ -199,8 +197,8 @@
                                  (last
                                   (string/split repo-name #"/"))
                                  repo-name)]
-                 [:span#repo-name {:title repo-name} repo-name])
-               [:span.dropdown-caret.ml-1 {:style {:border-top-color "#6b7280"}}]]])
+                 [:span#repo-name.font-medium {:title repo-name} repo-name])
+               [:span.dropdown-caret.ml-2 {:style {:border-top-color "#6b7280"}}]]])
            (mapv
             (fn [{:keys [id url]}]
               {:title (get-repo-name url)
@@ -219,7 +217,7 @@
             switch-repos)
            (cond->
             {:modal-class (util/hiccup->class
-                           "origin-top-right.absolute.left-0.mt-2.w-48.rounded-md.shadow-lg")
+                           "origin-top-right.absolute.left-0.mt-2.rounded-md.shadow-lg")
              :links-footer [:div
                             (when (seq switch-repos) [:hr.my-4])
                             [:a {:class "block px-4 py-2 text-sm transition ease-in-out duration-150 cursor menu-link"

+ 15 - 6
src/main/frontend/components/right_sidebar.cljs

@@ -22,8 +22,19 @@
 (rum/defc toggle
   []
   (when-not (util/mobile?)
-    [:a.button {:on-click state/toggle-sidebar-open?!}
-    (svg/menu)]))
+    (ui/tippy
+      {:html [:div.text-sm.font-medium
+              "Shortcut: "
+              [:code (util/->platform-shortcut "t r")]]
+       :delay 2000
+       :hideDelay 1
+       :position "left"
+       :interactive true
+       :arrow true}
+
+      [:a.button.fade-link.toggle
+       {:on-click state/toggle-sidebar-open?!}
+       (ui/icon "layout-sidebar-right" {:style {:fontSize "20px"}})])))
 
 (rum/defc block-cp < rum/reactive
   [repo idx block]
@@ -49,8 +60,7 @@
   [repo idx db-id block-type block-data t]
   (case block-type
     :contents
-    [(or (state/get-favorites-name)
-         (t :right-side-bar/favorites))
+    [(t :right-side-bar/contents)
      (contents)]
 
     :help
@@ -215,8 +225,7 @@
         [:div.ml-4.text-sm
          [:a.cp__right-sidebar-settings-btn {:on-click (fn [e]
                                                          (state/sidebar-add-block! repo "contents" :contents nil))}
-          (or (state/get-favorites-name)
-              (t :right-side-bar/favorites))]]
+          (t :right-side-bar/contents)]]
 
         [:div.ml-4.text-sm
          [:a.cp__right-sidebar-settings-btn {:on-click (fn []

+ 5 - 0
src/main/frontend/components/right_sidebar.css

@@ -14,3 +14,8 @@ html[data-theme=light] {
 .sidebar-item-list {
      padding-bottom: 150px;
 }
+
+
+html[data-theme='light'] a.toggle:hover {
+    color: var(--ls-primary-text-color);
+}

+ 3 - 4
src/main/frontend/components/search.cljs

@@ -284,11 +284,10 @@
                                  "Tip: " [:code (util/->platform-shortcut "Ctrl+Shift+p")] " to open the commands palette"]
                           :interactive     true
                           :arrow true}
-                         [:a.inline-block
-                          {:style {:margin-top 1
-                                   :margin-left 12}
+                         [:a.inline-block.fade-link
+                          {:style {:margin-left 12}
                            :on-click #(state/toggle! :ui/command-palette-open?)}
-                          (svg/icon-cmd 20)])])]
+                          (ui/icon "command" {:style {:font-size 20}})])])]
    (let [recent-search (mapv (fn [q] {:type :search :data q}) (db/get-key-value :recent/search))
          pages (->> (db/get-key-value :recent/pages)
                     (remove nil?)

+ 7 - 6
src/main/frontend/components/settings.cljs

@@ -584,18 +584,19 @@
 
         [:aside.md:w-64
          [:ul
-          (for [[label text icon] [[:general (t :settings-page/tab-general) (svg/adjustments 16)]
-                                   [:editor (t :settings-page/tab-editor) (svg/icon-editor 16)]
-                                   [:shortcuts (t :settings-page/tab-shortcuts) (svg/icon-cmd 18)]
-                                   [:git (t :settings-page/tab-version-control) svg/git]
-                                   [:advanced (t :settings-page/tab-advanced) (svg/icon-cli 16)]]]
+          (for [[label text icon] [[:general (t :settings-page/tab-general) (ui/icon "adjustments" {:style {:font-size 20}})]
+                                   [:editor (t :settings-page/tab-editor) (ui/icon "writing" {:style {:font-size 20}})]
+                                   [:shortcuts (t :settings-page/tab-shortcuts) (ui/icon "command" {:style {:font-size 20}})]
+                                   [:git (t :settings-page/tab-version-control) (ui/icon "history" {:style {:font-size 20}})]
+                                   [:advanced (t :settings-page/tab-advanced) (ui/icon "bulb" {:style {:font-size 20}})]]]
 
             [:li
              {:class    (util/classnames [{:active (= label @*active)}])
               :on-click #(reset! *active label)}
 
              [:a.flex.items-center
-              [[:i.flex.items-center icon] [:strong text]]]])]]
+              icon
+              [:strong text]]])]]
 
         [:article
 

+ 1 - 0
src/main/frontend/components/settings.css

@@ -44,6 +44,7 @@
               font-size: 14px;
               font-weight: normal;
               padding-left: 5px;
+              margin-top: 2px;
               opacity: .9;
             }
           }

+ 298 - 161
src/main/frontend/components/sidebar.cljs

@@ -9,19 +9,25 @@
             [frontend.components.settings :as settings]
             [frontend.components.theme :as theme]
             [frontend.components.widgets :as widgets]
+            [frontend.components.modals :refer [show-new-page-modal!]]
             [frontend.config :as config]
             [frontend.context.i18n :as i18n]
             [frontend.db :as db]
+            [frontend.db.model :as db-model]
+            [frontend.components.svg :as svg]
             [frontend.db-mixins :as db-mixins]
             [frontend.handler.editor :as editor-handler]
             [frontend.handler.route :as route-handler]
+            [frontend.handler.page :as page-handler]
             [frontend.mixins :as mixins]
             [frontend.modules.shortcut.data-helper :as shortcut-dh]
             [frontend.state :as state]
             [frontend.ui :as ui]
             [frontend.util :as util]
+            [reitit.frontend.easy :as rfe]
             [goog.dom :as gdom]
-            [rum.core :as rum]))
+            [rum.core :as rum]
+            [frontend.extensions.srs :as srs]))
 
 (defn nav-item
   [title href svg-d active? close-modal-fn]
@@ -37,34 +43,155 @@
       :stroke-linecap "round"}]]
    title])
 
+(rum/defc nav-content-item
+  [name {:keys [edit-fn class] :as opts} child]
+
+  [:div.nav-content-item.is-expand
+   {:class class}
+   [:div.hd.items-center.mb-2
+    {:on-click (fn [^js/MouseEvent e]
+                 (let [^js target (.-target e)
+                       ^js parent (.closest target ".nav-content-item")]
+                   (.toggle (.-classList parent) "is-expand")))}
+
+    [:a.font-medium.fade-link name]
+    [:span
+     [:a.more svg/arrow-down-v2]]]
+   [:div.bd child]])
+
+;; TODO: enhance
+(defn- pick-one-ast-page-ref
+  [block]
+  (when-let [title-ast (and block (:block/title block))]
+    (when-let [link-ref (and (= (ffirst title-ast) "Link")
+                             (:url (second (first title-ast))))]
+      (when (= "Page_ref" (first link-ref))
+        (second link-ref)))))
+
+(defn- delta-y
+  [e]
+  (let [rect (.. (.. e -target) getBoundingClientRect)]
+    (- (.. e -pageY) (.. rect -top))))
+
+(defn- move-up?
+  [e]
+  (let [delta (delta-y e)]
+    (< delta 14)))
+
+(rum/defcs favorite-item <
+  (rum/local nil ::up?)
+  (rum/local nil ::dragging-over)
+  [state t name]
+  (let [up? (get state ::up?)
+        dragging-over (get state ::dragging-over)
+        target (state/sub :favorites/dragging)]
+    [:li.favorite-item
+     {:key name
+      :class (if (and target @dragging-over (not= target @dragging-over))
+               "dragging-target"
+               "")
+      :draggable true
+      :on-drag-start (fn [event]
+                       (state/set-state! :favorites/dragging name))
+      :on-drag-over (fn [e]
+                      (util/stop e)
+                      (reset! dragging-over name)
+                      (when-not (= name (get @state/state :favorites/dragging))
+                        (reset! up? (move-up? e))))
+      :on-drag-leave (fn [e]
+                       (reset! dragging-over nil))
+      :on-drop (fn [e]
+                 (page-handler/reorder-favorites! {:to name
+                                                   :up? (move-up? e)})
+                 (reset! up? nil)
+                 (reset! dragging-over nil))}
+     [:a {:href (rfe/href :page {:name name})}
+      name]]))
+
+(rum/defc favorites < rum/reactive
+  [t]
+  (nav-content-item
+   [:a.flex.items-center.text-sm.font-medium.rounded-md
+    (ui/icon "star mr-1" {:style {:font-size 18}})
+    [:span.flex-1.uppercase {:style {:padding-top 2}}
+     (t :left-side-bar/nav-favorites)]]
+
+   {:class "favorites"
+    :edit-fn
+    (fn [e]
+      (rfe/push-state :page {:name "Favorites"})
+      (util/stop e))}
+
+   (let [favorites (:favorites (state/sub-graph-config))]
+     (when (seq favorites)
+       [:ul.favorites
+        (for [name favorites]
+          (when-not (string/blank? name)
+            (favorite-item t name)))]))))
+
+(rum/defc recent-pages
+  < rum/reactive db-mixins/query
+  [t]
+
+  (nav-content-item
+   [:a.flex.items-center.text-sm.font-medium.rounded-md
+    (ui/icon "history mr-1" {:style {:font-size 18}})
+    [:span.flex-1.uppercase {:style {:padding-top 2}}
+     (t :left-side-bar/nav-recent-pages)]]
+
+   {:class "recent"}
+
+   (let [pages (state/sub :editor/recent-pages)]
+
+     [:ul
+      (for [name pages]
+        [:li {:key name}
+         [:a {:href (rfe/href :page {:name name})} name]])])))
+
+(rum/defc flashcards < rum/reactive
+  []
+  (let [num (srs/get-srs-cards-total)]
+    [:a.item.group.flex.items-center.px-2.py-2.text-sm.font-medium.rounded-md {:on-click #(state/pub-event! [:modal/show-cards])}
+     (ui/icon "infinity mr-3" {:style {:font-size 20}})
+     [:span.flex-1 "Flashcards"]
+     (when (and num (not (zero? num)))
+       [:span.ml-3.inline-block.py-0.5.px-3.text-xs.font-medium.rounded-full num])]))
+
 (rum/defc sidebar-nav < rum/reactive
   [route-match close-modal-fn]
-  (let [white? (= "white" (state/sub :ui/theme))
-        active? (fn [route] (= route (get-in route-match [:data :name])))
-        page-active? (fn [page]
-                       (= page (get-in route-match [:parameters :path :name])))
-        right-sidebar? (state/sub :ui/sidebar-open?)
-        left-sidebar? (state/sub :ui/left-sidebar-open?)]
-    (when left-sidebar?
-      [:nav.flex-1.left-sidebar-inner
-       (nav-item "Journals" "#/"
-                 "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"
-                 (active? :home)
-                 close-modal-fn)
-       (nav-item "All Pages" "#/all-pages"
-                 "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"
-                 (active? :all-pages)
-                 close-modal-fn)
-       (when-not config/publishing?
-         (nav-item "All Files" "#/all-files"
-                   "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"
-                   (active? :all-files)
-                   close-modal-fn))
-       (when-not right-sidebar?
-         [:div.pl-4.pr-4 {:style {:height 1
-                                  :background-color (if white? "#f0f8ff" "#073642")
-                                  :margin 12}}])
-       (right-sidebar/contents)])))
+  (rum/with-context [[t] i18n/*tongue-context*]
+    (let [active? (fn [route] (= route (get-in route-match [:data :name])))
+          page-active? (fn [page]
+                         (= page (get-in route-match [:parameters :path :name])))
+          left-sidebar? (state/sub :ui/left-sidebar-open?)
+          white? (= "white" (state/sub :ui/theme))]
+      (when left-sidebar?
+        [:div.left-sidebar-inner.flex-1.flex.flex-col.min-h-0
+         [:div.flex.flex-col.pt-5.pb-4.overflow-y-auto
+          (when-not (util/mobile?)
+            [:div.flex.items-center.flex-shrink-0.px-4.mb-5
+             [:a {:href (rfe/href :home)}
+              (svg/logo (not white?))]
+             [:div.repos.ml-2
+              (repo/repos-dropdown nil nil)]])
+          [:nav.flex-1.px-2.space-y-1 {:aria-label "Sidebar"}
+           [:a.item.group.flex.items-center.px-2.py-2.text-sm.font-medium.rounded-md {:href (rfe/href :all-journals)}
+            (ui/icon "calendar mr-3" {:style {:font-size 20}})
+            [:span.flex-1 "Journals"]]
+
+           (flashcards)
+
+           [:a.item.group.flex.items-center.px-2.py-2.text-sm.font-medium.rounded-md {:href (rfe/href :graph)}
+            (ui/icon "hierarchy mr-3" {:style {:font-size 20}})
+            [:span.flex-1 "Graph view"]]
+
+           [:a.item.group.flex.items-center.px-2.py-2.text-sm.font-medium.rounded-md {:href (rfe/href :all-pages)}
+            (ui/icon "files mr-3" {:style {:font-size 20}})
+            [:span.flex-1 "All pages"]]]
+
+          (favorites t)
+
+          (recent-pages t)]]))))
 
 (rum/defc sidebar-mobile-sidebar < rum/reactive
   [{:keys [open? close-fn route-match]}]
@@ -99,41 +226,47 @@
   {:did-mount (fn [state]
                 (when-let [element (gdom/getElement "main-content")]
                   (dnd/subscribe!
-                    element
-                    :upload-files
-                    {:drop (fn [e files]
-                             (when-let [id (state/get-edit-input-id)]
-                               (let [format (:block/format (state/get-edit-block))]
-                                 (editor-handler/upload-asset id files format editor-handler/*asset-uploading? true))))}))
+                   element
+                   :upload-files
+                   {:drop (fn [e files]
+                            (when-let [id (state/get-edit-input-id)]
+                              (let [format (:block/format (state/get-edit-block))]
+                                (editor-handler/upload-asset id files format editor-handler/*asset-uploading? true))))}))
                 state)}
   [{:keys [route-match global-graph-pages? logged? home? route-name indexeddb-support? white? db-restoring? main-content]}]
-  (rum/with-context [[t] i18n/*tongue-context*]
-                    [:div#main-content.cp__sidebar-main-layout.flex-1.flex
-                     [:div#sidebar-nav-wrapper.flex-col.pt-4.hidden.sm:block
-                      {:style {:flex (if (state/get-left-sidebar-open?)
-                                       "0 1 20%"
-                                       "0 0 0px")}}
-                      (when (state/sub :ui/left-sidebar-open?)
-                        (sidebar-nav route-match nil))]
-                     [:div#main-content-container.w-full.flex.justify-center
-                      {:style {:margin-top (if global-graph-pages? 0 "2rem")}}
-                      [:div.cp__sidebar-main-content
-                       {:data-is-global-graph-pages global-graph-pages?
-                        :data-is-full-width (or global-graph-pages?
-                                                (contains? #{:all-files :all-pages :my-publishing} route-name))}
-                       (cond
-                         (not indexeddb-support?)
-                         nil
-
-                         db-restoring?
-                         [:div.mt-20
-                          [:div.ls-center
-                           (ui/loading (t :loading))]]
-
-                         :else
-                         [:div.pb-24 {:class (if global-graph-pages? "" (util/hiccup->class "max-w-7xl.mx-auto"))
-                                      :style {:margin-bottom (if global-graph-pages? 0 120)}}
-                          main-content])]]]))
+
+  (let [left-sidebar-open? (state/sub :ui/left-sidebar-open?)
+        mobile? (util/mobile?)]
+    (rum/with-context [[t] i18n/*tongue-context*]
+      [:div#main-content.cp__sidebar-main-layout.flex-1.flex
+       {:class (util/classnames [{:is-left-sidebar-open left-sidebar-open?}])}
+
+       ;; desktop left sidebar layout
+       (when-not mobile?
+         [:div#sidebar-nav-wrapper.cp__sidebar-left-layout
+          {:class (util/classnames [{:is-open left-sidebar-open?}])}
+
+          ;; sidebar contents
+          (sidebar-nav route-match nil)])
+
+       [:div#main-content-container.w-full.flex.justify-center
+        [:div.cp__sidebar-main-content
+         {:data-is-global-graph-pages global-graph-pages?
+          :data-is-full-width         (or global-graph-pages?
+                                          (contains? #{:all-files :all-pages :my-publishing} route-name))}
+         (cond
+           (not indexeddb-support?)
+           nil
+
+           db-restoring?
+           [:div.mt-20
+            [:div.ls-center
+             (ui/loading (t :loading))]]
+
+           :else
+           [:div.pb-24 {:class (if global-graph-pages? "" (util/hiccup->class "max-w-7xl.mx-auto"))
+                        :style {:margin-bottom (if global-graph-pages? 0 120)}}
+            main-content])]]])))
 
 (rum/defc footer
   []
@@ -155,22 +288,22 @@
 ;; TODO: simplify logic
 
 (rum/defc main-content < rum/reactive db-mixins/query
-                         {:init (fn [state]
-                                  (when-not @sidebar-inited?
-                                    (let [current-repo (state/sub :git/current-repo)
-                                          default-home (get-default-home-if-valid)
-                                          sidebar (:sidebar default-home)
-                                          sidebar (if (string? sidebar) [sidebar] sidebar)]
-                                      (when-let [pages (->> (seq sidebar)
-                                                            (remove string/blank?))]
-                                        (doseq [page pages]
-                                          (let [page (string/lower-case page)
-                                                [db-id block-type] (if (= page "contents")
-                                                                     ["contents" :contents]
-                                                                     [page :page])]
-                                            (state/sidebar-add-block! current-repo db-id block-type nil)))
-                                        (reset! sidebar-inited? true))))
-                                  state)}
+  {:init (fn [state]
+           (when-not @sidebar-inited?
+             (let [current-repo (state/sub :git/current-repo)
+                   default-home (get-default-home-if-valid)
+                   sidebar (:sidebar default-home)
+                   sidebar (if (string? sidebar) [sidebar] sidebar)]
+               (when-let [pages (->> (seq sidebar)
+                                     (remove string/blank?))]
+                 (doseq [page pages]
+                   (let [page (string/lower-case page)
+                         [db-id block-type] (if (= page "contents")
+                                              ["contents" :contents]
+                                              [page :page])]
+                     (state/sidebar-add-block! current-repo db-id block-type nil)))
+                 (reset! sidebar-inited? true))))
+           state)}
   []
   (let [today (state/sub :today)
         cloning? (state/sub :repo/cloning?)
@@ -184,60 +317,60 @@
         preferred-format (state/sub [:me :preferred_format])
         logged? (:name me)]
     (rum/with-context [[t] i18n/*tongue-context*]
-                      [:div
-                       (cond
-                         (and default-home
-                              (= :home (state/get-current-route))
-                              (not (state/route-has-p?))
-                              (:page default-home))
-                         (route-handler/redirect! {:to :page
-                                                   :path-params {:name (:page default-home)}})
+      [:div
+       (cond
+         (and default-home
+              (= :home (state/get-current-route))
+              (not (state/route-has-p?))
+              (:page default-home))
+         (route-handler/redirect! {:to :page
+                                   :path-params {:name (:page default-home)}})
 
-                         (and config/publishing?
-                              (not default-home)
-                              (empty? latest-journals))
-                         (route-handler/redirect! {:to :all-pages})
+         (and config/publishing?
+              (not default-home)
+              (empty? latest-journals))
+         (route-handler/redirect! {:to :all-pages})
 
-                         importing-to-db?
-                         (ui/loading (t :parsing-files))
+         importing-to-db?
+         (ui/loading (t :parsing-files))
 
-                         loading-files?
-                         (ui/loading (t :loading-files))
+         loading-files?
+         (ui/loading (t :loading-files))
 
-                         (and (not logged?) (seq latest-journals))
-                         (journal/journals latest-journals)
+         (and (not logged?) (seq latest-journals))
+         (journal/journals latest-journals)
 
-                         (and logged? (not preferred-format))
-                         (widgets/choose-preferred-format)
+         (and logged? (not preferred-format))
+         (widgets/choose-preferred-format)
 
                          ;; TODO: delay this
-                         (and logged? (nil? (:email me)))
-                         (settings/set-email)
+         (and logged? (nil? (:email me)))
+         (settings/set-email)
 
-                         cloning?
-                         (ui/loading (t :cloning))
+         cloning?
+         (ui/loading (t :cloning))
 
-                         (seq latest-journals)
-                         (journal/journals latest-journals)
+         (seq latest-journals)
+         (journal/journals latest-journals)
 
-                         (and logged? (empty? (:repos me)))
-                         (widgets/add-graph)
+         (and logged? (empty? (:repos me)))
+         (widgets/add-graph)
 
                          ;; FIXME: why will this happen?
-                         :else
-                         [:div])])))
+         :else
+         [:div])])))
 
 (rum/defc custom-context-menu < rum/reactive
   []
   (when (state/sub :custom-context-menu/show?)
     (when-let [links (state/sub :custom-context-menu/links)]
       (ui/css-transition
-        {:class-names "fade"
-         :timeout {:enter 500
-                   :exit 300}}
-        links
+       {:class-names "fade"
+        :timeout {:enter 500
+                  :exit 300}}
+       links
         ;; (custom-context-menu-content)
-        ))))
+))))
 
 (rum/defc new-block-mode < rum/reactive
   []
@@ -261,11 +394,11 @@
   (when-not (state/sub :ui/sidebar-open?)
     ;; TODO: remove with-context usage
     (rum/with-context [[t] i18n/*tongue-context*]
-                      [:div.cp__sidebar-help-btn
-                       {:title (t :help-shortcut-title)
-                        :on-click (fn []
-                                    (state/sidebar-add-block! (state/get-current-repo) "help" :help nil))}
-                       "?"])))
+      [:div.cp__sidebar-help-btn
+       {:title (t :help-shortcut-title)
+        :on-click (fn []
+                    (state/sidebar-add-block! (state/get-current-repo) "help" :help nil))}
+       "?"])))
 
 (rum/defc settings-modal < rum/reactive
   []
@@ -273,7 +406,7 @@
     (if settings-open?
       (do
         (state/set-modal!
-          (fn [] [:div.settings-modal (settings/settings)]))
+         (fn [] [:div.settings-modal (settings/settings)]))
         (util/lock-global-scroll settings-open?))
       (state/set-modal! nil))
     nil))
@@ -307,6 +440,7 @@
         system-theme? (state/sub :ui/system-theme?)
         white? (= "white" (state/sub :ui/theme))
         sidebar-open?  (state/sub :ui/sidebar-open?)
+        left-sidebar-open?  (state/sub :ui/left-sidebar-open?)
         route-name (get-in route-match [:data :name])
         global-graph-pages? (= :graph route-name)
         logged? (:name me)
@@ -329,47 +463,50 @@
                          (editor-handler/unhighlight-blocks!)
                          (util/fix-open-external-with-shift! e))}
 
-                        [:div.theme-inner
-                         (sidebar-mobile-sidebar
-                           {:open?       open?
-                            :close-fn    close-fn
-                            :route-match route-match})
-                         [:div.#app-container.h-screen.flex
-                          [:div.flex-1.h-full.flex.flex-col#left-container.relative
-                           {:class (if (state/sub :ui/sidebar-open?) "overflow-hidden" "w-full")}
-                           (header/header {:open-fn        open-fn
-                                           :white?         white?
-                                           :current-repo   current-repo
-                                           :logged?        logged?
-                                           :page?          page?
-                                           :route-match    route-match
-                                           :me             me
-                                           :default-home   default-home
-                                           :new-block-mode new-block-mode})
-
-                           [:div#main-container.scrollbar-spacing
-                            (main {:route-match         route-match
-                                   :global-graph-pages? global-graph-pages?
-                                   :logged?             logged?
-                                   :home?               home?
-                                   :route-name          route-name
-                                   :indexeddb-support?  indexeddb-support?
-                                   :white?              white?
-                                   :db-restoring?       db-restoring?
-                                   :main-content        main-content})]
-
-                           (footer)]
-                          (right-sidebar/sidebar)
-
-                          [:div#app-single-container]]
-
-                         (ui/notification)
-                         (ui/modal)
-                         (settings-modal)
-                         (command-palette/command-palette-modal)
-                         (custom-context-menu)
-                         [:a#download.hidden]
-                         (when
-                           (and (not config/mobile?)
-                                (not config/publishing?))
-                           (help-button))]))))
+       [:div.theme-inner
+        {:class (util/classnames [{:ls-left-sidebar-open left-sidebar-open?}])}
+
+        (sidebar-mobile-sidebar
+         {:open?       open?
+          :close-fn    close-fn
+          :route-match route-match})
+
+        [:div.#app-container.h-screen.flex
+         [:div.flex-1.h-full.flex.flex-col#left-container.relative
+          {:class (if (state/sub :ui/sidebar-open?) "overflow-hidden" "w-full")}
+          (header/header {:open-fn        open-fn
+                          :white?         white?
+                          :current-repo   current-repo
+                          :logged?        logged?
+                          :page?          page?
+                          :route-match    route-match
+                          :me             me
+                          :default-home   default-home
+                          :new-block-mode new-block-mode})
+
+          [:div#main-container.scrollbar-spacing
+           (main {:route-match         route-match
+                  :global-graph-pages? global-graph-pages?
+                  :logged?             logged?
+                  :home?               home?
+                  :route-name          route-name
+                  :indexeddb-support?  indexeddb-support?
+                  :white?              white?
+                  :db-restoring?       db-restoring?
+                  :main-content        main-content})]
+
+          (footer)]
+         (right-sidebar/sidebar)
+
+         [:div#app-single-container]]
+
+        (ui/notification)
+        (ui/modal)
+        (settings-modal)
+        (command-palette/command-palette-modal)
+        (custom-context-menu)
+        [:a#download.hidden]
+        (when
+         (and (not config/mobile?)
+              (not config/publishing?))
+          (help-button))]))))

+ 165 - 19
src/main/frontend/components/sidebar.css

@@ -10,14 +10,41 @@
   }
 }
 
+.is-electron.is-mac .cp__sidebar-left-layout {
+    padding-top: 30px;
+    transition: all 0.25s;
+    -webkit-transition: all 0.25s;
+}
+
+.is-electron.is-mac.is-fullscreen .cp__sidebar-left-layout {
+  padding-top: 0;
+}
+
+.ls-left-sidebar-open {
+  .cp__header {
+    padding-left: 0 !important;
+    margin-left: var(--ls-left-sidebar-width);
+  }
+}
+
 #app-container {
-    flex: 0 0 100%;
-    transition: all 200ms ease-in 0s;
+  flex: 0 0 100%;
+  transition: all 200ms ease-in 0s;
 }
 
-#main-content-container {
+#main-container {
+  position: relative;
+}
+
+#main-content {
+  &.is-left-sidebar-open {
+    padding-left: var(--ls-left-sidebar-width);
+  }
+
+  &-container {
     font-size: 1em;
     padding: 1rem;
+  }
 }
 
 #left-sidebar {
@@ -49,15 +76,130 @@
     color: var(--ls-active-primary-color);
   }
 
-  .left-sidebar-inner {
-    padding-right: 15px;
-  }
-
   nav > a {
     color: var(--ls-icon-color);
   }
 }
 
+.left-sidebar-inner {
+ height: 100%;
+
+ .dropdown-wrapper {
+    min-width: 180px;
+  }
+
+  .nav-content-item {
+    padding: 32px 0 0 0;
+
+    > .hd {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      padding-bottom: 5px;
+      user-select: none;
+      cursor: pointer;
+      padding: 0 18px;
+
+      > span {
+        > a {
+          opacity: .4;
+          padding-left: 12px;
+          display: none;
+          transition: none;
+
+          &:hover {
+            opacity: 1 !important;
+          }
+
+          &:last-child {
+            transform: translateY(-6px) translateX(2px) rotate(90deg);
+          }
+        }
+      }
+
+      &:hover {
+        > span {
+          > a {
+            display: block;
+
+            &:last-child {
+              display: block;
+              opacity: .6;
+            }
+          }
+        }
+      }
+    }
+
+    > .bd {
+      display: none;
+
+      ul {
+        list-style: none;
+        padding: 0;
+        margin: 0;
+
+        a {
+            width: 100%;
+            padding: 2px 20px;
+            display: flex;
+            text-overflow: ellipsis;
+            overflow: hidden;
+            white-space: nowrap;
+            color: var(--ls-primary-text-color);
+
+            &:hover {
+                background-color: var(--ls-quaternary-background-color);
+            }
+
+            &:active {
+                opacity: .7;
+            }
+        }
+      }
+    }
+
+    &.is-expand {
+      > .hd > span > a {
+        &:last-child {
+          transform: translateY(2px);
+        }
+      }
+
+      > .bd {
+        display: block;
+      }
+    }
+  }
+
+  a.item:hover {
+      background-color: var(--ls-quaternary-background-color);
+  }
+}
+
+.white-theme .left-sidebar-inner a{
+    color: var(--ls-primary-text-color);
+}
+
+.cp__sidebar-left-layout {
+  @apply flex-col sm:block;
+
+  background-color: var(--ls-secondary-background-color);
+  width: var(--ls-left-sidebar-width);
+  height: 100vh;
+  position: fixed;
+  top: 0;
+  left: 0;
+  z-index: 9;
+
+  transition: transform .25s;
+  transform: translateX(-100%);
+
+  &.is-open {
+    transform: translateX(0);
+  }
+}
+
 .settings-modal {
   margin: -15px;
 }
@@ -148,19 +290,19 @@
   }
 
   &-topbar {
-      position: sticky;
-      position: -webkit-sticky;
-      top: 0;
-      left: 0;
-      right: 0;
-      background-color: var(--ls-secondary-background-color, #d8e1e8);
-      z-index: 999;
-      user-select: none;
-      -webkit-app-region: drag;
+    position: sticky;
+    position: -webkit-sticky;
+    top: 0;
+    left: 0;
+    right: 0;
+    background-color: var(--ls-secondary-background-color, #d8e1e8);
+    z-index: 999;
+    user-select: none;
+    -webkit-app-region: drag;
 
-      a, svg {
-          -webkit-app-region: no-drag;
-      }
+    a, svg {
+      -webkit-app-region: no-drag;
+    }
   }
 
   .page {
@@ -219,3 +361,7 @@
     overflow-y: overlay;
   }
 }
+
+.favorites li.dragging-target {
+    border-left: 5px solid green;
+}

+ 1 - 1
src/main/frontend/components/svg.cljs

@@ -399,7 +399,7 @@
 (rum/defc logo
   [dark?]
   [:svg
-   {:fill (if dark? "currentColor" "#002B36"), :view-box "0 0 21 21", :height "21", :width "21"}
+   {:fill "currentColor", :view-box "0 0 21 21", :height "21", :width "21"}
    [:ellipse
     {:transform
          "matrix(0.987073 0.160274 -0.239143 0.970984 11.7346 2.59206)"

+ 5 - 0
src/main/frontend/db/model.cljs

@@ -1357,3 +1357,8 @@
    (filter :block/file)
    (sort-by :block/updated-at >)
    (take 200)))
+
+(defn refresh-recent-pages
+  []
+  (let [results (db-utils/get-key-value :recent/pages)]
+    (state/set-state! :editor/recent-pages results)))

+ 23 - 9
src/main/frontend/dicts.cljs

@@ -101,6 +101,10 @@
         :right-side-bar/favorites "Favorites"
         :right-side-bar/page-graph "Page graph"
         :right-side-bar/block-ref "Block reference"
+        :left-side-bar/new-page "New page"
+        :left-side-bar/nav-favorites "Favorites"
+        :left-side-bar/nav-shortcuts "Shortcuts"
+        :left-side-bar/nav-recent-pages "Recent"
         :git/set-access-token "Set GitHub personal access token"
         :git/token-is-encrypted "The token will be encrypted and stored in the browser local storage"
         :git/token-server "The server will never store it"
@@ -136,7 +140,7 @@
         :project/setup "Setup a public project on Logseq"
         :project/location "All published pages will be located under"
         :project/sync-settings "Sync project settings"
-        :page/presentation-mode "Presentation mode (powered by Reveal.js)"
+        :page/presentation-mode "Presentation"
         :page/edit-properties-placeholder "Properties"
         :page/delete-success "Page {1} was deleted successfully!"
         :page/delete-confirmation "Are you sure you want to delete this page and its file?"
@@ -156,6 +160,7 @@
         :page/publish-as-slide "Publish this page as a slide on Logseq"
         :page/unpublish "Un-publish this page on Logseq"
         :page/add-to-favorites "Add to Favorites"
+        :page/unfavorite "Unfavorite page"
         :page/show-journals "Show journals"
         :page/show-name "Show page name"
         :page/hide-name "Hide page name"
@@ -266,6 +271,8 @@
         :cards-view "View cards"
         :publishing "Publishing"
         :export "Export"
+        :export-graph "Export graph"
+        :export-page "Export page"
         :export-markdown "Export as standard Markdown (no block properties)"
         :export-opml "Export as OPML"
         :export-public-pages "Export public pages"
@@ -415,7 +422,7 @@
         :project/setup "Einrichten eines öffentlichen Projekts auf Logseq"
         :project/location "Alle veröffentlichten Seiten befinden sich unter"
         :project/sync-settings "Projekteinstellungen synchronisieren"
-        :page/presentation-mode "Präsentationsmodus (angetrieben von Reveal.js)"
+        :page/presentation-mode "Präsentationsmodus"
         :page/edit-properties-placeholder "Klicken Sie hier, um die Eigenschaften dieser Seite zu bearbeiten"
         :page/delete-success "Die Seite {1} wurde erfolgreich gelöscht!"
         :page/delete-confirmation "Diese Seite und die zugehörige Datei löschen?"
@@ -616,7 +623,7 @@
         :project/setup "Paramétrer un projet public sur Logseq"
         :project/location "Toutes les pages publiées seront joignables sous"
         :project/sync-settings "Synchroniser les paramètres du projet"
-        :page/presentation-mode "Mode présentation (avec Reveal.js)"
+        :page/presentation-mode "Mode présentation"
         :page/delete-success "Page {1} supprimée !"
         :page/delete-confirmation "Etes-vous sûr de vouloir supprimer la page ?"
         :page/rename-to "Renommer \"{1}\" en:"
@@ -820,6 +827,10 @@
            :right-side-bar/favorites "收藏"
            :right-side-bar/page-graph "页面图谱:"
            :right-side-bar/block-ref "块引用"
+           :left-side-bar/new-page "新页面"
+           :left-side-bar/nav-favorites "收藏页面"
+           :left-side-bar/nav-shortcuts "快捷导航"
+           :left-side-bar/nav-recent-pages "最近使用"
            :git/set-access-token "设定 GitHub 个人访问令牌"
            :git/token-is-encrypted "令牌将被加密并存储在浏览器本地存储"
            :git/token-server "服务器将永远不会存储它"
@@ -856,7 +867,7 @@
            :project/location "一切发布的页面将会被放到 "
            :project/sync-settings "同步项目设置"
            :page/edit-properties-placeholder "点击这里编辑当前页面的属性 (标签,别名等)"
-           :page/presentation-mode "演讲模式 (由 Reveal.js 驱动)"
+           :page/presentation-mode "打开幻灯片"
            :page/delete-success "页面 {1} 删除成功!"
            :page/delete-confirmation "您确定要删除此页面和文件吗?"
            :page/rename-to "重命名 \"{1}\" 至:"
@@ -875,6 +886,7 @@
            :page/publish-as-slide "将本页作为幻灯片发布至 Logseq"
            :page/unpublish "取消将本页发布至 Logseq"
            :page/add-to-favorites "添加收藏"
+           :page/unfavorite "取消收藏"
            :page/show-journals "显示日志"
            :page/show-name "显示页面名"
            :page/hide-name "隐藏页面名"
@@ -957,6 +969,8 @@
            :new-graph "添加图谱"
            :re-index "重新建立索引"
            :sync-from-local-files "刷新(读取本地最新文件)"
+           :export-graph "导出图谱"
+           :export-page "导出当前页面"
            :export-json "以 JSON 格式导出"
            :export-roam-json "以 Roam JSON 格式导出"
            :export-markdown "以 Markdown 格式导出"
@@ -1161,7 +1175,7 @@
              :project/setup "在 Logseq 上發布新的項目"
              :project/location "一切發布的頁面將會被放到 "
              :project/sync-settings "同步項目設置"
-             :page/presentation-mode "演講模式 (由 Reveal.js 驅動)"
+             :page/presentation-mode "演講模式"
              :page/delete-success "頁面 {1} 刪除成功!"
              :page/delete-confirmation "您確定要刪除此頁面嗎?"
              :page/rename-to "重命名 \"{1}\" 至:"
@@ -1397,7 +1411,7 @@
         :project/setup "Stel 'n publieke projek op by Logseq"
         :project/location "Alle gepubliseerde blaai sal voorkom onder"
         :project/sync-settings "Sinkroniseer projek instellings"
-        :page/presentation-mode "Aanbiedings modus (gedryf deur Reveal.js)"
+        :page/presentation-mode "Aanbiedings modus"
         :page/delete-success "Bladsy {1} is suksesvol uitgevee!"
         :page/delete-confirmation "Is jy seker jy wil die bladsy uitvee?"
         :page/rename-to "Hernoem \"{1}\" na:"
@@ -1618,7 +1632,7 @@
         :project/setup "Configurar un proyecto público en Logseq"
         :project/location "Todas las páginas publicadas se encontrarán en"
         :project/sync-settings "Opciones de sincronización del proyecto"
-        :page/presentation-mode "Modo presentación (usando Reveal.js)"
+        :page/presentation-mode "Modo presentación"
         :page/edit-properties-placeholder "Propiedades"
         :page/delete-success "¡Página {1} eliminada con éxito!"
         :page/delete-confirmation "¿Está seguro que desea eliminar esta página y su archivo?"
@@ -1905,7 +1919,7 @@
            :project/setup "Sett opp et offentlig prosjekt på Logseq"
            :project/location "Alle publiserte sider vil bli lokalisert under"
            :project/sync-settings "Synkroniser prosjektinnstillinger"
-           :page/presentation-mode "Presentasjonsmodus (Drevet av Reveal.js)"
+           :page/presentation-mode "Presentasjonsmodus"
            :page/edit-properties-placeholder "Egenskaper"
            :page/delete-success "Side {1} ble vellykket slettet!"
            :page/delete-confirmation "Er du sikker på at du vil slette denne siden og filen dens?"
@@ -2205,7 +2219,7 @@
     :project/setup "Configurar um projeto público em Logseq"
     :project/location "Todas as páginas publicadas ficarão localizadas em"
     :project/sync-settings "Definições de sincronização do projeto"
-    :page/presentation-mode "Modo de apresentação (usando Reveal.js)"
+    :page/presentation-mode "Modo de apresentação"
     :page/edit-properties-placeholder "Propriedades"
     :page/delete-success "Página {1} apagada com sucesso!"
     :page/delete-confirmation "Tem a certeza de que quer apagar esta página e o respetivo ficheiro?"

+ 2 - 2
src/main/frontend/extensions/pdf/highlights.cljs

@@ -1084,8 +1084,8 @@
        (if (or (= status-doc :loading)
                (nil? initial-hls))
 
-         [:div.flex.justify-center.items-center.h-screen.text-gray-500.text-md
-          "Downloading PDF file " url]
+         [:div.flex.justify-center.items-center.h-screen.text-gray-500.text-lg
+          svg/loading]
 
          [(rum/with-key (pdf-viewer
                           url initial-hls

+ 8 - 0
src/main/frontend/extensions/pdf/pdf.css

@@ -678,6 +678,14 @@ body.is-pdf-active {
   #head {
     padding-left: 0;
   }
+
+  #main-content.is-left-sidebar-open {
+    padding-left: unset;
+  }
+
+ .ls-left-sidebar-open .cp__header {
+    margin-left: unset;
+  }
 }
 
 /* overrides for pdf_viewer.css from PDF.JS web viewer */

+ 10 - 0
src/main/frontend/extensions/srs.cljs

@@ -515,6 +515,16 @@
 
 (component-macro/register cloze-macro-name cloze-macro-show)
 
+(defn get-srs-cards-total
+  []
+  (let [repo (state/get-current-repo)
+        query-string ""
+        *query-result (query repo query-string)]
+    (when *query-result
+      (let [blocks (rum/react *query-result)
+            {:keys [result]} (query-scheduled repo blocks (tl/local-now))]
+        (count result)))))
+
 ;;; register cards macro
 (rum/defcs cards
   < rum/reactive

+ 4 - 0
src/main/frontend/handler/events.cljs

@@ -6,6 +6,7 @@
             [frontend.components.diff :as diff]
             [frontend.components.plugins :as plugin]
             [frontend.components.encryption :as encryption]
+            [frontend.components.modals :as modals]
             [frontend.components.git :as git-component]
             [frontend.components.shell :as shell]
             [frontend.components.search :as search]
@@ -53,6 +54,9 @@
 (defmethod handle :repo/install-error [[_ repo-url title]]
   (show-install-error! repo-url title))
 
+(defmethod handle :modal/show-new-page-modal []
+  (modals/show-new-page-modal!))
+
 (defmethod handle :modal/encryption-setup-dialog [[_ repo-url close-fn]]
   (state/set-modal!
    (encryption/encryption-setup-dialog repo-url close-fn)))

+ 72 - 34
src/main/frontend/handler/page.cljs

@@ -19,6 +19,7 @@
             [frontend.handler.route :as route-handler]
             [frontend.handler.ui :as ui-handler]
             [frontend.handler.web.nfs :as web-nfs]
+            [frontend.handler.config :as config-handler]
             [frontend.modules.outliner.core :as outliner-core]
             [frontend.modules.outliner.file :as outliner-file]
             [frontend.modules.outliner.tree :as outliner-tree]
@@ -204,32 +205,6 @@
        (p/catch (fn [err]
                   (js/console.error "error: " err)))))))
 
-(defn delete!
-  [page-name ok-handler]
-  (when page-name
-    (when-let [repo (state/get-current-repo)]
-      (let [page-name (string/lower-case page-name)
-            blocks (db/get-page-blocks page-name)
-            tx-data (mapv
-                     (fn [block]
-                       [:db.fn/retractEntity [:block/uuid (:block/uuid block)]])
-                     blocks)]
-        (db/transact! tx-data)
-
-        (delete-file! repo page-name)
-
-        ;; if other page alias this pagename,
-        ;; then just remove some attrs of this entity instead of retractEntity
-        (if (model/get-alias-source-page (state/get-current-repo) page-name)
-          (when-let [id (:db/id (db/entity [:block/name page-name]))]
-            (let [txs (mapv (fn [attribute]
-                              [:db/retract id attribute])
-                            db-schema/retract-page-attributes)]
-              (db/transact! txs)))
-          (db/transact! [[:db.fn/retractEntity [:block/name page-name]]]))
-
-        (ok-handler)))))
-
 (defn- compute-new-file-path
   [old-path new-page-name]
   (let [result (string/split old-path "/")
@@ -344,6 +319,53 @@
           (p/let [_ (rename-file-aux! repo old-path new-path)]
             (println "Renamed " old-path " to " new-path)))))))
 
+(defn favorite-page!
+  [page-name]
+  (when-not (string/blank? page-name)
+    (let [favorites (->
+                     (cons
+                      page-name
+                      (or (:favorites (state/get-config)) []))
+                     (distinct)
+                     (vec))]
+      (config-handler/set-config! :favorites favorites))))
+
+(defn unfavorite-page!
+  [page-name]
+  (when-not (string/blank? page-name)
+    (let [favorites (->> (:favorites (state/get-config))
+                         (remove #(= (string/lower-case %) (string/lower-case page-name)))
+                         (vec))]
+      (config-handler/set-config! :favorites favorites))))
+
+(defn delete!
+  [page-name ok-handler]
+  (when page-name
+    (when-let [repo (state/get-current-repo)]
+      (let [page-name (string/lower-case page-name)
+            blocks (db/get-page-blocks page-name)
+            tx-data (mapv
+                     (fn [block]
+                       [:db.fn/retractEntity [:block/uuid (:block/uuid block)]])
+                     blocks)]
+        (db/transact! tx-data)
+
+        (delete-file! repo page-name)
+
+        ;; if other page alias this pagename,
+        ;; then just remove some attrs of this entity instead of retractEntity
+        (if (model/get-alias-source-page (state/get-current-repo) page-name)
+          (when-let [id (:db/id (db/entity [:block/name page-name]))]
+            (let [txs (mapv (fn [attribute]
+                              [:db/retract id attribute])
+                            db-schema/retract-page-attributes)]
+              (db/transact! txs)))
+          (db/transact! [[:db.fn/retractEntity [:block/name page-name]]]))
+
+        (unfavorite-page! page-name)
+
+        (ok-handler)))))
+
 (defn- rename-page-aux [old-name new-name]
   (when-let [repo (state/get-current-repo)]
     (when-let [page (db/pull [:block/name (string/lower-case old-name)])]
@@ -417,6 +439,9 @@
 
       (repo-handler/push-if-auto-enabled! repo)
 
+      (p/let [_ (unfavorite-page! old-name)]
+        (favorite-page! new-name))
+
       (ui-handler/re-render-root!))))
 
 (defn rename!
@@ -438,14 +463,27 @@
         :else
         (rename-page-aux old-name new-name)))))
 
-(defn handle-add-page-to-contents!
-  [page-name]
-  (let [content (str "[[" page-name "]]")]
-    (editor-handler/api-insert-new-block!
-     content
-     {:page "Contents"})
-    (notification/show! (util/format "Added to %s!" (state/get-favorites-name)) :success)
-    (editor-handler/clear-when-saved!)))
+(defn- split-col-by-element
+  [col element]
+  (let [col (vec col)
+        idx (.indexOf col element)]
+    [(subvec col 0 (inc idx))
+     (subvec col (inc idx))]))
+
+(defn reorder-favorites!
+  [{:keys [to up?]}]
+  (let [favorites (:favorites (state/get-config))
+        from (get @state/state :favorites/dragging)]
+    (when (and from to (not= from to))
+      (let [[prev next] (split-col-by-element favorites to)
+            [prev next] (mapv #(remove (fn [e] (= from e)) %) [prev next])
+            favorites (->>
+                       (if up?
+                         (concat (drop-last prev) [from (last prev)] next)
+                         (concat prev [from] next))
+                       (remove nil?)
+                       (distinct))]
+        (config-handler/set-config! :favorites favorites)))))
 
 (defn has-more-journals?
   []

+ 20 - 0
src/main/frontend/handler/repo.cljs

@@ -66,6 +66,24 @@
       (when-not file-exists?
         (file-handler/reset-file! repo-url path default-content)))))
 
+(defn create-favorites-file
+  [repo-url]
+  (spec/validate :repos/url repo-url)
+  (let [repo-dir (config/get-repo-dir repo-url)
+        format (state/get-preferred-format)
+        path (str (state/get-pages-directory)
+                  "/favorites."
+                  (config/get-file-extension format))
+        file-path (str "/" path)
+        default-content (case (name format)
+                          "org" (rc/inline "favorites.org")
+                          "markdown" (rc/inline "favorites.md")
+                          "")]
+    (p/let [_ (fs/mkdir-if-not-exists (str repo-dir "/" (state/get-pages-directory)))
+            file-exists? (fs/create-if-not-exists repo-url repo-dir file-path default-content)]
+      (when-not file-exists?
+        (file-handler/reset-file! repo-url path default-content)))))
+
 (defn create-custom-theme
   [repo-url]
   (spec/validate :repos/url repo-url)
@@ -145,6 +163,7 @@
        ;; TODO: move to frontend.handler.file
        (create-config-file-if-not-exists repo-url)
        (create-contents-file repo-url)
+       (create-favorites-file repo-url)
        (create-custom-theme repo-url)
        (state/pub-event! [:page/create-today-journal repo-url])))))
 
@@ -554,6 +573,7 @@
                  (create-today-journal-if-not-exists repo {:content tutorial})))
              (create-config-file-if-not-exists repo)
              (create-contents-file repo)
+             (create-favorites-file repo)
              (create-custom-theme repo)
              (state/set-db-restoring! false)
              (ui-handler/re-render-root!)))

+ 5 - 0
src/main/frontend/handler/ui.cljs

@@ -15,6 +15,11 @@
   (when-let [elem (gdom/getElement "close-left-bar")]
     (.click elem)))
 
+(defn toggle-left-sidebar!
+  []
+  (state/set-left-sidebar-open!
+    (not (@state/state :ui/left-sidebar-open?))))
+
 (defn hide-right-sidebar
   []
   (state/hide-right-sidebar!))

+ 21 - 13
src/main/frontend/modules/shortcut/config.cljs

@@ -43,11 +43,11 @@
    ^{:before m/prevent-default-behavior}
    {:pdf/previous-page
     {:desc    "Previous page of current pdf doc"
-     :binding "ctrl+p"
+     :binding "alt+p"
      :fn      pdf-utils/prev-page}
     :pdf/next-page
     {:desc    "Next page of current pdf doc"
-     :binding "ctrl+n"
+     :binding "alt+n"
      :fn      pdf-utils/next-page}}
 
    :shortcut.handler/auto-complete
@@ -142,7 +142,7 @@
      :fn      editor-handler/strike-through-format!}
     :editor/move-block-up
     {:desc    "Move block up"
-     :binding (if mac? "mod+shift+up"  "alt+shift+up")
+     :binding (if mac? "mod+shift+up" "alt+shift+up")
      :fn      (editor-handler/move-up-down true)}
     :editor/move-block-down
     {:desc    "Move block down"
@@ -185,13 +185,13 @@
      :binding (if mac? false "alt+w")
      :fn      editor-handler/backward-kill-word}
     :editor/replace-block-reference-at-point
-    {:desc "Replace block reference with its content at point"
+    {:desc    "Replace block reference with its content at point"
      :binding "mod+shift+r"
-     :fn editor-handler/replace-block-reference-with-content-at-point}
+     :fn      editor-handler/replace-block-reference-with-content-at-point}
     :editor/paste-text-in-one-block-at-point
-    {:desc "Paste text into one block at point"
+    {:desc    "Paste text into one block at point"
      :binding "mod+shift+v"
-     :fn editor-handler/paste-text-in-one-block-at-point}
+     :fn      editor-handler/paste-text-in-one-block-at-point}
     :editor/insert-youtube-timestamp
     {:desc    "Insert youtube timestamp"
      :binding "mod+shift+y"
@@ -264,7 +264,11 @@
     :editor/redo
     {:desc    "Redo"
      :binding ["shift+mod+z" "mod+y"]
-     :fn      history/redo!}}
+     :fn      history/redo!}
+    :editor/new-page
+    {:desc    "New page"
+     :binding "mod+n"
+     :fn      #(state/pub-event! [:modal/show-new-page-modal])}}
 
    :shortcut.handler/global-prevent-default
    ^{:before m/prevent-default-behavior}
@@ -327,12 +331,12 @@
    ;; always overrides the copy due to "mod+c mod+s"
    {:misc/copy
     {:binding "mod+c"
-     :fn     (fn [] (js/document.execCommand "copy"))}
+     :fn      (fn [] (js/document.execCommand "copy"))}
 
     :command-palette/toggle
-    {:desc "Toggle command palette"
+    {:desc    "Toggle command palette"
      :binding "mod+shift+p"
-     :fn  (fn [] (state/toggle! :ui/command-palette-open?))}}
+     :fn      (fn [] (state/toggle! :ui/command-palette-open?))}}
 
    :shortcut.handler/global-non-editing-only
    ^{:before m/enable-when-not-editing-mode!}
@@ -360,6 +364,10 @@
     {:desc    "Toggle right sidebar"
      :binding "t r"
      :fn      ui-handler/toggle-right-sidebar!}
+    :ui/toggle-left-sidebar
+    {:desc    "Toggle left sidebar"
+     :binding "t l"
+     :fn      ui-handler/toggle-left-sidebar!}
     :ui/toggle-help
     {:desc    "Toggle help"
      :binding "shift+/"
@@ -378,11 +386,11 @@
      :fn      ui-handler/toggle-wide-mode!}
     :ui/select-theme-color
     {:desc    "Select available theme colors"
-     :binding    "t i"
+     :binding "t i"
      :fn      plugin-handler/show-themes-modal!}
     :ui/goto-plugins
     {:desc    "Go to plugins dashboard"
-     :binding    "t p"
+     :binding "t p"
      :fn      plugin-handler/goto-plugins-dashboard!}
     :editor/toggle-open-blocks
     {:desc    "Toggle open blocks (collapse or expand all blocks)"

+ 7 - 2
src/main/frontend/state.cljs

@@ -61,7 +61,7 @@
       :ui/fullscreen? false
       :ui/settings-open? false
       :ui/sidebar-open? false
-      :ui/left-sidebar-open? false
+      :ui/left-sidebar-open? (boolean (storage/get "ls-left-sidebar-open?"))
       :ui/theme (or (storage/get :ui/theme) "dark")
       :ui/system-theme? ((fnil identity (or util/mac? util/win32? false)) (storage/get :ui/system-theme?))
       :ui/wide-mode? false
@@ -103,6 +103,8 @@
       :editor/document-mode? document-mode?
       :editor/args nil
       :editor/on-paste? false
+      :editor/recent-pages nil
+
       :db/last-transact-time {}
       :db/last-persist-transact-ids {}
       ;; whether database is persisted
@@ -171,7 +173,9 @@
 
       :debug/write-acks {}
 
-      :encryption/graph-parsing? false})))
+      :encryption/graph-parsing? false
+
+      :favorites/dragging nil})))
 
 
 (defn sub
@@ -1117,6 +1121,7 @@
 
 (defn set-left-sidebar-open!
   [value]
+  (storage/set "ls-left-sidebar-open?" (boolean value))
   (set-state! :ui/left-sidebar-open? value))
 
 (defn set-developer-mode!

+ 10 - 7
src/main/frontend/ui.cljs

@@ -99,13 +99,15 @@
                                    (on-click-fn e))
                                  (close-fn)))
               child (if hr
-                      [:hr.my-1]
+                      nil
                       [:div
                        {:style {:display "flex" :flex-direction "row"}}
                        [:div {:style {:margin-right "8px"}} title]])]
-          (rum/with-key
-            (menu-link new-options child)
-            title)))
+          (if hr
+            [:hr.my-1]
+            (rum/with-key
+              (menu-link new-options child)
+              title))))
       (when links-footer links-footer)])
    opts))
 
@@ -710,6 +712,7 @@
              :options               {:theme (when (= (state/sub :ui/theme) "dark") "dark")}
              :on-tweet-load-success #(reset! *loading? false)})]]))
 
-(rum/defc icon
-  [class]
-  [:i {:class (str "ti ti-" class)}])
+(defn icon
+  ([class] (icon class nil))
+  ([class opts]
+   [:i (merge {:class (str "ti ti-" class)} opts)]))

+ 7 - 0
src/main/logseq/api.cljs

@@ -14,6 +14,7 @@
             [frontend.db.query-dsl :as query-dsl]
             [frontend.db.utils :as db-utils]
             [frontend.fs :as fs]
+            [frontend.handler :as handler]
             [frontend.handler.dnd :as editor-dnd-handler]
             [frontend.handler.editor :as editor-handler]
             [frontend.handler.export :as export-handler]
@@ -22,6 +23,7 @@
             [frontend.handler.plugin :as plugin-handler]
             [frontend.modules.outliner.core :as outliner]
             [frontend.modules.outliner.tree :as outliner-tree]
+            [electron.listener :as el]
             [frontend.state :as state]
             [frontend.util :as util]
             [frontend.util.cursor :as cursor]
@@ -520,3 +522,8 @@
   ([content status] (let [hiccup? (and (string? content) (string/starts-with? (string/triml content) "[:"))
                           content (if hiccup? (parse-hiccup-ui content) content)]
                       (notification/show! content (keyword status)))))
+
+(defn ^:export force_save_graph
+  []
+  (p/let [_ (el/persist-dbs!)
+          _ (reset! handler/triggered? true)]))

+ 2 - 2
templates/config.edn

@@ -40,12 +40,12 @@
  ;; If not specified, Logseq default opens journals page on startup
  ;; value for `:page` is name of page
  ;; Possible options for `:sidebar` are
- ;; 1. `"Contents"` to open up `Favorites` in sidebar by default
+ ;; 1. `"Contents"` to open up `Contents` in sidebar by default
  ;; 2. `page name` to open up some page in sidebar
  ;; 3. Or multiple pages in an array ["Contents" "Page A" "Page B"]
  ;; If `:sidebar` is not set, sidebar will be hidden
  ;; Example:
- ;; 1. Setup page "Changelog" as home page and "Favorites" in sidebar
+ ;; 1. Setup page "Changelog" as home page and "Contents" in sidebar
  ;; :default-home {:page "Changelog", :sidebar "Contents"}
  ;; 2. Setup page "Jun 3rd, 2021" as home page without sidebar
  ;; :default-home {:page "Jun 3rd, 2021"}

+ 1 - 0
templates/favorites.md

@@ -0,0 +1 @@
+-

+ 1 - 0
templates/favorites.org

@@ -0,0 +1 @@
+*

+ 0 - 5
yarn.lock

@@ -7862,11 +7862,6 @@ react-grid-layout@^0.16.6:
     react-draggable "3.x"
     react-resizable "1.x"
 
[email protected]:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/react-icon-base/-/react-icon-base-2.1.0.tgz#a196e33fdf1e7aaa1fda3aefbb68bdad9e82a79d"
-  integrity sha1-oZbjP98eeqof2jrvu2i9rZ6Cp50=
-
 react-icons@^2.2.7:
   version "2.2.7"
   resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-2.2.7.tgz#d7860826b258557510dac10680abea5ca23cf650"

Some files were not shown because too many files changed in this diff