Explorar o código

feat(mobile): share intent (#4599)

* feat(mobile): share intent

* feat(ios): share content

* feat(share): fix kUTType* deprecation

* feat(share): fix result of call to 'openURL' is unused

* fix screenshot copy rewrite issue

* fix AndroidManifest.xml

* fix(iOS): app not showing in share list

* address comment
llcc %!s(int64=3) %!d(string=hai) anos
pai
achega
21e172feb6

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

@@ -14,6 +14,8 @@ dependencies {
     implementation project(':capacitor-filesystem')
     implementation project(':capacitor-keyboard')
     implementation project(':capacitor-splash-screen')
+    implementation project(':capacitor-status-bar')
+    implementation project(':send-intent')
 
 }
 

+ 9 - 0
android/app/src/main/AndroidManifest.xml

@@ -23,6 +23,15 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
 
+            <intent-filter>
+                <action android:name="android.intent.action.SEND" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="text/plain" />
+                <data android:mimeType="image/*" />
+                <data android:mimeType="application/*" />
+                <data android:mimeType="video/*" />
+            </intent-filter>
+
         </activity>
 
         <provider

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

@@ -18,5 +18,13 @@
 	{
 		"pkg": "@capacitor/splash-screen",
 		"classpath": "com.capacitorjs.plugins.splashscreen.SplashScreenPlugin"
+	},
+	{
+		"pkg": "@capacitor/status-bar",
+		"classpath": "com.capacitorjs.plugins.statusbar.StatusBarPlugin"
+	},
+	{
+		"pkg": "send-intent",
+		"classpath": "de.mindlib.sendIntent.SendIntent"
 	}
 ]

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

@@ -1,6 +1,8 @@
 package com.logseq.app;
 
+import android.content.Intent;
 import android.os.Bundle;
+import android.webkit.ValueCallback;
 
 import com.getcapacitor.BridgeActivity;
 
@@ -17,4 +19,22 @@ public class MainActivity extends BridgeActivity {
         overridePendingTransition(0, R.anim.byebye);
         super.onPause();
     }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+        String action = intent.getAction();
+        String type = intent.getType();
+        if (Intent.ACTION_SEND.equals(action) && type != null) {
+            bridge.getActivity().setIntent(intent);
+            bridge.eval("window.dispatchEvent(new Event('sendIntentReceived'))", new ValueCallback<String>() {
+                @Override
+                public void onReceiveValue(String s) {
+                    //
+                }
+            });
+        }
+    }
+
+
 }

+ 6 - 0
android/capacitor.settings.gradle

@@ -16,3 +16,9 @@ project(':capacitor-keyboard').projectDir = new File('../node_modules/@capacitor
 
 include ':capacitor-splash-screen'
 project(':capacitor-splash-screen').projectDir = new File('../node_modules/@capacitor/splash-screen/android')
+
+include ':capacitor-status-bar'
+project(':capacitor-status-bar').projectDir = new File('../node_modules/@capacitor/status-bar/android')
+
+include ':send-intent'
+project(':send-intent').projectDir = new File('../node_modules/send-intent/android')

+ 179 - 7
ios/App/App.xcodeproj/project.pbxproj

@@ -16,6 +16,9 @@
 		50B271D11FEDC1A000F3C39B /* public in Resources */ = {isa = PBXBuildFile; fileRef = 50B271D01FEDC1A000F3C39B /* public */; };
 		5FD5BB71278579F5008E6875 /* DownloadiCloudFiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FD5BB70278579F5008E6875 /* DownloadiCloudFiles.swift */; };
 		5FD5BB73278579FF008E6875 /* DownloadiCloudFiles.m in Sources */ = {isa = PBXBuildFile; fileRef = 5FD5BB72278579FF008E6875 /* DownloadiCloudFiles.m */; };
+		5FFF7D6D27E343FA00B00DA8 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FFF7D6C27E343FA00B00DA8 /* ShareViewController.swift */; };
+		5FFF7D7027E343FA00B00DA8 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5FFF7D6E27E343FA00B00DA8 /* MainInterface.storyboard */; };
+		5FFF7D7427E343FA00B00DA8 /* ShareViewController.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 5FFF7D6A27E343FA00B00DA8 /* ShareViewController.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
 		7435D10C2704659F00AB88E0 /* FolderPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7435D10B2704659F00AB88E0 /* FolderPicker.swift */; };
 		7435D10F2704660B00AB88E0 /* FolderPicker.m in Sources */ = {isa = PBXBuildFile; fileRef = 7435D10E2704660B00AB88E0 /* FolderPicker.m */; };
 		C3718FCEFAECFFB66E93FFC4 /* Pods_Logseq.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B2E26D73EA097D0B3B22942E /* Pods_Logseq.framework */; };
@@ -26,6 +29,30 @@
 		FE647FF627BDFEF500F3206B /* FsWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = FE647FF527BDFEF500F3206B /* FsWatcher.m */; };
 /* End PBXBuildFile section */
 
