Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add template editor rendering #69

Merged
merged 24 commits into from
May 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
6b7fde5
Remove some conditionals, limit some comments
kienstra Apr 27, 2021
e681ee0
Revert "Remove some conditionals, limit some comments"
kienstra Apr 27, 2021
0c0f005
Restore comment edits, trivial change from DocBlock to //
kienstra Apr 27, 2021
24adae4
Add an integration test that will keep failing until this feature works
kienstra Apr 27, 2021
8e60e81
Allow saving the template markup to the Block object
kienstra Apr 27, 2021
1e66022
Add a TemplateEditor class to render the markup
kienstra Apr 27, 2021
b161444
Implement the template rendering, replacing {{foo}} with block_field(…
kienstra Apr 27, 2021
fdc6747
Implement the template rendering, replacing {{foo}} with block_field(…
kienstra Apr 27, 2021
629745b
Remove an else block that's not needed
kienstra Apr 27, 2021
240834e
Indent translators comment, fixing my mistake
kienstra Apr 27, 2021
5847eb0
In the e2e test, ensure that the rendering works
kienstra Apr 27, 2021
f171d77
Make the matcher non-greedy, so the field names can have { or }
kienstra Apr 27, 2021
75fe170
Fix missing semicolon so linting passes
kienstra Apr 27, 2021
cec474e
Handle escaped { in the template, like Phil mentioned
kienstra Apr 28, 2021
fea137b
Render the CSS that was entered in the template editor
kienstra Apr 29, 2021
618b7c2
Fix the failing PHPUnit tests
kienstra Apr 30, 2021
458c0e2
Add a unit test class for TemplateEditor.php
kienstra May 4, 2021
2fb7086
Use the CSS in the front-end preview if it exists
kienstra May 5, 2021
fe8b84b
Allow saving the template CSS to JSON
kienstra May 5, 2021
8e92ac3
Render the <style> in the same place as the block, if it should be re…
kienstra May 5, 2021
15edeec
Remove needless comments, some copied
kienstra May 5, 2021
06bf5e0
Allow autocompletion, but not of the fields (at least yet)
kienstra May 5, 2021
df28138
Escape the rendered template with wp_kses_post()
kienstra May 7, 2021
eb4bc47
Use wp_strip_all_tags() for CSS escaping
kienstra May 7, 2021
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: 4 additions & 0 deletions js/src/edit-block/components/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ const Editor = ( { onError, postId, postType, settings } ) => {
<StrictMode>
<div className="h-screen flex flex-col items-center text-black">
{ template?.cssUrl ? <link rel="stylesheet" href={ template.cssUrl } type="text/css" /> : null }
{ ! template?.cssUrl && Boolean( block.templateCss )
? <style>{ block.templateCss }</style>
: null
}
<BrowserURL />
<UnsavedChangesWarning />
<EditorProvider
Expand Down
7 changes: 6 additions & 1 deletion js/src/edit-block/components/template-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/
import * as React from 'react';
import AceEditor from 'react-ace';
import 'ace-builds/src-noconflict/ext-language_tools';
import 'ace-builds/src-noconflict/mode-html';
import 'ace-builds/src-noconflict/theme-textmate';

Expand Down Expand Up @@ -84,8 +85,12 @@ const TemplateEditor = () => {
} );
} }
name="gcb-template-editor"
editorProps={ { $blockScrolling: true } }
editorProps={ {
$blockScrolling: true,
} }
setOptions={ {
enableBasicAutocompletion: true,
enableLiveAutocompletion: true,
highlightActiveLine: true,
useWorker: false,
} }
Expand Down
36 changes: 30 additions & 6 deletions php/Blocks/Block.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,20 @@ class Block {
*/
public $fields = [];

/**
* Template editor CSS.
*
* @var string
*/
public $template_css = '';

/**
* Template editor markup.
*
* @var string
*/
public $template_markup = '';

/**
* Block constructor.
*
Expand Down Expand Up @@ -143,6 +157,14 @@ public function from_array( $config ) {
$this->keywords = $config['keywords'];
}

if ( isset( $config['templateCss'] ) ) {
$this->template_markup = $config['templateCss'];
}

if ( isset( $config['templateMarkup'] ) ) {
$this->template_markup = $config['templateMarkup'];
}

if ( isset( $config['fields'] ) ) {
foreach ( $config['fields'] as $key => $field ) {
$this->fields[ $key ] = new Field( $field );
Expand All @@ -156,12 +178,14 @@ public function from_array( $config ) {
* @return string
*/
public function to_json() {
$config['name'] = $this->name;
$config['title'] = $this->title;
$config['excluded'] = $this->excluded;
$config['icon'] = $this->icon;
$config['category'] = $this->category;
$config['keywords'] = $this->keywords;
$config['name'] = $this->name;
$config['title'] = $this->title;
$config['excluded'] = $this->excluded;
$config['icon'] = $this->icon;
$config['category'] = $this->category;
$config['keywords'] = $this->keywords;
$config['templateCss'] = $this->template_css;
$config['templateMarkup'] = $this->template_markup;

$config['fields'] = [];
foreach ( $this->fields as $key => $field ) {
Expand Down
71 changes: 45 additions & 26 deletions php/Blocks/Loader.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,21 @@ class Loader extends ComponentAbstract {
*/
protected $data = [];

/**
* The template editor.
*
* @var TemplateEditor
*/
protected $template_editor;

/**
* Load the Loader.
*
* @return $this
*/
public function init() {
$this->assets = [
$this->template_editor = new TemplateEditor();
$this->assets = [
'path' => [
'entry' => $this->plugin->get_path( 'js/dist/block-editor.js' ),
'editor_style' => $this->plugin->get_path( 'css/dist/blocks.editor.css' ),
Expand Down Expand Up @@ -315,23 +323,19 @@ protected function render_block_template( $block, $attributes ) {
}

if ( ! is_admin() ) {
/**
* The block has been added, but its values weren't saved (not even the defaults). This is a phenomenon
* unique to frontend output, as the editor fetches its attributes from the form fields themselves.
*/
// The block has been added, but its values weren't saved (not even the defaults).
// This is unique to frontend output, as the editor fetches its attributes from the form fields themselves.
$missing_schema_attributes = array_diff_key( $block->fields, $attributes );
foreach ( $missing_schema_attributes as $attribute_name => $schema ) {
if ( isset( $schema->settings['default'] ) ) {
$attributes[ $attribute_name ] = $schema->settings['default'];
}
}

$this->enqueue_block_styles( $block->name, 'block' );
$did_enqueue_styles = $this->enqueue_block_styles( $block->name, 'block' );

/**
* The wp_enqueue_style function handles duplicates, so we don't need to worry about multiple blocks
* loading the global styles more than once.
*/
// The wp_enqueue_style function handles duplicates, so we don't need to worry about multiple blocks
// loading the global styles more than once.
$this->enqueue_global_styles();
}

Expand Down Expand Up @@ -372,16 +376,25 @@ protected function render_block_template( $block, $attributes ) {

ob_start();
$this->block_template( $block->name, $type );
$output = ob_get_clean();

return $output;
if ( empty( $did_enqueue_styles ) ) {
$this->template_editor->render_css(
isset( $this->blocks[ "genesis-custom-blocks/{$block->name}" ]['templateCss'] )
? $this->blocks[ "genesis-custom-blocks/{$block->name}" ]['templateCss']
: '',
$block->name
);
}

return ob_get_clean();
}

/**
* Enqueues styles for the block.
*
* @param string $name The name of the block (slug as defined in UI).
* @param string|array $type The type of template to load.
* @return bool Whether this found styles and enqueued them.
*/
protected function enqueue_block_styles( $name, $type = 'block' ) {
$locations = [];
Expand All @@ -397,18 +410,18 @@ protected function enqueue_block_styles( $name, $type = 'block' ) {
$stylesheet_path = genesis_custom_blocks()->locate_template( $locations );
$stylesheet_url = genesis_custom_blocks()->get_url_from_path( $stylesheet_path );

/**
* Enqueue the stylesheet, if it exists. The wp_enqueue_style function handles duplicates, so we don't need
* to worry about the same block loading its stylesheets more than once.
*/
if ( ! empty( $stylesheet_url ) ) {
wp_enqueue_style(
"genesis-custom-blocks__block-{$name}",
$stylesheet_url,
[],
wp_get_theme()->get( 'Version' )
);

return true;
}

return false;
}

/**
Expand Down Expand Up @@ -473,19 +486,25 @@ protected function block_template( $name, $type = 'block' ) {

// This is not a load once template, so require_once is false.
load_template( $theme_template, false );
} else {
if ( ! current_user_can( 'edit_posts' ) || ! isset( $templates[0] ) ) {
return;
}
// Hide the template not found notice on the frontend, unless WP_DEBUG is enabled.
if ( ! is_admin() && ! ( defined( 'WP_DEBUG' ) && WP_DEBUG ) ) {
return;
}
return;
}

if ( ! empty( $this->blocks[ "genesis-custom-blocks/{$name}" ]['templateMarkup'] ) ) {
$this->template_editor->render_markup( $this->blocks[ "genesis-custom-blocks/{$name}" ]['templateMarkup'] );
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This renders the markup:

renders-markup

return;
}

if ( ! current_user_can( 'edit_posts' ) || ! isset( $templates[0] ) ) {
Copy link

@johnstonphilip johnstonphilip May 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it makes sense to introduce a custom user permission for this? Maybe current_user_can ( 'edit_genesis_custom_blocks' ) or something?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, we could use genesis_custom_block_edit_block, but I'm not too sure it would make a big difference.

Here's the main context they would see this in:

the-template-file

If we used genesis_custom_block_edit_block, it would mean editors wouldn't see it.

But either way would probably be fine.

return;
}

// Only show the template not found notice on the frontend if WP_DEBUG is enabled.
if ( is_admin() || ( defined( 'WP_DEBUG' ) && WP_DEBUG ) ) {
printf(
'<div class="notice notice-warning">%s</div>',
wp_kses_post(
// Translators: Placeholder is a file path.
sprintf( __( 'Template file %s not found.', 'genesis-custom-blocks' ), '<code>' . esc_html( $templates[0] ) . '</code>' )
/* translators: %1$s: file path */
sprintf( __( 'Template file %1$s not found.', 'genesis-custom-blocks' ), '<code>' . esc_html( $templates[0] ) . '</code>' )
)
);
}
Expand Down
64 changes: 64 additions & 0 deletions php/Blocks/TemplateEditor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php
/**
* TemplateEditor.
*
* @package Genesis\CustomBlocks
* @copyright Copyright(c) 2021, Genesis Custom Blocks
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/

namespace Genesis\CustomBlocks\Blocks;

/**
* Class TemplateEditor
*/
class TemplateEditor {

/**
* The block names that have had their CSS rendered.
*
* @var string[]
*/
public $blocks_with_rendered_css = [];

/**
* Renders markup that was entered in the template editor.
*
* @param string $markup The markup to render.
*/
public function render_markup( $markup ) {
$rendered = preg_replace_callback(
'#{{(\S+?)}}#',
static function( $matches ) {
ob_start();
block_field( $matches[1] );
return ob_get_clean();
},
$markup
);

// Escape characters before { should be stripped, like \{\{example\}\}.
// Like if they have a tutorial on Mustache and need the template to render {{example}}.
$rendered = preg_replace( '#\\\{\\\{(\S+?)\\\}\\\}#', '{{\1}}', $rendered );

echo wp_kses_post( $rendered );
}

/**
* Renders CSS that was entered in the template editor.
*
* @param string $css The CSS to render, if any.
* @param string $block_name The block name, without the genesis-custom-blocks/ namespace.
*/
public function render_css( $css, $block_name ) {
if ( empty( $css ) || in_array( $block_name, $this->blocks_with_rendered_css, true ) ) {
return;
}

$this->blocks_with_rendered_css[] = $block_name;

?>
<style><?php echo wp_strip_all_tags( $css ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></style>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like it!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@johnstonphilip, thanks a lot!

<?php
}
}
15 changes: 12 additions & 3 deletions tests/e2e/specs/template-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ const customPostType = 'genesis_custom_block';

describe( 'TemplateEditor', () => {
it( 'creates a block with the template editor', async () => {
const { findByText, findByLabelText } = queries;
const { findByLabelText, findByRole, findByText } = queries;

const field = {
label: 'Text',
name: 'text',
type: 'text',
value: 'Here is an example value for this',
};
const blockName = 'Test Text';
const blockName = 'Test Template Editor';
const templateMarkup = `Here is the text field: {{${ field.name }}}`;

await visitAdminPage( 'post-new.php', `?post_type=${ customPostType }` );
Expand All @@ -33,6 +34,14 @@ describe( 'TemplateEditor', () => {
await page.keyboard.type( field.label );
await page.select( '#field-control', field.type );
await ( await findByText( $editBlockDocument, 'Template Editor' ) ).click();
( await page.waitForSelector( '#gcb-template-editor' ) ).type( templateMarkup );
await ( await page.waitForSelector( '#gcb-template-editor' ) ).click();
await page.keyboard.type( templateMarkup );

await ( await findByText( $editBlockDocument, 'Editor Preview' ) ).click();
await ( await findByLabelText( $editBlockDocument, field.label ) ).type( field.value );
await ( await findByRole( $editBlockDocument, 'button', { name: /save draft/i } ) ).click();

await ( await findByText( $editBlockDocument, 'Front-end Preview' ) ).click();
await findByText( $editBlockDocument, `Here is the text field: ${ field.value }` );
} );
} );
44 changes: 44 additions & 0 deletions tests/php/Integration/Fixtures/template-editor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php
/**
* A mock template in the template editor, testing all fields.
*
* @package Genesis\CustomBlocks
*/

// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaping could interfere with testing this template output.

$fields = [
'textarea',
'url',
'email',
'number',
'color',
'image',
'select',
'toggle',
'range',
'checkbox',
'radio',
'text',
'multiselect',
];

ob_start();

foreach ( $fields as $field ) :
?>
<p>
<?php
printf(
'Here is the result for %1$s: %2$s',
$field,
'{{' . $field . '}}'
);
?>
</p>
<?php
endforeach;

echo 'Here are escaped brackets: \{\{example\}\}';

return ob_get_clean();
Loading