Browse Source

Merge branch 'master' into perf/search

Tienson Qin 5 years ago
parent
commit
57b9e7f937

+ 23 - 0
.stylelintrc.json

@@ -0,0 +1,23 @@
+{
+  "extends": [
+    "stylelint-config-standard"
+  ],
+  "rules": {
+    "at-rule-no-unknown": [
+      true,
+      {
+        "ignoreAtRules": [
+          "tailwind",
+          "apply",
+          "variants",
+          "responsive",
+          "screen"
+        ]
+      }
+    ],
+    "declaration-block-trailing-semicolon": null,
+    "no-descending-specificity": null,
+    "declaration-empty-line-before": null,
+    "block-no-empty": null
+  }
+}

+ 9 - 6
package.json

@@ -3,8 +3,7 @@
     "version": "0.0.1",
     "private": true,
     "devDependencies": {
-        "@tailwindcss/ui": "^0.1.3",
-        "cross-env": "^7.0.3",
+        "@tailwindcss/ui": "0.7.2",
         "cssnano": "^4.1.10",
         "del": "^6.0.0",
         "gulp": "^4.0.2",
@@ -12,11 +11,14 @@
         "gulp-concat": "^2.6.1",
         "gulp-postcss": "^9.0.0",
         "npm-run-all": "^4.1.5",
-        "postcss": "^7.0.21",
-        "postcss-cli": "^6.1.3",
-        "purgecss": "^2.1.0",
+        "postcss": "8.2.1",
+        "postcss-cli": "8.3.0",
+        "postcss-nested": "^5.0.1",
+        "purgecss": "3.0.0",
         "shadow-cljs": "2.8.81",
-        "tailwindcss": "^1.3.4"
+        "stylelint": "^13.8.0",
+        "stylelint-config-standard": "^20.0.0",
+        "tailwindcss": "2.0.1"
     },
     "scripts": {
         "watch": "run-p cljs:watch gulp:build gulp:watch",
@@ -28,6 +30,7 @@
         "clean": "gulp clean",
         "test": "run-s cljs:test cljs:run-test",
         "report": "run-s cljs:report",
+        "style:lint": "stylelint \"src/**/*.css\" ",
         "gulp:watch": "gulp watch",
         "gulp:build": "cross-env NODE_ENV=production gulp build",
         "cljs:watch": "clojure -M:cljs watch app publishing",

+ 4 - 4
postcss.config.js

@@ -1,7 +1,7 @@
 module.exports = (ctx) => ({
   plugins: [
-    require("autoprefixer"),
-    require("tailwindcss")("tailwind.config.js"),
-    ctx.env === "production" ? require("cssnano")({ preset: "default" }) : null,
+    require('postcss-nested'),
+    require('tailwindcss')('tailwind.config.js'),
+    ctx.env === 'production' ? require('cssnano')({ preset: 'default' }) : null,
   ],
-});
+})

File diff suppressed because it is too large
+ 273 - 368
resources/css/common.css


+ 8 - 10
src/main/frontend/components/block.cljs

