Skip to content

Commit

Permalink
Add reply to and boosting option
Browse files Browse the repository at this point in the history
  • Loading branch information
akirk committed Feb 7, 2024
1 parent 3e679ae commit a3da08a
Show file tree
Hide file tree
Showing 9 changed files with 3,269 additions and 112 deletions.
136 changes: 136 additions & 0 deletions feed-parsers/class-feed-parser-activitypub.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ public function __construct( Feed $friends_feed ) {
\add_action( 'trashed_comment', array( $this, 'trashed_comment' ), 10, 2 );

\add_filter( 'friends_reblog_button_label', array( $this, 'friends_reblog_button_label' ), 10, 2 );
\add_filter( 'friends_search_autocomplete', array( $this, 'friends_search_autocomplete' ), 10, 2 );
\add_action( 'friends_after_header', array( $this, 'frontend_reply_form' ) );
\add_action( 'friends_after_header', array( $this, 'frontend_boost_form' ) );
\add_action( 'wp_ajax_friends-in-reply-to-preview', array( $this, 'ajax_in_reply_to_preview' ) );

\add_filter( 'friends_reblog', array( $this, 'unqueue_activitypub_create' ), 9 );
\add_action( 'mastodon_api_reblog', array( $this, 'mastodon_api_reblog' ) );
Expand Down Expand Up @@ -1534,6 +1538,138 @@ public function friends_reblog_button_label( $button_label ) {
return $button_label;
}


/**
* Get metadata for in_reply_to_preview.
*
* @param string $url The url.
*
* @return array|WP_Error The in reply to metadata.
*/
public function get_activitypub_ajax_metadata( $url ) {
$meta = apply_filters( 'friends_get_activitypub_metadata', array(), $url );
if ( is_wp_error( $meta ) ) {
return $meta;
}

if ( ! $meta || ! isset( $meta['attributedTo'] ) ) {
return new \WP_Error( 'no-activitypub', 'No ActivityPub metadata found.' );
}

$html = '<figcaption>' . make_clickable( $meta['id'] ) . '</figcaption>';
$html .= '<blockquote>' . force_balance_tags( wp_kses_post( $meta['content'] ) ) . '</blockquote>';

$webfinger = apply_filters( 'friends_get_activitypub_metadata', array(), $meta['attributedTo'] );
$mention = '';
if ( $webfinger && ! is_wp_error( $webfinger ) ) {
$mention = '@' . $webfinger['preferredUsername'] . '@' . parse_url( $url, PHP_URL_HOST );
}

return array(
'url' => $url,
'html' => $html,
'author' => $meta['attributedTo'],
'mention' => $mention,
);
}

/**
* The Ajax function to fill the in-reply-to-preview.
*/
public function ajax_in_reply_to_preview() {
$url = wp_unslash( $_POST['url'] );

if ( ! wp_parse_url( $url ) ) {
wp_send_json_error();
exit;
}

$meta = $this->get_activitypub_ajax_metadata( $_POST['url'] );

if ( is_wp_error( $meta ) ) {
wp_send_json_error( $meta->get_error_message() );
exit;
}
wp_send_json_success( $meta );
}

public function frontend_reply_form( $args ) {
if ( isset( $_GET['in_reply_to'] ) && wp_parse_url( $_GET['in_reply_to'] ) ) {
$args['in_reply_to'] = $this->get_activitypub_ajax_metadata( $_GET['in_reply_to'] );
$args['form_class'] = 'open';
Friends::template_loader()->get_template_part( 'frontend/activitypub/reply', true, $args );
}
}

public function frontend_boost_form( $args ) {
if ( isset( $_GET['boost'] ) && wp_parse_url( $_GET['boost'] ) ) {
$args['boost'] = $this->get_activitypub_ajax_metadata( $_GET['boost'] );
$args['form_class'] = 'open';
Friends::template_loader()->get_template_part( 'frontend/activitypub/boost', true, $args );
}
}

public function friends_search_autocomplete( $results, $q ) {
function url_truncate( $url, $max_length = 30 ) {
$p = wp_parse_url( $url );
$parts = array( $p['host'] );
if ( trim( $p['path'] ) ) {
$parts = array_merge( $parts, explode( '/', $p['path'] ) );
}

$url = join( '/', $parts );
$reduce = 4;
while ( strlen( $url ) > $max_length ) {
$last_part = array_pop( $parts );
$last_part = substr( $last_part, strlen( $last_part ) - $reduce );
foreach ( $parts as $k => $part ) {
$parts[ $k ] = substr( $part, 0, strlen( $part ) - $reduce );
}
$url = join( '../', array_filter( $parts ) ) . '../..' . $last_part;
array_push( $parts, $last_part );
$reduce = 1;

}

return $url;
}

$url = preg_match( '#^(?:https?:\/\/)?(?:w{3}\.)?[\w-]+(?:\.[\w-]+)+((?:\/[^\s\/]*)*)#i', $q, $m );
$url_with_path = isset( $m[1] ) && $m[1];

if ( ( $url && ! $url_with_path ) || preg_match( '/^@?' . self::ACTIVITYPUB_USERNAME_REGEXP . '$/i', $q ) ) {
$result = '<a href="' . esc_url( admin_url( 'admin.php?page=add-friend&url=' . urlencode( $q ) ) ) . '" class="has-icon-left">';
$result .= '<span class="ab-icon dashicons dashicons-users"></span>';
$result .= 'Follow ';
$result .= ' <small>';
$result .= esc_html( $q );
$result .= '</small></a>';
$results[] = $result;
}

if ( $url_with_path ) {
$result = '<a href="' . esc_url( home_url( '/friends/type/status/?boost=' . urlencode( $q ) ) ) . '" class="has-icon-left">';
$result .= '<span class="ab-icon dashicons dashicons-controls-repeat"></span>';
$result .= 'Boost ';
$result .= ' <small>';
$result .= esc_html( url_truncate( $q ) );
$result .= '</small></a>';
$results[] = $result;
}

if ( $url_with_path ) {
$result = '<a href="' . esc_url( home_url( '/friends/type/status/?in_reply_to=' . urlencode( $q ) ) ) . '" class="has-icon-left">';
$result .= '<span class="ab-icon dashicons dashicons-admin-comments"></span>';
$result .= 'Reply to ';
$result .= ' <small>';
$result .= esc_html( url_truncate( $q ) );
$result .= '</small></a>';
$results[] = $result;
}

return $results;
}

public function mastodon_api_react( $post_id, $reaction ) {
apply_filters( 'friends_react', null, $post_id, $reaction );
}
Expand Down
2,998 changes: 2,997 additions & 1 deletion friends.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion friends.css.map

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions friends.scss
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,19 @@ h2#page-title a.dashicons {
font-color: $dark-color;
font-size: .6rem;
}

