Skip to content

Commit

Permalink
🐛 fix broken sidebar after successful import
Browse files Browse the repository at this point in the history
closes TryGhost/Ghost#8307
- unloading the store and refreshing the session.user attribute after an import was triggering an odd rendering edgecase where the style was re-computed and a re-render was attempted after the sidebar has been destroyed
- rather than binding a style attribute directly to a CP in `gh-nav-menu` we pass in the menu icon and manually set the style attribute on the `didReceiveAttrs` hook so that outside changes don't trigger re-computations when we don't expect them and so we can still react to icons being uploaded or removed
- our usage of `settings.icon` is a bit of an odd situation because it's a link to an external resource that will only resolve correctly after a successful save - if we change `settings.icon` in the local store and the nav menu icon style updates before the save has been completed then the server will give us the old icon. To work around this a `settings.settledIcon` attribute has been added that is only updated when we receive data from the store ensuring that our cache-busting technique works correctly
  • Loading branch information
kevinansfield committed Apr 19, 2017
1 parent ff12348 commit 184b8c7
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 26 deletions.
56 changes: 35 additions & 21 deletions app/components/gh-nav-menu.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,29 @@
import Component from 'ember-component';
import {htmlSafe} from 'ember-string';
import injectService from 'ember-service/inject';
import computed from 'ember-computed';
import {htmlSafe} from 'ember-string';
import calculatePosition from 'ember-basic-dropdown/utils/calculate-position';

