Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a Latest Comments block #1931

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions blocks/library/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import './code';
import './html';
import './freeform';
import './latest-posts';
import './latest-comments';
import './cover-image';
import './cover-text';
import './verse';
27 changes: 27 additions & 0 deletions blocks/library/latest-comments/block.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
.wp-block-latest-comments.alignright {
margin-left: 2em;
}
.wp-block-latest-comments.alignleft {
margin-right: 2em;
}

.wp-block-latest-comments.has-avatars > li {
min-height: 48px;
}
.wp-block-latest-comments > li {
margin-bottom: 1em;
}

.wp-block-latest-comments__comment-timestamp {
display: block;
}

.wp-block-latest-comments .avatar,
.wp-block-latest-comments__comment-avatar {
display: block;
float: left;
height: 48px;
width: 48px;
border-radius: 24px;
margin-right: 5px;
}
20 changes: 20 additions & 0 deletions blocks/library/latest-comments/data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Returns a Promise with the latest comments or an error on failure.
*
* @param {Number} commentsToShow Number of comments to display.
*
* @returns {wp.api.collections.Comments} Returns a Promise with the latest comments.
*/
export function getLatestComments( commentsToShow = 5 ) {
const commentsCollection = new wp.api.collections.Comments();

const comments = commentsCollection.fetch( {
data: {
per_page: commentsToShow,
_embed: true,
},
} );

return comments;
}

227 changes: 227 additions & 0 deletions blocks/library/latest-comments/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
/**
* External dependencies
*/
import { keys, max } from 'lodash';

/**
* WordPress dependencies
*/
import { Component } from '@wordpress/element';
import { Placeholder, Spinner } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import moment from 'moment';
import classnames from 'classnames';

/**
* Internal dependencies
*/
import './style.scss';
import './block.scss';
import { registerBlockType } from '../../api';
import { getLatestComments } from './data.js';
import InspectorControls from '../../inspector-controls';
import TextControl from '../../inspector-controls/text-control';
import ToggleControl from '../../inspector-controls/toggle-control';
import BlockDescription from '../../block-description';
import BlockControls from '../../block-controls';
import BlockAlignmentToolbar from '../../block-alignment-toolbar';

const MIN_COMMENTS = 1;
const MAX_COMMENTS = 100;

