Skip to content

Commit

Permalink
Newsletters: Add Jetpack Newsletter Plugin Icon + Email Preview optio…
Browse files Browse the repository at this point in the history
…n. (#38675)
  • Loading branch information
lezama authored Aug 5, 2024
1 parent 43308c8 commit cbd228a
Show file tree
Hide file tree
Showing 4 changed files with 269 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<?php
/**
* Email Preview endpoint for the WordPress.com REST API.
*
* @package automattic/jetpack
*/

use Automattic\Jetpack\Connection\Manager;
use Automattic\Jetpack\Status\Host;

require_once __DIR__ . '/trait-wpcom-rest-api-proxy-request-trait.php';

/**
* Class WPCOM_REST_API_V2_Endpoint_Email_Preview
*
* Returns an email preview given a post id.
*/
class WPCOM_REST_API_V2_Endpoint_Email_Preview extends WP_REST_Controller {

use WPCOM_REST_API_Proxy_Request_Trait;

/**
* Constructor.
*/
public function __construct() {
$this->base_api_path = 'wpcom';
$this->version = 'v2';
$this->namespace = $this->base_api_path . '/' . $this->version;
$this->rest_base = '/email-preview';
$this->wpcom_is_wpcom_only_endpoint = true;
$this->wpcom_is_site_specific_endpoint = true;

add_action( 'rest_api_init', array( $this, 'register_routes' ) );
}

/**
* Registers the routes for email preview.
*
* @see register_rest_route()
*/
public function register_routes() {
$options = array(
'show_in_index' => true,
'methods' => 'GET',
// if this is not a wpcom site, we need to proxy the request to wpcom
'callback' => ( ( new Host() )->is_wpcom_simple() ) ? array(
$this,
'email_preview',
) : array( $this, 'proxy_request_to_wpcom_as_user' ),
'permission_callback' => array( $this, 'permissions_check' ),
'args' => array(
'id' => array(
'description' => __( 'Unique identifier for the post.', 'jetpack' ),
'type' => 'integer',
),
),
);

register_rest_route(
$this->namespace,
$this->rest_base,
$options
);
}

/**
* Checks if the user is connected and has access to edit the post
*
* @param WP_REST_Request $request Full data about the request.
*
* @return true|WP_Error True if the request has edit access, WP_Error object otherwise.
*/
public function permissions_check( $request ) {
if ( ! ( new Host() )->is_wpcom_simple() ) {
if ( ! ( new Manager() )->is_user_connected() ) {
return new WP_Error(
'rest_cannot_send_email_preview',
__( 'Please connect your user account to WordPress.com', 'jetpack' ),
array( 'status' => rest_authorization_required_code() )
);
}
}

$post = get_post( $request->get_param( 'post_id' ) );

if ( ! $post ) {
return new \WP_Error(
'post_not_found',
__( 'Post not found.', 'jetpack' ),
array( 'status' => 404 )
);
}

if ( ! current_user_can( 'edit_post', $post->ID ) ) {
return new WP_Error(
'rest_forbidden_context',
__( 'Please connect your user account to WordPress.com', 'jetpack' ),
array( 'status' => rest_authorization_required_code() )
);
}

return true;
}

/**
* Returns an email preview of a post.
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function email_preview( $request ) {
$post_id = $request['post_id'];
$post = get_post( $post_id );
return rest_ensure_response(
array(
'html' => apply_filters( 'jetpack_generate_email_preview_html', '', $post ),
)
);
}
}

wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V2_Endpoint_Email_Preview' );
4 changes: 4 additions & 0 deletions projects/plugins/jetpack/changelog/add-email-preview-endpoint
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: enhancement

Jetpack Newsletter: Adds Jetpack Newsletter menu with preview option.
26 changes: 20 additions & 6 deletions projects/plugins/jetpack/extensions/blocks/subscriptions/editor.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { registerJetpackPlugin } from '@automattic/jetpack-shared-extension-utils';
import { createBlock } from '@wordpress/blocks';
import { select } from '@wordpress/data';
import { addFilter } from '@wordpress/hooks';
import { atSymbol } from '@wordpress/icons';
import { registerJetpackBlockFromMetadata } from '../../shared/register-jetpack-block';
import metadata from './block.json';
import CommandPalette from './command-palette';
import deprecated from './deprecated';
import edit from './edit';
import NewsletterMenu from './menu';
import SubscribePanels from './panel';

const blockName = metadata.name.replace( 'jetpack/', '' );
Expand Down Expand Up @@ -62,14 +65,25 @@ registerJetpackBlockFromMetadata( metadata, {
deprecated,
} );

const shouldShowNewsletterMenu = () => {
const postType = select( 'core/editor' ).getCurrentPostType();
const isPost = postType === 'post';
// TODO: Remove the query param check once the feature is stable.
return isPost && new URLSearchParams( window.location.search ).has( 'showNewsletterMenu' );
};

// Registers slot/fill panels defined via settings.render and command palette commands
registerJetpackPlugin( blockName, {
render: () => (
<>
<SubscribePanels />,
<CommandPalette />
</>
),
render: () => {
return (
<>
<SubscribePanels />
{ shouldShowNewsletterMenu() && <NewsletterMenu /> }
<CommandPalette />
</>
);
},
icon: shouldShowNewsletterMenu() ? atSymbol : undefined,
} );

// Allows block to be inserted inside core navigation block
Expand Down
122 changes: 122 additions & 0 deletions projects/plugins/jetpack/extensions/blocks/subscriptions/menu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import apiFetch from '@wordpress/api-fetch';
import { Button, Modal, PanelBody, Path, SVG } from '@wordpress/components';
import { useSelect } from '@wordpress/data';
import { PluginSidebar } from '@wordpress/edit-post';
import { __ } from '@wordpress/i18n';
import { useState, useCallback, useEffect } from 'react';

// Fallback SVG for the send icon that will be released with Gutenberg 19.0
// Replace with import { send } from '@wordpress/icons' when available;
const sendIconSvg = (
<SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<Path
fillRule="evenodd"
clipRule="evenodd"
d="M6.332 5.748c-1.03-.426-2.06.607-1.632 1.636l1.702 3.93 7.481.575c.123.01.123.19 0 .2l-7.483.575-1.7 3.909c-.429 1.029.602 2.062 1.632 1.636l12.265-5.076c1.03-.426 1.03-1.884 0-2.31L6.332 5.748Z"
/>
</SVG>
);

const NewsletterMenu = () => {
const [ isModalOpen, setIsModalOpen ] = useState( false );
const [ isLoading, setIsLoading ] = useState( true );
const [ previewHtml, setPreviewHtml ] = useState( '' );

const { postId } = useSelect( select => {
return {
postId: select( 'core/editor' ).getCurrentPostId(),
};
}, [] );

const fetchPreview = useCallback( async () => {
if ( ! postId ) {
return;
}

setIsLoading( true );
try {
const response = await apiFetch( {
path: `/wpcom/v2/email-preview/?post_id=${ postId }`,
method: 'GET',
} );

if ( response && response.html ) {
setPreviewHtml( response.html );
} else {
throw new Error( 'Invalid response format' );
}
} catch ( error ) {
setPreviewHtml( `<html><body>${ __( 'Error loading preview', 'jetpack' ) }</body></html>` );
} finally {
setIsLoading( false );
}
}, [ postId ] );

const openModal = () => {
setIsModalOpen( true );
};

const closeModal = () => setIsModalOpen( false );

useEffect( () => {
if ( isModalOpen ) {
fetchPreview();
}
}, [ isModalOpen, fetchPreview ] );

const modalContent = (
<>
{ isLoading && <p>{ __( 'Loading preview…', 'jetpack' ) }</p> }
{ ! isLoading && (
<iframe
srcDoc={ previewHtml }
style={ {
width: '100%',
height: 'calc(100vh - 120px)',
border: 'none',
} }
title={ __( 'Email Preview', 'jetpack' ) }
/>
) }
</>
);

return (
<>
<PluginSidebar
name="newsletter-settings-sidebar"
title={ __( 'Newsletter', 'jetpack' ) }
icon={ sendIconSvg }
>
<PanelBody>
<p>
{ __(
'Ensure your email looks perfect. Use the buttons below to view a preview or send a test email.',
'jetpack'
) }
</p>
<Button
onClick={ openModal }
style={ {
marginRight: '18px',
} }
variant="secondary"
>
{ __( 'Preview email', 'jetpack' ) }
</Button>
</PanelBody>
</PluginSidebar>
{ isModalOpen && (
<Modal
title={ __( 'Preview email', 'jetpack' ) }
onRequestClose={ closeModal }
isFullScreen={ true }
>
{ modalContent }
</Modal>
) }
</>
);
};

export default NewsletterMenu;

0 comments on commit cbd228a

Please sign in to comment.