Ghost/ghost/admin/app/components/gh-brand-settings-form.js
Kevin Ansfield 66f6d25d4b 🐛 Fixed blank branding preview on split front-end/admin domain setups
no issue

- the POST request made to fetch the preview was going to the non-trailing-slash version of the site URL which was resulting in a redirect to the trailing-slash version which then failed due to CORS
2021-06-01 10:46:28 +01:00

164 lines
4.8 KiB
JavaScript

import Component from '@glimmer/component';
import config from 'ghost-admin/config/environment';
import {
ICON_EXTENSIONS,
ICON_MIME_TYPES,
IMAGE_EXTENSIONS,
IMAGE_MIME_TYPES
} from 'ghost-admin/components/gh-image-uploader';
import {action} from '@ember/object';
import {htmlSafe} from '@ember/template';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency-decorators';
import {timeout} from 'ember-concurrency';
export default class GhBrandSettingsFormComponent extends Component {
@service ajax;
@service config;
@service ghostPaths;
@service settings;
iconExtensions = ICON_EXTENSIONS;
iconMimeTypes = ICON_MIME_TYPES;
imageExtensions = IMAGE_EXTENSIONS;
imageMimeTypes = IMAGE_MIME_TYPES;
get accentColor() {
const color = this.settings.get('accentColor');
if (color && color[0] === '#') {
return color.slice(1);
}
return color;
}
get accentColorPickerValue() {
return this.settings.get('accentColor') || '#ffffff';
}
get accentColorBgStyle() {
return htmlSafe(`background-color: ${this.accentColorPickerValue}`);
}
get previewData() {
const params = new URLSearchParams();
params.append('c', this.accentColorPickerValue);
params.append('icon', this.settings.get('icon'));
params.append('logo', this.settings.get('logo'));
params.append('cover', this.settings.get('coverImage'));
return params.toString();
}
constructor() {
super(...arguments);
this.updatePreviewTask.perform();
}
willDestroy() {
this.settings.errors.remove('accentColor');
this.settings.rollbackAttributes();
}
@action
triggerFileDialog({target}) {
target.closest('.gh-setting-action')?.querySelector('input[type="file"]')?.click();
}
@action
async imageUploaded(property, results) {
if (results[0]) {
this.settings.set(property, results[0].url);
this.updatePreviewTask.perform();
}
}
@action
async removeImage(imageName) {
this.settings.set(imageName, '');
this.updatePreviewTask.perform();
}
@action
async updateAccentColor(event) {
let newColor = event.target.value;
const oldColor = this.settings.get('accentColor');
// reset errors and validation
this.settings.errors.remove('accentColor');
this.settings.hasValidated.removeObject('accentColor');
if (newColor === '') {
if (newColor === oldColor) {
return;
}
// Don't allow empty accent color
this.settings.errors.add('accentColor', 'Please select an accent color');
this.settings.hasValidated.pushObject('accentColor');
return;
}
// accentColor will be null unless the user has input something
if (!newColor) {
newColor = oldColor;
}
if (newColor[0] !== '#') {
newColor = `#${newColor}`;
}
if (newColor.match(/#[0-9A-Fa-f]{6}$/)) {
if (newColor === oldColor) {
return;
}
this.settings.set('accentColor', newColor);
this.updatePreviewTask.perform();
} else {
this.settings.errors.add('accentColor', 'Please enter a color in hex format');
this.settings.hasValidated.pushObject('accentColor');
}
}
@task({restartable: true})
*debounceUpdateAccentColor(event) {
yield timeout(500);
this.updateAccentColor(event);
}
@task
*updatePreviewTask() {
// skip during testing because we don't have mocks for the front-end
if (config.environment === 'test') {
return;
}
// grab the preview html
const ajaxOptions = {
contentType: 'text/html;charset=utf-8',
dataType: 'text',
headers: {
'x-ghost-preview': this.previewData
}
};
// TODO: config.blogUrl always removes trailing slash - switch to always have trailing slash
const frontendUrl = `${this.config.get('blogUrl')}/`;
const previewContents = yield this.ajax.post(frontendUrl, ajaxOptions);
// inject extra CSS to disable navigation and prevent clicks
const injectedCss = `html { pointer-events: none; }`;
const domParser = new DOMParser();
const htmlDoc = domParser.parseFromString(previewContents, 'text/html');
const stylesheet = htmlDoc.querySelector('style');
const originalCSS = stylesheet.innerHTML;
stylesheet.innerHTML = `${originalCSS}\n\n${injectedCss}`;
// replace the iframe contents with the doctored preview html
this.args.replacePreviewContents(htmlDoc.documentElement.innerHTML);
}
}