registerBlockType( 'core/latest-comments', {
title: __( 'Latest Comments' ),

icon: 'list-view',

category: 'widgets',

defaultAttributes: {
commentsToShow: 5,
displayAvatar: true,
displayExcerpt: true,
displayTimestamp: true,
},

getEditWrapperProps( attributes ) {
const { align } = attributes;
if ( 'left' === align || 'right' === align || 'wide' === align || 'full' === align ) {
return { 'data-align': align };
}
},

edit: class extends Component {
constructor() {
super( ...arguments );
this.toggleHandler = this.toggleHandler.bind( this );
this.changeCommentsToShow = this.changeCommentsToShow.bind( this );

const { commentsToShow } = this.props.attributes;

this.state = {
latestComments: [],
};

this.latestCommentsRequest = getLatestComments( commentsToShow );

this.latestCommentsRequest
.then( latestComments => this.setState( { latestComments } ) );
}

componentWillReceiveProps( nextProps ) {
const { commentsToShow: commentToShowCurrent } = this.props.attributes;
const { commentsToShow: commentsToShowNext } = nextProps.attributes;
const { setAttributes } = this.props;

if ( commentToShowCurrent === commentsToShowNext ) {
return;
}

if ( commentsToShowNext >= MIN_COMMENTS && commentsToShowNext <= MAX_COMMENTS ) {
this.latestCommentsRequest = getLatestComments( commentsToShowNext );

this.latestCommentsRequest
.then( latestComments => this.setState( { latestComments } ) );

setAttributes( { commentsToShow: commentsToShowNext } );
}
}

toggleHandler( propName ) {
return () => {
const value = this.props.attributes[ propName ];
const { setAttributes } = this.props;

setAttributes( { [ propName ]: ! value } );
};
}

changeCommentsToShow( commentsToShow ) {
const { setAttributes } = this.props;

setAttributes( { commentsToShow: parseInt( commentsToShow, 10 ) || 0 } );
}

render() {
const { latestComments } = this.state;
const { setAttributes } = this.props;

if ( ! latestComments.length ) {
return (
<Placeholder
icon="admin-post"
label={ __( 'Latest Comments' ) }
>
<Spinner />
</Placeholder>
);
}

// Removing comments from display should be instant.
const commentsDifference = latestComments.length - this.props.attributes.commentsToShow;
if ( commentsDifference > 0 ) {
latestComments.splice( this.props.attributes.commentsToShow, commentsDifference );
}

const { focus } = this.props;
const { align, displayAvatar, displayTimestamp, displayExcerpt } = this.props.attributes;

return [
focus && (
<BlockControls key="controls">
<BlockAlignmentToolbar
value={ align }
onChange={ ( nextAlign ) => {
setAttributes( { align: nextAlign } );
} }
controls={ [ 'left', 'center', 'right', 'wide', 'full' ] }
/>
</BlockControls>
),
focus && (
<InspectorControls key="inspector">
<BlockDescription>
<p>{ __( 'Shows a list of your site\'s most recent comments.' ) }</p>
</BlockDescription>
<h3>{ __( 'Latest Comments Settings' ) }</h3>

<ToggleControl
label={ __( 'Display avatar' ) }
checked={ displayAvatar }
onChange={ this.toggleHandler( 'displayAvatar' ) }
/>

<ToggleControl
label={ __( 'Display timestamp' ) }
checked={ displayTimestamp }
onChange={ this.toggleHandler( 'displayTimestamp' ) }
/>

<ToggleControl
label={ __( 'Display excerpt' ) }
checked={ this.props.attributes.displayExcerpt }
onChange={ this.toggleHandler( 'displayExcerpt' ) }
/>

<TextControl
label={ __( 'Number of comments to show' ) }
type="number"
min={ MIN_COMMENTS }
max={ MAX_COMMENTS }
value={ this.props.attributes.commentsToShow }
onChange={ ( value ) => this.changeCommentsToShow( value ) }
/>
</InspectorControls>
),
<ul
className={ classnames( this.props.className, {
'has-avatars': displayAvatar,
} ) }
key="latest-comments">
{ latestComments.map( ( comment, i ) => {
let maxSize;
if ( displayAvatar ) {
maxSize = max( keys( comment.author_avatar_urls ) );
}

let author;
if ( comment.author && comment._embedded.author[ 0 ] ) {
author = <a className={ `${ this.props.className }__comment-author` } href={ comment._embedded.author[ 0 ].link } target="_blank" rel="noopener noreferrer">{ comment._embedded.author[ 0 ].name }</a>;
} else if ( comment.author_url ) {
author = <a className={ `${ this.props.className }__comment-author` } href={ comment.author_url } target="_blank" rel="noopener noreferrer">{ comment.author_name }</a>;
} else {
author = <a className={ `${ this.props.className }__comment-author` }>{ comment.author_name }</a>;
}

return <li key={ i }>
{ displayAvatar && maxSize &&
<img className={ `${ this.props.className }__comment-avatar` } alt={ comment.author_name } src={ comment.author_avatar_urls[ maxSize ] } />
}
{ author }
{ __( ' on ' ) }
<a className={ `${ this.props.className }__comment-link` } href={ comment.link } target="_blank" rel="noopener noreferrer">{ comment._embedded.up[ 0 ].title.rendered.trim() || __( '(Untitled)' ) }</a>
{ displayTimestamp && comment.date_gmt &&
<time dateTime={ moment( comment.date_gmt ).utc().format() } className={ `${ this.props.className }__comment-timestamp` }>
{ moment( comment.date_gmt ).local().format( 'MMM DD h:mm A' ) }
</time>
}
{ displayExcerpt && comment.content &&
<div className={ `${ this.props.className }__comment-excerpt` } dangerouslySetInnerHTML={ { __html: comment.content.rendered } } />
}
</li>;
} ) }
</ul>,
];
}

componentWillUnmount() {
if ( this.latestCommentsRequest.state() === 'pending' ) {
this.latestCommentsRequest.abort();
}
}
},

save() {
return null;
},
} );
107 changes: 107 additions & 0 deletions blocks/library/latest-comments/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<?php
/**
* Server-side rendering of the `core/latest-comments` block.
*
* @package gutenberg
*/

