Full Site Editing - Version 0.1

Version Description

  • Initial Release
Download this release

Release Info

Developer obenland
Plugin Icon wp plugin Full Site Editing
Version 0.1
Comparing to
See all releases

Version 0.1

full-site-editing-plugin.php ADDED
@@ -0,0 +1,45 @@
1
+ <?php
2
+ /**
3
+ * Plugin Name: Full Site Editing
4
+ * Description: Enhances your page creation workflow within the Block Editor.
5
+ * Version: 0.1
6
+ * Author: Automattic
7
+ * Author URI: https://automattic.com/wordpress-plugins/
8
+ * License: GPLv2 or later
9
+ * Text Domain: full-site-editing
10
+ *
11
+ * @package full-site-editing
12
+ */
13
+
14
+ /**
15
+ * Load Posts List Block.
16
+ */
17
+ function a8c_load_posts_list_block() {
18
+ if ( function_exists( 'is_automattician' ) && ! is_automattician() ) {
19
+ return;
20
+ }
21
+
22
+ if ( class_exists( 'Posts_List_Block' ) ) {
23
+ return;
24
+ }
25
+
26
+ require_once __DIR__ . '/posts-list-block/utils.php';
27
+ require_once __DIR__ . '/posts-list-block/class-posts-list-block.php';
28
+
29
+ Posts_List_Block::get_instance();
30
+ }
31
+ add_action( 'plugins_loaded', 'a8c_load_posts_list_block' );
32
+
33
+ /**
34
+ * Load Starter_Page_Templates.
35
+ */
36
+ function a8c_load_starter_page_templates() {
37
+ if ( function_exists( 'is_automattician' ) && ! is_automattician() ) {
38
+ return;
39
+ }
40
+
41
+ require_once __DIR__ . '/starter-page-templates/class-starter-page-templates.php';
42
+
43
+ Starter_Page_Templates::get_instance();
44
+ }
45
+ add_action( 'plugins_loaded', 'a8c_load_starter_page_templates' );
posts-list-block/blocks/posts-list/block.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "a8c/posts-list",
3
+ "category": "layout",
4
+ "attributes": {
5
+ "postsPerPage": {
6
+ "type": "number",
7
+ "default": 10
8
+ }
9
+ }
10
+ }
posts-list-block/blocks/posts-list/index.js ADDED
@@ -0,0 +1,64 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { registerBlockType } from '@wordpress/blocks';
5
+ import { __ } from '@wordpress/i18n';
6
+ import { Placeholder, RangeControl, PanelBody } from '@wordpress/components';
7
+ import { Fragment } from '@wordpress/element';
8
+ import { InspectorControls } from '@wordpress/editor';
9
+
10
+ /**
11
+ * Internal dependencies
12
+ */
13
+ import * as metadata from './block.json';
14
+ import './style.scss';
15
+
16
+ const icon = (
17
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
18
+ <path opacity=".87" fill="none" d="M0 0h24v24H0V0z" />
19
+ <path d="M3 5v14h17V5H3zm4 2v2H5V7h2zm-2 6v-2h2v2H5zm0 2h2v2H5v-2zm13 2H9v-2h9v2zm0-4H9v-2h9v2zm0-4H9V7h9v2z" />
20
+ </svg>
21
+ );
22
+
23
+ registerBlockType( metadata.name, {
24
+ title: __( 'Blog Posts Listing', 'full-site-editing' ),
25
+ description: __( 'Displays your latest Blog Posts.', 'full-site-editing' ),
26
+ icon: icon,
27
+ category: 'layout',
28
+ supports: {
29
+ html: false,
30
+ multiple: false,
31
+ reusable: false,
32
+ },
33
+ attributes: metadata.attributes,
34
+ edit: ( { attributes, setAttributes, isSelected } ) => (
35
+ <Fragment>
36
+ <Placeholder
37
+ icon={ icon }
38
+ label={ __( 'Your recent blog posts will be displayed here.', 'full-site-editing' ) }
39
+ >
40
+ { isSelected ? (
41
+ <RangeControl
42
+ label={ __( 'Number of posts to show', 'full-site-editing' ) }
43
+ value={ attributes.postsPerPage }
44
+ onChange={ val => setAttributes( { postsPerPage: val } ) }
45
+ min={ 1 }
46
+ max={ 50 }
47
+ />
48
+ ) : null }
49
+ </Placeholder>
50
+ <InspectorControls>
51
+ <PanelBody>
52
+ <RangeControl
53
+ label={ __( 'Number of posts', 'full-site-editing' ) }
54
+ value={ attributes.postsPerPage }
55
+ onChange={ val => setAttributes( { postsPerPage: val } ) }
56
+ min={ 1 }
57
+ max={ 50 }
58
+ />
59
+ </PanelBody>
60
+ </InspectorControls>
61
+ </Fragment>
62
+ ),
63
+ save: () => null,
64
+ } );
posts-list-block/blocks/posts-list/style.scss ADDED
@@ -0,0 +1,9 @@
1
+ .a8c-posts-list__listing {
2
+ list-style: none;
3
+ margin: 0;
4
+ padding: 0;
5
+ }
6
+
7
+ .a8c-posts-list__item {
8
+ display: block;
9
+ }
posts-list-block/class-posts-list-block.php ADDED
@@ -0,0 +1,147 @@
1
+ <?php
2
+ /**
3
+ * Posts list block file.
4
+ *
5
+ * @package full-site-editing
6
+ */
7
+
8
+ /**
9
+ * Class Post_List_Block
10
+ */
11
+ class Posts_List_Block {
12
+
13
+ /**
14
+ * Class instance.
15
+ *
16
+ * @var Posts_List_Block
17
+ */
18
+ private static $instance = null;
19
+
20
+ /**
21
+ * A8C_Post_List constructor.
22
+ */
23
+ private function __construct() {
24
+ add_action( 'init', array( $this, 'register_blocks' ), 100 );
25
+ add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_scripts' ), 100 );
26
+ add_action( 'enqueue_block_assets', array( $this, 'enqueue_styles' ), 100 );
27
+ }
28
+
29
+ /**
30
+ * Creates instance.
31
+ *
32
+ * @return \Posts_List_Block
33
+ */
34
+ public static function get_instance() {
35
+ if ( null === self::$instance ) {
36
+ self::$instance = new self();
37
+ }
38
+
39
+ return self::$instance;
40
+ }
41
+
42
+ /**
43
+ * Enqueue block editor scripts.
44
+ */
45
+ public function enqueue_scripts() {
46
+ // phpcs:ignore WordPress
47
+ $script_dependencies = json_decode(
48
+ file_get_contents(
49
+ plugin_dir_path( __FILE__ ) . 'dist/posts-list-block.deps.json'
50
+ ),
51
+ true
52
+ );
53
+ wp_enqueue_script(
54
+ 'a8c-posts-list-script',
55
+ plugins_url( 'dist/posts-list-block.js', __FILE__ ),
56
+ is_array( $script_dependencies ) ? $script_dependencies : array(),
57
+ filemtime( plugin_dir_path( __FILE__ ) . 'dist/posts-list-block.js' ),
58
+ true
59
+ );
60
+ wp_set_script_translations( 'a8c-posts-list-script', 'full-site-editing' );
61
+ }
62
+
63
+ /**
64
+ * Enqueue block styles.
65
+ */
66
+ public function enqueue_styles() {
67
+ $style_file = is_rtl()
68
+ ? 'posts-list-block.rtl.css'
69
+ : 'posts-list-block.css';
70
+ wp_enqueue_style(
71
+ 'posts-list-block-style',
72
+ plugins_url( 'dist/' . $style_file, __FILE__ ),
73
+ array(),
74
+ filemtime( plugin_dir_path( __FILE__ ) . 'dist/' . $style_file )
75
+ );
76
+ }
77
+
78
+ /**
79
+ * Register block.
80
+ */
81
+ public function register_blocks() {
82
+ register_block_type(
83
+ 'a8c/posts-list',
84
+ array(
85
+ 'attributes' => array(
86
+ 'postsPerPage' => array(
87
+ 'type' => 'number',
88
+ 'default' => 10,
89
+ ),
90
+ ),
91
+ 'render_callback' => array( $this, 'render_a8c_post_list_block' ),
92
+ )
93
+ );
94
+ }
95
+
96
+ /**
97
+ * Renders posts list.
98
+ *
99
+ * @param array $attributes Block attributes.
100
+ * @param string $content Block content.
101
+ * @return string
102
+ */
103
+ public function render_a8c_post_list_block( $attributes, $content ) {
104
+ $posts_list = new WP_Query(
105
+ array(
106
+ 'post_type' => 'post',
107
+ 'posts_per_page' => $attributes['postsPerPage'],
108
+ 'post_status' => 'publish',
109
+ 'suppress_filters' => false,
110
+ )
111
+ );
112
+
113
+ add_filter( 'excerpt_more', array( $this, 'custom_excerpt_read_more' ) );
114
+
115
+ $content = a8c_pl_render_template(
116
+ 'posts-list',
117
+ array(
118
+ 'posts_list' => $posts_list,
119
+ )
120
+ );
121
+
122
+ remove_filter( 'excerpt_more', array( $this, 'custom_excerpt_read_more' ) );
123
+
124
+ // Reset the custom query.
125
+ wp_reset_postdata();
126
+
127
+ return $content;
128
+ }
129
+
130
+ /**
131
+ * Excerpt more string.
132
+ *
133
+ * @return string More string.
134
+ */
135
+ public function custom_excerpt_read_more() {
136
+ return sprintf(
137
+ '&hellip; <a href="%1$s" title="%2$s" class="a8c-posts-list-item__read-more">%3$s</a>',
138
+ esc_url( get_the_permalink() ),
139
+ sprintf(
140
+ /* translators: %s: Name of current post */
141
+ esc_attr__( 'Continue reading %s', 'full-site-editing' ),
142
+ the_title_attribute( array( 'echo' => false ) )
143
+ ),
144
+ esc_html__( 'Read more', 'full-site-editing' )
145
+ );
146
+ }
147
+ }
posts-list-block/dist/posts-list-block.css ADDED
@@ -0,0 +1 @@
1
+ .a8c-posts-list__listing{list-style:none;margin:0;padding:0}.a8c-posts-list__item{display:block}
posts-list-block/dist/posts-list-block.deps.json ADDED
@@ -0,0 +1 @@
1
+ ["wp-blocks","wp-components","wp-editor","wp-element","wp-i18n"]
posts-list-block/dist/posts-list-block.js ADDED
@@ -0,0 +1 @@
1
+ !function(e,t){for(var n in t)e[n]=t[n]}(window,function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=8)}([function(e,t){e.exports=wp.element},function(e,t){e.exports=wp.i18n},function(e,t){e.exports=wp.components},function(e){e.exports={b:"a8c/posts-list",a:{postsPerPage:{type:"number",default:10}}}},function(e,t){e.exports=wp.blocks},function(e,t){e.exports=wp.editor},function(e,t,n){},,function(e,t,n){"use strict";n.r(t);var r=n(0),o=n(4),l=n(1),i=n(2),u=n(5),c=n(3),s=(n(6),Object(r.createElement)("svg",{xmlns:"http://www.w3.org/2000/svg",width:"24",height:"24",viewBox:"0 0 24 24"},Object(r.createElement)("path",{opacity:".87",fill:"none",d:"M0 0h24v24H0V0z"}),Object(r.createElement)("path",{d:"M3 5v14h17V5H3zm4 2v2H5V7h2zm-2 6v-2h2v2H5zm0 2h2v2H5v-2zm13 2H9v-2h9v2zm0-4H9v-2h9v2zm0-4H9V7h9v2z"})));Object(o.registerBlockType)(c.b,{title:Object(l.__)("Blog Posts Listing","full-site-editing"),description:Object(l.__)("Displays your latest Blog Posts.","full-site-editing"),icon:s,category:"layout",supports:{html:!1,multiple:!1,reusable:!1},attributes:c.a,edit:function(e){var t=e.attributes,n=e.setAttributes,o=e.isSelected;return Object(r.createElement)(r.Fragment,null,Object(r.createElement)(i.Placeholder,{icon:s,label:Object(l.__)("Your recent blog posts will be displayed here.","full-site-editing")},o?Object(r.createElement)(i.RangeControl,{label:Object(l.__)("Number of posts to show","full-site-editing"),value:t.postsPerPage,onChange:function(e){return n({postsPerPage:e})},min:1,max:50}):null),Object(r.createElement)(u.InspectorControls,null,Object(r.createElement)(i.PanelBody,null,Object(r.createElement)(i.RangeControl,{label:Object(l.__)("Number of posts","full-site-editing"),value:t.postsPerPage,onChange:function(e){return n({postsPerPage:e})},min:1,max:50}))))},save:function(){return null}})}]));
posts-list-block/dist/posts-list-block.rtl.css ADDED
@@ -0,0 +1 @@
1
+ .a8c-posts-list__listing{list-style:none;margin:0;padding:0}.a8c-posts-list__item{display:block}
posts-list-block/index.js ADDED
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Internal dependencies
3
+ */
4
+ import './blocks/posts-list';
posts-list-block/templates/no-posts.php ADDED
@@ -0,0 +1,26 @@
1
+ <?php
2
+ /**
3
+ * Template part for displaying a message that posts cannot be found.
4
+ *
5
+ * @package full-site-editing
6
+ * @link https://developer.wordpress.org/themes/basics/template-hierarchy/
7
+ */
8
+
9
+ ?>
10
+
11
+ <p><?php esc_html_e( 'There are currently no posts to display.', 'full-site-editing' ); ?></p>
12
+ <?php
13
+ if ( current_user_can( 'publish_posts' ) ) :
14
+ printf(
15
+ '<p>' . wp_kses(
16
+ /* translators: 1: link to WP admin new post page. */
17
+ __( 'Ready to publish your first post? <a href="%1$s">Get started here</a>.', 'full-site-editing' ),
18
+ array(
19
+ 'a' => array(
20
+ 'href' => array(),
21
+ ),
22
+ )
23
+ ) . '</p>',
24
+ esc_url( admin_url( 'post-new.php' ) )
25
+ );
26
+ endif;
posts-list-block/templates/post-item.php ADDED
@@ -0,0 +1,42 @@
1
+ <?php
2
+ /**
3
+ * Post Item.
4
+ *
5
+ * @package full-site-editing
6
+ *
7
+ * phpcs:disable WordPress.XSS.EscapeOutput.OutputNotEscaped
8
+ */
9
+
10
+ ?>
11
+
12
+ <article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
13
+ <?php if ( has_post_thumbnail() ) : ?>
14
+ <figure class="a8c-posts-list-item__post-thumbnail">
15
+ <a href="<?php the_permalink(); ?>">
16
+ <?php the_post_thumbnail( 'post-thumbnail' ); ?>
17
+ </a>
18
+ </figure>
19
+ <?php endif; // has_post_thumbnail. ?>
20
+
21
+ <?php if ( is_sticky() ) : ?>
22
+ <div class="a8c-posts-list-item__featured">
23
+ <span><?php esc_html_e( 'Featured', 'full-site-editing' ); ?></span>
24
+ </div>
25
+ <?php endif; // is_sticky. ?>
26
+
27
+ <?php the_title( sprintf( '<h2 class="a8c-posts-list-item__title"><a href="%s" rel="bookmark">', esc_url( get_permalink() ) ), '</a></h2>' ); ?>
28
+
29
+ <div class="a8c-posts-list-item__meta">
30
+ <span class="a8c-posts-list-item__datetime"><?php echo esc_html( get_the_time( get_option( 'date_format' ) ) ); ?></span>
31
+ <span class="a8c-posts-list-item__author"><?php echo esc_html_x( 'by', 'designating the post author (eg: by John Doe', 'full-site-editing' ); ?>
32
+ <?php the_author_posts_link(); ?>
33
+ </span>
34
+ <span class="a8c-posts-list-item__edit-link">
35
+ <a href="<?php echo esc_attr( get_edit_post_link() ); ?>"><?php esc_html_e( 'Edit', 'full-site-editing' ); ?></a>
36
+ </span>
37
+ </div>
38
+
39
+ <div class="a8c-posts-list-item__excerpt">
40
+ <?php the_excerpt(); ?>
41
+ </div>
42
+ </article>
posts-list-block/templates/posts-list.php ADDED
@@ -0,0 +1,39 @@
1
+ <?php
2
+ /**
3
+ * Posts List
4
+ *
5
+ * @package full-site-editing
6
+ */
7
+
8
+ /**
9
+ * Posts list.
10
+ *
11
+ * @global \WP_Query $posts_list
12
+ */
13
+ global $posts_list;
14
+
15
+ if ( $posts_list instanceof WP_Query && $posts_list->have_posts() ) :
16
+ ?>
17
+ <div class="a8c-posts-list">
18
+ <ul class="a8c-posts-list__listing">
19
+ <?php
20
+ while ( $posts_list->have_posts() ) :
21
+ $posts_list->the_post();
22
+ ?>
23
+ <li class="a8c-posts-list__item">
24
+ <?php require dirname( __FILE__ ) . '/post-item.php'; ?>
25
+ </li>
26
+ <?php endwhile; ?>
27
+ </ul>
28
+
29
+ <a href="<?php echo esc_url( get_post_type_archive_link( 'post' ) ); ?>" class="a8c-posts-list__view-all">
30
+ <?php esc_html_e( 'View all posts', 'full-site-editing' ); ?>
31
+ </a>
32
+ </div>
33
+ <?php
34
+ else :
35
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
36
+ echo a8c_pl_render_template( 'no-posts' );
37
+ endif;
38
+
39
+
posts-list-block/utils.php ADDED
@@ -0,0 +1,46 @@
1
+ <?php
2
+ /**
3
+ * Template functions.
4
+ *
5
+ * @package full-site-editing
6
+ */
7
+
8
+ if ( ! function_exists( 'a8c_pl_render_template' ) ) {
9
+ /**
10
+ * OUTPUT BUFFERED LOAD TEMPLATE PART.
11
+ *
12
+ * Loads a given template using output buffering.
13
+ * Optionally including $data to be passed into template.
14
+ *
15
+ * @param string $template_name Name of the template to be located.
16
+ * @param array $data Optional. Associative array of data to be passed into the template. Default empty array.
17
+ * @return string
18
+ */
19
+ function a8c_pl_render_template( $template_name, $data = array() ) {
20
+
21
+ if ( ! strpos( $template_name, '.php' ) ) {
22
+ $template_name = $template_name . '.php';
23
+ }
24
+
25
+ $template_file = __DIR__ . '/templates/' . $template_name;
26
+
27
+ if ( ! file_exists( $template_file ) ) {
28
+ return '';
29
+ }
30
+
31
+ // Optionally provided an assoc array of data to pass to template
32
+ // and it will be extracted into variables.
33
+ if ( is_array( $data ) ) {
34
+ foreach ( $data as $name => $value ) {
35
+ $GLOBALS[ $name ] = $value;
36
+ }
37
+ }
38
+
39
+ ob_start();
40
+ require_once $template_file;
41
+ $content = ob_get_contents();
42
+ ob_end_clean();
43
+
44
+ return $content;
45
+ }
46
+ }
readme.txt ADDED
@@ -0,0 +1,44 @@
1
+ === Full Site Editing ===
2
+ Contributors: automattic, get_dave, marekhrabe, mppfeiffer, obenland
3
+ Tags: block, blocks, editor, gutenberg, page
4
+ Requires at least: 5.0
5
+ Tested up to: 5.2
6
+ Stable tag: 0.1
7
+ Requires PHP: 5.6.20
8
+ License: GPLv2 or later
9
+ License URI: https://www.gnu.org/licenses/gpl-2.0.html
10
+
11
+ Enhances your page creation workflow within the Block Editor.
12
+
13
+ == Description ==
14
+
15
+ This plugin comes with a custom block to display a list of your most recent blog posts, as well as a template selector
16
+ to give you a head start on creating new pages for your site.
17
+
18
+
19
+ == Installation ==
20
+
21
+ 1. Upload the plugin files to the `/wp-content/plugins/full-site-editing` directory, or install the plugin through the WordPress plugins screen directly.
22
+ 1. Activate the plugin through the 'Plugins' screen in WordPress.
23
+ 1. Create a new page and select a template that best suits your needs.
24
+ 1. Place the "Blog Posts Listing" block anywhere you want inside the block editor.
25
+
26
+
27
+ == Frequently Asked Questions ==
28
+
29
+ = Can I use this plugin in production? =
30
+
31
+ We'll be making frequent updates to the plugin as we flesh out its feature set. You're welcome to try it, just be aware that it is only designed to work on the WordPress.com environment and could break after an update.
32
+
33
+ = How is the Blog Posts Listing block different from the Latest Posts block in Core? =
34
+
35
+ It adds an excerpt! And meta information! It really is much more useful, especially if your looking for a block that gives readers a better idea about your latest posts than just the title.
36
+
37
+ = Do you provide support for this plugin? =
38
+
39
+ This plugin is experimental, so we don't provide any support for it outside of websites hosted on WordPress.com at this time.
40
+
41
+ == Changelog ==
42
+
43
+ = 0.1 =
44
+ * Initial Release
starter-page-templates/class-starter-page-templates.php ADDED
@@ -0,0 +1,134 @@
1
+ <?php
2
+ /**
3
+ * Starter page templates file.
4
+ *
5
+ * @package full-site-editing
6
+ */
7
+
8
+ /**
9
+ * Class Starter_Page_Templates
10
+ */
11
+ class Starter_Page_Templates {
12
+
13
+ /**
14
+ * Class instance.
15
+ *
16
+ * @var Starter_Page_Templates
17
+ */
18
+ private static $instance = null;
19
+
20
+ /**
21
+ * Starter_Page_Templates constructor.
22
+ */
23
+ private function __construct() {
24
+ add_action( 'init', array( $this, 'register_scripts' ) );
25
+ add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_assets' ) );
26
+ }
27
+
28
+ /**
29
+ * Creates instance.
30
+ *
31
+ * @return \Starter_Page_Templates
32
+ */
33
+ public static function get_instance() {
34
+ if ( null === self::$instance ) {
35
+ self::$instance = new self();
36
+ }
37
+
38
+ return self::$instance;
39
+ }
40
+
41
+ /**
42
+ * Register block editor scripts.
43
+ */
44
+ public function register_scripts() {
45
+ wp_register_script(
46
+ 'starter-page-templates',
47
+ plugins_url( 'dist/starter-page-templates.js', __FILE__ ),
48
+ array( 'wp-plugins', 'wp-edit-post', 'wp-element' ),
49
+ filemtime( plugin_dir_path( __FILE__ ) . 'dist/starter-page-templates.js' ),
50
+ true
51
+ );
52
+ }
53
+
54
+ /**
55
+ * Enqueue block editor assets.
56
+ */
57
+ public function enqueue_assets() {
58
+ $screen = get_current_screen();
59
+
60
+ // Return early if we don't meet conditions to show templates.
61
+ if ( 'page' !== $screen->id || 'add' !== $screen->action ) {
62
+ return;
63
+ }
64
+
65
+ // Load templates for this site.
66
+ $vertical_data = $this->fetch_vertical_data();
67
+ if ( empty( $vertical_data ) ) {
68
+ return;
69
+ }
70
+ $vertical_name = $vertical_data['vertical'];
71
+ $vertical_templates = $vertical_data['templates'];
72
+
73
+ // Bail early if we have no templates to offer.
74
+ if ( empty( $vertical_templates ) ) {
75
+ return;
76
+ }
77
+
78
+ wp_enqueue_script( 'starter-page-templates' );
79
+
80
+ $default_info = array(
81
+ 'title' => get_bloginfo( 'name' ),
82
+ 'vertical' => $vertical_name,
83
+ );
84
+ $default_templates = array(
85
+ array(
86
+ 'title' => 'Blank',
87
+ 'slug' => 'blank',
88
+ ),
89
+
90
+ );
91
+ $site_info = get_option( 'site_contact_info', array() );
92
+ $config = array(
93
+ 'siteInformation' => array_merge( $default_info, $site_info ),
94
+ 'templates' => array_merge( $default_templates, $vertical_templates ),
95
+ );
96
+ wp_localize_script( 'starter-page-templates', 'starterPageTemplatesConfig', $config );
97
+
98
+ // Enqueue styles.
99
+ $style_file = is_rtl()
100
+ ? 'starter-page-templates.rtl.css'
101
+ : 'starter-page-templates.css';
102
+
103
+ wp_enqueue_style(
104
+ 'starter-page-templates',
105
+ plugins_url( 'dist/' . $style_file, __FILE__ ),
106
+ array(),
107
+ filemtime( plugin_dir_path( __FILE__ ) . 'dist/' . $style_file )
108
+ );
109
+ }
110
+
111
+ /**
112
+ * Fetch vertical data from the API or return cached version if available.
113
+ *
114
+ * @return array Containing vertical name and template list or nothing if an error occurred.
115
+ */
116
+ public function fetch_vertical_data() {
117
+ $vertical_id = get_option( 'site_vertical', 'default' );
118
+ $transient_key = 'starter_page_templates_' . $vertical_id;
119
+ $vertical_templates = get_transient( $transient_key );
120
+
121
+ // Load fresh data if we don't have any or vertical_id doesn't match.
122
+ if ( false === $vertical_templates ) {
123
+ $request_url = 'https://public-api.wordpress.com/wpcom/v2/verticals/' . $vertical_id . '/templates';
124
+ $response = wp_remote_get( esc_url_raw( $request_url ) );
125
+ if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
126
+ return array();
127
+ }
128
+ $vertical_templates = json_decode( wp_remote_retrieve_body( $response ), true );
129
+ set_transient( $transient_key, $vertical_templates, DAY_IN_SECONDS );
130
+ }
131
+
132
+ return $vertical_templates;
133
+ }
134
+ }
starter-page-templates/dist/starter-page-templates.css ADDED
@@ -0,0 +1 @@
1
+ .page-template-modal{width:100%;height:100vh}@media screen and (min-width:783px){.page-template-modal{width:calc(100% - 65px);left:50px;transform:translateY(-50%)}}@media screen and (min-width:960px){.page-template-modal{width:calc(100% - 200px);left:180px}}.page-template-modal .components-modal__header-heading-container{justify-content:center}.page-template-modal__inner{max-width:700px;margin:0 auto;padding:1em 0 3em}.page-template-modal__intro{text-align:center}.page-template-modal__list{padding:1.5em 0}.page-template-modal__list .components-base-control__field{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));grid-gap:1.5em}.page-template-modal__list .components-base-control__label{border:0;clip:rect(1px,1px,1px,1px);-webkit-clip-path:inset(50%);clip-path:inset(50%);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;word-wrap:normal!important}.page-template-modal__list .template-selector-control__label{display:block;width:100%;text-align:center;border:1px solid transparent;padding:2em;border-radius:4px;cursor:pointer}.page-template-modal__list .template-selector-control__label:hover{background:#f3f4f5}.page-template-modal__list .template-selector-control__label:focus{box-shadow:0 0 0 2px #00a0d2;outline:2px solid transparent;outline-offset:-2px}.page-template-modal__list .template-selector-control__media-wrap{width:100%;display:block;margin:0 auto 2em;border:1px solid rgba(25,30,35,.2);background:#f6f6f6;border-radius:4px;overflow:hidden;padding-bottom:90%;box-sizing:content-box;position:relative;pointer-events:none}.page-template-modal__list .template-selector-control__media{width:100%;display:block;position:absolute;top:0;left:0}.page-template-modal__actions{display:flex;flex-direction:column;align-items:center}@media screen and (min-width:960px){.page-template-modal__actions{flex-direction:row;justify-content:flex-end}}@media screen and (max-width:960px){.page-template-modal__action{margin-bottom:1em}}@media screen and (min-width:960px){.page-template-modal__action-use{margin-right:1em}}
starter-page-templates/dist/starter-page-templates.deps.json ADDED
@@ -0,0 +1 @@
1
+ ["lodash","wp-components","wp-compose","wp-element"]
starter-page-templates/dist/starter-page-templates.js ADDED
@@ -0,0 +1,12 @@
1
+ !function(e,t){for(var n in t)e[n]=t[n]}(window,function(e){var t={};function n(r){if(t[r])return t[r].exports;var a=t[r]={i:r,l:!1,exports:{}};return e[r].call(a.exports,a,a.exports,n),a.l=!0,a.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var a in e)n.d(r,a,function(t){return e[t]}.bind(null,a));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=7)}([function(e,t){e.exports=wp.element},function(e,t){e.exports=lodash},function(e,t,n){var r;
2
+ /*!
3
+ Copyright (c) 2017 Jed Watson.
4
+ Licensed under the MIT License (MIT), see
5
+ http://jedwatson.github.io/classnames
6
+ */
7
+ /*!
8
+ Copyright (c) 2017 Jed Watson.
9
+ Licensed under the MIT License (MIT), see
10
+ http://jedwatson.github.io/classnames
11
+ */
12
+ !function(){"use strict";var n={}.hasOwnProperty;function a(){for(var e=[],t=0;t<arguments.length;t++){var r=arguments[t];if(r){var o=typeof r;if("string"===o||"number"===o)e.push(r);else if(Array.isArray(r)&&r.length){var l=a.apply(null,r);l&&e.push(l)}else if("object"===o)for(var c in r)n.call(r,c)&&r[c]&&e.push(c)}}return e.join(" ")}e.exports?(a.default=a,e.exports=a):void 0===(r=function(){return a}.apply(t,[]))||(e.exports=r)}()},function(e,t){e.exports=wp.compose},function(e,t){e.exports=wp.components},function(e,t,n){},,function(e,t,n){"use strict";n.r(t);var r,a=n(0),o={Address:"123 Main St",Phone:"555-555-5555",CompanyName:(r="Your Company Name",r)},l={CompanyName:"title",Address:"address",Phone:"phone"},c=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return e.replace(/{{(\w+)}}/g,function(e,n){var r=o[n];return t[l[n]]||r||n})},s=(n(5),n(1)),i=n(2),p=n.n(i),u=n(3),m=n(4);var f=Object(u.withInstanceId)(function(e){var t=e.label,n=e.className,r=e.help,o=e.instanceId,l=e.onClick,c=e.templates,i=void 0===c?[]:c,u="inspector-radio-control-".concat(o),f=function(e){return l(e.target.value)};return Object(s.isEmpty)(i)?null:Object(a.createElement)(m.BaseControl,{label:t,id:u,help:r,className:p()(n,"template-selector-control")},i.map(function(e,t){return Object(a.createElement)("div",{key:"".concat(u,"-").concat(t),className:"template-selector-control__option"},Object(a.createElement)("button",{type:"button",id:"".concat(u,"-").concat(t),className:"template-selector-control__label",value:e.value,onClick:f,"aria-describedby":r?"".concat(u,"__help"):void 0},Object(a.createElement)("div",{className:"template-selector-control__media-wrap"},e.preview&&Object(a.createElement)("img",{className:"template-selector-control__media",src:e.preview,alt:"Preview of "+e.label})),e.label))}))});if(window.starterPageTemplatesConfig){var d={home:"https://starterpagetemplatesprototype.files.wordpress.com/2019/05/starter-home-2.png",menu:"https://starterpagetemplatesprototype.files.wordpress.com/2019/05/starter-menu-2.png","contact-us":"https://starterpagetemplatesprototype.files.wordpress.com/2019/05/starter-contactus-2.png"};window.starterPageTemplatesConfig.templates=Object(s.map)(window.starterPageTemplatesConfig.templates,function(e){return e.preview=d[e.slug],e})}!function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=e.plugins.registerPlugin,r=e.components.Modal,o=e.compose.withState,l=t.siteInformation,i=void 0===l?{}:l,p=t.templates,u=void 0===p?[]:p,m=o({isOpen:!0,isLoading:!1,verticalTemplates:Object(s.keyBy)(u,"slug")})(function(t){var n=t.isOpen,o=t.verticalTemplates,l=t.setState;return Object(a.createElement)("div",null,n&&Object(a.createElement)(r,{title:"Select Page Template",onRequestClose:function(){return l({isOpen:!1})},className:"page-template-modal"},Object(a.createElement)("div",{className:"page-template-modal__inner"},Object(a.createElement)("div",{className:"page-template-modal__intro"},Object(a.createElement)("p",null,"Pick a Template that matches the purpose of your page."),Object(a.createElement)("p",null,"You can customise each Template to meet your needs.")),Object(a.createElement)("form",{className:"page-template-modal__form"},Object(a.createElement)("fieldset",{className:"page-template-modal__list"},Object(a.createElement)(f,{label:"Template",templates:Object.values(o).map(function(e){return{label:e.title,value:e.slug,preview:e.preview}}),onClick:function(t){l({isOpen:!1}),function(t){if(Object(s.has)(t,"content")){e.data.dispatch("core/editor").editPost({title:c(t.title,i)});var n=c(t.content,i),r=e.blocks.parse(n);e.data.dispatch("core/editor").insertBlocks(r)}}(o[t])}}))))))});n("page-templates",{render:function(){return Object(a.createElement)(m,null)}})}(window.wp,window.starterPageTemplatesConfig)}]));
starter-page-templates/dist/starter-page-templates.rtl.css ADDED
@@ -0,0 +1 @@
1
+ .page-template-modal{width:100%;height:100vh}@media screen and (min-width:783px){.page-template-modal{width:calc(100% - 65px);right:50px;transform:translateY(-50%)}}@media screen and (min-width:960px){.page-template-modal{width:calc(100% - 200px);right:180px}}.page-template-modal .components-modal__header-heading-container{justify-content:center}.page-template-modal__inner{max-width:700px;margin:0 auto;padding:1em 0 3em}.page-template-modal__intro{text-align:center}.page-template-modal__list{padding:1.5em 0}.page-template-modal__list .components-base-control__field{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));grid-gap:1.5em}.page-template-modal__list .components-base-control__label{border:0;clip:rect(1px,1px,1px,1px);-webkit-clip-path:inset(50%);clip-path:inset(50%);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;word-wrap:normal!important}.page-template-modal__list .template-selector-control__label{display:block;width:100%;text-align:center;border:1px solid transparent;padding:2em;border-radius:4px;cursor:pointer}.page-template-modal__list .template-selector-control__label:hover{background:#f3f4f5}.page-template-modal__list .template-selector-control__label:focus{box-shadow:0 0 0 2px #00a0d2;outline:2px solid transparent;outline-offset:-2px}.page-template-modal__list .template-selector-control__media-wrap{width:100%;display:block;margin:0 auto 2em;border:1px solid rgba(25,30,35,.2);background:#f6f6f6;border-radius:4px;overflow:hidden;padding-bottom:90%;box-sizing:content-box;position:relative;pointer-events:none}.page-template-modal__list .template-selector-control__media{width:100%;display:block;position:absolute;top:0;right:0}.page-template-modal__actions{display:flex;flex-direction:column;align-items:center}@media screen and (min-width:960px){.page-template-modal__actions{flex-direction:row;justify-content:flex-end}}@media screen and (max-width:960px){.page-template-modal__action{margin-bottom:1em}}@media screen and (min-width:960px){.page-template-modal__action-use{margin-left:1em}}
starter-page-templates/index.js ADDED
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Internal dependencies
3
+ */
4
+ import './page-template-modal';
starter-page-templates/page-template-modal/components/template-selector-control.js ADDED
@@ -0,0 +1,62 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { isEmpty } from 'lodash';
5
+ import classnames from 'classnames';
6
+
7
+ /**
8
+ * WordPress dependencies
9
+ */
10
+ import { withInstanceId } from '@wordpress/compose';
11
+ import { BaseControl } from '@wordpress/components';
12
+
13
+ function TemplateSelectorControl( {
14
+ label,
15
+ className,
16
+ help,
17
+ instanceId,
18
+ onClick,
19
+ templates = [],
20
+ } ) {
21
+ const id = `inspector-radio-control-${ instanceId }`;
22
+ const handleButtonClick = event => onClick( event.target.value );
23
+
24
+ if ( isEmpty( templates ) ) {
25
+ return null;
26
+ }
27
+
28
+ return (
29
+ <BaseControl
30
+ label={ label }
31
+ id={ id }
32
+ help={ help }
33
+ className={ classnames( className, 'template-selector-control' ) }
34
+ >
35
+ { templates.map( ( option, index ) => (
36
+ <div key={ `${ id }-${ index }` } className="template-selector-control__option">
37
+ <button
38
+ type="button"
39
+ id={ `${ id }-${ index }` }
40
+ className="template-selector-control__label"
41
+ value={ option.value }
42
+ onClick={ handleButtonClick }
43
+ aria-describedby={ help ? `${ id }__help` : undefined }
44
+ >
45
+ <div className="template-selector-control__media-wrap">
46
+ { option.preview && (
47
+ <img
48
+ className="template-selector-control__media"
49
+ src={ option.preview }
50
+ alt={ 'Preview of ' + option.label }
51
+ />
52
+ ) }
53
+ </div>
54
+ { option.label }
55
+ </button>
56
+ </div>
57
+ ) ) }
58
+ </BaseControl>
59
+ );
60
+ }
61
+
62
+ export default withInstanceId( TemplateSelectorControl );
starter-page-templates/page-template-modal/index.js ADDED
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Internal dependencies
3
+ */
4
+ import replacePlaceholders from './utils/replace-placeholders';
5
+ import './styles/starter-page-templates-editor.scss';
6
+ import TemplateSelectorControl from './components/template-selector-control';
7
+ import { keyBy, map, has } from 'lodash';
8
+
9
+ // TODO: remove once we have proper previews from API
10
+ if ( window.starterPageTemplatesConfig ) {
11
+ const PREVIEWS_BY_SLUG = {
12
+ home: 'https://starterpagetemplatesprototype.files.wordpress.com/2019/05/starter-home-2.png',
13
+ menu: 'https://starterpagetemplatesprototype.files.wordpress.com/2019/05/starter-menu-2.png',
14
+ 'contact-us':
15
+ 'https://starterpagetemplatesprototype.files.wordpress.com/2019/05/starter-contactus-2.png',
16
+ };
17
+ window.starterPageTemplatesConfig.templates = map(
18
+ window.starterPageTemplatesConfig.templates,
19
+ template => {
20
+ template.preview = PREVIEWS_BY_SLUG[ template.slug ];
21
+ return template;
22
+ }
23
+ );
24
+ }
25
+
26
+ ( function( wp, config = {} ) {
27
+ const registerPlugin = wp.plugins.registerPlugin;
28
+ const { Modal } = wp.components;
29
+ const { withState } = wp.compose;
30
+
31
+ const { siteInformation = {}, templates = [] } = config;
32
+
33
+ const insertTemplate = template => {
34
+ // Skip inserting if there's nothing to insert.
35
+ if ( ! has( template, 'content' ) ) {
36
+ return;
37
+ }
38
+
39
+ // set title
40
+ wp.data
41
+ .dispatch( 'core/editor' )
42
+ .editPost( { title: replacePlaceholders( template.title, siteInformation ) } );
43
+
44
+ // load content
45
+ const templateString = replacePlaceholders( template.content, siteInformation );
46
+ const blocks = wp.blocks.parse( templateString );
47
+ wp.data.dispatch( 'core/editor' ).insertBlocks( blocks );
48
+ };
49
+
50
+ const PageTemplateModal = withState( {
51
+ isOpen: true,
52
+ isLoading: false,
53
+ verticalTemplates: keyBy( templates, 'slug' ),
54
+ } )( ( { isOpen, verticalTemplates, setState } ) => (
55
+ <div>
56
+ { isOpen && (
57
+ <Modal
58
+ title="Select Page Template"
59
+ onRequestClose={ () => setState( { isOpen: false } ) }
60
+ className="page-template-modal"
61
+ >
62
+ <div className="page-template-modal__inner">
63
+ <div className="page-template-modal__intro">
64
+ <p>Pick a Template that matches the purpose of your page.</p>
65
+ <p>You can customise each Template to meet your needs.</p>
66
+ </div>
67
+ <form className="page-template-modal__form">
68
+ <fieldset className="page-template-modal__list">
69
+ <TemplateSelectorControl
70
+ label="Template"
71
+ templates={ Object.values( verticalTemplates ).map( template => ( {
72
+ label: template.title,
73
+ value: template.slug,
74
+ preview: template.preview,
75
+ } ) ) }
76
+ onClick={ newTemplate => {
77
+ setState( { isOpen: false } );
78
+ insertTemplate( verticalTemplates[ newTemplate ] );
79
+ } }
80
+ />
81
+ </fieldset>
82
+ </form>
83
+ </div>
84
+ </Modal>
85
+ ) }
86
+ </div>
87
+ ) );
88
+ registerPlugin( 'page-templates', {
89
+ render: function() {
90
+ return <PageTemplateModal />;
91
+ },
92
+ } );
93
+ } )( window.wp, window.starterPageTemplatesConfig );
starter-page-templates/page-template-modal/styles/starter-page-templates-editor.scss ADDED
@@ -0,0 +1,121 @@
1
+ @mixin screen-reader-text() {
2
+ border: 0;
3
+ clip: rect( 1px, 1px, 1px, 1px );
4
+ clip-path: inset( 50% );
5
+ height: 1px;
6
+ margin: -1px;
7
+ overflow: hidden;
8
+ padding: 0;
9
+ position: absolute;
10
+ width: 1px;
11
+ word-wrap: normal !important;
12
+ }
13
+ .page-template-modal {
14
+ width: 100%;
15
+ height: 100vh;
16
+
17
+ @media screen and ( min-width: 783px ) {
18
+ width: calc( 100% - 65px );
19
+ left: 50px;
20
+ transform: translateY( -50% );
21
+ }
22
+
23
+ @media screen and ( min-width: 960px ) {
24
+ width: calc( 100% - 200px );
25
+ left: 180px;
26
+ }
27
+ }
28
+
29
+ .page-template-modal .components-modal__header-heading-container {
30
+ justify-content: center;
31
+ }
32
+
33
+ .page-template-modal__inner {
34
+ max-width: 700px;
35
+ margin: 0 auto;
36
+ padding: 1em 0 3em;
37
+ }
38
+
39
+ .page-template-modal__intro {
40
+ text-align: center;
41
+ }
42
+
43
+ .page-template-modal__list {
44
+ padding: 1.5em 0;
45
+
46
+ .components-base-control__field {
47
+ display: grid;
48
+ // stylelint-disable-next-line unit-whitelist
49
+ grid-template-columns: repeat( auto-fit, minmax( 200px, 1fr ) );
50
+ grid-gap: 1.5em;
51
+ }
52
+
53
+ .components-base-control__label {
54
+ @include screen-reader-text();
55
+ }
56
+
57
+ .template-selector-control__label {
58
+ display: block;
59
+ width: 100%;
60
+ text-align: center;
61
+ border: 1px solid transparent;
62
+ padding: 2em;
63
+ border-radius: 4px;
64
+ cursor: pointer;
65
+
66
+ &:hover {
67
+ background: #f3f4f5;
68
+ }
69
+
70
+ &:focus {
71
+ box-shadow: 0 0 0 2px #00a0d2;
72
+ outline: 2px solid transparent;
73
+ outline-offset: -2px;
74
+ }
75
+ }
76
+
77
+ .template-selector-control__media-wrap {
78
+ width: 100%;
79
+ display: block;
80
+ margin: 0 auto 2em;
81
+ border: 1px solid rgba( 25, 30, 35, 0.2 );
82
+ background: #f6f6f6;
83
+ border-radius: 4px;
84
+ overflow: hidden;
85
+ padding-bottom: 90%;
86
+ box-sizing: content-box;
87
+ position: relative;
88
+ pointer-events: none;
89
+ }
90
+
91
+ .template-selector-control__media {
92
+ width: 100%;
93
+ display: block;
94
+ position: absolute;
95
+ top: 0;
96
+ left: 0;
97
+ }
98
+ }
99
+
100
+ .page-template-modal__actions {
101
+ display: flex;
102
+ flex-direction: column;
103
+ align-items: center;
104
+
105
+ @media screen and ( min-width: 960px ) {
106
+ flex-direction: row;
107
+ justify-content: flex-end;
108
+ }
109
+ }
110
+
111
+ .page-template-modal__action {
112
+ @media screen and ( max-width: 960px ) {
113
+ margin-bottom: 1em;
114
+ }
115
+ }
116
+
117
+ .page-template-modal__action-use {
118
+ @media screen and ( min-width: 960px ) {
119
+ margin-right: 1em;
120
+ }
121
+ }
starter-page-templates/page-template-modal/utils/replace-placeholders.js ADDED
@@ -0,0 +1,23 @@
1
+ const __ = a => a;
2
+
3
+ const PLACEHOLDER_DEFAULTS = {
4
+ Address: '123 Main St',
5
+ Phone: '555-555-5555',
6
+ CompanyName: __( 'Your Company Name' ),
7
+ };
8
+
9
+ const KEY_MAP = {
10
+ CompanyName: 'title',
11
+ Address: 'address',
12
+ Phone: 'phone',
13
+ };
14
+
15
+ const replacePlaceholders = ( pageContent, siteInformation = {} ) => {
16
+ return pageContent.replace( /{{(\w+)}}/g, ( match, placeholder ) => {
17
+ const defaultValue = PLACEHOLDER_DEFAULTS[ placeholder ];
18
+ const key = KEY_MAP[ placeholder ];
19
+ return siteInformation[ key ] || defaultValue || placeholder;
20
+ } );
21
+ };
22
+
23
+ export default replacePlaceholders;