mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-03-11 02:12:21 -05:00
Added links page and look feel settings handling (#64)
no issue - Handles new look feel settings for portal icon button - Adds auto width calculation for portal button based on text - Adds new link page for portal links and data attributes - Added contrast color calculation for accent color complimentary text - Added copy text utility for allowing copy to clipboard
This commit is contained in:
parent
c1f6545915
commit
fb6a2c950f
12 changed files with 353 additions and 38 deletions
|
@ -61,7 +61,7 @@ export default class App extends React.Component {
|
|||
// Loads default page and popup state for local UI testing
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
return {
|
||||
page: 'signup',
|
||||
page: 'links',
|
||||
showPopup: true
|
||||
};
|
||||
}
|
||||
|
@ -85,6 +85,7 @@ export default class App extends React.Component {
|
|||
...this.state.site,
|
||||
...(previewSite || {})
|
||||
},
|
||||
member: this.getPreviewMember(),
|
||||
...restPreview
|
||||
});
|
||||
}
|
||||
|
@ -105,7 +106,7 @@ export default class App extends React.Component {
|
|||
// Display the key/value pairs
|
||||
for (let pair of qsParams.entries()) {
|
||||
const key = pair[0];
|
||||
const value = pair[1];
|
||||
const value = decodeURIComponent(pair[1]);
|
||||
if (key === 'button') {
|
||||
previewSettings.site.portal_button = JSON.parse(value);
|
||||
} else if (key === 'name') {
|
||||
|
@ -118,6 +119,14 @@ export default class App extends React.Component {
|
|||
allowedPlans.push('yearly');
|
||||
} else if (key === 'page') {
|
||||
previewSettings.page = value;
|
||||
} else if (key === 'accentColor') {
|
||||
previewSettings.site.accent_color = value;
|
||||
} else if (key === 'buttonIcon') {
|
||||
previewSettings.site.portal_button_icon = value;
|
||||
} else if (key === 'signupButtonText') {
|
||||
previewSettings.site.portal_button_signup_text = value;
|
||||
} else if (key === 'buttonStyle') {
|
||||
previewSettings.site.portal_button_style = value;
|
||||
}
|
||||
}
|
||||
previewSettings.site.portal_plans = allowedPlans;
|
||||
|
@ -127,6 +136,17 @@ export default class App extends React.Component {
|
|||
return {};
|
||||
}
|
||||
|
||||
getPreviewMember() {
|
||||
if (this.isPreviewMode()) {
|
||||
const {site: previewSite, ...restPreview} = this.getPreviewState();
|
||||
if (restPreview.page.includes('account')) {
|
||||
return Fixtures.member.free;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async initSetup() {
|
||||
const {site, member} = await this.fetchData() || {};
|
||||
if (!site) {
|
||||
|
@ -143,7 +163,7 @@ export default class App extends React.Component {
|
|||
...site,
|
||||
...(previewSite || {})
|
||||
},
|
||||
member: member || (this.isPreviewMode() ? Fixtures.member.free : null),
|
||||
member: member || this.getPreviewMember(),
|
||||
page,
|
||||
showPopup,
|
||||
action: 'init:success',
|
||||
|
@ -197,8 +217,9 @@ export default class App extends React.Component {
|
|||
return {type, status, reason};
|
||||
}
|
||||
|
||||
getBrandColor() {
|
||||
return (this.state.site && this.state.site.brand && this.state.site.brand.primaryColor) || '#3db0ef';
|
||||
getAccentColor() {
|
||||
const {accent_color: accentColor = '#3db0ef'} = this.state.site || {};
|
||||
return accentColor;
|
||||
}
|
||||
|
||||
async onAction(action, data) {
|
||||
|
@ -334,7 +355,7 @@ export default class App extends React.Component {
|
|||
site,
|
||||
member,
|
||||
action,
|
||||
brandColor: this.getBrandColor(),
|
||||
brandColor: this.getAccentColor(),
|
||||
page,
|
||||
lastPage,
|
||||
onAction: (_action, data) => this.onAction(_action, data)
|
||||
|
|
|
@ -8,7 +8,7 @@ const setup = (overrides) => {
|
|||
site,
|
||||
member: member.free,
|
||||
action: 'init:success',
|
||||
brandColor: site.brand.primaryColor,
|
||||
brandColor: site.accent_color,
|
||||
page: 'signup',
|
||||
initStatus: 'success',
|
||||
showPopup: true
|
||||
|
|
|
@ -17,12 +17,14 @@ export default class Frame extends Component {
|
|||
setupFrameBaseStyle() {
|
||||
this.iframeHtml = this.node.contentDocument.documentElement;
|
||||
this.iframeHtml.style.fontSize = '62.5%';
|
||||
this.iframeHtml.style.height = '100%';
|
||||
|
||||
this.iframeHead = this.node.contentDocument.head;
|
||||
this.iframeRoot = this.node.contentDocument.body;
|
||||
this.iframeRoot.style.margin = '0px';
|
||||
this.iframeRoot.style.fontFamily = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif';
|
||||
this.iframeRoot.style.fontSize = '1.6rem';
|
||||
this.iframeRoot.style.height = '100%';
|
||||
this.iframeRoot.style.lineHeight = '1.6em';
|
||||
this.iframeRoot.style.fontWeight = '400';
|
||||
this.iframeRoot.style.fontStyle = 'normal';
|
||||
|
|
|
@ -9,6 +9,7 @@ import AppContext from '../AppContext';
|
|||
import FrameStyle from './Frame.styles';
|
||||
import AccountPlanPage from './pages/AccountPlanPage';
|
||||
import AccountProfilePage from './pages/AccountProfilePage';
|
||||
import LinkPage from './pages/LinkPage';
|
||||
|
||||
const React = require('react');
|
||||
|
||||
|
@ -80,7 +81,10 @@ const StylesWrapper = ({member}) => {
|
|||
minHeight: '290px',
|
||||
maxHeight: '290px'
|
||||
},
|
||||
accountProfile
|
||||
accountProfile,
|
||||
links: {
|
||||
width: '600px'
|
||||
}
|
||||
},
|
||||
popup: {
|
||||
container: {
|
||||
|
@ -118,7 +122,8 @@ const Pages = {
|
|||
accountPlan: AccountPlanPage,
|
||||
accountProfile: AccountProfilePage,
|
||||
magiclink: MagicLinkPage,
|
||||
loading: LoadingPage
|
||||
loading: LoadingPage,
|
||||
links: LinkPage
|
||||
};
|
||||
|
||||
class PopupContent extends React.Component {
|
||||
|
|
|
@ -3,23 +3,29 @@ import Frame from './Frame';
|
|||
import MemberGravatar from './common/MemberGravatar';
|
||||
import AppContext from '../AppContext';
|
||||
import {ReactComponent as UserIcon} from '../images/icons/user.svg';
|
||||
import {ReactComponent as CloseIcon} from '../images/icons/close.svg';
|
||||
import getContrastColor from '../utils/contrast-color';
|
||||
const React = require('react');
|
||||
|
||||
const Styles = ({brandColor}) => {
|
||||
const Styles = ({brandColor, hasText}) => {
|
||||
const frame = {
|
||||
...(hasText ? {borderRadius: '12px'} : {}),
|
||||
...(!hasText ? {width: '60px'} : {})
|
||||
};
|
||||
return {
|
||||
frame: {
|
||||
zIndex: '2147483000',
|
||||
position: 'fixed',
|
||||
bottom: '20px',
|
||||
right: '20px',
|
||||
width: '60px',
|
||||
width: '500px',
|
||||
maxWidth: '500px',
|
||||
height: '60px',
|
||||
boxShadow: 'rgba(0, 0, 0, 0.06) 0px 1px 6px 0px, rgba(0, 0, 0, 0.16) 0px 2px 32px 0px',
|
||||
borderRadius: '50%',
|
||||
backgroundColor: brandColor,
|
||||
animation: '250ms ease 0s 1 normal none running animation-bhegco',
|
||||
transition: 'opacity 0.3s ease 0s'
|
||||
transition: 'opacity 0.3s ease 0s',
|
||||
...frame
|
||||
},
|
||||
launcher: {
|
||||
position: 'absolute',
|
||||
|
@ -35,17 +41,17 @@ const Styles = ({brandColor}) => {
|
|||
overflow: 'hidden'
|
||||
},
|
||||
button: {
|
||||
userSelect: 'none',
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
WebkitBoxAlign: 'center',
|
||||
alignItems: 'center',
|
||||
WebkitBoxPack: 'center',
|
||||
justifyContent: 'center',
|
||||
position: 'absolute',
|
||||
top: '0px',
|
||||
bottom: '0px',
|
||||
width: '100%',
|
||||
opacity: '1',
|
||||
transform: 'rotate(0deg) scale(1)',
|
||||
height: '100%',
|
||||
transition: 'transform 0.16s linear 0s, opacity 0.08s linear 0s'
|
||||
},
|
||||
userIcon: {
|
||||
|
@ -61,44 +67,162 @@ const Styles = ({brandColor}) => {
|
|||
};
|
||||
};
|
||||
|
||||
export default class TriggerButton extends React.Component {
|
||||
class TriggerButtonContent extends React.Component {
|
||||
static contextType = AppContext;
|
||||
|
||||
onToggle() {
|
||||
this.context.onAction('togglePopup');
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { };
|
||||
this.container = React.createRef();
|
||||
this.height = null;
|
||||
this.width = null;
|
||||
}
|
||||
|
||||
updateHeight(height) {
|
||||
this.props.updateHeight && this.props.updateHeight(height);
|
||||
}
|
||||
|
||||
updateWidth(width) {
|
||||
this.props.updateWidth && this.props.updateWidth(width);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.container) {
|
||||
this.height = this.container.current && this.container.current.offsetHeight;
|
||||
this.width = this.container.current && this.container.current.offsetWidth;
|
||||
this.updateHeight(this.height);
|
||||
this.updateWidth(this.width);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.container) {
|
||||
const height = this.container.current && this.container.current.offsetHeight;
|
||||
let width = this.container.current && this.container.current.offsetWidth;
|
||||
if (height !== this.height) {
|
||||
this.height = height;
|
||||
this.updateHeight(this.height);
|
||||
}
|
||||
|
||||
if (width !== this.width) {
|
||||
this.width = width;
|
||||
this.updateWidth(this.width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
renderTriggerIcon() {
|
||||
const {portal_button_icon: buttonIcon = '', portal_button_style: buttonStyle = ''} = this.context.site || {};
|
||||
const Style = Styles({brandColor: this.context.brandColor});
|
||||
const memberGravatar = this.context.member && this.context.member.avatar_image;
|
||||
|
||||
if (!buttonStyle.includes('icon')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (memberGravatar) {
|
||||
return (
|
||||
<MemberGravatar gravatar={memberGravatar} />
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.isPopupOpen) {
|
||||
if (buttonIcon) {
|
||||
return (
|
||||
<CloseIcon style={Style.closeIcon} />
|
||||
<img style={{width: '26px', height: '26px'}} src={buttonIcon} alt="Icon" />
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<UserIcon style={Style.userIcon} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<UserIcon style={Style.userIcon} />
|
||||
);
|
||||
hasText() {
|
||||
const {
|
||||
portal_button_signup_text: buttonText,
|
||||
portal_button_style: buttonStyle
|
||||
} = this.context.site;
|
||||
return ['icon-and-text', 'text-only'].includes(buttonStyle) && !this.context.member && buttonText;
|
||||
}
|
||||
|
||||
renderText() {
|
||||
const {
|
||||
portal_button_signup_text: buttonText
|
||||
} = this.context.site;
|
||||
const {brandColor} = this.context;
|
||||
const textColor = getContrastColor(brandColor);
|
||||
if (this.hasText()) {
|
||||
return (
|
||||
<span style={{padding: '0 12px', color: textColor}}> {buttonText} </span>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
onToggle() {
|
||||
this.context.onAction('togglePopup');
|
||||
}
|
||||
|
||||
render() {
|
||||
const hasText = this.hasText();
|
||||
const Style = Styles({brandColor: this.context.brandColor});
|
||||
|
||||
return (
|
||||
<Frame style={Style.frame} title="membersjs-trigger">
|
||||
<div style={Style.launcher} onClick={e => this.onToggle(e)}>
|
||||
<div style={Style.button}>
|
||||
if (hasText) {
|
||||
return (
|
||||
<div style={Style.button} onClick={e => this.onToggle(e)}>
|
||||
<div style={{padding: '0 24px', display: 'flex'}} ref={this.container}>
|
||||
{this.renderTriggerIcon()}
|
||||
{this.renderText()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div style={Style.button} onClick={e => this.onToggle(e)}>
|
||||
<div style={{padding: '0 24px', display: 'flex'}}>
|
||||
{this.renderTriggerIcon()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default class TriggerButton extends React.Component {
|
||||
static contextType = AppContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
width: null
|
||||
};
|
||||
}
|
||||
|
||||
onWidthChange(width) {
|
||||
this.setState({width});
|
||||
}
|
||||
|
||||
hasText() {
|
||||
const {
|
||||
portal_button_signup_text: buttonText,
|
||||
portal_button_style: buttonStyle
|
||||
} = this.context.site;
|
||||
return ['icon-and-text', 'text-only'].includes(buttonStyle) && !this.context.member && buttonText;
|
||||
}
|
||||
|
||||
render() {
|
||||
const hasText = this.hasText();
|
||||
const Style = Styles({brandColor: this.context.brandColor, hasText});
|
||||
|
||||
const frameStyle = {
|
||||
...Style.frame
|
||||
};
|
||||
if (this.state.width) {
|
||||
const updatedWidth = this.state.width + 2;
|
||||
frameStyle.width = `${updatedWidth}px`;
|
||||
}
|
||||
|
||||
return (
|
||||
<Frame style={frameStyle} title="membersjs-trigger">
|
||||
<TriggerButtonContent isPopupOpen={this.props.isPopupOpen} updateWidth={width => this.onWidthChange(width)} />
|
||||
</Frame>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
import React from 'react';
|
||||
import getContrastColor from '../../utils/contrast-color';
|
||||
|
||||
const Styles = ({brandColor, retry, disabled, style = {}}) => {
|
||||
let backgroundColor = (brandColor || '#3eb0ef');
|
||||
if (retry) {
|
||||
backgroundColor = 'red';
|
||||
backgroundColor = '#FF0000';
|
||||
}
|
||||
|
||||
if (disabled) {
|
||||
backgroundColor = 'grey';
|
||||
backgroundColor = '#D3D3D3';
|
||||
}
|
||||
const textColor = getContrastColor(backgroundColor);
|
||||
return {
|
||||
button: {
|
||||
display: 'inline-block',
|
||||
|
@ -24,7 +26,7 @@ const Styles = ({brandColor, retry, disabled, style = {}}) => {
|
|||
borderRadius: '5px',
|
||||
cursor: 'pointer',
|
||||
transition: '.4s ease',
|
||||
color: '#fff',
|
||||
color: textColor,
|
||||
backgroundColor,
|
||||
boxShadow: 'none',
|
||||
userSelect: 'none',
|
||||
|
|
108
ghost/portal/src/components/pages/LinkPage.js
Normal file
108
ghost/portal/src/components/pages/LinkPage.js
Normal file
|
@ -0,0 +1,108 @@
|
|||
import AppContext from '../../AppContext';
|
||||
import CopyToClipboard from '../../utils/copy-to-clipboard';
|
||||
const React = require('react');
|
||||
|
||||
function getLinkOrAttribute({page, isLink, siteUrl}) {
|
||||
if (page === 'default') {
|
||||
return (isLink ? `${siteUrl}/#/portal` : 'data-portal');
|
||||
} else if (page === 'signup') {
|
||||
return (isLink ? `${siteUrl}/#/portal/signup` : `data-portal="signup"`);
|
||||
} else if (page === 'signin') {
|
||||
return (isLink ? `${siteUrl}/#/portal/signin` : `data-portal="signin"`);
|
||||
} else if (page === 'accountHome') {
|
||||
return (isLink ? `${siteUrl}/#/portal/account` : `data-portal="account"`);
|
||||
} else if (page === 'accountPlan') {
|
||||
return (isLink ? `${siteUrl}/#/portal/account/plans` : `data-portal="account/plans"`);
|
||||
} else if (page === 'accountProfile') {
|
||||
return (isLink ? `${siteUrl}/#/portal/account/profile` : `data-portal="account/profile"`);
|
||||
}
|
||||
}
|
||||
|
||||
const LinkAttributeValue = ({page, isLink, siteUrl}) => {
|
||||
const rightItemStyle = {
|
||||
paddingBottom: '24px',
|
||||
display: 'flex'
|
||||
};
|
||||
const value = getLinkOrAttribute({page, isLink, siteUrl});
|
||||
return (
|
||||
<div style={rightItemStyle}>
|
||||
<span style={{
|
||||
flexGrow: 1,
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
paddingRight: '12px'
|
||||
}}> {value} </span>
|
||||
<button type="button" onClick={(e) => {
|
||||
CopyToClipboard(value);
|
||||
}}> Copy </button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const LinkAttributeSection = ({siteUrl, showLinks: isLink, toggleShowLinks}) => {
|
||||
return (
|
||||
<div style={{flexGrow: 1, minWidth: '350px'}}>
|
||||
<div style={{display: 'flex', borderBottom: '1px solid black', marginBottom: '12px'}}>
|
||||
<span style={{flexGrow: 1, fontWeight: 'bold'}}> {isLink ? 'Link' : 'Data Attribute'} </span>
|
||||
<LinkAttributeToggle showLinks={isLink} toggleShowLinks={toggleShowLinks}/>
|
||||
</div>
|
||||
<LinkAttributeValue page="default" isLink={isLink} siteUrl={siteUrl} />
|
||||
<LinkAttributeValue page="signup" isLink={isLink} siteUrl={siteUrl} />
|
||||
<LinkAttributeValue page="signin" isLink={isLink} siteUrl={siteUrl} />
|
||||
<LinkAttributeValue page="accountHome" isLink={isLink} siteUrl={siteUrl} />
|
||||
<LinkAttributeValue page="accountPlan" isLink={isLink} siteUrl={siteUrl} />
|
||||
<LinkAttributeValue page="accountProfile" isLink={isLink} siteUrl={siteUrl} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const LinkAttributeToggle = ({showLinks, toggleShowLinks}) => {
|
||||
const text = showLinks ? 'Show Data Attributes' : 'Show Links';
|
||||
return (
|
||||
<span
|
||||
style={{cursor: 'pointer', color: '#3eb0ef'}}
|
||||
onClick={() => toggleShowLinks({showLinks: !showLinks})}> {text}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export default class LinkPage extends React.Component {
|
||||
static contextType = AppContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
showLinks: true
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const itemStyle = {
|
||||
paddingRight: '32px',
|
||||
paddingBottom: '24px'
|
||||
};
|
||||
const {url: siteUrl = ''} = this.context.site;
|
||||
return (
|
||||
<div style={{display: 'flex', flexDirection: 'column', color: '#313131', padding: '12px 24px'}}>
|
||||
<div> Use these links or data attributes to show the various sections of members modal.</div>
|
||||
<div style={{display: 'flex', marginTop: '12px'}}>
|
||||
<div style={{flexShrink: 0}}>
|
||||
<div style={{borderBottom: '1px solid black', marginBottom: '12px', fontWeight: 'bold'}}> Section </div>
|
||||
<div style={itemStyle}> Default </div>
|
||||
<div style={itemStyle}> Signup </div>
|
||||
<div style={itemStyle}> Signin </div>
|
||||
<div style={itemStyle}> Account home </div>
|
||||
<div style={itemStyle}> Account -> Plans </div>
|
||||
<div style={itemStyle}> Account -> Profile </div>
|
||||
</div>
|
||||
<LinkAttributeSection
|
||||
showLinks={this.state.showLinks}
|
||||
toggleShowLinks={({showLinks}) => this.setState({showLinks})}
|
||||
siteUrl={siteUrl}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -217,7 +217,7 @@ function setupGhostApi({siteUrl = window.location.origin}) {
|
|||
api.init = async () => {
|
||||
// Load member from fixtures for local development
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
return {site: Fixtures.site, member: Fixtures.member.free};
|
||||
return {site: Fixtures.site, member: null};
|
||||
}
|
||||
|
||||
const {site} = await api.site.read();
|
||||
|
|
39
ghost/portal/src/utils/contrast-color.js
Normal file
39
ghost/portal/src/utils/contrast-color.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
function padZero(str, len) {
|
||||
len = len || 2;
|
||||
var zeros = new Array(len).join('0');
|
||||
return (zeros + str).slice(-len);
|
||||
}
|
||||
|
||||
function invertColor(hex, bw = true) {
|
||||
if (!hex.match(/#[0-9A-Fa-f]{6}$/)) {
|
||||
// Return white in case not a valid hex
|
||||
return '#000000';
|
||||
}
|
||||
if (hex.indexOf('#') === 0) {
|
||||
hex = hex.slice(1);
|
||||
}
|
||||
// convert 3-digit hex to 6-digits.
|
||||
if (hex.length === 3) {
|
||||
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
|
||||
}
|
||||
if (hex.length !== 6) {
|
||||
throw new Error('Invalid HEX color.');
|
||||
}
|
||||
let r = parseInt(hex.slice(0, 2), 16),
|
||||
g = parseInt(hex.slice(2, 4), 16),
|
||||
b = parseInt(hex.slice(4, 6), 16);
|
||||
if (bw) {
|
||||
// http://stackoverflow.com/a/3943023/112731
|
||||
return (r * 0.299 + g * 0.587 + b * 0.114) > 186
|
||||
? '#000000'
|
||||
: '#FFFFFF';
|
||||
}
|
||||
// invert color components
|
||||
r = (255 - r).toString(16);
|
||||
g = (255 - g).toString(16);
|
||||
b = (255 - b).toString(16);
|
||||
// pad each with zeros and return
|
||||
return '#' + padZero(r) + padZero(g) + padZero(b);
|
||||
}
|
||||
|
||||
module.exports = invertColor;
|
13
ghost/portal/src/utils/copy-to-clipboard.js
Normal file
13
ghost/portal/src/utils/copy-to-clipboard.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
function copyTextToClipboard(text) {
|
||||
let textarea = document.createElement('textarea');
|
||||
textarea.value = text;
|
||||
textarea.setAttribute('readonly', '');
|
||||
textarea.style.position = 'absolute';
|
||||
textarea.style.left = '-9999px';
|
||||
document.body.appendChild(textarea);
|
||||
textarea.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(textarea);
|
||||
}
|
||||
|
||||
module.exports = copyTextToClipboard;
|
|
@ -2,9 +2,7 @@ export const site = {
|
|||
title: 'Ghost Site',
|
||||
description: 'Thoughts, stories and ideas.',
|
||||
logo: 'https://pbs.twimg.com/profile_images/1111773508231667713/mf2N0uqc_400x400.png',
|
||||
brand: {
|
||||
primaryColor: '#AB19E4'
|
||||
},
|
||||
accent_color: '#AB19E4',
|
||||
url: 'http://localhost:2368/',
|
||||
plans: {
|
||||
monthly: 12,
|
||||
|
@ -16,7 +14,10 @@ export const site = {
|
|||
is_stripe_configured: true,
|
||||
portal_button: true,
|
||||
portal_name: true,
|
||||
portal_plans: ['free', 'monthly', 'yearly']
|
||||
portal_plans: ['free', 'monthly', 'yearly'],
|
||||
portal_button_icon: 'https://raw.githubusercontent.com/TryGhost/members.js/master/src/images/icons/user.svg',
|
||||
portal_button_signup_text: 'Subscribe Now for free access to everything',
|
||||
portal_button_style: 'icon-and-text'
|
||||
};
|
||||
|
||||
export const member = {
|
||||
|
|
|
@ -21,7 +21,7 @@ const customRender = (ui, {options = {}, overrideContext = {}} = {}) => {
|
|||
site,
|
||||
member: member.free,
|
||||
action: 'init:success',
|
||||
brandColor: site.brand.primaryColor,
|
||||
brandColor: site.accent_color,
|
||||
page: 'signup',
|
||||
onAction: mockOnActionFn,
|
||||
...overrideContext
|
||||
|
|
Loading…
Add table
Reference in a new issue