From d560a45107874fbaf1960065ac30c9e2981c91dc Mon Sep 17 00:00:00 2001 From: Allison Levine Date: Mon, 1 Nov 2021 10:27:44 -0400 Subject: [PATCH] Publicize: Add new publicize icon toggle component (#20957) - Add new ConnectionToggle component that includes service profile picture, username, icon, and toggle. - Add new ConnectionIcon component that includes service profile picture, username, and icon. - Update Facebook and Twitter social icons to fulfill brand guidelines. This will also change the icons in the Social Previews panel and modal. - Update Publicize module and endpoint schemas to include profile_picture - Refresh Publicize connections and set a transient to stagger requests --- .../post-fields-publicize-connections.php | 31 ++++++---- .../changelog/add-re-publicize-icon-component | 4 ++ .../components/connection-icon/index.jsx | 40 +++++++++++++ .../components/connection-icon/style.scss | 36 +++++++++++ .../components/connection-toggle/index.jsx | 60 +++++++++++++++++++ .../components/connection-toggle/style.scss | 19 ++++++ .../publicize/components/connection/index.js | 23 +++---- .../publicize/components/form/index.js | 25 ++++---- .../extensions/plugins/publicize/editor.scss | 4 ++ .../plugins/publicize/store/effects.js | 6 +- .../jetpack/extensions/shared/icons.js | 16 ++++- .../modules/publicize/publicize-jetpack.php | 50 ++++++++++++++++ .../jetpack/modules/publicize/publicize.php | 52 ++++++++++------ 13 files changed, 305 insertions(+), 61 deletions(-) create mode 100644 projects/plugins/jetpack/changelog/add-re-publicize-icon-component create mode 100644 projects/plugins/jetpack/extensions/plugins/publicize/components/connection-icon/index.jsx create mode 100644 projects/plugins/jetpack/extensions/plugins/publicize/components/connection-icon/style.scss create mode 100644 projects/plugins/jetpack/extensions/plugins/publicize/components/connection-toggle/index.jsx create mode 100644 projects/plugins/jetpack/extensions/plugins/publicize/components/connection-toggle/style.scss diff --git a/projects/plugins/jetpack/_inc/lib/core-api/wpcom-fields/post-fields-publicize-connections.php b/projects/plugins/jetpack/_inc/lib/core-api/wpcom-fields/post-fields-publicize-connections.php index f4bd54ddd8593..8e4b282dac89c 100644 --- a/projects/plugins/jetpack/_inc/lib/core-api/wpcom-fields/post-fields-publicize-connections.php +++ b/projects/plugins/jetpack/_inc/lib/core-api/wpcom-fields/post-fields-publicize-connections.php @@ -6,12 +6,13 @@ * { # Post Object * ... * jetpack_publicize_connections: { # Defined below in this file. See schema for more detail. - * id: (string) Connection unique_id - * service_name: (string) Service slug - * display_name: (string) User name/display name of user/connection on Service - * enabled: (boolean) Is this connection slated to be shared to? context=edit only - * done: (boolean) Is this post (or connection) done sharing? context=edit only - * toggleable: (boolean) Can the current user change the `enabled` setting for this Connection+Post? context=edit only + * id: (string) Connection unique_id + * service_name: (string) Service slug + * display_name: (string) User name/display name of user/connection on Service + * profile_picture: (string) Profile picture of user/connection on Service + * enabled: (boolean) Is this connection slated to be shared to? context=edit only + * done: (boolean) Is this post (or connection) done sharing? context=edit only + * toggleable: (boolean) Can the current user change the `enabled` setting for this Connection+Post? context=edit only * } * ... * meta: { # Not defined in this file. Handled in modules/publicize/publicize.php via `register_meta()` @@ -71,36 +72,42 @@ private function post_connection_schema() { 'title' => 'jetpack-publicize-post-connection', 'type' => 'object', 'properties' => array( - 'id' => array( + 'id' => array( 'description' => __( 'Unique identifier for the Publicize Connection', 'jetpack' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), - 'service_name' => array( + 'service_name' => array( 'description' => __( 'Alphanumeric identifier for the Publicize Service', 'jetpack' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), - 'display_name' => array( + 'display_name' => array( 'description' => __( 'Username of the connected account', 'jetpack' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), - 'enabled' => array( + 'profile_picture' => array( + 'description' => __( 'Profile picture of the connected account', 'jetpack' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'enabled' => array( 'description' => __( 'Whether to share to this connection', 'jetpack' ), 'type' => 'boolean', 'context' => array( 'edit' ), ), - 'done' => array( + 'done' => array( 'description' => __( 'Whether Publicize has already finished sharing for this post', 'jetpack' ), 'type' => 'boolean', 'context' => array( 'edit' ), 'readonly' => true, ), - 'toggleable' => array( + 'toggleable' => array( 'description' => __( 'Whether `enable` can be changed for this post/connection', 'jetpack' ), 'type' => 'boolean', 'context' => array( 'edit' ), diff --git a/projects/plugins/jetpack/changelog/add-re-publicize-icon-component b/projects/plugins/jetpack/changelog/add-re-publicize-icon-component new file mode 100644 index 0000000000000..1e312c57daec3 --- /dev/null +++ b/projects/plugins/jetpack/changelog/add-re-publicize-icon-component @@ -0,0 +1,4 @@ +Significance: minor +Type: enhancement + +Add the user's profile picture and new styling to the Publicize toggle. diff --git a/projects/plugins/jetpack/extensions/plugins/publicize/components/connection-icon/index.jsx b/projects/plugins/jetpack/extensions/plugins/publicize/components/connection-icon/index.jsx new file mode 100644 index 0000000000000..477e3018ab701 --- /dev/null +++ b/projects/plugins/jetpack/extensions/plugins/publicize/components/connection-icon/index.jsx @@ -0,0 +1,40 @@ +/** + * External dependencies + */ +import PropTypes from 'prop-types'; + +/** + * Internal dependencies + */ +import { SocialServiceIcon } from '../../../../shared/icons'; + +/** + * Style dependencies + */ +import './style.scss'; + +const ConnectionIcon = props => { + const { id, serviceName, label, profilePicture } = props; + + return ( + + ); +}; + +ConnectionIcon.propTypes = { + id: PropTypes.string.isRequired, + serviceName: PropTypes.string, + label: PropTypes.string, + profilePicture: PropTypes.string, +}; + +export default ConnectionIcon; diff --git a/projects/plugins/jetpack/extensions/plugins/publicize/components/connection-icon/style.scss b/projects/plugins/jetpack/extensions/plugins/publicize/components/connection-icon/style.scss new file mode 100644 index 0000000000000..d4917399c4401 --- /dev/null +++ b/projects/plugins/jetpack/extensions/plugins/publicize/components/connection-icon/style.scss @@ -0,0 +1,36 @@ +.jetpack-publicize-connection-label { + display: flex; + align-items: center; + + // Icon and picture. + .components-connection-icon__picture { + display: grid; + + img, + .placeholder { + border-radius: 2px; + width: 24px; + height: 24px; + grid-area: 1 / 1 / 2 / 2; + } + + .placeholder { + display: block; + background-color: #a8bece; + } + + svg { + width: 15px; + height: 15px; + grid-area: 1 / 1 / 2 / 2; + margin-top: 14px; + margin-left: 14px; + border-radius: 2px; + background-color: white; + + &.is-facebook { + border-radius: 50%; + } + } + } +} diff --git a/projects/plugins/jetpack/extensions/plugins/publicize/components/connection-toggle/index.jsx b/projects/plugins/jetpack/extensions/plugins/publicize/components/connection-toggle/index.jsx new file mode 100644 index 0000000000000..408780f81457b --- /dev/null +++ b/projects/plugins/jetpack/extensions/plugins/publicize/components/connection-toggle/index.jsx @@ -0,0 +1,60 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; +import PropTypes from 'prop-types'; + +/** + * WordPress dependencies + */ +import { FormToggle } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import ConnectionIcon from '../connection-icon'; + +/** + * Style dependencies + */ +import './style.scss'; + +const ConnectionToggle = props => { + const { className, checked, id, disabled, onChange, serviceName, label, profilePicture } = props; + + const wrapperClasses = classnames( 'components-connection-toggle', { + 'is-not-checked': ! checked, + 'is-disabled': disabled, + } ); + + return ( +
+ + +
+ ); +}; + +ConnectionToggle.propTypes = { + className: PropTypes.string, + checked: PropTypes.bool, + id: PropTypes.string.isRequired, + disabled: PropTypes.bool, + onChange: PropTypes.func, + serviceName: PropTypes.string, + label: PropTypes.string, + profilePicture: PropTypes.string, +}; + +export default ConnectionToggle; diff --git a/projects/plugins/jetpack/extensions/plugins/publicize/components/connection-toggle/style.scss b/projects/plugins/jetpack/extensions/plugins/publicize/components/connection-toggle/style.scss new file mode 100644 index 0000000000000..ea43164623b88 --- /dev/null +++ b/projects/plugins/jetpack/extensions/plugins/publicize/components/connection-toggle/style.scss @@ -0,0 +1,19 @@ +@import '../../../../shared/styles/gutenberg-base-styles.scss'; + +.components-connection-toggle { + position: relative; + display: flex; + align-items: center; + width: 100%; + + // Unchecked state. + &.is-not-checked .jetpack-gutenberg-social-icon { + fill: $gray-300; + } + + // Disabled state: + &.is-disabled, + .components-disabled & { + opacity: 0.5; + } +} diff --git a/projects/plugins/jetpack/extensions/plugins/publicize/components/connection/index.js b/projects/plugins/jetpack/extensions/plugins/publicize/components/connection/index.js index a106c86d0a0d8..908cd68052bd8 100644 --- a/projects/plugins/jetpack/extensions/plugins/publicize/components/connection/index.js +++ b/projects/plugins/jetpack/extensions/plugins/publicize/components/connection/index.js @@ -10,7 +10,7 @@ */ import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; -import { Disabled, FormToggle, Notice, ExternalLink } from '@wordpress/components'; +import { Disabled, Notice, ExternalLink } from '@wordpress/components'; import { withSelect } from '@wordpress/data'; import { includes } from 'lodash'; @@ -18,7 +18,7 @@ import { includes } from 'lodash'; * Internal dependencies */ import getSiteFragment from '../../../../shared/get-site-fragment'; -import { SocialServiceIcon } from '../../../../shared/icons'; +import ConnectionToggle from '../connection-toggle'; class PublicizeConnection extends Component { /** @@ -61,17 +61,21 @@ class PublicizeConnection extends Component { } render() { - const { disabled, enabled, id, label, name } = this.props; + const { disabled, enabled, id, label, name, profilePicture } = this.props; const fieldId = 'connection-' + name + '-' + id; // Genericon names are dash separated const serviceName = name.replace( '_', '-' ); let toggle = ( - ); @@ -82,16 +86,7 @@ class PublicizeConnection extends Component { return (
  • { this.maybeDisplayLinkedInNotice() } -
    - - { toggle } -
    +
    { toggle }
  • ); } diff --git a/projects/plugins/jetpack/extensions/plugins/publicize/components/form/index.js b/projects/plugins/jetpack/extensions/plugins/publicize/components/form/index.js index f469890c438c6..298ca9fec2040 100644 --- a/projects/plugins/jetpack/extensions/plugins/publicize/components/form/index.js +++ b/projects/plugins/jetpack/extensions/plugins/publicize/components/form/index.js @@ -45,17 +45,20 @@ export default function PublicizeForm( { { hasConnections && (
      - { connections.map( ( { display_name, enabled, id, service_name, toggleable } ) => ( - - ) ) } + { connections.map( + ( { display_name, enabled, id, service_name, toggleable, profile_picture } ) => ( + + ) + ) }
    ) } diff --git a/projects/plugins/jetpack/extensions/plugins/publicize/editor.scss b/projects/plugins/jetpack/extensions/plugins/publicize/editor.scss index 1e3af16c0c2a4..2e9c133fca408 100644 --- a/projects/plugins/jetpack/extensions/plugins/publicize/editor.scss +++ b/projects/plugins/jetpack/extensions/plugins/publicize/editor.scss @@ -13,6 +13,10 @@ .publicize-jetpack-connection-container { display: flex; + + .components-disabled { + width: 100%; + } } .jetpack-publicize-gutenberg-social-icon { diff --git a/projects/plugins/jetpack/extensions/plugins/publicize/store/effects.js b/projects/plugins/jetpack/extensions/plugins/publicize/store/effects.js index 51d66e5fb4f99..5aa7069086469 100644 --- a/projects/plugins/jetpack/extensions/plugins/publicize/store/effects.js +++ b/projects/plugins/jetpack/extensions/plugins/publicize/store/effects.js @@ -53,14 +53,10 @@ export async function refreshConnectionTestResults() { done: false, enabled: true, toggleable: true, + profile_picture: freshConnection.profile_picture, }; } - // Populate the connection with extra fresh data. - if ( freshConnection.profile_picture ) { - connection.profile_picture = freshConnection.profile_picture; - } - connections.push( connection ); } diff --git a/projects/plugins/jetpack/extensions/shared/icons.js b/projects/plugins/jetpack/extensions/shared/icons.js index e72a1201d5a1e..7ba7694f6446b 100644 --- a/projects/plugins/jetpack/extensions/shared/icons.js +++ b/projects/plugins/jetpack/extensions/shared/icons.js @@ -209,7 +209,10 @@ const FacebookIcon = ( - + ); @@ -217,7 +220,16 @@ const TwitterIcon = ( - + ); diff --git a/projects/plugins/jetpack/modules/publicize/publicize-jetpack.php b/projects/plugins/jetpack/modules/publicize/publicize-jetpack.php index 358ddc0ecb01c..6f618c62e0604 100644 --- a/projects/plugins/jetpack/modules/publicize/publicize-jetpack.php +++ b/projects/plugins/jetpack/modules/publicize/publicize-jetpack.php @@ -5,6 +5,8 @@ class Publicize extends Publicize_Base { + const CONNECTION_REFRESH_WAIT_TRANSIENT = 'jetpack_publicize_connection_refresh_wait'; + function __construct() { parent::__construct(); @@ -37,6 +39,8 @@ function __construct() { add_filter( 'jetpack_sharing_twitter_via', array( $this, 'get_publicized_twitter_account' ), 10, 2 ); + add_action( 'updating_jetpack_version', array( $this, 'init_refresh_transient' ) ); + include_once( JETPACK__PLUGIN_DIR . 'modules/publicize/enhanced-open-graph.php' ); jetpack_require_lib( 'class.jetpack-keyring-service-helper' ); @@ -118,6 +122,7 @@ function register_update_publicize_connections_xmlrpc_method( $methods ) { } function get_all_connections() { + $this->refresh_connections(); $connections = Jetpack_Options::get_option( 'publicize_connections' ); if ( isset( $connections['google_plus'] ) ) { unset( $connections['google_plus'] ); @@ -272,6 +277,51 @@ function unglobalize_connection( $connection_id ) { } } + /** + * As Jetpack updates set the refresh transient to a random amount + * in order to spread out updates to the connection data. + * + * @param string $version The Jetpack version being updated to. + */ + public function init_refresh_transient( $version ) { + if ( version_compare( $version, '10.2.1', '>=' ) && ! get_transient( self::CONNECTION_REFRESH_WAIT_TRANSIENT ) ) { + $this->set_refresh_wait_transient( wp_rand( 10, HOUR_IN_SECONDS * 24 ) ); + } + } + + /** + * Grabs a fresh copy of the publicize connections data. + * Only refreshes once every 12 hours or retries after an hour with an error. + */ + public function refresh_connections() { + if ( get_transient( self::CONNECTION_REFRESH_WAIT_TRANSIENT ) ) { + return; + } + $xml = new Jetpack_IXR_Client(); + $xml->query( 'jetpack.fetchPublicizeConnections' ); + $wait_time = HOUR_IN_SECONDS * 24; + + if ( ! $xml->isError() ) { + $response = $xml->getResponse(); + $this->receive_updated_publicize_connections( $response ); + } else { + // Retry a bit quicker, but still wait. + $wait_time = HOUR_IN_SECONDS; + } + + $this->set_refresh_wait_transient( $wait_time ); + } + + /** + * Sets the transient to expire at the specified time in seconds. + * This prevents us from attempting to refresh the data too often. + * + * @param int $wait_time The number of seconds before the transient should expire. + */ + public function set_refresh_wait_transient( $wait_time ) { + set_transient( self::CONNECTION_REFRESH_WAIT_TRANSIENT, microtime( true ), $wait_time ); + } + function connect_url( $service_name, $for = 'publicize' ) { return Jetpack_Keyring_Service_Helper::connect_url( $service_name, $for ); } diff --git a/projects/plugins/jetpack/modules/publicize/publicize.php b/projects/plugins/jetpack/modules/publicize/publicize.php index 44feb1f9586ab..32d43d092a232 100644 --- a/projects/plugins/jetpack/modules/publicize/publicize.php +++ b/projects/plugins/jetpack/modules/publicize/publicize.php @@ -400,6 +400,22 @@ function get_display_name( $service_name, $connection ) { } } + /** + * Returns a profile picture for the Connection + * + * @param object|array $connection The Connection object (WordPress.com) or array (Jetpack). + * @return string + */ + private function get_profile_picture( $connection ) { + $cmeta = $this->get_connection_meta( $connection ); + + if ( isset( $cmeta['profile_picture'] ) ) { + return $cmeta['profile_picture']; + } + + return ''; + } + /** * Whether the user needs to select additional options after connecting * @@ -590,14 +606,15 @@ abstract function test_connection( $service_name, $connection ); * @return array { * Array of UI setup data for connection list form. * - * @type string 'unique_id' ID string representing connection - * @type string 'service_name' Slug of the connection's service (facebook, twitter, ...) - * @type string 'service_label' Service Label (Facebook, Twitter, ...) - * @type string 'display_name' Connection's human-readable Username: "@jetpack" - * @type bool 'enabled' Default value for the connection (e.g., for a checkbox). - * @type bool 'done' Has this connection already been publicized to? - * @type bool 'toggleable' Is the user allowed to change the value for the connection? - * @type bool 'global' Is this connection a global one? + * @type string 'unique_id' ID string representing connection + * @type string 'service_name' Slug of the connection's service (facebook, twitter, ...) + * @type string 'service_label' Service Label (Facebook, Twitter, ...) + * @type string 'display_name' Connection's human-readable Username: "@jetpack" + * @type string 'profile_picture' Connection profile picture. + * @type bool 'enabled' Default value for the connection (e.g., for a checkbox). + * @type bool 'done' Has this connection already been publicized to? + * @type bool 'toggleable' Is the user allowed to change the value for the connection? + * @type bool 'global' Is this connection a global one? * } */ public function get_filtered_connection_data( $selected_post_id = null ) { @@ -720,15 +737,16 @@ public function get_filtered_connection_data( $selected_post_id = null ) { } $connection_list[] = array( - 'unique_id' => $unique_id, - 'service_name' => $service_name, - 'service_label' => $this->get_service_label( $service_name ), - 'display_name' => $this->get_display_name( $service_name, $connection ), - - 'enabled' => $enabled, - 'done' => $done, - 'toggleable' => $toggleable, - 'global' => 0 == $connection_data['user_id'], + 'unique_id' => $unique_id, + 'service_name' => $service_name, + 'service_label' => $this->get_service_label( $service_name ), + 'display_name' => $this->get_display_name( $service_name, $connection ), + 'profile_picture' => $this->get_profile_picture( $connection ), + + 'enabled' => $enabled, + 'done' => $done, + 'toggleable' => $toggleable, + 'global' => 0 == $connection_data['user_id'], // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison -- Other types can be used at times. ); } }