#in_reply_to_preview {
background-color: #f7f8f9;
padding: .5em;
margin-top: 1em;
margin-bottom: 1em;
figcaption {
float: right;
a:any-link {
color: #999;
}
}
}
}

img.avatar {
Expand Down
78 changes: 17 additions & 61 deletions includes/class-frontend.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ private function register_hooks() {
add_action( 'wp_ajax_friends-change-post-format', array( $this, 'ajax_change_post_format' ) );
add_action( 'wp_ajax_friends-load-next-page', array( $this, 'ajax_load_next_page' ) );
add_action( 'wp_ajax_friends-autocomplete', array( $this, 'ajax_autocomplete' ) );
add_action( 'wp_ajax_friends-in-reply-to-preview', array( $this, 'ajax_in_reply_to_preview' ) );
add_action( 'friends_search_autocomplete', array( $this, 'autocomplete_user_search' ), 10, 2 );
add_action( 'wp_ajax_friends-star', array( $this, 'ajax_star_friend_user' ) );
add_action( 'wp_ajax_friends-load-comments', array( $this, 'ajax_load_comments' ) );
add_action( 'wp_ajax_friends-reblog', array( $this, 'wp_ajax_reblog' ) );
Expand Down Expand Up @@ -572,76 +572,36 @@ public function ajax_load_next_page() {
}

/**
* Get metadata for in_reply_to_preview.
*
* @param string $url The url.
*
* @return array|WP_Error The in reply to metadata.
* The Ajax function to autocomplete search.
*/
public function get_in_reply_to_metadata( $url ) {
$meta = apply_filters( 'friends_get_activitypub_metadata', array(), $url );
if ( is_wp_error( $meta ) ) {
return $meta;
}

if ( ! $meta || ! isset( $meta['attributedTo'] ) ) {
return new \WP_Error( 'no-activitypub', 'No ActivityPub metadata found.' );
}

$html = 'URL: ' . make_clickable( $meta['id'] );
$html .= '<blockquote>' . force_balance_tags( wp_kses_post( $meta['content'] ) ) . '</blockquote>';

$webfinger = apply_filters( 'friends_get_activitypub_metadata', array(), $meta['attributedTo'] );
$mention = '';
if ( $webfinger && ! is_wp_error( $webfinger ) ) {
$mention = '@' . $webfinger['preferredUsername'] . '@' . parse_url( $url, PHP_URL_HOST );
}
public function ajax_autocomplete() {
$q = wp_unslash( $_POST['q'] );
$results = apply_filters( 'friends_search_autocomplete', array(), $q );

return array(
'url' => $url,
'html' => $html,
'author' => $meta['attributedTo'],
'mention' => $mention,
);
wp_send_json_success( '<li class="menu-item">' . implode( '</li><li class="menu-item">', $results ) . '</li>' );
}

/**
* The Ajax function to fill the in-reply-to-preview.
*/
public function ajax_in_reply_to_preview() {
$url = wp_unslash( $_POST['url'] );

if ( ! wp_parse_url( $url ) ) {
wp_send_json_error();
exit;
}

$meta = $this->get_in_reply_to_metadata( $_POST['url'] );

if ( is_wp_error( $meta ) ) {
wp_send_json_error( $meta->get_error_message() );
exit;
}
wp_send_json_success( $meta );
}
/**
* The Ajax function to autocomplete search.
* The Add user search entries to the autocomplete results.
*
* @param array $results The autocomplete results.
* @param string $q The query.
*
* @return array The autocomplete results.
*/
public function ajax_autocomplete() {
$q = wp_unslash( $_POST['q'] );
public function autocomplete_user_search( $results, $q ) {
$users = User_Query::search( '*' . $q . '*' );
$results = array();

foreach ( $users->get_results() as $friend ) {
$result = '<li class="menu-item">';
$result .= '<a href="' . esc_url( $friend->get_local_friends_page_url() ) . '" class="has-icon-left">';
$result = '<a href="' . esc_url( $friend->get_local_friends_page_url() ) . '" class="has-icon-left">';
$result .= str_ireplace( $q, '<mark>' . $q . '</mark>', $friend->display_name );
$result .= ' <small>';
$result .= str_ireplace( $q, '<mark>' . $q . '</mark>', $friend->user_login );
$result .= '</small></a></li>';
$result .= '</small></a>';
$results[] = $result;
}

wp_send_json_success( implode( PHP_EOL, $results ) );
return $results;
}

/**
Expand Down Expand Up @@ -791,10 +751,6 @@ function ( $feed ) {
'post_format' => $this->post_format,
);

if ( isset( $_GET['in_reply_to'] ) && wp_parse_url( $_GET['in_reply_to'] ) ) {
$args['in_reply_to'] = $args['friends']->frontend->get_in_reply_to_metadata( $_GET['in_reply_to'] );
}

return Friends::template_loader()->get_template_part( 'frontend/index', $this->post_format, $args, false );
}

Expand Down
36 changes: 36 additions & 0 deletions templates/frontend/activitypub/boost.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php
/**
* This template contains the form for boosting a URL from the frontend.
*
* @version 1.0
* @package Friends
*/

?>
<form method="post" action="<?php echo esc_url( admin_url( 'admin-ajax.php' ) ); ?>" id="quick-post-panel" class="<?php echo esc_html( $args['form_class'] ); ?>">
<?php wp_nonce_field( 'friends-reblog' ); ?>
<input type="hidden" name="action" value="friends-reblog" />

<label for="boost"><?php esc_html_e( 'Boost this URL:', 'friends' ); ?></label>
<small>
<?php
echo wp_kses(
sprintf(
// translators: %s is a URL.
__( 'You could also <a href=%s>reply to</a> this.', 'friends' ),
'"?in_reply_to=' . urlencode( ! empty( $args['boost'] ) ? $args['boost']['url'] : '' ) . '"'
),
array(
'a' => array( 'href' => array() ),
)
);
?>
</small>
<input class="form-input" type="url" name="boost" placeholder="<?php esc_attr_e( 'In reply to https://...', 'friends' ); ?>" value="<?php echo esc_attr( ! empty( $args['boost'] ) ? $args['boost']['url'] : '' ); ?>" id="friends_boost" autocomplete="off"/>
<div id="in_reply_to_preview"><?php echo wp_kses_post( ! empty( $args['boost'] ) ? $args['boost']['html'] : '' ); ?></div>

<div class="form-group">
<button class="btn"><?php esc_html_e( 'Boost', 'friends' ); ?></button>
<a href="#" class="quick-post-panel-toggle"><?php /* phpcs:ignore WordPress.WP.I18n.MissingArgDomain */ esc_html_e( 'Cancel' ); ?></a>
</div>
</form>
66 changes: 66 additions & 0 deletions templates/frontend/activitypub/reply.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php
/**
* This template contains the form for replying to a URL from the frontend.
*
* @version 1.0
* @package Friends
*/

?><form method="post" action="<?php echo esc_url( admin_url( 'admin-ajax.php' ) ); ?>" id="quick-post-panel" class="<?php echo esc_html( $args['form_class'] ); ?>">
<?php wp_nonce_field( 'friends_publish' ); ?>
<input type="hidden" name="action" value="friends_publish" />
<input type="hidden" name="format" value="status" />
<input type="hidden" name="status" value="publish" />

<label for="boost"><?php esc_html_e( 'Reply to this URL:', 'friends' ); ?></label>
<small>
<?php
echo wp_kses(
sprintf(
// translators: %s is a URL.
__( 'You could also <a href=%s>boost</a> this.', 'friends' ),
'"?boost=' . urlencode( ! empty( $args['in_reply_to'] ) ? $args['in_reply_to']['url'] : '' ) . '"'
),
array(
'a' => array( 'href' => array() ),
)
);
?>
</small>
<input class="form-input" type="url" name="in_reply_to" placeholder="<?php esc_attr_e( 'In reply to https://...', 'friends' ); ?>" value="<?php echo esc_attr( ! empty( $args['in_reply_to'] ) ? $args['in_reply_to']['url'] : '' ); ?>" id="friends_in_reply_to" autocomplete="off"/>
<div id="in_reply_to_preview"><?php echo wp_kses_post( ! empty( $args['in_reply_to'] ) ? $args['in_reply_to']['html'] : '' ); ?></div>
<p class="description"><?php esc_html_e( 'Click the mentions to copy them into your reply.', 'friends' ); ?></p>
<div class="form-group<?php echo esc_attr( $args['blocks-everywhere'] ? ' blocks-everywhere iso-editor__loading' : '' ); ?>">
<label class="form-label" for="content"><?php esc_html_e( 'Text', 'friends' ); ?></label>
<textarea class="form-input friends-status-content<?php echo esc_attr( $args['blocks-everywhere'] ? ' blocks-everywhere-enabled' : '' ); ?>" name="content" rows="5" cols="70" placeholder="<?php echo /* translators: %s is a user display name. */ esc_attr( sprintf( __( "What's on your mind, %s?", 'friends' ), wp_get_current_user()->display_name ) ); ?>"><?php echo wp_kses_post( ! empty( $args['in_reply_to'] ) ? ( $args['blocks-everywhere'] ? '<!-- wp:paragraph -->' . PHP_EOL . '<p>' . $args['in_reply_to']['mention'] . PHP_EOL . '</p>' . PHP_EOL . '<!-- /wp:paragraph -->' . PHP_EOL : $args['in_reply_to']['mention'] . ' ' ) : '' ); ?></textarea><br />
<?php
do_action( 'friends_post_status_form' );
?>
</div>

<div class="form-group col-4">
<select name="status" class="form-select">
<option value="publish"><?php esc_html_e( 'Visible to everyone', 'friends' ); ?></option>
<option value="private"><?php esc_html_e( 'Only visible to my friends', 'friends' ); ?></option>
</select>
</div>

<div class="form-group col-4">
<p>
<?php
echo esc_html(
sprintf(
// translators: %s is the name of a post format.
__( 'Post Format: %s', 'friends' ),
__( 'Status' ) // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
)
);
?>
</p>
</div>

<div class="form-group">
<button class="btn"><?php esc_html_e( 'Publish Reply', 'friends' ); ?></button>
<a href="#" class="quick-post-panel-toggle"><?php /* phpcs:ignore WordPress.WP.I18n.MissingArgDomain */ esc_html_e( 'Cancel' ); ?></a>
</div>
</form>
Loading

0 comments on commit a3da08a

Please sign in to comment.