-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Settings: Allow Users to Insert Header Code #86079
base: trunk
Are you sure you want to change the base?
Changes from 5 commits
65ee5f3
3181733
68d9726
41b73cd
6c3af5c
e77dc8c
c86ee66
2f8c8df
0597568
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
import { FEATURE_INSTALL_PLUGINS, getPlan, PLAN_BUSINESS } from '@automattic/calypso-products'; | ||
import { Button, Card, Gridicon } from '@automattic/components'; | ||
import { html } from '@codemirror/lang-html'; | ||
import CodeMirror from '@uiw/react-codemirror'; | ||
Aurorum marked this conversation as resolved.
Show resolved
Hide resolved
|
||
import classNames from 'classnames'; | ||
import { localize } from 'i18n-calypso'; | ||
import { useEffect, useState } from 'react'; | ||
import { connect } from 'react-redux'; | ||
import illustration404 from 'calypso/assets/images/illustrations/illustration-404.svg'; | ||
import UpsellNudge from 'calypso/blocks/upsell-nudge'; | ||
import DocumentHead from 'calypso/components/data/document-head'; | ||
import QuerySiteSettings from 'calypso/components/data/query-site-settings'; | ||
import EmptyContent from 'calypso/components/empty-content'; | ||
import HeaderCake from 'calypso/components/header-cake'; | ||
import InlineSupportLink from 'calypso/components/inline-support-link'; | ||
import Main from 'calypso/components/main'; | ||
import NavigationHeader from 'calypso/components/navigation-header'; | ||
import { successNotice, errorNotice } from 'calypso/state/notices/actions'; | ||
import { canCurrentUser } from 'calypso/state/selectors/can-current-user'; | ||
import getSiteSetting from 'calypso/state/selectors/get-site-setting'; | ||
import siteHasFeature from 'calypso/state/selectors/site-has-feature'; | ||
import { saveSiteSettings } from 'calypso/state/site-settings/actions'; | ||
import { | ||
isRequestingSiteSettings, | ||
isSavingSiteSettings, | ||
isSiteSettingsSaveSuccessful, | ||
getSiteSettingsSaveError, | ||
} from 'calypso/state/site-settings/selectors'; | ||
import { isJetpackSite } from 'calypso/state/sites/selectors'; | ||
import { getSelectedSiteId, getSelectedSiteSlug } from 'calypso/state/ui/selectors'; | ||
import './style.scss'; | ||
|
||
const HeaderCodeSettings = ( { | ||
errorMessage, | ||
hasPlanFeature, | ||
headCode, | ||
isAdmin, | ||
isJetpack, | ||
isRequestingSettings, | ||
isSaveFailure, | ||
isSaveSuccess, | ||
isSavingSettings, | ||
saveSettings, | ||
siteId, | ||
siteSlug, | ||
successMessage, | ||
translate, | ||
} ) => { | ||
const [ htmlCode, setHtmlCode ] = useState( '' ); | ||
Aurorum marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
useEffect( () => { | ||
if ( 'string' === typeof headCode && headCode.length ) { | ||
setHtmlCode( headCode ); | ||
} | ||
|
||
// Necessary for if switching to a site without any code. | ||
if ( ! headCode ) { | ||
setHtmlCode( '' ); | ||
Aurorum marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
}, [ headCode, siteId ] ); | ||
|
||
useEffect( () => { | ||
if ( isSaveSuccess ) { | ||
successMessage( translate( 'Code updated successfully!' ), { | ||
duration: 4000, | ||
Aurorum marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} ); | ||
} else if ( isSaveFailure && ! isSavingSettings ) { | ||
Aurorum marked this conversation as resolved.
Show resolved
Hide resolved
|
||
errorMessage( translate( 'There was an error saving your code.' ), { | ||
duration: 4000, | ||
} ); | ||
} | ||
}, [ isSaveSuccess, isSavingSettings, isSaveFailure ] ); | ||
|
||
const handleSaveSettings = () => { | ||
saveSettings( siteId, { | ||
jetpack_header_code: htmlCode, | ||
} ); | ||
}; | ||
|
||
const isSupported = isJetpack || hasPlanFeature; | ||
|
||
return ( | ||
<Main className="header-code"> | ||
<DocumentHead title={ translate( 'Header Code' ) } /> | ||
{ siteId && <QuerySiteSettings siteId={ siteId } /> } | ||
{ ! isAdmin && ( | ||
Aurorum marked this conversation as resolved.
Show resolved
Hide resolved
|
||
<EmptyContent | ||
illustration={ illustration404 } | ||
Aurorum marked this conversation as resolved.
Show resolved
Hide resolved
|
||
title={ translate( 'You are not authorized to view this page' ) } | ||
/> | ||
) } | ||
|
||
{ isAdmin && ( | ||
Aurorum marked this conversation as resolved.
Show resolved
Hide resolved
|
||
<> | ||
<NavigationHeader | ||
title={ translate( 'Header Code' ) } | ||
subtitle={ translate( 'Insert code within the Header element of your site.' ) } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did you mean There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wasn't actually sure! This refers to it as the Header element: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/header Happy to change if you'd prefer. :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "Header element" is definitely confusing when we talk about the "head" element. So we might want to change it indeed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was thinking about this a little further, and I feel like it repeats the text below! What do you think about something like this which explains why this feature exists and why most people might use it? Might be more informative from a user's perspective. :)
|
||
/> | ||
<HeaderCake backHref={ `/settings/general/${ siteSlug }` } isCompact> | ||
<h1>{ translate( 'Header Code' ) }</h1> | ||
</HeaderCake> | ||
<Card> | ||
<UpsellNudge | ||
plan={ PLAN_BUSINESS } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This leads to the WP.com business plan and not a Jetpack plan though. Are you sure this was intended? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes - all Jetpack sites should be able to use this, though it's primarily intended for WP.com Atomic, but WordPress.com Simple sites shouldn't be able to. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we're redirecting unsupported sites to the General Settings page instead, this upsell is defunct and I've removed it. But just to clarify, Jetpack sites wouldn't have seen that notice - unless I'm missing something? It was hidden from them. |
||
title={ translate( 'Add custom code to your site with the %(planName)s plan.', { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this really the case? Do we really need a business plan to insert the header code? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it'll be stripped out on WP.com Simple sites. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about Personal (AKA Starter) and Premium (AKA Explorer) plans? Does only the WP.com business plan have this feature? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, indeed. I believe that's for security reasons since Simple sites share the platform alongside product reasons (but of course, that's been determined well above me!). Users wouldn't even be able to insert such code with the Custom HTML block: https://wordpress.com/support/wordpress-editor/blocks/custom-html-block/#supported-html-tags |
||
args: { planName: getPlan( PLAN_BUSINESS ).getTitle() }, | ||
} ) } | ||
description={ translate( | ||
'Upgrade to insert JavaScript and HTML meta tags to your site.' | ||
) } | ||
showIcon | ||
forceDisplay={ ! isSupported } | ||
/> | ||
<p className="header-code__text"> | ||
{ translate( | ||
'The following code will be inserted within {{code}}<head>{{/code}} on your site. {{link}}Learn more{{/link}}.', | ||
{ | ||
components: { | ||
code: <code />, | ||
link: ( | ||
<InlineSupportLink supportContext="insert-header-code" showIcon={ false } /> | ||
), | ||
}, | ||
} | ||
) } | ||
</p> | ||
<div | ||
className={ classNames( 'header-code__editor', { | ||
'is-loading': isRequestingSettings, | ||
Aurorum marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} ) } | ||
> | ||
<CodeMirror | ||
value={ htmlCode } | ||
onChange={ ( value ) => setHtmlCode( value ) } | ||
height="235px" | ||
extensions={ [ html() ] } | ||
/> | ||
</div> | ||
<div className="header-code__warning"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of introducing a new component, can we use the existing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Definitely! However, I feel quite strongly that italic text looks weird on this screen. I also noticed that there's a few cases of an icon with non-italic text around Probably out of scope of this PR, but might be nice one day to have There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't have strong feelings on using |
||
<Gridicon icon="info" size={ 18 } /> | ||
<p className="header-code__warning-text"> | ||
{ translate( "This code will be run for all your site's viewers." ) }{ ' ' } | ||
Aurorum marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ translate( | ||
'Please make sure that you understand any code and trust its source before adding it here.' | ||
) } | ||
</p> | ||
</div> | ||
<div className="header-code__button"> | ||
<Button | ||
disabled={ headCode === htmlCode || ! isSupported || isRequestingSettings } | ||
primary | ||
busy={ isSavingSettings } | ||
onClick={ handleSaveSettings } | ||
> | ||
{ translate( 'Save' ) } | ||
</Button> | ||
</div> | ||
</Card> | ||
</> | ||
) } | ||
</Main> | ||
); | ||
}; | ||
|
||
export default connect( | ||
Aurorum marked this conversation as resolved.
Show resolved
Hide resolved
|
||
( state ) => { | ||
const siteId = getSelectedSiteId( state ); | ||
return { | ||
hasPlanFeature: siteHasFeature( state, siteId, FEATURE_INSTALL_PLUGINS ), | ||
headCode: getSiteSetting( state, siteId, 'jetpack_header_code' ), | ||
isAdmin: canCurrentUser( state, siteId, 'manage_options' ), | ||
isJetpack: isJetpackSite( state, siteId ), | ||
isRequestingSettings: isRequestingSiteSettings( state, siteId ), | ||
isSaveFailure: getSiteSettingsSaveError( state, siteId ), | ||
isSaveSuccess: isSiteSettingsSaveSuccessful( state, siteId ), | ||
isSavingSettings: isSavingSiteSettings( state, siteId ), | ||
siteId, | ||
siteSlug: getSelectedSiteSlug( state, siteId ), | ||
}; | ||
}, | ||
{ saveSettings: saveSiteSettings, successMessage: successNotice, errorMessage: errorNotice } | ||
)( localize( HeaderCodeSettings ) ); | ||
Aurorum marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
.header-code__warning { | ||
display: flex; | ||
margin-bottom: 1.5em; | ||
|
||
.gridicon { | ||
fill: var(--color-text-subtle); | ||
margin-right: 6px; | ||
} | ||
|
||
.header-code__warning-text { | ||
color: var(--color-text-subtle); | ||
font-size: $font-body-extra-small; | ||
margin: 0; | ||
} | ||
} | ||
|
||
.header-code__editor .cm-editor { | ||
border: 1px solid var(--studio-gray-5); | ||
font-size: $font-body-small; | ||
margin-bottom: 1em; | ||
outline: none; | ||
|
||
&.cm-focused { | ||
border-color: var(--studio-gray-10); | ||
} | ||
|
||
* { | ||
font-family: $code; | ||
} | ||
} | ||
|
||
.header-code__editor.is-loading .cm-editor { | ||
@include placeholder(); | ||
pointer-events: none; | ||
|
||
&:after { | ||
display: none; | ||
} | ||
|
||
.cm-line { | ||
visibility: hidden; | ||
} | ||
} | ||
|
||
.header-code__text { | ||
Aurorum marked this conversation as resolved.
Show resolved
Hide resolved
|
||
margin-bottom: 1em; | ||
} | ||
|
||
.header-code__button { | ||
Aurorum marked this conversation as resolved.
Show resolved
Hide resolved
|
||
text-align: right; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -56,12 +56,14 @@ class SiteTools extends Component { | |
showClone, | ||
showDeleteContent, | ||
showDeleteSite, | ||
showHeaderCode, | ||
showManageConnection, | ||
showStartSiteTransfer, | ||
siteId, | ||
} = this.props; | ||
|
||
const changeAddressLink = `/domains/manage/${ siteSlug }`; | ||
const headerCodeLink = `/settings/header-code/${ siteSlug }`; | ||
const startOverLink = `/settings/start-over/${ siteSlug }`; | ||
const startSiteTransferLink = `/settings/start-site-transfer/${ siteSlug }`; | ||
const deleteSiteLink = `/settings/delete-site/${ siteSlug }`; | ||
|
@@ -74,6 +76,9 @@ class SiteTools extends Component { | |
"Remove all posts, pages, and media to start fresh while keeping your site's address." | ||
); | ||
|
||
const headerCode = translate( 'Header code' ); | ||
const headerCodeText = translate( 'Insert code within the Header element of your site.' ); | ||
Aurorum marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
const deleteSite = translate( 'Delete your site permanently' ); | ||
const deleteSiteText = translate( | ||
"Delete all your posts, pages, media, and data, and give up your site's address." | ||
|
@@ -117,6 +122,13 @@ class SiteTools extends Component { | |
description={ copyText } | ||
/> | ||
) } | ||
{ showHeaderCode && ( | ||
<SiteToolsLink | ||
href={ headerCodeLink } | ||
title={ headerCode } | ||
description={ headerCodeText } | ||
/> | ||
) } | ||
{ showClone && ( | ||
<SiteToolsLink href={ cloneUrl } title={ cloneTitle } description={ cloneText } /> | ||
) } | ||
|
@@ -219,6 +231,7 @@ export default compose( [ | |
showClone: 'active' === rewindState.state && ! isAtomic, | ||
showDeleteContent: isAtomic || ( ! isJetpack && ! isVip && ! isP2Hub ), | ||
showDeleteSite: ( ! isJetpack || isAtomic ) && ! isVip && sitePurchasesLoaded, | ||
showHeaderCode: isJetpack || isAtomic, | ||
Aurorum marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we show this only to admins? (if the current user has the capability to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not necessary in this case! The entire page is blocked for non-admins. |
||
showManageConnection: isJetpack && ! isAtomic, | ||
showStartSiteTransfer, | ||
siteId, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will all site types support this? I see Jetpack sites will, but what about WP.com simple / Atomic / etc.?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code in Automattic/jetpack#34881 explicitly excludes Simple. As things stand I don't see anything stopping it from Atomic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In that case, we should make sure to redirect in Calypso if someone tries to access this with a WP.com simple site.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my comments below!