mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-04 08:54:36 +03:00
b4cdc85a59
refs https://github.com/TryGhost/Ghost/issues/6949 Handle version mismatch errors by: - displaying an alert asking the user to copy any data and refresh - disabling navigation so that unsaved data is not accidentally lost Detailed changes: - add `error` action to application route for global route-based error handling - remove 404-handler mixin, move logic into app route error handler - update `.catch` in validation-engine so that promises are rejected with the original error objects - add `VersionMismatchError` and `isVersionMismatchError` to ajax service - add `upgrade-status` service - has a method to trigger the alert and toggle the "upgrade required" mode - is injected into all routes by default so that it can be checked before transitioning - add `Route` override - updates the `willTransition` hook to check the `upgrade-status` service and abort the transition if we're in "upgrade required" mode - update notifications `showAPIError` method to handle version mismatch errors - update any areas where we were catching ajax errors manually so that the version mismatch error handling is obeyed - fix redirect tests in editor acceptance test - fix mirage's handling of 404s for unknown posts in get post requests - adjust alert z-index to to appear above modal backgrounds
208 lines
6.3 KiB
JavaScript
208 lines
6.3 KiB
JavaScript
/* global key */
|
|
/* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */
|
|
import Component from 'ember-component';
|
|
import RSVP from 'rsvp';
|
|
import computed from 'ember-computed';
|
|
import run from 'ember-runloop';
|
|
import injectService from 'ember-service/inject';
|
|
import {isBlank, isEmpty} from 'ember-utils';
|
|
|
|
export function computedGroup(category) {
|
|
return computed('content', 'currentSearch', function () {
|
|
if (!this.get('currentSearch') || !this.get('content')) {
|
|
return [];
|
|
}
|
|
|
|
return this.get('content').filter((item) => {
|
|
let search = new RegExp(this.get('currentSearch'), 'ig');
|
|
|
|
return (item.category === category) &&
|
|
item.title.match(search);
|
|
});
|
|
});
|
|
}
|
|
|
|
export default Component.extend({
|
|
|
|
selection: null,
|
|
content: [],
|
|
isLoading: false,
|
|
contentExpiry: 10 * 1000,
|
|
contentExpiresAt: false,
|
|
currentSearch: '',
|
|
|
|
posts: computedGroup('Posts'),
|
|
pages: computedGroup('Pages'),
|
|
users: computedGroup('Users'),
|
|
tags: computedGroup('Tags'),
|
|
|
|
_store: injectService('store'),
|
|
_routing: injectService('-routing'),
|
|
ajax: injectService(),
|
|
notifications: injectService(),
|
|
|
|
refreshContent() {
|
|
let promises = [];
|
|
let now = new Date();
|
|
let contentExpiry = this.get('contentExpiry');
|
|
let contentExpiresAt = this.get('contentExpiresAt');
|
|
|
|
if (this.get('isLoading') || contentExpiresAt > now) {
|
|
return RSVP.resolve();
|
|
}
|
|
|
|
this.set('isLoading', true);
|
|
this.set('content', []);
|
|
promises.pushObject(this._loadPosts());
|
|
promises.pushObject(this._loadUsers());
|
|
promises.pushObject(this._loadTags());
|
|
|
|
return RSVP.all(promises).then(() => { }).finally(() => {
|
|
this.set('isLoading', false);
|
|
this.set('contentExpiresAt', new Date(now.getTime() + contentExpiry));
|
|
});
|
|
},
|
|
|
|
groupedContent: computed('posts', 'pages', 'users', 'tags', function () {
|
|
let groups = [];
|
|
|
|
if (!isEmpty(this.get('posts'))) {
|
|
groups.pushObject({groupName: 'Posts', options: this.get('posts')});
|
|
}
|
|
|
|
if (!isEmpty(this.get('pages'))) {
|
|
groups.pushObject({groupName: 'Pages', options: this.get('pages')});
|
|
}
|
|
|
|
if (!isEmpty(this.get('users'))) {
|
|
groups.pushObject({groupName: 'Users', options: this.get('users')});
|
|
}
|
|
|
|
if (!isEmpty(this.get('tags'))) {
|
|
groups.pushObject({groupName: 'Tags', options: this.get('tags')});
|
|
}
|
|
|
|
return groups;
|
|
}),
|
|
|
|
_loadPosts() {
|
|
let store = this.get('_store');
|
|
let postsUrl = `${store.adapterFor('post').urlForQuery({}, 'post')}/`;
|
|
let postsQuery = {fields: 'id,title,page', limit: 'all', status: 'all', staticPages: 'all'};
|
|
let content = this.get('content');
|
|
|
|
return this.get('ajax').request(postsUrl, {data: postsQuery}).then((posts) => {
|
|
content.pushObjects(posts.posts.map((post) => {
|
|
return {
|
|
id: `post.${post.id}`,
|
|
title: post.title,
|
|
category: post.page ? 'Pages' : 'Posts'
|
|
};
|
|
}));
|
|
}).catch((error) => {
|
|
this.get('notifications').showAPIError(error, {key: 'search.loadPosts.error'});
|
|
});
|
|
},
|
|
|
|
_loadUsers() {
|
|
let store = this.get('_store');
|
|
let usersUrl = `${store.adapterFor('user').urlForQuery({}, 'user')}/`;
|
|
let usersQuery = {fields: 'name,slug', limit: 'all'};
|
|
let content = this.get('content');
|
|
|
|
return this.get('ajax').request(usersUrl, {data: usersQuery}).then((users) => {
|
|
content.pushObjects(users.users.map((user) => {
|
|
return {
|
|
id: `user.${user.slug}`,
|
|
title: user.name,
|
|
category: 'Users'
|
|
};
|
|
}));
|
|
}).catch((error) => {
|
|
this.get('notifications').showAPIError(error, {key: 'search.loadUsers.error'});
|
|
});
|
|
},
|
|
|
|
_loadTags() {
|
|
let store = this.get('_store');
|
|
let tagsUrl = `${store.adapterFor('tag').urlForQuery({}, 'tag')}/`;
|
|
let tagsQuery = {fields: 'name,slug', limit: 'all'};
|
|
let content = this.get('content');
|
|
|
|
return this.get('ajax').request(tagsUrl, {data: tagsQuery}).then((tags) => {
|
|
content.pushObjects(tags.tags.map((tag) => {
|
|
return {
|
|
id: `tag.${tag.slug}`,
|
|
title: tag.name,
|
|
category: 'Tags'
|
|
};
|
|
}));
|
|
}).catch((error) => {
|
|
this.get('notifications').showAPIError(error, {key: 'search.loadTags.error'});
|
|
});
|
|
},
|
|
|
|
_performSearch(term, resolve, reject) {
|
|
if (isBlank(term)) {
|
|
return resolve([]);
|
|
}
|
|
|
|
this.refreshContent().then(() => {
|
|
this.set('currentSearch', term);
|
|
|
|
return resolve(this.get('groupedContent'));
|
|
}).catch(reject);
|
|
},
|
|
|
|
_setKeymasterScope() {
|
|
key.setScope('search-input');
|
|
},
|
|
|
|
_resetKeymasterScope() {
|
|
key.setScope('default');
|
|
},
|
|
|
|
willDestroy() {
|
|
this._super(...arguments);
|
|
this._resetKeymasterScope();
|
|
},
|
|
|
|
actions: {
|
|
openSelected(selected) {
|
|
if (!selected) {
|
|
return;
|
|
}
|
|
|
|
if (selected.category === 'Posts' || selected.category === 'Pages') {
|
|
let id = selected.id.replace('post.', '');
|
|
this.get('_routing.router').transitionTo('editor.edit', id);
|
|
}
|
|
|
|
if (selected.category === 'Users') {
|
|
let id = selected.id.replace('user.', '');
|
|
this.get('_routing.router').transitionTo('team.user', id);
|
|
}
|
|
|
|
if (selected.category === 'Tags') {
|
|
let id = selected.id.replace('tag.', '');
|
|
this.get('_routing.router').transitionTo('settings.tags.tag', id);
|
|
}
|
|
},
|
|
|
|
onFocus() {
|
|
this._setKeymasterScope();
|
|
},
|
|
|
|
onBlur() {
|
|
this._resetKeymasterScope();
|
|
},
|
|
|
|
search(term) {
|
|
return new RSVP.Promise((resolve, reject) => {
|
|
run.debounce(this, this._performSearch, term, resolve, reject, 200);
|
|
});
|
|
}
|
|
}
|
|
|
|
});
|