+/* Begin PBXContainerItemProxy section */
+		5FFF7D7227E343FA00B00DA8 /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = 504EC2FC1FED79650016851F /* Project object */;
+			proxyType = 1;
+			remoteGlobalIDString = 5FFF7D6927E343FA00B00DA8;
+			remoteInfo = ShareViewController;
+		};
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+		5FFF7D7527E343FA00B00DA8 /* Embed App Extensions */ = {
+			isa = PBXCopyFilesBuildPhase;
+			buildActionMask = 2147483647;
+			dstPath = "";
+			dstSubfolderSpec = 13;
+			files = (
+				5FFF7D7427E343FA00B00DA8 /* ShareViewController.appex in Embed App Extensions */,
+			);
+			name = "Embed App Extensions";
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXCopyFilesBuildPhase 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>"; };
@@ -38,6 +65,11 @@
 		50B271D01FEDC1A000F3C39B /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = "<group>"; };
 		5FD5BB70278579F5008E6875 /* DownloadiCloudFiles.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DownloadiCloudFiles.swift; sourceTree = "<group>"; };
 		5FD5BB72278579FF008E6875 /* DownloadiCloudFiles.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DownloadiCloudFiles.m; sourceTree = "<group>"; };
+		5FFF7D6A27E343FA00B00DA8 /* ShareViewController.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ShareViewController.appex; sourceTree = BUILT_PRODUCTS_DIR; };
+		5FFF7D6C27E343FA00B00DA8 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; };
+		5FFF7D6F27E343FA00B00DA8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = "<group>"; };
+		5FFF7D7127E343FA00B00DA8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+		5FFF7D7927E4E70700B00DA8 /* ShareViewController.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ShareViewController.entitlements; sourceTree = "<group>"; };
 		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>"; };
@@ -63,6 +95,13 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
+		5FFF7D6727E343FA00B00DA8 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 /* End PBXFrameworksBuildPhase section */
 
 /* Begin PBXGroup section */
@@ -70,6 +109,7 @@
 			isa = PBXGroup;
 			children = (
 				504EC3061FED79650016851F /* App */,
+				5FFF7D6B27E343FA00B00DA8 /* ShareViewController */,
 				504EC3051FED79650016851F /* Products */,
 				D337740F89DEEAD18C87762B /* Pods */,
 				9FC5AB18C7E7E43B09B33A61 /* Frameworks */,
@@ -80,6 +120,7 @@
 			isa = PBXGroup;
 			children = (
 				504EC3041FED79650016851F /* Logseq.app */,
+				5FFF7D6A27E343FA00B00DA8 /* ShareViewController.appex */,
 			);
 			name = Products;
 			sourceTree = "<group>";
@@ -110,6 +151,17 @@
 			path = App;
 			sourceTree = "<group>";
 		};
+		5FFF7D6B27E343FA00B00DA8 /* ShareViewController */ = {
+			isa = PBXGroup;
+			children = (
+				5FFF7D7927E4E70700B00DA8 /* ShareViewController.entitlements */,
+				5FFF7D6C27E343FA00B00DA8 /* ShareViewController.swift */,
+				5FFF7D6E27E343FA00B00DA8 /* MainInterface.storyboard */,
+				5FFF7D7127E343FA00B00DA8 /* Info.plist */,
+			);
+			path = ShareViewController;
+			sourceTree = "<group>";
+		};
 		9FC5AB18C7E7E43B09B33A61 /* Frameworks */ = {
 			isa = PBXGroup;
 			children = (
@@ -140,23 +192,42 @@
 				504EC3011FED79650016851F /* Frameworks */,
 				504EC3021FED79650016851F /* Resources */,
 				4BF32F1E9453A6AB603D7CD2 /* [CP] Embed Pods Frameworks */,
+				5FFF7D7527E343FA00B00DA8 /* Embed App Extensions */,
 			);
 			buildRules = (
 			);
 			dependencies = (
+				5FFF7D7327E343FA00B00DA8 /* PBXTargetDependency */,
 			);
 			name = Logseq;
 			productName = App;
 			productReference = 504EC3041FED79650016851F /* Logseq.app */;
 			productType = "com.apple.product-type.application";
 		};
+		5FFF7D6927E343FA00B00DA8 /* ShareViewController */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 5FFF7D7827E343FA00B00DA8 /* Build configuration list for PBXNativeTarget "ShareViewController" */;
+			buildPhases = (
+				5FFF7D6627E343FA00B00DA8 /* Sources */,
+				5FFF7D6727E343FA00B00DA8 /* Frameworks */,
+				5FFF7D6827E343FA00B00DA8 /* Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = ShareViewController;
+			productName = ShareViewController;
+			productReference = 5FFF7D6A27E343FA00B00DA8 /* ShareViewController.appex */;
+			productType = "com.apple.product-type.app-extension";
+		};
 /* End PBXNativeTarget section */
 
 /* Begin PBXProject section */
 		504EC2FC1FED79650016851F /* Project object */ = {
 			isa = PBXProject;
 			attributes = {
-				LastSwiftUpdateCheck = 0920;
+				LastSwiftUpdateCheck = 1330;
 				LastUpgradeCheck = 1310;
 				TargetAttributes = {
 					504EC3031FED79650016851F = {
@@ -164,6 +235,10 @@
 						LastSwiftMigration = 1250;
 						ProvisioningStyle = Automatic;
 					};
+					5FFF7D6927E343FA00B00DA8 = {
+						CreatedOnToolsVersion = 13.3;
+						ProvisioningStyle = Automatic;
+					};
 				};
 			};
 			buildConfigurationList = 504EC2FF1FED79650016851F /* Build configuration list for PBXProject "App" */;
@@ -180,6 +255,7 @@
 			projectRoot = "";
 			targets = (
 				504EC3031FED79650016851F /* Logseq */,
+				5FFF7D6927E343FA00B00DA8 /* ShareViewController */,
 			);
 		};
 /* End PBXProject section */
