Updated brand settings modal to use new preview

- the new preview mode doesn't require settings to be saved in advance
- TODO: prevent interaction with the iframe a la portal
Notes:
 - this works by sending a post message to the frontend with a custom header
 - generated HTML is loaded into the iframe using postMessage
 - we can possibly write the HTML direct to the iframe - something to experiment with later
This commit is contained in:
Hannah Wolfe 2021-02-15 10:17:31 +00:00
parent c54e1dc2c5
commit e75720d390
3 changed files with 93 additions and 21 deletions

View File

@ -2,8 +2,28 @@
<div class="gh-branding-settings-header">
<h4>Branding</h4>
<div class="gh-branding-settings-actions">
<a class="gh-btn gh-btn-primary" href="" role="button" title="Close" {{action "closeModal"}}><span>Save and close</span></a>
<button
class="gh-btn mr3"
{{action "closeModal"}}
{{!-- disable mouseDown so it doesn't trigger focus-out validations --}}
{{on "mousedown" (optional this.noop)}}
data-test-button="cancel-custom-view-form"
>
<span>Cancel</span>
</button>
<GhTaskButton
@buttonText="Save and close"
@successText="Saved"
@task={{this.saveTask}}
@idleClass="gh-btn-primary"
@class="gh-btn gh-btn-icon"
data-test-button="save-members-modal-setting"
/>
</div>
</div>
<div class="gh-branding-settings">
<section class="gh-branding-settings-options">
@ -164,8 +184,12 @@
</section>
<section class="gh-branding-settings-right">
<GhBrowserPreview class="gh-branding-settings-previewcontainer" @icon={{this.settings.icon}} @title={{this.config.blogTitle}}>
<GhSiteIframe class="gh-branding-settings-preview" @guid={{this.previewGuid}}></GhSiteIframe>
<GhBrowserPreview class="gh-branding-settings-previewcontainer" @icon={{this.icon}} @title={{this.config.blogTitle}}>
<GhSiteIframe
class="gh-branding-settings-preview"
@src={{this.themePreviewUrl}}
@guid={{this.previewGuid}}
></GhSiteIframe>
</GhBrowserPreview>
</section>
</div>

View File

@ -20,16 +20,27 @@ export default ModalComponent.extend({
notifications: service(),
session: service(),
settings: service(),
ajax: service(),
imageExtensions: IMAGE_EXTENSIONS,
imageMimeTypes: IMAGE_MIME_TYPES,
iconExtensions: null,
iconMimeTypes: 'image/png,image/x-icon',
dirtyAttributes: false,
previewGuid: (new Date()).valueOf(),
frontendUrl: computed('config.blogUrl', function () {
return `${this.get('config.blogUrl')}`;
}),
themePreviewUrl: computed(function () {
let origin = window.location.origin;
let subdir = this.ghostPaths.subdir;
let url = this.ghostPaths.url.join(origin, subdir);
return url.replace(/\/$/, '/ghost/preview/');
}),
accentColorPickerValue: computed('settings.accentColor', function () {
return this.get('settings.accentColor') || '#ffffff';
}),
@ -46,10 +57,23 @@ export default ModalComponent.extend({
return htmlSafe(`background-color: ${this.accentColorPickerValue}`);
}),
getPreviewData: computed('settings.{accentColor,icon,logo,coverImage}', function () {
let string = `c=${encodeURIComponent(this.get('settings.accentColor'))}&icon=${encodeURIComponent(this.get('settings.icon'))}&logo=${encodeURIComponent(this.get('settings.logo'))}&cover=${encodeURIComponent(this.get('settings.coverImage'))}`;
return string;
}),
init() {
this._super(...arguments);
this.iconExtensions = ICON_EXTENSIONS;
this.refreshPreview();
},
didInsertElement() {
window.addEventListener('message', (event) => {
if (event && event.data && event.data === 'loaded') {
this.replacePreview();
}
});
},
actions: {
@ -92,7 +116,6 @@ export default ModalComponent.extend({
// roll back changes on settings props
settings.rollbackAttributes();
this.set('dirtyAttributes', false);
return transition.retry();
},
@ -102,7 +125,6 @@ export default ModalComponent.extend({
async removeImage(image) {
// setting `null` here will error as the server treats it as "null"
this.settings.set(image, '');
await this.save.perform();
this.refreshPreview();
},
@ -130,7 +152,6 @@ export default ModalComponent.extend({
async imageUploaded(property, results) {
if (results[0]) {
let result = this.settings.set(property, results[0].url);
await this.save.perform();
this.refreshPreview();
return result;
}
@ -141,18 +162,48 @@ export default ModalComponent.extend({
}
},
replacePreview() {
const ghostFrontendUrl = this.frontendUrl;
const options = {
contentType: 'text/html;charset=utf-8',
// ember-ajax will try and parse the response as JSON if not explicitly set
dataType: 'text',
headers: {
'x-ghost-preview': this.getPreviewData
}
};
this.ajax
.post(ghostFrontendUrl, options)
.then((response) => {
this.getPreviewIframe().contentWindow.postMessage(response, '*');
})
.catch(() => {
this.notifications.showAlert('Sorry, there was an error with preview. Please let the Ghost team know what happened.', {type: 'error'});
});
},
refreshPreview() {
// this.set('previewGuid',(new Date()).valueOf());
// REset the src and trigger a reload
this.getPreviewIframe().src = this.themePreviewUrl;
},
getPreviewIframe() {
return document.getElementById('site-frame');
},
debounceUpdateAccentColor: task(function* (event) {
yield timeout(500);
this._updateAccentColor(event);
}).restartable(),
save: task(function* () {
saveTask: task(function* () {
let notifications = this.notifications;
let validationPromises = [];
try {
yield RSVP.all(validationPromises);
this.set('dirtyAttributes', false);
return yield this.settings.save();
} catch (error) {
if (error) {
@ -177,7 +228,6 @@ export default ModalComponent.extend({
// clear out the accent color
this.settings.set('accentColor', '');
await this.save.perform();
this.refreshPreview();
return;
}
@ -197,16 +247,12 @@ export default ModalComponent.extend({
}
this.set('settings.accentColor', newColor);
await this.save.perform();
this.refreshPreview();
} else {
this.get('settings.errors').add('accentColor', 'The colour should be in valid hex format');
this.get('settings.hasValidated').pushObject('accentColor');
return;
}
},
refreshPreview() {
this.set('previewGuid',(new Date()).valueOf());
}
});
});

View File

@ -6,17 +6,19 @@ export default Controller.extend({
settings: service(),
queryParams: ['showPortalSettings', 'showBrandingModal'],
showPortalSettings: false,
showBrandingModal: false,
showLeaveSettingsModal: false,
tagName: '',
actions: {
openStripeSettings() {
this.set('membersStripeOpen', true);
},
closePortalSettings() {
const changedAttributes = this.settings.changedAttributes();
if (changedAttributes && Object.keys(changedAttributes).length > 0) {
@ -41,4 +43,4 @@ export default Controller.extend({
}
}
});
});