* We now use a new custom view, {{cloaked-collection}} to display posts in a topic.
* Posts are removed and inserted (cloaked/uncloaked) into the DOM dynamically based on whether they
are visible in the current browser viewport.
* There's been a lot of refactoring to ensure the relationship between the post views and the topic
controller is sane.
* Lots of fixes involving jumping to a post, including a new LockOn component to that tries to stay
focused on an element even if stuff is loading before it in the DOM that would normally push it
down.
222 lines
6.2 KiB
JavaScript
222 lines
6.2 KiB
JavaScript
/**
|
|
URL related functions.
|
|
|
|
@class URL
|
|
@namespace Discourse
|
|
@module Discourse
|
|
**/
|
|
Discourse.URL = Em.Object.createWithMixins({
|
|
|
|
// Used for matching a topic
|
|
TOPIC_REGEXP: /\/t\/([^\/]+)\/(\d+)\/?(\d+)?/,
|
|
|
|
// Used for matching a /more URL
|
|
MORE_REGEXP: /\/more$/,
|
|
|
|
/**
|
|
Browser aware replaceState. Will only be invoked if the browser supports it.
|
|
|
|
@method replaceState
|
|
@param {String} path The path we are replacing our history state with.
|
|
**/
|
|
replaceState: function(path) {
|
|
|
|
if (window.history &&
|
|
window.history.pushState &&
|
|
window.history.replaceState &&
|
|
!navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]|WebApps\/.+CFNetwork)/) &&
|
|
(window.location.pathname !== path)) {
|
|
|
|
// Always use replaceState in the next runloop to prevent weird routes changing
|
|
// while URLs are loading. For example, while a topic loads it sets `currentPost`
|
|
// which triggers a replaceState even though the topic hasn't fully loaded yet!
|
|
Em.run.next(function() {
|
|
var location = Discourse.URL.get('router.location');
|
|
if (location && location.replaceURL) { location.replaceURL(path); }
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
Our custom routeTo method is used to intelligently overwrite default routing
|
|
behavior.
|
|
|
|
It contains the logic necessary to route within a topic using replaceState to
|
|
keep the history intact.
|
|
|
|
@method routeTo
|
|
@param {String} path The path we are routing to.
|
|
**/
|
|
routeTo: function(path) {
|
|
|
|
var oldPath = window.location.pathname;
|
|
path = path.replace(/https?\:\/\/[^\/]+/, '');
|
|
|
|
// If the URL is absolute, remove rootURL
|
|
if (path.match(/^\//)) {
|
|
var rootURL = (Discourse.BaseUri === undefined ? "/" : Discourse.BaseUri);
|
|
rootURL = rootURL.replace(/\/$/, '');
|
|
path = path.replace(rootURL, '');
|
|
}
|
|
|
|
// TODO: Extract into rules we can inject into the URL handler
|
|
if (this.navigatedToHome(oldPath, path)) { return; }
|
|
if (this.navigatedToListMore(oldPath, path)) { return; }
|
|
if (this.navigatedToPost(oldPath, path)) { return; }
|
|
|
|
if (path.match(/^\/?users\/[^\/]+$/)) {
|
|
path += "/activity";
|
|
}
|
|
// Be wary of looking up the router. In this case, we have links in our
|
|
// HTML, say form compiled markdown posts, that need to be routed.
|
|
var router = this.get('router');
|
|
router.router.updateURL(path);
|
|
return router.handleURL(path);
|
|
},
|
|
|
|
/**
|
|
Replaces the query parameters in the URL. Use no parameters to clear them.
|
|
|
|
@method replaceQueryParams
|
|
**/
|
|
queryParams: Em.computed.alias('router.location.queryParams'),
|
|
|
|
/**
|
|
Redirect to a URL.
|
|
This has been extracted so it can be tested.
|
|
|
|
@method redirectTo
|
|
**/
|
|
redirectTo: function(url) {
|
|
window.location = Discourse.getURL(url);
|
|
},
|
|
|
|
/**
|
|
@private
|
|
|
|
If we're viewing more topics, scroll to where we were previously.
|
|
|
|
@method navigatedToListMore
|
|
@param {String} oldPath the previous path we were on
|
|
@param {String} path the path we're navigating to
|
|
**/
|
|
navigatedToListMore: function(oldPath, path) {
|
|
// If we transition from a /more path, scroll to the top
|
|
if (this.MORE_REGEXP.exec(oldPath) && (oldPath.indexOf(path) === 0)) {
|
|
window.scrollTo(0, 0);
|
|
}
|
|
return false;
|
|
},
|
|
|
|
/**
|
|
@private
|
|
|
|
If the URL is in the topic form, /t/something/:topic_id/:post_number
|
|
then we want to apply some special logic. If the post_number changes within the
|
|
same topic, use replaceState and instruct our controller to load more posts.
|
|
|
|
@method navigatedToPost
|
|
@param {String} oldPath the previous path we were on
|
|
@param {String} path the path we're navigating to
|
|
**/
|
|
navigatedToPost: function(oldPath, path) {
|
|
|
|
var newMatches = this.TOPIC_REGEXP.exec(path),
|
|
newTopicId = newMatches ? newMatches[2] : null;
|
|
|
|
if (newTopicId) {
|
|
var oldMatches = this.TOPIC_REGEXP.exec(oldPath),
|
|
oldTopicId = oldMatches ? oldMatches[2] : null;
|
|
|
|
// If the topic_id is the same
|
|
if (oldTopicId === newTopicId) {
|
|
Discourse.URL.replaceState(path);
|
|
|
|
var topicController = Discourse.__container__.lookup('controller:topic'),
|
|
opts = {},
|
|
postStream = topicController.get('postStream');
|
|
|
|
if (newMatches[3]) opts.nearPost = newMatches[3];
|
|
var closest = opts.nearPost || 1;
|
|
|
|
postStream.refresh(opts).then(function() {
|
|
topicController.setProperties({
|
|
currentPost: closest,
|
|
progressPosition: closest,
|
|
highlightOnInsert: closest,
|
|
enteredAt: new Date().getTime().toString()
|
|
});
|
|
}).then(function() {
|
|
Discourse.TopicView.jumpToPost(closest);
|
|
});
|
|
|
|
// Abort routing, we have replaced our state.
|
|
return true;
|
|
}
|
|
this.set('queryParams', null);
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
/**
|
|
@private
|
|
|
|
Handle the custom case of routing to the root path from itself.
|
|
|
|
@param {String} oldPath the previous path we were on
|
|
@param {String} path the path we're navigating to
|
|
**/
|
|
navigatedToHome: function(oldPath, path) {
|
|
|
|
var defaultFilter = "/" + Discourse.ListController.filters[0];
|
|
|
|
if (path === "/" && (oldPath === "/" || oldPath === defaultFilter)) {
|
|
// Refresh our list
|
|
this.controllerFor('list').refresh();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
/**
|
|
@private
|
|
|
|
Get the origin of the current location.
|
|
This has been extracted so it can be tested.
|
|
|
|
@method origin
|
|
**/
|
|
origin: function() {
|
|
return window.location.origin;
|
|
},
|
|
|
|
/**
|
|
@private
|
|
|
|
Get a handle on the application's router. Note that currently it uses `__container__` which is not
|
|
advised but there is no other way to access the router.
|
|
|
|
@property router
|
|
**/
|
|
router: function() {
|
|
return Discourse.__container__.lookup('router:main');
|
|
}.property(),
|
|
|
|
/**
|
|
@private
|
|
|
|
Get a controller. Note that currently it uses `__container__` which is not
|
|
advised but there is no other way to access the router.
|
|
|
|
@method controllerFor
|
|
@param {String} name the name of the controller
|
|
**/
|
|
controllerFor: function(name) {
|
|
return Discourse.__container__.lookup('controller:' + name);
|
|
}
|
|
|
|
|
|
});
|