@@ -198,6 +274,14 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
+		5FFF7D6827E343FA00B00DA8 /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				5FFF7D7027E343FA00B00DA8 /* MainInterface.storyboard in Resources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 /* End PBXResourcesBuildPhase section */
 
 /* Begin PBXShellScriptBuildPhase section */
@@ -257,8 +341,24 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
+		5FFF7D6627E343FA00B00DA8 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				5FFF7D6D27E343FA00B00DA8 /* ShareViewController.swift in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 /* End PBXSourcesBuildPhase section */
 
+/* Begin PBXTargetDependency section */
+		5FFF7D7327E343FA00B00DA8 /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			target = 5FFF7D6927E343FA00B00DA8 /* ShareViewController */;
+			targetProxy = 5FFF7D7227E343FA00B00DA8 /* PBXContainerItemProxy */;
+		};
+/* End PBXTargetDependency section */
+
 /* Begin PBXVariantGroup section */
 		504EC30B1FED79650016851F /* Main.storyboard */ = {
 			isa = PBXVariantGroup;
@@ -276,6 +376,14 @@
 			name = LaunchScreen.storyboard;
 			sourceTree = "<group>";
 		};
+		5FFF7D6E27E343FA00B00DA8 /* MainInterface.storyboard */ = {
+			isa = PBXVariantGroup;
+			children = (
+				5FFF7D6F27E343FA00B00DA8 /* Base */,
+			);
+			name = MainInterface.storyboard;
+			sourceTree = "<group>";
+		};
 /* End PBXVariantGroup section */
 
 /* Begin XCBuildConfiguration section */
@@ -396,16 +504,17 @@
 			isa = XCBuildConfiguration;
 			baseConfigurationReference = DE5650F4AD4E2242AB9C012D /* Pods-Logseq.debug.xcconfig */;
 			buildSettings = {
+				ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CLANG_ENABLE_MODULES = YES;
 				CODE_SIGN_ENTITLEMENTS = App/AppDebug.entitlements;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 1;
+				CURRENT_PROJECT_VERSION = 16;
 				DEVELOPMENT_TEAM = K378MFWK59;
 				INFOPLIST_FILE = App/Info.plist;
-				IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
-				MARKETING_VERSION = 0.0.1;
+				MARKETING_VERSION = 0.0.7;
 				OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq;
 				PRODUCT_NAME = "$(TARGET_NAME)";
@@ -421,16 +530,17 @@
 			isa = XCBuildConfiguration;
 			baseConfigurationReference = 8A489CEC51E94726DDD58810 /* Pods-Logseq.release.xcconfig */;
 			buildSettings = {
+				ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CLANG_ENABLE_MODULES = YES;
 				CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 1;
+				CURRENT_PROJECT_VERSION = 16;
 				DEVELOPMENT_TEAM = K378MFWK59;
 				INFOPLIST_FILE = App/Info.plist;
-				IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
-				MARKETING_VERSION = 0.0.1;
+				MARKETING_VERSION = 0.0.7;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
@@ -440,6 +550,59 @@
 			};
 			name = Release;
 		};
+		5FFF7D7627E343FA00B00DA8 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
+				CLANG_ENABLE_OBJC_WEAK = YES;
+				CODE_SIGN_ENTITLEMENTS = ShareViewController/ShareViewController.entitlements;
+				CODE_SIGN_STYLE = Automatic;
+				CURRENT_PROJECT_VERSION = 16;
+				DEVELOPMENT_TEAM = K378MFWK59;
+				GENERATE_INFOPLIST_FILE = YES;
+				INFOPLIST_FILE = ShareViewController/Info.plist;
+				INFOPLIST_KEY_CFBundleDisplayName = ShareViewController;
+				INFOPLIST_KEY_NSHumanReadableCopyright = "";
+				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
+				MARKETING_VERSION = 0.0.7;
+				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+				MTL_FAST_MATH = YES;
+				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq.ShareViewController;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SKIP_INSTALL = YES;
+				SWIFT_EMIT_LOC_STRINGS = YES;
+				SWIFT_VERSION = 5.0;
+				TARGETED_DEVICE_FAMILY = "1,2";
+			};
+			name = Debug;
+		};
+		5FFF7D7727E343FA00B00DA8 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
+				CLANG_ENABLE_OBJC_WEAK = YES;
+				CODE_SIGN_ENTITLEMENTS = ShareViewController/ShareViewController.entitlements;
+				CODE_SIGN_STYLE = Automatic;
+				CURRENT_PROJECT_VERSION = 16;
+				DEVELOPMENT_TEAM = K378MFWK59;
+				GENERATE_INFOPLIST_FILE = YES;
+				INFOPLIST_FILE = ShareViewController/Info.plist;
+				INFOPLIST_KEY_CFBundleDisplayName = ShareViewController;
+				INFOPLIST_KEY_NSHumanReadableCopyright = "";
+				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
+				MARKETING_VERSION = 0.0.7;
+				MTL_FAST_MATH = YES;
+				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq.ShareViewController;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SKIP_INSTALL = YES;
+				SWIFT_EMIT_LOC_STRINGS = YES;
+				SWIFT_VERSION = 5.0;
+				TARGETED_DEVICE_FAMILY = "1,2";
+			};
+			name = Release;
+		};
 /* End XCBuildConfiguration section */
 
 /* Begin XCConfigurationList section */
