Browse Source

Merge pull request #3198 from logseq/mobile

Mobile APP
Tienson Qin 4 years ago
parent
commit
8f6a05acdf
100 changed files with 895 additions and 442 deletions
  1. 2 0
      .gitignore
  2. 46 0
      android/README.md
  3. 2 2
      android/app/build.gradle
  4. 1 0
      android/app/capacitor.build.gradle
  5. 2 2
      android/app/src/main/AndroidManifest.xml
  6. 4 1
      android/app/src/main/assets/capacitor.config.json
  7. 4 0
      android/app/src/main/assets/capacitor.plugins.json
  8. 12 3
      android/app/src/main/java/com/logseq/app/FolderPicker.java
  9. 6 0
      android/app/src/main/java/com/logseq/app/MainActivity.java
  10. 12 0
      android/app/src/main/res/anim/byebye.xml
  11. BIN
      android/app/src/main/res/drawable-land-hdpi/splash.png
  12. BIN
      android/app/src/main/res/drawable-land-mdpi/splash.png
  13. BIN
      android/app/src/main/res/drawable-land-xhdpi/splash.png
  14. BIN
      android/app/src/main/res/drawable-land-xxhdpi/splash.png
  15. BIN
      android/app/src/main/res/drawable-land-xxxhdpi/splash.png
  16. BIN
      android/app/src/main/res/drawable-port-hdpi/splash.png
  17. BIN
      android/app/src/main/res/drawable-port-mdpi/splash.png
  18. BIN
      android/app/src/main/res/drawable-port-xhdpi/splash.png
  19. BIN
      android/app/src/main/res/drawable-port-xxhdpi/splash.png
  20. BIN
      android/app/src/main/res/drawable-port-xxxhdpi/splash.png
  21. BIN
      android/app/src/main/res/drawable/splash.png
  22. 0 1
      android/app/src/main/res/values/styles.xml
  23. 3 0
      android/capacitor.settings.gradle
  24. 1 1
      android/gradle.properties
  25. 3 3
      android/variables.gradle
  26. 6 3
      capacitor.config.ts
  27. 32 17
      ios/App/App.xcodeproj/project.pbxproj
  28. 20 0
      ios/App/App/App.entitlements
  29. 22 0
      ios/App/App/AppDebug.entitlements
  30. BIN
      ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.png
  31. BIN
      ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.png
  32. BIN
      ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.png
  33. 17 1
      ios/App/App/FolderPicker.swift
  34. 82 47
      ios/App/App/Info.plist
  35. 9 5
      ios/App/App/capacitor.config.json
  36. 2 1
      ios/App/Podfile
  37. 5 3
      package.json
  38. 11 0
      public/index.html
  39. BIN
      resources/android/splash/drawable-land-hdpi-screen.png
  40. BIN
      resources/android/splash/drawable-land-ldpi-screen.png
  41. BIN
      resources/android/splash/drawable-land-mdpi-screen.png
  42. BIN
      resources/android/splash/drawable-land-xhdpi-screen.png
  43. BIN
      resources/android/splash/drawable-land-xxhdpi-screen.png
  44. BIN
      resources/android/splash/drawable-land-xxxhdpi-screen.png
  45. BIN
      resources/android/splash/drawable-port-hdpi-screen.png
  46. BIN
      resources/android/splash/drawable-port-ldpi-screen.png
  47. BIN
      resources/android/splash/drawable-port-mdpi-screen.png
  48. BIN
      resources/android/splash/drawable-port-xhdpi-screen.png
  49. BIN
      resources/android/splash/drawable-port-xxhdpi-screen.png
  50. BIN
      resources/android/splash/drawable-port-xxxhdpi-screen.png
  51. 15 6
      resources/css/common.css
  52. BIN
      resources/ios/splash/Default-1792h~iphone.png
  53. BIN
      resources/ios/splash/Default-2436h.png
  54. BIN
      resources/ios/splash/Default-2688h~iphone.png
  55. BIN
      resources/ios/splash/Default-568h@2x~iphone.png
  56. BIN
      resources/ios/splash/Default-667h.png
  57. BIN
      resources/ios/splash/Default-736h.png
  58. BIN
      resources/ios/splash/Default-Landscape-1792h~iphone.png
  59. BIN
      resources/ios/splash/Default-Landscape-2436h.png
  60. BIN
      resources/ios/splash/Default-Landscape-2688h~iphone.png
  61. BIN
      resources/ios/splash/Default-Landscape-736h.png
  62. BIN
      resources/ios/splash/Default-Landscape@2x~ipad.png
  63. BIN
      resources/ios/splash/Default-Landscape@~ipadpro.png
  64. BIN
      resources/ios/splash/Default-Landscape~ipad.png
  65. BIN
      resources/ios/splash/Default-Portrait@2x~ipad.png
  66. BIN
      resources/ios/splash/Default-Portrait@~ipadpro.png
  67. BIN
      resources/ios/splash/Default-Portrait~ipad.png
  68. BIN
      resources/ios/splash/Default@2x~iphone.png
  69. BIN
      resources/ios/splash/Default@2x~universal~anyany.png
  70. BIN
      resources/ios/splash/Default~iphone.png
  71. BIN
      resources/splash.png
  72. 4 2
      src/electron/electron/handler.cljs
  73. 2 1
      src/main/frontend/commands.cljs
  74. 78 44
      src/main/frontend/components/block.cljs
  75. 1 0
      src/main/frontend/components/block.css
  76. 2 2
      src/main/frontend/components/command_palette.css
  77. 19 23
      src/main/frontend/components/datetime.cljs
  78. 27 18
      src/main/frontend/components/editor.cljs
  79. 43 20
      src/main/frontend/components/header.cljs
  80. 4 1
      src/main/frontend/components/header.css
  81. 2 1
      src/main/frontend/components/hierarchy.cljs
  82. 15 10
      src/main/frontend/components/page.cljs
  83. 22 0
      src/main/frontend/components/page.css
  84. 10 8
      src/main/frontend/components/page_menu.cljs
  85. 5 3
      src/main/frontend/components/reference.cljs
  86. 16 7
      src/main/frontend/components/repo.cljs
  87. 4 1
      src/main/frontend/components/search.cljs
  88. 30 18
      src/main/frontend/components/settings.cljs
  89. 36 18
      src/main/frontend/components/sidebar.cljs
  90. 19 1
      src/main/frontend/components/sidebar.css
  91. 42 36
      src/main/frontend/components/theme.cljs
  92. 49 21
      src/main/frontend/components/widgets.cljs
  93. 42 41
      src/main/frontend/extensions/zotero.cljs
  94. 8 2
      src/main/frontend/extensions/zotero.css
  95. 1 1
      src/main/frontend/fs.cljs
  96. 70 57
      src/main/frontend/fs/capacitor_fs.cljs
  97. 8 5
      src/main/frontend/handler.cljs
  98. 6 1
      src/main/frontend/handler/editor/lifecycle.cljs
  99. 6 2
      src/main/frontend/handler/extract.cljs
  100. 5 2
      src/main/frontend/handler/file.cljs

+ 2 - 0
.gitignore

@@ -36,4 +36,6 @@ charlie/
 .vscode
 /.preprocessor-cljs
 docker
+android/app/src/main/assets/capacitor.plugin.json
+ios/App/App/capacitor.config.json
 yarn.lock

+ 46 - 0
android/README.md

@@ -0,0 +1,46 @@
+## Set up development environment 
+* Install Android studio [^1] and SDK (newer than 30) tools
+   Note: for M1 MacBook users.
+   - Download version **Mac with Apple Chip** 
+   - unzip it and move **Android Studio.app** file to **Applications**, or you will get the following error later.
+     ```
+     [error] Unable to launch Android Studio. Is it installed?
+        Attempted to open Android Studio at: /Applications/Android Studio.app
+        You can configure this with the CAPACITOR_ANDROID_STUDIO_PATH environment variable.
+     ```
+* In Android Studio, open **Tools** -> **SDK Manager** to install other SDK tools [^2].
+  > In the SDK Tools tab, make sure to install at least the following:
+  >> - Android SDK Build-Tools
+  >> - Android SDK Command-line Tools
+  >> - Android Emulator
+  >> - Android SDK Platform-Tools
+
+## Build the development app in Android emulator
+* Replace `server url` with your local-ip-address:3001 (run ifconfig to check) in *capacitor.config.ts*.
+* Run `yarn && yarn app-watch` from the logseq project root directory in terminal.
+* Run `npx cap sync android` in another termimal (all-in-one cmd).
+* In Android Studio, open **Tools** -> **AVD Manager** to create Android Virtual Device (AVD), and lanuch it in the emulator.
+* In Android Studio, open **Run** -> **Run** to run Logseq.
+* After logseq startup in Android virtual device, repl should be able to connect
+* For browser console print and devtool remote debug, open chrome, type url chrome://inspect/#devices, you should see your device there, click inspect
+
+## Build a release and install it to your android device 
+* Comment in `server url` in *capacitor.config.ts*.
+* Connect your device to PC.
+* Run `yarn clean && yarn release-app && rm -rf ./public/static && rm -rf ./static/js/*.map && mv static ./public && npx cap sync android && npx cap run android`
+
+## Build a apk
+* Comment out `server url` in *capacitor.config.ts*.
+* Run `yarn clean && yarn release-app && rm -rf ./public/static && rm -rf ./static/js/*.map && mv static ./public && npx cap sync android`.
+* In Android Studio, open **Build** -> **Build Bundles / APKs** -> **Build APKs**.
+* Get your apk in `android/app/build/apk/debug`.
+
+[^1] https://developer.android.com/studio/index.html
+
+[^2] https://capacitorjs.com/docs/getting-started/environment-setup
+
+## Develop without opening Android Studio
+1. brew install gradle
+2. make sure java version using 11
+3. cd web/android && gradle wrapper
+4. install android sdk 30

+ 2 - 2
android/app/build.gradle

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

+ 1 - 0
android/app/capacitor.build.gradle

@@ -9,6 +9,7 @@ android {
 
 apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
 dependencies {
+    implementation project(':capacitor-app')
     implementation project(':capacitor-filesystem')
     implementation project(':capacitor-splash-screen')
 

+ 2 - 2
android/app/src/main/AndroidManifest.xml

@@ -3,7 +3,6 @@
     package="com.logseq.app">
 
     <application
-        android:requestLegacyExternalStorage="true"
         android:allowBackup="true"
         android:icon="@mipmap/ic_launcher"
         android:label="@string/app_name"
@@ -39,6 +38,7 @@
     <!-- Permissions -->
 
     <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 </manifest>

+ 4 - 1
android/app/src/main/assets/capacitor.config.json

@@ -8,8 +8,11 @@
 			"launchShowDuration": 3000,
 			"launchAutoHide": false,
 			"androidScaleType": "CENTER_CROP",
-			"splashImmersive": true,
+			"splashImmersive": false,
 			"backgroundColor": "#002b36"
 		}
+	},
+	"ios": {
+		"scheme": "Logseq"
 	}
 }

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

@@ -1,4 +1,8 @@
 [
+	{
+		"pkg": "@capacitor/app",
+		"classpath": "com.capacitorjs.plugins.app.AppPlugin"
+	},
 	{
 		"pkg": "@capacitor/filesystem",
 		"classpath": "com.capacitorjs.plugins.filesystem.FilesystemPlugin"

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

@@ -4,7 +4,9 @@ import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
+import android.os.Environment;
 import android.provider.DocumentsContract;
+import android.provider.Settings;
 
 import androidx.activity.result.ActivityResult;
 import androidx.documentfile.provider.DocumentFile;
@@ -21,9 +23,16 @@ import com.getcapacitor.PluginMethod;
 public class FolderPicker extends Plugin {
     @PluginMethod()
     public void pickFolder(PluginCall call) {
-        Intent i = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
-        i.addCategory(Intent.CATEGORY_DEFAULT);
-        startActivityForResult(call, i, "folderPickerResult");
+        if (Environment.isExternalStorageManager()) {
+            Intent i = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
+            i.addCategory(Intent.CATEGORY_DEFAULT);
+            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);
+            intent.setData(uri);
+            startActivityForResult(call, intent, 20);
+        }
     }
 
     @ActivityCallback

+ 6 - 0
android/app/src/main/java/com/logseq/app/MainActivity.java

@@ -10,4 +10,10 @@ public class MainActivity extends BridgeActivity {
         super.onCreate(savedInstanceState);
         registerPlugin(FolderPicker.class);
     }
+
+    @Override
+    public void onPause() {
+        overridePendingTransition(0, R.anim.byebye);
+        super.onPause();
+    }
 }

+ 12 - 0
android/app/src/main/res/anim/byebye.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:zAdjustment="top">
+    <translate android:fromYDelta="0" android:toYDelta="5%p"
+        android:duration="250"
+        android:interpolator="@android:interpolator/fast_out_slow_in"/>
+    <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
+        android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+        android:startOffset="100" android:duration="150"
+        android:interpolator="@android:interpolator/fast_out_slow_in" />
+</set>

BIN
android/app/src/main/res/drawable-land-hdpi/splash.png


BIN
android/app/src/main/res/drawable-land-mdpi/splash.png


BIN
android/app/src/main/res/drawable-land-xhdpi/splash.png


BIN
android/app/src/main/res/drawable-land-xxhdpi/splash.png


BIN
android/app/src/main/res/drawable-land-xxxhdpi/splash.png


BIN
android/app/src/main/res/drawable-port-hdpi/splash.png


BIN
android/app/src/main/res/drawable-port-mdpi/splash.png


BIN
android/app/src/main/res/drawable-port-xhdpi/splash.png


BIN
android/app/src/main/res/drawable-port-xxhdpi/splash.png


BIN
android/app/src/main/res/drawable-port-xxxhdpi/splash.png


BIN
android/app/src/main/res/drawable/splash.png


+ 0 - 1
android/app/src/main/res/values/styles.xml

@@ -16,7 +16,6 @@
         <item name="android:windowIsTranslucent">true</item>
     </style>
 
-
     <style name="AppTheme.NoActionBarLaunch" parent="AppTheme.NoActionBar">
         <item name="android:background">@drawable/splash</item>
     </style>

+ 3 - 0
android/capacitor.settings.gradle

@@ -2,6 +2,9 @@
 include ':capacitor-android'
 project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
 
+include ':capacitor-app'
+project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/android')
+
 include ':capacitor-filesystem'
 project(':capacitor-filesystem').projectDir = new File('../node_modules/@capacitor/filesystem/android')
 

+ 1 - 1
android/gradle.properties

@@ -9,7 +9,7 @@
 
 # Specifies the JVM arguments used for the daemon process.
 # The setting is particularly useful for tweaking memory settings.
-org.gradle.jvmargs=-Xmx1536m
+org.gradle.jvmargs=-Xmx4096m
 
 # When configured, Gradle will run in incubating parallel mode.
 # This option should only be used with decoupled projects. More details, visit

+ 3 - 3
android/variables.gradle

@@ -1,7 +1,7 @@
 ext {
     minSdkVersion = 21
-    compileSdkVersion = 29
-    targetSdkVersion = 29
+    compileSdkVersion = 30
+    targetSdkVersion = 30
     androidxActivityVersion = '1.2.0'
     androidxAppCompatVersion = '1.2.0'
     androidxCoordinatorLayoutVersion = '1.1.0'
@@ -11,4 +11,4 @@ ext {
     androidxJunitVersion = '1.1.2'
     androidxEspressoCoreVersion = '3.3.0'
     cordovaAndroidVersion = '7.0.0'
-}
+}

+ 6 - 3
capacitor.config.ts

@@ -10,15 +10,18 @@ const config: CapacitorConfig = {
             launchShowDuration: 3000,
             launchAutoHide: false,
             androidScaleType: "CENTER_CROP",
-            splashImmersive: true,
+            splashImmersive: false,
             backgroundColor: "#002b36"
         },
+    },
+    ios: {
+        scheme: "Logseq"
     }
     // do not commit this into source control
     // source: https://capacitorjs.com/docs/guides/live-reload
     // , server: {
