Skip to content
Merged
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
4 changes: 4 additions & 0 deletions .github/changelog/3036-from-description
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

Add a "Posts and Replies" tab bar for author archives that filters between posts and replies, similar to Mastodon's profile view.
26 changes: 26 additions & 0 deletions build/posts-and-replies/block.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions build/posts-and-replies/index.asset.php

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions build/posts-and-replies/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

50 changes: 50 additions & 0 deletions build/posts-and-replies/render.php

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions build/posts-and-replies/style-index-rtl.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions build/posts-and-replies/style-index.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

56 changes: 56 additions & 0 deletions includes/class-blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ public static function init() {
self::register_patterns();
self::register_templates();

\add_action( 'pre_get_posts', array( self::class, 'filter_query_loop_vars' ) );

\add_action( 'load-post-new.php', array( self::class, 'handle_in_reply_to_get_param' ) );
// Add editor plugin.
\add_action( 'enqueue_block_editor_assets', array( self::class, 'enqueue_editor_assets' ) );
Expand Down Expand Up @@ -140,6 +142,7 @@ public static function register_blocks() {
\register_block_type_from_metadata( ACTIVITYPUB_PLUGIN_DIR . '/build/extra-fields' );
\register_block_type_from_metadata( ACTIVITYPUB_PLUGIN_DIR . '/build/follow-me' );
\register_block_type_from_metadata( ACTIVITYPUB_PLUGIN_DIR . '/build/followers' );
\register_block_type_from_metadata( ACTIVITYPUB_PLUGIN_DIR . '/build/posts-and-replies' );

// Only register the Following block if the Following feature is enabled.
if ( '1' === \get_option( 'activitypub_following_ui', '0' ) ) {
Expand Down Expand Up @@ -249,6 +252,7 @@ public static function register_templates() {
<!-- wp:spacer {"height":"32px"} -->
<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>
<!-- /wp:spacer -->
<!-- wp:activitypub/posts-and-replies /-->
<!-- wp:query {"queryId":0,"query":{"perPage":10,"pages":0,"offset":0,"postType":"post","order":"desc","orderBy":"date","author":"","search":"","exclude":[],"sticky":"","inherit":true}} -->
<div class="wp-block-query">
<!-- wp:post-template -->
Expand Down Expand Up @@ -1154,4 +1158,56 @@ private static function to_block( $tag, $html ) {

return \get_comment_delimited_block_content( $block_type, $block_attrs, \trim( $html ) );
}

/**
* Filter the main query to exclude replies.
*
* When the "Posts" tab is active (default), adds a WHERE clause to
* exclude posts containing the `activitypub/reply` block. This
* filters the main query so that Query Loop blocks with
* `inherit: true` also pick up the filter.
*
* @since unreleased
*
* @param WP_Query $query The WP_Query instance.
*/
public static function filter_query_loop_vars( $query ) {
if ( ! $query->is_main_query() || $query->is_singular() ) {
return;
}

// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$active_tab = isset( $_GET['filter'] ) ? \sanitize_key( $_GET['filter'] ) : 'posts';

// Only filter when the "Posts" tab is active (default).
if ( 'posts-and-replies' === $active_tab ) {
return;
}

\add_filter( 'posts_where', array( self::class, 'exclude_replies_where' ) );
}

/**
* Exclude posts containing the activitypub/reply block.
*
* Removes itself after the first execution to avoid
* affecting secondary queries on the same page.
*
* @since unreleased
*
* @param string $where The WHERE clause.
* @return string Modified WHERE clause.
*/
public static function exclude_replies_where( $where ) {
\remove_filter( 'posts_where', array( self::class, 'exclude_replies_where' ) );

global $wpdb;

$where .= $wpdb->prepare(
" AND {$wpdb->posts}.post_content NOT LIKE %s",
'%<!-- wp:activitypub/reply%'
);

return $where;
}
}
18 changes: 18 additions & 0 deletions src/posts-and-replies/block.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"name": "activitypub/posts-and-replies",
"apiVersion": 3,
"version": "unreleased",
"title": "Posts and Replies",
"category": "widgets",
"description": "Display a tab bar to filter between posts only and posts with replies on author archives.",
"textdomain": "activitypub",
"icon": "admin-post",
"keywords": [ "fediverse", "activitypub", "posts", "replies", "tabs" ],
"supports": {
"html": false
},
"editorScript": "file:./index.js",
"style": [ "file:./style-index.css" ],
"render": "file:./render.php"
}
25 changes: 25 additions & 0 deletions src/posts-and-replies/edit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useBlockProps } from '@wordpress/block-editor';
import { Placeholder } from '@wordpress/components';
import { __ } from '@wordpress/i18n';

/**
* Edit component for Posts and Replies block.
*
* @return {Element} Component element.
*/
export default function Edit() {
const blockProps = useBlockProps();

return (
<div { ...blockProps }>
<Placeholder
icon="admin-post"
label={ __( 'Posts and Replies', 'activitypub' ) }
instructions={ __(
'Displays a tab bar to filter between "Posts" (excluding replies) and "Posts & Replies" on author archives. Place above a Query Loop block with "Inherit query from template" enabled.',
'activitypub'
) }
/>
</div>
);
}
6 changes: 6 additions & 0 deletions src/posts-and-replies/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { registerBlockType } from '@wordpress/blocks';
import edit from './edit';
import metadata from './block.json';
import './style.scss';

registerBlockType( metadata, { edit, save: () => null } );
50 changes: 50 additions & 0 deletions src/posts-and-replies/render.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php
/**
* Server-side rendering of the `activitypub/posts-and-replies` block.
*
* Renders a tab bar that controls query filtering via URL parameter.
* Works with a sibling `core/query` block that inherits from the template.
* The actual query filtering happens in {@see Blocks::filter_query_loop_vars()}.
*
* @since unreleased
*
* @package Activitypub
*/

use function Activitypub\is_activitypub_request;

if ( is_activitypub_request() || \is_feed() ) {
return;
}

// Determine active tab from URL parameter.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$active_tab = isset( $_GET['filter'] ) ? \sanitize_key( $_GET['filter'] ) : 'posts';
if ( ! \in_array( $active_tab, array( 'posts', 'posts-and-replies' ), true ) ) {
$active_tab = 'posts';
}

$current_url = \remove_query_arg( array( 'filter', 'paged' ) );
$posts_url = \add_query_arg( 'filter', 'posts', $current_url );
$all_url = \add_query_arg( 'filter', 'posts-and-replies', $current_url );

$wrapper_attributes = \get_block_wrapper_attributes();
?>
<nav <?php echo $wrapper_attributes; // phpcs:ignore WordPress.Security.EscapeOutput ?> aria-label="<?php \esc_attr_e( 'Post filtering', 'activitypub' ); ?>">
<div class="ap-tabs">
<a
class="ap-tabs__tab <?php echo 'posts' === $active_tab ? 'is-active' : ''; ?>"
href="<?php echo \esc_url( $posts_url ); ?>"
<?php echo 'posts' === $active_tab ? 'aria-current="page"' : ''; ?>
>
<?php \esc_html_e( 'Posts', 'activitypub' ); ?>
</a>
<a
class="ap-tabs__tab <?php echo 'posts-and-replies' === $active_tab ? 'is-active' : ''; ?>"
href="<?php echo \esc_url( $all_url ); ?>"
<?php echo 'posts-and-replies' === $active_tab ? 'aria-current="page"' : ''; ?>
>
<?php \esc_html_e( 'Posts & Replies', 'activitypub' ); ?>
</a>
</div>
</nav>
34 changes: 34 additions & 0 deletions src/posts-and-replies/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
.wp-block-activitypub-posts-and-replies {

.ap-tabs {
display: flex;
gap: 0;
border-bottom: 1px solid currentcolor;
margin-bottom: 1.5em;
}

.ap-tabs__tab {
padding: 0.5em 1em;
border: none;
background: none;
cursor: pointer;
font-size: inherit;
font-family: inherit;
color: inherit;
text-decoration: none;
opacity: 0.6;
border-bottom: 2px solid transparent;
margin-bottom: -1px;
transition: opacity 0.2s, border-color 0.2s;

&:hover,
&:focus-visible {
opacity: 1;
}

&.is-active {
opacity: 1;
border-bottom-color: currentcolor;
}
}
}
Loading