@@ -461,6 +624,15 @@
 			defaultConfigurationIsVisible = 0;
 			defaultConfigurationName = Release;
 		};
+		5FFF7D7827E343FA00B00DA8 /* Build configuration list for PBXNativeTarget "ShareViewController" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				5FFF7D7627E343FA00B00DA8 /* Debug */,
+				5FFF7D7727E343FA00B00DA8 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
 /* End XCConfigurationList section */
 	};
 	rootObject = 504EC2FC1FED79650016851F /* Project object */;

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

@@ -16,5 +16,9 @@
 	<array>
 		<string>iCloud.com.logseq.logseq</string>
 	</array>
+	<key>com.apple.security.application-groups</key>
+	<array>
+		<string>group.com.logseq.logseq</string>
+	</array>
 </dict>
 </plist>

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

@@ -18,5 +18,9 @@
 	<array>
 		<string>iCloud.com.logseq.logseq</string>
 	</array>
+	<key>com.apple.security.application-groups</key>
+	<array>
+		<string>group.com.logseq.logseq</string>
+	</array>
 </dict>
 </plist>

+ 39 - 7
ios/App/App/AppDelegate.swift

@@ -1,16 +1,18 @@
 import UIKit
 import Capacitor
+import SendIntent
 
 @UIApplicationMain
 class AppDelegate: UIResponder, UIApplicationDelegate {
 
     var window: UIWindow?
-
+    let store = ShareStore.store
+    
     func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
         // Override point for customization after application launch.
         return true
     }
-
+    
     func applicationWillResignActive(_ application: UIApplication) {
         // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
         // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
@@ -33,11 +35,41 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
         // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
     }
 
-    func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
-        // Called when the app was launched with a url. Feel free to add additional processing here,
-        // but if you want the App API to support tracking app url opens, make sure to keep this call
-        return ApplicationDelegateProxy.shared.application(app, open: url, options: options)
-    }
+    func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
+            
+            var success = true
+            if CAPBridge.handleOpenUrl(url, options) {
+                success = ApplicationDelegateProxy.shared.application(app, open: url, options: options)
+            }
+            
+            guard let components = NSURLComponents(url: url, resolvingAgainstBaseURL: true),
+                  let params = components.queryItems else {
+                      return false
+                  }
+            let titles = params.filter { $0.name == "title" }
+            let descriptions = params.filter { $0.name == "description" }
+            let types = params.filter { $0.name == "type" }
+            let urls = params.filter { $0.name == "url" }
+            
+            store.shareItems.removeAll()
+        
+            if(titles.count > 0){
+                for index in 0...titles.count-1 {
+                    var shareItem: JSObject = JSObject()
+                    shareItem["title"] = titles[index].value!
+                    shareItem["description"] = descriptions[index].value!
+                    shareItem["type"] = types[index].value!
+                    shareItem["url"] = urls[index].value!
+                    store.shareItems.append(shareItem)
+                }
+            }
+            
+            store.processed = false
+            let nc = NotificationCenter.default
+            nc.post(name: Notification.Name("triggerSendIntent"), object: nil )
+            
+            return success
+        }
 
     func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
         // Called when the app was launched with an activity, including Universal Links.

+ 13 - 0
ios/App/App/Info.plist

@@ -29,6 +29,19 @@
 	<string>APPL</string>
 	<key>CFBundleShortVersionString</key>
 	<string>$(MARKETING_VERSION)</string>
+	<key>CFBundleURLTypes</key>
+	<array>
+		<dict>
+			<key>CFBundleTypeRole</key>
+			<string>Viewer</string>
+			<key>CFBundleURLName</key>
+			<string>com.logseq.logseq</string>
+			<key>CFBundleURLSchemes</key>
+			<array>
+				<string>logseq</string>
+			</array>
+		</dict>
+	</array>
 	<key>CFBundleVersion</key>
 	<string>$(CURRENT_PROJECT_VERSION)</string>
 	<key>LSApplicationCategoryType</key>

+ 1 - 0
ios/App/Podfile

@@ -15,6 +15,7 @@ def capacitor_pods
   pod 'CapacitorKeyboard', :path => '../../node_modules/@capacitor/keyboard'
   pod 'CapacitorSplashScreen', :path => '../../node_modules/@capacitor/splash-screen'
   pod 'CapacitorStatusBar', :path => '../../node_modules/@capacitor/status-bar'
+  pod 'SendIntent', :path => '../../node_modules/send-intent'
 end
 
 target 'Logseq' do

+ 24 - 0
ios/App/ShareViewController/Base.lproj/MainInterface.storyboard

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="j1y-V4-xli">
+    <dependencies>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
+        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <scenes>
+        <!--Share View Controller-->
+        <scene sceneID="ceB-am-kn3">
+            <objects>
+                <viewController id="j1y-V4-xli" customClass="ShareViewController" customModuleProvider="target" sceneMemberID="viewController">
+                    <view key="view" opaque="NO" contentMode="scaleToFill" id="wbc-yd-nQP">
+                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
+                        <viewLayoutGuide key="safeArea" id="1Xd-am-t49"/>
+                    </view>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="CEy-Cv-SGf" userLabel="First Responder" sceneMemberID="firstResponder"/>
+            </objects>
+        </scene>
+    </scenes>
+</document>

