mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-30 01:42:29 +03:00
b60761706b
refs https://github.com/orgs/TryGhost/projects/88/views/2?pane=issue&itemId=26198422 Prevented post history from being viewed in the following scenarios: - When a post is sent as and email only - When a post is published and sent as an email
620 lines
16 KiB
JavaScript
620 lines
16 KiB
JavaScript
import Component from '@ember/component';
|
||
import boundOneWay from 'ghost-admin/utils/bound-one-way';
|
||
import classic from 'ember-classic-decorator';
|
||
import moment from 'moment-timezone';
|
||
import {action, computed} from '@ember/object';
|
||
import {alias, or} from '@ember/object/computed';
|
||
import {inject} from 'ghost-admin/decorators/inject';
|
||
import {inject as service} from '@ember/service';
|
||
import {tagName} from '@ember-decorators/component';
|
||
import {tracked} from '@glimmer/tracking';
|
||
|
||
@classic
|
||
@tagName('')
|
||
export default class GhPostSettingsMenu extends Component {
|
||
@service feature;
|
||
@service store;
|
||
@service ajax;
|
||
@service ghostPaths;
|
||
@service notifications;
|
||
@service slugGenerator;
|
||
@service session;
|
||
@service settings;
|
||
@service ui;
|
||
|
||
@inject config;
|
||
|
||
@tracked showPostHistory = false;
|
||
|
||
post = null;
|
||
isViewingSubview = false;
|
||
|
||
@alias('post.canonicalUrlScratch')
|
||
canonicalUrlScratch;
|
||
|
||
@alias('post.customExcerptScratch')
|
||
customExcerptScratch;
|
||
|
||
@alias('post.codeinjectionFootScratch')
|
||
codeinjectionFootScratch;
|
||
|
||
@alias('post.codeinjectionHeadScratch')
|
||
codeinjectionHeadScratch;
|
||
|
||
@alias('post.metaDescriptionScratch')
|
||
metaDescriptionScratch;
|
||
|
||
@alias('post.metaTitleScratch')
|
||
metaTitleScratch;
|
||
|
||
@alias('post.ogDescriptionScratch')
|
||
ogDescriptionScratch;
|
||
|
||
@alias('post.ogTitleScratch')
|
||
ogTitleScratch;
|
||
|
||
@alias('post.twitterDescriptionScratch')
|
||
twitterDescriptionScratch;
|
||
|
||
@alias('post.twitterTitleScratch')
|
||
twitterTitleScratch;
|
||
|
||
@boundOneWay('post.slug')
|
||
slugValue;
|
||
|
||
@boundOneWay('post.uuid')
|
||
uuidValue;
|
||
|
||
@or('metaDescriptionScratch', 'customExcerptScratch')
|
||
seoDescription;
|
||
|
||
@or(
|
||
'ogDescriptionScratch',
|
||
'customExcerptScratch',
|
||
'seoDescription',
|
||
'post.excerpt',
|
||
'settings.description',
|
||
''
|
||
)
|
||
facebookDescription;
|
||
|
||
@or(
|
||
'post.ogImage',
|
||
'post.featureImage',
|
||
'settings.ogImage',
|
||
'settings.coverImage'
|
||
)
|
||
facebookImage;
|
||
|
||
@or('ogTitleScratch', 'seoTitle')
|
||
facebookTitle;
|
||
|
||
@or(
|
||
'twitterDescriptionScratch',
|
||
'customExcerptScratch',
|
||
'seoDescription',
|
||
'post.excerpt',
|
||
'settings.description',
|
||
''
|
||
)
|
||
twitterDescription;
|
||
|
||
@or(
|
||
'post.twitterImage',
|
||
'post.featureImage',
|
||
'settings.twitterImage',
|
||
'settings.coverImage'
|
||
)
|
||
twitterImage;
|
||
|
||
@or('twitterTitleScratch', 'seoTitle')
|
||
twitterTitle;
|
||
|
||
@or(
|
||
'session.user.isOwnerOnly',
|
||
'session.user.isAdminOnly',
|
||
'session.user.isEditor'
|
||
)
|
||
showVisibilityInput;
|
||
|
||
@computed('metaTitleScratch', 'post.titleScratch')
|
||
get seoTitle() {
|
||
return this.metaTitleScratch || this.post.titleScratch || '(Untitled)';
|
||
}
|
||
|
||
@computed('post.{slug,canonicalUrl}', 'config.blogUrl')
|
||
get seoURL() {
|
||
const urlParts = [];
|
||
|
||
if (this.post.canonicalUrl) {
|
||
try {
|
||
const canonicalUrl = new URL(this.post.canonicalUrl);
|
||
urlParts.push(canonicalUrl.host);
|
||
urlParts.push(...canonicalUrl.pathname.split('/').reject(p => !p));
|
||
} catch (e) {
|
||
// no-op, invalid URL
|
||
}
|
||
} else {
|
||
const blogUrl = new URL(this.config.blogUrl);
|
||
urlParts.push(blogUrl.host);
|
||
urlParts.push(...blogUrl.pathname.split('/').reject(p => !p));
|
||
urlParts.push(this.post.slug);
|
||
}
|
||
|
||
return urlParts.join(' › ');
|
||
}
|
||
|
||
get canViewPostHistory() {
|
||
let showPostHistory = this.feature.postHistory === true
|
||
&& this.post.lexical !== null
|
||
&& this.post.emailOnly === false;
|
||
|
||
if (this.post.isPublished === true) {
|
||
showPostHistory = this.post.hasEmail === false;
|
||
}
|
||
|
||
return showPostHistory;
|
||
}
|
||
|
||
willDestroyElement() {
|
||
super.willDestroyElement(...arguments);
|
||
|
||
let post = this.post;
|
||
let errors = post.get('errors');
|
||
|
||
// reset the publish date if it has an error
|
||
if (errors.has('publishedAtBlogDate') || errors.has('publishedAtBlogTime')) {
|
||
post.set('publishedAtBlogTZ', post.get('publishedAtUTC'));
|
||
post.validate({attribute: 'publishedAtBlog'});
|
||
}
|
||
|
||
this.setSidebarWidthVariable(0);
|
||
}
|
||
|
||
@action
|
||
showSubview(subview) {
|
||
this.set('isViewingSubview', true);
|
||
this.set('subview', subview);
|
||
}
|
||
|
||
@action
|
||
closeSubview() {
|
||
this.set('isViewingSubview', false);
|
||
this.set('subview', null);
|
||
}
|
||
|
||
@action
|
||
discardEnter() {
|
||
return false;
|
||
}
|
||
|
||
@action
|
||
toggleFeatured() {
|
||
this.toggleProperty('post.featured');
|
||
|
||
// If this is a new post. Don't save the post. Defer the save
|
||
// to the user pressing the save button
|
||
if (this.get('post.isNew')) {
|
||
return;
|
||
}
|
||
|
||
this.savePostTask.perform().catch((error) => {
|
||
this.showError(error);
|
||
this.post.rollbackAttributes();
|
||
});
|
||
}
|
||
|
||
@action
|
||
openPostHistory() {
|
||
this.showPostHistory = true;
|
||
}
|
||
|
||
@action
|
||
closePostHistory() {
|
||
this.showPostHistory = false;
|
||
}
|
||
|
||
/**
|
||
* triggered by user manually changing slug
|
||
*/
|
||
@action
|
||
updateSlug(newSlug) {
|
||
return this.updateSlugTask
|
||
.perform(newSlug)
|
||
.catch((error) => {
|
||
this.showError(error);
|
||
this.post.rollbackAttributes();
|
||
});
|
||
}
|
||
|
||
@action
|
||
setPublishedAtBlogDate(date) {
|
||
// date is a Date object that contains the correct date string in the blog timezone
|
||
let post = this.post;
|
||
let dateString = moment.tz(date, this.settings.get('timezone')).format('YYYY-MM-DD');
|
||
|
||
post.get('errors').remove('publishedAtBlogDate');
|
||
|
||
if (post.get('isNew') || date === post.get('publishedAtBlogDate')) {
|
||
post.validate({property: 'publishedAtBlog'});
|
||
} else {
|
||
post.set('publishedAtBlogDate', dateString);
|
||
return this.savePostTask.perform();
|
||
}
|
||
}
|
||
|
||
@action
|
||
async setVisibility(segment) {
|
||
this.post.set('tiers', segment);
|
||
try {
|
||
await this.post.validate({property: 'visibility'});
|
||
await this.post.validate({property: 'tiers'});
|
||
if (this.post.get('isDraft') && this.post.changedAttributes().tiers) {
|
||
await this.savePostTask.perform();
|
||
}
|
||
} catch (e) {
|
||
if (!e) {
|
||
// validation error
|
||
return;
|
||
}
|
||
|
||
throw e;
|
||
}
|
||
}
|
||
|
||
@action
|
||
setPublishedAtBlogTime(time) {
|
||
let post = this.post;
|
||
|
||
post.get('errors').remove('publishedAtBlogDate');
|
||
|
||
if (post.get('isNew') || time === post.get('publishedAtBlogTime')) {
|
||
post.validate({property: 'publishedAtBlog'});
|
||
} else {
|
||
post.set('publishedAtBlogTime', time);
|
||
return this.savePostTask.perform();
|
||
}
|
||
}
|
||
|
||
@action
|
||
setCustomExcerpt(excerpt) {
|
||
let post = this.post;
|
||
let currentExcerpt = post.get('customExcerpt');
|
||
|
||
if (excerpt === currentExcerpt) {
|
||
return;
|
||
}
|
||
|
||
post.set('customExcerpt', excerpt);
|
||
|
||
return post.validate({property: 'customExcerpt'}).then(() => this.savePostTask.perform());
|
||
}
|
||
|
||
@action
|
||
setHeaderInjection(code) {
|
||
let post = this.post;
|
||
let currentCode = post.get('codeinjectionHead');
|
||
|
||
if (code === currentCode) {
|
||
return;
|
||
}
|
||
|
||
post.set('codeinjectionHead', code);
|
||
|
||
return post.validate({property: 'codeinjectionHead'}).then(() => this.savePostTask.perform());
|
||
}
|
||
|
||
@action
|
||
setFooterInjection(code) {
|
||
let post = this.post;
|
||
let currentCode = post.get('codeinjectionFoot');
|
||
|
||
if (code === currentCode) {
|
||
return;
|
||
}
|
||
|
||
post.set('codeinjectionFoot', code);
|
||
|
||
return post.validate({property: 'codeinjectionFoot'}).then(() => this.savePostTask.perform());
|
||
}
|
||
|
||
@action
|
||
setMetaTitle(metaTitle) {
|
||
// Grab the post and current stored meta title
|
||
let post = this.post;
|
||
let currentTitle = post.get('metaTitle');
|
||
|
||
// If the title entered matches the stored meta title, do nothing
|
||
if (currentTitle === metaTitle) {
|
||
return;
|
||
}
|
||
|
||
// If the title entered is different, set it as the new meta title
|
||
post.set('metaTitle', metaTitle);
|
||
|
||
// Make sure the meta title is valid and if so, save it into the post
|
||
return post.validate({property: 'metaTitle'}).then(() => {
|
||
if (post.get('isNew')) {
|
||
return;
|
||
}
|
||
|
||
return this.savePostTask.perform();
|
||
});
|
||
}
|
||
|
||
@action
|
||
setMetaDescription(metaDescription) {
|
||
// Grab the post and current stored meta description
|
||
let post = this.post;
|
||
let currentDescription = post.get('metaDescription');
|
||
|
||
// If the title entered matches the stored meta title, do nothing
|
||
if (currentDescription === metaDescription) {
|
||
return;
|
||
}
|
||
|
||
// If the title entered is different, set it as the new meta title
|
||
post.set('metaDescription', metaDescription);
|
||
|
||
// Make sure the meta title is valid and if so, save it into the post
|
||
return post.validate({property: 'metaDescription'}).then(() => {
|
||
if (post.get('isNew')) {
|
||
return;
|
||
}
|
||
|
||
return this.savePostTask.perform();
|
||
});
|
||
}
|
||
|
||
@action
|
||
setCanonicalUrl(value) {
|
||
// Grab the post and current stored meta description
|
||
let post = this.post;
|
||
let currentCanonicalUrl = post.canonicalUrl;
|
||
|
||
// If the value entered matches the stored value, do nothing
|
||
if (currentCanonicalUrl === value) {
|
||
return;
|
||
}
|
||
|
||
// If the value supplied is different, set it as the new value
|
||
post.set('canonicalUrl', value);
|
||
|
||
// Make sure the value is valid and if so, save it into the post
|
||
return post.validate({property: 'canonicalUrl'}).then(() => {
|
||
if (post.get('isNew')) {
|
||
return;
|
||
}
|
||
|
||
return this.savePostTask.perform();
|
||
});
|
||
}
|
||
|
||
@action
|
||
setOgTitle(ogTitle) {
|
||
// Grab the post and current stored facebook title
|
||
let post = this.post;
|
||
let currentTitle = post.get('ogTitle');
|
||
|
||
// If the title entered matches the stored facebook title, do nothing
|
||
if (currentTitle === ogTitle) {
|
||
return;
|
||
}
|
||
|
||
// If the title entered is different, set it as the new facebook title
|
||
post.set('ogTitle', ogTitle);
|
||
|
||
// Make sure the facebook title is valid and if so, save it into the post
|
||
return post.validate({property: 'ogTitle'}).then(() => {
|
||
if (post.get('isNew')) {
|
||
return;
|
||
}
|
||
|
||
return this.savePostTask.perform();
|
||
});
|
||
}
|
||
|
||
@action
|
||
setOgDescription(ogDescription) {
|
||
// Grab the post and current stored facebook description
|
||
let post = this.post;
|
||
let currentDescription = post.get('ogDescription');
|
||
|
||
// If the title entered matches the stored facebook description, do nothing
|
||
if (currentDescription === ogDescription) {
|
||
return;
|
||
}
|
||
|
||
// If the description entered is different, set it as the new facebook description
|
||
post.set('ogDescription', ogDescription);
|
||
|
||
// Make sure the facebook description is valid and if so, save it into the post
|
||
return post.validate({property: 'ogDescription'}).then(() => {
|
||
if (post.get('isNew')) {
|
||
return;
|
||
}
|
||
|
||
return this.savePostTask.perform();
|
||
});
|
||
}
|
||
|
||
@action
|
||
setTwitterTitle(twitterTitle) {
|
||
// Grab the post and current stored twitter title
|
||
let post = this.post;
|
||
let currentTitle = post.get('twitterTitle');
|
||
|
||
// If the title entered matches the stored twitter title, do nothing
|
||
if (currentTitle === twitterTitle) {
|
||
return;
|
||
}
|
||
|
||
// If the title entered is different, set it as the new twitter title
|
||
post.set('twitterTitle', twitterTitle);
|
||
|
||
// Make sure the twitter title is valid and if so, save it into the post
|
||
return post.validate({property: 'twitterTitle'}).then(() => {
|
||
if (post.get('isNew')) {
|
||
return;
|
||
}
|
||
|
||
return this.savePostTask.perform();
|
||
});
|
||
}
|
||
|
||
@action
|
||
setTwitterDescription(twitterDescription) {
|
||
// Grab the post and current stored twitter description
|
||
let post = this.post;
|
||
let currentDescription = post.get('twitterDescription');
|
||
|
||
// If the description entered matches the stored twitter description, do nothing
|
||
if (currentDescription === twitterDescription) {
|
||
return;
|
||
}
|
||
|
||
// If the description entered is different, set it as the new twitter description
|
||
post.set('twitterDescription', twitterDescription);
|
||
|
||
// Make sure the twitter description is valid and if so, save it into the post
|
||
return post.validate({property: 'twitterDescription'}).then(() => {
|
||
if (post.get('isNew')) {
|
||
return;
|
||
}
|
||
|
||
return this.savePostTask.perform();
|
||
});
|
||
}
|
||
|
||
@action
|
||
setCoverImage(image) {
|
||
this.set('post.featureImage', image);
|
||
|
||
if (this.get('post.isNew')) {
|
||
return;
|
||
}
|
||
|
||
this.savePostTask.perform().catch((error) => {
|
||
this.showError(error);
|
||
this.post.rollbackAttributes();
|
||
});
|
||
}
|
||
|
||
@action
|
||
clearCoverImage() {
|
||
this.set('post.featureImage', '');
|
||
|
||
if (this.get('post.isNew')) {
|
||
return;
|
||
}
|
||
|
||
this.savePostTask.perform().catch((error) => {
|
||
this.showError(error);
|
||
this.post.rollbackAttributes();
|
||
});
|
||
}
|
||
|
||
@action
|
||
setOgImage(image) {
|
||
this.set('post.ogImage', image);
|
||
|
||
if (this.get('post.isNew')) {
|
||
return;
|
||
}
|
||
|
||
this.savePostTask.perform().catch((error) => {
|
||
this.showError(error);
|
||
this.post.rollbackAttributes();
|
||
});
|
||
}
|
||
|
||
@action
|
||
clearOgImage() {
|
||
this.set('post.ogImage', '');
|
||
|
||
if (this.get('post.isNew')) {
|
||
return;
|
||
}
|
||
|
||
this.savePostTask.perform().catch((error) => {
|
||
this.showError(error);
|
||
this.post.rollbackAttributes();
|
||
});
|
||
}
|
||
|
||
@action
|
||
setTwitterImage(image) {
|
||
this.set('post.twitterImage', image);
|
||
|
||
if (this.get('post.isNew')) {
|
||
return;
|
||
}
|
||
|
||
this.savePostTask.perform().catch((error) => {
|
||
this.showError(error);
|
||
this.post.rollbackAttributes();
|
||
});
|
||
}
|
||
|
||
@action
|
||
clearTwitterImage() {
|
||
this.set('post.twitterImage', '');
|
||
|
||
if (this.get('post.isNew')) {
|
||
return;
|
||
}
|
||
|
||
this.savePostTask.perform().catch((error) => {
|
||
this.showError(error);
|
||
this.post.rollbackAttributes();
|
||
});
|
||
}
|
||
|
||
@action
|
||
changeAuthors(newAuthors) {
|
||
let post = this.post;
|
||
|
||
// return if nothing changed
|
||
if (newAuthors.mapBy('id').join() === post.get('authors').mapBy('id').join()) {
|
||
return;
|
||
}
|
||
|
||
post.set('authors', newAuthors);
|
||
post.validate({property: 'authors'});
|
||
|
||
// if this is a new post (never been saved before), don't try to save it
|
||
if (post.get('isNew')) {
|
||
return;
|
||
}
|
||
|
||
this.savePostTask.perform().catch((error) => {
|
||
this.showError(error);
|
||
post.rollbackAttributes();
|
||
});
|
||
}
|
||
|
||
@action
|
||
deletePostInternal() {
|
||
if (this.deletePost) {
|
||
this.deletePost();
|
||
}
|
||
}
|
||
|
||
@action
|
||
setSidebarWidthFromElement(element) {
|
||
const width = element.getBoundingClientRect().width;
|
||
this.setSidebarWidthVariable(width);
|
||
}
|
||
|
||
showError(error) {
|
||
// TODO: remove null check once ValidationEngine has been removed
|
||
if (error) {
|
||
this.notifications.showAPIError(error);
|
||
}
|
||
}
|
||
|
||
setSidebarWidthVariable(width) {
|
||
document.documentElement.style.setProperty('--editor-sidebar-width', `${width}px`);
|
||
}
|
||
}
|