From cacfa209815c8b68016ccf1cbe29c4d2b434785f Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Wed, 6 Aug 2025 10:42:19 -0400 Subject: [PATCH 01/16] feat: Display suggestions UI for GutenbergKit autocompletion --- ...CommentGutenbergEditorViewController.swift | 4 + .../NewGutenbergViewController.swift | 109 ++++++++++++++++-- 2 files changed, 101 insertions(+), 12 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Comments/Controllers/Editor/CommentGutenbergEditorViewController.swift b/WordPress/Classes/ViewRelated/Comments/Controllers/Editor/CommentGutenbergEditorViewController.swift index 53e7e4df9f4a..6d0a0216678f 100644 --- a/WordPress/Classes/ViewRelated/Comments/Controllers/Editor/CommentGutenbergEditorViewController.swift +++ b/WordPress/Classes/ViewRelated/Comments/Controllers/Editor/CommentGutenbergEditorViewController.swift @@ -91,4 +91,8 @@ extension CommentGutenbergEditorViewController: GutenbergKit.EditorViewControlle func editor(_ viewController: GutenbergKit.EditorViewController, didRequestMediaFromSiteMediaLibrary config: GutenbergKit.OpenMediaLibraryAction) { // Do nothing } + + func editor(_ viewController: GutenbergKit.EditorViewController, didTriggerAutocompleter type: String) { + // Do nothing + } } diff --git a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift index 2001677e45b1..8e109d8154e2 100644 --- a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift +++ b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift @@ -79,6 +79,10 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor self.performAutoSave() } + // MARK: - Private Properties + + private var previousFirstResponder: UIResponder? + // TODO: remove (none of these APIs are needed for the new editor) func prepopulateMediaItems(_ media: [Media]) {} var debouncer = WordPressShared.Debouncer(delay: 10) @@ -459,6 +463,39 @@ extension NewGutenbergViewController: GutenbergKit.EditorViewControllerDelegate } } + func editor(_ viewController: GutenbergKit.EditorViewController, didTriggerAutocompleter type: String) { + DispatchQueue.main.async { [weak self] in + switch type { + case "at-symbol": + self?.showSuggestions(type: .mention) { result in + // Handle the result - we'll need to implement sending it back to the editor + switch result { + case .success(let suggestion): + // TODO: Send suggestion back to editor via JavaScript + // For now, we'll just log it. The editor will need a method to accept the suggestion. + print("Selected mention: \(suggestion)") + case .failure(let error): + print("Mention selection cancelled or failed: \(error)") + } + } + case "plus-symbol": + self?.showSuggestions(type: .xpost) { result in + // Handle the result - we'll need to implement sending it back to the editor + switch result { + case .success(let suggestion): + // TODO: Send suggestion back to editor via JavaScript + // For now, we'll just log it. The editor will need a method to accept the suggestion. + print("Selected xpost: \(suggestion)") + case .failure(let error): + print("Xpost selection cancelled or failed: \(error)") + } + } + default: + print("Unknown autocompleter type: \(type)") + } + } + } + private func convertMediaInfoArrayToJSONString(_ mediaInfoArray: [MediaInfo]) -> String? { do { let jsonData = try JSONEncoder().encode(mediaInfoArray) @@ -521,18 +558,66 @@ extension NewGutenbergViewController { present(lightboxVC, animated: true) } - // TODO: reimplement -// func gutenbergDidRequestMention(callback: @escaping (Swift.Result) -> Void) { -// DispatchQueue.main.async(execute: { [weak self] in -// self?.showSuggestions(type: .mention, callback: callback) -// }) -// } -// -// func gutenbergDidRequestXpost(callback: @escaping (Swift.Result) -> Void) { -// DispatchQueue.main.async(execute: { [weak self] in -// self?.showSuggestions(type: .xpost, callback: callback) -// }) -// } +} + +// MARK: - Suggestions implementation + +extension NewGutenbergViewController { + + private func showSuggestions(type: SuggestionType, callback: @escaping (Swift.Result) -> Void) { + guard let siteID = post.blog.dotComID else { + callback(.failure(GutenbergSuggestionsViewController.SuggestionError.notAvailable as NSError)) + return + } + + switch type { + case .mention: + guard SuggestionService.shared.shouldShowSuggestions(for: post.blog) else { return } + case .xpost: + guard SiteSuggestionService.shared.shouldShowSuggestions(for: post.blog) else { return } + } + + previousFirstResponder = view.findFirstResponder() + let suggestionsController = GutenbergSuggestionsViewController(siteID: siteID, suggestionType: type) + suggestionsController.onCompletion = { (result) in + callback(result) + suggestionsController.view.removeFromSuperview() + suggestionsController.removeFromParent() + if let previousFirstResponder = self.previousFirstResponder { + previousFirstResponder.becomeFirstResponder() + } + + var analyticsName: String + switch type { + case .mention: + analyticsName = "user" + case .xpost: + analyticsName = "xpost" + } + + var didSelectSuggestion = false + if case let .success(text) = result, !text.isEmpty { + didSelectSuggestion = true + } + + let analyticsProperties: [String: Any] = [ + "suggestion_type": analyticsName, + "did_select_suggestion": didSelectSuggestion + ] + + WPAnalytics.track(.gutenbergSuggestionSessionFinished, properties: analyticsProperties) + } + addChild(suggestionsController) + view.addSubview(suggestionsController.view) + suggestionsController.view.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + suggestionsController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), + suggestionsController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), + suggestionsController.view.topAnchor.constraint(equalTo: view.topAnchor), + suggestionsController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor) + ]) + suggestionsController.didMove(toParent: self) + } } // MARK: - GutenbergBridgeDataSource From 5bff6f93529d9b3bad8ed4e3069b428cda74d941 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Wed, 6 Aug 2025 10:51:33 -0400 Subject: [PATCH 02/16] feat: Dynamically position suggestions UI above virtual keyboard --- .../NewGutenbergViewController.swift | 64 +++++++++++++++++-- 1 file changed, 59 insertions(+), 5 deletions(-) diff --git a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift index 8e109d8154e2..7909c99ab4f6 100644 --- a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift +++ b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift @@ -81,6 +81,10 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor // MARK: - Private Properties + private var keyboardShowObserver: Any? + private var keyboardHideObserver: Any? + private var keyboardFrame = CGRect.zero + private var suggestionViewBottomConstraint: NSLayoutConstraint? private var previousFirstResponder: UIResponder? // TODO: remove (none of these APIs are needed for the new editor) @@ -150,10 +154,15 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor fatalError() } + deinit { + tearDownKeyboardObservers() + } + // MARK: - Lifecycle methods override func viewDidLoad() { super.viewDidLoad() + setupKeyboardObservers() view.backgroundColor = .systemBackground @@ -277,6 +286,49 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor } } + // MARK: - Keyboard Observers + + private func setupKeyboardObservers() { + keyboardShowObserver = NotificationCenter.default.addObserver(forName: UIResponder.keyboardDidShowNotification, object: nil, queue: .main) { [weak self] (notification) in + if let self, let keyboardRect = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect { + self.keyboardFrame = keyboardRect + self.updateConstraintsToAvoidKeyboard(frame: keyboardRect) + } + } + keyboardHideObserver = NotificationCenter.default.addObserver(forName: UIResponder.keyboardDidHideNotification, object: nil, queue: .main) { [weak self] (notification) in + if let self, let keyboardRect = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect { + self.keyboardFrame = keyboardRect + self.updateConstraintsToAvoidKeyboard(frame: keyboardRect) + } + } + } + + private func tearDownKeyboardObservers() { + if let keyboardShowObserver { + NotificationCenter.default.removeObserver(keyboardShowObserver) + } + if let keyboardHideObserver { + NotificationCenter.default.removeObserver(keyboardHideObserver) + } + } + + private func updateConstraintsToAvoidKeyboard(frame: CGRect) { + keyboardFrame = frame + let minimumKeyboardHeight = CGFloat(50) + guard let suggestionViewBottomConstraint else { + return + } + + // There are cases where the keyboard is not visible, but the system instead of returning zero, returns a low number, for example: 0, 3, 69. + // So in those scenarios, we just need to take in account the safe area and ignore the keyboard all together. + if keyboardFrame.height < minimumKeyboardHeight { + suggestionViewBottomConstraint.constant = -self.view.safeAreaInsets.bottom + } + else { + suggestionViewBottomConstraint.constant = -self.keyboardFrame.height + } + } + // MARK: - Activity Indicator private func showActivityIndicator() { @@ -609,13 +661,15 @@ extension NewGutenbergViewController { } addChild(suggestionsController) view.addSubview(suggestionsController.view) - suggestionsController.view.translatesAutoresizingMaskIntoConstraints = false + let suggestionsBottomConstraint = suggestionsController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0) NSLayoutConstraint.activate([ - suggestionsController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), - suggestionsController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), - suggestionsController.view.topAnchor.constraint(equalTo: view.topAnchor), - suggestionsController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor) + suggestionsController.view.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 0), + suggestionsController.view.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: 0), + suggestionsBottomConstraint, + suggestionsController.view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor) ]) + self.suggestionViewBottomConstraint = suggestionsBottomConstraint + updateConstraintsToAvoidKeyboard(frame: keyboardFrame) suggestionsController.didMove(toParent: self) } } From 73581b2cce6c4dbe220795649e6923c1b6b68666 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Wed, 6 Aug 2025 11:20:17 -0400 Subject: [PATCH 03/16] feat: Append suggestion text at GutenbergKit cursor --- .../NewGutenberg/NewGutenbergViewController.swift | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift index 7909c99ab4f6..1b92a0d97f22 100644 --- a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift +++ b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift @@ -520,24 +520,22 @@ extension NewGutenbergViewController: GutenbergKit.EditorViewControllerDelegate switch type { case "at-symbol": self?.showSuggestions(type: .mention) { result in - // Handle the result - we'll need to implement sending it back to the editor switch result { case .success(let suggestion): - // TODO: Send suggestion back to editor via JavaScript - // For now, we'll just log it. The editor will need a method to accept the suggestion. - print("Selected mention: \(suggestion)") + // Append empty string to suggestion tocomplete GutenbergKit's autocomplete session, + // otherwise it will immediately restart + self?.editorViewController.appendTextAtCursor(suggestion + " ") case .failure(let error): print("Mention selection cancelled or failed: \(error)") } } case "plus-symbol": self?.showSuggestions(type: .xpost) { result in - // Handle the result - we'll need to implement sending it back to the editor switch result { case .success(let suggestion): - // TODO: Send suggestion back to editor via JavaScript - // For now, we'll just log it. The editor will need a method to accept the suggestion. - print("Selected xpost: \(suggestion)") + // Append empty string to suggestion to complete GutenbergKit's autocomplete session, + // otherwise it will immediately restart + self?.editorViewController.appendTextAtCursor(suggestion + " ") case .failure(let error): print("Xpost selection cancelled or failed: \(error)") } From efcb6b48ee18eec26e2c5ffc69a27d8925b2364b Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Wed, 6 Aug 2025 11:36:34 -0400 Subject: [PATCH 04/16] fix: Prevent rendering duplicative suggestions UI Stepping through history state may retrigger autocompletion events, which led to rendering duplicative UI views. --- .../NewGutenberg/NewGutenbergViewController.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift index 1b92a0d97f22..f0154ccdee7d 100644 --- a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift +++ b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift @@ -85,6 +85,7 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor private var keyboardHideObserver: Any? private var keyboardFrame = CGRect.zero private var suggestionViewBottomConstraint: NSLayoutConstraint? + private var currentSuggestionsController: GutenbergSuggestionsViewController? private var previousFirstResponder: UIResponder? // TODO: remove (none of these APIs are needed for the new editor) @@ -615,6 +616,10 @@ extension NewGutenbergViewController { extension NewGutenbergViewController { private func showSuggestions(type: SuggestionType, callback: @escaping (Swift.Result) -> Void) { + // Prevent multiple suggestions UI instances - simply ignore if already showing + guard currentSuggestionsController == nil else { + return + } guard let siteID = post.blog.dotComID else { callback(.failure(GutenbergSuggestionsViewController.SuggestionError.notAvailable as NSError)) return @@ -629,8 +634,13 @@ extension NewGutenbergViewController { previousFirstResponder = view.findFirstResponder() let suggestionsController = GutenbergSuggestionsViewController(siteID: siteID, suggestionType: type) + currentSuggestionsController = suggestionsController suggestionsController.onCompletion = { (result) in callback(result) + // Clear the current controller reference + self.currentSuggestionsController = nil + self.suggestionViewBottomConstraint = nil + // Clean up the UI suggestionsController.view.removeFromSuperview() suggestionsController.removeFromParent() if let previousFirstResponder = self.previousFirstResponder { From 5cd6ee8cb7c1cb1c244f995df79898a56190cb6b Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Wed, 6 Aug 2025 11:50:04 -0400 Subject: [PATCH 05/16] perf: Prefetch suggestions Improve performance and avoid displaying empty suggestions UI for blogs without support for suggestions. --- .../NewGutenberg/NewGutenbergViewController.swift | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift index f0154ccdee7d..c1f8e1592494 100644 --- a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift +++ b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift @@ -232,10 +232,9 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor setTitle(post.postTitle ?? "") editorViewController.setContent(content) - // TODO: reimplement -// SiteSuggestionService.shared.prefetchSuggestionsIfNeeded(for: post.blog) { [weak self] in -// self?.gutenberg.updateCapabilities() -// } + SiteSuggestionService.shared.prefetchSuggestionsIfNeeded(for: post.blog) { + // Do nothing + } } private func refreshInterface() { From 112a4f49199aeaec43c7b970aee70438a4d8e64b Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Thu, 7 Aug 2025 09:29:29 -0400 Subject: [PATCH 06/16] build: Update GutenbergKit version --- Modules/Package.resolved | 5 ++--- Modules/Package.swift | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Modules/Package.resolved b/Modules/Package.resolved index c5c459ee1f70..773a39d103db 100644 --- a/Modules/Package.resolved +++ b/Modules/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "94d091cc524b2f58bb430f21396f16dc040d3bcd7183a98eb196356139a4de86", + "originHash" : "60fad9947ff2f173fc01f9410847b104c998dbf33f80fe4015f8c125c96b35d0", "pins" : [ { "identity" : "alamofire", @@ -149,8 +149,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/wordpress-mobile/GutenbergKit", "state" : { - "revision" : "780efc3905275804aa52127aa532c3fc12d81a68", - "version" : "0.7.0" + "revision" : "da6ffc5cca13151f74a760122549e0025d0ad593" } }, { diff --git a/Modules/Package.swift b/Modules/Package.swift index 5b69b85dc041..421a0e92294b 100644 --- a/Modules/Package.swift +++ b/Modules/Package.swift @@ -55,7 +55,7 @@ let package = Package( .package(url: "https://github.com/zendesk/support_sdk_ios", from: "8.0.3"), // We can't use wordpress-rs branches nor commits here. Only tags work. .package(url: "https://github.com/Automattic/wordpress-rs", revision: "alpha-20250715"), - .package(url: "https://github.com/wordpress-mobile/GutenbergKit", from: "0.7.0"), + .package(url: "https://github.com/wordpress-mobile/GutenbergKit", revision: "da6ffc5cca13151f74a760122549e0025d0ad593"), .package( url: "https://github.com/Automattic/color-studio", revision: "bf141adc75e2769eb469a3e095bdc93dc30be8de" From acb2d2a16cd1b06a7df0a9438a80c09945b9204e Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Thu, 7 Aug 2025 09:35:40 -0400 Subject: [PATCH 07/16] style: Fix lint warnings --- .../Editor/CommentGutenbergEditorViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Comments/Controllers/Editor/CommentGutenbergEditorViewController.swift b/WordPress/Classes/ViewRelated/Comments/Controllers/Editor/CommentGutenbergEditorViewController.swift index 6d0a0216678f..19978578bd2a 100644 --- a/WordPress/Classes/ViewRelated/Comments/Controllers/Editor/CommentGutenbergEditorViewController.swift +++ b/WordPress/Classes/ViewRelated/Comments/Controllers/Editor/CommentGutenbergEditorViewController.swift @@ -91,7 +91,7 @@ extension CommentGutenbergEditorViewController: GutenbergKit.EditorViewControlle func editor(_ viewController: GutenbergKit.EditorViewController, didRequestMediaFromSiteMediaLibrary config: GutenbergKit.OpenMediaLibraryAction) { // Do nothing } - + func editor(_ viewController: GutenbergKit.EditorViewController, didTriggerAutocompleter type: String) { // Do nothing } From 9bc8b0995016ca3b559982e00a6426dbd5d943e7 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Thu, 7 Aug 2025 14:33:37 -0400 Subject: [PATCH 08/16] build: Update GutenbergKit version --- Modules/Package.resolved | 4 ++-- Modules/Package.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/Package.resolved b/Modules/Package.resolved index 773a39d103db..20c5eec89087 100644 --- a/Modules/Package.resolved +++ b/Modules/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "60fad9947ff2f173fc01f9410847b104c998dbf33f80fe4015f8c125c96b35d0", + "originHash" : "591f83647b2ecc65761376a439b264f50c57a0b1e6fe1b3a840a98be700f9db1", "pins" : [ { "identity" : "alamofire", @@ -149,7 +149,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/wordpress-mobile/GutenbergKit", "state" : { - "revision" : "da6ffc5cca13151f74a760122549e0025d0ad593" + "revision" : "3225cfa2a724eb333cd3e16cea2e9a76d2aeed50" } }, { diff --git a/Modules/Package.swift b/Modules/Package.swift index 421a0e92294b..5b88225ac6b1 100644 --- a/Modules/Package.swift +++ b/Modules/Package.swift @@ -55,7 +55,7 @@ let package = Package( .package(url: "https://github.com/zendesk/support_sdk_ios", from: "8.0.3"), // We can't use wordpress-rs branches nor commits here. Only tags work. .package(url: "https://github.com/Automattic/wordpress-rs", revision: "alpha-20250715"), - .package(url: "https://github.com/wordpress-mobile/GutenbergKit", revision: "da6ffc5cca13151f74a760122549e0025d0ad593"), + .package(url: "https://github.com/wordpress-mobile/GutenbergKit", revision: "3225cfa2a724eb333cd3e16cea2e9a76d2aeed50"), .package( url: "https://github.com/Automattic/color-studio", revision: "bf141adc75e2769eb469a3e095bdc93dc30be8de" From e49d4472f73d900573e8604159f9824ded2d35f7 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Thu, 7 Aug 2025 17:09:45 -0400 Subject: [PATCH 09/16] build: Update GutenbergKit version --- Modules/Package.resolved | 4 ++-- Modules/Package.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/Package.resolved b/Modules/Package.resolved index 20c5eec89087..91eb6e0fc81e 100644 --- a/Modules/Package.resolved +++ b/Modules/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "591f83647b2ecc65761376a439b264f50c57a0b1e6fe1b3a840a98be700f9db1", + "originHash" : "6872e7a3b7f455e2dab721f85ccfb9df2f56357de0f47af3626aa88d755ce322", "pins" : [ { "identity" : "alamofire", @@ -149,7 +149,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/wordpress-mobile/GutenbergKit", "state" : { - "revision" : "3225cfa2a724eb333cd3e16cea2e9a76d2aeed50" + "revision" : "4dc75c11a8f32423c990ee3d9c9d972b1015c228" } }, { diff --git a/Modules/Package.swift b/Modules/Package.swift index 5b88225ac6b1..95d90ab3bcfd 100644 --- a/Modules/Package.swift +++ b/Modules/Package.swift @@ -55,7 +55,7 @@ let package = Package( .package(url: "https://github.com/zendesk/support_sdk_ios", from: "8.0.3"), // We can't use wordpress-rs branches nor commits here. Only tags work. .package(url: "https://github.com/Automattic/wordpress-rs", revision: "alpha-20250715"), - .package(url: "https://github.com/wordpress-mobile/GutenbergKit", revision: "3225cfa2a724eb333cd3e16cea2e9a76d2aeed50"), + .package(url: "https://github.com/wordpress-mobile/GutenbergKit", revision: "4dc75c11a8f32423c990ee3d9c9d972b1015c228"), .package( url: "https://github.com/Automattic/color-studio", revision: "bf141adc75e2769eb469a3e095bdc93dc30be8de" From 36a57566a3c7a92c2e115cb7c13619a525953114 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Fri, 8 Aug 2025 08:39:04 -0400 Subject: [PATCH 10/16] docs: Shorten inline comment --- .../NewGutenberg/NewGutenbergViewController.swift | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift index c1f8e1592494..889c73f4f35b 100644 --- a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift +++ b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift @@ -522,8 +522,7 @@ extension NewGutenbergViewController: GutenbergKit.EditorViewControllerDelegate self?.showSuggestions(type: .mention) { result in switch result { case .success(let suggestion): - // Append empty string to suggestion tocomplete GutenbergKit's autocomplete session, - // otherwise it will immediately restart + // Appended space completes the autocomplete session self?.editorViewController.appendTextAtCursor(suggestion + " ") case .failure(let error): print("Mention selection cancelled or failed: \(error)") @@ -533,8 +532,7 @@ extension NewGutenbergViewController: GutenbergKit.EditorViewControllerDelegate self?.showSuggestions(type: .xpost) { result in switch result { case .success(let suggestion): - // Append empty string to suggestion to complete GutenbergKit's autocomplete session, - // otherwise it will immediately restart + // Appended space completes the autocomplete session self?.editorViewController.appendTextAtCursor(suggestion + " ") case .failure(let error): print("Xpost selection cancelled or failed: \(error)") From 96080f320a8895128baaf8edc48c22f221b27bee Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Fri, 8 Aug 2025 08:42:22 -0400 Subject: [PATCH 11/16] build: Update GutenbergKit version --- Modules/Package.resolved | 4 ++-- Modules/Package.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/Package.resolved b/Modules/Package.resolved index 91eb6e0fc81e..0ceab8332c8f 100644 --- a/Modules/Package.resolved +++ b/Modules/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "6872e7a3b7f455e2dab721f85ccfb9df2f56357de0f47af3626aa88d755ce322", + "originHash" : "53cb4680c18e7875fa9db13620776e4b402e67993b8ecd4be51c5ddccac36bf3", "pins" : [ { "identity" : "alamofire", @@ -149,7 +149,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/wordpress-mobile/GutenbergKit", "state" : { - "revision" : "4dc75c11a8f32423c990ee3d9c9d972b1015c228" + "revision" : "e4177142999950c481add8ef69bd28eb94a63f75" } }, { diff --git a/Modules/Package.swift b/Modules/Package.swift index 95d90ab3bcfd..45dbd5f27a70 100644 --- a/Modules/Package.swift +++ b/Modules/Package.swift @@ -55,7 +55,7 @@ let package = Package( .package(url: "https://github.com/zendesk/support_sdk_ios", from: "8.0.3"), // We can't use wordpress-rs branches nor commits here. Only tags work. .package(url: "https://github.com/Automattic/wordpress-rs", revision: "alpha-20250715"), - .package(url: "https://github.com/wordpress-mobile/GutenbergKit", revision: "4dc75c11a8f32423c990ee3d9c9d972b1015c228"), + .package(url: "https://github.com/wordpress-mobile/GutenbergKit", revision: "e4177142999950c481add8ef69bd28eb94a63f75"), .package( url: "https://github.com/Automattic/color-studio", revision: "bf141adc75e2769eb469a3e095bdc93dc30be8de" From 9cb0d27a11f73b8ab9f6a4921c4bbfff42801f75 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Fri, 8 Aug 2025 08:48:30 -0400 Subject: [PATCH 12/16] refactor: Replace `print` usage with project logs --- .../NewGutenberg/NewGutenbergViewController.swift | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift index 889c73f4f35b..916a1e0a09ab 100644 --- a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift +++ b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift @@ -7,6 +7,7 @@ import SafariServices import WordPressData import WordPressShared import WebKit +import CocoaLumberjackSwift class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor { let errorDomain: String = "GutenbergViewController.errorDomain" @@ -258,7 +259,7 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor let startTime = CFAbsoluteTimeGetCurrent() let editorData = try? await editorViewController.getTitleAndContent() let duration = CFAbsoluteTimeGetCurrent() - startTime - print("gutenbergkit-measure_get-latest-content:", duration) + DDLogDebug("gutenbergkit-measure_get-latest-content: \(duration)") if let title = editorData?.title, let content = editorData?.content, @@ -525,7 +526,7 @@ extension NewGutenbergViewController: GutenbergKit.EditorViewControllerDelegate // Appended space completes the autocomplete session self?.editorViewController.appendTextAtCursor(suggestion + " ") case .failure(let error): - print("Mention selection cancelled or failed: \(error)") + DDLogError("Mention selection cancelled or failed: \(error)") } } case "plus-symbol": @@ -535,11 +536,11 @@ extension NewGutenbergViewController: GutenbergKit.EditorViewControllerDelegate // Appended space completes the autocomplete session self?.editorViewController.appendTextAtCursor(suggestion + " ") case .failure(let error): - print("Xpost selection cancelled or failed: \(error)") + DDLogError("Xpost selection cancelled or failed: \(error)") } } default: - print("Unknown autocompleter type: \(type)") + DDLogError("Unknown autocompleter type: \(type)") } } } @@ -551,7 +552,7 @@ extension NewGutenbergViewController: GutenbergKit.EditorViewControllerDelegate return jsonString } } catch { - print("Error encoding MediaInfo array: \(error)") + DDLogError("Error encoding MediaInfo array: \(error)") } return nil } From cf0664348ab93ef20a0ddeb7b1c6b3827d15caa5 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Tue, 12 Aug 2025 09:48:23 -0400 Subject: [PATCH 13/16] refactor: Use weak reference to avoid a retain cycle --- .../NewGutenbergViewController.swift | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift index 916a1e0a09ab..d59603d4c14d 100644 --- a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift +++ b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift @@ -633,16 +633,21 @@ extension NewGutenbergViewController { previousFirstResponder = view.findFirstResponder() let suggestionsController = GutenbergSuggestionsViewController(siteID: siteID, suggestionType: type) currentSuggestionsController = suggestionsController - suggestionsController.onCompletion = { (result) in + suggestionsController.onCompletion = { [weak self] (result) in callback(result) - // Clear the current controller reference - self.currentSuggestionsController = nil - self.suggestionViewBottomConstraint = nil - // Clean up the UI - suggestionsController.view.removeFromSuperview() - suggestionsController.removeFromParent() - if let previousFirstResponder = self.previousFirstResponder { - previousFirstResponder.becomeFirstResponder() + + if let self { + // Clear the current controller reference + self.currentSuggestionsController = nil + self.suggestionViewBottomConstraint = nil + + // Clean up the UI (should only happen if parent still exists) + suggestionsController.view.removeFromSuperview() + suggestionsController.removeFromParent() + + if let previousFirstResponder = self.previousFirstResponder { + previousFirstResponder.becomeFirstResponder() + } } var analyticsName: String From 399f641a6c657aa766a9c57537bbe36ae47eb0e5 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Tue, 12 Aug 2025 09:49:02 -0400 Subject: [PATCH 14/16] refactor: Remove unnecessary dispatch async --- .../NewGutenbergViewController.swift | 42 +++++++++---------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift index d59603d4c14d..4ca705d33fc0 100644 --- a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift +++ b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift @@ -517,31 +517,29 @@ extension NewGutenbergViewController: GutenbergKit.EditorViewControllerDelegate } func editor(_ viewController: GutenbergKit.EditorViewController, didTriggerAutocompleter type: String) { - DispatchQueue.main.async { [weak self] in - switch type { - case "at-symbol": - self?.showSuggestions(type: .mention) { result in - switch result { - case .success(let suggestion): - // Appended space completes the autocomplete session - self?.editorViewController.appendTextAtCursor(suggestion + " ") - case .failure(let error): - DDLogError("Mention selection cancelled or failed: \(error)") - } + switch type { + case "at-symbol": + showSuggestions(type: .mention) { [weak self] result in + switch result { + case .success(let suggestion): + // Appended space completes the autocomplete session + self?.editorViewController.appendTextAtCursor(suggestion + " ") + case .failure(let error): + DDLogError("Mention selection cancelled or failed: \(error)") } - case "plus-symbol": - self?.showSuggestions(type: .xpost) { result in - switch result { - case .success(let suggestion): - // Appended space completes the autocomplete session - self?.editorViewController.appendTextAtCursor(suggestion + " ") - case .failure(let error): - DDLogError("Xpost selection cancelled or failed: \(error)") - } + } + case "plus-symbol": + showSuggestions(type: .xpost) { [weak self] result in + switch result { + case .success(let suggestion): + // Appended space completes the autocomplete session + self?.editorViewController.appendTextAtCursor(suggestion + " ") + case .failure(let error): + DDLogError("Xpost selection cancelled or failed: \(error)") } - default: - DDLogError("Unknown autocompleter type: \(type)") } + default: + DDLogError("Unknown autocompleter type: \(type)") } } From 3c2285b3c5795e2bf01af56314b06f340a22b76d Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Tue, 12 Aug 2025 10:11:40 -0400 Subject: [PATCH 15/16] refactor: Remove unnecessary view property --- .../NewGutenberg/NewGutenbergViewController.swift | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift index 4ca705d33fc0..5d861bc70ee5 100644 --- a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift +++ b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift @@ -87,7 +87,6 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor private var keyboardFrame = CGRect.zero private var suggestionViewBottomConstraint: NSLayoutConstraint? private var currentSuggestionsController: GutenbergSuggestionsViewController? - private var previousFirstResponder: UIResponder? // TODO: remove (none of these APIs are needed for the new editor) func prepopulateMediaItems(_ media: [Media]) {} @@ -628,7 +627,7 @@ extension NewGutenbergViewController { guard SiteSuggestionService.shared.shouldShowSuggestions(for: post.blog) else { return } } - previousFirstResponder = view.findFirstResponder() + let previousFirstResponder = view.findFirstResponder() let suggestionsController = GutenbergSuggestionsViewController(siteID: siteID, suggestionType: type) currentSuggestionsController = suggestionsController suggestionsController.onCompletion = { [weak self] (result) in @@ -643,9 +642,7 @@ extension NewGutenbergViewController { suggestionsController.view.removeFromSuperview() suggestionsController.removeFromParent() - if let previousFirstResponder = self.previousFirstResponder { - previousFirstResponder.becomeFirstResponder() - } + previousFirstResponder?.becomeFirstResponder() } var analyticsName: String From f9433900cdcb9dd419dec8ba1dcf8b3f3d99194f Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Tue, 12 Aug 2025 21:04:41 -0400 Subject: [PATCH 16/16] build: Update GutenbergKit version --- Modules/Package.resolved | 5 +++-- Modules/Package.swift | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Modules/Package.resolved b/Modules/Package.resolved index 0ceab8332c8f..6e0b80554082 100644 --- a/Modules/Package.resolved +++ b/Modules/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "53cb4680c18e7875fa9db13620776e4b402e67993b8ecd4be51c5ddccac36bf3", + "originHash" : "1c49d626b4b59d58f254cb80d59693d56bdbbcf7cc9bf672790a764d6cfb897a", "pins" : [ { "identity" : "alamofire", @@ -149,7 +149,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/wordpress-mobile/GutenbergKit", "state" : { - "revision" : "e4177142999950c481add8ef69bd28eb94a63f75" + "revision" : "9de6625fa92ebf83af0d1a1603ec3426a79a3ddf", + "version" : "0.8.0-alpha.0" } }, { diff --git a/Modules/Package.swift b/Modules/Package.swift index 45dbd5f27a70..24187b2c9cc5 100644 --- a/Modules/Package.swift +++ b/Modules/Package.swift @@ -55,7 +55,7 @@ let package = Package( .package(url: "https://github.com/zendesk/support_sdk_ios", from: "8.0.3"), // We can't use wordpress-rs branches nor commits here. Only tags work. .package(url: "https://github.com/Automattic/wordpress-rs", revision: "alpha-20250715"), - .package(url: "https://github.com/wordpress-mobile/GutenbergKit", revision: "e4177142999950c481add8ef69bd28eb94a63f75"), + .package(url: "https://github.com/wordpress-mobile/GutenbergKit", from: "0.8.0-alpha.0"), .package( url: "https://github.com/Automattic/color-studio", revision: "bf141adc75e2769eb469a3e095bdc93dc30be8de"