2017-08-22 09:53:26 +02:00
import Evented from '@ember/object/evented' ;
2017-06-08 17:00:10 +02:00
import RSVP from 'rsvp' ;
2017-10-30 10:38:01 +01:00
import Service , { inject as service } from '@ember/service' ;
2017-08-22 09:53:26 +02:00
import { computed } from '@ember/object' ;
2017-06-08 17:00:10 +02:00
export default Service . extend ( Evented , {
2017-10-30 10:38:01 +01:00
ghostPaths : service ( ) ,
session : service ( ) ,
2017-06-08 17:00:10 +02:00
// this service is responsible for managing tour item visibility and syncing
// the viewed state with the server
//
// tour items need to be centrally defined here so that we have a single
// source of truth for marking all tour items as viewed
//
// a {{gh-tour-item "unique-id"}} component can be inserted in any template,
// this will use the tour service to grab content and determine visibility
// with the component in control of rendering the throbber/controlling the
// modal - this allows the component lifecycle hooks to perform automatic
// display/cleanup when the relevant UI is visible.
2017-11-24 19:53:19 +01:00
viewed : null ,
2017-06-08 17:00:10 +02:00
// IDs should **NOT** be changed if they have been part of a release unless
// the re-display of the throbber should be forced. In that case it may be
// useful to add a version number eg. `my-feature` -> `my-feature-v2`.
// Format is as follows:
//
// {
// id: 'test',
// title: 'This is a test',
// message: 'This is a test of our <strong>feature tour</strong> feature'
// }
//
// TODO: it may be better to keep this configuration elsewhere to keep the
// service clean. Eventually we'll want apps to be able to register their
// own throbbers and tour content
2017-11-24 19:53:19 +01:00
throbbers : null ,
2017-06-08 17:00:10 +02:00
init ( ) {
2018-03-19 12:54:54 +01:00
this . _super ( ... arguments ) ;
2017-06-08 17:00:10 +02:00
let adminUrl = ` ${ window . location . origin } ${ this . get ( 'ghostPaths.url' ) . admin ( ) } ` ;
let adminDisplayUrl = adminUrl . replace ( ` ${ window . location . protocol } // ` , '' ) ;
2017-11-24 19:53:19 +01:00
this . viewed = [ ] ;
2017-06-08 17:00:10 +02:00
this . throbbers = [ {
id : 'getting-started' ,
title : 'Getting started with Ghost' ,
2019-03-21 10:33:14 +01:00
message : ` Welcome to Ghost Admin! From here you can browse your site, manage your content, and edit your settings.<br><br>You can always login to Ghost Admin by visiting <a href=" ${ adminUrl } " target="_blank"> ${ adminDisplayUrl } </a> `
2017-06-08 17:00:10 +02:00
} , {
id : 'using-the-editor' ,
title : 'Using the Ghost editor' ,
message : 'Ghost uses Markdown to allow you to write and format content quickly and easily. This toolbar also helps! Hit the <strong>?</strong> icon for more editor shortcuts.'
} , {
id : 'featured-post' ,
title : 'Setting a featured post' ,
message : 'Depending on your theme, featured posts receive special styling to make them stand out as a particularly important or emphasised story.'
} , {
id : 'upload-a-theme' ,
title : 'Customising your publication' ,
2019-07-25 09:15:31 +02:00
message : 'Using custom themes you can completely control the look and feel of your site to suit your brand. Here\'s a full guide to help: <strong><a href="https://ghost.org/docs/api/handlebars-themes/" target="_blank">https://ghost.org/docs/api/handlebars-themes/</a></strong>'
2017-06-08 17:00:10 +02:00
} ] ;
} ,
_activeThrobbers : computed ( 'viewed.[]' , 'throbbers.[]' , function ( ) {
// return throbbers that haven't been viewed
2019-03-06 14:53:54 +01:00
let viewed = this . viewed ;
let throbbers = this . throbbers ;
2017-06-08 17:00:10 +02:00
2018-01-05 16:38:23 +01:00
return throbbers . reject ( throbber => viewed . includes ( throbber . id ) ) ;
2017-06-08 17:00:10 +02:00
} ) ,
// retrieve the IDs of the viewed throbbers from the server, always returns
// a promise
fetchViewed ( ) {
return this . get ( 'session.user' ) . then ( ( user ) => {
let viewed = user . get ( 'tour' ) || [ ] ;
this . set ( 'viewed' , viewed ) ;
return viewed ;
} ) ;
} ,
// save the list of viewed throbbers to the server overwriting the
// entire list
syncViewed ( ) {
2019-03-06 14:53:54 +01:00
let viewed = this . viewed ;
2017-06-08 17:00:10 +02:00
return this . get ( 'session.user' ) . then ( ( user ) => {
user . set ( 'tour' , viewed ) ;
return user . save ( ) ;
} ) ;
} ,
// returns throbber content for a given ID only if that throbber hasn't been
// viewed. Used by the {{gh-tour-item}} component to determine visibility
activeThrobber ( id ) {
2019-03-06 14:53:54 +01:00
let activeThrobbers = this . _activeThrobbers ;
2017-06-08 17:00:10 +02:00
return activeThrobbers . findBy ( 'id' , id ) ;
} ,
// when a throbber is opened the component will call this method to mark
// it as viewed and sync with the server. Always returns a promise
markThrobberAsViewed ( id ) {
2019-03-06 14:53:54 +01:00
let viewed = this . viewed ;
2017-06-08 17:00:10 +02:00
if ( ! viewed . includes ( id ) ) {
2017-07-10 14:15:20 +02:00
viewed . pushObject ( id ) ;
2017-06-08 17:00:10 +02:00
this . trigger ( 'viewed' , id ) ;
return this . syncViewed ( ) ;
} else {
return RSVP . resolve ( true ) ;
}
} ,
// opting-out will use the list of IDs defined in this file making it the
// single-source-of-truth and allowing future client updates to control when
// new UI should be surfaced through tour items
optOut ( ) {
2019-03-06 14:53:54 +01:00
let allThrobberIds = this . throbbers . mapBy ( 'id' ) ;
2017-06-08 17:00:10 +02:00
this . set ( 'viewed' , allThrobberIds ) ;
this . trigger ( 'optOut' ) ;
return this . syncViewed ( ) ;
} ,
// this is not used anywhere at the moment but it's useful to use via ember
// inspector as a reset mechanism
reEnable ( ) {
this . set ( 'viewed' , [ ] ) ;
return this . syncViewed ( ) ;
}
} ) ;