1
0
Эх сурвалжийг харах

Merge remote-tracking branch 'upstream/master' into whiteboards

Peng Xiao 3 жил өмнө
parent
commit
031ad9c4a9

+ 0 - 1
.clj-kondo/config.edn

@@ -64,7 +64,6 @@
            rum.core/defcontext clojure.core/def
            clojure.test.check.clojure-test/defspec clojure.core/def
            clojure.test.check.properties/for-all clojure.core/for
-           nubank.workspaces.core/defcard clojure.core/def
            ;; src/main
            frontend.modules.outliner.datascript/auto-transact! clojure.core/let
            frontend.namespaces/import-vars potemkin/import-vars

+ 2 - 0
.gitignore

@@ -45,3 +45,5 @@ startup.png
 
 /src/test/docs
 ~*~
+
+ios/App/App/capacitor.config.json

+ 2 - 2
android/app/build.gradle

@@ -6,8 +6,8 @@ android {
         applicationId "com.logseq.app"
         minSdkVersion rootProject.ext.minSdkVersion
         targetSdkVersion rootProject.ext.targetSdkVersion
-        versionCode 40
-        versionName "0.8.6"
+        versionCode 41
+        versionName "0.8.7"
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
         aaptOptions {
              // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.

+ 29 - 26
capacitor.config.ts

@@ -1,32 +1,35 @@
-import { CapacitorConfig } from '@capacitor/cli';
+import { CapacitorConfig } from '@capacitor/cli'
 
 const config: CapacitorConfig = {
-    appId: 'com.logseq.app',
-    appName: 'Logseq',
-    bundledWebRuntime: false,
-    webDir: 'public',
-    plugins: {
-        SplashScreen: {
-            launchShowDuration: 500,
-            launchAutoHide: false,
-            androidScaleType: "CENTER_CROP",
-            splashImmersive: false,
-            backgroundColor: "#002b36"
-        },
-
-        Keyboard: {
-            resize: "none"
-        }
+  appId: 'com.logseq.app',
+  appName: 'Logseq',
+  bundledWebRuntime: false,
+  webDir: 'public',
+  plugins: {
+    SplashScreen: {
+      launchShowDuration: 500,
+      launchAutoHide: false,
+      androidScaleType: 'CENTER_CROP',
+      splashImmersive: false,
+      backgroundColor: '#002b36'
     },
-    ios: {
-        scheme: "Logseq"
+
+    Keyboard: {
+      resize: 'none'
+    }
+  },
+  ios: {
+    scheme: 'Logseq'
+  }
+}
+
+if (process.env.LOGSEQ_APP_SERVER_URL) {
+  Object.assign(config, {
+    server: {
+      url: process.env.LOGSEQ_APP_SERVER_URL,
+      cleartext: true
     }
-    // do not commit this into source control
-    // source: https://capacitorjs.com/docs/guides/live-reload
-    // , server: {
-    //    url: process.env.LOGSEQ_APP_SERVER_URL,
-    //    cleartext: true
-    // }
-};
+  })
+}
 
 export = config;

+ 42 - 5
gulpfile.js

@@ -6,6 +6,7 @@ const path = require('path')
 const gulp = require('gulp')
 const cleanCSS = require('gulp-clean-css')
 const del = require('del')
+const ip = require('ip')
 
 const outputPath = path.join(__dirname, 'static')
 const resourcesPath = path.join(__dirname, 'resources')
@@ -49,13 +50,13 @@ const common = {
   syncAssetFiles (...params) {
     return gulp.series(
       () => gulp.src([
-          "./node_modules/@excalidraw/excalidraw/dist/excalidraw-assets/**",
-          "!**/*/i18n-*.js"
-        ])
+        './node_modules/@excalidraw/excalidraw/dist/excalidraw-assets/**',
+        '!**/*/i18n-*.js'
+      ])
         .pipe(gulp.dest(path.join(outputPath, 'js', 'excalidraw-assets'))),
-      () => gulp.src("node_modules/@tabler/icons/iconfont/tabler-icons.min.css")
+      () => gulp.src('node_modules/@tabler/icons/iconfont/tabler-icons.min.css')
         .pipe(gulp.dest(path.join(outputPath, 'css'))),
-      () => gulp.src("node_modules/@tabler/icons/iconfont/fonts/**")
+      () => gulp.src('node_modules/@tabler/icons/iconfont/fonts/**')
         .pipe(gulp.dest(path.join(outputPath, 'css', 'fonts'))),
     )(...params)
   },
@@ -83,6 +84,41 @@ const common = {
       path.join(outputPath, 'js/**'),
       path.join(outputPath, 'css/**')
     ], { ignoreInitial: true }, common.syncJS_CSSinRt)
+  },
+
+  async runCapWithLocalDevServerEntry (cb) {
+    const mode = process.env.PLATFORM || 'ios'
+
+    const IP = ip.address()
+    const LOGSEQ_APP_SERVER_URL = `http://${IP}:3001`
+
+    if (typeof global.fetch === 'function') {
+      try {
+        await fetch(LOGSEQ_APP_SERVER_URL)
+      } catch (e) {
+        return cb(new Error(`/* ❌ Please check if the service is ON. (${LOGSEQ_APP_SERVER_URL}) ❌ */`))
+      }
+    }
+
+    console.log(`------ Cap ${mode.toUpperCase()} -----`)
+    console.log(`Dev serve at: ${LOGSEQ_APP_SERVER_URL}`)
+    console.log(`--------------------------------------`)
+
+    cp.execSync(`npx cap sync ${mode}`, {
+      stdio: 'inherit',
+      env: Object.assign(process.env, {
+        LOGSEQ_APP_SERVER_URL
+      })
+    })
+
+    cp.execSync(`npx cap run ${mode} --external`, {
+      stdio: 'inherit',
+      env: Object.assign(process.env, {
+        LOGSEQ_APP_SERVER_URL
+      })
+    })
+
+    cb()
   }
 }
 
