initial
This commit is contained in:
+259
@@ -0,0 +1,259 @@
|
||||
<?php
|
||||
/**
|
||||
* Jetpack Related Posts Abilities Registration
|
||||
*
|
||||
* Registers Jetpack Related Posts abilities with the WordPress Abilities API.
|
||||
*
|
||||
* @package automattic/jetpack
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Plugin\Abilities;
|
||||
|
||||
use Automattic\Jetpack\WP_Abilities\Registrar;
|
||||
use Jetpack_RelatedPosts;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* Registers Jetpack Related Posts abilities with the WordPress Abilities API.
|
||||
*
|
||||
* Exposes related-post lookups through the standard `wp-abilities/v1` REST
|
||||
* surface. Display-settings management is intentionally not exposed: classic
|
||||
* themes consume the `relatedposts` option only when an off-by-default filter
|
||||
* is enabled, and block themes ignore it altogether (rendering is controlled
|
||||
* per-instance by the Jetpack Related Posts block in templates) — so an agent
|
||||
* editing those values would be writing data nothing reads.
|
||||
*/
|
||||
class Related_Posts_Abilities extends Registrar {
|
||||
|
||||
// Mirrors the cap the upstream Related Posts ES query enforces; raising it
|
||||
// here would be silently truncated downstream.
|
||||
private const MAX_SIZE = 20;
|
||||
|
||||
private const DEFAULT_SIZE = 3;
|
||||
|
||||
/**
|
||||
* Returns the category slug this registrar owns.
|
||||
*/
|
||||
public static function get_category_slug(): string {
|
||||
return 'jetpack-related-posts';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the category definition passed to wp_register_ability_category().
|
||||
*/
|
||||
public static function get_category_definition(): array {
|
||||
return array(
|
||||
// "Jetpack" is a product name and should not be translated.
|
||||
'label' => 'Jetpack Related Posts',
|
||||
'description' => __( 'Abilities for reading related posts.', 'jetpack' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the abilities this registrar owns as a [ slug => spec ] map.
|
||||
*/
|
||||
public static function get_abilities(): array {
|
||||
$related_post_schema = array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'id' => array( 'type' => 'integer' ),
|
||||
'url' => array( 'type' => 'string' ),
|
||||
'title' => array( 'type' => 'string' ),
|
||||
'excerpt' => array( 'type' => 'string' ),
|
||||
'date' => array( 'type' => 'string' ),
|
||||
'post_type' => array( 'type' => 'string' ),
|
||||
'format' => array( 'type' => array( 'string', 'null' ) ),
|
||||
),
|
||||
);
|
||||
|
||||
return array(
|
||||
'jetpack-related-posts/get-related-posts' => array(
|
||||
'label' => __( 'Get related posts', 'jetpack' ),
|
||||
'description' => __( 'Return related posts for a single post as an array of { id, url, title, excerpt, date, post_type, format }. The caller must be able to edit the source post (edit_post capability); unauthorized requests return jetpack_related_posts_forbidden. Backed by Elasticsearch via the Jetpack connection: when Related Posts is disabled, the post is unknown, or the ES backend is unreachable, the array is empty (not an error). Use per_page to control the result count (1..20, default 20); the underlying Elasticsearch query is hard-capped at 20, so values above 20 are rejected by the input schema and pagination beyond the first 20 results is not supported. The legacy "size" alias is accepted for backward compatibility and defaults to 3 when no per_page is supplied. Read-only and idempotent. Use jetpack/get-modules to confirm the related-posts module is active, jetpack/set-module-status to activate it.', 'jetpack' ),
|
||||
'input_schema' => array(
|
||||
'type' => 'object',
|
||||
'required' => array( 'post_id' ),
|
||||
'properties' => array(
|
||||
'post_id' => array(
|
||||
'type' => 'integer',
|
||||
'description' => __( 'WordPress post ID to find related posts for. Must reference an existing post.', 'jetpack' ),
|
||||
'minimum' => 1,
|
||||
),
|
||||
'per_page' => array(
|
||||
'type' => 'integer',
|
||||
'description' => __( 'Maximum number of related posts to return per call. Must be between 1 and 20 — the Elasticsearch backend hard-caps results at 20, so larger values are not supported and pagination past the first 20 results is unavailable. Defaults to 20.', 'jetpack' ),
|
||||
'minimum' => 1,
|
||||
'maximum' => self::MAX_SIZE,
|
||||
'default' => self::MAX_SIZE,
|
||||
),
|
||||
'size' => array(
|
||||
'type' => 'integer',
|
||||
'description' => __( 'Deprecated alias for per_page. Defaults to 3 when per_page is omitted. Capped at 20.', 'jetpack' ),
|
||||
'minimum' => 1,
|
||||
'maximum' => self::MAX_SIZE,
|
||||
'default' => self::DEFAULT_SIZE,
|
||||
),
|
||||
'post_type' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'Restrict matches to a single post type slug (e.g. "post", "page"). Defaults to the source post\'s type.', 'jetpack' ),
|
||||
'minLength' => 1,
|
||||
),
|
||||
'exclude_post_ids' => array(
|
||||
'type' => 'array',
|
||||
'description' => __( 'Post IDs to exclude from the result.', 'jetpack' ),
|
||||
'items' => array(
|
||||
'type' => 'integer',
|
||||
'minimum' => 1,
|
||||
),
|
||||
'default' => array(),
|
||||
),
|
||||
),
|
||||
'additionalProperties' => false,
|
||||
),
|
||||
'output_schema' => array(
|
||||
'type' => 'array',
|
||||
'items' => $related_post_schema,
|
||||
),
|
||||
'execute_callback' => array( __CLASS__, 'get_related_posts' ),
|
||||
'permission_callback' => array( __CLASS__, 'can_view_related_posts' ),
|
||||
'meta' => array(
|
||||
'annotations' => array(
|
||||
'readonly' => true,
|
||||
'destructive' => false,
|
||||
'idempotent' => true,
|
||||
),
|
||||
'show_in_rest' => true,
|
||||
'mcp' => array(
|
||||
'public' => true,
|
||||
'type' => 'tool', // default is already "tool", but can be explicit.
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Permission gate for the ability menu.
|
||||
*
|
||||
* Returns true if the caller can edit any post — keeps the ability listed
|
||||
* for agents that have at least one editable post. The actual per-post
|
||||
* authorization runs inside `get_related_posts()` once the source post_id
|
||||
* is known, mirroring the existing /wpcom/v2/related-posts/{id} endpoint.
|
||||
*/
|
||||
public static function can_view_related_posts(): bool {
|
||||
return current_user_can( 'edit_posts' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute: return related posts for a given post.
|
||||
*
|
||||
* @param array|null $input Input matching the ability's input_schema.
|
||||
* @return array|WP_Error Array of related-post summaries, or WP_Error on validation failure.
|
||||
*/
|
||||
public static function get_related_posts( $input = null ) {
|
||||
$input = is_array( $input ) ? $input : array();
|
||||
|
||||
$post_id = isset( $input['post_id'] ) ? (int) $input['post_id'] : 0;
|
||||
if ( $post_id <= 0 ) {
|
||||
return new WP_Error(
|
||||
'jetpack_related_posts_missing_post_id',
|
||||
__( 'A post_id is required to fetch related posts.', 'jetpack' )
|
||||
);
|
||||
}
|
||||
|
||||
$post = get_post( $post_id );
|
||||
if ( null === $post || empty( $post->ID ) ) {
|
||||
return new WP_Error(
|
||||
'jetpack_related_posts_invalid_post_id',
|
||||
__( 'Unknown post ID. Verify the post exists and is accessible.', 'jetpack' )
|
||||
);
|
||||
}
|
||||
|
||||
// Match the per-post gate the existing /wpcom/v2/related-posts/{id}
|
||||
// endpoint uses: the broad `edit_posts` cap on permission_callback lets
|
||||
// the ability appear in the agent menu, but the actual lookup is
|
||||
// authorized only when the caller can edit this specific post.
|
||||
if ( ! current_user_can( 'edit_post', $post->ID ) ) {
|
||||
return new WP_Error(
|
||||
'jetpack_related_posts_forbidden',
|
||||
__( 'You are not allowed to fetch related posts for this post.', 'jetpack' )
|
||||
);
|
||||
}
|
||||
|
||||
if ( isset( $input['per_page'] ) && is_int( $input['per_page'] ) ) {
|
||||
// per_page wins when both are supplied; schema enforces the 1..MAX_SIZE
|
||||
// range, the clamp here is defense in depth for direct callers that
|
||||
// bypass schema validation.
|
||||
$size = max( 1, min( self::MAX_SIZE, $input['per_page'] ) );
|
||||
} elseif ( isset( $input['size'] ) && is_int( $input['size'] ) ) {
|
||||
$size = max( 1, min( self::MAX_SIZE, $input['size'] ) );
|
||||
} else {
|
||||
$size = self::DEFAULT_SIZE;
|
||||
}
|
||||
|
||||
$args = array( 'size' => $size );
|
||||
|
||||
if ( isset( $input['post_type'] ) && is_string( $input['post_type'] ) && '' !== $input['post_type'] ) {
|
||||
$args['post_type'] = $input['post_type'];
|
||||
}
|
||||
|
||||
if ( isset( $input['exclude_post_ids'] ) && is_array( $input['exclude_post_ids'] ) ) {
|
||||
$args['exclude_post_ids'] = array_values(
|
||||
array_filter(
|
||||
array_map( 'intval', $input['exclude_post_ids'] ),
|
||||
static function ( $id ) {
|
||||
return $id > 0;
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$results = self::related_posts_instance()->get_for_post_id( $post->ID, $args );
|
||||
if ( ! is_array( $results ) || array() === $results ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// Prime the post cache once so `get_post_type()` inside summarize_related_post
|
||||
// doesn't trigger N individual lookups when the cache is cold.
|
||||
_prime_post_caches( wp_list_pluck( $results, 'id' ), false, false );
|
||||
|
||||
$out = array();
|
||||
foreach ( $results as $related ) {
|
||||
$out[] = self::summarize_related_post( $related );
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Jetpack Related Posts raw instance, loading the class file lazily
|
||||
* when it has not been included by the module's own load action yet.
|
||||
*
|
||||
* @return \Jetpack_RelatedPosts
|
||||
*/
|
||||
private static function related_posts_instance() {
|
||||
if ( ! class_exists( Jetpack_RelatedPosts::class, false ) ) {
|
||||
require_once __DIR__ . '/../jetpack-related-posts.php';
|
||||
}
|
||||
return Jetpack_RelatedPosts::init_raw();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduce the rich Related Posts result to a high-signal summary.
|
||||
*
|
||||
* @param array $related Single related-post entry from get_for_post_id().
|
||||
* @return array
|
||||
*/
|
||||
private static function summarize_related_post( array $related ): array {
|
||||
$id = isset( $related['id'] ) ? (int) $related['id'] : 0;
|
||||
|
||||
return array(
|
||||
'id' => $id,
|
||||
'url' => isset( $related['url'] ) ? (string) $related['url'] : '',
|
||||
'title' => isset( $related['title'] ) ? (string) $related['title'] : '',
|
||||
'excerpt' => isset( $related['excerpt'] ) ? (string) $related['excerpt'] : '',
|
||||
'date' => isset( $related['date'] ) ? (string) $related['date'] : '',
|
||||
'post_type' => $id > 0 ? (string) get_post_type( $id ) : '',
|
||||
'format' => isset( $related['format'] ) && '' !== $related['format'] ? (string) $related['format'] : null,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
|
||||
|
||||
use Automattic\Jetpack\Assets;
|
||||
|
||||
// Exit if file is accessed directly.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to include elements to modify Related Posts look in Customizer.
|
||||
*
|
||||
* @since 4.4.0
|
||||
* @phan-constructor-used-for-side-effects
|
||||
*/
|
||||
class Jetpack_Related_Posts_Customize {
|
||||
|
||||
/**
|
||||
* Key for panel, section and prefix for options. Same option name than in Options > Reading.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $prefix = 'jetpack_relatedposts';
|
||||
|
||||
/**
|
||||
* Control to focus when customizer loads
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $focus = '';
|
||||
|
||||
/**
|
||||
* Class initialization.
|
||||
*
|
||||
* @since 4.4.0
|
||||
*/
|
||||
public function __construct() {
|
||||
if ( ! wp_is_block_theme() ) {
|
||||
add_action( 'customize_register', array( $this, 'customize_register' ) );
|
||||
add_action( 'customize_controls_enqueue_scripts', array( $this, 'customize_controls_enqueue_scripts' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize Customizer controls.
|
||||
*
|
||||
* @since 4.4.0
|
||||
*
|
||||
* @param WP_Customize_Manager $wp_customize Customizer instance.
|
||||
*/
|
||||
public function customize_register( $wp_customize ) {
|
||||
|
||||
$wp_customize->add_section(
|
||||
$this->prefix,
|
||||
array(
|
||||
'title' => esc_html__( 'Related Posts', 'jetpack' ),
|
||||
'description' => '',
|
||||
'capability' => 'edit_theme_options',
|
||||
'priority' => 200,
|
||||
)
|
||||
);
|
||||
|
||||
$selective_options = array();
|
||||
|
||||
foreach ( $this->get_options( $wp_customize ) as $key => $field ) {
|
||||
$control_id = "$this->prefix[$key]";
|
||||
$selective_options[] = $control_id;
|
||||
$wp_customize->add_setting(
|
||||
$control_id,
|
||||
array(
|
||||
'default' => $field['default'] ?? '',
|
||||
'type' => $field['setting_type'] ?? 'option',
|
||||
'capability' => $field['capability'] ?? 'edit_theme_options',
|
||||
'transport' => $field['transport'] ?? 'postMessage',
|
||||
)
|
||||
);
|
||||
$control_settings = array(
|
||||
'label' => $field['label'] ?? '',
|
||||
'description' => $field['description'] ?? '',
|
||||
'settings' => $control_id,
|
||||
'type' => $field['control_type'] ?? 'text',
|
||||
'section' => $this->prefix,
|
||||
'priority' => 10,
|
||||
'active_callback' => $field['active_callback'] ?? __CLASS__ . '::is_single',
|
||||
);
|
||||
switch ( $field['control_type'] ) {
|
||||
case 'text':
|
||||
case 'checkbox':
|
||||
default:
|
||||
$wp_customize->add_control( new WP_Customize_Control( $wp_customize, $control_id, $control_settings ) );
|
||||
break;
|
||||
case 'select':
|
||||
if ( isset( $field['choices'] ) ) {
|
||||
$control_settings['choices'] = $field['choices'];
|
||||
$wp_customize->add_control( new WP_Customize_Control( $wp_customize, $control_id, $control_settings ) );
|
||||
}
|
||||
break;
|
||||
case 'message':
|
||||
$wp_customize->add_control( new Jetpack_Message_Control( $wp_customize, $control_id, $control_settings ) );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If selective refresh is available, implement it.
|
||||
if ( isset( $wp_customize->selective_refresh ) ) {
|
||||
$wp_customize->selective_refresh->add_partial(
|
||||
"$this->prefix",
|
||||
array(
|
||||
'selector' => '.jp-relatedposts:not(.jp-relatedposts-block)',
|
||||
'settings' => $selective_options,
|
||||
'render_callback' => __CLASS__ . '::render_callback',
|
||||
'container_inclusive' => false,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback that outputs the headline based on user choice.
|
||||
*
|
||||
* @since 4.4.0
|
||||
*/
|
||||
public static function render_callback() {
|
||||
echo Jetpack_RelatedPosts::init()->get_headline(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- content escaped in get_headline method
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the current post contains a Related Posts block.
|
||||
*
|
||||
* @since 6.9.0
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function contains_related_posts_block() {
|
||||
if ( has_block( 'jetpack/related-posts' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that we're in a single post view.
|
||||
* Will return `false` if the current post contains a Related Posts block,
|
||||
* because in that case we want to hide the Customizer controls.
|
||||
*
|
||||
* @since 4.4.0
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_single() {
|
||||
if ( self::contains_related_posts_block() ) {
|
||||
return false;
|
||||
}
|
||||
return is_single();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that we're not in a single post view.
|
||||
* Will return `false` if the current post contains a Related Posts block,
|
||||
* because in that case we want to hide the Customizer controls.
|
||||
*
|
||||
* @since 4.4.0
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_not_single() {
|
||||
if ( self::contains_related_posts_block() ) {
|
||||
return false;
|
||||
}
|
||||
return ! is_single();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return list of options to modify.
|
||||
*
|
||||
* @since 4.4.0
|
||||
*
|
||||
* @param object $wp_customize Instance of WP Customizer.
|
||||
*
|
||||
* @return mixed|void
|
||||
*/
|
||||
public function get_options( $wp_customize ) {
|
||||
$transport = isset( $wp_customize->selective_refresh ) ? 'postMessage' : 'refresh';
|
||||
|
||||
$switched_locale = switch_to_locale( get_user_locale() );
|
||||
$headline = __( 'Related', 'jetpack' );
|
||||
if ( $switched_locale ) {
|
||||
restore_previous_locale();
|
||||
}
|
||||
|
||||
/**
|
||||
* The filter allows you to change the options used to display Related Posts in the Customizer.
|
||||
*
|
||||
* @module related-posts
|
||||
*
|
||||
* @since 4.4.0
|
||||
*
|
||||
* @param array $options Array of options used to display Related Posts in the Customizer.
|
||||
*/
|
||||
return apply_filters(
|
||||
'jetpack_related_posts_customize_options',
|
||||
array(
|
||||
'enabled' => array(
|
||||
'control_type' => 'hidden',
|
||||
'default' => 1,
|
||||
'setting_type' => 'option',
|
||||
'transport' => $transport,
|
||||
),
|
||||
'show_headline' => array(
|
||||
'label' => esc_html__( 'Show a headline', 'jetpack' ),
|
||||
'description' => esc_html__( 'This helps to clearly separate the related posts from post content.', 'jetpack' ),
|
||||
'control_type' => 'checkbox',
|
||||
'default' => 1,
|
||||
'setting_type' => 'option',
|
||||
'transport' => $transport,
|
||||
),
|
||||
'headline' => array(
|
||||
'label' => '',
|
||||
'description' => esc_html__( 'Enter text to use as headline.', 'jetpack' ),
|
||||
'control_type' => 'text',
|
||||
'default' => esc_html( $headline ),
|
||||
'setting_type' => 'option',
|
||||
'transport' => $transport,
|
||||
),
|
||||
'show_thumbnails' => array(
|
||||
'label' => esc_html__( 'Show thumbnails', 'jetpack' ),
|
||||
'description' => esc_html__( 'Show a thumbnail image where available.', 'jetpack' ),
|
||||
'control_type' => 'checkbox',
|
||||
'default' => 1,
|
||||
'setting_type' => 'option',
|
||||
'transport' => $transport,
|
||||
),
|
||||
'show_date' => array(
|
||||
'label' => esc_html__( 'Show date', 'jetpack' ),
|
||||
'description' => esc_html__( 'Display date when entry was published.', 'jetpack' ),
|
||||
'control_type' => 'checkbox',
|
||||
'default' => 1,
|
||||
'setting_type' => 'option',
|
||||
'transport' => $transport,
|
||||
),
|
||||
'show_context' => array(
|
||||
'label' => esc_html__( 'Show context', 'jetpack' ),
|
||||
'description' => esc_html__( "Display entry's category or tag.", 'jetpack' ),
|
||||
'control_type' => 'checkbox',
|
||||
'default' => 1,
|
||||
'setting_type' => 'option',
|
||||
'transport' => $transport,
|
||||
),
|
||||
'layout' => array(
|
||||
'label' => esc_html__( 'Layout', 'jetpack' ),
|
||||
'description' => esc_html__( 'Arrange entries in different layouts.', 'jetpack' ),
|
||||
'control_type' => 'select',
|
||||
'choices' => array(
|
||||
'grid' => esc_html__( 'Grid', 'jetpack' ),
|
||||
'list' => esc_html__( 'List', 'jetpack' ),
|
||||
),
|
||||
'default' => 'grid',
|
||||
'setting_type' => 'option',
|
||||
'transport' => $transport,
|
||||
),
|
||||
'msg_go_to_single' => array(
|
||||
'description' => esc_html__( 'Please visit a single post view to reveal the customization options.', 'jetpack' ),
|
||||
'control_type' => 'message',
|
||||
'active_callback' => __CLASS__ . '::is_not_single',
|
||||
),
|
||||
'msg_example' => array(
|
||||
'description' => esc_html__( 'Please note that the related posts displayed now are only for previewing purposes.', 'jetpack' ),
|
||||
'control_type' => 'message',
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue assets for Customizer controls.
|
||||
*
|
||||
* @since 4.4.0
|
||||
*/
|
||||
public function customize_controls_enqueue_scripts() {
|
||||
wp_enqueue_script(
|
||||
'jetpack_related-posts-customizer',
|
||||
Assets::get_file_url_for_environment(
|
||||
'_inc/build/related-posts/related-posts-customizer.min.js',
|
||||
'modules/related-posts/related-posts-customizer.js'
|
||||
),
|
||||
array( 'customize-controls' ),
|
||||
JETPACK__VERSION,
|
||||
false
|
||||
);
|
||||
}
|
||||
} // class end
|
||||
|
||||
/**
|
||||
* Control that displays a message in Customizer.
|
||||
*
|
||||
* @since 4.4.0
|
||||
* @todo break this out into its own file.
|
||||
*/
|
||||
class Jetpack_Message_Control extends WP_Customize_Control { // phpcs:ignore
|
||||
|
||||
/**
|
||||
* Render the message.
|
||||
*
|
||||
* @since 4.4.0
|
||||
*/
|
||||
public function render_content() {
|
||||
echo '<p class="description">' . esc_html( $this->description ) . '</p>';
|
||||
}
|
||||
} // class end
|
||||
|
||||
// Initialize controls.
|
||||
new Jetpack_Related_Posts_Customize();
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Adds functionality for Related Posts controls in Customizer.
|
||||
*/
|
||||
( function ( api ) {
|
||||
'use strict';
|
||||
|
||||
api( 'jetpack_relatedposts[show_headline]', function ( showHeadlineSetting ) {
|
||||
var setupHeadlineControl = function ( headlineControl ) {
|
||||
var setActiveState, isDisplayed;
|
||||
|
||||
isDisplayed = function () {
|
||||
return showHeadlineSetting.findControls()[ 0 ].active.get() && showHeadlineSetting.get();
|
||||
};
|
||||
|
||||
setActiveState = function () {
|
||||
headlineControl.active.set( isDisplayed() );
|
||||
};
|
||||
|
||||
headlineControl.active.validate = isDisplayed;
|
||||
|
||||
setActiveState();
|
||||
|
||||
showHeadlineSetting.bind( setActiveState );
|
||||
};
|
||||
|
||||
api.control( 'jetpack_relatedposts[headline]', setupHeadlineControl );
|
||||
} );
|
||||
} )( wp.customize );
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,347 @@
|
||||
/**
|
||||
* Jetpack related posts
|
||||
*/
|
||||
|
||||
/**
|
||||
* The Gutenberg block
|
||||
*/
|
||||
|
||||
.jp-related-posts-i2 {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.jp-related-posts-i2__list {
|
||||
--hgap: 1rem;
|
||||
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
column-gap: var(--hgap);
|
||||
row-gap: 2rem;
|
||||
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.jp-related-posts-i2__post {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
/* Default: 2 items by row */
|
||||
flex-basis: calc(( 100% - var(--hgap) ) / 2);
|
||||
}
|
||||
|
||||
/* Quantity qeuries: see https://alistapart.com/article/quantity-queries-for-css/ */
|
||||
.jp-related-posts-i2__post:nth-last-child(n+3):first-child,
|
||||
.jp-related-posts-i2__post:nth-last-child(n+3):first-child ~ * {
|
||||
|
||||
/* From 3 total items on, 3 items by row */
|
||||
flex-basis: calc(( 100% - var(--hgap) * 2 ) / 3);
|
||||
}
|
||||
|
||||
.jp-related-posts-i2__post:nth-last-child(4):first-child,
|
||||
.jp-related-posts-i2__post:nth-last-child(4):first-child ~ * {
|
||||
|
||||
/* Exception for 4 total items: 2 items by row */
|
||||
flex-basis: calc(( 100% - var(--hgap) ) / 2);
|
||||
}
|
||||
|
||||
.jp-related-posts-i2__post-link {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 0.5rem;
|
||||
|
||||
width: 100%;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.jp-related-posts-i2__post-link:focus-visible {
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.jp-related-posts-i2__post-img {
|
||||
order: -1;
|
||||
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.jp-related-posts-i2__post-defs {
|
||||
margin: 0;
|
||||
|
||||
list-style-type: unset;
|
||||
}
|
||||
|
||||
/* Hide, except from screen readers */
|
||||
.jp-related-posts-i2__post-defs dt {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
clip-path: inset(50%);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.jp-related-posts-i2__post-defs dd {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* List view */
|
||||
|
||||
.jp-relatedposts-i2[data-layout="list"] .jp-related-posts-i2__list {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.jp-relatedposts-i2[data-layout="list"] .jp-related-posts-i2__post {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
/* Breakpoints */
|
||||
|
||||
@media only screen and (max-width: 640px) {
|
||||
|
||||
.jp-related-posts-i2__list {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.jp-related-posts-i2__post {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Container */
|
||||
|
||||
#jp-relatedposts {
|
||||
display: none;
|
||||
padding-top: 1em;
|
||||
margin: 1em 0;
|
||||
position: relative;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.jp-relatedposts::after {
|
||||
content: "";
|
||||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
/* Headline above related posts section, labeled "Related" */
|
||||
|
||||
#jp-relatedposts h3.jp-relatedposts-headline {
|
||||
margin: 0 0 1em 0;
|
||||
display: inline-block;
|
||||
float: left;
|
||||
font-size: 9pt;
|
||||
font-weight: 700;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
#jp-relatedposts h3.jp-relatedposts-headline em::before {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 100%;
|
||||
min-width: 30px;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.2);
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
#jp-relatedposts h3.jp-relatedposts-headline em {
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* Related posts items (wrapping items) */
|
||||
|
||||
#jp-relatedposts .jp-relatedposts-items {
|
||||
clear: left;
|
||||
}
|
||||
|
||||
#jp-relatedposts .jp-relatedposts-items-visual {
|
||||
margin-right: -20px;
|
||||
}
|
||||
|
||||
/* Related posts item */
|
||||
|
||||
#jp-relatedposts .jp-relatedposts-items .jp-relatedposts-post {
|
||||
float: left;
|
||||
width: 33%;
|
||||
margin: 0 0 1em; /* Needs to be same as the main outer wrapper for Related Posts */
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#jp-relatedposts .jp-relatedposts-items-visual .jp-relatedposts-post {
|
||||
padding-right: 20px;
|
||||
filter: alpha(opacity=80);
|
||||
-moz-opacity: 0.8;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
#jp-relatedposts .jp-relatedposts-items .jp-relatedposts-post:nth-child(3n+4),
|
||||
#jp-relatedposts .jp-relatedposts-items-visual .jp-relatedposts-post:nth-child(3n+4) {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
#jp-relatedposts .jp-relatedposts-items .jp-relatedposts-post:hover .jp-relatedposts-post-title a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#jp-relatedposts .jp-relatedposts-items .jp-relatedposts-post:hover {
|
||||
filter: alpha(opacity=100);
|
||||
-moz-opacity: 1;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Related posts item content */
|
||||
|
||||
#jp-relatedposts .jp-relatedposts-items-visual h4.jp-relatedposts-post-title,
|
||||
#jp-relatedposts .jp-relatedposts-items p,
|
||||
#jp-relatedposts .jp-relatedposts-items time {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#jp-relatedposts .jp-relatedposts-items-visual .jp-relatedposts-post-nothumbs {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#jp-relatedposts .jp-relatedposts-items-visual .jp-relatedposts-post-nothumbs a.jp-relatedposts-post-aoverlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: block;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
#jp-relatedposts .jp-relatedposts-items p,
|
||||
#jp-relatedposts .jp-relatedposts-items time {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#jp-relatedposts .jp-relatedposts-items-visual h4.jp-relatedposts-post-title {
|
||||
text-transform: none;
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
#jp-relatedposts .jp-relatedposts-items .jp-relatedposts-post .jp-relatedposts-post-title a {
|
||||
font-size: inherit;
|
||||
font-weight: 400;
|
||||
text-decoration: none;
|
||||
filter: alpha(opacity=100);
|
||||
-moz-opacity: 1;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#jp-relatedposts .jp-relatedposts-items .jp-relatedposts-post .jp-relatedposts-post-title a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#jp-relatedposts .jp-relatedposts-items .jp-relatedposts-post img.jp-relatedposts-post-img,
|
||||
#jp-relatedposts .jp-relatedposts-items .jp-relatedposts-post span {
|
||||
display: block;
|
||||
max-width: 90%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
#jp-relatedposts .jp-relatedposts-items-visual .jp-relatedposts-post img.jp-relatedposts-post-img,
|
||||
#jp-relatedposts .jp-relatedposts-items-visual .jp-relatedposts-post span {
|
||||
height: auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
#jp-relatedposts .jp-relatedposts-items .jp-relatedposts-post .jp-relatedposts-post-date,
|
||||
#jp-relatedposts .jp-relatedposts-items .jp-relatedposts-post .jp-relatedposts-post-context {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* Hide the date by default, but leave the element there if
|
||||
* a theme wants to use css to make it visible. */
|
||||
.jp-relatedposts-items .jp-relatedposts-post .jp-relatedposts-post-date {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Behavior when there are thumbnails in visual mode */
|
||||
#jp-relatedposts .jp-relatedposts-items-visual div.jp-relatedposts-post-thumbs p.jp-relatedposts-post-excerpt {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Behavior when there are no thumbnails in visual mode */
|
||||
#jp-relatedposts .jp-relatedposts-items-visual .jp-relatedposts-post-nothumbs p.jp-relatedposts-post-excerpt {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#jp-relatedposts .jp-relatedposts-items-visual .jp-relatedposts-post-nothumbs span {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
/* List Layout */
|
||||
#jp-relatedposts .jp-relatedposts-list .jp-relatedposts-post {
|
||||
clear: both;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#jp-relatedposts .jp-relatedposts-list .jp-relatedposts-post img.jp-relatedposts-post-img {
|
||||
float: left;
|
||||
overflow: hidden;
|
||||
max-width: 33%;
|
||||
margin-right: 3%;
|
||||
}
|
||||
|
||||
#jp-relatedposts .jp-relatedposts-list h4.jp-relatedposts-post-title {
|
||||
display: inline-block;
|
||||
max-width: 63%;
|
||||
}
|
||||
|
||||
/*
|
||||
* Responsive
|
||||
*/
|
||||
|
||||
@media only screen and (max-width: 640px) {
|
||||
|
||||
#jp-relatedposts .jp-relatedposts-items .jp-relatedposts-post {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
#jp-relatedposts .jp-relatedposts-items .jp-relatedposts-post:nth-child(3n) {
|
||||
clear: left;
|
||||
}
|
||||
|
||||
#jp-relatedposts .jp-relatedposts-items-visual {
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 320px) {
|
||||
|
||||
#jp-relatedposts .jp-relatedposts-items .jp-relatedposts-post {
|
||||
width: 100%;
|
||||
clear: both;
|
||||
margin: 0 0 1em;
|
||||
}
|
||||
|
||||
#jp-relatedposts .jp-relatedposts-list .jp-relatedposts-post img.jp-relatedposts-post-img,
|
||||
#jp-relatedposts .jp-relatedposts-list h4.jp-relatedposts-post-title {
|
||||
float: none;
|
||||
max-width: 100%;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Hide the related post section in the print view of a post
|
||||
*/
|
||||
|
||||
@media print {
|
||||
|
||||
.jp-relatedposts {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
/* globals related_posts_js_options */
|
||||
|
||||
/**
|
||||
* Load related posts
|
||||
*/
|
||||
( function () {
|
||||
'use strict';
|
||||
|
||||
var jprp = {
|
||||
response: null,
|
||||
|
||||
/**
|
||||
* Utility get related posts JSON endpoint from URLs
|
||||
*
|
||||
* @param {string} URL (optional)
|
||||
* @return {string} Endpoint URL
|
||||
*/
|
||||
getEndpointURL: function ( URL ) {
|
||||
var locationObject,
|
||||
is_customizer =
|
||||
'undefined' !== typeof wp &&
|
||||
wp.customize &&
|
||||
wp.customize.settings &&
|
||||
wp.customize.settings.url &&
|
||||
wp.customize.settings.url.self;
|
||||
|
||||
// If we're in Customizer, write the correct URL.
|
||||
if ( is_customizer ) {
|
||||
locationObject = document.createElement( 'a' );
|
||||
locationObject.href = wp.customize.settings.url.self;
|
||||
} else {
|
||||
locationObject = document.location;
|
||||
}
|
||||
|
||||
if ( 'string' === typeof URL && URL.match( /^https?:\/\// ) ) {
|
||||
locationObject = document.createElement( 'a' );
|
||||
locationObject.href = URL;
|
||||
}
|
||||
|
||||
var args = 'relatedposts=1';
|
||||
var relatedPosts = document.querySelector( '#jp-relatedposts' );
|
||||
|
||||
if ( ! relatedPosts ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( relatedPosts.hasAttribute( 'data-exclude' ) ) {
|
||||
args += '&relatedposts_exclude=' + relatedPosts.getAttribute( 'data-exclude' );
|
||||
}
|
||||
|
||||
if ( is_customizer ) {
|
||||
args += '&jetpackrpcustomize=1';
|
||||
}
|
||||
|
||||
var pathname = locationObject.pathname;
|
||||
if ( '/' !== pathname[ 0 ] ) {
|
||||
pathname = '/' + pathname;
|
||||
}
|
||||
|
||||
if ( '' === locationObject.search ) {
|
||||
return pathname + '?' + args;
|
||||
}
|
||||
return pathname + locationObject.search + '&' + args;
|
||||
},
|
||||
|
||||
getAnchor: function ( post, classNames ) {
|
||||
var anchorTitle = post.title;
|
||||
var anchor = document.createElement( 'a' );
|
||||
anchor.setAttribute( 'class', classNames );
|
||||
anchor.setAttribute( 'href', post.url );
|
||||
anchor.setAttribute( 'title', anchorTitle );
|
||||
anchor.setAttribute( 'data-origin', post.url_meta.origin );
|
||||
anchor.setAttribute( 'data-position', post.url_meta.position );
|
||||
|
||||
if ( '' !== post.rel ) {
|
||||
anchor.setAttribute( 'rel', post.rel );
|
||||
}
|
||||
|
||||
var div = document.createElement( 'div' );
|
||||
div.appendChild( anchor );
|
||||
|
||||
var anchorHTML = div.innerHTML;
|
||||
return [ anchorHTML.substring( 0, anchorHTML.length - 4 ), '</a>' ];
|
||||
},
|
||||
|
||||
generateMinimalHtml: function ( posts, options ) {
|
||||
var self = this;
|
||||
var html = '';
|
||||
|
||||
posts.forEach( function ( post, index ) {
|
||||
var anchor = self.getAnchor( post, 'jp-relatedposts-post-a' );
|
||||
var classes = 'jp-relatedposts-post jp-relatedposts-post' + index;
|
||||
|
||||
if ( post.classes.length > 0 ) {
|
||||
classes += ' ' + post.classes.join( ' ' );
|
||||
}
|
||||
|
||||
html +=
|
||||
'<p class="' +
|
||||
classes +
|
||||
'" data-post-id="' +
|
||||
post.id +
|
||||
'" data-post-format="' +
|
||||
post.format +
|
||||
'">';
|
||||
html +=
|
||||
'<span class="jp-relatedposts-post-title">' +
|
||||
anchor[ 0 ] +
|
||||
post.title +
|
||||
anchor[ 1 ] +
|
||||
'</span>';
|
||||
if ( options.showDate ) {
|
||||
html +=
|
||||
'<time class="jp-relatedposts-post-date" datetime="' +
|
||||
post.date +
|
||||
'">' +
|
||||
post.date +
|
||||
'</time>';
|
||||
}
|
||||
if ( options.showContext ) {
|
||||
html += '<span class="jp-relatedposts-post-context">' + post.context + '</span>';
|
||||
}
|
||||
html += '</p>';
|
||||
} );
|
||||
return (
|
||||
'<div class="jp-relatedposts-items jp-relatedposts-items-minimal jp-relatedposts-' +
|
||||
options.layout +
|
||||
' ">' +
|
||||
html +
|
||||
'</div>'
|
||||
);
|
||||
},
|
||||
|
||||
generateVisualHtml: function ( posts, options ) {
|
||||
var self = this;
|
||||
var html = '';
|
||||
|
||||
posts.forEach( function ( post, index ) {
|
||||
var anchor = self.getAnchor( post, 'jp-relatedposts-post-a' );
|
||||
var classes = 'jp-relatedposts-post jp-relatedposts-post' + index;
|
||||
|
||||
if ( post.classes.length > 0 ) {
|
||||
classes += ' ' + post.classes.join( ' ' );
|
||||
}
|
||||
|
||||
if ( ! post.img.src ) {
|
||||
classes += ' jp-relatedposts-post-nothumbs';
|
||||
} else {
|
||||
classes += ' jp-relatedposts-post-thumbs';
|
||||
}
|
||||
|
||||
var dummyContainer = document.createElement( 'p' );
|
||||
dummyContainer.innerHTML = post.excerpt;
|
||||
var excerpt = dummyContainer.textContent;
|
||||
|
||||
html +=
|
||||
'<div class="' +
|
||||
classes +
|
||||
'" data-post-id="' +
|
||||
post.id +
|
||||
'" data-post-format="' +
|
||||
post.format +
|
||||
'">';
|
||||
if ( post.img.src ) {
|
||||
html +=
|
||||
anchor[ 0 ] +
|
||||
'<img class="jp-relatedposts-post-img" loading="lazy" src="' +
|
||||
post.img.src +
|
||||
'" width="' +
|
||||
post.img.width +
|
||||
'" height="' +
|
||||
post.img.height +
|
||||
( post.img.srcset ? '" srcset="' + post.img.srcset : '' ) +
|
||||
( post.img.sizes ? '" sizes="' + post.img.sizes : '' ) +
|
||||
'" alt="' +
|
||||
post.img.alt_text +
|
||||
'" />' +
|
||||
anchor[ 1 ];
|
||||
} else {
|
||||
var anchor_overlay = self.getAnchor(
|
||||
post,
|
||||
'jp-relatedposts-post-a jp-relatedposts-post-aoverlay'
|
||||
);
|
||||
html += anchor_overlay[ 0 ] + anchor_overlay[ 1 ];
|
||||
}
|
||||
html +=
|
||||
'<' +
|
||||
related_posts_js_options.post_heading +
|
||||
' class="jp-relatedposts-post-title">' +
|
||||
anchor[ 0 ] +
|
||||
post.title +
|
||||
anchor[ 1 ] +
|
||||
'</' +
|
||||
related_posts_js_options.post_heading +
|
||||
'>';
|
||||
html += '<p class="jp-relatedposts-post-excerpt">' + excerpt + '</p>';
|
||||
if ( options.showDate ) {
|
||||
html +=
|
||||
'<time class="jp-relatedposts-post-date" datetime="' +
|
||||
post.date +
|
||||
'">' +
|
||||
post.date +
|
||||
'</time>';
|
||||
}
|
||||
if ( options.showContext ) {
|
||||
html += '<p class="jp-relatedposts-post-context">' + post.context + '</p>';
|
||||
}
|
||||
html += '</div>';
|
||||
} );
|
||||
return (
|
||||
'<div class="jp-relatedposts-items jp-relatedposts-items-visual jp-relatedposts-' +
|
||||
options.layout +
|
||||
' ">' +
|
||||
html +
|
||||
'</div>'
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* We want to set a max height on the excerpt however we want to set
|
||||
* this according to the natual pacing of the page as we never want to
|
||||
* cut off a line of text in the middle so we need to do some detective
|
||||
* work.
|
||||
*/
|
||||
setVisualExcerptHeights: function () {
|
||||
var elements = document.querySelectorAll(
|
||||
'#jp-relatedposts .jp-relatedposts-post-nothumbs .jp-relatedposts-post-excerpt'
|
||||
);
|
||||
|
||||
if ( ! elements.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
var firstElementStyles = getComputedStyle( elements[ 0 ] );
|
||||
|
||||
var fontSize = parseInt( firstElementStyles.fontSize, 10 );
|
||||
var lineHeight = parseInt( firstElementStyles.lineHeight, 10 );
|
||||
|
||||
// Show 5 lines of text
|
||||
for ( var i = 0; i < elements.length; i++ ) {
|
||||
elements[ i ].style.maxHeight = ( 5 * lineHeight ) / fontSize + 'em';
|
||||
}
|
||||
},
|
||||
|
||||
getTrackedUrl: function ( anchor ) {
|
||||
var args = 'relatedposts_hit=1';
|
||||
args += '&relatedposts_origin=' + anchor.getAttribute( 'data-origin' );
|
||||
args += '&relatedposts_position=' + anchor.getAttribute( 'data-position' );
|
||||
|
||||
var pathname = anchor.pathname;
|
||||
if ( '/' !== pathname[ 0 ] ) {
|
||||
pathname = '/' + pathname;
|
||||
}
|
||||
|
||||
if ( '' === anchor.search ) {
|
||||
return pathname + '?' + args;
|
||||
}
|
||||
return pathname + anchor.search + '&' + args;
|
||||
},
|
||||
|
||||
cleanupTrackedUrl: function () {
|
||||
if ( 'function' !== typeof history.replaceState ) {
|
||||
return;
|
||||
}
|
||||
|
||||
var cleaned_search = document.location.search.replace(
|
||||
/\brelatedposts_[a-z]+=[0-9]*&?\b/gi,
|
||||
''
|
||||
);
|
||||
if ( '?' === cleaned_search ) {
|
||||
cleaned_search = '';
|
||||
}
|
||||
if ( document.location.search !== cleaned_search ) {
|
||||
history.replaceState( {}, document.title, document.location.pathname + cleaned_search );
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function afterPostsHaveLoaded() {
|
||||
jprp.setVisualExcerptHeights();
|
||||
var posts = document.querySelectorAll( '#jp-relatedposts a.jp-relatedposts-post-a' );
|
||||
|
||||
Array.prototype.forEach.call( posts, function ( post ) {
|
||||
document.addEventListener( 'click', function () {
|
||||
post.href = jprp.getTrackedUrl( post );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize Related Posts.
|
||||
*/
|
||||
function startRelatedPosts() {
|
||||
jprp.cleanupTrackedUrl();
|
||||
|
||||
var endpointURL = jprp.getEndpointURL();
|
||||
|
||||
if ( ! endpointURL ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( document.querySelectorAll( '#jp-relatedposts .jp-relatedposts-post' ).length ) {
|
||||
afterPostsHaveLoaded();
|
||||
return;
|
||||
}
|
||||
|
||||
var relatedPosts = document.querySelector( '#jp-relatedposts' );
|
||||
var request = new XMLHttpRequest();
|
||||
request.open( 'GET', endpointURL, true );
|
||||
request.setRequestHeader( 'x-requested-with', 'XMLHttpRequest' );
|
||||
|
||||
request.onreadystatechange = function () {
|
||||
if ( this.readyState === XMLHttpRequest.DONE && this.status === 200 ) {
|
||||
try {
|
||||
var response = JSON.parse( request.responseText );
|
||||
|
||||
if ( 0 === response.items.length || 0 === relatedPosts.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
jprp.response = response;
|
||||
|
||||
var html,
|
||||
showThumbnails,
|
||||
options = {};
|
||||
|
||||
if ( 'undefined' !== typeof wp && wp.customize ) {
|
||||
showThumbnails = wp.customize.instance( 'jetpack_relatedposts[show_thumbnails]' ).get();
|
||||
options.showDate = wp.customize.instance( 'jetpack_relatedposts[show_date]' ).get();
|
||||
options.showContext = wp.customize
|
||||
.instance( 'jetpack_relatedposts[show_context]' )
|
||||
.get();
|
||||
options.layout = wp.customize.instance( 'jetpack_relatedposts[layout]' ).get();
|
||||
} else {
|
||||
showThumbnails = response.show_thumbnails;
|
||||
options.showDate = response.show_date;
|
||||
options.showContext = response.show_context;
|
||||
options.layout = response.layout;
|
||||
}
|
||||
|
||||
html = ! showThumbnails
|
||||
? jprp.generateMinimalHtml( response.items, options )
|
||||
: jprp.generateVisualHtml( response.items, options );
|
||||
|
||||
var div = document.createElement( 'div' );
|
||||
relatedPosts.appendChild( div );
|
||||
div.outerHTML = html;
|
||||
|
||||
if ( options.showDate ) {
|
||||
var dates = relatedPosts.querySelectorAll( '.jp-relatedposts-post-date' );
|
||||
|
||||
Array.prototype.forEach.call( dates, function ( date ) {
|
||||
date.style.display = 'block';
|
||||
} );
|
||||
}
|
||||
|
||||
relatedPosts.style.display = 'block';
|
||||
afterPostsHaveLoaded();
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
request.send();
|
||||
}
|
||||
|
||||
function init() {
|
||||
if ( 'undefined' !== typeof wp && wp.customize ) {
|
||||
if ( wp.customize.selectiveRefresh ) {
|
||||
wp.customize.selectiveRefresh.bind( 'partial-content-rendered', function ( placement ) {
|
||||
if ( 'jetpack_relatedposts' === placement.partial.id ) {
|
||||
startRelatedPosts();
|
||||
}
|
||||
} );
|
||||
}
|
||||
wp.customize.bind( 'preview-ready', startRelatedPosts );
|
||||
} else {
|
||||
startRelatedPosts();
|
||||
}
|
||||
}
|
||||
|
||||
if ( document.readyState !== 'loading' ) {
|
||||
init();
|
||||
} else {
|
||||
document.addEventListener( 'DOMContentLoaded', init );
|
||||
}
|
||||
} )();
|
||||
@@ -0,0 +1,190 @@
|
||||
/* This file was automatically generated on Dec 01 2014 22:02:36 */
|
||||
|
||||
/**
|
||||
* Styles for Jetpack related posts
|
||||
*/
|
||||
|
||||
/* Container */
|
||||
|
||||
div#jp-relatedposts {
|
||||
display: none;
|
||||
padding-top: 1em;
|
||||
margin: 1em 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
div.jp-relatedposts:after {
|
||||
content: '';
|
||||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
/* Headline above related posts section, labeled "Related" */
|
||||
|
||||
div#jp-relatedposts h3.jp-relatedposts-headline {
|
||||
margin: 0 0 1em 0;
|
||||
display: inline-block;
|
||||
float: right;
|
||||
font-size: 9pt;
|
||||
font-weight: bold;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
div#jp-relatedposts h3.jp-relatedposts-headline em:before {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 100%;
|
||||
min-width: 30px;
|
||||
border-top: 1px solid #ddd;
|
||||
border-top: 1px solid rgba(0,0,0,.2);
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
div#jp-relatedposts h3.jp-relatedposts-headline em {
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Related posts items (wrapping items) */
|
||||
|
||||
div#jp-relatedposts div.jp-relatedposts-items {
|
||||
clear: right;
|
||||
}
|
||||
|
||||
div#jp-relatedposts div.jp-relatedposts-items-visual {
|
||||
margin-left: -20px;
|
||||
}
|
||||
|
||||
/* Related posts item */
|
||||
|
||||
div#jp-relatedposts div.jp-relatedposts-items .jp-relatedposts-post {
|
||||
float: right;
|
||||
width: 33%;
|
||||
margin: 0 0 1em; /* Needs to be same as the main outer wrapper for Related Posts */
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
}
|
||||
|
||||
div#jp-relatedposts div.jp-relatedposts-items-visual .jp-relatedposts-post {
|
||||
padding-left: 20px;
|
||||
/*cursor: pointer;*/
|
||||
filter: alpha(opacity=80);
|
||||
-moz-opacity: .8;
|
||||
opacity: .8;
|
||||
}
|
||||
|
||||
div#jp-relatedposts div.jp-relatedposts-items div.jp-relatedposts-post:hover .jp-relatedposts-post-title a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
div#jp-relatedposts div.jp-relatedposts-items div.jp-relatedposts-post:hover {
|
||||
filter: alpha(opacity=100);
|
||||
-moz-opacity: 1;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Related posts item content */
|
||||
|
||||
div#jp-relatedposts div.jp-relatedposts-items-visual h4.jp-relatedposts-post-title,
|
||||
div#jp-relatedposts div.jp-relatedposts-items p {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
margin: 0;
|
||||
}
|
||||
div#jp-relatedposts div.jp-relatedposts-items-visual div.jp-relatedposts-post-nothumbs {
|
||||
position:relative;
|
||||
}
|
||||
div#jp-relatedposts div.jp-relatedposts-items-visual div.jp-relatedposts-post-nothumbs a.jp-relatedposts-post-aoverlay {
|
||||
position:absolute;
|
||||
top:0;
|
||||
bottom:0;
|
||||
right:0;
|
||||
left:0;
|
||||
display:block;
|
||||
}
|
||||
|
||||
div#jp-relatedposts div.jp-relatedposts-items p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
div#jp-relatedposts div.jp-relatedposts-items-visual h4.jp-relatedposts-post-title {
|
||||
text-transform: none;
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
div#jp-relatedposts div.jp-relatedposts-items .jp-relatedposts-post .jp-relatedposts-post-title a {
|
||||
font-size: inherit;
|
||||
font-weight: normal;
|
||||
text-decoration: none;
|
||||
filter: alpha(opacity=100);
|
||||
-moz-opacity: 1;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
div#jp-relatedposts div.jp-relatedposts-items .jp-relatedposts-post .jp-relatedposts-post-title a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
div#jp-relatedposts div.jp-relatedposts-items .jp-relatedposts-post img.jp-relatedposts-post-img,
|
||||
div#jp-relatedposts div.jp-relatedposts-items .jp-relatedposts-post span {
|
||||
display: block;
|
||||
max-width: 90%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
div#jp-relatedposts div.jp-relatedposts-items-visual .jp-relatedposts-post img.jp-relatedposts-post-img,
|
||||
div#jp-relatedposts div.jp-relatedposts-items-visual .jp-relatedposts-post span {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
div#jp-relatedposts div.jp-relatedposts-items .jp-relatedposts-post .jp-relatedposts-post-context {
|
||||
opacity: .6;
|
||||
}
|
||||
|
||||
/* Behavior when there are thumbnails in visual mode */
|
||||
div#jp-relatedposts div.jp-relatedposts-items-visual div.jp-relatedposts-post-thumbs p.jp-relatedposts-post-excerpt {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Behavior when there are no thumbnails in visual mode */
|
||||
div#jp-relatedposts div.jp-relatedposts-items-visual div.jp-relatedposts-post-nothumbs p.jp-relatedposts-post-excerpt {
|
||||
overflow: hidden;
|
||||
}
|
||||
div#jp-relatedposts div.jp-relatedposts-items-visual div.jp-relatedposts-post-nothumbs span {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
/**
|
||||
* Responsive
|
||||
*/
|
||||
|
||||
@media only screen and (max-width: 640px) {
|
||||
|
||||
div#jp-relatedposts div.jp-relatedposts-items .jp-relatedposts-post {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
div#jp-relatedposts div.jp-relatedposts-items .jp-relatedposts-post:nth-child(3n) {
|
||||
clear: right;
|
||||
}
|
||||
|
||||
div#jp-relatedposts div.jp-relatedposts-items-visual {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 320px) {
|
||||
|
||||
div#jp-relatedposts div.jp-relatedposts-items .jp-relatedposts-post {
|
||||
width: 100%;
|
||||
clear: both;
|
||||
margin: 0 0 1em;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user