-    //     url: process.env.LOGSEQ_APP_SERVER_URL,
-    //     cleartext: true
+    //    url: process.env.LOGSEQ_APP_SERVER_URL,
+    //    cleartext: true
     // }
 };
 

+ 32 - 17
ios/App/App.xcodeproj/project.pbxproj

@@ -16,25 +16,31 @@
 		50B271D11FEDC1A000F3C39B /* public in Resources */ = {isa = PBXBuildFile; fileRef = 50B271D01FEDC1A000F3C39B /* public */; };
 		7435D10C2704659F00AB88E0 /* FolderPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7435D10B2704659F00AB88E0 /* FolderPicker.swift */; };
 		7435D10F2704660B00AB88E0 /* FolderPicker.m in Sources */ = {isa = PBXBuildFile; fileRef = 7435D10E2704660B00AB88E0 /* FolderPicker.m */; };
-		BDDF3BC6333CA3EA105F48B2 /* Pods_App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A16FBA319D846498E23D2B9 /* Pods_App.framework */; };
+		B50C6194D06FE919721B594B /* Pods_Logseq.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 659A484D0A608EE5D778DEF1 /* Pods_Logseq.framework */; };
+		D32752BE275496C60039291C /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D32752BD275496C60039291C /* CloudKit.framework */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXFileReference section */
 		2FAD9762203C412B000D30F8 /* config.xml */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = config.xml; sourceTree = "<group>"; };
 		50379B222058CBB4000EE86E /* capacitor.config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = capacitor.config.json; sourceTree = "<group>"; };
-		504EC3041FED79650016851F /* App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = App.app; sourceTree = BUILT_PRODUCTS_DIR; };
+		504EC3041FED79650016851F /* Logseq.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Logseq.app; sourceTree = BUILT_PRODUCTS_DIR; };
 		504EC3071FED79650016851F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
 		504EC30C1FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
 		504EC30E1FED79650016851F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
 		504EC3111FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
 		504EC3131FED79650016851F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		50B271D01FEDC1A000F3C39B /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = "<group>"; };
-		6A16FBA319D846498E23D2B9 /* Pods_App.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+		659A484D0A608EE5D778DEF1 /* Pods_Logseq.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Logseq.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		7435D10B2704659F00AB88E0 /* FolderPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FolderPicker.swift; sourceTree = "<group>"; };
 		7435D10D2704660A00AB88E0 /* App-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "App-Bridging-Header.h"; sourceTree = "<group>"; };
 		7435D10E2704660B00AB88E0 /* FolderPicker.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FolderPicker.m; sourceTree = "<group>"; };
+		8A489CEC51E94726DDD58810 /* Pods-Logseq.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Logseq.release.xcconfig"; path = "Target Support Files/Pods-Logseq/Pods-Logseq.release.xcconfig"; sourceTree = "<group>"; };
 		A693BFCEA424C37DF4D9AF1B /* Pods-App.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.release.xcconfig"; path = "Target Support Files/Pods-App/Pods-App.release.xcconfig"; sourceTree = "<group>"; };
 		B9A79754543D95E609C23F91 /* Pods-App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.debug.xcconfig"; path = "Target Support Files/Pods-App/Pods-App.debug.xcconfig"; sourceTree = "<group>"; };
+		D32752BC275496A60039291C /* App.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = App.entitlements; sourceTree = "<group>"; };
+		D32752BD275496C60039291C /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = System/Library/Frameworks/CloudKit.framework; sourceTree = SDKROOT; };
+		D32752BF2754C5AB0039291C /* AppDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AppDebug.entitlements; sourceTree = "<group>"; };
+		DE5650F4AD4E2242AB9C012D /* Pods-Logseq.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Logseq.debug.xcconfig"; path = "Target Support Files/Pods-Logseq/Pods-Logseq.debug.xcconfig"; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -42,7 +48,8 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				BDDF3BC6333CA3EA105F48B2 /* Pods_App.framework in Frameworks */,
+				D32752BE275496C60039291C /* CloudKit.framework in Frameworks */,
+				B50C6194D06FE919721B594B /* Pods_Logseq.framework in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -62,7 +69,7 @@
 		504EC3051FED79650016851F /* Products */ = {
 			isa = PBXGroup;
 			children = (
-				504EC3041FED79650016851F /* App.app */,
+				504EC3041FED79650016851F /* Logseq.app */,
 			);
 			name = Products;
 			sourceTree = "<group>";
@@ -70,6 +77,8 @@
 		504EC3061FED79650016851F /* App */ = {
 			isa = PBXGroup;
 			children = (
+				D32752BF2754C5AB0039291C /* AppDebug.entitlements */,
+				D32752BC275496A60039291C /* App.entitlements */,
 				50379B222058CBB4000EE86E /* capacitor.config.json */,
 				504EC3071FED79650016851F /* AppDelegate.swift */,
 				504EC30B1FED79650016851F /* Main.storyboard */,
@@ -88,7 +97,8 @@
 		9FC5AB18C7E7E43B09B33A61 /* Frameworks */ = {
 			isa = PBXGroup;
 			children = (
-				6A16FBA319D846498E23D2B9 /* Pods_App.framework */,
+				D32752BD275496C60039291C /* CloudKit.framework */,
+				659A484D0A608EE5D778DEF1 /* Pods_Logseq.framework */,
 			);
 			name = Frameworks;
 			sourceTree = "<group>";
@@ -98,17 +108,18 @@
 			children = (
 				B9A79754543D95E609C23F91 /* Pods-App.debug.xcconfig */,
 				A693BFCEA424C37DF4D9AF1B /* Pods-App.release.xcconfig */,
+				DE5650F4AD4E2242AB9C012D /* Pods-Logseq.debug.xcconfig */,
+				8A489CEC51E94726DDD58810 /* Pods-Logseq.release.xcconfig */,
 			);
-			name = Pods;
 			path = Pods;
 			sourceTree = "<group>";
 		};
 /* End PBXGroup section */
 
 /* Begin PBXNativeTarget section */
-		504EC3031FED79650016851F /* App */ = {
+		504EC3031FED79650016851F /* Logseq */ = {
 			isa = PBXNativeTarget;
-			buildConfigurationList = 504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "App" */;
+			buildConfigurationList = 504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "Logseq" */;
 			buildPhases = (
 				818545F0788D5DB466761623 /* [CP] Check Pods Manifest.lock */,
 				504EC3001FED79650016851F /* Sources */,
@@ -120,9 +131,9 @@
 			);
 			dependencies = (
 			);
-			name = App;
+			name = Logseq;
 			productName = App;
-			productReference = 504EC3041FED79650016851F /* App.app */;
+			productReference = 504EC3041FED79650016851F /* Logseq.app */;
 			productType = "com.apple.product-type.application";
 		};
 /* End PBXNativeTarget section */
@@ -154,7 +165,7 @@
 			projectDirPath = "";
 			projectRoot = "";
 			targets = (
-				504EC3031FED79650016851F /* App */,
+				504EC3031FED79650016851F /* Logseq */,
 			);
 		};
 /* End PBXProject section */
@@ -188,7 +199,7 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
-			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-App/Pods-App-frameworks.sh\"\n";
+			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Logseq/Pods-Logseq-frameworks.sh\"\n";
 			showEnvVarsInLog = 0;
 		};
 		818545F0788D5DB466761623 /* [CP] Check Pods Manifest.lock */ = {
@@ -206,7 +217,7 @@
 			outputFileListPaths = (
 			);
 			outputPaths = (
-				"$(DERIVED_FILE_DIR)/Pods-App-checkManifestLockResult.txt",
+				"$(DERIVED_FILE_DIR)/Pods-Logseq-checkManifestLockResult.txt",
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
@@ -363,11 +374,13 @@
 		};
 		504EC3171FED79650016851F /* Debug */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = B9A79754543D95E609C23F91 /* Pods-App.debug.xcconfig */;
+			baseConfigurationReference = DE5650F4AD4E2242AB9C012D /* Pods-Logseq.debug.xcconfig */;
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CLANG_ENABLE_MODULES = YES;
+				CODE_SIGN_ENTITLEMENTS = App/AppDebug.entitlements;
 				CODE_SIGN_STYLE = Automatic;
+				DEVELOPMENT_TEAM = K378MFWK59;
 				INFOPLIST_FILE = App/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 12.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
@@ -384,11 +397,13 @@
 		};
 		504EC3181FED79650016851F /* Release */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = A693BFCEA424C37DF4D9AF1B /* Pods-App.release.xcconfig */;
+			baseConfigurationReference = 8A489CEC51E94726DDD58810 /* Pods-Logseq.release.xcconfig */;
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CLANG_ENABLE_MODULES = YES;
+				CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
 				CODE_SIGN_STYLE = Automatic;
+				DEVELOPMENT_TEAM = K378MFWK59;
 				INFOPLIST_FILE = App/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 12.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
@@ -413,7 +428,7 @@
 			defaultConfigurationIsVisible = 0;
 			defaultConfigurationName = Release;
 		};
-		504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "App" */ = {
+		504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "Logseq" */ = {
 			isa = XCConfigurationList;
 			buildConfigurations = (
 				504EC3171FED79650016851F /* Debug */,

+ 20 - 0
ios/App/App/App.entitlements

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>aps-environment</key>
+	<string>development</string>
+	<key>com.apple.developer.icloud-container-identifiers</key>
+	<array>
+		<string>iCloud.com.logseq.app</string>
+	</array>
+	<key>com.apple.developer.icloud-services</key>
+	<array>
+		<string>CloudDocuments</string>
+	</array>
+	<key>com.apple.developer.ubiquity-container-identifiers</key>
+	<array>
+		<string>iCloud.com.logseq.app</string>
+	</array>
+</dict>
+</plist>

+ 22 - 0
ios/App/App/AppDebug.entitlements

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>aps-environment</key>
+	<string>development</string>
+	<key>com.apple.developer.icloud-container-environment</key>
+	<string>Development</string>
+	<key>com.apple.developer.icloud-container-identifiers</key>
+	<array>
+		<string>iCloud.com.logseq.app</string>
+	</array>
+	<key>com.apple.developer.icloud-services</key>
+	<array>
+		<string>CloudDocuments</string>
+	</array>
+	<key>com.apple.developer.ubiquity-container-identifiers</key>
+	<array>
+		<string>iCloud.com.logseq.app</string>
+	</array>
+</dict>
+</plist>

BIN
ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.png


BIN
ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.png


BIN
ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.png


+ 17 - 1
ios/App/App/FolderPicker.swift

@@ -14,7 +14,23 @@ public class FolderPicker: CAPPlugin, UIDocumentPickerDelegate {
     
     public var _call: CAPPluginCall? = nil
     
+    var containerUrl: URL? {
+        let id = "iCloud.com.logseq.app"
+        return FileManager.default.url(forUbiquityContainerIdentifier: id)?.appendingPathComponent("Documents")
+    }
+    
     @objc func pickFolder(_ call: CAPPluginCall) {
+        // check for container existence
+        if let url = self.containerUrl, !FileManager.default.fileExists(atPath: url.path, isDirectory: nil) {
+            do {
+                print("the url = " + url.path)
+                try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil)
+            }
+            catch {
+                print("container doesn't exist")
+                print(error.localizedDescription)
+            }
+        }
         
         self._call = call
         
@@ -47,7 +63,7 @@ public class FolderPicker: CAPPlugin, UIDocumentPickerDelegate {
           }
           
           self._call?.resolve([
-            "path": items.first
+            "path": items.first as Any
           ])
       }
 }

+ 82 - 47
ios/App/App/Info.plist

@@ -2,53 +2,88 @@
 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">
 <dict>
-	<key>CFBundleDevelopmentRegion</key>
-	<string>en</string>
-	<key>CFBundleDisplayName</key>
+        <key>APFiles</key>
+        <dict>
+                <key>APFileDescriptionKey</key>
+                <string></string>
+                <key>APFileDestinationPath</key>
+                <string></string>
+                <key>APFileName</key>
+                <string></string>
+                <key>APFileSourcePath</key>
+                <string></string>
+        </dict>
+        <key>CFBundleDevelopmentRegion</key>
+        <string>en</string>
+        <key>CFBundleDisplayName</key>
         <string>Logseq</string>
-	<key>CFBundleExecutable</key>
-	<string>$(EXECUTABLE_NAME)</string>
-	<key>CFBundleIdentifier</key>
-	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
-	<key>CFBundleInfoDictionaryVersion</key>
-	<string>6.0</string>
-	<key>CFBundleName</key>
-	<string>$(PRODUCT_NAME)</string>
-	<key>CFBundlePackageType</key>
-	<string>APPL</string>
-	<key>CFBundleShortVersionString</key>
-	<string>1.0</string>
-	<key>CFBundleVersion</key>
-	<string>1</string>
-	<key>LSRequiresIPhoneOS</key>
-	<true/>
-	<key>NSAppTransportSecurity</key>
-	<dict>
-		<key>NSAllowsArbitraryLoads</key>
-		<true/>
-	</dict>
-	<key>UILaunchStoryboardName</key>
-	<string>LaunchScreen</string>
-	<key>UIMainStoryboardFile</key>
-	<string>Main</string>
-	<key>UIRequiredDeviceCapabilities</key>
-	<array>
-		<string>armv7</string>
-	</array>
-	<key>UISupportedInterfaceOrientations</key>
-	<array>
-		<string>UIInterfaceOrientationPortrait</string>
-		<string>UIInterfaceOrientationLandscapeLeft</string>
-		<string>UIInterfaceOrientationLandscapeRight</string>
-	</array>
-	<key>UISupportedInterfaceOrientations~ipad</key>
-	<array>
-		<string>UIInterfaceOrientationPortrait</string>
-		<string>UIInterfaceOrientationPortraitUpsideDown</string>
-		<string>UIInterfaceOrientationLandscapeLeft</string>
-		<string>UIInterfaceOrientationLandscapeRight</string>
-	</array>
-	<key>UIViewControllerBasedStatusBarAppearance</key>
-	<true/>
+        <key>CFBundleExecutable</key>
+        <string>$(EXECUTABLE_NAME)</string>
+        <key>CFBundleIdentifier</key>
+        <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+        <key>CFBundleInfoDictionaryVersion</key>
+        <string>6.0</string>
+        <key>CFBundleName</key>
+        <string>$(PRODUCT_NAME)</string>
+        <key>CFBundlePackageType</key>
+        <string>APPL</string>
+        <key>CFBundleShortVersionString</key>
+        <string>2.1</string>
+        <key>CFBundleVersion</key>
+        <string>2.1</string>
+        <key>LSRequiresIPhoneOS</key>
+        <true/>
+        <key>LSSupportsOpeningDocumentsInPlace</key>
+        <true/>
+        <key>NSAppTransportSecurity</key>
+        <dict>
+                <key>NSAllowsArbitraryLoads</key>
+                <true/>
+        </dict>
+        <key>NSDocumentsFolderUsageDescription</key>
+        <string></string>
+        <key>NSDownloadsFolderUsageDescription</key>
+        <string></string>
+        <key>NSFileProviderDomainUsageDescription</key>
+        <string></string>
+        <key>NSFileProviderPresenceUsageDescription</key>
+        <string></string>
+        <key>NSUbiquitousContainers</key>
+        <dict>
+                <key>iCloud.$(PRODUCT_BUNDLE_IDENTIFIER)</key>
+                <dict>
+                        <key>NSUbiquitousContainerIsDocumentScopePublic</key>
+                        <true/>
+                        <key>NSUbiquitousContainerName</key>
+                        <string>Logseq</string>
+                        <key>NSUbiquitousContainerSupportedFolderLevels</key>
+                        <string>None</string>
+                </dict>
+        </dict>
+        <key>UILaunchStoryboardName</key>
+        <string>LaunchScreen</string>
+        <key>UIMainStoryboardFile</key>
+        <string>Main</string>
+        <key>UIRequiredDeviceCapabilities</key>
+        <array>
+                <string>armv7</string>
+        </array>
+        <key>UISupportedInterfaceOrientations</key>
+        <array>
+                <string>UIInterfaceOrientationPortrait</string>
+                <string>UIInterfaceOrientationLandscapeLeft</string>
+                <string>UIInterfaceOrientationLandscapeRight</string>
+        </array>
+        <key>UISupportedInterfaceOrientations~ipad</key>
+        <array>
+                <string>UIInterfaceOrientationPortrait</string>
+                <string>UIInterfaceOrientationPortraitUpsideDown</string>
+                <string>UIInterfaceOrientationLandscapeLeft</string>
+                <string>UIInterfaceOrientationLandscapeRight</string>
+        </array>
+        <key>UISupportsDocumentBrowser</key>
+        <true/>
+        <key>UIViewControllerBasedStatusBarAppearance</key>
+        <true/>
 </dict>
 </plist>

+ 9 - 5
ios/App/App/capacitor.config.json

@@ -8,12 +8,16 @@
 			"launchShowDuration": 3000,
 			"launchAutoHide": false,
 			"androidScaleType": "CENTER_CROP",
-			"splashImmersive": true,
+			"splashImmersive": false,
+			"showSpinner": true,
 			"backgroundColor": "#002b36"
 		}
-	}
-    "server": {
-		"url": "http://192.168.0.104:3001",
+	},
+	"ios": {
+		"scheme": "Logseq"
+	},
+	"server": {
+		"url": "http://192.168.1.59:3001",
 		"cleartext": true
-	} 
+	}
 }