export default Component.extend({
config: injectService(),
session: injectService(),
ghostPaths: injectService(),
feature: injectService(),
routing: injectService('-routing'),

tagName: 'nav',
classNames: ['gh-nav'],
classNameBindings: ['open'],

open: false,
iconStyle: '',

navMenuIcon: computed('config.blogUrl', 'settings.icon', 'ghostPaths.subdir', 'ghostPaths.url', function () {
let subdirRegExp = new RegExp(`^${this.get('ghostPaths.subdir')}`);
let blogIcon = this.get('settings.icon') ? this.get('settings.icon') : 'favicon.ico';
let url;

blogIcon = blogIcon.replace(subdirRegExp, '');

url = this.get('ghostPaths.url').join(this.get('config.blogUrl'), blogIcon).replace(/\/$/, '');
url += `?t=${(new Date()).valueOf()}`;

return htmlSafe(`background-image: url(${url})`);
}),

config: injectService(),
settings: injectService(),
session: injectService(),
ghostPaths: injectService(),
feature: injectService(),
routing: injectService('-routing'),
// the menu has a rendering issue (#8307) when the the world is reloaded
// during an import which we have worked around by not binding the icon
// style directly. However we still need to keep track of changing icons
// so that we can refresh when a new icon is uploaded
didReceiveAttrs() {
this._setIconStyle();
},

mouseEnter() {
this.sendAction('onMouseEnter');
Expand All @@ -46,6 +40,26 @@ export default Component.extend({
return {horizontalPosition, verticalPosition, style};
},

_setIconStyle() {
let icon = this.get('icon');

if (icon === this._icon) {
return;
}

let subdirRegExp = new RegExp(`^${this.get('ghostPaths.subdir')}`);
let blogIcon = icon ? icon : 'favicon.ico';
let iconUrl;

blogIcon = blogIcon.replace(subdirRegExp, '');

iconUrl = this.get('ghostPaths.url').join(this.get('config.blogUrl'), blogIcon).replace(/\/$/, '');
iconUrl += `?t=${(new Date()).valueOf()}`;

this.set('iconStyle', htmlSafe(`background-image: url(${iconUrl})`));
this._icon = icon;
},

actions: {
toggleAutoNav() {
this.sendAction('toggleMaximise');
Expand Down
1 change: 1 addition & 0 deletions app/controllers/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import injectService from 'ember-service/inject';
export default Controller.extend({
dropdown: injectService(),
session: injectService(),
settings: injectService(),

showNavMenu: computed('currentPath', 'session.isAuthenticated', 'session.user.isFulfilled', function () {
// we need to defer showing the navigation menu until the session.user
Expand Down
6 changes: 4 additions & 2 deletions app/controllers/settings/labs.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,12 @@ export default Controller.extend({
processData: false
});
}).then(() => {
let store = this.get('store');

// Clear the store, so that all the new data gets fetched correctly.
this.store.unloadAll();
store.unloadAll();
// Reload currentUser and set session
this.set('session.user', this.store.findRecord('user', currentUserId));
this.set('session.user', store.findRecord('user', currentUserId));
// TODO: keep as notification, add link to view content
notifications.showNotification('Import successful.', {key: 'import.upload.success'});
}).catch((response) => {
Expand Down
11 changes: 10 additions & 1 deletion app/services/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Service from 'ember-service';
import injectService from 'ember-service/inject';
import RSVP from 'rsvp';
import ValidationEngine from 'ghost-admin/mixins/validation-engine';
import get from 'ember-metal/get';

// ember-cli-shims doesn't export _ProxyMixin
const {_ProxyMixin} = Ember;
Expand All @@ -17,6 +18,10 @@ export default Service.extend(_ProxyMixin, ValidationEngine, {
validationType: 'setting',
_loadingPromise: null,

// this is an odd case where we only want to react to changes that we get
// back from the API rather than local updates
settledIcon: '',

// the settings API endpoint is a little weird as it's singular and we have
// to pass in all types - if we ever fetch settings without all types then
// save we have problems with the missing settings being removed or reset
Expand Down Expand Up @@ -44,6 +49,7 @@ export default Service.extend(_ProxyMixin, ValidationEngine, {
reload() {
return this._loadSettings().then((settings) => {
this.set('content', settings);
this.set('settledIcon', get(settings, 'icon'));
return this;
});
},
Expand All @@ -55,7 +61,10 @@ export default Service.extend(_ProxyMixin, ValidationEngine, {
return false;
}

return settings.save();
return settings.save().then((settings) => {
this.set('settledIcon', get(settings, 'icon'));
return settings;
});
},

rollbackAttributes() {
Expand Down
8 changes: 7 additions & 1 deletion app/templates/application.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@

<div class="gh-viewport {{if autoNav 'gh-autonav'}} {{if showSettingsMenu 'settings-menu-expanded'}} {{if showMobileMenu 'mobile-menu-expanded'}}">
{{#if showNavMenu}}
{{gh-nav-menu open=autoNavOpen toggleMaximise="toggleAutoNav" openAutoNav="openAutoNav" showMarkdownHelp="toggleMarkdownHelpModal" closeMobileMenu="closeMobileMenu"}}
{{gh-nav-menu
open=autoNavOpen
icon=settings.settledIcon
toggleMaximise="toggleAutoNav"
openAutoNav="openAutoNav"
showMarkdownHelp="toggleMarkdownHelpModal"
closeMobileMenu="closeMobileMenu"}}
{{/if}}

{{#gh-main onMouseEnter="closeAutoNav" data-notification-count=topNotificationCount}}
Expand Down
2 changes: 1 addition & 1 deletion app/templates/components/gh-nav-menu.hbs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{{gh-menu-toggle desktopAction="toggleAutoNav" mobileAction="closeMobileMenu"}}
{{#gh-basic-dropdown horizontalPosition="right" calculatePosition=userDropdownPosition as |dropdown|}}
{{#dropdown.trigger tagName="header" class="gh-nav-menu"}}
<div class="gh-nav-menu-icon" style={{navMenuIcon}}></div>
<div class="gh-nav-menu-icon" style={{iconStyle}}></div>
<div class="gh-nav-menu-details">
<div class="gh-nav-menu-details-blog">{{config.blogTitle}}</div>
<div class="gh-nav-menu-details-user">{{session.user.name}}</div>
Expand Down

0 comments on commit 184b8c7

Please sign in to comment.