+ 33 - 0
ios/App/ShareViewController/Info.plist

@@ -0,0 +1,33 @@
+<?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>NSExtension</key>
+	<dict>
+		<key>NSExtensionAttributes</key>
+		<dict>
+			<key>NSExtensionActivationRule</key>
+			<dict>
+				<key>NSExtensionActivationSupportsFileWithMaxCount</key>
+				<integer>5</integer>
+				<key>NSExtensionActivationSupportsImageWithMaxCount</key>
+				<integer>5</integer>
+				<key>NSExtensionActivationSupportsMovieWithMaxCount</key>
+				<integer>5</integer>
+				<key>NSExtensionActivationSupportsText</key>
+				<true/>
+				<key>NSExtensionActivationSupportsWebPageWithMaxCount</key>
+				<integer>1</integer>
+				<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
+				<integer>1</integer>
+				<key>NSExtensionActivationUsesStrictMatching</key>
+				<false/>
+			</dict>
+		</dict>
+		<key>NSExtensionMainStoryboard</key>
+		<string>MainInterface</string>
+		<key>NSExtensionPointIdentifier</key>
+		<string>com.apple.share-services</string>
+	</dict>
+</dict>
+</plist>

+ 10 - 0
ios/App/ShareViewController/ShareViewController.entitlements

@@ -0,0 +1,10 @@
+<?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>com.apple.security.application-groups</key>
+	<array>
+		<string>group.com.logseq.logseq</string>
+	</array>
+</dict>
+</plist>

+ 201 - 0
ios/App/ShareViewController/ShareViewController.swift

@@ -0,0 +1,201 @@
+//
+//  ShareViewController.swift
+//  ShareViewController
+//
+//  Created by leizhe on 2022/3/17.
+//
+
+
+import MobileCoreServices
+import Social
+import UIKit
+
+class ShareItem {
+    public var title: String?
+    public var type: String?
+    public var url: String?
+}
+
+class ShareViewController: UIViewController {
+    
+    private var shareItems: [ShareItem] = []
+    
+    var groupContainerUrl: URL? {
+        return FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.logseq.logseq")
+    }
+    
+    override public func viewDidAppear(_ animated: Bool) {
+       super.viewDidAppear(animated)
+       self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
+    }
+    
+    private func sendData() {
+        let queryItems = shareItems.map {
+            [
+                URLQueryItem(
+                    name: "title",
+                    value: $0.title?.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? ""),
+                URLQueryItem(name: "description", value: ""),
+                URLQueryItem(
+                    name: "type",
+                    value: $0.type?.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? ""),
+                URLQueryItem(
+                    name: "url",
+                    value: $0.url?.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? ""),
+            ]
+        }.flatMap({ $0 })
+        var urlComps = URLComponents(string: "logseq://")!
+        urlComps.queryItems = queryItems
+        openURL(urlComps.url!)
+    }
+    
+    fileprivate func createSharedFileUrl(_ url: URL?) -> String {
+        
+        let copyFileUrl = groupContainerUrl!.absoluteString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)! + "/" + url!
+            .lastPathComponent.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
+        try? Data(contentsOf: url!).write(to: URL(string: copyFileUrl)!)
+        
+        return copyFileUrl
+    }
+    
+    func saveScreenshot(_ image: UIImage) -> String {
+        
+        let dateFormatter = DateFormatter()
+        dateFormatter.dateFormat = "yyyy-MM-dd-HH-mm-ss"
+        
+        let copyFileUrl = groupContainerUrl!.absoluteString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
+        + dateFormatter.string(from: Date()) + ".png"
+        
+        do {
+            try image.pngData()?.write(to: URL(string: copyFileUrl)!)
+            return copyFileUrl
+        } catch {
+            print(error.localizedDescription)
+            return ""
+        }
+    }
+    
+    fileprivate func handleTypeUrl(_ attachment: NSItemProvider)
+    async throws -> ShareItem
+    {
+        let results = try await attachment.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil)
+        let url = results as! URL?
+        let shareItem: ShareItem = ShareItem()
+        
+        if url!.isFileURL {
+            shareItem.title = url!.lastPathComponent
+            shareItem.type = "application/" + url!.pathExtension.lowercased()
+            shareItem.url = createSharedFileUrl(url)
+        } else {
+            shareItem.title = url!.absoluteString
+            shareItem.url = url!.absoluteString
+            shareItem.type = "text/plain"
+        }
+        
+        return shareItem
+    }
+    
+    fileprivate func handleTypeText(_ attachment: NSItemProvider)
+    async throws -> ShareItem
+    {
+        let results = try await attachment.loadItem(forTypeIdentifier: kUTTypeText as String, options: nil)
+        let shareItem: ShareItem = ShareItem()
+        let text = results as! String
+        shareItem.title = text
+        shareItem.type = "text/plain"
+        
+        return shareItem
+    }
+    
+    fileprivate func handleTypeMovie(_ attachment: NSItemProvider)
+    async throws -> ShareItem
+    {
+        let results = try await attachment.loadItem(forTypeIdentifier: kUTTypeMovie as String, options: nil)
+        let shareItem: ShareItem = ShareItem()
+        
+        let url = results as! URL?
+        shareItem.title = url!.lastPathComponent
+        shareItem.type = "video/" + url!.pathExtension.lowercased()
+        shareItem.url = createSharedFileUrl(url)
+        
+        return shareItem
+    }
+    
+    fileprivate func handleTypeImage(_ attachment: NSItemProvider)
+    async throws -> ShareItem
+    {
+        let data = try await attachment.loadItem(forTypeIdentifier: kUTTypeImage as String, options: nil)
+        
+        let shareItem: ShareItem = ShareItem()
+        switch data {
+        case let image as UIImage:
+            shareItem.title = "screenshot"
+            shareItem.type = "image/png"
+            shareItem.url = self.saveScreenshot(image)
+        case let url as URL:
+            shareItem.title = url.lastPathComponent
+            shareItem.type = "image/" + url.pathExtension.lowercased()
+            shareItem.url = self.createSharedFileUrl(url)
+        default:
+            print("Unexpected image data:", type(of: data))
+        }
+        
+        return shareItem
+    }
+    
+    
+    override public func viewDidLoad() {
+        super.viewDidLoad()
+        
+        shareItems.removeAll()
+        
+        let extensionItem = extensionContext?.inputItems.first as! NSExtensionItem
+        Task {
+            try await withThrowingTaskGroup(
+                of: ShareItem.self,
+                body: { taskGroup in
+                    
+                    for attachment in extensionItem.attachments! {
+                        if attachment.hasItemConformingToTypeIdentifier(kUTTypeURL as String) {
+                            taskGroup.addTask {
+                                return try await self.handleTypeUrl(attachment)
+                            }
+                        } else if attachment.hasItemConformingToTypeIdentifier(kUTTypeText as String) {
+                            taskGroup.addTask {
+                                return try await self.handleTypeText(attachment)
+                            }
+                        } else if attachment.hasItemConformingToTypeIdentifier(kUTTypeMovie as String) {
+                            taskGroup.addTask {
+                                return try await self.handleTypeMovie(attachment)
+                            }
+                        } else if attachment.hasItemConformingToTypeIdentifier(kUTTypeImage as String) {
+                            taskGroup.addTask {
+                                return try await self.handleTypeImage(attachment)
+                            }
+                        }
+                    }
+                    
+                    for try await item in taskGroup {
+                        self.shareItems.append(item)
+                    }
+                })
+            
+            self.sendData()
+            
+        }
+    }
+    
+    @discardableResult
+    @objc func openURL(_ url: URL) -> Bool {
+        var responder: UIResponder? = self
+        while responder != nil {
+            if let application = responder as? UIApplication {
+                return application.perform(#selector(openURL(_:)), with: url) != nil
+            }
+            responder = responder?.next
+        }
+        return false
+    }
+    
+}
+

