Browse Source

Merge branch 'master' into feat/cmdk

Ben Yorke 2 years ago
parent
commit
69e02edaeb
100 changed files with 1858 additions and 885 deletions
  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:
   CLOJURE_VERSION: '1.10.1.763'
   NODE_VERSION: '18'
-  JAVA_VERSION: '11'
+  JAVA_VERSION: '17'
 
 jobs:
   build-apk:

+ 3 - 2
android/app/build.gradle

@@ -1,13 +1,14 @@
 apply plugin: 'com.android.application'
 
 android {
+    namespace "com.logseq.app"
     compileSdkVersion rootProject.ext.compileSdkVersion
     defaultConfig {
         applicationId "com.logseq.app"
         minSdkVersion rootProject.ext.minSdkVersion
         targetSdkVersion rootProject.ext.targetSdkVersion
-        versionCode 68
-        versionName "0.9.15"
+        versionCode 72
+        versionName "0.9.19"
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
         aaptOptions {
              // 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 {
   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-status-bar')
     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(':capacitor-voice-recorder')
     implementation project(':send-intent')

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

@@ -1,7 +1,5 @@
 <?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
         android:allowBackup="true"
         android:icon="@mipmap/ic_launcher"
@@ -37,7 +35,7 @@
                 <action android:name="android.intent.action.VIEW" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="logseq" android:host="auth-callback" />
+                <data android:scheme="logseq" />
             </intent-filter>
 
         </activity>
@@ -56,7 +54,11 @@
     <!-- Permissions -->
 
     <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.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.RECORD_AUDIO" />
 </manifest>

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

@@ -40,8 +40,8 @@
 		"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",

+ 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");
         } else {
             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);
             startActivityForResult(call, intent, 20);
         }

+ 2 - 2
android/build.gradle

@@ -8,8 +8,8 @@ buildscript {
         mavenCentral()
     }
     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
         // 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'
 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'
 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
 # https://developer.android.com/topic/libraries/support-library/androidx-rn
 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
 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
 zipStorePath=wrapper/dists

+ 10 - 10
android/variables.gradle

@@ -1,16 +1,16 @@
 ext {
     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'
-    androidxCoreVersion = '1.8.0'
-    androidxFragmentVersion = '1.4.1'
+    androidxCoreVersion = '1.10.0'
+    androidxFragmentVersion = '1.5.6'
     junitVersion = '4.13.2'
-    androidxJunitVersion = '1.1.3'
-    androidxEspressoCoreVersion = '3.4.0'
+    androidxJunitVersion = '1.1.5'
+    androidxEspressoCoreVersion = '3.5.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,
   webDir: 'public',
   loggingBehavior: 'debug',
+  server: {
+    // https://capacitorjs.com/docs/updating/5-0#update-androidscheme
+    androidScheme: 'http',
+  },
   plugins: {
     SplashScreen: {
       launchShowDuration: 500,

+ 1 - 1
deps.edn

@@ -4,7 +4,7 @@
   rum/rum                               {:mvn/version "0.12.9"}
   datascript/datascript                 {:mvn/version "1.3.8"}
   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"}
   medley/medley                         {:mvn/version "1.4.0"}
   metosin/reitit-frontend               {:mvn/version "0.3.10"}

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

@@ -294,7 +294,7 @@
 ;; compat
 (defn basename
   [path]
-  (let [path (string/replace path #"/$" "")]
+  (let [path (string/replace path #"/+$" "")]
     (filename path)))
 
 (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
 ## 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.
-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
 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

+ 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
 	- [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
@@ -6,7 +6,7 @@
 		- Level **A** is the minimum level.
 		- Level **AA** includes all Level A and AA 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.
 - ## 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.
@@ -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).
 	- 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
-	- 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
 		- Keyboard-only navigation
 		- 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
 
 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
 
@@ -83,7 +83,7 @@ Almost all translations are small. The only exceptions to this are the keys `:tu
 ### 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 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.
 ## Fix Mistakes
 
@@ -105,4 +105,4 @@ To add a new language:
 * 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.
 * 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)
     const accessibilityScanResults = await new AxeBuilder({ page })
         .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
+        .disableRules(['meta-viewport'])
         .setLegacyMode()
         .analyze()
 

+ 13 - 0
externs.js

@@ -8,6 +8,8 @@ fs.unlink = function() {};
 fs.readdir = function() {};
 fs.rmdir = function() {};
 fs.rimraf = function() {};
+fs.lstat = function () {};
+
 var dummy = {};
 dummy.populateStat = function() {};
 dummy.populateHash = function() {};
@@ -141,6 +143,17 @@ dummy.ELEMENT = function() {};
 dummy.TEXT = 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 {{
  *     recursive: (undefined | boolean),

+ 1 - 1
ios/.gitignore

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

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

@@ -519,7 +519,7 @@
 				INFOPLIST_FILE = App/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
 				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\"";
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq;
 				PRODUCT_NAME = "$(TARGET_NAME)";
@@ -546,7 +546,7 @@
 				INFOPLIST_FILE = App/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
 				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_NAME = "$(TARGET_NAME)";
 				SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
@@ -571,7 +571,7 @@
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
 				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_FAST_MATH = YES;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq.ShareViewController;
@@ -598,7 +598,7 @@
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
 				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;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq.ShareViewController;
 				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 'CapacitorStatusBar', :path => '../../node_modules/@capacitor/status-bar'
   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 'CapacitorVoiceRecorder', :path => '../../node_modules/capacitor-voice-recorder'
   pod 'SendIntent', :path => '../../node_modules/send-intent'

+ 2 - 1
libs/src/LSPlugin.ts

@@ -192,6 +192,7 @@ export interface BlockEntity {
   level?: number
   meta?: { timestamps: any; properties: any; startPos: number; endPos: number }
   title?: Array<any>
+	marker?: string
 }
 
 /**
@@ -811,7 +812,7 @@ export interface IEditorProxy extends Record<string, any> {
     opts?: { replaceState: boolean }
   ) => void
 
-  openInRightSidebar: (uuid: BlockUUID) => void
+  openInRightSidebar: (id: BlockUUID | EntityID) => void
 
   /**
    * @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",
     "devDependencies": {
         "@axe-core/playwright": "=4.4.4",
-        "@capacitor/cli": "^4.0.0",
+        "@capacitor/cli": "^5.0.0",
         "@playwright/test": "=1.31.0",
         "@tailwindcss/aspect-ratio": "0.4.2",
         "@tailwindcss/forms": "0.5.3",
@@ -77,24 +77,24 @@
         "postinstall": "yarn tldraw:build && yarn amplify:build "
     },
     "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",
         "@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",
-        "@logseq/capacitor-file-sync": "0.0.35",
+        "@logseq/capacitor-file-sync": "5.0.0",
         "@logseq/diff-merge": "0.2.2",
         "@logseq/react-tweet-embed": "1.3.1-1",
         "@radix-ui/colors": "^0.1.8",
@@ -103,11 +103,11 @@
         "@tabler/icons": "^1.96.0",
         "@tippyjs/react": "4.2.5",
         "bignumber.js": "^9.0.2",
-        "capacitor-voice-recorder": "4.0.0",
+        "capacitor-voice-recorder": "^5.0.0",
         "check-password-strength": "2.0.7",
         "chokidar": "3.5.1",
         "chrono-node": "2.2.4",
-        "codemirror": "5.58.1",
+        "codemirror": "5.65.13",
         "d3-force": "3.0.0",
         "diff": "5.0.0",
         "dompurify": "2.4.0",
@@ -144,7 +144,7 @@
         "remove-accents": "0.4.2",
         "reveal.js": "^4.5.0",
         "sanitize-filename": "1.6.3",
-        "send-intent": "3.0.11",
+        "send-intent": "^5.0.0",
         "shepherd.js": "^9.1.0",
         "tailwind-capitalize-first-letter": "^1.0.4",
         "threads": "1.6.5",
@@ -152,6 +152,14 @@
         "yargs-parser": "20.2.4"
     },
     "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/constants": "6.2.0",
         "pixi-graph-fork/@pixi/core": "6.2.0",

+ 1 - 1
resources/forge.config.js

@@ -4,7 +4,7 @@ module.exports = {
   packagerConfig: {
     name: 'Logseq',
     icon: './icons/logseq_big_sur.icns',
-    buildVersion: 68,
+    buildVersion: 72,
     protocols: [
       {
         "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', {
   doAction: async (arg) => {
     return await ipcRenderer.invoke('main', arg)
@@ -116,15 +130,21 @@ contextBridge.exposeInMainWorld('apis', {
 
     const dest = path.join(repoPathRoot, to)
     const assetsRoot = path.dirname(dest)
-    
+
     await fs.promises.mkdir(assetsRoot, { recursive: true })
 
-    from = from && decodeURIComponent(from || getFilePathFromClipboard())
+    from = from || getFilePathFromClipboard()
 
     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
@@ -166,6 +186,8 @@ contextBridge.exposeInMainWorld('apis', {
 
   getFilePathFromClipboard,
 
+  getClipboardData,
+
   setZoomFactor (factor) {
     webFrame.setZoomFactor(factor)
   },

+ 3 - 2
resources/package.json

@@ -1,7 +1,7 @@
 {
   "name": "Logseq",
   "productName": "Logseq",
-  "version": "0.9.15",
+  "version": "0.9.19",
   "main": "electron.js",
   "author": "Logseq",
   "license": "AGPL-3.0",
@@ -34,7 +34,8 @@
     "update-electron-app": "2.0.1",
     "extract-zip": "2.0.1",
     "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",
     "posthog-js": "1.10.2",
     "@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] === '') {
   const today = new Date()
   console.log(
-    ver + '-nightly.' + today.toISOString().split('T')[0].replaceAll('-', '')
+    ver + '-alpha+nightly.' + today.toISOString().split('T')[0].replaceAll('-', '')
   )
 } else {
   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
             :settings-page/tab-editor :shortcut.category/whiteboard :whiteboard/medium
             :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
   "Looks up duplicates for all languages"

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

@@ -1,9 +1,9 @@
 (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
 (defonce dot-root (.join node-path (.getPath app "home") ".logseq"))
@@ -17,14 +17,14 @@
     (let [body (.toString (.readFileSync fs cfg-path))]
       (if (seq body) (reader/read-string body) {}))
     (catch :default e
-      (js/console.error :cfg-error e))))
+      (logger/error :cfg-error e))))
 
 (defn- write-cfg!
   [cfg]
   (try
     (.writeFileSync fs cfg-path (pr-str cfg)) cfg
     (catch :default e
-      (js/console.error :cfg-error e))))
+      (logger/error :cfg-error e))))
 
 (defn set-item!
   [k v]

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

@@ -16,7 +16,6 @@
             ["os" :as os]
             ["electron" :refer [BrowserWindow Menu app protocol ipcMain dialog shell] :as electron]
             ["electron-deeplink" :refer [Deeplink]]
-            [clojure.core.async :as async]
             [electron.state :as state]
             [electron.git :as git]
             [electron.window :as win]
@@ -207,6 +206,10 @@
                                     {:role "about"
                                      :label "About Logseq"
                                      :click about-fn}]}))
+        ;; Enable Cmd/Ctrl+= Zoom In
+        template (conj template
+                       {:role "zoomin"
+                        :accelerator "CommandOrControl+="})
         menu (.buildFromTemplate Menu (clj->js template))]
     (.setApplicationMenu Menu menu)))
 
@@ -235,15 +238,15 @@
                       :bypassCSP       true
                       :supportFetchAPI true}]
       (.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!)
       (setup-deeplink!)
@@ -294,33 +297,28 @@
                (@*setup-fn)
 
                ;; 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]
                                   (when @*quit-dirty? ;; when not updating
                                     (.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]
                                         (reset! win/*quitting? true)))
 

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

@@ -6,6 +6,7 @@
             ["buffer" :as buffer]
             ["diff-match-patch" :as google-diff]
             ["electron" :refer [app autoUpdater dialog ipcMain shell]]
+            ["electron-window-state" :as windowStateKeeper]
             ["fs" :as fs]
             ["fs-extra" :as fs-extra]
             ["os" :as os]
@@ -96,6 +97,12 @@
     (catch :default _e
       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]]
   (logger/info ::copy-file from-path to-path)
   (fs-extra/copy from-path to-path))
@@ -106,7 +113,7 @@
                       (.from Buf content)
                       content)]
     (try
-      (when (and (fs/existsSync path) (not (writable? path)))
+      (when (and (chmod-enabled?) (fs/existsSync path) (not (writable? path)))
         (fs/chmodSync path "644"))
       (fs/writeFileSync path content)
       (fs/statSync path)
@@ -243,7 +250,7 @@
                                   (.toString (.readFileSync fs txid-path)))]
           (reader/read-string sync-meta))))
     (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]]
   (if (seq graphs)
@@ -618,6 +625,10 @@
 (defmethod handle :window-close [^js win]
   (.close win))
 
+(defmethod handle :theme-loaded [^js win]
+  (.manage (windowStateKeeper) win)
+  (.show win))
+
 ;;;;;;;;;;;;;;;;;;;;;;;
 ;; file-sync-rs-apis ;;
 ;;;;;;;;;;;;;;;;;;;;;;;

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

@@ -145,6 +145,7 @@
     [db-name (node-path/join search-dir db-name)]))
 
 (defn open-db!
+  "Open a SQLite db for search index"
   [db-name]
   (let [[db-sanitized-name db-full-path] (get-db-full-path db-name)]
     (try (let [db (sqlite3 db-full-path nil)]
@@ -157,7 +158,13 @@
            (swap! databases assoc db-sanitized-name db))
          (catch :default e
            (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!
   []
@@ -177,9 +184,15 @@
   (str "(" (->> (map (fn [id] (str "'" id "'")) ids)
                 (string/join ", ")) ")"))
 
+(defn- get-or-open-db [repo]
+  (or (get-db repo)
+      (do
+        (open-db! repo)
+        (get-db repo))))
+
 (defn upsert-pages!
   [repo pages]
-  (if-let [db (get-db repo)]
+  (when-let [db (get-or-open-db repo)]
     ;; TODO: what if a CONFLICT on uuid
     ;; 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)
@@ -187,10 +200,7 @@
                                     (fn [pages]
                                       (doseq [page pages]
                                         (.run ^object insert page))))]
-      (insert-many pages))
-    (do
-      (open-db! repo)
-      (upsert-pages! repo pages))))
+      (insert-many pages))))
 
 (defn delete-pages!
   [repo ids]
@@ -201,7 +211,7 @@
 
 (defn upsert-blocks!
   [repo blocks]
-  (if-let [db (get-db repo)]
+  (when-let [db (get-or-open-db repo)]
     ;; TODO: what if a CONFLICT on uuid
     ;; 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)
@@ -209,10 +219,7 @@
                                     (fn [blocks]
                                       (doseq [block blocks]
                                         (.run ^object insert block))))]
-      (insert-many blocks))
-    (do
-      (open-db! repo)
-      (upsert-blocks! repo blocks))))
+      (insert-many blocks))))
 
 (defn delete-blocks!
   [repo ids]

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

@@ -26,7 +26,8 @@
 (defonce *fetchAgent (atom nil))
 
 (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 extract-zip (js/require "extract-zip"))
 
@@ -67,7 +68,12 @@
   [{:keys [protocol host port]}]
   (if (and protocol host port (or (= protocol "http") (= protocol "socks5")))
     (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)))
 
 (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 stats = {
+      return {
+        path: filePath,
         size: fileStats.size,
         accessTime: fileStats.atimeMs,
         modifiedTime: fileStats.mtimeMs,
         changeTime: fileStats.ctimeMs,
-        birthTime: fileStats.birthtimeMs,
+        birthTime: fileStats.birthtimeMs
       }
-
-      return { path: filePath, ...stats }
     })
   )
   return files.flat().filter((it) => it != null)

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

@@ -35,6 +35,7 @@
                       :titleBarStyle        "hiddenInset"
                       :trafficLightPosition {:x 16 :y 16}
                       :autoHideMenuBar      (not mac?)
+                      :show                 false
                       :webPreferences
                       {:plugins                 true        ; pdf
                        :nodeIntegration         false
@@ -55,7 +56,6 @@
                      linux?
                      (assoc :icon (node-path/join js/__dirname "icons/logseq.png")))
          win       (BrowserWindow. (clj->js win-opts))]
-     (.manage win-state win)
      (.onBeforeSendHeaders (.. session -defaultSession -webRequest)
                            (clj->js {:urls (array "*://*.youtube.com/*")})
                            (fn [^js details callback]
@@ -76,25 +76,37 @@
      ;;(when dev? (.. win -webContents (openDevTools)))
      win)))
 
+(defn get-all-windows
+  []
+  (.getAllWindows BrowserWindow))
+
 (defn destroy-window!
   [^js 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!
   ;; TODO merge with the on close in core
   [^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!
   [^js win]
@@ -102,10 +114,6 @@
     (.restore win))
   (.focus win))
 
-(defn get-all-windows
-  []
-  (.getAllWindows BrowserWindow))
-
 (defn get-graph-all-windows
   [graph-path] ;; graph-path == dir
   (->> (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)
 
                   [_ _ _ "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)]
                          (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)))]
         [:div.flex.justify-between.items-center.mt-2
          [: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 (t :bug-report/inspector-page-tip)]
          (ui/button (t :bug-report/inspector-page-btn-back) :on-click reset-step!)]
@@ -118,4 +118,4 @@
     [:div.flex.flex-col
      [:h1.text-2xl (t :bug-report/section-issues-title)]
      [: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 {
-    @apply fixed bottom-4 right-8;
+    @apply fixed bottom-4 right-4 sm:right-8;
 
     > .inner {
       @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 {
       flex: 1;
     }
@@ -734,22 +726,13 @@ html[data-theme='dark'] {
       @apply h-full;
 
       .button {
-        @apply hidden p-0 ml-2 flex items-center;
+        @apply p-0 ml-2 flex items-center;
 
         &:focus {
           @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)
         editing-key    (first (keys (:editor/editing? @state/state)))
         *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)
                            (> (- max-height' to-max-height) Y-BOUNDARY-HEIGHT))
         to-max-height (if y-overflow-vh? max-height' to-max-height)
@@ -430,22 +420,36 @@
         style (merge
                {:top        (+ top offset-top (if (int? y-diff) y-diff 0))
                 :max-height to-max-height
-                :max-width 700
+                :max-width  700
                 ;; TODO: auto responsive fixed size
-                :width "fit-content"
+                :width      "fit-content"
                 :z-index    11}
                (when set-default-width?
                  {:width max-width})
                (if (<= vw-max-width (+ left (if set-default-width? max-width 500)))
                  {: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
-     {:ref *el
+     {:ref             *el
       :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]))
 
 (rum/defc transition-cp < rum/reactive

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

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

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

@@ -2,14 +2,14 @@
   (:require ["/frontend/utils" :as utils]
             [clojure.string :as string]
             [frontend.components.block :as component-block]
-            [frontend.components.query :as query]
             [frontend.components.content :as content]
             [frontend.components.editor :as editor]
             [frontend.components.hierarchy :as hierarchy]
             [frontend.components.plugins :as plugins]
+            [frontend.components.query :as query]
             [frontend.components.reference :as reference]
-            [frontend.components.svg :as svg]
             [frontend.components.scheduled-deadlines :as scheduled]
+            [frontend.components.svg :as svg]
             [frontend.config :as config]
             [frontend.context.i18n :refer [t]]
             [frontend.date :as date]
@@ -21,6 +21,7 @@
             [frontend.format.block :as block]
             [frontend.handler.common :as common-handler]
             [frontend.handler.config :as config-handler]
+            [frontend.handler.dnd :as dnd]
             [frontend.handler.editor :as editor-handler]
             [frontend.handler.graph :as graph-handler]
             [frontend.handler.notification :as notification]
@@ -34,12 +35,13 @@
             [frontend.util :as util]
             [frontend.util.text :as text-util]
             [goog.object :as gobj]
+            [logseq.graph-parser.mldoc :as gp-mldoc]
             [logseq.graph-parser.util :as gp-util]
+            [logseq.graph-parser.util.page-ref :as page-ref]
             [medley.core :as medley]
+            [promesa.core :as p]
             [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
   [state]
@@ -109,10 +111,26 @@
 
 (rum/defc dummy-block
   [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.items-center.mr-2.ml-1 {:style {:height 24}}
        [:span.bullet-container.cursor
@@ -120,8 +138,12 @@
       [:div.flex.flex-1 {:tabIndex 0
                          :on-key-press (fn [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
         "Click here to edit..."]]]]))
 

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

@@ -433,6 +433,8 @@
                           (assoc opts :test (util/trim-safe (util/evalue %))))
           :value       (:test opts)}]
         [: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://s3.amazonaws.com"]
          [:option "https://clients3.google.com/generate_204"]]]
@@ -445,9 +447,10 @@
                                  (-> (p/let [result (ipc/ipc :testProxyUrl val opts)]
                                        (js->clj result :keywordize-keys true))
                                      (p/then (fn [{:keys [code response-ms]}]
+                                               (notification/clear! :proxy-net-check)
                                                (notification/show! (str "Success! Status " code " in " response-ms "ms.") :success)))
                                      (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.pt-2

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

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

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

@@ -526,15 +526,6 @@
 ;;             (let [value (not enable-block-timestamps?)]
 ;;               (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 []
   [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start
    [:label.block.text-sm.font-medium.leading-5.opacity-70
@@ -664,6 +655,20 @@
    {:left-label (t :settings-page/network-proxy)
     :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 []
   (row-with-button-action
    {:left-label (t :settings-page/filename-format)
@@ -703,8 +708,7 @@
      (when (config/global-config-enabled?) (edit-global-config-edn))
      (when current-repo (edit-config-edn))
      (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
   [_state current-repo]
@@ -776,6 +780,7 @@
      (usage-diagnostics-row t instrument-disabled?)
      (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?) (auto-chmod-row t))
      (when (and (util/electron?) (not (config/demo-graph? current-repo))) (filename-format-row))
      (clear-cache-row t)
 

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

@@ -284,7 +284,8 @@
 
       [:div.shortcuts-keys-wrap
        [: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
            (-> x (string/trim) (string/lower-case) (shortcut-utils/decorate-binding))
            [:a.x {:on-click (fn [] (set-current-binding!
@@ -428,7 +429,9 @@
                           disabled? (or (false? user-binding)
                                         (false? (first binding)))
                           unset? (and (not disabled?)
-                                      (= user-binding []))]]
+                                      (or (= user-binding [])
+                                          (and (= binding [])
+                                               (nil? user-binding))))]]
 
                 (when (or (nil? (seq filters))
                           (when (contains? filters :Custom) custom?)
@@ -453,11 +456,11 @@
 
                      [:a.action-wrap
                       {:class    (util/classnames [{:disabled disabled?}])
-                       :on-click (when-not disabled?
+                       :on-click (when (and id (not disabled?))
                                    #(open-customize-shortcut-dialog! id))}
 
                       (cond
-                        (or user-binding (false? user-binding))
+                        (or unset? user-binding (false? user-binding))
                         [:code.dark:bg-green-800.bg-green-300
                          (if unset?
                            (t :keymap/unset)
@@ -473,4 +476,5 @@
                         (for [x binding]
                           [:code.tracking-wide
                            {: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
-  (:require [frontend.extensions.pdf.core :as pdf]
+  (:require [electron.ipc :as ipc]
+            [frontend.extensions.pdf.core :as pdf]
             [frontend.config :as config]
             [frontend.handler.plugin :as plugin-handler]
             [frontend.handler.plugin-config :as plugin-config-handler]
@@ -35,6 +36,10 @@
      #(let [doc js/document.documentElement]
         (.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!
      #(when (and restored-sidebar?
                  (mounted-fn))

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

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

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

@@ -1,27 +1,11 @@
 (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.handler.window :as window-handler]
             [frontend.state :as state]
             [frontend.ui :as ui]
             [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
   []
   (let [maximized?  (state/sub :electron/window-maximized?)
@@ -30,23 +14,23 @@
      (if fullscreen?
        [:button.button.icon.fullscreen-toggle
         {:title (t :window/exit-fullscreen)
-         :on-click toggle-fullscreen}
+         :on-click window-handler/toggle-fullscreen!}
         (ui/icon "arrows-minimize")]
        [:<>
         [:button.button.icon.minimize
          {:title (t :window/minimize)
-          :on-click minimize}
+          :on-click window-handler/minimize!}
          (svg/window-minimize)]
 
         [:button.button.icon.maximize-toggle
          {:title (if maximized? (t :window/restore) (t :window/maximize))
           :class (if maximized? "restore" "maximize")
-          :on-click toggle-maximized}
+          :on-click window-handler/toggle-maximized!}
          (if maximized?
            (svg/window-restore)
            (svg/window-maximize))]
 
         [:button.button.icon.close
          {:title (t :window/close)
-          :on-click close}
+          :on-click window-handler/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"
   ;; ../assets/xxx -> {assets|file}://{current-graph-root-path}/xxx
   [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
   []

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

@@ -58,7 +58,8 @@
    :ko      (edn-resource "dicts/ko.edn")
    :pl      (edn-resource "dicts/pl.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
   "List of languages presented to user"
@@ -80,7 +81,8 @@
    {:label "Türkçe" :value :tr}
    {:label "Українська" :value :uk}
    {: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)))
         "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)))
                    (let [code (nth (:rum/args state) 3)
                          editor @(:editor-atom state)]
-                     (when (not= (.getValue editor) code)
+                     (when (and editor (not= (.getValue editor) code))
                        (.setValue editor code))))
                  state)}
   [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 {}))))
 
-(defn resolve-ref-page
+(defn ensure-ref-page!
   [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!
   ([pdf hl] (ensure-ref-block! pdf hl nil))
   ([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)]
        (if-not (nil? (:block/content ref-block))
          (do

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

@@ -848,7 +848,6 @@
                        (confirm-fn password)))}
         "Submit"]]]]))
 
-
 (rum/defc ^:large-vars/data-var pdf-loader
   [{:keys [url hls-file identity filename] :as pdf-current}]
   (let [*doc-ref       (rum/use-ref nil)
@@ -861,6 +860,13 @@
         set-hls-extra! (fn [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
     (rum/use-effect!
      (fn []
@@ -886,21 +892,25 @@
      [hls-file])
 
     ;; 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
     (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]
-  (let [reg #"^(?:(\d+):)?([0-5]\d):([0-5]\d)$"
+  (let [reg #"^(?:(\d+):)?([0-5]?\d):([0-5]?\d)$"
         reg-number #"^\d+$"
         timestamp (str timestamp)
         total-seconds (some-> (re-matches reg-number timestamp)
                               util/safe-parse-int)
         [_ 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
       total-seconds
       total-seconds
@@ -133,19 +133,13 @@ Remember: You can paste a raw YouTube url as embedded video on mobile."
       (and minutes seconds)
       (+ (* 3600 hours) (* 60 minutes) seconds)
 
-      minutes
-      (+ (* 3600 hours) (* 60 minutes))
-
-      hours
-      (* 3600 hours)
-
       :else
       nil)))
 
 (comment
   ;; 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
 

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

@@ -193,6 +193,11 @@
 
 ;;; ### configs ends
 
+(defn- guard-ex
+  [x]
+  (when (instance? ExceptionInfo x) x))
+
+
 (def ws-addr config/WS-URL)
 
 ;; Warning: make sure to `persist-var/-load` graphs-txid before using it.
@@ -232,17 +237,25 @@
     (when-let [ws (:ws @*ws)]
       (.close ws))))
 
+(def *ws-connect-retries (atom 0))
+(declare <sync-stop)
 (defn- ws-listen!*
   [graph-uuid *ws remote-changes-chan]
   (reset! *ws {:ws (js/WebSocket. (util/format ws-addr graph-uuid)) :stop false})
   (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]
-                                 (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]
                                    (let [data (js->clj (js/JSON.parse (.-data e)) :keywordize-keys true)]
                                      (when (some? (:txid data))
@@ -264,7 +277,10 @@
 (defn- get-json-body [body]
   (or (and (not (string? body)) body)
       (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]
   (-> resp (:body) (get-json-body)))
@@ -841,9 +857,8 @@
   (<get-local-all-files-meta [this graph-uuid base-path]
     (go
       (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]
     (go
       (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)
     (go
       (<! (<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]
     (go
       (<! (<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]
     (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]
     (let [normalized-filepaths (mapv path-normalize filepaths)]
@@ -883,17 +899,21 @@
     (let [normalized-filepaths (mapv path-normalize filepaths)]
       (go
         (<! (<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]
     (let [normalized-filepaths (mapv path-normalize filepaths)]
       (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))))))
   (<decrypt-fnames [_ graph-uuid fnames] (go
                                            (let [r (<! (p->c (ipc/ipc "decrypt-fnames" graph-uuid fnames)))]
@@ -931,9 +951,8 @@
     (go
       (let [r (<! (p->c (.getLocalAllFilesMeta mobile-util/file-sync (clj->js {:graphUUID graph-uuid
                                                                                :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]
     (go
@@ -953,32 +972,35 @@
 
   (<update-local-files [this graph-uuid base-path filepaths]
     (go
-      (let [token (<! (<get-token this))
+      (let [token-or-exp (<! (<get-token this))
             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]
     (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
                                              (clj->js {:graphUUID graph-uuid
                                                        :basePath base-path
                                                        :filePaths filepaths
-                                                       :token token})))))]
-        (js->clj (.-value r)))))
+                                                       :token token-or-exp})))))))))))
   (<download-version-files [this graph-uuid base-path filepaths]
     (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]
     (let [normalized-filepaths (mapv path-normalize filepaths)]
@@ -992,40 +1014,39 @@
   (<update-remote-files [this graph-uuid base-path filepaths local-txid]
     (let [normalized-filepaths (mapv path-normalize filepaths)]
       (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]
     (let [normalized-filepaths (mapv path-normalize filepaths)]
       (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]
     (go
       (let [r (<! (p->c (.encryptFnames mobile-util/file-sync
                                         (clj->js {:graphUUID graph-uuid
                                                   :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]
     (go (let [r (<! (p->c (.decryptFnames mobile-util/file-sync
                                           (clj->js {:graphUUID graph-uuid
@@ -1147,19 +1168,21 @@
 
   (<request [this api-name body]
     (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]
     {:pre [(map? files)
            (number? txid)]}
@@ -1232,68 +1255,63 @@
                                       {}
                                       (remove (comp nil? second)
                                               {: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]
     {:pre [(coll? filepaths)]}
     (user/<wrap-ensure-id&access-token
      (let [encrypted-paths* (<! (<encrypt-fnames rsapi graph-uuid filepaths))
            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]
     {:pre [(or graph-name-opt graph-uuid-opt)]}
@@ -1320,23 +1338,22 @@
   (<get-deletion-logs [this graph-uuid from-txid]
     (user/<wrap-ensure-id&access-token
      (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]
     ;; 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)))]
        (loop [[files & others] partitioned-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
   (declare remoteapi)
@@ -1445,8 +1464,9 @@
       (if (< now expired-at)
         r
         (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 {}))
 (defn update-graph-encrypt-keys-cache [graph-uuid v]
@@ -1512,9 +1532,11 @@
 (defn- assert-local-txid<=remote-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
   [graph-uuid base-path relative-paths]
@@ -2034,17 +2056,17 @@
   "- persist encrypted pwd at local-storage"
   [pwd graph-uuid]
   (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!
   "restore pwd from persisted encrypted-pwd, update `pwd-map`"
@@ -2386,8 +2408,8 @@
   if local-txid != remote-txid, return {:need-sync-remote true}"))
 
 (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
   (set-local->remote-syncer! [_ s] (set! local->remote-syncer s))
   (sync-files-remote->local!
@@ -2426,34 +2448,33 @@
     (go
       (let [r
             (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
           (instance? ExceptionInfo r)       {:unknown r}
           @*stopped                         {:stop true}
@@ -2468,7 +2489,8 @@
             remote-all-files-meta-or-exp (<! remote-all-files-meta-c)]
         (if (or (storage-exceed-limit? 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
                                 :data  {:graph-uuid graph-uuid
                                         :exp        remote-all-files-meta-or-exp
@@ -2478,11 +2500,11 @@
                 local-all-files-meta    (<! local-all-files-meta-c)
                 {diff-remote-files :result elapsed-time :time}
                 (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))
                 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))
                 latest-txid             (:TXId remote-txid-or-ex)]
             (if (or (instance? ExceptionInfo remote-txid-or-ex) (nil? latest-txid))
@@ -2769,7 +2791,8 @@
         (cond
           (or (storage-exceed-limit? 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
                                 :data  {:graph-uuid graph-uuid
                                         :exp        remote-all-files-meta-or-exp
@@ -2931,7 +2954,8 @@
           remote->local
           (let [txid
                 (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)]
             (when (some? txid)
               (>! ops-chan {:remote->local txid}))
@@ -3257,8 +3281,6 @@
     (reset! current-sm-graph-uuid graph-uuid)
     (sync-manager user-uuid graph-uuid base-path repo txid *sync-state)))
 
-;; Avoid sync reentrancy
-(defonce *sync-entered? (atom false))
 
 (defn <sync-stop []
   (go
@@ -3269,8 +3291,6 @@
 
       (<! (-stop! sm))
 
-      (reset! *sync-entered? false)
-
       (println "[SyncManager]" "stopped"))
 
     (reset! current-sm-graph-uuid nil)))
@@ -3337,69 +3357,93 @@
   []
   (go
     (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)
           r2* (<! r2)
           ok? (and (= 200 (:status r1*))
                    (= 200 (:status r2*))
                    (= "OK" (:body r2*)))]
       (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?)))
 
 (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
   []
   (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,
               ;; 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!
   [is-active?]
@@ -3469,6 +3513,13 @@
              (when (nil? n)
                (<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

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

@@ -1,5 +1,5 @@
 (ns frontend.handler.dnd
-  "Provides fns for drag n drop"
+  "Provides fns for drag and drop"
   (:require [frontend.handler.editor :as editor-handler]
             [frontend.handler.editor.property :as editor-property]
             [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/uuid" (db/entity [:block/uuid (uuid 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))]
         (state/sidebar-add-block!
          (state/get-current-repo)
@@ -2564,7 +2564,7 @@
             :down util/get-next-block-non-collapsed)
         sibling-block (f (gdom/getElement (state/get-editing-block-dom-id)))
         {: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")]
         (let [value (state/get-edit-content)]
           (when (not= (clean-content! format content)
@@ -2576,7 +2576,10 @@
               block (db/pull repo '[*] [:block/uuid new-uuid])]
           (edit-block! block
                        [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
   [direction]

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

@@ -91,14 +91,19 @@
 
 (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
   []
   (when-not (state/sub [:file-sync/remote-graphs :loading])
     (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
   []

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

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

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

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

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

@@ -86,6 +86,24 @@
                        (keyword (util/get-file-ext (:file/path file)))))
           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!`
 (defn ls-dir-files-with-handler!
   "Read files from directory and setup repo (for the first time setup a repo)"
@@ -113,6 +131,7 @@
         (reset! *repo repo)
         (when-not (string/blank? root-dir)
           (p/let [files (:files result)
+                  _ (precheck-graph-dir root-dir (:files result))
                   files (-> (->db-files files nfs?)
                             ;; filter again, in case fs backend does not handle this
                             (remove-ignore-files root-dir nfs?))

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

@@ -246,8 +246,8 @@
           point (-> (.getShapeById app source-shape)
                     (.-bounds)
                     ((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)]
       (when (uuid? block-uuid) (editor-handler/set-blocks-id! [block-uuid]))
       (.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
+  "Block Action bar, activated when swipe on a block"
   (:require
    [frontend.db :as db]
    [frontend.extensions.srs :as srs]

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

@@ -82,8 +82,16 @@
                                   (state/get-left-sidebar-open?)
                                   (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 "#/")
                              (string/ends-with? href "/")
                              (not (string/includes? href "#/")))

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

@@ -35,6 +35,7 @@
   border-radius: 10px;
   background-color: var(--ls-secondary-background-color);
   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;
   z-index: 100;
 

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

@@ -16,6 +16,7 @@
             [frontend.handler.export :as export-handler]
             [frontend.handler.whiteboard :as whiteboard-handler]
             [frontend.handler.plugin-config :as plugin-config-handler]
+            [frontend.handler.window :as window-handler]
             [frontend.modules.editor.undo-redo :as undo-redo]
             [frontend.dicts :as dicts]
             [frontend.modules.shortcut.before :as m]
@@ -174,7 +175,7 @@
    :cards/recall                            {:binding "t"
                                              :fn      srs/recall}
 
-   :editor/escape-editing                   {:binding false
+   :editor/escape-editing                   {:binding []
                                              :fn      (fn [_ _]
                                                         (editor-handler/escape-editing))}
 
@@ -331,7 +332,7 @@
    :editor/zoom-out                         {:binding (if mac? "mod+," "alt+left")
                                              :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!}
 
    :editor/toggle-number-list               {:binding "t n"
@@ -400,7 +401,7 @@
 
    :graph/export-as-html                    {:fn      #(export-handler/download-repo-as-html!
                                                          (state/get-current-repo))
-                                             :binding false}
+                                             :binding []}
 
    :graph/open                              {:fn      #(do
                                                          (editor-handler/escape-editing)
@@ -410,18 +411,18 @@
    :graph/remove                            {:fn      #(do
                                                          (editor-handler/escape-editing)
                                                          (state/set-state! :ui/open-select :graph-remove))
-                                             :binding false}
+                                             :binding []}
 
    :graph/add                               {:fn      (fn [] (route-handler/redirect! {:to :repo-add}))
-                                             :binding false}
+                                             :binding []}
 
    :graph/save                              {:fn      #(state/pub-event! [:graph/save])
-                                             :binding false}
+                                             :binding []}
 
    :graph/re-index                          {:fn      (fn []
                                                         (p/let [multiple-windows? (ipc/ipc "graphHasMultipleWindows" (state/get-current-repo))]
                                                           (state/pub-event! [:graph/ask-for-re-index (atom multiple-windows?) nil])))
-                                             :binding false}
+                                             :binding []}
 
    :command/run                             {:binding  "mod+shift+1"
                                              :inactive (not (util/electron?))
@@ -498,10 +499,14 @@
                                              :inactive (not (util/electron?))
                                              :fn       page-handler/copy-current-file}
 
-   :editor/copy-page-url                    {:binding  false
+   :editor/copy-page-url                    {:binding  []
                                              :inactive (not (util/electron?))
                                              :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"
                                              :fn      ui-handler/toggle-wide-mode!}
 
@@ -516,7 +521,7 @@
                                              :inactive (not (config/plugin-config-enabled?))
                                              :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!}
 
    :editor/toggle-open-blocks               {:binding "t o"
@@ -535,19 +540,19 @@
                                              :inactive (not (util/electron?))
                                              :fn       commit/show-commit-modal!}
 
-   :dev/show-block-data                     {:binding  false
+   :dev/show-block-data                     {:binding  []
                                              :inactive (not (state/developer-mode?))
                                              :fn       :frontend.handler.common.developer/show-block-data}
 
-   :dev/show-block-ast                      {:binding  false
+   :dev/show-block-ast                      {:binding  []
                                              :inactive (not (state/developer-mode?))
                                              :fn       :frontend.handler.common.developer/show-block-ast}
 
-   :dev/show-page-data                      {:binding  false
+   :dev/show-page-data                      {:binding  []
                                              :inactive (not (state/developer-mode?))
                                              :fn       :frontend.handler.common.developer/show-page-data}
 
-   :dev/show-page-ast                       {:binding  false
+   :dev/show-page-ast                       {:binding  []
                                              :inactive (not (state/developer-mode?))
                                              :fn       :frontend.handler.common.developer/show-page-ast}})
 
@@ -693,7 +698,8 @@
            :search/re-index
            :sidebar/clear
            :sidebar/open-today-page
-           :ui/toggle-brackets])
+           :ui/toggle-brackets
+           :window/close])
         (with-meta {:before m/prevent-default-behavior}))
 
     :shortcut.handler/global-non-editing-only
@@ -833,90 +839,91 @@
      :editor/select-parent
      :editor/select-block-up
      :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
 []

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

@@ -102,7 +102,7 @@
    (when-let [handler (-> (get @*installed-handlers install-id)
                           :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))))
 
 (defn install-shortcut-handler!
@@ -146,7 +146,7 @@
 
       (.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)
 
       install-id)))
@@ -181,9 +181,9 @@
        :will-remount
        (fn [old-state new-state]
          (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*
   "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?))
               (set-state! :mobile/show-action-bar? false)))))))))
 
