Ghost/ghost/admin/app/components/gh-post-settings-menu.js
Kevin Ansfield 7b443d4b63 Removed need for .get() with config service
no issue

The `config` service has been a source of confusion when writing with modern Ember patterns because it's use of the deprecated `ProxyMixin` forced all property access/setting to go via `.get()` and `.set()` whereas the rest of the system has mostly (there are a few other uses of ProxyObjects remaining) eliminated the use of the non-native get/set methods.

- removed use of `ProxyMixin` in the `config` service by grabbing the API response after fetching and using `Object.defineProperty()` to add native getters/setters that pass through to a tracked object holding the API response data. Ember's autotracking automatically works across the native getters/setters so we can then use the service as if it was any other native object
- updated all code to use `config.{attrName}` directly for getting/setting instead of `.get()` and `.set()`
- removed unnecessary async around `config.availableTimezones` which wasn't making any async calls
2022-10-07 16:14:57 +01:00

600 lines
16 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 as service} from '@ember/service';
import {tagName} from '@ember-decorators/component';
@classic
@tagName('')
export default class GhPostSettingsMenu extends Component {
@service feature;
@service store;
@service config;
@service ajax;
@service ghostPaths;
@service notifications;
@service slugGenerator;
@service session;
@service settings;
@service ui;
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', 'post.excerpt')
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;
@or(
'session.user.isOwnerOnly',
'session.user.isAdminOnly',
'session.user.isEditor'
)
showEmailNewsletter;
@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(' ');
}
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();
});
}
/**
* 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`);
}
}