+ 1 - 0
package.json

@@ -112,6 +112,7 @@
         "react-transition-group": "4.3.0",
         "reakit": "0.11.1",
         "remove-accents": "0.4.2",
+        "send-intent": "3.0.11",
         "threads": "1.6.5",
         "url": "^0.11.0",
         "yargs-parser": "20.2.4"

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

@@ -190,6 +190,16 @@
     ["[]()" 1]
     ["" 0]))
 
+(defn link-format
+  [format label link]
+  (if label
+    (case format
+      :org
+      (util/format "[[%s][%s]]" link label)
+      :markdown
+      (util/format "[%s](%s)" label link))
+    link))
+
 (defn with-default-link
   [format link]
   (case format

+ 4 - 0
src/main/frontend/mobile/core.cljs

@@ -5,6 +5,7 @@
             ["@capacitor/keyboard" :refer [^js Keyboard]]
             #_:clj-kondo/ignore
             ["@capacitor/status-bar" :refer [^js StatusBar]]
+            [frontend.mobile.intent :as intent]
             [clojure.string :as string]
             [frontend.fs.capacitor-fs :as fs]
             [frontend.handler.editor :as editor-handler]
@@ -62,3 +63,6 @@
                       (let [is-active? (.-isActive state)]
                         (when is-active?
                           (editor-handler/save-current-block!))))))))
