From 3d859a6939ed1b01af8f8ac0610b5b76ff7772e6 Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Thu, 9 Oct 2025 10:24:50 -0400 Subject: [PATCH 1/6] 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 1a352dfa..6ae6d110 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 7c6a9cf1..febb2dce 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 25392ba0..297ce436 100644 --- a/ios/Sources/GutenbergKit/Sources/EditorViewController.swift +++ b/ios/Sources/GutenbergKit/Sources/EditorViewController.swift @@ -155,6 +155,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)', @@ -265,14 +266,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) { @@ -315,8 +314,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 a606be03..8ec4a222 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 c7d49c8e..311dfc32 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 c0bb71f0..ce9f36af 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 20499527e6b9a6f7440af9e4d05c6b60a94aa7ce Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Thu, 9 Oct 2025 10:26:09 -0400 Subject: [PATCH 2/6] 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 6ac49af3..42521b22 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 f13b38a21d8a6598ee2fb56e640d9eea1a3f5fac Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Thu, 9 Oct 2025 10:27:38 -0400 Subject: [PATCH 3/6] 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 def6ad61..985715e5 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 d9c1786cf751511735a77121befd569d1a104900 Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Thu, 9 Oct 2025 10:53:39 -0400 Subject: [PATCH 4/6] 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 ce9cd273..bb239e9d 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("Show 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 42521b22..8bf12f6c 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 19816f60ace7ad4da53ce3307f33be968e2fa68f Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Thu, 9 Oct 2025 10:54:54 -0400 Subject: [PATCH 5/6] 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 bb239e9d..ecf0dc5a 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 c0b3604ade4b354a12bb29eda38c4e3997ddc920 Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Thu, 9 Oct 2025 12:35:20 -0400 Subject: [PATCH 6/6] 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 8ec4a222..7c73eca8 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 && (