+ 2 - 1
ios/App/Podfile

@@ -9,11 +9,12 @@ install! 'cocoapods', :disable_input_output_paths => true
 def capacitor_pods
   pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
   pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
+  pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app'
   pod 'CapacitorFilesystem', :path => '../../node_modules/@capacitor/filesystem'
   pod 'CapacitorSplashScreen', :path => '../../node_modules/@capacitor/splash-screen'
 end
 
-target 'App' do
+target 'Logseq' do
   capacitor_pods
   # Add your Pods here
 end

+ 5 - 3
package.json

@@ -4,7 +4,7 @@
     "private": true,
     "main": "static/electron.js",
     "devDependencies": {
-        "@capacitor/cli": "^3.2.2",
+        "@capacitor/cli": "3.2.2",
         "@playwright/test": "^1.16.3",
         "@tailwindcss/ui": "0.7.2",
         "@types/gulp": "^4.0.7",
@@ -38,7 +38,8 @@
         "release-electron": "run-s gulp:build && gulp electronMaker",
         "debug-electron": "cd static/ && yarn electron:debug",
         "e2e-test": "npx playwright test --reporter github",
-        "run-android-release": "yarn clean && yarn release-app && rm -rf ./public/static && mv static ./public && npx cap copy android && npx cap run android",
+        "run-android-release": "yarn clean && yarn release-app && rm -rf ./public/static && rm -rf ./static/js/*.map && mv static ./public && npx cap sync android && npx cap run android",
+        "run-ios-release": "yarn clean && yarn release-app && rm -rf ./public/static && rm -rf ./static/js/*.map && mv static ./public && npx cap sync ios && npx cap run ios",
         "clean": "gulp clean",
         "test": "run-s cljs:test cljs:run-test",
         "report": "run-s cljs:report",
@@ -63,8 +64,9 @@
     },
     "dependencies": {
         "@capacitor/android": "3.2.2",
+        "@capacitor/app": "1.0.6",
         "@capacitor/core": "3.2.2",
-        "@capacitor/filesystem": "1.0.3",
+        "@capacitor/filesystem": "1.0.6",
         "@capacitor/ios": "3.2.2",
         "@capacitor/splash-screen": "1.1.3",
         "@excalidraw/excalidraw": "0.4.2",

+ 11 - 0
public/index.html

@@ -55,5 +55,16 @@ const portal = new MagicPortal(worker);
 <script defer src="/static/js/code-editor.js"></script>
 <script defer src="/static/js/age-encryption.js"></script>
 <script defer src="/static/js/excalidraw.js"></script>
+<script>
+  /*!
+ * swiped-events.js - v1.1.6
+ * Pure JavaScript swipe events
+ * https://github.com/john-doherty/swiped-events
+ * @inspiration https://stackoverflow.com/questions/16348031/disable-scrolling-when-touch-moving-certain-element
+ * @author John Doherty <www.johndoherty.info>
+ * @license MIT
+ */
+!function(t,e){"use strict";"function"!=typeof t.CustomEvent&&(t.CustomEvent=function(t,n){n=n||{bubbles:!1,cancelable:!1,detail:void 0};var a=e.createEvent("CustomEvent");return a.initCustomEvent(t,n.bubbles,n.cancelable,n.detail),a},t.CustomEvent.prototype=t.Event.prototype),e.addEventListener("touchstart",function(t){if("true"===t.target.getAttribute("data-swipe-ignore"))return;s=t.target,r=Date.now(),n=t.touches[0].clientX,a=t.touches[0].clientY,u=0,i=0},!1),e.addEventListener("touchmove",function(t){if(!n||!a)return;var e=t.touches[0].clientX,r=t.touches[0].clientY;u=n-e,i=a-r},!1),e.addEventListener("touchend",function(t){if(s!==t.target)return;var e=parseInt(l(s,"data-swipe-threshold","20"),10),o=parseInt(l(s,"data-swipe-timeout","500"),10),c=Date.now()-r,d="",p=t.changedTouches||t.touches||[];Math.abs(u)>Math.abs(i)?Math.abs(u)>e&&c<o&&(d=u>0?"swiped-left":"swiped-right"):Math.abs(i)>e&&c<o&&(d=i>0?"swiped-up":"swiped-down");if(""!==d){var b={dir:d.replace(/swiped-/,""),touchType:(p[0]||{}).touchType||"direct",xStart:parseInt(n,10),xEnd:parseInt((p[0]||{}).clientX||-1,10),yStart:parseInt(a,10),yEnd:parseInt((p[0]||{}).clientY||-1,10)};s.dispatchEvent(new CustomEvent("swiped",{bubbles:!0,cancelable:!0,detail:b})),s.dispatchEvent(new CustomEvent(d,{bubbles:!0,cancelable:!0,detail:b}))}n=null,a=null,r=null},!1);var n=null,a=null,u=null,i=null,r=null,s=null;function l(t,n,a){for(;t&&t!==e.documentElement;){var u=t.getAttribute(n);if(u)return u;t=t.parentNode}return a}}(window,document);
+</script>
 </body>
 </html>

BIN
resources/android/splash/drawable-land-hdpi-screen.png


BIN
resources/android/splash/drawable-land-ldpi-screen.png


BIN
resources/android/splash/drawable-land-mdpi-screen.png


BIN
resources/android/splash/drawable-land-xhdpi-screen.png


BIN
resources/android/splash/drawable-land-xxhdpi-screen.png


BIN
resources/android/splash/drawable-land-xxxhdpi-screen.png


BIN
resources/android/splash/drawable-port-hdpi-screen.png


BIN
resources/android/splash/drawable-port-ldpi-screen.png


BIN
resources/android/splash/drawable-port-mdpi-screen.png


BIN
resources/android/splash/drawable-port-xhdpi-screen.png


BIN
resources/android/splash/drawable-port-xxhdpi-screen.png


BIN
resources/android/splash/drawable-port-xxxhdpi-screen.png


+ 15 - 6
resources/css/common.css

@@ -3,9 +3,9 @@
   --ls-tag-text-hover-opacity: 1;
   --ls-page-text-size: 1em;
   --ls-page-title-size: 36px;
-  --ls-font-family: 'Inter';
   --ls-main-content-max-width: 810px;
   --ls-main-content-max-width-wide: 100%;
+  --ls-font-family: Inter;
   --ls-scrollbar-width: 6px;
   --ls-border-radius-low: 4px;
   --ls-border-radius-medium: 8px;
@@ -143,13 +143,15 @@ html[data-theme='light'] {
   --color-level-5: #bbdaf6;
 }
 
+html:not(.is-native-android) {
+  font-family: var(--ls-font-family), sans-serif, system-ui,
+  -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue',
+  Arial, 'Noto Sans', serif, Apple Color Emoji, Segoe UI Emoji,
+  Segoe UI Symbol !important;
+}
+
 /* region Reset top elements */
 html {
-  font-family: var(--ls-font-family), Inter, sans-serif, system-ui,
-    -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue',
-    Arial, 'Noto Sans', serif, Apple Color Emoji, Segoe UI Emoji,
-    Segoe UI Symbol !important;
-
   /* FIXME: rewrite revealjs.css ? */
   height: unset !important;
   overflow: auto !important;
@@ -1122,6 +1124,13 @@ a.tooltip-priority {
   background: var(--ls-tertiary-background-color);
 }
 
+.references-blocks .block-control {
+    margin-left: -22px;
+    @screen sm {
+        margin-left: 2px;
+    }
+}
+
 #head .fade-link {
   font-weight: 600;
   font-size: 13px;

BIN
resources/ios/splash/Default-1792h~iphone.png


BIN
resources/ios/splash/Default-2436h.png


BIN
resources/ios/splash/Default-2688h~iphone.png


BIN
resources/ios/splash/Default-568h@2x~iphone.png


BIN
resources/ios/splash/Default-667h.png


BIN
resources/ios/splash/Default-736h.png


BIN
resources/ios/splash/Default-Landscape-1792h~iphone.png


BIN
resources/ios/splash/Default-Landscape-2436h.png


BIN
resources/ios/splash/Default-Landscape-2688h~iphone.png


BIN
resources/ios/splash/Default-Landscape-736h.png


BIN
resources/ios/splash/Default-Landscape@2x~ipad.png


BIN
resources/ios/splash/Default-Landscape@~ipadpro.png


BIN
resources/ios/splash/Default-Landscape~ipad.png


BIN
resources/ios/splash/Default-Portrait@2x~ipad.png


BIN
resources/ios/splash/Default-Portrait@~ipadpro.png


BIN
resources/ios/splash/Default-Portrait~ipad.png


BIN
resources/ios/splash/Default@2x~iphone.png


BIN
resources/ios/splash/Default@2x~universal~anyany.png


BIN
resources/ios/splash/Default~iphone.png


BIN
resources/splash.png


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

@@ -15,7 +15,8 @@
             [clojure.core.async :as async]
             [electron.search :as search]
             [electron.git :as git]
-            [electron.plugin :as plugin]))
+            [electron.plugin :as plugin]
+            [frontend.handler.route :as route-handler]))
 
 (defmulti handle (fn [_window args] (keyword (first args))))
 
