From 3179db13c3a15b8bfda1a21a019dd4e02c8eb691 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Thu, 3 Apr 2025 16:22:03 -0400 Subject: [PATCH 01/23] hoist settings --- .../RawBlockEditorSettingsService.swift | 53 ++++++++++++ .../NewGutenbergViewController.swift | 81 ++++++++++++++++++- 2 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 WordPress/Classes/Services/RawBlockEditorSettingsService.swift diff --git a/WordPress/Classes/Services/RawBlockEditorSettingsService.swift b/WordPress/Classes/Services/RawBlockEditorSettingsService.swift new file mode 100644 index 000000000000..d47e769133bc --- /dev/null +++ b/WordPress/Classes/Services/RawBlockEditorSettingsService.swift @@ -0,0 +1,53 @@ +import Foundation +import WordPressKit +import WordPressShared + +class RawBlockEditorSettingsService { + typealias CompletionHandler = (Result<[String: Any], Error>) -> Void + + private let blog: Blog + private let remoteAPI: WordPressOrgRestApi + + init?(blog: Blog) { + guard let remoteAPI = WordPressOrgRestApi(blog: blog) else { + return nil + } + + self.blog = blog + self.remoteAPI = remoteAPI + } + + func fetchSettings(completion: @escaping CompletionHandler) { + Task { @MainActor in + do { + let result = await self.remoteAPI.get(path: "/wp-block-editor/v1/settings", parameters: ["context": "mobile"]) + switch result { + case .success(let response): + guard let dictionary = response as? [String: Any] else { + completion(.failure(NSError(domain: "RawBlockEditorSettingsService", code: 1, userInfo: [NSLocalizedDescriptionKey: "Invalid response format"]))) + return + } + completion(.success(dictionary)) + case .failure(let error): + completion(.failure(error)) + } + } catch { + completion(.failure(error)) + } + } + } + + @MainActor + func fetchSettings() async throws -> [String: Any] { + let result = await self.remoteAPI.get(path: "/wp-block-editor/v1/settings", parameters: ["context": "mobile"]) + switch result { + case .success(let response): + guard let dictionary = response as? [String: Any] else { + throw NSError(domain: "RawBlockEditorSettingsService", code: 1, userInfo: [NSLocalizedDescriptionKey: "Invalid response format"]) + } + return dictionary + case .failure(let error): + throw error + } + } +} diff --git a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift index c14a7556b513..c499b8f2a297 100644 --- a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift +++ b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift @@ -65,9 +65,15 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor BlockEditorSettingsService(blog: post.blog, coreDataStack: ContextManager.shared) }() + // New service for fetching raw block editor settings + lazy var rawBlockEditorSettingsService: RawBlockEditorSettingsService? = { + return RawBlockEditorSettingsService(blog: post.blog) + }() + // MARK: - GutenbergKit - private let editorViewController: GutenbergKit.EditorViewController + private var editorViewController: GutenbergKit.EditorViewController + private var activityIndicator: UIActivityIndicatorView? lazy var autosaver = Autosaver() { self.performAutoSave() @@ -202,7 +208,11 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor configureNavigationBar() refreshInterface() - fetchBlockSettings() + // Show activity indicator while fetching settings + showActivityIndicator() + + // Fetch block editor settings + fetchBlockEditorSettings() // TODO: reimplement // service?.syncJetpackSettingsForBlog(post.blog, success: { [weak self] in @@ -316,6 +326,73 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor WordPressAppDelegate.crashLogging?.logJavaScriptException(exception, callback: callback) } } + + // MARK: - Activity Indicator + + private func showActivityIndicator() { + let indicator = UIActivityIndicatorView(style: .large) + indicator.color = .gray + indicator.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(indicator) + + NSLayoutConstraint.activate([ + indicator.centerXAnchor.constraint(equalTo: view.centerXAnchor), + indicator.centerYAnchor.constraint(equalTo: view.centerYAnchor) + ]) + + indicator.startAnimating() + self.activityIndicator = indicator + } + + private func hideActivityIndicator() { + activityIndicator?.stopAnimating() + activityIndicator?.removeFromSuperview() + activityIndicator = nil + } + + // MARK: - Block Editor Settings + + private func fetchBlockEditorSettings() { + guard let service = rawBlockEditorSettingsService else { + hideActivityIndicator() + return + } + + service.fetchSettings { [weak self] result in + guard let self = self else { return } + + DispatchQueue.main.async { + self.hideActivityIndicator() + + switch result { + case .success(let settings): + // Update the editor configuration with the fetched settings + var updatedConfig = self.editorViewController.configuration + updatedConfig.blockEditorSettings = settings + + // Create a new editor view controller with the updated configuration + let newEditorVC = GutenbergKit.EditorViewController(configuration: updatedConfig) + newEditorVC.delegate = self + + // Replace the old editor view controller with the new one + self.editorViewController.willMove(toParent: nil) + self.editorViewController.view.removeFromSuperview() + self.editorViewController.removeFromParent() + + self.addChild(newEditorVC) + self.view.addSubview(newEditorVC.view) + self.view.pinSubviewToAllEdges(newEditorVC.view) + newEditorVC.didMove(toParent: self) + + // Update the reference to the editor view controller + self.editorViewController = newEditorVC + + case .failure(let error): + DDLogError("Error fetching block editor settings: \(error)") + } + } + } + } } extension NewGutenbergViewController: GutenbergKit.EditorViewControllerDelegate { From dfce8e6dc4cf742a48cd2214739fc3993310d11a Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Fri, 4 Apr 2025 09:36:12 -0400 Subject: [PATCH 02/23] refactor: Update settings via JavaScript bridge --- .../NewGutenbergViewController.swift | 26 ++++--------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift index c499b8f2a297..d27041253863 100644 --- a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift +++ b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift @@ -366,27 +366,11 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor switch result { case .success(let settings): - // Update the editor configuration with the fetched settings - var updatedConfig = self.editorViewController.configuration - updatedConfig.blockEditorSettings = settings - - // Create a new editor view controller with the updated configuration - let newEditorVC = GutenbergKit.EditorViewController(configuration: updatedConfig) - newEditorVC.delegate = self - - // Replace the old editor view controller with the new one - self.editorViewController.willMove(toParent: nil) - self.editorViewController.view.removeFromSuperview() - self.editorViewController.removeFromParent() - - self.addChild(newEditorVC) - self.view.addSubview(newEditorVC.view) - self.view.pinSubviewToAllEdges(newEditorVC.view) - newEditorVC.didMove(toParent: self) - - // Update the reference to the editor view controller - self.editorViewController = newEditorVC - + // Update the editor settings using the bridge method + let settingsJSON = try? JSONSerialization.data(withJSONObject: settings, options: []) + if let jsonString = settingsJSON.flatMap({ String(data: $0, encoding: .utf8) }) { + self.editorViewController.webView.evaluateJavaScript("window.editor.updateSettings(\(jsonString));") + } case .failure(let error): DDLogError("Error fetching block editor settings: \(error)") } From 1952cc9d6f0755e5675b620bc371889b2c4bcdd9 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Fri, 4 Apr 2025 10:17:51 -0400 Subject: [PATCH 03/23] fix: Fetch the entirety of the editor settings --- WordPress/Classes/Services/RawBlockEditorSettingsService.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/Classes/Services/RawBlockEditorSettingsService.swift b/WordPress/Classes/Services/RawBlockEditorSettingsService.swift index d47e769133bc..24f88efbd732 100644 --- a/WordPress/Classes/Services/RawBlockEditorSettingsService.swift +++ b/WordPress/Classes/Services/RawBlockEditorSettingsService.swift @@ -20,7 +20,7 @@ class RawBlockEditorSettingsService { func fetchSettings(completion: @escaping CompletionHandler) { Task { @MainActor in do { - let result = await self.remoteAPI.get(path: "/wp-block-editor/v1/settings", parameters: ["context": "mobile"]) + let result = await self.remoteAPI.get(path: "/wp-block-editor/v1/settings") switch result { case .success(let response): guard let dictionary = response as? [String: Any] else { From 8ebe71ec112a4e0598a48c8972cbec86ed646568 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Fri, 4 Apr 2025 10:18:33 -0400 Subject: [PATCH 04/23] Revert "refactor: Update settings via JavaScript bridge" This reverts commit e4ec71b6c77e2439e9d98c0273f9bdcff64d098f. --- .../NewGutenbergViewController.swift | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift index d27041253863..c499b8f2a297 100644 --- a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift +++ b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift @@ -366,11 +366,27 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor switch result { case .success(let settings): - // Update the editor settings using the bridge method - let settingsJSON = try? JSONSerialization.data(withJSONObject: settings, options: []) - if let jsonString = settingsJSON.flatMap({ String(data: $0, encoding: .utf8) }) { - self.editorViewController.webView.evaluateJavaScript("window.editor.updateSettings(\(jsonString));") - } + // Update the editor configuration with the fetched settings + var updatedConfig = self.editorViewController.configuration + updatedConfig.blockEditorSettings = settings + + // Create a new editor view controller with the updated configuration + let newEditorVC = GutenbergKit.EditorViewController(configuration: updatedConfig) + newEditorVC.delegate = self + + // Replace the old editor view controller with the new one + self.editorViewController.willMove(toParent: nil) + self.editorViewController.view.removeFromSuperview() + self.editorViewController.removeFromParent() + + self.addChild(newEditorVC) + self.view.addSubview(newEditorVC.view) + self.view.pinSubviewToAllEdges(newEditorVC.view) + newEditorVC.didMove(toParent: self) + + // Update the reference to the editor view controller + self.editorViewController = newEditorVC + case .failure(let error): DDLogError("Error fetching block editor settings: \(error)") } From b3b2129d2ed93a2b756ec19f1d1e197be0c9858d Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Fri, 4 Apr 2025 10:43:17 -0400 Subject: [PATCH 05/23] feat: Postpone editor initialization --- .../NewGutenbergViewController.swift | 52 ++++++++----------- 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift index c499b8f2a297..a17398d454ce 100644 --- a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift +++ b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift @@ -72,7 +72,8 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor // MARK: - GutenbergKit - private var editorViewController: GutenbergKit.EditorViewController + private var editorViewController: GutenbergKit.EditorViewController? + private var configuration: EditorConfiguration private var activityIndicator: UIActivityIndicatorView? lazy var autosaver = Autosaver() { @@ -121,7 +122,6 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor editorSession: PostEditorAnalyticsSession? = nil, navigationBarManager: PostEditorNavigationBarManager? = nil ) { - self.post = post self.replaceEditor = replaceEditor @@ -184,11 +184,10 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor } } - self.editorViewController = GutenbergKit.EditorViewController(configuration: conf) + self.configuration = conf super.init(nibName: nil, bundle: nil) - self.editorViewController.delegate = self self.navigationBarManager.delegate = self } @@ -233,6 +232,8 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor private func setupEditorView() { view.tintColor = UIAppColor.editorPrimary + guard let editorViewController = editorViewController else { return } + addChild(editorViewController) view.addSubview(editorViewController.view) view.pinSubviewToAllEdges(editorViewController.view) @@ -270,7 +271,7 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor let content = post.content ?? String() setTitle(post.postTitle ?? "") - editorViewController.setContent(content) + editorViewController?.setContent(content) // TODO: reimplement // SiteSuggestionService.shared.prefetchSuggestionsIfNeeded(for: post.blog) { [weak self] in @@ -286,7 +287,7 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor } func toggleEditingMode() { - editorViewController.isCodeEditorEnabled.toggle() + editorViewController?.isCodeEditorEnabled.toggle() } private func performAutoSave() { @@ -297,7 +298,7 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor private func getLatestContent() async { let startTime = CFAbsoluteTimeGetCurrent() - let editorData = try? await editorViewController.getTitleAndContent() + let editorData = try? await editorViewController?.getTitleAndContent() let duration = CFAbsoluteTimeGetCurrent() - startTime print("gutenbergkit-measure_get-latest-content:", duration) @@ -366,26 +367,17 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor switch result { case .success(let settings): - // Update the editor configuration with the fetched settings - var updatedConfig = self.editorViewController.configuration + // Update the configuration with the fetched settings + var updatedConfig = self.configuration updatedConfig.blockEditorSettings = settings - // Create a new editor view controller with the updated configuration - let newEditorVC = GutenbergKit.EditorViewController(configuration: updatedConfig) - newEditorVC.delegate = self - - // Replace the old editor view controller with the new one - self.editorViewController.willMove(toParent: nil) - self.editorViewController.view.removeFromSuperview() - self.editorViewController.removeFromParent() - - self.addChild(newEditorVC) - self.view.addSubview(newEditorVC.view) - self.view.pinSubviewToAllEdges(newEditorVC.view) - newEditorVC.didMove(toParent: self) + // Create the editor view controller with the updated configuration + let editorVC = GutenbergKit.EditorViewController(configuration: updatedConfig) + editorVC.delegate = self + self.editorViewController = editorVC - // Update the reference to the editor view controller - self.editorViewController = newEditorVC + // Setup the editor view + self.setupEditorView() case .failure(let error): DDLogError("Error fetching block editor settings: \(error)") @@ -449,7 +441,7 @@ extension NewGutenbergViewController: GutenbergKit.EditorViewControllerDelegate mediaPickerHelper.presentSiteMediaPicker(filter: flags, allowMultipleSelection: config.multiple, initialSelection: initialSelectionArray) { [weak self] assets in guard let self, let media = assets as? [Media] else { - self?.editorViewController.setMediaUploadAttachment("[]") + self?.editorViewController?.setMediaUploadAttachment("[]") return } let mediaInfos = media.map { item in @@ -462,7 +454,7 @@ extension NewGutenbergViewController: GutenbergKit.EditorViewControllerDelegate if let jsonString = convertMediaInfoArrayToJSONString(mediaInfos) { // Escape the string for JavaScript let escapedJsonString = jsonString.replacingOccurrences(of: "'", with: "\\'") - editorViewController.setMediaUploadAttachment(escapedJsonString) + editorViewController?.setMediaUploadAttachment(escapedJsonString) } } } @@ -671,11 +663,11 @@ extension NewGutenbergViewController: PostEditorNavigationBarManagerDelegate { } func navigationBarManager(_ manager: PostEditorNavigationBarManager, undoWasPressed sender: UIButton) { - editorViewController.undo() + editorViewController?.undo() } func navigationBarManager(_ manager: PostEditorNavigationBarManager, redoWasPressed sender: UIButton) { - editorViewController.redo() + editorViewController?.redo() } func navigationBarManager(_ manager: PostEditorNavigationBarManager, moreWasPressed sender: UIButton) { @@ -762,8 +754,8 @@ extension NewGutenbergViewController { private func makeMoreMenuActions() -> [UIAction] { var actions: [UIAction] = [] - let toggleModeTitle = editorViewController.isCodeEditorEnabled ? Strings.visualEditor : Strings.codeEditor - let toggleModeIconName = editorViewController.isCodeEditorEnabled ? "doc.richtext" : "curlybraces" + let toggleModeTitle = editorViewController?.isCodeEditorEnabled ?? false ? Strings.visualEditor : Strings.codeEditor + let toggleModeIconName = editorViewController?.isCodeEditorEnabled ?? false ? "doc.richtext" : "curlybraces" actions.append(UIAction(title: toggleModeTitle, image: UIImage(systemName: toggleModeIconName)) { [weak self] _ in self?.toggleEditingMode() }) From f9231fa7dce01c8930c1a6c1eaf93df9a6da282e Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Fri, 4 Apr 2025 10:43:28 -0400 Subject: [PATCH 06/23] Revert "feat: Postpone editor initialization" This reverts commit 39ac9caf71089853e5f256697f4882cc55d2bffd. --- .../NewGutenbergViewController.swift | 52 +++++++++++-------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift index a17398d454ce..c499b8f2a297 100644 --- a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift +++ b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift @@ -72,8 +72,7 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor // MARK: - GutenbergKit - private var editorViewController: GutenbergKit.EditorViewController? - private var configuration: EditorConfiguration + private var editorViewController: GutenbergKit.EditorViewController private var activityIndicator: UIActivityIndicatorView? lazy var autosaver = Autosaver() { @@ -122,6 +121,7 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor editorSession: PostEditorAnalyticsSession? = nil, navigationBarManager: PostEditorNavigationBarManager? = nil ) { + self.post = post self.replaceEditor = replaceEditor @@ -184,10 +184,11 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor } } - self.configuration = conf + self.editorViewController = GutenbergKit.EditorViewController(configuration: conf) super.init(nibName: nil, bundle: nil) + self.editorViewController.delegate = self self.navigationBarManager.delegate = self } @@ -232,8 +233,6 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor private func setupEditorView() { view.tintColor = UIAppColor.editorPrimary - guard let editorViewController = editorViewController else { return } - addChild(editorViewController) view.addSubview(editorViewController.view) view.pinSubviewToAllEdges(editorViewController.view) @@ -271,7 +270,7 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor let content = post.content ?? String() setTitle(post.postTitle ?? "") - editorViewController?.setContent(content) + editorViewController.setContent(content) // TODO: reimplement // SiteSuggestionService.shared.prefetchSuggestionsIfNeeded(for: post.blog) { [weak self] in @@ -287,7 +286,7 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor } func toggleEditingMode() { - editorViewController?.isCodeEditorEnabled.toggle() + editorViewController.isCodeEditorEnabled.toggle() } private func performAutoSave() { @@ -298,7 +297,7 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor private func getLatestContent() async { let startTime = CFAbsoluteTimeGetCurrent() - let editorData = try? await editorViewController?.getTitleAndContent() + let editorData = try? await editorViewController.getTitleAndContent() let duration = CFAbsoluteTimeGetCurrent() - startTime print("gutenbergkit-measure_get-latest-content:", duration) @@ -367,17 +366,26 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor switch result { case .success(let settings): - // Update the configuration with the fetched settings - var updatedConfig = self.configuration + // Update the editor configuration with the fetched settings + var updatedConfig = self.editorViewController.configuration updatedConfig.blockEditorSettings = settings - // Create the editor view controller with the updated configuration - let editorVC = GutenbergKit.EditorViewController(configuration: updatedConfig) - editorVC.delegate = self - self.editorViewController = editorVC + // Create a new editor view controller with the updated configuration + let newEditorVC = GutenbergKit.EditorViewController(configuration: updatedConfig) + newEditorVC.delegate = self + + // Replace the old editor view controller with the new one + self.editorViewController.willMove(toParent: nil) + self.editorViewController.view.removeFromSuperview() + self.editorViewController.removeFromParent() + + self.addChild(newEditorVC) + self.view.addSubview(newEditorVC.view) + self.view.pinSubviewToAllEdges(newEditorVC.view) + newEditorVC.didMove(toParent: self) - // Setup the editor view - self.setupEditorView() + // Update the reference to the editor view controller + self.editorViewController = newEditorVC case .failure(let error): DDLogError("Error fetching block editor settings: \(error)") @@ -441,7 +449,7 @@ extension NewGutenbergViewController: GutenbergKit.EditorViewControllerDelegate mediaPickerHelper.presentSiteMediaPicker(filter: flags, allowMultipleSelection: config.multiple, initialSelection: initialSelectionArray) { [weak self] assets in guard let self, let media = assets as? [Media] else { - self?.editorViewController?.setMediaUploadAttachment("[]") + self?.editorViewController.setMediaUploadAttachment("[]") return } let mediaInfos = media.map { item in @@ -454,7 +462,7 @@ extension NewGutenbergViewController: GutenbergKit.EditorViewControllerDelegate if let jsonString = convertMediaInfoArrayToJSONString(mediaInfos) { // Escape the string for JavaScript let escapedJsonString = jsonString.replacingOccurrences(of: "'", with: "\\'") - editorViewController?.setMediaUploadAttachment(escapedJsonString) + editorViewController.setMediaUploadAttachment(escapedJsonString) } } } @@ -663,11 +671,11 @@ extension NewGutenbergViewController: PostEditorNavigationBarManagerDelegate { } func navigationBarManager(_ manager: PostEditorNavigationBarManager, undoWasPressed sender: UIButton) { - editorViewController?.undo() + editorViewController.undo() } func navigationBarManager(_ manager: PostEditorNavigationBarManager, redoWasPressed sender: UIButton) { - editorViewController?.redo() + editorViewController.redo() } func navigationBarManager(_ manager: PostEditorNavigationBarManager, moreWasPressed sender: UIButton) { @@ -754,8 +762,8 @@ extension NewGutenbergViewController { private func makeMoreMenuActions() -> [UIAction] { var actions: [UIAction] = [] - let toggleModeTitle = editorViewController?.isCodeEditorEnabled ?? false ? Strings.visualEditor : Strings.codeEditor - let toggleModeIconName = editorViewController?.isCodeEditorEnabled ?? false ? "doc.richtext" : "curlybraces" + let toggleModeTitle = editorViewController.isCodeEditorEnabled ? Strings.visualEditor : Strings.codeEditor + let toggleModeIconName = editorViewController.isCodeEditorEnabled ? "doc.richtext" : "curlybraces" actions.append(UIAction(title: toggleModeTitle, image: UIImage(systemName: toggleModeIconName)) { [weak self] _ in self?.toggleEditingMode() }) From 17d5798c6b9d1636f276fe1eae22cfbe2f5baff1 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Fri, 4 Apr 2025 15:46:08 -0400 Subject: [PATCH 07/23] refactor: Defer editor start --- .../NewGutenbergViewController.swift | 28 +++++-------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift index c499b8f2a297..2f372a460874 100644 --- a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift +++ b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift @@ -359,36 +359,21 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor } service.fetchSettings { [weak self] result in - guard let self = self else { return } + guard let self else { return } DispatchQueue.main.async { - self.hideActivityIndicator() - switch result { case .success(let settings): // Update the editor configuration with the fetched settings var updatedConfig = self.editorViewController.configuration - updatedConfig.blockEditorSettings = settings - - // Create a new editor view controller with the updated configuration - let newEditorVC = GutenbergKit.EditorViewController(configuration: updatedConfig) - newEditorVC.delegate = self - - // Replace the old editor view controller with the new one - self.editorViewController.willMove(toParent: nil) - self.editorViewController.view.removeFromSuperview() - self.editorViewController.removeFromParent() - - self.addChild(newEditorVC) - self.view.addSubview(newEditorVC.view) - self.view.pinSubviewToAllEdges(newEditorVC.view) - newEditorVC.didMove(toParent: self) - - // Update the reference to the editor view controller - self.editorViewController = newEditorVC + updatedConfig.updateEditorSettings(settings) + self.editorViewController.updateConfiguration(updatedConfig) + self.editorViewController.startEditorSetup() case .failure(let error): + // Start the editor with the default settings DDLogError("Error fetching block editor settings: \(error)") + self.editorViewController.startEditorSetup() } } } @@ -404,6 +389,7 @@ extension NewGutenbergViewController: GutenbergKit.EditorViewControllerDelegate // is still reflecting the actual startup time of the editor editorSession.start() } + self.hideActivityIndicator() } func editor(_ viewContoller: GutenbergKit.EditorViewController, didDisplayInitialContent content: String) { From de5e535aa3db04c992fa7767b356ad8f2e73f07d Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Sat, 5 Apr 2025 08:12:17 -0400 Subject: [PATCH 08/23] refactor: Simplify RawBlockEditorSettingsService Remove unnecessary completion handler structure. --- .../RawBlockEditorSettingsService.swift | 24 +-------------- .../NewGutenbergViewController.swift | 29 ++++++++----------- 2 files changed, 13 insertions(+), 40 deletions(-) diff --git a/WordPress/Classes/Services/RawBlockEditorSettingsService.swift b/WordPress/Classes/Services/RawBlockEditorSettingsService.swift index 24f88efbd732..c412d3b5cb1b 100644 --- a/WordPress/Classes/Services/RawBlockEditorSettingsService.swift +++ b/WordPress/Classes/Services/RawBlockEditorSettingsService.swift @@ -3,8 +3,6 @@ import WordPressKit import WordPressShared class RawBlockEditorSettingsService { - typealias CompletionHandler = (Result<[String: Any], Error>) -> Void - private let blog: Blog private let remoteAPI: WordPressOrgRestApi @@ -17,29 +15,9 @@ class RawBlockEditorSettingsService { self.remoteAPI = remoteAPI } - func fetchSettings(completion: @escaping CompletionHandler) { - Task { @MainActor in - do { - let result = await self.remoteAPI.get(path: "/wp-block-editor/v1/settings") - switch result { - case .success(let response): - guard let dictionary = response as? [String: Any] else { - completion(.failure(NSError(domain: "RawBlockEditorSettingsService", code: 1, userInfo: [NSLocalizedDescriptionKey: "Invalid response format"]))) - return - } - completion(.success(dictionary)) - case .failure(let error): - completion(.failure(error)) - } - } catch { - completion(.failure(error)) - } - } - } - @MainActor func fetchSettings() async throws -> [String: Any] { - let result = await self.remoteAPI.get(path: "/wp-block-editor/v1/settings", parameters: ["context": "mobile"]) + let result = await self.remoteAPI.get(path: "/wp-block-editor/v1/settings") switch result { case .success(let response): guard let dictionary = response as? [String: Any] else { diff --git a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift index 2f372a460874..5af826dcc30e 100644 --- a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift +++ b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift @@ -358,23 +358,18 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor return } - service.fetchSettings { [weak self] result in - guard let self else { return } - - DispatchQueue.main.async { - switch result { - case .success(let settings): - // Update the editor configuration with the fetched settings - var updatedConfig = self.editorViewController.configuration - updatedConfig.updateEditorSettings(settings) - self.editorViewController.updateConfiguration(updatedConfig) - self.editorViewController.startEditorSetup() - - case .failure(let error): - // Start the editor with the default settings - DDLogError("Error fetching block editor settings: \(error)") - self.editorViewController.startEditorSetup() - } + Task { @MainActor in + do { + let settings = try await service.fetchSettings() + // Update the editor configuration with the fetched settings + var updatedConfig = self.editorViewController.configuration + updatedConfig.updateEditorSettings(settings) + self.editorViewController.updateConfiguration(updatedConfig) + self.editorViewController.startEditorSetup() + } catch { + // Start the editor with the default settings + DDLogError("Error fetching block editor settings: \(error)") + self.editorViewController.startEditorSetup() } } } From e5222b438b3a62c38a59ce18175e9e6a6b7c9470 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Sat, 5 Apr 2025 08:25:44 -0400 Subject: [PATCH 09/23] feat: Cache editor settings fetch Improve editor start performance. --- .../Services/RawBlockEditorSettingsService.swift | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/WordPress/Classes/Services/RawBlockEditorSettingsService.swift b/WordPress/Classes/Services/RawBlockEditorSettingsService.swift index c412d3b5cb1b..f1026be61dbf 100644 --- a/WordPress/Classes/Services/RawBlockEditorSettingsService.swift +++ b/WordPress/Classes/Services/RawBlockEditorSettingsService.swift @@ -6,6 +6,10 @@ class RawBlockEditorSettingsService { private let blog: Blog private let remoteAPI: WordPressOrgRestApi + // Cache for the settings + private var cachedSettings: [String: Any]? + private var lastFetchTime: Date? + init?(blog: Blog) { guard let remoteAPI = WordPressOrgRestApi(blog: blog) else { return nil @@ -17,12 +21,22 @@ class RawBlockEditorSettingsService { @MainActor func fetchSettings() async throws -> [String: Any] { + // If we have cached settings and they're less than 5 minutes old, return them + if let cachedSettings = cachedSettings, + let lastFetchTime = lastFetchTime, + Date().timeIntervalSince(lastFetchTime) < 300 { // 5 minutes + return cachedSettings + } + let result = await self.remoteAPI.get(path: "/wp-block-editor/v1/settings") switch result { case .success(let response): guard let dictionary = response as? [String: Any] else { throw NSError(domain: "RawBlockEditorSettingsService", code: 1, userInfo: [NSLocalizedDescriptionKey: "Invalid response format"]) } + // Cache the successful response + cachedSettings = dictionary + lastFetchTime = Date() return dictionary case .failure(let error): throw error From 45c644cecc1926ff4c7e89958e8cab3b412d3815 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Sat, 5 Apr 2025 08:26:27 -0400 Subject: [PATCH 10/23] feat: Editor settings use stale-while-revalidate cache Mitigate stale editor settings. --- .../RawBlockEditorSettingsService.swift | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/WordPress/Classes/Services/RawBlockEditorSettingsService.swift b/WordPress/Classes/Services/RawBlockEditorSettingsService.swift index f1026be61dbf..21c6e5edbe7b 100644 --- a/WordPress/Classes/Services/RawBlockEditorSettingsService.swift +++ b/WordPress/Classes/Services/RawBlockEditorSettingsService.swift @@ -9,6 +9,7 @@ class RawBlockEditorSettingsService { // Cache for the settings private var cachedSettings: [String: Any]? private var lastFetchTime: Date? + private var isRefreshing: Bool = false init?(blog: Blog) { guard let remoteAPI = WordPressOrgRestApi(blog: blog) else { @@ -21,20 +22,38 @@ class RawBlockEditorSettingsService { @MainActor func fetchSettings() async throws -> [String: Any] { - // If we have cached settings and they're less than 5 minutes old, return them - if let cachedSettings = cachedSettings, - let lastFetchTime = lastFetchTime, - Date().timeIntervalSince(lastFetchTime) < 300 { // 5 minutes + // Start a background refresh if needed + if !isRefreshing && (lastFetchTime == nil || Date().timeIntervalSince(lastFetchTime!) >= 300) { + isRefreshing = true + Task { + do { + let result = await self.remoteAPI.get(path: "/wp-block-editor/v1/settings") + switch result { + case .success(let response): + if let dictionary = response as? [String: Any] { + cachedSettings = dictionary + lastFetchTime = Date() + } + case .failure(let error): + DDLogError("Error refreshing block editor settings: \(error)") + } + isRefreshing = false + } + } + } + + // Return cached settings if available, otherwise fetch fresh + if let cachedSettings = cachedSettings { return cachedSettings } + // If no cache, fetch synchronously let result = await self.remoteAPI.get(path: "/wp-block-editor/v1/settings") switch result { case .success(let response): guard let dictionary = response as? [String: Any] else { throw NSError(domain: "RawBlockEditorSettingsService", code: 1, userInfo: [NSLocalizedDescriptionKey: "Invalid response format"]) } - // Cache the successful response cachedSettings = dictionary lastFetchTime = Date() return dictionary From 4ce5fd927cf6c82cc8bcff85578cd51eb50e6887 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Sat, 5 Apr 2025 08:36:29 -0400 Subject: [PATCH 11/23] style: Use shorthand --- WordPress/Classes/Services/RawBlockEditorSettingsService.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/Classes/Services/RawBlockEditorSettingsService.swift b/WordPress/Classes/Services/RawBlockEditorSettingsService.swift index 21c6e5edbe7b..8b114127a502 100644 --- a/WordPress/Classes/Services/RawBlockEditorSettingsService.swift +++ b/WordPress/Classes/Services/RawBlockEditorSettingsService.swift @@ -43,7 +43,7 @@ class RawBlockEditorSettingsService { } // Return cached settings if available, otherwise fetch fresh - if let cachedSettings = cachedSettings { + if let cachedSettings { return cachedSettings } From 0e4ba9743493cedfa40243bea392c9f03d64dde1 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Sat, 5 Apr 2025 08:53:28 -0400 Subject: [PATCH 12/23] feat: Cache editor settings across editor sessions The cache was discarded each time the editor closed, which defeats the purpose of the cache. We must store it somewhere outside the editor view controller. --- .../Blog/Blog+RawBlockEditorSettings.swift | 27 +++++++++++++++++++ .../RawBlockEditorSettingsService.swift | 16 +++++------ 2 files changed, 33 insertions(+), 10 deletions(-) create mode 100644 WordPress/Classes/Models/Blog/Blog+RawBlockEditorSettings.swift diff --git a/WordPress/Classes/Models/Blog/Blog+RawBlockEditorSettings.swift b/WordPress/Classes/Models/Blog/Blog+RawBlockEditorSettings.swift new file mode 100644 index 000000000000..0a4c595dc6f9 --- /dev/null +++ b/WordPress/Classes/Models/Blog/Blog+RawBlockEditorSettings.swift @@ -0,0 +1,27 @@ +import Foundation +import CoreData + +extension Blog { + private static let rawBlockEditorSettingsKey = "rawBlockEditorSettings" + private static let rawBlockEditorSettingsLastFetchTimeKey = "rawBlockEditorSettingsLastFetchTime" + + /// Stores the raw block editor settings dictionary + var rawBlockEditorSettings: [String: Any]? { + get { + return getOptionValue(Self.rawBlockEditorSettingsKey) as? [String: Any] + } + set { + setValue(newValue, forOption: Self.rawBlockEditorSettingsKey) + } + } + + /// Stores the last time the raw block editor settings were fetched + var rawBlockEditorSettingsLastFetchTime: Date? { + get { + return getOptionValue(Self.rawBlockEditorSettingsLastFetchTimeKey) as? Date + } + set { + setValue(newValue, forOption: Self.rawBlockEditorSettingsLastFetchTimeKey) + } + } +} diff --git a/WordPress/Classes/Services/RawBlockEditorSettingsService.swift b/WordPress/Classes/Services/RawBlockEditorSettingsService.swift index 8b114127a502..c882fb254252 100644 --- a/WordPress/Classes/Services/RawBlockEditorSettingsService.swift +++ b/WordPress/Classes/Services/RawBlockEditorSettingsService.swift @@ -5,10 +5,6 @@ import WordPressShared class RawBlockEditorSettingsService { private let blog: Blog private let remoteAPI: WordPressOrgRestApi - - // Cache for the settings - private var cachedSettings: [String: Any]? - private var lastFetchTime: Date? private var isRefreshing: Bool = false init?(blog: Blog) { @@ -23,7 +19,7 @@ class RawBlockEditorSettingsService { @MainActor func fetchSettings() async throws -> [String: Any] { // Start a background refresh if needed - if !isRefreshing && (lastFetchTime == nil || Date().timeIntervalSince(lastFetchTime!) >= 300) { + if !isRefreshing && (blog.rawBlockEditorSettingsLastFetchTime == nil || Date().timeIntervalSince(blog.rawBlockEditorSettingsLastFetchTime!) >= 0) { isRefreshing = true Task { do { @@ -31,8 +27,8 @@ class RawBlockEditorSettingsService { switch result { case .success(let response): if let dictionary = response as? [String: Any] { - cachedSettings = dictionary - lastFetchTime = Date() + blog.rawBlockEditorSettings = dictionary + blog.rawBlockEditorSettingsLastFetchTime = Date() } case .failure(let error): DDLogError("Error refreshing block editor settings: \(error)") @@ -43,7 +39,7 @@ class RawBlockEditorSettingsService { } // Return cached settings if available, otherwise fetch fresh - if let cachedSettings { + if let cachedSettings = blog.rawBlockEditorSettings { return cachedSettings } @@ -54,8 +50,8 @@ class RawBlockEditorSettingsService { guard let dictionary = response as? [String: Any] else { throw NSError(domain: "RawBlockEditorSettingsService", code: 1, userInfo: [NSLocalizedDescriptionKey: "Invalid response format"]) } - cachedSettings = dictionary - lastFetchTime = Date() + blog.rawBlockEditorSettings = dictionary + blog.rawBlockEditorSettingsLastFetchTime = Date() return dictionary case .failure(let error): throw error From c379b1ae4c318396e76aa41423fc42e957319f08 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Sat, 5 Apr 2025 09:24:13 -0400 Subject: [PATCH 13/23] refactor: Avoid duplicative settings requests Avoid sending both foreground and background requests. --- .../RawBlockEditorSettingsService.swift | 60 +++++++++++-------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/WordPress/Classes/Services/RawBlockEditorSettingsService.swift b/WordPress/Classes/Services/RawBlockEditorSettingsService.swift index c882fb254252..6ed9efb7cd4b 100644 --- a/WordPress/Classes/Services/RawBlockEditorSettingsService.swift +++ b/WordPress/Classes/Services/RawBlockEditorSettingsService.swift @@ -16,45 +16,57 @@ class RawBlockEditorSettingsService { self.remoteAPI = remoteAPI } + @MainActor + private func fetchSettingsFromAPI() async throws -> [String: Any] { + let result = await self.remoteAPI.get(path: "/wp-block-editor/v1/settings") + switch result { + case .success(let response): + guard let dictionary = response as? [String: Any] else { + throw NSError(domain: "RawBlockEditorSettingsService", code: 1, userInfo: [NSLocalizedDescriptionKey: "Invalid response format"]) + } + blog.rawBlockEditorSettings = dictionary + blog.rawBlockEditorSettingsLastFetchTime = Date() + return dictionary + case .failure(let error): + throw error + } + } + @MainActor func fetchSettings() async throws -> [String: Any] { // Start a background refresh if needed - if !isRefreshing && (blog.rawBlockEditorSettingsLastFetchTime == nil || Date().timeIntervalSince(blog.rawBlockEditorSettingsLastFetchTime!) >= 0) { + if !isRefreshing && (blog.rawBlockEditorSettingsLastFetchTime == nil || Date().timeIntervalSince(blog.rawBlockEditorSettingsLastFetchTime!) >= 300) { isRefreshing = true Task { do { - let result = await self.remoteAPI.get(path: "/wp-block-editor/v1/settings") - switch result { - case .success(let response): - if let dictionary = response as? [String: Any] { - blog.rawBlockEditorSettings = dictionary - blog.rawBlockEditorSettingsLastFetchTime = Date() - } - case .failure(let error): - DDLogError("Error refreshing block editor settings: \(error)") - } - isRefreshing = false + _ = try await fetchSettingsFromAPI() + } catch { + DDLogError("Error refreshing block editor settings: \(error)") } + isRefreshing = false } } - // Return cached settings if available, otherwise fetch fresh + // Return cached settings if available if let cachedSettings = blog.rawBlockEditorSettings { return cachedSettings } - // If no cache, fetch synchronously - let result = await self.remoteAPI.get(path: "/wp-block-editor/v1/settings") - switch result { - case .success(let response): - guard let dictionary = response as? [String: Any] else { - throw NSError(domain: "RawBlockEditorSettingsService", code: 1, userInfo: [NSLocalizedDescriptionKey: "Invalid response format"]) + // If no cache and no background refresh in progress, fetch synchronously + if !isRefreshing { + return try await fetchSettingsFromAPI() + } + + // If we're here, it means a background refresh is in progress + // Wait for it to complete and return the cached result + while isRefreshing { + try await Task.sleep(nanoseconds: 100_000_000) // 100ms + if let cachedSettings = blog.rawBlockEditorSettings { + return cachedSettings } - blog.rawBlockEditorSettings = dictionary - blog.rawBlockEditorSettingsLastFetchTime = Date() - return dictionary - case .failure(let error): - throw error } + + // If we still don't have settings after the refresh completed, throw an error + throw NSError(domain: "RawBlockEditorSettingsService", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to fetch block editor settings"]) } } From e19b6691defae9fff43fedfac250f12a00fe78ef Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Sat, 5 Apr 2025 09:34:15 -0400 Subject: [PATCH 14/23] feat: Remove time-based cache Revalidating the cache on each editor launch will ensure the latest settings are available. --- .../Models/Blog/Blog+RawBlockEditorSettings.swift | 11 ----------- .../Services/RawBlockEditorSettingsService.swift | 5 ++--- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/WordPress/Classes/Models/Blog/Blog+RawBlockEditorSettings.swift b/WordPress/Classes/Models/Blog/Blog+RawBlockEditorSettings.swift index 0a4c595dc6f9..29889c9170d1 100644 --- a/WordPress/Classes/Models/Blog/Blog+RawBlockEditorSettings.swift +++ b/WordPress/Classes/Models/Blog/Blog+RawBlockEditorSettings.swift @@ -3,7 +3,6 @@ import CoreData extension Blog { private static let rawBlockEditorSettingsKey = "rawBlockEditorSettings" - private static let rawBlockEditorSettingsLastFetchTimeKey = "rawBlockEditorSettingsLastFetchTime" /// Stores the raw block editor settings dictionary var rawBlockEditorSettings: [String: Any]? { @@ -14,14 +13,4 @@ extension Blog { setValue(newValue, forOption: Self.rawBlockEditorSettingsKey) } } - - /// Stores the last time the raw block editor settings were fetched - var rawBlockEditorSettingsLastFetchTime: Date? { - get { - return getOptionValue(Self.rawBlockEditorSettingsLastFetchTimeKey) as? Date - } - set { - setValue(newValue, forOption: Self.rawBlockEditorSettingsLastFetchTimeKey) - } - } } diff --git a/WordPress/Classes/Services/RawBlockEditorSettingsService.swift b/WordPress/Classes/Services/RawBlockEditorSettingsService.swift index 6ed9efb7cd4b..e5ed260db38f 100644 --- a/WordPress/Classes/Services/RawBlockEditorSettingsService.swift +++ b/WordPress/Classes/Services/RawBlockEditorSettingsService.swift @@ -25,7 +25,6 @@ class RawBlockEditorSettingsService { throw NSError(domain: "RawBlockEditorSettingsService", code: 1, userInfo: [NSLocalizedDescriptionKey: "Invalid response format"]) } blog.rawBlockEditorSettings = dictionary - blog.rawBlockEditorSettingsLastFetchTime = Date() return dictionary case .failure(let error): throw error @@ -34,8 +33,8 @@ class RawBlockEditorSettingsService { @MainActor func fetchSettings() async throws -> [String: Any] { - // Start a background refresh if needed - if !isRefreshing && (blog.rawBlockEditorSettingsLastFetchTime == nil || Date().timeIntervalSince(blog.rawBlockEditorSettingsLastFetchTime!) >= 300) { + // Start a background refresh if not already refreshing + if !isRefreshing { isRefreshing = true Task { do { From 09ffd57ebc485ec77852cecc6fa6c585c7c69f0b Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Wed, 9 Apr 2025 15:07:05 -0400 Subject: [PATCH 15/23] build: Update GutenbergKit ref --- Modules/Package.swift | 2 +- WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/Package.swift b/Modules/Package.swift index e3cb397670b8..09f15bdb2395 100644 --- a/Modules/Package.swift +++ b/Modules/Package.swift @@ -52,7 +52,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-20250411"), - .package(url: "https://github.com/wordpress-mobile/GutenbergKit", revision: "fc369073730384c8946bee15ec8ff7c763cf69c9"), + .package(url: "https://github.com/wordpress-mobile/GutenbergKit", revision: "9092f64e3346a998ed652def5efef01b8b3b84f6"), .package(url: "https://github.com/Automattic/color-studio", branch: "trunk"), .package(url: "https://github.com/wordpress-mobile/AztecEditor-iOS", from: "1.20.0"), ], diff --git a/WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved b/WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved index 72e7a8c77931..38c712c68583 100644 --- a/WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "645dd3ef573ab8e20ff04cddccb0ffcc29b7a5f354c0eb4d8f44d125b5a8694d", + "originHash" : "5798ddaf4845488f6935e7f5e7e50c607e70ab571da3ad4aeaf198ed82975e75", "pins" : [ { "identity" : "alamofire", @@ -150,7 +150,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/wordpress-mobile/GutenbergKit", "state" : { - "revision" : "fc369073730384c8946bee15ec8ff7c763cf69c9" + "revision" : "9092f64e3346a998ed652def5efef01b8b3b84f6" } }, { From 676f0ff5ea71ff72187e636f58918d4da0ba4e99 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Fri, 11 Apr 2025 14:37:38 -0400 Subject: [PATCH 16/23] build: Update GutenbergKit ref --- Modules/Package.swift | 2 +- WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/Package.swift b/Modules/Package.swift index 09f15bdb2395..3c3acaa194db 100644 --- a/Modules/Package.swift +++ b/Modules/Package.swift @@ -52,7 +52,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-20250411"), - .package(url: "https://github.com/wordpress-mobile/GutenbergKit", revision: "9092f64e3346a998ed652def5efef01b8b3b84f6"), + .package(url: "https://github.com/wordpress-mobile/GutenbergKit", revision: "cf5773f9f463d464d1f34bbab839a1a8b6c0833a"), .package(url: "https://github.com/Automattic/color-studio", branch: "trunk"), .package(url: "https://github.com/wordpress-mobile/AztecEditor-iOS", from: "1.20.0"), ], diff --git a/WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved b/WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved index 38c712c68583..daa21c3343ab 100644 --- a/WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "5798ddaf4845488f6935e7f5e7e50c607e70ab571da3ad4aeaf198ed82975e75", + "originHash" : "8ed966263b0e58dfaa8e186784f474fac0c0ea68c15a1a6eb1d8c26ae1c46a27", "pins" : [ { "identity" : "alamofire", @@ -150,7 +150,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/wordpress-mobile/GutenbergKit", "state" : { - "revision" : "9092f64e3346a998ed652def5efef01b8b3b84f6" + "revision" : "cf5773f9f463d464d1f34bbab839a1a8b6c0833a" } }, { From bf7fcfff7768264b0693b199aac833418330fdf0 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Fri, 11 Apr 2025 15:54:13 -0400 Subject: [PATCH 17/23] build: Update GutenbergKit ref --- Modules/Package.swift | 2 +- WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/Package.swift b/Modules/Package.swift index 3c3acaa194db..b53ec46e55a6 100644 --- a/Modules/Package.swift +++ b/Modules/Package.swift @@ -52,7 +52,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-20250411"), - .package(url: "https://github.com/wordpress-mobile/GutenbergKit", revision: "cf5773f9f463d464d1f34bbab839a1a8b6c0833a"), + .package(url: "https://github.com/wordpress-mobile/GutenbergKit", revision: "170d68265cda8d360e58aece4b9faf7ee2726dea"), .package(url: "https://github.com/Automattic/color-studio", branch: "trunk"), .package(url: "https://github.com/wordpress-mobile/AztecEditor-iOS", from: "1.20.0"), ], diff --git a/WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved b/WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved index daa21c3343ab..7b5202b48d58 100644 --- a/WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "8ed966263b0e58dfaa8e186784f474fac0c0ea68c15a1a6eb1d8c26ae1c46a27", + "originHash" : "dd9b1083a3c310f3ae1856ebbcac1d23a5e730c699c98da5026a2ac36f8a791d", "pins" : [ { "identity" : "alamofire", @@ -150,7 +150,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/wordpress-mobile/GutenbergKit", "state" : { - "revision" : "cf5773f9f463d464d1f34bbab839a1a8b6c0833a" + "revision" : "170d68265cda8d360e58aece4b9faf7ee2726dea" } }, { From fe5ff15be06f74b66edb43437161331fd14f3ef4 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Thu, 17 Apr 2025 14:32:48 -0400 Subject: [PATCH 18/23] build: Update GutenbergKit ref --- Modules/Package.swift | 2 +- WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/Package.swift b/Modules/Package.swift index b53ec46e55a6..db37db909baa 100644 --- a/Modules/Package.swift +++ b/Modules/Package.swift @@ -52,7 +52,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-20250411"), - .package(url: "https://github.com/wordpress-mobile/GutenbergKit", revision: "170d68265cda8d360e58aece4b9faf7ee2726dea"), + .package(url: "https://github.com/wordpress-mobile/GutenbergKit", revision: "a1c456afd2e873700ad742535cefd1a5f95c9306"), .package(url: "https://github.com/Automattic/color-studio", branch: "trunk"), .package(url: "https://github.com/wordpress-mobile/AztecEditor-iOS", from: "1.20.0"), ], diff --git a/WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved b/WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved index 7b5202b48d58..ce42e32419f1 100644 --- a/WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "dd9b1083a3c310f3ae1856ebbcac1d23a5e730c699c98da5026a2ac36f8a791d", + "originHash" : "3e4dc68b43ea48afc0ea0cc78b65ead27a1c670f1eaac891c609860328aa0abf", "pins" : [ { "identity" : "alamofire", @@ -150,7 +150,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/wordpress-mobile/GutenbergKit", "state" : { - "revision" : "170d68265cda8d360e58aece4b9faf7ee2726dea" + "revision" : "a1c456afd2e873700ad742535cefd1a5f95c9306" } }, { From bfa177ea60631691b9d85da52b594e3e77a38d1a Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Thu, 17 Apr 2025 14:46:03 -0400 Subject: [PATCH 19/23] perf: Preload editor settings within My Site Improve load speed of editor. --- .../Blog/My Site/MySiteViewController.swift | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController.swift b/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController.swift index becc44a14f12..9461a1bd2de3 100644 --- a/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController.swift +++ b/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController.swift @@ -70,6 +70,10 @@ final class MySiteViewController: UIViewController, UIScrollViewDelegate, NoSite // MARK: - Dependencies private let overlaysCoordinator: MySiteOverlaysCoordinator + private lazy var editorSettingsService: RawBlockEditorSettingsService? = { + guard let blog, FeatureFlag.newGutenberg.enabled || RemoteFeatureFlag.newGutenberg.enabled() else { return nil } + return RawBlockEditorSettingsService(blog: blog) + }() // TODO: (reader) factor if out of `MySiteVC` for a production version var isReaderAppModeEnabled = false @@ -381,6 +385,24 @@ final class MySiteViewController: UIViewController, UIScrollViewDelegate, NoSite hideBlogDetails() showDashboard(for: blog) } + + if FeatureFlag.newGutenberg.enabled || RemoteFeatureFlag.newGutenberg.enabled() { + // Update editor settings service with new blog and fetch settings + editorSettingsService = RawBlockEditorSettingsService(blog: blog) + fetchEditorSettings() + } + } + + private func fetchEditorSettings() { + guard let service = editorSettingsService else { return } + + Task { @MainActor in + do { + _ = try await service.fetchSettings() + } catch { + DDLogError("Error fetching editor settings: \(error)") + } + } } @objc From 8f121333de70af9188dcbd435504acd494e3fdeb Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Thu, 17 Apr 2025 14:46:49 -0400 Subject: [PATCH 20/23] fix: Enable GutenbergKit warmup via remote feature flag This conditional was overlooked when the local experimental feature toggle was augmented with a remote feature flag for roll out control. https://github.com/wordpress-mobile/WordPress-iOS/pull/24465 --- .../Classes/ViewRelated/Blog/My Site/MySiteViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController.swift b/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController.swift index 9461a1bd2de3..e70cee4926bd 100644 --- a/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController.swift +++ b/WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController.swift @@ -172,7 +172,7 @@ final class MySiteViewController: UIViewController, UIScrollViewDelegate, NoSite subscribeToPostPublished() subscribeToWillEnterForeground() - if FeatureFlag.newGutenberg.enabled { + if FeatureFlag.newGutenberg.enabled || RemoteFeatureFlag.newGutenberg.enabled() { GutenbergKit.EditorViewController.warmup() } } From acd2567e79094f222bdf6efaa7d27e2044b73ca5 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Wed, 23 Apr 2025 17:11:06 -0400 Subject: [PATCH 21/23] feat: Start editor without settings after three seconds Avoid postponing the editor start for longer than three seconds. We instead present the editor without any site-specific settings. --- .../NewGutenbergViewController.swift | 41 +++++++++++++++---- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift index 5af826dcc30e..43cde8ef58f1 100644 --- a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift +++ b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift @@ -74,6 +74,7 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor private var editorViewController: GutenbergKit.EditorViewController private var activityIndicator: UIActivityIndicatorView? + private var hasEditorStarted = false lazy var autosaver = Autosaver() { self.performAutoSave() @@ -354,22 +355,44 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor private func fetchBlockEditorSettings() { guard let service = rawBlockEditorSettingsService else { - hideActivityIndicator() + self.editorViewController.startEditorSetup() return } Task { @MainActor in + // Start the editor with default settings after 3 seconds + let timeoutTask = Task { + try await Task.sleep(nanoseconds: 3_000_000_000) // 3 seconds + if !Task.isCancelled && !self.hasEditorStarted { + self.hasEditorStarted = true + self.editorViewController.startEditorSetup() + } + } + do { let settings = try await service.fetchSettings() - // Update the editor configuration with the fetched settings - var updatedConfig = self.editorViewController.configuration - updatedConfig.updateEditorSettings(settings) - self.editorViewController.updateConfiguration(updatedConfig) - self.editorViewController.startEditorSetup() + // Cancel the timeout task since we got settings in time + timeoutTask.cancel() + + // Only start the editor if it hasn't been started yet + if !self.hasEditorStarted { + self.hasEditorStarted = true + // Update the editor configuration with the fetched settings + var updatedConfig = self.editorViewController.configuration + updatedConfig.updateEditorSettings(settings) + self.editorViewController.updateConfiguration(updatedConfig) + self.editorViewController.startEditorSetup() + } } catch { - // Start the editor with the default settings - DDLogError("Error fetching block editor settings: \(error)") - self.editorViewController.startEditorSetup() + // Cancel the timeout task since we got an error + timeoutTask.cancel() + + // Only start the editor if it hasn't been started yet + if !self.hasEditorStarted { + self.hasEditorStarted = true + // Start the editor with the default settings + self.editorViewController.startEditorSetup() + } } } } From e8a892c58c3176ab6974874ea2a4cc093cd24715 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Wed, 23 Apr 2025 17:16:49 -0400 Subject: [PATCH 22/23] refactor: DRY up fetching editor settings --- .../NewGutenbergViewController.swift | 41 ++++++++----------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift index 43cde8ef58f1..b518b0a2f38a 100644 --- a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift +++ b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift @@ -355,7 +355,7 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor private func fetchBlockEditorSettings() { guard let service = rawBlockEditorSettingsService else { - self.editorViewController.startEditorSetup() + startEditor() return } @@ -363,39 +363,34 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor // Start the editor with default settings after 3 seconds let timeoutTask = Task { try await Task.sleep(nanoseconds: 3_000_000_000) // 3 seconds - if !Task.isCancelled && !self.hasEditorStarted { - self.hasEditorStarted = true - self.editorViewController.startEditorSetup() + if !Task.isCancelled { + startEditor() } } do { let settings = try await service.fetchSettings() - // Cancel the timeout task since we got settings in time timeoutTask.cancel() - - // Only start the editor if it hasn't been started yet - if !self.hasEditorStarted { - self.hasEditorStarted = true - // Update the editor configuration with the fetched settings - var updatedConfig = self.editorViewController.configuration - updatedConfig.updateEditorSettings(settings) - self.editorViewController.updateConfiguration(updatedConfig) - self.editorViewController.startEditorSetup() - } + startEditor(with: settings) } catch { - // Cancel the timeout task since we got an error timeoutTask.cancel() - - // Only start the editor if it hasn't been started yet - if !self.hasEditorStarted { - self.hasEditorStarted = true - // Start the editor with the default settings - self.editorViewController.startEditorSetup() - } + DDLogError("Error fetching block editor settings: \(error)") + startEditor() } } } + + private func startEditor(with settings: [String: Any]? = nil) { + guard !hasEditorStarted else { return } + hasEditorStarted = true + + if let settings { + var updatedConfig = self.editorViewController.configuration + updatedConfig.updateEditorSettings(settings) + self.editorViewController.updateConfiguration(updatedConfig) + } + self.editorViewController.startEditorSetup() + } } extension NewGutenbergViewController: GutenbergKit.EditorViewControllerDelegate { From 487ede46443b77ad0109a1c5087f32c26e9b26f4 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Thu, 24 Apr 2025 16:00:06 -0400 Subject: [PATCH 23/23] build: Update GutenbergKit ref --- Modules/Package.swift | 2 +- WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/Package.swift b/Modules/Package.swift index db37db909baa..704b911a3908 100644 --- a/Modules/Package.swift +++ b/Modules/Package.swift @@ -52,7 +52,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-20250411"), - .package(url: "https://github.com/wordpress-mobile/GutenbergKit", revision: "a1c456afd2e873700ad742535cefd1a5f95c9306"), + .package(url: "https://github.com/wordpress-mobile/GutenbergKit", revision: "fa72e630203e7472d55f4abedfd5c462d2333584"), .package(url: "https://github.com/Automattic/color-studio", branch: "trunk"), .package(url: "https://github.com/wordpress-mobile/AztecEditor-iOS", from: "1.20.0"), ], diff --git a/WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved b/WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved index ce42e32419f1..302e7c504c9e 100644 --- a/WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "3e4dc68b43ea48afc0ea0cc78b65ead27a1c670f1eaac891c609860328aa0abf", + "originHash" : "c7fc894bd07cdfaf1241217c60af90ce11e6474d9488e6d63dd1e81668a87272", "pins" : [ { "identity" : "alamofire", @@ -150,7 +150,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/wordpress-mobile/GutenbergKit", "state" : { - "revision" : "a1c456afd2e873700ad742535cefd1a5f95c9306" + "revision" : "fa72e630203e7472d55f4abedfd5c462d2333584" } }, {