From 064d9449f2a62d513df23554838bf7fe9b8d01cc Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Thu, 9 Oct 2025 10:24:50 -0400 Subject: [PATCH 1/9] Add initial infrastructureto show native block inseter --- .../xcshareddata/xcschemes/Gutenberg.xcscheme | 2 +- .../Sources/EditorConfiguration.swift | 16 ++++++++ .../Sources/EditorJSMessage.swift | 6 ++- .../Sources/EditorViewController.swift | 20 +++++----- src/components/editor-toolbar/index.jsx | 38 ++++++++++++++----- src/components/editor-toolbar/style.scss | 16 ++++++++ src/utils/bridge.js | 14 ++++++- 7 files changed, 89 insertions(+), 23 deletions(-) diff --git a/ios/Demo-iOS/Gutenberg.xcodeproj/xcshareddata/xcschemes/Gutenberg.xcscheme b/ios/Demo-iOS/Gutenberg.xcodeproj/xcshareddata/xcschemes/Gutenberg.xcscheme index 1a352dfa2..6ae6d110b 100644 --- a/ios/Demo-iOS/Gutenberg.xcodeproj/xcshareddata/xcschemes/Gutenberg.xcscheme +++ b/ios/Demo-iOS/Gutenberg.xcodeproj/xcshareddata/xcschemes/Gutenberg.xcscheme @@ -54,7 +54,7 @@ + isEnabled = "YES"> EditorConfigurationBuilder { + var copy = self + copy.isNativeInserterEnabled = isNativeInserterEnabled + return copy + } + public func setEditorAssetsEndpoint(_ editorAssetsEndpoint: URL?) -> EditorConfigurationBuilder { var copy = self copy.editorAssetsEndpoint = editorAssetsEndpoint @@ -278,6 +292,7 @@ public struct EditorConfigurationBuilder { authHeader: authHeader, editorSettings: editorSettings, locale: locale, + isNativeInserterEnabled: isNativeInserterEnabled, editorAssetsEndpoint: editorAssetsEndpoint ) } @@ -296,3 +311,4 @@ private extension String { .replacingOccurrences(of: "\u{12}", with: "\\f") } } + diff --git a/ios/Sources/GutenbergKit/Sources/EditorJSMessage.swift b/ios/Sources/GutenbergKit/Sources/EditorJSMessage.swift index 7c6a9cf18..febb2dce9 100644 --- a/ios/Sources/GutenbergKit/Sources/EditorJSMessage.swift +++ b/ios/Sources/GutenbergKit/Sources/EditorJSMessage.swift @@ -34,7 +34,7 @@ struct EditorJSMessage { /// The editor logged an exception. case onEditorExceptionLogged /// The user tapped the inserter button. - case showBlockPicker + case showBlockInserter /// User requested the Media Library. case openMediaLibrary /// The user triggered an autocompleter. @@ -65,4 +65,8 @@ struct EditorJSMessage { struct ModalDialogBody: Decodable { let dialogType: String } + + struct ShowBlockInserterBody: Decodable { + let blocks: [EditorBlock] + } } diff --git a/ios/Sources/GutenbergKit/Sources/EditorViewController.swift b/ios/Sources/GutenbergKit/Sources/EditorViewController.swift index a92eae01d..a62f299e6 100644 --- a/ios/Sources/GutenbergKit/Sources/EditorViewController.swift +++ b/ios/Sources/GutenbergKit/Sources/EditorViewController.swift @@ -128,6 +128,7 @@ public final class EditorViewController: UIViewController, GutenbergEditorContro namespaceExcludedPaths: \(Array(configuration.namespaceExcludedPaths)), authHeader: '\(configuration.authHeader)', themeStyles: \(configuration.shouldUseThemeStyles), + enableNativeBlockInserter: \(configuration.isNativeInserterEnabled), hideTitle: \(configuration.shouldHideTitle), editorSettings: \(configuration.editorSettings), locale: '\(configuration.locale)', @@ -238,14 +239,12 @@ public final class EditorViewController: UIViewController, GutenbergEditorContro // MARK: - Internal (Block Inserter) - // TODO: wire with JS and pass blocks - private func showBlockInserter() { -// let viewModel = EditorBlockPickerViewModel(blockTypes: service.blockTypes) -// let view = NavigationView { -// EditorBlockPicker(viewModel: viewModel) -// } -// let host = UIHostingController(rootView: view) -// present(host, animated: true) + private func showBlockInserter(blocks: [EditorBlock]) { + present(UIHostingController(rootView: NavigationView { + List(blocks) { + Text($0.name) + } + }), animated: true) } private func openMediaLibrary(_ config: OpenMediaLibraryAction) { @@ -288,8 +287,9 @@ public final class EditorViewController: UIViewController, GutenbergEditorContro return } delegate?.editor(self, didLogException: editorException) - case .showBlockPicker: - showBlockInserter() + case .showBlockInserter: + let body = try message.decode(EditorJSMessage.ShowBlockInserterBody.self) + showBlockInserter(blocks: body.blocks) case .openMediaLibrary: let config = try message.decode(OpenMediaLibraryAction.self) openMediaLibrary(config) diff --git a/src/components/editor-toolbar/index.jsx b/src/components/editor-toolbar/index.jsx index a606be036..8ec4a2221 100644 --- a/src/components/editor-toolbar/index.jsx +++ b/src/components/editor-toolbar/index.jsx @@ -17,7 +17,7 @@ import { ToolbarButton, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { close, cog } from '@wordpress/icons'; +import { close, cog, plus } from '@wordpress/icons'; import clsx from 'clsx'; import { store as editorStore } from '@wordpress/editor'; @@ -27,6 +27,8 @@ import { store as editorStore } from '@wordpress/editor'; import './style.scss'; import { useModalize } from './use-modalize'; import { useModalDialogState } from '../editor/use-modal-dialog-state'; +import { showBlockInserter, getGBKit } from '../../utils/bridge'; + /** * Renders the editor toolbar containing block-related actions. @@ -36,6 +38,8 @@ import { useModalDialogState } from '../editor/use-modal-dialog-state'; * @return {JSX.Element} The rendered editor toolbar component. */ const EditorToolbar = ( { className } ) => { + const { enableNativeBlockInserter } = getGBKit(); + const [ isBlockInspectorShown, setBlockInspectorShown ] = useState( false ); const { isSelected } = useSelect( ( select ) => { const { getSelectedBlockClientId } = select( blockEditorStore ); @@ -77,6 +81,29 @@ const EditorToolbar = ( { className } ) => { const classes = clsx( 'gutenberg-kit-editor-toolbar', className ); + const addBlockButton = enableNativeBlockInserter ? ( + { + if ( isInserterOpened ) { + setIsInserterOpened( false ); + } + showBlockInserter(); + } } + className="gutenberg-kit-add-block-button" + /> + ) : ( + + ); + return ( <> { variant="unstyled" > - + { addBlockButton } { isSelected && ( diff --git a/src/components/editor-toolbar/style.scss b/src/components/editor-toolbar/style.scss index c7d49c8e3..311dfc32a 100644 --- a/src/components/editor-toolbar/style.scss +++ b/src/components/editor-toolbar/style.scss @@ -71,3 +71,19 @@ $min-touch-target-size: 46px; left: 6px; right: 6px; } + +// Style the add block button with rounded black background +.gutenberg-kit-editor-toolbar .gutenberg-kit-add-block-button { + svg { + background: #eae9ec; + border-radius: 18px; + color: wordpress.$black; + padding: 1px; + width: 32px; + height: 32px; + display: block; + } + + // width: 50px; + margin-left: 8px; +} \ No newline at end of file diff --git a/src/utils/bridge.js b/src/utils/bridge.js index c0bb71f00..ce9f36af2 100644 --- a/src/utils/bridge.js +++ b/src/utils/bridge.js @@ -5,6 +5,7 @@ import parseException from './exception-parser'; import { debug } from './logger'; import { isDevMode } from './dev-mode'; import { basicFetch } from './fetch'; +import { getBlockTypes } from '@wordpress/blocks'; /** * Generic function to dispatch messages to both Android and iOS bridges. @@ -91,8 +92,17 @@ export function onBlocksChanged( isEmpty = false ) { * * @return {void} */ -export function showBlockPicker() { - dispatchToBridge( 'showBlockPicker', {} ); +export function showBlockInserter() { + const blocks = getBlockTypes().map( ( blockType ) => { + return { + name: blockType.name, + title: blockType.title, + description: blockType.description, + category: blockType.category, + keywords: blockType.keywords || [], + }; + } ); + dispatchToBridge( 'showBlockInserter', { blocks } ); } /** From a36286aa55f50da282746098c6a246de8c9e4dac Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Thu, 9 Oct 2025 10:26:09 -0400 Subject: [PATCH 2/9] Remove unused state --- ios/Demo-iOS/Sources/EditorView.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/ios/Demo-iOS/Sources/EditorView.swift b/ios/Demo-iOS/Sources/EditorView.swift index 6ac49af3b..42521b228 100644 --- a/ios/Demo-iOS/Sources/EditorView.swift +++ b/ios/Demo-iOS/Sources/EditorView.swift @@ -5,7 +5,6 @@ struct EditorView: View { private let configuration: EditorConfiguration @State private var viewModel = EditorViewModel() - @State private var editorViewController: EditorViewController? @Environment(\.dismiss) private var dismiss From 45acafd1e38247624dbb64be08f9ad705c2d7bdc Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Thu, 9 Oct 2025 10:27:38 -0400 Subject: [PATCH 3/9] Revert --- ios/Sources/GutenbergKit/Sources/EditorConfiguration.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/Sources/GutenbergKit/Sources/EditorConfiguration.swift b/ios/Sources/GutenbergKit/Sources/EditorConfiguration.swift index def6ad61b..985715e5c 100644 --- a/ios/Sources/GutenbergKit/Sources/EditorConfiguration.swift +++ b/ios/Sources/GutenbergKit/Sources/EditorConfiguration.swift @@ -137,7 +137,7 @@ public struct EditorConfigurationBuilder { authHeader: String = "", editorSettings: String = "undefined", locale: String = "en", - isNativeInserterEnabled: Bool = true, + isNativeInserterEnabled: Bool = false, editorAssetsEndpoint: URL? = nil ){ self.title = title From 85d95e2c3d6bdd7f5c45f6eeba85c6595f5ace29 Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Thu, 9 Oct 2025 10:53:39 -0400 Subject: [PATCH 4/9] Add a way to configure the remote editor --- ios/Demo-iOS/Sources/ContentView.swift | 62 +++++++++++++++++++------- ios/Demo-iOS/Sources/EditorView.swift | 1 - 2 files changed, 46 insertions(+), 17 deletions(-) diff --git a/ios/Demo-iOS/Sources/ContentView.swift b/ios/Demo-iOS/Sources/ContentView.swift index 2fe02b047..07b609fcb 100644 --- a/ios/Demo-iOS/Sources/ContentView.swift +++ b/ios/Demo-iOS/Sources/ContentView.swift @@ -1,32 +1,32 @@ import SwiftUI import GutenbergKit -let editorURL: URL? = ProcessInfo.processInfo.environment["GUTENBERG_EDITOR_URL"].flatMap(URL.init) - struct ContentView: View { + private let remoteEditors: [RemoteEditorRow] = [ + .init(id: "template", configuration: .template) + ] + + @State private var isDefaultEditorShown = false + @State private var selectedRemoteEditor: RemoteEditorRow? - let remoteEditorConfigurations: [EditorConfiguration] = [.template] + @AppStorage("isNativeInserterEnabled") private var isNativeInserterEnabled = false var body: some View { List { Section { - NavigationLink { - EditorView(configuration: .default) - } label: { - Text("Bundled editor") + Button("Bundled Editor") { + isDefaultEditorShown = true } } Section { - ForEach(remoteEditorConfigurations, id: \.siteURL) { configuration in - NavigationLink { - EditorView(configuration: configuration) - } label: { - Text(URL(string: configuration.siteURL)?.host ?? configuration.siteURL) + ForEach(remoteEditors) { editor in + Button(editor.title) { + selectedRemoteEditor = editor } } - if remoteEditorConfigurations.isEmpty { + if remoteEditors.isEmpty { Text("Add `EditorConfiguration` instances to the `remoteEditorConfigurations` array to launch remote editors here.") } } header: { @@ -38,18 +38,32 @@ struct ContentView: View { Text("Note: The editor is backed by the compiled web app created by `make build`.") } } + + Section("Configuration") { + Toggle("Native Inserter", isOn: $isNativeInserterEnabled) + } + } + .fullScreenCover(isPresented: $isDefaultEditorShown) { + NavigationView { + EditorView(configuration: preconfigure(.default)) + } + } + .fullScreenCover(item: $selectedRemoteEditor) { editor in + NavigationView { + EditorView(configuration: preconfigure(editor.configuration)) + } } .toolbar { ToolbarItem(placement: .primaryAction) { Button { Task { NSLog("Start to fetch assets") - for configuration in remoteEditorConfigurations { - let library = EditorAssetsLibrary(configuration: configuration) + for editor in remoteEditors { + let library = EditorAssetsLibrary(configuration: editor.configuration) do { try await library.fetchAssets() } catch { - NSLog("Failed to fetch assets for \(configuration.siteURL): \(error)") + NSLog("Failed to fetch assets for \(editor.configuration.siteURL): \(error)") } } NSLog("Done fetching assets") @@ -61,6 +75,22 @@ struct ContentView: View { } } } + + private func preconfigure(_ configuration: EditorConfiguration) -> EditorConfiguration { + configuration + .toBuilder() + .setNativeInserterEnabled(isNativeInserterEnabled) + .build() + } +} + +private struct RemoteEditorRow: Identifiable { + let id: String + let configuration: EditorConfiguration + + var title: String { + URL(string: configuration.siteURL)?.host ?? configuration.siteURL + } } private extension EditorConfiguration { diff --git a/ios/Demo-iOS/Sources/EditorView.swift b/ios/Demo-iOS/Sources/EditorView.swift index 42521b228..8bf12f6c9 100644 --- a/ios/Demo-iOS/Sources/EditorView.swift +++ b/ios/Demo-iOS/Sources/EditorView.swift @@ -14,7 +14,6 @@ struct EditorView: View { var body: some View { _EditorView(configuration: configuration, viewModel: viewModel) - .navigationBarBackButtonHidden(true) .toolbar { toolbar } } From c14bdb6a616e163a97580ba5ba5db63299656453 Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Thu, 9 Oct 2025 10:54:54 -0400 Subject: [PATCH 5/9] Remove warnings --- ios/Demo-iOS/Sources/ContentView.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ios/Demo-iOS/Sources/ContentView.swift b/ios/Demo-iOS/Sources/ContentView.swift index 07b609fcb..6a47966d7 100644 --- a/ios/Demo-iOS/Sources/ContentView.swift +++ b/ios/Demo-iOS/Sources/ContentView.swift @@ -96,8 +96,9 @@ private struct RemoteEditorRow: Identifiable { private extension EditorConfiguration { static var template: Self { - #warning("1. Update the siteURL and authHeader values below") - #warning("2. Install the Jetpack plugin to the site") + // Steps: + // 1. Update the siteURL and authHeader values below + // 2. Install the Jetpack plugin to the site let siteUrl: String = "https://modify-me.com" let authHeader: String = "Insert the Authorization header value here" let siteApiRoot: String = "\(siteUrl)/wp-json/" From 492b480ae7a498e2aa4320a4fad41f839f29358f Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Thu, 9 Oct 2025 12:35:20 -0400 Subject: [PATCH 6/9] Fix lint errors --- src/components/editor-toolbar/index.jsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/components/editor-toolbar/index.jsx b/src/components/editor-toolbar/index.jsx index 8ec4a2221..7c73eca82 100644 --- a/src/components/editor-toolbar/index.jsx +++ b/src/components/editor-toolbar/index.jsx @@ -29,7 +29,6 @@ import { useModalize } from './use-modalize'; import { useModalDialogState } from '../editor/use-modal-dialog-state'; import { showBlockInserter, getGBKit } from '../../utils/bridge'; - /** * Renders the editor toolbar containing block-related actions. * @@ -102,7 +101,7 @@ const EditorToolbar = ( { className } ) => { open={ isInserterOpened } onToggle={ setIsInserterOpened } /> - ); + ); return ( <> @@ -111,9 +110,7 @@ const EditorToolbar = ( { className } ) => { label="Editor toolbar" variant="unstyled" > - - { addBlockButton } - + { addBlockButton } { isSelected && ( From 7e9157d9b07dee9c33d4346839b3ed530a6adddd Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Mon, 20 Oct 2025 10:43:23 -0400 Subject: [PATCH 7/9] Update README --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 977bcbad5..9993c7592 100644 --- a/README.md +++ b/README.md @@ -26,17 +26,19 @@ Once finished, the web app can now be accessed in your browser by visiting the U ### Demo App -This demo app is useful for quickly testing changes made to the editor. By default, the demo app uses a production build of the web app bundled with the GutenbergKit package—i.e., the output of the project's `make build` command. During development, however, it is more useful to run the web app with a server and provide the server URL as an environment variable for the demo app, so that changes are displayed in the app immediately. +This demo app is useful for quickly testing changes made to the editor. #### iOS +The iOS demo app loads the development server by default. + 1. Start the development server by running `make dev-server`. 1. Launch Xcode and open the `ios/Demo-iOS/Gutenberg.xcodeproj` project. 1. Select the `Gutenberg` target. -1. Navigate to _Product_ → _Scheme_ → _Edit Scheme_. -1. Add an environment variable named `GUTENBERG_EDITOR_URL` with the development server URL. 1. Run the app. +Alternatively, you can load a production build of the web app bundled with the GutenbergKit package by running `make build` and disabling the `GUTENBERG_EDITOR_URL` environment variable by navigating to _Product_ → _Scheme_ → _Edit Scheme_ in Xcode. +
Example Xcode environment variable @@ -46,6 +48,8 @@ This demo app is useful for quickly testing changes made to the editor. By defau #### Android +The Android demo app loads the production build of the web app bundled with the GutenbergKit package by default—i.e., the output of the project's `make build` command). It can be configured to load the development server by setting a `GUTENBERG_EDITOR_URL` environment variable in the `android/local.properties` file. + 1. Start the development server by running `make dev-server`. 1. Launch Android Studio and open the `android` project. 1. Modify the `android/local.properties` file to include an environment variable named `GUTENBERG_EDITOR_URL` with the development server URL. From 44fce466c107501a4daaafa9598471f052e25be5 Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Mon, 20 Oct 2025 11:01:06 -0400 Subject: [PATCH 8/9] Enable GUTENBERG_EDITOR_REMOTE_URL --- .../xcshareddata/xcschemes/Gutenberg.xcscheme | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/Demo-iOS/Gutenberg.xcodeproj/xcshareddata/xcschemes/Gutenberg.xcscheme b/ios/Demo-iOS/Gutenberg.xcodeproj/xcshareddata/xcschemes/Gutenberg.xcscheme index 6ae6d110b..b6fd3e51d 100644 --- a/ios/Demo-iOS/Gutenberg.xcodeproj/xcshareddata/xcschemes/Gutenberg.xcscheme +++ b/ios/Demo-iOS/Gutenberg.xcodeproj/xcshareddata/xcschemes/Gutenberg.xcscheme @@ -59,7 +59,7 @@ + isEnabled = "YES"> From f770ff3bfccb6791a08fc3bbe40797e0f24aa7e8 Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Mon, 20 Oct 2025 11:04:05 -0400 Subject: [PATCH 9/9] Update CSS --- src/components/editor-toolbar/style.scss | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/editor-toolbar/style.scss b/src/components/editor-toolbar/style.scss index 311dfc32a..d71511b50 100644 --- a/src/components/editor-toolbar/style.scss +++ b/src/components/editor-toolbar/style.scss @@ -74,6 +74,8 @@ $min-touch-target-size: 46px; // Style the add block button with rounded black background .gutenberg-kit-editor-toolbar .gutenberg-kit-add-block-button { + margin-left: 8px; + svg { background: #eae9ec; border-radius: 18px; @@ -83,7 +85,4 @@ $min-touch-target-size: 46px; height: 32px; display: block; } - - // width: 50px; - margin-left: 8px; } \ No newline at end of file