Commit realizado el 12:13:52 08-04-2024
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
/**
|
||||
* The Block Admin
|
||||
*
|
||||
* @since 1.0.104
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Schema\Blocks;
|
||||
|
||||
use RankMath\Helper;
|
||||
use RankMath\Traits\Hooker;
|
||||
use RankMath\Helpers\Arr;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Block Admin class.
|
||||
*/
|
||||
class Admin {
|
||||
|
||||
use Hooker;
|
||||
|
||||
/**
|
||||
* The Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->filter( 'rank_math/settings/general', 'add_general_settings' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add block settings into general optional panel.
|
||||
*
|
||||
* @param array $tabs Array of option panel tabs.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_general_settings( $tabs ) {
|
||||
Arr::insert(
|
||||
$tabs,
|
||||
[
|
||||
'blocks' => [
|
||||
'icon' => 'rm-icon rm-icon-stories',
|
||||
'title' => esc_html__( 'Blocks', 'rank-math' ),
|
||||
'desc' => esc_html__( 'Take control over the default settings available for Rank Math Blocks.', 'rank-math' ),
|
||||
'file' => dirname( __FILE__ ) . '/views/options-general.php',
|
||||
],
|
||||
],
|
||||
7
|
||||
);
|
||||
|
||||
return $tabs;
|
||||
}
|
||||
}
|
@@ -0,0 +1,238 @@
|
||||
<?php
|
||||
/**
|
||||
* The FAQ Block
|
||||
*
|
||||
* @since 0.9.0
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Schema;
|
||||
|
||||
use WP_Block_Type_Registry;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Block_FAQ class.
|
||||
*/
|
||||
class Block_FAQ extends Block {
|
||||
|
||||
/**
|
||||
* Block type name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $block_type = 'rank-math/faq-block';
|
||||
|
||||
/**
|
||||
* The single instance of the class.
|
||||
*
|
||||
* @var Block_FAQ
|
||||
*/
|
||||
protected static $instance = null;
|
||||
|
||||
/**
|
||||
* Retrieve main Block_FAQ instance.
|
||||
*
|
||||
* Ensure only one instance is loaded or can be loaded.
|
||||
*
|
||||
* @return Block_FAQ
|
||||
*/
|
||||
public static function get() {
|
||||
if ( is_null( self::$instance ) && ! ( self::$instance instanceof Block_FAQ ) ) {
|
||||
self::$instance = new Block_FAQ();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
|
||||
if ( WP_Block_Type_Registry::get_instance()->is_registered( $this->block_type ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
register_block_type(
|
||||
$this->block_type,
|
||||
[
|
||||
'render_callback' => [ $this, 'render' ],
|
||||
'editor_style' => 'rank-math-block-admin',
|
||||
'attributes' => [
|
||||
'listStyle' => [
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
],
|
||||
'titleWrapper' => [
|
||||
'type' => 'string',
|
||||
'default' => 'h3',
|
||||
],
|
||||
'sizeSlug' => [
|
||||
'type' => 'string',
|
||||
'default' => 'thumbnail',
|
||||
],
|
||||
'questions' => [
|
||||
'type' => 'array',
|
||||
'default' => [],
|
||||
'items' => [ 'type' => 'object' ],
|
||||
],
|
||||
'listCssClasses' => [
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
],
|
||||
'titleCssClasses' => [
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
],
|
||||
'contentCssClasses' => [
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
],
|
||||
'textAlign' => [
|
||||
'type' => 'string',
|
||||
'default' => 'left',
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
add_filter( 'rank_math/schema/block/faq-block', [ $this, 'add_graph' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add FAQ schema data in JSON-LD array.
|
||||
*
|
||||
* @param array $data Array of JSON-LD data.
|
||||
* @param array $block JsonLD Instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_graph( $data, $block ) {
|
||||
// Early bail.
|
||||
if ( ! $this->has_questions( $block['attrs'] ) ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
if ( ! isset( $data['faqs'] ) ) {
|
||||
$data['faqs'] = [
|
||||
'@type' => 'FAQPage',
|
||||
'mainEntity' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$permalink = get_permalink() . '#';
|
||||
foreach ( $block['attrs']['questions'] as $question ) {
|
||||
if ( empty( $question['title'] ) || empty( $question['content'] ) || empty( $question['visible'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$question['title'] = do_shortcode( $question['title'] );
|
||||
$question['content'] = do_shortcode( $question['content'] );
|
||||
|
||||
if ( empty( $question['id'] ) ) {
|
||||
$question['id'] = 'rm-faq-' . md5( $question['title'] );
|
||||
}
|
||||
|
||||
$data['faqs']['mainEntity'][] = [
|
||||
'@type' => 'Question',
|
||||
'url' => $permalink . $question['id'],
|
||||
'name' => wp_strip_all_tags( $question['title'] ),
|
||||
'acceptedAnswer' => [
|
||||
'@type' => 'Answer',
|
||||
'text' => $this->clean_text( $question['content'] ),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render block content.
|
||||
*
|
||||
* @param array $attributes Array of atributes.
|
||||
* @return string
|
||||
*/
|
||||
public static function markup( $attributes = [] ) {
|
||||
$list_tag = self::get()->get_list_style( $attributes['listStyle'] );
|
||||
$item_tag = self::get()->get_list_item_style( $attributes['listStyle'] );
|
||||
$class = 'rank-math-block';
|
||||
if ( ! empty( $attributes['className'] ) ) {
|
||||
$class .= ' ' . esc_attr( $attributes['className'] );
|
||||
}
|
||||
|
||||
// HTML.
|
||||
$out = [];
|
||||
$out[] = sprintf( '<div id="rank-math-faq" class="%1$s"%2$s>', $class, self::get()->get_styles( $attributes ) );
|
||||
$out[] = sprintf( '<%1$s class="rank-math-list %2$s">', $list_tag, esc_attr( $attributes['listCssClasses'] ) );
|
||||
|
||||
// Questions.
|
||||
foreach ( $attributes['questions'] as $question ) {
|
||||
if ( empty( $question['title'] ) || empty( $question['content'] ) || empty( $question['visible'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( empty( $question['id'] ) ) {
|
||||
$question['id'] = 'rm-faq-' . md5( $question['title'] );
|
||||
}
|
||||
|
||||
$out[] = sprintf( '<%1$s id="%2$s" class="rank-math-list-item">', $item_tag, $question['id'] );
|
||||
|
||||
$out[] = sprintf(
|
||||
'<%1$s class="rank-math-question %2$s">%3$s</%1$s>',
|
||||
apply_filters( 'rank_math/blocks/faq/title_wrapper', esc_attr( $attributes['titleWrapper'] ) ),
|
||||
esc_attr( $attributes['titleCssClasses'] ),
|
||||
wp_kses_post( $question['title'] )
|
||||
);
|
||||
|
||||
$out[] = '<div class="rank-math-answer ' . esc_attr( $attributes['contentCssClasses'] ) . '">';
|
||||
|
||||
if ( ! empty( $question['imageUrl'] ) ) {
|
||||
$out[] = '<img src="' . esc_url( $question['imageUrl'] ) . '" />';
|
||||
} else {
|
||||
$out[] = self::get()->get_image( $question, $attributes['sizeSlug'] );
|
||||
}
|
||||
|
||||
$out[] = self::get()->normalize_text( $question['content'], 'faq' );
|
||||
$out[] = '</div>';
|
||||
|
||||
$out[] = sprintf( '</%1$s>', $item_tag );
|
||||
}
|
||||
|
||||
$out[] = sprintf( '</%1$s>', $list_tag );
|
||||
$out[] = '</div>';
|
||||
|
||||
return join( "\n", $out );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render block content
|
||||
*
|
||||
* @param array $attributes Array of atributes.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function render( $attributes ) {
|
||||
// Early bail.
|
||||
if ( ! $this->has_questions( $attributes ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return self::markup( $attributes );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if FAQ block has questions data.
|
||||
*
|
||||
* @param array $attributes Array of attributes.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private function has_questions( $attributes ) {
|
||||
return ! isset( $attributes['questions'] ) || empty( $attributes['questions'] ) ? false : true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,513 @@
|
||||
<?php
|
||||
/**
|
||||
* The HowTo Block
|
||||
*
|
||||
* @since 0.9.0
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Schema;
|
||||
|
||||
use RankMath\Paper\Paper;
|
||||
use RankMath\Helpers\Str;
|
||||
use RankMath\Helpers\Attachment;
|
||||
use WP_Block_Type_Registry;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* HowTo Block class.
|
||||
*/
|
||||
class Block_HowTo extends Block {
|
||||
|
||||
/**
|
||||
* Block type name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $block_type = 'rank-math/howto-block';
|
||||
|
||||
/**
|
||||
* The single instance of the class.
|
||||
*
|
||||
* @var Block_HowTo
|
||||
*/
|
||||
protected static $instance = null;
|
||||
|
||||
/**
|
||||
* Retrieve main Block_HowTo instance.
|
||||
*
|
||||
* Ensure only one instance is loaded or can be loaded.
|
||||
*
|
||||
* @return Block_HowTo
|
||||
*/
|
||||
public static function get() {
|
||||
if ( is_null( self::$instance ) && ! ( self::$instance instanceof Block_HowTo ) ) {
|
||||
self::$instance = new Block_HowTo();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
if ( WP_Block_Type_Registry::get_instance()->is_registered( $this->block_type ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
register_block_type(
|
||||
$this->block_type,
|
||||
[
|
||||
'render_callback' => [ $this, 'render' ],
|
||||
'editor_style' => 'rank-math-block-admin',
|
||||
'attributes' => [
|
||||
'hasDuration' => [
|
||||
'type' => 'boolean',
|
||||
'default' => false,
|
||||
],
|
||||
'days' => [
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
],
|
||||
'hours' => [
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
],
|
||||
'minutes' => [
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
],
|
||||
'description' => [
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
],
|
||||
'steps' => [
|
||||
'type' => 'array',
|
||||
'default' => [],
|
||||
'items' => [ 'type' => 'object' ],
|
||||
],
|
||||
'sizeSlug' => [
|
||||
'type' => 'string',
|
||||
'default' => 'full',
|
||||
],
|
||||
'imageID' => [
|
||||
'type' => 'integer',
|
||||
],
|
||||
'mainSizeSlug' => [
|
||||
'type' => 'string',
|
||||
'default' => 'full',
|
||||
],
|
||||
'listStyle' => [
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
],
|
||||
'timeLabel' => [
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
],
|
||||
'titleWrapper' => [
|
||||
'type' => 'string',
|
||||
'default' => 'h3',
|
||||
],
|
||||
'listCssClasses' => [
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
],
|
||||
'titleCssClasses' => [
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
],
|
||||
'contentCssClasses' => [
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
],
|
||||
'textAlign' => [
|
||||
'type' => 'string',
|
||||
'default' => 'left',
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
add_filter( 'rank_math/schema/block/howto-block', [ $this, 'add_graph' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add HowTO schema data in JSON-LD array..
|
||||
*
|
||||
* @param array $data Array of JSON-LD data.
|
||||
* @param array $block JsonLD Instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_graph( $data, $block ) {
|
||||
// Early bail.
|
||||
if ( ! $this->has_steps( $block['attrs'] ) ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$attrs = $block['attrs'];
|
||||
|
||||
if ( ! isset( $data['howto'] ) ) {
|
||||
$data['howto'] = [
|
||||
'@type' => 'HowTo',
|
||||
'name' => Paper::get()->get_title(),
|
||||
'description' => isset( $attrs['description'] ) ? $this->clean_text( do_shortcode( $attrs['description'] ) ) : '',
|
||||
'totalTime' => '',
|
||||
'step' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$this->add_step_image( $data['howto'], $attrs );
|
||||
$this->add_duration( $data['howto'], $attrs );
|
||||
$permalink = get_permalink() . '#';
|
||||
|
||||
foreach ( $attrs['steps'] as $index => $step ) {
|
||||
if ( empty( $step['visible'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$schema_step = $this->add_step( $step, $permalink . $step['id'] );
|
||||
if ( $schema_step ) {
|
||||
$data['howto']['step'][] = $schema_step;
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render block content.
|
||||
*
|
||||
* @param array $attributes Array of atributes.
|
||||
* @return string
|
||||
*/
|
||||
public static function markup( $attributes = [] ) {
|
||||
$list_style = isset( $attributes['listStyle'] ) ? esc_attr( $attributes['listStyle'] ) : '';
|
||||
$list_css_classes = isset( $attributes['listCssClasses'] ) ? esc_attr( $attributes['listCssClasses'] ) : '';
|
||||
$title_wrapper = isset( $attributes['titleWrapper'] ) ? esc_attr( $attributes['titleWrapper'] ) : 'h2';
|
||||
$title_css_classes = isset( $attributes['titleCssClasses'] ) ? esc_attr( $attributes['titleCssClasses'] ) : '';
|
||||
$content_css_classes = isset( $attributes['contentCssClasses'] ) ? esc_attr( $attributes['contentCssClasses'] ) : '';
|
||||
$size_slug = isset( $attributes['sizeSlug'] ) ? esc_attr( $attributes['sizeSlug'] ) : '';
|
||||
|
||||
$list_tag = self::get()->get_list_style( $list_style );
|
||||
$item_tag = self::get()->get_list_item_style( $list_style );
|
||||
$class = 'rank-math-block';
|
||||
if ( ! empty( $attributes['className'] ) ) {
|
||||
$class .= ' ' . esc_attr( $attributes['className'] );
|
||||
}
|
||||
|
||||
// HTML.
|
||||
$out = [];
|
||||
$out[] = sprintf( '<div id="rank-math-howto" class="%1$s" %2$s>', $class, self::get()->get_styles( $attributes ) );
|
||||
|
||||
// HeaderContent.
|
||||
$out[] = '<div class="rank-math-howto-description">';
|
||||
|
||||
if ( ! empty( $attributes['imageUrl'] ) ) {
|
||||
$out[] = '<img src="' . esc_url( $attributes['imageUrl'] ) . '" />';
|
||||
} elseif ( ! empty( $attributes['mainSizeSlug'] ) ) {
|
||||
$out[] = self::get()->get_image( $attributes, $attributes['mainSizeSlug'], '' );
|
||||
}
|
||||
|
||||
if ( ! empty( $attributes['description'] ) ) {
|
||||
$out[] = self::get()->normalize_text( $attributes['description'], 'howto' );
|
||||
}
|
||||
|
||||
$out[] = '</div>';
|
||||
|
||||
$out[] = self::get()->build_duration( $attributes );
|
||||
|
||||
$out[] = sprintf( '<%1$s class="rank-math-steps %2$s">', $list_tag, $list_css_classes );
|
||||
|
||||
// Steps.
|
||||
foreach ( $attributes['steps'] as $index => $step ) {
|
||||
if ( empty( $step['visible'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$step_id = isset( $step['id'] ) ? esc_attr( $step['id'] ) : '';
|
||||
|
||||
$out[] = sprintf( '<%1$s id="%2$s" class="rank-math-step">', $item_tag, $step_id );
|
||||
|
||||
if ( ! empty( $step['title'] ) ) {
|
||||
$out[] = sprintf(
|
||||
'<%1$s class="rank-math-step-title %2$s">%3$s</%1$s>',
|
||||
$title_wrapper,
|
||||
$title_css_classes,
|
||||
$step['title']
|
||||
);
|
||||
}
|
||||
|
||||
$step_content = ! empty( $step['content'] ) ? self::get()->normalize_text( $step['content'], 'howto' ) : '';
|
||||
$step_image = ! empty( $step['imageUrl'] ) ? '<img src="' . esc_url( $step['imageUrl'] ) . '" />' : self::get()->get_image( $step, $size_slug, '' );
|
||||
|
||||
$out[] = sprintf(
|
||||
'<div class="rank-math-step-content %2$s">%4$s%3$s</div>',
|
||||
$title_wrapper,
|
||||
$content_css_classes,
|
||||
$step_content,
|
||||
$step_image
|
||||
);
|
||||
|
||||
$out[] = sprintf( '</%1$s>', $item_tag );
|
||||
}
|
||||
|
||||
$out[] = sprintf( '</%1$s>', $list_tag );
|
||||
$out[] = '</div>';
|
||||
|
||||
return apply_filters( 'rank_math/schema/block/howto/content', join( "\n", $out ), $out, $attributes );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render block content.
|
||||
*
|
||||
* @param array $attributes Array of atributes.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function render( $attributes ) {
|
||||
// Early bail.
|
||||
if ( ! $this->has_steps( $attributes ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return self::markup( $attributes );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Step
|
||||
*
|
||||
* @param array $step Step.
|
||||
* @param string $permalink Permalink.
|
||||
*/
|
||||
private function add_step( $step, $permalink ) {
|
||||
$name = wp_strip_all_tags( do_shortcode( $step['title'] ) );
|
||||
$text = $this->clean_text( do_shortcode( $step['content'] ) );
|
||||
|
||||
if ( empty( $name ) && empty( $text ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$schema_step = [
|
||||
'@type' => 'HowToStep',
|
||||
'url' => '' . $permalink,
|
||||
];
|
||||
|
||||
if ( empty( $name ) ) {
|
||||
$schema_step['text'] = '';
|
||||
|
||||
if ( empty( $text ) && empty( $schema_step['image'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! empty( $text ) ) {
|
||||
$schema_step['text'] = $text;
|
||||
}
|
||||
} elseif ( empty( $text ) ) {
|
||||
$schema_step['text'] = $name;
|
||||
} else {
|
||||
$schema_step['name'] = $name;
|
||||
if ( ! empty( $text ) ) {
|
||||
$schema_step['itemListElement'] = [
|
||||
[
|
||||
'@type' => 'HowToDirection',
|
||||
'text' => $text,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if ( false === $this->add_step_image( $schema_step, $step ) ) {
|
||||
$this->add_step_image_from_content( $schema_step, $step );
|
||||
}
|
||||
|
||||
return $schema_step;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we have an inline image and add it.
|
||||
*
|
||||
* @param array $schema_step Our Schema output for the Step.
|
||||
* @param array $step The step block data.
|
||||
*/
|
||||
private function add_step_image_from_content( &$schema_step, $step ) {
|
||||
// Early Bail.
|
||||
if ( empty( $step['content'] ) || ! Str::contains( '<img', $step['content'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Search for image.
|
||||
preg_match_all( '/<img.+?src=[\'"]([^\'"]+)[\'"].*?>/i', $step['content'], $matches );
|
||||
|
||||
if ( ! isset( $matches[1][0] ) || empty( $matches[1][0] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$schema_image = [
|
||||
'@type' => 'ImageObject',
|
||||
'url' => $matches[1][0],
|
||||
];
|
||||
|
||||
$image_id = Attachment::get_by_url( $schema_image['url'] );
|
||||
|
||||
if ( $image_id > 0 ) {
|
||||
$this->add_caption( $schema_image, $image_id );
|
||||
$this->add_image_size( $schema_image, $image_id );
|
||||
}
|
||||
|
||||
$schema_step['image'] = $schema_image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we have a step image and add it.
|
||||
*
|
||||
* @copyright Copyright (C) 2008-2019, Yoast BV
|
||||
* The following code is a derivative work of the code from the Yoast(https://github.com/Yoast/wordpress-seo/), which is licensed under GPL v3.
|
||||
*
|
||||
* @param array $schema_step Our Schema output for the Step.
|
||||
* @param array $step The step block data.
|
||||
*/
|
||||
private function add_step_image( &$schema_step, $step ) {
|
||||
if ( empty( $step['imageID'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$image_id = absint( $step['imageID'] );
|
||||
if ( ! ( $image_id > 0 ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$image_url = wp_get_attachment_image_url( $image_id, 'full' );
|
||||
if ( ! $image_url ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$schema_image = [
|
||||
'@type' => 'ImageObject',
|
||||
'url' => $image_url,
|
||||
];
|
||||
|
||||
$this->add_caption( $schema_image, $image_id );
|
||||
$this->add_image_size( $schema_image, $image_id );
|
||||
|
||||
$schema_step['image'] = $schema_image;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add caption to schema.
|
||||
*
|
||||
* @param array $schema_image Our Schema output for the Image.
|
||||
* @param int $image_id The image ID.
|
||||
*/
|
||||
private function add_caption( &$schema_image, $image_id ) {
|
||||
$caption = wp_get_attachment_caption( $image_id );
|
||||
if ( ! empty( $caption ) ) {
|
||||
$schema_image['caption'] = $caption;
|
||||
return;
|
||||
}
|
||||
|
||||
$caption = Attachment::get_alt_tag( $image_id );
|
||||
if ( ! empty( $caption ) ) {
|
||||
$schema_image['caption'] = $caption;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add image size to schema.
|
||||
*
|
||||
* @param array $schema_image Our Schema output for the Image.
|
||||
* @param int $image_id The image ID.
|
||||
*/
|
||||
private function add_image_size( &$schema_image, $image_id ) {
|
||||
$image_meta = wp_get_attachment_metadata( $image_id );
|
||||
if ( empty( $image_meta['width'] ) || empty( $image_meta['height'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$schema_image['width'] = $image_meta['width'];
|
||||
$schema_image['height'] = $image_meta['height'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add duration to schema.
|
||||
*
|
||||
* @param array $data Our Schema output.
|
||||
* @param array $attrs The block attributes.
|
||||
*/
|
||||
private function add_duration( &$data, $attrs ) {
|
||||
if ( ! empty( $attrs['hasDuration'] ) ) {
|
||||
$days = absint( $attrs['days'] ?? 0 );
|
||||
$hours = absint( $attrs['hours'] ?? 0 );
|
||||
$minutes = absint( $attrs['minutes'] ?? 0 );
|
||||
if ( ( $days + $hours + $minutes ) > 0 ) {
|
||||
$data['totalTime'] = esc_attr( 'P' . $days . 'DT' . $hours . 'H' . $minutes . 'M' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate HowTo duration property.
|
||||
*
|
||||
* @param array $attrs The block attributes.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function build_duration( $attrs ) {
|
||||
if ( empty( $attrs['hasDuration'] ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$days = isset( $attrs['days'] ) ? absint( $attrs['days'] ) : 0;
|
||||
$hours = isset( $attrs['hours'] ) ? absint( $attrs['hours'] ) : 0;
|
||||
$minutes = isset( $attrs['minutes'] ) ? absint( $attrs['minutes'] ) : 0;
|
||||
|
||||
$elements = [];
|
||||
if ( $days > 0 ) {
|
||||
/* translators: %d is the number of days. */
|
||||
$elements[] = sprintf( _n( '%d day', '%d days', $days, 'rank-math' ), $days );
|
||||
}
|
||||
|
||||
if ( $hours > 0 ) {
|
||||
/* translators: %d is the number of hours. */
|
||||
$elements[] = sprintf( _n( '%d hour', '%d hours', $hours, 'rank-math' ), $hours );
|
||||
}
|
||||
|
||||
if ( $minutes > 0 ) {
|
||||
/* translators: %d is the number of minutes. */
|
||||
$elements[] = sprintf( _n( '%d minute', '%d minutes', $minutes, 'rank-math' ), $minutes );
|
||||
}
|
||||
|
||||
$count = count( $elements );
|
||||
$formats = [
|
||||
1 => '%1$s',
|
||||
/* translators: placeholders are units of time, e.g. '1 hour and 30 minutes' */
|
||||
2 => __( '%1$s and %2$s', 'rank-math' ),
|
||||
/* translators: placeholders are units of time, e.g. '1 day, 8 hours and 30 minutes' */
|
||||
3 => __( '%1$s, %2$s and %3$s', 'rank-math' ),
|
||||
];
|
||||
|
||||
return sprintf(
|
||||
'<p class="rank-math-howto-duration"><strong>%2$s</strong> <span>%1$s</span></p>',
|
||||
isset( $formats[ $count ] ) ? vsprintf( $formats[ $count ], $elements ) : '',
|
||||
empty( $attrs['timeLabel'] ) ? __( 'Total Time:', 'rank-math' ) : esc_html( $attrs['timeLabel'] )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to check the HowTo block have steps data.
|
||||
*
|
||||
* @param array $attributes Array of attributes.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private function has_steps( $attributes ) {
|
||||
return ! isset( $attributes['steps'] ) || empty( $attributes['steps'] ) ? false : true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
/**
|
||||
* The Block Parser
|
||||
*
|
||||
* @since 0.9.0
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Schema;
|
||||
|
||||
use RankMath\Helper;
|
||||
use RankMath\Helpers\Str;
|
||||
use RankMath\Traits\Hooker;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Block_Parser class.
|
||||
*/
|
||||
class Block_Parser {
|
||||
|
||||
use Hooker;
|
||||
|
||||
/**
|
||||
* Holds the parsed blocks.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $blocks = [];
|
||||
|
||||
/**
|
||||
* The Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->action( 'rank_math/json_ld', 'parse', 8 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter function to add Blocks data in schema.
|
||||
*
|
||||
* @param array $data Array of JSON-LD data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function parse( $data ) {
|
||||
if ( ! function_exists( 'parse_blocks' ) || ! is_singular() ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$this->get_parsed_blocks();
|
||||
foreach ( $this->blocks as $block_type => $blocks ) {
|
||||
foreach ( $blocks as $block ) {
|
||||
/**
|
||||
* Filter: 'rank_math/schema/block/<block-type>' - Allows filtering graph output per block.
|
||||
*
|
||||
* @param array $data Array of JSON-LD data.
|
||||
* @param array $block The block.
|
||||
*/
|
||||
$data = $this->do_filter( 'schema/block/' . $block_type, $data, $block );
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the blocks and loop through them.
|
||||
*/
|
||||
private function get_parsed_blocks() {
|
||||
$post = get_post();
|
||||
$parsed_blocks = parse_blocks( $post->post_content );
|
||||
|
||||
/**
|
||||
* Filter: 'rank_math/schema/block/before_filter'
|
||||
*
|
||||
* @param array $parsed_blocks Array of parsed blocks.
|
||||
*/
|
||||
$parsed_blocks = $this->do_filter( 'schema/block/before_filter', $parsed_blocks );
|
||||
|
||||
$this->filter_blocks( $parsed_blocks );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter blocks.
|
||||
*
|
||||
* @param array $blocks Blocks to filter.
|
||||
*/
|
||||
private function filter_blocks( $blocks ) {
|
||||
foreach ( $blocks as $block ) {
|
||||
if ( $this->is_nested_block( $block ) || ! $this->is_valid_block( $block ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = \str_replace( 'rank-math/', '', $block['blockName'] );
|
||||
$name = strtolower( $name );
|
||||
if ( ! isset( $this->blocks[ $name ] ) || ! is_array( $this->blocks[ $name ] ) ) {
|
||||
$this->blocks[ $name ] = [];
|
||||
}
|
||||
|
||||
$this->blocks[ $name ][] = $block;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is nested block.
|
||||
*
|
||||
* @param array $block Block.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private function is_nested_block( $block ) {
|
||||
if ( empty( $block['blockName'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter: 'rank_math/schema/nested_blocks' - Allows filtering for nested blocks.
|
||||
*
|
||||
* @param array $data Array of JSON-LD data.
|
||||
* @param array $block The block.
|
||||
*/
|
||||
$nested = $this->do_filter(
|
||||
'schema/nested_blocks',
|
||||
[
|
||||
'core/group',
|
||||
'core/columns',
|
||||
'core/column',
|
||||
]
|
||||
);
|
||||
|
||||
if ( ! in_array( $block['blockName'], $nested, true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->filter_blocks( $block['innerBlocks'] );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is block valid.
|
||||
*
|
||||
* @param array $block Block.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private function is_valid_block( $block ) {
|
||||
return ! empty( $block['blockName'] ) && Str::starts_with( 'rank-math', $block['blockName'] );
|
||||
}
|
||||
}
|
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
/**
|
||||
* The Block Base
|
||||
*
|
||||
* @since 0.9.0
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Schema;
|
||||
|
||||
use RankMath\Helper;
|
||||
use RankMath\Traits\Hooker;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Block class.
|
||||
*/
|
||||
class Block {
|
||||
|
||||
/**
|
||||
* Function to certain tags from the text.
|
||||
*
|
||||
* @param string $text Block content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function clean_text( $text ) {
|
||||
return strip_tags( $text, '<h1><h2><h3><h4><h5><h6><br><ol><ul><li><a><p><b><strong><i><em>' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to get the block image.
|
||||
*
|
||||
* @param array $attrs Block attributes data.
|
||||
* @param string $size Image size.
|
||||
* @param string $class Attachment image class.
|
||||
*
|
||||
* @return string The HTML image element.
|
||||
*/
|
||||
protected function get_image( $attrs, $size = 'thumbnail', $class = 'class=alignright' ) {
|
||||
$image_id = empty( $attrs['imageID'] ) ? '' : absint( $attrs['imageID'] );
|
||||
if ( ! $image_id ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$html = wp_get_attachment_image( $image_id, $size, false, $class );
|
||||
|
||||
return $html ? $html : wp_get_attachment_image( $image_id, 'full', false, $class );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get styles
|
||||
*
|
||||
* @param array $attributes Array of attributes.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_styles( $attributes ) {
|
||||
return empty( $attributes['textAlign'] ) || 'left' === $attributes['textAlign']
|
||||
? ''
|
||||
: ' style="' . join( ';', [ 'text-align:' . $attributes['textAlign'] ] ) . '"';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list style
|
||||
*
|
||||
* @param string $style Style.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_list_style( $style ) {
|
||||
if ( 'numbered' === $style ) {
|
||||
return 'ol';
|
||||
}
|
||||
|
||||
return 'unordered' === $style ? 'ul' : 'div';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list item style
|
||||
*
|
||||
* @param string $style Style.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_list_item_style( $style ) {
|
||||
return in_array( $style, [ 'numbered', 'unordered' ], true ) ? 'li' : 'div';
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize the block text.
|
||||
*
|
||||
* @param string $text Text.
|
||||
* @param string $block Block name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function normalize_text( $text, $block ) {
|
||||
/**
|
||||
* Filter: Allow developers to preserve line breaks.
|
||||
*
|
||||
* @param bool $return If set, this will convert all remaining line breaks after paragraphing.
|
||||
* @param string $block Block name.
|
||||
*/
|
||||
return wpautop( $text, apply_filters( 'rank_math/block/preserve_line_breaks', true, $block ) );
|
||||
}
|
||||
}
|
@@ -0,0 +1 @@
|
||||
.wp-block-rank-math-toc-block nav li,.wp-block-rank-math-toc-block nav div{position:relative;min-height:28px;margin-bottom:0}.wp-block-rank-math-toc-block nav li.disabled,.wp-block-rank-math-toc-block nav div.disabled{display:block !important;opacity:0.5}.wp-block-rank-math-toc-block nav li .components-base-control,.wp-block-rank-math-toc-block nav div .components-base-control{position:absolute;top:2px;left:-4px;right:-3px}.wp-block-rank-math-toc-block nav li .rank-math-block-actions,.wp-block-rank-math-toc-block nav div .rank-math-block-actions{position:absolute;top:1px;right:0;display:none;line-height:1}.wp-block-rank-math-toc-block nav li .rank-math-block-actions button.components-button,.wp-block-rank-math-toc-block nav div .rank-math-block-actions button.components-button{min-width:24px;width:24px;height:24px;line-height:34px}.wp-block-rank-math-toc-block nav li:hover .rank-math-block-actions,.wp-block-rank-math-toc-block nav li:focus .rank-math-block-actions,.wp-block-rank-math-toc-block nav li .components-base-control+.rank-math-block-actions .rank-math-block-actions,.wp-block-rank-math-toc-block nav div:hover .rank-math-block-actions,.wp-block-rank-math-toc-block nav div:focus .rank-math-block-actions,.wp-block-rank-math-toc-block nav div .components-base-control+.rank-math-block-actions .rank-math-block-actions{display:block}.rank-math-toc-exclude-headings{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.rank-math-toc-exclude-headings>div{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;margin-bottom:10px !important}
|
@@ -0,0 +1 @@
|
||||
.wp-block-rank-math-toc-block nav ol{counter-reset:item}.wp-block-rank-math-toc-block nav ol li{display:block}.wp-block-rank-math-toc-block nav ol li:before{content:counters(item, ".") ". ";counter-increment:item}
|
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* Block script dependencies.
|
||||
*
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
return [
|
||||
'dependencies' => [
|
||||
'wp-blocks',
|
||||
'wp-element',
|
||||
'wp-components',
|
||||
'wp-block-editor',
|
||||
'wp-data',
|
||||
'wp-dom',
|
||||
'wp-url',
|
||||
'wp-i18n',
|
||||
'lodash',
|
||||
'wp-primitives',
|
||||
'wp-reusable-blocks',
|
||||
],
|
||||
'version' => rank_math()->version,
|
||||
];
|
File diff suppressed because one or more lines are too long
@@ -0,0 +1,52 @@
|
||||
.wp-block-rank-math-toc-block {
|
||||
nav {
|
||||
li, div {
|
||||
position: relative;
|
||||
min-height: 28px;
|
||||
margin-bottom: 0;
|
||||
|
||||
&.disabled {
|
||||
display: block !important;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.components-base-control {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: -4px;
|
||||
right: -3px;
|
||||
}
|
||||
|
||||
.rank-math-block-actions {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
right: 0;
|
||||
display: none;
|
||||
line-height: 1;
|
||||
|
||||
button.components-button {
|
||||
min-width: 24px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
line-height: 34px;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover, &:focus, .components-base-control + .rank-math-block-actions {
|
||||
.rank-math-block-actions {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rank-math-toc-exclude-headings {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
> div {
|
||||
flex: 0 0 50%;
|
||||
margin-bottom: 10px!important;
|
||||
}
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
.wp-block-rank-math-toc-block {
|
||||
nav {
|
||||
ol {
|
||||
counter-reset: item;
|
||||
|
||||
li {
|
||||
display: block;
|
||||
}
|
||||
|
||||
li:before {
|
||||
content: counters(item, ".") ". ";
|
||||
counter-increment: item;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,55 @@
|
||||
import { linearToNestedHeadingList } from './utils'
|
||||
import { useBlockProps } from '@wordpress/block-editor'
|
||||
import List from './list'
|
||||
|
||||
const attributes = {
|
||||
title: {
|
||||
type: 'text',
|
||||
},
|
||||
headings: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
listStyle: {
|
||||
type: 'text',
|
||||
},
|
||||
titleWrapper: {
|
||||
type: 'text',
|
||||
default: 'h2',
|
||||
},
|
||||
excludeHeadings: {
|
||||
type: 'array',
|
||||
},
|
||||
}
|
||||
|
||||
const v1 = {
|
||||
attributes,
|
||||
save( { attributes } ) {
|
||||
if ( attributes.headings.length === 0 ) {
|
||||
return null
|
||||
}
|
||||
|
||||
const TitleWrapper = attributes.titleWrapper
|
||||
const headings = linearToNestedHeadingList( attributes.headings )
|
||||
const ListStyle = attributes.listStyle
|
||||
|
||||
return (
|
||||
<div { ...useBlockProps.save() }>
|
||||
{ attributes.title && <TitleWrapper dangerouslySetInnerHTML={ { __html: attributes.title } }></TitleWrapper> }
|
||||
<nav>
|
||||
<ListStyle>
|
||||
<List
|
||||
headings={ headings }
|
||||
ListStyle={ ListStyle }
|
||||
isSave={ true }
|
||||
/>
|
||||
</ListStyle>
|
||||
</nav>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
export default [ v1 ]
|
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { isUndefined, map, includes, remove } from 'lodash'
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n'
|
||||
import {
|
||||
useBlockProps,
|
||||
RichText,
|
||||
store as blockEditorStore,
|
||||
} from '@wordpress/block-editor'
|
||||
import { useDispatch } from '@wordpress/data'
|
||||
import { Placeholder } from '@wordpress/components'
|
||||
import { useEffect, useState } from '@wordpress/element'
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { GetLatestHeadings, linearToNestedHeadingList } from './utils'
|
||||
import List from './list'
|
||||
import InspectControls from './inspectControls'
|
||||
import Toolbar from './toolbar'
|
||||
|
||||
export default ( {
|
||||
attributes,
|
||||
setAttributes,
|
||||
} ) => {
|
||||
const blockProps = useBlockProps()
|
||||
|
||||
// State to monitor edit heading links.
|
||||
const [ edit, toggleEdit ] = useState( false )
|
||||
const [ excludeHeading, toggleExcludeHeading ] = useState( {} )
|
||||
if ( ! attributes.listStyle ) {
|
||||
setAttributes( { listStyle: rankMath.listStyle } )
|
||||
}
|
||||
|
||||
const ListStyle = attributes.listStyle
|
||||
const tocTitle = attributes.title ?? rankMath.tocTitle
|
||||
const excludeHeadings = ! isUndefined( attributes.excludeHeadings ) ? attributes.excludeHeadings : rankMath.tocExcludeHeadings
|
||||
|
||||
// Function to hide certain heading.
|
||||
const hideHeading = ( value, key ) => {
|
||||
const headings = map( attributes.headings, ( heading ) => {
|
||||
if ( heading.key === key ) {
|
||||
heading.disable = value
|
||||
}
|
||||
|
||||
return heading
|
||||
} )
|
||||
|
||||
setAttributes( { headings } )
|
||||
}
|
||||
|
||||
// Function to update Heading link.
|
||||
const onHeadingUpdate = ( value, key, isContent = false ) => {
|
||||
const headings = map( attributes.headings, ( heading ) => {
|
||||
if ( heading.key === key ) {
|
||||
if ( isContent ) {
|
||||
heading.content = value
|
||||
heading.isUpdated = true
|
||||
} else {
|
||||
heading.isGeneratedLink = false
|
||||
heading.link = value
|
||||
}
|
||||
}
|
||||
|
||||
return heading
|
||||
} )
|
||||
|
||||
setAttributes( { headings } )
|
||||
}
|
||||
|
||||
const setExcludeHeadings = ( headingLevel ) => {
|
||||
if ( includes( excludeHeadings, headingLevel ) ) {
|
||||
remove( excludeHeadings, ( heading ) => {
|
||||
return heading === headingLevel
|
||||
} )
|
||||
} else {
|
||||
excludeHeadings.push( headingLevel )
|
||||
}
|
||||
setAttributes( { excludeHeadings } )
|
||||
toggleExcludeHeading( ! excludeHeading )
|
||||
}
|
||||
|
||||
const { __unstableMarkNextChangeAsNotPersistent } = useDispatch( blockEditorStore )
|
||||
|
||||
// Get Latest headings from the content.
|
||||
const latestHeadings = GetLatestHeadings( attributes.headings, excludeHeadings )
|
||||
useEffect( () => {
|
||||
if ( latestHeadings !== null ) {
|
||||
__unstableMarkNextChangeAsNotPersistent();
|
||||
setAttributes( { headings: latestHeadings } )
|
||||
}
|
||||
}, [ latestHeadings ] )
|
||||
|
||||
const headingTree = linearToNestedHeadingList( attributes.headings )
|
||||
if ( isUndefined( attributes.headings ) || attributes.headings.length === 0 ) {
|
||||
return (
|
||||
<div { ...blockProps }>
|
||||
<Placeholder
|
||||
label={ __( 'Table of Contents', 'rank-math' ) }
|
||||
instructions={ __( 'Add Heading blocks to this page to generate the Table of Contents.', 'rank-math' ) }
|
||||
/>
|
||||
<InspectControls attributes={ attributes } setAttributes={ setAttributes } excludeHeadings={ excludeHeadings } setExcludeHeadings={ setExcludeHeadings } />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div { ...blockProps }>
|
||||
<RichText
|
||||
tagName={ attributes.titleWrapper }
|
||||
value={ tocTitle }
|
||||
onChange={ ( newTitle ) => {
|
||||
setAttributes( { title: newTitle } )
|
||||
} }
|
||||
placeholder={ __( 'Enter a title', 'rank-math' ) }
|
||||
/>
|
||||
<nav>
|
||||
<ListStyle>
|
||||
<List
|
||||
headings={ headingTree }
|
||||
onHeadingUpdate={ onHeadingUpdate }
|
||||
edit={ edit }
|
||||
toggleEdit={ toggleEdit }
|
||||
hideHeading={ hideHeading }
|
||||
ListStyle={ ListStyle }
|
||||
/>
|
||||
</ListStyle>
|
||||
</nav>
|
||||
<Toolbar setAttributes={ setAttributes } />
|
||||
<InspectControls attributes={ attributes } setAttributes={ setAttributes } excludeHeadings={ excludeHeadings } setExcludeHeadings={ setExcludeHeadings } />
|
||||
</div>
|
||||
)
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { registerBlockType } from '@wordpress/blocks'
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import edit from './edit'
|
||||
import save from './save'
|
||||
import deprecated from './deprecated'
|
||||
|
||||
/**
|
||||
* Register TOC block
|
||||
*/
|
||||
registerBlockType(
|
||||
'rank-math/toc-block',
|
||||
{
|
||||
edit,
|
||||
save,
|
||||
deprecated,
|
||||
}
|
||||
)
|
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { map, includes, toUpper } from 'lodash'
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n'
|
||||
import { InspectorControls } from '@wordpress/block-editor'
|
||||
import {
|
||||
PanelBody,
|
||||
SelectControl,
|
||||
CheckboxControl,
|
||||
} from '@wordpress/components'
|
||||
|
||||
export default ( { attributes, setAttributes, excludeHeadings, setExcludeHeadings } ) => {
|
||||
return (
|
||||
<InspectorControls>
|
||||
<PanelBody title={ __( 'Settings', 'rank-math' ) }>
|
||||
|
||||
<SelectControl
|
||||
label={ __( 'Title Wrapper', 'rank-math' ) }
|
||||
value={ attributes.titleWrapper }
|
||||
options={ [
|
||||
{ value: 'h2', label: __( 'H2', 'rank-math' ) },
|
||||
{ value: 'h3', label: __( 'H3', 'rank-math' ) },
|
||||
{ value: 'h4', label: __( 'H4', 'rank-math' ) },
|
||||
{ value: 'h5', label: __( 'H5', 'rank-math' ) },
|
||||
{ value: 'h6', label: __( 'H6', 'rank-math' ) },
|
||||
{ value: 'p', label: __( 'P', 'rank-math' ) },
|
||||
{ value: 'div', label: __( 'DIV', 'rank-math' ) },
|
||||
] }
|
||||
onChange={ ( titleWrapper ) => {
|
||||
setAttributes( { titleWrapper } )
|
||||
} }
|
||||
/>
|
||||
|
||||
<br />
|
||||
<h3>{ __( 'Exclude Headings', 'rank-math' ) }</h3>
|
||||
<div className="rank-math-toc-exclude-headings">
|
||||
{
|
||||
map( [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ], ( value ) => {
|
||||
return (
|
||||
<CheckboxControl
|
||||
key={ value }
|
||||
label={ __( 'Heading ', 'rank-math' ) + toUpper( value ) }
|
||||
checked={ includes( excludeHeadings, value ) }
|
||||
onChange={ ( newVlaue ) => setExcludeHeadings( value, newVlaue ) }
|
||||
/>
|
||||
)
|
||||
} )
|
||||
}
|
||||
</div>
|
||||
</PanelBody>
|
||||
</InspectorControls>
|
||||
)
|
||||
}
|
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { isEmpty } from 'lodash'
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n'
|
||||
import { Button, TextControl } from '@wordpress/components'
|
||||
import { RichText } from '@wordpress/block-editor'
|
||||
|
||||
export default function List( { headings = {}, onHeadingUpdate = {}, edit = {}, toggleEdit = {}, hideHeading = {}, ListStyle = 'ul', isSave = false } ) {
|
||||
if ( isEmpty( headings ) ) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{ headings.map( ( heading ) => {
|
||||
if ( isSave && heading.heading.disable ) {
|
||||
return false
|
||||
}
|
||||
|
||||
const { content, link, disable, key } = heading.heading
|
||||
const TagName = 'div' === ListStyle ? 'div' : 'li'
|
||||
return (
|
||||
<TagName key={ key } className={ disable ? 'disabled' : '' }>
|
||||
{
|
||||
isSave &&
|
||||
<a href={ link }>
|
||||
{ content }
|
||||
</a>
|
||||
}
|
||||
{
|
||||
! isSave &&
|
||||
<RichText
|
||||
tagName="a"
|
||||
value={ content }
|
||||
allowedFormats={ [] }
|
||||
onChange={ ( newContent ) => onHeadingUpdate( newContent, key, true ) }
|
||||
placeholder={ __( 'Heading text', 'rank-math' ) }
|
||||
/>
|
||||
}
|
||||
{
|
||||
heading.children &&
|
||||
<ListStyle>
|
||||
<List
|
||||
headings={ heading.children }
|
||||
onHeadingUpdate={ onHeadingUpdate }
|
||||
edit={ edit }
|
||||
toggleEdit={ toggleEdit }
|
||||
hideHeading={ hideHeading }
|
||||
ListStyle={ ListStyle }
|
||||
isSave={ isSave }
|
||||
/>
|
||||
</ListStyle>
|
||||
}
|
||||
{
|
||||
key === edit &&
|
||||
<TextControl
|
||||
placeholder={ __( 'Heading Link', 'rank-math' ) }
|
||||
value={ link }
|
||||
onChange={ ( newLink ) => onHeadingUpdate( newLink, key ) }
|
||||
/>
|
||||
}
|
||||
{
|
||||
! isSave &&
|
||||
<span className="rank-math-block-actions">
|
||||
<Button
|
||||
icon={ edit === key ? 'saved' : 'admin-links' }
|
||||
className="rank-math-item-visbility"
|
||||
onClick={ () => toggleEdit( edit === key ? false : key ) }
|
||||
title={ __( 'Edit Link', 'rank-math' ) }
|
||||
/>
|
||||
|
||||
<Button
|
||||
className="rank-math-item-delete"
|
||||
icon={ ! disable ? 'visibility' : 'hidden' }
|
||||
onClick={ () => hideHeading( ! disable, key ) }
|
||||
title={ __( 'Hide', 'rank-math' ) }
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
</TagName>
|
||||
)
|
||||
} ) }
|
||||
</>
|
||||
)
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { isUndefined } from 'lodash'
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { useBlockProps } from '@wordpress/block-editor'
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { linearToNestedHeadingList } from './utils'
|
||||
import List from './list'
|
||||
|
||||
export default function save( { attributes } ) {
|
||||
if ( isUndefined( attributes.headings ) || attributes.headings.length === 0 ) {
|
||||
return null
|
||||
}
|
||||
|
||||
const TitleWrapper = attributes.titleWrapper
|
||||
const headings = linearToNestedHeadingList( attributes.headings )
|
||||
const ListStyle = attributes.listStyle
|
||||
|
||||
return (
|
||||
<div { ...useBlockProps.save() } id="rank-math-toc">
|
||||
{ attributes.title && <TitleWrapper dangerouslySetInnerHTML={ { __html: attributes.title } }></TitleWrapper> }
|
||||
<nav>
|
||||
<ListStyle>
|
||||
<List
|
||||
headings={ headings }
|
||||
ListStyle={ ListStyle }
|
||||
isSave={ true }
|
||||
/>
|
||||
</ListStyle>
|
||||
</nav>
|
||||
</div>
|
||||
)
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n'
|
||||
import { BlockControls } from '@wordpress/block-editor'
|
||||
import {
|
||||
Toolbar,
|
||||
ToolbarButton,
|
||||
} from '@wordpress/components'
|
||||
import { formatListBullets, formatListNumbered, alignLeft } from '@wordpress/icons'
|
||||
|
||||
export default ( { setAttributes } ) => {
|
||||
return (
|
||||
<BlockControls>
|
||||
<Toolbar label={ __( 'Table of Content Options', 'rank-math' ) }>
|
||||
<ToolbarButton
|
||||
icon={ formatListBullets }
|
||||
label={ __( 'Unordered List', 'rank-math' ) }
|
||||
onClick={ () => setAttributes( { listStyle: 'ul' } ) }
|
||||
/>
|
||||
<ToolbarButton
|
||||
icon={ formatListNumbered }
|
||||
label={ __( 'Ordered List', 'rank-math' ) }
|
||||
onClick={ () => setAttributes( { listStyle: 'ol' } ) }
|
||||
/>
|
||||
<ToolbarButton
|
||||
icon={ alignLeft }
|
||||
label={ __( 'None', 'rank-math' ) }
|
||||
onClick={ () => setAttributes( { listStyle: 'div' } ) }
|
||||
/>
|
||||
</Toolbar>
|
||||
</BlockControls>
|
||||
)
|
||||
}
|
@@ -0,0 +1,196 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { isEmpty, isUndefined, isString, kebabCase, includes, forEach, isEqual, map, isNull } from 'lodash'
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { store as blockEditorStore } from '@wordpress/block-editor'
|
||||
import { __unstableStripHTML as stripHTML } from '@wordpress/dom'
|
||||
import { useSelect, useDispatch } from '@wordpress/data'
|
||||
|
||||
/**
|
||||
* Get the headings from the content.
|
||||
*
|
||||
* @param {Array} headings Array of headings data
|
||||
* @param {Array} excludeHeadings Heading levels to exclude
|
||||
*/
|
||||
export function GetLatestHeadings( headings, excludeHeadings ) {
|
||||
return useSelect(
|
||||
( select ) => {
|
||||
const {
|
||||
getBlockAttributes,
|
||||
getBlockName,
|
||||
getClientIdsWithDescendants,
|
||||
} = select( blockEditorStore )
|
||||
const { __experimentalConvertBlockToStatic: convertBlockToStatic } = useDispatch( 'core/reusable-blocks' )
|
||||
|
||||
// Get the client ids of all blocks in the editor.
|
||||
const allBlockClientIds = getClientIdsWithDescendants()
|
||||
const _latestHeadings = []
|
||||
let i = 0
|
||||
const anchors = []
|
||||
for ( const blockClientId of allBlockClientIds ) {
|
||||
const blockName = getBlockName( blockClientId )
|
||||
if ( blockName === 'core/block' ) {
|
||||
const attrs = getBlockAttributes( blockClientId )
|
||||
if ( ! isNull( attrs.ref ) ) {
|
||||
const reusableBlock = wp.data.select( 'core' ).getEditedEntityRecord( 'postType', 'wp_block', attrs.ref )
|
||||
const blocks = map( reusableBlock.blocks, ( block ) => {
|
||||
return block.name
|
||||
} )
|
||||
|
||||
if ( includes( blocks, 'rank-math/toc-block' ) && ! isNull( getBlockAttributes( blockClientId ) ) ) {
|
||||
convertBlockToStatic( blockClientId )
|
||||
}
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if ( ! includes( [ 'rank-math/faq-block', 'rank-math/howto-block', 'core/heading' ], blockName ) ) {
|
||||
continue
|
||||
}
|
||||
|
||||
const headingAttributes = getBlockAttributes( blockClientId )
|
||||
if ( blockName === 'rank-math/faq-block' || blockName === 'rank-math/howto-block' ) {
|
||||
const titleWrapper = headingAttributes.titleWrapper
|
||||
if (
|
||||
includes( excludeHeadings, titleWrapper ) ||
|
||||
includes( [ 'div', 'p' ], titleWrapper )
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
const data = blockName === 'rank-math/howto-block' ? headingAttributes.steps : headingAttributes.questions
|
||||
if ( isEmpty( data ) ) {
|
||||
continue
|
||||
}
|
||||
|
||||
forEach( data, ( value ) => {
|
||||
const currentHeading = ! isUndefined( headings ) && ! isEmpty( headings[ _latestHeadings.length ] ) ? headings[ _latestHeadings.length ] : {
|
||||
content: '',
|
||||
level: '',
|
||||
disable: false,
|
||||
isUpdated: false,
|
||||
isGeneratedLink: true,
|
||||
}
|
||||
|
||||
const isGeneratedLink = ! isUndefined( currentHeading.isGeneratedLink ) && currentHeading.isGeneratedLink
|
||||
const content = ! isUndefined( currentHeading.isUpdated ) && currentHeading.isUpdated ? currentHeading.content : value.title
|
||||
|
||||
_latestHeadings.push( {
|
||||
key: value.id,
|
||||
content: stripHTML( content ),
|
||||
level: parseInt( headingAttributes.titleWrapper.replace( 'h', '' ) ),
|
||||
link: ! isGeneratedLink ? currentHeading.link : `#${ value.id }`,
|
||||
disable: currentHeading.disable ? currentHeading.disable : false,
|
||||
isUpdated: ! isUndefined( currentHeading.isUpdated ) ? currentHeading.isUpdated : false,
|
||||
isGeneratedLink,
|
||||
} )
|
||||
} )
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if ( blockName === 'core/heading' ) {
|
||||
if ( includes( excludeHeadings, 'h' + headingAttributes.level ) ) {
|
||||
continue
|
||||
}
|
||||
|
||||
const currentHeading = ! isUndefined( headings ) && ! isEmpty( headings[ _latestHeadings.length ] ) ? headings[ _latestHeadings.length ] : {
|
||||
content: '',
|
||||
level: '',
|
||||
disable: false,
|
||||
isUpdated: false,
|
||||
isGeneratedLink: true,
|
||||
}
|
||||
|
||||
const isGeneratedLink = ! isUndefined( currentHeading.isGeneratedLink ) && currentHeading.isGeneratedLink
|
||||
|
||||
let anchor = headingAttributes.anchor
|
||||
const headingText = ! isEmpty( headingAttributes.content.text ) ? headingAttributes.content.text : headingAttributes.content
|
||||
if ( isEmpty( headingAttributes.anchor ) || isGeneratedLink ) {
|
||||
anchor = kebabCase( stripHTML( headingText ) )
|
||||
}
|
||||
|
||||
if ( includes( anchors, anchor ) ) {
|
||||
i += 1
|
||||
anchor = anchor + '-' + i
|
||||
}
|
||||
|
||||
anchors.push( anchor )
|
||||
headingAttributes.anchor = anchor
|
||||
const headingContent = isString( headingText ) ? stripHTML(
|
||||
headingText.replace(
|
||||
/(<br *\/?>)+/g,
|
||||
' '
|
||||
)
|
||||
) : ''
|
||||
|
||||
const content = ! isUndefined( currentHeading.isUpdated ) && currentHeading.isUpdated ? currentHeading.content : headingContent
|
||||
|
||||
_latestHeadings.push( {
|
||||
key: blockClientId,
|
||||
content: stripHTML( content ),
|
||||
level: headingAttributes.level,
|
||||
link: ! isGeneratedLink ? currentHeading.link : `#${ headingAttributes.anchor }`,
|
||||
disable: currentHeading.disable ? currentHeading.disable : false,
|
||||
isUpdated: ! isUndefined( currentHeading.isUpdated ) ? currentHeading.isUpdated : false,
|
||||
isGeneratedLink,
|
||||
} )
|
||||
}
|
||||
}
|
||||
|
||||
if ( isEqual( headings, _latestHeadings ) ) {
|
||||
return null
|
||||
}
|
||||
|
||||
return _latestHeadings
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Nest heading based on the Heading level.
|
||||
*
|
||||
* @param {Array} headingList The flat list of headings to nest.
|
||||
*
|
||||
* @return {Array} The nested list of headings.
|
||||
*/
|
||||
export function linearToNestedHeadingList( headingList = [] ) {
|
||||
const nestedHeadingList = []
|
||||
forEach( headingList, ( heading, key ) => {
|
||||
if ( isEmpty( heading.content ) ) {
|
||||
return
|
||||
}
|
||||
|
||||
// Make sure we are only working with the same level as the first iteration in our set.
|
||||
if ( heading.level === headingList[ 0 ].level ) {
|
||||
if ( headingList[ key + 1 ]?.level > heading.level ) {
|
||||
let endOfSlice = headingList.length
|
||||
for ( let i = key + 1; i < headingList.length; i++ ) {
|
||||
if ( headingList[ i ].level === heading.level ) {
|
||||
endOfSlice = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
nestedHeadingList.push( {
|
||||
heading,
|
||||
children: linearToNestedHeadingList(
|
||||
headingList.slice( key + 1, endOfSlice )
|
||||
),
|
||||
} )
|
||||
} else {
|
||||
nestedHeadingList.push( {
|
||||
heading,
|
||||
children: null,
|
||||
} )
|
||||
}
|
||||
}
|
||||
} )
|
||||
|
||||
return nestedHeadingList
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"apiVersion": 2,
|
||||
"title": "Table of Contents by Rank Math",
|
||||
"description": "Automatically generate the Table of Contents from the Headings added to this page.",
|
||||
"name": "rank-math/toc-block",
|
||||
"category": "rank-math-blocks",
|
||||
"icon": "rm-icon rm-icon-stories",
|
||||
"textdomain": "rank-math",
|
||||
"editorScript": "file:./assets/js/index.js",
|
||||
"editorStyle": "file:./assets/css/toc.css",
|
||||
"style": "file:./assets/css/toc_list_style.css",
|
||||
"attributes": {
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"headings": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"listStyle": {
|
||||
"type": "string"
|
||||
},
|
||||
"titleWrapper": {
|
||||
"type": "string",
|
||||
"default": "h2"
|
||||
},
|
||||
"excludeHeadings": {
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"supports": {
|
||||
"color": {
|
||||
"link": true,
|
||||
"gradients": true
|
||||
},
|
||||
"multiple": false,
|
||||
"spacing": {
|
||||
"margin": true,
|
||||
"padding": true
|
||||
},
|
||||
"typography": {
|
||||
"fontSize": true,
|
||||
"lineHeight": true,
|
||||
"__experimentalDefaultControls": {
|
||||
"fontSize": true
|
||||
}
|
||||
},
|
||||
"align": true
|
||||
}
|
||||
}
|
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
/**
|
||||
* The TOC Block
|
||||
*
|
||||
* @since 1.0.104
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
* @author Rank Math <support@rankmath.com>
|
||||
*/
|
||||
|
||||
namespace RankMath\Schema;
|
||||
|
||||
use WP_Block_Type_Registry;
|
||||
use RankMath\Helper;
|
||||
use RankMath\Traits\Hooker;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* HowTo Block class.
|
||||
*/
|
||||
class Block_TOC extends Block {
|
||||
|
||||
use Hooker;
|
||||
|
||||
/**
|
||||
* Block type name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $block_type = 'rank-math/toc-block';
|
||||
|
||||
/**
|
||||
* The single instance of the class.
|
||||
*
|
||||
* @var Block_TOC
|
||||
*/
|
||||
protected static $instance = null;
|
||||
|
||||
/**
|
||||
* Retrieve main Block_TOC instance.
|
||||
*
|
||||
* Ensure only one instance is loaded or can be loaded.
|
||||
*
|
||||
* @return Block_TOC
|
||||
*/
|
||||
public static function get() {
|
||||
if ( is_null( self::$instance ) && ! ( self::$instance instanceof Block_TOC ) ) {
|
||||
self::$instance = new Block_TOC();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
if ( WP_Block_Type_Registry::get_instance()->is_registered( $this->block_type ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->filter( 'rank_math/schema/block/toc-block', 'add_graph', 10, 2 );
|
||||
$this->filter( 'render_block_rank-math/toc-block', 'render_toc_block_content', 10, 2 );
|
||||
$this->filter( 'rank_math/metabox/post/values', 'block_settings_metadata' );
|
||||
register_block_type( RANK_MATH_PATH . 'includes/modules/schema/blocks/toc/block.json' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add meta data to use in the TOC block.
|
||||
*
|
||||
* @param array $values Aray of tabs.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function block_settings_metadata( $values ) {
|
||||
$values['tocTitle'] = Helper::get_settings( 'general.toc_block_title' );
|
||||
$values['tocExcludeHeadings'] = Helper::get_settings( 'general.toc_block_exclude_headings', [] );
|
||||
$values['listStyle'] = Helper::get_settings( 'general.toc_block_list_style', 'ul' );
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add default TOC title.
|
||||
*
|
||||
* @param string $block_content Block content.
|
||||
* @param array $parsed_block The full block, including name and attributes.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function render_toc_block_content( $block_content, $parsed_block ) {
|
||||
if ( isset( $parsed_block['attrs']['title'] ) ) {
|
||||
return $block_content;
|
||||
}
|
||||
|
||||
$title = Helper::get_settings( 'general.toc_block_title' );
|
||||
if ( ! $title ) {
|
||||
return $block_content;
|
||||
}
|
||||
|
||||
$title_wrapper = $parsed_block['attrs']['titleWrapper'] ?? 'h2';
|
||||
|
||||
$block_content = preg_replace_callback(
|
||||
'/(<div class=".*?wp-block-rank-math-toc-block.*?"\>)/i',
|
||||
function( $value ) use ( $title, $block_content, $title_wrapper ) {
|
||||
if ( ! isset( $value[0] ) ) {
|
||||
return $block_content;
|
||||
}
|
||||
|
||||
$value[0] = str_replace( '>', ' id="rank-math-toc">', $value[0] );
|
||||
return $value[0] . '<' . tag_escape( $title_wrapper ) . '>' . esc_html( $title ) . '</' . tag_escape( $title_wrapper ) . '>';
|
||||
},
|
||||
$block_content
|
||||
);
|
||||
|
||||
return str_replace( 'class=""', '', $block_content );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add TOC schema data in JSON-LD array.
|
||||
*
|
||||
* @param array $data Array of JSON-LD data.
|
||||
* @param array $block JsonLD Instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_graph( $data, $block ) {
|
||||
$attributes = $block['attrs'];
|
||||
// Early bail.
|
||||
if ( empty( $attributes['headings'] ) ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
if ( ! isset( $data['toc'] ) ) {
|
||||
$data['toc'] = [];
|
||||
}
|
||||
|
||||
foreach ( $attributes['headings'] as $heading ) {
|
||||
if ( ! empty( $heading['disable'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data['toc'][] = [
|
||||
'@context' => 'https://schema.org',
|
||||
'@type' => 'SiteNavigationElement',
|
||||
'@id' => '#rank-math-toc',
|
||||
'name' => $heading['content'],
|
||||
'url' => get_permalink() . $heading['link'],
|
||||
];
|
||||
}
|
||||
|
||||
if ( empty( $data['toc'] ) ) {
|
||||
unset( $data['toc'] );
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/**
|
||||
* Blocks general settings.
|
||||
*
|
||||
* @package RankMath
|
||||
* @subpackage RankMath\Schema
|
||||
*/
|
||||
|
||||
use RankMath\Helper;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
$cmb->add_field(
|
||||
[
|
||||
'id' => 'toc_block_title',
|
||||
'type' => 'text',
|
||||
'name' => esc_html__( 'Table of Contents Title', 'rank-math' ),
|
||||
'desc' => esc_html__( 'Enter the default title to use for the Table of Contents block.', 'rank-math' ),
|
||||
'classes' => 'rank-math-advanced-option',
|
||||
'default' => esc_html__( 'Table of Contents', 'rank-math' ),
|
||||
]
|
||||
);
|
||||
|
||||
$cmb->add_field(
|
||||
[
|
||||
'id' => 'toc_block_list_style',
|
||||
'type' => 'select',
|
||||
'name' => esc_html__( 'Table of Contents List style', 'rank-math' ),
|
||||
'desc' => esc_html__( 'Select the default list style for the Table of Contents block.', 'rank-math' ),
|
||||
'options' => [
|
||||
'div' => esc_html__( 'None', 'rank-math' ),
|
||||
'ol' => esc_html__( 'Numbered', 'rank-math' ),
|
||||
'ul' => esc_html__( 'Unordered', 'rank-math' ),
|
||||
],
|
||||
'default' => 'unordered',
|
||||
]
|
||||
);
|
||||
|
||||
$cmb->add_field(
|
||||
[
|
||||
'id' => 'toc_block_exclude_headings',
|
||||
'name' => esc_html__( 'Table of Contents Exclude Headings', 'rank-math' ),
|
||||
'desc' => esc_html__( 'Choose the headings to exclude from the Table of Contents block.', 'rank-math' ),
|
||||
'type' => 'multicheck',
|
||||
'options' => [
|
||||
'h1' => esc_html__( 'Heading H1', 'rank-math' ),
|
||||
'h2' => esc_html__( 'Heading H2', 'rank-math' ),
|
||||
'h3' => esc_html__( 'Heading H3', 'rank-math' ),
|
||||
'h4' => esc_html__( 'Heading H4', 'rank-math' ),
|
||||
'h5' => esc_html__( 'Heading H5', 'rank-math' ),
|
||||
'h6' => esc_html__( 'Heading H6', 'rank-math' ),
|
||||
],
|
||||
]
|
||||
);
|
Reference in New Issue
Block a user