Quellcode durchsuchen

Merge branch 'master' into feat/cmdk

Ben Yorke vor 2 Jahren
Ursprung
Commit
69e02edaeb
100 geänderte Dateien mit 1858 neuen und 885 gelöschten Zeilen
  1. 1 1
      .github/workflows/build-android.yml
  2. 3 2
      android/app/build.gradle
  3. 3 3
      android/app/capacitor.build.gradle
  4. 7 5
      android/app/src/main/AndroidManifest.xml
  5. 2 2
      android/app/src/main/assets/capacitor.plugins.json
  6. 1 1
      android/app/src/main/java/com/logseq/app/FolderPicker.java
  7. 2 2
      android/build.gradle
  8. 2 2
      android/capacitor.settings.gradle
  9. 1 2
      android/gradle.properties
  10. 1 1
      android/gradle/wrapper/gradle-wrapper.properties
  11. 10 10
      android/variables.gradle
  12. 4 0
      capacitor.config.ts
  13. 1 1
      deps.edn
  14. 1 1
      deps/common/src/logseq/common/path.cljs
  15. 1 1
      docs/Build LogSeq Desktop for windows on Ubuntu.md
  16. 3 3
      docs/accessibility.md
  17. 3 3
      docs/contributing-to-translations.md
  18. 1 0
      e2e-tests/accessibility.spec.ts
  19. 13 0
      externs.js
  20. 1 1
      ios/.gitignore
  21. 4 4
      ios/App/App.xcodeproj/project.pbxproj
  22. BIN
      ios/App/App/Assets.xcassets/AppIcon.appiconset/[email protected]
  23. BIN
      ios/App/App/Assets.xcassets/AppIcon.appiconset/[email protected]
  24. BIN
      ios/App/App/Assets.xcassets/AppIcon.appiconset/[email protected]
  25. BIN
      ios/App/App/Assets.xcassets/AppIcon.appiconset/[email protected]
  26. BIN
      ios/App/App/Assets.xcassets/AppIcon.appiconset/[email protected]
  27. BIN
      ios/App/App/Assets.xcassets/AppIcon.appiconset/[email protected]
  28. BIN
      ios/App/App/Assets.xcassets/AppIcon.appiconset/[email protected]
  29. BIN
      ios/App/App/Assets.xcassets/AppIcon.appiconset/[email protected]
  30. BIN
      ios/App/App/Assets.xcassets/AppIcon.appiconset/[email protected]
  31. BIN
      ios/App/App/Assets.xcassets/AppIcon.appiconset/[email protected]
  32. BIN
      ios/App/App/Assets.xcassets/AppIcon.appiconset/[email protected]
  33. BIN
      ios/App/App/Assets.xcassets/AppIcon.appiconset/[email protected]
  34. BIN
      ios/App/App/Assets.xcassets/AppIcon.appiconset/[email protected]
  35. BIN
      ios/App/App/Assets.xcassets/AppIcon.appiconset/[email protected]
  36. BIN
      ios/App/App/Assets.xcassets/AppIcon.appiconset/[email protected]
  37. BIN
      ios/App/App/Assets.xcassets/AppIcon.appiconset/[email protected]
  38. BIN
      ios/App/App/Assets.xcassets/AppIcon.appiconset/[email protected]
  39. 11 113
      ios/App/App/Assets.xcassets/AppIcon.appiconset/Contents.json
  40. 1 1
      ios/App/Podfile
  41. 2 1
      libs/src/LSPlugin.ts
  42. 27 19
      package.json
  43. 1 1
      resources/forge.config.js
  44. 27 5
      resources/js/preload.js
  45. 3 2
      resources/package.json
  46. 1 1
      scripts/get-pkg-version.js
  47. 2 1
      scripts/src/logseq/tasks/lang.clj
  48. 7 7
      src/electron/electron/configs.cljs
  49. 32 34
      src/electron/electron/core.cljs
  50. 13 2
      src/electron/electron/handler.cljs
  51. 18 11
      src/electron/electron/search.cljs
  52. 8 2
      src/electron/electron/utils.cljs
  53. 3 4
      src/electron/electron/utils.js
  54. 25 17
      src/electron/electron/window.cljs
  55. 1 1
      src/main/frontend/components/block.cljs
  56. 2 2
      src/main/frontend/components/bug_report.cljs
  57. 2 19
      src/main/frontend/components/container.css
  58. 22 18
      src/main/frontend/components/editor.cljs
  59. 6 3
      src/main/frontend/components/header.cljs
  60. 33 11
      src/main/frontend/components/page.cljs
  61. 4 1
      src/main/frontend/components/plugins.cljs
  62. 1 0
      src/main/frontend/components/right_sidebar.cljs
  63. 16 11
      src/main/frontend/components/settings.cljs
  64. 9 5
      src/main/frontend/components/shortcut2.cljs
  65. 6 1
      src/main/frontend/components/theme.cljs
  66. 1 1
      src/main/frontend/components/whiteboard.css
  67. 6 22
      src/main/frontend/components/window_controls.cljs
  68. 14 10
      src/main/frontend/config.cljs
  69. 4 2
      src/main/frontend/dicts.cljc
  70. 1 1
      src/main/frontend/extensions/code.cljs
  71. 31 32
      src/main/frontend/extensions/pdf/assets.cljs
  72. 26 16
      src/main/frontend/extensions/pdf/core.cljs
  73. 4 10
      src/main/frontend/extensions/video/youtube.cljs
  74. 313 262
      src/main/frontend/fs/sync.cljs
  75. 1 1
      src/main/frontend/handler/dnd.cljs
  76. 6 3
      src/main/frontend/handler/editor.cljs
  77. 8 3
      src/main/frontend/handler/file_sync.cljs
  78. 5 3
      src/main/frontend/handler/route.cljs
  79. 11 5
      src/main/frontend/handler/user.cljs
  80. 19 0
      src/main/frontend/handler/web/nfs.cljs
  81. 2 2
      src/main/frontend/handler/whiteboard.cljs
  82. 19 0
      src/main/frontend/handler/window.cljs
  83. 1 0
      src/main/frontend/mobile/action_bar.cljs
  84. 9 1
      src/main/frontend/mobile/core.cljs
  85. 1 0
      src/main/frontend/mobile/index.css
  86. 105 98
      src/main/frontend/modules/shortcut/config.cljs
  87. 5 5
      src/main/frontend/modules/shortcut/core.cljs
  88. 4 0
      src/main/frontend/state.cljs
  89. 22 18
      src/main/frontend/ui.cljs
  90. 3 3
      src/main/frontend/util.cljc
  91. 1 1
      src/main/frontend/version.cljs
  92. 20 10
      src/main/logseq/api.cljs
  93. 0 2
      src/resources/dicts/de.edn
  94. 5 3
      src/resources/dicts/en.edn
  95. 42 25
      src/resources/dicts/es.edn
  96. 0 2
      src/resources/dicts/fr.edn
  97. 821 0
      src/resources/dicts/id.edn
  98. 0 2
      src/resources/dicts/it.edn
  99. 0 2
      src/resources/dicts/ja.edn
  100. 0 2
      src/resources/dicts/ko.edn

+ 1 - 1
.github/workflows/build-android.yml

@@ -43,7 +43,7 @@ on:
 env:
 env:
   CLOJURE_VERSION: '1.10.1.763'
   CLOJURE_VERSION: '1.10.1.763'
   NODE_VERSION: '18'
   NODE_VERSION: '18'
-  JAVA_VERSION: '11'
+  JAVA_VERSION: '17'
 
 
 jobs:
 jobs:
   build-apk:
   build-apk:

+ 3 - 2
android/app/build.gradle

