UX: keyboard shortcuts (j/k) should work with /categories
This commit is contained in:
parent
bed26ea0b3
commit
b4a13a1afa
|
@ -1,86 +1,92 @@
|
||||||
import DiscourseURL from "discourse/lib/url";
|
import DiscourseURL from 'discourse/lib/url';
|
||||||
import Composer from "discourse/models/composer";
|
import Composer from 'discourse/models/composer';
|
||||||
import { minimumOffset } from "discourse/lib/offset-calculator";
|
import { minimumOffset } from 'discourse/lib/offset-calculator';
|
||||||
|
|
||||||
const bindings = {
|
const bindings = {
|
||||||
"!": { postAction: "showFlags" },
|
'!': {postAction: 'showFlags'},
|
||||||
"#": { handler: "goToPost", anonymous: true },
|
'#': {handler: 'goToPost', anonymous: true},
|
||||||
"/": { handler: "toggleSearch", anonymous: true },
|
'/': {handler: 'toggleSearch', anonymous: true},
|
||||||
"ctrl+alt+f": { handler: "toggleSearch", anonymous: true },
|
'ctrl+alt+f': {handler: 'toggleSearch', anonymous: true},
|
||||||
"=": { handler: "toggleHamburgerMenu", anonymous: true },
|
'=': {handler: 'toggleHamburgerMenu', anonymous: true},
|
||||||
"?": { handler: "showHelpModal", anonymous: true },
|
'?': {handler: 'showHelpModal', anonymous: true},
|
||||||
".": { click: ".alert.alert-info.clickable", anonymous: true }, // show incoming/updated topics
|
'.': {click: '.alert.alert-info.clickable', anonymous: true}, // show incoming/updated topics
|
||||||
b: { handler: "toggleBookmark" },
|
'b': {handler: 'toggleBookmark'},
|
||||||
c: { handler: "createTopic" },
|
'c': {handler: 'createTopic'},
|
||||||
C: { handler: "focusComposer" },
|
'C': {handler: 'focusComposer'},
|
||||||
"ctrl+f": { handler: "showPageSearch", anonymous: true },
|
'ctrl+f': {handler: 'showPageSearch', anonymous: true},
|
||||||
"command+f": { handler: "showPageSearch", anonymous: true },
|
'command+f': {handler: 'showPageSearch', anonymous: true},
|
||||||
"ctrl+p": { handler: "printTopic", anonymous: true },
|
'ctrl+p': {handler: 'printTopic', anonymous: true},
|
||||||
"command+p": { handler: "printTopic", anonymous: true },
|
'command+p': {handler: 'printTopic', anonymous: true},
|
||||||
d: { postAction: "deletePost" },
|
'd': {postAction: 'deletePost'},
|
||||||
e: { postAction: "editPost" },
|
'e': {postAction: 'editPost'},
|
||||||
end: { handler: "goToLastPost", anonymous: true },
|
'end': {handler: 'goToLastPost', anonymous: true},
|
||||||
"command+down": { handler: "goToLastPost", anonymous: true },
|
'command+down': {handler: 'goToLastPost', anonymous: true},
|
||||||
f: { handler: "toggleBookmarkTopic" },
|
'f': {handler: 'toggleBookmarkTopic'},
|
||||||
"g h": { path: "/", anonymous: true },
|
'g h': {path: '/', anonymous: true},
|
||||||
"g l": { path: "/latest", anonymous: true },
|
'g l': {path: '/latest', anonymous: true},
|
||||||
"g n": { path: "/new" },
|
'g n': {path: '/new'},
|
||||||
"g u": { path: "/unread" },
|
'g u': {path: '/unread'},
|
||||||
"g c": { path: "/categories", anonymous: true },
|
'g c': {path: '/categories', anonymous: true},
|
||||||
"g t": { path: "/top", anonymous: true },
|
'g t': {path: '/top', anonymous: true},
|
||||||
"g b": { path: "/bookmarks" },
|
'g b': {path: '/bookmarks'},
|
||||||
"g p": { path: "/my/activity" },
|
'g p': {path: '/my/activity'},
|
||||||
"g m": { path: "/my/messages" },
|
'g m': {path: '/my/messages'},
|
||||||
home: { handler: "goToFirstPost", anonymous: true },
|
'home': {handler: 'goToFirstPost', anonymous: true},
|
||||||
"command+up": { handler: "goToFirstPost", anonymous: true },
|
'command+up': {handler: 'goToFirstPost', anonymous: true},
|
||||||
j: { handler: "selectDown", anonymous: true },
|
'j': {handler: 'selectDown', anonymous: true},
|
||||||
k: { handler: "selectUp", anonymous: true },
|
'k': {handler: 'selectUp', anonymous: true},
|
||||||
l: { click: ".topic-post.selected button.toggle-like" },
|
'l': {click: '.topic-post.selected button.toggle-like'},
|
||||||
"m m": { handler: "setTrackingToMuted" }, // mark topic as muted
|
'm m': {handler: 'setTrackingToMuted'}, // mark topic as muted
|
||||||
"m r": { handler: "setTrackingToRegular" }, // mark topic as regular
|
'm r': {handler: 'setTrackingToRegular'}, // mark topic as regular
|
||||||
"m t": { handler: "setTrackingToTracking" }, // mark topic as tracking
|
'm t': {handler: 'setTrackingToTracking'}, // mark topic as tracking
|
||||||
"m w": { handler: "setTrackingToWatching" }, // mark topic as watching
|
'm w': {handler: 'setTrackingToWatching'}, // mark topic as watching
|
||||||
"o,enter": { click: ".topic-list tr.selected a.title", anonymous: true }, // open selected topic
|
'o,enter': {click: ['.topic-list tr.selected a.title',
|
||||||
p: { handler: "showCurrentUser" },
|
'.category-list tr.selected .category-text-title',
|
||||||
q: { handler: "quoteReply" },
|
'.subcategory-list span.selected a.badge-wrapper',
|
||||||
r: { postAction: "replyToPost" },
|
'.categories-topics-list .categories-topics-list-item.selected div.main-link a.title',
|
||||||
s: { click: ".topic-post.selected a.post-date", anonymous: true }, // share post
|
'.latest .featured-topic.selected a.title'
|
||||||
"shift+j": { handler: "nextSection", anonymous: true },
|
].join(", "), anonymous: true}, // open selected topic, category, subcategory or featured topic
|
||||||
"shift+k": { handler: "prevSection", anonymous: true },
|
'tab': {handler: 'switchFocusCategoriesPage', anonymous: true},
|
||||||
"shift+p": { handler: "pinUnpinTopic" },
|
'p': {handler: 'showCurrentUser'},
|
||||||
"shift+r": { handler: "replyToTopic" },
|
'q': {handler: 'quoteReply'},
|
||||||
"shift+s": { click: "#topic-footer-buttons button.share", anonymous: true }, // share topic
|
'r': {postAction: 'replyToPost'},
|
||||||
"shift+u": { handler: "goToUnreadPost" },
|
's': {click: '.topic-post.selected a.post-date', anonymous: true}, // share post
|
||||||
"shift+z shift+z": { handler: "logout" },
|
'shift+j': {handler: 'nextSection', anonymous: true},
|
||||||
t: { postAction: "replyAsNewTopic" },
|
'shift+k': {handler: 'prevSection', anonymous: true},
|
||||||
u: { handler: "goBack", anonymous: true },
|
'shift+p': {handler: 'pinUnpinTopic'},
|
||||||
"x r": {
|
'shift+r': {handler: 'replyToTopic'},
|
||||||
click: "#dismiss-new,#dismiss-new-top,#dismiss-posts,#dismiss-posts-top"
|
'shift+s': {click: '#topic-footer-buttons button.share', anonymous: true}, // share topic
|
||||||
}, // dismiss new/posts
|
'shift+u': {handler: 'goToUnreadPost'},
|
||||||
"x t": { click: "#dismiss-topics,#dismiss-topics-top" } // dismiss topics
|
'shift+z shift+z': {handler: 'logout'},
|
||||||
|
't': {postAction: 'replyAsNewTopic'},
|
||||||
|
'u': {handler: 'goBack', anonymous: true},
|
||||||
|
'x r': {click: '#dismiss-new,#dismiss-new-top,#dismiss-posts,#dismiss-posts-top'}, // dismiss new/posts
|
||||||
|
'x t': {click: '#dismiss-topics,#dismiss-topics-top'} // dismiss topics
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const FOCUS_ORDER = ["parent-cats", "sub-cats", "latest-topics", "none"];
|
||||||
|
let CURRENT_FOCUS = FOCUS_ORDER[0];
|
||||||
|
let $FOCUSED_PARENT;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
bindEvents(keyTrapper, container) {
|
bindEvents(keyTrapper, container) {
|
||||||
this.keyTrapper = keyTrapper;
|
this.keyTrapper = keyTrapper;
|
||||||
this.container = container;
|
this.container = container;
|
||||||
this._stopCallback();
|
this._stopCallback();
|
||||||
|
|
||||||
this.searchService = this.container.lookup("search-service:main");
|
this.searchService = this.container.lookup('search-service:main');
|
||||||
this.appEvents = this.container.lookup("app-events:main");
|
this.appEvents = this.container.lookup('app-events:main');
|
||||||
this.currentUser = this.container.lookup("current-user:main");
|
this.currentUser = this.container.lookup('current-user:main');
|
||||||
let siteSettings = this.container.lookup("site-settings:main");
|
let siteSettings = this.container.lookup('site-settings:main');
|
||||||
|
|
||||||
// Disable the shortcut if private messages are disabled
|
// Disable the shortcut if private messages are disabled
|
||||||
if (!siteSettings.enable_personal_messages) {
|
if (!siteSettings.enable_personal_messages) {
|
||||||
delete bindings["g m"];
|
delete bindings['g m'];
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.keys(bindings).forEach(key => {
|
Object.keys(bindings).forEach(key => {
|
||||||
const binding = bindings[key];
|
const binding = bindings[key];
|
||||||
if (!binding.anonymous && !this.currentUser) {
|
if (!binding.anonymous && !this.currentUser) { return; }
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (binding.path) {
|
if (binding.path) {
|
||||||
this._bindToPath(binding.path, key);
|
this._bindToPath(binding.path, key);
|
||||||
|
@ -95,47 +101,47 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleBookmark() {
|
toggleBookmark() {
|
||||||
this.sendToSelectedPost("toggleBookmark");
|
this.sendToSelectedPost('toggleBookmark');
|
||||||
this.sendToTopicListItemView("toggleBookmark");
|
this.sendToTopicListItemView('toggleBookmark');
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleBookmarkTopic() {
|
toggleBookmarkTopic() {
|
||||||
const topic = this.currentTopic();
|
const topic = this.currentTopic();
|
||||||
// BIG hack, need a cleaner way
|
// BIG hack, need a cleaner way
|
||||||
if (topic && $(".posts-wrapper").length > 0) {
|
if (topic && $('.posts-wrapper').length > 0) {
|
||||||
this.container.lookup("controller:topic").send("toggleBookmark");
|
this.container.lookup('controller:topic').send('toggleBookmark');
|
||||||
} else {
|
} else {
|
||||||
this.sendToTopicListItemView("toggleBookmark");
|
this.sendToTopicListItemView('toggleBookmark');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
logout() {
|
logout() {
|
||||||
this.container.lookup("route:application").send("logout");
|
this.container.lookup('route:application').send('logout');
|
||||||
},
|
},
|
||||||
|
|
||||||
quoteReply() {
|
quoteReply() {
|
||||||
this.sendToSelectedPost("replyToPost");
|
this.sendToSelectedPost("replyToPost");
|
||||||
// lazy but should work for now
|
// lazy but should work for now
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
$(".d-editor .quote").click();
|
$('.d-editor .quote').click();
|
||||||
}, 500);
|
}, 500);
|
||||||
},
|
},
|
||||||
|
|
||||||
goToFirstPost() {
|
goToFirstPost() {
|
||||||
this._jumpTo("jumpTop");
|
this._jumpTo('jumpTop');
|
||||||
},
|
},
|
||||||
|
|
||||||
goToLastPost() {
|
goToLastPost() {
|
||||||
this._jumpTo("jumpBottom");
|
this._jumpTo('jumpBottom');
|
||||||
},
|
},
|
||||||
|
|
||||||
goToUnreadPost() {
|
goToUnreadPost() {
|
||||||
this._jumpTo("jumpUnread");
|
this._jumpTo('jumpUnread');
|
||||||
},
|
},
|
||||||
|
|
||||||
_jumpTo(direction) {
|
_jumpTo(direction) {
|
||||||
if ($(".container.posts").length) {
|
if ($('.container.posts').length) {
|
||||||
this.container.lookup("controller:topic").send(direction);
|
this.container.lookup('controller:topic').send(direction);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -165,108 +171,78 @@ export default {
|
||||||
|
|
||||||
showPageSearch(event) {
|
showPageSearch(event) {
|
||||||
Ember.run(() => {
|
Ember.run(() => {
|
||||||
this.appEvents.trigger("header:keyboard-trigger", {
|
this.appEvents.trigger('header:keyboard-trigger', {type: 'page-search', event});
|
||||||
type: "page-search",
|
|
||||||
event
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
printTopic(event) {
|
printTopic(event) {
|
||||||
Ember.run(() => {
|
Ember.run(() => {
|
||||||
if ($(".container.posts").length) {
|
if ($('.container.posts').length) {
|
||||||
event.preventDefault(); // We need to stop printing the current page in Firefox
|
event.preventDefault(); // We need to stop printing the current page in Firefox
|
||||||
this.container.lookup("controller:topic").print();
|
this.container.lookup('controller:topic').print();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
createTopic() {
|
createTopic() {
|
||||||
if (this.currentUser && this.currentUser.can_create_topic) {
|
if (this.currentUser && this.currentUser.can_create_topic) {
|
||||||
this.container.lookup("controller:composer").open({
|
this.container.lookup('controller:composer').open({action: Composer.CREATE_TOPIC, draftKey: Composer.CREATE_TOPIC});
|
||||||
action: Composer.CREATE_TOPIC,
|
|
||||||
draftKey: Composer.CREATE_TOPIC
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
focusComposer() {
|
focusComposer() {
|
||||||
const composer = this.container.lookup("controller:composer");
|
const composer = this.container.lookup('controller:composer');
|
||||||
if (composer.get("model.viewOpen")) {
|
if (composer.get('model.viewOpen')) {
|
||||||
setTimeout(() => $("textarea.d-editor-input").focus(), 0);
|
setTimeout(() => $('textarea.d-editor-input').focus(), 0);
|
||||||
} else {
|
} else {
|
||||||
composer.send("openIfDraft");
|
composer.send('openIfDraft');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
pinUnpinTopic() {
|
pinUnpinTopic() {
|
||||||
this.container.lookup("controller:topic").togglePinnedState();
|
this.container.lookup('controller:topic').togglePinnedState();
|
||||||
},
|
},
|
||||||
|
|
||||||
goToPost() {
|
goToPost() {
|
||||||
this.appEvents.trigger("topic:keyboard-trigger", { type: "jump" });
|
this.appEvents.trigger('topic:keyboard-trigger', { type: 'jump' });
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleSearch(event) {
|
toggleSearch(event) {
|
||||||
this.appEvents.trigger("header:keyboard-trigger", {
|
this.appEvents.trigger('header:keyboard-trigger', {type: 'search', event});
|
||||||
type: "search",
|
|
||||||
event
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleHamburgerMenu(event) {
|
toggleHamburgerMenu(event) {
|
||||||
this.appEvents.trigger("header:keyboard-trigger", {
|
this.appEvents.trigger('header:keyboard-trigger', {type: 'hamburger', event});
|
||||||
type: "hamburger",
|
|
||||||
event
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
showCurrentUser(event) {
|
showCurrentUser(event) {
|
||||||
this.appEvents.trigger("header:keyboard-trigger", { type: "user", event });
|
this.appEvents.trigger('header:keyboard-trigger', {type: 'user', event});
|
||||||
},
|
},
|
||||||
|
|
||||||
showHelpModal() {
|
showHelpModal() {
|
||||||
this.container
|
this.container.lookup('controller:application').send('showKeyboardShortcutsHelp');
|
||||||
.lookup("controller:application")
|
|
||||||
.send("showKeyboardShortcutsHelp");
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setTrackingToMuted(event) {
|
setTrackingToMuted(event) {
|
||||||
this.appEvents.trigger("topic-notifications-button:changed", {
|
this.appEvents.trigger('topic-notifications-button:changed', {type: 'notification', id: 0, event});
|
||||||
type: "notification",
|
|
||||||
id: 0,
|
|
||||||
event
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setTrackingToRegular(event) {
|
setTrackingToRegular(event) {
|
||||||
this.appEvents.trigger("topic-notifications-button:changed", {
|
this.appEvents.trigger('topic-notifications-button:changed', {type: 'notification', id: 1, event});
|
||||||
type: "notification",
|
|
||||||
id: 1,
|
|
||||||
event
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setTrackingToTracking(event) {
|
setTrackingToTracking(event) {
|
||||||
this.appEvents.trigger("topic-notifications-button:changed", {
|
this.appEvents.trigger('topic-notifications-button:changed', {type: 'notification', id: 2, event});
|
||||||
type: "notification",
|
|
||||||
id: 2,
|
|
||||||
event
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setTrackingToWatching(event) {
|
setTrackingToWatching(event) {
|
||||||
this.appEvents.trigger("topic-notifications-button:changed", {
|
this.appEvents.trigger('topic-notifications-button:changed', {type: 'notification', id: 3, event});
|
||||||
type: "notification",
|
|
||||||
id: 3,
|
|
||||||
event
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
sendToTopicListItemView(action) {
|
sendToTopicListItemView(action) {
|
||||||
const elem = $("tr.selected.topic-list-item.ember-view")[0];
|
const elem = $('tr.selected.topic-list-item.ember-view')[0];
|
||||||
if (elem) {
|
if (elem) {
|
||||||
const registry = this.container.lookup("-view-registry:main");
|
const registry = this.container.lookup('-view-registry:main');
|
||||||
if (registry) {
|
if (registry) {
|
||||||
const view = registry[elem.id];
|
const view = registry[elem.id];
|
||||||
view.send(action);
|
view.send(action);
|
||||||
|
@ -275,9 +251,9 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
currentTopic() {
|
currentTopic() {
|
||||||
const topicController = this.container.lookup("controller:topic");
|
const topicController = this.container.lookup('controller:topic');
|
||||||
if (topicController) {
|
if (topicController) {
|
||||||
const topic = topicController.get("model");
|
const topic = topicController.get('model');
|
||||||
if (topic) {
|
if (topic) {
|
||||||
return topic;
|
return topic;
|
||||||
}
|
}
|
||||||
|
@ -287,26 +263,21 @@ export default {
|
||||||
sendToSelectedPost(action) {
|
sendToSelectedPost(action) {
|
||||||
const container = this.container;
|
const container = this.container;
|
||||||
// TODO: We should keep track of the post without a CSS class
|
// TODO: We should keep track of the post without a CSS class
|
||||||
const selectedPostId = parseInt(
|
const selectedPostId = parseInt($('.topic-post.selected article.boxed').data('post-id'), 10);
|
||||||
$(".topic-post.selected article.boxed").data("post-id"),
|
|
||||||
10
|
|
||||||
);
|
|
||||||
if (selectedPostId) {
|
if (selectedPostId) {
|
||||||
const topicController = container.lookup("controller:topic");
|
const topicController = container.lookup('controller:topic');
|
||||||
const post = topicController
|
const post = topicController.get('model.postStream.posts').findBy('id', selectedPostId);
|
||||||
.get("model.postStream.posts")
|
|
||||||
.findBy("id", selectedPostId);
|
|
||||||
if (post) {
|
if (post) {
|
||||||
// TODO: Use ember closure actions
|
// TODO: Use ember closure actions
|
||||||
let actionMethod = topicController._actions[action];
|
let actionMethod = topicController._actions[action];
|
||||||
if (!actionMethod) {
|
if (!actionMethod) {
|
||||||
const topicRoute = container.lookup("route:topic");
|
const topicRoute = container.lookup('route:topic');
|
||||||
actionMethod = topicRoute._actions[action];
|
actionMethod = topicRoute._actions[action];
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = actionMethod.call(topicController, post);
|
const result = actionMethod.call(topicController, post);
|
||||||
if (result && result.then) {
|
if (result && result.then) {
|
||||||
this.appEvents.trigger("post-stream:refresh", { id: selectedPostId });
|
this.appEvents.trigger('post-stream:refresh', { id: selectedPostId });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -317,13 +288,11 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
_bindToPath(path, key) {
|
_bindToPath(path, key) {
|
||||||
this.keyTrapper.bind(key, () =>
|
this.keyTrapper.bind(key, () => DiscourseURL.routeTo(Discourse.BaseUri + path));
|
||||||
DiscourseURL.routeTo(Discourse.BaseUri + path)
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_bindToClick(selector, binding) {
|
_bindToClick(selector, binding) {
|
||||||
binding = binding.split(",");
|
binding = binding.split(',');
|
||||||
this.keyTrapper.bind(binding, function(e) {
|
this.keyTrapper.bind(binding, function(e) {
|
||||||
const $sel = $(selector);
|
const $sel = $(selector);
|
||||||
|
|
||||||
|
@ -343,7 +312,7 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
_bindToFunction(func, binding) {
|
_bindToFunction(func, binding) {
|
||||||
if (typeof this[func] === "function") {
|
if (typeof this[func] === 'function') {
|
||||||
this.keyTrapper.bind(binding, _.bind(this[func], this));
|
this.keyTrapper.bind(binding, _.bind(this[func], this));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -351,38 +320,35 @@ export default {
|
||||||
_moveSelection(direction) {
|
_moveSelection(direction) {
|
||||||
const $articles = this._findArticles();
|
const $articles = this._findArticles();
|
||||||
|
|
||||||
if (typeof $articles === "undefined") return;
|
if (typeof $articles === 'undefined') return;
|
||||||
|
|
||||||
const $selected =
|
const $selected = ($articles.filter('.selected').length !== 0)
|
||||||
$articles.filter(".selected").length !== 0
|
? $articles.filter('.selected')
|
||||||
? $articles.filter(".selected")
|
: $articles.filter('[data-islastviewedtopic=true]');
|
||||||
: $articles.filter("[data-islastviewedtopic=true]");
|
|
||||||
|
|
||||||
let index = $articles.index($selected);
|
let index = $articles.index($selected);
|
||||||
|
|
||||||
if ($selected.length !== 0) {
|
if ($selected.length !== 0) {
|
||||||
if (direction === -1 && index === 0) return;
|
if (direction === -1 && index === 0) return;
|
||||||
if (direction === 1 && index === $articles.length - 1) return;
|
if (direction === 1 && index === $articles.length - 1) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// when nothing is selected
|
// when nothing is selected
|
||||||
if ($selected.length === 0) {
|
if ($selected.length === 0) {
|
||||||
// select the first post with its top visible
|
// select the first post with its top visible
|
||||||
const offset = minimumOffset();
|
const offset = minimumOffset();
|
||||||
index = $articles
|
index = $articles.toArray().findIndex(article => article.getBoundingClientRect().top > offset);
|
||||||
.toArray()
|
|
||||||
.findIndex(article => article.getBoundingClientRect().top > offset);
|
|
||||||
direction = 0;
|
direction = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const $article = $articles.eq(index + direction);
|
const $article = $articles.eq(index + direction);
|
||||||
|
|
||||||
if ($article.length > 0) {
|
if ($article.length > 0) {
|
||||||
$articles.removeClass("selected");
|
$articles.removeClass('selected');
|
||||||
$article.addClass("selected");
|
$article.addClass('selected');
|
||||||
|
|
||||||
if ($article.is(".topic-post")) {
|
if ($article.is('.topic-post')) {
|
||||||
$("a.tabLoc", $article).focus();
|
$('a.tabLoc', $article).focus();
|
||||||
this._scrollToPost($article);
|
this._scrollToPost($article);
|
||||||
} else {
|
} else {
|
||||||
this._scrollList($article, direction);
|
this._scrollList($article, direction);
|
||||||
|
@ -402,72 +368,143 @@ export default {
|
||||||
// Try to keep the article on screen
|
// Try to keep the article on screen
|
||||||
const pos = $article.offset();
|
const pos = $article.offset();
|
||||||
const height = $article.height();
|
const height = $article.height();
|
||||||
const headerHeight = $("header.d-header").height();
|
const headerHeight = $('header.d-header').height();
|
||||||
const scrollTop = $(window).scrollTop();
|
const scrollTop = $(window).scrollTop();
|
||||||
const windowHeight = $(window).height();
|
const windowHeight = $(window).height();
|
||||||
|
|
||||||
// skip if completely on screen
|
// skip if completely on screen
|
||||||
if (
|
if ((pos.top - headerHeight) > scrollTop && (pos.top + height) < (scrollTop + windowHeight)) {
|
||||||
pos.top - headerHeight > scrollTop &&
|
|
||||||
pos.top + height < scrollTop + windowHeight
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let scrollPos = pos.top + height / 2 - windowHeight * 0.5;
|
let scrollPos = (pos.top + (height/2)) - (windowHeight * 0.5);
|
||||||
if (height > windowHeight - headerHeight) {
|
if (height > (windowHeight - headerHeight)) { scrollPos = (pos.top - headerHeight); }
|
||||||
scrollPos = pos.top - headerHeight;
|
if (scrollPos < 0) { scrollPos = 0; }
|
||||||
}
|
|
||||||
if (scrollPos < 0) {
|
|
||||||
scrollPos = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._scrollAnimation) {
|
if (this._scrollAnimation) {
|
||||||
this._scrollAnimation.stop();
|
this._scrollAnimation.stop();
|
||||||
}
|
}
|
||||||
this._scrollAnimation = $("html, body").animate(
|
this._scrollAnimation = $("html, body").animate({ scrollTop: scrollPos + "px"}, 100);
|
||||||
{ scrollTop: scrollPos + "px" },
|
},
|
||||||
100
|
|
||||||
);
|
setCurrentFocus(newFocus) {
|
||||||
|
CURRENT_FOCUS = newFocus;
|
||||||
|
},
|
||||||
|
|
||||||
|
shouldHijackTab() {
|
||||||
|
return $FOCUSED_PARENT || [
|
||||||
|
this.parentCategoriesSelection(),
|
||||||
|
this.topicsSelection()
|
||||||
|
].some(list => list.is(".selected"));
|
||||||
|
},
|
||||||
|
|
||||||
|
switchFocusCategoriesPage(e) {
|
||||||
|
if (!this.shouldHijackTab()) { return; }
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const prevFocusedParent = $FOCUSED_PARENT;
|
||||||
|
const prevFocus = CURRENT_FOCUS;
|
||||||
|
const nextFocus = this.findNextFocus(CURRENT_FOCUS);
|
||||||
|
|
||||||
|
if (CURRENT_FOCUS === "sub-cats") {
|
||||||
|
$FOCUSED_PARENT = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setCurrentFocus(nextFocus);
|
||||||
|
|
||||||
|
this.codesMapToItemLists(CURRENT_FOCUS).first().addClass("selected");
|
||||||
|
this.codesMapToItemLists(prevFocus, prevFocusedParent).removeClass("selected");
|
||||||
|
},
|
||||||
|
|
||||||
|
codesMapToItemLists(code, prevParent) {
|
||||||
|
const map = {
|
||||||
|
"parent-cats": this.parentCategoriesSelection(),
|
||||||
|
"sub-cats": this.subcategoriesSelection(prevParent),
|
||||||
|
"latest-topics": this.topicsSelection(),
|
||||||
|
"none": $()
|
||||||
|
};
|
||||||
|
return map[code];
|
||||||
|
},
|
||||||
|
|
||||||
|
findNextFocus(current) {
|
||||||
|
let index = FOCUS_ORDER.indexOf(current) + 1;
|
||||||
|
|
||||||
|
if (current === "parent-cats") {
|
||||||
|
const $selectedCat = this.parentCategoriesSelection().filter(".selected");
|
||||||
|
if (this.subcategoriesSelection($selectedCat).length === 0) {
|
||||||
|
++index;
|
||||||
|
} else {
|
||||||
|
$FOCUSED_PARENT = $selectedCat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.topicsSelection().length === 0 && FOCUS_ORDER[index] === "latest-topics") { ++index; }
|
||||||
|
|
||||||
|
return FOCUS_ORDER[index] || _.first(FOCUS_ORDER);
|
||||||
|
},
|
||||||
|
|
||||||
|
parentCategoriesSelection() {
|
||||||
|
return $(".category-list .category-list-item");
|
||||||
|
},
|
||||||
|
|
||||||
|
subcategoriesSelection($parent) {
|
||||||
|
$parent = $parent || $FOCUSED_PARENT;
|
||||||
|
return $parent && $parent.find(".subcategory-list .subcategory-list-item");
|
||||||
|
},
|
||||||
|
|
||||||
|
topicsSelection() {
|
||||||
|
const setting = this.container.lookup('site-settings:main').desktop_category_page_style;
|
||||||
|
switch (setting) {
|
||||||
|
case "categories_only":
|
||||||
|
return $();
|
||||||
|
case "categories_with_featured_topics":
|
||||||
|
return $(".latest .featured-topic");
|
||||||
|
default:
|
||||||
|
return $(".categories-topics-list .categories-topics-list-item"); // latest or top topics
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
categoriesPageList() {
|
||||||
|
switch (CURRENT_FOCUS) {
|
||||||
|
case "sub-cats":
|
||||||
|
return this.subcategoriesSelection();
|
||||||
|
case "latest-topics":
|
||||||
|
return this.topicsSelection();
|
||||||
|
default:
|
||||||
|
this.setCurrentFocus("parent-cats");
|
||||||
|
return this.parentCategoriesSelection();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_findArticles() {
|
_findArticles() {
|
||||||
const $topicList = $(".topic-list");
|
const $topicList = $(".topic-list");
|
||||||
const $postsWrapper = $(".posts-wrapper");
|
const $postsWrapper = $(".posts-wrapper");
|
||||||
|
const $categoriesPageList = this.categoriesPageList();
|
||||||
|
|
||||||
if ($postsWrapper.length > 0) {
|
if ($postsWrapper.length > 0) {
|
||||||
return $(".posts-wrapper .topic-post, .topic-list tbody tr");
|
return $(".posts-wrapper .topic-post, .topic-list tbody tr");
|
||||||
} else if ($topicList.length > 0) {
|
} else if ($topicList.length > 0) {
|
||||||
return $topicList.find(".topic-list-item");
|
return $topicList.find(".topic-list-item");
|
||||||
|
} else if ($categoriesPageList.length > 0) {
|
||||||
|
return $categoriesPageList;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_changeSection(direction) {
|
_changeSection(direction) {
|
||||||
const $sections = $(".nav.nav-pills li"),
|
const $sections = $('.nav.nav-pills li'),
|
||||||
active = $(".nav.nav-pills li.active"),
|
active = $('.nav.nav-pills li.active'),
|
||||||
index = $sections.index(active) + direction;
|
index = $sections.index(active) + direction;
|
||||||
|
|
||||||
if (index >= 0 && index < $sections.length) {
|
if (index >= 0 && index < $sections.length) {
|
||||||
$sections
|
$sections.eq(index).find('a').click();
|
||||||
.eq(index)
|
|
||||||
.find("a")
|
|
||||||
.click();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_stopCallback() {
|
_stopCallback() {
|
||||||
const oldStopCallback = this.keyTrapper.prototype.stopCallback;
|
const oldStopCallback = this.keyTrapper.prototype.stopCallback;
|
||||||
|
|
||||||
this.keyTrapper.prototype.stopCallback = function(
|
this.keyTrapper.prototype.stopCallback = function(e, element, combo, sequence) {
|
||||||
e,
|
if ((combo === 'ctrl+f' || combo === 'command+f') && element.id === 'search-term') {
|
||||||
element,
|
|
||||||
combo,
|
|
||||||
sequence
|
|
||||||
) {
|
|
||||||
if (
|
|
||||||
(combo === "ctrl+f" || combo === "command+f") &&
|
|
||||||
element.id === "search-term"
|
|
||||||
) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return oldStopCallback.call(this, e, element, combo, sequence);
|
return oldStopCallback.call(this, e, element, combo, sequence);
|
||||||
|
@ -475,6 +512,6 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
_replyToPost() {
|
_replyToPost() {
|
||||||
this.container.lookup("controller:topic").send("replyToPost");
|
this.container.lookup('controller:topic').send('replyToPost');
|
||||||
}
|
}
|
||||||
};
|
};
|
|
@ -3,5 +3,5 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class='column'>
|
<div class='column'>
|
||||||
{{categories-topic-list topics=topics filter="latest" class="latest-topic-list"}}
|
{{categories-topic-list topics=topics filter="latest" class="latest-topic-list categories-topics-list"}}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
</thead>
|
</thead>
|
||||||
<tbody aria-labelledby="categories-only-category">
|
<tbody aria-labelledby="categories-only-category">
|
||||||
{{#each categories as |c|}}
|
{{#each categories as |c|}}
|
||||||
<tr data-category-id={{c.id}} class="{{if c.description_excerpt 'has-description' 'no-description'}} {{if c.uploaded_logo.url 'has-logo' 'no-logo'}}">
|
<tr data-category-id={{c.id}} class="{{if c.description_excerpt 'has-description' 'no-description'}} {{if c.uploaded_logo.url 'has-logo' 'no-logo'}} category-list-item">
|
||||||
<td class="category" style={{border-color c.color}}>
|
<td class="category" style={{border-color c.color}}>
|
||||||
<div>
|
<div>
|
||||||
{{category-title-link category=c}}
|
{{category-title-link category=c}}
|
||||||
|
@ -21,9 +21,9 @@
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
</div>
|
</div>
|
||||||
{{#if c.subcategories}}
|
{{#if c.subcategories}}
|
||||||
<div class='subcategories'>
|
<div class='subcategories subcategory-list'>
|
||||||
{{#each c.subcategories as |s|}}
|
{{#each c.subcategories as |s|}}
|
||||||
<span class='subcategory'>
|
<span class='subcategory subcategory-list-item'>
|
||||||
{{category-title-before category=s}}
|
{{category-title-before category=s}}
|
||||||
{{category-link s hideParent="true"}}
|
{{category-link s hideParent="true"}}
|
||||||
{{category-unread category=s}}
|
{{category-unread category=s}}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
{{#if topics}}
|
{{#if topics}}
|
||||||
{{#each topics as |t|}}
|
{{#each topics as |t|}}
|
||||||
{{latest-topic-list-item topic=t}}
|
{{latest-topic-list-item topic=t class="categories-topics-list-item"}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
<div class="more-topics">
|
<div class="more-topics">
|
||||||
<a href="/{{filter}}" class="btn pull-right">{{i18n "more"}}</a>
|
<a href="/{{filter}}" class="btn pull-right">{{i18n "more"}}</a>
|
||||||
|
|
|
@ -5,10 +5,22 @@
|
||||||
|
|
||||||
.topic-list tr.selected td:first-child,
|
.topic-list tr.selected td:first-child,
|
||||||
.topic-list-item.selected td:first-child,
|
.topic-list-item.selected td:first-child,
|
||||||
|
.latest-topic-list-item.selected,
|
||||||
|
.subcategory-list-item.selected,
|
||||||
|
.featured-topic.selected,
|
||||||
.topic-post.selected {
|
.topic-post.selected {
|
||||||
box-shadow: -3px 0 0 $danger;
|
box-shadow: -3px 0 0 $danger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.subcategory-list-item,
|
||||||
|
.latest .featured-topic {
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-list-item.selected {
|
||||||
|
box-shadow: 3px 0 0 $danger;
|
||||||
|
}
|
||||||
|
|
||||||
.topic-list-item.selected {
|
.topic-list-item.selected {
|
||||||
background-color: inherit;
|
background-color: inherit;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue