Ghost/ghost/admin/app/services/tour.js
Kevin Ansfield 2231dd84c2 Migrated to <AngleBracketSyntax /> (#1460)
no issue

Ember is migrating to `<AngleBracketSyntax />` for component invocation, see https://github.com/emberjs/rfcs/blob/master/text/0311-angle-bracket-invocation.md

We were in a half-way situation where some templates used angle bracket syntax in some places, this PR updates templates to use the syntax everywhere.

This simplifies the rules for what template code is referring to...

`<Component>` = a component
`{{helper}}` = a helper (or locally assigned handlebars variable)
`{{this.foo}}` = data on the template backing context (a component/controller)
`{{@foo}}` = a named argument passed into the component that the component backing class has not modified (note: this commit does not introduce any named arguments)

- ran codemod https://github.com/ember-codemods/ember-angle-brackets-codemod on the following directories:
  - `app/templates`
  - `lib/koenig-editor/addon/templates`
- removed positional params from components as angle bracket syntax does not support them
  - `gh-feature-flag`
  - `gh-tour-item`
  - `gh-cm-editor`
  - `gh-fullscreen-modal`
  - `gh-task-button`
- updates some code that was missed in 3c851293c1 to use explicit this
2020-01-16 15:14:03 +00:00

140 lines
5.2 KiB
JavaScript

import Evented from '@ember/object/evented';
import RSVP from 'rsvp';
import Service, {inject as service} from '@ember/service';
import {computed} from '@ember/object';
export default Service.extend(Evented, {
ghostPaths: service(),
session: service(),
// 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 <GhTourItem @trobberId="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.
viewed: null,
// 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
throbbers: null,
init() {
this._super(...arguments);
let adminUrl = `${window.location.origin}${this.get('ghostPaths.url').admin()}`;
let adminDisplayUrl = adminUrl.replace(`${window.location.protocol}//`, '');
this.viewed = [];
this.throbbers = [{
id: 'getting-started',
title: 'Getting started with Ghost',
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>`
}, {
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',
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>'
}];
},
_activeThrobbers: computed('viewed.[]', 'throbbers.[]', function () {
// return throbbers that haven't been viewed
let viewed = this.viewed;
let throbbers = this.throbbers;
return throbbers.reject(throbber => viewed.includes(throbber.id));
}),
// 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() {
let viewed = this.viewed;
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 <GhTourItem /> component to determine visibility
activeThrobber(id) {
let activeThrobbers = this._activeThrobbers;
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) {
let viewed = this.viewed;
if (!viewed.includes(id)) {
viewed.pushObject(id);
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() {
let allThrobberIds = this.throbbers.mapBy('id');
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();
}
});