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:
parent
c21cd827bc
commit
5152e0e042
8 changed files with 266 additions and 203 deletions
|
@ -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 <body> 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>
|
127
ghost/admin/app/components/modals/settings/signup-form-embed.js
Normal file
127
ghost/admin/app/components/modals/settings/signup-form-embed.js
Normal 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, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -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 <body> 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>
|
||||
|
|
|
@ -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, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
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, {}, {});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<div style={{html-safe (concat "height: " this.cachedHeight "px")}}>
|
||||
<div>
|
||||
<iframe
|
||||
srcDoc="<!DOCTYPE html>"
|
||||
class="gh-signup-form-iframe"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue