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
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ npm run dev:remote
The web editor is built with React and WordPress packages:

- **Entry Points**:
- `src/index.jsx` - Local editor entry
- `src/remote.jsx` - Remote editor entry (for plugin support)
- `src/index.js` - Bundled editor entry
- `src/remote.js` - Remote editor entry (supports plugins)
- **Core Components**:
- `src/components/editor/` - Main editor component with host bridge integration
- `src/components/visual-editor/` - Visual editing interface
Expand Down
2 changes: 1 addition & 1 deletion android/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<!-- Configuration Activity -->
<string name="demo_title">GutenbergKit Demo</string>
<string name="bundled_editor">Bundled editor</string>
<string name="bundled_editor_subtitle">Offline editor with bundled assets</string>
<string name="bundled_editor_subtitle">Local editor without plugin support</string>
<string name="add_remote_editor_description">Add remote editor</string>

<!-- Dialog strings -->
Expand Down
16 changes: 8 additions & 8 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ GutenbergKit/
│ │ └── text-editor/ # HTML text editing interface
│ ├── utils/ # Utility functions
│ │ └── bridge.js # Native-to-web communication
│ ├── index.jsx # Local editor entry point
│ └── remote.jsx # Remote editor entry point
│ ├── index.js # Bundled editor entry point
│ └── remote.js # Remote editor entry point
├── ios/ # iOS Swift package
│ └── Sources/
│ └── GutenbergKit/
Expand Down Expand Up @@ -90,28 +90,28 @@ The `make build` command builds both the local and remote editors by default. To
Additionally, a `make dev-server-remote` command is available for serving the latest remote editor changes through a development server. To load the development server in the Demo app, add an environment variable named `GUTENBERG_EDITOR_REMOTE_URL` with the URL of the development server plus `/remote.html`—i.e., `http://<YOUR_LOCAL_IP>:5173/remote.html`.

> [!TIP]
> The remote editor redirects to the local editor when loading fails. If you need to debug the failure, disable redirects via the `?dev_mode` query parameter..
> The remote editor redirects to the bundled editor when loading fails. If you need to debug the failure, disable redirects via the `?dev_mode` query parameter..

### Local Editor (`index.html`)
### Bundled Editor (`index.html`)

The local editor bundles all WordPress packages and runs entirely within the WebView. This variant:
The bundled editor relies upon local `@wordpress` packages. This variant:

- Provides offline capability
- Has faster initial load times
- Limited to core blocks only
- No plugin support

**Entry point:** `src/index.jsx`
**Entry point:** `src/index.js`

### Remote Editor (`remote.html`)

The remote editor loads WordPress packages and plugins from a remote server. This variant:
The remote editor loads `@wordpress` packages and plugins from a remote server. This variant:

- Supports custom blocks and plugins
- Requires network connectivity
- Used in production environments with custom implementations

**Entry point:** `src/remote.jsx`
**Entry point:** `src/remote.js`

## Testing

Expand Down
40 changes: 40 additions & 0 deletions ios/Demo-iOS/Gutenberg.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
objects = {

/* Begin PBXBuildFile section */
0C4F59A22BEFF4980028BD96 /* AddSiteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C4F59A32BEFF4980028BD96 /* AddSiteView.swift */; };
0C4F59A42BEFF4980028BD96 /* AuthenticationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C4F59A52BEFF4980028BD96 /* AuthenticationManager.swift */; };
0C4F59A62BEFF4980028BD96 /* ConfigurationItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C4F59A72BEFF4980028BD96 /* ConfigurationItem.swift */; };
0C4F59A82BEFF4980028BD96 /* ConfigurationStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C4F59A92BEFF4980028BD96 /* ConfigurationStorage.swift */; };
0CE8E78B2C339B0600B9DC67 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0CE8E7842C339B0600B9DC67 /* Assets.xcassets */; };
0CE8E78C2C339B0600B9DC67 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE8E7852C339B0600B9DC67 /* ContentView.swift */; };
0CE8E78D2C339B0600B9DC67 /* EditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE8E7862C339B0600B9DC67 /* EditorView.swift */; };
Expand All @@ -17,6 +21,10 @@

