Skip to content

Commit aafcbc5

Browse files
jkmasseldcalhoun
andauthored
Simplify iOS Settings (#179)
* Remove WebViewGlobals #146 (comment) * Change `editorSettings` to a `String` The host app doesn’t need to parse it, so we can pass the raw settings * Fix tests * Add EditorConfigurationBuilder helper * Remove cookie configuration * Add tests * docs: Fix comment typo --------- Co-authored-by: David Calhoun <[email protected]>
1 parent ec8639d commit aafcbc5

File tree

4 files changed

+61
-229
lines changed

4 files changed

+61
-229
lines changed

ios/Sources/GutenbergKit/Sources/EditorConfiguration.swift

Lines changed: 27 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,13 @@ public struct EditorConfiguration {
2626
public let namespaceExcludedPaths: [String]
2727
/// Authorization header
2828
public let authHeader: String
29-
/// Global variables to be made available to the editor
30-
public let webViewGlobals: [WebViewGlobal]
3129
/// Raw block editor settings from the WordPress REST API
32-
public let editorSettings: EditorSettings
30+
public let editorSettings: String
3331
/// Locale used for translations
3432
public let locale: String
3533
/// Endpoint for loading editor assets, used when enabling `shouldUsePlugins`
3634
public var editorAssetsEndpoint: URL?
3735

38-
// Cookies
39-
public let cookies: [HTTPCookie]
40-
4136
/// Deliberately non-public – consumers should use `EditorConfigurationBuilder` to construct a configuration
4237
init(
4338
title: String,
@@ -52,11 +47,9 @@ public struct EditorConfiguration {
5247
siteApiNamespace: [String],
5348
namespaceExcludedPaths: [String],
5449
authHeader: String,
55-
webViewGlobals: [WebViewGlobal],
56-
editorSettings: EditorSettings,
50+
editorSettings: String,
5751
locale: String,
5852
editorAssetsEndpoint: URL? = nil,
59-
cookies: [HTTPCookie] = []
6053
) {
6154
self.title = title
6255
self.content = content
@@ -70,11 +63,9 @@ public struct EditorConfiguration {
7063
self.siteApiNamespace = siteApiNamespace
7164
self.namespaceExcludedPaths = namespaceExcludedPaths
7265
self.authHeader = authHeader
73-
self.webViewGlobals = webViewGlobals
7466
self.editorSettings = editorSettings
7567
self.locale = locale
7668
self.editorAssetsEndpoint = editorAssetsEndpoint
77-
self.cookies = cookies
7869
}
7970

8071
public func toBuilder() -> EditorConfigurationBuilder {
@@ -91,7 +82,6 @@ public struct EditorConfiguration {
9182
siteApiNamespace: siteApiNamespace,
9283
namespaceExcludedPaths: namespaceExcludedPaths,
9384
authHeader: authHeader,
94-
webViewGlobals: webViewGlobals,
9585
editorSettings: editorSettings,
9686
locale: locale,
9787
editorAssetsEndpoint: editorAssetsEndpoint
@@ -106,12 +96,6 @@ public struct EditorConfiguration {
10696
content.addingPercentEncoding(withAllowedCharacters: .alphanumerics)!
10797
}
10898

109-
var editorSettingsJSON: String {
110-
// `editorSettings` values are always `encodable` so this should never fail
111-
let jsonData = try! JSONSerialization.data(withJSONObject: editorSettings, options: [])
112-
return String(data: jsonData, encoding: .utf8) ?? "undefined"
113-
}
114-
11599
public static let `default` = EditorConfigurationBuilder().build()
116100
}
117101

@@ -128,8 +112,7 @@ public struct EditorConfigurationBuilder {
128112
private var siteApiNamespace: [String]
129113
private var namespaceExcludedPaths: [String]
130114
private var authHeader: String
131-
private var webViewGlobals: [WebViewGlobal]
132-
private var editorSettings: EditorSettings
115+
private var editorSettings: String
133116
private var locale: String
134117
private var editorAssetsEndpoint: URL?
135118

@@ -146,8 +129,7 @@ public struct EditorConfigurationBuilder {
146129
siteApiNamespace: [String] = [],
147130
namespaceExcludedPaths: [String] = [],
148131
authHeader: String = "",
149-
webViewGlobals: [WebViewGlobal] = [],
150-
editorSettings: EditorSettings = [:],
132+
editorSettings: String = "undefined",
151133
locale: String = "en",
152134
editorAssetsEndpoint: URL? = nil
153135
){
@@ -163,7 +145,6 @@ public struct EditorConfigurationBuilder {
163145
self.siteApiNamespace = siteApiNamespace
164146
self.namespaceExcludedPaths = namespaceExcludedPaths
165147
self.authHeader = authHeader
166-
self.webViewGlobals = webViewGlobals
167148
self.editorSettings = editorSettings
168149
self.locale = locale
169150
self.editorAssetsEndpoint = editorAssetsEndpoint
@@ -241,13 +222,7 @@ public struct EditorConfigurationBuilder {
241222
return copy
242223
}
243224

244-
public func setWebViewGlobals(_ webViewGlobals: [WebViewGlobal]) -> EditorConfigurationBuilder {
245-
var copy = self
246-
copy.webViewGlobals = webViewGlobals
247-
return copy
248-
}
249-
250-
public func setEditorSettings(_ editorSettings: EditorSettings) -> EditorConfigurationBuilder {
225+
public func setEditorSettings(_ editorSettings: String) -> EditorConfigurationBuilder {
251226
var copy = self
252227
copy.editorSettings = editorSettings
253228
return copy
@@ -265,6 +240,28 @@ public struct EditorConfigurationBuilder {
265240
return copy
266241
}
267242

243+
/// Simplify conditionally applying a configuration change
244+
///
245+
/// Sample Code:
246+
/// ```swift
247+
/// // Before
248+
/// let configurationBuilder = EditorConfigurationBuilder()
249+
/// if let postID = post.id {
250+
/// configurationBuilder = configurationBuilder.setPostID(postID)
251+
/// }
252+
///
253+
/// // After
254+
/// let configurationBuilder = EditorConfigurationBuilder()
255+
/// .apply(post.id, { $0.setPostID($1) } )
256+
/// ```
257+
public func apply<T>(_ value: T?, _ closure: (EditorConfigurationBuilder, T) -> EditorConfigurationBuilder) -> Self {
258+
guard let value else {
259+
return self
260+
}
261+
262+
return closure(self, value)
263+
}
264+
268265
public func build() -> EditorConfiguration {
269266
EditorConfiguration(
270267
title: title,
@@ -279,69 +276,13 @@ public struct EditorConfigurationBuilder {
279276
siteApiNamespace: siteApiNamespace,
280277
namespaceExcludedPaths: namespaceExcludedPaths,
281278
authHeader: authHeader,
282-
webViewGlobals: webViewGlobals,
283279
editorSettings: editorSettings,
284280
locale: locale,
285281
editorAssetsEndpoint: editorAssetsEndpoint
286282
)
287283
}
288284
}
289285

290-
public struct WebViewGlobal: Equatable {
291-
let name: String
292-
let value: WebViewGlobalValue
293-
294-
public init(name: String, value: WebViewGlobalValue) throws {
295-
// Validate name is a valid JavaScript identifier
296-
guard Self.isValidJavaScriptIdentifier(name) else {
297-
throw WebViewGlobalError.invalidIdentifier(name)
298-
}
299-
self.name = name
300-
self.value = value
301-
}
302-
303-
private static func isValidJavaScriptIdentifier(_ name: String) -> Bool {
304-
// Add validation logic for JavaScript identifiers
305-
return name.range(of: "^[a-zA-Z_$][a-zA-Z0-9_$]*$", options: .regularExpression) != nil
306-
}
307-
}
308-
309-
public enum WebViewGlobalError: Error {
310-
case invalidIdentifier(String)
311-
}
312-
313-
public enum WebViewGlobalValue: Equatable {
314-
case string(String)
315-
case number(Double)
316-
case boolean(Bool)
317-
case object([String: WebViewGlobalValue])
318-
case array([WebViewGlobalValue])
319-
case null
320-
321-
func toJavaScript() -> String {
322-
switch self {
323-
case .string(let str):
324-
return "\"\(str.escaped)\""
325-
case .number(let num):
326-
return "\(num)"
327-
case .boolean(let bool):
328-
return "\(bool)"
329-
case .object(let dict):
330-
let sortedKeys = dict.keys.sorted()
331-
var pairs: [String] = []
332-
for key in sortedKeys {
333-
let value = dict[key]!
334-
pairs.append("\"\(key.escaped)\": \(value.toJavaScript())")
335-
}
336-
return "{\(pairs.joined(separator: ","))}"
337-
case .array(let array):
338-
return "[\(array.map { $0.toJavaScript() }.joined(separator: ","))]"
339-
case .null:
340-
return "null"
341-
}
342-
}
343-
}
344-
345286
public typealias EditorSettings = [String: Encodable]
346287

347288
// String escaping extension

ios/Sources/GutenbergKit/Sources/EditorViewController.swift

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -146,13 +146,7 @@ public final class EditorViewController: UIViewController, GutenbergEditorContro
146146

147147
private func getEditorConfiguration() -> WKUserScript {
148148

149-
// Generate JavaScript globals
150-
let globalsJS = configuration.webViewGlobals.map { global in
151-
"window[\"\(global.name)\"] = \(global.value.toJavaScript());"
152-
}.joined(separator: "\n")
153-
154149
let jsCode = """
155-
\(globalsJS)
156150
157151
window.GBKit = {
158152
siteURL: '\(configuration.siteURL)',
@@ -162,7 +156,7 @@ public final class EditorViewController: UIViewController, GutenbergEditorContro
162156
authHeader: '\(configuration.authHeader)',
163157
themeStyles: \(configuration.shouldUseThemeStyles),
164158
hideTitle: \(configuration.shouldHideTitle),
165-
editorSettings: \(configuration.editorSettingsJSON),
159+
editorSettings: \(configuration.editorSettings),
166160
locale: '\(configuration.locale)',
167161
post: {
168162
id: \(configuration.postID ?? -1),

ios/Tests/GutenbergKitTests/EditorConfigurationBuilderTests.swift

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,14 @@ struct EditorConfigurationBuilderTests {
2020
#expect(builder.siteApiNamespace == [])
2121
#expect(builder.namespaceExcludedPaths == [])
2222
#expect(builder.authHeader == "")
23-
#expect(builder.webViewGlobals == [])
24-
#expect(builder.editorSettings.isEmpty)
23+
#expect(builder.editorSettings == "undefined")
2524
#expect(builder.locale == "en")
2625
#expect(builder.editorAssetsEndpoint == nil)
2726
}
2827

2928
@Test("Editor Configuration to Builder")
3029
func testThatEditorConfigurationToBuilder() throws {
31-
let configuration = try EditorConfigurationBuilder()
30+
let configuration = EditorConfigurationBuilder()
3231
.setTitle("Title")
3332
.setContent("Content")
3433
.setPostID(123)
@@ -41,8 +40,7 @@ struct EditorConfigurationBuilderTests {
4140
.setSiteApiNamespace(["wp", "v2"])
4241
.setNamespaceExcludedPaths(["jetpack"])
4342
.setAuthHeader("Bearer Token")
44-
.setWebViewGlobals([WebViewGlobal(name: "foo", value: .string("bar"))])
45-
.setEditorSettings(["foo":"bar"])
43+
.setEditorSettings(#"{"foo":"bar"}"#)
4644
.setLocale("fr")
4745
.setEditorAssetsEndpoint(URL(string: "https://example.com/wp-content/plugins/gutenberg/build/"))
4846
.build() // Convert to a configuration
@@ -61,8 +59,7 @@ struct EditorConfigurationBuilderTests {
6159
#expect(configuration.siteApiNamespace == ["wp", "v2"])
6260
#expect(configuration.namespaceExcludedPaths == ["jetpack"])
6361
#expect(configuration.authHeader == "Bearer Token")
64-
#expect(configuration.webViewGlobals == [try WebViewGlobal(name: "foo", value: .string("bar"))])
65-
#expect(configuration.editorSettingsJSON == #"{"foo":"bar"}"#)
62+
#expect(configuration.editorSettings == #"{"foo":"bar"}"#)
6663
#expect(configuration.locale == "fr")
6764
#expect(configuration.editorAssetsEndpoint == URL(string: "https://example.com/wp-content/plugins/gutenberg/build/"))
6865
}
@@ -138,25 +135,15 @@ struct EditorConfigurationBuilderTests {
138135
#expect(EditorConfigurationBuilder().setAuthHeader("Bearer token").build().authHeader == "Bearer token")
139136
}
140137

141-
@Test("Sets webViewGlobals Correctly")
142-
func editorConfigurationBuilderSetsWebViewGlobalsCorrectly() throws {
143-
#expect(
144-
try EditorConfigurationBuilder()
145-
.setWebViewGlobals([WebViewGlobal(name: "foo", value: .string("bar"))])
146-
.build()
147-
.webViewGlobals
148-
== [WebViewGlobal(name: "foo", value: .string("bar"))]
149-
)
150-
}
151-
152138
@Test("Sets editorSettings Correctly")
153139
func editorConfigurationBuilderSetsEditorSettingsCorrectly() throws {
140+
let json = #"{"foo":"bar"}"#
154141
#expect(
155142
EditorConfigurationBuilder()
156-
.setEditorSettings(["foo": "bar"])
143+
.setEditorSettings(json)
157144
.build()
158-
.editorSettingsJSON
159-
== "{\"foo\":\"bar\"}"
145+
.editorSettings
146+
== json
160147
)
161148
}
162149

@@ -169,4 +156,29 @@ struct EditorConfigurationBuilderTests {
169156
func editorConfigurationBuilderSetsEditorAssetsEndpointCorrectly() throws {
170157
#expect(EditorConfigurationBuilder().setEditorAssetsEndpoint(URL(string: "https://example.com/wp-content/plugins/gutenberg/build/")).build().editorAssetsEndpoint == URL(string: "https://example.com/wp-content/plugins/gutenberg/build/"))
171158
}
159+
160+
@Test("Applies values correctly")
161+
func editorConfigurationBuilderAppliesValuesCorrectly() throws {
162+
let string = "test"
163+
let nilString: String? = nil
164+
165+
let int = 1
166+
let nilInt: Int? = nil
167+
168+
#expect(EditorConfigurationBuilder().apply(string, { $0.setTitle($1) }).build().title == string)
169+
#expect(EditorConfigurationBuilder().apply(nilString, { $0.setTitle($1)}).build().title == "")
170+
171+
#expect(EditorConfigurationBuilder().apply(int, { $0.setPostID($1) }).build().postID == int)
172+
#expect(EditorConfigurationBuilder().apply(nilInt, { $0.setPostID($1)}).build().postID == nil)
173+
}
174+
175+
@Test("apply never calls the closure if the value is nil")
176+
func editorConfigurationBuilderApplyDoesNotCallClosureWithNilValue() throws {
177+
let string: String? = nil
178+
179+
_ = EditorConfigurationBuilder().apply(string, { builder, value in
180+
Issue.record("Closure was called")
181+
return builder
182+
})
183+
}
172184
}

0 commit comments

Comments
 (0)