diff --git a/blocks/api/categories.js b/blocks/api/categories.js
index 05361f55ff39bc..493070492d0eac 100644
--- a/blocks/api/categories.js
+++ b/blocks/api/categories.js
@@ -16,6 +16,7 @@ const categories = [
{ slug: 'formatting', title: __( 'Formatting' ) },
{ slug: 'embed', title: __( 'Embed' ) },
{ slug: 'layout', title: __( 'Layout Blocks' ) },
+ { slug: 'rest-api', title: __( 'REST API Blocks' ) },
];
/**
diff --git a/blocks/library/index.js b/blocks/library/index.js
index 59f625113cf564..5f1b28bcf894d2 100644
--- a/blocks/library/index.js
+++ b/blocks/library/index.js
@@ -11,3 +11,4 @@ import './pullquote';
import './table';
import './preformatted';
import './code';
+import './latest-posts';
diff --git a/blocks/library/latest-posts/data.js b/blocks/library/latest-posts/data.js
new file mode 100644
index 00000000000000..e48dbd2544d48f
--- /dev/null
+++ b/blocks/library/latest-posts/data.js
@@ -0,0 +1,19 @@
+/**
+ * Returns a Promise with the latest posts or an error on failure.
+ *
+ * @param {Number} postsToShow Number of posts to display.
+ *
+ * @returns {wp.api.collections.Posts} Returns a Promise with the latest posts.
+ */
+export function getLatestPosts( postsToShow = 5 ) {
+ const postsCollection = new wp.api.collections.Posts();
+
+ const posts = postsCollection.fetch( {
+ data: {
+ per_page: postsToShow,
+ },
+ } );
+
+ return posts;
+}
+
diff --git a/blocks/library/latest-posts/index.js b/blocks/library/latest-posts/index.js
new file mode 100644
index 00000000000000..af82a1bad7988e
--- /dev/null
+++ b/blocks/library/latest-posts/index.js
@@ -0,0 +1,74 @@
+/**
+ * WordPress dependencies
+ */
+import { Placeholder } from 'components';
+import { __ } from 'i18n';
+
+/**
+ * Internal dependencies
+ */
+import { registerBlockType } from '../../api';
+import { getLatestPosts } from './data.js';
+
+registerBlockType( 'core/latestposts', {
+ title: __( 'Latest Posts' ),
+
+ icon: 'list-view',
+
+ category: 'rest-api',
+
+ defaultAttributes: {
+ poststoshow: 5,
+ },
+
+ edit: class extends wp.element.Component {
+ constructor() {
+ super( ...arguments );
+
+ const { poststoshow } = this.props.attributes;
+
+ this.state = {
+ latestPosts: [],
+ };
+
+ this.latestPostsRequest = getLatestPosts( poststoshow );
+
+ this.latestPostsRequest
+ .then( latestPosts => this.setState( { latestPosts } ) );
+ }
+
+ render() {
+ const { latestPosts } = this.state;
+
+ if ( ! latestPosts.length ) {
+ return (
+
+
+ );
+ }
+
+ return (
+
+ );
+ }
+ },
+
+ componentWillUnmount() {
+ if ( this.latestPostsRequest.state() === 'pending' ) {
+ this.latestPostsRequest.abort();
+ }
+ },
+
+ save() {
+ return null;
+ },
+} );
diff --git a/blocks/library/latest-posts/index.php b/blocks/library/latest-posts/index.php
new file mode 100644
index 00000000000000..e947a54f8aaa2e
--- /dev/null
+++ b/blocks/library/latest-posts/index.php
@@ -0,0 +1,60 @@
+ 0 &&
+ $posts_to_show_attr < 100
+ ) {
+ $posts_to_show = $attributes['poststoshow'];
+ }
+ }
+
+ $recent_posts = wp_get_recent_posts( array(
+ 'numberposts' => $posts_to_show,
+ 'post_status' => 'publish',
+ ) );
+
+ $posts_content = '';
+
+ foreach ( $recent_posts as $post ) {
+ $post_id = $post['ID'];
+ $post_permalink = get_permalink( $post_id );
+ $post_title = get_the_title( $post_id );
+
+ $posts_content .= "{$post_title}\n";
+ }
+
+ $block_content = <<
+
+
+
+CONTENT;
+
+ return $block_content;
+}
+
+register_block_type( 'core/latestposts', array(
+ 'render' => 'gutenberg_block_core_latest_posts',
+) );
diff --git a/blocks/test/fixtures/core-latestposts.html b/blocks/test/fixtures/core-latestposts.html
new file mode 100644
index 00000000000000..cc09cb936f430a
--- /dev/null
+++ b/blocks/test/fixtures/core-latestposts.html
@@ -0,0 +1,2 @@
+
+
diff --git a/blocks/test/fixtures/core-latestposts.json b/blocks/test/fixtures/core-latestposts.json
new file mode 100644
index 00000000000000..619edde2c7b2c0
--- /dev/null
+++ b/blocks/test/fixtures/core-latestposts.json
@@ -0,0 +1,9 @@
+[
+ {
+ "uid": "_uid_0",
+ "name": "core/latestposts",
+ "attributes": {
+ "poststoshow": 5
+ }
+ }
+]
diff --git a/blocks/test/fixtures/core-latestposts.serialized.html b/blocks/test/fixtures/core-latestposts.serialized.html
new file mode 100644
index 00000000000000..cc09cb936f430a
--- /dev/null
+++ b/blocks/test/fixtures/core-latestposts.serialized.html
@@ -0,0 +1,2 @@
+
+
diff --git a/gutenberg.php b/gutenberg.php
index 6dae588b0c734a..98fa84bc9c0519 100644
--- a/gutenberg.php
+++ b/gutenberg.php
@@ -9,6 +9,8 @@
* @package gutenberg
*/
+define( 'GUTENBERG__PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
+
require_once dirname( __FILE__ ) . '/lib/blocks.php';
require_once dirname( __FILE__ ) . '/lib/client-assets.php';
require_once dirname( __FILE__ ) . '/lib/i18n.php';
diff --git a/lib/blocks.php b/lib/blocks.php
index 58d23c7eb8a9d1..7864b5f9b31f7b 100644
--- a/lib/blocks.php
+++ b/lib/blocks.php
@@ -9,6 +9,8 @@
die( 'Silence is golden.' );
}
+define( 'GUTENBERG__BLOCKS_LIBRARY_DIR', GUTENBERG__PLUGIN_DIR . 'blocks/library' );
+
$wp_registered_blocks = array();
/**
@@ -128,3 +130,12 @@ function do_blocks( $content ) {
return $new_content;
}
add_filter( 'the_content', 'do_blocks', 10 ); // BEFORE do_shortcode().
+
+/**
+ * Loads the server-side rendering of blocks. If your block supports
+ * server-side rendering, add it here.
+ */
+function gutenberg_load_blocks_server_side_rendering() {
+ require_once GUTENBERG__BLOCKS_LIBRARY_DIR . '/latest-posts/index.php';
+}
+add_action( 'init', 'gutenberg_load_blocks_server_side_rendering' );