0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-24 23:48:13 -05: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 357a1c1654
commit beb03dbb43
6 changed files with 101 additions and 14 deletions

View file

@ -14,9 +14,10 @@ const ObjectPromiseProxy = ObjectProxy.extend(PromiseProxyMixin);
export default Component.extend({ export default Component.extend({
tagName: 'li', tagName: 'li',
classNames: ['gh-posts-list-item'], classNames: ['gh-posts-list-item'],
classNameBindings: ['active'],
post: null, post: null,
previewIsHidden: false, active: false,
isFeatured: alias('post.featured'), isFeatured: alias('post.featured'),
isPage: alias('post.page'), isPage: alias('post.page'),
@ -63,11 +64,40 @@ export default Component.extend({
return htmlSafe(`${text.slice(0, 80)}…`); return htmlSafe(`${text.slice(0, 80)}…`);
}), }),
didReceiveAttrs() {
if (this.get('active')) {
this.scrollIntoView();
}
},
click() { click() {
this.sendAction('onClick', this.get('post')); this.sendAction('onClick', this.get('post'));
}, },
doubleClick() { doubleClick() {
this.sendAction('onDoubleClick', this.get('post')); 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({ export default ModalComponent.extend({
post: alias('model'), post: alias('model.post'),
onSuccess: alias('model.onSuccess'),
notifications: injectService(), notifications: injectService(),
routing: injectService('-routing'), routing: injectService('-routing'),
@ -24,8 +25,10 @@ export default ModalComponent.extend({
// clear any previous error messages // clear any previous error messages
this.get('notifications').closeAlerts('post.delete'); this.get('notifications').closeAlerts('post.delete');
// redirect to content screen // trigger the success action
this.get('routing').transitionTo('posts'); if (this.get('onSuccess')) {
this.get('onSuccess')();
}
}, },
_failure(error) { _failure(error) {

View file

@ -49,6 +49,10 @@ export default AuthenticatedRoute.extend(base, {
actions: { actions: {
authorizationFailed() { authorizationFailed() {
this.get('controller').send('toggleReAuthenticateModal'); 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', totalPagesParam: 'meta.pagination.pages',
_type: null, _type: null,
_selectedPostIndex: null,
model(params) { model(params) {
this.set('_type', params.type); this.set('_type', params.type);
@ -60,10 +61,18 @@ export default AuthenticatedRoute.extend(InfinityRoute, ShortcutsRoute, {
}), }),
stepThroughPosts(step) { stepThroughPosts(step) {
let currentPost = this.get('controller.currentPost'); let currentPost = this.get('controller.selectedPost');
let posts = this.get('controller.sortedPosts'); let posts = this.get('controller.model');
let length = posts.get('length'); 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 // if we are on the first or last item
// just do nothing (desired behavior is to not // just do nothing (desired behavior is to not
@ -74,20 +83,40 @@ export default AuthenticatedRoute.extend(InfinityRoute, ShortcutsRoute, {
return; return;
} }
// TODO: highlight post this._selectedPostIndex = newPosition;
// this.transitionTo('posts.post', posts.objectAt(newPosition)); this.set('controller.selectedPost', posts.objectAt(newPosition));
}, },
shortcuts: { shortcuts: {
'up, k': 'moveUp', 'up, k': 'moveUp',
'down, j': 'moveDown', '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: { actions: {
willTransition() {
this._selectedPostIndex = null;
if (this.get('controller')) {
this.resetController();
}
},
queryParamsDidChange() { queryParamsDidChange() {
// on direct page load controller won't exist so we want to
// avoid a double transition
if (this.get('controller')) {
this.refresh(); this.refresh();
// reset the scroll position }
// scroll back to the top
$('.content-list').scrollTop(0); $('.content-list').scrollTop(0);
}, },
@ -101,6 +130,23 @@ export default AuthenticatedRoute.extend(InfinityRoute, ShortcutsRoute, {
moveDown() { moveDown() {
this.stepThroughPosts(1); 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}} {{#if showDeletePostModal}}
{{gh-fullscreen-modal "delete-post" {{gh-fullscreen-modal "delete-post"
model=model model=(hash post=model onSuccess=(route-action 'redirectToContentScreen'))
close=(action "toggleDeletePostModal") close=(action "toggleDeletePostModal")
modifier="action wide"}} modifier="action wide"}}
{{/if}} {{/if}}

View file

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