+(defn action-bar-open?
+  []
+  (:mobile/show-action-bar? @state))
+
 (defn remove-watch-state [key]
   (remove-watch state key))
 

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

@@ -87,20 +87,24 @@
 (rum/defc ls-textarea
   < rum/reactive
   {: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
                   (.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)}
   [{:keys [on-change] :as props}]
   (let [skip-composition? (state/sub :editor/action)
@@ -113,16 +117,16 @@
                                                 (on-change e))
                              (state/set-editor-in-composition! true))))
         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)))
 
 (rum/defc dropdown-content-wrapper
   < {:did-mount    (fn [state]
-                     (let [k    (inc (count (state/sub :modal/dropdowns)))
+                     (let [k (inc (count (state/sub :modal/dropdowns)))
                            args (:rum/args state)]
                        (state/set-state! [:modal/dropdowns k] (second args))
                        (assoc state ::k k)))

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

@@ -6,7 +6,7 @@
             ["/frontend/selection" :as selection]
             ["/frontend/utils" :as utils]
             ["@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]
             ["remove-accents" :as removeAccents]
             ["sanitize-filename" :as sanitizeFilename]
@@ -209,7 +209,7 @@
      (p/do!
       (.setStyle StatusBar (clj->js {:style (.-Light Style)}))
       (when (mobile-util/native-android?)
-        (.setColor NavigationBar (clj->js {:color "#ffffff"}))
+        (.setNavigationBarColor NavigationBar (clj->js {:color "#ffffff"}))
         (.setBackgroundColor StatusBar (clj->js {:color "#ffffff"}))))))
 
 #?(:cljs
@@ -218,7 +218,7 @@
      (p/do!
       (.setStyle StatusBar (clj->js {:style (.-Dark Style)}))
       (when (mobile-util/native-android?)
-        (.setColor NavigationBar (clj->js {:color "#002b36"}))
+        (.setNavigationBarColor NavigationBar (clj->js {:color "#002b36"}))
         (.setBackgroundColor StatusBar (clj->js {:color "#002b36"}))))))
 
 (defn find-first

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

@@ -1,3 +1,3 @@
 (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.config :as config]
             [frontend.handler.config :as config-handler]
+            [frontend.handler.route :as route-handler]
             [frontend.db :as db]
             [frontend.db.model :as db-model]
             [frontend.db.query-dsl :as query-dsl]
@@ -449,17 +450,23 @@
 
 (def ^:export push_state
   (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
   (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
   [pid]
@@ -563,8 +570,11 @@
   page-handler/rename!)
 
 (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 []
   (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-global-configuration "Benutzerdefinierte globale Konfiguration"
  :settings-page/custom-theme "Individuelles Theme"
- :settings-page/customize-shortcuts "Tastaturbefehle"
  :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/disable-sentry "Nutzungs- und Diagnostik-Daten an Logseq senden"
@@ -605,7 +604,6 @@
  :settings-page/preferred-outdenting "Logische Ausrückung"
  :settings-page/preferred-pasting-file "Einfügen der Datei bevorzugen"
  :settings-page/preferred-workflow "Bevorzugter Workflow"
- :settings-page/shortcut-settings "Verknüpfungen anpassen"
  :settings-page/show-brackets "Klammern anzeigen"
  :settings-page/show-full-blocks "Alle Zeilen einer Blockreferenz anzeigen"
  :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-journals "Journals"
  :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/enable-block-time "Block timestamps"
  :settings-page/clear-cache "Clear cache"
@@ -353,6 +351,8 @@
  :settings-page/update-error-2 " Please check out the "
  :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"
 
  :submit "Submit"
@@ -465,6 +465,7 @@
  :whiteboard/dashboard-card-edited "Edited "
  :whiteboard/toggle-grid "Toggle 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-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 "
@@ -827,4 +828,5 @@
   :dev/show-block-data             "(Dev) Show block data"
   :dev/show-block-ast              "(Dev) Show block AST"
   :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"
  :discourse-title                                   "¡Nuestro foro!"
  :download                                          "Descargar"
+ :export                                            "Exportar"
  :export-copied-to-clipboard                        "¡Copiado al portapapeles!"
  :export-copy-to-clipboard                          "Copiar al portapapeles"
  :export-edn                                        "Exportar a EDN"
- :export                                            "Exportar"
  :export-graph                                      "Exportar grafo"
  :export-json                                       "Exportar a JSON"
  :export-markdown                                   "Exportar como Markdown estándar (sin propiedades de bloque)"
@@ -53,23 +53,24 @@
  :parsing-files                                     "Analizando archivos"
  :plugins                                           "Extensiones"
  :port                                              "Puerto"
+ :re-index                                          "Reindexar"
  :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-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?"
  :remove-background                                 "Eliminar el fondo"
  :remove-heading                                    "Eliminar encabezado"
  :remove-orphaned-pages                             "¿Eliminar páginas huérfanas?"
+ :reset                                             "Reiniciar"
  :save                                              "Guardar"
  :search                                            "Buscar o Crear Página"
- :settings-of-plugins                               "Opciones de Extensiones"
  :settings                                          "Opciones"
+ :settings-of-plugins                               "Opciones de Extensiones"
  :strikethrough                                     "Tachado"
  :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-files-detail                      "Importar cambios de los archivos locales"
  :sync-from-local-files                             "Refrescar"
+ :sync-from-local-files-detail                      "Importar cambios de los archivos locales"
  :themes                                            "Temas"
  :toggle-theme                                      "Alternar tema"
  :type                                              "Tipo"
@@ -151,8 +152,8 @@
  :command.editor/copy-text                          "Copiar selección como texto"
  :command.editor/cut                                "Pegar"
  :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-selection                   "Eliminar bloques seleccionados"
  :command.editor/down                               "Mover cursor abajo / Seleccionar abajo"
  :command.editor/end-of-block                       "Mover cursor al final del bloque"
  :command.editor/escape-editing                     "Escapar de edición"
@@ -269,6 +270,7 @@
  :command.whiteboard/zoom-out                       "Alejar"
  :command.whiteboard/zoom-to-fit                    "Zoom al dibujo"
  :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/copy-block-emebed                         "Copiar bloque a incrustar (embed)"
  :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/no-action                                 "¡Bien hecho! No es necesario realizar más acciones."
  :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-2                       ". Estas acciones no estarán disponibles una vez cierres este panel."
  :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/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/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-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/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."
@@ -353,11 +356,11 @@
  :flashcards/modal-welcome-title                    "¡Hora de crear una tarjeta!"
  :graph/all-graphs                                  "Todos los grafos"
  :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-error                               "Falló la sincronización del estado interno."
  :graph/remote-graphs                               "Grafos remotos:"
- :graph/save-error                                  "Falló el guardado"
  :graph/save                                        "Guardando..."
+ :graph/save-error                                  "Falló el guardado"
  :graph/save-success                                "Guardado satisfactoriamente"
  :header/go-back                                    "Ir hacia atrás"
  :header/go-forward                                 "Ir haca adelante"
@@ -393,6 +396,18 @@
  :help/title-development                            "Desarrollo"
  :help/title-terms                                  "Términos"
  :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/journals                            "Diarios"
  :left-side-bar/nav-favorites                       "Favoritos"
@@ -464,8 +479,8 @@
  :page/backlinks                                    "Vínculos de retroceso"
  :page/copy-page-url                                "Copiar URL de la página"
  :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-confirmation                          "¿Está seguro que desea eliminar esta página y su archivo?"
  :page/earlier                                      "Anteriormente"
  :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:"
@@ -477,8 +492,8 @@
  :page/page-already-exists                          "¡La página “{1}” ya existe!"
  :page/show-journals                                "Mostrar diarios"
  :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-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/step                                         "Paso {1}"
  :page/try                                          "Intentar"
@@ -509,6 +524,7 @@
  :plugin/enabled                                    "Habilitado"
  :plugin/found-n-updates                            "Se encontraron {1} actualizaciones"
  :plugin/found-updates                              "Nuevas actualizaciones"
+ :plugin/install                                    "Instalar"
  :plugin/installed                                  "Instalado"
  :plugin/installed-plugin                           "Extensión instalada: {1}"
  :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/title                    "Instalar extensiones desde  plugins.edn"
  :plugin/installing                                 "Instalando"
- :plugin/install                                    "Instalar"
  :plugin/list-of-updates                            "Actualizaciones de extensiones: "
  :plugin/load-unpacked                              "Cargar extensión desempaquetada"
  :plugin/marketplace                                "Tienda"
@@ -557,14 +572,14 @@
  :right-side-bar/history-undos                      "Deshacer"
  :right-side-bar/new-page                           "Nueva 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-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-all                  "Colapsar todo"
  :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-all                    "Expandir todo"
  :right-side-bar/pane-more                          "Más"
  :right-side-bar/pane-open-as-page                  "Abrir como página"
  :right-side-bar/separator                          "Right sidebar resize handler "
@@ -593,6 +608,8 @@
  :select.graph/prompt                               "Seleccione un grafo"
  :settings-page/alpha-features                      "Características Alfa"
  :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-tip          "Esta opción controla si expandir el bloque de referencias automáticamente al hacer un acercamiento."
  :settings-page/auto-updater                        "Auto actualizador"
@@ -600,21 +617,20 @@
  :settings-page/changelog                           "¿Qué hay de nuevo?"
  :settings-page/check-for-updates                   "Comprobar actualizaciones"
  :settings-page/checking                            "Comprobando ..."
+ :settings-page/clear                               "Limpiar"
  :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                               "Limpiar"
  :settings-page/current-version                     "Versión actual"
  :settings-page/custom-configuration                "Configuración personalizada"
  :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-warning          "¡Se requiere reindexar! ¡Las referencias existentes del diario podrían estar rotas!"
  :settings-page/custom-global-configuration         "Configuración global personalizada"
- :settings-page/customize-shortcuts                 "Atajos de teclado"
  :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/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-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-custom-css                     "Editar custom.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-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-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-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/revision                            "Revisión: "
- :settings-page/shortcut-settings                   "Personalizar atajos"
  :settings-page/show-brackets                       "Mostrar corchetes"
  :settings-page/show-full-blocks                    "Mostrar todas las líneas de una referencia a bloque"
  :settings-page/spell-checker                       "Corrector ortográfico"
+ :settings-page/sync                                "Sincronizar"
  :settings-page/sync-desc-1                         "Clic"
  :settings-page/sync-desc-2                         "aquí"
  :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-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                                "Sincronizar"
  :settings-page/tab-account                         "Cuenta"
  :settings-page/tab-advanced                        "Avanzado"
  :settings-page/tab-assets                          "Recursos"
  :settings-page/tab-editor                          "Editor"
  :settings-page/tab-features                        "Características"
  :settings-page/tab-general                         "General"
+ :settings-page/tab-keymap                          "Mapa de teclado"
  :settings-page/tab-version-control                 "Control de versiones"
  :settings-page/theme-dark                          "oscuro"
  :settings-page/theme-light                         "claro"
@@ -737,8 +753,8 @@
  :whiteboard/medium                                 "Medio"
  :whiteboard/move-to-back                           "Mover al fondo"
  :whiteboard/move-to-front                          "Mover al frente"
- :whiteboard/new-block-no-colon                     "Nuevo bloque"
  :whiteboard/new-block                              "Nuevo bloque:"
+ :whiteboard/new-block-no-colon                     "Nuevo bloque"
  :whiteboard/new-page                               "Nueva página:"
  :whiteboard/new-whiteboard                         "Nueva pizarra"
  :whiteboard/opacity                                "Opacidad"
@@ -749,8 +765,8 @@
  :whiteboard/open-youtube-url                       "Abrir url de YouTube"
  :whiteboard/pack-into-rectangle                    "Empacar en un rectángulo"
  :whiteboard/pan                                    "Mover"
- :whiteboard/paste-as-link                          "Pegar como enlace"
  :whiteboard/paste                                  "Pegar"
+ :whiteboard/paste-as-link                          "Pegar como enlace"
  :whiteboard/rectangle                              "Recargar"
  :whiteboard/redo                                   "Rehacer"
  :whiteboard/references                             "Referencias"
@@ -759,9 +775,9 @@
  :whiteboard/scale-level                            "Escalar nivel"
  :whiteboard/search-only-blocks                     "Buscar solo bloques"
  :whiteboard/search-only-pages                      "Buscar solo páginas"
+ :whiteboard/select                                 "Seleccionar"
  :whiteboard/select-all                             "Seleccionar todo"
  :whiteboard/select-custom-color                    "Seleccionar color personalizado"
- :whiteboard/select                                 "Seleccionar"
  :whiteboard/shape                                  "Forma"
  :whiteboard/shape-quick-links                      "Enlaces rápidos de forma"
  :whiteboard/small                                  "Pequeño"
@@ -770,6 +786,7 @@
  :whiteboard/stroke-type                            "Tipo de línea"
  :whiteboard/text                                   "Texto"
  :whiteboard/toggle-grid                            "Alternar cuadrícula"
+ :whiteboard/toggle-pen-mode                        "Alternar modo pluma"
  :whiteboard/triangle                               "Triángulo"
  :whiteboard/twitter-url                            "url de Twitter"
  :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-global-configuration "Configuration globale personnalisée"
     :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/edit-custom-css "Modifier custom.css"
     :settings-page/edit-export-css "Modifier export.css"
@@ -281,7 +280,6 @@
     :settings-page/network-proxy "Proxy réseau"
     :settings-page/plugin-system "Extensions"
     :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/spell-checker "Vérification orthographique"
     :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-journals "Diario"
  :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/enable-block-time "Indicatori temporali sui blocchi"
  :settings-page/clear-cache "Pulisci cache"

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

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

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

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

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