/**
* Renders the `core/latest-comments` block on server.
*
* @param array $attributes The block attributes.
*
* @return string Returns the post content with latest comments added.
*/
function gutenberg_render_block_core_latest_comments( $attributes ) {

$default_attributes = array(
'commentsToShow' => 5,
'displayAvatar' => true,
'displayExcerpt' => true,
'displayTimestamp' => true,
);
$attributes = array_merge( $default_attributes, $attributes );

// Basic attribute validation.
if (
! is_numeric( $attributes['commentsToShow'] ) ||
$attributes['commentsToShow'] < 0 &&
$attributes['commentsToShow'] > 100
) {
$attributes['commentsToShow'] = $default_attributes['commentsToShow'];
}

$align = 'center';
if ( isset( $attributes['align'] ) && in_array( $attributes['align'], array( 'left', 'right', 'wide', 'full' ), true ) ) {
$align = $attributes['align'];
}

/** This filter is documented in wp-includes/widgets/class-wp-widget-recent-comments.php */
$comments = get_comments( apply_filters( 'widget_comments_args', array(
'number' => $attributes['commentsToShow'],
'status' => 'approve',
'post_status' => 'publish',
) ) );

$list_items_markup = '';
if ( ! empty( $comments ) ) {

// Prime cache for associated posts. This is copied from \WP_Widget_Recent_Comments::widget().
$post_ids = array_unique( wp_list_pluck( $comments, 'comment_post_ID' ) );
_prime_post_caches( $post_ids, strpos( get_option( 'permalink_structure' ), '%category%' ), false );

foreach ( $comments as $comment ) {
$list_items_markup .= '<li class="recentcomments">';
if ( $attributes['displayAvatar'] ) {
$avatar = get_avatar( $comment, 48, '', '', array(
'class' => 'wp-block-latest-comments__comment-avatar',
) );
if ( $avatar ) {
$list_items_markup .= $avatar;
}
}

$author_url = get_comment_author_url( $comment );
if ( empty( $author_url ) && ! empty( $comment->user_id ) ) {
$author_url = get_author_posts_url( $comment->user_id );
}
if ( $author_url ) {
$list_items_markup .= '<a class="wp-block-latest-comments__comment-author" href="' . esc_url( $author_url ) . '">' . get_comment_author( $comment ) . '</a>';
} else {
$list_items_markup .= '<a class="wp-block-latest-comments__comment-author">' . get_comment_author( $comment ) . '</a>';
}

$list_items_markup .= __( ' on ', 'gutenberg' );
$list_items_markup .= '<a class="wp-block-latest-comments__comment-link" href="' . esc_url( get_comment_link( $comment ) ) . '">' . get_the_title( $comment->comment_post_ID ) . '</a>';

if ( $attributes['displayTimestamp'] ) {
$list_items_markup .= sprintf(
'<time datetime="%1$s" class="wp-block-latest-comments__comment-timestamp">%2$s</time>',
esc_attr( get_comment_date( 'c', $comment ) ),
esc_html( get_comment_date( '', $comment ) )
);
}
if ( $attributes['displayExcerpt'] ) {
$list_items_markup .= '<div class="wp-block-latest-comments__comment-excerpt">' . wpautop( get_comment_excerpt( $comment ) ) . '</div>';
}
$list_items_markup .= '</li>';
}
}

$class = "wp-block-latest-comments align{$align}";
if ( $attributes['displayAvatar'] ) {
$class .= ' has-avatars';
}

$block_content = sprintf(
'<ul class="%1$s">%2$s</ul>',
esc_attr( $class ),
$list_items_markup
);

return $block_content;
}

register_block_type( 'core/latest-comments', array(
'render_callback' => 'gutenberg_render_block_core_latest_comments',
) );
Loading