From e0b68a750828926cd1e14dced483127ccebed5f8 Mon Sep 17 00:00:00 2001 From: James Nylen Date: Sun, 6 Aug 2017 12:35:29 -0500 Subject: [PATCH 1/5] Render "normal" metaboxes created by Advanced Custom Fields Lots of unshippable hacks here. See TODO comments. --- editor/index.js | 23 ++++++++++++ editor/layout/index.js | 2 ++ editor/metaboxes/index.js | 60 +++++++++++++++++++++++++++++++ editor/metaboxes/normal.js | 18 ++++++++++ editor/metaboxes/style.scss | 4 +++ editor/state.js | 17 +++++++++ lib/client-assets.php | 14 ++++++-- lib/register.php | 71 +++++++++++++++++++++++++++++++++++++ 8 files changed, 206 insertions(+), 3 deletions(-) create mode 100644 editor/metaboxes/index.js create mode 100644 editor/metaboxes/normal.js create mode 100644 editor/metaboxes/style.scss diff --git a/editor/index.js b/editor/index.js index 2fe956712646c..cac696c70bf05 100644 --- a/editor/index.js +++ b/editor/index.js @@ -6,6 +6,7 @@ import { Provider as ReduxProvider } from 'react-redux'; import { Provider as SlotFillProvider } from 'react-slot-fill'; import moment from 'moment-timezone'; import 'moment-timezone/moment-timezone-utils'; +import { partial } from 'lodash'; /** * WordPress dependencies @@ -84,6 +85,24 @@ function preparePostState( store, post ) { } } +/** + * Initializes Redux state with metaboxes for the current post. + * + * @param {Redux.Store} store Redux store instance + * @param {Object} metaboxes Metabox data for the current post. + */ +function prepareMetaboxesState( store, metaboxes ) { + if ( ! metaboxes ) { + // eslint-disable-next-line no-console + console.error( 'No data about post metaboxes!' ); + return; + } + store.dispatch( { + type: 'RESET_METABOXES', + metaboxes, + } ); +} + /** * Initializes and returns an instance of Editor. * @@ -124,4 +143,8 @@ export function createEditorInstance( id, post, editorSettings = DEFAULT_SETTING , document.getElementById( id ) ); + + return { + setPostMetaboxes: partial( prepareMetaboxesState, store ), + }; } diff --git a/editor/layout/index.js b/editor/layout/index.js index c0685be6cd0fb..c96ed4917486e 100644 --- a/editor/layout/index.js +++ b/editor/layout/index.js @@ -19,6 +19,7 @@ import TextEditor from '../modes/text-editor'; import VisualEditor from '../modes/visual-editor'; import UnsavedChangesWarning from '../unsaved-changes-warning'; import DocumentTitle from '../document-title'; +import NormalMetaboxes from '../metaboxes/normal'; import { removeNotice } from '../actions'; import { getEditorMode, @@ -40,6 +41,7 @@ function Layout( { mode, isSidebarOpened, notices, ...props } ) {
{ mode === 'text' && } { mode === 'visual' && } +
{ isSidebarOpened && } diff --git a/editor/metaboxes/index.js b/editor/metaboxes/index.js new file mode 100644 index 0000000000000..a6f56e49d307f --- /dev/null +++ b/editor/metaboxes/index.js @@ -0,0 +1,60 @@ +/** + * External dependencies + */ +import { connect } from 'react-redux'; +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { sprintf, __ } from '@wordpress/i18n'; +import { Component } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import './style.scss'; + +export class Metaboxes extends Component { + render() { + const classes = classnames( + 'gutenberg-metaboxes', + 'gutenberg-metaboxes__' + this.props.context + ); + + return ( +
+

{ this.props.headingText }

+ { this.renderBody() } +
+ ); + } + + renderBody() { + const { metaboxes, context } = this.props; + if ( ! metaboxes || ! metaboxes[ context ] ) { + const noMetaboxesMessage = + /* translators: %s: metabox context ('normal', 'advanced', 'side') */ + sprintf( __( 'No metaboxes for context: %s' ), context ); + return ( +
+ { noMetaboxesMessage } +
+ ); + } + + return ( +
+ ); + } +} + +export default connect( + ( state ) => { + return { + metaboxes: state.currentPostMetaboxes, + }; + } +)( Metaboxes ); diff --git a/editor/metaboxes/normal.js b/editor/metaboxes/normal.js new file mode 100644 index 0000000000000..46a0eb83830fd --- /dev/null +++ b/editor/metaboxes/normal.js @@ -0,0 +1,18 @@ +/** + * WordPress dependencies + */ +import { __ } from 'i18n'; + +/** + * Internal dependencies + */ +import Metaboxes from './index'; + +export default function MetaboxesNormal() { + return ( + + ); +} diff --git a/editor/metaboxes/style.scss b/editor/metaboxes/style.scss new file mode 100644 index 0000000000000..a9f4140a71e9b --- /dev/null +++ b/editor/metaboxes/style.scss @@ -0,0 +1,4 @@ +.gutenberg-metaboxes { + margin: 0 auto; + max-width: 700px; +} diff --git a/editor/state.js b/editor/state.js index bfee2a272e306..099dc6d53c3fd 100644 --- a/editor/state.js +++ b/editor/state.js @@ -289,6 +289,22 @@ export function currentPost( state = {}, action ) { return state; } +/** + * Reducer returning information about the metaboxes for the current post. + * + * @param {Object} state Current state + * @param {Object} action Dispatched action + * @return {Object} Updated state + */ +export function currentPostMetaboxes( state = {}, action ) { + switch ( action.type ) { + case 'RESET_METABOXES': + return action.metaboxes; + } + + return state; +} + /** * Reducer returning selected block state. * @@ -538,6 +554,7 @@ export function createReduxStore() { const reducer = optimist( combineReducers( { editor, currentPost, + currentPostMetaboxes, selectedBlock, isTyping, multiSelectedBlocks, diff --git a/lib/client-assets.php b/lib/client-assets.php index 6c702b1dd84b3..c429870ab729d 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -615,6 +615,7 @@ function gutenberg_editor_scripts_and_styles( $hook ) { if ( is_wp_error( $post_to_edit ) ) { wp_die( $post_to_edit->get_error_message() ); } + $GLOBALS['_gutenberg_post_id'] = $post_id; // TODO something other than this. // Set initial title to empty string for auto draft for duration of edit. $is_new_post = 'auto-draft' === $post_to_edit['status']; @@ -661,10 +662,17 @@ function gutenberg_editor_scripts_and_styles( $hook ) { 'colors' => $color_palette, ); - wp_add_inline_script( 'wp-editor', 'wp.api.init().done( function() {' - . 'wp.editor.createEditorInstance( \'editor\', window._wpGutenbergPost, ' . json_encode( $editor_settings ) . ' ); ' - . '} );' + $editor_settings_encoded = json_encode( $editor_settings ); + $create_editor_instance_script = << $pagenow, + ); + $pagenow = 'post.php'; + // As early as possible, but after any logic that adds metaboxes. + add_action( 'admin_head', 'gutenberg_collect_metabox_data', 99 ); +} +// As late as possible, but before any logic that adds metaboxes. +add_action( + 'load-toplevel_page_gutenberg', + 'gutenberg_trick_plugins_into_registering_metaboxes' +); + +/** + * Collect information about metaboxes registered for the current post. + * + * @since ??? + */ +function gutenberg_collect_metabox_data() { + global $_gutenberg_post_id, $_gutenberg_restore_globals_after_metaboxes; + + // WIP: Collect and send information needed to render metaboxes. + // From wp-admin/edit-form-advanced.php + // Relevant code there: + // do_action( 'do_meta_boxes', $post_type, {'normal','advanced','side'}, $post ); + // do_meta_boxes( $post_type, 'side', $post ); + // do_meta_boxes( null, 'normal', $post ); + // do_meta_boxes( null, 'advanced', $post ); + $meta_boxes_output = array(); + + $post = get_post( $_gutenberg_post_id ); + foreach ( array( 'normal', 'advanced', 'side' ) as $location ) { + ob_start(); + do_action( + 'do_meta_boxes', + $post->post_type, + $location, + $post + ); + do_meta_boxes( + 'side' === $location ? $post->post_type : null, + $location, + $post + ); + $meta_boxes_output[ $location ] = ob_get_clean(); + } + wp_add_inline_script( + 'wp-editor', + 'window._wpGutenbergMetaboxes = ' . wp_json_encode( $meta_boxes_output ) . ';' + . "\nwindow._wpGutenbergInstance.setPostMetaboxes( window._wpGutenbergMetaboxes );" + ); + + // Restore any global variables that we temporarily modified above. + foreach ( $_gutenberg_restore_globals_after_metaboxes as $name => $value ) { + $GLOBALS[ $name ] = $value; + } +} + /** * Gutenberg's Menu. * From 9bfc6093c3a10b4c1228bb5baace659b3e63dd6c Mon Sep 17 00:00:00 2001 From: James Nylen Date: Sun, 6 Aug 2017 13:02:44 -0500 Subject: [PATCH 2/5] Fix code style issues --- editor/index.js | 2 ++ lib/register.php | 26 +++++++++++++++----------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/editor/index.js b/editor/index.js index cac696c70bf05..6e9e5ddc39938 100644 --- a/editor/index.js +++ b/editor/index.js @@ -109,6 +109,8 @@ function prepareMetaboxesState( store, metaboxes ) { * @param {String} id Unique identifier for editor instance * @param {Object} post API entity for post to edit (type required) * @param {Object} editorSettings Editor settings object + * @return {Object} Object containing functions that can be + * called to manipulate the editor instance. */ export function createEditorInstance( id, post, editorSettings = DEFAULT_SETTINGS ) { const store = createReduxStore(); diff --git a/lib/register.php b/lib/register.php index 1ec9c1d81f652..cc53cea755356 100644 --- a/lib/register.php +++ b/lib/register.php @@ -25,10 +25,12 @@ function the_gutenberg_project() { Date: Tue, 15 Aug 2017 14:55:28 -0500 Subject: [PATCH 3/5] WIP: Try writing a metaboxes API endpoint --- gutenberg.php | 1 + .../class-gutenberg-metaboxes-controller.php | 175 ++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 lib/api/class-gutenberg-metaboxes-controller.php diff --git a/gutenberg.php b/gutenberg.php index d71b60d448113..a66ed479cb59b 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -16,6 +16,7 @@ require_once dirname( __FILE__ ) . '/lib/init-checks.php'; if ( gutenberg_can_init() ) { // Load API functions, register scripts and actions, etc. + require_once dirname( __FILE__ ) . '/lib/api/class-gutenberg-metaboxes-controller.php'; require_once dirname( __FILE__ ) . '/lib/class-wp-block-type.php'; require_once dirname( __FILE__ ) . '/lib/class-wp-block-type-registry.php'; require_once dirname( __FILE__ ) . '/lib/blocks.php'; diff --git a/lib/api/class-gutenberg-metaboxes-controller.php b/lib/api/class-gutenberg-metaboxes-controller.php new file mode 100644 index 0000000000000..1d4e7f481f41a --- /dev/null +++ b/lib/api/class-gutenberg-metaboxes-controller.php @@ -0,0 +1,175 @@ + WP_REST_Server::READABLE, + 'permission_callback' => array( __CLASS__, 'get_items_permissions_check' ), + 'callback' => array( __CLASS__, 'get_items' ), + 'args' => array( + 'post_id' => array( + 'description' => __( + 'The ID of the post for which to fetch metaboxes.', + 'gutenberg' + ), + 'type' => 'integer', + ), + ), + ), + ) ); + } + + /** + * Check permissions for reading metaboxes. Requires `edit_post` for the + * requested post. + * + * @param WP_REST_Request $request Full details about the request. + * @return bool|WP_Error True if the request has read access for the item, WP_Error object otherwise. + */ + public static function get_items_permissions_check( $request ) { + $post = get_post( $request['post_id'] ); + if ( ! $post ) { + return new WP_Error( + 'rest_post_invalid_id', + __( 'Invalid post ID.', 'gutenberg' ), + array( + 'status' => 404, + ) + ); + } + + $post_type = get_post_type_object( $post->post_type ); + $allowed_post_types = get_post_types( array( + 'show_in_rest' => true, + ) ); + if ( ! in_array( $post_type->name, $allowed_post_types, true ) ) { + return new WP_Error( + 'rest_post_invalid_type', + __( 'Invalid post type.', 'gutenberg' ), + array( + 'status' => 404, + ) + ); + } + + if ( current_user_can( $post_type->cap->edit_post, $post->ID ) ) { + return true; + } + + return false; + } + + /** + * Returns information about metaboxes for the requested post. + * + * Plugins that add metaboxes along with their timing: + * + * advanced-custom-fields + * ====================== + * An 'admin_enqueue_scripts' callback (priority 10) verifies that `$pagenow` + * is `post.php` or `post-new.php`, then adds an 'admin_head' callback which + * calls add_meta_box(). + * + * @param WP_REST_Request $request Full details about the request. + * @return array Information about metaboxes for the requested post. + */ + public static function get_items( $request ) { + $post = get_post( $request['post_id'] ); + $output = array(); + + // advanced-custom-fields doesn't do anything unless is_admin(). + define( 'WP_ADMIN', true ); + + // advanced-custom-fields doesn't register metaboxes without these. + $GLOBALS['pagenow'] = 'post.php'; + $GLOBALS['typenow'] = $post->post_type; + + // Load wp-admin APIs and functions. This shouldn't produce any + // output, but we can collect it just in case and verify during + // testing. + ob_start(); + require_once ABSPATH . 'wp-admin/includes/admin.php'; + $output['include_admin'] = ob_get_clean(); + + // Capture output produced by the following actions. + // TODO: Collect more specific info about enqueued assets. + ob_start(); + + /* + * Normally called via wp-admin/post.php -> + * wp-admin/edit-form-advanced.php -> wp-admin/admin-header.php + */ + do_action( 'admin_enqueue_scripts', 'post.php' ); + do_action( 'admin_print_styles-post.php' ); + do_action( 'admin_print_styles' ); + do_action( 'admin_print_scripts-post.php' ); + do_action( 'admin_print_scripts' ); + do_action( 'admin_head-post.php' ); + do_action( 'admin_head' ); + + $output['admin_head'] = ob_get_clean(); + + /* + * Adapted from code in wp-admin/edit-form-advanced.php: + * do_action( 'do_meta_boxes', $post_type, {'normal','advanced','side'}, $post ); + * do_meta_boxes( $post_type, 'side', $post ); + * do_meta_boxes( null, 'normal', $post ); + * do_meta_boxes( null, 'advanced', $post ); + */ + foreach ( array( 'normal', 'advanced', 'side' ) as $location ) { + ob_start(); + do_action( + 'do_meta_boxes', + $post->post_type, + $location, + $post + ); + do_meta_boxes( + 'side' === $location ? $post->post_type : null, + $location, + $post + ); + $output[ $location ] = ob_get_clean(); + } + + // TODO wp-admin/post.php -> wp-admin/admin-footer.php ? + ; // Just for you, phpcs. + + return array( + 'html' => $output, + ); + } +} + +add_action( + 'rest_api_init', + array( 'Gutenberg_Metaboxes_Controller', 'register_routes' ) +); From 0caf44560b98bea51de3af3e4b7f750ddac6ea3e Mon Sep 17 00:00:00 2001 From: James Nylen Date: Tue, 15 Aug 2017 14:56:17 -0500 Subject: [PATCH 4/5] Remove local debugging stuff --- lib/api/class-gutenberg-metaboxes-controller.php | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/lib/api/class-gutenberg-metaboxes-controller.php b/lib/api/class-gutenberg-metaboxes-controller.php index 1d4e7f481f41a..ac390f4735629 100644 --- a/lib/api/class-gutenberg-metaboxes-controller.php +++ b/lib/api/class-gutenberg-metaboxes-controller.php @@ -9,18 +9,6 @@ die( 'Silence is golden.' ); } -__local_log( 'load gutenberg', substr( __FILE__, strlen( ABSPATH ) ) ); - -add_action( 'init', function() { - __local_log( 'init' ); -} ); -add_action( 'plugins_loaded', function() { - __local_log( 'plugins_loaded' ); -} ); -add_action( 'rest_api_init', function() { - __local_log( 'rest_api_init' ); -} ); - /** * Controller class for metaboxes API endpoint. */ From 03a7414f01bffdb2d22dc734c29293d9279d972a Mon Sep 17 00:00:00 2001 From: BE-Webdesign Date: Tue, 15 Aug 2017 18:25:40 -0400 Subject: [PATCH 5/5] Adding a metabox endpoint of sorts. To use this hit the `wp-admin/post.php?post=ID&action=edit&metabox=some-location`. This will currently render the metaboxes for the base set of meta boxes. Adds and iframe as well to pull these in from endpoint. --- lib/metabox-api.php | 171 ++++++++++++++++++++++++++++++++++++++++++++ lib/register.php | 13 ++++ 2 files changed, 184 insertions(+) create mode 100644 lib/metabox-api.php diff --git a/lib/metabox-api.php b/lib/metabox-api.php new file mode 100644 index 0000000000000..5c16a7589080e --- /dev/null +++ b/lib/metabox-api.php @@ -0,0 +1,171 @@ + + + + + ', var_dump( $wp_meta_boxes ), ''; + + /** + * Prints scripts or data before the default footer scripts. + * + * @since 1.2.0 + * + * @param string $data The data to print. + */ + do_action( 'admin_footer', '' ); + + /** + * Prints scripts and data queued for the footer. + * + * The dynamic portion of the hook name, `$hook_suffix`, + * refers to the global hook suffix of the current page. + * + * @since 4.6.0 + */ + do_action( "admin_print_footer_scripts-{$hook_suffix}" ); + + /** + * Prints any scripts and data queued for the footer. + * + * @since 2.8.0 + */ + do_action( 'admin_print_footer_scripts' ); + + /** + * Prints scripts or data after the default footer scripts. + * + * The dynamic portion of the hook name, `$hook_suffix`, + * refers to the global hook suffix of the current page. + * + * @since 2.8.0 + */ + do_action( "admin_footer-{$hook_suffix}" ); + + exit( 'YOLO' ); + + /** + * Shutdown hooks potentially firing. + * + * Try Query Monitor plugin to make sure the output isn't janky. + */ + } +} + +add_action( 'do_meta_boxes', 'gutenberg_meta_box_api' ); + +?> diff --git a/lib/register.php b/lib/register.php index cc53cea755356..999f1481ed476 100644 --- a/lib/register.php +++ b/lib/register.php @@ -18,10 +18,23 @@ * @since 0.1.0 */ function the_gutenberg_project() { + // Somehow get post ID. + global $post; + $post_id = $post->ID; + $meta_box_api_url = get_admin_url(); + $meta_box_api_url = $meta_box_api_url . 'post.php'; + $meta_box_api_url = add_query_arg( array( + 'post' => $post_id, + 'metabox' => 'some-location', + 'action' => 'edit', + ), $meta_box_api_url ); ?>
+ +

Meta Boxes: Wait for iFrame to load!

+