+    (.addEventListener js/window "sendIntentReceived"
+                       #(intent/handle-received))
+

+ 154 - 0
src/main/frontend/mobile/intent.cljs

@@ -0,0 +1,154 @@
+(ns frontend.mobile.intent
+  (:require ["@capacitor/filesystem" :refer [Filesystem]]
+            ["send-intent" :refer [^js SendIntent]]
+            [lambdaisland.glogi :as log]
+            [promesa.core :as p]
+            [clojure.string :as string]
+            [frontend.db :as db]
+            [frontend.handler.editor :as editor-handler]
+            [frontend.state :as state]
+            [frontend.date :as date]
+            [frontend.util :as util]
+            [frontend.config :as config]
+            [frontend.format.mldoc :as mldoc]
+            ["path" :as path]
+            [frontend.mobile.util :as mobile-util]
+            [frontend.handler.notification :as notification]
+            [clojure.pprint :as pprint]))
+
+(defn- handle-received-text [result]
+  (let [{:keys [title url]} result
+        page (or (state/get-current-page)
+                 (string/lower-case (date/journal-name)))
+        format (db/get-page-format page)
+        time (date/get-current-time)
+        url (if (and (mldoc/link? format title) (not url))
+              title
+              url)
+        text (if (= url title) nil title)
+        [text url] (if (or (mldoc/link? format url) (not url))
+                     [text url]
+                     (string/split url "\"\n"))
+        text (some-> text (string/replace #"^\"" ""))
+        url (and url
+                 (cond (or (string/includes? url "youtube.com")
+                           (string/includes? url "youtu.be"))
+                       (util/format "{{youtube %s}}" url)
+
+                       (and (string/includes? url "twitter.com")
+                            (string/includes? url "status"))
+                       (util/format "{{twitter %s}}" url)
+
+                       :else
+                       (if text
+                         (config/link-format format text url)
+                         url)))
+        template (get-in (state/get-config)
+                         [:quick-capture-template :text]
+                         "**{time}** [[quick capture]]: {text} {url}")
+        values (-> (string/replace template "{time}" time)
+                   (string/replace "{text}" (or text ""))
+                   (string/replace "{url}" (or url "")))]
+    (if (state/get-edit-block)
+      (state/append-current-edit-content! values)
+      (editor-handler/api-insert-new-block! values {:page page}))))
+
+(defn get-asset-path
+  [filename]
+  (p/let [[repo-dir assets-dir]
+          (editor-handler/ensure-assets-dir! (state/get-current-repo))
+          path (path/join repo-dir assets-dir filename)]
+    (if (mobile-util/native-android?)
+      path
+      (js/encodeURI (js/decodeURI path)))))
+
+(defn- handle-received-media [result]
+  (p/let [{:keys [title url]} result
+          page (or (state/get-current-page)
+                   (string/lower-case (date/journal-name)))
+          format (db/get-page-format page)
+          time (date/get-current-time)
+          basename (path/basename url)
+          label (-> basename util/node-path.name)
+          path (get-asset-path (or (path/basename url) title))
+          _ (p/catch
+                (.copy Filesystem (clj->js {:from url :to path}))
+                (fn [error]
+                  (log/error :copy-file-error {:error error})))
+          url (util/format "../assets/%s" basename)
+          url (editor-handler/get-asset-file-link format url label true)
+          template (get-in (state/get-config)
+                           [:quick-capture-template :image]
+                           "**{time}** [[quick capture]]: {url}")
+          values (-> (string/replace template "{time}" time)
+                     (string/replace "{url}" (or url "")))]
+    (if (state/get-edit-block)
+      (state/append-current-edit-content! values)
+      (editor-handler/api-insert-new-block! values {:page page}))))
+
+(defn- handle-received-application [result]
+  (p/let [{:keys [title url]} result
+          page (or (state/get-current-page) (string/lower-case (date/journal-name)))
+          time (date/get-current-time)
+          title (some-> (or title (path/basename url))
+                        js/decodeURIComponent
+                        util/node-path.name)
+          path (and url (path/join (config/get-repo-dir (state/get-current-repo))
+                                   (config/get-pages-directory)
+                                   (path/basename url)))
+          _ (p/catch
+                (.copy Filesystem (clj->js {:from url :to path}))
+                (fn [error]
+                  (log/error :copy-file-error {:error error})))
+          url (util/format "[[%s]]" title)
+          template (get-in (state/get-config)
+                           [:quick-capture-template :image]
+                           "**{time}** [[quick capture]]: {url}")
+          values (-> (string/replace template "{time}" time)
+                     (string/replace "{url}" (or url "")))]
+    (if (state/get-edit-block)
+      (state/append-current-edit-content! values)
+      (editor-handler/api-insert-new-block! values {:page page}))))
+
+(defn decode-received-result [m]
+  (into {} (for [[k v] m]
+             [k (cond (vector? v)
+                      (vec (map decode-received-result v))
+
+                      (string/blank? v)
+                      nil
+
+                      :else
+                      (if (mobile-util/native-ios?)
+                        (js/decodeURIComponent v)
+                        v))])))
+
+(defn handle-received []
+  (p/let [received (p/catch
+                    (.checkSendIntentReceived SendIntent)
+                    (fn [error]
+                      (log/error :intent-received-error {:error error})))]
+    (when received
+      (let [result (-> (js->clj received :keywordize-keys true)
+                       decode-received-result)]
+        (when-let [type (:type result)]
+          (cond
+            (string/starts-with? type "text/")
+            (handle-received-text result)
+
+            (or (string/starts-with? type "image/")
+                (string/starts-with? type "video/")
+                (string/starts-with? type "audio/"))
+            (handle-received-media result)
+
+            (string/starts-with? type "application/")
+            (handle-received-application result)
+
+            :else
+            (notification/show!
+             [:div
+              "Parsing current shared content are not supported. Please report the following codes on "
+              [:a {:href "https://github.com/logseq/logseq/issues"
+                   :target "_blank"} "Github"]
+              ". We will look into it soon."
+              [:pre.code (with-out-str (pprint/pprint result))]] :warning false)))))))

+ 12 - 0
templates/config.edn

@@ -199,4 +199,16 @@
 
  ;; ignore #+keyword: for parsing page references in orgmode
  ;; :ignored-page-references-keywords #{"author" "startup"}
+ 
+ ;; Quick capture templates on mobile for recieving contents from other apps.
+ ;; Each template contains three elements {time}, {text} and {url}, which can be auto-expanded
+ ;; by received contents from other apps. Note: the {} cannot be omitted.
+ ;; - {time}: capture time
+ ;; - {text}: text that users selected before sharing.
+ ;; - {url}: url or assets path for media files stored in Logseq.
+ ;; You can also reorder them, or even only use one or two of them in the template.
+ ;; You can also insert or format any text in the template as shown in the following examples.
+ ;; :quick-capture-templates
+ ;; {:text "[[quick capture]] **{time}**: \n - {text} from {url}"
+ ;;  :media "[[quick capture]] **{time}**: \n - {url}"}
  }

+ 46 - 3
yarn.lock

@@ -2,6 +2,11 @@
 # yarn lockfile v1
 
 
+"[email protected]":
+  version "0.0.1"
+  resolved "https://registry.yarnpkg.com/-/-/--0.0.1.tgz#db6db7cd866142880dd03e5b8781d1b4fac0e5bd"
+  integrity sha512-3HfneK3DGAm05fpyj20sT3apkNcvPpCuccOThOPdzz8sY7GgQGe0l93XH9bt+YzibcTIgUAIMoyVJI740RtgyQ==
+
 "@ampproject/remapping@^2.1.0":
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.1.2.tgz#4edca94973ded9630d20101cd8559cedb8d8bd34"
@@ -501,6 +506,28 @@
     tslib "^2.1.0"
     xml2js "^0.4.23"
 
+"@capacitor/cli@>= 3 < 4":
+  version "3.4.3"
+  resolved "https://registry.yarnpkg.com/@capacitor/cli/-/cli-3.4.3.tgz#15a14cbd9574980da5f9f04b6ce8623455784c6e"
+  integrity sha512-mu2pGoIh/OapMzWwXGXUlOmapXthW9XcHGERpZxNE45aBEYC9eTJWmbcXVlJuyx/9GFnnV+pXjGl3i0grci/Zw==
+  dependencies:
+    "@ionic/cli-framework-output" "^2.2.1"
+    "@ionic/utils-fs" "^3.1.5"
+    "@ionic/utils-subprocess" "^2.1.6"
+    "@ionic/utils-terminal" "^2.3.0"
+    commander "^6.0.0"
+    debug "^4.2.0"
+    env-paths "^2.2.0"
+    kleur "^4.1.1"
+    native-run "^1.5.0"
+    open "^7.4.2"
+    plist "^3.0.2"
+    prompts "^2.3.2"
+    semver "^7.3.2"
+    tar "^6.1.11"
+    tslib "^2.1.0"
+    xml2js "^0.4.23"
+
 "@capacitor/[email protected]":
   version "3.2.2"
   resolved "https://registry.yarnpkg.com/@capacitor/core/-/core-3.2.2.tgz#5926788920ba9117aa735d2941f5b2bdc4a6889a"
@@ -508,6 +535,13 @@
   dependencies:
     tslib "^2.1.0"
 
+"@capacitor/core@>= 3 < 4":
+  version "3.4.3"
+  resolved "https://registry.yarnpkg.com/@capacitor/core/-/core-3.4.3.tgz#3cf343b7035f1f6f9b73854dfb828600cd8a5e07"
+  integrity sha512-drfu0IiDMyeJtL4QALQgNFqdgN19DZJkbKh945eXyK44Sk2YkFXUs7Ewq1ZlVI30QF79mHGuM13oZeki0gv5Tw==
+  dependencies:
+    tslib "^2.1.0"
+
 "@capacitor/[email protected]":
   version "1.0.6"
   resolved "https://registry.yarnpkg.com/@capacitor/filesystem/-/filesystem-1.0.6.tgz#b837585e6b5d48dc705ee89e49cc7c6aeb8874ec"
@@ -5279,7 +5313,7 @@ nanomatch@^1.2.9:
     snapdragon "^0.8.1"
     to-regex "^3.0.1"
 
-native-run@^1.4.0:
+native-run@^1.4.0, native-run@^1.5.0:
   version "1.5.0"
   resolved "https://registry.yarnpkg.com/native-run/-/native-run-1.5.0.tgz#d0d04c802e5aeb9d5dc855b3df3ed26bf9f9d248"
   integrity sha512-YYsYYAljk70N+FJLeY5KuafsBit7agOBR3ch1KTXOaKkREkM49yCwJIwVz2faoWpJ2MeyLdXltk5PskUuPQqZA==
@@ -5584,7 +5618,7 @@ [email protected]:
     is-docker "^2.1.1"
     is-wsl "^2.2.0"
 
-open@^7.1.0:
+open@^7.1.0, open@^7.4.2:
   version "7.4.2"
   resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321"
   integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==
@@ -7219,6 +7253,15 @@ semver@^7.3.2, semver@^7.3.4:
   dependencies:
     lru-cache "^6.0.0"
 
[email protected]:
+  version "3.0.11"
+  resolved "https://registry.yarnpkg.com/send-intent/-/send-intent-3.0.11.tgz#85bf84b57e147a79a24429f8e51229339da36a78"
+  integrity sha512-v272/VBygddJdCz/Xk6o6Clh4NJMfCIzB0h8ZVqbnGvNeK1raCZ+LlXPYdkRw1zY5I0LiwEDudumWREd7AgzwA==
+  dependencies:
+    "-" "0.0.1"
+    "@capacitor/cli" ">= 3 < 4"
+    "@capacitor/core" ">= 3 < 4"
+
 serialize-error@^7.0.1:
   version "7.0.1"
   resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18"
@@ -7920,7 +7963,7 @@ [email protected]:
     resolve "^1.20.0"
     tmp "^0.2.1"
 
-tar@^6.0.5:
+tar@^6.0.5, tar@^6.1.11:
   version "6.1.11"
   resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621"
   integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==