Skip to content
Merged
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
Expand Up @@ -3,48 +3,6 @@ package org.wordpress.gutenberg
import android.os.Parcelable
import kotlinx.parcelize.Parcelize

@Parcelize
sealed class WebViewGlobalValue : Parcelable {
@Parcelize
data class StringValue(val value: String) : WebViewGlobalValue()
@Parcelize
data class NumberValue(val value: Double) : WebViewGlobalValue()
@Parcelize
data class BooleanValue(val value: Boolean) : WebViewGlobalValue()
@Parcelize
data class ObjectValue(val value: Map<String, WebViewGlobalValue>) : WebViewGlobalValue()
@Parcelize
data class ArrayValue(val value: List<WebViewGlobalValue>) : WebViewGlobalValue()
@Parcelize
object NullValue : WebViewGlobalValue()

fun toJavaScript(): String {
return when (this) {
is StringValue -> "\"${value.replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t")}\""
is NumberValue -> value.toString()
is BooleanValue -> value.toString()
is ObjectValue -> {
val pairs = value.map { (key, value) -> "\"$key\": ${value.toJavaScript()}" }
"{${pairs.joinToString(",")}}"
}
is ArrayValue -> "[${value.joinToString(",") { it.toJavaScript() }}]"
is NullValue -> "null"
}
}
}

@Parcelize
data class WebViewGlobal(
val name: String,
val value: WebViewGlobalValue
) : Parcelable {
init {
require(name.matches(Regex("^[a-zA-Z_$][a-zA-Z0-9_$]*$"))) {
"Invalid JavaScript identifier: $name"
}
}
}

