0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-01-06 22:40:14 -05:00

Moved signup form embed to a modal

refs https://github.com/TryGhost/Team/issues/3340
This commit is contained in:
Simon Backx 2023-06-02 09:31:09 +02:00
parent c21cd827bc
commit 5152e0e042
8 changed files with 266 additions and 203 deletions

View file

@ -0,0 +1,82 @@
<div class="modal-content modal-signup-form-embed" data-test-modal="signup-form-embed">
<button type="button" class="close" title="Close" {{on "click" @close}}>{{svg-jar "close"}}<span class="hidden">Close</span></button>
<div class="modal-signup-form-embed-preview">
<Settings::SignupForm::Preview
@html={{this.previewCode}}
/>
</div>
<div class="modal-signup-form-embed-main">
<header class="modal-header">
<h1>Embed a signup form</h1>
</header>
<div class="modal-body">
<div class="gh-signup-form-container">
<div class="gh-stack-item gh-setting-first">
<div class="flex-grow-1">
<label class="gh-setting-title">
Choose layout
</label>
<Settings::SignupForm::StyleSelect
@value={{this.style}}
@onChange={{this.setStyle}}
/>
</div>
</div>
<div class="gh-stack-item gh-setting-first">
<div class="flex-grow-1">
<label class="gh-setting-title">
Apply labels at signup
</label>
<GhMemberLabelInput
@onChange={{this.setLabels}}
@allowEdit={{false}}
@labels={{this.labels}}
@triggerId="label-input"
/>
</div>
</div>
<div>
<div class="form-group gh-stack-item row">
<label class="fw6 f8">Background color</label>
<Modals::Newsletters::Components::ColorPicker
@color={{this.backgroundColor}}
@presetColors={{this.backgroundPresetColors}}
@onColorChange={{this.setBackgroundColor}}
/>
</div>
</div>
<div class="gh-stack-item gh-setting-first">
<div class="flex-grow-1">
<label class="gh-setting-title">
Embed code
</label>
<textarea id="gh-signup-form-embed-code-input" class="gh-input" readonly>{{this.generatedCode}}</textarea>
<p>Copy and paste this HTML code somewhere inside the &lt;body&gt; tag of your site.</p>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<GhTaskButton
@buttonText="Copy code"
@task={{this.copyText}}
@successText="Copied"
@class="gh-btn gh-btn-black gh-btn-icon"
/>
</div>
</div>
</div>

View file

