Ghost/ghost/admin/app/components/gh-brand-settings-form.js
Rishabh Garg 85c2628049 Added validation to not allow empty accent color (#1863)
closes https://github.com/TryGhost/Team/issues/535

To ensure accent color is always set for a site, this updates brand settings screen and launch wizard to not allow empty accent color to be set or updated, adding the relevant error on the page which needs to be fixed before saving or continuing on the screens.

- Shows error message for empty accent color on brand settings and launch wizard
- Shows error message for invalid accent color on brand settings and launch wizard
- Blocks save and continue with invalid/empty accent color on brand settings and launch wizard

Co-authored-by: Peter Zimon <zimo@ghost.org>
2021-03-09 23:55:06 +05:30

162 lines
4.7 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/string';
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
}
};
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);
}
}