diff --git a/android/app/build.gradle b/android/app/build.gradle index 1bc29bab0..f206bfe10 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -6,8 +6,8 @@ android { applicationId "com.logseq.app" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 59 - versionName "0.9.6" + versionCode 60 + versionName "0.9.7" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" aaptOptions { // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. diff --git a/android/app/src/main/java/com/logseq/app/FsWatcher.java b/android/app/src/main/java/com/logseq/app/FsWatcher.java index 3b07e9b5d..9d441c9e7 100644 --- a/android/app/src/main/java/com/logseq/app/FsWatcher.java +++ b/android/app/src/main/java/com/logseq/app/FsWatcher.java @@ -92,10 +92,23 @@ public class FsWatcher extends Plugin { shouldRead = true; } - URI dir = (new File(mPath)).toURI(); - URI fpath = f.toURI(); + Uri dir = Uri.fromFile(new File(mPath)); + Uri fpath = Uri.fromFile(f); + String relpath = null; - obj.put("path", Normalizer.normalize(dir.relativize(fpath).toString(), Normalizer.Form.NFC)); + if (fpath.getPath().startsWith(dir.getPath())) { + relpath = fpath.getPath().substring(dir.getPath().length()); + if (relpath.startsWith("/")) { + relpath = relpath.substring(1); + } + relpath = Uri.decode(relpath); + } else { + Log.e("FsWatcher", "file path not under watch path"); + return; + } + + + obj.put("path", Normalizer.normalize(relpath, Normalizer.Form.NFC)); obj.put("dir", Uri.fromFile(new File(mPath))); // Uri is for Android. URI is for RFC compatible JSObject stat; diff --git a/ios/App/App.xcodeproj/project.pbxproj b/ios/App/App.xcodeproj/project.pbxproj index 2893ab95c..db79ee721 100644 --- a/ios/App/App.xcodeproj/project.pbxproj +++ b/ios/App/App.xcodeproj/project.pbxproj @@ -27,6 +27,7 @@ D3D62A0C275C928F0003FBDC /* FileContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = D3D62A0B275C928F0003FBDC /* FileContainer.m */; }; FE647FF427BDFEDE00F3206B /* FsWatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE647FF327BDFEDE00F3206B /* FsWatcher.swift */; }; FE647FF627BDFEF500F3206B /* FsWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = FE647FF527BDFEF500F3206B /* FsWatcher.m */; }; + FE96D6102A1B811A001ECE32 /* SharedData.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE96D60F2A1B811A001ECE32 /* SharedData.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -83,6 +84,7 @@ 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 = ""; }; FE647FF327BDFEDE00F3206B /* FsWatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FsWatcher.swift; sourceTree = ""; }; FE647FF527BDFEF500F3206B /* FsWatcher.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FsWatcher.m; sourceTree = ""; }; + FE96D60F2A1B811A001ECE32 /* SharedData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedData.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -156,6 +158,7 @@ children = ( 5FFF7D7927E4E70700B00DA8 /* ShareViewController.entitlements */, 5FFF7D6C27E343FA00B00DA8 /* ShareViewController.swift */, + FE96D60F2A1B811A001ECE32 /* SharedData.swift */, 5FFF7D6E27E343FA00B00DA8 /* MainInterface.storyboard */, 5FFF7D7127E343FA00B00DA8 /* Info.plist */, ); @@ -345,6 +348,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + FE96D6102A1B811A001ECE32 /* SharedData.swift in Sources */, 5FFF7D6D27E343FA00B00DA8 /* ShareViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -438,7 +442,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -492,7 +496,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; @@ -515,7 +519,7 @@ INFOPLIST_FILE = App/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 0.9.6; + MARKETING_VERSION = 0.9.7; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -542,7 +546,7 @@ INFOPLIST_FILE = App/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 0.9.6; + MARKETING_VERSION = 0.9.7; PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = ""; @@ -565,9 +569,9 @@ INFOPLIST_FILE = ShareViewController/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = ShareViewController; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 0.9.6; + MARKETING_VERSION = 0.9.7; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq.ShareViewController; @@ -592,9 +596,9 @@ INFOPLIST_FILE = ShareViewController/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = ShareViewController; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 0.9.6; + MARKETING_VERSION = 0.9.7; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq.ShareViewController; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/ios/App/ShareViewController/Info.plist b/ios/App/ShareViewController/Info.plist index 6e05b6afa..ae080feea 100644 --- a/ios/App/ShareViewController/Info.plist +++ b/ios/App/ShareViewController/Info.plist @@ -8,6 +8,8 @@ NSExtensionActivationRule + NSExtensionActivationDictionaryVersion + 2 NSExtensionActivationSupportsFileWithMaxCount 5 NSExtensionActivationSupportsImageWithMaxCount @@ -17,9 +19,9 @@ NSExtensionActivationSupportsText NSExtensionActivationSupportsWebPageWithMaxCount - 1 + 3 NSExtensionActivationSupportsWebURLWithMaxCount - 1 + 3 NSExtensionActivationUsesStrictMatching diff --git a/ios/App/ShareViewController/ShareViewController.swift b/ios/App/ShareViewController/ShareViewController.swift index 44d824f8a..79d1b0845 100644 --- a/ios/App/ShareViewController/ShareViewController.swift +++ b/ios/App/ShareViewController/ShareViewController.swift @@ -9,182 +9,180 @@ import MobileCoreServices import Social import UIKit - -class ShareItem { - public var title: String? - public var type: String? - public var url: String? -} +import UniformTypeIdentifiers class ShareViewController: UIViewController { - - private var shareItems: [ShareItem] = [] - + + private var sharedData: SharedData = SharedData.init(resources: []) + 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) + super.viewDidAppear(animated) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { + self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil) + } } - + private func sendData() { - let queryItems = shareItems.map { + let encoder: JSONEncoder = JSONEncoder() + let data = try? encoder.encode(self.sharedData) + let queryPayload = String(decoding: data!, as: UTF8.self) + + let queryItems = [ 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) ?? ""), + name: "payload", + value: queryPayload.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? ""), ] - }.flatMap({ $0 }) var urlComps = URLComponents(string: "logseq://shared?")! urlComps.queryItems = queryItems openURL(urlComps.url!) } - - fileprivate func createSharedFileUrl(_ url: URL?) -> String { - - let copyFileUrl = groupContainerUrl!.absoluteString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)! + "/" + url! + + fileprivate func createSharedFileUrl(_ url: URL?) -> URL? { + let tempFilename = url! .lastPathComponent.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)! - try? Data(contentsOf: url!).write(to: URL(string: copyFileUrl)!) - + let copyFileUrl = groupContainerUrl!.appendingPathComponent(tempFilename) + try? Data(contentsOf: url!).write(to: copyFileUrl) return copyFileUrl } - - func saveScreenshot(_ image: UIImage) -> String { - + + // Screenshots, shared images from some system App are passed as UIImage + func saveUIImage(_ image: UIImage) -> URL? { let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd-HH-mm-ss" - - let copyFileUrl = groupContainerUrl!.absoluteString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)! - + dateFormatter.string(from: Date()) + ".png" - + let filename = dateFormatter.string(from: Date()) + ".png" + + let copyFileUrl = groupContainerUrl!.appendingPathComponent(filename) + do { - try image.pngData()?.write(to: URL(string: copyFileUrl)!) + try image.pngData()?.write(to: copyFileUrl) return copyFileUrl } catch { print(error.localizedDescription) - return "" + return nil } } - + + // Can be a path or a web URL fileprivate func handleTypeUrl(_ attachment: NSItemProvider) - async throws -> ShareItem + async throws -> SharedResource { let results = try await attachment.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil) let url = results as! URL? - let shareItem: ShareItem = ShareItem() - + + var res = SharedResource() + if url!.isFileURL { - shareItem.title = url!.lastPathComponent - shareItem.type = "application/" + url!.pathExtension.lowercased() - shareItem.url = createSharedFileUrl(url) + res.name = url!.lastPathComponent + res.ext = url!.pathExtension + res.type = url!.pathExtensionAsMimeType() + res.url = createSharedFileUrl(url) } else { - shareItem.title = url!.absoluteString - shareItem.url = url!.absoluteString - shareItem.type = "text/plain" + res.name = url!.absoluteString + res.type = "text/plain" } - - return shareItem + + return res } - + fileprivate func handleTypeText(_ attachment: NSItemProvider) - async throws -> ShareItem + async throws -> SharedResource? { - 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 + let item = try await attachment.loadItem(forTypeIdentifier: kUTTypeText as String, options: nil) + self.sharedData.text = item as? String + return nil } - + fileprivate func handleTypeMovie(_ attachment: NSItemProvider) - async throws -> ShareItem + async throws -> SharedResource { 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 + + let name = url!.lastPathComponent + let ext = url!.pathExtension.lowercased() + let type = url!.pathExtensionAsMimeType() + let sharedUrl = createSharedFileUrl(url) + + let res = SharedResource(name: name, ext: ext, type: type, url: sharedUrl) + + return res } - + fileprivate func handleTypeImage(_ attachment: NSItemProvider) - async throws -> ShareItem + async throws -> SharedResource { let data = try await attachment.loadItem(forTypeIdentifier: kUTTypeImage as String, options: nil) - - let shareItem: ShareItem = ShareItem() + + var res = SharedResource() + switch data { case let image as UIImage: - shareItem.title = "screenshot" - shareItem.type = "image/png" - shareItem.url = self.saveScreenshot(image) + res.url = self.saveUIImage(image) + res.ext = "png" + res.name = res.url?.lastPathComponent + res.type = res.url?.pathExtensionAsMimeType() case let url as URL: - shareItem.title = url.lastPathComponent - shareItem.type = "image/" + url.pathExtension.lowercased() - shareItem.url = self.createSharedFileUrl(url) + res.name = url.lastPathComponent + res.ext = url.pathExtension.lowercased() + res.type = url.pathExtensionAsMimeType() + res.url = self.createSharedFileUrl(url) default: print("Unexpected image data:", type(of: data)) } - - return shareItem + + return res } - - + + override public func viewDidLoad() { super.viewDidLoad() - - shareItems.removeAll() - - let extensionItem = extensionContext?.inputItems.first as! NSExtensionItem + + sharedData.empty() + let inputItems = extensionContext?.inputItems as! [NSExtensionItem] Task { try await withThrowingTaskGroup( - of: ShareItem.self, + of: SharedResource?.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 extensionItem in inputItems { + 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) + if let item = item { + self.sharedData.resources.append(item) + } } }) - + self.sendData() - + } } - + @discardableResult @objc func openURL(_ url: URL) -> Bool { var responder: UIResponder? = self @@ -196,6 +194,14 @@ class ShareViewController: UIViewController { } return false } - + + +} + +extension URL { + func pathExtensionAsMimeType() -> String? { + let type = UTType(filenameExtension: self.pathExtension) + return type?.preferredMIMEType + } } diff --git a/ios/App/ShareViewController/SharedData.swift b/ios/App/ShareViewController/SharedData.swift new file mode 100644 index 000000000..9bfe18402 --- /dev/null +++ b/ios/App/ShareViewController/SharedData.swift @@ -0,0 +1,25 @@ +// +// SharedData.swift +// ShareViewController +// +// Created by Mono Wang on 5/22/23. +// + +import Foundation + +public struct SharedResource: Decodable, Encodable { + var name: String? + var ext: String? + var type: String? + var url: URL? +} + +public struct SharedData: Decodable, Encodable { + var text: String? + var resources: [SharedResource] + + mutating func empty() { + text = nil + resources = [] + } +} diff --git a/package.json b/package.json index 21c0ffe44..3ae0bac8c 100644 --- a/package.json +++ b/package.json @@ -122,7 +122,7 @@ "path-complete-extname": "1.0.0", "pixi-graph-fork": "0.2.0", "pixi.js": "6.2.0", - "posthog-js": "1.10.2", + "posthog-js": "1.57.2", "react": "17.0.2", "react-dom": "17.0.2", "react-grid-layout": "0.16.6", diff --git a/resources/package.json b/resources/package.json index 75ad337bf..515a0a30e 100644 --- a/resources/package.json +++ b/resources/package.json @@ -1,7 +1,7 @@ { "name": "Logseq", "productName": "Logseq", - "version": "0.9.6", + "version": "0.9.7", "main": "electron.js", "author": "Logseq", "license": "AGPL-3.0", diff --git a/src/main/frontend/fs/sync.cljs b/src/main/frontend/fs/sync.cljs index c332f4a57..c668222d9 100644 --- a/src/main/frontend/fs/sync.cljs +++ b/src/main/frontend/fs/sync.cljs @@ -1777,7 +1777,7 @@ (when (sync-state--valid-to-accept-filewatcher-event? sync-state) (when (or (:mtime stat) (= type "unlink")) (go - (let [path (path-normalize (remove-dir-prefix dir path)) + (let [path (path-normalize path) files-meta (and (not= "unlink" type) (> (util/get-blocks-noncollapse) (f))] (when block - (scroll-to-block block) + (util/scroll-to-block block) (state/exit-editing-and-set-selected-blocks! [block])))) (defn- select-up-down [direction] @@ -2509,7 +2508,7 @@ :down util/get-next-block-non-collapsed) sibling-block (f selected)] (when (and sibling-block (dom/attr sibling-block "blockid")) - (scroll-to-block sibling-block) + (util/scroll-to-block sibling-block) (state/exit-editing-and-set-selected-blocks! [sibling-block])))) (defn- move-cross-boundary-up-down diff --git a/src/main/frontend/handler/paste.cljs b/src/main/frontend/handler/paste.cljs index da8e61282..583db899f 100644 --- a/src/main/frontend/handler/paste.cljs +++ b/src/main/frontend/handler/paste.cljs @@ -233,12 +233,20 @@ (state/set-state! :editor/on-paste? true) (let [clipboard-data (gobj/get e "clipboardData") html (.getData clipboard-data "text/html") - text (.getData clipboard-data "text")] + text (.getData clipboard-data "text") + has-files? (seq (.-files clipboard-data))] (cond (and (string/blank? text) (string/blank? html)) + ;; When both text and html are blank, paste file if exists. + ;; NOTE: util/stop is not called here if no file is provided, + ;; so the default paste behavior of the native platform will be used. + (when has-files? + (paste-file-if-exists id e)) + + ;; both file attachment and text/html exist + (and has-files? (state/preferred-pasting-file?)) (paste-file-if-exists id e) - (and (seq (.-files clipboard-data)) (state/preferred-pasting-file?)) - (paste-file-if-exists id e) + :else (let [text' (or (when (gp-util/url? text) (wrap-macro-url text)) diff --git a/src/main/frontend/mobile/deeplink.cljs b/src/main/frontend/mobile/deeplink.cljs index f1f398cdb..57647355b 100644 --- a/src/main/frontend/mobile/deeplink.cljs +++ b/src/main/frontend/mobile/deeplink.cljs @@ -9,7 +9,8 @@ [frontend.handler.route :as route-handler] [frontend.mobile.intent :as intent] [frontend.state :as state] - [frontend.util.text :as text-util])) + [frontend.util.text :as text-util] + [logseq.graph-parser.util :as gp-util])) (def *link-to-another-graph (atom false)) @@ -70,8 +71,14 @@ (= hostname "shared") (let [result (into {} (map (fn [key] [(keyword key) (.get search-params key)]) - ["title" "url" "type"]))] - (intent/handle-result result)) + ["title" "url" "type" "payload"]))] + (if (:payload result) + (let [raw (gp-util/safe-decode-uri-component (:payload result)) + payload (-> raw + js/JSON.parse + (js->clj :keywordize-keys true))] + (intent/handle-payload payload)) + (intent/handle-result result))) :else nil))) diff --git a/src/main/frontend/mobile/intent.cljs b/src/main/frontend/mobile/intent.cljs index 36639972c..d44da915b 100644 --- a/src/main/frontend/mobile/intent.cljs +++ b/src/main/frontend/mobile/intent.cljs @@ -152,7 +152,92 @@ (gp-util/safe-decode-uri-component v) v))]))) -(defn handle-result [result] +(defn- handle-asset-file [url format] + (p/let [basename (node-path/basename url) + label (-> basename util/node-path.name) + path (editor-handler/get-asset-path basename) + _file (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-link (editor-handler/get-asset-file-link format url label true)] + url-link)) + +(defn- handle-payload-resource + [{:keys [type name ext url] :as resource} format] + (if url + (cond + (contains? (set/union config/doc-formats config/media-formats) + (keyword ext)) + (handle-asset-file url format) + + :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/new?labels=from:in-app&template=bug_report.yaml" + :target "_blank"} "Github"] + ". We will look into it soon." + [:pre.code (with-out-str (pprint/pprint resource))]] :warning false)) + + (cond + (= type "text/plain") + name + + :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/new?labels=from:in-app&template=bug_report.yaml" + :target "_blank"} "Github"] + ". We will look into it soon." + [:pre.code (with-out-str (pprint/pprint resource))]] :warning false)))) + +(defn handle-payload + "Mobile share intent handler v2, use complex payload to support more types of content." + [payload] + ;; use :text template, use {url} as rich text placeholder + (p/let [page (or (state/get-current-page) (string/lower-case (date/journal-name))) + format (db/get-page-format page) + + template (get-in (state/get-config) + [:quick-capture-templates :text] + "**{time}** [[quick capture]]: {text} {url}") + {:keys [text resources]} payload + text (or text "") + rich-content (-> (p/all (map (fn [resource] + (handle-payload-resource resource format)) + resources)) + (p/then (partial string/join "\n")))] + (when (or (not-empty text) (not-empty rich-content)) + (let [time (date/get-current-time) + date-ref-name (date/today) + content (-> template + (string/replace "{time}" time) + (string/replace "{date}" date-ref-name) + (string/replace "{text}" text) + (string/replace "{url}" rich-content)) + edit-content (state/get-edit-content) + edit-content-blank? (string/blank? edit-content) + edit-content-include-capture? (and (not-empty edit-content) + (string/includes? edit-content "[[quick capture]]"))] + (if (and (state/editing?) (not edit-content-include-capture?)) + (if edit-content-blank? + (editor-handler/insert content) + (editor-handler/insert (str "\n" content))) + + (do + (editor-handler/escape-editing) + (js/setTimeout #(editor-handler/api-insert-new-block! content {:page page + :edit-block? true + :replace-empty-target? true}) + 100))))))) + + +(defn handle-result + "Mobile share intent handler v1, legacy. Only for Android" + [result] (let [result (decode-received-result result)] (when-let [type (:type result)] (cond diff --git a/src/main/frontend/util.cljc b/src/main/frontend/util.cljc index eb49d51a9..eaae05720 100644 --- a/src/main/frontend/util.cljc +++ b/src/main/frontend/util.cljc @@ -405,6 +405,16 @@ %)) (take-while (complement nil?) (iterate #(.-parentElement %) element))))) +#?(:cljs + (defn element-visible? + [element] + (when element + (when-let [r (.getBoundingClientRect element)] + (and (>= (.-top r) 0) + (<= (+ (.-bottom r) 64) + (or (.-innerHeight js/window) + (js/document.documentElement.clientHeight)))))))) + #?(:cljs (defn element-top [elem top] (when elem @@ -455,6 +465,19 @@ ([animate?] (scroll-to (app-scroll-container-node) 0 animate?)))) +#?(:cljs + (defn scroll-to-block + "Scroll into the view to vertically align a non-visible block to the centre + of the visible area" + ([block] + (scroll-to-block block true)) + ([block animate?] + (when block + (when-not (element-visible? block) + (.scrollIntoView block + #js {:behavior (if animate? "smooth" "auto") + :block "center"})))))) + #?(:cljs (defn link? [node] @@ -1404,16 +1427,6 @@ (fn [resolve] (load url resolve))))) -#?(:cljs - (defn element-visible? - [element] - (when element - (when-let [r (.getBoundingClientRect element)] - (and (>= (.-top r) 0) - (<= (+ (.-bottom r) 64) - (or (.-innerHeight js/window) - (js/document.documentElement.clientHeight)))))))) - #?(:cljs (defn copy-image-to-clipboard [src] diff --git a/src/main/frontend/version.cljs b/src/main/frontend/version.cljs index 2b900d6c5..ca95340ba 100644 --- a/src/main/frontend/version.cljs +++ b/src/main/frontend/version.cljs @@ -1,3 +1,3 @@ (ns ^:no-doc frontend.version) -(defonce version "0.9.6") +(defonce version "0.9.7") diff --git a/yarn.lock b/yarn.lock index feb45f1dd..809ff1d57 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5807,12 +5807,13 @@ postcss@^8.2.1: picocolors "^1.0.0" source-map-js "^1.0.2" -posthog-js@1.10.2: - version "1.10.2" - resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.10.2.tgz#74d6c84f9675b65dfd4ff6f4051ed8d3cb974076" - integrity sha512-JNjWstHEexhj5CEKldSeYNyPJbtOvZQ3ZPL55fxU7+f+gTBL8RlOb8eFohCPYIk0VhMf2UM1rXxwVBOeMQQQFw== +posthog-js@1.57.2: + version "1.57.2" + resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.57.2.tgz#131fb93e2ad099baff4317f3d91a4d6c96a08e7f" + integrity sha512-ER4gkYZasrd2Zwmt/yLeZ5G/nZJ6tpaYBCpx3CvocDx+3F16WdawJlYMT0IyLKHXDniC5+AsjzFd6fi8uyYlJA== dependencies: fflate "^0.4.1" + rrweb-snapshot "^1.1.14" prepend-http@^2.0.0: version "2.0.0" @@ -6384,6 +6385,11 @@ roarr@^2.15.3: semver-compare "^1.0.0" sprintf-js "^1.1.2" +rrweb-snapshot@^1.1.14: + version "1.1.14" + resolved "https://registry.yarnpkg.com/rrweb-snapshot/-/rrweb-snapshot-1.1.14.tgz#9d4d9be54a28a893373428ee4393ec7e5bd83fcc" + integrity sha512-eP5pirNjP5+GewQfcOQY4uBiDnpqxNRc65yKPW0eSoU1XamDfc4M8oqpXGMyUyvLyxFDB0q0+DChuxxiU2FXBQ== + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"