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:
parent
357a1c1654
commit
beb03dbb43
6 changed files with 101 additions and 14 deletions
|
@ -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
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -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}}
|
||||||
|
|
|
@ -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}}
|
||||||
|
|
Loading…
Add table
Reference in a new issue