From a775b7cbc23efae79965c612ad4b8cc0c2dc472d Mon Sep 17 00:00:00 2001 From: Pooja Bhimani <82029773+poojabhimani12@users.noreply.github.com> Date: Wed, 23 Oct 2024 12:31:16 -0400 Subject: [PATCH] Add Inline comment experimental flag (#60622) Co-authored-by: MD-sunilprajapati Co-authored-by: poojabhimani12 Co-authored-by: rishishah-multidots Co-authored-by: ingeniumed Co-authored-by: ellatrix Co-authored-by: tyxla Co-authored-by: ciampo Co-authored-by: mtias Co-authored-by: jasmussen Co-authored-by: youknowriad Co-authored-by: annezazu Co-authored-by: jameskoster --- backport-changelog/6.8/7488.md | 3 + backport-changelog/6.8/7498.md | 3 + lib/compat/wordpress-6.8/block-comments.php | 70 +++ ...-gutenberg-rest-comment-controller-6-8.php | 135 ++++++ lib/experimental/editor-settings.php | 3 + lib/experiments-page.php | 12 + lib/load.php | 4 + .../block-settings-dropdown.js | 4 + .../components/block-settings-menu/index.js | 3 + .../collab/block-comment-icon-slot.js | 12 + .../collab/block-comment-icon-toolbar-slot.js | 12 + packages/block-editor/src/private-apis.js | 5 +- .../components/collab-sidebar/add-comment.js | 124 ++++++ .../collab-sidebar/comment-button-toolbar.js | 29 ++ .../collab-sidebar/comment-button.js | 31 ++ .../src/components/collab-sidebar/comments.js | 404 ++++++++++++++++++ .../components/collab-sidebar/constants.js | 1 + .../src/components/collab-sidebar/index.js | 299 +++++++++++++ .../src/components/collab-sidebar/style.scss | 111 +++++ .../src/components/collab-sidebar/utils.js | 9 + .../editor/src/components/header/index.js | 2 + packages/editor/src/style.scss | 1 + 22 files changed, 1276 insertions(+), 1 deletion(-) create mode 100644 backport-changelog/6.8/7488.md create mode 100644 backport-changelog/6.8/7498.md create mode 100644 lib/compat/wordpress-6.8/block-comments.php create mode 100644 lib/compat/wordpress-6.8/class-gutenberg-rest-comment-controller-6-8.php create mode 100644 packages/block-editor/src/components/collab/block-comment-icon-slot.js create mode 100644 packages/block-editor/src/components/collab/block-comment-icon-toolbar-slot.js create mode 100644 packages/editor/src/components/collab-sidebar/add-comment.js create mode 100644 packages/editor/src/components/collab-sidebar/comment-button-toolbar.js create mode 100644 packages/editor/src/components/collab-sidebar/comment-button.js create mode 100644 packages/editor/src/components/collab-sidebar/comments.js create mode 100644 packages/editor/src/components/collab-sidebar/constants.js create mode 100644 packages/editor/src/components/collab-sidebar/index.js create mode 100644 packages/editor/src/components/collab-sidebar/style.scss create mode 100644 packages/editor/src/components/collab-sidebar/utils.js diff --git a/backport-changelog/6.8/7488.md b/backport-changelog/6.8/7488.md new file mode 100644 index 00000000000000..a588bef0e01796 --- /dev/null +++ b/backport-changelog/6.8/7488.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/7488 + +* https://github.com/WordPress/gutenberg/pull/60622 \ No newline at end of file diff --git a/backport-changelog/6.8/7498.md b/backport-changelog/6.8/7498.md new file mode 100644 index 00000000000000..6c903246166b64 --- /dev/null +++ b/backport-changelog/6.8/7498.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/7498 + +* https://github.com/WordPress/gutenberg/pull/60622 \ No newline at end of file diff --git a/lib/compat/wordpress-6.8/block-comments.php b/lib/compat/wordpress-6.8/block-comments.php new file mode 100644 index 00000000000000..ff9f03b69aa1c3 --- /dev/null +++ b/lib/compat/wordpress-6.8/block-comments.php @@ -0,0 +1,70 @@ +query_vars['type'] ) && '' === $query->query_vars['type'] ) { + $query->set( 'type', '' ); + + add_filter( + 'comments_clauses', + function ( $clauses ) { + global $wpdb; + // Exclude comments of type 'block_comment' + $clauses['where'] .= " AND {$wpdb->comments}.comment_type != 'block_comment'"; + return $clauses; + } + ); + } + } + add_action( 'pre_get_comments', 'exclude_block_comments_from_admin' ); +} diff --git a/lib/compat/wordpress-6.8/class-gutenberg-rest-comment-controller-6-8.php b/lib/compat/wordpress-6.8/class-gutenberg-rest-comment-controller-6-8.php new file mode 100644 index 00000000000000..981b9dbd840319 --- /dev/null +++ b/lib/compat/wordpress-6.8/class-gutenberg-rest-comment-controller-6-8.php @@ -0,0 +1,135 @@ + 401 ) + ); + } + + /** + * Filters whether comments can be created via the REST API without authentication. + * + * Enables creating comments for anonymous users. + * + * @since 4.7.0 + * + * @param bool $allow_anonymous Whether to allow anonymous comments to + * be created. Default `false`. + * @param WP_REST_Request $request Request used to generate the + * response. + */ + $allow_anonymous = apply_filters( 'rest_allow_anonymous_comments', false, $request ); + + if ( ! $allow_anonymous ) { + return new WP_Error( + 'rest_comment_login_required', + __( 'Sorry, you must be logged in to comment.' ), + array( 'status' => 401 ) + ); + } + } + + // Limit who can set comment `author`, `author_ip` or `status` to anything other than the default. + if ( isset( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( 'moderate_comments' ) ) { + return new WP_Error( + 'rest_comment_invalid_author', + /* translators: %s: Request parameter. */ + sprintf( __( "Sorry, you are not allowed to edit '%s' for comments." ), 'author' ), + array( 'status' => rest_authorization_required_code() ) + ); + } + + if ( isset( $request['author_ip'] ) && ! current_user_can( 'moderate_comments' ) ) { + if ( empty( $_SERVER['REMOTE_ADDR'] ) || $request['author_ip'] !== $_SERVER['REMOTE_ADDR'] ) { + return new WP_Error( + 'rest_comment_invalid_author_ip', + /* translators: %s: Request parameter. */ + sprintf( __( "Sorry, you are not allowed to edit '%s' for comments." ), 'author_ip' ), + array( 'status' => rest_authorization_required_code() ) + ); + } + } + + if ( isset( $request['status'] ) && ! current_user_can( 'moderate_comments' ) ) { + return new WP_Error( + 'rest_comment_invalid_status', + /* translators: %s: Request parameter. */ + sprintf( __( "Sorry, you are not allowed to edit '%s' for comments." ), 'status' ), + array( 'status' => rest_authorization_required_code() ) + ); + } + + if ( empty( $request['post'] ) ) { + return new WP_Error( + 'rest_comment_invalid_post_id', + __( 'Sorry, you are not allowed to create this comment without a post.' ), + array( 'status' => 403 ) + ); + } + + $post = get_post( (int) $request['post'] ); + + if ( ! $post ) { + return new WP_Error( + 'rest_comment_invalid_post_id', + __( 'Sorry, you are not allowed to create this comment without a post.' ), + array( 'status' => 403 ) + ); + } + + if ( 'draft' === $post->post_status && 'comment' === $request['comment_type'] ) { + return new WP_Error( + 'rest_comment_draft_post', + __( 'Sorry, you are not allowed to create a comment on this post.' ), + array( 'status' => 403 ) + ); + } + + if ( 'trash' === $post->post_status ) { + return new WP_Error( + 'rest_comment_trash_post', + __( 'Sorry, you are not allowed to create a comment on this post.' ), + array( 'status' => 403 ) + ); + } + + if ( ! $this->check_read_post_permission( $post, $request ) ) { + return new WP_Error( + 'rest_cannot_read_post', + __( 'Sorry, you are not allowed to read the post for this comment.' ), + array( 'status' => rest_authorization_required_code() ) + ); + } + + if ( ! comments_open( $post->ID ) && 'comment' === $request['comment_type'] ) { + return new WP_Error( + 'rest_comment_closed', + __( 'Sorry, comments are closed for this item.' ), + array( 'status' => 403 ) + ); + } + + return true; + } +} + +add_action( + 'rest_api_init', + function () { + $controller = new Gutenberg_REST_Comment_Controller_6_8(); + $controller->register_routes(); + } +); diff --git a/lib/experimental/editor-settings.php b/lib/experimental/editor-settings.php index 126382f85a513e..afc6d7e220f676 100644 --- a/lib/experimental/editor-settings.php +++ b/lib/experimental/editor-settings.php @@ -28,6 +28,9 @@ function gutenberg_enable_experiments() { if ( gutenberg_is_experiment_enabled( 'gutenberg-full-page-client-side-navigation' ) ) { wp_add_inline_script( 'wp-block-library', 'window.__experimentalFullPageClientSideNavigation = true', 'before' ); } + if ( $gutenberg_experiments && array_key_exists( 'gutenberg-block-comment', $gutenberg_experiments ) ) { + wp_add_inline_script( 'wp-block-editor', 'window.__experimentalEnableBlockComment = true', 'before' ); + } if ( $gutenberg_experiments && array_key_exists( 'gutenberg-quick-edit-dataviews', $gutenberg_experiments ) ) { wp_add_inline_script( 'wp-block-editor', 'window.__experimentalQuickEditDataViews = true', 'before' ); } diff --git a/lib/experiments-page.php b/lib/experiments-page.php index 27a54b920f4d52..946b68283a3e0b 100644 --- a/lib/experiments-page.php +++ b/lib/experiments-page.php @@ -163,6 +163,18 @@ function gutenberg_initialize_experiments_settings() { ) ); + add_settings_field( + 'gutenberg-block-comment', + __( 'Block Comments', 'gutenberg' ), + 'gutenberg_display_experiment_field', + 'gutenberg-experiments', + 'gutenberg_experiments_section', + array( + 'label' => __( 'Enable multi-user commenting on blocks', 'gutenberg' ), + 'id' => 'gutenberg-block-comment', + ) + ); + add_settings_field( 'gutenberg-media-processing', __( 'Client-side media processing', 'gutenberg' ), diff --git a/lib/load.php b/lib/load.php index c26160eba2b67d..2c8a0fd0347c92 100644 --- a/lib/load.php +++ b/lib/load.php @@ -46,6 +46,10 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/compat/wordpress-6.7/class-gutenberg-rest-server.php'; require __DIR__ . '/compat/wordpress-6.7/rest-api.php'; + // WordPress 6.8 compat. + require __DIR__ . '/compat/wordpress-6.8/block-comments.php'; + require __DIR__ . '/compat/wordpress-6.8/class-gutenberg-rest-comment-controller-6-8.php'; + // Plugin specific code. require_once __DIR__ . '/class-wp-rest-global-styles-controller-gutenberg.php'; require_once __DIR__ . '/class-wp-rest-edit-site-export-controller-gutenberg.php'; diff --git a/packages/block-editor/src/components/block-settings-menu/block-settings-dropdown.js b/packages/block-editor/src/components/block-settings-menu/block-settings-dropdown.js index ac2b99ac2bb620..5b5360cc48a8fe 100644 --- a/packages/block-editor/src/components/block-settings-menu/block-settings-dropdown.js +++ b/packages/block-editor/src/components/block-settings-menu/block-settings-dropdown.js @@ -19,6 +19,7 @@ import { pipe, useCopyToClipboard } from '@wordpress/compose'; * Internal dependencies */ import BlockActions from '../block-actions'; +import __unstableCommentIconFill from '../../components/collab/block-comment-icon-slot'; import BlockHTMLConvertButton from './block-html-convert-button'; import __unstableBlockSettingsMenuFirstItem from './block-settings-menu-first-item'; import BlockSettingsMenuControls from '../block-settings-menu-controls'; @@ -294,6 +295,9 @@ export function BlockSettingsDropdown( { ) } + <__unstableCommentIconFill.Slot + fillProps={ { onClose } } + /> { canCopyStyles && ! isContentOnly && ( diff --git a/packages/block-editor/src/components/block-settings-menu/index.js b/packages/block-editor/src/components/block-settings-menu/index.js index 0b0b1a775e736f..50e8abe09d018b 100644 --- a/packages/block-editor/src/components/block-settings-menu/index.js +++ b/packages/block-editor/src/components/block-settings-menu/index.js @@ -7,10 +7,13 @@ import { ToolbarGroup, ToolbarItem } from '@wordpress/components'; * Internal dependencies */ import BlockSettingsDropdown from './block-settings-dropdown'; +import __unstableCommentIconToolbarFill from '../../components/collab/block-comment-icon-toolbar-slot'; export function BlockSettingsMenu( { clientIds, ...props } ) { return ( + <__unstableCommentIconToolbarFill.Slot /> + { ( toggleProps ) => ( { + const { getSettings } = select( blockEditorStore ); + const { __experimentalDiscussionSettings } = getSettings(); + const selectedBlock = select( blockEditorStore ).getSelectedBlock(); + const userData = select( coreStore ).getCurrentUser(); + return { + defaultAvatar: __experimentalDiscussionSettings?.avatarURL, + clientId: selectedBlock?.clientId, + blockCommentId: selectedBlock?.attributes?.blockCommentId, + showAddCommentBoard: showCommentBoard, + currentUser: userData, + }; + } ); + + const userAvatar = + currentUser && currentUser.avatar_urls && currentUser.avatar_urls[ 48 ] + ? currentUser.avatar_urls[ 48 ] + : defaultAvatar; + + useEffect( () => { + setInputComment( '' ); + }, [ clientId ] ); + + const handleCancel = () => { + setShowCommentBoard( false ); + setInputComment( '' ); + }; + + if ( ! showAddCommentBoard || ! clientId || undefined !== blockCommentId ) { + return null; + } + + return ( + + + + + { currentUser?.name ?? '' } + + + + + + + + + + ); +} + +/** + * Renders the header of a comment in the collaboration sidebar. + * + * @param {Object} props - The component props. + * @param {Object} props.thread - The comment thread object. + * @param {Function} props.onResolve - The function to resolve the comment. + * @param {Function} props.onEdit - The function to edit the comment. + * @param {Function} props.onDelete - The function to delete the comment. + * @param {Function} props.onReply - The function to reply to the comment. + * @param {string} props.status - The status of the comment. + * @return {JSX.Element} The rendered comment header. + */ +function CommentHeader( { + thread, + onResolve, + onEdit, + onDelete, + onReply, + status, +} ) { + const dateSettings = getDateSettings(); + const [ dateTimeFormat = dateSettings.formats.time ] = useEntityProp( + 'root', + 'site', + 'time_format' + ); + + const actions = [ + { + title: _x( 'Edit', 'Edit comment' ), + onClick: onEdit, + }, + { + title: _x( 'Delete', 'Delete comment' ), + onClick: onDelete, + }, + { + title: _x( 'Reply', 'Reply on a comment' ), + onClick: onReply, + }, + ]; + + const moreActions = actions.filter( ( item ) => item.onClick ); + + return ( + + + + + { thread.author_name } + + + + + { status !== 'approved' && ( + + { 0 === thread.parent && onResolve && ( +