Skip to content

Commit

Permalink
New block: Chapter List (#650)
Browse files Browse the repository at this point in the history
* New block: Chapter List

* Enforce no top margin
  • Loading branch information
ryelle committed Sep 11, 2024
1 parent 79c26f0 commit 3f82d10
Show file tree
Hide file tree
Showing 7 changed files with 495 additions and 0 deletions.
95 changes: 95 additions & 0 deletions mu-plugins/blocks/chapter-list/class-chapter-walker.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php
/**
* Custom walker for chapter block page list.
*
* Identical to `Walker_Page` except with a `walk()` that ignores orphaned
* pages, which are pages with an ancestor that is not published.
*/

namespace WordPressdotorg\MU_Plugins\Chapter_List;

class Chapter_Walker extends \Walker_Page {

/**
* Display array of elements hierarchically.
*
* Does not assume any existing order of elements.
*
* $max_depth = -1 means flatly display every element.
* $max_depth = 0 means display all levels.
* $max_depth > 0 specifies the number of display levels.
*
* NOTE: This is identical to `Walker::walk()` except that it ignores orphaned
* pages, which are essentially pages whose ancestor is not published.
*
* @param array $elements An array of elements.
* @param int $max_depth The maximum hierarchical depth.
* @param mixed ...$args Optional additional arguments.
* @return string The hierarchical item output.
*/
public function walk( $elements, $max_depth, ...$args ) {
$output = '';

// invalid parameter or nothing to walk.
if ( $max_depth < -1 || empty( $elements ) ) {
return $output;
}

$parent_field = $this->db_fields['parent'];

// flat display.
if ( -1 === $max_depth ) {
$empty_array = array();
foreach ( $elements as $e ) {
$this->display_element( $e, $empty_array, 1, 0, $args, $output );
}
return $output;
}

/*
* Need to display in hierarchical order.
* Separate elements into two buckets: top level and children elements.
* Children_elements is two dimensional array, eg.
* Children_elements[10][] contains all sub-elements whose parent is 10.
*/
$top_level_elements = array();
$children_elements = array();
foreach ( $elements as $e ) {
if ( empty( $e->$parent_field ) ) {
$top_level_elements[] = $e;
} else {
$children_elements[ $e->$parent_field ][] = $e;
}
}

/*
* When none of the elements is top level.
* Assume the first one must be root of the sub elements.
*/
if ( empty( $top_level_elements ) ) {
$first = array_slice( $elements, 0, 1 );
$root = $first[0];

$top_level_elements = array();
$children_elements = array();
foreach ( $elements as $e ) {
if ( $root->$parent_field === $e->$parent_field ) {
$top_level_elements[] = $e;
} else {
$children_elements[ $e->$parent_field ][] = $e;
}
}
}

foreach ( $top_level_elements as $e ) {
$this->display_element( $e, $children_elements, $max_depth, 0, $args, $output );
}

/*
* Here is where it differs from the original `walk()`. The original would
* automatically display orphans.
*/

return $output;
}
}
80 changes: 80 additions & 0 deletions mu-plugins/blocks/chapter-list/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php
namespace WordPressdotorg\MU_Plugins\Chapter_List;

defined( 'WPINC' ) || die();

require_once __DIR__ . '/class-chapter-walker.php';

add_action( 'init', __NAMESPACE__ . '\init' );

/**
* Registers the block using the metadata loaded from the `block.json` file.
* Behind the scenes, it registers also all assets so they can be enqueued
* through the block editor in the corresponding context.
*
* @see https://developer.wordpress.org/reference/functions/register_block_type/
*/
function init() {
register_block_type(
__DIR__ . '/build',
array(
'render_callback' => __NAMESPACE__ . '\render',
)
);
}

/**
* Render the block content.
*
* @param array $attributes Block attributes.
* @param string $content Block default content.
* @param WP_Block $block Block instance.
*
* @return string Returns the block markup.
*/
function render( $attributes, $content, $block ) {
if ( ! isset( $block->context['postId'] ) ) {
return '';
}

$post_id = $block->context['postId'];
$post_type = get_post_type( $post_id );

$args = array(
'title_li' => '',
'echo' => 0,
'sort_column' => 'menu_order, title',
'post_type' => $post_type,

// Use custom walker that excludes display of orphaned pages (an ancestor
// of such a page is likely not published and thus this is not accessible).
'walker' => new Chapter_Walker(),
);

$post_type_obj = get_post_type_object( $post_type );

if ( $post_type_obj && current_user_can( $post_type_obj->cap->read_private_posts ) ) {
$args['post_status'] = array( 'publish', 'private' );
}

$content = wp_list_pages( $args );

$header = '<div class="wporg-chapter-list__header">';
$header .= do_blocks(
'<!-- wp:heading {"style":{"typography":{"fontStyle":"normal","fontWeight":"400"}},"fontSize":"normal","fontFamily":"inter"} -->
<h2 class="wp-block-heading has-inter-font-family has-normal-font-size" style="font-style:normal;font-weight:400">' . esc_html__( 'Chapters', 'wporg' ) . '</h2>
<!-- /wp:heading -->'
);
$header .= '<button type="button" class="wporg-chapter-list__toggle" aria-expanded="false">';
$header .= '<span class="screen-reader-text">' . esc_html__( 'Chapter list', 'wporg' ) . '</span>';
$header .= '</button>';
$header .= '</div>';

$wrapper_attributes = get_block_wrapper_attributes();
return sprintf(
'<aside %1$s><nav>%2$s<ul class="wporg-chapter-list__list">%3$s</ul></nav></aside>',
$wrapper_attributes,
$header,
$content
);
}
35 changes: 35 additions & 0 deletions mu-plugins/blocks/chapter-list/src/block.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 2,
"name": "wporg/chapter-list",
"version": "0.1.0",
"title": "Chapter Navigation",
"category": "widgets",
"icon": "smiley",
"description": "",
"usesContext": [ "postId" ],
"attributes": {
"postType": {
"type": "string"
}
},
"supports": {
"html": false,
"spacing": {
"margin": [
"top",
"bottom"
],
"padding": true,
"blockGap": true
},
"typography": {
"fontSize": true,
"lineHeight": true
}
},
"textdomain": "wporg",
"editorScript": "file:./index.js",
"style": "file:./style-index.css",
"viewScript": "file:./view.js"
}
25 changes: 25 additions & 0 deletions mu-plugins/blocks/chapter-list/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* WordPress dependencies
*/
import { registerBlockType } from '@wordpress/blocks';
import { useBlockProps } from '@wordpress/block-editor';
import ServerSideRender from '@wordpress/server-side-render';

/**
* Internal dependencies
*/
import metadata from './block.json';
import './style.scss';

const Edit = ( { attributes, name } ) => {
const blockProps = useBlockProps();
return (
<div { ...blockProps }>
<ServerSideRender block={ name } attributes={ attributes } skipBlockSupportAttributes />
</div>
);
};

registerBlockType( metadata.name, {
edit: Edit,
} );
Loading

0 comments on commit 3f82d10

Please sign in to comment.