/* Begin PBXFileReference section */
0C4F598B2BEFF4970028BD96 /* Gutenberg.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Gutenberg.app; sourceTree = BUILT_PRODUCTS_DIR; };
0C4F59A32BEFF4980028BD96 /* AddSiteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddSiteView.swift; sourceTree = "<group>"; };
0C4F59A52BEFF4980028BD96 /* AuthenticationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationManager.swift; sourceTree = "<group>"; };
0C4F59A72BEFF4980028BD96 /* ConfigurationItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationItem.swift; sourceTree = "<group>"; };
0C4F59A92BEFF4980028BD96 /* ConfigurationStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationStorage.swift; sourceTree = "<group>"; };
0CE8E7842C339B0600B9DC67 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
0CE8E7852C339B0600B9DC67 /* ContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
0CE8E7862C339B0600B9DC67 /* EditorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditorView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -68,6 +76,10 @@
0CE8E7882C339B0600B9DC67 /* Sources */ = {
isa = PBXGroup;
children = (
0C4F59A32BEFF4980028BD96 /* AddSiteView.swift */,
0C4F59A52BEFF4980028BD96 /* AuthenticationManager.swift */,
0C4F59A72BEFF4980028BD96 /* ConfigurationItem.swift */,
0C4F59A92BEFF4980028BD96 /* ConfigurationStorage.swift */,
0CE8E7852C339B0600B9DC67 /* ContentView.swift */,
0CE8E7862C339B0600B9DC67 /* EditorView.swift */,
0CE8E7872C339B0600B9DC67 /* GutenbergApp.swift */,
Expand Down Expand Up @@ -108,6 +120,7 @@
name = Gutenberg;
packageProductDependencies = (
0CF6E04B2BEFF60E00EDEE8A /* GutenbergKit */,
0C4F59A12BEFF4980028BD96 /* WordPressAPI */,
);
productName = Gutenberg;
productReference = 0C4F598B2BEFF4970028BD96 /* Gutenberg.app */;
Expand Down Expand Up @@ -137,6 +150,9 @@
Base,
);
mainGroup = 0C4F59822BEFF4970028BD96;
packageReferences = (
0C4F59A02BEFF4980028BD96 /* XCRemoteSwiftPackageReference "wordpress-rs" */,
);
productRefGroup = 0C4F598C2BEFF4970028BD96 /* Products */;
projectDirPath = "";
projectRoot = "";
Expand All @@ -163,6 +179,10 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
0C4F59A22BEFF4980028BD96 /* AddSiteView.swift in Sources */,
0C4F59A42BEFF4980028BD96 /* AuthenticationManager.swift in Sources */,
0C4F59A62BEFF4980028BD96 /* ConfigurationItem.swift in Sources */,
0C4F59A82BEFF4980028BD96 /* ConfigurationStorage.swift in Sources */,
0CE8E78E2C339B0600B9DC67 /* GutenbergApp.swift in Sources */,
0CE8E78C2C339B0600B9DC67 /* ContentView.swift in Sources */,
0CE8E78D2C339B0600B9DC67 /* EditorView.swift in Sources */,
Expand Down Expand Up @@ -304,6 +324,8 @@
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = PZYM8XX95Q;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleURLTypes = "$(INFOPLIST_KEY_CFBundleURLTypes)";
"INFOPLIST_KEY_CFBundleURLTypes[0]" = "{ CFBundleURLSchemes = (gutenbergkit); }";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
Expand Down Expand Up @@ -336,6 +358,8 @@
DEVELOPMENT_TEAM = "";
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleURLTypes = "$(INFOPLIST_KEY_CFBundleURLTypes)";
"INFOPLIST_KEY_CFBundleURLTypes[0]" = "{ CFBundleURLSchemes = (gutenbergkit); }";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
Expand Down Expand Up @@ -379,7 +403,23 @@
};
/* End XCConfigurationList section */

/* Begin XCRemoteSwiftPackageReference section */
0C4F59A02BEFF4980028BD96 /* XCRemoteSwiftPackageReference "wordpress-rs" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/Automattic/wordpress-rs";
requirement = {
kind = revision;
revision = "alpha-20250926";
};
};
/* End XCRemoteSwiftPackageReference section */

/* Begin XCSwiftPackageProductDependency section */
0C4F59A12BEFF4980028BD96 /* WordPressAPI */ = {
isa = XCSwiftPackageProductDependency;
package = 0C4F59A02BEFF4980028BD96 /* XCRemoteSwiftPackageReference "wordpress-rs" */;
productName = WordPressAPI;
};
0CF6E04B2BEFF60E00EDEE8A /* GutenbergKit */ = {
isa = XCSwiftPackageProductDependency;
productName = GutenbergKit;
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

85 changes: 85 additions & 0 deletions ios/Demo-iOS/Sources/AddSiteView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import SwiftUI
import AuthenticationServices

/// View for adding a new remote editor site
struct AddSiteView: View {
@Binding var siteUrl: String
@ObservedObject var authenticationManager: AuthenticationManager
let onAdd: (RemoteEditorConfiguration) -> Void
let onCancel: () -> Void

@State private var presentationContextProvider = WebAuthPresentationContextProvider()

var body: some View {
NavigationStack {
Form {
Section {
TextField("Site URL", text: $siteUrl)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
.keyboardType(.URL)
.onSubmit {
startAuthentication()
}
} header: {
Text("WordPress Site")
} footer: {
Text("Enter the URL of your WordPress site (e.g., https://example.com)")
}

if let errorMessage = authenticationManager.errorMessage {
Section {
Text(errorMessage)
.foregroundColor(.red)
}
}
}
.navigationTitle("Add Remote Editor")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") {
onCancel()
}
.disabled(authenticationManager.isAuthenticating)
}

ToolbarItem(placement: .confirmationAction) {
if authenticationManager.isAuthenticating {
ProgressView()
} else {
Button("Add") {
startAuthentication()
}
.disabled(siteUrl.trimmingCharacters(in: .whitespaces).isEmpty)
}
}
}
}
}

private func startAuthentication() {
let trimmedUrl = siteUrl.trimmingCharacters(in: .whitespaces)
guard !trimmedUrl.isEmpty else { return }

authenticationManager.startAuthentication(
siteUrl: trimmedUrl,
presentationContext: presentationContextProvider
) { configuration in
onAdd(configuration)
}
}
}

/// Provides the presentation context for web authentication
class WebAuthPresentationContextProvider: NSObject, ASWebAuthenticationPresentationContextProviding {
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
// Return the first window that can be used as a presentation anchor
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let window = windowScene.windows.first {
return window
}
// Fallback to a new window (shouldn't normally happen)
return ASPresentationAnchor()
}
}
Loading