@ -0,0 +1,127 @@
import Component from '@glimmer/component';
import {action} from '@ember/object';
import {inject} from 'ghost-admin/decorators/inject';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency';
import {textColorForBackgroundColor} from '@tryghost/color-utils';
import {tracked} from '@glimmer/tracking';
function escapeHtml(unsafe) {
if (!unsafe) {
return '';
}
return unsafe
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
export default class SignupFormEmbedModal extends Component {
@service modals;
@service settings;
@service feature;
@tracked style = 'all-in-one';
@tracked labels = [];
@tracked backgroundColor = '#f9f9f9';
@inject config;
@service notifications;
static modalOptions = {
className: 'fullwidth-modal'
};
@action
setStyle(style) {
this.style = style;
}
@action
setLabels(labels) {
this.labels = labels;
}
@action
setBackgroundColor(backgroundColor) {
this.backgroundColor = backgroundColor;
}
get backgroundPresetColors() {
return [
{
value: '#f9f9f9',
name: 'Light grey',
class: '',
style: 'background: #f9f9f9 !important;'
},
{
value: '#000000',
name: 'Black',
class: '',
style: 'background: #000000 !important;'
}
];
}
get generatedCode() {
return this.generateCode({preview: false});
}
get previewCode() {
return this.generateCode({preview: true});
}
generateCode({preview}) {
const siteUrl = this.config.blogUrl;
const scriptUrl = this.config.signupForm.url.replace('{version}', this.config.signupForm.version);
const options = {
site: siteUrl,
'button-color': this.settings.accentColor,
'button-text-color': textColorForBackgroundColor(this.settings.accentColor).hex()
};
for (const [i, label] of this.labels.entries()) {
options[`label-${i + 1}`] = label.name;
}
let style = 'min-height: 58px';
if (this.style === 'all-in-one') {
options.logo = this.settings.icon;
options.title = this.settings.title;
options.description = this.settings.description;
options['background-color'] = this.backgroundColor;
options['text-color'] = textColorForBackgroundColor(this.backgroundColor).hex();
style = 'height: 40vmin; min-height: 360px;';
}
if (preview) {
style = 'height: 100vh';
}
let dataOptionsString = '';
for (const [key, value] of Object.entries(options)) {
dataOptionsString += ` data-${key}="${escapeHtml(value)}"`;
}
return `<div style="${escapeHtml(style)}"><script src="${encodeURI(scriptUrl)}"${dataOptionsString}></script></div>`;
}
@task
*copyText() {
// Copy this.generatedCode tp the clipboard
const el = document.createElement('textarea');
el.value = this.generatedCode;
document.body.appendChild(el);
el.select();
document.execCommand('copy');
document.body.removeChild(el);
yield true;
return true;
}
}

View file

@ -1,4 +1,4 @@
<section class="gh-main-section">
<section class="gh-main-section">
<h4 class="gh-main-section-header small bn">Signup Form</h4>
<div class="gh-expandable">
<div class="gh-expandable-block">
@ -7,78 +7,10 @@
<h4 class="gh-expandable-title">Embeddable Signup Form</h4>
<p class="gh-expandable-description">Grow your audience from anywhere on the web</p>
</div>
<button type="button" class="gh-btn" {{on "click" (toggle "opened" this)}}>
<span>{{if this.opened "Close" "Expand"}}</span>
<button type="button" class="gh-btn" {{on "click" this.open}}>
<span>Embed</span>
</button>
</div>
<div class="gh-expandable-content">
{{#liquid-if this.opened}}
<div class="gh-setting-content-extended">
<div class="gh-signup-form-container">
<div>
<div class="form-group max-width">
<label class="fw6 f8">Embed style</label>
<div class="gh-setting-richdd-container gh-setting-rich-dropdown">
<Settings::SignupForm::StyleSelect
@value={{this.style}}
@onChange={{this.setStyle}}
/>
</div>
</div>
</div>
<div>
<div class="form-group max-width">
<label class="fw6 f8">Apply labels</label>
<div class="gh-setting-richdd-container gh-setting-rich-dropdown">
<GhMemberLabelInput
@onChange={{this.setLabels}}
@allowEdit={{false}}
@labels={{this.labels}}
@triggerId="label-input"
/>
<p>Members who sign up via the form will have these labels.</p>
</div>
</div>
<div class="form-group gh-stack-item row">
<label class="fw6 f8">Background color</label>
<Modals::Newsletters::Components::ColorPicker
@color={{this.backgroundColor}}
@presetColors={{this.backgroundPresetColors}}
@onColorChange={{this.setBackgroundColor}}
/>
</div>
</div>
</div>
<div class="form-group max-width">
<label class="fw6 f8" for="gh-signup-form-embed-code-input">Embed code</label>
<div class="gh-setting-richdd-container gh-setting-rich-dropdown">
<div class="gh-signup-form-embed-code">
<input id="gh-signup-form-embed-code-input" class="gh-input" type="text" readonly value={{this.generatedCode}}/>
<GhTaskButton
@buttonText="Copy"
@task={{this.copyText}}
@successText="Copied"
@class="gh-btn gh-btn-black gh-btn-icon"
/>
</div>
<p>Copy and paste this HTML code somewhere inside the &lt;body&gt; tag of your site.</p>
</div>
</div>
<div class="form-group max-width">
<label class="fw6 f8">Preview</label>
<div class="gh-setting-richdd-container gh-setting-rich-dropdown">
<Settings::SignupForm::Preview
@html={{this.generatedCode}}
/>
</div>
</div>
</div>
{{/liquid-if}}
</div>
</div>
</div>
</section>

View file

@ -1,111 +1,13 @@
import Component from '@glimmer/component';
import SignupFormEmbedModal from '../../components/modals/settings/signup-form-embed';
import {action} from '@ember/object';
import {inject} from 'ghost-admin/decorators/inject';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency';
import {textColorForBackgroundColor} from '@tryghost/color-utils';
import {tracked} from '@glimmer/tracking';
function escapeHtml(unsafe) {
if (!unsafe) {
return '';
}
return unsafe
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
export default class SignupFormEmbed extends Component {
@service settings;
@service feature;
@tracked opened = false;
@tracked style = 'all-in-one';
@tracked labels = [];
@tracked backgroundColor = '#f9f9f9';
@inject config;
@service notifications;
@service modals;
@action
setStyle(style) {
this.style = style;
}
@action
setLabels(labels) {
this.labels = labels;
}
@action
setBackgroundColor(backgroundColor) {
this.backgroundColor = backgroundColor;
}
get backgroundPresetColors() {
return [
{
value: '#f9f9f9',
name: 'Light grey',
class: '',
style: 'background: #f9f9f9 !important;'
},
{
value: '#000000',
name: 'Black',
class: '',
style: 'background: #000000 !important;'
}
];
}
get generatedCode() {
const siteUrl = this.config.blogUrl;
const scriptUrl = this.config.signupForm.url.replace('{version}', this.config.signupForm.version);
const options = {
site: siteUrl,
'button-color': this.settings.accentColor,
'button-text-color': textColorForBackgroundColor(this.settings.accentColor).hex()
};
for (const [i, label] of this.labels.entries()) {
options[`label-${i + 1}`] = label.name;
}
let style = 'min-height: 58px';
if (this.style === 'all-in-one') {
options.logo = this.settings.icon;
options.title = this.settings.title;
options.description = this.settings.description;
options['background-color'] = this.backgroundColor;
options['text-color'] = textColorForBackgroundColor(this.backgroundColor).hex();
style = 'height: 40vmin; min-height: 360px;';
}
let dataOptionsString = '';
for (const [key, value] of Object.entries(options)) {
dataOptionsString += ` data-${key}="${escapeHtml(value)}"`;
}
return `<div style="${escapeHtml(style)}"><script src="${encodeURI(scriptUrl)}"${dataOptionsString}></script></div>`;
}
@task
*copyText() {
// Copy this.generatedCode tp the clipboard
const el = document.createElement('textarea');
el.value = this.generatedCode;
document.body.appendChild(el);
el.select();
document.execCommand('copy');
document.body.removeChild(el);
yield true;
return true;
open() {
this.modals.open(SignupFormEmbedModal, {}, {});
}
}

View file

@ -1,4 +1,4 @@
<div style={{html-safe (concat "height: " this.cachedHeight "px")}}>
<div>
<iframe
srcDoc="<!DOCTYPE html>"
class="gh-signup-form-iframe"

View file

@ -43,7 +43,7 @@ export default class Preview extends Component {
const height = this.iframeRoot.scrollHeight || this.cachedHeight;
this.iframe.style.height = `${height}px`;
//this.iframe.style.height = `${height}px`;
if (height > 30) {
this.cachedHeight = height;

View file

@ -1,16 +1,16 @@
<PowerSelect
@options={{this.options}}
@selected={{this.selectedOption}}
@onChange={{this.setRecipients}}
@triggerClass="gh-setting-dropdown"
@dropdownClass="gh-setting-dropdown-list"
as |option|
<span
class="gh-select mt2"
data-select-text="test"
tabindex="0"
>
<div class="gh-setting-dropdown-content">
{{svg-jar option.icon class=(concat "w8 h8 mr2 fill-" (or option.icon_color "green"))}}
<div class="gh-radio-label">
{{option.name}}<br>
<div class="gh-radio-desc">{{option.description}}</div>
</div>
</div>
</PowerSelect>
<OneWaySelect
@id="portal-button-style"
@name="portal[button-style]"
@options={{this.options}}
@optionValuePath="value"
@optionLabelPath="name"
@value={{this.selectedOption.value}}
@update={{this.setRecipients}}
/>
{{svg-jar "arrow-down-small"}}
</span>

View file

@ -3775,17 +3775,37 @@ p.theme-validation-details {
border: 0;
}
@media (min-width: 800px) {
.gh-signup-form-container {
display: flex;
gap: 20px;
width: 100%;
}
.gh-signup-form-container > * {
flex-basis: 0;
flex-shrink: 0;
min-width: 0;
flex-grow: 1;
}
.fullwidth-modal {
max-width: 100%;
}
.epm-modal .modal-signup-form-embed {
display: flex;
flex-direction: row;
margin: 32px;
align-items: stretch;
padding: 0;
}
.modal-signup-form-embed-preview {
flex-basis: 65%;
position: relative;
min-width: 500px;
}
.modal-signup-form-embed-preview iframe {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
}
.modal-signup-form-embed-main {
flex-basis: 35%;
flex-shrink: 0;
padding: 32px;
max-height: 80vh;
min-width: 400px;
overflow-y: auto;
}