@@ -313,7 +313,7 @@
 (rum/defc block-embed < rum/reactive db-mixins/query
   [config id]
   (let [blocks (db/get-block-and-children (state/get-current-repo) id)]
-    [:div.ls-embed-block.bg-base-2
+    [:div.color-level.embed-block.bg-base-2 {:style {:z-index 2}}
      [:div.px-3.pt-1.pb-2
       (blocks-container blocks (assoc config
                                       :embed? true
@@ -324,8 +324,9 @@
   (let [page-name (string/lower-case page-name)
         page-original-name (:page/original-name (db/entity [:page/name page-name]))
         current-page (state/get-current-page)]
-    [:div.ls-embed-page.bg-base-2
-     [:div.flex.items-center.p-1.embed-header
+    [:div.color-level.embed.embed-page.bg-base-2
+     {:class (if (:sidebar? config) "in-sidebar")}
+     [:section.flex.items-center.p-1.embed-header
       [:div.mr-3 svg/page]
       (page-cp config {:page/name page-name})]
      (when (and
@@ -1100,9 +1101,7 @@
                           (editor-handler/unhighlight-block!))}]
     [:div.flex.relative
      [:div.flex-1.flex-col.relative.block-content
-      (cond-> {:id (str "block-content-" uuid)
-               :style {:cursor "text"
-                       :min-height 24}}
+      (cond-> {:id (str "block-content-" uuid)}
         (not slide?)
         (merge attrs))
 
@@ -1158,8 +1157,7 @@
          (when (and start-time finish-time (> finish-time start-time))
            [:div.text-sm.absolute.time-spent {:style {:top 0
                                                       :right 0
-                                                      :padding-left 2
-                                                      :z-index 4}
+                                                      :padding-left 2}
                                               :title (str (date/int->local-time start-time) " ~ " (date/int->local-time finish-time))}
             [:span.opacity-70
              (utils/timeConversion (- finish-time start-time))]])))]))
@@ -1624,7 +1622,7 @@
       ;; TODO: speedup
       (if (re-find #"\"Export_Snippet\" \"embed\"" (str l))
         (->elem :div (map-inline config l))
-        (->elem :p (map-inline config l)))
+        (->elem :div.is-paragraph (map-inline config l)))
 
       ["Horizontal_Rule"]
       (when-not (:slide? config)
@@ -1812,7 +1810,7 @@
                                sidebar?
                                0
                                :else
-                               -18)}}
+                               -10)}}
        (let [first-block (first blocks)
              blocks' (if (and (:block/pre-block? first-block)
                               (db/pre-block-with-only-title? (:block/repo first-block) (:block/uuid first-block)))

+ 77 - 78
src/main/frontend/components/block.css

@@ -2,144 +2,107 @@
 }
 
 .block-content {
-}
-
-.block-children {
-}
-
-.ls-block {
-}
-
-.block-content img {
-  width: 100%;
-}
-
-/* copied from https://github.com/drdogbot7/tailwindcss-responsive-embed */
-.embed-responsive {
-  position: relative;
-  display: block;
-  height: 0;
-  padding: 0;
-  overflow: hidden;
+  min-height: 24px;
+  max-width: 100%;
+  overflow: initial;
+  cursor: text;
 
-  .embed-responsive-item,
-  iframe,
-  embed,
-  object,
-  video {
-    position: absolute;
-    top: 0;
-    left: 0;
-    bottom: 0;
-    height: 100%;
-    width: 100%;
-    border: 0;
+  img {
+    max-width: 100%;
   }
-
-}
-
-.embed {
-  border-radius: var(--ls-border-radius-low);
 }
 
-.embed-header {
-  font-weight: 600;
+.block-children {
 }
 
-.ls-embed-page {
-    @apply color-level embed py-2 my-2 px-3;
-}
+.embed-page {
+  @apply py-2 my-2 px-2;
 
-.ls-embed-block {
-    @apply color-level embed;
-    z-index: 2;
-}
-
-.dark-theme {
-  --color-level-1: var(--ls-secondary-background-color);
-  --color-level-2: var(--ls-tertiary-background-color);
-  --color-level-3: var(--ls-quaternary-background-color);
-  --color-level-4: #195D6C;
-  --color-level-5: #266C7D;
-  --color-level-6: #3A7E8E;
-}
+  > section {
+    margin-bottom: 5px;
+  }
 
-.white-theme {
-  --color-level-1: var(--ls-secondary-background-color);
-  --color-level-2: var(--ls-tertiary-background-color);
-  --color-level-3: var(--ls-quaternary-background-color);
-  --color-level-4: #d0e6fa;
-  --color-level-5: #bbdaf6;
-  /* --color-level-4: #b7d7c9;
-  --color-level-5: #a5cdbc;
-  --color-level-6: #92c2af; */
+  &.in-sidebar {
+    background-color: var(--ls-tertiary-background-color);
+  }
 }
 
 .color-level {
   background-color: var(--color-level-1);
 }
+
 .color-level .color-level {
   background-color: var(--color-level-2);
 }
+
 .color-level .color-level .color-level {
   background-color: var(--color-level-3);
 }
+
 .color-level .color-level .color-level .color-level {
   background-color: var(--color-level-4);
 }
+
 .color-level .color-level .color-level .color-level .color-level {
   background-color: var(--color-level-5);
 }
+
 .color-level .color-level .color-level .color-level .color-level .color-level {
   background-color: var(--color-level-3);
 }
+
 .color-level .color-level .color-level .color-level .color-level .color-level .color-level {
   background-color: var(--color-level-4);
 }
+
 .color-level .color-level .color-level .color-level .color-level .color-level .color-level .color-level {
   background-color: var(--color-level-5);
 }
+
 .color-level .color-level .color-level .color-level .color-level .color-level .color-level .color-level .color-level {
   background-color: var(--color-level-3);
 }
+
 .color-level .color-level .color-level .color-level .color-level .color-level .color-level .color-level .color-level .color-level {
   background-color: var(--color-level-4);
 }
+
 .color-level .color-level .color-level .color-level .color-level .color-level .color-level .color-level .color-level .color-level .color-level {
   background-color: var(--color-level-5);
 }
 
 .ls-block {
   min-height: 24px;
-}
 
-.ls-block, .foldable-title {
-  max-width: 700px;
+  img {
+    box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
+  }
 }
 
-.ls-block, .editor-wrapper {
-margin-right: auto;
-margin-left: auto;
+.ls-block,
+.foldable-title {
+  max-width: var(--ls-main-content-max-width);
 }
 
-.ls-block img {
-  box-shadow: 0 20px 25px -5px rgba(0, 0, 0, .1), 0 10px 10px -5px rgba(0, 0, 0, .04);
+.ls-block,
+.editor-wrapper {
+  margin-right: auto;
+  margin-left: auto;
 }
 
-
 .ls-block h1 {
   font-size: 2em;
-  margin: .67em 0
+  margin: 0.67em 0
 }
 
 .ls-block h2 {
   font-size: 1.5em;
-  margin: .75em 0
+  margin: 0.75em 0
 }
 
 .ls-block h3 {
   font-size: 1.17em;
-  margin: .83em 0
+  margin: 0.83em 0
 }
 
 .ls-block h4 {
@@ -147,15 +110,51 @@ margin-left: auto;
 }
 
 .ls-block h5 {
-  font-size: .83em;
+  font-size: 0.83em;
   margin: 1.5em 0
 }
 
 .ls-block h6 {
-  font-size: .75em;
+  font-size: 0.75em;
   margin: 1.67em 0
 }
 
-.ls-block h1, .ls-block h2, .ls-block h3, .ls-block h4, .ls-block h5, .ls-block h6 {
+.ls-block h1,
+.ls-block h2,
+.ls-block h3,
+.ls-block h4,
+.ls-block h5,
+.ls-block h6 {
   font-weight: 600
 }
+
+/* copied from https://github.com/drdogbot7/tailwindcss-responsive-embed */
+.embed-responsive {
+  position: relative;
+  display: block;
+  height: 0;
+  padding: 0;
+  overflow: hidden;
+
+  .embed-responsive-item,
+  iframe,
+  embed,
+  object,
+  video {
+    position: absolute;
+    top: 0;
+    left: 0;
+    bottom: 0;
+    height: 100%;
+    width: 100%;
+    border: 0;
+  }
+}
+
+.embed {
+  border-radius: var(--ls-border-radius-low);
+}
+
+.embed-header {
+  font-weight: 600;
+}

+ 4 - 4
src/main/frontend/components/editor.css

@@ -14,15 +14,15 @@
   padding: 0;
 }
 
-.non-block-editor textarea, pre {
+.non-block-editor textarea,
+pre {
   display: block;
   padding: 0.5rem;
-  box-shadow: 0 0 0 1px rgba(0, 0, 0, .02);
+  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.02);
   border-radius: 4px;
 }
 
 .non-block-editor textarea {
-  background: #F6F8FA;
+  background: #f6f8fa;
   background: var(--ls-secondary-background-color);
 }
-

+ 35 - 35
src/main/frontend/components/header.css

@@ -1,64 +1,64 @@
 .cp__header {
-    @apply shadow z-10 h-16 pr-4;
-
-    position: relative;
-    display: flex;
-    align-items: center;
-    flex: 0 0 auto;
+  @apply shadow z-10 h-12 pr-4;
+  position: relative;
+  display: flex;
+  align-items: center;
+  flex: 0 0 auto;
 }
 
 .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%;
+  @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);
+  @apply outline-none;
+  background: var(--ls-menu-hover-color);
 }
 
 .cp__header-logo {
-    @apply px-4 mr-3;
-    height: 100%;
+  @apply px-4 mr-3;
+  height: 100%;
 }
 
 .cp__header-logo,
 .cp__right-menu-button {
-    opacity: 0.7;
-    display: none;
+  opacity: 0.7;
+  display: none;
 }
 
 .cp__header-logo:hover,
 .cp__right-menu-button:hover {
-    opacity: 1;
+  opacity: 1;
 }
 
 .cp__header-logo-img {
-    width: 24px;
-    height: 24px;
+  width: 24px;
+  height: 24px;
 }
 
 .cp__right-menu-button {
-    @apply ml-3;
+  @apply ml-3;
 }
 
 @screen sm {
-    .cp__header {
-        @apply h-12 shadow-none;
-    }
+  .cp__header {
+    @apply shadow-none;
+  }
 
-    .cp__header-left-menu {
-        display: none;
-    }
+  .cp__header-left-menu {
+    display: none;
+  }
 
-    .cp__header-logo {
-        display: flex;
-        align-items: center;
-    }
+  .cp__header-logo {
+    display: flex;
+    align-items: center;
+  }
 
-    .cp__right-menu-button {
-        display: block;
-    }
-}
+  .cp__right-menu-button {
+    display: block;
+  }
+}

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

@@ -106,7 +106,7 @@
                 last-pulled-at (db/sub-key-value repo :git/last-pulled-at)
                 ;; db-persisted? (state/sub [:db/persisted? repo])
                 editing? (seq (state/sub :editor/editing?))]
-            [:div.flex-row.flex.items-center
+            [:div.flex-row.flex.items-center.cp__repo-indicator
              (when pushing?
                [:span.lds-dual-ring.mt-1])
              (ui/dropdown

+ 6 - 0
src/main/frontend/components/repo.css

@@ -0,0 +1,6 @@
+.cp__repo-indicator {
+  .sync-content {
+    max-height: 80vh;
+    overflow: auto;
+  }
+}

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

@@ -226,36 +226,28 @@
         dark? (= "dark" theme)
         t (i18n/use-tongue)]
     (rum/with-context [[t] i18n/*tongue-context*]
-      [:div#right-sidebar.flex-col {:style {:height "100%"
-                                            :overflow "hidden"
-                                            :flex (if sidebar-open?
-                                                    "1 0 40%"
-                                                    "0 0 0px")}}
+      [:div#right-sidebar.cp__right-sidebar
+       {:class (if sidebar-open? "is-open")}
        (if sidebar-open?
-         [:div.hide-scrollbar {:style {:flex "1 1 auto"
-                                       :padding 12
-                                       :height "100%"
-                                       :overflow-y "auto"
-                                       :overflow-x "hidden"
-                                       :box-sizing "content-box"}}
-          [:div.flex.flex-row.mb-2 {:key "right-sidebar-settings"}
-           [:div.mr-4.text-sm
-            [:a.right-sidebar-button {:on-click (fn [e]
+         [:div.cp__right-sidebar-inner
+          [:div.cp__right-sidebar-settings.hide-scrollbar {:key "right-sidebar-settings"}
+           [:div.ml-4.text-sm
+            [:a.cp__right-sidebar-settings-btn {:on-click (fn [e]
                                                   (state/sidebar-add-block! repo "contents" :contents nil))}
              (t :right-side-bar/contents)]]
 
-           [:div.mr-4.text-sm
-            [:a.right-sidebar-button {:on-click (fn [_e]
+           [:div.ml-4.text-sm
+            [:a.cp__right-sidebar-settings-btn {:on-click (fn [_e]
                                                   (state/sidebar-add-block! repo "recent" :recent nil))}
              (t :right-side-bar/recent)]]
 
            (when config/publishing?
-             [:div.mr-4.text-sm
+             [:div.ml-4.text-sm
               [:a {:href (rfe/href :all-pages)}
                (t :all-pages)]])
 
-           [:div.mr-4.text-sm
-            [:a.right-sidebar-button {:on-click (fn []
+           [:div.ml-4.text-sm
+            [:a.cp__right-sidebar-settings-btn {:on-click (fn []
                                                   (when-let [page (get-current-page)]
                                                     (state/sidebar-add-block!
                                                      repo
@@ -264,16 +256,16 @@
                                                      page)))}
              (t :right-side-bar/page)]]
 
-           [:div.mr-4.text-sm
+           [:div.ml-4.text-sm
             (let [theme (if dark? "white" "dark")]
-              [:a.right-sidebar-button {:title (t :right-side-bar/switch-theme theme)
+              [:a.cp__right-sidebar-settings-btn {:title (t :right-side-bar/switch-theme theme)
                                         :on-click (fn []
                                                     (state/set-theme! theme))}
                (t :right-side-bar/theme (t (keyword theme)))])]
 
            (when-not config/publishing?
-             [:div.mr-4.text-sm
-              [:a.right-sidebar-button {:on-click (fn [_e]
+             [:div.ml-4.text-sm
+              [:a.cp__right-sidebar-settings-btn {:on-click (fn [_e]
                                                     (state/sidebar-add-block! repo "help" :help nil))}
                (t :right-side-bar/help)]])]
 

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

@@ -153,7 +153,7 @@
              {:for "enable_timetracking"}
              (t :settings-page/enable-timetracking)]
             [:div.mt-1.sm:mt-0.sm:col-span-2
-             [:div.max-w-lg.rounded-md.shadow-sm.sm:max-w-xs
+             [:div.max-w-lg.rounded-md.sm:max-w-xs
               (ui/toggle enable-timetracking?
                          (fn []
                            (let [value (not enable-timetracking?)]

+ 45 - 39
src/main/frontend/components/sidebar.cljs

@@ -1,6 +1,7 @@
 (ns frontend.components.sidebar
   (:require [rum.core :as rum]
             [frontend.ui :as ui]
+            [frontend.components.theme :as theme]
             [frontend.mixins :as mixins]
             [frontend.db-mixins :as db-mixins]
             [frontend.db :as db]
@@ -38,8 +39,8 @@
     {:viewBox "0 0 24 24", :fill "none", :stroke "currentColor"}
     [:path
      {:d svg-d
-      :stroke-width "2",
-      :stroke-linejoin "round",
+      :stroke-width "2"
+      :stroke-linejoin "round"
       :stroke-linecap "round"}]]
    title])
 
@@ -52,7 +53,7 @@
         right-sidebar? (state/sub :ui/sidebar-open?)
         left-sidebar? (state/sub :ui/left-sidebar-open?)]
     (when left-sidebar?
-      [:nav.flex-1
+      [: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)
@@ -97,7 +98,7 @@
            :stroke-linejoin "round"
            :stroke-linecap "round"}]]]])
     [:div.flex-shrink-0.flex.items-center.px-4.h-16 {:style {:background-color "#002b36"}}
-     (repo/repos-dropdown false)]
+     (repo/repos-dropdown false nil)]
     [:div.flex-1.h-0.overflow-y-auto
      (sidebar-nav route-match close-fn)]]])
 
@@ -236,9 +237,8 @@
   (when-not (state/sub :ui/sidebar-open?)
     ;; TODO: remove with-context usage
     (rum/with-context [[t] i18n/*tongue-context*]
-      [:div#help.font-bold.absolute.bottom-4.bg-base-2.rounded-full.h-8.w-8.flex.items-center.justify-center.font-bold.cursor.opacity-70.hover:opacity-100
-       {:style {:right 24}
-        :title (t :help-shortcut-title)
+      [:div.cp__sidebar-help
+       {:title (t :help-shortcut-title)
         :on-click (fn []
                     (state/sidebar-add-block! (state/get-current-repo) "help" :help nil))}
        "?"])))
@@ -300,6 +300,7 @@
         current-repo (state/sub :git/current-repo)
         theme (state/sub :ui/theme)
         white? (= "white" (state/sub :ui/theme))
+        sidebar-open? (state/sub :ui/sidebar-open?)
         route-name (get-in route-match [:data :name])
         global-graph-pages? (= :graph route-name)
         logged? (:name me)
@@ -309,44 +310,49 @@
         home? (= :home route-name)
         default-home (get-default-home-if-valid)]
     (rum/with-context [[t] i18n/*tongue-context*]
-      [:div {:class (if white? "white-theme" "dark-theme")
-             :on-click editor-handler/unhighlight-block!}
-       (sidebar-mobile-sidebar {:open? open?
-                                :close-fn close-fn
-                                :route-match route-match})
+      (theme/container
+       {:theme theme
+        :on-click editor-handler/unhighlight-block!}
 
-       [:div.cp__sidebar-layout.h-screen
-        (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.theme-inner
+        (sidebar-mobile-sidebar
+         {:open?       open?
+          :close-fn    close-fn
+          :route-match route-match})
+        [:div.#app-container.cp__sidebar-layout
+         {:class (if sidebar-open? "is-right-sidebar-open")}
+         (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})
 
-        (sidebar-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})]
+         (sidebar-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})]
 
-       (ui/notification)
-       (ui/modal)
-       (custom-context-menu)
-       [:a#download.hidden]
-       (when (and (not config/mobile?)
-                  (not config/publishing?))
-         (help-button)
+        (ui/notification)
+        (ui/modal)
+        (custom-context-menu)
+        [:a#download.hidden]
+        (when
+         (and (not config/mobile?)
+              (not config/publishing?))
+          (help-button)
          ;; [:div.font-bold.absolute.bottom-4.bg-base-2.rounded-full.h-8.w-8.flex.items-center.justify-center.font-bold.cursor.opacity-70.hover:opacity-100
          ;;  {:style {:left 24}
          ;;   :title "Click to show/hide sidebar"
          ;;   :on-click (fn []
          ;;               (state/set-left-sidebar-open! (not (state/get-left-sidebar-open?))))}
          ;;  (if (state/sub :ui/left-sidebar-open?) "<" ">")]
-)])))
+)]))))

+ 140 - 16
src/main/frontend/components/sidebar.css

@@ -1,36 +1,160 @@
+#left-sidebar {
+  width: 240px;
+  height: 100%;
+  top: 0;
+  left: -240px;
+  position: absolute;
+  z-index: 11;
+  opacity: 0;
+  transition: all 0.25s;
+  -webkit-transition: all 0.25s;
+  background-color: #002b36;
+
+  .enter {
+    opacity: 1;
+    left: 0;
+  }
+}
+
+#left-bar {
+  .left-sidebar-inner {
+    padding-right: 15px;
+  }
+}
+
 .cp__sidebar-layout {
-    display: flex;
-    flex-direction: column;
-    height: 100vh;
+  display: flex;
+  flex-direction: column;
+  min-height: 100vh;
 }
 
 .cp__sidebar-main-layout {
-    flex: 1 1 0;
-    overflow-y: auto;
-    display: flex;
+  overflow-y: auto;
+  display: flex;
+  background-color: var(--ls-primary-background-color);
+}
+
+.cp__sidebar-layout.is-right-sidebar-open {
+  .cp__sidebar-main-layout {
+    margin-right: 40%;
+  }
 }
 
 .cp__sidebar-main-content-container {
-    position: relative;
-    flex: 1 1 65%;
+  position: relative;
+  flex: 1;
+  display: flex;
+  width: 100%;
+  align-items: center;
+  justify-content: center;
 }
 
 .cp__sidebar-main-content {
-    padding: 3rem 1.5rem;
-    margin: 0 auto;
-    max-width: 700px;
+  padding: 3rem 1.5rem;
+  max-width: var(--ls-main-content-max-width);
+  min-height: 100vh;
+  flex: 1;
 }
 
-@media (max-width: 640px) {
-    .cp__sidebar-main-content {
-        max-width: 100vw;
+.cp__sidebar-help {
+  @apply font-bold fixed bottom-4
+    rounded-full h-8 w-8 flex items-center justify-center font-bold
+    opacity-70 hover:opacity-100;
+
+  user-select: none;
+  cursor: help;
+  right: 24px;
+  background-color: var(--ls-secondary-background-color);
+}
+
+.cp__right-sidebar {
+  position: fixed;
+  top: 3rem;
+  right: 0;
+  width: 0;
+  opacity: 0.5;
+  height: calc(100vh - 3rem);
+  overflow-x: hidden;
+  overflow-y: auto;
+  z-index: 9;
+  transition: width 0.3s, opacity 0.2s;
+  border-radius: var(--ls-border-radius-low) 0 0 0;
+
+  box-sizing: border-box;
+  background-color: var(--ls-secondary-background-color, #d8e1e8);
+  padding-bottom: 48px;
+
+  &::-webkit-scrollbar {
+    display: none;
+  }
+
+  &-inner {
+    padding: 15px;
+  }
+
+  &-settings {
+    @apply flex flex-row mb-2;
+    margin: -15px;
+    margin-bottom: 0;
+    overflow: auto;
+
+    &-btn {
+      display: block;
+      padding: 10px 5px;
+      white-space: nowrap;
+    }
+  }
+
+  &.is-open {
+    display: block;
+    width: 40%;
+    opacity: 1;
+  }
+
+  .page {
+    margin-top: 0;
+  }
+
+  .non-block-editor textarea,
+  pre,
+  pre.code {
+    background: var(--ls-right-sidebar-code-bg-color);
+  }
+
+  pre.CodeMirror-line {
+    background: #fff;
+  }
+
+  .references {
+    margin-left: 12px;
+  }
+
+  .sidebar-item {
+    border-top: 1px solid;
+    border-top-color: #ccc;
+    border-top-color: var(--ls-border-color);
+    margin-bottom: 24px;
+    padding-top: 24px;
+
+    &:first-child {
+      border-top: none;
+    }
+
+    .close {
+      transform: scale(0.8);
+      transition: transform 0.1s;
+
+      &:hover {
+        transform: scale(1);
+      }
     }
+  }
 }
 
 .cp__sidebar-main-content[data-is-full-width="true"] {
-    max-width: 100vw;
+  max-width: 100vw;
 }
 
 .cp__sidebar-main-content[data-is-global-graph-pages="true"] {
-    padding: 0;
+  padding: 0;
 }

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

@@ -72,6 +72,17 @@
    [:path
     {:d
      "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"}]])
+
+(defonce loading
+  [:svg.h-5.w-5.animate-spin
+   {:version "1.1"
+    :view-box "0 0 24 24"
+    :fill "none"
+    :display "inline-block"}
+   [:circle.opacity-25 {:cx 12 :cy 12 :r 10 :stroke "currentColor" :stroke-width 4}]
+   [:path.opacity-75 {:fill "currentColor"
+                      :d "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"}]])
+
 (defn- hero-icon
   ([d]
    (hero-icon d {}))
@@ -169,7 +180,6 @@
      :stroke-linecap "round"
      :stroke-width "2"
      :d "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"}]])
-
 (def save
   [:svg
    {:fill "currentColor", :view-box "0 0 448 512", :height "24", :width "24"}

+ 17 - 0
src/main/frontend/components/theme.cljs

@@ -0,0 +1,17 @@
+(ns frontend.components.theme
+  (:require [rum.core :as rum]))
+
+(rum/defc container
+  [{:keys [theme on-click] :as props} child]
+  rum/use-effect! (let [doc js/document.documentElement
+                        cls (.-classList doc)]
+                    (.setAttribute doc "data-theme" (if (= theme "white") "light" theme))
+                    (if (= theme "dark")                    ;; for tailwind dark mode
+                      (.add cls "dark")
+                      (.remove cls "dark")))
+
+  [theme]
+  [:div
+   {:class (str theme "-theme")
+    :on-click on-click}
+   child])

+ 40 - 0
src/main/frontend/components/theme.css

@@ -0,0 +1,40 @@
+:root {
+  scrollbar-color: var(--ls-scrollbar-foreground-color) var(--ls-scrollbar-background-color) !important;
+  scrollbar-width: thin !important;
+}
+
+html:not(.is-mac)[data-theme=dark] {
+  &::-webkit-scrollbar,
+  html::-webkit-scrollbar-track-piece {
+    background-color: var(--ls-scrollbar-background-color);
+    border: 4px solid;
+    border-color: var(--ls-scrollbar-background-color);
+  }
+
+  &::-webkit-scrollbar-thumb {
+    background-color: var(--ls-scrollbar-foreground-color);
+    background-clip: padding-box;
+    min-height: 28px;
+  }
+
+  &::-webkit-scrollbar-thumb:hover {
+    background-color: var(--ls-scrollbar-thumb-hover-color);
+  }
+}
+
+html[data-theme=dark] {
+  input {
+    color: var(--ls-secondary-text-color);
+  }
+
+  input.form-input {
+    background: none;
+  }
+
+  .form-checkbox {
+    color: var(--ls-page-checkbox-color, #6093a0);
+    background-color: var(--ls-page-checkbox-color, #6093a0);
+    border-color: var(--ls-page-checkbox-border-color, #6093a0);
+    border: none;
+  }
+}

+ 22 - 3
src/main/frontend/handler/web/nfs.cljs

@@ -2,6 +2,7 @@
   "The File System Access API, https://web.dev/file-system-access/."
   (:require [cljs-bean.core :as bean]
             [promesa.core :as p]
+            [medley.core :as medley]
             [goog.object :as gobj]
             [goog.dom :as gdom]
             [frontend.util :as util]
@@ -57,12 +58,30 @@
                        (keyword (util/get-file-ext (:file/path file)))))
           files))
 
+(defn- set-batch!
+  [handles]
+  (let [handles (map (fn [[path handle]]
+                       {:key path
+                        :value handle}) handles)]
+    (idb/set-batch! handles)))
+
+(defn- set-files-aux!
+  [handles]
+  (if (seq handles)
+    (let [[h t] (split-at 50 handles)]
+      (p/let [_ (p/promise (fn [_]
+                             (js/setTimeout (fn []
+                                              (p/resolved nil)) 10)))
+              _ (set-batch! h)]
+        (when (seq t)
+          (set-files-aux! t))))))
+
 (defn- set-files!
   [handles]
   (doseq [[path handle] handles]
     (let [handle-path (str config/local-handle-prefix path)]
-      (idb/set-item! handle-path handle)
-      (fs/add-nfs-file-handle! handle-path handle))))
+      (fs/add-nfs-file-handle! handle-path handle)))
+  (set-files-aux! handles))
 
 (defn ls-dir-files
   []
@@ -223,7 +242,7 @@
                                         (rename-f "modify" modified))]
                              (when (or (and (seq diffs) (seq modified-files))
                                        (seq diffs) ; delete
-                                       )
+)
                                (repo-handler/load-repo-to-db! repo
                                                               {:diffs diffs
                                                                :nfs-files modified-files})))))))))

+ 18 - 15
src/main/frontend/idb.cljs

@@ -1,26 +1,24 @@
 (ns frontend.idb
-  (:require ["localforage" :as localforage]
-            [cljs-bean.core :as bean]
+  (:require [cljs-bean.core :as bean]
             [goog.object :as gobj]
             [promesa.core :as p]
             [clojure.string :as string]
             [frontend.config :as config]
             [frontend.util :as util]
-            [frontend.storage :as storage]))
+            [frontend.storage :as storage]
+            ["/frontend/idbkv" :as idb-keyval :refer [Store]]))
+
 
 ;; offline db
-(def store-name "dbs")
-(.config localforage
-         (bean/->js
-          {:name "logseq-datascript"
-           :version 1.0
-           :storeName store-name}))
 
-(defonce localforage-instance (.createInstance localforage store-name))
+;; To maintain backward compatibility
+
+
+(defonce store (Store. "localforage" "keyvaluepairs" 2))
 
 (defn clear-idb!
   []
-  (p/let [_ (.clear localforage-instance)
+  (p/let [_ (idb-keyval/clear store)
           dbs (js/window.indexedDB.databases)]
     (doseq [db dbs]
       (js/window.indexedDB.deleteDatabase (gobj/get db "name")))))
@@ -33,21 +31,26 @@
 (defn remove-item!
   [key]
   (when key
-    (.removeItem localforage-instance key)))
+    (idb-keyval/del key store)))
 
 (defn set-item!
   [key value]
   (when key
-    (.setItem localforage-instance key value)))
+    (idb-keyval/set key value store)))
+
+(defn set-batch!
+  [items]
+  (when (seq items)
+    (idb-keyval/setBatch (clj->js items) store)))
 
 (defn get-item
   [key]
   (when key
-    (.getItem localforage-instance key)))
+    (idb-keyval/get key store)))
 
 (defn get-keys
   []
-  (.keys localforage-instance))
+  (idb-keyval/keys store))
 
 (defn get-nfs-dbs
   []

+ 142 - 0
src/main/frontend/idbkv.js

@@ -0,0 +1,142 @@
+'use strict';
+
+Object.defineProperty(exports, '__esModule', { value: true });
+
+class Store {
+    constructor(dbName = 'keyval-store', storeName = 'keyval', version = 1) {
+        this.storeName = storeName;
+        this._dbName = dbName;
+        this._storeName = storeName;
+        this._version = version;
+        this.id = `dbName:${dbName};;storeName:${storeName}`;
+        this._init();
+    }
+    _init() {
+        if (this._dbp) {
+            return;
+        }
+        this._dbp = new Promise((resolve, reject) => {
+            const openreq = indexedDB.open(this._dbName, this._version);
+            openreq.onerror = () => reject(openreq.error);
+            openreq.onsuccess = () => resolve(openreq.result);
+            // First time setup: create an empty object store
+            openreq.onupgradeneeded = () => {
+                openreq.result.createObjectStore(this._storeName);
+            };
+        });
+    }
+    _withIDBStore(type, callback) {
+        this._init();
+        return this._dbp.then(db => new Promise((resolve, reject) => {
+            const transaction = db.transaction(this.storeName, type);
+            transaction.oncomplete = () => resolve();
+            transaction.onabort = transaction.onerror = () => reject(transaction.error);
+            callback(transaction.objectStore(this.storeName));
+        }));
+    }
+    _close() {
+        this._init();
+        return this._dbp.then(db => {
+            db.close();
+            this._dbp = undefined;
+        });
+    }
+}
+class Batcher {
+    constructor(executor) {
+        this.executor = executor;
+        this.items = [];
+    }
+  async process() {
+        const toProcess = this.items;
+        this.items = [];
+        await this.executor(toProcess.map(({ item }) => item));
+        toProcess.map(({ onProcessed }) => onProcessed());
+        if (this.items.length) {
+            this.ongoing = this.process();
+        }
+        else {
+            this.ongoing = undefined;
+        }
+    }
+    async queue(item) {
+        const result = new Promise((resolve) => this.items.push({ item, onProcessed: resolve }));
+        if (!this.ongoing)
+            this.ongoing = this.process();
+        return result;
+    }
+}
+let store;
+function getDefaultStore() {
+    if (!store)
+        store = new Store();
+    return store;
+}
+function get(key, store = getDefaultStore()) {
+    let req;
+    return store._withIDBStore('readwrite', store => {
+        req = store.get(key);
+    }).then(() => req.result);
+}
+const setBatchers = {};
+function set(key, value, store = getDefaultStore()) {
+    if (!setBatchers[store.id]) {
+        setBatchers[store.id] = new Batcher((items) => store._withIDBStore('readwrite', store => {
+            for (const item of items) {
+                store.put(item.value, item.key);
+            }
+        }));
+    }
+    return setBatchers[store.id].queue({ key, value });
+}
+function setBatch(items, store = getDefaultStore()) {
+  return store._withIDBStore('readwrite', store => {
+    for (const item of items) {
+            store.put(item.value, item.key);
+    }
+  });
+}
+function update(key, updater, store = getDefaultStore()) {
+    return store._withIDBStore('readwrite', store => {
+        const req = store.get(key);
+        req.onsuccess = () => {
+            store.put(updater(req.result), key);
+        };
+    });
+}
+function del(key, store = getDefaultStore()) {
+    return store._withIDBStore('readwrite', store => {
+        store.delete(key);
+    });
+}
+function clear(store = getDefaultStore()) {
+    return store._withIDBStore('readwrite', store => {
+        store.clear();
+    });
+}
+function keys(store = getDefaultStore()) {
+    const keys = [];
+    return store._withIDBStore('readwrite', store => {
+        // This would be store.getAllKeys(), but it isn't supported by Edge or Safari.
+        // And openKeyCursor isn't supported by Safari.
+        (store.openKeyCursor || store.openCursor).call(store).onsuccess = function () {
+            if (!this.result)
+                return;
+            keys.push(this.result.key);
+            this.result.continue();
+        };
+    }).then(() => keys);
+}
+function close(store = getDefaultStore()) {
+    return store._close();
+}
+
+exports.Store = Store;
+exports.get = get;
+exports.set = set;
+exports.setBatch = setBatch;
+exports.update = update;
+exports.del = del;
+exports.clear = clear;
+exports.keys = keys;
+exports.close = close;

+ 15 - 12
src/main/frontend/ui.cljs

@@ -93,7 +93,7 @@
                      [:div {:style {:margin-right "8px"}} title]
                      ;; [:div {:style {:position "absolute" :right "8px"}}
                      ;;  icon]
-                     ]]
+]]
           (rum/with-key
             (menu-link new-options child)
             title)))])
@@ -123,7 +123,7 @@
     (let [[color-class svg]
           (case status
             :success
-            ["text-gray-900"
+            ["text-gray-900 dark:text-gray-300 "
              [:svg.h-6.w-6.text-green-400
               {:stroke "currentColor", :viewBox "0 0 24 24", :fill "none"}
               [:path
@@ -149,7 +149,7 @@
                 :d
                 "M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z"
                 :fill-rule "evenodd"}]]])]
-      [:div.inset-0.flex.items-end.justify-center.px-4.py-3.pointer-events-none.sm:px-6.sm:py-3.sm:items-start.sm:justify-end
+      [:div.ui__notifications-content
        {:style {:z-index (if (or (= state "exiting")
                                  (= state "exited"))
                            -1
@@ -186,7 +186,7 @@
   []
   (let [contents (state/sub :notification/contents)]
     (transition-group
-     {:class-name "notifications"}
+     {:class-name "notifications ui__notifications"}
      (doall (map (fn [el]
                    (let [k (first el)
                          v (second el)]
@@ -209,6 +209,9 @@
    text])
 
 ;; scroll
+(defn get-doc-scroll-top []
+  (.-scrollTop js/document.documentElement))
+
 (defn main-node
   []
   (gdom/getElement "main-content"))
@@ -223,6 +226,7 @@
 (defn inject-document-devices-envs!
   []
   (let [cl (.-classList js/document.documentElement)]
+    (if (util/mac?) (.add cl "is-mac"))
     (if (util/ios?) (.add cl "is-ios"))
     (if (util/safari?) (.add cl "is-safari"))))
 
@@ -268,7 +272,7 @@
 ;; FIXME: compute the right scroll position when scrolling back to the top
 (defn on-scroll
   [on-load on-top-reached]
-  (let [node (main-node)
+  (let [node js/document.documentElement
         full-height (gobj/get node "scrollHeight")
         scroll-top (gobj/get node "scrollTop")
         client-height (gobj/get node "clientHeight")
@@ -286,7 +290,7 @@
         debounced-on-scroll (util/debounce 500 #(on-scroll
                                                  (:on-load opts) ; bottom reached
                                                  (:on-top-reached opts)))]
-    (mixins/listen state (main-node) :scroll debounced-on-scroll)))
+    (mixins/listen state js/document :scroll debounced-on-scroll)))
 
 (rum/defcs infinite-list <
   (mixins/event-mixin attach-listeners)
@@ -353,7 +357,7 @@
   (let [current-idx (get state ::current-idx)]
     [:div#ui__ac {:class class}
      (if (seq matched)
-       [:div#ui__ac-inner
+       [:div#ui__ac-inner.hide-scrollbar
         (for [[idx item] (medley/indexed matched)]
           (rum/with-key
             (menu-link
@@ -389,7 +393,7 @@
   ([label children {:keys [label-style]}]
    [:div.Tooltip {:style {:display "inline"}}
     [:div (cond->
-            {:class "Tooltip__label"}
+           {:class "Tooltip__label"}
             label-style
             (assoc :style label-style))
      label]
@@ -448,10 +452,9 @@
 
 (defn loading
   [content]
-  [:div.flex.flex-row.align-center
-   [:span.lds-dual-ring.mr-2]
-   [:span {:style {:margin-top 2}}
-    content]])
+  [:div.flex.flex-row.items-center
+   [:span.icon.flex.items-center svg/loading]
+   [:span.text.pl-2 content]])
 
 (rum/defcs foldable <
   (rum/local false ::control?)

+ 31 - 0
src/main/frontend/ui.css

@@ -0,0 +1,31 @@
+#ui__ac {
+  &-inner {
+    max-height: 400px;
+    overflow-x: hidden;
+    overflow-y: auto;
+    position: relative;
+    -webkit-overflow-scrolling: touch;
+
+    > .menu-link {
+      padding: 6px 8px;
+    }
+  }
+}
+
+.ui__notifications {
+  position: fixed;
+  z-index: 99;
+  width: 100%;
+  top: 3.2em;
+
+  &-content {
+    @apply inset-0 flex items-end justify-center px-4 py-3
+      pointer-events-none sm:px-6 sm:py-3 sm:items-start
+      sm:justify-end;
+  }
+
+  .notification-area {
+    background-color: var(--ls-tertiary-background-color, #fff);
+    color: var(--ls-primary-text-color);
+  }
+}

+ 11 - 5
src/main/frontend/util.cljs

@@ -24,7 +24,14 @@
   (-pr-writer [sym writer _]
     (-write writer (str "\"" (.toString sym) "\""))))
 
+;; doms
+(defn html-node []  js/document.documentElement)
+
 ;; envs
+(defn mac?
+  []
+  (string/includes? js/navigator.appVersion "Mac"))
+
 (defn ios?
   []
   (not (nil? (re-find #"iPad|iPhone|iPod" js/navigator.userAgent))))
@@ -324,7 +331,7 @@
   (when-not (re-find #"^/\d+$" elem-id)
     (when elem-id
       (when-let [elem (gdom/getElement elem-id)]
-        (.scroll (gdom/getElement "main-content")
+        (.scroll (html-node)
                  #js {:top (let [top (element-top elem 0)]
                              (if (> top 68)
                                (- top 68)
@@ -333,10 +340,9 @@
 
 (defn scroll-to
   [pos]
-  (when-let [main-content (gdom/getElement "main-content")]
-    (.scroll main-content
-             #js {:top pos
-                  :behavior "smooth"})))
+  (.scroll (html-node)
+           #js {:top      pos
+                :behavior "smooth"}))
 
 (defn scroll-to-top
   []

+ 3 - 2
tailwind.config.js

@@ -4,5 +4,6 @@ module.exports = {
     './src/**/*.cljs',
     './resources/**/*.html',
   ],
-  plugins: [require("@tailwindcss/ui")],
-};
+  plugins: [require('@tailwindcss/ui')],
+  darkMode: 'class',
+}

File diff suppressed because it is too large
+ 470 - 217
yarn.lock


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