@@ -1,13 +1,14 @@
 apply plugin: 'com.android.application'
 apply plugin: 'com.android.application'
 
 
 android {
 android {
+    namespace "com.logseq.app"
     compileSdkVersion rootProject.ext.compileSdkVersion
     compileSdkVersion rootProject.ext.compileSdkVersion
     defaultConfig {
     defaultConfig {
         applicationId "com.logseq.app"
         applicationId "com.logseq.app"
         minSdkVersion rootProject.ext.minSdkVersion
         minSdkVersion rootProject.ext.minSdkVersion
         targetSdkVersion rootProject.ext.targetSdkVersion
         targetSdkVersion rootProject.ext.targetSdkVersion
-        versionCode 68
-        versionName "0.9.15"
+        versionCode 72
+        versionName "0.9.19"
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
         aaptOptions {
         aaptOptions {
              // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
              // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.

+ 3 - 3
android/app/capacitor.build.gradle

@@ -2,8 +2,8 @@
 
 
 android {
 android {
   compileOptions {
   compileOptions {
-      sourceCompatibility JavaVersion.VERSION_11
-      targetCompatibility JavaVersion.VERSION_11
+      sourceCompatibility JavaVersion.VERSION_17
+      targetCompatibility JavaVersion.VERSION_17
   }
   }
 }
 }
 
 
@@ -19,7 +19,7 @@ dependencies {
     implementation project(':capacitor-splash-screen')
     implementation project(':capacitor-splash-screen')
     implementation project(':capacitor-status-bar')
     implementation project(':capacitor-status-bar')
     implementation project(':capawesome-capacitor-background-task')
     implementation project(':capawesome-capacitor-background-task')
-    implementation project(':hugotomazi-capacitor-navigation-bar')
+    implementation project(':capgo-capacitor-navigation-bar')
     implementation project(':logseq-capacitor-file-sync')
     implementation project(':logseq-capacitor-file-sync')
     implementation project(':capacitor-voice-recorder')
     implementation project(':capacitor-voice-recorder')
     implementation project(':send-intent')
     implementation project(':send-intent')

+ 7 - 5
android/app/src/main/AndroidManifest.xml

@@ -1,7 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.logseq.app">
-
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
     <application
     <application
         android:allowBackup="true"
         android:allowBackup="true"
         android:icon="@mipmap/ic_launcher"
         android:icon="@mipmap/ic_launcher"
@@ -37,7 +35,7 @@
                 <action android:name="android.intent.action.VIEW" />
                 <action android:name="android.intent.action.VIEW" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <category android:name="android.intent.category.BROWSABLE" />
                 <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="logseq" android:host="auth-callback" />
+                <data android:scheme="logseq" />
             </intent-filter>
             </intent-filter>
 
 
         </activity>
         </activity>
@@ -56,7 +54,11 @@
     <!-- Permissions -->
     <!-- Permissions -->
 
 
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
+    <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
+    <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.RECORD_AUDIO" />
 </manifest>
 </manifest>

+ 2 - 2
android/app/src/main/assets/capacitor.plugins.json

@@ -40,8 +40,8 @@
 		"classpath": "io.capawesome.capacitorjs.plugins.backgroundtask.BackgroundTaskPlugin"
 		"classpath": "io.capawesome.capacitorjs.plugins.backgroundtask.BackgroundTaskPlugin"
 	},
 	},
 	{
 	{
-		"pkg": "@hugotomazi/capacitor-navigation-bar",
-		"classpath": "br.com.tombus.capacitor.plugin.navigationbar.NavigationBarPlugin"
+		"pkg": "@capgo/capacitor-navigation-bar",
+		"classpath": "ee.forgr.capacitor_navigation_bar.NavigationBarPlugin"
 	},
 	},
 	{
 	{
 		"pkg": "@logseq/capacitor-file-sync",
 		"pkg": "@logseq/capacitor-file-sync",

+ 1 - 1
android/app/src/main/java/com/logseq/app/FolderPicker.java

@@ -43,7 +43,7 @@ public class FolderPicker extends Plugin {
             startActivityForResult(call, i, "folderPickerResult");
             startActivityForResult(call, i, "folderPickerResult");
         } else {
         } else {
             Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
             Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
-            Uri uri = Uri.fromParts("package", BuildConfig.APPLICATION_ID, null);
+            Uri uri = Uri.fromParts("package", this.getContext().getPackageName(), null);
             intent.setData(uri);
             intent.setData(uri);
             startActivityForResult(call, intent, 20);
             startActivityForResult(call, intent, 20);
         }
         }

+ 2 - 2
android/build.gradle

@@ -8,8 +8,8 @@ buildscript {
         mavenCentral()
         mavenCentral()
     }
     }
     dependencies {
     dependencies {
-        classpath 'com.android.tools.build:gradle:7.2.1'
-        classpath 'com.google.gms:google-services:4.3.13'
+        classpath 'com.android.tools.build:gradle:8.1.1'
+        classpath 'com.google.gms:google-services:4.3.15'
 
 
         // NOTE: Do not place your application dependencies here; they belong
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files
         // in the individual module build.gradle files

+ 2 - 2
android/capacitor.settings.gradle

@@ -32,8 +32,8 @@ project(':capacitor-status-bar').projectDir = new File('../node_modules/@capacit
 include ':capawesome-capacitor-background-task'
 include ':capawesome-capacitor-background-task'
 project(':capawesome-capacitor-background-task').projectDir = new File('../node_modules/@capawesome/capacitor-background-task/android')
 project(':capawesome-capacitor-background-task').projectDir = new File('../node_modules/@capawesome/capacitor-background-task/android')
 
 
-include ':hugotomazi-capacitor-navigation-bar'
-project(':hugotomazi-capacitor-navigation-bar').projectDir = new File('../node_modules/@hugotomazi/capacitor-navigation-bar/android')
+include ':capgo-capacitor-navigation-bar'
+project(':capgo-capacitor-navigation-bar').projectDir = new File('../node_modules/@capgo/capacitor-navigation-bar/android')
 
 
 include ':logseq-capacitor-file-sync'
 include ':logseq-capacitor-file-sync'
 project(':logseq-capacitor-file-sync').projectDir = new File('../node_modules/@logseq/capacitor-file-sync/android')
 project(':logseq-capacitor-file-sync').projectDir = new File('../node_modules/@logseq/capacitor-file-sync/android')

+ 1 - 2
android/gradle.properties

@@ -20,5 +20,4 @@ org.gradle.jvmargs=-Xmx4096m
 # Android operating system, and which are packaged with your app's APK
 # Android operating system, and which are packaged with your app's APK
 # https://developer.android.com/topic/libraries/support-library/androidx-rn
 # https://developer.android.com/topic/libraries/support-library/androidx-rn
 android.useAndroidX=true
 android.useAndroidX=true
-# Automatically convert third-party libraries to use AndroidX
-android.enableJetifier=true
+

+ 1 - 1
android/gradle/wrapper/gradle-wrapper.properties

@@ -1,5 +1,5 @@
 distributionBase=GRADLE_USER_HOME
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-all.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
 zipStorePath=wrapper/dists

+ 10 - 10
android/variables.gradle

@@ -1,16 +1,16 @@
 ext {
 ext {
     minSdkVersion = 22
     minSdkVersion = 22
-    compileSdkVersion = 32
-    targetSdkVersion = 32
-    androidxActivityVersion = '1.4.0'
-    androidxAppCompatVersion = '1.4.2'
+    compileSdkVersion = 33
+    targetSdkVersion = 33
+    androidxActivityVersion = '1.7.0'
+    androidxAppCompatVersion = '1.6.1'
     androidxCoordinatorLayoutVersion = '1.2.0'
     androidxCoordinatorLayoutVersion = '1.2.0'
-    androidxCoreVersion = '1.8.0'
-    androidxFragmentVersion = '1.4.1'
+    androidxCoreVersion = '1.10.0'
+    androidxFragmentVersion = '1.5.6'
     junitVersion = '4.13.2'
     junitVersion = '4.13.2'
-    androidxJunitVersion = '1.1.3'
-    androidxEspressoCoreVersion = '3.4.0'
+    androidxJunitVersion = '1.1.5'
+    androidxEspressoCoreVersion = '3.5.1'
     cordovaAndroidVersion = '10.1.1'
     cordovaAndroidVersion = '10.1.1'
-    coreSplashScreenVersion = '1.0.0-rc01'
-    androidxWebkitVersion = '1.4.0'
+    coreSplashScreenVersion = '1.0.0'
+    androidxWebkitVersion = '1.6.1'
 }
 }

+ 4 - 0
capacitor.config.ts

@@ -9,6 +9,10 @@ const config: CapacitorConfig = {
   bundledWebRuntime: false,
   bundledWebRuntime: false,
   webDir: 'public',
   webDir: 'public',
   loggingBehavior: 'debug',
   loggingBehavior: 'debug',
+  server: {
+    // https://capacitorjs.com/docs/updating/5-0#update-androidscheme
+    androidScheme: 'http',
+  },
   plugins: {
   plugins: {
     SplashScreen: {
     SplashScreen: {
       launchShowDuration: 500,
       launchShowDuration: 500,

+ 1 - 1
deps.edn

@@ -4,7 +4,7 @@
   rum/rum                               {:mvn/version "0.12.9"}
   rum/rum                               {:mvn/version "0.12.9"}
   datascript/datascript                 {:mvn/version "1.3.8"}
   datascript/datascript                 {:mvn/version "1.3.8"}
   datascript-transit/datascript-transit {:mvn/version "0.3.0"}
   datascript-transit/datascript-transit {:mvn/version "0.3.0"}
-  borkdude/rewrite-edn                  {:mvn/version "0.4.6"}
+  borkdude/rewrite-edn                  {:mvn/version "0.4.7"}
   funcool/promesa                       {:mvn/version "4.0.2"}
   funcool/promesa                       {:mvn/version "4.0.2"}
   medley/medley                         {:mvn/version "1.4.0"}
   medley/medley                         {:mvn/version "1.4.0"}
   metosin/reitit-frontend               {:mvn/version "0.3.10"}
   metosin/reitit-frontend               {:mvn/version "0.3.10"}

+ 1 - 1
deps/common/src/logseq/common/path.cljs

@@ -294,7 +294,7 @@
 ;; compat
 ;; compat
 (defn basename
 (defn basename
   [path]
   [path]
-  (let [path (string/replace path #"/$" "")]
+  (let [path (string/replace path #"/+$" "")]
     (filename path)))
     (filename path)))
 
 
 (defn dirname
 (defn dirname

+ 1 - 1
docs/Build LogSeq Desktop for windows on Ubuntu.md

@@ -1,7 +1,7 @@
 # Building Logseq Desktop app for Windows on Ubuntu
 # Building Logseq Desktop app for Windows on Ubuntu
 ## Intro
 ## Intro
 My Logseq dev machine is on Ubuntu 18.x and my production machine is running Windows 10, I needed a way to compile the Logseq desktop APP for Windows.
 My Logseq dev machine is on Ubuntu 18.x and my production machine is running Windows 10, I needed a way to compile the Logseq desktop APP for Windows.
-I tired & failed to make the "build" run on my windows machine but I did, however, succeed in letting my Ubuntu machine make Windows x64 files
+I tried & failed to make the "build" run on my windows machine but I did, however, succeed in letting my Ubuntu machine make Windows x64 files
 ## Pre-requisites
 ## Pre-requisites
 These are the steps I took to make it work on my Ubuntu machine, sharing them hoping it helps someone else. I assume you have all the basic pre-requisites for Logseq, if not you can find them at https://github.com/logseq/logseq#1-requirements
 These are the steps I took to make it work on my Ubuntu machine, sharing them hoping it helps someone else. I assume you have all the basic pre-requisites for Logseq, if not you can find them at https://github.com/logseq/logseq#1-requirements
 1. clone Logseq repo if you haven't already
 1. clone Logseq repo if you haven't already

+ 3 - 3
docs/accessibility.md

@@ -1,4 +1,4 @@
-- Accessibility is a vague term, which is why it is usually misunderstood. It is not just about people with with specific disabilities. You can read more about [what is accessibility](https://developer.mozilla.org/en-US/docs/Learn/Accessibility/What_is_accessibility#so_what_is_accessibility) and [diverse abilities and barriers](https://www.w3.org/WAI/people-use-web/abilities-barriers/).
+- Accessibility is a vague term, which is why it is usually misunderstood. It is not just about people with specific disabilities. You can read more about [what is accessibility](https://developer.mozilla.org/en-US/docs/Learn/Accessibility/What_is_accessibility#so_what_is_accessibility) and [diverse abilities and barriers](https://www.w3.org/WAI/people-use-web/abilities-barriers/).
 - ## Web Content Accessibility Guidelines
 - ## Web Content Accessibility Guidelines
 	- [WCAG](https://www.w3.org/WAI/standards-guidelines/wcag/) (Web Content Accessibility Guidelines) is the international standard for web content accessibility, developed by [W3C](https://www.w3.org/). Logseq is a web application, so conforming with WCAG should be our first priority. In general, there is no simple way to determine if a website is accessible or not, but WCAG can help us make the tool usable by as many people as possible.
 	- [WCAG](https://www.w3.org/WAI/standards-guidelines/wcag/) (Web Content Accessibility Guidelines) is the international standard for web content accessibility, developed by [W3C](https://www.w3.org/). Logseq is a web application, so conforming with WCAG should be our first priority. In general, there is no simple way to determine if a website is accessible or not, but WCAG can help us make the tool usable by as many people as possible.
 - ## Levels of conformance
 - ## Levels of conformance
@@ -6,7 +6,7 @@
 		- Level **A** is the minimum level.
 		- Level **A** is the minimum level.
 		- Level **AA** includes all Level A and AA requirements.
 		- Level **AA** includes all Level A and AA requirements.
 		- Level **AAA** includes all Level A, AA, and AAA requirements.
 		- Level **AAA** includes all Level A, AA, and AAA requirements.
-	- Many organizations strive to meet Level AA. The reason behind this decision, is that in some cases AAA standard is too strict. That does't mean that triple-A issues should be disregarded. On the contrary, all of them should be handled if possible.
+	- Many organizations strive to meet Level AA. The reason behind this decision, is that in some cases AAA standard is too strict. That doesn't mean that triple-A issues should be disregarded. On the contrary, all of them should be handled if possible.
 	- We can also provide alternative options in order to conform with AAA standards. For instance, our default themes can aim for AA, but we can provide a high-contrast theme that aims for AAA. Providing [alternative versions](https://www.w3.org/WAI/GL/2007/05/alternate-versions.html) with different levels of conformance is permitted according to WCAG, if there is an accessible way to reach those alternatives.
 	- We can also provide alternative options in order to conform with AAA standards. For instance, our default themes can aim for AA, but we can provide a high-contrast theme that aims for AAA. Providing [alternative versions](https://www.w3.org/WAI/GL/2007/05/alternate-versions.html) with different levels of conformance is permitted according to WCAG, if there is an accessible way to reach those alternatives.
 - ## Simple development guidelines
 - ## Simple development guidelines
 	- Use semantically correct markup whenever possible. Every time you are about to decide which html tag you are going to use, choose the one that behaves the way you want it. For example, let's say you want to create an element that looks like plain text, but triggers an action on click. Usually, the best approach would be to create a `<button>` and make it look like a `<span>` using css. If you use a `span`, you will also have to override other html attributes like `tabindex` and `role` to make the element behave like a button. This is almost always a bad sign, and should be avoided. If you use the appropriate html element, the browser will be able to properly handle it.
 	- Use semantically correct markup whenever possible. Every time you are about to decide which html tag you are going to use, choose the one that behaves the way you want it. For example, let's say you want to create an element that looks like plain text, but triggers an action on click. Usually, the best approach would be to create a `<button>` and make it look like a `<span>` using css. If you use a `span`, you will also have to override other html attributes like `tabindex` and `role` to make the element behave like a button. This is almost always a bad sign, and should be avoided. If you use the appropriate html element, the browser will be able to properly handle it.
@@ -18,7 +18,7 @@
 	- There is a [huge list of tools](https://www.w3.org/WAI/ER/tools/) that can help us test our application. Most of them use [axe-core](https://github.com/dequelabs/axe-core) internally. There are [browser extensions](https://www.deque.com/axe/browser-extensions/) based on axe, a [VSCode Linter Plugin](https://marketplace.visualstudio.com/items?itemName=deque-systems.vscode-axe-linter) and also [multiple community projects](https://github.com/dequelabs/axe-core/blob/develop/doc/projects.md#community-projects).
 	- There is a [huge list of tools](https://www.w3.org/WAI/ER/tools/) that can help us test our application. Most of them use [axe-core](https://github.com/dequelabs/axe-core) internally. There are [browser extensions](https://www.deque.com/axe/browser-extensions/) based on axe, a [VSCode Linter Plugin](https://marketplace.visualstudio.com/items?itemName=deque-systems.vscode-axe-linter) and also [multiple community projects](https://github.com/dequelabs/axe-core/blob/develop/doc/projects.md#community-projects).
 	- Basic accessibility testing could be integrated into our CI, by using the appropriate axe package (e.g. [@axe-core/playwright](https://github.com/dequelabs/axe-core-npm/blob/develop/packages/playwright/README.md))
 	- Basic accessibility testing could be integrated into our CI, by using the appropriate axe package (e.g. [@axe-core/playwright](https://github.com/dequelabs/axe-core-npm/blob/develop/packages/playwright/README.md))
 - ## Manual testing
 - ## Manual testing
-	- In theory, all of the cases described by WCAG should be testable. In practice, there are issues that can't be replicated by code. Also, manual accessibility testing would help us have a better understanding of the difficulties that certain people might encounter. Even if the all the individual cases pass the tests, the overall navigation might be nonsensical.
+	- In theory, all of the cases described by WCAG should be testable. In practice, there are issues that can't be replicated by code. Also, manual accessibility testing would help us have a better understanding of the difficulties that certain people might encounter. Even if all the individual cases pass the tests, the overall navigation might be nonsensical.
 	- ### Manual accessibility testing musts
 	- ### Manual accessibility testing musts
 		- Keyboard-only navigation
 		- Keyboard-only navigation
 		- Screen reader testing and compatibility
 		- Screen reader testing and compatibility

+ 3 - 3
docs/contributing-to-translations.md

@@ -14,7 +14,7 @@ In order to run the commands in this doc, you will need to install
 ## Where to Contribute
 ## Where to Contribute
 
 
 Language translations are under,
 Language translations are under,
-[src/resources/dicts/](https://github.com/logseq/logseq/blob/master/src/resources/dicts/) with each language having it's own file. For example, the es locale is in `es.edn`.
+[src/resources/dicts/](https://github.com/logseq/logseq/blob/master/src/resources/dicts/) with each language having its own file. For example, the es locale is in `es.edn`.
 
 
 ## Language Overview
 ## Language Overview
 
 
@@ -83,7 +83,7 @@ Almost all translations are small. The only exceptions to this are the keys `:tu
 ### Editing Tips
 ### Editing Tips
 
 
 * Some translations may include punctuation like `:` or `!`. When translating them, please use the punctuation that makes the most sense for your language as you don't have to follow the English ones.
 * Some translations may include punctuation like `:` or `!`. When translating them, please use the punctuation that makes the most sense for your language as you don't have to follow the English ones.
-* Some translations may include arguments/interpolations e.g. `{1}`. If you see them in a translation, be sure to include them. These arguments are substituted in the string and are usually used something the app needs to calculate e.g. a number. See [these docs](https://github.com/tonsky/tongue#interpolation) for more examples.
+* Some translations may include arguments/interpolations e.g. `{1}`. If you see them in a translation, be sure to include them. These arguments are substituted in the string and are usually used for something the app needs to calculate e.g. a number. See [these docs](https://github.com/tonsky/tongue#interpolation) for more examples.
 * Rarely, a translation may need to translate formatted text by returning [hiccup-style HTML](https://github.com/weavejester/hiccup#syntax). In this case, a Clojure function is the recommended approach. For example, a function translation would look like `(fn [] [:div "FOO"])`. See `:on-boarding/main-title` for an example.
 * Rarely, a translation may need to translate formatted text by returning [hiccup-style HTML](https://github.com/weavejester/hiccup#syntax). In this case, a Clojure function is the recommended approach. For example, a function translation would look like `(fn [] [:div "FOO"])`. See `:on-boarding/main-title` for an example.
 ## Fix Mistakes
 ## Fix Mistakes
 
 
@@ -105,4 +105,4 @@ To add a new language:
 * Add an entry to `frontend.dicts/languages`
 * Add an entry to `frontend.dicts/languages`
 * Create a new file under `src/resources/dicts/` and name the file the same as the locale e.g. zz.edn for a hypothetical zz locale.
 * Create a new file under `src/resources/dicts/` and name the file the same as the locale e.g. zz.edn for a hypothetical zz locale.
 * Add an entry in `frontend.dicts/dicts` referencing the file you created.
 * Add an entry in `frontend.dicts/dicts` referencing the file you created.
-* Then start translating for your language and adding entries in your language's EDN file using the `bb lang:missing` workflow.
+* Then start translating for your language and adding entries in your language's EDN file using the `bb lang:missing` workflow.

+ 1 - 0
e2e-tests/accessibility.spec.ts

@@ -8,6 +8,7 @@ test('should not have any automatically detectable accessibility issues', async
     await page.waitForTimeout(2000)
     await page.waitForTimeout(2000)
     const accessibilityScanResults = await new AxeBuilder({ page })
     const accessibilityScanResults = await new AxeBuilder({ page })
         .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
         .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
+        .disableRules(['meta-viewport'])
         .setLegacyMode()
         .setLegacyMode()
         .analyze()
         .analyze()
 
 

+ 13 - 0
externs.js

@@ -8,6 +8,8 @@ fs.unlink = function() {};
 fs.readdir = function() {};
 fs.readdir = function() {};
 fs.rmdir = function() {};
 fs.rmdir = function() {};
 fs.rimraf = function() {};
 fs.rimraf = function() {};
+fs.lstat = function () {};
+
 var dummy = {};
 var dummy = {};
 dummy.populateStat = function() {};
 dummy.populateStat = function() {};
 dummy.populateHash = function() {};
 dummy.populateHash = function() {};
@@ -141,6 +143,17 @@ dummy.ELEMENT = function() {};
 dummy.TEXT = function() {};
 dummy.TEXT = function() {};
 dummy.isAbsolute = function() {};
 dummy.isAbsolute = function() {};
 
 
+var utils = {}
+utils.withFileTypes = true;
+utils.accessTime = 0;
+utils.modifiedTime = 0;
+utils.changeTime = 0;
+utils.birthTime = 0;
+utils.atimeMs = 0;
+utils.mtimeMs = 0;
+utils.ctimeMs = 0;
+utils.birthtimeMs = 0;
+
 /**
 /**
  * @typedef {{
  * @typedef {{
  *     recursive: (undefined | boolean),
  *     recursive: (undefined | boolean),

+ 1 - 1
ios/.gitignore

@@ -1,9 +1,9 @@
 App/build
 App/build
 App/Pods
 App/Pods
-App/Podfile.lock
 App/App/public
 App/App/public
 DerivedData
 DerivedData
 xcuserdata
 xcuserdata
 
 
 # Cordova plugins for Capacitor
 # Cordova plugins for Capacitor
 capacitor-cordova-ios-plugins
 capacitor-cordova-ios-plugins
+

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

@@ -519,7 +519,7 @@
 				INFOPLIST_FILE = App/Info.plist;
 				INFOPLIST_FILE = App/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
-				MARKETING_VERSION = 0.9.16;
+				MARKETING_VERSION = 0.9.20;
 				OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
 				OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PRODUCT_NAME = "$(TARGET_NAME)";
@@ -546,7 +546,7 @@
 				INFOPLIST_FILE = App/Info.plist;
 				INFOPLIST_FILE = App/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
-				MARKETING_VERSION = 0.9.16;
+				MARKETING_VERSION = 0.9.20;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
 				SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
@@ -571,7 +571,7 @@
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
-				MARKETING_VERSION = 0.9.16;
+				MARKETING_VERSION = 0.9.20;
 				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
 				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
 				MTL_FAST_MATH = YES;
 				MTL_FAST_MATH = YES;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq.ShareViewController;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq.ShareViewController;
@@ -598,7 +598,7 @@
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
-				MARKETING_VERSION = 0.9.16;
+				MARKETING_VERSION = 0.9.20;
 				MTL_FAST_MATH = YES;
 				MTL_FAST_MATH = YES;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq.ShareViewController;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq.ShareViewController;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PRODUCT_NAME = "$(TARGET_NAME)";

BIN
ios/App/App/Assets.xcassets/AppIcon.appiconset/[email protected]


BIN
ios/App/App/Assets.xcassets/AppIcon.appiconset/[email protected]


BIN
ios/App/App/Assets.xcassets/AppIcon.appiconset/[email protected]


BIN
ios/App/App/Assets.xcassets/AppIcon.appiconset/[email protected]


BIN
ios/App/App/Assets.xcassets/AppIcon.appiconset/[email protected]


BIN
ios/App/App/Assets.xcassets/AppIcon.appiconset/[email protected]


BIN
ios/App/App/Assets.xcassets/AppIcon.appiconset/[email protected]


BIN
ios/App/App/Assets.xcassets/AppIcon.appiconset/[email protected]


BIN
ios/App/App/Assets.xcassets/AppIcon.appiconset/[email protected]


BIN
ios/App/App/Assets.xcassets/AppIcon.appiconset/[email protected]


BIN
ios/App/App/Assets.xcassets/AppIcon.appiconset/[email protected]


BIN
ios/App/App/Assets.xcassets/AppIcon.appiconset/[email protected]


BIN
ios/App/App/Assets.xcassets/AppIcon.appiconset/[email protected]


BIN
ios/App/App/Assets.xcassets/AppIcon.appiconset/[email protected]


BIN
ios/App/App/Assets.xcassets/AppIcon.appiconset/[email protected]


BIN
ios/App/App/Assets.xcassets/AppIcon.appiconset/[email protected]


BIN
ios/App/App/Assets.xcassets/AppIcon.appiconset/[email protected]


+ 11 - 113
ios/App/App/Assets.xcassets/AppIcon.appiconset/Contents.json

@@ -1,116 +1,14 @@
 {
 {
-  "images" : [
-    {
-      "size" : "20x20",
-      "idiom" : "iphone",
-      "filename" : "[email protected]",
-      "scale" : "2x"
-    },
-    {
-      "size" : "20x20",
-      "idiom" : "iphone",
-      "filename" : "[email protected]",
-      "scale" : "3x"
-    },
-    {
-      "size" : "29x29",
-      "idiom" : "iphone",
-      "filename" : "[email protected]",
-      "scale" : "2x"
-    },
-    {
-      "size" : "29x29",
-      "idiom" : "iphone",
-      "filename" : "[email protected]",
-      "scale" : "3x"
-    },
-    {
-      "size" : "40x40",
-      "idiom" : "iphone",
-      "filename" : "[email protected]",
-      "scale" : "2x"
-    },
-    {
-      "size" : "40x40",
-      "idiom" : "iphone",
-      "filename" : "[email protected]",
-      "scale" : "3x"
-    },
-    {
-      "size" : "60x60",
-      "idiom" : "iphone",
-      "filename" : "[email protected]",
-      "scale" : "2x"
-    },
-    {
-      "size" : "60x60",
-      "idiom" : "iphone",
-      "filename" : "[email protected]",
-      "scale" : "3x"
-    },
-    {
-      "size" : "20x20",
-      "idiom" : "ipad",
-      "filename" : "[email protected]",
-      "scale" : "1x"
-    },
-    {
-      "size" : "20x20",
-      "idiom" : "ipad",
-      "filename" : "[email protected]",
-      "scale" : "2x"
-    },
-    {
-      "size" : "29x29",
-      "idiom" : "ipad",
-      "filename" : "[email protected]",
-      "scale" : "1x"
-    },
-    {
-      "size" : "29x29",
-      "idiom" : "ipad",
-      "filename" : "[email protected]",
-      "scale" : "2x"
-    },
-    {
-      "size" : "40x40",
-      "idiom" : "ipad",
-      "filename" : "[email protected]",
-      "scale" : "1x"
-    },
-    {
-      "size" : "40x40",
-      "idiom" : "ipad",
-      "filename" : "[email protected]",
-      "scale" : "2x"
-    },
-    {
-      "size" : "76x76",
-      "idiom" : "ipad",
-      "filename" : "[email protected]",
-      "scale" : "1x"
-    },
-    {
-      "size" : "76x76",
-      "idiom" : "ipad",
-      "filename" : "[email protected]",
-      "scale" : "2x"
-    },
-    {
-      "size" : "83.5x83.5",
-      "idiom" : "ipad",
-      "filename" : "[email protected]",
-      "scale" : "2x"
-    },
-    {
-      "size" : "1024x1024",
-      "idiom" : "ios-marketing",
-      "filename" : "[email protected]",
-      "scale" : "1x"
+    "images" : [
+      {
+        "filename" : "[email protected]",
+        "idiom" : "universal",
+        "platform" : "ios",
+        "size" : "1024x1024"
+      }
+    ],
+    "info" : {
+      "author" : "xcode",
+      "version" : 1
     }
     }
-  ],
-  "info" : {
-    "version" : 1,
-    "author" : "xcode"
-  }
 }
 }

+ 1 - 1
ios/App/Podfile

@@ -21,7 +21,7 @@ def capacitor_pods
   pod 'CapacitorSplashScreen', :path => '../../node_modules/@capacitor/splash-screen'
   pod 'CapacitorSplashScreen', :path => '../../node_modules/@capacitor/splash-screen'
   pod 'CapacitorStatusBar', :path => '../../node_modules/@capacitor/status-bar'
   pod 'CapacitorStatusBar', :path => '../../node_modules/@capacitor/status-bar'
   pod 'CapawesomeCapacitorBackgroundTask', :path => '../../node_modules/@capawesome/capacitor-background-task'
   pod 'CapawesomeCapacitorBackgroundTask', :path => '../../node_modules/@capawesome/capacitor-background-task'
-  pod 'HugotomaziCapacitorNavigationBar', :path => '../../node_modules/@hugotomazi/capacitor-navigation-bar'
+  pod 'CapgoCapacitorNavigationBar', :path => '../../node_modules/@capgo/capacitor-navigation-bar'
   pod 'LogseqCapacitorFileSync', :path => '../../node_modules/@logseq/capacitor-file-sync'
   pod 'LogseqCapacitorFileSync', :path => '../../node_modules/@logseq/capacitor-file-sync'
   pod 'CapacitorVoiceRecorder', :path => '../../node_modules/capacitor-voice-recorder'
   pod 'CapacitorVoiceRecorder', :path => '../../node_modules/capacitor-voice-recorder'
   pod 'SendIntent', :path => '../../node_modules/send-intent'
   pod 'SendIntent', :path => '../../node_modules/send-intent'

+ 2 - 1
libs/src/LSPlugin.ts

@@ -192,6 +192,7 @@ export interface BlockEntity {
   level?: number
   level?: number
   meta?: { timestamps: any; properties: any; startPos: number; endPos: number }
   meta?: { timestamps: any; properties: any; startPos: number; endPos: number }
   title?: Array<any>
   title?: Array<any>
+	marker?: string
 }
 }
 
 
 /**
 /**
@@ -811,7 +812,7 @@ export interface IEditorProxy extends Record<string, any> {
     opts?: { replaceState: boolean }
     opts?: { replaceState: boolean }
   ) => void
   ) => void
 
 
-  openInRightSidebar: (uuid: BlockUUID) => void
+  openInRightSidebar: (id: BlockUUID | EntityID) => void
 
 
   /**
   /**
    * @example https://github.com/logseq/logseq-plugin-samples/tree/master/logseq-a-translator
    * @example https://github.com/logseq/logseq-plugin-samples/tree/master/logseq-a-translator

+ 27 - 19
package.json

@@ -5,7 +5,7 @@
     "main": "static/electron.js",
     "main": "static/electron.js",
     "devDependencies": {
     "devDependencies": {
         "@axe-core/playwright": "=4.4.4",
         "@axe-core/playwright": "=4.4.4",
-        "@capacitor/cli": "^4.0.0",
+        "@capacitor/cli": "^5.0.0",
         "@playwright/test": "=1.31.0",
         "@playwright/test": "=1.31.0",
         "@tailwindcss/aspect-ratio": "0.4.2",
         "@tailwindcss/aspect-ratio": "0.4.2",
         "@tailwindcss/forms": "0.5.3",
         "@tailwindcss/forms": "0.5.3",
@@ -77,24 +77,24 @@
         "postinstall": "yarn tldraw:build && yarn amplify:build "
         "postinstall": "yarn tldraw:build && yarn amplify:build "
     },
     },
     "dependencies": {
     "dependencies": {
-        "@capacitor/android": "^4.0.0",
-        "@capacitor/app": "^4.0.0",
-        "@capacitor/camera": "^4.0.0",
-        "@capacitor/clipboard": "^4.0.0",
-        "@capacitor/core": "^4.0.0",
-        "@capacitor/filesystem": "^4.0.0",
-        "@capacitor/haptics": "^4.0.0",
-        "@capacitor/ios": "^4.0.0",
-        "@capacitor/keyboard": "^4.0.0",
-        "@capacitor/share": "^4.0.0",
-        "@capacitor/splash-screen": "^4.0.0",
-        "@capacitor/status-bar": "^4.0.0",
-        "@capawesome/capacitor-background-task": "^2.0.0",
+        "@capacitor/android": "^5.0.0",
+        "@capacitor/app": "^5.0.0",
+        "@capacitor/camera": "^5.0.0",
+        "@capacitor/clipboard": "^5.0.0",
+        "@capacitor/core": "^5.0.0",
+        "@capacitor/filesystem": "^5.0.0",
+        "@capacitor/haptics": "^5.0.0",
+        "@capacitor/ios": "^5.0.0",
+        "@capacitor/keyboard": "^5.0.0",
+        "@capacitor/share": "^5.0.0",
+        "@capacitor/splash-screen": "^5.0.0",
+        "@capacitor/status-bar": "^5.0.0",
+        "@capawesome/capacitor-background-task": "^5.0.0",
         "@excalidraw/excalidraw": "0.15.3",
         "@excalidraw/excalidraw": "0.15.3",
         "@highlightjs/cdn-assets": "10.4.1",
         "@highlightjs/cdn-assets": "10.4.1",
-        "@hugotomazi/capacitor-navigation-bar": "^2.0.0",
+        "@capgo/capacitor-navigation-bar": "^6.0.0",
         "@isomorphic-git/lightning-fs": "^4.6.0",
         "@isomorphic-git/lightning-fs": "^4.6.0",
-        "@logseq/capacitor-file-sync": "0.0.35",
+        "@logseq/capacitor-file-sync": "5.0.0",
         "@logseq/diff-merge": "0.2.2",
         "@logseq/diff-merge": "0.2.2",
         "@logseq/react-tweet-embed": "1.3.1-1",
         "@logseq/react-tweet-embed": "1.3.1-1",
         "@radix-ui/colors": "^0.1.8",
         "@radix-ui/colors": "^0.1.8",
@@ -103,11 +103,11 @@
         "@tabler/icons": "^1.96.0",
         "@tabler/icons": "^1.96.0",
         "@tippyjs/react": "4.2.5",
         "@tippyjs/react": "4.2.5",
         "bignumber.js": "^9.0.2",
         "bignumber.js": "^9.0.2",
-        "capacitor-voice-recorder": "4.0.0",
+        "capacitor-voice-recorder": "^5.0.0",
         "check-password-strength": "2.0.7",
         "check-password-strength": "2.0.7",
         "chokidar": "3.5.1",
         "chokidar": "3.5.1",
         "chrono-node": "2.2.4",
         "chrono-node": "2.2.4",
-        "codemirror": "5.58.1",
+        "codemirror": "5.65.13",
         "d3-force": "3.0.0",
         "d3-force": "3.0.0",
         "diff": "5.0.0",
         "diff": "5.0.0",
         "dompurify": "2.4.0",
         "dompurify": "2.4.0",
@@ -144,7 +144,7 @@
         "remove-accents": "0.4.2",
         "remove-accents": "0.4.2",
         "reveal.js": "^4.5.0",
         "reveal.js": "^4.5.0",
         "sanitize-filename": "1.6.3",
         "sanitize-filename": "1.6.3",
-        "send-intent": "3.0.11",
+        "send-intent": "^5.0.0",
         "shepherd.js": "^9.1.0",
         "shepherd.js": "^9.1.0",
         "tailwind-capitalize-first-letter": "^1.0.4",
         "tailwind-capitalize-first-letter": "^1.0.4",
         "threads": "1.6.5",
         "threads": "1.6.5",
@@ -152,6 +152,14 @@
         "yargs-parser": "20.2.4"
         "yargs-parser": "20.2.4"
     },
     },
     "resolutions": {
     "resolutions": {
+        "**/postcss": "8.4.17",
+        "**/postcss-colormin": "5.3.0",
+        "**/postcss-convert-values": "5.1.2",
+        "**/postcss-js": "4.0.0",
+        "**/postcss-merge-longhand": "5.1.6",
+        "**/postcss-merge-rules": "5.1.2",
+        "**/postcss-minify-params": "5.1.3",
+        "**/postcss-reduce-initial": "5.1.0",
         "pixi-graph-fork/@pixi/app": "6.2.0",
         "pixi-graph-fork/@pixi/app": "6.2.0",
         "pixi-graph-fork/@pixi/constants": "6.2.0",
         "pixi-graph-fork/@pixi/constants": "6.2.0",
         "pixi-graph-fork/@pixi/core": "6.2.0",
         "pixi-graph-fork/@pixi/core": "6.2.0",

+ 1 - 1
resources/forge.config.js

@@ -4,7 +4,7 @@ module.exports = {
   packagerConfig: {
   packagerConfig: {
     name: 'Logseq',
     name: 'Logseq',
     icon: './icons/logseq_big_sur.icns',
     icon: './icons/logseq_big_sur.icns',
-    buildVersion: 68,
+    buildVersion: 72,
     protocols: [
     protocols: [
       {
       {
         "protocol": "logseq",
         "protocol": "logseq",

+ 27 - 5
resources/js/preload.js

@@ -18,6 +18,20 @@ function getFilePathFromClipboard () {
   }
   }
 }
 }
 
 
+/**
+ * Read the contents of the clipboard for a custom format.
+ * @param  {string} format The custom format to read.
+ * @returns Buffer containing the contents of the clipboard for the specified format, or null if not available.
+ */
+function getClipboardData (format) {
+  if (clipboard.has(format, "clipboard")) {
+    return clipboard.readBuffer(format)
+  }
+  else {
+    return null;
+  }
+}
+
 contextBridge.exposeInMainWorld('apis', {
 contextBridge.exposeInMainWorld('apis', {
   doAction: async (arg) => {
   doAction: async (arg) => {
     return await ipcRenderer.invoke('main', arg)
     return await ipcRenderer.invoke('main', arg)
@@ -116,15 +130,21 @@ contextBridge.exposeInMainWorld('apis', {
 
 
     const dest = path.join(repoPathRoot, to)
     const dest = path.join(repoPathRoot, to)
     const assetsRoot = path.dirname(dest)
     const assetsRoot = path.dirname(dest)
-    
+
     await fs.promises.mkdir(assetsRoot, { recursive: true })
     await fs.promises.mkdir(assetsRoot, { recursive: true })
 
 
-    from = from && decodeURIComponent(from || getFilePathFromClipboard())
+    from = from || getFilePathFromClipboard()
 
 
     if (from) {
     if (from) {
-      // console.debug('copy file: ', from, dest)
-      await fs.promises.copyFile(from, dest)
-      return path.basename(from)
+      try {
+        // console.debug('copy file: ', from, dest)
+        await fs.promises.copyFile(from, dest)
+        return path.basename(from)
+      } catch (e) {
+        from = decodeURIComponent(from)
+        await fs.promises.copyFile(from, dest)
+        return path.basename(from)
+      }
     }
     }
 
 
     // support image
     // support image
@@ -166,6 +186,8 @@ contextBridge.exposeInMainWorld('apis', {
 
 
   getFilePathFromClipboard,
   getFilePathFromClipboard,
 
 
+  getClipboardData,
+
   setZoomFactor (factor) {
   setZoomFactor (factor) {
     webFrame.setZoomFactor(factor)
     webFrame.setZoomFactor(factor)
   },
   },

+ 3 - 2
resources/package.json

@@ -1,7 +1,7 @@
 {
 {
   "name": "Logseq",
   "name": "Logseq",
   "productName": "Logseq",
   "productName": "Logseq",
-  "version": "0.9.15",
+  "version": "0.9.19",
   "main": "electron.js",
   "main": "electron.js",
   "author": "Logseq",
   "author": "Logseq",
   "license": "AGPL-3.0",
   "license": "AGPL-3.0",
@@ -34,7 +34,8 @@
     "update-electron-app": "2.0.1",
     "update-electron-app": "2.0.1",
     "extract-zip": "2.0.1",
     "extract-zip": "2.0.1",
     "diff-match-patch": "1.0.5",
     "diff-match-patch": "1.0.5",
-    "https-proxy-agent": "5.0.0",
+    "https-proxy-agent": "7.0.2",
+    "socks-proxy-agent": "8.0.2",
     "@sentry/electron": "2.5.1",
     "@sentry/electron": "2.5.1",
     "posthog-js": "1.10.2",
     "posthog-js": "1.10.2",
     "@logseq/rsapi": "0.0.73",
     "@logseq/rsapi": "0.0.73",

+ 1 - 1
scripts/get-pkg-version.js

@@ -21,7 +21,7 @@ if (match) {
 if (process.argv[2] === 'nightly' || process.argv[2] === '') {
 if (process.argv[2] === 'nightly' || process.argv[2] === '') {
   const today = new Date()
   const today = new Date()
   console.log(
   console.log(
-    ver + '-nightly.' + today.toISOString().split('T')[0].replaceAll('-', '')
+    ver + '-alpha+nightly.' + today.toISOString().split('T')[0].replaceAll('-', '')
   )
   )
 } else {
 } else {
   console.log(ver)
   console.log(ver)

+ 2 - 1
scripts/src/logseq/tasks/lang.clj

@@ -176,7 +176,8 @@
             :search-item/whiteboard :settings-page/enable-flashcards :settings-page/enable-whiteboards
             :search-item/whiteboard :settings-page/enable-flashcards :settings-page/enable-whiteboards
             :settings-page/tab-editor :shortcut.category/whiteboard :whiteboard/medium
             :settings-page/tab-editor :shortcut.category/whiteboard :whiteboard/medium
             :whiteboard/twitter-url :whiteboard/youtube-url :right-side-bar/history-global}
             :whiteboard/twitter-url :whiteboard/youtube-url :right-side-bar/history-global}
-   :tr #{:help/awesome-logseq}})
+   :tr #{:help/awesome-logseq}
+   :id #{:host :port :on-boarding/section-app :right-side-bar/history-global}})
 
 
 (defn- validate-languages-dont-have-duplicates
 (defn- validate-languages-dont-have-duplicates
   "Looks up duplicates for all languages"
   "Looks up duplicates for all languages"

+ 7 - 7
src/electron/electron/configs.cljs

@@ -1,9 +1,9 @@
 (ns electron.configs
 (ns electron.configs
-  (:require
-    ["fs-extra" :as ^js fs]
-    ["path" :as ^js node-path]
-    ["electron" :refer [^js app] :as electron]
-    [cljs.reader :as reader]))
+  (:require ["electron" :refer [^js app] :as electron]
+            ["fs-extra" :as ^js fs]
+            ["path" :as ^js node-path]
+            [cljs.reader :as reader]
+            [electron.logger :as logger]))
 
 
 ;; FIXME: move configs.edn to where it should be
 ;; FIXME: move configs.edn to where it should be
 (defonce dot-root (.join node-path (.getPath app "home") ".logseq"))
 (defonce dot-root (.join node-path (.getPath app "home") ".logseq"))
@@ -17,14 +17,14 @@
     (let [body (.toString (.readFileSync fs cfg-path))]
     (let [body (.toString (.readFileSync fs cfg-path))]
       (if (seq body) (reader/read-string body) {}))
       (if (seq body) (reader/read-string body) {}))
     (catch :default e
     (catch :default e
-      (js/console.error :cfg-error e))))
+      (logger/error :cfg-error e))))
 
 
 (defn- write-cfg!
 (defn- write-cfg!
   [cfg]
   [cfg]
   (try
   (try
     (.writeFileSync fs cfg-path (pr-str cfg)) cfg
     (.writeFileSync fs cfg-path (pr-str cfg)) cfg
     (catch :default e
     (catch :default e
-      (js/console.error :cfg-error e))))
+      (logger/error :cfg-error e))))
 
 
 (defn set-item!
 (defn set-item!
   [k v]
   [k v]

+ 32 - 34
src/electron/electron/core.cljs

@@ -16,7 +16,6 @@
             ["os" :as os]
             ["os" :as os]
             ["electron" :refer [BrowserWindow Menu app protocol ipcMain dialog shell] :as electron]
             ["electron" :refer [BrowserWindow Menu app protocol ipcMain dialog shell] :as electron]
             ["electron-deeplink" :refer [Deeplink]]
             ["electron-deeplink" :refer [Deeplink]]
-            [clojure.core.async :as async]
             [electron.state :as state]
             [electron.state :as state]
             [electron.git :as git]
             [electron.git :as git]
             [electron.window :as win]
             [electron.window :as win]
@@ -207,6 +206,10 @@
                                     {:role "about"
                                     {:role "about"
                                      :label "About Logseq"
                                      :label "About Logseq"
                                      :click about-fn}]}))
                                      :click about-fn}]}))
+        ;; Enable Cmd/Ctrl+= Zoom In
+        template (conj template
+                       {:role "zoomin"
+                        :accelerator "CommandOrControl+="})
         menu (.buildFromTemplate Menu (clj->js template))]
         menu (.buildFromTemplate Menu (clj->js template))]
     (.setApplicationMenu Menu menu)))
     (.setApplicationMenu Menu menu)))
 
 
@@ -235,15 +238,15 @@
                       :bypassCSP       true
                       :bypassCSP       true
                       :supportFetchAPI true}]
                       :supportFetchAPI true}]
       (.registerSchemesAsPrivileged
       (.registerSchemesAsPrivileged
-        protocol (bean/->js [{:scheme     LSP_SCHEME
-                              :privileges privileges}
-                             {:scheme     FILE_LSP_SCHEME
-                              :privileges privileges}
-                             {:scheme     FILE_ASSETS_SCHEME
-                              :privileges {:standard        false
-                                           :secure          false
-                                           :bypassCSP       false
-                                           :supportFetchAPI false}}]))
+       protocol (bean/->js [{:scheme     LSP_SCHEME
+                             :privileges privileges}
+                            {:scheme     FILE_LSP_SCHEME
+                             :privileges privileges}
+                            {:scheme     FILE_ASSETS_SCHEME
+                             :privileges {:standard        false
+                                          :secure          false
+                                          :bypassCSP       false
+                                          :supportFetchAPI false}}]))
 
 
       (set-app-menu!)
       (set-app-menu!)
       (setup-deeplink!)
       (setup-deeplink!)
@@ -294,33 +297,28 @@
                (@*setup-fn)
                (@*setup-fn)
 
 
                ;; main window events
                ;; main window events
-               ;; TODO merge with window/on-close-actions!
-               ;; TODO elimilate the difference between main and non-main windows
                (.on win "close" (fn [e]
                (.on win "close" (fn [e]
                                   (when @*quit-dirty? ;; when not updating
                                   (when @*quit-dirty? ;; when not updating
                                     (.preventDefault e)
                                     (.preventDefault e)
-                                    (let [web-contents (. win -webContents)]
-                                      (.send web-contents "persist-zoom-level" (.getZoomLevel web-contents))
-                                      (.send web-contents "persistent-dbs"))
-                                    (async/go
-                                      (let [_ (async/<! state/persistent-dbs-chan)]
-                                        (if (or @win/*quitting? (not mac?))
-                                          ;; MacOS: only cmd+q quitting will trigger actual closing
-                                          ;; otherwise, it's just hiding - don't do any actual closing in that case
-                                          ;; except saving transit
-                                          (when-let [win @*win]
-                                            (when-let [dir (state/get-window-graph-path win)]
-                                              (handler/close-watcher-when-orphaned! win dir))
-                                            (state/close-window! win)
-                                            (win/destroy-window! win)
-                                            ;; FIXME: what happens when closing main window on Windows?
-                                            (reset! *win nil))
-                                          ;; Just hiding - don't do any actual closing operation
-                                          (do (.preventDefault ^js/Event e)
-                                              (if (and mac? (.isFullScreen win))
-                                                (do (.once win "leave-full-screen" #(.hide win))
-                                                    (.setFullScreen win false))
-                                                (.hide win)))))))))
+
+                                    (let [windows (win/get-all-windows)
+                                          window @*win
+                                          multiple-windows? (> (count windows) 1)]
+                                      (cond
+                                        (or multiple-windows? (not mac?) @win/*quitting?)
+                                        (when window
+                                          (win/close-handler win handler/close-watcher-when-orphaned! e)
+                                          (reset! *win nil))
+
+                                        (and mac? (not multiple-windows?))
+                                        ;; Just hiding - don't do any actual closing operation
+                                        (do (.preventDefault ^js/Event e)
+                                            (if (and mac? (.isFullScreen win))
+                                              (do (.once win "leave-full-screen" #(.hide win))
+                                                  (.setFullScreen win false))
+                                              (.hide win)))
+                                        :else
+                                        nil)))))
                (.on app "before-quit" (fn [_e]
                (.on app "before-quit" (fn [_e]
                                         (reset! win/*quitting? true)))
                                         (reset! win/*quitting? true)))
 
 

+ 13 - 2
src/electron/electron/handler.cljs

@@ -6,6 +6,7 @@
             ["buffer" :as buffer]
             ["buffer" :as buffer]
             ["diff-match-patch" :as google-diff]
             ["diff-match-patch" :as google-diff]
             ["electron" :refer [app autoUpdater dialog ipcMain shell]]
             ["electron" :refer [app autoUpdater dialog ipcMain shell]]
+            ["electron-window-state" :as windowStateKeeper]
             ["fs" :as fs]
             ["fs" :as fs]
             ["fs-extra" :as fs-extra]
             ["fs-extra" :as fs-extra]
             ["os" :as os]
             ["os" :as os]
@@ -96,6 +97,12 @@
     (catch :default _e
     (catch :default _e
       false)))
       false)))
 
 
+(defn chmod-enabled?
+  []
+  (if (= nil (cfgs/get-item :feature/enable-automatic-chmod?))
+    true
+    (cfgs/get-item :feature/enable-automatic-chmod?)))
+
 (defmethod handle :copyFile [_window [_ _repo from-path to-path]]
 (defmethod handle :copyFile [_window [_ _repo from-path to-path]]
   (logger/info ::copy-file from-path to-path)
   (logger/info ::copy-file from-path to-path)
   (fs-extra/copy from-path to-path))
   (fs-extra/copy from-path to-path))
@@ -106,7 +113,7 @@
                       (.from Buf content)
                       (.from Buf content)
                       content)]
                       content)]
     (try
     (try
-      (when (and (fs/existsSync path) (not (writable? path)))
+      (when (and (chmod-enabled?) (fs/existsSync path) (not (writable? path)))
         (fs/chmodSync path "644"))
         (fs/chmodSync path "644"))
       (fs/writeFileSync path content)
       (fs/writeFileSync path content)
       (fs/statSync path)
       (fs/statSync path)
@@ -243,7 +250,7 @@
                                   (.toString (.readFileSync fs txid-path)))]
                                   (.toString (.readFileSync fs txid-path)))]
           (reader/read-string sync-meta))))
           (reader/read-string sync-meta))))
     (catch :default e
     (catch :default e
-      (js/console.debug "[read txid meta] #" root (.-message e)))))
+      (logger/error "[read txid meta] #" root (.-message e)))))
 
 
 (defmethod handle :inflateGraphsInfo [_win [_ graphs]]
 (defmethod handle :inflateGraphsInfo [_win [_ graphs]]
   (if (seq graphs)
   (if (seq graphs)
@@ -618,6 +625,10 @@
 (defmethod handle :window-close [^js win]
 (defmethod handle :window-close [^js win]
   (.close win))
   (.close win))
 
 
+(defmethod handle :theme-loaded [^js win]
+  (.manage (windowStateKeeper) win)
+  (.show win))
+
 ;;;;;;;;;;;;;;;;;;;;;;;
 ;;;;;;;;;;;;;;;;;;;;;;;
 ;; file-sync-rs-apis ;;
 ;; file-sync-rs-apis ;;
 ;;;;;;;;;;;;;;;;;;;;;;;
 ;;;;;;;;;;;;;;;;;;;;;;;

+ 18 - 11
src/electron/electron/search.cljs

@@ -145,6 +145,7 @@
     [db-name (node-path/join search-dir db-name)]))
     [db-name (node-path/join search-dir db-name)]))
 
 
 (defn open-db!
 (defn open-db!
+  "Open a SQLite db for search index"
   [db-name]
   [db-name]
   (let [[db-sanitized-name db-full-path] (get-db-full-path db-name)]
   (let [[db-sanitized-name db-full-path] (get-db-full-path db-name)]
     (try (let [db (sqlite3 db-full-path nil)]
     (try (let [db (sqlite3 db-full-path nil)]
@@ -157,7 +158,13 @@
            (swap! databases assoc db-sanitized-name db))
            (swap! databases assoc db-sanitized-name db))
          (catch :default e
          (catch :default e
            (logger/error (str e ": " db-name))
            (logger/error (str e ": " db-name))
-           (fs/unlinkSync db-full-path)))))
+           (try
+             (fs/unlinkSync db-full-path)
+             (catch :default e
+               (logger/error "cannot unlink search db:" e)
+               (utils/send-to-renderer "notification"
+                                       {:type    "error"
+                                        :payload (str "Search index error, please manually delete “" db-full-path "”: \n" e)})))))))
 
 
 (defn open-dbs!
 (defn open-dbs!
   []
   []
@@ -177,9 +184,15 @@
   (str "(" (->> (map (fn [id] (str "'" id "'")) ids)
   (str "(" (->> (map (fn [id] (str "'" id "'")) ids)
                 (string/join ", ")) ")"))
                 (string/join ", ")) ")"))
 
 
+(defn- get-or-open-db [repo]
+  (or (get-db repo)
+      (do
+        (open-db! repo)
+        (get-db repo))))
+
 (defn upsert-pages!
 (defn upsert-pages!
   [repo pages]
   [repo pages]
-  (if-let [db (get-db repo)]
+  (when-let [db (get-or-open-db repo)]
     ;; TODO: what if a CONFLICT on uuid
     ;; TODO: what if a CONFLICT on uuid
     ;; Should update all values on id conflict
     ;; Should update all values on id conflict
     (let [insert (prepare db "INSERT INTO pages (id, uuid, content) VALUES (@id, @uuid, @content) ON CONFLICT (id) DO UPDATE SET (uuid, content) = (@uuid, @content)" repo)
     (let [insert (prepare db "INSERT INTO pages (id, uuid, content) VALUES (@id, @uuid, @content) ON CONFLICT (id) DO UPDATE SET (uuid, content) = (@uuid, @content)" repo)
@@ -187,10 +200,7 @@
                                     (fn [pages]
                                     (fn [pages]
                                       (doseq [page pages]
                                       (doseq [page pages]
                                         (.run ^object insert page))))]
                                         (.run ^object insert page))))]
-      (insert-many pages))
-    (do
-      (open-db! repo)
-      (upsert-pages! repo pages))))
+      (insert-many pages))))
 
 
 (defn delete-pages!
 (defn delete-pages!
   [repo ids]
   [repo ids]
@@ -201,7 +211,7 @@
 
 
 (defn upsert-blocks!
 (defn upsert-blocks!
   [repo blocks]
   [repo blocks]
-  (if-let [db (get-db repo)]
+  (when-let [db (get-or-open-db repo)]
     ;; TODO: what if a CONFLICT on uuid
     ;; TODO: what if a CONFLICT on uuid
     ;; Should update all values on id conflict
     ;; Should update all values on id conflict
     (let [insert (prepare db "INSERT INTO blocks (id, uuid, content, page) VALUES (@id, @uuid, @content, @page) ON CONFLICT (id) DO UPDATE SET (uuid, content, page) = (@uuid, @content, @page)" repo)
     (let [insert (prepare db "INSERT INTO blocks (id, uuid, content, page) VALUES (@id, @uuid, @content, @page) ON CONFLICT (id) DO UPDATE SET (uuid, content, page) = (@uuid, @content, @page)" repo)
@@ -209,10 +219,7 @@
                                     (fn [blocks]
                                     (fn [blocks]
                                       (doseq [block blocks]
                                       (doseq [block blocks]
                                         (.run ^object insert block))))]
                                         (.run ^object insert block))))]
-      (insert-many blocks))
-    (do
-      (open-db! repo)
-      (upsert-blocks! repo blocks))))
+      (insert-many blocks))))
 
 
 (defn delete-blocks!
 (defn delete-blocks!
   [repo ids]
   [repo ids]

+ 8 - 2
src/electron/electron/utils.cljs

@@ -26,7 +26,8 @@
 (defonce *fetchAgent (atom nil))
 (defonce *fetchAgent (atom nil))
 
 
 (defonce open (js/require "open"))
 (defonce open (js/require "open"))
-(defonce HttpsProxyAgent (js/require "https-proxy-agent"))
+(defonce HttpsProxyAgent (.-HttpsProxyAgent (js/require "https-proxy-agent")))
+(defonce SocksProxyAgent (.-SocksProxyAgent (js/require "socks-proxy-agent")))
 (defonce _fetch (js/require "node-fetch"))
 (defonce _fetch (js/require "node-fetch"))
 (defonce extract-zip (js/require "extract-zip"))
 (defonce extract-zip (js/require "extract-zip"))
 
 
@@ -67,7 +68,12 @@
   [{:keys [protocol host port]}]
   [{:keys [protocol host port]}]
   (if (and protocol host port (or (= protocol "http") (= protocol "socks5")))
   (if (and protocol host port (or (= protocol "http") (= protocol "socks5")))
     (let [proxy-url (str protocol "://" host ":" port)]
     (let [proxy-url (str protocol "://" host ":" port)]
-      (reset! *fetchAgent (new HttpsProxyAgent proxy-url)))
+      (condp = protocol
+        "http"
+        (reset! *fetchAgent (new HttpsProxyAgent proxy-url))
+        "socks5"
+        (reset! *fetchAgent (new SocksProxyAgent proxy-url))
+        (logger/error "Unknown proxy protocol:" protocol)))
     (reset! *fetchAgent nil)))
     (reset! *fetchAgent nil)))
 
 
 (defn- set-rsapi-proxy
 (defn- set-rsapi-proxy

+ 3 - 4
src/electron/electron/utils.js

@@ -57,15 +57,14 @@ export async function getAllFiles(dir, exts) {
 
 
       const fileStats = await fse.lstat(filePath)
       const fileStats = await fse.lstat(filePath)
 
 
-      const stats = {
+      return {
+        path: filePath,
         size: fileStats.size,
         size: fileStats.size,
         accessTime: fileStats.atimeMs,
         accessTime: fileStats.atimeMs,
         modifiedTime: fileStats.mtimeMs,
         modifiedTime: fileStats.mtimeMs,
         changeTime: fileStats.ctimeMs,
         changeTime: fileStats.ctimeMs,
-        birthTime: fileStats.birthtimeMs,
+        birthTime: fileStats.birthtimeMs
       }
       }
-
-      return { path: filePath, ...stats }
     })
     })
   )
   )
   return files.flat().filter((it) => it != null)
   return files.flat().filter((it) => it != null)

+ 25 - 17
src/electron/electron/window.cljs

@@ -35,6 +35,7 @@
                       :titleBarStyle        "hiddenInset"
                       :titleBarStyle        "hiddenInset"
                       :trafficLightPosition {:x 16 :y 16}
                       :trafficLightPosition {:x 16 :y 16}
                       :autoHideMenuBar      (not mac?)
                       :autoHideMenuBar      (not mac?)
+                      :show                 false
                       :webPreferences
                       :webPreferences
                       {:plugins                 true        ; pdf
                       {:plugins                 true        ; pdf
                        :nodeIntegration         false
                        :nodeIntegration         false
@@ -55,7 +56,6 @@
                      linux?
                      linux?
                      (assoc :icon (node-path/join js/__dirname "icons/logseq.png")))
                      (assoc :icon (node-path/join js/__dirname "icons/logseq.png")))
          win       (BrowserWindow. (clj->js win-opts))]
          win       (BrowserWindow. (clj->js win-opts))]
-     (.manage win-state win)
      (.onBeforeSendHeaders (.. session -defaultSession -webRequest)
      (.onBeforeSendHeaders (.. session -defaultSession -webRequest)
                            (clj->js {:urls (array "*://*.youtube.com/*")})
                            (clj->js {:urls (array "*://*.youtube.com/*")})
                            (fn [^js details callback]
                            (fn [^js details callback]
@@ -76,25 +76,37 @@
      ;;(when dev? (.. win -webContents (openDevTools)))
      ;;(when dev? (.. win -webContents (openDevTools)))
      win)))
      win)))
 
 
+(defn get-all-windows
+  []
+  (.getAllWindows BrowserWindow))
+
 (defn destroy-window!
 (defn destroy-window!
   [^js win]
   [^js win]
   (.destroy win))
   (.destroy win))
 
 
+(defn close-handler
+  [^js win close-watcher-f e]
+  (.preventDefault e)
+  (when-let [dir (state/get-window-graph-path win)]
+    (close-watcher-f win dir))
+  (state/close-window! win)
+  (let [web-contents (. win -webContents)]
+    (.send web-contents "persist-zoom-level" (.getZoomLevel web-contents))
+    (.send web-contents "persistent-dbs"))
+  (async/go
+    (let [_ (async/<! state/persistent-dbs-chan)]
+      (destroy-window! win)
+      ;; (if @*quitting?
+      ;;   (doseq [win (get-all-windows)]
+      ;;     (destroy-window! win))
+      ;;   (destroy-window! win))
+      (when @*quitting?
+        (async/put! state/persistent-dbs-chan true)))))
+
 (defn on-close-actions!
 (defn on-close-actions!
   ;; TODO merge with the on close in core
   ;; TODO merge with the on close in core
   [^js win close-watcher-f] ;; injected watcher related func
   [^js win close-watcher-f] ;; injected watcher related func
-  (.on win "close" (fn [e]
-                     (.preventDefault e)
-                     (when-let [dir (state/get-window-graph-path win)]
-                       (close-watcher-f win dir))
-                     (state/close-window! win)
-                     (let [web-contents (. win -webContents)]
-                       (.send web-contents "persistent-dbs"))
-                     (async/go
-                       (let [_ (async/<! state/persistent-dbs-chan)]
-                         (destroy-window! win)
-                         (when @*quitting?
-                           (async/put! state/persistent-dbs-chan true)))))))
+  (.on win "close" (fn [e] (close-handler win close-watcher-f e))))
 
 
 (defn switch-to-window!
 (defn switch-to-window!
   [^js win]
   [^js win]
@@ -102,10 +114,6 @@
     (.restore win))
     (.restore win))
   (.focus win))
   (.focus win))
 
 
-(defn get-all-windows
-  []
-  (.getAllWindows BrowserWindow))
-
 (defn get-graph-all-windows
 (defn get-graph-all-windows
   [graph-path] ;; graph-path == dir
   [graph-path] ;; graph-path == dir
   (->> (group-by second (:window/graph @state/state))
   (->> (group-by second (:window/graph @state/state))

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

@@ -1335,7 +1335,7 @@
                   (str "https://player.vimeo.com/video/" id)
                   (str "https://player.vimeo.com/video/" id)
 
 
                   [_ _ _ "bilibili.com" _ id & query]
                   [_ _ _ "bilibili.com" _ id & query]
-                  (str "https://player.bilibili.com/player.html?bvid=" id "&high_quality=1"
+                  (str "https://player.bilibili.com/player.html?bvid=" id "&high_quality=1&autoplay=0"
                        (when-let [page (second query)]
                        (when-let [page (second query)]
                          (str "&page=" page)))
                          (str "&page=" page)))
 
 

+ 2 - 2
src/main/frontend/components/bug_report.cljs

@@ -73,7 +73,7 @@
          (ui/button (t :bug-report/inspector-page-btn-copy) :on-click #(copy-result-to-clipboard! (js/JSON.stringify (clj->js result) nil 2)))]
          (ui/button (t :bug-report/inspector-page-btn-copy) :on-click #(copy-result-to-clipboard! (js/JSON.stringify (clj->js result) nil 2)))]
         [:div.flex.justify-between.items-center.mt-2
         [:div.flex.justify-between.items-center.mt-2
          [:div (t :bug-report/inspector-page-desc-create-issue)]
          [:div (t :bug-report/inspector-page-desc-create-issue)]
-         (ui/button (t :bug-report/inspector-page-btn-create-issue) :href header/bug-report-url)]
+         (ui/button (t :bug-report/inspector-page-btn-create-issue) :href (header/bug-report-url))]
         [:div.flex.justify-between.items-center.mt-2
         [:div.flex.justify-between.items-center.mt-2
          [:div (t :bug-report/inspector-page-tip)]
          [:div (t :bug-report/inspector-page-tip)]
          (ui/button (t :bug-report/inspector-page-btn-back) :on-click reset-step!)]
          (ui/button (t :bug-report/inspector-page-btn-back) :on-click reset-step!)]
@@ -118,4 +118,4 @@
     [:div.flex.flex-col
     [:div.flex.flex-col
      [:h1.text-2xl (t :bug-report/section-issues-title)]
      [:h1.text-2xl (t :bug-report/section-issues-title)]
      [:div.opacity-60 (t :bug-report/section-issues-desc)]
      [:div.opacity-60 (t :bug-report/section-issues-desc)]
-     (report-item-button (t :bug-report/section-issues-btn-title) (t :bug-report/section-issues-btn-desc) "message-report" {:on-click #(util/open-url header/bug-report-url)})]]])
+     (report-item-button (t :bug-report/section-issues-btn-title) (t :bug-report/section-issues-btn-desc) "message-report" {:on-click #(util/open-url (header/bug-report-url))})]]])

+ 2 - 19
src/main/frontend/components/container.css

@@ -558,7 +558,7 @@ html[data-theme='dark'] {
   }
   }
 
 
   &-btn {
   &-btn {
-    @apply fixed bottom-4 right-8;
+    @apply fixed bottom-4 right-4 sm:right-8;
 
 
     > .inner {
     > .inner {
       @apply font-bold
       @apply font-bold
@@ -718,14 +718,6 @@ html[data-theme='dark'] {
       }
       }
     }
     }
 
 
-    &:not(:hover) {
-      ::-webkit-scrollbar-thumb,
-      ::-webkit-scrollbar,
-      ::-webkit-scrollbar-thumb:active {
-        background-color: transparent;
-      }
-    }
-
     .initial {
     .initial {
       flex: 1;
       flex: 1;
     }
     }
@@ -734,22 +726,13 @@ html[data-theme='dark'] {
       @apply h-full;
       @apply h-full;
 
 
       .button {
       .button {
-        @apply hidden p-0 ml-2 flex items-center;
+        @apply p-0 ml-2 flex items-center;
 
 
         &:focus {
         &:focus {
           @apply flex;
           @apply flex;
         }
         }
       }
       }
     }
     }
-
-    .is-mobile &,
-    &:hover {
-      .item-actions {
-        .button {
-          @apply flex;
-        }
-      }
-    }
   }
   }
 }
 }
 
 

+ 22 - 18
src/main/frontend/components/editor.cljs

@@ -411,16 +411,6 @@
         right-sidebar? (:ui/sidebar-open? @state/state)
         right-sidebar? (:ui/sidebar-open? @state/state)
         editing-key    (first (keys (:editor/editing? @state/state)))
         editing-key    (first (keys (:editor/editing? @state/state)))
         *el (rum/use-ref nil)
         *el (rum/use-ref nil)
-        _ (rum/use-effect! (fn []
-                             (when-let [^js/HTMLElement cnt
-                                        (and right-sidebar? editing-key
-                                             (js/document.querySelector "#main-content-container"))]
-                               (when (.contains cnt (js/document.querySelector (str "#" editing-key)))
-                                 (let [el  (rum/deref *el)
-                                       ofx (- (.-scrollWidth cnt) (.-clientWidth cnt))]
-                                   (when (> ofx 0)
-                                     (set! (.-transform (.-style el)) (str "translateX(-" (+ ofx 20) "px)")))))))
-                           [right-sidebar? editing-key])
         y-overflow-vh? (or (< to-max-height Y-BOUNDARY-HEIGHT)
         y-overflow-vh? (or (< to-max-height Y-BOUNDARY-HEIGHT)
                            (> (- max-height' to-max-height) Y-BOUNDARY-HEIGHT))
                            (> (- max-height' to-max-height) Y-BOUNDARY-HEIGHT))
         to-max-height (if y-overflow-vh? max-height' to-max-height)
         to-max-height (if y-overflow-vh? max-height' to-max-height)
@@ -430,22 +420,36 @@
         style (merge
         style (merge
                {:top        (+ top offset-top (if (int? y-diff) y-diff 0))
                {:top        (+ top offset-top (if (int? y-diff) y-diff 0))
                 :max-height to-max-height
                 :max-height to-max-height
-                :max-width 700
+                :max-width  700
                 ;; TODO: auto responsive fixed size
                 ;; TODO: auto responsive fixed size
-                :width "fit-content"
+                :width      "fit-content"
                 :z-index    11}
                 :z-index    11}
                (when set-default-width?
                (when set-default-width?
                  {:width max-width})
                  {:width max-width})
                (if (<= vw-max-width (+ left (if set-default-width? max-width 500)))
                (if (<= vw-max-width (+ left (if set-default-width? max-width 500)))
                  {:right 0}
                  {:right 0}
-                 {:left (if (or (nil? y-diff) (and y-diff (= y-diff 0))) left 0)}))]
+                 {:left 0}))]
+
+    (rum/use-effect!
+     (fn []
+       (when-let [^js/HTMLElement cnt
+                  (and right-sidebar? editing-key
+                       (js/document.querySelector "#main-content-container"))]
+         (when (.contains cnt (js/document.querySelector (str "#" editing-key)))
+           (let [el  (rum/deref *el)
+                 ofx (- (.-scrollWidth cnt) (.-clientWidth cnt))]
+             (when (> ofx 0)
+               (set! (.-transform (.-style el))
+                     (util/format "translate(-%spx, %s)" (+ ofx 20) (if y-overflow-vh? "calc(-100% - 2rem)" 0))))))))
+     [right-sidebar? editing-key y-overflow-vh?])
+
     [:div.absolute.rounded-md.shadow-lg.absolute-modal
     [:div.absolute.rounded-md.shadow-lg.absolute-modal
-     {:ref *el
+     {:ref             *el
       :data-modal-name modal-name
       :data-modal-name modal-name
-      :class (if y-overflow-vh? "is-overflow-vh-y" "")
-      :on-mouse-down (fn [e]
-                       (.stopPropagation e))
-      :style style}
+      :class           (if y-overflow-vh? "is-overflow-vh-y" "")
+      :on-mouse-down   (fn [e]
+                         (.stopPropagation e))
+      :style           style}
      cp]))
      cp]))
 
 
 (rum/defc transition-cp < rum/reactive
 (rum/defc transition-cp < rum/reactive

+ 6 - 3
src/main/frontend/components/header.cljs

@@ -64,13 +64,16 @@
       :on-click on-click}
       :on-click on-click}
      (ui/icon "menu-2" {:size ui/icon-size})]))
      (ui/icon "menu-2" {:size ui/icon-size})]))
 
 
-(def bug-report-url
+(defn bug-report-url []
   (let [ua (.-userAgent js/navigator)
   (let [ua (.-userAgent js/navigator)
         safe-ua (string/replace ua #"[^_/a-zA-Z0-9\.\(\)]+" " ")
         safe-ua (string/replace ua #"[^_/a-zA-Z0-9\.\(\)]+" " ")
         platform (str "App Version: " version "\n"
         platform (str "App Version: " version "\n"
                       "Git Revision: " config/REVISION "\n"
                       "Git Revision: " config/REVISION "\n"
                       "Platform: " safe-ua "\n"
                       "Platform: " safe-ua "\n"
-                      "Language: " (.-language js/navigator))]
+                      "Language: " (.-language js/navigator) "\n"
+                      "Plugins: " (string/join ", " (map (fn [[k v]]
+                                                           (str (name k) " (" (:version v) ")"))
+                                                         (:plugin/installed-plugins @state/state))))]
     (str "https://github.com/logseq/logseq/issues/new?"
     (str "https://github.com/logseq/logseq/issues/new?"
          "title=&"
          "title=&"
          "template=bug_report.yaml&"
          "template=bug_report.yaml&"
@@ -187,7 +190,7 @@
            :options {:href (rfe/href :import)}
            :options {:href (rfe/href :import)}
            :icon (ui/icon "file-upload")})
            :icon (ui/icon "file-upload")})
 
 
-        (when-not config/publishing? 
+        (when-not config/publishing?
           {:title [:div.flex-row.flex.justify-between.items-center
           {:title [:div.flex-row.flex.justify-between.items-center
                    [:span (t :join-community)]]
                    [:span (t :join-community)]]
            :options {:href "https://discuss.logseq.com"
            :options {:href "https://discuss.logseq.com"

+ 33 - 11
src/main/frontend/components/page.cljs

@@ -2,14 +2,14 @@
   (:require ["/frontend/utils" :as utils]
   (:require ["/frontend/utils" :as utils]
             [clojure.string :as string]
             [clojure.string :as string]
             [frontend.components.block :as component-block]
             [frontend.components.block :as component-block]
-            [frontend.components.query :as query]
             [frontend.components.content :as content]
             [frontend.components.content :as content]
             [frontend.components.editor :as editor]
             [frontend.components.editor :as editor]
             [frontend.components.hierarchy :as hierarchy]
             [frontend.components.hierarchy :as hierarchy]
             [frontend.components.plugins :as plugins]
             [frontend.components.plugins :as plugins]
+            [frontend.components.query :as query]
             [frontend.components.reference :as reference]
             [frontend.components.reference :as reference]
-            [frontend.components.svg :as svg]
             [frontend.components.scheduled-deadlines :as scheduled]
             [frontend.components.scheduled-deadlines :as scheduled]
+            [frontend.components.svg :as svg]
             [frontend.config :as config]
             [frontend.config :as config]
             [frontend.context.i18n :refer [t]]
             [frontend.context.i18n :refer [t]]
             [frontend.date :as date]
             [frontend.date :as date]
@@ -21,6 +21,7 @@
             [frontend.format.block :as block]
             [frontend.format.block :as block]
             [frontend.handler.common :as common-handler]
             [frontend.handler.common :as common-handler]
             [frontend.handler.config :as config-handler]
             [frontend.handler.config :as config-handler]
+            [frontend.handler.dnd :as dnd]
             [frontend.handler.editor :as editor-handler]
             [frontend.handler.editor :as editor-handler]
             [frontend.handler.graph :as graph-handler]
             [frontend.handler.graph :as graph-handler]
             [frontend.handler.notification :as notification]
             [frontend.handler.notification :as notification]
@@ -34,12 +35,13 @@
             [frontend.util :as util]
             [frontend.util :as util]
             [frontend.util.text :as text-util]
             [frontend.util.text :as text-util]
             [goog.object :as gobj]
             [goog.object :as gobj]
+            [logseq.graph-parser.mldoc :as gp-mldoc]
             [logseq.graph-parser.util :as gp-util]
             [logseq.graph-parser.util :as gp-util]
+            [logseq.graph-parser.util.page-ref :as page-ref]
             [medley.core :as medley]
             [medley.core :as medley]
+            [promesa.core :as p]
             [reitit.frontend.easy :as rfe]
             [reitit.frontend.easy :as rfe]
-            [rum.core :as rum]
-            [logseq.graph-parser.util.page-ref :as page-ref]
-            [logseq.graph-parser.mldoc :as gp-mldoc]))
+            [rum.core :as rum]))
 
 
 (defn- get-page-name
 (defn- get-page-name
   [state]
   [state]
@@ -109,10 +111,26 @@
 
 
 (rum/defc dummy-block
 (rum/defc dummy-block
   [page-name]
   [page-name]
-  (let [handler-fn (fn []
-                     (let [block (editor-handler/insert-first-page-block-if-not-exists! page-name {:redirect? false})]
-                       (js/setTimeout #(editor-handler/edit-block! block :max (:block/uuid block)) 0)))]
-    [:div.ls-block.flex-1.flex-col.rounded-sm {:style {:width "100%"}}
+  (let [[hover set-hover!] (rum/use-state false)
+        click-handler-fn (fn []
+                           (let [block (editor-handler/insert-first-page-block-if-not-exists! page-name {:redirect? false})]
+                             (js/setTimeout #(editor-handler/edit-block! block :max (:block/uuid block)) 0)))
+        drop-handler-fn (fn [^js event]
+                          (util/stop event)
+                          (p/let [block-uuids (state/get-selection-block-ids)
+                                  lookup-refs (map (fn [id] [:block/uuid id]) block-uuids)
+                                  selected (db/pull-many (state/get-current-repo) '[*] lookup-refs)
+                                  blocks (if (seq selected) selected [@component-block/*dragging-block])
+                                  _ (editor-handler/insert-first-page-block-if-not-exists! page-name {:redirect? false})]
+                            (js/setTimeout #(let [target-block (db/pull (:db/id (db/get-page page-name)))]
+                                              (dnd/move-blocks event blocks target-block :sibling))
+                                           0)))]
+    [:div.ls-block.flex-1.flex-col.rounded-sm
+     {:style {:width "100%"
+              ;; The same as .dnd-separator
+              :border-top (if hover
+                            "3px solid #ccc"
+                            nil)}}
      [:div.flex.flex-row
      [:div.flex.flex-row
       [:div.flex.flex-row.items-center.mr-2.ml-1 {:style {:height 24}}
       [:div.flex.flex-row.items-center.mr-2.ml-1 {:style {:height 24}}
        [:span.bullet-container.cursor
        [:span.bullet-container.cursor
@@ -120,8 +138,12 @@
       [:div.flex.flex-1 {:tabIndex 0
       [:div.flex.flex-1 {:tabIndex 0
                          :on-key-press (fn [e]
                          :on-key-press (fn [e]
                                          (when (= "Enter" (util/ekey e))
                                          (when (= "Enter" (util/ekey e))
-                                           (handler-fn)))
-                         :on-click handler-fn}
+                                           (click-handler-fn)))
+                         :on-click click-handler-fn
+                         :on-drag-enter #(set-hover! true)
+                         :on-drag-over #(util/stop %)
+                         :on-drop drop-handler-fn
+                         :on-drag-leave #(set-hover! false)}
        [:span.opacity-70
        [:span.opacity-70
         "Click here to edit..."]]]]))
         "Click here to edit..."]]]]))
 
 

+ 4 - 1
src/main/frontend/components/plugins.cljs

@@ -433,6 +433,8 @@
                           (assoc opts :test (util/trim-safe (util/evalue %))))
                           (assoc opts :test (util/trim-safe (util/evalue %))))
           :value       (:test opts)}]
           :value       (:test opts)}]
         [:datalist#proxy-test-url-datalist
         [:datalist#proxy-test-url-datalist
+         [:option "https://api.logseq.com/logseq/version"]
+         [:option "https://logseq-connectivity-testing-prod.s3.us-east-1.amazonaws.com/logseq-connectivity-testing"]
          [:option "https://www.google.com"]
          [:option "https://www.google.com"]
          [:option "https://s3.amazonaws.com"]
          [:option "https://s3.amazonaws.com"]
          [:option "https://clients3.google.com/generate_204"]]]
          [:option "https://clients3.google.com/generate_204"]]]
@@ -445,9 +447,10 @@
                                  (-> (p/let [result (ipc/ipc :testProxyUrl val opts)]
                                  (-> (p/let [result (ipc/ipc :testProxyUrl val opts)]
                                        (js->clj result :keywordize-keys true))
                                        (js->clj result :keywordize-keys true))
                                      (p/then (fn [{:keys [code response-ms]}]
                                      (p/then (fn [{:keys [code response-ms]}]
+                                               (notification/clear! :proxy-net-check)
                                                (notification/show! (str "Success! Status " code " in " response-ms "ms.") :success)))
                                                (notification/show! (str "Success! Status " code " in " response-ms "ms.") :success)))
                                      (p/catch (fn [e]
                                      (p/catch (fn [e]
-                                                (notification/show! (str e) :error)))
+                                                (notification/show! (str e) :error false :proxy-net-check)))
                                      (p/finally (fn [] (set-testing?! false)))))))]
                                      (p/finally (fn [] (set-testing?! false)))))))]
 
 
       [:p.pt-2
       [:p.pt-2

+ 1 - 0
src/main/frontend/components/right_sidebar.cljs

@@ -377,6 +377,7 @@
                 :aria-label (t :right-side-bar/separator)
                 :aria-label (t :right-side-bar/separator)
                 :aria-valuemin (* min-ratio 100)
                 :aria-valuemin (* min-ratio 100)
                 :aria-valuemax (* max-ratio 100)
                 :aria-valuemax (* max-ratio 100)
+                :aria-valuenow 50
                 :tabIndex "0"
                 :tabIndex "0"
                 :data-expanded sidebar-open?}]))
                 :data-expanded sidebar-open?}]))
 
 

+ 16 - 11
src/main/frontend/components/settings.cljs

@@ -526,15 +526,6 @@
 ;;             (let [value (not enable-block-timestamps?)]
 ;;             (let [value (not enable-block-timestamps?)]
 ;;               (config-handler/set-config! :feature/enable-block-timestamps? value)))))
 ;;               (config-handler/set-config! :feature/enable-block-timestamps? value)))))
 
 
-(rum/defc keyboard-shortcuts-row [t]
-  (row-with-button-action
-    {:left-label   (t :settings-page/customize-shortcuts)
-     :button-label (t :settings-page/shortcut-settings)
-     :on-click      (fn []
-                      (state/close-settings!)
-                      (route-handler/redirect! {:to :shortcut-setting}))
-     :-for         "customize_shortcuts"}))
-
 (defn zotero-settings-row []
 (defn zotero-settings-row []
   [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start
   [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start
    [:label.block.text-sm.font-medium.leading-5.opacity-70
    [:label.block.text-sm.font-medium.leading-5.opacity-70
@@ -664,6 +655,20 @@
    {:left-label (t :settings-page/network-proxy)
    {:left-label (t :settings-page/network-proxy)
     :action (user-proxy-settings agent-opts)}))
     :action (user-proxy-settings agent-opts)}))
 
 
+(rum/defcs auto-chmod-row < rum/reactive
+  [state t]
+  (let [enabled? (if (= nil (state/sub [:electron/user-cfgs :feature/enable-automatic-chmod?]))
+                   true
+                   (state/sub [:electron/user-cfgs :feature/enable-automatic-chmod?]))]
+    (toggle
+     "automatic-chmod"
+     (t :settings-page/auto-chmod)
+     enabled?
+     #(do
+       (state/set-state! [:electron/user-cfgs :feature/enable-automatic-chmod?] (not enabled?))
+       (ipc/ipc :userAppCfgs :feature/enable-automatic-chmod? (not enabled?)))
+     [:span.text-sm.opacity-50 (t :settings-page/auto-chmod-desc)])))
+
 (defn filename-format-row []
 (defn filename-format-row []
   (row-with-button-action
   (row-with-button-action
    {:left-label (t :settings-page/filename-format)
    {:left-label (t :settings-page/filename-format)
@@ -703,8 +708,7 @@
      (when (config/global-config-enabled?) (edit-global-config-edn))
      (when (config/global-config-enabled?) (edit-global-config-edn))
      (when current-repo (edit-config-edn))
      (when current-repo (edit-config-edn))
      (when current-repo (edit-custom-css))
      (when current-repo (edit-custom-css))
-     (when current-repo (edit-export-css))
-     (keyboard-shortcuts-row t)]))
+     (when current-repo (edit-export-css))]))
 
 
 (rum/defcs settings-editor < rum/reactive
 (rum/defcs settings-editor < rum/reactive
   [_state current-repo]
   [_state current-repo]
@@ -776,6 +780,7 @@
      (usage-diagnostics-row t instrument-disabled?)
      (usage-diagnostics-row t instrument-disabled?)
      (when-not (mobile-util/native-platform?) (developer-mode-row t developer-mode?))
      (when-not (mobile-util/native-platform?) (developer-mode-row t developer-mode?))
      (when (util/electron?) (https-user-agent-row https-agent-opts))
      (when (util/electron?) (https-user-agent-row https-agent-opts))
+     (when (util/electron?) (auto-chmod-row t))
      (when (and (util/electron?) (not (config/demo-graph? current-repo))) (filename-format-row))
      (when (and (util/electron?) (not (config/demo-graph? current-repo))) (filename-format-row))
      (clear-cache-row t)
      (clear-cache-row t)
 
 

+ 9 - 5
src/main/frontend/components/shortcut2.cljs

@@ -284,7 +284,8 @@
 
 
       [:div.shortcuts-keys-wrap
       [:div.shortcuts-keys-wrap
        [:span.keyboard-shortcut.flex.flex-wrap.mr-2.space-x-2
        [:span.keyboard-shortcut.flex.flex-wrap.mr-2.space-x-2
-        (for [x current-binding]
+        (for [x current-binding
+              :when (string? x)]
           [:code.tracking-wider
           [:code.tracking-wider
            (-> x (string/trim) (string/lower-case) (shortcut-utils/decorate-binding))
            (-> x (string/trim) (string/lower-case) (shortcut-utils/decorate-binding))
            [:a.x {:on-click (fn [] (set-current-binding!
            [:a.x {:on-click (fn [] (set-current-binding!
@@ -428,7 +429,9 @@
                           disabled? (or (false? user-binding)
                           disabled? (or (false? user-binding)
                                         (false? (first binding)))
                                         (false? (first binding)))
                           unset? (and (not disabled?)
                           unset? (and (not disabled?)
-                                      (= user-binding []))]]
+                                      (or (= user-binding [])
+                                          (and (= binding [])
+                                               (nil? user-binding))))]]
 
 
                 (when (or (nil? (seq filters))
                 (when (or (nil? (seq filters))
                           (when (contains? filters :Custom) custom?)
                           (when (contains? filters :Custom) custom?)
@@ -453,11 +456,11 @@
 
 
                      [:a.action-wrap
                      [:a.action-wrap
                       {:class    (util/classnames [{:disabled disabled?}])
                       {:class    (util/classnames [{:disabled disabled?}])
-                       :on-click (when-not disabled?
+                       :on-click (when (and id (not disabled?))
                                    #(open-customize-shortcut-dialog! id))}
                                    #(open-customize-shortcut-dialog! id))}
 
 
                       (cond
                       (cond
-                        (or user-binding (false? user-binding))
+                        (or unset? user-binding (false? user-binding))
                         [:code.dark:bg-green-800.bg-green-300
                         [:code.dark:bg-green-800.bg-green-300
                          (if unset?
                          (if unset?
                            (t :keymap/unset)
                            (t :keymap/unset)
@@ -473,4 +476,5 @@
                         (for [x binding]
                         (for [x binding]
                           [:code.tracking-wide
                           [:code.tracking-wide
                            {:key (str x)}
                            {:key (str x)}
-                           (dh/binding-for-display id x)]))]]))))])])]]))
+                           (dh/binding-for-display id x)]))
+                      ]]))))])])]]))

+ 6 - 1
src/main/frontend/components/theme.cljs

@@ -1,5 +1,6 @@
 (ns frontend.components.theme
 (ns frontend.components.theme
-  (:require [frontend.extensions.pdf.core :as pdf]
+  (:require [electron.ipc :as ipc]
+            [frontend.extensions.pdf.core :as pdf]
             [frontend.config :as config]
             [frontend.config :as config]
             [frontend.handler.plugin :as plugin-handler]
             [frontend.handler.plugin :as plugin-handler]
             [frontend.handler.plugin-config :as plugin-config-handler]
             [frontend.handler.plugin-config :as plugin-config-handler]
@@ -35,6 +36,10 @@
      #(let [doc js/document.documentElement]
      #(let [doc js/document.documentElement]
         (.setAttribute doc "lang" preferred-language)))
         (.setAttribute doc "lang" preferred-language)))
 
 
+    (rum/use-effect!
+     #(js/setTimeout (fn [] (ipc/ipc "theme-loaded")) 100) ; Wait for the theme to be applied
+     [])
+
     (rum/use-effect!
     (rum/use-effect!
      #(when (and restored-sidebar?
      #(when (and restored-sidebar?
                  (mounted-fn))
                  (mounted-fn))

+ 1 - 1
src/main/frontend/components/whiteboard.css

@@ -217,7 +217,7 @@ input.tl-text-input {
 
 
   .tl-action-bar {
   .tl-action-bar {
     left: 0.5rem;
     left: 0.5rem;
-    bottom: 0.5rem;
+    bottom: 0;
   }
   }
 
 
   .tl-primary-tools {
   .tl-primary-tools {

+ 6 - 22
src/main/frontend/components/window_controls.cljs

@@ -1,27 +1,11 @@
 (ns frontend.components.window-controls
 (ns frontend.components.window-controls
-  (:require [electron.ipc :as ipc]
-            [frontend.components.svg :as svg]
+  (:require [frontend.components.svg :as svg]
             [frontend.context.i18n :refer [t]]
             [frontend.context.i18n :refer [t]]
+            [frontend.handler.window :as window-handler]
             [frontend.state :as state]
             [frontend.state :as state]
             [frontend.ui :as ui]
             [frontend.ui :as ui]
             [rum.core :as rum]))
             [rum.core :as rum]))
 
 
-(defn minimize
-  []
-  (ipc/ipc "window-minimize"))
-
-(defn toggle-maximized
-  []
-  (ipc/ipc "window-toggle-maximized"))
-
-(defn close
-  []
-  (ipc/ipc "window-close"))
-
-(defn toggle-fullscreen
-  []
-  (ipc/ipc "window-toggle-fullscreen"))
-
 (rum/defc container < rum/reactive
 (rum/defc container < rum/reactive
   []
   []
   (let [maximized?  (state/sub :electron/window-maximized?)
   (let [maximized?  (state/sub :electron/window-maximized?)
@@ -30,23 +14,23 @@
      (if fullscreen?
      (if fullscreen?
        [:button.button.icon.fullscreen-toggle
        [:button.button.icon.fullscreen-toggle
         {:title (t :window/exit-fullscreen)
         {:title (t :window/exit-fullscreen)
-         :on-click toggle-fullscreen}
+         :on-click window-handler/toggle-fullscreen!}
         (ui/icon "arrows-minimize")]
         (ui/icon "arrows-minimize")]
        [:<>
        [:<>
         [:button.button.icon.minimize
         [:button.button.icon.minimize
          {:title (t :window/minimize)
          {:title (t :window/minimize)
-          :on-click minimize}
+          :on-click window-handler/minimize!}
          (svg/window-minimize)]
          (svg/window-minimize)]
 
 
         [:button.button.icon.maximize-toggle
         [:button.button.icon.maximize-toggle
          {:title (if maximized? (t :window/restore) (t :window/maximize))
          {:title (if maximized? (t :window/restore) (t :window/maximize))
           :class (if maximized? "restore" "maximize")
           :class (if maximized? "restore" "maximize")
-          :on-click toggle-maximized}
+          :on-click window-handler/toggle-maximized!}
          (if maximized?
          (if maximized?
            (svg/window-restore)
            (svg/window-restore)
            (svg/window-maximize))]
            (svg/window-maximize))]
 
 
         [:button.button.icon.close
         [:button.button.icon.close
          {:title (t :window/close)
          {:title (t :window/close)
-          :on-click close}
+          :on-click window-handler/close!}
          (svg/window-close)]])]))
          (svg/window-close)]])]))

+ 14 - 10
src/main/frontend/config.cljs

@@ -465,16 +465,20 @@
   "Resolve all relative links in custom.css to assets:// URL"
   "Resolve all relative links in custom.css to assets:// URL"
   ;; ../assets/xxx -> {assets|file}://{current-graph-root-path}/xxx
   ;; ../assets/xxx -> {assets|file}://{current-graph-root-path}/xxx
   [source]
   [source]
-  (let [protocol (and (string? source)
-                      (not (string/blank? source))
-                      (if (util/electron?) "assets://" "file://"))
-        ;; BUG: use "assets" as fake current directory
-        assets-link-fn (fn [_]
-                         (str (path/path-join protocol
-                                              (get-repo-dir (state/get-current-repo)) "assets") "/"))]
-    (when (not-empty source)
-      (string/replace source #"\.\./assets/"
-                      assets-link-fn))))
+  (when-not (string/blank? source)
+    (let [protocol (and (string? source)
+                        (not (string/blank? source))
+                        (if (util/electron?) "assets://" "file://"))
+          ;; BUG: use "assets" as fake current directory
+          assets-link-fn (fn [_]
+                           (let [graph-root (get-repo-dir (state/get-current-repo))
+                                 protocol (if (string/starts-with? graph-root "file:") "" protocol)
+                                 full-path (path/path-join protocol graph-root "assets")]
+                             (str (cond-> full-path
+                                          (mobile-util/native-platform?)
+                                          (mobile-util/convert-file-src))
+                                  "/")))]
+      (string/replace source #"\.\./assets/" assets-link-fn))))
 
 
 (defn get-current-repo-assets-root
 (defn get-current-repo-assets-root
   []
   []

+ 4 - 2
src/main/frontend/dicts.cljc

@@ -58,7 +58,8 @@
    :ko      (edn-resource "dicts/ko.edn")
    :ko      (edn-resource "dicts/ko.edn")
    :pl      (edn-resource "dicts/pl.edn")
    :pl      (edn-resource "dicts/pl.edn")
    :sk      (edn-resource "dicts/sk.edn")
    :sk      (edn-resource "dicts/sk.edn")
-   :uk      (edn-resource "dicts/uk.edn")})
+   :uk      (edn-resource "dicts/uk.edn")
+   :id      (edn-resource "dicts/id.edn")})
 
 
 (def languages
 (def languages
   "List of languages presented to user"
   "List of languages presented to user"
@@ -80,7 +81,8 @@
    {:label "Türkçe" :value :tr}
    {:label "Türkçe" :value :tr}
    {:label "Українська" :value :uk}
    {:label "Українська" :value :uk}
    {:label "한국어" :value :ko}
    {:label "한국어" :value :ko}
-   {:label "Slovenčina" :value :sk}])
+   {:label "Slovenčina" :value :sk}
+   {:label "Bahasa Indonesia" :value :id}])
 
 
 (assert (= (set (keys dicts)) (set (map :value languages)))
 (assert (= (set (keys dicts)) (set (map :value languages)))
         "List of user-facing languages must match list of dictionaries")
         "List of user-facing languages must match list of dictionaries")

+ 1 - 1
src/main/frontend/extensions/code.cljs

@@ -500,7 +500,7 @@
                  (when-not (:file? (first (:rum/args state)))
                  (when-not (:file? (first (:rum/args state)))
                    (let [code (nth (:rum/args state) 3)
                    (let [code (nth (:rum/args state) 3)
                          editor @(:editor-atom state)]
                          editor @(:editor-atom state)]
-                     (when (not= (.getValue editor) code)
+                     (when (and editor (not= (.getValue editor) code))
                        (.setValue editor code))))
                        (.setValue editor code))))
                  state)}
                  state)}
   [state _config id attr code _theme _options]
   [state _config id attr code _theme _options]

+ 31 - 32
src/main/frontend/extensions/pdf/assets.cljs

@@ -147,43 +147,42 @@
 
 
       (fs/unlink! repo-cur fpath {}))))
       (fs/unlink! repo-cur fpath {}))))
 
 
-(defn resolve-ref-page
+(defn ensure-ref-page!
   [pdf-current]
   [pdf-current]
-  (let [page-name (:key pdf-current)
-        page-name (string/trim page-name)
-        page-name (str "hls__" page-name)
-        page      (db-model/get-page page-name)
-        file-path (:original-path pdf-current)
-        format    (state/get-preferred-format)
-        repo-dir  (config/get-repo-dir (state/get-current-repo))
-        asset-dir (util/node-path.join repo-dir gp-config/local-assets-dir)
-        url       (if (string/includes? file-path asset-dir)
-                    (str ".." (last (string/split file-path repo-dir)))
-                    file-path)]
-    (if-not page
-      (let [label (:filename pdf-current)]
-        (page-handler/create! page-name {:redirect?        false :create-first-block? false
-                                         :split-namespace? false
-                                         :format           format
-                                         :properties       {:file      (case format
-                                                                         :markdown
-                                                                         (util/format "[%s](%s)" label url)
-
-                                                                         :org
-                                                                         (util/format "[[%s][%s]]" url label)
-
-                                                                         url)
-                                                            :file-path url}})
-        (db-model/get-page page-name))
-
-      ;; try to update file path
-      (page-property/add-property! page-name :file-path url))
-    page))
+  (when-let [page-name (util/trim-safe (:key pdf-current))]
+    (let [page-name (str "hls__" page-name)
+          page (db-model/get-page page-name)
+          file-path (:original-path pdf-current)
+          format (state/get-preferred-format)
+          repo-dir (config/get-repo-dir (state/get-current-repo))
+          asset-dir (util/node-path.join repo-dir gp-config/local-assets-dir)
+          url (if (string/includes? file-path asset-dir)
+                (str ".." (last (string/split file-path repo-dir)))
+                file-path)]
+      (if-not page
+        (let [label (:filename pdf-current)]
+          (page-handler/create! page-name {:redirect?        false :create-first-block? false
+                                           :split-namespace? false
+                                           :format           format
+                                           :properties       {:file      (case format
+                                                                           :markdown
+                                                                           (util/format "[%s](%s)" label url)
+
+                                                                           :org
+                                                                           (util/format "[[%s][%s]]" url label)
+
+                                                                           url)
+                                                              :file-path url}})
+          (db-model/get-page page-name))
+
+        ;; try to update file path
+        (page-property/add-property! page-name :file-path url))
+      page)))
 
 
 (defn ensure-ref-block!
 (defn ensure-ref-block!
   ([pdf hl] (ensure-ref-block! pdf hl nil))
   ([pdf hl] (ensure-ref-block! pdf hl nil))
   ([pdf-current {:keys [id content page properties]} insert-opts]
   ([pdf-current {:keys [id content page properties]} insert-opts]
-   (when-let [ref-page (and pdf-current (resolve-ref-page pdf-current))]
+   (when-let [ref-page (and pdf-current (ensure-ref-page! pdf-current))]
      (let [ref-block (db-model/query-block-by-uuid id)]
      (let [ref-block (db-model/query-block-by-uuid id)]
        (if-not (nil? (:block/content ref-block))
        (if-not (nil? (:block/content ref-block))
          (do
          (do

+ 26 - 16
src/main/frontend/extensions/pdf/core.cljs

@@ -848,7 +848,6 @@
                        (confirm-fn password)))}
                        (confirm-fn password)))}
         "Submit"]]]]))
         "Submit"]]]]))
 
 
-
 (rum/defc ^:large-vars/data-var pdf-loader
 (rum/defc ^:large-vars/data-var pdf-loader
   [{:keys [url hls-file identity filename] :as pdf-current}]
   [{:keys [url hls-file identity filename] :as pdf-current}]
   (let [*doc-ref       (rum/use-ref nil)
   (let [*doc-ref       (rum/use-ref nil)
@@ -861,6 +860,13 @@
         set-hls-extra! (fn [extra]
         set-hls-extra! (fn [extra]
                          (set-hls-state! #(merge % {:extra extra})))]
                          (set-hls-state! #(merge % {:extra extra})))]
 
 
+    ;; current pdf effects
+    (rum/use-effect!
+     (fn []
+       (when pdf-current
+         (pdf-assets/ensure-ref-page! pdf-current)))
+     [pdf-current])
+
     ;; load highlights
     ;; load highlights
     (rum/use-effect!
     (rum/use-effect!
      (fn []
      (fn []
@@ -886,21 +892,25 @@
      [hls-file])
      [hls-file])
 
 
     ;; cache highlights
     ;; cache highlights
-    (rum/use-effect!
-     (fn []
-       (when (= :completed (:status loader-state))
-         (p/catch
-          (when-not (:error hls-state)
-            (p/do!
-              (p/delay 100)
-              (pdf-assets/persist-hls-data$
-                pdf-current (:latest-hls hls-state) (:extra hls-state))))
-
-          ;; write hls file error
-          (fn [e]
-            (js/console.error "[write hls error]" e)))))
-
-     [(:latest-hls hls-state) (:extra hls-state)])
+    (let [persist-hls-data!
+          (rum/use-callback
+           (util/debounce
+            4000 (fn [latest-hls extra]
+                   (pdf-assets/persist-hls-data$
+                    pdf-current latest-hls extra))) [pdf-current])]
+
+      (rum/use-effect!
+       (fn []
+         (when (= :completed (:status loader-state))
+           (p/catch
+            (when-not (:error hls-state)
+              (p/do! (persist-hls-data! (:latest-hls hls-state) (:extra hls-state))))
+
+            ;; write hls file error
+            (fn [e]
+              (js/console.error "[write hls error]" e)))))
+
+       [(:latest-hls hls-state) (:extra hls-state)]))
 
 
     ;; load document
     ;; load document
     (rum/use-effect!
     (rum/use-effect!

+ 4 - 10
src/main/frontend/extensions/video/youtube.cljs

@@ -119,13 +119,13 @@ Remember: You can paste a raw YouTube url as embedded video on mobile."
 
 
 
 
 (defn parse-timestamp [timestamp]
 (defn parse-timestamp [timestamp]
-  (let [reg #"^(?:(\d+):)?([0-5]\d):([0-5]\d)$"
+  (let [reg #"^(?:(\d+):)?([0-5]?\d):([0-5]?\d)$"
         reg-number #"^\d+$"
         reg-number #"^\d+$"
         timestamp (str timestamp)
         timestamp (str timestamp)
         total-seconds (some-> (re-matches reg-number timestamp)
         total-seconds (some-> (re-matches reg-number timestamp)
                               util/safe-parse-int)
                               util/safe-parse-int)
         [_ hours minutes seconds] (re-matches reg timestamp)
         [_ hours minutes seconds] (re-matches reg timestamp)
-        [hours minutes seconds] (map util/safe-parse-int (remove nil? [hours minutes seconds]))]
+        [hours minutes seconds] (map #(if (nil? %) 0 (util/safe-parse-int %)) [hours minutes seconds])]
     (cond
     (cond
       total-seconds
       total-seconds
       total-seconds
       total-seconds
@@ -133,19 +133,13 @@ Remember: You can paste a raw YouTube url as embedded video on mobile."
       (and minutes seconds)
       (and minutes seconds)
       (+ (* 3600 hours) (* 60 minutes) seconds)
       (+ (* 3600 hours) (* 60 minutes) seconds)
 
 
-      minutes
-      (+ (* 3600 hours) (* 60 minutes))
-
-      hours
-      (* 3600 hours)
-
       :else
       :else
       nil)))
       nil)))
 
 
 (comment
 (comment
   ;; hh:mm:ss
   ;; hh:mm:ss
-  (re-matches #"^(?:(\d+):)?([0-5]\d):([0-5]\d)$" "123:22:23") ;; => ["123:22:23" "123" "22" "23"]
-  (re-matches #"^(?:(\d+):)?([0-5]\d):([0-5]\d)$" "30:23") ;; => ["30:23" nil "30" "23"]
+  (re-matches #"^(?:(\d+):)?([0-5]?\d):([0-5]?\d)$" "123:22:23") ;; => ["123:22:23" "123" "22" "23"]
+  (re-matches #"^(?:(\d+):)?([0-5]?\d):([0-5]?\d)$" "30:23") ;; => ["30:23" nil "30" "23"]
 
 
   (parse-timestamp "01:23") ;; => 83
   (parse-timestamp "01:23") ;; => 83
 
 

+ 313 - 262
src/main/frontend/fs/sync.cljs

@@ -193,6 +193,11 @@
 
 
 ;;; ### configs ends
 ;;; ### configs ends
 
 
+(defn- guard-ex
+  [x]
+  (when (instance? ExceptionInfo x) x))
+
+
 (def ws-addr config/WS-URL)
 (def ws-addr config/WS-URL)
 
 
 ;; Warning: make sure to `persist-var/-load` graphs-txid before using it.
 ;; Warning: make sure to `persist-var/-load` graphs-txid before using it.
@@ -232,17 +237,25 @@
     (when-let [ws (:ws @*ws)]
     (when-let [ws (:ws @*ws)]
       (.close ws))))
       (.close ws))))
 
 
+(def *ws-connect-retries (atom 0))
+(declare <sync-stop)
 (defn- ws-listen!*
 (defn- ws-listen!*
   [graph-uuid *ws remote-changes-chan]
   [graph-uuid *ws remote-changes-chan]
   (reset! *ws {:ws (js/WebSocket. (util/format ws-addr graph-uuid)) :stop false})
   (reset! *ws {:ws (js/WebSocket. (util/format ws-addr graph-uuid)) :stop false})
   (ws-ping-loop (:ws @*ws))
   (ws-ping-loop (:ws @*ws))
-  ;; (set! (.-onopen (:ws @*ws)) #(println (util/format "ws opened: graph '%s'" graph-uuid %)))
+  (set! (.-onopen (:ws @*ws)) #(reset! *ws-connect-retries 0))
   (set! (.-onclose (:ws @*ws)) (fn [_e]
   (set! (.-onclose (:ws @*ws)) (fn [_e]
-                                 (when-not (true? (:stop @*ws))
-                                   (go
-                                     (timeout 1000)
-                                     (println "re-connecting graph" graph-uuid)
-                                     (ws-listen!* graph-uuid *ws remote-changes-chan)))))
+                                 (if (> @*ws-connect-retries 3)
+                                   (do (println "ws-listen! retry count > 3, stop retry")
+                                       (reset! *ws-connect-retries 0)
+                                       (swap! *ws (fn [o] (assoc o :stop true)))
+                                       (<sync-stop))
+                                   (when-not (true? (:stop @*ws))
+                                     (go
+                                       (timeout 1000)
+                                       (println "re-connecting graph" graph-uuid)
+                                       (swap! *ws-connect-retries inc)
+                                       (ws-listen!* graph-uuid *ws remote-changes-chan))))))
   (set! (.-onmessage (:ws @*ws)) (fn [e]
   (set! (.-onmessage (:ws @*ws)) (fn [e]
                                    (let [data (js->clj (js/JSON.parse (.-data e)) :keywordize-keys true)]
                                    (let [data (js->clj (js/JSON.parse (.-data e)) :keywordize-keys true)]
                                      (when (some? (:txid data))
                                      (when (some? (:txid data))
@@ -264,7 +277,10 @@
 (defn- get-json-body [body]
 (defn- get-json-body [body]
   (or (and (not (string? body)) body)
   (or (and (not (string? body)) body)
       (or (string/blank? body) nil)
       (or (string/blank? body) nil)
-      (js->clj (js/JSON.parse body) :keywordize-keys true)))
+      (try (js->clj (js/JSON.parse body) :keywordize-keys true)
+           (catch :default e
+             (prn :invalid-json body)
+             e))))
 
 
 (defn- get-resp-json-body [resp]
 (defn- get-resp-json-body [resp]
   (-> resp (:body) (get-json-body)))
   (-> resp (:body) (get-json-body)))
@@ -841,9 +857,8 @@
   (<get-local-all-files-meta [this graph-uuid base-path]
   (<get-local-all-files-meta [this graph-uuid base-path]
     (go
     (go
       (let [r (<! (<retry-rsapi #(p->c (ipc/ipc "get-local-all-files-meta" graph-uuid base-path))))]
       (let [r (<! (<retry-rsapi #(p->c (ipc/ipc "get-local-all-files-meta" graph-uuid base-path))))]
-        (if (instance? ExceptionInfo r)
-          r
-          (<! (<build-local-file-metadatas this graph-uuid r))))))
+        (or (guard-ex r)
+            (<! (<build-local-file-metadatas this graph-uuid r))))))
   (<get-local-files-meta [this graph-uuid base-path filepaths]
   (<get-local-files-meta [this graph-uuid base-path filepaths]
     (go
     (go
       (let [r (<! (<retry-rsapi #(p->c (ipc/ipc "get-local-files-meta" graph-uuid base-path filepaths))))]
       (let [r (<! (<retry-rsapi #(p->c (ipc/ipc "get-local-files-meta" graph-uuid base-path filepaths))))]
@@ -857,20 +872,21 @@
     (println "update-local-files" graph-uuid base-path filepaths)
     (println "update-local-files" graph-uuid base-path filepaths)
     (go
     (go
       (<! (<rsapi-cancel-all-requests))
       (<! (<rsapi-cancel-all-requests))
-      (let [token (<! (<get-token this))]
-        (<! (p->c (ipc/ipc "update-local-files" graph-uuid base-path filepaths token))))))
+      (let [token-or-exp (<! (<get-token this))]
+        (or (guard-ex token-or-exp)
+            (<! (p->c (ipc/ipc "update-local-files" graph-uuid base-path filepaths token-or-exp)))))))
   (<fetch-remote-files [this graph-uuid base-path filepaths]
   (<fetch-remote-files [this graph-uuid base-path filepaths]
     (go
     (go
       (<! (<rsapi-cancel-all-requests))
       (<! (<rsapi-cancel-all-requests))
-      (let [token (<! (<get-token this))]
-        (<! (p->c (ipc/ipc "fetch-remote-files" graph-uuid base-path filepaths token))))))
+      (let [token-or-exp (<! (<get-token this))]
+        (or (guard-ex token-or-exp)
+            (<! (p->c (ipc/ipc "fetch-remote-files" graph-uuid base-path filepaths token-or-exp)))))))
 
 
   (<download-version-files [this graph-uuid base-path filepaths]
   (<download-version-files [this graph-uuid base-path filepaths]
     (go
     (go
-      (let [token (<! (<get-token this))
-            r (<! (<retry-rsapi
-                   #(p->c (ipc/ipc "download-version-files" graph-uuid base-path filepaths token))))]
-        r)))
+      (let [token-or-exp (<! (<get-token this))]
+        (or (guard-ex token-or-exp)
+            (<! (<retry-rsapi #(p->c (ipc/ipc "download-version-files" graph-uuid base-path filepaths token-or-exp))))))))
 
 
   (<delete-local-files [_ graph-uuid base-path filepaths]
   (<delete-local-files [_ graph-uuid base-path filepaths]
     (let [normalized-filepaths (mapv path-normalize filepaths)]
     (let [normalized-filepaths (mapv path-normalize filepaths)]
@@ -883,17 +899,21 @@
     (let [normalized-filepaths (mapv path-normalize filepaths)]
     (let [normalized-filepaths (mapv path-normalize filepaths)]
       (go
       (go
         (<! (<rsapi-cancel-all-requests))
         (<! (<rsapi-cancel-all-requests))
-        (let [token (<! (<get-token this))]
-          (<! (<retry-rsapi
-               #(p->c (ipc/ipc "update-remote-files" graph-uuid base-path normalized-filepaths local-txid token))))))))
+        (let [token-or-exp (<! (<get-token this))]
+          (or (guard-ex token-or-exp)
+              (<! (<retry-rsapi
+                   #(p->c (ipc/ipc "update-remote-files" graph-uuid base-path normalized-filepaths local-txid token-or-exp)))))))))
 
 
   (<delete-remote-files [this graph-uuid base-path filepaths local-txid]
   (<delete-remote-files [this graph-uuid base-path filepaths local-txid]
     (let [normalized-filepaths (mapv path-normalize filepaths)]
     (let [normalized-filepaths (mapv path-normalize filepaths)]
       (go
       (go
-        (let [token (<! (<get-token this))]
-          (<!
-           (<retry-rsapi
-            #(p->c (ipc/ipc "delete-remote-files" graph-uuid base-path normalized-filepaths local-txid token))))))))
+        (let [token-or-exp (<! (<get-token this))]
+          (or (guard-ex token-or-exp)
+              (<!
+               (<retry-rsapi
+                #(p->c
+                  (ipc/ipc "delete-remote-files" graph-uuid base-path normalized-filepaths local-txid token-or-exp)))))))))
+
   (<encrypt-fnames [_ graph-uuid fnames] (go (js->clj (<! (p->c (ipc/ipc "encrypt-fnames" graph-uuid fnames))))))
   (<encrypt-fnames [_ graph-uuid fnames] (go (js->clj (<! (p->c (ipc/ipc "encrypt-fnames" graph-uuid fnames))))))
   (<decrypt-fnames [_ graph-uuid fnames] (go
   (<decrypt-fnames [_ graph-uuid fnames] (go
                                            (let [r (<! (p->c (ipc/ipc "decrypt-fnames" graph-uuid fnames)))]
                                            (let [r (<! (p->c (ipc/ipc "decrypt-fnames" graph-uuid fnames)))]
@@ -931,9 +951,8 @@
     (go
     (go
       (let [r (<! (p->c (.getLocalAllFilesMeta mobile-util/file-sync (clj->js {:graphUUID graph-uuid
       (let [r (<! (p->c (.getLocalAllFilesMeta mobile-util/file-sync (clj->js {:graphUUID graph-uuid
                                                                                :basePath base-path}))))]
                                                                                :basePath base-path}))))]
-        (if (instance? ExceptionInfo r)
-          r
-          (<! (<build-local-file-metadatas this graph-uuid (.-result r)))))))
+        (or (guard-ex r)
+            (<! (<build-local-file-metadatas this graph-uuid (.-result r)))))))
 
 
   (<get-local-files-meta [this graph-uuid base-path filepaths]
   (<get-local-files-meta [this graph-uuid base-path filepaths]
     (go
     (go
@@ -953,32 +972,35 @@
 
 
   (<update-local-files [this graph-uuid base-path filepaths]
   (<update-local-files [this graph-uuid base-path filepaths]
     (go
     (go
-      (let [token (<! (<get-token this))
+      (let [token-or-exp (<! (<get-token this))
             filepaths' (map path-normalize filepaths)]
             filepaths' (map path-normalize filepaths)]
-        (<! (p->c (.updateLocalFiles mobile-util/file-sync (clj->js {:graphUUID graph-uuid
-                                                                     :basePath base-path
-                                                                     :filePaths filepaths'
-                                                                     :token token})))))))
+        (or (guard-ex token-or-exp)
+            (<! (p->c (.updateLocalFiles mobile-util/file-sync (clj->js {:graphUUID graph-uuid
+                                                                         :basePath base-path
+                                                                         :filePaths filepaths'
+                                                                         :token token-or-exp}))))))))
   (<fetch-remote-files [this graph-uuid base-path filepaths]
   (<fetch-remote-files [this graph-uuid base-path filepaths]
     (go
     (go
-      (let [token (<! (<get-token this))
-            r (<! (<retry-rsapi
+      (let [token-or-exp (<! (<get-token this))]
+        (or (guard-ex token-or-exp)
+            (js->clj
+             (.-value
+              (<! (<retry-rsapi
                    #(p->c (.fetchRemoteFiles mobile-util/file-sync
                    #(p->c (.fetchRemoteFiles mobile-util/file-sync
                                              (clj->js {:graphUUID graph-uuid
                                              (clj->js {:graphUUID graph-uuid
                                                        :basePath base-path
                                                        :basePath base-path
                                                        :filePaths filepaths
                                                        :filePaths filepaths
-                                                       :token token})))))]
-        (js->clj (.-value r)))))
+                                                       :token token-or-exp})))))))))))
   (<download-version-files [this graph-uuid base-path filepaths]
   (<download-version-files [this graph-uuid base-path filepaths]
     (go
     (go
-      (let [token (<! (<get-token this))
-            r (<! (<retry-rsapi
-                   #(p->c (.updateLocalVersionFiles mobile-util/file-sync
-                                                    (clj->js {:graphUUID graph-uuid
-                                                              :basePath base-path
-                                                              :filePaths filepaths
-                                                              :token token})))))]
-        r)))
+      (let [token-or-exp (<! (<get-token this))]
+        (or (guard-ex token-or-exp)
+            (<! (<retry-rsapi
+                 #(p->c (.updateLocalVersionFiles mobile-util/file-sync
+                                                  (clj->js {:graphUUID graph-uuid
+                                                            :basePath base-path
+                                                            :filePaths filepaths
+                                                            :token token-or-exp})))))))))
 
 
   (<delete-local-files [_ graph-uuid base-path filepaths]
   (<delete-local-files [_ graph-uuid base-path filepaths]
     (let [normalized-filepaths (mapv path-normalize filepaths)]
     (let [normalized-filepaths (mapv path-normalize filepaths)]
@@ -992,40 +1014,39 @@
   (<update-remote-files [this graph-uuid base-path filepaths local-txid]
   (<update-remote-files [this graph-uuid base-path filepaths local-txid]
     (let [normalized-filepaths (mapv path-normalize filepaths)]
     (let [normalized-filepaths (mapv path-normalize filepaths)]
       (go
       (go
-        (let [token (<! (<get-token this))
-              r (<! (p->c (.updateRemoteFiles mobile-util/file-sync
-                                              (clj->js {:graphUUID graph-uuid
-                                                        :basePath base-path
-                                                        :filePaths normalized-filepaths
-                                                        :txid local-txid
-                                                        :token token
-                                                        :fnameEncryption true}))))]
-          (if (instance? ExceptionInfo r)
-            r
-            (get (js->clj r) "txid"))))))
+        (let [token-or-exp (<! (<get-token this))
+              r (or (guard-ex token-or-exp)
+                    (<! (p->c (.updateRemoteFiles mobile-util/file-sync
+                                                  (clj->js {:graphUUID graph-uuid
+                                                            :basePath base-path
+                                                            :filePaths normalized-filepaths
+                                                            :txid local-txid
+                                                            :token token-or-exp
+                                                            :fnameEncryption true})))))]
+          (or (guard-ex r)
+              (get (js->clj r) "txid"))))))
 
 
   (<delete-remote-files [this graph-uuid base-path filepaths local-txid]
   (<delete-remote-files [this graph-uuid base-path filepaths local-txid]
     (let [normalized-filepaths (mapv path-normalize filepaths)]
     (let [normalized-filepaths (mapv path-normalize filepaths)]
       (go
       (go
-        (let [token (<! (<get-token this))
-              r (<! (p->c (.deleteRemoteFiles mobile-util/file-sync
-                                              (clj->js {:graphUUID graph-uuid
-                                                        :basePath base-path
-                                                        :filePaths normalized-filepaths
-                                                        :txid local-txid
-                                                        :token token}))))]
-          (if (instance? ExceptionInfo r)
-            r
-            (get (js->clj r) "txid"))))))
+        (let [token-or-exp (<! (<get-token this))
+              r (or (guard-ex token-or-exp)
+                    (<! (p->c (.deleteRemoteFiles mobile-util/file-sync
+                                                  (clj->js {:graphUUID graph-uuid
+                                                            :basePath base-path
+                                                            :filePaths normalized-filepaths
+                                                            :txid local-txid
+                                                            :token token-or-exp})))))]
+          (or (guard-ex r)
+              (get (js->clj r) "txid"))))))
 
 
   (<encrypt-fnames [_ graph-uuid fnames]
   (<encrypt-fnames [_ graph-uuid fnames]
     (go
     (go
       (let [r (<! (p->c (.encryptFnames mobile-util/file-sync
       (let [r (<! (p->c (.encryptFnames mobile-util/file-sync
                                         (clj->js {:graphUUID graph-uuid
                                         (clj->js {:graphUUID graph-uuid
                                                   :filePaths fnames}))))]
                                                   :filePaths fnames}))))]
-        (if (instance? ExceptionInfo r)
-          (.-cause r)
-          (get (js->clj r) "value")))))
+        (or (guard-ex r)
+            (get (js->clj r) "value")))))
   (<decrypt-fnames [_ graph-uuid fnames]
   (<decrypt-fnames [_ graph-uuid fnames]
     (go (let [r (<! (p->c (.decryptFnames mobile-util/file-sync
     (go (let [r (<! (p->c (.decryptFnames mobile-util/file-sync
                                           (clj->js {:graphUUID graph-uuid
                                           (clj->js {:graphUUID graph-uuid
@@ -1147,19 +1168,21 @@
 
 
   (<request [this api-name body]
   (<request [this api-name body]
     (go
     (go
-      (let [resp (<! (<request api-name body (<! (<get-token this)) *stopped?))]
-        (if (http/unexceptional-status? (:status resp))
-          (get-resp-json-body resp)
-          (let [exp (ex-info "request failed"
-                             {:err          resp
-                              :body         (:body resp)
-                              :api-name     api-name
-                              :request-body body})]
-            (fire-file-sync-storage-exceed-limit-event! exp)
-            (fire-file-sync-graph-count-exceed-limit-event! exp)
-            exp)))))
-
-  ;; for test
+      (let [token-or-exp (<! (<get-token this))]
+        (or (guard-ex token-or-exp)
+            (let [resp (<! (<request api-name body token-or-exp *stopped?))]
+              (if (http/unexceptional-status? (:status resp))
+                (get-resp-json-body resp)
+                (let [exp (ex-info "request failed"
+                                   {:err          resp
+                                    :body         (:body resp)
+                                    :api-name     api-name
+                                    :request-body body})]
+                  (fire-file-sync-storage-exceed-limit-event! exp)
+                  (fire-file-sync-graph-count-exceed-limit-event! exp)
+                  exp)))))))
+
+;; for test
   (update-files [this graph-uuid txid files]
   (update-files [this graph-uuid txid files]
     {:pre [(map? files)
     {:pre [(map? files)
            (number? txid)]}
            (number? txid)]}
@@ -1232,68 +1255,63 @@
                                       {}
                                       {}
                                       (remove (comp nil? second)
                                       (remove (comp nil? second)
                                               {:GraphUUID graph-uuid :ContinuationToken continuation-token}))))]
                                               {:GraphUUID graph-uuid :ContinuationToken continuation-token}))))]
-                (if (instance? ExceptionInfo r)
-                  r
-                  (let [next-continuation-token (:NextContinuationToken r)
-                        objs                    (:Objects r)]
-                    (apply conj! encrypted-path-list (map (comp remove-user-graph-uuid-prefix :Key) objs))
-                    (apply conj! file-meta-list
-                           (map
-                            #(hash-map :checksum (:checksum %)
-                                       :encrypted-path (remove-user-graph-uuid-prefix (:Key %))
-                                       :size (:Size %)
-                                       :last-modified (:LastModified %)
-                                       :txid (:Txid %))
-                            objs))
-                    (when-not (empty? next-continuation-token)
-                      (recur next-continuation-token)))))))]
-       (if (instance? ExceptionInfo exp-r)
-         exp-r
-         (let [file-meta-list*      (persistent! file-meta-list)
-               encrypted-path-list* (persistent! encrypted-path-list)
-               path-list-or-exp     (<! (<decrypt-fnames rsapi graph-uuid encrypted-path-list*))]
-           (if (instance? ExceptionInfo path-list-or-exp)
-             path-list-or-exp
-             (let [encrypted-path->path-map (zipmap encrypted-path-list* path-list-or-exp)]
-               (set
-                (mapv
-                 #(->FileMetadata (:size %)
-                                  (:checksum %)
-                                  (get encrypted-path->path-map (:encrypted-path %))
-                                  (:encrypted-path %)
-                                  (:last-modified %)
-                                  true
-                                  (:txid %)
-                                  nil)
-                 (-> file-meta-list*
-                     (filter-files-with-unnormalized-path encrypted-path->path-map)
-                     (filter-case-different-same-files encrypted-path->path-map)))))))))))
+                (or (guard-ex r)
+                    (let [next-continuation-token (:NextContinuationToken r)
+                          objs                    (:Objects r)]
+                      (apply conj! encrypted-path-list (map (comp remove-user-graph-uuid-prefix :Key) objs))
+                      (apply conj! file-meta-list
+                             (map
+                              #(hash-map :checksum (:checksum %)
+                                         :encrypted-path (remove-user-graph-uuid-prefix (:Key %))
+                                         :size (:Size %)
+                                         :last-modified (:LastModified %)
+                                         :txid (:Txid %))
+                              objs))
+                      (when-not (empty? next-continuation-token)
+                        (recur next-continuation-token)))))))]
+       (or (guard-ex exp-r)
+           (let [file-meta-list*      (persistent! file-meta-list)
+                 encrypted-path-list* (persistent! encrypted-path-list)
+                 path-list-or-exp     (<! (<decrypt-fnames rsapi graph-uuid encrypted-path-list*))]
+             (or (guard-ex path-list-or-exp)
+                 (let [encrypted-path->path-map (zipmap encrypted-path-list* path-list-or-exp)]
+                   (set
+                    (mapv
+                     #(->FileMetadata (:size %)
+                                      (:checksum %)
+                                      (get encrypted-path->path-map (:encrypted-path %))
+                                      (:encrypted-path %)
+                                      (:last-modified %)
+                                      true
+                                      (:txid %)
+                                      nil)
+                     (-> file-meta-list*
+                         (filter-files-with-unnormalized-path encrypted-path->path-map)
+                         (filter-case-different-same-files encrypted-path->path-map)))))))))))
 
 
   (<get-remote-files-meta [this graph-uuid filepaths]
   (<get-remote-files-meta [this graph-uuid filepaths]
     {:pre [(coll? filepaths)]}
     {:pre [(coll? filepaths)]}
     (user/<wrap-ensure-id&access-token
     (user/<wrap-ensure-id&access-token
      (let [encrypted-paths* (<! (<encrypt-fnames rsapi graph-uuid filepaths))
      (let [encrypted-paths* (<! (<encrypt-fnames rsapi graph-uuid filepaths))
            r                (<! (.<request this "get_files_meta" {:GraphUUID graph-uuid :Files encrypted-paths*}))]
            r                (<! (.<request this "get_files_meta" {:GraphUUID graph-uuid :Files encrypted-paths*}))]
-       (if (instance? ExceptionInfo r)
-         r
-         (let [encrypted-paths (mapv :FilePath r)
-               paths-or-exp    (<! (<decrypt-fnames rsapi graph-uuid encrypted-paths))]
-           (if (instance? ExceptionInfo paths-or-exp)
-             paths-or-exp
-             (let [encrypted-path->path-map (zipmap encrypted-paths paths-or-exp)]
-               (into #{}
-                     (comp
-                      (filter #(not= "filepath too long" (:Error %)))
-                      (map #(->FileMetadata (:Size %)
-                                            (:Checksum %)
-                                            (some->> (get encrypted-path->path-map (:FilePath %))
-                                                     path-normalize)
-                                            (:FilePath %)
-                                            (:LastModified %)
-                                            true
-                                            (:Txid %)
-                                            nil)))
-                     r))))))))
+       (or (guard-ex r)
+           (let [encrypted-paths (mapv :FilePath r)
+                 paths-or-exp    (<! (<decrypt-fnames rsapi graph-uuid encrypted-paths))]
+             (or (guard-ex paths-or-exp)
+                 (let [encrypted-path->path-map (zipmap encrypted-paths paths-or-exp)]
+                   (into #{}
+                         (comp
+                          (filter #(not= "filepath too long" (:Error %)))
+                          (map #(->FileMetadata (:Size %)
+                                                (:Checksum %)
+                                                (some->> (get encrypted-path->path-map (:FilePath %))
+                                                         path-normalize)
+                                                (:FilePath %)
+                                                (:LastModified %)
+                                                true
+                                                (:Txid %)
+                                                nil)))
+                         r))))))))
 
 
   (<get-remote-graph [this graph-name-opt graph-uuid-opt]
   (<get-remote-graph [this graph-name-opt graph-uuid-opt]
     {:pre [(or graph-name-opt graph-uuid-opt)]}
     {:pre [(or graph-name-opt graph-uuid-opt)]}
@@ -1320,23 +1338,22 @@
   (<get-deletion-logs [this graph-uuid from-txid]
   (<get-deletion-logs [this graph-uuid from-txid]
     (user/<wrap-ensure-id&access-token
     (user/<wrap-ensure-id&access-token
      (let [r (<! (.<request this "get_deletion_log_v20221212" {:GraphUUID graph-uuid :FromTXId from-txid}))]
      (let [r (<! (.<request this "get_deletion_log_v20221212" {:GraphUUID graph-uuid :FromTXId from-txid}))]
-       (if (instance? ExceptionInfo r)
-         r
-         (let [txns-with-encrypted-paths (mapv (fn [txn]
-                                                 (assoc txn :paths
-                                                        (mapv remove-user-graph-uuid-prefix (:paths txn))))
-                                               (:Transactions r))
-               encrypted-paths           (mapcat :paths txns-with-encrypted-paths)
-               encrypted-path->path-map
-               (zipmap
-                encrypted-paths
-                (<! (<decrypt-fnames rsapi graph-uuid encrypted-paths)))
-               txns
-               (mapv
-                (fn [txn]
-                  (assoc txn :paths (mapv #(get encrypted-path->path-map %) (:paths txn))))
-                txns-with-encrypted-paths)]
-           txns)))))
+       (or (guard-ex r)
+           (let [txns-with-encrypted-paths (mapv (fn [txn]
+                                                   (assoc txn :paths
+                                                          (mapv remove-user-graph-uuid-prefix (:paths txn))))
+                                                 (:Transactions r))
+                 encrypted-paths           (mapcat :paths txns-with-encrypted-paths)
+                 encrypted-path->path-map
+                 (zipmap
+                  encrypted-paths
+                  (<! (<decrypt-fnames rsapi graph-uuid encrypted-paths)))
+                 txns
+                 (mapv
+                  (fn [txn]
+                    (assoc txn :paths (mapv #(get encrypted-path->path-map %) (:paths txn))))
+                  txns-with-encrypted-paths)]
+             txns)))))
 
 
   (<get-diff [this graph-uuid from-txid]
   (<get-diff [this graph-uuid from-txid]
     ;; TODO: path in transactions should be relative path(now s3 key, which includes graph-uuid and user-uuid)
     ;; TODO: path in transactions should be relative path(now s3 key, which includes graph-uuid and user-uuid)
@@ -1418,9 +1435,11 @@
      (let [partitioned-files (partition-all 20 (<! (<encrypt-fnames rsapi graph-uuid filepaths)))]
      (let [partitioned-files (partition-all 20 (<! (<encrypt-fnames rsapi graph-uuid filepaths)))]
        (loop [[files & others] partitioned-files]
        (loop [[files & others] partitioned-files]
          (when files
          (when files
-           (let [current-txid (:TXId (<! (<get-remote-txid this graph-uuid)))]
-             (<! (.<request this "delete_files" {:GraphUUID graph-uuid :TXId current-txid :Files files}))
-             (recur others))))))))
+           (let [r (<! (<get-remote-txid this graph-uuid))]
+             (or (guard-ex r)
+                 (let [current-txid (:TXId r)]
+                   (<! (.<request this "delete_files" {:GraphUUID graph-uuid :TXId current-txid :Files files}))
+                   (recur others))))))))))
 
 
 (comment
 (comment
   (declare remoteapi)
   (declare remoteapi)
@@ -1445,8 +1464,9 @@
       (if (< now expired-at)
       (if (< now expired-at)
         r
         r
         (let [r (<! (<get-graph-salt remoteapi graph-uuid))]
         (let [r (<! (<get-graph-salt remoteapi graph-uuid))]
-          (swap! *get-graph-salt-memoize-cache conj [graph-uuid r])
-          r)))))
+          (or (guard-ex r)
+              (do (swap! *get-graph-salt-memoize-cache conj [graph-uuid r])
+                  r)))))))
 
 
 (def ^:private *get-graph-encrypt-keys-memoize-cache (atom {}))
 (def ^:private *get-graph-encrypt-keys-memoize-cache (atom {}))
 (defn update-graph-encrypt-keys-cache [graph-uuid v]
 (defn update-graph-encrypt-keys-cache [graph-uuid v]
@@ -1512,9 +1532,11 @@
 (defn- assert-local-txid<=remote-txid
 (defn- assert-local-txid<=remote-txid
   []
   []
   (when-let [local-txid (last @graphs-txid)]
   (when-let [local-txid (last @graphs-txid)]
-    (go (let [remote-txid (:TXId (<! (<get-remote-txid remoteapi (second @graphs-txid))))]
-          (assert (<= local-txid remote-txid)
-                  [@graphs-txid local-txid remote-txid])))))
+    (go (let [r (<! (<get-remote-txid remoteapi (second @graphs-txid)))]
+          (when-not (guard-ex r)
+            (let [remote-txid (:TXId r)]
+              (assert (<= local-txid remote-txid)
+                      [@graphs-txid local-txid remote-txid])))))))
 
 
 (defn- get-local-files-checksum
 (defn- get-local-files-checksum
   [graph-uuid base-path relative-paths]
   [graph-uuid base-path relative-paths]
@@ -2034,17 +2056,17 @@
   "- persist encrypted pwd at local-storage"
   "- persist encrypted pwd at local-storage"
   [pwd graph-uuid]
   [pwd graph-uuid]
   (go
   (go
-    (let [[value expired-at gone?]
-          ((juxt :value :expired-at #(-> % ex-data :err :status (= 410)))
-           (<! (<get-graph-salt-memoize remoteapi graph-uuid)))
-          [salt-value _expired-at]
-          (if gone?
-            (let [r (<! (<create-graph-salt remoteapi graph-uuid))]
-              (update-graph-salt-cache graph-uuid r)
-              ((juxt :value :expired-at) r))
-            [value expired-at])
-          encrypted-pwd (<! (<encrypt-content pwd salt-value))]
-      (persist-pwd! encrypted-pwd graph-uuid))))
+    (let [[value _expired-at gone?] ((juxt :value :expired-at #(-> % ex-data :err :status (= 410)))
+                                     (<! (<get-graph-salt-memoize remoteapi graph-uuid)))]
+      (if gone?
+        (let [r (<! (<create-graph-salt remoteapi graph-uuid))]
+          (or (guard-ex r)
+              (do (update-graph-salt-cache graph-uuid r)
+                  (let [[salt-value _expired-at] ((juxt :value :expired-at) r)
+                        encrypted-pwd (<! (<encrypt-content pwd salt-value))]
+                    (persist-pwd! encrypted-pwd graph-uuid)))))
+        (let [encrypted-pwd (<! (<encrypt-content pwd value))]
+          (persist-pwd! encrypted-pwd graph-uuid))))))
 
 
 (defn restore-pwd!
 (defn restore-pwd!
   "restore pwd from persisted encrypted-pwd, update `pwd-map`"
   "restore pwd from persisted encrypted-pwd, update `pwd-map`"
@@ -2386,8 +2408,8 @@
   if local-txid != remote-txid, return {:need-sync-remote true}"))
   if local-txid != remote-txid, return {:need-sync-remote true}"))
 
 
 (defrecord ^:large-vars/cleanup-todo
 (defrecord ^:large-vars/cleanup-todo
-  Remote->LocalSyncer [user-uuid graph-uuid base-path repo *txid *txid-for-get-deletion-log *sync-state remoteapi
-                       ^:mutable local->remote-syncer *stopped *paused]
+ Remote->LocalSyncer [user-uuid graph-uuid base-path repo *txid *txid-for-get-deletion-log *sync-state remoteapi
+                      ^:mutable local->remote-syncer *stopped *paused]
   Object
   Object
   (set-local->remote-syncer! [_ s] (set! local->remote-syncer s))
   (set-local->remote-syncer! [_ s] (set! local->remote-syncer s))
   (sync-files-remote->local!
   (sync-files-remote->local!
@@ -2426,34 +2448,33 @@
     (go
     (go
       (let [r
       (let [r
             (let [diff-r (<! (<get-diff remoteapi graph-uuid @*txid))]
             (let [diff-r (<! (<get-diff remoteapi graph-uuid @*txid))]
-              (if (instance? ExceptionInfo diff-r)
-                diff-r
-                (let [[diff-txns latest-txid min-txid] diff-r]
-                  (if (> (dec min-txid) @*txid) ;; min-txid-1 > @*txid, need to remote->local-full-sync
-                    (do (println "min-txid" min-txid "request-txid" @*txid)
-                        {:need-remote->local-full-sync true})
-
-                    (when (pos-int? latest-txid)
-                      (let [filtered-diff-txns (-> (transduce (diffs->filetxns) conj '() (reverse diff-txns))
-                                                   filter-download-files-with-reserved-chars)
-                            partitioned-filetxns (transduce (partition-filetxns download-batch-size)
-                                                            (completing (fn [r i] (conj r (reverse i)))) ;reverse
-                                                            '()
-                                                            filtered-diff-txns)]
-                        (put-sync-event! {:event :start
-                                          :data  {:type       :remote->local
-                                                  :graph-uuid graph-uuid
-                                                  :full-sync? false
-                                                  :epoch      (tc/to-epoch (t/now))}})
-                        (if (empty? (flatten partitioned-filetxns))
-                          (do
-                            (swap! *sync-state #(sync-state-reset-full-remote->local-files % []))
-                            (<! (<update-graphs-txid! latest-txid graph-uuid user-uuid repo))
-                            (reset! *txid latest-txid)
-                            {:succ true})
-                          (<! (apply-filetxns-partitions
-                               *sync-state user-uuid graph-uuid base-path
-                               partitioned-filetxns repo *txid *stopped *paused false)))))))))]
+              (or (guard-ex diff-r)
+                  (let [[diff-txns latest-txid min-txid] diff-r]
+                    (if (> (dec min-txid) @*txid) ;; min-txid-1 > @*txid, need to remote->local-full-sync
+                      (do (println "min-txid" min-txid "request-txid" @*txid)
+                          {:need-remote->local-full-sync true})
+
+                      (when (pos-int? latest-txid)
+                        (let [filtered-diff-txns (-> (transduce (diffs->filetxns) conj '() (reverse diff-txns))
+                                                     filter-download-files-with-reserved-chars)
+                              partitioned-filetxns (transduce (partition-filetxns download-batch-size)
+                                                              (completing (fn [r i] (conj r (reverse i)))) ;reverse
+                                                              '()
+                                                              filtered-diff-txns)]
+                          (put-sync-event! {:event :start
+                                            :data  {:type       :remote->local
+                                                    :graph-uuid graph-uuid
+                                                    :full-sync? false
+                                                    :epoch      (tc/to-epoch (t/now))}})
+                          (if (empty? (flatten partitioned-filetxns))
+                            (do
+                              (swap! *sync-state #(sync-state-reset-full-remote->local-files % []))
+                              (<! (<update-graphs-txid! latest-txid graph-uuid user-uuid repo))
+                              (reset! *txid latest-txid)
+                              {:succ true})
+                            (<! (apply-filetxns-partitions
+                                 *sync-state user-uuid graph-uuid base-path
+                                 partitioned-filetxns repo *txid *stopped *paused false)))))))))]
         (cond
         (cond
           (instance? ExceptionInfo r)       {:unknown r}
           (instance? ExceptionInfo r)       {:unknown r}
           @*stopped                         {:stop true}
           @*stopped                         {:stop true}
@@ -2468,7 +2489,8 @@
             remote-all-files-meta-or-exp (<! remote-all-files-meta-c)]
             remote-all-files-meta-or-exp (<! remote-all-files-meta-c)]
         (if (or (storage-exceed-limit? remote-all-files-meta-or-exp)
         (if (or (storage-exceed-limit? remote-all-files-meta-or-exp)
                 (sync-stop-when-api-flying? remote-all-files-meta-or-exp)
                 (sync-stop-when-api-flying? remote-all-files-meta-or-exp)
-                (decrypt-exp? remote-all-files-meta-or-exp))
+                (decrypt-exp? remote-all-files-meta-or-exp)
+                (instance? ExceptionInfo remote-all-files-meta-or-exp))
           (do (put-sync-event! {:event :exception-decrypt-failed
           (do (put-sync-event! {:event :exception-decrypt-failed
                                 :data  {:graph-uuid graph-uuid
                                 :data  {:graph-uuid graph-uuid
                                         :exp        remote-all-files-meta-or-exp
                                         :exp        remote-all-files-meta-or-exp
@@ -2478,11 +2500,11 @@
                 local-all-files-meta    (<! local-all-files-meta-c)
                 local-all-files-meta    (<! local-all-files-meta-c)
                 {diff-remote-files :result elapsed-time :time}
                 {diff-remote-files :result elapsed-time :time}
                 (util/with-time (diff-file-metadata-sets remote-all-files-meta local-all-files-meta))
                 (util/with-time (diff-file-metadata-sets remote-all-files-meta local-all-files-meta))
-                 _ (println ::diff-file-metadata-sets-elapsed-time elapsed-time "ms")
+                _ (println ::diff-file-metadata-sets-elapsed-time elapsed-time "ms")
                 recent-10-days-range    ((juxt #(tc/to-long (t/minus % (t/days 10))) #(tc/to-long %)) (t/today))
                 recent-10-days-range    ((juxt #(tc/to-long (t/minus % (t/days 10))) #(tc/to-long %)) (t/today))
                 sorted-diff-remote-files
                 sorted-diff-remote-files
-                                        (sort-by
-                                         (sort-file-metadata-fn :recent-days-range recent-10-days-range) > diff-remote-files)
+                (sort-by
+                 (sort-file-metadata-fn :recent-days-range recent-10-days-range) > diff-remote-files)
                 remote-txid-or-ex       (<! (<get-remote-txid remoteapi graph-uuid))
                 remote-txid-or-ex       (<! (<get-remote-txid remoteapi graph-uuid))
                 latest-txid             (:TXId remote-txid-or-ex)]
                 latest-txid             (:TXId remote-txid-or-ex)]
             (if (or (instance? ExceptionInfo remote-txid-or-ex) (nil? latest-txid))
             (if (or (instance? ExceptionInfo remote-txid-or-ex) (nil? latest-txid))
@@ -2769,7 +2791,8 @@
         (cond
         (cond
           (or (storage-exceed-limit? remote-all-files-meta-or-exp)
           (or (storage-exceed-limit? remote-all-files-meta-or-exp)
               (sync-stop-when-api-flying? remote-all-files-meta-or-exp)
               (sync-stop-when-api-flying? remote-all-files-meta-or-exp)
-              (decrypt-exp? remote-all-files-meta-or-exp))
+              (decrypt-exp? remote-all-files-meta-or-exp)
+              (instance? ExceptionInfo remote-all-files-meta-or-exp))
           (do (put-sync-event! {:event :get-remote-all-files-failed
           (do (put-sync-event! {:event :get-remote-all-files-failed
                                 :data  {:graph-uuid graph-uuid
                                 :data  {:graph-uuid graph-uuid
                                         :exp        remote-all-files-meta-or-exp
                                         :exp        remote-all-files-meta-or-exp
@@ -2931,7 +2954,8 @@
           remote->local
           remote->local
           (let [txid
           (let [txid
                 (if (true? remote->local)
                 (if (true? remote->local)
-                  {:txid (:TXId (<! (<get-remote-txid remoteapi graph-uuid)))}
+                  (let [r (<! (<get-remote-txid remoteapi graph-uuid))]
+                    (when-not (guard-ex r) {:txid (:TXId r)}))
                   remote->local)]
                   remote->local)]
             (when (some? txid)
             (when (some? txid)
               (>! ops-chan {:remote->local txid}))
               (>! ops-chan {:remote->local txid}))
@@ -3257,8 +3281,6 @@
     (reset! current-sm-graph-uuid graph-uuid)
     (reset! current-sm-graph-uuid graph-uuid)
     (sync-manager user-uuid graph-uuid base-path repo txid *sync-state)))
     (sync-manager user-uuid graph-uuid base-path repo txid *sync-state)))
 
 
-;; Avoid sync reentrancy
-(defonce *sync-entered? (atom false))
 
 
 (defn <sync-stop []
 (defn <sync-stop []
   (go
   (go
@@ -3269,8 +3291,6 @@
 
 
       (<! (-stop! sm))
       (<! (-stop! sm))
 
 
-      (reset! *sync-entered? false)
-
       (println "[SyncManager]" "stopped"))
       (println "[SyncManager]" "stopped"))
 
 
     (reset! current-sm-graph-uuid nil)))
     (reset! current-sm-graph-uuid nil)))
@@ -3337,69 +3357,93 @@
   []
   []
   (go
   (go
     (let [api-url (str "https://" config/API-DOMAIN "/logseq/version")
     (let [api-url (str "https://" config/API-DOMAIN "/logseq/version")
-          r1 (http/get api-url)
-          r2 (http/get config/CONNECTIVITY-TESTING-S3-URL)
+          r1 (http/get api-url {:with-credentials? false})
+          r2 (http/get config/CONNECTIVITY-TESTING-S3-URL {:with-credentials? false})
           r1* (<! r1)
           r1* (<! r1)
           r2* (<! r2)
           r2* (<! r2)
           ok? (and (= 200 (:status r1*))
           ok? (and (= 200 (:status r1*))
                    (= 200 (:status r2*))
                    (= 200 (:status r2*))
                    (= "OK" (:body r2*)))]
                    (= "OK" (:body r2*)))]
       (if ok?
       (if ok?
-        (println :connectivity-testing-succ)
-        (notification/show! (str (t :file-sync/connectivity-testing-failed)
-                                 (print-str [config/CONNECTIVITY-TESTING-S3-URL api-url])) :warning false))
+        (notification/clear! :sync-connection-failed)
+        (notification/show! [:div
+                             (t :file-sync/connectivity-testing-failed)
+                             [:a {:href api-url} api-url]
+                             " and "
+                             [:a
+                              {:href config/CONNECTIVITY-TESTING-S3-URL}
+                              config/CONNECTIVITY-TESTING-S3-URL]]
+                            :warning
+                            false
+                            :sync-connection-failed))
       ok?)))
       ok?)))
 
 
 (declare network-online-cursor)
 (declare network-online-cursor)
 
 
+(def ^:private *sync-starting
+  "Avoid running multiple sync instances simultaneously."
+  (atom false))
+
+(defn- <should-start-sync?
+  []
+  (go
+    (and (state/enable-sync?)
+         @network-online-cursor     ;; is online
+         (user/has-refresh-token?)  ;; has refresh token, should bring up sync
+         (or (= ::stop (:state (state/get-file-sync-state))) ;; state=stopped
+             (nil? (state/get-file-sync-state)))  ;; the whole sync state not inited yet, happens when app starts without network
+         (<! (p->c (persist-var/-load graphs-txid))))))  ;; not a sync graph))
+
 (defn <sync-start
 (defn <sync-start
   []
   []
   (go
   (go
-    (when (and (state/enable-sync?)
-               (false? @*sync-entered?)
-               (<! (<connectivity-testing)))
-      (reset! *sync-entered? true)
-      (let [*sync-state                 (atom (sync-state))
-            current-user-uuid           (<! (user/<user-uuid))
+    (when-not @*sync-starting
+      (reset! *sync-starting true)
+      (if-not (and (<! (<should-start-sync?))
+                   (<! (<connectivity-testing)))
+        (reset! *sync-starting false)
+        (try
+          (let [*sync-state                 (atom (sync-state))
+                current-user-uuid           (<! (user/<user-uuid))
               ;; put @graph-uuid & get-current-repo together,
               ;; put @graph-uuid & get-current-repo together,
               ;; prevent to get older repo dir and current graph-uuid.
               ;; prevent to get older repo dir and current graph-uuid.
-            _                           (<! (p->c (persist-var/-load graphs-txid)))
-            [user-uuid graph-uuid txid] @graphs-txid
-            txid                        (or txid 0)
-            repo                        (state/get-current-repo)]
-        (when-not (instance? ExceptionInfo current-user-uuid)
-          (when (and repo
-                     @network-online-cursor
-                     user-uuid graph-uuid txid
-                     (graph-sync-off? graph-uuid)
-                     (user/logged-in?)
-                     (not (config/demo-graph? repo)))
-            (try
-              (when-let [sm (sync-manager-singleton current-user-uuid graph-uuid
-                                                    (config/get-repo-dir repo) repo
-                                                    txid *sync-state)]
-                (when (check-graph-belong-to-current-user current-user-uuid user-uuid)
-                  (if-not (<! (<check-remote-graph-exists graph-uuid)) ; remote graph has been deleted
-                    (clear-graphs-txid! repo)
-                    (do
-                      (state/set-file-sync-state graph-uuid @*sync-state)
-                      (state/set-file-sync-manager graph-uuid sm)
-
-                        ;; update global state when *sync-state changes
-                      (add-watch *sync-state ::update-global-state
-                                 (fn [_ _ _ n]
-                                   (state/set-file-sync-state graph-uuid n)))
-
-                      (state/set-state! [:file-sync/graph-state :current-graph-uuid] graph-uuid)
-
-                      (.start sm)
-
-                      (offer! remote->local-full-sync-chan true)
-                      (offer! full-sync-chan true)))))
-              (catch :default e
-                (prn "Sync start error: ")
-                (log/error :exception e)))))
-        (reset! *sync-entered? false)))))
+                _                           (<! (p->c (persist-var/-load graphs-txid)))
+                [user-uuid graph-uuid txid] @graphs-txid
+                txid                        (or txid 0)
+                repo                        (state/get-current-repo)]
+            (when-not (instance? ExceptionInfo current-user-uuid)
+              (when (and repo
+                         @network-online-cursor
+                         user-uuid graph-uuid txid
+                         (graph-sync-off? graph-uuid)
+                         (user/logged-in?)
+                         (not (config/demo-graph? repo)))
+                (when-let [sm (sync-manager-singleton current-user-uuid graph-uuid
+                                                      (config/get-repo-dir repo) repo
+                                                      txid *sync-state)]
+                  (when (check-graph-belong-to-current-user current-user-uuid user-uuid)
+                    (if-not (<! (<check-remote-graph-exists graph-uuid)) ; remote graph has been deleted
+                      (clear-graphs-txid! repo)
+                      (do
+                        (state/set-file-sync-state graph-uuid @*sync-state)
+                        (state/set-file-sync-manager graph-uuid sm)
+
+                      ;; update global state when *sync-state changes
+                        (add-watch *sync-state ::update-global-state
+                                   (fn [_ _ _ n]
+                                     (state/set-file-sync-state graph-uuid n)))
+
+                        (state/set-state! [:file-sync/graph-state :current-graph-uuid] graph-uuid)
+
+                        (.start sm)
+
+                        (offer! remote->local-full-sync-chan true)
+                        (offer! full-sync-chan true))))))))
+          (catch :default e
+            (prn "Sync start error: ")
+            (log/error :exception e))
+          (finally
+            (reset! *sync-starting false)))))))
 
 
 (defn- restart-if-stopped!
 (defn- restart-if-stopped!
   [is-active?]
   [is-active?]
@@ -3469,6 +3513,13 @@
              (when (nil? n)
              (when (nil? n)
                (<sync-stop))))
                (<sync-stop))))
 
 
+;; try to re-start sync when state=stopped every 1min
+(go-loop []
+  (<! (timeout 60000))
+  (when (<! (<should-start-sync?))
+    (println "trying to restart sync..." (tc/to-string (t/now)))
+    (<sync-start))
+  (recur))
 
 
 
 
 ;;; ### some sync events handler
 ;;; ### some sync events handler

+ 1 - 1
src/main/frontend/handler/dnd.cljs

@@ -1,5 +1,5 @@
 (ns frontend.handler.dnd
 (ns frontend.handler.dnd
-  "Provides fns for drag n drop"
+  "Provides fns for drag and drop"
   (:require [frontend.handler.editor :as editor-handler]
   (:require [frontend.handler.editor :as editor-handler]
             [frontend.handler.editor.property :as editor-property]
             [frontend.handler.editor.property :as editor-property]
             [frontend.modules.outliner.core :as outliner-core]
             [frontend.modules.outliner.core :as outliner-core]

+ 6 - 3
src/main/frontend/handler/editor.cljs

@@ -205,7 +205,7 @@
   ; (js/console.log "db-entity/string" (db/entity [:block/uuid (str block-id)]))
   ; (js/console.log "db-entity/string" (db/entity [:block/uuid (str block-id)]))
   ; (js/console.log "db-entity/uuid" (db/entity [:block/uuid (uuid block-id)]))
   ; (js/console.log "db-entity/uuid" (db/entity [:block/uuid (uuid block-id)]))
   (when block-id
   (when block-id
-    (when-let [block (db/entity [:block/uuid block-id])]
+    (when-let [block (db/entity (if (number? block-id) block-id [:block/uuid block-id]))]
       (let [page? (nil? (:block/page block))]
       (let [page? (nil? (:block/page block))]
         (state/sidebar-add-block!
         (state/sidebar-add-block!
          (state/get-current-repo)
          (state/get-current-repo)
@@ -2564,7 +2564,7 @@
             :down util/get-next-block-non-collapsed)
             :down util/get-next-block-non-collapsed)
         sibling-block (f (gdom/getElement (state/get-editing-block-dom-id)))
         sibling-block (f (gdom/getElement (state/get-editing-block-dom-id)))
         {:block/keys [uuid content format]} (state/get-edit-block)]
         {:block/keys [uuid content format]} (state/get-edit-block)]
-    (when sibling-block
+    (if sibling-block
       (when-let [sibling-block-id (dom/attr sibling-block "blockid")]
       (when-let [sibling-block-id (dom/attr sibling-block "blockid")]
         (let [value (state/get-edit-content)]
         (let [value (state/get-edit-content)]
           (when (not= (clean-content! format content)
           (when (not= (clean-content! format content)
@@ -2576,7 +2576,10 @@
               block (db/pull repo '[*] [:block/uuid new-uuid])]
               block (db/pull repo '[*] [:block/uuid new-uuid])]
           (edit-block! block
           (edit-block! block
                        [direction line-pos]
                        [direction line-pos]
-                       new-id))))))
+                       new-id)))
+      (case direction
+        :up (cursor/move-cursor-to input 0)
+        :down (cursor/move-cursor-to-end input)))))
 
 
 (defn keydown-up-down-handler
 (defn keydown-up-down-handler
   [direction]
   [direction]

+ 8 - 3
src/main/frontend/handler/file_sync.cljs

@@ -91,14 +91,19 @@
 
 
 (defn <list-graphs
 (defn <list-graphs
   []
   []
-  (go (:Graphs (<! (sync/<list-remote-graphs sync/remoteapi)))))
+  (go
+    (let [r (<! (sync/<list-remote-graphs sync/remoteapi))]
+      (if (instance? ExceptionInfo r)
+        r
+        (:Graphs r)))))
 
 
 (defn load-session-graphs
 (defn load-session-graphs
   []
   []
   (when-not (state/sub [:file-sync/remote-graphs :loading])
   (when-not (state/sub [:file-sync/remote-graphs :loading])
     (go (state/set-state! [:file-sync/remote-graphs :loading] true)
     (go (state/set-state! [:file-sync/remote-graphs :loading] true)
-        (let [graphs (<! (<list-graphs))]
-          (state/set-state! :file-sync/remote-graphs {:loading false :graphs graphs})))))
+        (let [graphs-or-exp (<! (<list-graphs))]
+          (when-not (instance? ExceptionInfo graphs-or-exp)
+            (state/set-state! :file-sync/remote-graphs {:loading false :graphs graphs-or-exp}))))))
 
 
 (defn reset-session-graphs
 (defn reset-session-graphs
   []
   []

+ 5 - 3
src/main/frontend/handler/route.cljs

@@ -77,11 +77,13 @@
      (recent-handler/add-page-to-recent! (state/get-current-repo) page-name
      (recent-handler/add-page-to-recent! (state/get-current-repo) page-name
                                          click-from-recent?)
                                          click-from-recent?)
      (let [m (cond->
      (let [m (cond->
-              (default-page-route page-name)
+               (default-page-route page-name)
+
                anchor
                anchor
                (assoc :query-params {:anchor anchor})
                (assoc :query-params {:anchor anchor})
-               push
-               (assoc :push push))]
+
+              (boolean? push)
+              (assoc :push push))]
        (redirect! m)))))
        (redirect! m)))))
 
 
 (defn redirect-to-whiteboard!
 (defn redirect-to-whiteboard!

+ 11 - 5
src/main/frontend/handler/user.cljs

@@ -12,7 +12,8 @@
             [cljs.core.async :as async :refer [go <!]]
             [cljs.core.async :as async :refer [go <!]]
             [goog.crypt.Sha256]
             [goog.crypt.Sha256]
             [goog.crypt.Hmac]
             [goog.crypt.Hmac]
-            [goog.crypt :as crypt]))
+            [goog.crypt :as crypt]
+            [frontend.handler.notification :as notification]))
 
 
 (defn set-preferred-format!
 (defn set-preferred-format!
   [format]
   [format]
@@ -143,7 +144,7 @@
           nil                           ; do nothing
           nil                           ; do nothing
 
 
           (not (http/unexceptional-status? (:status resp)))
           (not (http/unexceptional-status? (:status resp)))
-          (clear-tokens true)
+          (notification/show! "exceptional status when refresh-token" :warning true)
 
 
           :else                         ; ok
           :else                         ; ok
           (when (and (:id_token (:body resp)) (:access_token (:body resp)))
           (when (and (:id_token (:body resp)) (:access_token (:body resp)))
@@ -160,6 +161,11 @@
         ;; refresh remote graph list by pub login event
         ;; refresh remote graph list by pub login event
         (when (user-uuid) (state/pub-event! [:user/fetch-info-and-graphs]))))))
         (when (user-uuid) (state/pub-event! [:user/fetch-info-and-graphs]))))))
 
 
+(defn has-refresh-token?
+  "Has refresh-token"
+  []
+  (boolean (js/localStorage.getItem "refresh-token")))
+
 (defn login-callback
 (defn login-callback
   [session]
   [session]
   (set-tokens!
   (set-tokens!
@@ -199,14 +205,14 @@
   (state/clear-user-info!)
   (state/clear-user-info!)
   (state/pub-event! [:user/logout]))
   (state/pub-event! [:user/logout]))
 
 
-(defn upgrade [] 
+(defn upgrade []
   (let [base-upgrade-url "https://logseqdemo.lemonsqueezy.com/checkout/buy/13e194b5-c927-41a8-af58-ed1a36d6000d"
   (let [base-upgrade-url "https://logseqdemo.lemonsqueezy.com/checkout/buy/13e194b5-c927-41a8-af58-ed1a36d6000d"
         user-uuid (user-uuid)
         user-uuid (user-uuid)
         url (cond-> base-upgrade-url
         url (cond-> base-upgrade-url
               user-uuid (str "?checkout[custom][user_uuid]=" (name user-uuid)))]
               user-uuid (str "?checkout[custom][user_uuid]=" (name user-uuid)))]
     (println " ~~~ LEMON: " url " ~~~ ")
     (println " ~~~ LEMON: " url " ~~~ ")
     (js/window.open url)))
     (js/window.open url)))
-  ; (js/window.open 
+  ; (js/window.open
   ;   "https://logseqdemo.lemonsqueezy.com/checkout/buy/13e194b5-c927-41a8-af58-ed1a36d6000d"))
   ;   "https://logseqdemo.lemonsqueezy.com/checkout/buy/13e194b5-c927-41a8-af58-ed1a36d6000d"))
 
 
 (defn <ensure-id&access-token
 (defn <ensure-id&access-token
@@ -218,7 +224,7 @@
       (<! (<refresh-id-token&access-token))
       (<! (<refresh-id-token&access-token))
       (when (or (nil? (state/get-auth-id-token))
       (when (or (nil? (state/get-auth-id-token))
                 (-> (state/get-auth-id-token) parse-jwt expired?))
                 (-> (state/get-auth-id-token) parse-jwt expired?))
-        (ex-info "empty or expired token and refresh failed" {})))))
+        (ex-info "empty or expired token and refresh failed" {:anom :expired-token})))))
 
 
 (defn <user-uuid
 (defn <user-uuid
   []
   []

+ 19 - 0
src/main/frontend/handler/web/nfs.cljs

@@ -86,6 +86,24 @@
                        (keyword (util/get-file-ext (:file/path file)))))
                        (keyword (util/get-file-ext (:file/path file)))))
           files))
           files))
 
 
+(defn- precheck-graph-dir
+  "Check graph dir, notify user if:
+
+   - Graph dir contains a nested graph, which should be avoided
+   - Over 10000 files found in graph dir, which might cause performance issues"
+  [_dir files]
+  ;; disable this check for now
+  (when (some #(string/ends-with? (:path %) "/logseq/config.edn") files)
+    (state/pub-event!
+     [:notification/show {:content "It seems that you are trying to open a Logseq graph folder with nested graph. Please unlink this graph and choose a correct folder."
+                          :status :warning
+                          :clear? false}]))
+  (when (>= (count files) 10000)
+    (state/pub-event!
+     [:notification/show {:content "It seems that you are trying to open a Logseq graph folder that contains an excessive number of files, This might lead to performance issues."
+                          :status :warning
+                          :clear? true}])))
+
 ;; TODO: extract code for `ls-dir-files` and `reload-dir!`
 ;; TODO: extract code for `ls-dir-files` and `reload-dir!`
 (defn ls-dir-files-with-handler!
 (defn ls-dir-files-with-handler!
   "Read files from directory and setup repo (for the first time setup a repo)"
   "Read files from directory and setup repo (for the first time setup a repo)"
@@ -113,6 +131,7 @@
         (reset! *repo repo)
         (reset! *repo repo)
         (when-not (string/blank? root-dir)
         (when-not (string/blank? root-dir)
           (p/let [files (:files result)
           (p/let [files (:files result)
+                  _ (precheck-graph-dir root-dir (:files result))
                   files (-> (->db-files files nfs?)
                   files (-> (->db-files files nfs?)
                             ;; filter again, in case fs backend does not handle this
                             ;; filter again, in case fs backend does not handle this
                             (remove-ignore-files root-dir nfs?))
                             (remove-ignore-files root-dir nfs?))

+ 2 - 2
src/main/frontend/handler/whiteboard.cljs

@@ -246,8 +246,8 @@
           point (-> (.getShapeById app source-shape)
           point (-> (.getShapeById app source-shape)
                     (.-bounds)
                     (.-bounds)
                     ((fn [bounds] (if bottom?
                     ((fn [bounds] (if bottom?
-                                    [(.-minX bounds) (+ 64 (.-maxY bounds))]
-                                    [(+ 64 (.-maxX bounds)) (.-minY bounds)]))))
+                                    [(.-minX ^js bounds) (+ 64 (.-maxY ^js bounds))]
+                                    [(+ 64 (.-maxX ^js bounds)) (.-minY ^js bounds)]))))
           shape (->logseq-portal-shape block-uuid point)]
           shape (->logseq-portal-shape block-uuid point)]
       (when (uuid? block-uuid) (editor-handler/set-blocks-id! [block-uuid]))
       (when (uuid? block-uuid) (editor-handler/set-blocks-id! [block-uuid]))
       (.createShapes api (clj->js shape))
       (.createShapes api (clj->js shape))

+ 19 - 0
src/main/frontend/handler/window.cljs

@@ -0,0 +1,19 @@
+(ns frontend.handler.window
+  "Window management ns"
+  (:require [electron.ipc :as ipc]))
+
+(defn minimize!
+  []
+  (ipc/ipc "window-minimize"))
+
+(defn toggle-maximized!
+  []
+  (ipc/ipc "window-toggle-maximized"))
+
+(defn close!
+  []
+  (ipc/ipc "window-close"))
+
+(defn toggle-fullscreen!
+  []
+  (ipc/ipc "window-toggle-fullscreen"))

+ 1 - 0
src/main/frontend/mobile/action_bar.cljs

@@ -1,4 +1,5 @@
 (ns frontend.mobile.action-bar
 (ns frontend.mobile.action-bar
+  "Block Action bar, activated when swipe on a block"
   (:require
   (:require
    [frontend.db :as db]
    [frontend.db :as db]
    [frontend.extensions.srs :as srs]
    [frontend.extensions.srs :as srs]

+ 9 - 1
src/main/frontend/mobile/core.cljs

@@ -82,8 +82,16 @@
                                   (state/get-left-sidebar-open?)
                                   (state/get-left-sidebar-open?)
                                   (state/set-left-sidebar-open! false)
                                   (state/set-left-sidebar-open! false)
 
 
-                                  :else true))
+                                  (state/action-bar-open?)
+                                  (state/set-state! :mobile/show-action-bar? false)
+
+                                  (not-empty (state/get-selection-blocks))
+                                  (editor-handler/clear-selection!)
 
 
+                                  (state/editing?)
+                                  (editor-handler/escape-editing)
+
+                                  :else true))
                      (if (or (string/ends-with? href "#/")
                      (if (or (string/ends-with? href "#/")
                              (string/ends-with? href "/")
                              (string/ends-with? href "/")
                              (not (string/includes? href "#/")))
                              (not (string/includes? href "#/")))

+ 1 - 0
src/main/frontend/mobile/index.css

@@ -35,6 +35,7 @@
   border-radius: 10px;
   border-radius: 10px;
   background-color: var(--ls-secondary-background-color);
   background-color: var(--ls-secondary-background-color);
   overflow-x: overlay;
   overflow-x: overlay;
+  overflow-y: hidden;
   box-shadow: rgba(0, 0, 0, 0.02) 0px 1px 1px 0px, rgba(27, 31, 35, 0.10) 0px 0px 0px 1px;
   box-shadow: rgba(0, 0, 0, 0.02) 0px 1px 1px 0px, rgba(27, 31, 35, 0.10) 0px 0px 0px 1px;
   z-index: 100;
   z-index: 100;
 
 

+ 105 - 98
src/main/frontend/modules/shortcut/config.cljs

@@ -16,6 +16,7 @@
             [frontend.handler.export :as export-handler]
             [frontend.handler.export :as export-handler]
             [frontend.handler.whiteboard :as whiteboard-handler]
             [frontend.handler.whiteboard :as whiteboard-handler]
             [frontend.handler.plugin-config :as plugin-config-handler]
             [frontend.handler.plugin-config :as plugin-config-handler]
+            [frontend.handler.window :as window-handler]
             [frontend.modules.editor.undo-redo :as undo-redo]
             [frontend.modules.editor.undo-redo :as undo-redo]
             [frontend.dicts :as dicts]
             [frontend.dicts :as dicts]
             [frontend.modules.shortcut.before :as m]
             [frontend.modules.shortcut.before :as m]
@@ -174,7 +175,7 @@
    :cards/recall                            {:binding "t"
    :cards/recall                            {:binding "t"
                                              :fn      srs/recall}
                                              :fn      srs/recall}
 
 
-   :editor/escape-editing                   {:binding false
+   :editor/escape-editing                   {:binding []
                                              :fn      (fn [_ _]
                                              :fn      (fn [_ _]
                                                         (editor-handler/escape-editing))}
                                                         (editor-handler/escape-editing))}
 
 
@@ -331,7 +332,7 @@
    :editor/zoom-out                         {:binding (if mac? "mod+," "alt+left")
    :editor/zoom-out                         {:binding (if mac? "mod+," "alt+left")
                                              :fn      editor-handler/zoom-out!}
                                              :fn      editor-handler/zoom-out!}
 
 
-   :editor/toggle-undo-redo-mode            {:binding false
+   :editor/toggle-undo-redo-mode            {:binding []
                                              :fn      undo-redo/toggle-undo-redo-mode!}
                                              :fn      undo-redo/toggle-undo-redo-mode!}
 
 
    :editor/toggle-number-list               {:binding "t n"
    :editor/toggle-number-list               {:binding "t n"
@@ -400,7 +401,7 @@
 
 
    :graph/export-as-html                    {:fn      #(export-handler/download-repo-as-html!
    :graph/export-as-html                    {:fn      #(export-handler/download-repo-as-html!
                                                          (state/get-current-repo))
                                                          (state/get-current-repo))
-                                             :binding false}
+                                             :binding []}
 
 
    :graph/open                              {:fn      #(do
    :graph/open                              {:fn      #(do
                                                          (editor-handler/escape-editing)
                                                          (editor-handler/escape-editing)
@@ -410,18 +411,18 @@
    :graph/remove                            {:fn      #(do
    :graph/remove                            {:fn      #(do
                                                          (editor-handler/escape-editing)
                                                          (editor-handler/escape-editing)
                                                          (state/set-state! :ui/open-select :graph-remove))
                                                          (state/set-state! :ui/open-select :graph-remove))
-                                             :binding false}
+                                             :binding []}
 
 
    :graph/add                               {:fn      (fn [] (route-handler/redirect! {:to :repo-add}))
    :graph/add                               {:fn      (fn [] (route-handler/redirect! {:to :repo-add}))
-                                             :binding false}
+                                             :binding []}
 
 
    :graph/save                              {:fn      #(state/pub-event! [:graph/save])
    :graph/save                              {:fn      #(state/pub-event! [:graph/save])
-                                             :binding false}
+                                             :binding []}
 
 
    :graph/re-index                          {:fn      (fn []
    :graph/re-index                          {:fn      (fn []
                                                         (p/let [multiple-windows? (ipc/ipc "graphHasMultipleWindows" (state/get-current-repo))]
                                                         (p/let [multiple-windows? (ipc/ipc "graphHasMultipleWindows" (state/get-current-repo))]
                                                           (state/pub-event! [:graph/ask-for-re-index (atom multiple-windows?) nil])))
                                                           (state/pub-event! [:graph/ask-for-re-index (atom multiple-windows?) nil])))
-                                             :binding false}
+                                             :binding []}
 
 
    :command/run                             {:binding  "mod+shift+1"
    :command/run                             {:binding  "mod+shift+1"
                                              :inactive (not (util/electron?))
                                              :inactive (not (util/electron?))
@@ -498,10 +499,14 @@
                                              :inactive (not (util/electron?))
                                              :inactive (not (util/electron?))
                                              :fn       page-handler/copy-current-file}
                                              :fn       page-handler/copy-current-file}
 
 
-   :editor/copy-page-url                    {:binding  false
+   :editor/copy-page-url                    {:binding  []
                                              :inactive (not (util/electron?))
                                              :inactive (not (util/electron?))
                                              :fn       #(page-handler/copy-page-url)}
                                              :fn       #(page-handler/copy-page-url)}
 
 
+   :window/close                            {:binding  "mod+w"
+                                             :inactive (not (util/electron?))
+                                             :fn       window-handler/close!}
+
    :ui/toggle-wide-mode                     {:binding "t w"
    :ui/toggle-wide-mode                     {:binding "t w"
                                              :fn      ui-handler/toggle-wide-mode!}
                                              :fn      ui-handler/toggle-wide-mode!}
 
 
@@ -516,7 +521,7 @@
                                              :inactive (not (config/plugin-config-enabled?))
                                              :inactive (not (config/plugin-config-enabled?))
                                              :fn       plugin-config-handler/open-replace-plugins-modal}
                                              :fn       plugin-config-handler/open-replace-plugins-modal}
 
 
-   :ui/clear-all-notifications              {:binding false
+   :ui/clear-all-notifications              {:binding []
                                              :fn      :frontend.handler.notification/clear-all!}
                                              :fn      :frontend.handler.notification/clear-all!}
 
 
    :editor/toggle-open-blocks               {:binding "t o"
    :editor/toggle-open-blocks               {:binding "t o"
@@ -535,19 +540,19 @@
                                              :inactive (not (util/electron?))
                                              :inactive (not (util/electron?))
                                              :fn       commit/show-commit-modal!}
                                              :fn       commit/show-commit-modal!}
 
 
-   :dev/show-block-data                     {:binding  false
+   :dev/show-block-data                     {:binding  []
                                              :inactive (not (state/developer-mode?))
                                              :inactive (not (state/developer-mode?))
                                              :fn       :frontend.handler.common.developer/show-block-data}
                                              :fn       :frontend.handler.common.developer/show-block-data}
 
 
-   :dev/show-block-ast                      {:binding  false
+   :dev/show-block-ast                      {:binding  []
                                              :inactive (not (state/developer-mode?))
                                              :inactive (not (state/developer-mode?))
                                              :fn       :frontend.handler.common.developer/show-block-ast}
                                              :fn       :frontend.handler.common.developer/show-block-ast}
 
 
-   :dev/show-page-data                      {:binding  false
+   :dev/show-page-data                      {:binding  []
                                              :inactive (not (state/developer-mode?))
                                              :inactive (not (state/developer-mode?))
                                              :fn       :frontend.handler.common.developer/show-page-data}
                                              :fn       :frontend.handler.common.developer/show-page-data}
 
 
-   :dev/show-page-ast                       {:binding  false
+   :dev/show-page-ast                       {:binding  []
                                              :inactive (not (state/developer-mode?))
                                              :inactive (not (state/developer-mode?))
                                              :fn       :frontend.handler.common.developer/show-page-ast}})
                                              :fn       :frontend.handler.common.developer/show-page-ast}})
 
 
@@ -693,7 +698,8 @@
            :search/re-index
            :search/re-index
            :sidebar/clear
            :sidebar/clear
            :sidebar/open-today-page
            :sidebar/open-today-page
-           :ui/toggle-brackets])
+           :ui/toggle-brackets
+           :window/close])
         (with-meta {:before m/prevent-default-behavior}))
         (with-meta {:before m/prevent-default-behavior}))
 
 
     :shortcut.handler/global-non-editing-only
     :shortcut.handler/global-non-editing-only
@@ -833,90 +839,91 @@
      :editor/select-parent
      :editor/select-parent
      :editor/select-block-up
      :editor/select-block-up
      :editor/select-block-down
      :editor/select-block-down
-     :editor/delete-selection]}))
-
-:shortcut.category/toggle
-[:ui/toggle-help
- :editor/toggle-open-blocks
- :editor/toggle-undo-redo-mode
- :editor/toggle-number-list
- :ui/toggle-wide-mode
- :ui/toggle-cards
- :ui/toggle-document-mode
- :ui/toggle-brackets
- :ui/toggle-theme
- :ui/toggle-left-sidebar
- :ui/toggle-right-sidebar
- :ui/toggle-settings
- :ui/toggle-contents
- :ui/cycle-color-off
- :ui/cycle-color] 
-
-:shortcut.category/whiteboard
-[:editor/new-whiteboard
- :whiteboard/select
- :whiteboard/pan
- :whiteboard/portal
- :whiteboard/pencil
- :whiteboard/highlighter
- :whiteboard/eraser
- :whiteboard/connector
- :whiteboard/text
- :whiteboard/rectangle
- :whiteboard/ellipse
- :whiteboard/reset-zoom
- :whiteboard/zoom-to-fit
- :whiteboard/zoom-to-selection
- :whiteboard/zoom-out
- :whiteboard/zoom-in
- :whiteboard/send-backward
- :whiteboard/send-to-back
- :whiteboard/bring-forward
- :whiteboard/bring-to-front
- :whiteboard/lock
- :whiteboard/unlock
- :whiteboard/group
- :whiteboard/ungroup
- :whiteboard/toggle-grid]
-
-:shortcut.category/others
-[:pdf/previous-page
- :pdf/next-page
- :pdf/close
- :pdf/find
- :command/toggle-favorite
- :command/run
- :command-palette/toggle
- :graph/export-as-html
- :graph/open
- :graph/remove
- :graph/add
- :graph/save
- :graph/re-index
- :sidebar/close-top
- :sidebar/clear
- :sidebar/open-today-page
- :search/re-index
- :editor/insert-youtube-timestamp
- :editor/open-file-in-default-app
- :editor/open-file-in-directory
- :editor/copy-page-url
- :auto-complete/prev
- :auto-complete/next
- :auto-complete/complete
- :auto-complete/shift-complete
- :auto-complete/open-link
- :date-picker/prev-day
- :date-picker/next-day
- :date-picker/prev-week
- :date-picker/next-week
- :date-picker/complete
- :git/commit
- :dev/show-block-data
- :dev/show-block-ast
- :dev/show-page-data
- :dev/show-page-ast
- :ui/clear-all-notifications]
+     :editor/delete-selection]
+
+    :shortcut.category/toggle
+    [:ui/toggle-help
+     :editor/toggle-open-blocks
+     :editor/toggle-undo-redo-mode
+     :editor/toggle-number-list
+     :ui/toggle-wide-mode
+     :ui/toggle-cards
+     :ui/toggle-document-mode
+     :ui/toggle-brackets
+     :ui/toggle-theme
+     :ui/toggle-left-sidebar
+     :ui/toggle-right-sidebar
+     :ui/toggle-settings
+     :ui/toggle-contents
+     :ui/cycle-color-off
+     :ui/cycle-color] 
+
+    :shortcut.category/whiteboard
+    [:editor/new-whiteboard
+     :whiteboard/select
+     :whiteboard/pan
+     :whiteboard/portal
+     :whiteboard/pencil
+     :whiteboard/highlighter
+     :whiteboard/eraser
+     :whiteboard/connector
+     :whiteboard/text
+     :whiteboard/rectangle
+     :whiteboard/ellipse
+     :whiteboard/reset-zoom
+     :whiteboard/zoom-to-fit
+     :whiteboard/zoom-to-selection
+     :whiteboard/zoom-out
+     :whiteboard/zoom-in
+     :whiteboard/send-backward
+     :whiteboard/send-to-back
+     :whiteboard/bring-forward
+     :whiteboard/bring-to-front
+     :whiteboard/lock
+     :whiteboard/unlock
+     :whiteboard/group
+     :whiteboard/ungroup
+     :whiteboard/toggle-grid
+
+     :shortcut.category/others
+     [:pdf/previous-page
+      :pdf/next-page
+      :pdf/close
+      :pdf/find
+      :command/toggle-favorite
+      :command/run
+      :command-palette/toggle
+      :graph/export-as-html
+      :graph/open
+      :graph/remove
+      :graph/add
+      :graph/save
+      :graph/re-index
+      :sidebar/close-top
+      :sidebar/clear
+      :sidebar/open-today-page
+      :search/re-index
+      :editor/insert-youtube-timestamp
+      :editor/open-file-in-default-app
+      :editor/open-file-in-directory
+      :editor/copy-page-url
+      :window/close
+      :auto-complete/prev
+      :auto-complete/next
+      :auto-complete/complete
+      :auto-complete/shift-complete
+      :auto-complete/open-link
+      :date-picker/prev-day
+      :date-picker/next-day
+      :date-picker/prev-week
+      :date-picker/next-week
+      :date-picker/complete
+      :git/commit
+      :dev/show-block-data
+      :dev/show-block-ast
+      :dev/show-page-data
+      :dev/show-page-ast
+      :ui/clear-all-notifications]]}))
 
 
 :shortcut.category/plugins
 :shortcut.category/plugins
 []
 []

+ 5 - 5
src/main/frontend/modules/shortcut/core.cljs

@@ -102,7 +102,7 @@
    (when-let [handler (-> (get @*installed-handlers install-id)
    (when-let [handler (-> (get @*installed-handlers install-id)
                           :handler)]
                           :handler)]
      (.dispose ^js handler)
      (.dispose ^js handler)
-     (js/console.debug "[shortcuts]" "uninstall handler" (-> @*installed-handlers (get install-id) :group str) (if refresh? "*" ""))
+     (log/debug :shortcuts/uninstall-handler (-> @*installed-handlers (get install-id) :group (str (if refresh? "*" ""))))
      (swap! *installed-handlers dissoc install-id))))
      (swap! *installed-handlers dissoc install-id))))
 
 
 (defn install-shortcut-handler!
 (defn install-shortcut-handler!
@@ -146,7 +146,7 @@
 
 
       (.listen handler EventType/SHORTCUT_TRIGGERED f)
       (.listen handler EventType/SHORTCUT_TRIGGERED f)
 
 
-      (js/console.debug "[shortcuts] install handler" (str handler-id))
+      (log/debug :shortcuts/install-handler (str handler-id))
       (swap! *installed-handlers merge data)
       (swap! *installed-handlers merge data)
 
 
       install-id)))
       install-id)))
@@ -181,9 +181,9 @@
        :will-remount
        :will-remount
        (fn [old-state new-state]
        (fn [old-state new-state]
          (util/profile "[shortcuts] reinstalled:"
          (util/profile "[shortcuts] reinstalled:"
-           (uninstall-shortcut-handler! (::install-id old-state))
-           (when-let [install-id (install-shortcut-handler! handler-id {:state new-state})]
-             (assoc new-state ::install-id install-id))))))))
+                       (uninstall-shortcut-handler! (::install-id old-state))
+                       (when-let [install-id (install-shortcut-handler! handler-id {:state new-state})]
+                         (assoc new-state ::install-id install-id))))))))
 
 
 (defn mixin*
 (defn mixin*
   "This is an optimized version compared to (mixin).
   "This is an optimized version compared to (mixin).

+ 4 - 0
src/main/frontend/state.cljs

@@ -1899,6 +1899,10 @@ Similar to re-frame subscriptions"
             (when (or (util/mobile?) (mobile-util/native-platform?))
             (when (or (util/mobile?) (mobile-util/native-platform?))
               (set-state! :mobile/show-action-bar? false)))))))))
               (set-state! :mobile/show-action-bar? false)))))))))
 
 
+(defn action-bar-open?
+  []
+  (:mobile/show-action-bar? @state))
+
 (defn remove-watch-state [key]
 (defn remove-watch-state [key]
   (remove-watch state key))
   (remove-watch state key))
 
 

+ 22 - 18
src/main/frontend/ui.cljs

@@ -87,20 +87,24 @@
 (rum/defc ls-textarea
 (rum/defc ls-textarea
   < rum/reactive
   < rum/reactive
   {:did-mount (fn [state]
   {:did-mount (fn [state]
-                (let [^js el (rum/dom-node state)]
+                (let [^js el (rum/dom-node state)
+                      *mouse-point (volatile! nil)]
                   ;; Passing aria-label as a prop to TextareaAutosize removes the dash
                   ;; Passing aria-label as a prop to TextareaAutosize removes the dash
                   (.setAttribute el "aria-label" "editing block")
                   (.setAttribute el "aria-label" "editing block")
-                  (. el addEventListener "select"
-                     #(let [start (util/get-selection-start el)
-                            end (util/get-selection-end el)]
-                        (when (and start end)
-                          (when-let [e (and (not= start end)
-                                            {:caret (cursor/get-caret-pos el)
-                                             :start start :end end
-                                             :text  (. (.-value el) substring start end)
-                                             :point {:x (.-x %) :y (.-y %)}})]
-
-                            (plugin-handler/hook-plugin-editor :input-selection-end (bean/->js e)))))))
+                  (doto el
+                    (.addEventListener "select"
+                       #(let [start (util/get-selection-start el)
+                              end (util/get-selection-end el)]
+                          (when (and start end)
+                            (when-let [e (and (not= start end)
+                                              (let [caret-pos (cursor/get-caret-pos el)]
+                                                {:caret caret-pos
+                                                 :start start :end end
+                                                 :text  (. (.-value el) substring start end)
+                                                 :point (select-keys (or @*mouse-point caret-pos) [:x :y])}))]
+                              (plugin-handler/hook-plugin-editor :input-selection-end (bean/->js e))
+                              (vreset! *mouse-point nil)))))
+                    (.addEventListener "mouseup" #(vreset! *mouse-point {:x (.-x %) :y (.-y %)}))))
                 state)}
                 state)}
   [{:keys [on-change] :as props}]
   [{:keys [on-change] :as props}]
   (let [skip-composition? (state/sub :editor/action)
   (let [skip-composition? (state/sub :editor/action)
@@ -113,16 +117,16 @@
                                                 (on-change e))
                                                 (on-change e))
                              (state/set-editor-in-composition! true))))
                              (state/set-editor-in-composition! true))))
         props (assoc props
         props (assoc props
-                     :on-change (fn [e] (when-not (state/editor-in-composition?)
-                                          (on-change e)))
-                     :on-composition-start on-composition
-                     :on-composition-update on-composition
-                     :on-composition-end on-composition)]
+                :on-change (fn [e] (when-not (state/editor-in-composition?)
+                                     (on-change e)))
+                :on-composition-start on-composition
+                :on-composition-update on-composition
+                :on-composition-end on-composition)]
     (textarea props)))
     (textarea props)))
 
 
 (rum/defc dropdown-content-wrapper
 (rum/defc dropdown-content-wrapper
   < {:did-mount    (fn [state]
   < {:did-mount    (fn [state]
-                     (let [k    (inc (count (state/sub :modal/dropdowns)))
+                     (let [k (inc (count (state/sub :modal/dropdowns)))
                            args (:rum/args state)]
                            args (:rum/args state)]
                        (state/set-state! [:modal/dropdowns k] (second args))
                        (state/set-state! [:modal/dropdowns k] (second args))
                        (assoc state ::k k)))
                        (assoc state ::k k)))

+ 3 - 3
src/main/frontend/util.cljc

@@ -6,7 +6,7 @@
             ["/frontend/selection" :as selection]
             ["/frontend/selection" :as selection]
             ["/frontend/utils" :as utils]
             ["/frontend/utils" :as utils]
             ["@capacitor/status-bar" :refer [^js StatusBar Style]]
             ["@capacitor/status-bar" :refer [^js StatusBar Style]]
-            ["@hugotomazi/capacitor-navigation-bar" :refer [^js NavigationBar]]
+            ["@capgo/capacitor-navigation-bar" :refer [^js NavigationBar]]
             ["grapheme-splitter" :as GraphemeSplitter]
             ["grapheme-splitter" :as GraphemeSplitter]
             ["remove-accents" :as removeAccents]
             ["remove-accents" :as removeAccents]
             ["sanitize-filename" :as sanitizeFilename]
             ["sanitize-filename" :as sanitizeFilename]
@@ -209,7 +209,7 @@
      (p/do!
      (p/do!
       (.setStyle StatusBar (clj->js {:style (.-Light Style)}))
       (.setStyle StatusBar (clj->js {:style (.-Light Style)}))
       (when (mobile-util/native-android?)
       (when (mobile-util/native-android?)
-        (.setColor NavigationBar (clj->js {:color "#ffffff"}))
+        (.setNavigationBarColor NavigationBar (clj->js {:color "#ffffff"}))
         (.setBackgroundColor StatusBar (clj->js {:color "#ffffff"}))))))
         (.setBackgroundColor StatusBar (clj->js {:color "#ffffff"}))))))
 
 
 #?(:cljs
 #?(:cljs
@@ -218,7 +218,7 @@
      (p/do!
      (p/do!
       (.setStyle StatusBar (clj->js {:style (.-Dark Style)}))
       (.setStyle StatusBar (clj->js {:style (.-Dark Style)}))
       (when (mobile-util/native-android?)
       (when (mobile-util/native-android?)
-        (.setColor NavigationBar (clj->js {:color "#002b36"}))
+        (.setNavigationBarColor NavigationBar (clj->js {:color "#002b36"}))
         (.setBackgroundColor StatusBar (clj->js {:color "#002b36"}))))))
         (.setBackgroundColor StatusBar (clj->js {:color "#002b36"}))))))
 
 
 (defn find-first
 (defn find-first

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

@@ -1,3 +1,3 @@
 (ns ^:no-doc frontend.version)
 (ns ^:no-doc frontend.version)
 
 
-(defonce version "0.9.15")
+(defonce version "0.9.19")

+ 20 - 10
src/main/logseq/api.cljs

@@ -13,6 +13,7 @@
             [frontend.components.plugins :as plugins]
             [frontend.components.plugins :as plugins]
             [frontend.config :as config]
             [frontend.config :as config]
             [frontend.handler.config :as config-handler]
             [frontend.handler.config :as config-handler]
+            [frontend.handler.route :as route-handler]
             [frontend.db :as db]
             [frontend.db :as db]
             [frontend.db.model :as db-model]
             [frontend.db.model :as db-model]
             [frontend.db.query-dsl :as query-dsl]
             [frontend.db.query-dsl :as query-dsl]
@@ -449,17 +450,23 @@
 
 
 (def ^:export push_state
 (def ^:export push_state
   (fn [^js k ^js params ^js query]
   (fn [^js k ^js params ^js query]
-    (rfe/push-state
-      (keyword k)
-      (bean/->clj params)
-      (bean/->clj query))))
+    (let [k (keyword k)
+          page? (= k :page)
+          params (bean/->clj params)
+          query (bean/->clj query)]
+      (if-let [page-name (and page? (:name params))]
+        (route-handler/redirect-to-page! page-name {:anchor (:anchor query) :push true})
+        (rfe/push-state k params query)))))
 
 
 (def ^:export replace_state
 (def ^:export replace_state
   (fn [^js k ^js params ^js query]
   (fn [^js k ^js params ^js query]
-    (rfe/replace-state
-      (keyword k)
-      (bean/->clj params)
-      (bean/->clj query))))
+    (let [k (keyword k)
+          page? (= k :page)
+          params (bean/->clj params)
+          query (bean/->clj query)]
+      (if-let [page-name (and page? (:name params))]
+        (route-handler/redirect-to-page! page-name {:anchor (:anchor query) :push false})
+        (rfe/replace-state k params query)))))
 
 
 (defn ^:export get_external_plugin
 (defn ^:export get_external_plugin
   [pid]
   [pid]
@@ -563,8 +570,11 @@
   page-handler/rename!)
   page-handler/rename!)
 
 
 (defn ^:export open_in_right_sidebar
 (defn ^:export open_in_right_sidebar
-  [block-uuid]
-  (editor-handler/open-block-in-sidebar! (sdk-utils/uuid-or-throw-error block-uuid)))
+  [block-id-or-uuid]
+  (editor-handler/open-block-in-sidebar!
+    (if (number? block-id-or-uuid)
+      block-id-or-uuid
+      (sdk-utils/uuid-or-throw-error block-id-or-uuid))))
 
 
 (defn ^:export new_block_uuid []
 (defn ^:export new_block_uuid []
   (str (db/new-block-id)))
   (str (db/new-block-id)))

+ 0 - 2
src/resources/dicts/de.edn

@@ -576,7 +576,6 @@
  :settings-page/custom-date-format-warning "Neuindizierung erforderlich! Vorhandene Journal-Verweise würden nicht mehr funktionieren!"
  :settings-page/custom-date-format-warning "Neuindizierung erforderlich! Vorhandene Journal-Verweise würden nicht mehr funktionieren!"
  :settings-page/custom-global-configuration "Benutzerdefinierte globale Konfiguration"
  :settings-page/custom-global-configuration "Benutzerdefinierte globale Konfiguration"
  :settings-page/custom-theme "Individuelles Theme"
  :settings-page/custom-theme "Individuelles Theme"
- :settings-page/customize-shortcuts "Tastaturbefehle"
  :settings-page/developer-mode "Entwicklermodus"
  :settings-page/developer-mode "Entwicklermodus"
  :settings-page/developer-mode-desc "Der Entwicklermodus hilft Mitwirkenden und Entwicklern von Erweiterungen, ihre Integration mit Logseq effizienter zu testen."
  :settings-page/developer-mode-desc "Der Entwicklermodus hilft Mitwirkenden und Entwicklern von Erweiterungen, ihre Integration mit Logseq effizienter zu testen."
  :settings-page/disable-sentry "Nutzungs- und Diagnostik-Daten an Logseq senden"
  :settings-page/disable-sentry "Nutzungs- und Diagnostik-Daten an Logseq senden"
@@ -605,7 +604,6 @@
  :settings-page/preferred-outdenting "Logische Ausrückung"
  :settings-page/preferred-outdenting "Logische Ausrückung"
  :settings-page/preferred-pasting-file "Einfügen der Datei bevorzugen"
  :settings-page/preferred-pasting-file "Einfügen der Datei bevorzugen"
  :settings-page/preferred-workflow "Bevorzugter Workflow"
  :settings-page/preferred-workflow "Bevorzugter Workflow"
- :settings-page/shortcut-settings "Verknüpfungen anpassen"
  :settings-page/show-brackets "Klammern anzeigen"
  :settings-page/show-brackets "Klammern anzeigen"
  :settings-page/show-full-blocks "Alle Zeilen einer Blockreferenz anzeigen"
  :settings-page/show-full-blocks "Alle Zeilen einer Blockreferenz anzeigen"
  :settings-page/spell-checker "Rechtschreibprüfung"
  :settings-page/spell-checker "Rechtschreibprüfung"

+ 5 - 3
src/resources/dicts/en.edn

@@ -308,8 +308,6 @@
  :settings-page/enable-tooltip "Tooltips"
  :settings-page/enable-tooltip "Tooltips"
  :settings-page/enable-journals "Journals"
  :settings-page/enable-journals "Journals"
  :settings-page/enable-all-pages-public "All pages public when publishing"
  :settings-page/enable-all-pages-public "All pages public when publishing"
- :settings-page/customize-shortcuts "Keyboard shortcuts"
- :settings-page/shortcut-settings "Customize shortcuts"
  :settings-page/home-default-page "Set the default home page"
  :settings-page/home-default-page "Set the default home page"
  :settings-page/enable-block-time "Block timestamps"
  :settings-page/enable-block-time "Block timestamps"
  :settings-page/clear-cache "Clear cache"
  :settings-page/clear-cache "Clear cache"
@@ -353,6 +351,8 @@
  :settings-page/update-error-2 " Please check out the "
  :settings-page/update-error-2 " Please check out the "
  :settings-permission/start-granting "Grant"
  :settings-permission/start-granting "Grant"
 
 
+ :settings-page/auto-chmod "Automatically change file permissions"
+ :settings-page/auto-chmod-desc "Disable to allow editing by multiple users with permissions granted by group membership."
  :yes "Yes"
  :yes "Yes"
 
 
  :submit "Submit"
  :submit "Submit"
@@ -465,6 +465,7 @@
  :whiteboard/dashboard-card-edited "Edited "
  :whiteboard/dashboard-card-edited "Edited "
  :whiteboard/toggle-grid "Toggle grid"
  :whiteboard/toggle-grid "Toggle grid"
  :whiteboard/snap-to-grid "Snap to grid"
  :whiteboard/snap-to-grid "Snap to grid"
+ :whiteboard/toggle-pen-mode "Toggle pen mode"
  :flashcards/modal-welcome-title "Time to create a card!"
  :flashcards/modal-welcome-title "Time to create a card!"
  :flashcards/modal-welcome-desc-1 "You can add \"#card\" to any block to turn it into a card or trigger \"/cloze\" to add some clozes."
  :flashcards/modal-welcome-desc-1 "You can add \"#card\" to any block to turn it into a card or trigger \"/cloze\" to add some clozes."
  :flashcards/modal-welcome-desc-2 "You can "
  :flashcards/modal-welcome-desc-2 "You can "
@@ -827,4 +828,5 @@
   :dev/show-block-data             "(Dev) Show block data"
   :dev/show-block-data             "(Dev) Show block data"
   :dev/show-block-ast              "(Dev) Show block AST"
   :dev/show-block-ast              "(Dev) Show block AST"
   :dev/show-page-data              "(Dev) Show page data"
   :dev/show-page-data              "(Dev) Show page data"
-  :dev/show-page-ast               "(Dev) Show page AST"}}
+  :dev/show-page-ast               "(Dev) Show page AST"
+  :window/close                    "Close window"}}

+ 42 - 25
src/resources/dicts/es.edn

@@ -13,10 +13,10 @@
  :delete                                            "Eliminar"
  :delete                                            "Eliminar"
  :discourse-title                                   "¡Nuestro foro!"
  :discourse-title                                   "¡Nuestro foro!"
  :download                                          "Descargar"
  :download                                          "Descargar"
+ :export                                            "Exportar"
  :export-copied-to-clipboard                        "¡Copiado al portapapeles!"
  :export-copied-to-clipboard                        "¡Copiado al portapapeles!"
  :export-copy-to-clipboard                          "Copiar al portapapeles"
  :export-copy-to-clipboard                          "Copiar al portapapeles"
  :export-edn                                        "Exportar a EDN"
  :export-edn                                        "Exportar a EDN"
- :export                                            "Exportar"
  :export-graph                                      "Exportar grafo"
  :export-graph                                      "Exportar grafo"
  :export-json                                       "Exportar a JSON"
  :export-json                                       "Exportar a JSON"
  :export-markdown                                   "Exportar como Markdown estándar (sin propiedades de bloque)"
  :export-markdown                                   "Exportar como Markdown estándar (sin propiedades de bloque)"
@@ -53,23 +53,24 @@
  :parsing-files                                     "Analizando archivos"
  :parsing-files                                     "Analizando archivos"
  :plugins                                           "Extensiones"
  :plugins                                           "Extensiones"
  :port                                              "Puerto"
  :port                                              "Puerto"
+ :re-index                                          "Reindexar"
  :re-index-detail                                   "Reconstruir el grafo"
  :re-index-detail                                   "Reconstruir el grafo"
  :re-index-discard-unsaved-changes-warning          "Al reindexar se descartará el grafo actual y se procesarán nuevamente todos los archivos según como están actualmente almacenados en disco. Perderá los cambios no guardados y puede tardar un poco. ¿Continuar?"
  :re-index-discard-unsaved-changes-warning          "Al reindexar se descartará el grafo actual y se procesarán nuevamente todos los archivos según como están actualmente almacenados en disco. Perderá los cambios no guardados y puede tardar un poco. ¿Continuar?"
  :re-index-multiple-windows-warning                 "Debe cerrar las otras ventanas antes de reindexar este grafo."
  :re-index-multiple-windows-warning                 "Debe cerrar las otras ventanas antes de reindexar este grafo."
- :re-index                                          "Reindexar"
  :relaunch-confirm-to-work                          "Debe relanzar la aplicación para hacer que funcione. ¿Desea reiniciarla ahora?"
  :relaunch-confirm-to-work                          "Debe relanzar la aplicación para hacer que funcione. ¿Desea reiniciarla ahora?"
  :remove-background                                 "Eliminar el fondo"
  :remove-background                                 "Eliminar el fondo"
  :remove-heading                                    "Eliminar encabezado"
  :remove-heading                                    "Eliminar encabezado"
  :remove-orphaned-pages                             "¿Eliminar páginas huérfanas?"
  :remove-orphaned-pages                             "¿Eliminar páginas huérfanas?"
+ :reset                                             "Reiniciar"
  :save                                              "Guardar"
  :save                                              "Guardar"
  :search                                            "Buscar o Crear Página"
  :search                                            "Buscar o Crear Página"
- :settings-of-plugins                               "Opciones de Extensiones"
  :settings                                          "Opciones"
  :settings                                          "Opciones"
+ :settings-of-plugins                               "Opciones de Extensiones"
  :strikethrough                                     "Tachado"
  :strikethrough                                     "Tachado"
  :submit                                            "Enviar"
  :submit                                            "Enviar"
  :sync-from-local-changes-detected                  "Refrescar detecta y procesa los archivos modificados en su disco que difieren del contenido actual de la página en Logseq. ¿Continuar?"
  :sync-from-local-changes-detected                  "Refrescar detecta y procesa los archivos modificados en su disco que difieren del contenido actual de la página en Logseq. ¿Continuar?"
- :sync-from-local-files-detail                      "Importar cambios de los archivos locales"
  :sync-from-local-files                             "Refrescar"
  :sync-from-local-files                             "Refrescar"
+ :sync-from-local-files-detail                      "Importar cambios de los archivos locales"
  :themes                                            "Temas"
  :themes                                            "Temas"
  :toggle-theme                                      "Alternar tema"
  :toggle-theme                                      "Alternar tema"
  :type                                              "Tipo"
  :type                                              "Tipo"
@@ -151,8 +152,8 @@
  :command.editor/copy-text                          "Copiar selección como texto"
  :command.editor/copy-text                          "Copiar selección como texto"
  :command.editor/cut                                "Pegar"
  :command.editor/cut                                "Pegar"
  :command.editor/cycle-todo                         "Rotar estado TODO del elemento"
  :command.editor/cycle-todo                         "Rotar estado TODO del elemento"
- :command.editor/delete-selection                   "Eliminar bloques seleccionados"
  :command.editor/delete                             "Suprimir / Eliminar hacia delante"
  :command.editor/delete                             "Suprimir / Eliminar hacia delante"
+ :command.editor/delete-selection                   "Eliminar bloques seleccionados"
  :command.editor/down                               "Mover cursor abajo / Seleccionar abajo"
  :command.editor/down                               "Mover cursor abajo / Seleccionar abajo"
  :command.editor/end-of-block                       "Mover cursor al final del bloque"
  :command.editor/end-of-block                       "Mover cursor al final del bloque"
  :command.editor/escape-editing                     "Escapar de edición"
  :command.editor/escape-editing                     "Escapar de edición"
@@ -269,6 +270,7 @@
  :command.whiteboard/zoom-out                       "Alejar"
  :command.whiteboard/zoom-out                       "Alejar"
  :command.whiteboard/zoom-to-fit                    "Zoom al dibujo"
  :command.whiteboard/zoom-to-fit                    "Zoom al dibujo"
  :command.whiteboard/zoom-to-selection              "Zoom para ajustar a la selección"
  :command.whiteboard/zoom-to-selection              "Zoom para ajustar a la selección"
+ :command.window/close                              "Cerrar ventana"
  :content/click-to-edit                             "Clic para editar"
  :content/click-to-edit                             "Clic para editar"
  :content/copy-block-emebed                         "Copiar bloque a incrustar (embed)"
  :content/copy-block-emebed                         "Copiar bloque a incrustar (embed)"
  :content/copy-block-ref                            "Copiar referencia de bloque"
  :content/copy-block-ref                            "Copiar referencia de bloque"
@@ -317,8 +319,8 @@
  :file-rn/need-action                               "Se sugieren acciones de cambio de nombre de archivo para que coincidan con el nuevo formato. Cuando se sincronicen los archivos renombrados se requiere volver a indexar en todos los dispositivos."
  :file-rn/need-action                               "Se sugieren acciones de cambio de nombre de archivo para que coincidan con el nuevo formato. Cuando se sincronicen los archivos renombrados se requiere volver a indexar en todos los dispositivos."
  :file-rn/no-action                                 "¡Bien hecho! No es necesario realizar más acciones."
  :file-rn/no-action                                 "¡Bien hecho! No es necesario realizar más acciones."
  :file-rn/optional-rename                           "Sugerencia: "
  :file-rn/optional-rename                           "Sugerencia: "
- :file-rn/or-select-actions-2                       ". Estas acciones no estarán disponibles una vez cierres este panel."
  :file-rn/or-select-actions                         " o cambie el nombre de los archivos a continuación individualmente, luego "
  :file-rn/or-select-actions                         " o cambie el nombre de los archivos a continuación individualmente, luego "
+ :file-rn/or-select-actions-2                       ". Estas acciones no estarán disponibles una vez cierres este panel."
  :file-rn/otherwise-breaking                        "O el título se convertirá"
  :file-rn/otherwise-breaking                        "O el título se convertirá"
  :file-rn/re-index                                  "Se recomienda encarecidamente volver a indexar después de cambiar el nombre de los archivos y en otros dispositivos después de la sincronización."
  :file-rn/re-index                                  "Se recomienda encarecidamente volver a indexar después de cambiar el nombre de los archivos y en otros dispositivos después de la sincronización."
  :file-rn/rename                                    "Renombrar \"{1}\" a \"{2}\""
  :file-rn/rename                                    "Renombrar \"{1}\" a \"{2}\""
@@ -326,6 +328,7 @@
  :file-rn/select-format                             "(Opción modo desarrollador, ¡peligroso!) Seccione el formato de nombre de archivo"
  :file-rn/select-format                             "(Opción modo desarrollador, ¡peligroso!) Seccione el formato de nombre de archivo"
  :file-rn/suggest-rename                            "Acción necesaria: "
  :file-rn/suggest-rename                            "Acción necesaria: "
  :file-rn/unreachable-title                         "¡Advertencia! El nombre de la página se convertirá en {1} en el formato de nombre de archivo actual, a no ser que la propiedad `title::` se establezca manualmente"
  :file-rn/unreachable-title                         "¡Advertencia! El nombre de la página se convertirá en {1} en el formato de nombre de archivo actual, a no ser que la propiedad `title::` se establezca manualmente"
+ :file-sync/connectivity-testing-failed             "Fallaron las pruebas de conexión de red. Consulte la configuración de su red. URLs de prueba: "
  :file-sync/graph-deleted                           "El gráfico remoto actual se ha eliminado"
  :file-sync/graph-deleted                           "El gráfico remoto actual se ha eliminado"
  :file-sync/other-user-graph                        "El gráfico local actual está unido al gráfico remoto de otro usuario, así que no se puede empezar a sincronizar"
  :file-sync/other-user-graph                        "El gráfico local actual está unido al gráfico remoto de otro usuario, así que no se puede empezar a sincronizar"
  :file-sync/rsapi-cannot-upload-err                 "Incapaz de comenzar la sincronización, comprueba si el tiempo local es correcto."
  :file-sync/rsapi-cannot-upload-err                 "Incapaz de comenzar la sincronización, comprueba si el tiempo local es correcto."
@@ -353,11 +356,11 @@
  :flashcards/modal-welcome-title                    "¡Hora de crear una tarjeta!"
  :flashcards/modal-welcome-title                    "¡Hora de crear una tarjeta!"
  :graph/all-graphs                                  "Todos los grafos"
  :graph/all-graphs                                  "Todos los grafos"
  :graph/local-graphs                                "Grafos locales:"
  :graph/local-graphs                                "Grafos locales:"
- :graph/persist-error                               "Falló la sincronización del estado interno."
  :graph/persist                                     "Logseq está sincronizando su estado interno, por favor espere unos segundos."
  :graph/persist                                     "Logseq está sincronizando su estado interno, por favor espere unos segundos."
+ :graph/persist-error                               "Falló la sincronización del estado interno."
  :graph/remote-graphs                               "Grafos remotos:"
  :graph/remote-graphs                               "Grafos remotos:"
- :graph/save-error                                  "Falló el guardado"
  :graph/save                                        "Guardando..."
  :graph/save                                        "Guardando..."
+ :graph/save-error                                  "Falló el guardado"
  :graph/save-success                                "Guardado satisfactoriamente"
  :graph/save-success                                "Guardado satisfactoriamente"
  :header/go-back                                    "Ir hacia atrás"
  :header/go-back                                    "Ir hacia atrás"
  :header/go-forward                                 "Ir haca adelante"
  :header/go-forward                                 "Ir haca adelante"
@@ -393,6 +396,18 @@
  :help/title-development                            "Desarrollo"
  :help/title-development                            "Desarrollo"
  :help/title-terms                                  "Términos"
  :help/title-terms                                  "Términos"
  :help/title-usage                                  "Uso"
  :help/title-usage                                  "Uso"
+ :keymap/all                                        "Todo"
+ :keymap/conflicts-for-label                        "Conflicto de combinación de teclas para"
+ :keymap/custom                                     "Personalizado"
+ :keymap/customize-for-label                        "Personalizar atajos"
+ :keymap/disabled                                   "Deshabilitado"
+ :keymap/keystroke-filter                           "Filtro de pulsación de teclas"
+ :keymap/keystroke-record-desc                      "Presione cualquier secuencia de teclas para filtrar atajos"
+ :keymap/keystroke-record-setup-label               "Presione cualquier secuencia de teclas para establecer un atajo"
+ :keymap/restore-to-default                         "Reiniciar a valor predeterminado del sistema"
+ :keymap/search                                     "Buscar"
+ :keymap/total                                      "Atajos de teclado totales"
+ :keymap/unset                                      "Sin asignar"
  :left-side-bar/create                              "Crear"
  :left-side-bar/create                              "Crear"
  :left-side-bar/journals                            "Diarios"
  :left-side-bar/journals                            "Diarios"
  :left-side-bar/nav-favorites                       "Favoritos"
  :left-side-bar/nav-favorites                       "Favoritos"
@@ -464,8 +479,8 @@
  :page/backlinks                                    "Vínculos de retroceso"
  :page/backlinks                                    "Vínculos de retroceso"
  :page/copy-page-url                                "Copiar URL de la página"
  :page/copy-page-url                                "Copiar URL de la página"
  :page/created-at                                   "Creada el"
  :page/created-at                                   "Creada el"
- :page/delete-confirmation                          "¿Está seguro que desea eliminar esta página y su archivo?"
  :page/delete                                       "Eliminar página"
  :page/delete                                       "Eliminar página"
+ :page/delete-confirmation                          "¿Está seguro que desea eliminar esta página y su archivo?"
  :page/earlier                                      "Anteriormente"
  :page/earlier                                      "Anteriormente"
  :page/illegal-page-name                            "¡Nombre de página ilegal!"
  :page/illegal-page-name                            "¡Nombre de página ilegal!"
  :page/logseq-is-having-a-problem                   "Logseq está teniendo un problema. Para intentar volver a un estado de trabajo, intenta los siguientes pasos seguros en orden:"
  :page/logseq-is-having-a-problem                   "Logseq está teniendo un problema. Para intentar volver a un estado de trabajo, intenta los siguientes pasos seguros en orden:"
@@ -477,8 +492,8 @@
  :page/page-already-exists                          "¡La página “{1}” ya existe!"
  :page/page-already-exists                          "¡La página “{1}” ya existe!"
  :page/show-journals                                "Mostrar diarios"
  :page/show-journals                                "Mostrar diarios"
  :page/show-whiteboards                             "Mostrar pizarras"
  :page/show-whiteboards                             "Mostrar pizarras"
- :page/slide-view-tip-go-fullscreen (fn [] [[:span.opacity-70 "Consejo: presiona "] [:code "f"] [:span.opacity-70 " para ir a pantalla completa"]])
  :page/slide-view                                   "Ver como diapositivas"
  :page/slide-view                                   "Ver como diapositivas"
+ :page/slide-view-tip-go-fullscreen (fn [] [[:span.opacity-70 "Consejo: presiona "] [:code "f"] [:span.opacity-70 " para ir a pantalla completa"]])
  :page/something-went-wrong                         "Algo malió sal"
  :page/something-went-wrong                         "Algo malió sal"
  :page/step                                         "Paso {1}"
  :page/step                                         "Paso {1}"
  :page/try                                          "Intentar"
  :page/try                                          "Intentar"
@@ -509,6 +524,7 @@
  :plugin/enabled                                    "Habilitado"
  :plugin/enabled                                    "Habilitado"
  :plugin/found-n-updates                            "Se encontraron {1} actualizaciones"
  :plugin/found-n-updates                            "Se encontraron {1} actualizaciones"
  :plugin/found-updates                              "Nuevas actualizaciones"
  :plugin/found-updates                              "Nuevas actualizaciones"
+ :plugin/install                                    "Instalar"
  :plugin/installed                                  "Instalado"
  :plugin/installed                                  "Instalado"
  :plugin/installed-plugin                           "Extensión instalada: {1}"
  :plugin/installed-plugin                           "Extensión instalada: {1}"
  :plugin.install-from-file/menu-title               "Instalar desde plugins.edn"
  :plugin.install-from-file/menu-title               "Instalar desde plugins.edn"
@@ -516,7 +532,6 @@
  :plugin.install-from-file/success                  "¡Todas las extensiones fueron instaladas!"
  :plugin.install-from-file/success                  "¡Todas las extensiones fueron instaladas!"
  :plugin.install-from-file/title                    "Instalar extensiones desde  plugins.edn"
  :plugin.install-from-file/title                    "Instalar extensiones desde  plugins.edn"
  :plugin/installing                                 "Instalando"
  :plugin/installing                                 "Instalando"
- :plugin/install                                    "Instalar"
  :plugin/list-of-updates                            "Actualizaciones de extensiones: "
  :plugin/list-of-updates                            "Actualizaciones de extensiones: "
  :plugin/load-unpacked                              "Cargar extensión desempaquetada"
  :plugin/load-unpacked                              "Cargar extensión desempaquetada"
  :plugin/marketplace                                "Tienda"
  :plugin/marketplace                                "Tienda"
@@ -557,14 +572,14 @@
  :right-side-bar/history-undos                      "Deshacer"
  :right-side-bar/history-undos                      "Deshacer"
  :right-side-bar/new-page                           "Nueva página"
  :right-side-bar/new-page                           "Nueva página"
  :right-side-bar/page-graph                         "Grafo de página"
  :right-side-bar/page-graph                         "Grafo de página"
+ :right-side-bar/pane-close                         "Cerrar"
  :right-side-bar/pane-close-all                     "Cerrar todo"
  :right-side-bar/pane-close-all                     "Cerrar todo"
  :right-side-bar/pane-close-others                  "Cerrar otros"
  :right-side-bar/pane-close-others                  "Cerrar otros"
- :right-side-bar/pane-close                         "Cerrar"
- :right-side-bar/pane-collapse-all                  "Colapsar todo"
  :right-side-bar/pane-collapse                      "Colapsar"
  :right-side-bar/pane-collapse                      "Colapsar"
+ :right-side-bar/pane-collapse-all                  "Colapsar todo"
  :right-side-bar/pane-collapse-others               "Colapsar otros"
  :right-side-bar/pane-collapse-others               "Colapsar otros"
- :right-side-bar/pane-expand-all                    "Expandir todo"
  :right-side-bar/pane-expand                        "Expandir"
  :right-side-bar/pane-expand                        "Expandir"
+ :right-side-bar/pane-expand-all                    "Expandir todo"
  :right-side-bar/pane-more                          "Más"
  :right-side-bar/pane-more                          "Más"
  :right-side-bar/pane-open-as-page                  "Abrir como página"
  :right-side-bar/pane-open-as-page                  "Abrir como página"
  :right-side-bar/separator                          "Right sidebar resize handler "
  :right-side-bar/separator                          "Right sidebar resize handler "
@@ -593,6 +608,8 @@
  :select.graph/prompt                               "Seleccione un grafo"
  :select.graph/prompt                               "Seleccione un grafo"
  :settings-page/alpha-features                      "Características Alfa"
  :settings-page/alpha-features                      "Características Alfa"
  :settings-page/app-updated                         "Tu aplicación está actualizada 🎉"
  :settings-page/app-updated                         "Tu aplicación está actualizada 🎉"
+ :settings-page/auto-chmod                          "Cambiar automaticamente los permisos de archivo"
+ :settings-page/auto-chmod-desc                     "Desactivar el permitir la edición de múltiples usuarios con permisos otorgados por la membresía del grupo."
  :settings-page/auto-expand-block-refs              "Expandir referencias de bloque automáticamente al hacer un acercamiento"
  :settings-page/auto-expand-block-refs              "Expandir referencias de bloque automáticamente al hacer un acercamiento"
  :settings-page/auto-expand-block-refs-tip          "Esta opción controla si expandir el bloque de referencias automáticamente al hacer un acercamiento."
  :settings-page/auto-expand-block-refs-tip          "Esta opción controla si expandir el bloque de referencias automáticamente al hacer un acercamiento."
  :settings-page/auto-updater                        "Auto actualizador"
  :settings-page/auto-updater                        "Auto actualizador"
@@ -600,21 +617,20 @@
  :settings-page/changelog                           "¿Qué hay de nuevo?"
  :settings-page/changelog                           "¿Qué hay de nuevo?"
  :settings-page/check-for-updates                   "Comprobar actualizaciones"
  :settings-page/check-for-updates                   "Comprobar actualizaciones"
  :settings-page/checking                            "Comprobando ..."
  :settings-page/checking                            "Comprobando ..."
+ :settings-page/clear                               "Limpiar"
  :settings-page/clear-cache                         "Limpiar caché"
  :settings-page/clear-cache                         "Limpiar caché"
  :settings-page/clear-cache-warning                 "Limpiar la caché va a desechar los grafos abiertos. Vas a perder los cambios no guardados."
  :settings-page/clear-cache-warning                 "Limpiar la caché va a desechar los grafos abiertos. Vas a perder los cambios no guardados."
- :settings-page/clear                               "Limpiar"
  :settings-page/current-version                     "Versión actual"
  :settings-page/current-version                     "Versión actual"
  :settings-page/custom-configuration                "Configuración personalizada"
  :settings-page/custom-configuration                "Configuración personalizada"
  :settings-page/custom-date-format                  "Formato de fecha preferido"
  :settings-page/custom-date-format                  "Formato de fecha preferido"
  :settings-page/custom-date-format-notification     "Debes reindexar tu grafo para que este cambio tome efecto"
  :settings-page/custom-date-format-notification     "Debes reindexar tu grafo para que este cambio tome efecto"
  :settings-page/custom-date-format-warning          "¡Se requiere reindexar! ¡Las referencias existentes del diario podrían estar rotas!"
  :settings-page/custom-date-format-warning          "¡Se requiere reindexar! ¡Las referencias existentes del diario podrían estar rotas!"
  :settings-page/custom-global-configuration         "Configuración global personalizada"
  :settings-page/custom-global-configuration         "Configuración global personalizada"
- :settings-page/customize-shortcuts                 "Atajos de teclado"
  :settings-page/custom-theme                        "Tema personalizado"
  :settings-page/custom-theme                        "Tema personalizado"
- :settings-page/developer-mode-desc                 "El modo desarrollador permite a los colaboradores y desarrolladores de extensiones probar sus integraciones con Logseq más eficientemente."
  :settings-page/developer-mode                      "Modo desarrollador"
  :settings-page/developer-mode                      "Modo desarrollador"
- :settings-page/disable-sentry-desc                 " Logseq nunca va a recolectar tu base de datos de grafos local o vender tus datos."
+ :settings-page/developer-mode-desc                 "El modo desarrollador permite a los colaboradores y desarrolladores de extensiones probar sus integraciones con Logseq más eficientemente."
  :settings-page/disable-sentry                      "Enviar datos de uso y diagnósticos a Logseq"
  :settings-page/disable-sentry                      "Enviar datos de uso y diagnósticos a Logseq"
+ :settings-page/disable-sentry-desc                 " Logseq nunca va a recolectar tu base de datos de grafos local o vender tus datos."
  :settings-page/edit-config-edn                     "Editar config.edn (para este repositorio)"
  :settings-page/edit-config-edn                     "Editar config.edn (para este repositorio)"
  :settings-page/edit-custom-css                     "Editar custom.css"
  :settings-page/edit-custom-css                     "Editar custom.css"
  :settings-page/edit-export-css                     "Editar export.css"
  :settings-page/edit-export-css                     "Editar export.css"
@@ -647,27 +663,27 @@
  :settings-page/preferred-outdenting                "Disminución lógica de sangría"
  :settings-page/preferred-outdenting                "Disminución lógica de sangría"
  :settings-page/preferred-outdenting-tip            "El lado izquierdo muestra  sangría con la configuración por defecto, y el derecho muestra con sangría lógica habilitada"
  :settings-page/preferred-outdenting-tip            "El lado izquierdo muestra  sangría con la configuración por defecto, y el derecho muestra con sangría lógica habilitada"
  :settings-page/preferred-outdenting-tip-more       "→ Aprende más"
  :settings-page/preferred-outdenting-tip-more       "→ Aprende más"
- :settings-page/preferred-pasting-file-hint         "Cuando está habilitado, al pegar una imagen de internet la imagen será descargada e insertada. Cuando está deshabilitado, el enlace a la imagen será pegado."
  :settings-page/preferred-pasting-file              "Preferir pegar archivo"
  :settings-page/preferred-pasting-file              "Preferir pegar archivo"
+ :settings-page/preferred-pasting-file-hint         "Cuando está habilitado, al pegar una imagen de internet la imagen será descargada e insertada. Cuando está deshabilitado, el enlace a la imagen será pegado."
  :settings-page/preferred-workflow                  "Flujo de trabajo preferido"
  :settings-page/preferred-workflow                  "Flujo de trabajo preferido"
  :settings-page/revision                            "Revisión: "
  :settings-page/revision                            "Revisión: "
- :settings-page/shortcut-settings                   "Personalizar atajos"
  :settings-page/show-brackets                       "Mostrar corchetes"
  :settings-page/show-brackets                       "Mostrar corchetes"
  :settings-page/show-full-blocks                    "Mostrar todas las líneas de una referencia a bloque"
  :settings-page/show-full-blocks                    "Mostrar todas las líneas de una referencia a bloque"
  :settings-page/spell-checker                       "Corrector ortográfico"
  :settings-page/spell-checker                       "Corrector ortográfico"
+ :settings-page/sync                                "Sincronizar"
  :settings-page/sync-desc-1                         "Clic"
  :settings-page/sync-desc-1                         "Clic"
  :settings-page/sync-desc-2                         "aquí"
  :settings-page/sync-desc-2                         "aquí"
  :settings-page/sync-desc-3                         "por instrucciones de cómo configurar y usar Sync."
  :settings-page/sync-desc-3                         "por instrucciones de cómo configurar y usar Sync."
- :settings-page/sync-diff-merge-desc                "Unir actualizaciones locales con archivos remotos automáticamente cuando ocurre un conflicto, en lugar de sobreescribir los archivos remotos"
  :settings-page/sync-diff-merge                     "Habilitar unión inteligente al sincronizar"
  :settings-page/sync-diff-merge                     "Habilitar unión inteligente al sincronizar"
+ :settings-page/sync-diff-merge-desc                "Unir actualizaciones locales con archivos remotos automáticamente cuando ocurre un conflicto, en lugar de sobreescribir los archivos remotos"
  :settings-page/sync-diff-merge-warn                "La capacidad de unión inteligente solo se activa en un cliente después de la primera sincronización exitosa con el servidor remoto en el grafo en la nueva versión de LogSeq. Habilita esto en todos los dispositivos para alcanzar la mejor experiencia."
  :settings-page/sync-diff-merge-warn                "La capacidad de unión inteligente solo se activa en un cliente después de la primera sincronización exitosa con el servidor remoto en el grafo en la nueva versión de LogSeq. Habilita esto en todos los dispositivos para alcanzar la mejor experiencia."
- :settings-page/sync                                "Sincronizar"
  :settings-page/tab-account                         "Cuenta"
  :settings-page/tab-account                         "Cuenta"
  :settings-page/tab-advanced                        "Avanzado"
  :settings-page/tab-advanced                        "Avanzado"
  :settings-page/tab-assets                          "Recursos"
  :settings-page/tab-assets                          "Recursos"
  :settings-page/tab-editor                          "Editor"
  :settings-page/tab-editor                          "Editor"
  :settings-page/tab-features                        "Características"
  :settings-page/tab-features                        "Características"
  :settings-page/tab-general                         "General"
  :settings-page/tab-general                         "General"
+ :settings-page/tab-keymap                          "Mapa de teclado"
  :settings-page/tab-version-control                 "Control de versiones"
  :settings-page/tab-version-control                 "Control de versiones"
  :settings-page/theme-dark                          "oscuro"
  :settings-page/theme-dark                          "oscuro"
  :settings-page/theme-light                         "claro"
  :settings-page/theme-light                         "claro"
@@ -737,8 +753,8 @@
  :whiteboard/medium                                 "Medio"
  :whiteboard/medium                                 "Medio"
  :whiteboard/move-to-back                           "Mover al fondo"
  :whiteboard/move-to-back                           "Mover al fondo"
  :whiteboard/move-to-front                          "Mover al frente"
  :whiteboard/move-to-front                          "Mover al frente"
- :whiteboard/new-block-no-colon                     "Nuevo bloque"
  :whiteboard/new-block                              "Nuevo bloque:"
  :whiteboard/new-block                              "Nuevo bloque:"
+ :whiteboard/new-block-no-colon                     "Nuevo bloque"
  :whiteboard/new-page                               "Nueva página:"
  :whiteboard/new-page                               "Nueva página:"
  :whiteboard/new-whiteboard                         "Nueva pizarra"
  :whiteboard/new-whiteboard                         "Nueva pizarra"
  :whiteboard/opacity                                "Opacidad"
  :whiteboard/opacity                                "Opacidad"
@@ -749,8 +765,8 @@
  :whiteboard/open-youtube-url                       "Abrir url de YouTube"
  :whiteboard/open-youtube-url                       "Abrir url de YouTube"
  :whiteboard/pack-into-rectangle                    "Empacar en un rectángulo"
  :whiteboard/pack-into-rectangle                    "Empacar en un rectángulo"
  :whiteboard/pan                                    "Mover"
  :whiteboard/pan                                    "Mover"
- :whiteboard/paste-as-link                          "Pegar como enlace"
  :whiteboard/paste                                  "Pegar"
  :whiteboard/paste                                  "Pegar"
+ :whiteboard/paste-as-link                          "Pegar como enlace"
  :whiteboard/rectangle                              "Recargar"
  :whiteboard/rectangle                              "Recargar"
  :whiteboard/redo                                   "Rehacer"
  :whiteboard/redo                                   "Rehacer"
  :whiteboard/references                             "Referencias"
  :whiteboard/references                             "Referencias"
@@ -759,9 +775,9 @@
  :whiteboard/scale-level                            "Escalar nivel"
  :whiteboard/scale-level                            "Escalar nivel"
  :whiteboard/search-only-blocks                     "Buscar solo bloques"
  :whiteboard/search-only-blocks                     "Buscar solo bloques"
  :whiteboard/search-only-pages                      "Buscar solo páginas"
  :whiteboard/search-only-pages                      "Buscar solo páginas"
+ :whiteboard/select                                 "Seleccionar"
  :whiteboard/select-all                             "Seleccionar todo"
  :whiteboard/select-all                             "Seleccionar todo"
  :whiteboard/select-custom-color                    "Seleccionar color personalizado"
  :whiteboard/select-custom-color                    "Seleccionar color personalizado"
- :whiteboard/select                                 "Seleccionar"
  :whiteboard/shape                                  "Forma"
  :whiteboard/shape                                  "Forma"
  :whiteboard/shape-quick-links                      "Enlaces rápidos de forma"
  :whiteboard/shape-quick-links                      "Enlaces rápidos de forma"
  :whiteboard/small                                  "Pequeño"
  :whiteboard/small                                  "Pequeño"
@@ -770,6 +786,7 @@
  :whiteboard/stroke-type                            "Tipo de línea"
  :whiteboard/stroke-type                            "Tipo de línea"
  :whiteboard/text                                   "Texto"
  :whiteboard/text                                   "Texto"
  :whiteboard/toggle-grid                            "Alternar cuadrícula"
  :whiteboard/toggle-grid                            "Alternar cuadrícula"
+ :whiteboard/toggle-pen-mode                        "Alternar modo pluma"
  :whiteboard/triangle                               "Triángulo"
  :whiteboard/triangle                               "Triángulo"
  :whiteboard/twitter-url                            "url de Twitter"
  :whiteboard/twitter-url                            "url de Twitter"
  :whiteboard/undo                                   "Deshacer"
  :whiteboard/undo                                   "Deshacer"

+ 0 - 2
src/resources/dicts/fr.edn

@@ -259,7 +259,6 @@
     :settings-page/custom-date-format "Format de date préféré"
     :settings-page/custom-date-format "Format de date préféré"
     :settings-page/custom-global-configuration "Configuration globale personnalisée"
     :settings-page/custom-global-configuration "Configuration globale personnalisée"
     :settings-page/custom-theme "Thème personnalisé"
     :settings-page/custom-theme "Thème personnalisé"
-    :settings-page/customize-shortcuts "Raccourcis clavier"
     :settings-page/disable-sentry "Envoyer des données d'utilisation et de diagnostique à Logseq"
     :settings-page/disable-sentry "Envoyer des données d'utilisation et de diagnostique à Logseq"
     :settings-page/edit-custom-css "Modifier custom.css"
     :settings-page/edit-custom-css "Modifier custom.css"
     :settings-page/edit-export-css "Modifier export.css"
     :settings-page/edit-export-css "Modifier export.css"
@@ -281,7 +280,6 @@
     :settings-page/network-proxy "Proxy réseau"
     :settings-page/network-proxy "Proxy réseau"
     :settings-page/plugin-system "Extensions"
     :settings-page/plugin-system "Extensions"
     :settings-page/preferred-outdenting "Mise en retrait logique"
     :settings-page/preferred-outdenting "Mise en retrait logique"
-    :settings-page/shortcut-settings "Personnaliser les raccourcis"
     :settings-page/show-brackets "Montrer les parenthèses, crochets et accolades"
     :settings-page/show-brackets "Montrer les parenthèses, crochets et accolades"
     :settings-page/spell-checker "Vérification orthographique"
     :settings-page/spell-checker "Vérification orthographique"
     :settings-page/sync "Synchronisation"
     :settings-page/sync "Synchronisation"

+ 821 - 0
src/resources/dicts/id.edn

@@ -0,0 +1,821 @@
+{:accessibility/skip-to-main-content "Lompat ke konten utama"
+ :tutorial/text #resource "tutorials/tutorial-id.md"
+ :tutorial/dummy-notes #resource "tutorials/dummy-notes-id.md"
+ :on-boarding/demo-graph "Ini adalah grafik demo, perubahan tidak akan disimpan sampai Anda membuka folder lokal."
+ :on-boarding/add-graph "Tambahkan grafik"
+ :on-boarding/open-local-dir "Buka direktori lokal"
+ :on-boarding/new-graph-desc-1 "Logseq mendukung mode Markdown dan Org. Anda dapat membuka direktori yang sudah ada atau membuat direktori baru pada perangkat Anda, direktori juga dikenal sebagai folder. Data Anda hanya akan disimpan pada perangkat ini."
+ :on-boarding/new-graph-desc-2 "Setelah Anda membuka direktori Anda, ini akan membuat tiga folder dalam direktori tersebut:"
+ :on-boarding/new-graph-desc-3 "/journals - menyimpan halaman jurnal Anda"
+ :on-boarding/new-graph-desc-4 "/pages - menyimpan halaman lainnya"
+ :on-boarding/new-graph-desc-5 "/logseq - menyimpan konfigurasi, custom.css, dan beberapa metadata."
+ :on-boarding/welcome-whiteboard-modal-title "Kanvas baru untuk pikiran Anda."
+ :on-boarding/welcome-whiteboard-modal-description "Papan tulis adalah alat yang hebat untuk curah pendapat dan pengorganisasian. Sekarang Anda dapat menempatkan pemikiran Anda dari basis pengetahuan atau pemikiran baru di samping satu sama lain pada kanvas spasial untuk menghubungkan, mengasosiasikan, dan memahami dengan cara yang baru."
+ :on-boarding/welcome-whiteboard-modal-skip "Lewati"
+ :on-boarding/welcome-whiteboard-modal-start "Mulai menulis di papan tulis"
+ :on-boarding/tour-whiteboard-home "{1} Rumah untuk papan tulis Anda"
+ :on-boarding/tour-whiteboard-home-description "Papan tulis memiliki bagiannya sendiri di aplikasi di mana Anda dapat melihatnya sekilas, membuat yang baru, atau menghapusnya dengan mudah."
+ :on-boarding/tour-whiteboard-new "{1} Buat papan tulis baru"
+ :on-boarding/tour-whiteboard-new-description "Ada beberapa cara untuk membuat papan tulis baru. Salah satunya selalu ada di sini, di dasbor."
+ :on-boarding/tour-whiteboard-btn-next "Berikutnya"
+ :on-boarding/tour-whiteboard-btn-back "Kembali"
+ :on-boarding/tour-whiteboard-btn-finish "Selesai"
+ :on-boarding/quick-tour-btn-next "Berikutnya"
+ :on-boarding/quick-tour-btn-back "Kembali"
+ :on-boarding/quick-tour-btn-finish "Selesai"
+ :on-boarding/quick-tour-btn-skip "Lewati Tur Singkat"
+ :on-boarding/quick-tour-steps "LANGKAH "
+ :on-boarding/quick-tour-help-title "❓ Bantuan"
+ :on-boarding/quick-tour-help-desc "Anda bisa klik di sini untuk bantuan dan informasi lain tentang Logseq."
+ :on-boarding/quick-tour-journal-page-title "📆 Halaman Jurnal Harian"
+ :on-boarding/quick-tour-journal-page-desc-1 "Ini adalah halaman jurnal harian hari ini. Di sini Anda dapat menuangkan pemikiran, pembelajaran, dan ide Anda. Jangan khawatir tentang pengorganisasian. Tulis saja dan"
+ :on-boarding/quick-tour-journal-page-desc-2 "[[tautan]]"
+ :on-boarding/quick-tour-journal-page-desc-3 "pemikiran Anda."
+ :on-boarding/quick-tour-left-sidebar-title "👀 Bilah Sisi Kiri"
+ :on-boarding/quick-tour-left-sidebar-desc "Buka bilah sisi kiri untuk menjelajahi item menu penting di Logseq."
+ :on-boarding/quick-tour-favorites-title "⭐️ Favorit"
+ :on-boarding/quick-tour-favorites-desc-1 "Sematkan halaman favorit Anda melalui menu `... `di halaman mana pun."
+ :on-boarding/quick-tour-favorites-desc-2 "Kami juga telah menambahkan beberapa halaman template di sini untuk membantu Anda memulai. Anda dapat menghapusnya setelah Anda mulai menulis catatan Anda sendiri."
+ :on-boarding/command-palette-quick-tour "Tur singkat untuk orientasi"
+ :on-boarding/importing-main-title "Mengimpor catatan yang ada"
+ :on-boarding/importing-main-desc "Anda juga dapat melakukan ini nanti di aplikasi."
+ :on-boarding/importing-title "Apakah Anda sudah memiliki catatan yang ingin Anda impor?"
+ :on-boarding/importing-desc "Jika ia dalam format JSON, EDN atau Markdown, Logseq dapat bekerja dengannya."
+ :on-boarding/importing-roam-desc "Mengimpor Ekspor JSON dari grafik Roam Anda"
+ :on-boarding/importing-lsq-desc "Mengimpor EDN atau Ekspor JSON dari grafik Logseq Anda"
+ :on-boarding/importing-opml-desc " Mengimpor berkas OPML"
+ :on-boarding/main-title (fn [] ["Selamat datang di " [:strong "Logseq!"]])
+ :on-boarding/main-desc "Pertama, Anda harus memilih folder di mana Logseq akan menyimpan pemikiran, ide, catatan Anda."
+ :on-boarding/section-btn-title "Pilih folder"
+ :on-boarding/section-btn-desc "Buka direktori yang ada atau Buat baru"
+ :on-boarding/section-title "Bagaimana Logseq menghemat pekerjaan Anda"
+ :on-boarding/section-desc "Di dalam direktori yang Anda pilih, Logseq akan membuat 4 folder."
+ :on-boarding/section-tip-1 "Setiap halaman adalah berkas yang disimpan hanya pada {1} Anda."
+ :on-boarding/section-tip-2 "Anda dapat memilih untuk menyinkronkannya nanti."
+ :on-boarding/section-assets "Grafik & Dokumen"
+ :on-boarding/section-computer "komputer"
+ :on-boarding/section-journals "Catatan harian"
+ :on-boarding/section-pages "HALAMAN"
+ :on-boarding/section-phone "telepon"
+ :on-boarding/section-app "APP Internal"
+ :on-boarding/section-config "Berkas Konfigurasi"
+ :query/config-property-settings "Pengaturan properti untuk kueri ini:"
+ :bug-report/main-title "Laporan bug"
+ :bug-report/clipboard-inspector-title "Pemeriksa data papan klip"
+ :bug-report/main-desc "Dapatkah Anda membantu kami dengan mengirimkan laporan bug? Kami akan menyelesaikannya sesegera mungkin."
+ :bug-report/section-clipboard-title "Apakah bug yang Anda temui terkait dengan fitur-fitur ini?"
+ :bug-report/section-clipboard-desc "Anda dapat menggunakan alat bantu praktis ini untuk memberikan informasi tambahan kepada kami."
+ :bug-report/section-clipboard-btn-title "Pembantu papan klip"
+ :bug-report/section-clipboard-btn-desc "Memeriksa dan mengumpulkan data papan klip"
+ :bug-report/section-issues-title "Atau..."
+ :bug-report/section-issues-desc "Jika tidak ada alat bantu yang tersedia bagi Anda untuk mengumpulkan informasi tambahan, silakan laporkan bug secara langsung."
+ :bug-report/section-issues-btn-title "Mengirimkan laporan bug"
+ :bug-report/section-issues-btn-desc "Bantu Jadikan Logseq Lebih Baik!"
+ :bug-report/inspector-page-desc-1 "Tekan Ctrl+V / ⌘+V untuk memeriksa data papan klip Anda"
+ :bug-report/inspector-page-desc-2 "atau klik di sini untuk menempelkan jika Anda menggunakan versi seluler"
+ :bug-report/inspector-page-placeholder "Tekan lama di sini untuk menempelkan jika Anda menggunakan ponsel"
+ :bug-report/inspector-page-tip "Ada yang salah? Tidak masalah, klik untuk kembali ke langkah sebelumnya."
+ :bug-report/inspector-page-btn-back "Kembali"
+ :bug-report/inspector-page-btn-copy "Salin hasilnya"
+ :bug-report/inspector-page-copy-notif "Disalin ke papan klip!"
+ :bug-report/inspector-page-btn-create-issue "Buat masalah"
+ :bug-report/inspector-page-desc-clipboard "Berikut ini adalah data yang dibaca dari clipboard."
+ :bug-report/inspector-page-desc-copy "Jika ini boleh dibagikan, klik tombol salin."
+ :bug-report/inspector-page-desc-create-issue "Sekarang Anda dapat melaporkan hasil yang ditempelkan ke clipboard Anda. Silakan tempelkan hasilnya di bagian 'Konteks Tambahan' dan sebutkan dari mana Anda menyalin konten asli. Terima kasih!"
+ :help/title-usage "Penggunaan"
+ :help/title-community "Komunitas"
+ :help/title-development "Pengembangan"
+ :help/title-about "Tentang"
+ :help/title-terms "Ketentuan"
+ :help/start "Memulai"
+ :help/about "Tentang Logseq"
+ :help/roadmap "Peta jalan"
+ :help/bug "Laporan bug"
+ :help/feature "Permintaan fitur"
+ :help/changelog "Catatan perubahan"
+ :help/blog "Blog Logseq"
+ :help/docs "Dokumentasi"
+ :help/privacy "Kebijakan privasi"
+ :help/terms "Ketentuan"
+ :help/forum-community "Komunitas forum"
+ :help/awesome-logseq "Logseq yang mengagumkan"
+ :help/shortcuts "Pintasan keyboard"
+ :help/shortcuts-triggers "Pemicu"
+ :help/shortcut "Pintasan"
+ :help/slash-autocomplete "Pelengkapan otomatis garis miring"
+ :help/block-content-autocomplete "Pelengkapan otomatis konten blok"
+ :help/reference-autocomplete "Pelengkapan otomatis referensi halaman"
+ :help/block-reference "Blok referensi"
+ :help/open-link-in-sidebar "Buka tautan di bilah sisi"
+ :more "Lebih lanjut"
+ :search/result-for "Hasil pencarian untuk "
+ :search/items "item"
+ :search/page-names "Nama halaman pencarian"
+ :search/recent "Pencarian terbaru:"
+ :search/blocks-in-page "Blok pencarian dalam halaman:"
+ :search/command-palette-tip-1 "Tip:"
+ :search/command-palette-tip-2 "Untuk membuka palet perintah"
+ :search/cache-outdated "Cache sudah kedaluwarsa. Silakan klik tombol 'Indeks ulang' di menu tarik-turun grafik."
+ :search-item/whiteboard "Papan tulis"
+ :search-item/page "Halaman"
+ :search-item/file "Berkas"
+ :search-item/block "Blok"
+ :search-item/no-result "Tidak ada hasil yang cocok"
+ :help/context-menu "Blokir menu konteks"
+ :help/markdown-syntax "Sintaks penurunan harga"
+ :help/org-mode-syntax "Sintaks mode org"
+ :bold "Cetak tebal"
+ :italics "Cetak miring"
+ :highlight "Sorot"
+ :strikethrough "Dicoret"
+ :code "Kode"
+ :untitled "Tanpa judul"
+ :right-side-bar/help "Bantuan"
+ :right-side-bar/switch-theme "Mode tema"
+ :right-side-bar/contents "Konten"
+ :right-side-bar/page-graph "Grafik halaman"
+ :right-side-bar/history "(Dev) Urungkan/Ubah riwayat"
+ :right-side-bar/history-undos "Urungkan"
+ :right-side-bar/history-redos "Pengulangan"
+ :right-side-bar/history-global "global"
+ :right-side-bar/history-pageonly "halaman saja"
+ :right-side-bar/block-ref "Referensi blok"
+ :right-side-bar/graph-view "Tampilan grafik"
+ :right-side-bar/all-pages "Semua halaman"
+ :right-side-bar/whiteboards "Papan tulis"
+ :right-side-bar/flashcards "Kartu flash"
+ :right-side-bar/new-page "Halaman baru"
+ :right-side-bar/show-journals "Tampilkan Jurnal"
+ :right-side-bar/separator "Pengatur ukuran bilah sisi kanan"
+ :right-side-bar/toggle-right-sidebar "Beralih ke bilah sisi kanan"
+ :right-side-bar/pane-close "Tutup"
+ :right-side-bar/pane-close-others "Tutup yang lain"
+ :right-side-bar/pane-close-all "Tutup semua"
+ :right-side-bar/pane-collapse "Menciutkan"
+ :right-side-bar/pane-collapse-others "Menciutkan yang lain"
+ :right-side-bar/pane-collapse-all "Tutup semua"
+ :right-side-bar/pane-expand "Memperluas"
+ :right-side-bar/pane-expand-all "Perluas semua"
+ :right-side-bar/pane-open-as-page "Buka sebagai halaman"
+ :right-side-bar/pane-more "Lebih banyak"
+ :left-side-bar/switch "Beralih ke:"
+ :left-side-bar/journals "Jurnal"
+ :left-side-bar/create "Membuat"
+ :left-side-bar/new-page "Halaman baru"
+ :left-side-bar/new-whiteboard "Papan tulis baru"
+ :left-side-bar/nav-favorites "Favorit"
+ :left-side-bar/nav-recent-pages "Halaman terbaru"
+ :page/something-went-wrong "Ada masalah"
+ :page/logseq-is-having-a-problem "Logseq mengalami masalah. Untuk mencoba mengembalikannya ke keadaan yang berfungsi, silakan coba langkah-langkah aman berikut ini secara berurutan:"
+ :page/step "Langkah {1}"
+ :page/try "Coba"
+ :page/slide-view "Tampilkan sebagai slide"
+ :page/slide-view-tip-go-fullscreen (fn [] [[:span.opacity-70 "Tip: tekan "] [:code "f"] [:span.opacity-70 " untuk masuk ke mode layar penuh"]])
+ :page/delete-confirmation "Apakah Anda yakin ingin menghapus halaman ini beserta berkasnya?"
+ :page/open-in-finder "Buka dalam direktori"
+ :page/open-with-default-app "Buka dengan aplikasi default"
+ :page/make-public "Buat publik untuk dipublikasikan"
+ :page/version-history "Lihat riwayat halaman"
+ :page/open-backup-directory "Buka direktori cadangan halaman"
+ :page/make-private "Buat pribadi"
+ :page/delete "Hapus halaman"
+ :page/add-to-favorites "Tambahkan ke Favorit"
+ :page/unfavorite "Hapus dari Favorit"
+ :page/show-journals "Tampilkan jurnal"
+ :page/show-whiteboards "Tampilkan papan tulis"
+ :block/name "Nama Halaman"
+ :page/earlier "Lebih awal"
+ :page/copy-page-url "Salin URL halaman"
+ :page/illegal-page-name "Nama halaman tidak sah!"
+ :page/page-already-exists "Halaman “{1}” sudah ada!"
+ :page/whiteboard-to-journal-error "Halaman papan tulis tidak dapat diubah namanya menjadi judul jurnal!"
+ :file/name "Nama berkas"
+ :file/last-modified-at "Terakhir diubah pada"
+ :file/no-data "Tidak ada data"
+ :file/format-not-supported "Format .{1} tidak didukung."
+ :file/validate-existing-file-error "Halaman sudah ada dengan berkas lain: {1}, berkas saat ini: {2}. Harap pertahankan hanya satu dari mereka dan re-indeks grafik Anda."
+ :file-rn/re-index "Re-indeks sangat dianjurkan setelah berkas diubah nama dan pada perangkat lain setelah sinkronisasi."
+ :file-rn/need-action "Tindakan penggantian nama berkas disarankan agar sesuai dengan format baru. Re-indeks diperlukan pada semua perangkat saat berkas yang diubah nama disinkronkan."
+ :file-rn/or-select-actions " atau ubah nama berkas secara individual di bawah, lalu "
+ :file-rn/or-select-actions-2 ". Tindakan ini tidak tersedia setelah Anda menutup panel ini."
+ :file-rn/legend "🟢 Tindakan penggantian nama opsional; 🟡 Tindakan penggantian nama diperlukan untuk menghindari perubahan judul; 🔴 Perubahan besar."
+ :file-rn/close-panel "Tutup Panel"
+ :file-rn/all-action "Terapkan Semua Tindakan! ({1})"
+ :file-rn/select-format "(Opsi Mode Pengembang, Berbahaya!) Pilih format nama berkas"
+ :file-rn/rename "ubah nama berkas \"{1}\" menjadi \"{2}\""
+ :file-rn/apply-rename "Terapkan operasi penggantian nama berkas"
+ :file-rn/suggest-rename "Tindakan diperlukan: "
+ :file-rn/otherwise-breaking "Atau judul akan menjadi"
+ :file-rn/no-action "Bagus! Tidak diperlukan tindakan lebih lanjut."
+ :file-rn/confirm-proceed "Perbarui format!"
+ :file-rn/select-confirm-proceed "Pengembang: tulis format"
+ :file-rn/unreachable-title "Peringatan! Nama halaman akan menjadi {1} dalam format nama berkas saat ini, kecuali properti title:: diatur secara manual"
+ :file-rn/optional-rename "Saran: "
+ :file-rn/format-deprecated "Anda saat ini menggunakan format yang sudah ketinggalan zaman. Diperbarui ke format terbaru sangat disarankan. Harap cadangkan data Anda dan tutup aplikasi Logseq di perangkat lain sebelum operasi ini."
+ :file-rn/filename-desc-1 "Pengaturan ini mengkonfigurasi bagaimana sebuah halaman disimpan ke dalam berkas Logseq menyimpan halaman ke dalam berkas dengan nama yang sama."
+ :file-rn/filename-desc-2 "Beberapa karakter seperti \"/\" atau \"?\" tidak valid untuk nama berkas"
+ :file-rn/filename-desc-3 "Logseq mengganti karakter yang tidak valid dengan ekivalen URL mereka untuk membuatnya valid (misalnya, \"?\" menjadi \"%3F\")."
+ :file-rn/filename-desc-4 "Pemisah ruang nama \"/\" juga diganti dengan \"___\" (tiga garis bawah) untuk pertimbangan estetika."
+ :file-rn/instruct-1 "Ini adalah proses 2 langkah untuk memperbarui format nama berkas:"
+ :file-rn/instruct-2 "1. Klik "
+ :file-rn/instruct-3 "2. Ikuti instruksi di bawah ini untuk mengubah nama berkas ke format baru:"
+ :page/created-at "Dibuat pada"
+ :page/updated-at "Diperbarui pada"
+ :page/backlinks "Tautan balik"
+ :linked-references/filter-search "Cari di halaman terhubung"
+ :editor/block-search "Cari blok"
+ :text/image "Gambar"
+ :asset/show-in-folder "Tampilkan gambar di folder"
+ :asset/open-in-browser "Buka gambar di peramban"
+ :asset/delete "Hapus gambar"
+ :asset/copy "Salin gambar"
+ :asset/maximize "Perbesar gambar"
+ :asset/confirm-delete "Apakah Anda yakin ingin menghapus {1} ini?"
+ :asset/physical-delete "Hapus juga berkasnya (perhatikan bahwa ini tidak dapat dikembalikan)"
+ :color/gray "Abu-abu"
+ :color/red "Merah"
+ :color/yellow "Kuning"
+ :color/green "Hijau"
+ :color/blue "Biru"
+ :color/purple "Ungu"
+ :color/pink "Merah muda"
+ :editor/copy "Salin"
+ :editor/cut "Potong"
+ :editor/expand-block-children "Perluas semua"
+ :editor/collapse-block-children "Tutup semua"
+ :editor/delete-selection "Hapus blok terpilih"
+ :editor/cycle-todo "Putar status TODO item saat ini"
+ :dev/show-page-data "(Dev) Tampilkan data halaman"
+ :dev/show-block-data "(Dev) Tampilkan data blok"
+ :dev/show-block-ast "(Dev) Tampilkan AST blok"
+ :dev/show-page-ast "(Dev) Tampilkan AST halaman"
+ :content/copy-export-as "Salin / Ekspor sebagai.."
+ :content/copy-block-url "Salin URL blok"
+ :content/copy-block-ref "Salin referensi blok"
+ :content/copy-block-emebed "Salin penanaman blok"
+ :content/copy-ref "Salin referensi ini"
+ :content/delete-ref "Hapus referensi ini"
+ :content/replace-with-text "Ganti dengan teks"
+ :content/replace-with-embed "Ganti dengan penanaman"
+ :content/open-in-sidebar "Buka di sidebar"
+ :content/click-to-edit "Klik untuk menyunting"
+ :context-menu/make-a-flashcard "Buat Kartu Belajar"
+ :context-menu/toggle-number-list "Alihkan daftar nomor"
+ :context-menu/preview-flashcard "Pratinjau Kartu Belajar"
+ :context-menu/make-a-template "Buat Templat"
+ :context-menu/input-template-name "Apa nama templatnya?"
+ :context-menu/template-include-parent-block "Termasuk blok induk dalam templat?"
+ :context-menu/template-exists-warning "Templat sudah ada!"
+ :settings-page/git-tip "Jika Anda telah mengaktifkan Sinkronisasi Logseq, Anda dapat melihat riwayat oenyuntingan halaman secara langsung. Bagian ini hanya untuk yang berpengalaman dalam teknologi."
+ :settings-page/git-desc-1 "Untuk melihat riwayat penyuntingan halaman, klik tiga titik horizontal di sudut kanan atas dan pilih \"Lihat riwayat halaman\"."
+ :settings-page/git-desc-2 "Untuk pengguna profesional, Logseq juga mendukung penggunaan "
+ :settings-page/git-desc-3 " untuk kontrol versi. Gunakan Git dengan risiko Anda sendiri karena masalah umum Git tidak didukung oleh tim Logseq."
+ :settings-page/git-switcher-label "Aktifkan komit otomatis Git"
+ :settings-page/git-commit-delay "Detik komit otomatis Git"
+ :settings-page/git-confirm "Anda perlu me-restart aplikasi setelah memperbarui pengaturan Git."
+ :settings-page/edit-config-edn "Sunting config.edn"
+ :settings-page/edit-global-config-edn "Sunting global config.edn"
+ :settings-page/edit-custom-css "Sunting custom.css"
+ :settings-page/edit-export-css "Sunting export.css"
+ :settings-page/edit-setting "Sunting"
+ :settings-page/custom-configuration "Konfigurasi kustom"
+ :settings-page/custom-global-configuration "Konfigurasi global kustom"
+ :settings-page/theme-light "terang"
+ :settings-page/theme-dark "gelap"
+ :settings-page/theme-system "sistem"
+ :settings-page/custom-theme "Tema kustom"
+ :settings-page/export-theme "Ekspor tema"
+ :settings-page/show-brackets "Tampilkan tanda kurung"
+ :settings-page/spell-checker "Pemeriksa ejaan"
+ :settings-page/auto-updater "Pembaruan otomatis"
+ :settings-page/disable-sentry "Kirim data penggunaan dan diagnosa ke Logseq"
+ :settings-page/disable-sentry-desc "Logseq tidak akan pernah mengumpulkan database grafik lokal Anda atau menjual data Anda."
+ :settings-page/preferred-outdenting "Pengaturan penyingkiran logis"
+ :settings-page/preferred-outdenting-tip "Sisi kiri menunjukkan penyingkiran dengan pengaturan default, dan sisi kanan menunjukkan penyingkiran logis yang diaktifkan "
+ :settings-page/preferred-outdenting-tip-more "→ Pelajari lebih lanjut"
+ :settings-page/show-full-blocks "Tampilkan semua baris referensi blok"
+ :settings-page/auto-expand-block-refs "Perluas referensi blok secara otomatis saat zoom-in"
+ :settings-page/auto-expand-block-refs-tip "Opsi ini mengontrol apakah referensi blok akan diperluas secara otomatis saat zoom-in."
+ :settings-page/custom-date-format "Format tanggal yang diinginkan"
+ :settings-page/custom-date-format-warning "Diperlukan re-indeks! Referensi jurnal yang ada akan rusak!"
+ :settings-page/custom-date-format-notification "Anda harus me-re-indeks grafik Anda agar perubahan ini berlaku"
+ :settings-page/preferred-pasting-file-hint "Ketika diaktifkan, menempelkan gambar dari internet akan mengunduh dan menyisipkan gambar. Ketika dinonaktifkan, akan menempelkan tautan ke gambar."
+ :settings-page/preferred-file-format "Format berkas yang diinginkan"
+ :settings-page/preferred-workflow "Alur kerja yang diinginkan"
+ :settings-page/preferred-pasting-file "Lebih suka menempelkan berkas"
+ :settings-page/enable-shortcut-tooltip "Aktifkan tooltip pintasan"
+ :settings-page/enable-timetracking "Pelacakan waktu"
+ :settings-page/enable-tooltip "Tooltip"
+ :settings-page/enable-journals "Jurnal"
+ :settings-page/enable-all-pages-public "Semua halaman menjadi publik saat dipublikasikan"
+ :settings-page/home-default-page "Atur halaman beranda default"
+ :settings-page/enable-block-time "Waktu blok"
+ :settings-page/clear-cache "Hapus cache"
+ :settings-page/clear "Hapus"
+ :settings-page/clear-cache-warning "Menghapus cache akan menghapus grafik yang terbuka. Anda akan kehilangan perubahan yang belum disimpan."
+ :settings-page/developer-mode "Mode pengembang"
+ :settings-page/developer-mode-desc "Mode pengembang membantu kontributor dan pengembang ekstensi menguji integrasi mereka dengan Logseq dengan lebih efisien."
+ :settings-page/current-version "Versi saat ini"
+ :settings-page/tab-general "Umum"
+ :settings-page/tab-editor "Penyunting"
+ :settings-page/tab-keymap "Pemetaan tombol"
+ :settings-page/tab-version-control "Kontrol versi"
+ :settings-page/tab-account "Akun"
+ :settings-page/tab-advanced "Lanjutan"
+ :settings-page/tab-assets "Aset"
+ :settings-page/tab-features "Fitur"
+ :settings-page/plugin-system "Pengaya"
+ :settings-page/enable-flashcards "Kartu Belajar"
+ :settings-page/network-proxy "Proxy jaringan"
+ :settings-page/filename-format "Format nama berkas"
+ :settings-page/alpha-features "Fitur Alpha"
+ :settings-page/beta-features "Fitur Beta"
+ :settings-page/login-prompt "Untuk mengakses fitur-fitur baru sebelum orang lain, Anda harus menjadi Sponsor atau Pendukung Logseq di Open Collective dan oleh karena itu harus masuk terlebih dahulu."
+ :settings-page/sync "Sinkronisasi"
+ :settings-page/sync-desc-1 "Klik"
+ :settings-page/sync-desc-2 "di sini"
+ :settings-page/sync-desc-3 "untuk petunjuk tentang cara menyiapkan dan menggunakan Sinkronisasi."
+ :settings-page/sync-diff-merge "Aktifkan penggabungan cerdas saat sinkronisasi"
+ :settings-page/sync-diff-merge-desc "Gabungkan pembaruan lokal dengan berkas remote secara otomatis saat terjadi konflik, daripada menimpa berkas remote."
+ :settings-page/sync-diff-merge-warn "Kemampuan penggabungan cerdas hanya diaktifkan pada perangkat setelah sinkronisasi pertama yang berhasil dengan server remote pada grafik dalam versi Logseq yang baru. Aktifkan ini di semua perangkat untuk mencapai pengalaman terbaik."
+ :settings-page/enable-whiteboards "Papan tulis"
+ :settings-page/native-titlebar "Bilah judul asli"
+ :settings-page/native-titlebar-desc "Aktifkan bilah judul jendela asli di Windows dan Linux."
+ :settings-page/check-for-updates "Periksa pembaruan"
+ :settings-page/checking "Memeriksa ..."
+ :settings-page/revision "Revisi: "
+ :settings-page/changelog "Apa yang baru?"
+ :settings-page/app-updated "Aplikasi Anda sudah terbaru 🎉"
+ :settings-page/update-available "Ditemukan rilis baru "
+ :settings-page/update-error-1 "⚠️ Ups, Ada Sesuatu yang Salah!"
+ :settings-page/update-error-2 " Silakan cek "
+ :settings-permission/start-granting "Berikan"
+
+ :yes "Ya"
+
+ :submit "Kirim"
+ :cancel "Batal"
+ :close "Tutup"
+ :delete "Hapus"
+ :save "Simpan"
+ :reset "Atur ulang"
+ :type "Jenis"
+ :host "Host"
+ :port "Port"
+ :re-index "Re-indeks"
+ :re-index-detail "Membangun ulang grafik"
+ :re-index-multiple-windows-warning "Anda perlu menutup jendela lain sebelum me-re-indeks grafik ini."
+ :re-index-discard-unsaved-changes-warning "Re-indeks akan membuang grafik saat ini, dan kemudian memproses semua berkas lagi sesuai dengan yang saat ini tersimpan di disk. Anda akan kehilangan perubahan yang belum disimpan dan ini mungkin memakan waktu. Lanjutkan?"
+ :open-new-window "Jendela baru"
+ :sync-from-local-files "Segarkan"
+ :sync-from-local-files-detail "Impor perubahan dari berkas lokal"
+ :sync-from-local-changes-detected "Segarkan mendeteksi dan memproses berkas yang diubah di disk Anda yang telah berbeda dari konten halaman Logseq saat ini. Lanjutkan?"
+ 
+ :search/publishing "Cari"
+ :search "Cari atau buat halaman"
+ :whiteboard/link-whiteboard-or-block "Tautkan papan tulis/halaman/blok"
+ :whiteboard/align-left "Rata kiri"
+ :whiteboard/align-center-horizontally "Rata tengah secara horizontal"
+ :whiteboard/align-right "Rata kanan"
+ :whiteboard/distribute-horizontally "Sebarkan secara horizontal"
+ :whiteboard/align-top "Rata atas"
+ :whiteboard/align-center-vertically "Rata tengah secara vertikal"
+ :whiteboard/align-bottom "Rata bawah"
+ :whiteboard/distribute-vertically "Sebarkan secara vertikal"
+ :whiteboard/pack-into-rectangle "Paket ke dalam persegi panjang"
+ :whiteboard/zoom-to-fit "Perbesar untuk cocokkan"
+ :whiteboard/ungroup "Pecah kelompok"
+ :whiteboard/group "Kelompok"
+ :whiteboard/cut "Potong"
+ :whiteboard/copy "Salin"
+ :whiteboard/paste "Tempel"
+ :whiteboard/paste-as-link "Tempel sebagai tautan"
+ :whiteboard/export "Ekspor"
+ :whiteboard/select-all "Pilih semua"
+ :whiteboard/deselect-all "Batal pilih semua"
+ :whiteboard/lock "Kunci"
+ :whiteboard/unlock "Buka kunci"
+ :whiteboard/delete "Hapus"
+ :whiteboard/flip-horizontally "Putar secara horizontal"
+ :whiteboard/flip-vertically "Putar secara vertikal"
+ :whiteboard/move-to-front "Pindah ke depan"
+ :whiteboard/move-to-back "Pindah ke belakang"
+ :whiteboard/dev-print-shape-props "(Dev) Cetak properti bentuk"
+ :whiteboard/auto-resize "Perbesar otomatis"
+ :whiteboard/expand "Perluas"
+ :whiteboard/collapse "Ciutkan"
+ :whiteboard/website-url "URL situs web"
+ :whiteboard/reload "Muat ulang"
+ :whiteboard/open-website-url "Buka URL situs web"
+ :whiteboard/youtube-url "URL YouTube"
+ :whiteboard/open-youtube-url "Buka URL YouTube"
+ :whiteboard/twitter-url "URL Twitter"
+ :whiteboard/open-twitter-url "Buka URL Twitter"
+ :whiteboard/fill "Isi"
+ :whiteboard/stroke-type "Jenis garis"
+ :whiteboard/arrow-head "Kepala panah"
+ :whiteboard/bold "Tebal"
+ :whiteboard/italic "Miring"
+ :whiteboard/undo "Batal"
+ :whiteboard/redo "Ulang"
+ :whiteboard/zoom-in "Perbesar"
+ :whiteboard/zoom-out "Perkecil"
+ :whiteboard/select "Pilih"
+ :whiteboard/pan "Geser"
+ :whiteboard/add-block-or-page "Tambahkan blok atau halaman"
+ :whiteboard/draw "Gambar"
+ :whiteboard/highlight "Sorot"
+ :whiteboard/eraser "Penghapus"
+ :whiteboard/connector "Penyambung"
+ :whiteboard/text "Teks"
+ :whiteboard/color "Warna"
+ :whiteboard/select-custom-color "Pilih warna kustom"
+ :whiteboard/opacity "Keburaman"
+ :whiteboard/extra-small "Sangat Kecil"
+ :whiteboard/small "Kecil"
+ :whiteboard/medium "Sedang"
+ :whiteboard/large "Besar"
+ :whiteboard/extra-large "Sangat Besar"
+ :whiteboard/huge "Besar Sekali"
+ :whiteboard/scale-level "Tingkat Skala"
+ :whiteboard/rectangle "Persegi Panjang"
+ :whiteboard/circle "Lingkaran"
+ :whiteboard/triangle "Segitiga"
+ :whiteboard/shape "Bentuk"
+ :whiteboard/open-page "Buka halaman"
+ :whiteboard/open-page-in-sidebar "Buka halaman di sidebar"
+ :whiteboard/remove-link "Hapus tautan"
+ :whiteboard/link "Tautan"
+ :whiteboard/references "Referensi"
+ :whiteboard/link-to-any-page-or-block "Tautkan ke halaman atau blok apa pun"
+ :whiteboard/start-typing-to-search "Mulai mengetik untuk mencari..."
+ :whiteboard/new-block-no-colon "Blok baru"
+ :whiteboard/new-block "Blok baru:"
+ :whiteboard/new-page "Halaman baru:"
+ :whiteboard/new-whiteboard "Papan tulis baru"
+ :whiteboard/search-only-blocks "Cari hanya blok"
+ :whiteboard/search-only-pages "Cari hanya halaman"
+ :whiteboard/cache-outdated "Cache sudah ketinggalan zaman. Klik tombol 'Re-indeks' dalam menu tarik-turun grafik."
+ :whiteboard/shape-quick-links "Tautan Cepat Bentuk"
+ :whiteboard/edit-pdf "Sunting PDF"
+ :whiteboard/dashboard-card-new-whiteboard "Papan tulis baru"
+ :whiteboard/dashboard-card-created "Dibuat "
+ :whiteboard/dashboard-card-edited "Disunting "
+ :whiteboard/toggle-grid "Alihkan grid"
+ :whiteboard/snap-to-grid "Tetapkan ke grid"
+ :flashcards/modal-welcome-title "Saatnya membuat kartu!"
+ :flashcards/modal-welcome-desc-1 "Anda dapat menambahkan \"#card\" ke blok apa pun untuk mengubahnya menjadi kartu atau memicu \"/cloze\" untuk menambahkan beberapa cloze."
+ :flashcards/modal-welcome-desc-2 "Anda dapat "
+ :flashcards/modal-welcome-desc-3 "klik tautan ini"
+ :flashcards/modal-welcome-desc-4 " untuk memeriksa dokumentasinya."
+ :flashcards/modal-btn-show-answers "Tampilkan jawaban"
+ :flashcards/modal-btn-hide-answers "Sembunyikan jawaban"
+ :flashcards/modal-btn-show-clozes "Tampilkan cloze"
+ :flashcards/modal-btn-next-card "Berikutnya"
+ :flashcards/modal-btn-reset "Atur ulang"
+ :flashcards/modal-btn-reset-tip "Atur ulang kartu ini sehingga Anda dapat memeriksanya segera."
+ :flashcards/modal-btn-forgotten "Lupa"
+ :flashcards/modal-btn-remembered "Ingat"
+ :flashcards/modal-btn-recall "Memerlukan waktu untuk diingat"
+ :flashcards/modal-finished "Selamat, Anda telah meninjau semua kartu untuk pertanyaan ini, sampai jumpa berikutnya! 💯"
+ :flashcards/modal-select-all "Semua"
+ :flashcards/modal-select-switch "Beralih ke"
+ :flashcards/modal-current-total "Saat ini/Total"
+ :flashcards/modal-overdue-total "Ketinggalan/Total"
+ :flashcards/modal-toggle-preview-mode "Alihkan mode pratinjau"
+ :flashcards/modal-toggle-random-mode "Alihkan mode acak"
+
+ :page-search "Cari di halaman ini"
+ :graph-search "Cari grafik"
+ :home "Beranda"
+ :new-page "Halaman baru:"
+ :whiteboard "Papan tulis"
+ :whiteboards "Papan tulis"
+ :new-whiteboard "Papan tulis baru:"
+ :new-graph "Tambahkan grafik baru"
+ :graph "Grafik"
+ :graph/persist "Logseq sedang menyinkronkan status internal, harap tunggu beberapa detik."
+ :graph/persist-error "Sinkronisasi status internal gagal."
+ :graph/save "Menyimpan..."
+ :graph/save-success "Tersimpan dengan sukses"
+ :graph/save-error "Gagal menyimpan"
+ :graph/all-graphs "Semua grafik"
+ :graph/local-graphs "Grafik lokal:"
+ :graph/remote-graphs "Grafik jarak jauh:"
+ :export "Ekspor"
+ :export-graph "Ekspor grafik"
+ :export-page "Ekspor halaman"
+ :export-markdown "Ekspor sebagai Markdown standar (tanpa properti blok)"
+ :export-opml "Ekspor sebagai OPML"
+ :export-public-pages "Ekspor halaman publik"
+ :export-json "Ekspor sebagai JSON"
+ :export-roam-json "Ekspor sebagai JSON Roam"
+ :export-edn "Ekspor sebagai EDN"
+ :export-transparent-background "Latar belakang transparan"
+ :export-copy-to-clipboard "Salin ke clipboard"
+ :export-copied-to-clipboard "Disalin ke clipboard!"
+ :export-save-to-file "Simpan ke berkas"
+ :all-graphs "Semua grafik"
+ :all-pages "Semua halaman"
+ :all-whiteboards "Semua papan tulis"
+ :all-files "Semua berkas"
+ :remove-orphaned-pages "Hapus halaman yang tidak terhubung?"
+ :all-journals "Semua jurnal"
+ :settings "Pengaturan"
+ :settings-of-plugins "Pengaya"
+ :plugins "Pengaya"
+ :themes "Tema"
+ :relaunch-confirm-to-work "Harus mengulang aplikasi untuk membuatnya berfungsi. Apakah Anda ingin me-restart sekarang?"
+ :import "Impor"
+ :importing "Mengimpor"
+ :join-community "Bergabung dengan komunitas"
+ :discourse-title "Forum kami!"
+ :help-shortcut-title "Klik untuk memeriksa pintasan dan tips lainnya"
+ :loading "Memuat..."
+ :parsing-files "Menganalisis berkas"
+ :loading-files "Memuat berkas"
+ :login "Masuk"
+ :logout "Keluar"
+ :logout-user "Keluar ({1})"
+ :download "Unduh"
+ :language "Bahasa"
+ :remove-background "Hapus latar belakang"
+ :remove-heading "Hapus judul"
+ :heading "Judul {1}"
+ :auto-heading "Judul otomatis"
+ :open-a-directory "Buka direktori lokal"
+ :toggle-theme "Alihkan tema"
+
+ :help/shortcut-page-title "Pintasan Papan Ketik"
+
+ :plugin/installed "Terpasang"
+ :plugin/installed-plugin "Plugin Terpasang: {1}"
+ :plugin/not-installed "Belum Terpasang"
+ :plugin/installing "Sedang Menginstal"
+ :plugin/install "Instal"
+ :plugin/reload "Muat Ulang"
+ :plugin/update "Perbarui"
+ :plugin/update-plugin "Perbarui Plugin: {1} - {2}"
+ :plugin/check-update "Periksa Pembaruan"
+ :plugin/check-all-updates "Periksa Semua Pembaruan"
+ :plugin/found-updates "Pembaruan Baru"
+ :plugin/found-n-updates "Ditemukan {1} Pembaruan"
+ :plugin/update-all-selected "Perbarui Semua yang Dipilih"
+ :plugin/all-updated "Semua Telah Diperbarui!"
+ :plugin/updates-downloading "Mengunduh Pembaruan"
+ :plugin/refresh-lists "Segarkan Daftar"
+ :plugin/enabled "Aktif"
+ :plugin/disabled "Nonaktif"
+ :plugin/update-available "Pembaruan Tersedia"
+ :plugin/updating "Sedang Memperbarui"
+ :plugin/uninstall "Uninstal"
+ :plugin/marketplace "Pasar"
+ :plugin/downloads "Unduhan"
+ :plugin/stars "Bintang"
+ :plugin/title "Judul ({1})"
+ :plugin/all "Semua"
+ :plugin/unpacked "Diekstrak"
+ :plugin/delete-alert "Apakah Anda yakin ingin menginstal plugin ini [{1}]?"
+ :plugin/open-settings "Buka pengaturan"
+ :plugin/open-package "Buka paket"
+ :plugin/load-unpacked "Muatkan plugin yang diekstrak"
+ :plugin/restart "Restart Aplikasi"
+ :plugin/unpacked-tips "Pilih direktori plugin"
+ :plugin/contribute "✨ Tulis dan kirimkan plugin baru"
+ :plugin/up-to-date "Ini terbaru {1}"
+ :plugin/custom-js-alert "Ditemukan berkas custom.js, izinkan eksekusi? (Jika Anda tidak memahami kontennya, disarankan untuk tidak mengizinkan eksekusi, yang memiliki risiko keamanan tertentu.)"
+ :plugin/security-warning "Plugin dapat mengakses grafik dan berkas lokal Anda, mengeluarkan permintaan jaringan.
+       Mereka juga dapat menyebabkan kerusakan atau kehilangan data. Kami sedang mengerjakan aturan akses yang tepat untuk grafik Anda.
+       Sementara itu, pastikan Anda memiliki cadangan rutin dari grafik Anda dan hanya menginstal plugin ketika Anda dapat membaca dan
+       mengerti kode sumbernya."
+ :plugin/search-plugin "Cari plugin"
+ :plugin/open-preferences "Buka Preferensi"
+ :plugin/open-logseq-dir "Buka"
+ :plugin/remote-error "Kesalahan jarak jauh: "
+ :plugin/checking-for-updates "Memeriksa pembaruan plugin ..."
+ :plugin/list-of-updates "Pembaruan Plugin: "
+ :plugin/auto-check-for-updates "Periksa otomatis pembaruan"
+ :plugin.install-from-file/menu-title "Instal dari plugins.edn"
+ :plugin.install-from-file/title "Instal plugin dari plugins.edn"
+ :plugin.install-from-file/notice "Plugin berikut akan menggantikan plugin Anda:"
+ :plugin.install-from-file/success "Semua plugin terinstal!"
+ :pdf/copy-ref "Salin ref"
+ :pdf/copy-text "Salin teks"
+ :pdf/linked-ref "Referensi terkait"
+ :pdf/toggle-dashed "Gaya putus-putus untuk sorotan area"
+ :pdf/hl-block-colored "Label berwarna untuk blok sorotan"
+ :pdf/doc-metadata "Metadata Dokumen"
+ 
+ :updater/new-version-install "Versi baru telah diunduh."
+ :updater/quit-and-install "Mulai ulang untuk menginstal"
+ 
+ :paginates/pages "Total {1} halaman"
+ :paginates/prev "Sebelumnya"
+ :paginates/next "Berikutnya"
+ 
+ :tips/all-done "Semua Selesai!"
+ 
+ :command-palette/prompt "Ketik perintah"
+ :select/default-prompt "Pilih salah satu"
+ :select/default-select-multiple "Pilih satu atau beberapa"
+ :select.graph/prompt "Pilih grafik"
+ :select.graph/empty-placeholder-description "Tidak ada grafik yang cocok. Apakah Anda ingin menambahkan yang lain?"
+ :select.graph/add-graph "Ya, tambahkan grafik lain"
+ 
+ :file-sync/other-user-graph "Grafik lokal saat ini terikat ke grafik jarak jauh pengguna lain. Jadi tidak dapat memulai sinkronisasi."
+ :file-sync/graph-deleted "Grafik jarak jauh saat ini telah dihapus"
+ :file-sync/rsapi-cannot-upload-err "Tidak dapat memulai sinkronisasi, harap periksa apakah waktu lokal sudah benar."
+ :file-sync/connectivity-testing-failed "Pengujian koneksi jaringan gagal. Harap periksa pengaturan jaringan Anda. URL pengujian: "
+ 
+ :notification/clear-all "Hapus semua"
+ 
+ :shortcut.category/basics "Dasar"
+ :shortcut.category/formatting "Pemformatan"
+ :shortcut.category/navigating "Navigasi"
+ :shortcut.category/block-editing "Penyuntingan Blok Umum"
+ :shortcut.category/block-command-editing "Penyuntingan Perintah Blok"
+ :shortcut.category/block-selection "Pemilihan Blok (tekan Esc untuk keluar dari pemilihan)"
+ :shortcut.category/toggle "Beralih"
+ :shortcut.category/others "Lainnya"
+ :shortcut.category/plugins "Plugin"
+ :shortcut.category/whiteboard "Papan Tulis"
+ 
+ :keymap/all "Semua"
+ :keymap/disabled "Nonaktifkan"
+ :keymap/unset "Tidak diatur"
+ :keymap/custom "Kustom"
+ :keymap/search "Cari"
+ :keymap/total "Total pintasan"
+ :keymap/keystroke-filter "Filter Pekerjaan Tombol"
+ :keymap/keystroke-record-desc "Tekan urutan tombol apa saja untuk memfilter pintasan"
+ :keymap/keystroke-record-setup-label "Tekan urutan tombol apa saja untuk mengatur pintasan"
+ :keymap/restore-to-default "Kembalikan ke default sistem"
+ :keymap/customize-for-label "Sesuaikan pintasan"
+ :keymap/conflicts-for-label "Konflik Keymap untuk"
+
+ :window/minimize "Minimalkan"
+ :window/maximize "Maksimalkan"
+ :window/restore "Pulihkan"
+ :window/close "Tutup"
+ :window/exit-fullscreen "Keluar dari layar penuh"
+ 
+ :header/toggle-left-sidebar "Alihkan bilah sisi kiri"
+ :header/search "Cari"
+ :header/more "Lainnya"
+ :header/go-back "Kembali"
+ :header/go-forward "Maju"
+
+ :command.auto-complete/complete "Pelengkapan otomatis: Pilih item yang dipilih"
+ :command.auto-complete/next "Pelengkapan otomatis: Pilih item berikutnya"
+ :command.auto-complete/open-link "Pelengkapan otomatis: Buka item yang dipilih di browser"
+ :command.auto-complete/prev "Pelengkapan otomatis: Pilih item sebelumnya"
+ :command.auto-complete/shift-complete "APelengkapan otomatis: Buka item yang dipilih di sidebar"
+ :command.cards/forgotten "Kartu: terlupakan"
+ :command.cards/next-card "Kartu: kartu berikutnya"
+ :command.cards/recall "Kartu: ingatlah sejenak"
+ :command.cards/remembered "Kartu: diingatkan"
+ :command.cards/toggle-answers "Kartu: tampilkan / sembunyikan jawaban/clozes"
+ :command.command/run "Jalankan perintah git"
+ :command.command/toggle-favorite "Tambahkan/hapus dari favorit"
+ :command.command-palette/toggle "Aktifkan/matikan palet perintah"
+ :command.date-picker/complete "Date picker: Pilih hari yang dipilih"
+ :command.date-picker/next-day "Date picker: Pilih hari berikutnya"
+ :command.date-picker/next-week "Date picker: Pilih minggu berikutnya"
+ :command.date-picker/prev-day "Date picker: Pilih hari sebelumnya"
+ :command.date-picker/prev-week "Date picker: Pilih minggu sebelumnya"
+ :command.dev/show-block-ast "(Dev) Tampilkan AST blok"
+ :command.dev/show-block-data "(Dev) Tampilkan data blok"
+ :command.dev/show-page-ast "(Dev) Tampilkan AST halaman"
+ :command.dev/show-page-data "(Dev) Tampilkan data halaman"
+ :command.editor/backspace "Backspace / Hapus ke belakang"
+ :command.editor/backward-kill-word "Hapus kata ke belakang"
+ :command.editor/backward-word "Geser kursor ke belakang satu kata"
+ :command.editor/beginning-of-block "Geser kursor ke awal blok"
+ :command.editor/bold "Tebal"
+ :command.editor/clear-block "Hapus seluruh isi blok"
+ :command.editor/collapse-block-children "Kecilkan"
+ :command.editor/copy "Salin (menyalin seleksi atau referensi blok)"
+ :command.editor/copy-current-file "Salin berkas saat ini"
+ :command.editor/copy-embed "Salin referensi blok yang mengarah ke blok saat ini"
+ :command.editor/copy-page-url "Salin URL halaman"
+ :command.editor/copy-text "Salin seleksi sebagai teks"
+ :command.editor/cut "Potong"
+ :command.editor/cycle-todo "Putar status TODO item saat ini"
+ :command.editor/delete "Hapus / Hapus ke depan"
+ :command.editor/delete-selection "Hapus blok yang dipilih"
+ :command.editor/down "Geser kursor ke bawah / Pilih ke bawah"
+ :command.editor/end-of-block "Geser kursor ke akhir blok"
+ :command.editor/escape-editing "Keluar dari mode penyuntingan"
+ :command.editor/expand-block-children "Perluas"
+ :command.editor/follow-link "Ikuti tautan di bawah kursor"
+ :command.editor/forward-kill-word "Hapus kata ke depan"
+ :command.editor/forward-word "Geser kursor maju satu kata"
+ :command.editor/highlight "Sorot"
+ :command.editor/indent "Sisipkan blok"
+ :command.editor/insert-link "Tautan HTML"
+ :command.editor/insert-youtube-timestamp "Sisipkan penanda waktu YouTube"
+ :command.editor/italics "Miring"
+ :command.editor/kill-line-after "Hapus baris setelah posisi kursor"
+ :command.editor/kill-line-before "Hapus baris sebelum posisi kursor"
+ :command.editor/left "Geser kursor ke kiri / Buka blok yang dipilih di awal"
+ :command.editor/move-block-down "Geser blok ke bawah"
+ :command.editor/move-block-up "Geser blok ke atas"
+ :command.editor/new-block "Buat blok baru"
+ :command.editor/new-line "Baris baru dalam blok saat ini"
+ :command.editor/new-whiteboard "Papan tulis baru"
+ :command.editor/open-edit "Sunting blok yang dipilih"
+ :command.editor/open-file-in-default-app "Buka berkas dalam aplikasi default"
+ :command.editor/open-file-in-directory "Buka berkas di direktori induk"
+ :command.editor/open-link-in-sidebar "Buka tautan di sidebar"
+ :command.editor/outdent "Kurangi sisipan blok"
+ :command.editor/paste-text-in-one-block-at-point "Tempelkan teks ke dalam satu blok pada posisi kursor"
+ :command.editor/redo "Lakukan lagi"
+ :command.editor/replace-block-reference-at-point "Ganti referensi blok dengan isinya pada posisi kursor"
+ :command.editor/right "Geser kursor ke kanan / Buka blok yang dipilih di akhir"
+ :command.editor/select-all-blocks "Pilih semua blok"
+ :command.editor/select-block-down "Pilih blok di bawah"
+ :command.editor/select-block-up "Pilih blok di atas"
+ :command.editor/select-down "Pilih konten di bawah"
+ :command.editor/select-parent "Pilih blok induk"
+ :command.editor/select-up "Pilih konten di atas"
+ :command.editor/strike-through "Coret"
+ :command.editor/toggle-number-list "Hidupkan/matikan daftar angka"
+ :command.editor/toggle-open-blocks "Hidupkan/matikan blok terbuka (perluas atau perkecil semua blok)"
+ :command.editor/toggle-undo-redo-mode "Hidupkan/matikan mode undo redo (global atau hanya halaman)"
+ :command.editor/undo "Batal"
+ :command.editor/up "Geser kursor ke atas / Pilih ke atas"
+ :command.editor/zoom-in "Perbesar blok yang sedang disunting / Maju sebaliknya"
+ :command.editor/zoom-out "Perkecil blok yang sedang disunting / Mundur sebaliknya"
+ :command.git/commit "Buat commit git dengan pesan"
+ :command.go/all-graphs "Buka semua grafik"
+ :command.go/all-pages "Buka semua halaman"
+ :command.go/backward "Mundur"
+ :command.go/electron-find-in-page "Cari teks di halaman"
+ :command.go/electron-jump-to-the-next "Lompat ke pencocokan berikutnya dengan pencarian di bilah Temukan Anda"
+ :command.go/electron-jump-to-the-previous "Lompat ke pencocokan sebelumnya dengan pencarian di bilah Temukan Anda"
+ :command.go/flashcards "Alihkan kartu flash"
+ :command.go/forward "Maju"
+ :command.go/graph-view "Buka tampilan grafik"
+ :command.go/home "Buka beranda"
+ :command.go/journals "Buka jurnal"
+ :command.go/keyboard-shortcuts "Buka pintasan keyboard"
+ :command.go/next-journal "Buka jurnal berikutnya"
+ :command.go/prev-journal "Buka jurnal sebelumnya"
+ :command.go/search "Cari halaman dan blok"
+ :command.go/search-in-page "Cari blok di halaman saat ini"
+ :command.go/tomorrow "Buka ke esok hari"
+ :command.go/whiteboards "Buka papan tulis"
+ :command.graph/add "Tambahkan grafik"
+ :command.graph/export-as-html "Ekspor halaman grafik sebagai HTML"
+ :command.graph/open "Pilih grafik untuk dibuka"
+ :command.graph/re-index "Reindeks grafik saat ini"
+ :command.graph/remove "Hapus grafik"
+ :command.graph/save "Simpan grafik saat ini ke disk"
+ :command.misc/copy "Salin"
+ :command.pdf/close "Pdf: Tutup dokumen pdf saat ini"
+ :command.pdf/find "Pdf: Cari teks dokumen pdf saat ini"
+ :command.pdf/next-page "Pdf: Halaman berikutnya dokumen pdf saat ini"
+ :command.pdf/previous-page "Pdf: Halaman sebelumnya dokumen pdf saat ini"
+ :command.search/re-index "Bangun ulang indeks pencarian"
+ :command.sidebar/clear "Hapus semua di bilah sisi kanan"
+ :command.sidebar/close-top "Tutup item teratas di bilah sisi kanan"
+ :command.sidebar/open-today-page "Buka halaman hari ini di bilah sisi kanan"
+ :command.ui/clear-all-notifications "Hapus semua pemberitahuan"
+ :command.ui/goto-plugins "Buka dasbor plugin"
+ :command.ui/install-plugins-from-file "Pasang plugin dari plugins.edn"
+ :command.ui/select-theme-color "Pilih warna tema yang tersedia"
+ :command.ui/toggle-brackets "Alihkan tampilan tanda kurung"
+ :command.ui/toggle-cards "Alihkan kartu"
+ :command.ui/toggle-contents "Alihkan Konten di bilah samping"
+ :command.ui/toggle-document-mode "Alihkan mode dokumen"
+ :command.ui/toggle-help "Alihkan bantuan"
+ :command.ui/toggle-left-sidebar "Alihkan bilah sisi kiri"
+ :command.ui/toggle-right-sidebar "Alihkan bilah sisi kanan"
+ :command.ui/toggle-settings "Alihkan pengaturan"
+ :command.ui/toggle-theme "Alihkan antara tema gelap/cerah"
+ :command.ui/toggle-wide-mode "Alihkan mode lebar"
+ :command.whiteboard/bring-forward "Pindahkan ke depan"
+ :command.whiteboard/bring-to-front "Pindahkan ke depan sekali"
+ :command.whiteboard/connector "Alat konektor"
+ :command.whiteboard/ellipse "Alat elips"
+ :command.whiteboard/eraser "Alat penghapus"
+ :command.whiteboard/group "Pilihan grup"
+ :command.whiteboard/highlighter "Alat penggaris"
+ :command.whiteboard/lock "Kunci pilihan"
+ :command.whiteboard/pan "Alat geser"
+ :command.whiteboard/pencil "Alat pensil"
+ :command.whiteboard/portal "Alat portal"
+ :command.whiteboard/rectangle "Alat persegi panjang"
+ :command.whiteboard/reset-zoom "Atur ulang zoom"
+ :command.whiteboard/select "Alat pilihan"
+ :command.whiteboard/send-backward "Pindahkan ke belakang"
+ :command.whiteboard/send-to-back "Pindahkan ke belakang sekali"
+ :command.whiteboard/text "Alat teks"
+ :command.whiteboard/toggle-grid "Alihkan tampilan grid kanvas"
+ :command.whiteboard/ungroup "Pilihan ungroup"
+ :command.whiteboard/unlock "Buka kunci pilihan"
+ :command.whiteboard/zoom-in "Perbesar"
+ :command.whiteboard/zoom-out "Perkecil"
+ :command.whiteboard/zoom-to-fit "Zoom ke gambar"
+ :command.whiteboard/zoom-to-selection "Zoom ke seleksi"
+}

+ 0 - 2
src/resources/dicts/it.edn

@@ -102,8 +102,6 @@
  :settings-page/enable-tooltip "Suggerimenti"
  :settings-page/enable-tooltip "Suggerimenti"
  :settings-page/enable-journals "Diario"
  :settings-page/enable-journals "Diario"
  :settings-page/enable-all-pages-public "Tutte le pagine pubbliche durante la pubblicazione"
  :settings-page/enable-all-pages-public "Tutte le pagine pubbliche durante la pubblicazione"
- :settings-page/customize-shortcuts "Scorciatoie da tastiera"
- :settings-page/shortcut-settings "Personalizza scorciatoie"
  :settings-page/home-default-page "Imposta la home page predefinita"
  :settings-page/home-default-page "Imposta la home page predefinita"
  :settings-page/enable-block-time "Indicatori temporali sui blocchi"
  :settings-page/enable-block-time "Indicatori temporali sui blocchi"
  :settings-page/clear-cache "Pulisci cache"
  :settings-page/clear-cache "Pulisci cache"

+ 0 - 2
src/resources/dicts/ja.edn

@@ -308,8 +308,6 @@
  :settings-page/enable-tooltip "ツールチップ"
  :settings-page/enable-tooltip "ツールチップ"
  :settings-page/enable-journals "日誌"
  :settings-page/enable-journals "日誌"
  :settings-page/enable-all-pages-public "パブリッシュ時には全てのページを公開する"
  :settings-page/enable-all-pages-public "パブリッシュ時には全てのページを公開する"
- :settings-page/customize-shortcuts "キーボードショートカット"
- :settings-page/shortcut-settings "ショートカットをカスタマイズ"
  :settings-page/home-default-page "デフォルトのホームページを設定"
  :settings-page/home-default-page "デフォルトのホームページを設定"
  :settings-page/enable-block-time "ブロックタイムスタンプ"
  :settings-page/enable-block-time "ブロックタイムスタンプ"
  :settings-page/clear-cache "キャッシュをクリア"
  :settings-page/clear-cache "キャッシュをクリア"

+ 0 - 2
src/resources/dicts/ko.edn

@@ -104,8 +104,6 @@
  :settings-page/enable-tooltip "툴팁 활성화"
  :settings-page/enable-tooltip "툴팁 활성화"
  :settings-page/enable-journals "일지 활성화"
  :settings-page/enable-journals "일지 활성화"
  :settings-page/enable-all-pages-public "출판할 때 모든 페이지 공개로 설정"
  :settings-page/enable-all-pages-public "출판할 때 모든 페이지 공개로 설정"
- :settings-page/customize-shortcuts "키보드 단축키"
- :settings-page/shortcut-settings "단축키 설정"
  :settings-page/home-default-page "기본 홈 페이지 설정"
  :settings-page/home-default-page "기본 홈 페이지 설정"
  :settings-page/enable-block-time "블록 타임스탬프"
  :settings-page/enable-block-time "블록 타임스탬프"
  :settings-page/clear-cache "캐시 지우기"
  :settings-page/clear-cache "캐시 지우기"

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.