mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-24 14:43:08 +03:00
Added share/send preview footers to post preview modal
refs https://github.com/TryGhost/Team/issues/451 - disabled email tab when previewing a page (pages can't be emailed) - added share preview url footer to browser tab - added send preview footer to email tab - added first pass at social tab contents
This commit is contained in:
parent
76419917bf
commit
7f7d2cafc6
@ -2,8 +2,10 @@
|
||||
<header class="modal-header gh-pe-header gh-pe-header-border" data-test-modal="preview-email" style="display:flex">
|
||||
<h2 class="f6 fw6">Preview post</h2>
|
||||
<div class="gh-contentfilter gh-btn-group gh-pe-btn-group" style="display:flex;flex-grow:1;justify-content:center">
|
||||
<button type="button" class="gh-btn {{if (eq this.tab "desktop") "gh-btn-group-selected"}}" {{on "click" (fn this.changeTab "desktop")}}><span>Desktop</span></button>
|
||||
<button type="button" class="gh-btn {{if (eq this.tab "email") "gh-btn-group-selected"}}" {{on "click" (fn this.changeTab "email")}}><span>Email</span></button>
|
||||
<button type="button" class="gh-btn {{if (eq this.tab "browser") "gh-btn-group-selected"}}" {{on "click" (fn this.changeTab "browser")}}><span>Browser</span></button>
|
||||
{{#if this.model.isPost}}
|
||||
<button type="button" class="gh-btn {{if (eq this.tab "email") "gh-btn-group-selected"}}" {{on "click" (fn this.changeTab "email")}}><span>Email</span></button>
|
||||
{{/if}}
|
||||
<button type="button" class="gh-btn {{if (eq this.tab "social") "gh-btn-group-selected"}}" {{on "click" (fn this.changeTab "social")}}><span>Social</span></button>
|
||||
</div>
|
||||
<div class="gh-pe-close">
|
||||
@ -13,11 +15,11 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{{#if (eq this.tab "desktop")}}
|
||||
<ModalPostPreview::Desktop @post={{@model}} />
|
||||
{{#if (eq this.tab "browser")}}
|
||||
<ModalPostPreview::Browser @post={{@model}} />
|
||||
{{/if}}
|
||||
|
||||
{{#if (eq this.tab "email")}}
|
||||
{{#if (and (eq this.tab "email") this.model.isPost)}}
|
||||
<ModalPostPreview::Email @post={{@model}} />
|
||||
{{/if}}
|
||||
|
||||
|
@ -6,7 +6,7 @@ import {tracked} from '@glimmer/tracking';
|
||||
// TODO: update modals to work fully with Glimmer components
|
||||
@classic
|
||||
export default class ModalPostPreviewComponent extends ModalBase {
|
||||
@tracked tab = 'desktop';
|
||||
@tracked tab = 'browser';
|
||||
|
||||
@action
|
||||
changeTab(tab) {
|
||||
@ -17,4 +17,15 @@ export default class ModalPostPreviewComponent extends ModalBase {
|
||||
close() {
|
||||
this.closeModal();
|
||||
}
|
||||
|
||||
actions = {
|
||||
confirm() {
|
||||
// noop - needed to override ModalBase.actions.confirm
|
||||
},
|
||||
|
||||
// needed because ModalBase uses .send() for keyboard events
|
||||
closeModal() {
|
||||
this.closeModal();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
20
ghost/admin/app/components/modal-post-preview/browser.hbs
Normal file
20
ghost/admin/app/components/modal-post-preview/browser.hbs
Normal file
@ -0,0 +1,20 @@
|
||||
<div class="modal-body modal-preview-email-content gh-pe-desktop-container flex-grow-1 h-auto overflow-auto">
|
||||
<div class="gh-pe-emailclient-mockup">
|
||||
<iframe class="gh-pe-iframe" src={{@post.previewUrl}} style="height: 100%"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center pa4 pl8 bt b--whitegrey">
|
||||
<span class="mr3 flex-grow-1 nowrap">Share this preview privately</span>
|
||||
<div class="mr3 truncate midlightgrey">
|
||||
{{@post.previewUrl}}
|
||||
</div>
|
||||
<button type="button" {{on "click" (perform this.copyPreviewUrl)}} class="gh-btn gh-btn-green gh-btn-icon">
|
||||
<span>
|
||||
{{#if this.copyPreviewUrl.isRunning}}
|
||||
{{svg-jar "check-circle" class="w3 v-mid mr2 stroke-white"}} Copied
|
||||
{{else}}
|
||||
Copy
|
||||
{{/if}}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
12
ghost/admin/app/components/modal-post-preview/browser.js
Normal file
12
ghost/admin/app/components/modal-post-preview/browser.js
Normal file
@ -0,0 +1,12 @@
|
||||
import Component from '@glimmer/component';
|
||||
import copyTextToClipboard from 'ghost-admin/utils/copy-text-to-clipboard';
|
||||
import {task} from 'ember-concurrency-decorators';
|
||||
import {timeout} from 'ember-concurrency';
|
||||
|
||||
export default class ModalPostPreviewBrowserComponent extends Component {
|
||||
@task
|
||||
*copyPreviewUrl() {
|
||||
copyTextToClipboard(this.args.post.previewUrl);
|
||||
yield timeout(this.isTesting ? 50 : 3000);
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
<div class="modal-body modal-preview-email-content gh-pe-desktop-container flex-grow-1" style="height: auto">
|
||||
<div class="gh-pe-emailclient-mockup">
|
||||
<iframe class="gh-pe-iframe" src={{@post.previewUrl}} style="height: 100%"></iframe>
|
||||
</div>
|
||||
</div>
|
@ -1,4 +1,4 @@
|
||||
<div class="modal-body modal-preview-email-content gh-pe-mobile-container">
|
||||
<div class="modal-body modal-preview-email-content gh-pe-mobile-container h-auto overflow-auto">
|
||||
<div class="gh-pe-mobile-bezel">
|
||||
<div class="gh-pe-mobile-screen">
|
||||
<div class="gh-pe-emailclient-sender">
|
||||
@ -11,3 +11,25 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center pa4 pl8 bt b--whitegrey">
|
||||
<span class="mr3 flex-grow-1 nowrap">Send a preview newsletter</span>
|
||||
<div class="mr3 truncate midlightgrey">
|
||||
<Input
|
||||
@value={{this.previewEmailAddress}}
|
||||
class="gh-input"
|
||||
placeholder="you@yoursite.com"
|
||||
{{on-key "Enter" (perform this.sendPreviewEmailTask)}}
|
||||
/>
|
||||
|
||||
{{#if this.sendPreviewEmailError}}
|
||||
<div class="error"><p class="response">{{this.sendPreviewEmailError}}</p></div>
|
||||
{{/if}}
|
||||
</div>
|
||||
<GhTaskButton
|
||||
@task={{this.sendPreviewEmailTask}}
|
||||
@buttonText="Send"
|
||||
@successText="Sent"
|
||||
@runningText="Sending..."
|
||||
@class="gh-btn gh-btn-green gh-btn-icon"
|
||||
/>
|
||||
</div>
|
@ -1,6 +1,9 @@
|
||||
import Component from '@glimmer/component';
|
||||
import validator from 'validator';
|
||||
import {action} from '@ember/object';
|
||||
import {htmlSafe} from '@ember/string';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task} from 'ember-concurrency-decorators';
|
||||
import {timeout} from 'ember-concurrency';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
@ -24,6 +27,15 @@ export default class ModalPostPreviewEmailComponent extends Component {
|
||||
|
||||
@tracked html = '';
|
||||
@tracked subject = '';
|
||||
@tracked emailPreviewAddress = '';
|
||||
@tracked sendPreviewEmailError = '';
|
||||
|
||||
get mailgunIsEnabled() {
|
||||
return this.config.get('mailgunIsConfigured') ||
|
||||
this.settings.get('mailgunApiKey') &&
|
||||
this.settings.get('mailgunDomain') &&
|
||||
this.settings.get('mailgunBaseUrl');
|
||||
}
|
||||
|
||||
@action
|
||||
async renderEmailPreview(iframe) {
|
||||
@ -38,6 +50,46 @@ export default class ModalPostPreviewEmailComponent extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
@task({drop: true})
|
||||
*sendPreviewEmailTask() {
|
||||
try {
|
||||
const resourceId = this.post.id;
|
||||
const testEmail = this.emailPreviewAddress.trim();
|
||||
|
||||
if (!validator.isEmail(testEmail)) {
|
||||
this.sendPreviewEmailError = 'Please enter a valid email';
|
||||
return false;
|
||||
}
|
||||
if (!this.mailgunIsEnabled) {
|
||||
this.sendPreviewEmailError = 'Please verify your email settings';
|
||||
return false;
|
||||
}
|
||||
this.sendPreviewEmailError = '';
|
||||
|
||||
const url = this.ghostPaths.url.api('/email_preview/posts', resourceId);
|
||||
const data = {emails: [testEmail]};
|
||||
const options = {
|
||||
data,
|
||||
dataType: 'json'
|
||||
};
|
||||
|
||||
return yield this.ajax.post(url, options);
|
||||
} catch (error) {
|
||||
if (error) {
|
||||
let message = 'Email could not be sent, verify mail settings';
|
||||
|
||||
// grab custom error message if present
|
||||
if (
|
||||
error.payload && error.payload.errors
|
||||
&& error.payload.errors[0] && error.payload.errors[0].message) {
|
||||
message = htmlSafe(error.payload.errors[0].message);
|
||||
}
|
||||
|
||||
this.sendPreviewEmailError = message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async _fetchEmailData() {
|
||||
let {html, subject} = this;
|
||||
let {post} = this.args;
|
||||
|
@ -0,0 +1,42 @@
|
||||
|
||||
<div class="modal-body pa8 flex-grow-1">
|
||||
<div class="gh-og-preview w-40 mb6">
|
||||
{{#if this.facebookImage}}
|
||||
<div class="gh-og-preview-image" style={{background-image-style this.facebookImage}}></div>
|
||||
{{/if}}
|
||||
<div class="gh-og-preview-content">
|
||||
<div class="gh-og-preview-title">{{truncate this.facebookTitle 88}}</div>
|
||||
<div class="gh-og-preview-description">{{truncate this.facebookDescription 300}}</div>
|
||||
<div class="gh-og-preview-footer">
|
||||
<div class="gh-og-preview-footer-left">
|
||||
{{this.config.blogDomain}} <span class="gh-og-preview-footer-left-divider">|</span> by <span class="gh-og-preview-footer-author">{{author-names @post.authors}}</span>
|
||||
</div>
|
||||
<div class="gh-og-preview-footer-right">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gh-twitter-preview w-40 mb6">
|
||||
{{#if this.twitterImage}}
|
||||
<div class="gh-twitter-preview-image" style={{background-image-style this.twitterImage}}></div>
|
||||
{{/if}}
|
||||
<div class="gh-twitter-preview-content">
|
||||
<div class="gh-twitter-preview-title">{{this.twitterTitle}}</div>
|
||||
<div class="gh-twitter-preview-description">{{truncate this.twitterDescription 155}}</div>
|
||||
<div class="gh-twitter-preview-footer">
|
||||
<div class="gh-twitter-preview-footer-left">
|
||||
{{this.config.blogDomain}}
|
||||
</div>
|
||||
<div class="gh-twitter-preview-footer-right">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="seo-preview w-40 mb6">
|
||||
<div class="seo-preview-title">{{truncate this.serpTitle 70}}</div>
|
||||
<div class="seo-preview-link">{{truncate this.serpURL 70}}</div>
|
||||
<div class="seo-preview-description">{{truncate this.serpDescription 300}}</div>
|
||||
</div>
|
||||
</div>
|
65
ghost/admin/app/components/modal-post-preview/social.js
Normal file
65
ghost/admin/app/components/modal-post-preview/social.js
Normal file
@ -0,0 +1,65 @@
|
||||
import Component from '@glimmer/component';
|
||||
import formatMarkdown from 'ghost-admin/utils/format-markdown';
|
||||
import {inject as service} from '@ember/service';
|
||||
|
||||
export default class ModalPostPreviewSocialComponent extends Component {
|
||||
@service config;
|
||||
@service settings;
|
||||
|
||||
get _fallbackDescription() {
|
||||
return this.args.post.customExcerpt ||
|
||||
this.serpDescription ||
|
||||
this.args.post.excerpt ||
|
||||
this.settings.get('description');
|
||||
}
|
||||
|
||||
// SERP
|
||||
|
||||
get serpTitle() {
|
||||
return this.args.post.metaTitle || this.args.post.title || '(Untitled)';
|
||||
}
|
||||
|
||||
get serpDescription() {
|
||||
const metaDescription = this.args.post.metaDescription;
|
||||
|
||||
if (metaDescription) {
|
||||
return metaDescription;
|
||||
}
|
||||
|
||||
const mobiledoc = this.args.post.scratch;
|
||||
const [markdownCard] = (mobiledoc && mobiledoc.cards) || [];
|
||||
const markdown = markdownCard && markdownCard[1] && markdownCard[1].markdown;
|
||||
|
||||
let serpDescription;
|
||||
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = formatMarkdown(markdown, false);
|
||||
|
||||
// Strip HTML
|
||||
serpDescription = div.textContent;
|
||||
// Replace new lines and trim
|
||||
serpDescription = serpDescription.replace(/\n+/g, ' ').trim();
|
||||
|
||||
return serpDescription;
|
||||
}
|
||||
|
||||
// Facebook
|
||||
|
||||
get facebookTitle() {
|
||||
return this.args.post.ogTitle || this.serpTitle;
|
||||
}
|
||||
|
||||
get facebookDescription() {
|
||||
return this.args.post.ogDescription || this._fallbackDescription;
|
||||
}
|
||||
|
||||
// Twitter
|
||||
|
||||
get twitterTitle() {
|
||||
return this.args.post.twitterTitle || this.serpTitle;
|
||||
}
|
||||
|
||||
get twitterDescription() {
|
||||
return this.args.post.twitterDescription || this._fallbackDescription;
|
||||
}
|
||||
}
|
@ -19,6 +19,7 @@
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
min-height: 70px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@ -146,9 +147,9 @@
|
||||
}
|
||||
|
||||
.fullscreen-modal-email-preview .gh-pe-mobile-container {
|
||||
height: calc(100vh - 140px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
align-items: center;
|
||||
background: var(--whitegrey-l1);
|
||||
padding: 30px 30px 45px;
|
||||
|
@ -35,6 +35,10 @@ module.exports = function (environment) {
|
||||
|
||||
moment: {
|
||||
includeTimezone: 'all'
|
||||
},
|
||||
|
||||
emberKeyboard: {
|
||||
disableInputsInitializer: true
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -84,6 +84,7 @@
|
||||
"ember-fetch": "8.0.4",
|
||||
"ember-in-viewport": "3.8.1",
|
||||
"ember-infinity": "2.1.2",
|
||||
"ember-keyboard": "^6.0.2",
|
||||
"ember-load": "0.0.17",
|
||||
"ember-load-initializers": "2.1.2",
|
||||
"ember-mocha": "0.16.2",
|
||||
|
@ -6276,6 +6276,16 @@ ember-invoke-action@^1.5.0:
|
||||
dependencies:
|
||||
ember-cli-babel "^6.6.0"
|
||||
|
||||
ember-keyboard@^6.0.2:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/ember-keyboard/-/ember-keyboard-6.0.2.tgz#efe260be34621a403a4d4688b038d65b371f6892"
|
||||
integrity sha512-ZGAXYGfN4gxGFRcv3ix2X8HA8j/VluwhlENT9pfbFjAAGtvFz9wzNUJuaq3LS5Ksj+f2oXL5f++tSOrO7Ha1wA==
|
||||
dependencies:
|
||||
ember-cli-babel "^7.22.1"
|
||||
ember-cli-htmlbars "^5.3.1"
|
||||
ember-compatibility-helpers "^1.2.1"
|
||||
ember-modifier "^2.1.0"
|
||||
|
||||
ember-load-initializers@2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ember-load-initializers/-/ember-load-initializers-2.1.2.tgz#8a47a656c1f64f9b10cecdb4e22a9d52ad9c7efa"
|
||||
|
Loading…
Reference in New Issue
Block a user