mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-29 15:12:58 +03:00
Improved publishing flow end screen (#20701)
ref DES-594 - this update introduces some improvements to the publishing flow end screen - everything's behind a feature flag — publishFlowEndScreen
This commit is contained in:
parent
37fd9eaad5
commit
5ee67892dc
1
apps/admin-x-design-system/src/assets/icons/share.svg
Normal file
1
apps/admin-x-design-system/src/assets/icons/share.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" id="Share-1--Streamline-Streamline--3.0.svg" height="24" width="24"><desc>Share 1 Streamline Icon: https://streamlinehq.com</desc><defs></defs><title>share-1</title><path d="M17.25 8.25h1.5a1.5 1.5 0 0 1 1.5 1.5v12a1.5 1.5 0 0 1 -1.5 1.5H5.25a1.5 1.5 0 0 1 -1.5 -1.5v-12a1.5 1.5 0 0 1 1.5 -1.5h1.5" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="m12 0.75 0 10.5" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="M8.25 4.5 12 0.75l3.75 3.75" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path></svg>
|
After Width: | Height: | Size: 750 B |
@ -59,6 +59,14 @@ const features = [{
|
|||||||
title: 'Content Visibility',
|
title: 'Content Visibility',
|
||||||
description: 'Enables content visibility in Emails',
|
description: 'Enables content visibility in Emails',
|
||||||
flag: 'contentVisibility'
|
flag: 'contentVisibility'
|
||||||
|
},{
|
||||||
|
title: 'Publish Flow — End Screen',
|
||||||
|
description: 'Enables improved publish flow',
|
||||||
|
flag: 'publishFlowEndScreen'
|
||||||
|
},{
|
||||||
|
title: 'Post Analytics — Refresh',
|
||||||
|
description: 'Adds a refresh button to the post analytics screen',
|
||||||
|
flag: 'postAnalyticsRefresh'
|
||||||
}];
|
}];
|
||||||
|
|
||||||
const AlphaFeatures: React.FC = () => {
|
const AlphaFeatures: React.FC = () => {
|
||||||
|
@ -45,12 +45,14 @@
|
|||||||
@close={{@close}}
|
@close={{@close}}
|
||||||
/>
|
/>
|
||||||
{{else if this.isComplete}}
|
{{else if this.isComplete}}
|
||||||
<Editor::Modals::PublishFlow::Complete
|
{{#unless (feature "publishFlowEndScreen")}}
|
||||||
@publishOptions={{@data.publishOptions}}
|
<Editor::Modals::PublishFlow::Complete
|
||||||
@recipientType={{this.recipientType}}
|
@publishOptions={{@data.publishOptions}}
|
||||||
@postCount={{this.postCount}}
|
@recipientType={{this.recipientType}}
|
||||||
@close={{@close}}
|
@postCount={{this.postCount}}
|
||||||
/>
|
@close={{@close}}
|
||||||
|
/>
|
||||||
|
{{/unless}}
|
||||||
{{else}}
|
{{else}}
|
||||||
<Editor::Modals::PublishFlow::Options
|
<Editor::Modals::PublishFlow::Options
|
||||||
@publishOptions={{@data.publishOptions}}
|
@publishOptions={{@data.publishOptions}}
|
||||||
|
@ -13,6 +13,7 @@ function isString(str) {
|
|||||||
|
|
||||||
export default class PublishFlowOptions extends Component {
|
export default class PublishFlowOptions extends Component {
|
||||||
@service settings;
|
@service settings;
|
||||||
|
@service feature;
|
||||||
|
|
||||||
@tracked errorMessage;
|
@tracked errorMessage;
|
||||||
|
|
||||||
@ -91,6 +92,15 @@ export default class PublishFlowOptions extends Component {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
yield this.args.saveTask.perform();
|
yield this.args.saveTask.perform();
|
||||||
|
if (this.feature.publishFlowEndScreen) {
|
||||||
|
if (this.args.publishOptions.isScheduled) {
|
||||||
|
localStorage.setItem('ghost-last-scheduled-post', this.args.publishOptions.post.id);
|
||||||
|
window.location.href = '/ghost/#/posts?type=scheduled';
|
||||||
|
} else {
|
||||||
|
localStorage.setItem('ghost-last-published-post', this.args.publishOptions.post.id);
|
||||||
|
window.location.href = `/ghost/#/posts/analytics/${this.args.publishOptions.post.id}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e === undefined && this.args.publishOptions.post.errors.length !== 0) {
|
if (e === undefined && this.args.publishOptions.post.errors.length !== 0) {
|
||||||
// validation error
|
// validation error
|
||||||
|
152
ghost/admin/app/components/modal-post-success.hbs
Normal file
152
ghost/admin/app/components/modal-post-success.hbs
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
<div class="modal-content">
|
||||||
|
{{#if this.post.featureImage}}
|
||||||
|
<figure class="modal-image">
|
||||||
|
<img src="{{this.post.featureImage}}" alt="{{this.post.title}}">
|
||||||
|
</figure>
|
||||||
|
{{else if this.post.twitterImage}}
|
||||||
|
<figure class="modal-image">
|
||||||
|
<img src="{{this.post.twitterImage}}" alt="{{this.post.title}}">
|
||||||
|
</figure>
|
||||||
|
{{else if this.post.ogImage}}
|
||||||
|
<figure class="modal-image">
|
||||||
|
<img src="{{this.post.ogImage}}" alt="{{this.post.title}}">
|
||||||
|
</figure>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<header class="modal-header">
|
||||||
|
<h1>
|
||||||
|
{{#if this.post.isScheduled}}
|
||||||
|
<span>All set!</span>
|
||||||
|
{{else}}
|
||||||
|
<span>
|
||||||
|
{{#if this.showPostCount}}
|
||||||
|
Boom! It's out there.
|
||||||
|
{{else}}
|
||||||
|
Your post is out there.
|
||||||
|
{{/if}}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{{#if this.post.emailOnly}}
|
||||||
|
Your email has been sent.
|
||||||
|
{{else}}
|
||||||
|
{{#if this.showPostCount}}
|
||||||
|
That's {{format-number this.postCount}} {{gh-pluralize this.postCount "post" without-count=true}} published.
|
||||||
|
{{else}}
|
||||||
|
Share it with the world!
|
||||||
|
{{/if}}
|
||||||
|
{{/if}}
|
||||||
|
</span>
|
||||||
|
{{/if}}
|
||||||
|
</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<button type="button" class="close" title="Close" {{on "click" @close}}>{{svg-jar "close"}}<span class="hidden">Close</span></button>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
{{#if (and this.post.isPublished (not this.post.emailOnly))}}
|
||||||
|
{{#if this.showPostCount}}
|
||||||
|
Keep up the good work. Now, share your post with the world!
|
||||||
|
{{else}}
|
||||||
|
Spread the word to your audience and increase your reach.
|
||||||
|
{{/if}}
|
||||||
|
{{else}}
|
||||||
|
{{#if this.post.isSent}}
|
||||||
|
It
|
||||||
|
{{else}}
|
||||||
|
{{if this.post.emailOnly "Your email" "Your post"}}
|
||||||
|
{{/if}}
|
||||||
|
{{if this.post.isScheduled "will be" "was"}}
|
||||||
|
{{#if this.post.emailOnly}}
|
||||||
|
sent to
|
||||||
|
{{else if this.post.willEmail}}
|
||||||
|
published on your site, and sent to
|
||||||
|
{{else}}
|
||||||
|
published on your site
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if (or this.post.hasEmail this.post.willEmail)}}
|
||||||
|
{{#let (members-count-fetcher query=(hash filter=this.post.fullRecipientFilter)) as |countFetcher|}}
|
||||||
|
<strong>
|
||||||
|
{{if (eq @recipientType "all") "all"}}
|
||||||
|
|
||||||
|
{{format-number countFetcher.count}}
|
||||||
|
|
||||||
|
{{!-- @recipientType = free/paid/all/specific --}}
|
||||||
|
{{if (not-eq @recipientType "all") @recipientType}}
|
||||||
|
|
||||||
|
{{gh-pluralize countFetcher.count "subscriber" without-count=true}}
|
||||||
|
</strong>
|
||||||
|
|
||||||
|
of <strong>{{this.post.newsletter.name}}</strong>
|
||||||
|
{{/let}}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#let (moment-site-tz this.post.publishedAtUTC) as |publishedAt|}}
|
||||||
|
on
|
||||||
|
{{moment-format publishedAt "D MMM YYYY"}}
|
||||||
|
at
|
||||||
|
{{moment-format publishedAt "HH:mm"}}.
|
||||||
|
{{/let}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer class="modal-footer">
|
||||||
|
{{#if (and this.post.isPublished (not this.post.emailOnly))}}
|
||||||
|
<button
|
||||||
|
class="gh-btn twitter"
|
||||||
|
type="button"
|
||||||
|
{{on "click" this.handleTwitter}}
|
||||||
|
{{on "mousedown" (optional this.noop)}}
|
||||||
|
>
|
||||||
|
<span>{{svg-jar "social-x"}}</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="gh-btn threads"
|
||||||
|
type="button"
|
||||||
|
{{on "click" this.handleThreads}}
|
||||||
|
{{on "mousedown" (optional this.noop)}}
|
||||||
|
>
|
||||||
|
<span>{{svg-jar "social-threads"}}</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="gh-btn facebook"
|
||||||
|
type="button"
|
||||||
|
{{on "click" this.handleFacebook}}
|
||||||
|
{{on "mousedown" (optional this.noop)}}
|
||||||
|
>
|
||||||
|
<span>{{svg-jar "social-facebook"}}</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="gh-btn linkedin"
|
||||||
|
type="button"
|
||||||
|
{{on "click" this.handleLinkedIn}}
|
||||||
|
{{on "mousedown" (optional this.noop)}}
|
||||||
|
>
|
||||||
|
<span>{{svg-jar "social-linkedin"}}</span>
|
||||||
|
</button>
|
||||||
|
<GhTaskButton
|
||||||
|
@buttonText="Copy link"
|
||||||
|
@task={{this.handleCopyLink}}
|
||||||
|
@showIcon={{true}}
|
||||||
|
@successText="Link copied"
|
||||||
|
@class="gh-btn gh-btn-primary gh-btn-icon copy-link" />
|
||||||
|
{{else}}
|
||||||
|
{{#if (and this.post.isScheduled (not this.post.emailOnly))}}
|
||||||
|
<GhTaskButton
|
||||||
|
@buttonText="Copy preview link"
|
||||||
|
@task={{this.handleCopyPreviewLink}}
|
||||||
|
@successText="Link copied"
|
||||||
|
@class="gh-btn gh-btn-icon copy-preview-link" />
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="gh-btn gh-btn-primary dismiss"
|
||||||
|
type="button"
|
||||||
|
{{on "click" @close}}
|
||||||
|
{{on "mousedown" (optional this.noop)}}
|
||||||
|
>
|
||||||
|
<span>OK</span>
|
||||||
|
</button>
|
||||||
|
{{/if}}
|
||||||
|
</footer>
|
||||||
|
</div>
|
95
ghost/admin/app/components/modal-post-success.js
Normal file
95
ghost/admin/app/components/modal-post-success.js
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
import Component from '@glimmer/component';
|
||||||
|
import copyTextToClipboard from 'ghost-admin/utils/copy-text-to-clipboard';
|
||||||
|
import {action} from '@ember/object';
|
||||||
|
import {capitalize} from '@ember/string';
|
||||||
|
import {inject as service} from '@ember/service';
|
||||||
|
import {task, timeout} from 'ember-concurrency';
|
||||||
|
|
||||||
|
export default class PostSuccessModal extends Component {
|
||||||
|
@service store;
|
||||||
|
@service router;
|
||||||
|
@service notifications;
|
||||||
|
|
||||||
|
static modalOptions = {
|
||||||
|
className: 'fullscreen-modal-wide fullscreen-modal-action modal-post-success'
|
||||||
|
};
|
||||||
|
|
||||||
|
get post() {
|
||||||
|
return this.args.data.post;
|
||||||
|
}
|
||||||
|
|
||||||
|
get postCount() {
|
||||||
|
return this.args.data.postCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
get showPostCount() {
|
||||||
|
return this.args.data.showPostCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
handleTwitter() {
|
||||||
|
window.open(`https://twitter.com/intent/tweet?url=${encodeURI(this.post.url)}`, '_blank');
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
handleThreads() {
|
||||||
|
window.open(`https://threads.net/intent/post?text=${encodeURI(this.post.url)}`, '_blank');
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
handleFacebook() {
|
||||||
|
window.open(`https://www.facebook.com/sharer/sharer.php?u=${encodeURI(this.post.url)}`, '_blank');
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
handleLinkedIn() {
|
||||||
|
window.open(`http://www.linkedin.com/shareArticle?mini=true&url=${encodeURI(this.post.url)}`, '_blank');
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
viewInBrowser() {
|
||||||
|
window.open(this.post.url, '_blank');
|
||||||
|
}
|
||||||
|
|
||||||
|
@task
|
||||||
|
*handleCopyLink() {
|
||||||
|
copyTextToClipboard(this.post.url);
|
||||||
|
yield timeout(1000);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@task
|
||||||
|
*handleCopyPreviewLink() {
|
||||||
|
copyTextToClipboard(this.post.previewUrl);
|
||||||
|
yield timeout(1000);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@task
|
||||||
|
*revertToDraftTask() {
|
||||||
|
const currentPost = this.post;
|
||||||
|
const originalStatus = currentPost.status;
|
||||||
|
const originalPublishedAtUTC = currentPost.publishedAtUTC;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (currentPost.isScheduled) {
|
||||||
|
currentPost.publishedAtUTC = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPost.status = 'draft';
|
||||||
|
currentPost.emailOnly = false;
|
||||||
|
|
||||||
|
yield currentPost.save();
|
||||||
|
this.router.transitionTo('lexical-editor.edit', 'post', currentPost.id);
|
||||||
|
|
||||||
|
const postType = capitalize(currentPost.displayName);
|
||||||
|
this.notifications.showNotification(`${postType} reverted to a draft.`, {type: 'success'});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
currentPost.status = originalStatus;
|
||||||
|
currentPost.publishedAtUTC = originalPublishedAtUTC;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
<MultiList::List @model={{@list}} class="posts-list gh-list {{unless @model "no-posts"}} feature-memberAttribution" as |list| >
|
<MultiList::List @model={{@list}} class="posts-list gh-list {{unless @model "no-posts"}} feature-memberAttribution" as |list| >
|
||||||
{{!-- always order as scheduled, draft, remainder --}}
|
{{!-- always order as scheduled, draft, remainder --}}
|
||||||
{{#if (or @model.scheduledInfinityModel (or @model.draftInfinityModel @model.publishedAndSentInfinityModel))}}
|
{{#if (or @model.scheduledInfinityModel (or @model.draftInfinityModel @model.publishedAndSentInfinityModel))}}
|
||||||
{{#if @model.scheduledInfinityModel}}
|
{{#if @model.scheduledInfinityModel}}
|
||||||
{{#each @model.scheduledInfinityModel as |post|}}
|
{{#each @model.scheduledInfinityModel as |post|}}
|
||||||
@ -42,4 +42,4 @@
|
|||||||
as |menu|
|
as |menu|
|
||||||
>
|
>
|
||||||
<PostsList::ContextMenu @menu={{menu}} />
|
<PostsList::ContextMenu @menu={{menu}} />
|
||||||
</GhContextMenu>
|
</GhContextMenu>
|
@ -1,7 +1,39 @@
|
|||||||
import Component from '@glimmer/component';
|
import Component from '@glimmer/component';
|
||||||
|
import PostSuccessModal from '../modal-post-success';
|
||||||
|
import {inject as service} from '@ember/service';
|
||||||
|
import {task} from 'ember-concurrency';
|
||||||
|
|
||||||
export default class PostsList extends Component {
|
export default class PostsList extends Component {
|
||||||
|
@service store;
|
||||||
|
@service modals;
|
||||||
|
@service feature;
|
||||||
|
|
||||||
|
latestScheduledPost = null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(...arguments);
|
||||||
|
if (this.feature.publishFlowEndScreen) {
|
||||||
|
this.checkPublishFlowModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async checkPublishFlowModal() {
|
||||||
|
if (localStorage.getItem('ghost-last-scheduled-post')) {
|
||||||
|
await this.getLatestScheduledPost.perform();
|
||||||
|
this.modals.open(PostSuccessModal, {
|
||||||
|
post: this.latestScheduledPost
|
||||||
|
});
|
||||||
|
localStorage.removeItem('ghost-last-scheduled-post');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get list() {
|
get list() {
|
||||||
return this.args.list;
|
return this.args.list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@task
|
||||||
|
*getLatestScheduledPost() {
|
||||||
|
const result = yield this.store.query('post', {filter: `id:${localStorage.getItem('ghost-last-scheduled-post')}`, limit: 1});
|
||||||
|
this.latestScheduledPost = result.toArray()[0];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,9 +34,61 @@
|
|||||||
{{moment-format publishedAt "HH:mm"}}
|
{{moment-format publishedAt "HH:mm"}}
|
||||||
{{/let}}
|
{{/let}}
|
||||||
</div>
|
</div>
|
||||||
<LinkTo @route="lexical-editor.edit" @models={{array this.post.displayName this.post.id}} class="gh-post-list-cta edit" title="">
|
{{#if (feature "publishFlowEndScreen")}}
|
||||||
{{svg-jar "pen" title=""}}<span>Edit post</span>
|
<div style="display: flex; gap: 8px;">
|
||||||
</LinkTo>
|
{{#if (feature "postAnalyticsRefresh")}}
|
||||||
|
<button type="button" class="gh-post-list-cta edit" {{on "click" this.fetchPostTask.perform}}>
|
||||||
|
{{svg-jar "reload" title="Refresh post analytics"}}<span>Refresh</span>
|
||||||
|
</button>
|
||||||
|
{{/if}}
|
||||||
|
{{#unless this.post.emailOnly}}
|
||||||
|
<button type="button" class="gh-post-list-cta edit share" {{on "click" this.togglePublishFlowModal}}>
|
||||||
|
{{svg-jar "share" title="Share post"}}<span>Share</span>
|
||||||
|
</button>
|
||||||
|
{{/unless}}
|
||||||
|
|
||||||
|
<span class="dropdown">
|
||||||
|
<GhDropdownButton
|
||||||
|
@dropdownName="analytics-actions-menu"
|
||||||
|
@classNames="gh-post-list-cta edit gh-btn-icon icon-only gh-btn-action-icon"
|
||||||
|
@title="Analytics Actions"
|
||||||
|
data-test-button="analytics-actions"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
{{svg-jar "dotdotdot"}}
|
||||||
|
<span class="hidden">Actions</span>
|
||||||
|
</span>
|
||||||
|
</GhDropdownButton>
|
||||||
|
<GhDropdown
|
||||||
|
@name="analytics-actions-menu"
|
||||||
|
@tagName="ul"
|
||||||
|
@classNames="gh-analytics-actions-menu dropdown-menu dropdown-triangle-top-right"
|
||||||
|
@closeOnClick={{true}}
|
||||||
|
>
|
||||||
|
<li>
|
||||||
|
<LinkTo class="edit-post" @route="lexical-editor.edit" @models={{array this.post.displayName this.post.id}}>Edit post</LinkTo>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="view-browser" href="{{this.post.url}}" target="_blank" rel="noopener noreferrer">View in browser</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="delete-post mr2"
|
||||||
|
{{on "click" this.confirmDeleteMember}}
|
||||||
|
data-test-button="delete-post"
|
||||||
|
>
|
||||||
|
<span class="red">Delete post</span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</GhDropdown>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<LinkTo @route="lexical-editor.edit" @models={{array this.post.displayName this.post.id}} class="gh-post-list-cta edit" title="">
|
||||||
|
{{svg-jar "pen" title=""}}<span>Edit post</span>
|
||||||
|
</LinkTo>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</GhCanvasHeader>
|
</GhCanvasHeader>
|
||||||
@ -201,4 +253,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</section>
|
</section>
|
@ -1,4 +1,6 @@
|
|||||||
import Component from '@glimmer/component';
|
import Component from '@glimmer/component';
|
||||||
|
import DeletePostModal from '../modals/delete-post';
|
||||||
|
import PostSuccessModal from '../modal-post-success';
|
||||||
import {action} from '@ember/object';
|
import {action} from '@ember/object';
|
||||||
import {didCancel, task} from 'ember-concurrency';
|
import {didCancel, task} from 'ember-concurrency';
|
||||||
import {inject as service} from '@ember/service';
|
import {inject as service} from '@ember/service';
|
||||||
@ -24,6 +26,9 @@ export default class Analytics extends Component {
|
|||||||
@service utils;
|
@service utils;
|
||||||
@service feature;
|
@service feature;
|
||||||
@service store;
|
@service store;
|
||||||
|
@service router;
|
||||||
|
@service modals;
|
||||||
|
@service notifications;
|
||||||
|
|
||||||
@tracked sources = null;
|
@tracked sources = null;
|
||||||
@tracked links = null;
|
@tracked links = null;
|
||||||
@ -31,12 +36,47 @@ export default class Analytics extends Component {
|
|||||||
@tracked sortColumn = 'signups';
|
@tracked sortColumn = 'signups';
|
||||||
@tracked showSuccess;
|
@tracked showSuccess;
|
||||||
@tracked updateLinkId;
|
@tracked updateLinkId;
|
||||||
|
@tracked _post = null;
|
||||||
|
@tracked postCount = null;
|
||||||
|
@tracked showPostCount = false;
|
||||||
displayOptions = DISPLAY_OPTIONS;
|
displayOptions = DISPLAY_OPTIONS;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(...arguments);
|
||||||
|
if (this.feature.publishFlowEndScreen) {
|
||||||
|
this.checkPublishFlowModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
openPublishFlowModal() {
|
||||||
|
this.modals.open(PostSuccessModal, {
|
||||||
|
post: this.post,
|
||||||
|
postCount: this.postCount,
|
||||||
|
showPostCount: this.showPostCount
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async checkPublishFlowModal() {
|
||||||
|
if (localStorage.getItem('ghost-last-published-post')) {
|
||||||
|
await this.fetchPostCountTask.perform();
|
||||||
|
this.showPostCount = true;
|
||||||
|
this.openPublishFlowModal();
|
||||||
|
localStorage.removeItem('ghost-last-published-post');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get post() {
|
get post() {
|
||||||
|
if (this.feature.publishFlowEndScreen) {
|
||||||
|
return this._post ?? this.args.post;
|
||||||
|
}
|
||||||
|
|
||||||
return this.args.post;
|
return this.args.post;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set post(value) {
|
||||||
|
this._post = value;
|
||||||
|
}
|
||||||
|
|
||||||
get allowedDisplayOptions() {
|
get allowedDisplayOptions() {
|
||||||
if (!this.hasPaidConversionData) {
|
if (!this.hasPaidConversionData) {
|
||||||
return this.displayOptions.filter(d => d.value === 'signups');
|
return this.displayOptions.filter(d => d.value === 'signups');
|
||||||
@ -142,6 +182,19 @@ export default class Analytics extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
togglePublishFlowModal() {
|
||||||
|
this.showPostCount = false;
|
||||||
|
this.openPublishFlowModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
confirmDeleteMember() {
|
||||||
|
this.modals.open(DeletePostModal, {
|
||||||
|
post: this.post
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
updateLinkData(linksData) {
|
updateLinkData(linksData) {
|
||||||
let updatedLinks;
|
let updatedLinks;
|
||||||
if (this.links?.length) {
|
if (this.links?.length) {
|
||||||
@ -302,6 +355,29 @@ export default class Analytics extends Component {
|
|||||||
this.mentions = yield this.store.query('mention', {limit: 5, order: 'created_at desc', filter});
|
this.mentions = yield this.store.query('mention', {limit: 5, order: 'created_at desc', filter});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@task
|
||||||
|
*fetchPostCountTask() {
|
||||||
|
if (!this.post.emailOnly) {
|
||||||
|
const result = yield this.store.query('post', {filter: 'status:published', limit: 1});
|
||||||
|
let count = result.meta.pagination.total;
|
||||||
|
|
||||||
|
this.postCount = count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@task
|
||||||
|
*fetchPostTask() {
|
||||||
|
const result = yield this.store.query('post', {filter: `id:${this.post.id}`, limit: 1});
|
||||||
|
this.post = result.toArray()[0];
|
||||||
|
|
||||||
|
if (this.post.email) {
|
||||||
|
this.notifications.showNotification('Post analytics refreshing', {
|
||||||
|
description: 'It can take up to five minutes for all data to show.',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get showLinks() {
|
get showLinks() {
|
||||||
return this.post.showEmailClickAnalytics;
|
return this.post.showEmailClickAnalytics;
|
||||||
}
|
}
|
||||||
|
@ -78,6 +78,8 @@ export default class FeatureService extends Service {
|
|||||||
@feature('ActivityPub') ActivityPub;
|
@feature('ActivityPub') ActivityPub;
|
||||||
@feature('editorExcerpt') editorExcerpt;
|
@feature('editorExcerpt') editorExcerpt;
|
||||||
@feature('contentVisibility') contentVisibility;
|
@feature('contentVisibility') contentVisibility;
|
||||||
|
@feature('publishFlowEndScreen') publishFlowEndScreen;
|
||||||
|
@feature('postAnalyticsRefresh') postAnalyticsRefresh;
|
||||||
|
|
||||||
_user = null;
|
_user = null;
|
||||||
|
|
||||||
|
@ -393,7 +393,8 @@ Post context menu
|
|||||||
stroke-width: 1.8px;
|
stroke-width: 1.8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gh-posts-context-menu li:last-child::before {
|
.gh-posts-context-menu li:last-child::before,
|
||||||
|
.gh-analytics-actions-menu li:last-child::before {
|
||||||
display: block;
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
content: "";
|
content: "";
|
||||||
|
@ -880,3 +880,123 @@
|
|||||||
height: 20px;
|
height: 20px;
|
||||||
margin-right: 6px;
|
margin-right: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Publish flow modal
|
||||||
|
/* ---------------------------------------------------------- */
|
||||||
|
|
||||||
|
.modal-post-success {
|
||||||
|
max-width: 640px;
|
||||||
|
--padding: 40px;
|
||||||
|
--radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-post-success .modal-content {
|
||||||
|
padding: var(--padding);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-post-success .modal-image {
|
||||||
|
aspect-ratio: 16 / 7.55;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: calc(var(--padding) * -1) calc(var(--padding) * -1) var(--padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-post-success .modal-image img {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: var(--radius) var(--radius) 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-post-success .modal-header {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-post-success .modal-header h1 {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 3.6rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: -0.03em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-post-success .modal-header h1 span:has(+ span) {
|
||||||
|
color: var(--green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-post-success .modal-body {
|
||||||
|
margin-top: 16px;
|
||||||
|
font-size: 1.8rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
letter-spacing: -0.002em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-post-success .modal-footer {
|
||||||
|
gap: 16px;
|
||||||
|
margin-top: var(--padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-post-success .modal-footer .gh-btn {
|
||||||
|
min-width: 64px;
|
||||||
|
height: 44px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-post-success .modal-footer .gh-btn:not(:first-child) {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-post-success .modal-footer .gh-btn span {
|
||||||
|
padding-inline: 18px;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-post-success .modal-footer .gh-btn-primary {
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-post-success .modal-footer:has(.twitter) .gh-btn-primary {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-post-success .modal-footer .gh-btn:is(.twitter, .threads, .facebook, .linkedin) {
|
||||||
|
width: 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-post-success .modal-footer .gh-btn:is(.twitter, .threads, .facebook, .linkedin) span {
|
||||||
|
font-size: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-post-success .modal-footer .gh-btn svg {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-post-success .modal-footer .gh-btn.twitter svg path {
|
||||||
|
fill: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-post-success:has(.modal-image) .close {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-post-success:has(.modal-image) .close:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-post-success:has(.modal-image) .close svg {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-post-success:has(.modal-image) .close svg path {
|
||||||
|
fill: white;
|
||||||
|
}
|
||||||
|
@ -776,6 +776,17 @@
|
|||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gh-analytics-actions-menu {
|
||||||
|
top: calc(100% + 6px);
|
||||||
|
left: auto;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gh-analytics-actions-menu.fade-out {
|
||||||
|
animation-duration: .001s;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.feature-audienceFeedback .gh-post-analytics-box.gh-post-analytics-newsletter-clicks,
|
.feature-audienceFeedback .gh-post-analytics-box.gh-post-analytics-newsletter-clicks,
|
||||||
.feature-audienceFeedback .gh-post-analytics-box.gh-post-analytics-source-attribution,
|
.feature-audienceFeedback .gh-post-analytics-box.gh-post-analytics-source-attribution,
|
||||||
.gh-post-analytics-box.gh-post-analytics-mentions {
|
.gh-post-analytics-box.gh-post-analytics-mentions {
|
||||||
@ -1523,6 +1534,10 @@
|
|||||||
transition: all .1s linear;
|
transition: all .1s linear;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.dropdown .gh-post-list-cta > span {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.gh-post-list-cta.edit.is-hovered > *,
|
.gh-post-list-cta.edit.is-hovered > *,
|
||||||
.gh-post-list-cta.edit.is-hovered:hover > *,
|
.gh-post-list-cta.edit.is-hovered:hover > *,
|
||||||
.gh-post-list-cta.edit:not(.is-hovered):hover > * {
|
.gh-post-list-cta.edit:not(.is-hovered):hover > * {
|
||||||
|
@ -73,4 +73,4 @@
|
|||||||
|
|
||||||
</section>
|
</section>
|
||||||
{{outlet}}
|
{{outlet}}
|
||||||
</section>
|
</section>
|
@ -1,6 +1,6 @@
|
|||||||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
<title>reload</title>
|
<title>reload</title>
|
||||||
<g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round" stroke="#000" stroke-width="1.5">
|
<g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round" stroke="currentColor" stroke-width="1.5">
|
||||||
<path d="M15.667 9.752h6.09V2.444"/>
|
<path d="M15.667 9.752h6.09V2.444"/>
|
||||||
<path d="M21.705 9.579C20.622 5.226 16.688 2 12 2 6.477 2 2 6.477 2 12s4.477 10 10 10c3.3 0 6.228-1.6 8.05-4.065"/>
|
<path d="M21.705 9.579C20.622 5.226 16.688 2 12 2 6.477 2 2 6.477 2 12s4.477 10 10 10c3.3 0 6.228-1.6 8.05-4.065"/>
|
||||||
</g>
|
</g>
|
||||||
|
Before Width: | Height: | Size: 400 B After Width: | Height: | Size: 408 B |
1
ghost/admin/public/assets/icons/share.svg
Normal file
1
ghost/admin/public/assets/icons/share.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" id="Share-1--Streamline-Streamline--3.0.svg" height="24" width="24"><desc>Share 1 Streamline Icon: https://streamlinehq.com</desc><defs></defs><title>share-1</title><path d="M17.25 8.25h1.5a1.5 1.5 0 0 1 1.5 1.5v12a1.5 1.5 0 0 1 -1.5 1.5H5.25a1.5 1.5 0 0 1 -1.5 -1.5v-12a1.5 1.5 0 0 1 1.5 -1.5h1.5" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="m12 0.75 0 10.5" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="M8.25 4.5 12 0.75l3.75 3.75" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path></svg>
|
After Width: | Height: | Size: 750 B |
10
ghost/admin/public/assets/icons/social-threads.svg
Normal file
10
ghost/admin/public/assets/icons/social-threads.svg
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_351_18008)">
|
||||||
|
<path d="M13.0332 8.37917C12.96 8.34407 12.8856 8.3103 12.8103 8.27795C12.6791 5.86015 11.3579 4.47596 9.1396 4.4618C9.12955 4.46173 9.11955 4.46173 9.1095 4.46173C7.78265 4.46173 6.67913 5.0281 5.99992 6.05871L7.21993 6.89561C7.72733 6.12579 8.52364 5.96167 9.11008 5.96167C9.11686 5.96167 9.12366 5.96168 9.13036 5.96174C9.86078 5.96639 10.4119 6.17876 10.7687 6.59291C11.0283 6.89442 11.2019 7.31107 11.2879 7.8369C10.6403 7.72683 9.93993 7.69299 9.19122 7.73592C7.08214 7.8574 5.72624 9.08747 5.81731 10.7967C5.86352 11.6637 6.29544 12.4096 7.03346 12.8968C7.65745 13.3087 8.46111 13.5101 9.29635 13.4645C10.3994 13.4041 11.2647 12.9832 11.8684 12.2137C12.3268 11.6293 12.6168 10.872 12.7448 9.91782C13.2705 10.2351 13.6601 10.6525 13.8753 11.1544C14.2411 12.0075 14.2624 13.4094 13.1186 14.5523C12.1164 15.5535 10.9117 15.9866 9.09104 16C7.07147 15.9851 5.54409 15.3374 4.55103 14.0749C3.62111 12.8928 3.14053 11.1854 3.1226 9C3.14053 6.8146 3.62111 5.10714 4.55103 3.92503C5.54409 2.66262 7.07144 2.01495 9.09101 1.99994C11.1252 2.01506 12.6792 2.66585 13.7103 3.93435C14.2159 4.55641 14.597 5.3387 14.8483 6.25081L16.278 5.86936C15.9734 4.74665 15.4942 3.7792 14.842 2.97686C13.5201 1.35059 11.5869 0.517279 9.096 0.5H9.08603C6.60019 0.517219 4.68862 1.3537 3.40443 2.98619C2.26168 4.4389 1.67221 6.46024 1.65241 8.99402L1.65234 9L1.65241 9.00598C1.67221 11.5397 2.26168 13.5611 3.40443 15.0138C4.68862 16.6463 6.60019 17.4828 9.08603 17.5H9.096C11.306 17.4847 12.8638 16.9061 14.1472 15.6239C15.8262 13.9465 15.7756 11.8439 15.2222 10.5531C14.8252 9.62749 14.0683 8.8757 13.0332 8.37917ZM9.21739 11.9668C8.29301 12.0188 7.33268 11.6039 7.28533 10.7152C7.25023 10.0563 7.75426 9.32105 9.27412 9.23347C9.44817 9.22343 9.61897 9.21852 9.78676 9.21852C10.3388 9.21852 10.8553 9.27215 11.3248 9.3748C11.1497 11.562 10.1224 11.9171 9.21739 11.9668Z" fill="black"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_351_18008">
|
||||||
|
<rect width="17" height="17" fill="white" transform="translate(0.5 0.5)"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
@ -45,7 +45,9 @@ const ALPHA_FEATURES = [
|
|||||||
'importMemberTier',
|
'importMemberTier',
|
||||||
'lexicalIndicators',
|
'lexicalIndicators',
|
||||||
'adminXDemo',
|
'adminXDemo',
|
||||||
'contentVisibility'
|
'contentVisibility',
|
||||||
|
'publishFlowEndScreen',
|
||||||
|
'postAnalyticsRefresh'
|
||||||
];
|
];
|
||||||
|
|
||||||
module.exports.GA_KEYS = [...GA_FEATURES];
|
module.exports.GA_KEYS = [...GA_FEATURES];
|
||||||
|
Loading…
Reference in New Issue
Block a user