From 1ce4697b22f396ea72ddeecf4d688dcafadea5e2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 May 2023 20:35:37 +0000 Subject: [PATCH 01/13] chore(deps): bump posthog-js from 1.10.2 to 1.57.2 Bumps [posthog-js](https://github.com/PostHog/posthog-js) from 1.10.2 to 1.57.2. - [Release notes](https://github.com/PostHog/posthog-js/releases) - [Changelog](https://github.com/PostHog/posthog-js/blob/master/CHANGELOG.md) - [Commits](https://github.com/PostHog/posthog-js/compare/v1.10.2...v1.57.2) --- updated-dependencies: - dependency-name: posthog-js dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 21c0ffe44c..3ae0bac8c9 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/yarn.lock b/yarn.lock index feb45f1dd6..809ff1d578 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" From 7f0457f23f5422ba058d7c03e70a1ac816af222d Mon Sep 17 00:00:00 2001 From: Andelf Date: Mon, 22 May 2023 12:02:05 +0800 Subject: [PATCH 02/13] fix(android): apply url decode before fs watcher notifies --- .../main/java/com/logseq/app/FsWatcher.java | 19 ++++++++++++++++--- src/main/frontend/fs/sync.cljs | 2 +- 2 files changed, 17 insertions(+), 4 deletions(-) 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 3b07e9b5d9..9d441c9e78 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/src/main/frontend/fs/sync.cljs b/src/main/frontend/fs/sync.cljs index c332f4a576..c668222d91 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) ( Date: Tue, 23 May 2023 11:57:41 +0200 Subject: [PATCH 03/13] fix: scroll the view when selecting multiple blocks (#9444) --- src/main/frontend/handler/editor.cljs | 29 ++++++++++++----------- src/main/frontend/util.cljc | 33 +++++++++++++++++++-------- 2 files changed, 37 insertions(+), 25 deletions(-) diff --git a/src/main/frontend/handler/editor.cljs b/src/main/frontend/handler/editor.cljs index c91b398e60..3ef23e4361 100644 --- a/src/main/frontend/handler/editor.cljs +++ b/src/main/frontend/handler/editor.cljs @@ -1193,10 +1193,6 @@ (common-handler/copy-to-clipboard-without-id-property! (:block/format block) md-content html sorted-blocks) (delete-block-aux! block true)))) -(defn clear-last-selected-block! - [] - (state/drop-last-selection-block!)) - (defn highlight-selection-area! [end-block] (when-let [start-block (state/get-selection-start-block-or-first)] @@ -1212,13 +1208,17 @@ (cond ;; when editing, quit editing and select current block (state/editing?) - (state/exit-editing-and-set-selected-blocks! [(gdom/getElement (state/get-editing-block-dom-id))]) + (let [element (gdom/getElement (state/get-editing-block-dom-id))] + (when element + (util/scroll-to-block element) + (state/exit-editing-and-set-selected-blocks! [element]))) ;; when selection and one block selected, select next block (and (state/selection?) (== 1 (count (state/get-selection-blocks)))) (let [f (if (= :up direction) util/get-prev-block-non-collapsed util/get-next-block-non-collapsed-skip) element (f (first (state/get-selection-blocks)))] (when element + (util/scroll-to-block element) (state/conj-selection-block! element direction))) ;; if same direction, keep conj on same direction @@ -1227,11 +1227,17 @@ first-last (if (= :up direction) first last) element (f (first-last (state/get-selection-blocks)))] (when element + (util/scroll-to-block element) (state/conj-selection-block! element direction))) ;; if different direction, keep clear until one left (state/selection?) - (clear-last-selected-block!)) + (let [f (if (= :up direction) util/get-prev-block-non-collapsed util/get-next-block-non-collapsed) + last-first (if (= :up direction) last first) + element (f (last-first (state/get-selection-blocks)))] + (when element + (util/scroll-to-block element) + (state/drop-last-selection-block!)))) nil) (defn on-select-block @@ -2482,13 +2488,6 @@ (.preventDefault e) (keydown-new-line)))) -(defn- scroll-to-block - [block] - (when block - (when-not (util/element-visible? block) - (.scrollIntoView block #js {:behavior "smooth" - :block "center"})))) - (defn- select-first-last "Select first or last block in viewpoint" [direction] @@ -2496,7 +2495,7 @@ block (->> (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/util.cljc b/src/main/frontend/util.cljc index eb49d51a95..eaae057209 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] From 8ec5ea7a5063490a3fcabd3dfdb4e63adabdbe76 Mon Sep 17 00:00:00 2001 From: Andelf Date: Tue, 23 May 2023 10:02:48 +0800 Subject: [PATCH 04/13] fix(ios): no handling of uri pasting Fix #9433 --- src/main/frontend/handler/paste.cljs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/frontend/handler/paste.cljs b/src/main/frontend/handler/paste.cljs index da8e612821..583db899ff 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)) From 016fd2b519d4909fe0fc6ade2ff39b4c554aa611 Mon Sep 17 00:00:00 2001 From: Andelf Date: Mon, 22 May 2023 20:32:29 +0800 Subject: [PATCH 05/13] refactor(ios): new share intent implementation --- ios/App/App.xcodeproj/project.pbxproj | 12 +- ios/App/ShareViewController/Info.plist | 6 +- .../ShareViewController.swift | 228 +++++++++--------- ios/App/ShareViewController/SharedData.swift | 25 ++ src/main/frontend/mobile/deeplink.cljs | 13 +- src/main/frontend/mobile/intent.cljs | 87 ++++++- 6 files changed, 250 insertions(+), 121 deletions(-) create mode 100644 ios/App/ShareViewController/SharedData.swift diff --git a/ios/App/App.xcodeproj/project.pbxproj b/ios/App/App.xcodeproj/project.pbxproj index 2893ab95c3..e56b7e2231 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"; @@ -565,7 +569,7 @@ 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; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; @@ -592,7 +596,7 @@ 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; MTL_FAST_MATH = YES; diff --git a/ios/App/ShareViewController/Info.plist b/ios/App/ShareViewController/Info.plist index 6e05b6afa8..ae080feea2 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 44d824f8a9..79d1b0845c 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 0000000000..9bfe184022 --- /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/src/main/frontend/mobile/deeplink.cljs b/src/main/frontend/mobile/deeplink.cljs index f1f398cdbc..57647355bc 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 36639972c8..d44da915b5 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 From 00a304e49cf9c6f08010dff24f6c841389234801 Mon Sep 17 00:00:00 2001 From: Andelf Date: Tue, 23 May 2023 17:55:27 +0800 Subject: [PATCH 06/13] chore(release): bump version 0.9.7 --- android/app/build.gradle | 4 ++-- ios/App/App.xcodeproj/project.pbxproj | 8 ++++---- resources/package.json | 2 +- src/main/frontend/version.cljs | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 1bc29bab0f..f206bfe108 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/ios/App/App.xcodeproj/project.pbxproj b/ios/App/App.xcodeproj/project.pbxproj index e56b7e2231..db79ee7212 100644 --- a/ios/App/App.xcodeproj/project.pbxproj +++ b/ios/App/App.xcodeproj/project.pbxproj @@ -519,7 +519,7 @@ INFOPLIST_FILE = App/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 0.9.6; + MARKETING_VERSION = 0.9.7; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -546,7 +546,7 @@ INFOPLIST_FILE = App/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 0.9.6; + MARKETING_VERSION = 0.9.7; PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = ""; @@ -571,7 +571,7 @@ INFOPLIST_KEY_NSHumanReadableCopyright = ""; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 0.9.6; + MARKETING_VERSION = 0.9.7; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq.ShareViewController; @@ -598,7 +598,7 @@ INFOPLIST_KEY_NSHumanReadableCopyright = ""; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 0.9.6; + MARKETING_VERSION = 0.9.7; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq.ShareViewController; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/resources/package.json b/resources/package.json index 75ad337bf9..515a0a30ed 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/version.cljs b/src/main/frontend/version.cljs index 2b900d6c56..ca95340bab 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") From b799170d21e318b4885c422cee7f1a3573caba30 Mon Sep 17 00:00:00 2001 From: Andelf Date: Wed, 24 May 2023 02:34:57 +0800 Subject: [PATCH 07/13] Revert "chore(deps): bump posthog-js from 1.10.2 to 1.57.2" This reverts commit 1ce4697b22f396ea72ddeecf4d688dcafadea5e2. --- package.json | 2 +- yarn.lock | 14 ++++---------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 3ae0bac8c9..21c0ffe44c 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.57.2", + "posthog-js": "1.10.2", "react": "17.0.2", "react-dom": "17.0.2", "react-grid-layout": "0.16.6", diff --git a/yarn.lock b/yarn.lock index 809ff1d578..feb45f1dd6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5807,13 +5807,12 @@ postcss@^8.2.1: picocolors "^1.0.0" source-map-js "^1.0.2" -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== +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== dependencies: fflate "^0.4.1" - rrweb-snapshot "^1.1.14" prepend-http@^2.0.0: version "2.0.0" @@ -6385,11 +6384,6 @@ 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" From 82cf4d3c65acbf230a3170640fe271dd74095067 Mon Sep 17 00:00:00 2001 From: Andelf Date: Wed, 24 May 2023 02:39:49 +0800 Subject: [PATCH 08/13] chore(release): bump version 0.9.8 --- android/app/build.gradle | 4 ++-- ios/App/App.xcodeproj/project.pbxproj | 8 ++++---- resources/package.json | 2 +- src/main/frontend/version.cljs | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index f206bfe108..cec721ad49 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 60 - versionName "0.9.7" + versionCode 61 + versionName "0.9.8" 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/ios/App/App.xcodeproj/project.pbxproj b/ios/App/App.xcodeproj/project.pbxproj index db79ee7212..9aab440474 100644 --- a/ios/App/App.xcodeproj/project.pbxproj +++ b/ios/App/App.xcodeproj/project.pbxproj @@ -519,7 +519,7 @@ INFOPLIST_FILE = App/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 0.9.7; + MARKETING_VERSION = 0.9.8; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -546,7 +546,7 @@ INFOPLIST_FILE = App/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 0.9.7; + MARKETING_VERSION = 0.9.8; PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = ""; @@ -571,7 +571,7 @@ INFOPLIST_KEY_NSHumanReadableCopyright = ""; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 0.9.7; + MARKETING_VERSION = 0.9.8; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq.ShareViewController; @@ -598,7 +598,7 @@ INFOPLIST_KEY_NSHumanReadableCopyright = ""; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 0.9.7; + MARKETING_VERSION = 0.9.8; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq.ShareViewController; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/resources/package.json b/resources/package.json index 515a0a30ed..28ff89818d 100644 --- a/resources/package.json +++ b/resources/package.json @@ -1,7 +1,7 @@ { "name": "Logseq", "productName": "Logseq", - "version": "0.9.7", + "version": "0.9.8", "main": "electron.js", "author": "Logseq", "license": "AGPL-3.0", diff --git a/src/main/frontend/version.cljs b/src/main/frontend/version.cljs index ca95340bab..c7942737be 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.7") +(defonce version "0.9.8") From 29b4936e5cc3f6a01e915a6453bf0b4b146010ca Mon Sep 17 00:00:00 2001 From: Bad3r Date: Wed, 24 May 2023 05:19:58 +0000 Subject: [PATCH 09/13] enhance(E2E): editor string formatting and symbols autopairing tests (#9388) * enhance: only autopair tilda when text is selected * Enhance/editor string formatting e2e (#48) * enhance(E2E): Test editor italic, bold, strikethrough and underline. ... --------- Signed-off-by: Bad3r --- e2e-tests/editor.spec.ts | 200 ++++++++++++++++++++++++++++++++++++++- e2e-tests/utils.ts | 65 +++++++++++++ 2 files changed, 264 insertions(+), 1 deletion(-) diff --git a/e2e-tests/editor.spec.ts b/e2e-tests/editor.spec.ts index d680ae5912..a79d0cdd88 100644 --- a/e2e-tests/editor.spec.ts +++ b/e2e-tests/editor.spec.ts @@ -1,6 +1,15 @@ import { expect } from '@playwright/test' import { test } from './fixtures' -import { createRandomPage, enterNextBlock, modKey } from './utils' +import { + createRandomPage, + enterNextBlock, + modKey, + repeatKeyPress, + moveCursor, + selectCharacters, + getSelection, + getCursorPos, +} from './utils' import { dispatch_kb_events } from './util/keyboard-events' import * as kb_events from './util/keyboard-events' @@ -619,3 +628,192 @@ test('should keep correct undo and redo seq after indenting or outdenting the bl await page.keyboard.press(modKey + '+Shift+z') await expect(page.locator('textarea >> nth=0')).toHaveText("aaa bbb") }) + +test.describe('Text Formatting', () => { + const formats = [ + { name: 'bold', prefix: '**', postfix: '**', shortcut: modKey + '+b' }, + { name: 'italic', prefix: '*', postfix: '*', shortcut: modKey + '+i' }, + { + name: 'strikethrough', + prefix: '~~', + postfix: '~~', + shortcut: modKey + '+Shift+s', + }, + // { + // name: 'underline', + // prefix: '', + // postfix: '', + // shortcut: modKey + '+u', + // }, + ] + + for (const format of formats) { + test.describe(`${format.name} formatting`, () => { + test('Applying to an empty selection inserts placeholder formatting and places cursor correctly', async ({ + page, + block, + }) => { + await createRandomPage(page) + + const text = 'Lorem ipsum' + await block.mustFill(text) + + // move the cursor to the end of Lorem + await repeatKeyPress(page, 'ArrowLeft', text.length - 'ipsum'.length) + await page.keyboard.press('Space') + + // Apply formatting + await page.keyboard.press(format.shortcut) + + await expect(page.locator('textarea >> nth=0')).toHaveText( + `Lorem ${format.prefix}${format.postfix} ipsum` + ) + + // Verify cursor position + const cursorPos = await getCursorPos(page) + expect(cursorPos).toBe(' ipsum'.length + format.prefix.length) + }) + + test('Applying to an entire block encloses the block in formatting and places cursor correctly', async ({ + page, + block, + }) => { + await createRandomPage(page) + + const text = 'Lorem ipsum-dolor sit.' + await block.mustFill(text) + + // Select the entire block + await page.keyboard.press(modKey + '+a') + + // Apply formatting + await page.keyboard.press(format.shortcut) + + await expect(page.locator('textarea >> nth=0')).toHaveText( + `${format.prefix}${text}${format.postfix}` + ) + + // Verify cursor position + const cursorPosition = await getCursorPos(page) + expect(cursorPosition).toBe(format.prefix.length + text.length) + }) + + test('Applying and then removing from a word connected with a special character correctly formats and then reverts', async ({ + page, + block, + }) => { + await createRandomPage(page) + + await block.mustFill('Lorem ipsum-dolor sit.') + + // Select 'ipsum' + // Move the cursor to the desired position + await moveCursor(page, -16) + + // Select the desired length of text + await selectCharacters(page, 5) + + // Apply formatting + await page.keyboard.press(format.shortcut) + + // Verify that 'ipsum' is formatted + await expect(page.locator('textarea >> nth=0')).toHaveText( + `Lorem ${format.prefix}ipsum${format.postfix}-dolor sit.` + ) + + // Re-select 'ipsum' + // Move the cursor to the desired position + await moveCursor(page, -5) + + // Select the desired length of text + await selectCharacters(page, 5) + + // Remove formatting + await page.keyboard.press(format.shortcut) + await expect(page.locator('textarea >> nth=0')).toHaveText( + 'Lorem ipsum-dolor sit.' + ) + + // Verify the word 'ipsum' is still selected + const selection = await getSelection(page) + expect(selection).toBe('ipsum') + }) + }) + } +}) + +test.describe('Always auto-pair symbols', () => { + // Define the symbols that should be auto-paired + const autoPairSymbols = [ + { name: 'square brackets', prefix: '[', postfix: ']' }, + { name: 'curly brackets', prefix: '{', postfix: '}' }, + { name: 'parentheses', prefix: '(', postfix: ')' }, + // { name: 'angle brackets', prefix: '<', postfix: '>' }, + { name: 'backtick', prefix: '`', postfix: '`' }, + // { name: 'single quote', prefix: "'", postfix: "'" }, + // { name: 'double quote', prefix: '"', postfix: '"' }, + ] + + for (const symbol of autoPairSymbols) { + test(`${symbol.name} auto-pairing`, async ({ page }) => { + await createRandomPage(page) + + // Type prefix and check that the postfix is automatically added + page.type('textarea >> nth=0', symbol.prefix, { delay: 100 }) + await expect(page.locator('textarea >> nth=0')).toHaveText( + `${symbol.prefix}${symbol.postfix}` + ) + + // Check that the cursor is positioned correctly between the prefix and postfix + const CursorPos = await getCursorPos(page) + expect(CursorPos).toBe(symbol.prefix.length) + }) + } +}) + +test.describe('Auto-pair symbols only with text selection', () => { + const autoPairSymbols = [ + // { name: 'tilde', prefix: '~', postfix: '~' }, + { name: 'asterisk', prefix: '*', postfix: '*' }, + { name: 'underscore', prefix: '_', postfix: '_' }, + { name: 'caret', prefix: '^', postfix: '^' }, + { name: 'equal', prefix: '=', postfix: '=' }, + { name: 'slash', prefix: '/', postfix: '/' }, + { name: 'plus', prefix: '+', postfix: '+' }, + ] + + for (const symbol of autoPairSymbols) { + test(`Only auto-pair ${symbol.name} with text selection`, async ({ + page, + block, + }) => { + await createRandomPage(page) + + // type the symbol + page.type('textarea >> nth=0', symbol.prefix, { delay: 100 }) + + // Verify that there is no auto-pairing + await expect(page.locator('textarea >> nth=0')).toHaveText(symbol.prefix) + + // remove prefix + await page.keyboard.press('Backspace') + + // add text + await block.mustType('Lorem') + // select text + await page.keyboard.press(modKey + '+a') + + // Type the prefix + await page.type('textarea >> nth=0', symbol.prefix, { delay: 100 }) + + // Verify that an additional postfix was automatically added around 'Lorem' + await expect(page.locator('textarea >> nth=0')).toHaveText( + `${symbol.prefix}Lorem${symbol.postfix}` + ) + + // Verify 'Lorem' is selected + const selection = await getSelection(page) + expect(selection).toBe('Lorem') + }) + } +}) \ No newline at end of file diff --git a/e2e-tests/utils.ts b/e2e-tests/utils.ts index e61023a8aa..22472a74b3 100644 --- a/e2e-tests/utils.ts +++ b/e2e-tests/utils.ts @@ -237,3 +237,68 @@ export async function navigateToStartOfBlock(page: Page, block: Block) { await page.keyboard.press('ArrowLeft') } } + +/** + * Repeats a key press a certain number of times. + * @param {Page} page - The Page object. + * @param {string} key - The key to press. + * @param {number} times - The number of times to press the key. + * @return {Promise} - Promise which resolves when the key press repetition is done. + */ +export async function repeatKeyPress(page: Page, key: string, times: number): Promise { + for (let i = 0; i < times; i++) { + await page.keyboard.press(key); + } +} + +/** + * Moves the cursor a certain number of characters to the right (positive value) or left (negative value). + * @param {Page} page - The Page object. + * @param {number} shift - The number of characters to move the cursor. Positive moves to the right, negative to the left. + * @return {Promise} - Promise which resolves when the cursor has moved. + */ +export async function moveCursor(page: Page, shift: number): Promise { + const direction = shift < 0 ? 'ArrowLeft' : 'ArrowRight'; + const absShift = Math.abs(shift); + await repeatKeyPress(page, direction, absShift); +} + +/** + * Selects a certain length of text in a textarea to the right of the cursor. + * @param {Page} page - The Page object. + * @param {number} length - The number of characters to select. + * @return {Promise} - Promise which resolves when the text selection is done. + */ +export async function selectCharacters(page: Page, length: number): Promise { + await page.keyboard.down('Shift'); + await repeatKeyPress(page, 'ArrowRight', length); + await page.keyboard.up('Shift'); +} + +/** + * Retrieves the selected text in a textarea. + * @param {Page} page - The page object. + * @return {Promise} - Promise which resolves to the selected text or null. + */ +export async function getSelection(page: Page): Promise { + const selection = await page.evaluate(() => { + const textarea = document.querySelector('textarea') + return textarea?.value.substring(textarea.selectionStart, textarea.selectionEnd) || null + }) + + return selection +} + +/** + * Retrieves the current cursor position in a textarea. + * @param {Page} page - The page object. + * @return {Promise} - Promise which resolves to the cursor position or null. + */ +export async function getCursorPos(page: Page): Promise { + const cursorPosition = await page.evaluate(() => { + const textarea = document.querySelector('textarea'); + return textarea ? textarea.selectionStart : null; + }); + + return cursorPosition; +} From 1c7ef7ed2ad76c1c091661b45c6388abfd33f134 Mon Sep 17 00:00:00 2001 From: Andelf Date: Wed, 24 May 2023 21:34:51 +0800 Subject: [PATCH 10/13] ci: run e2e when releasing (#9478) --- .github/workflows/build-desktop-release.yml | 44 ++++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-desktop-release.yml b/.github/workflows/build-desktop-release.yml index 6fa9f8417e..b856e1ef34 100644 --- a/.github/workflows/build-desktop-release.yml +++ b/.github/workflows/build-desktop-release.yml @@ -171,6 +171,46 @@ jobs: name: static path: static + e2e-test: + name: E2E Test Shard ${{ matrix.shard }} + runs-on: ubuntu-latest + strategy: + matrix: + shard: [1, 2, 3] + needs: [ compile-cljs ] + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Download The Static Asset + uses: actions/download-artifact@v3 + with: + name: static + path: static + + - name: Set up Node + uses: actions/setup-node@v3 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'yarn' + cache-dependency-path: | + yarn.lock + static/yarn.lock + + - name: Fetch yarn deps for E2E test + run: | + yarn install + (cd static && yarn install && yarn rebuild:all) + env: + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: true + + - name: Run Playwright test + run: xvfb-run -- npx playwright test --reporter github --shard=${{ matrix.shard }}/3 + env: + LOGSEQ_CI: true + DEBUG: "pw:api" + RELEASE: true # skip dev only test + build-linux: runs-on: ubuntu-20.04 needs: [ compile-cljs ] @@ -436,7 +476,7 @@ jobs: nightly-release: if: ${{ github.event_name == 'schedule' || github.event.inputs.build-target == 'nightly' }} - needs: [ build-macos-x64, build-macos-arm64, build-linux, build-windows, build-android ] + needs: [ build-macos-x64, build-macos-arm64, build-linux, build-windows, build-android, e2e-test ] runs-on: ubuntu-20.04 steps: - name: Download MacOS x64 Artifacts @@ -503,7 +543,7 @@ jobs: release: # NOTE: For now, we only have beta channel to be released on Github if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.build-target == 'beta' }} - needs: [ build-macos-x64, build-macos-arm64, build-linux, build-windows ] + needs: [ build-macos-x64, build-macos-arm64, build-linux, build-windows, e2e-test ] runs-on: ubuntu-20.04 steps: - name: Download MacOS x64 Artifacts From e1486fa169dcc41b6e86b4781905b273dd7b526d Mon Sep 17 00:00:00 2001 From: level101 Date: Tue, 23 May 2023 07:20:48 +0200 Subject: [PATCH 11/13] main/frontend/dicts/ru: prepare translation, add all elements --- src/main/frontend/dicts/ru.cljc | 60 +++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/src/main/frontend/dicts/ru.cljc b/src/main/frontend/dicts/ru.cljc index b61c51833d..8700238825 100644 --- a/src/main/frontend/dicts/ru.cljc +++ b/src/main/frontend/dicts/ru.cljc @@ -65,6 +65,7 @@ :right-side-bar/switch-theme "Тема" :right-side-bar/contents "Содержание" :right-side-bar/page-graph "Граф страницы" + :right-side-bar/history "(Dev) Отменить/Повторить историю" :right-side-bar/block-ref "Ссылка на блок" :right-side-bar/graph-view "Визуальный граф" :right-side-bar/all-pages "Все страницы" @@ -151,10 +152,31 @@ :color/pink "Розовый" :editor/copy "Копировать" :editor/cut "Вырезать" + :editor/expand-block-children "Раскрыть всё" + :editor/collapse-block-children "Свернуть всё" + :editor/delete-selection "Удалить выбранные блоки" + :editor/cycle-todo "Изменить статус TODO для текущего элемента" + :dev/show-page-data "(Dev) Показать данные страницы" + :dev/show-block-data "(Dev) Показать данные блока" + :dev/show-block-ast "(Dev) Показать AST блока" + :dev/show-page-ast "(Dev) Показать AST страницы" + :content/copy-export-as "Копировать / Экспортировать как.." + :content/copy-block-url "Копировать URL блока" :content/copy-block-ref "Копировать ссылку блока" :content/copy-block-emebed "Копировать встроенный блок" + :content/copy-ref "Скопировать эту ссылку" + :content/delete-ref "Удалить эту ссылку" + :content/replace-with-text "Заменить на текст" + :content/replace-with-embed "Заменить на встроенный элемент" :content/open-in-sidebar "Открыть в боковой панели" :content/click-to-edit "Нажмите для редактирования" + :context-menu/make-a-flashcard "Создать карточку" + :context-menu/toggle-number-list "Переключить номерной список" + :context-menu/preview-flashcard "Предварительный просмотр карточки" + :context-menu/make-a-template "Создать шаблон" + :context-menu/input-template-name "Как назовём шаблон?" + :context-menu/template-include-parent-block "Включить родительский блок в шаблон?" + :context-menu/template-exists-warning "Шаблон уже существует!" :settings-page/git-confirm "Необходимо перезапустить приложение после изменения настроек Git." :settings-page/git-switcher-label "Включить автокоммит в Git" :settings-page/git-commit-delay "Задержка автокоммита Git в секундах" @@ -294,6 +316,10 @@ :plugin/update "Обновить" :plugin/check-update "Проверить обновления" :plugin/check-all-updates "Проверить все обновления" + :plugin/found-updates "Новые обновления" + :plugin/found-n-updates "Найдено обновлений {1}" + :plugin/update-all-selected "Обновить все выбранные" + :plugin/updates-downloading "Загрузка обновлений" :plugin/refresh-lists "Обновить списки" :plugin/enabled "Включено" :plugin/disabled "Отключено" @@ -345,7 +371,8 @@ :file-sync/other-user-graph "Текущий локальный граф привязан к удаленному графу другого пользователя. Поэтому синхронизацию начать нельзя." :file-sync/graph-deleted "Текущий удаленный граф был удален" - + :file-sync/rsapi-cannot-upload-err "Невозможно начать синхронизацию, пожалуйста, проверьте правильное ли установлено локальное время." + :notification/clear-all "Очистить всё" :shortcut.category/basics "Базовые" @@ -355,6 +382,7 @@ :shortcut.category/block-command-editing "Команды редактирования блока" :shortcut.category/block-selection "Выделение блоков (нажмите Esc для отмены)" :shortcut.category/toggle "Переключатели" + :shortcut.category/whiteboard "Интерактивная доска" :shortcut.category/others "Разное" :command.date-picker/complete "Выбор даты: Выбрать указанный день" :command.date-picker/prev-day "Выбор даты: Выбрать предыдущий день" @@ -400,7 +428,7 @@ :command.editor/replace-block-reference-at-point "Заменить ссылку на блок своим содержимым в указанном месте" :command.editor/paste-text-in-one-block-at-point "Вставить текст в один блок в указанном месте" :command.editor/insert-youtube-timestamp "Вставить временную метку на Youtube" - :command.editor/cycle-todo "Переключить статус данной задачи (TODO)" + :command.editor/cycle-todo "Изменить статус TODO для текущего элемента" :command.editor/up "Переместить курсор вверх / Выбрать вверх" :command.editor/down "Переместить курсор вниз / Выбрать вниз" :command.editor/left "Переместить курсор влево / Открыть выбранный блок в начале" @@ -427,6 +455,32 @@ :command.editor/select-parent "Выбрать родительский блок" :command.editor/zoom-in "Увеличить / Вперед" :command.editor/zoom-out "Уменьшить / Назад" + :command.editor/toggle-undo-redo-mode "Переключить режим отменить/повторить (глобально или только на странице)" + :command.editor/toggle-number-list "Переключить режим нумерованный список" + :command.whiteboard/select "Select tool" + :command.whiteboard/pan "Pan tool" + :command.whiteboard/portal "Portal tool" + :command.whiteboard/pencil "Pencil tool" + :command.whiteboard/highlighter "Highlighter tool" + :command.whiteboard/eraser "Eraser tool" + :command.whiteboard/connector "Connector tool" + :command.whiteboard/text "Text tool" + :command.whiteboard/rectangle "Rectangle tool" + :command.whiteboard/ellipse "Ellipse tool" + :command.whiteboard/reset-zoom "Reset zoom" + :command.whiteboard/zoom-to-fit "Zoom to drawing" + :command.whiteboard/zoom-to-selection "Zoom to fit selection" + :command.whiteboard/zoom-out "Zoom out" + :command.whiteboard/zoom-in "Zoom in" + :command.whiteboard/send-backward "Move backward" + :command.whiteboard/send-to-back "Move to back" + :command.whiteboard/bring-forward "Move forward" + :command.whiteboard/bring-to-front "Move to front" + :command.whiteboard/lock "Lock selection" + :command.whiteboard/unlock "Unlock selection" + :command.whiteboard/group "Group selection" + :command.whiteboard/ungroup "Ungroup selection" + :command.whiteboard/toggle-grid "Toggle the canvas grid" :command.ui/toggle-brackets "Переключить отображение скобок" :command.go/search-in-page "Поиск блоков на текущей странице" :command.go/electron-find-in-page "Поиск текста на странице" @@ -466,7 +520,7 @@ :command.ui/toggle-help "Переключить помощь" :command.ui/toggle-theme "Переключение между темной/светлой темой" :command.ui/toggle-contents "Переключить Контент на боковой панели" - ;; :ui/open-new-window "Открыть другое окно" + ;; :command.ui/open-new-window "Открыть новое окно" :command.command/toggle-favorite "Добавить или удалить из избранного" :command.editor/open-file-in-default-app "Открыть файл в программе по умолчанию" :command.editor/open-file-in-directory "Открыть файл в родительском каталоге" From 719a22f6fa08d207ac66ad0da226c9380845c98f Mon Sep 17 00:00:00 2001 From: level101 Date: Tue, 23 May 2023 09:09:32 +0200 Subject: [PATCH 12/13] main/frontend/dicts/ru: update translation --- src/main/frontend/dicts/ru.cljc | 60 ++++++++++++++++----------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/main/frontend/dicts/ru.cljc b/src/main/frontend/dicts/ru.cljc index 8700238825..554c663de3 100644 --- a/src/main/frontend/dicts/ru.cljc +++ b/src/main/frontend/dicts/ru.cljc @@ -389,10 +389,10 @@ :command.date-picker/next-day "Выбор даты: Выбрать следующий день" :command.date-picker/prev-week "Выбор даты: Выбрать предыдущую неделю" :command.date-picker/next-week "Выбор даты: Выбрать следующую неделю" - :command.pdf/previous-page "Предыдущая страница текущего PDF" - :command.pdf/next-page "Следующая страница текущего PDF" - :command.pdf/close "Закрыть текущий просмотр PDF" - :command.pdf/find "Pdf: Поиск текста в текущем PDF-документе" + :command.pdf/previous-page "PDF: Предыдущая страница текущего PDF" + :command.pdf/next-page "PDF: Следующая страница текущего PDF" + :command.pdf/close "PDF: Закрыть текущий просмотр PDF" + :command.pdf/find "PDF: Поиск текста в текущем PDF-документе" :command.auto-complete/complete "Автодополнение: Использовать выбранный элемент" :command.auto-complete/prev "Автодополнение: Выбрать предыдущий" :command.auto-complete/next "Автодополнение: Выбрать следующий" @@ -455,32 +455,32 @@ :command.editor/select-parent "Выбрать родительский блок" :command.editor/zoom-in "Увеличить / Вперед" :command.editor/zoom-out "Уменьшить / Назад" - :command.editor/toggle-undo-redo-mode "Переключить режим отменить/повторить (глобально или только на странице)" - :command.editor/toggle-number-list "Переключить режим нумерованный список" - :command.whiteboard/select "Select tool" - :command.whiteboard/pan "Pan tool" - :command.whiteboard/portal "Portal tool" - :command.whiteboard/pencil "Pencil tool" - :command.whiteboard/highlighter "Highlighter tool" - :command.whiteboard/eraser "Eraser tool" - :command.whiteboard/connector "Connector tool" - :command.whiteboard/text "Text tool" - :command.whiteboard/rectangle "Rectangle tool" - :command.whiteboard/ellipse "Ellipse tool" - :command.whiteboard/reset-zoom "Reset zoom" - :command.whiteboard/zoom-to-fit "Zoom to drawing" - :command.whiteboard/zoom-to-selection "Zoom to fit selection" - :command.whiteboard/zoom-out "Zoom out" - :command.whiteboard/zoom-in "Zoom in" - :command.whiteboard/send-backward "Move backward" - :command.whiteboard/send-to-back "Move to back" - :command.whiteboard/bring-forward "Move forward" - :command.whiteboard/bring-to-front "Move to front" - :command.whiteboard/lock "Lock selection" - :command.whiteboard/unlock "Unlock selection" - :command.whiteboard/group "Group selection" - :command.whiteboard/ungroup "Ungroup selection" - :command.whiteboard/toggle-grid "Toggle the canvas grid" + :command.editor/toggle-undo-redo-mode "Переключить режим отменить/повторить (глобально или только на странице)" + :command.editor/toggle-number-list "Переключить режим нумерованный список" + :command.whiteboard/select "Выбрать инструмент" + :command.whiteboard/pan "Прокруктка" + :command.whiteboard/portal "Добавить блок или страницу" + :command.whiteboard/pencil "Карандаш" + :command.whiteboard/highlighter "Маркер" + :command.whiteboard/eraser "Ластик" + :command.whiteboard/connector "Соединитель" + :command.whiteboard/text "Текст" + :command.whiteboard/rectangle "Прямоугольник" + :command.whiteboard/ellipse "Эллипс" + :command.whiteboard/reset-zoom "Сбросить масштаб" + :command.whiteboard/zoom-to-fit "Показать все элементы" + :command.whiteboard/zoom-to-selection "Показать элемент" + :command.whiteboard/zoom-out "Уменьшить" + :command.whiteboard/zoom-in "Увеличить" + :command.whiteboard/send-backward "Переместить назад" + :command.whiteboard/send-to-back "На задний план" + :command.whiteboard/bring-forward "Переместить вперёд" + :command.whiteboard/bring-to-front "На передний план" + :command.whiteboard/lock "Блокировать" + :command.whiteboard/unlock "Разблокировать" + :command.whiteboard/group "Группировать" + :command.whiteboard/ungroup "Разгруппировать" + :command.whiteboard/toggle-grid "Переключить отображение сетки" :command.ui/toggle-brackets "Переключить отображение скобок" :command.go/search-in-page "Поиск блоков на текущей странице" :command.go/electron-find-in-page "Поиск текста на странице" From 39f8d6f9525838c058d8f824e558ac4fdf976e37 Mon Sep 17 00:00:00 2001 From: level101 Date: Tue, 23 May 2023 09:59:53 +0200 Subject: [PATCH 13/13] main/frontend/dicts/ru: update cycle-todo --- src/main/frontend/dicts/ru.cljc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/frontend/dicts/ru.cljc b/src/main/frontend/dicts/ru.cljc index 554c663de3..1dca5b3d3e 100644 --- a/src/main/frontend/dicts/ru.cljc +++ b/src/main/frontend/dicts/ru.cljc @@ -155,7 +155,7 @@ :editor/expand-block-children "Раскрыть всё" :editor/collapse-block-children "Свернуть всё" :editor/delete-selection "Удалить выбранные блоки" - :editor/cycle-todo "Изменить статус TODO для текущего элемента" + :editor/cycle-todo "Изменить статус TODO текущего элемента" :dev/show-page-data "(Dev) Показать данные страницы" :dev/show-block-data "(Dev) Показать данные блока" :dev/show-block-ast "(Dev) Показать AST блока" @@ -428,7 +428,7 @@ :command.editor/replace-block-reference-at-point "Заменить ссылку на блок своим содержимым в указанном месте" :command.editor/paste-text-in-one-block-at-point "Вставить текст в один блок в указанном месте" :command.editor/insert-youtube-timestamp "Вставить временную метку на Youtube" - :command.editor/cycle-todo "Изменить статус TODO для текущего элемента" + :command.editor/cycle-todo "Изменить статус TODO текущего элемента" :command.editor/up "Переместить курсор вверх / Выбрать вверх" :command.editor/down "Переместить курсор вниз / Выбрать вниз" :command.editor/left "Переместить курсор влево / Открыть выбранный блок в начале"