Skip to content

refactor: Use GutenbergKit configuration builder #24662

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: trunk
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import Foundation
import GutenbergKit
import WordPressData
import WordPressShared

extension EditorConfigurationBuilder {
init(blog: Blog) {
let selfHostedApiUrl = blog.url(withPath: "wp-json/")
let isWPComSite = blog.isHostedAtWPcom || blog.isAtomic()
let siteApiRoot = blog.isAccessibleThroughWPCom() && isWPComSite ? blog.wordPressComRestApi?.baseURL.absoluteString : selfHostedApiUrl
let siteId = blog.dotComID?.stringValue
let siteDomain = blog.primaryDomainAddress
let authToken = blog.authToken ?? ""
var authHeader = "Bearer \(authToken)"

let applicationPassword = try? blog.getApplicationToken()

if let appPassword = applicationPassword, let username = blog.username {
let credentials = "\(username):\(appPassword)"
if let credentialsData = credentials.data(using: .utf8) {
let base64Credentials = credentialsData.base64EncodedString()
authHeader = "Basic \(base64Credentials)"
}
}

// Must provide both namespace forms to detect usages of both forms in third-party code
var siteApiNamespace: [String] = []
if isWPComSite {
if let siteId {
siteApiNamespace.append("sites/\(siteId)")
}
siteApiNamespace.append("sites/\(siteDomain)")
}

self = EditorConfigurationBuilder()
.setSiteUrl(blog.url ?? "")
.setSiteApiRoot(siteApiRoot ?? "")
.setSiteApiNamespace(siteApiNamespace)
.setNamespaceExcludedPaths(["/wpcom/v2/following/recommendations", "/wpcom/v2/following/mine"])
.setAuthHeader(authHeader)
.setShouldUseThemeStyles(FeatureFlag.newGutenbergThemeStyles.enabled)

// Limited to Simple sites until application password auth is supported
if RemoteFeatureFlag.newGutenbergPlugins.enabled() && blog.isHostedAtWPcom {
self = self.setShouldUsePlugins(true)
if var editorAssetsEndpoint = blog.wordPressComRestApi?.baseURL {
editorAssetsEndpoint.appendPathComponent("wpcom/v2/sites")
if let siteId {
editorAssetsEndpoint.appendPathComponent(siteId)
} else {
editorAssetsEndpoint.appendPathComponent(siteDomain)
}
editorAssetsEndpoint.appendPathComponent("editor-assets")
self = self.setEditorAssetsEndpoint(editorAssetsEndpoint)
}
}
self = self.setLocale(WordPressComLanguageDatabase().deviceLanguage.slug)

if !blog.isSelfHosted {
let siteType: String = blog.isHostedAtWPcom ? "simple" : "atomic"
do {
self = self.setWebViewGlobals([
try WebViewGlobal(name: "_currentSiteType", value: .string(siteType))
])
} catch {
wpAssertionFailure("Failed to create WebViewGlobal", userInfo: ["error": "\(error)"])
self = self.setWebViewGlobals([])
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ final class MySiteViewController: UIViewController, UIScrollViewDelegate, NoSite

if RemoteFeatureFlag.newGutenberg.enabled() {
GutenbergKit.EditorViewController.warmup(
configuration: blog.flatMap(EditorConfiguration.init(blog:)) ?? .default
configuration: blog.flatMap { EditorConfigurationBuilder(blog: $0).build() } ?? .default
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ final class CommentGutenbergEditorViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()

var configuration = EditorConfiguration(content: initialContent ?? "")
configuration.hideTitle = true
let configuration = EditorConfigurationBuilder(content: initialContent ?? "")
.setShouldHideTitle(true)
.build()

let editorVC = GutenbergKit.EditorViewController(configuration: configuration)
editorVC.delegate = self
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,14 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor
self.editorSession = PostEditorAnalyticsSession(editor: .gutenbergKit, post: post)
self.navigationBarManager = navigationBarManager ?? PostEditorNavigationBarManager()

var conf = EditorConfiguration(blog: post.blog)
conf.title = post.postTitle ?? ""
conf.content = post.content ?? ""
conf.postID = post.postID?.intValue != -1 ? post.postID?.intValue : nil
conf.postType = post is Page ? "page" : "post"
let configuration = EditorConfigurationBuilder(blog: post.blog)
.setTitle(post.postTitle ?? "")
.setContent(post.content ?? "")
.setPostID(post.postID?.intValue != -1 ? post.postID?.intValue : nil)
.setPostType(post is Page ? "page" : "post")
.build()

self.editorViewController = GutenbergKit.EditorViewController(configuration: conf)
self.editorViewController = GutenbergKit.EditorViewController(configuration: configuration)

super.init(nibName: nil, bundle: nil)

Expand Down Expand Up @@ -331,8 +332,12 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor
hasEditorStarted = true

if let settings {
var updatedConfig = self.editorViewController.configuration
updatedConfig.updateEditorSettings(settings)
// TODO: `setEditorSettings` throws due to incompatibility between `[String: Any]`
// and `[String: Encodable]`. The latter is now expected by
// `GutenbergKitConfiguration.EditorSettings`. How should we reconcile this?
let updatedConfig = self.editorViewController.configuration.toBuilder()
.setEditorSettings(settings)
.build()
Comment on lines +335 to +340
Copy link
Member Author

@dcalhoun dcalhoun Jul 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@crazytonyli would you please help me determine a reasonable approach for managing type safety of fetching editor settings from the API endpoint and passing it to GutenbergKit?

GutenbergKit was updated in wordpress-mobile/GutenbergKit#146 to now expect [String: Encodable] rather than [String: Any]. This seems like a sound change, but my lack of Swift experience inhibits me from determining an appropriate solution for the logic here in WP-iOS.

Please feel free to push directly to this branch if that is easiest. Thank you!

self.editorViewController.updateConfiguration(updatedConfig)
}
self.editorViewController.startEditorSetup()
Expand Down Expand Up @@ -840,74 +845,6 @@ extension NewGutenbergViewController {
}
}

extension EditorConfiguration {
init(blog: Blog) {
let selfHostedApiUrl = blog.url(withPath: "wp-json/")
let isWPComSite = blog.isHostedAtWPcom || blog.isAtomic()
let siteApiRoot = blog.isAccessibleThroughWPCom() && isWPComSite ? blog.wordPressComRestApi?.baseURL.absoluteString : selfHostedApiUrl
let siteId = blog.dotComID?.stringValue
let siteDomain = blog.primaryDomainAddress
let authToken = blog.authToken ?? ""
var authHeader = "Bearer \(authToken)"

let applicationPassword = try? blog.getApplicationToken()

if let appPassword = applicationPassword, let username = blog.username {
let credentials = "\(username):\(appPassword)"
if let credentialsData = credentials.data(using: .utf8) {
let base64Credentials = credentialsData.base64EncodedString()
authHeader = "Basic \(base64Credentials)"
}
}

// Must provide both namespace forms to detect usages of both forms in third-party code
var siteApiNamespace: [String] = []
if isWPComSite {
if let siteId {
siteApiNamespace.append("sites/\(siteId)")
}
siteApiNamespace.append("sites/\(siteDomain)")
}

self = EditorConfiguration()

self.siteURL = blog.url ?? ""
self.siteApiRoot = siteApiRoot ?? ""
self.siteApiNamespace = siteApiNamespace
self.namespaceExcludedPaths = ["/wpcom/v2/following/recommendations", "/wpcom/v2/following/mine"]
self.authHeader = authHeader

self.themeStyles = FeatureFlag.newGutenbergThemeStyles.enabled
// Limited to Simple sites until application password auth is supported
if RemoteFeatureFlag.newGutenbergPlugins.enabled() && blog.isHostedAtWPcom {
self.plugins = true
if var editorAssetsEndpoint = blog.wordPressComRestApi?.baseURL {
editorAssetsEndpoint.appendPathComponent("wpcom/v2/sites")
if let siteId {
editorAssetsEndpoint.appendPathComponent(siteId)
} else {
editorAssetsEndpoint.appendPathComponent(siteDomain)
}
editorAssetsEndpoint.appendPathComponent("editor-assets")
self.editorAssetsEndpoint = editorAssetsEndpoint
}
}
self.locale = WordPressComLanguageDatabase().deviceLanguage.slug

if !blog.isSelfHosted {
let siteType: String = blog.isHostedAtWPcom ? "simple" : "atomic"
do {
self.webViewGlobals = [
try WebViewGlobal(name: "_currentSiteType", value: .string(siteType))
]
} catch {
wpAssertionFailure("Failed to create WebViewGlobal", userInfo: ["error": "\(error)"])
self.webViewGlobals = []
}
}
}
}

// Extend Gutenberg JavaScript exception struct to conform the protocol defined in the Crash Logging service
extension GutenbergJSException.StacktraceLine: @retroactive AutomatticTracks.JSStacktraceLine {}
extension GutenbergJSException: @retroactive AutomatticTracks.JSException {}