@@ -187,7 +188,8 @@
         (try
           (fs-extra/removeSync path)
           (catch js/Error e
-            (js/console.error e)))))))
+            (js/console.error e)))))
+    (route-handler/redirect-to-home!)))
 
 (defmethod handle :clearCache [_window _]
   (search/close!)

+ 2 - 1
src/main/frontend/commands.cljs

@@ -503,7 +503,8 @@
                          (not beginning-of-line?))
                   (str "\n" value)
                   value)]
-      (insert! input-id value option))))
+      (insert! input-id value option)
+      (reset! *show-commands false))))
 
 (defmethod handle-step :editor/cursor-back [[_ n]]
   (when-let [input-id (state/get-edit-input-id)]

+ 78 - 44
src/main/frontend/components/block.cljs

@@ -1704,7 +1704,7 @@
   [state block typ ast]
   (let [show? (get state ::show?)]
     [:div.flex.flex-col.timestamp
-     [:div.text-sm.mb-1.flex.flex-row
+     [:div.text-sm.flex.flex-row
       [:div.opacity-50.font-medium.timestamp-label
        (str typ ": ")]
       [:a.opacity-80.hover:opacity-100
@@ -1787,6 +1787,32 @@
                   (= move-to :nested)))
           (dnd-separator move-to block-content?))))))
 
+(defn clock-summary-cp
+  [block body]
+  [:span.text-right {:style {:max-width 100}}
+   (when (and (state/enable-timetracking?)
+              (or (= (:block/marker block) "DONE")
+                  (contains? #{"TODO" "LATER"} (:block/marker block))))
+     (let [summary (clock/clock-summary body true)]
+       (when (and summary
+                  (not= summary "0m")
+                  (not (string/blank? summary)))
+         (ui/tippy {:html        (fn []
+                                   (when-let [logbook (drawer/get-logbook body)]
+                                     (let [clocks (->> (last logbook)
+                                                       (filter #(string/starts-with? % "CLOCK:"))
+                                                       (remove string/blank?))]
+                                       [:div.p-4
+                                        [:div.font-bold.mb-2 "LOGBOOK:"]
+                                        [:ul
+                                         (for [clock (take 10 (reverse clocks))]
+                                           [:li clock])]])))
+                    :interactive true
+                    :delay       [1000, 100]}
+                   [:div.text-sm.time-spent.ml-1 {:style {:padding-top 3}}
+                    [:a.fade-link
+                     summary]]))))])
+
 (rum/defc block-content < rum/reactive
   [config {:block/keys [uuid title body content children properties scheduled deadline] :as block} edit-input-id block-id slide?]
   (let [collapsed? (get properties :collapsed)
@@ -1797,14 +1823,14 @@
         mouse-down-key (if (util/ios?)
                          :on-click
                          :on-mouse-down ; TODO: it seems that Safari doesn't work well with on-mouse-down
-)
+                         )
         attrs (cond->
-               {:blockid       (str uuid)
-                :data-type (name block-type)
-                :style {:width "100%"}}
-                (not block-ref?)
-                (assoc mouse-down-key (fn [e]
-                                        (block-content-on-mouse-down e block block-id content edit-input-id))))]
+                  {:blockid       (str uuid)
+                   :data-type (name block-type)
+                   :style {:width "100%"}}
+                  (not block-ref?)
+                  (assoc mouse-down-key (fn [e]
+                                          (block-content-on-mouse-down e block block-id content edit-input-id))))]
     [:div.block-content.inline
      (cond-> {:id (str "block-content-" uuid)
               :on-mouse-up (fn [_e]
@@ -1820,12 +1846,16 @@
       ;; .flex.relative {:style {:width "100%"}}
       [:span
        ;; .flex-1.flex-col.relative.block-content
-       (cond
-         (seq title)
-         (build-block-title config block)
+       [:span.flex.flex-row.justify-between
+        [:span
+         (cond
+           (seq title)
+           (build-block-title config block)
 
-         :else
-         nil)
+           :else
+           nil)]
+
+        (clock-summary-cp block body)]
 
        (when (seq children)
          (dnd-separator-wrapper block block-id slide? false true))
@@ -1921,29 +1951,6 @@
                                (editor-handler/edit-block! block :max (:block/uuid block))))}
            svg/edit])
 
-        (when (and (state/enable-timetracking?)
-                   (or (= (:block/marker block) "DONE")
-                       (contains? #{"TODO" "LATER"} (:block/marker block))))
-          (let [summary (clock/clock-summary body true)]
-            (when (and summary
-                       (not= summary "0m")
-                       (not (string/blank? summary)))
-              (ui/tippy {:html        (fn []
-                                        (when-let [logbook (drawer/get-logbook body)]
-                                          (let [clocks (->> (last logbook)
-                                                            (filter #(string/starts-with? % "CLOCK:"))
-                                                            (remove string/blank?))]
-                                            [:div.p-4
-                                             [:div.font-bold.mb-2 "LOGBOOK:"]
-                                             [:ul
-                                              (for [clock (take 10 (reverse clocks))]
-                                                [:li clock])]])))
-                         :interactive true
-                         :delay       [1000, 100]}
-                        [:div.text-sm.time-spent.ml-1 {:style {:padding-top 3}}
-                         [:a.fade-link
-                          summary]]))))
-
         (block-refs-count block)]])))
 
 (defn non-dragging?
@@ -2359,6 +2366,32 @@
        tb-col-groups
        (cons head groups)))]))
 
+(defn logbook-cp
+  [log]
+  (let [clocks (filter #(string/starts-with? % "CLOCK:") log)
+        clocks (reverse (sort-by str clocks))
+        ;; TODO: diplay states change log
+        states (filter #(not (string/starts-with? % "CLOCK:")) log)]
+    (when (seq clocks)
+      (let [tr (fn [elm cols] (->elem :tr
+                                      (mapv (fn [col] (->elem elm col)) cols)))
+            head  [:thead.overflow-x-scroll (tr :th.py-0 ["Type" "Start" "End" "Span"])]
+            clock-tbody (->elem
+                         :tbody.overflow-scroll.sm:overflow-auto
+                         (mapv (fn [clock]
+                                 (let [cols (->> (string/split clock #": |--|=>")
+                                                 (map string/trim))]
+                                   (mapv #(tr :td.py-0 %) [cols])))
+                               clocks))]
+        [:div.overflow-x-scroll.sm:overflow-auto
+         (->elem
+          :table.m-0 
+          {:class "logbook-table"
+           :border 0
+           :style {:width "max-content"}
+           :cell-spacing 15}
+          (cons head [clock-tbody]))]))))
+
 (defn map-inline
   [config col]
   (map #(inline config %) col))
@@ -2524,7 +2557,8 @@
 
               :else
               [:div.text-sm.mt-2.ml-2.font-medium.opacity-50 "Empty"])]
-           {:default-collapsed? collapsed?}))]))))
+           {:default-collapsed? collapsed?
+            :title-trigger? true}))]))))
 
 (defn admonition
   [config type result]
@@ -2596,15 +2630,15 @@
                                       [:logbook/settings :enabled-in-timestamped-blocks] true)
                           (or (:block/scheduled (:block config))
                               (:block/deadline (:block config)))))))
