1
0
Fork 0
mirror of https://github.com/TryGhost/Ghost-Admin.git synced 2023-12-14 02:33:04 +01:00

bring keyboard navigation back to the content screen

refs https://github.com/TryGhost/Ghost/issues/7860
- restores the previous up/down/enter/cmd+backspace functionality
- modifies the `delete-post` modal to accept a hash with an `onSuccess` action
This commit is contained in:
Kevin Ansfield 2017-02-03 18:35:13 +00:00 committed by Austin Burdine
parent ffd055c422
commit 6d26685b65
6 changed files with 101 additions and 14 deletions

View file

@ -14,9 +14,10 @@ const ObjectPromiseProxy = ObjectProxy.extend(PromiseProxyMixin);
export default Component.extend({
tagName: 'li',
classNames: ['gh-posts-list-item'],
classNameBindings: ['active'],
post: null,
previewIsHidden: false,
active: false,
isFeatured: alias('post.featured'),
isPage: alias('post.page'),
@ -63,11 +64,40 @@ export default Component.extend({
return htmlSafe(`${text.slice(0, 80)}…`);
}),
didReceiveAttrs() {
if (this.get('active')) {
this.scrollIntoView();
}
},
click() {
this.sendAction('onClick', this.get('post'));
},
doubleClick() {
this.sendAction('onDoubleClick', this.get('post'));
},
scrollIntoView() {
let element = this.$();
let offset = element.offset().top;
let elementHeight = element.height();
let container = $('.content-list');
let containerHeight = container.height();
let currentScroll = container.scrollTop();
let isBelowTop, isAboveBottom, isOnScreen;
isAboveBottom = offset < containerHeight;
isBelowTop = offset > elementHeight;
isOnScreen = isBelowTop && isAboveBottom;
if (!isOnScreen) {
// Scroll so that element is centered in container
// 40 is the amount of padding on the container
container.clearQueue().animate({
scrollTop: currentScroll + offset - 40 - containerHeight / 2
});
}
}
});

View file

@ -5,7 +5,8 @@ import {task} from 'ember-concurrency';
export default ModalComponent.extend({
post: alias('model'),
post: alias('model.post'),
onSuccess: alias('model.onSuccess'),
notifications: injectService(),
routing: injectService('-routing'),
@ -24,8 +25,10 @@ export default ModalComponent.extend({
// clear any previous error messages
this.get('notifications').closeAlerts('post.delete');
// redirect to content screen
this.get('routing').transitionTo('posts');
// trigger the success action
if (this.get('onSuccess')) {
this.get('onSuccess')();
}
},
_failure(error) {

View file

@ -49,6 +49,10 @@ export default AuthenticatedRoute.extend(base, {
actions: {
authorizationFailed() {
this.get('controller').send('toggleReAuthenticateModal');
},
redirectToContentScreen() {
this.transitionTo('posts');
}
}
});

View file

@ -12,6 +12,7 @@ export default AuthenticatedRoute.extend(InfinityRoute, ShortcutsRoute, {
totalPagesParam: 'meta.pagination.pages',
_type: null,
_selectedPostIndex: null,
model(params) {
this.set('_type', params.type);
@ -60,10 +61,18 @@ export default AuthenticatedRoute.extend(InfinityRoute, ShortcutsRoute, {
}),
stepThroughPosts(step) {
let currentPost = this.get('controller.currentPost');
let posts = this.get('controller.sortedPosts');
let currentPost = this.get('controller.selectedPost');
let posts = this.get('controller.model');
let length = posts.get('length');
let newPosition = posts.indexOf(currentPost) + step;
let newPosition;
// when the currentPost is deleted we won't be able to use indexOf.
// we keep track of the index locally so we can select next after deletion
if (this._selectedPostIndex !== null && length) {
newPosition = this._selectedPostIndex + step;
} else {
newPosition = posts.indexOf(currentPost) + step;
}
// if we are on the first or last item
// just do nothing (desired behavior is to not
@ -74,20 +83,40 @@ export default AuthenticatedRoute.extend(InfinityRoute, ShortcutsRoute, {
return;
}
// TODO: highlight post
// this.transitionTo('posts.post', posts.objectAt(newPosition));
this._selectedPostIndex = newPosition;
this.set('controller.selectedPost', posts.objectAt(newPosition));
},
shortcuts: {
'up, k': 'moveUp',
'down, j': 'moveDown',
c: 'newPost'
'enter': 'editPost',
'c': 'newPost',
'command+backspace, ctrl+backspace': 'deletePost'
},
resetController() {
this.set('controller.selectedPost', null);
this.set('controller.showDeletePostModal', false);
},
actions: {
willTransition() {
this._selectedPostIndex = null;
if (this.get('controller')) {
this.resetController();
}
},
queryParamsDidChange() {
this.refresh();
// reset the scroll position
// on direct page load controller won't exist so we want to
// avoid a double transition
if (this.get('controller')) {
this.refresh();
}
// scroll back to the top
$('.content-list').scrollTop(0);
},
@ -101,6 +130,23 @@ export default AuthenticatedRoute.extend(InfinityRoute, ShortcutsRoute, {
moveDown() {
this.stepThroughPosts(1);
},
editPost() {
let selectedPost = this.get('controller.selectedPost');
if (selectedPost) {
this.transitionTo('editor.edit', selectedPost.get('id'));
}
},
deletePost() {
this.get('controller').send('toggleDeletePostModal');
},
onPostDeletion() {
// select next post (re-select the current index)
this.stepThroughPosts(0);
}
}
});

View file

@ -58,7 +58,7 @@
{{#if showDeletePostModal}}
{{gh-fullscreen-modal "delete-post"
model=model
model=(hash post=model onSuccess=(route-action 'redirectToContentScreen'))
close=(action "toggleDeletePostModal")
modifier="action wide"}}
{{/if}}

View file

@ -3,6 +3,7 @@
{{#each model as |post|}}
{{gh-posts-list-item
post=post
active=(eq post selectedPost)
onDoubleClick="openEditor"
data-test-posts-list-item-id=post.id}}
{{else}}
@ -28,7 +29,10 @@
{{#if showDeletePostModal}}
{{gh-fullscreen-modal "delete-post"
model=currentPost
model=(hash
post=selectedPost
onSuccess=(route-action 'onPostDeletion')
)
close=(action "toggleDeletePostModal")
modifier="action wide"}}
{{/if}}