Skip to content
This repository has been archived by the owner on Nov 28, 2022. It is now read-only.

Commit

Permalink
✨ Added "What's new" indicator and modal to highlight recent updates (#…
Browse files Browse the repository at this point in the history
…1292)

no issue

- adds `whats-new` service that fetches the changelog from ghost.org and exposes the latest changelog entries
- trigger a background fetch of the changelog from ghost.org when first loading the admin when logged in, or after signing in
- adds a "What's new" menu item next to the user popup menu
- adds an indicator to the user menu button and what's new menu item if there are unseen changelog entries
- closing the changelog modal will update the "last seen date", clearing both indicators
  • Loading branch information
kevinansfield committed Aug 23, 2019
1 parent c3804fc commit a63943e
Show file tree
Hide file tree
Showing 18 changed files with 322 additions and 6 deletions.
6 changes: 6 additions & 0 deletions app/authenticators/cookie.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export default Authenticator.extend({
ghostPaths: service(),
settings: service(),
tour: service(),
whatsNew: service(),

sessionEndpoint: computed('ghostPaths.apiRoot', function () {
return `${this.ghostPaths.apiRoot}/session`;
Expand Down Expand Up @@ -37,6 +38,11 @@ export default Authenticator.extend({
this.tour.fetchViewed()
];

// kick off background update of "whats new"
// - we don't want to block the router for this
// - we need the user details to know what the user has seen
this.whatsNew.fetchLatest.perform();

return RSVP.all(preloadPromises).then(() => {
return authResult;
});
Expand Down
1 change: 1 addition & 0 deletions app/components/gh-nav-menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export default Component.extend(ShortcutsMixin, {
router: service(),
session: service(),
ui: service(),
whatsNew: service(),

tagName: 'nav',
classNames: ['gh-nav'],
Expand Down
6 changes: 6 additions & 0 deletions app/components/gh-whats-new.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Component from '@ember/component';
import {inject as service} from '@ember/service';

export default Component.extend({
whatsNew: service()
});
8 changes: 8 additions & 0 deletions app/components/modal-whats-new.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import ModalComponent from 'ghost-admin/components/modal-base';
import {inject as service} from '@ember/service';

export default ModalComponent.extend({
whatsNew: service(),

confirm() {}
});
7 changes: 7 additions & 0 deletions app/routes/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export default Route.extend(ApplicationRouteMixin, ShortcutsRoute, {
settings: service(),
tour: service(),
ui: service(),
whatsNew: service(),

shortcuts,

Expand Down Expand Up @@ -69,6 +70,12 @@ export default Route.extend(ApplicationRouteMixin, ShortcutsRoute, {
this.tour.fetchViewed()
]).then((results) => {
this._appLoaded = true;

// kick off background update of "whats new"
// - we don't want to block the router for this
// - we need the user details to know what the user has seen
this.whatsNew.fetchLatest.perform();

return results;
});
}
Expand Down
93 changes: 93 additions & 0 deletions app/services/whats-new.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import Service from '@ember/service';
import fetch from 'fetch';
import moment from 'moment';
import {action} from '@ember/object';
import {computed} from '@ember/object';
import {isEmpty} from '@ember/utils';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency';

export default Service.extend({
session: service(),

entries: null,
changelogUrl: 'https://ghost.org/blog/',
isShowingModal: false,

_user: null,

init() {
this._super(...arguments);
this.entries = [];
},

whatsNewSettings: computed('_user.accessibility', function () {
let settingsJson = this.get('_user.accessibility') || '{}';
let settings = JSON.parse(settingsJson);
return settings.whatsNew;
}),

hasNew: computed('whatsNewSettings.lastSeenDate', 'entries.[]', function () {
if (isEmpty(this.entries)) {
return false;
}

let [latestEntry] = this.entries;

let lastSeenDate = this.get('whatsNewSettings.lastSeenDate') || '2019-01-01 00:00:00';
let lastSeenMoment = moment(lastSeenDate);
let latestDate = latestEntry.published_at;
let latestMoment = moment(latestDate || lastSeenDate);
return latestMoment.isAfter(lastSeenMoment);
}),

showModal: action(function () {
this.set('isShowingModal', true);
}),

closeModal: action(function () {
this.set('isShowingModal', false);
this.updateLastSeen.perform();
}),

fetchLatest: task(function* () {
try {
// we should already be logged in at this point so lets grab the user
// record and store it locally so that we don't have to deal with
// session.user being a promise and causing issues with CPs
let user = yield this.session.user;
this.set('_user', user);

let response = yield fetch('https://ghost.org/changelog.json');
if (!response.ok) {
// eslint-disable-next-line
return console.error('Failed to fetch changelog', {response});
}

let result = yield response.json();
this.set('entries', result.posts || []);
this.set('changelogUrl', result.changelogUrl);
} catch (e) {
console.error(e); // eslint-disable-line
}
}),

updateLastSeen: task(function* () {
let settingsJson = this._user.accessibility || '{}';
let settings = JSON.parse(settingsJson);
let [latestEntry] = this.entries;

if (!latestEntry) {
return;
}

if (!settings.whatsNew) {
settings.whatsNew = {};
}

settings.whatsNew.lastSeenDate = latestEntry.published_at;

this._user.set('accessibility', JSON.stringify(settings));
yield this._user.save();
})
});
1 change: 1 addition & 0 deletions app/styles/app-dark.css
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
@import "layouts/packages.css";
@import "layouts/subscribers.css";
@import "layouts/labs.css";
@import "layouts/whats-new.css";


:root {
Expand Down
1 change: 1 addition & 0 deletions app/styles/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
@import "layouts/packages.css";
@import "layouts/subscribers.css";
@import "layouts/labs.css";
@import "layouts/whats-new.css";


/* ---------------------------✈️----------------------------- */
Expand Down
20 changes: 18 additions & 2 deletions app/styles/layouts/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -116,21 +116,26 @@
}

.gh-nav-menu-dropdown .dropdown-menu {
top: -300px;
top: -315px;
left: 30px;
margin: 10px 0 0;
box-shadow: 0 0 1px rgba(0, 0, 0, 0.1), 0 0 3px rgba(0, 0, 0, 0.08), 0 18px 45px -5px rgba(0, 0, 0, 0.15);
min-width: 244px;
padding: 6px 0;
}

.gh-nav-menu-dropdown .dropdown-menu>li>a {
.gh-nav-menu-dropdown .dropdown-menu>li>a,
.gh-nav-menu-dropdown .dropdown-menu>li>button {
font-size: 1.4rem;
margin: 0;
width: unset;
padding: 7px 14px 8px;
}

.gh-nav-menu-dropdown .dropdown-menu>li>button {
width: 100%;
}

.gh-nav-menu-dropdown .dropdown-menu .divider {
margin: 6px 0;
}
Expand Down Expand Up @@ -670,6 +675,17 @@
margin: 0 10px;
}

/* What's new badges
/* ---------------------------------------------------- */
.gh-whats-new-badge-account {
top: -6px;
right: -6px;
border: 1px solid var(--white);
width: 12px;
height: 12px;
}


/* Used on tags, settings, labs, design etc.
/* TODO: remove and re-use gh-canvas...
/* ---------------------------------------------------- */
Expand Down
79 changes: 79 additions & 0 deletions app/styles/layouts/whats-new.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
.gh-wn-header {
position: relative;
display: flex;
align-items: center;
margin: -32px -32px 0;
border-top-left-radius: 6px;
border-top-right-radius: 6px;
overflow: hidden;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
background: var(--purple);
background: linear-gradient(135deg, color-mod(var(--purple) h(-10) s(+5%) l(-10%)) 0%, rgba(173,38,180,1) 100%);
padding: 18px 32px 12px;
}

.gh-wn-header .background-img {
position: absolute;
top: -30px;
left: 0;
}

.gh-wn-header h2 {
font-size: 1.3rem;
font-weight: 600;
text-transform: uppercase;
color: #FFF;
margin: 0 8px 4px;
}

.gh-wn-content {
margin: 0px -32px;
padding: 0px 32px;
max-height: calc(100vh - 170px);
overflow-y: scroll;
}

.gh-wn-close {
stroke: #FFF;
opacity: 0.6;
transition: all 0.2s ease-in-out;
}

.gh-wn-close:hover {
opacity: 1.0;
}

.gh-wn-entry {
margin: 0 -32px;
padding: 0 32px;
border-bottom: 1px solid var(--whitegrey);
}

.gh-wn-entry:last-of-type {
margin-bottom: 0px;
border-bottom: none;
padding-bottom: 0px;
}

.gh-wn-entry h4 {
font-size: 1.2rem;
font-weight: 500;
letter-spacing: 0.3px;
text-transform: uppercase;
margin: 24px 0 4px;
color: var(--midlightgrey);
}

.gh-wn-entry p {
margin: 0 0 20px;
padding: 0;
}

.gh-wn-footer {
margin: 0 -32px -32px;
padding: 14px 32px 16px;
border-top: 1px solid var(--whitegrey);
justify-content: space-between;
}
2 changes: 2 additions & 0 deletions app/templates/application.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
{{gh-content-cover}}

{{gh-mobile-nav-bar}}

<GhWhatsNew />
</div>{{!gh-viewport}}
{{/gh-app}}

Expand Down
16 changes: 13 additions & 3 deletions app/templates/components/gh-nav-menu.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@
{{#gh-basic-dropdown horizontalPosition="left" verticalPosition="top" calculatePosition=userDropdownPosition as |dropdown|}}
{{#dropdown.trigger tagName="div" class="flex items-center outline-0 pointer space-between pa2 pl4 pr3"}}
<div class="flex-auto flex items-center">
<div class="gh-user-avatar" style={{background-image-style session.user.profileImageUrl}}></div>
<div class="gh-user-avatar relative" style={{background-image-style session.user.profileImageUrl}}>
{{#if this.whatsNew.hasNew}}<span class="absolute dib bg-blue ba b--white br-100 gh-whats-new-badge-account"></span>{{/if}}
</div>
<div class="flex flex-column items-start justify-center">
<span class="gh-user-name {{if session.user.name "mb1"}}" title="{{session.user.name}}">{{session.user.name}}</span>
<span class="gh-user-email" title="{{session.user.email}}">{{session.user.email}}</span>
Expand All @@ -114,7 +116,15 @@
{{#link-to "about" classNames="dropdown-item" role="menuitem" tabindex="-1" data-test-nav="about"}}
{{svg-jar "store"}} About Ghost
{{/link-to}}
</li>
</li>
<li role="presentation">
<button class="dropdown-item" role="menuitem" tabindex="-1" {{on "click" this.whatsNew.showModal}}>
{{svg-jar "gift"}} What's new?
{{#if this.whatsNew.hasNew}}
<div class="flex-grow-1 flex justify-end"><span class="dib w2 h2 top-0 right-0 bg-blue br-100"></span></div>
{{/if}}
</button>
</li>
<li class="divider"></li>
<li role="presentation">
{{#link-to "staff.user" session.user.slug classNames="dropdown-item" role="menuitem" tabindex="-1" data-test-nav="user-profile"}}
Expand Down Expand Up @@ -171,4 +181,4 @@
throbberAttachment="middle right"
popoverTriangleClass="left-top"
throbberOffset="0px 0px"
}}
}}
7 changes: 7 additions & 0 deletions app/templates/components/gh-whats-new.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{{#if this.whatsNew.isShowingModal}}
<GhFullscreenModal
@modal="whats-new"
@modifier="wide"
@close={{fn this.whatsNew.closeModal}}
/>
{{/if}}
32 changes: 32 additions & 0 deletions app/templates/components/modal-whats-new.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<header class="modal-header gh-wn-header" data-test-modal="whats-new">
<img src="assets/img/whats-new-header-bg.svg" class="background-img" />
<span>{{svg-jar "gift" class="w5 h5 fill-white"}}</span>
<h2>What's new?</h2>
</header>

<button class="close gh-wn-close" href="" title="Close" {{on "click" this.closeModal}}>
{{svg-jar "close"}}
</button>

<div class="modal-body gh-wn-content">
{{#each this.whatsNew.entries as |entry|}}
<div class="gh-wn-entry">
<h4>{{moment-format entry.published_at "DD MMMM YYYY"}}</h4>
<h1 class="f1 fw6 ma0 pa0 mb3">{{entry.title}}</h1>
{{#if entry.feature_image}}
<img class="mb5" src={{entry.feature_image}}>
{{/if}}
{{#if entry.custom_excerpt}}
<p>{{entry.custom_excerpt}}</p>
<a href={{entry.url}} class="dib fw6 mb6" target="_blank" rel="noopener noreferrer">Read the full post →</a>
{{else}}
{{{entry.html}}}
{{/if}}
</div>
{{/each}}
</div>
<div class="modal-footer gh-wn-footer">
<a href={{this.whatsNew.changelogUrl}} target="_blank" rel="noopener noreferrer">
See all past updates
</a>
</div>

0 comments on commit a63943e

Please sign in to comment.