-          [:div.flex.flex-col
-           [:div.text-sm.mt-1.flex.flex-row
+          [:div
+           [:div.text-sm
             [:div.drawer {:data-drawer-name name}
              (ui/foldable
               [:div.opacity-50.font-medium
                (util/format ":%s:" (string/upper-case name))]
-              [:div (apply str lines)
-               [:div.opacity-50.font-medium {:style {:width 95}}
-                ":END:"]]
+              [:div.opacity-50.font-medium
+               (logbook-cp lines)
+               [:div ":END:"]]
               {:default-collapsed? true
                :title-trigger? true})]]])
 
@@ -2877,7 +2911,7 @@
                 parent-blocks (group-by :block/parent blocks)]
             [:div.my-2 (cond-> {:key (str "page-" (:db/id page))}
                          (:ref? config)
-                         (assoc :class "color-level px-7 py-2 rounded"))
+                         (assoc :class "color-level px-2 sm:px-7 py-2 rounded"))
              (ui/foldable
               [:div
                (page-cp config page)
@@ -2900,7 +2934,7 @@
                 page (db/entity (:db/id page))]
             [:div.my-2 (cond-> {:key (str "page-" (:db/id page))}
                          (:ref? config)
-                         (assoc :class "color-level px-7 py-2 rounded"))
+                         (assoc :class "color-level px-2 sm:px-7 py-2 rounded"))
              (ui/foldable
               [:div
                (page-cp config page)

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

@@ -531,3 +531,4 @@ span.cloze-revealed {
 .block-parents a:hover {
   opacity: 1;
 }
+

+ 2 - 2
src/main/frontend/components/command_palette.css

@@ -1,6 +1,6 @@
 .cp__palette {
   --palettle-input-height: 64px;
-  --palettle-container-height: 70vh;
+  --palettle-container-height: 75vh;
 
   &-main {
     max-height: var(--palettle-container-height);
@@ -9,7 +9,7 @@
     display: flex;
     flex-direction: column;
 
-    width: 80vw;
+    /* width: 80vw; */
 
     @screen lg {
       width: 820px;

+ 19 - 23
src/main/frontend/components/datetime.cljs

@@ -22,9 +22,8 @@
   (let [show? (rum/react *show-time?)]
     (if (or show? (not (string/blank? default-value)))
       [:div.flex.flex-row {:style {:height 32}}
-       [:input#time.form-input
-        {:style {:width 240}
-         :default-value default-value
+       [:input#time.form-input.w-20.ms:w-60
+        {:default-value default-value
          :on-change (fn [event]
                       (util/stop event)
                       (let [value (util/evalue event)]
@@ -45,31 +44,28 @@
   [{:keys [num duration kind]}]
   (let [show? (rum/react *show-repeater?)]
     (if (or show? (and num duration kind))
-      [:div.flex.flex-row.justify-center {:style {:height 32}}
-       [:div.block.text-medium.mr-2.mt-1 {:style {:width 110}}
-        "Every"]
-       [:input#repeater-num.form-input.mt-1
-        {:style {:width 48}
-         :default-value num
+      [:div.w.full.flex.flex-row.justify-left {:style {:height 32}}
+       [:input#repeater-num.form-input.mt-1.w-8.px-1.sm:w-20.sm:px-2.text-center
+        {:default-value num
          :on-change (fn [event]
                       (let [value (util/evalue event)]
                         (swap! *timestamp assoc-in [:repeater :num] value)))}]
        (ui/select
-         (mapv
-          (fn [item]
-            (if (= (:label item) duration)
-              (assoc item :selected "selected")
-              item))
-          [{:label "h"}
-           {:label "d"}
-           {:label "w"}
-           {:label "m"}
-           {:label "y"}])
-         (fn [value]
-           (swap! *timestamp assoc-in [:repeater :duration] value))
-         nil)
+        (mapv
+         (fn [item]
+           (if (= (:label item) duration)
+             (assoc item :selected "selected")
+             item))
+         [{:label "h"}
+          {:label "d"}
+          {:label "w"}
+          {:label "m"}
+          {:label "y"}])
+        (fn [value]
+          (swap! *timestamp assoc-in [:repeater :duration] value))
+        nil)
 
-       [:a.ml-2.self-center {:on-click (fn []
+       [:a.ml-1.self-center {:on-click (fn []
                                          (reset! *show-repeater? false)
                                          (swap! *timestamp assoc :repeater {}))}
         svg/close]]

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

@@ -218,37 +218,35 @@
 (rum/defc mobile-bar < rum/reactive
   [parent-state parent-id]
   [:div#mobile-editor-toolbar.bg-base-2.fix-ios-fixed-bottom
-   [:div.flex.justify-evenly.w-full
+   [:div.flex.justify-around.w-full
     [:div
      [:button.bottom-action
       {:on-mouse-down (fn [e]
                         (util/stop e)
                         (editor-handler/indent-outdent true))}
-      (ui/icon "chevrons-right")]]
+      (ui/icon "arrow-bar-right"
+               {:style {:fontSize ui/icon-size}})]]
     [:div
      [:button.bottom-action
       {:on-mouse-down (fn [e]
                         (util/stop e)
                         (editor-handler/indent-outdent false))}
-      (ui/icon "chevrons-left")]]
+      (ui/icon "arrow-bar-left"
+               {:style {:fontSize ui/icon-size}})]]
     [:div
      [:button.bottom-action
       {:on-mouse-down (fn [e]
                         (util/stop e)
                         ((editor-handler/move-up-down true)))}
-      (ui/icon "chevron-up")]]
+      (ui/icon "arrow-bar-to-up"
+               {:style {:fontSize ui/icon-size}})]]
     [:div
      [:button.bottom-action
       {:on-mouse-down (fn [e]
                         (util/stop e)
                         ((editor-handler/move-up-down false)))}
-      (ui/icon "chevron-down")]]
-    [:div
-     [:button.bottom-action
-      {:on-mouse-down (fn [e]
-                        (util/stop e)
-                        (editor-handler/cycle-todo!))}
-      (ui/icon "checkbox")]]
+      (ui/icon "arrow-bar-to-down"
+               {:style {:fontSize ui/icon-size}})]]
     [:div
      [:button.bottom-action
       {:on-mouse-down (fn [e]
@@ -258,9 +256,17 @@
                         ;; TODO: should we add this focus step to `simple-insert!`?
                         (when-let [input (gdom/getElement parent-id)]
                           (.focus input)))}
-      (ui/icon "arrow-back")]]
+      (ui/icon "arrow-back"
+               {:style {:fontSize ui/icon-size}})]]
     [:div
-     [:button.bottom-action.text-sm
+     [:button.bottom-action
+      {:on-mouse-down (fn [e]
+                        (util/stop e)
+                        (editor-handler/cycle-todo!))}
+      (ui/icon "checkbox"
+               {:style {:fontSize ui/icon-size}})]]
+    [:div
+     [:button.bottom-action
       {:on-mouse-down (fn [e]
                         (util/stop e)
                         (commands/simple-insert!
@@ -271,9 +277,10 @@
                                           (commands/handle-step [:editor/search-page]))})
                         (when-let [input (gdom/getElement parent-id)]
                           (.focus input)))}
-      "[["]]
+      (ui/icon "brackets"
+               {:style {:fontSize ui/icon-size}})]]
     [:div
-     [:button.bottom-action.text-sm
+     [:button.bottom-action
       {:on-mouse-down (fn [e]
                         (util/stop e)
                         (commands/simple-insert!
@@ -284,15 +291,17 @@
                                           (commands/handle-step [:editor/search-block]))})
                         (when-let [input (gdom/getElement parent-id)]
                           (.focus input)))}
-      "(("]]
+      (ui/icon "parentheses"
+               {:style {:fontSize ui/icon-size}})]]
     [:div
-     [:button.bottom-action.text-sm
+     [:button.bottom-action
       {:on-mouse-down (fn [e]
                         (util/stop e)
                         (commands/simple-insert! parent-id "/" {})
                         (when-let [input (gdom/getElement parent-id)]
                           (.focus input)))}
-      "/"]]]])
+      (ui/icon "command"
+               {:style {:fontSize ui/icon-size}})]]]])
 
 (rum/defcs input < rum/reactive
   (rum/local {} ::input-value)

+ 43 - 20
src/main/frontend/components/header.cljs

@@ -24,14 +24,15 @@
             [reitit.frontend.easy :as rfe]
             [rum.core :as rum]
             [frontend.mobile.util :as mobile-util]
-            [frontend.components.widgets :as widgets]))
+            [frontend.components.widgets :as widgets]
+            [frontend.handler.web.nfs :as nfs-handler]))
 
 (rum/defc home-button []
   (ui/with-shortcut :go/home "left"
     [:a.button
      {:href     (rfe/href :home)
       :on-click route-handler/go-to-journals!}
-     (ui/icon "home" {:style {:fontSize 20}})]))
+     (ui/icon "home" {:style {:fontSize ui/icon-size}})]))
 
 (rum/defc login
   [logged?]
@@ -62,7 +63,7 @@
     [:a#left-menu.cp__header-left-menu.button
      {:on-click on-click
       :style {:margin-left 12}}
-     (ui/icon "menu-2" {:style {:fontSize 20}})]))
+     (ui/icon "menu-2" {:style {:fontSize ui/icon-size}})]))
 
 (rum/defc dropdown-menu < rum/reactive
   [{:keys [me current-repo t default-home]}]
@@ -76,7 +77,7 @@
      (fn [{:keys [toggle-fn]}]
        [:a.button
         {:on-click toggle-fn}
-        (ui/icon "dots" {:style {:fontSize 20}})])
+        (ui/icon "dots" {:style {:fontSize ui/icon-size}})])
      (->>
       [(when-not (state/publishing-enable-editing?)
          {:title (t :settings)
@@ -125,12 +126,12 @@
    (ui/with-shortcut :go/backward "bottom"
      [:a.it.navigation.nav-left.button
       {:title "Go back" :on-click #(js/window.history.back)}
-      (ui/icon "arrow-left")])
+      (ui/icon "arrow-left" {:style {:fontSize ui/icon-size}})])
 
    (ui/with-shortcut :go/forward "bottom"
      [:a.it.navigation.nav-right.button
       {:title "Go forward" :on-click #(js/window.history.forward)}
-      (ui/icon "arrow-right")])])
+      (ui/icon "arrow-right" {:style {:fontSize ui/icon-size}})])])
 
 (rum/defc updater-tips-new-version
   [t]
@@ -156,9 +157,8 @@
          (svg/reload 16) [:strong (t :updater/quit-and-install)]]]])))
 
 (rum/defc header < rum/reactive
-  [{:keys [open-fn current-repo white? logged? page? route-match me default-home new-block-mode]}]
-  (let [local-repo? (= current-repo config/local-repo)
-        repos (->> (state/sub [:me :repos])
+  [{:keys [open-fn current-repo logged? me default-home new-block-mode]}]
+  (let [repos (->> (state/sub [:me :repos])
                    (remove #(= (:url %) config/local-repo)))
         electron-mac? (and util/mac? (util/electron?))
         show-open-folder? (and (or (nfs/supported?)
@@ -168,12 +168,15 @@
         refreshing? (state/sub :nfs/refreshing?)]
     (rum/with-context [[t] i18n/*tongue-context*]
       [:div.cp__header#head
-       {:class (when electron-mac? "electron-mac")
+       {:class (cond electron-mac? "electron-mac"
+                     (mobile-util/native-ios?) "native-ios"
+                     (mobile-util/native-android?) "native-android")
         :on-double-click (fn [^js e]
                            (when-let [target (.-target e)]
                              (when (and (util/electron?)
                                         (or (.. target -classList (contains "cp__header"))))
-                               (js/window.apis.toggleMaxOrMinActiveWindow))))}
+                               (js/window.apis.toggleMaxOrMinActiveWindow))))
+        :style {:fontSize 50}}
        [:div.l.flex
         (left-menu-button {:on-click (fn []
                                        (open-fn)
@@ -184,12 +187,11 @@
           (ui/with-shortcut :go/search "right"
             [:a.button#search-button
              {:on-click #(state/pub-event! [:go/search])}
-             (ui/icon "search" {:style {:fontSize 20}})]))]
+             (ui/icon "search" {:style {:fontSize ui/icon-size}})]))]
 
        [:div.r.flex
-        (when (and
-                (not (mobile-util/is-native-platform?))
-                (not (util/electron?)))
+        (when (and (not (mobile-util/is-native-platform?))
+                   (not (util/electron?)))
           (login logged?))
 
         (when plugin-handler/lsp-enabled?
@@ -198,17 +200,38 @@
         (when (not= (state/get-current-route) :home)
           (home-button))
 
-        (when (util/electron?) (back-and-forward))
+        (when (or (util/electron?)
+                  ;; (mobile-util/is-native-platform?)
+                  )
+          (back-and-forward))
 
         (new-block-mode)
 
-        (when refreshing?
-          [:div {:class "animate-spin-reverse"}
-           svg/refresh])
+        (when (mobile-util/is-native-platform?)
+          [:a.text-sm.font-medium.button
+           {:on-click
+            (fn []
+              (state/pub-event!
+               [:modal/show
+                [:div {:style {:max-width 700}}
+                 [:p "Refresh detects and processes files modified on your disk and diverged from the actual Logseq page content. Continue?"]
+                 (ui/button
+                  "Yes"
+                  :on-click (fn []
+                              (state/close-modal!)
+                              (nfs-handler/refresh! (state/get-current-repo) repo/refresh-cb)))]]))}
+           (if refreshing?
+             [:div {:class "animate-spin-reverse"}
+              svg/refresh]
+             (when (seq repos)
+               [:div.flex.flex-row.text-center.open-button__inner.items-center
+                (ui/icon "refresh" {:style {:fontSize ui/icon-size}})]))])
 
         (repo/sync-status current-repo)
 
-        (when show-open-folder?
+        (when (and
+               show-open-folder?
+               (not (mobile-util/is-native-platform?)))
           [:a.text-sm.font-medium.button
            {:on-click #(page-handler/ls-dir-files! shortcut/refresh!)}
            [:div.flex.flex-row.text-center.open-button__inner.items-center

+ 4 - 1
src/main/frontend/components/header.css

@@ -160,7 +160,10 @@ a.button {
 
   &:hover, &.active {
     opacity: 1;
-    background: var(--ls-tertiary-background-color);
+    background: none;
+    @screen md {
+        background: var(--ls-tertiary-background-color);
+    }
   }
 }
 

+ 2 - 1
src/main/frontend/components/hierarchy.cljs

@@ -44,4 +44,5 @@
                                          {}
                                          page))))
              (interpose [:span.mx-2.opacity-30 "/"]))])]
-        {:default-collapsed? true})])))
+        {:default-collapsed? true
+         :title-trigger? true})])))

+ 15 - 10
src/main/frontend/components/page.cljs

@@ -842,16 +842,21 @@
                    (ui/icon "x")])])]]
 
            [:div.r.flex.items-center.justify-between
-            [:a.ml-1.pr-2.opacity-70.hover:opacity-100
-             {:on-click (fn [] (state/set-modal!
-                                (batch-delete-dialog
-                                 (model/get-orphaned-pages {}) true
-                                 #(do
-                                    (reset! *checks nil)
-                                    (refresh-pages)))))}
-             [:span
-              (ui/icon "file-x")
-              [:span.ml-1 (t :remove-orphaned-pages)]]]
+            (let [orphaned-pages (model/get-orphaned-pages {})
+                  orphaned-pages? (seq orphaned-pages)]
+              [:a.ml-1.pr-2.opacity-70.hover:opacity-100
+               {:on-click (fn []
+                            (if orphaned-pages?
+                              (state/set-modal!
+                               (batch-delete-dialog
+                                orphaned-pages  true
+                                #(do
+                                   (reset! *checks nil)
+                                   (refresh-pages))))
+                              (notification/show! "Congratulations, no orphaned pages in your graph!" :success)))}
+               [:span
+                (ui/icon "file-x")
+                [:span.ml-1 (t :remove-orphaned-pages)]]])
 
             [:a.ml-1.pr-2.opacity-70.hover:opacity-100 {:href (rfe/href :all-files)}
              [:span

+ 22 - 0
src/main/frontend/components/page.css

@@ -289,3 +289,25 @@
 .cp__right-sidebar .add-button-link {
     margin-left: 21px;
 }
+
+html.is-native-android,
+html.is-native-ios {
+  .cp__all_pages {
+    .actions > .r {
+      position: relative;
+      padding: 15px 0;
+      padding-right: 10px;
+      padding-bottom: 25px;
+      justify-content: space-between;
+
+      .paginates {
+        position: absolute;
+        top: 40px;
+        right: 0;
+        width: 100%;
+        display: flex;
+        justify-content: flex-end;
+      }
+    }
+  }
+}

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

@@ -14,7 +14,8 @@
             [frontend.util :as util]
             [rum.core :as rum]
             [frontend.handler.shell :as shell]
-            [frontend.handler.plugin :as plugin-handler]))
+            [frontend.handler.plugin :as plugin-handler]
+            [frontend.mobile.util :as mobile-util]))
 
 (defn- delete-page!
   [page-name]
@@ -83,13 +84,14 @@
                          (page-handler/unfavorite-page! page-original-name)
                          (page-handler/favorite-page! page-original-name)))}}
 
-          {:title (t :page/presentation-mode)
-           :options {:on-click (fn []
-                                 (state/sidebar-add-block!
-                                  repo
-                                  (:db/id page)
-                                  :page-presentation
-                                  {:page page}))}}
+          (when-not (mobile-util/is-native-platform?)
+           {:title (t :page/presentation-mode)
+            :options {:on-click (fn []
+                                  (state/sidebar-add-block!
+                                   repo
+                                   (:db/id page)
+                                   :page-presentation
+                                   {:page page}))}})
 
           ;; TODO: In the future, we'd like to extract file-related actions
           ;; (such as open-in-finder & open-with-default-app) into a sub-menu of

+ 5 - 3
src/main/frontend/components/reference.cljs

@@ -109,7 +109,7 @@
                                                 :editor-box editor/box}
                                                {})]
                 (content/content page-name {:hiccup ref-hiccup}))]
-             {}))
+             {:title-trigger? true}))
 
           (when (seq refed-blocks-ids)
             (ui/foldable
@@ -164,7 +164,8 @@
                     (content/content page-name
                                      {:hiccup ref-hiccup}))]))
 
-             {:default-collapsed? default-collapsed?}))]]))))
+             {:default-collapsed? default-collapsed?
+              :title-trigger? true}))]]))))
 
 (rum/defcs unlinked-references-aux
   < rum/reactive db-mixins/query
@@ -205,4 +206,5 @@
                                                   "s"))
               "Unlinked References")]
            (fn [] (unlinked-references-aux page-name n-ref))
-           {:default-collapsed? true})]]))))
+           {:default-collapsed? true
+            :title-trigger? true})]]))))

+ 16 - 7
src/main/frontend/components/repo.cljs

@@ -22,7 +22,8 @@
             [frontend.version :as version]
             [reitit.frontend.easy :as rfe]
             [rum.core :as rum]
-            [frontend.mobile.util :as mobile-util]))
+            [frontend.mobile.util :as mobile-util]
+            [frontend.text :as text]))
 
 (rum/defc add-repo
   [args]
@@ -62,10 +63,11 @@
               :intent "logseq"))]
           (for [{:keys [id url] :as repo} repos]
             (let [local? (config/local-db? url)]
-              [:div.flex.justify-between.mb-1 {:key id}
+              [:div.flex.justify-between.mb-4 {:key id}
                (if local?
                  [:a
-                  (config/get-local-dir url)]
+                  (some-> (config/get-local-dir url)
+                          (text/get-graph-name-from-path))]
                  [:a {:target "_blank"
                       :href url}
                   (db/get-repo-path url)])
@@ -75,9 +77,10 @@
                                :on-click (fn []
                                            (state/set-modal! (encryption/encryption-dialog url)))}
                    "🔐"])
-                [:a.text-gray-400.ml-4 {:title "No worries, unlink this graph will clear its cache only, it does not remove your files on the disk."
-                                        :on-click (fn []
-                                                    (repo-handler/remove-repo! repo))}
+                [:a.text-gray-400.ml-4.font-medium.text-sm
+                 {:title "No worries, unlink this graph will clear its cache only, it does not remove your files on the disk."
+                  :on-click (fn []
+                              (repo-handler/remove-repo! repo))}
                  "Unlink"]]]))]]
         (widgets/add-graph)))))
 
@@ -178,8 +181,14 @@
   (when-let [current-repo (state/sub :git/current-repo)]
     (rum/with-context [[t] i18n/*tongue-context*]
       (let [get-repo-name (fn [repo]
-                            (if (config/local-db? repo)
+                            (cond
+                              (mobile-util/is-native-platform?)
+                              (text/get-graph-name-from-path repo)
+
+                              (config/local-db? repo)
                               (config/get-local-dir repo)
+
+                              :else
                               (db/get-repo-path repo)))
             repos (state/sub [:me :repos])
             repos (remove (fn [r] (= config/local-repo (:url r))) repos)

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

@@ -20,7 +20,8 @@
             [frontend.context.i18n :as i18n]
             [frontend.date :as date]
             [reitit.frontend.easy :as rfe]
-            [frontend.modules.shortcut.core :as shortcut]))
+            [frontend.modules.shortcut.core :as shortcut]
+            [frontend.mobile.util :as mobile-util]))
 
 (defn- partition-between
   "Split `coll` at positions where `pred?` is true."
@@ -362,6 +363,8 @@
     (rum/with-context [[t] i18n/*tongue-context*]
       (let [input (::input state)]
         [:div.cp__palette.cp__palette-main
+         (when (mobile-util/is-native-platform?)
+          {:style {:min-height "50vh"}})
 
          [:div.input-wrap
           [:input.cp__palette-input.w-full

+ 30 - 18
src/main/frontend/components/settings.cljs

@@ -22,7 +22,8 @@
             [frontend.version :refer [version]]
             [goog.object :as gobj]
             [reitit.frontend.easy :as rfe]
-            [rum.core :as rum]))
+            [rum.core :as rum]
+            [frontend.mobile.util :as mobile-util]))
 
 (rum/defcs set-email < (rum/local "" ::email)
   [state]
@@ -179,7 +180,9 @@
                              :class    "text-sm p-1"
                              :href     href
                              :on-click on-click))]
-    [:div.text-sm desc]]])
+    (when-not (or (util/mobile?)
+                  (mobile-util/is-native-platform?))
+      [:div.text-sm desc])]])
 
 
 (defn edit-config-edn []
@@ -201,12 +204,18 @@
       :-for         "customize_css"})))
 
 (defn show-brackets-row [t show-brackets?]
-  (toggle "show_brackets"
-          (t :settings-page/show-brackets)
-          show-brackets?
-          config-handler/toggle-ui-show-brackets!
-          [:span {:text-align "right"}
-           (ui/render-keyboard-shortcut (shortcut-helper/gen-shortcut-seq :ui/toggle-brackets))]))
+  [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start
+   [:label.block.text-sm.font-medium.leading-5.opacity-70
+    {:for "show_brackets"}
+    (t :settings-page/show-brackets)]
+   [:div
+    [:div.rounded-md.sm:max-w-xs
+     (ui/toggle show-brackets?
+                config-handler/toggle-ui-show-brackets!
+                true)]]
+   (when (not (or (util/mobile?) (mobile-util/is-native-platform?)))
+     [:div {:style {:text-align "right"}}
+      (ui/render-keyboard-shortcut (shortcut-helper/gen-shortcut-seq :ui/toggle-brackets))])])
 
 (rum/defcs switch-spell-check-row < rum/reactive
   [state t]
@@ -566,16 +575,18 @@
           (for [[label text icon]
                 [[:general (t :settings-page/tab-general) (ui/icon "adjustments" {:style {:font-size 20}})]
                  [:editor (t :settings-page/tab-editor) (ui/icon "writing" {:style {:font-size 20}})]
-                 [:git (t :settings-page/tab-version-control) (ui/icon "history" {:style {:font-size 20}})]
+                 (when-not (mobile-util/is-native-platform?)
+                   [:git (t :settings-page/tab-version-control) (ui/icon "history" {:style {:font-size 20}})])
                  [:advanced (t :settings-page/tab-advanced) (ui/icon "bulb" {:style {:font-size 20}})]]]
 
-            [:li
-             {:class    (util/classnames [{:active (= label @*active)}])
-              :on-click #(reset! *active label)}
+            (when label
+              [:li
+               {:class    (util/classnames [{:active (= label @*active)}])
+                :on-click #(reset! *active label)}
 
-             [:a.flex.items-center
-              icon
-              [:strong text]]])]]
+               [:a.flex.items-center
+                icon
+                [:strong text]]]))]]
 
         [:article
 
@@ -600,8 +611,9 @@
             (show-brackets-row t show-brackets?)
             (when (util/electron?) (switch-spell-check-row t))
             (outdenting-row t logical-outdenting?)
-            (shortcut-tooltip-row t enable-shortcut-tooltip?)
-            (tooltip-row t enable-tooltip?)
+            (when-not (or (util/mobile?) (mobile-util/is-native-platform?))
+              (shortcut-tooltip-row t enable-shortcut-tooltip?)
+              (tooltip-row t enable-tooltip?))
             (timetracking-row t enable-timetracking?)
             (journal-row t enable-journals?)
             (encryption-row t enable-encryption?)
@@ -632,7 +644,7 @@
            [:div.panel-wrap.is-advanced
             (when (and util/mac? (util/electron?)) (app-auto-update-row t))
             (usage-diagnostics-row t instrument-disabled?)
-            (developer-mode-row t developer-mode?)
+            (if-not (mobile-util/is-native-platform?) (developer-mode-row t developer-mode?))
             (clear-cache-row t)
 
             (ui/admonition

+ 36 - 18
src/main/frontend/components/sidebar.cljs

@@ -30,7 +30,10 @@
             [goog.object :as gobj]
             [rum.core :as rum]
             [frontend.extensions.srs :as srs]
-            [frontend.extensions.pdf.assets :as pdf-assets]))
+            [frontend.extensions.pdf.assets :as pdf-assets]
+            [frontend.components.widgets :as widgets]
+            [frontend.mobile.util :as mobile-util]
+            [frontend.handler.mobile.swipe :as swipe]))
 
 (defn nav-item
   [title href svg-d active? close-modal-fn]
@@ -280,29 +283,34 @@
           [:nav.px-2.space-y-1 {:aria-label "Sidebar"
                                 :class "new-page"}
            (when-not config/publishing?
-             [:a.item.group.flex.items-center.px-2.py-2.text-sm.font-medium.rounded-md {:on-click #(state/pub-event! [:go/search])}
+             [:a.item.group.flex.items-center.px-2.py-2.text-sm.font-medium.rounded-md
+              {:on-click (fn []
+                           (state/toggle-left-sidebar!)
+                           (state/pub-event! [:go/search]))}
               (ui/icon "circle-plus mr-3" {:style {:font-size 20}})
               [:span.flex-1 (t :right-side-bar/new-page)]])]]]))))
 
 (rum/defc sidebar-mobile-sidebar < rum/reactive
-  [{:keys [open? left-sidebar-open? close-fn route-match]}]
+  [{:keys [left-sidebar-open? close-fn route-match]}]
   [:div.md:hidden.ls-mobile-left-sidebar
    {:class (if left-sidebar-open? "is-left-sidebar-open" "")}
    [:div.fixed.inset-0.z-30.bg-gray-600.pointer-events-none.ease-linear.duration-300
-    {:class (if @open?
+    {:class (if left-sidebar-open?
               "opacity-75 pointer-events-auto"
               "opacity-0 pointer-events-none")
      :on-click close-fn}]
-   [:div#left-bar.fixed.inset-y-0.left-0.flex.flex-col.z-40.w-full.transform.ease-in-out.duration-300
-    {:class (if @open?
+   [:div#left-bar.fixed.inset-y-0.left-0.flex.flex-col.z-40.transform.ease-in-out.duration-300
+    {:class (if left-sidebar-open?
               "translate-x-0"
               "-translate-x-full")
-     :style {:max-width "86vw"}}
-    (when @open?
-      [:div.absolute.top-0.right-0.p-1.z-10
-       [:a.button
-        {:on-click close-fn}
-        (ui/icon "x" {:style {:font-size 24}})]])
+     :style {:padding-top (ui/main-content-top-padding)}}
+    (when left-sidebar-open?
+      [:div.cp__header#head
+       [:div.l.flex
+        (header/left-menu-button
+         {:on-click (fn []
+                      (state/set-left-sidebar-open!
+                       (not (:ui/left-sidebar-open? @state/state))))})]])
     [:div.flex-1.h-0.overflow-y-auto
      (sidebar-nav route-match close-fn)]]])
 
@@ -323,7 +331,8 @@
         mobile? (util/mobile?)]
     (rum/with-context [[t] i18n/*tongue-context*]
       [:div#main-content.cp__sidebar-main-layout.flex-1.flex
-       {:class (util/classnames [{:is-left-sidebar-open left-sidebar-open?}])}
+       {:class (util/classnames [{:is-left-sidebar-open left-sidebar-open?}])
+        :style {:padding-top (ui/main-content-top-padding)}}
 
        ;; desktop left sidebar layout
        (when-not mobile?
@@ -338,7 +347,9 @@
          {:data-is-global-graph-pages global-graph-pages?
           :data-is-full-width         (or global-graph-pages?
                                           (contains? #{:all-files :all-pages :my-publishing} route-name))}
-         (widgets/demo-graph-alert)
+
+         (when-not (mobile-util/is-native-platform?)
+           (widgets/demo-graph-alert))
 
          (widgets/github-integration-soon-deprecated-alert)
 
@@ -429,7 +440,10 @@
          (seq latest-journals)
          (journal/journals latest-journals)
 
-         (and logged? (empty? (:repos me)))
+         (or
+          (and (mobile-util/is-native-platform?)
+               (nil? (state/get-current-repo)))
+          (and logged? (empty? (:repos me))))
          (widgets/add-graph)
 
                          ;; FIXME: why will this happen?
@@ -504,6 +518,9 @@
                         (if (state/modal-opened?)
                           (state/close-modal!)
                           (hide-context-menu-and-clear-selection)))))))
+  {:did-mount (fn [state]
+                (swipe/setup-listeners!)
+                state)}
   [state route-match main-content]
   (let [{:keys [open? close-fn open-fn]} state
         close-fn (fn []
@@ -525,6 +542,7 @@
         indexeddb-support? (state/sub :indexeddb/support?)
         page? (= :page route-name)
         home? (= :home route-name)
+        edit? (:editor/editing? @state/state)
         default-home (get-default-home-if-valid)]
     (rum/with-context [[t] i18n/*tongue-context*]
       (theme/container
@@ -532,6 +550,7 @@
         :theme         theme
         :route         route-match
         :current-repo  current-repo
+        :edit?         edit?
         :nfs-granted?  granted?
         :db-restoring? db-restoring?
         :sidebar-open? sidebar-open?
@@ -545,12 +564,11 @@
         {:class (util/classnames [{:ls-left-sidebar-open left-sidebar-open?}])}
 
         (sidebar-mobile-sidebar
-         {:open?       open?
-          :left-sidebar-open? left-sidebar-open?
+         {:left-sidebar-open? left-sidebar-open?
           :close-fn    close-fn
           :route-match route-match})
 
-        [:div.#app-container.h-screen.flex
+        [:div.#app-container.h-screen.flex {:style {:padding-top (ui/main-content-top-padding)}}
          [:div.flex-1.h-full.flex.flex-col#left-container.relative
           {:class (if (state/sub :ui/sidebar-open?) "overflow-hidden" "w-full")}
           (header/header {:open-fn        open-fn

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

@@ -1,3 +1,10 @@
+@supports(padding: max(0px)) {
+    .post {
+        padding-left: max(12px, env(safe-area-inset-left));
+        padding-right: max(12px, env(safe-area-inset-right));
+    }
+}
+
 #app-container {
   background-color: var(--ls-primary-background-color, #fff);
   position: relative;
@@ -33,6 +40,12 @@
   }
 }
 
+html.is-mobile {
+  #main-content.is-left-sidebar-open {
+    padding-left: 0;
+  }
+}
+
 #left-sidebar {
   width: 240px;
   height: 100%;
@@ -51,6 +64,8 @@
 
 #left-bar {
   background-color: var(--ls-primary-background-color);
+  width:             70vw;
+  max-width:         300px;
 
   > .head-wrap {
     background-color: var(--ls-search-background-color);
@@ -85,7 +100,10 @@
   overflow: auto;
 
   > .wrap {
-    padding-top: 60px;
+      padding-top: 24px;
+      @screen md {
+          padding-top: 60px;
+      }
   }
 
   .dropdown-wrapper {

+ 42 - 36
src/main/frontend/components/theme.cljs

@@ -6,61 +6,67 @@
             [frontend.ui :as ui]
             [frontend.util :as util]
             [frontend.rum :refer [use-mounted]]
-            [rum.core :as rum]))
+            [rum.core :as rum]
+            [frontend.mobile.util :as mobile-util]))
 
 (rum/defc container
   [{:keys [t route theme on-click current-repo nfs-granted? db-restoring?
-           sidebar-open? system-theme? sidebar-blocks-len] :as props} child]
+           sidebar-open? system-theme? sidebar-blocks-len edit?] :as props} child]
   (let [mounted-fn (use-mounted)
         [restored-sidebar? set-restored-sidebar?] (rum/use-state false)]
 
     (rum/use-effect!
-      #(let [doc js/document.documentElement
-             cls (.-classList doc)]
-         (.setAttribute doc "data-theme" (if (= theme "white") "light" theme))
-         (if (= theme "dark")                               ;; for tailwind dark mode
-           (.add cls "dark")
-           (.remove cls "dark"))
-         (plugin-handler/hook-plugin-app :theme-mode-changed {:mode (if (= theme "white") "light" theme)} nil))
-      [theme])
+     #(let [doc js/document.documentElement
+            cls (.-classList doc)]
+        (.setAttribute doc "data-theme" (if (= theme "white") "light" theme))
+        (if (= theme "dark") ;; for tailwind dark mode
+          (.add cls "dark")
+          (.remove cls "dark"))
+        (plugin-handler/hook-plugin-app :theme-mode-changed {:mode (if (= theme "white") "light" theme)} nil))
+     [theme])
 
     (rum/use-effect!
-      #(when (and restored-sidebar?
-                  (mounted-fn))
-         (plugin-handler/hook-plugin-app :sidebar-visible-changed {:visible sidebar-open?})
-         (ui-handler/persist-right-sidebar-state!))
-      [sidebar-open? restored-sidebar? sidebar-blocks-len])
+     #(when (and restored-sidebar?
+                 (mounted-fn))
+        (plugin-handler/hook-plugin-app :sidebar-visible-changed {:visible sidebar-open?})
+        (ui-handler/persist-right-sidebar-state!))
+     [sidebar-open? restored-sidebar? sidebar-blocks-len])
 
     (rum/use-effect!
-      #(if lsp-enabled?
-         (plugin-handler/setup-install-listener! t))
-      [t])
+     #(if lsp-enabled?
+        (plugin-handler/setup-install-listener! t))
+     [t])
 
     (rum/use-effect!
-      (fn []
-        (ui-handler/add-style-if-exists!)
-        (pdf/reset-current-pdf!)
-        (plugin-handler/hook-plugin-app :current-graph-changed {}))
-      [current-repo])
+     (fn []
+       (ui-handler/add-style-if-exists!)
+       (pdf/reset-current-pdf!)
+       (plugin-handler/hook-plugin-app :current-graph-changed {}))
+     [current-repo])
 
     (rum/use-effect!
-      #(let [db-restored? (false? db-restoring?)]
-         (if db-restoring?
-           (util/set-title! "Loading")
-           (when (or nfs-granted? db-restored?)
-             (route-handler/update-page-title! route))))
-      [nfs-granted? db-restoring? route])
+     #(let [db-restored? (false? db-restoring?)]
+        (if db-restoring?
+          (util/set-title! "Loading")
+          (when (or nfs-granted? db-restored?)
+            (route-handler/update-page-title! route))))
+     [nfs-granted? db-restoring? route])
 
     (rum/use-effect!
-      #(when-not db-restoring?
-         (ui-handler/restore-right-sidebar-state!)
-         (set-restored-sidebar? true))
-      [db-restoring?])
+     #(when-not db-restoring?
+        (ui-handler/restore-right-sidebar-state!)
+        (set-restored-sidebar? true))
+     [db-restoring?])
 
     (rum/use-effect!
-      #(when system-theme?
-         (ui/setup-system-theme-effect!))
-      [system-theme?])
+     #(when system-theme?
+        (ui/setup-system-theme-effect!))
+     [system-theme?])
+
+    (rum/use-effect!
+     #(when (mobile-util/native-ios?)
+        (ui/setup-patch-ios-fixed-bottom-position!))
+     [edit?])
 
     [:div
      {:class    (str theme "-theme")

+ 49 - 21
src/main/frontend/components/widgets.cljs

@@ -13,7 +13,8 @@
             [frontend.ui :as ui]
             [frontend.util :as util]
             [rum.core :as rum]
-            [frontend.config :as config]))
+            [frontend.config :as config]
+            [frontend.mobile.util :as mobile-util]))
 
 (rum/defc choose-preferred-format
   []
@@ -88,27 +89,54 @@
     [:div.flex.flex-col
      [:h1.title (t :on-boarding/add-graph)]
      (let [nfs-supported? (or (nfs/supported?) (mobile/is-native-platform?))]
-       [:div.cp__widgets-open-local-directory
-        [:div.select-file-wrap.cursor
-         (when nfs-supported?
-           {:on-click #(page-handler/ls-dir-files! shortcut/refresh!)})
+       (if (mobile-util/is-native-platform?)
+         [:div.text-sm
+          (ui/button "Open a local directory"
+            :on-click #(page-handler/ls-dir-files! shortcut/refresh!))
+          [:hr]
+          [:ol
+           [:li
+            [:div.font-bold.mb-2 "How to sync my notes?"]
+            (if (mobile-util/native-android?)
+              [:div
+               [:p "We're developing our built-in paid Logseq Sync, but you can use any third-party sync service to keep your notes sync with other devices."]
+               [:p "If you prefer to use Dropbox to sync your notes, you can use "
+                [:a {:href "https://play.google.com/store/apps/details?id=com.ttxapps.dropsync"
+                     :target "_blank"}
+                 "Dropsync"]
+                ". Or you can use "
+                [:a {:href "https://play.google.com/store/apps/details?id=dk.tacit.android.foldersync.lite"
+                     :target "_blank"}
+                 "FolderSync"]
+                "."]]
+              [:div
+               [:p "iCloud TBD"]])]
 
-         [:div
-          [:h1.title (t :on-boarding/open-local-dir)]
-          [:p (t :on-boarding/new-graph-desc-1)]
-          [:p (t :on-boarding/new-graph-desc-2)]
-          [:ul
-           [:li (t :on-boarding/new-graph-desc-3)]
-           [:li (t :on-boarding/new-graph-desc-4)]
-           [:li (t :on-boarding/new-graph-desc-5)]]
-          (when-not nfs-supported?
-            (ui/admonition :warning
-                           [:p "It seems that your browser doesn't support the "
-
-                            [:a {:href   "https://web.dev/file-system-access/"
-                                 :target "_blank"}
-                             "new native filesystem API"]
-                            [:span ", please use any Chromium 86+ based browser like Chrome, Vivaldi, Edge, etc. Notice that the API doesn't support mobile browsers at the moment."]]))]]])]))
+           [:li.mt-8
+            [:div.font-bold.mb-2 "I need some help"]
+            [:p "👋 Join our discord group to chat with the makers and our helpful community members."]
+            (ui/button "Join the community"
+              :href "https://discord.gg/KpN4eHY"
+              :target "_blank")]]]
+         [:div.cp__widgets-open-local-directory
+          [:div.select-file-wrap.cursor
+           (when nfs-supported?
+             {:on-click #(page-handler/ls-dir-files! shortcut/refresh!)})
+           [:div
+            [:h1.title (t :on-boarding/open-local-dir)]
+            [:p (t :on-boarding/new-graph-desc-1)]
+            [:p (t :on-boarding/new-graph-desc-2)]
+            [:ul
+             [:li (t :on-boarding/new-graph-desc-3)]
+             [:li (t :on-boarding/new-graph-desc-4)]
+             [:li (t :on-boarding/new-graph-desc-5)]]
+            (when-not nfs-supported?
+              (ui/admonition :warning
+                             [:p "It seems that your browser doesn't support the "
+                              [:a {:href   "https://web.dev/file-system-access/"
+                                   :target "_blank"}
+                               "new native filesystem API"]
+                              [:span ", please use any Chromium 86+ based browser like Chrome, Vivaldi, Edge, etc. Notice that the API doesn't support mobile browsers at the moment."]]))]]]))]))
 
 (rum/defcs add-graph <
   [state & {:keys [graph-types]

+ 42 - 41
src/main/frontend/extensions/zotero.cljs

@@ -83,7 +83,7 @@
       (notification/show! "Please setup Zotero API key and user/group id first!" :warn false))
 
     [:div#zotero-search.zotero-search.p-4
-     {:style {:width 600}}
+     {:style {:width 720}}
 
      [:div.flex.items-center.mb-2
       [[:input.p-2.border.mr-2.flex-1.focus:outline-none
@@ -123,7 +123,7 @@
   [state]
   [:div
    [:div.row
-    [:label.title
+    [:label.title.w-72
      {:for "zotero_type"}
      "Zotero user or group?"]
     [:div.mt-1.sm:mt-0.sm:col-span-2
@@ -139,7 +139,7 @@
          [:option {:key type :value type} (str/capitalize type)])]]]]
 
    [:div.row
-    [:label.title
+    [:label.title.w-72
      {:for "zotero_type_id"}
      "User or Group ID"]
     [:div.mt-1.sm:mt-0.sm:col-span-2
@@ -166,7 +166,7 @@
   []
   [:div
    [:div.row
-    [:label.title
+    [:label.title.w-72
      {:for "zotero_overwrite_mode"}
      "Overwrite existing item page?"]
     [:div
@@ -185,7 +185,7 @@
   []
   [:div
    [:div.row
-    [:label.title
+    [:label.title.w-72
      {:for "zotero_include_attachment_links"}
      "Include attachment links?"]
     [:div
@@ -195,7 +195,7 @@
                  true)]]]
    (when (setting/setting :include-attachments?)
      [:div.row
-      [:label.title
+      [:label.title.w-72
        {:for "zotero_attachments_block_text"}
        "Attachment under block of:"]
       [:div.mt-1.sm:mt-0.sm:col-span-2
@@ -205,7 +205,7 @@
           :on-blur       (fn [e] (setting/set-setting! :attachments-block-text (util/evalue e)))}]]]])
    (when (setting/setting :include-attachments?)
      [:div.row
-      [:label.title
+      [:label.title.w-72
        {:for "zotero_linked_attachment_base_directory"}
        "Zotero linked attachment base directory"
        [:a.ml-2
@@ -224,10 +224,10 @@
   rum/reactive
   []
   [:div.row
-   [:label.title
+   [:label.title.w-72
     {:for   "zotero_prefer_citekey"
      :title "Make sure to install Better BibTeX and pin your item first"}
-    "Always prefer citekey as your page title?"]
+    "Use citekey as your page title?"]
    [:div
     [:div.rounded-md.sm:max-w-xs
      (ui/toggle (setting/setting :prefer-citekey?)
@@ -236,7 +236,7 @@
 
 (rum/defc api-key-setting []
   [:div.row
-   [:label.title
+   [:label.title.w-72
     {:for "zotero_api_key"}
     "Zotero API key"]
    [:div.mt-1.sm:mt-0.sm:col-span-2
@@ -251,7 +251,7 @@
   []
   [:div
    [:div.row
-    [:label.title
+    [:label.title.w-72
      {:for "zotero_include_notes"}
      "Include notes?"]
     [:div
@@ -329,7 +329,7 @@
 
      [:div.mt-5.sm:mt-4.sm:flex.sm:flex-row-reverse
       [:span.flex.w-full.rounded-md.shadow-sm.sm:ml-3.sm:w-auto
-       [:button.inline-flex.justify-center.w-full.rounded-md.border.border-transparent.px-4.py-2.bg-indigo-600.text-base.leading-6.font-medium.text-white.shadow-sm.hover:bg-indigo-500.focus:outline-none.focus:border-indigo-700.focus:shadow-outline-indigo.transition.ease-in-out.duration-150.sm:text-sm.sm:leading-5
+       [:button.inline-flex.justify-center.w-full.rounded-md.border.border-transparent.px-4.py-2.bg-indigo-720.text-base.leading-6.font-medium.text-white.shadow-sm.hover:bg-indigo-500.focus:outline-none.focus:border-indigo-700.focus:shadow-outline-indigo.transition.ease-in-out.duration-150.sm:text-sm.sm:leading-5
         {:type "button"
          :class "ui__modal-enter"
          :on-click (fn []
@@ -349,37 +349,38 @@
 (rum/defc zotero-profile-selector <
   rum/reactive
   [profile*]
-  [:div.zotero-profile-selector.my-4
+  [:div.row
    [:label.mr-32 {:for "profile-select"} "Choose a profile:"]
-   [:select
-    {:value @profile*
-     :on-change
-     (fn [e]
-       (when-let [profile (util/evalue e)]
-         (p/let [_ (setting/set-profile profile)]
-           (reset! profile* profile))))}
-    (map-indexed (fn [i x] [:option
-                            {:key      i
-                             :value    x}
-                            x]) (setting/all-profiles))]
-   (ui/button
-    "New profile"
-    :small? true
-    :class "ml-4"
-    :on-click
-    (fn []
-      (state/set-modal!
+   [:span.justify-evenly
+    [:select
+     {:value @profile*
+      :on-change
+      (fn [e]
+        (when-let [profile (util/evalue e)]
+          (p/let [_ (setting/set-profile profile)]
+            (reset! profile* profile))))}
+     (map-indexed (fn [i x] [:option
+                             {:key      i
+                              :value    x}
+                             x]) (setting/all-profiles))]
+    (ui/button
+     "New profile"
+     :small? true
+     :class "ml-4"
+     :on-click
+     (fn []
+       (state/set-modal!
         (fn [close-fn]
           (profile-name-dialog-inner profile* close-fn)))))
-   (ui/button
-    "Delete profile!"
-    :small? true
-    :background "red"
-    :class "ml-4"
-    :on-click
-    (fn []
-      (p/let [_ (setting/remove-profile @profile*)]
-        (reset! profile* (setting/profile)))))])
+    (ui/button
+     "Delete profile!"
+     :small? true
+     :background "red"
+     :class "ml-4"
+     :on-click
+     (fn []
+       (p/let [_ (setting/remove-profile @profile*)]
+         (reset! profile* (setting/profile)))))]])
 
 (rum/defcs add-all-items <
   (rum/local nil ::progress)
@@ -389,7 +390,7 @@
   [state]
   [:div
    [:div.row
-    [:label.title
+    [:label.title.w-72
      {:for "zotero_import_all"}
      "Add all zotero items"]
     [:div.mt-1.sm:mt-0.sm:col-span-2

+ 8 - 2
src/main/frontend/extensions/zotero.css

@@ -1,6 +1,6 @@
 .zotero-settings {
   .row {
-    @apply sm:grid sm:grid-cols-3 sm:gap-4 mb-4 flex items-center;
+    @apply sm:grid sm:grid-cols-3 sm:gap-4 mb-4 flex flex-wrap items-center;
   }
 
   .title {
@@ -8,7 +8,13 @@
   }
 
   .form-select {
-    @apply py-2;
+      @apply py-2;
+      min-width: 20rem;
+  }
+
+  .form-input {
+      @apply py-2;
+      min-width: 20rem;
   }
 
   svg {

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

@@ -159,7 +159,7 @@
       (fn [_stat])
       (fn [error]
         (mkdir! dir))))
-   (p/catch (fn [_error] nil))))
+   (p/catch (fn [error] (js/console.error error)))))
 
 (defn create-if-not-exists
   ([repo dir path]

+ 70 - 57
src/main/frontend/fs/capacitor_fs.cljs

@@ -20,62 +20,73 @@
       (p/do!
        (.requestPermissions Filesystem)))))
 
+(defn- clean-uri
+  [uri]
+  (when (string? uri)
+    (-> uri
+        (string/replace "file://" "")
+        (futil/url-decode))))
+
 (defn readdir
   "readdir recursively"
   [path]
-  (p/loop [result []
-           dirs [path]]
-    (if (empty? dirs)
-      result
-      (p/let [d (first dirs)
-              files (.readdir Filesystem (clj->js {:path d}))
-              files (-> files
-                        js->clj
-                        (get "files" []))
-              files (->> files
-                         (remove (fn [file] (string/starts-with? file "."))))
-              files (->> files
-                         (map (fn [file] (futil/node-path.join d file))))
-              files-with-stats (p/all
-                                (mapv
-                                 (fn [file]
-                                   (p/chain
-                                    (.stat Filesystem (clj->js {:path file}))
-                                    #(js->clj % :keywordize-keys true)))
-                                 files))
-              files-dir (->> files-with-stats
-                             (filterv
-                              (fn [{:keys [type]}]
-                                (contains? #{"directory" "NSFileTypeDirectory"} type)))
-                             (mapv :uri))
+  (p/let [result (p/loop [result []
+                          dirs [path]]
+                   (if (empty? dirs)
+                     result
+                     (p/let [d (first dirs)
+                             files (.readdir Filesystem (clj->js {:path d}))
+                             files (-> files
+                                       js->clj
+                                       (get "files" []))
+                             files (->> files
+                                        (remove (fn [file]
+                                                  (or (string/starts-with? file ".")
+                                                      (= file "bak")))))
+                             files (->> files
+                                        (map (fn [file]
+                                               (futil/node-path.join d file))))
+                             files-with-stats (p/all
+                                               (mapv
+                                                (fn [file]
+                                                  (p/chain
+                                                   (.stat Filesystem (clj->js {:path file}))
+                                                   #(js->clj % :keywordize-keys true)))
+                                                files))
+                             files-dir (->> files-with-stats
+                                            (filterv
+                                             (fn [{:keys [type]}]
+                                               (contains? #{"directory" "NSFileTypeDirectory"} type)))
+                                            (mapv :uri))
 
-              files-result
-              (p/all
-               (->> files-with-stats
-                    (filter
-                     (fn [{:keys [type]}]
-                       (contains? #{"file" "NSFileTypeRegular"} type)))
-                    (filter
-                     (fn [{:keys [uri]}]
-                       (some #(string/ends-with? uri %)
-                             [".md" ".markdown" ".org" ".edn" ".css"])))
-                    (mapv
-                     (fn [{:keys [uri] :as file-result}]
-                       (p/chain
-                        (.readFile Filesystem
-                                   (clj->js
-                                    {:path uri
-                                     :encoding (.-UTF8 Encoding)}))
-                        #(js->clj % :keywordize-keys true)
-                        :data
-                        #(assoc file-result :content %))))))]
-        (p/recur (concat result files-result)
-                 (concat (rest dirs) files-dir))))))
+                             files-result
+                             (p/all
+                              (->> files-with-stats
+                                   (filter
+                                    (fn [{:keys [type]}]
+                                      (contains? #{"file" "NSFileTypeRegular"} type)))
+                                   (filter
+                                    (fn [{:keys [uri]}]
+                                      (some #(string/ends-with? uri %)
+                                            [".md" ".markdown" ".org" ".edn" ".css"])))
+                                   (mapv
+                                    (fn [{:keys [uri] :as file-result}]
+                                      (p/chain
+                                       (.readFile Filesystem
+                                                  (clj->js
+                                                   {:path uri
+                                                    :encoding (.-UTF8 Encoding)}))
+                                       #(js->clj % :keywordize-keys true)
+                                       :data
+                                       #(assoc file-result :content %))))))]
+                       (p/recur (concat result files-result)
+                                (concat (rest dirs) files-dir)))))
+          result (js->clj result :keywordize-keys true)]
+    (map (fn [result] (update result :uri clean-uri)) result)))
 
 (defrecord Capacitorfs []
   protocol/Fs
   (mkdir! [this dir]
-    (prn "mkdir: " dir)
     (p/let [result (.mkdir Filesystem
                            (clj->js
                             {:path dir
@@ -100,12 +111,15 @@
     nil)
   (read-file [this dir path _options]
     (let [path (str dir path)]
-      (p/let [content (.readFile Filesystem
-                                 (clj->js
-                                  {:path path
-                                   :directory (.-ExternalStorage Directory)
-                                   :encoding (.-UTF8 Encoding)}))]
-        content)))
+      (->
+       (p/let [content (.readFile Filesystem
+                                  (clj->js
+                                   {:path path
+                                    ;; :directory (.-ExternalStorage Directory)
+                                    :encoding (.-UTF8 Encoding)}))]
+         content)
+       (p/catch (fn [error]
+                  (js/alert error))))))
   (write-file! [this repo dir path content {:keys [ok-handler error-handler] :as opts}]
     (let [path (cond
                  (= (util/platform) "ios")
@@ -145,9 +159,8 @@
                   (.pickFolder util/folder-picker)
                   #(js->clj % :keywordize-keys true)
                   :path)
-            files (readdir path)]
-      (js/console.log path)
-      (js/console.log files)
+            files (readdir path)
+            files (js->clj files :keywordize-keys true)]
       (into [] (concat [{:path path}] files))))
   (get-files [this path-or-handle _ok-handler]
     (readdir path-or-handle))

+ 8 - 5
src/main/frontend/handler.cljs

@@ -18,7 +18,8 @@
             [frontend.handler.repo :as repo-handler]
             [frontend.handler.ui :as ui-handler]
             [frontend.extensions.srs :as srs]
-            [frontend.mobile.util :as mobile]
+            [frontend.mobile.core :as mobile]
+            [frontend.mobile.util :as mobile-util]
             [frontend.idb :as idb]
             [frontend.modules.instrumentation.core :as instrument]
             [frontend.modules.shortcut.core :as shortcut]
@@ -110,7 +111,8 @@
                               (and (not logged?)
                                    (not (seq (db/get-files config/local-repo)))
                                    ;; Not native local directory
-                                   (not (some config/local-db? (map :url repos))))
+                                   (not (some config/local-db? (map :url repos)))
+                                   (not (mobile-util/is-native-platform?)))
                               (repo-handler/setup-local-repo-if-not-exists!)
 
                               :else
@@ -227,8 +229,8 @@
     (p/let [repos (get-repos)]
       (state/set-repos! repos)
       (restore-and-setup! me repos logged? db-schema)
-      (when (mobile/is-native-platform?)
-        (p/do! (mobile/hide-splash))))
+      (when (mobile-util/is-native-platform?)
+        (p/do! (mobile-util/hide-splash))))
 
     (reset! db/*sync-search-indice-f search/sync-search-indice!)
     (db/run-batch-txs!)
@@ -237,7 +239,8 @@
     (when config/dev?
       (enable-datalog-console))
     (when (util/electron?)
-      (el/listen!))))
+      (el/listen!))
+    (mobile/init!)))
 
 (defn stop! []
   (prn "stop!"))

+ 6 - 1
src/main/frontend/handler/editor/lifecycle.cljs

@@ -2,6 +2,8 @@
   (:require [frontend.handler.editor :as editor-handler :refer [get-state]]
             [frontend.handler.editor.keyboards :as keyboards-handler]
             [frontend.state :as state]
+            [frontend.util :as util]
+            [frontend.mobile.util :as mobile-util]
             [goog.dom :as gdom]))
 
 (defn did-mount!
@@ -18,7 +20,10 @@
     (js/setTimeout #(keyboards-handler/esc-save! state) 100)
 
     (when-let [element (gdom/getElement id)]
-      (.focus element)))
+      (.focus element)
+      (when (and (util/mobile?)
+                 (not (mobile-util/native-ios?)))
+        (js/setTimeout #(util/make-el-into-viewport element 60) 64))))
   state)
 
 (defn did-remount!

+ 6 - 2
src/main/frontend/handler/extract.cljs

@@ -16,7 +16,8 @@
             [frontend.util :as util]
             [frontend.util.property :as property]
             [lambdaisland.glogi :as log]
-            [promesa.core :as p]))
+            [promesa.core :as p]
+            [frontend.mobile.util :as mobile]))
 
 (defn- extract-page-list
   [content]
@@ -150,7 +151,10 @@
      (p/resolved [])
      (p/let [format (format/get-format file)
              _ (println "Parsing start : " file)
-             ast (mldoc/->edn-async content (mldoc/default-config format))]
+             parse-f (if (mobile/is-native-platform?)
+                       mldoc/->edn
+                       mldoc/->edn-async)
+             ast (parse-f content (mldoc/default-config format))]
        _ (println "Parsing finished : " file)
        (let [journal? (config/journal? file)
              first-block (ffirst ast)

+ 5 - 2
src/main/frontend/handler/file.cljs

@@ -24,7 +24,8 @@
             [frontend.util :as util]
             [lambdaisland.glogi :as log]
             [promesa.core :as p]
-            [frontend.debug :as debug]))
+            [frontend.debug :as debug]
+            [frontend.mobile.util :as mobile]))
 
 ;; TODO: extract all git ops using a channel
 
@@ -134,7 +135,6 @@
   [repo-url file content]
   (let [electron-local-repo? (and (util/electron?)
                                   (config/local-db? repo-url))
-        ;; FIXME: store relative path in db
         file (cond
                (and electron-local-repo?
                     util/win32?
@@ -146,6 +146,9 @@
                                           (not= "/" (first file))))
                (str (config/get-repo-dir repo-url) "/" file)
 
+               (and (mobile/is-native-platform?) (not= "/" (first file)))
+               (str (config/get-repo-dir repo-url) "/" file)
+
                :else
                file)
         new? (nil? (db/entity [:file/path file]))]

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