@@ -130,6 +166,7 @@ exports.electronMaker = async () => {
   })
 }
 
+exports.cap = common.runCapWithLocalDevServerEntry
 exports.clean = common.clean
 exports.watch = gulp.series(common.syncResourceFile, common.syncAssetFiles, common.syncAllStatic,
   gulp.parallel(common.keepSyncResourceFile, css.watchCSS))

+ 4 - 4
ios/App/App.xcodeproj/project.pbxproj

@@ -547,7 +547,7 @@
 				INFOPLIST_FILE = App/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
-				MARKETING_VERSION = 0.8.6;
+				MARKETING_VERSION = 0.8.7;
 				OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq;
 				PRODUCT_NAME = "$(TARGET_NAME)";
@@ -574,7 +574,7 @@
 				INFOPLIST_FILE = App/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
-				MARKETING_VERSION = 0.8.6;
+				MARKETING_VERSION = 0.8.7;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
@@ -599,7 +599,7 @@
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
-				MARKETING_VERSION = 0.8.6;
+				MARKETING_VERSION = 0.8.7;
 				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
 				MTL_FAST_MATH = YES;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq.ShareViewController;
@@ -626,7 +626,7 @@
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
-				MARKETING_VERSION = 0.8.6;
+				MARKETING_VERSION = 0.8.7;
 				MTL_FAST_MATH = YES;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq.ShareViewController;
 				PRODUCT_NAME = "$(TARGET_NAME)";

+ 4 - 4
ios/App/App/FileSync/FileSync.swift

@@ -225,13 +225,13 @@ public class FileSync: CAPPlugin, SyncDebugDelegate {
                   call.reject("required parameters: passphrase, content")
                   return
               }