@Parcelize
open class EditorConfiguration constructor(
val title: String,
Expand All @@ -59,7 +17,6 @@ open class EditorConfiguration constructor(
val siteApiNamespace: Array<String>,
val namespaceExcludedPaths: Array<String>,
val authHeader: String,
val webViewGlobals: List<WebViewGlobal>,
val editorSettings: String?,
val locale: String?,
val cookies: Map<String, String>,
Expand All @@ -85,7 +42,6 @@ open class EditorConfiguration constructor(
private var siteApiNamespace: Array<String> = arrayOf()
private var namespaceExcludedPaths: Array<String> = arrayOf()
private var authHeader: String = ""
private var webViewGlobals: List<WebViewGlobal> = emptyList()
private var editorSettings: String? = null
private var locale: String? = "en"
private var cookies: Map<String, String> = mapOf()
Expand All @@ -105,7 +61,6 @@ open class EditorConfiguration constructor(
fun setSiteApiNamespace(siteApiNamespace: Array<String>) = apply { this.siteApiNamespace = siteApiNamespace }
fun setNamespaceExcludedPaths(namespaceExcludedPaths: Array<String>) = apply { this.namespaceExcludedPaths = namespaceExcludedPaths }
fun setAuthHeader(authHeader: String) = apply { this.authHeader = authHeader }
fun setWebViewGlobals(webViewGlobals: List<WebViewGlobal>) = apply { this.webViewGlobals = webViewGlobals }
fun setEditorSettings(editorSettings: String?) = apply { this.editorSettings = editorSettings }
fun setLocale(locale: String?) = apply { this.locale = locale }
fun setCookies(cookies: Map<String, String>) = apply { this.cookies = cookies }
Expand All @@ -126,7 +81,6 @@ open class EditorConfiguration constructor(
siteApiNamespace = siteApiNamespace,
namespaceExcludedPaths = namespaceExcludedPaths,
authHeader = authHeader,
webViewGlobals = webViewGlobals,
editorSettings = editorSettings,
locale = locale,
cookies = cookies,
Expand Down Expand Up @@ -154,7 +108,6 @@ open class EditorConfiguration constructor(
if (!siteApiNamespace.contentEquals(other.siteApiNamespace)) return false
if (!namespaceExcludedPaths.contentEquals(other.namespaceExcludedPaths)) return false
if (authHeader != other.authHeader) return false
if (webViewGlobals != other.webViewGlobals) return false
if (editorSettings != other.editorSettings) return false
if (locale != other.locale) return false
if (cookies != other.cookies) return false
Expand All @@ -178,7 +131,6 @@ open class EditorConfiguration constructor(
result = 31 * result + siteApiNamespace.contentHashCode()
result = 31 * result + namespaceExcludedPaths.contentHashCode()
result = 31 * result + authHeader.hashCode()
result = 31 * result + webViewGlobals.hashCode()
result = 31 * result + (editorSettings?.hashCode() ?: 0)
result = 31 * result + (locale?.hashCode() ?: 0)
result = 31 * result + cookies.hashCode()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,11 +294,6 @@ class GutenbergView : WebView {
}

private fun setGlobalJavaScriptVariables() {
// Generate JavaScript globals
val globalsJS = configuration.webViewGlobals.map { global ->
"window[\"${global.name}\"] = ${global.value.toJavaScript()};"
}.joinToString("\n")

val escapedTitle = encodeForEditor(configuration.title)
val escapedContent = encodeForEditor(configuration.content)
val editorSettings = configuration.editorSettings ?: "undefined"
Expand All @@ -325,13 +320,7 @@ class GutenbergView : WebView {
localStorage.setItem('GBKit', JSON.stringify(window.GBKit));
""".trimIndent()

val combinedJS = if (globalsJS.isNotEmpty()) {
"$globalsJS\n$gbKitConfig"
} else {
gbKitConfig
}

this.evaluateJavascript(combinedJS, null)
this.evaluateJavascript(gbKitConfig, null)
}

private fun encodeForEditor(value: String): String {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,6 @@ class EditorConfigurationTest {
.setSiteApiNamespace(arrayOf("wp/v2"))
.setNamespaceExcludedPaths(arrayOf("users"))
.setAuthHeader("Bearer token")
.setWebViewGlobals(listOf(
WebViewGlobal("testString", WebViewGlobalValue.StringValue("test")),
WebViewGlobal("testNumber", WebViewGlobalValue.NumberValue(42.0)),
WebViewGlobal("testBoolean", WebViewGlobalValue.BooleanValue(true))
))
.build()
}

Expand All @@ -44,60 +39,6 @@ class EditorConfigurationTest {
assertArrayEquals(arrayOf("wp/v2"), editorConfig.siteApiNamespace)
assertArrayEquals(arrayOf("users"), editorConfig.namespaceExcludedPaths)
assertEquals("Bearer token", editorConfig.authHeader)
assertEquals(3, editorConfig.webViewGlobals.size)
}

@Test
fun `test WebViewGlobal StringValue toJavaScript conversion`() {
val stringValue = WebViewGlobalValue.StringValue("test\nvalue")
assertEquals("\"test\\nvalue\"", stringValue.toJavaScript())
}

@Test
fun `test WebViewGlobal NumberValue toJavaScript conversion`() {
val numberValue = WebViewGlobalValue.NumberValue(42.0)
assertEquals("42.0", numberValue.toJavaScript())
}

@Test
fun `test WebViewGlobal BooleanValue toJavaScript conversion`() {
val booleanValue = WebViewGlobalValue.BooleanValue(true)
assertEquals("true", booleanValue.toJavaScript())
}

@Test
fun `test WebViewGlobal ObjectValue toJavaScript conversion`() {
val objectValue = WebViewGlobalValue.ObjectValue(mapOf(
"key1" to WebViewGlobalValue.StringValue("value1"),
"key2" to WebViewGlobalValue.NumberValue(42.0)
))
assertEquals("{\"key1\": \"value1\",\"key2\": 42.0}", objectValue.toJavaScript())
}

@Test
fun `test WebViewGlobal ArrayValue toJavaScript conversion`() {
val arrayValue = WebViewGlobalValue.ArrayValue(listOf(
WebViewGlobalValue.StringValue("value1"),
WebViewGlobalValue.NumberValue(42.0)
))
assertEquals("[\"value1\",42.0]", arrayValue.toJavaScript())
}

@Test
fun `test WebViewGlobal NullValue toJavaScript conversion`() {
val nullValue = WebViewGlobalValue.NullValue
assertEquals("null", nullValue.toJavaScript())
}

@Test
fun `test WebViewGlobal valid identifier`() {
val validGlobal = WebViewGlobal("validName", WebViewGlobalValue.StringValue("test"))
assertEquals("validName", validGlobal.name)
}

@Test(expected = IllegalArgumentException::class)
fun `test WebViewGlobal invalid identifier throws exception`() {
WebViewGlobal("123invalid", WebViewGlobalValue.StringValue("test"))
}

@Test
Expand Down Expand Up @@ -130,4 +71,4 @@ class EditorConfigurationTest {

assertNotEquals(config1, config2)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ class MainActivity : AppCompatActivity(), AuthenticationManager.AuthenticationCa
.setSiteApiNamespace(arrayOf())
.setNamespaceExcludedPaths(arrayOf())
.setAuthHeader("")
.setWebViewGlobals(emptyList())
.setCookies(emptyMap())
.build()

Expand All @@ -94,7 +93,6 @@ class MainActivity : AppCompatActivity(), AuthenticationManager.AuthenticationCa
.setPostType("post")
.setThemeStyles(false)
.setHideTitle(false)
.setWebViewGlobals(emptyList())
.setCookies(emptyMap())

private fun launchEditor(configuration: EditorConfiguration) {
Expand Down
65 changes: 0 additions & 65 deletions ios/Sources/GutenbergKit/Sources/EditorConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ public struct EditorConfiguration {
public var siteApiNamespace: [String] = []
public var namespaceExcludedPaths: [String] = []
public var authHeader = ""
public var webViewGlobals: [WebViewGlobal] = []
/// Raw block editor settings from the WordPress REST API
public var editorSettings: [String: Any]?
/// The locale to use for translations
Expand All @@ -34,67 +33,3 @@ public struct EditorConfiguration {

public static let `default` = EditorConfiguration()
}

public struct WebViewGlobal {
let name: String
let value: WebViewGlobalValue

public init(name: String, value: WebViewGlobalValue) throws {
// Validate name is a valid JavaScript identifier
guard Self.isValidJavaScriptIdentifier(name) else {
throw WebViewGlobalError.invalidIdentifier(name)
}
self.name = name
self.value = value
}

private static func isValidJavaScriptIdentifier(_ name: String) -> Bool {
// Add validation logic for JavaScript identifiers
return name.range(of: "^[a-zA-Z_$][a-zA-Z0-9_$]*$", options: .regularExpression) != nil
}
}

public enum WebViewGlobalError: Error {
case invalidIdentifier(String)
}

public enum WebViewGlobalValue {
case string(String)
case number(Double)
case boolean(Bool)
case object([String: WebViewGlobalValue])
case array([WebViewGlobalValue])
case null

func toJavaScript() -> String {
switch self {
case .string(let str):
return "\"\(str.escaped)\""
case .number(let num):
return "\(num)"
case .boolean(let bool):
return "\(bool)"
case .object(let dict):
let pairs = dict.map { key, value in
"\"\(key.escaped)\": \(value.toJavaScript())"
}
return "{\(pairs.joined(separator: ","))}"
case .array(let array):
return "[\(array.map { $0.toJavaScript() }.joined(separator: ","))]"
case .null:
return "null"
}
}
}

// String escaping extension
private extension String {
var escaped: String {
return self.replacingOccurrences(of: "\"", with: "\\\"")
.replacingOccurrences(of: "\n", with: "\\n")
.replacingOccurrences(of: "\r", with: "\\r")
.replacingOccurrences(of: "\t", with: "\\t")
.replacingOccurrences(of: "\u{8}", with: "\\b")
.replacingOccurrences(of: "\u{12}", with: "\\f")
}
}
7 changes: 0 additions & 7 deletions ios/Sources/GutenbergKit/Sources/EditorViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,6 @@ public final class EditorViewController: UIViewController, GutenbergEditorContro
let escapedTitle = configuration.title.addingPercentEncoding(withAllowedCharacters: .alphanumerics)!
let escapedContent = configuration.content.addingPercentEncoding(withAllowedCharacters: .alphanumerics)!

// Generate JavaScript globals
let globalsJS = configuration.webViewGlobals.map { global in
"window[\"\(global.name)\"] = \(global.value.toJavaScript());"
}.joined(separator: "\n")

// Convert editor settings to JSON string if available
var editorSettingsJS = "undefined"
if let settings = configuration.editorSettings {
Expand All @@ -165,8 +160,6 @@ public final class EditorViewController: UIViewController, GutenbergEditorContro
}

let jsCode = """
\(globalsJS)

window.GBKit = {
siteURL: '\(configuration.siteURL)',
siteApiRoot: '\(configuration.siteApiRoot)',
Expand Down
Loading