diff --git a/README.md b/README.md index 977bcbad..9993c759 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. diff --git a/ios/Demo-iOS/Gutenberg.xcodeproj/xcshareddata/xcschemes/Gutenberg.xcscheme b/ios/Demo-iOS/Gutenberg.xcodeproj/xcshareddata/xcschemes/Gutenberg.xcscheme index 1a352dfa..b6fd3e51 100644 --- a/ios/Demo-iOS/Gutenberg.xcodeproj/xcshareddata/xcschemes/Gutenberg.xcscheme +++ b/ios/Demo-iOS/Gutenberg.xcodeproj/xcshareddata/xcschemes/Gutenberg.xcscheme @@ -54,12 +54,12 @@ + isEnabled = "YES"> + isEnabled = "YES"> diff --git a/ios/Demo-iOS/Sources/ContentView.swift b/ios/Demo-iOS/Sources/ContentView.swift index 2fe02b04..6a47966d 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,13 +75,30 @@ 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 { 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/" diff --git a/ios/Demo-iOS/Sources/EditorView.swift b/ios/Demo-iOS/Sources/EditorView.swift index 6ac49af3..8bf12f6c 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 @@ -15,7 +14,6 @@ struct EditorView: View { var body: some View { _EditorView(configuration: configuration, viewModel: viewModel) - .navigationBarBackButtonHidden(true) .toolbar { toolbar } } diff --git a/ios/Sources/GutenbergKit/Sources/EditorConfiguration.swift b/ios/Sources/GutenbergKit/Sources/EditorConfiguration.swift index 3284af0b..985715e5 100644 --- a/ios/Sources/GutenbergKit/Sources/EditorConfiguration.swift +++ b/ios/Sources/GutenbergKit/Sources/EditorConfiguration.swift @@ -30,6 +30,8 @@ public struct EditorConfiguration: Sendable { public let editorSettings: String /// Locale used for translations public let locale: String + /// Enables the native inserter UI in the editor + public let isNativeInserterEnabled: Bool /// Endpoint for loading editor assets, used when enabling `shouldUsePlugins` public var editorAssetsEndpoint: URL? @@ -49,6 +51,7 @@ public struct EditorConfiguration: Sendable { authHeader: String, editorSettings: String, locale: String, + isNativeInserterEnabled: Bool, editorAssetsEndpoint: URL? = nil, ) { self.title = title @@ -65,6 +68,7 @@ public struct EditorConfiguration: Sendable { self.authHeader = authHeader self.editorSettings = editorSettings self.locale = locale + self.isNativeInserterEnabled = isNativeInserterEnabled self.editorAssetsEndpoint = editorAssetsEndpoint } @@ -84,6 +88,7 @@ public struct EditorConfiguration: Sendable { authHeader: authHeader, editorSettings: editorSettings, locale: locale, + isNativeInserterEnabled: isNativeInserterEnabled, editorAssetsEndpoint: editorAssetsEndpoint ) } @@ -114,6 +119,7 @@ public struct EditorConfigurationBuilder { private var authHeader: String private var editorSettings: String private var locale: String + private var isNativeInserterEnabled: Bool private var editorAssetsEndpoint: URL? public init( @@ -131,6 +137,7 @@ public struct EditorConfigurationBuilder { authHeader: String = "", editorSettings: String = "undefined", locale: String = "en", + isNativeInserterEnabled: Bool = false, editorAssetsEndpoint: URL? = nil ){ self.title = title @@ -147,6 +154,7 @@ public struct EditorConfigurationBuilder { self.authHeader = authHeader self.editorSettings = editorSettings self.locale = locale + self.isNativeInserterEnabled = isNativeInserterEnabled self.editorAssetsEndpoint = editorAssetsEndpoint } @@ -234,6 +242,12 @@ public struct EditorConfigurationBuilder { return copy } + public func setNativeInserterEnabled(_ isNativeInserterEnabled: Bool = true) -> 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 a92eae01..a62f299e 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 a606be03..7c73eca8 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,7 @@ 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 +37,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 +80,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 ( <> { label="Editor toolbar" variant="unstyled" > - - - + { addBlockButton } { isSelected && ( diff --git a/src/components/editor-toolbar/style.scss b/src/components/editor-toolbar/style.scss index c7d49c8e..d71511b5 100644 --- a/src/components/editor-toolbar/style.scss +++ b/src/components/editor-toolbar/style.scss @@ -71,3 +71,18 @@ $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 { + margin-left: 8px; + + svg { + background: #eae9ec; + border-radius: 18px; + color: wordpress.$black; + padding: 1px; + width: 32px; + height: 32px; + display: block; + } +} \ 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 } ); } /**