-        guard let ciphertext = content.data(using: .utf8) else {
+        guard let plaintext = content.data(using: .utf8) else {
             call.reject("cannot decode ciphertext with utf8")
             return
         }
         call.keepAlive = true
-        DispatchQueue.global(qos: .background).async {
-            if let encrypted = AgeEncryption.encryptWithPassphrase(ciphertext, passphrase, armor: true) {
+        DispatchQueue.global(qos: .default).async {
+            if let encrypted = AgeEncryption.encryptWithPassphrase(plaintext, passphrase, armor: true) {
                 call.resolve(["data": String(data: encrypted, encoding: .utf8) as Any])
             } else {
                 call.reject("cannot encrypt with passphrase")
@@ -251,7 +251,7 @@ public class FileSync: CAPPlugin, SyncDebugDelegate {
             return
         }
         call.keepAlive = true
-        DispatchQueue.global(qos: .background).async {
+        DispatchQueue.global(qos: .default).async {
             if let decrypted = AgeEncryption.decryptWithPassphrase(ciphertext, passphrase) {
                 call.resolve(["data": String(data: decrypted, encoding: .utf8) as Any])
             } else {

+ 0 - 21
ios/App/App/capacitor.config.json

@@ -1,21 +0,0 @@
-{
-	"appId": "com.logseq.app",
-	"appName": "Logseq",
-	"bundledWebRuntime": false,
-	"webDir": "public",
-	"plugins": {
-		"SplashScreen": {
-			"launchShowDuration": 500,
-			"launchAutoHide": false,
-			"androidScaleType": "CENTER_CROP",
-			"splashImmersive": false,
-			"backgroundColor": "#002b36"
-		},
-		"Keyboard": {
-			"resize": "none"
-		}
-	},
-	"ios": {
-		"scheme": "Logseq"
-	}
-}

+ 4 - 1
package.json

@@ -26,7 +26,8 @@
         "stylelint": "^13.8.0",
         "stylelint-config-standard": "^20.0.0",
         "tailwindcss": "2.2.16",
-        "typescript": "^4.4.3"
+        "typescript": "^4.4.3",
+        "ip": "1.1.8"
     },
     "scripts": {
         "watch": "run-p gulp:watch cljs:watch",
@@ -64,6 +65,8 @@
         "cljs:report": "clojure -M:cljs run shadow.cljs.build-report app report.html",
         "cljs:build-electron": "clojure -A:cljs compile app electron",
         "cljs:lint": "clojure -M:clj-kondo --parallel --lint src --cache false",
+        "ios:dev": "cross-env PLATFORM=ios gulp cap",
+        "android:dev": "cross-env PLATFORM=android gulp cap",
         "tldraw:build": "cd tldraw && yarn",
         "postinstall": "yarn tldraw:build"
     },

+ 1 - 1
resources/css/common.css

@@ -3,7 +3,7 @@
   --ls-tag-text-hover-opacity: 1;
   --ls-page-text-size: 1em;
   --ls-page-title-size: 36px;
-  --ls-main-content-max-width: 1200px;
+  --ls-main-content-max-width: 810px;
   --ls-main-content-max-width-wide: 100%;
   --ls-font-family: Inter;
   --ls-scrollbar-width: 6px;

+ 1 - 1
resources/package.json

@@ -1,6 +1,6 @@
 {
   "name": "Logseq",
-  "version": "0.8.6",
+  "version": "0.8.7",
   "main": "electron.js",
   "author": "Logseq",
   "license": "AGPL-3.0",

+ 37 - 31
src/main/frontend/components/file_sync.cljs

@@ -182,11 +182,12 @@
 
         status                 (:state sync-state)
         status                 (or (nil? status) (keyword (name status)))
-        off?                   (or (nil? sync-state) (fs-sync/sync-state--stopped? sync-state))
+        off?                   (fs-sync/sync-off? sync-state)
         full-syncing?          (contains? #{:local->remote-full-sync :remote->local-full-sync} status)
         syncing?               (or full-syncing? (contains? #{:local->remote :remote->local} status))
         idle?                  (contains? #{:idle} status)
-        need-password?         (contains? #{:need-password} status)
+        need-password?         (and (contains? #{:need-password} status)
+                                    (not (fs-sync/graph-encrypted?)))
         queuing?               (and idle? (boolean (seq queuing-files)))
         no-active-files?       (empty? (concat downloading-files queuing-files uploading-files))
         create-remote-graph-fn #(when (and current-repo (not (config/demo-graph? current-repo)))
@@ -198,36 +199,41 @@
                                           (create-remote-graph-panel current-repo graph-name close-fn))]
 
                                     (state/set-modal! confirm-fn {:center? true :close-btn? false})))
-        turn-on                #(async/go
-                                  (async/<! (p->c (persist-var/-load fs-sync/graphs-txid)))
-                                  (cond
-                                    @*beta-unavailable?
-                                    (state/pub-event! [:file-sync/onboarding-tip :unavailable])
-
-                                    ;; current graph belong to other user, do nothing
-                                    (and (first @fs-sync/graphs-txid)
-                                         (not (fs-sync/check-graph-belong-to-current-user (user-handler/user-uuid)
-                                                                                          (first @fs-sync/graphs-txid))))
-                                    nil
-
-                                    (and synced-file-graph?
-                                         (second @fs-sync/graphs-txid)
-                                         (async/<! (fs-sync/<check-remote-graph-exists (second @fs-sync/graphs-txid))))
-                                    (fs-sync/sync-start)
-
-
-                                    ;; remote graph already has been deleted, clear repos first, then create-remote-graph
-                                    synced-file-graph?      ; <check-remote-graph-exists -> false
-                                    (do (state/set-repos!
-                                         (map (fn [r]
-                                                (if (= (:url r) current-repo)
-                                                  (dissoc r :GraphUUID :GraphName :remote?)
-                                                  r))
+        turn-on                (fn []
+                                 (when-not (file-sync-handler/current-graph-sync-on?)
+                                   (async/go
+                                     (async/<! (p->c (persist-var/-load fs-sync/graphs-txid)))
+                                     (cond
+                                       @*beta-unavailable?
+                                       (state/pub-event! [:file-sync/onboarding-tip :unavailable])
+
+                                       ;; current graph belong to other user, do nothing
+                                       (and (first @fs-sync/graphs-txid)
+                                            (not (fs-sync/check-graph-belong-to-current-user (user-handler/user-uuid)
+                                                                                             (first @fs-sync/graphs-txid))))
+                                       nil
+
+                                       (and synced-file-graph?
+                                            (fs-sync/graph-sync-off? current-repo)
+                                            (second @fs-sync/graphs-txid)
+                                            (async/<! (fs-sync/<check-remote-graph-exists (second @fs-sync/graphs-txid))))
+                                       (fs-sync/sync-start)
+
+                                       ;; remote graph already has been deleted, clear repos first, then create-remote-graph
+                                       synced-file-graph?      ; <check-remote-graph-exists -> false
+                                       (do (state/set-repos!
+                                            (map (fn [r]
+                                                   (if (= (:url r) current-repo)
+                                                     (dissoc r :GraphUUID :GraphName :remote?)
+                                                     r))
                                               (state/get-repos)))
-                                        (create-remote-graph-fn))
+                                           (create-remote-graph-fn))
 
-                                    :else
-                                    (create-remote-graph-fn)))]
+                                       (second @fs-sync/graphs-txid) ; sync not started yet
+                                       nil
+
+                                       :else
+                                       (create-remote-graph-fn)))))]
 
     (if creating-remote-graph?
       (ui/loading "")
@@ -261,7 +267,7 @@
                (if need-password?
                  [{:title   [:div.file-item
                              (ui/icon "lock") "Password is required"]
-                   :options {:on-click #(state/pub-event! [:file-sync/restart])}}]
+                   :options {:on-click fs-sync/sync-need-password!}}]
                  [{:title   [:div.file-item.is-first ""]
                    :options {:class "is-first-placeholder"}}]))
 

+ 10 - 10
src/main/frontend/components/plugins_settings.cljs

@@ -87,19 +87,19 @@
 
 (rum/defc settings-container
   [schema ^js pl]
-  (let [^js settings (.-settings pl)
+  (let [^js plugin-settings (.-settings pl)
         pid (.-id pl)
-        [settings, set-settings] (rum/use-state (bean/->clj (.toJSON settings)))
-        update-setting! (fn [k v] (.set settings (name k) (bean/->js v)))]
+        [settings, set-settings] (rum/use-state (bean/->clj (.toJSON plugin-settings)))
+        update-setting! (fn [k v] (.set plugin-settings (name k) (bean/->js v)))]
 
     (rum/use-effect!
-      (fn []
-        (let [on-change (fn [^js s]
-                          (when-let [s (bean/->clj s)]
-                            (set-settings s)))]
-          (.on settings "change" on-change)
-          #(.off settings "change" on-change)))
-      [pid])
+     (fn []
+       (let [on-change (fn [^js s]
+                         (when-let [s (bean/->clj s)]
+                           (set-settings s)))]
+         (.on plugin-settings "change" on-change)
+         #(.off plugin-settings "change" on-change)))
+     [pid])
 
     (if (seq schema)
       [:div.cp__plugins-settings-inner

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

@@ -110,7 +110,7 @@
                   keys))
           desc? (desc? *desc? p-desc?)
           result (sort-result-by (fn [item]
-                                   (sort-by-fn sort-by-item item))
+                                   (block/normalize-block (sort-by-fn sort-by-item item)))
                                  desc?
                                  result)]
       [:div.overflow-x-auto {:on-mouse-down (fn [e] (.stopPropagation e))

+ 13 - 9
src/main/frontend/components/right_sidebar.cljs

@@ -164,6 +164,8 @@
         min-ratio 0.1
         max-ratio 0.7
         keyboard-step 5
+        add-resizing-class #(.. js/document.documentElement -classList (add "is-resizing-buf"))
+        remove-resizing-class #(.. js/document.documentElement -classList (remove "is-resizing-buf"))
         set-width! (fn [ratio element]
                      (when (and el-ref element)
                        (let [width (str (* ratio 100) "%")]
@@ -201,20 +203,22 @@
                          #(.. js/document.documentElement -classList (remove cursor-class)))
                        (when (> ratio (/ min-ratio 2)) (state/open-right-sidebar!)))))}}))
              (.styleCursor false)
-             (.on "dragstart" #(.. js/document.documentElement -classList (add "is-resizing-buf")))
-             (.on "dragend" #(.. js/document.documentElement -classList (remove "is-resizing-buf")))
+             (.on "dragstart" add-resizing-class)
+             (.on "dragend" remove-resizing-class)
              (.on "keydown" (fn [e]
                               (when-let [sidebar-el (js/document.getElementById sidebar-id)]
                                 (let [width js/document.documentElement.clientWidth
-                                      offset (+
-                                              (.-x (.getBoundingClientRect sidebar-el))
-                                              (case (.-code e)
-                                                "ArrowLeft" (- keyboard-step)
-                                                "ArrowRight" keyboard-step
-                                                :else 0))
+                                      keyboard-step (case (.-code e)
+                                                      "ArrowLeft" (- keyboard-step)
+                                                      "ArrowRight" keyboard-step
+                                                      0)
+                                      offset (+ (.-x (.getBoundingClientRect sidebar-el)) keyboard-step)
                                       ratio (.toFixed (/ offset width) 6)
                                       ratio (if (= handler-position :west) (- 1 ratio) ratio)]
-                                  (when (and (> ratio min-ratio) (< ratio max-ratio)) (set-width! ratio sidebar-el))))))))
+                                  (when (and (> ratio min-ratio) (< ratio max-ratio) (not (zero? keyboard-step)))
+                                    ((add-resizing-class)
+                                     (set-width! ratio sidebar-el)))))))
+             (.on "keyup" remove-resizing-class)))
        #())
      [])
     [:.resizer {:ref el-ref

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

@@ -368,7 +368,7 @@
      (left-sidebar {:left-sidebar-open? left-sidebar-open?
                     :route-match route-match})
 
-     [:div#main-content-container.scrollbar-spacing.w-full.flex.justify-center.flex-row
+     [:div#main-content-container.scrollbar-spacing.w-full.flex.justify-center.flex-row.outline-none
 
       {:tabIndex "-1"
        :data-is-margin-less-pages margin-less-pages?}

+ 1 - 5
src/main/frontend/components/sidebar.css

@@ -144,11 +144,7 @@
     }
 
     &:hover {
-      opacity: .8;
-
-      .ti, .tie {
-        opacity: .8;
-      }
+      color: var(--ls-secondary-text-color);
     }
   }
 

+ 31 - 6
src/main/frontend/format/block.cljs

@@ -1,16 +1,18 @@
 (ns frontend.format.block
   "Block code needed by app but not graph-parser"
-  (:require [clojure.string :as string]
-            [logseq.graph-parser.block :as gp-block]
+  (:require ["@sentry/react" :as Sentry]
+            [cljs-time.format :as tf]
+            [clojure.string :as string]
             [frontend.config :as config]
+            [frontend.date :as date]
             [frontend.db :as db]
             [frontend.format :as format]
-            [frontend.state :as state]
             [frontend.handler.notification :as notification]
-            ["@sentry/react" :as Sentry]
+            [frontend.state :as state]
+            [logseq.graph-parser.block :as gp-block]
             [logseq.graph-parser.config :as gp-config]
-            [logseq.graph-parser.property :as gp-property]
-            [logseq.graph-parser.mldoc :as gp-mldoc]))
+            [logseq.graph-parser.mldoc :as gp-mldoc]
+            [logseq.graph-parser.property :as gp-property]))
 
 (defn extract-blocks
   "Wrapper around logseq.graph-parser.block/extract-blocks that adds in system state
@@ -36,6 +38,29 @@ and handles unexpected failure."
   ([original-page-name with-id? with-timestamp?]
    (gp-block/page-name->map original-page-name with-id? (db/get-db (state/get-current-repo)) with-timestamp? (state/get-date-formatter))))
 
+(defn- normalize-as-percentage
+  ([block]
+   (some->> block
+            str
+            (re-matches #"(-?\d+\.?\d*)%")
+            second
+            (#(/ % 100)))))
+
+(defn- normalize-as-date
+  ([block]
+   (some->> block
+            str
+            date/valid?
+            (tf/unparse date/custom-formatter))))
+
+(defn normalize-block
+  "Normalizes supported formats such as dates and percentages."
+  ([block]
+   (->> [normalize-as-percentage normalize-as-date identity]
+        (map #(% block))
+        (remove nil?)
+        (first))))
+
 (defn parse-block
   ([block]
    (parse-block block nil))

+ 28 - 7
src/main/frontend/fs/sync.cljs

@@ -71,8 +71,9 @@
                  ::local->remote-full-sync
                  ;; remote->local full sync
                  ::remote->local-full-sync
-                 ::stop
-                 ::pause})
+                 ;; snapshot state when switching between apps on iOS
+                 ::pause
+                 ::stop})
 (s/def ::path string?)
 (s/def ::time t/date?)
 (s/def ::remote->local-type #{:delete :update
@@ -1679,6 +1680,10 @@
   [graph-uuid]
   (js/localStorage.removeItem (local-storage-pwd-path graph-uuid)))
 
+(defn get-pwd
+  [graph-uuid]
+  (js/localStorage.getItem (local-storage-pwd-path graph-uuid)))
+
 (defn remove-all-pwd!
   []
   (doseq [k (filter #(string/starts-with? % "encrypted-pwd/") (js->clj (js-keys js/localStorage)))]
@@ -1706,7 +1711,7 @@
   "restore pwd from persisted encrypted-pwd, update `pwd-map`"
   [graph-uuid]
   (go
-    (let [encrypted-pwd (js/localStorage.getItem (local-storage-pwd-path graph-uuid))]
+    (let [encrypted-pwd (get-pwd graph-uuid)]
       (if (nil? encrypted-pwd)
         {:restore-pwd-failed true}
         (let [[salt-value _expired-at gone?]
@@ -2713,6 +2718,11 @@
       (state/set-file-sync-manager nil))
     (reset! current-sm-graph-uuid nil)))
 
+(defn sync-need-password!
+  []
+  (when-let [sm ^SyncManager (state/get-file-sync-manager)]
+    (.need-password sm)))
+
 (defn check-graph-belong-to-current-user
   [current-user-uuid graph-user-uuid]
   (cond
@@ -2746,6 +2756,20 @@
         (notification/show! (t :file-sync/graph-deleted) :warning false))
       result)))
 
+(defn sync-off?
+  [sync-state]
+  (or (nil? sync-state) (sync-state--stopped? sync-state)))
+
+(defn graph-sync-off?
+  "Is sync not running for this `graph`?"
+  [graph]
+  (sync-off? (state/get-file-sync-state graph)))
+
+(defn graph-encrypted?
+  []
+  (when-let [graph-uuid (second @graphs-txid)]
+    (get-pwd graph-uuid)))
+
 (declare network-online-cursor)
 
 (defn sync-start []
@@ -2753,10 +2777,7 @@
         current-user-uuid           (user/user-uuid)
         repo                        (state/get-current-repo)]
     (go
-      (when @network-online-cursor
-        ;; stop previous sync
-        (<! (<sync-stop))
-
+      (when (and (graph-sync-off? repo) @network-online-cursor)
         (<! (p->c (persist-var/-load graphs-txid)))
 
         (let [[user-uuid graph-uuid txid] @graphs-txid]

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

@@ -88,7 +88,7 @@
                                     (vector? (:sync-meta %))
                                     (util/uuid-string? (first (:sync-meta %)))
                                     (util/uuid-string? (second (:sync-meta %)))) repos)
-                    (file-sync-restart!)))))
+                    (sync/sync-start)))))
             (file-sync/maybe-onboarding-show status)))))))
 
 (defmethod handle :user/logout [[_]]
@@ -639,9 +639,6 @@
   (notification/show! "file sync graph count exceed limit" :warning false)
   (file-sync-stop!))
 
-(defmethod handle :file-sync/restart [[_]]
-  (file-sync-restart!))
-
 (defmethod handle :graph/restored [[_ _graph]]
   (mobile/init!)
   (when-not (mobile-util/native-ios?)

+ 1 - 1
src/main/frontend/version.cljs

@@ -1,3 +1,3 @@
 (ns frontend.version)
 
-(defonce version "0.8.6")
+(defonce version "0.8.7")

+ 42 - 0
src/test/frontend/format/block_test.cljs

@@ -0,0 +1,42 @@
+(ns frontend.format.block-test 
+  (:require [cljs.test :refer [deftest testing are]]
+            [frontend.format.block :as block]))
+
+(deftest test-normalize-date
+  (testing "normalize date values"
+    (are [x y] (= (block/normalize-block x) y)
+         "Aug 12th, 2022"
+         "2022-08-12T00:00:00Z"
+
+         "2022-08-12T00:00:00Z"
+         "2022-08-12T00:00:00Z")))
+
+(deftest test-normalize-percentage
+  (testing "normalize percentages"
+    (are [x y] (= (block/normalize-block x) y)
+         "50%"
+         0.5
+
+         "0%"
+         0
+
+         "-5%"
+         -0.05)))
+
+(deftest test-random-values
+  (testing "random values should not be processed"
+    (are [x y] (= (block/normalize-block x) y)
+         "anreanre"
+         "anreanre"
+
+         ""
+         ""
+
+         "a.0%"
+         "a.0%"
+
+         "%"
+         "%"
+
+         "-%"
+         "-%")))

+ 5 - 0
yarn.lock

@@ -3706,6 +3706,11 @@ invert-kv@^2.0.0:
   resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02"
   integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==
 
[email protected]:
+  version "1.1.8"
+  resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.8.tgz#ae05948f6b075435ed3307acce04629da8cdbf48"
+  integrity sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==
+
 is-absolute@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-1.0.0.tgz#395e1ae84b11f26ad1795e73c17378e48a301576"