2014-07-01 15:55:05 -04:00
|
|
|
/*global LockOn:true*/
|
2014-10-16 12:15:31 -04:00
|
|
|
var jumpScheduled = false,
|
|
|
|
rewrites = [];
|
2014-07-16 18:14:07 -04:00
|
|
|
|
2015-01-14 12:20:37 -05:00
|
|
|
Discourse.URL = Ember.Object.createWithMixins({
|
2013-02-26 14:54:43 -05:00
|
|
|
|
2013-02-26 15:17:52 -05:00
|
|
|
// Used for matching a topic
|
|
|
|
TOPIC_REGEXP: /\/t\/([^\/]+)\/(\d+)\/?(\d+)?/,
|
|
|
|
|
2014-07-16 18:14:07 -04:00
|
|
|
isJumpScheduled: function() {
|
|
|
|
return jumpScheduled;
|
|
|
|
},
|
|
|
|
|
2014-07-01 15:55:05 -04:00
|
|
|
/**
|
|
|
|
Jumps to a particular post in the stream
|
|
|
|
**/
|
2015-03-04 23:01:17 -05:00
|
|
|
jumpToPost: function(postNumber, opts) {
|
2014-07-01 15:55:05 -04:00
|
|
|
var holderId = '#post-cloak-' + postNumber;
|
|
|
|
|
2015-03-04 23:01:17 -05:00
|
|
|
var offset = function(){
|
|
|
|
|
|
|
|
var $header = $('header'),
|
|
|
|
$title = $('#topic-title'),
|
|
|
|
windowHeight = $(window).height() - $title.height(),
|
|
|
|
expectedOffset = $title.height() - $header.find('.contents').height() + (windowHeight / 5);
|
|
|
|
|
|
|
|
return $header.outerHeight(true) + ((expectedOffset < 0) ? 0 : expectedOffset);
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2014-07-01 15:55:05 -04:00
|
|
|
Em.run.schedule('afterRender', function() {
|
|
|
|
if (postNumber === 1) {
|
|
|
|
$(window).scrollTop(0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-03-04 23:01:17 -05:00
|
|
|
var lockon = new LockOn(holderId, {offsetCalculator: offset});
|
|
|
|
var holder = $(holderId);
|
|
|
|
|
|
|
|
if(holder.length > 0 && opts && opts.skipIfOnScreen){
|
2015-03-04 23:09:31 -05:00
|
|
|
|
|
|
|
// if we are on screen skip
|
2015-03-04 23:01:17 -05:00
|
|
|
var elementTop = lockon.elementTop(),
|
|
|
|
scrollTop = $(window).scrollTop(),
|
|
|
|
windowHeight = $(window).height()-offset(),
|
|
|
|
height = holder.height();
|
|
|
|
|
|
|
|
if (elementTop > scrollTop &&
|
|
|
|
(elementTop + height) < (scrollTop + windowHeight)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
lockon.lock();
|
2014-07-01 15:55:05 -04:00
|
|
|
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2013-02-26 14:54:43 -05:00
|
|
|
/**
|
|
|
|
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)) {
|
2013-02-26 17:25:56 -05:00
|
|
|
|
|
|
|
// 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() {
|
2013-06-20 17:20:08 -04:00
|
|
|
var location = Discourse.URL.get('router.location');
|
2014-04-15 15:33:08 -04:00
|
|
|
if (location && location.replaceURL) {
|
|
|
|
location.replaceURL(path);
|
|
|
|
}
|
2013-02-26 17:25:56 -05:00
|
|
|
});
|
2013-02-26 14:54:43 -05:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2014-07-24 12:52:43 -04:00
|
|
|
// Scroll to the same page, different anchor
|
|
|
|
scrollToId: function(id) {
|
|
|
|
if (Em.isEmpty(id)) { return; }
|
|
|
|
|
|
|
|
jumpScheduled = true;
|
|
|
|
Em.run.schedule('afterRender', function() {
|
|
|
|
var $elem = $(id);
|
2014-08-19 16:51:53 -04:00
|
|
|
if ($elem.length === 0) {
|
|
|
|
$elem = $("[name=" + id.replace('#', ''));
|
|
|
|
}
|
2014-07-24 12:52:43 -04:00
|
|
|
if ($elem.length > 0) {
|
|
|
|
$('html,body').scrollTop($elem.offset().top - $('header').height() - 15);
|
|
|
|
jumpScheduled = false;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2013-02-26 14:54:43 -05:00
|
|
|
/**
|
|
|
|
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) {
|
2014-01-14 00:59:08 -05:00
|
|
|
|
2014-05-04 14:30:17 -04:00
|
|
|
if (Em.isEmpty(path)) { return; }
|
|
|
|
|
2014-10-16 12:15:31 -04:00
|
|
|
if (Discourse.get('requiresRefresh')) {
|
2014-01-14 20:07:42 -05:00
|
|
|
document.location.href = path;
|
|
|
|
return;
|
2014-01-14 00:59:08 -05:00
|
|
|
}
|
|
|
|
|
2014-05-12 16:30:00 -04:00
|
|
|
// Protocol relative URLs
|
|
|
|
if (path.indexOf('//') === 0) {
|
|
|
|
document.location = path;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-07-16 18:14:07 -04:00
|
|
|
// Scroll to the same page, different anchor
|
2014-05-06 15:09:19 -04:00
|
|
|
if (path.indexOf('#') === 0) {
|
2014-07-24 12:52:43 -04:00
|
|
|
this.scrollToId(path);
|
2014-05-06 15:09:19 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-02-26 15:17:52 -05:00
|
|
|
var oldPath = window.location.pathname;
|
2014-01-29 05:31:36 -05:00
|
|
|
path = path.replace(/(https?\:)?\/\/[^\/]+/, '');
|
2013-07-03 14:06:34 -04:00
|
|
|
|
2014-01-29 05:31:36 -05:00
|
|
|
// handle prefixes
|
2013-03-14 08:01:52 -04:00
|
|
|
if (path.match(/^\//)) {
|
2013-04-03 18:26:47 -04:00
|
|
|
var rootURL = (Discourse.BaseUri === undefined ? "/" : Discourse.BaseUri);
|
|
|
|
rootURL = rootURL.replace(/\/$/, '');
|
2013-03-14 08:01:52 -04:00
|
|
|
path = path.replace(rootURL, '');
|
|
|
|
}
|
2013-02-26 14:54:43 -05:00
|
|
|
|
2014-04-21 11:52:11 -04:00
|
|
|
// Rewrite /my/* urls
|
|
|
|
if (path.indexOf('/my/') === 0) {
|
|
|
|
var currentUser = Discourse.User.current();
|
|
|
|
if (currentUser) {
|
|
|
|
path = path.replace('/my/', '/users/' + currentUser.get('username_lower') + "/");
|
|
|
|
} else {
|
|
|
|
document.location.href = "/404";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-16 12:15:31 -04:00
|
|
|
rewrites.forEach(function(rw) {
|
|
|
|
path = path.replace(rw.regexp, rw.replacement);
|
|
|
|
});
|
2014-10-14 15:10:51 -04:00
|
|
|
|
2014-06-05 02:59:18 -04:00
|
|
|
if (this.navigatedToPost(oldPath, path)) { return; }
|
|
|
|
// Schedule a DOM cleanup event
|
|
|
|
Em.run.scheduleOnce('afterRender', Discourse.Route, 'cleanDOM');
|
|
|
|
|
2013-07-04 17:31:06 -04:00
|
|
|
// TODO: Extract into rules we can inject into the URL handler
|
|
|
|
if (this.navigatedToHome(oldPath, path)) { return; }
|
|
|
|
|
2014-07-24 14:59:53 -04:00
|
|
|
if (oldPath === path) {
|
|
|
|
// If navigating to the same path send an app event. Views can watch it
|
|
|
|
// and tell their controllers to refresh
|
|
|
|
this.appEvents.trigger('url:refresh');
|
2013-07-24 17:15:21 -04:00
|
|
|
}
|
2014-01-29 05:31:36 -05:00
|
|
|
|
|
|
|
return this.handleURL(path);
|
2013-07-04 17:31:06 -04:00
|
|
|
},
|
|
|
|
|
2014-10-16 12:15:31 -04:00
|
|
|
rewrite: function(regexp, replacement) {
|
|
|
|
rewrites.push({ regexp: regexp, replacement: replacement });
|
|
|
|
},
|
2013-07-04 17:31:06 -04:00
|
|
|
|
|
|
|
redirectTo: function(url) {
|
|
|
|
window.location = Discourse.getURL(url);
|
|
|
|
},
|
|
|
|
|
2014-01-23 11:08:52 -05:00
|
|
|
/**
|
2014-01-14 15:20:46 -05:00
|
|
|
* Determines whether a URL is internal or not
|
|
|
|
*
|
|
|
|
* @method isInternal
|
|
|
|
* @param {String} url
|
|
|
|
**/
|
|
|
|
isInternal: function(url) {
|
|
|
|
if (url && url.length) {
|
2014-05-20 03:54:22 -04:00
|
|
|
if (url.indexOf('#') === 0) { return true; }
|
2014-01-14 15:20:46 -05:00
|
|
|
if (url.indexOf('/') === 0) { return true; }
|
|
|
|
if (url.indexOf(this.origin()) === 0) { return true; }
|
2014-01-14 15:38:12 -05:00
|
|
|
if (url.replace(/^http/, 'https').indexOf(this.origin()) === 0) { return true; }
|
2014-01-14 15:20:46 -05:00
|
|
|
if (url.replace(/^https/, 'http').indexOf(this.origin()) === 0) { return true; }
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
2013-07-04 17:31:06 -04:00
|
|
|
/**
|
|
|
|
@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) {
|
2013-07-03 14:06:34 -04:00
|
|
|
var newMatches = this.TOPIC_REGEXP.exec(path),
|
|
|
|
newTopicId = newMatches ? newMatches[2] : null;
|
|
|
|
|
2013-02-26 14:54:43 -05:00
|
|
|
if (newTopicId) {
|
2013-07-03 14:06:34 -04:00
|
|
|
var oldMatches = this.TOPIC_REGEXP.exec(oldPath),
|
|
|
|
oldTopicId = oldMatches ? oldMatches[2] : null;
|
2013-02-26 15:17:52 -05:00
|
|
|
|
|
|
|
// If the topic_id is the same
|
|
|
|
if (oldTopicId === newTopicId) {
|
2013-02-26 14:54:43 -05:00
|
|
|
Discourse.URL.replaceState(path);
|
2013-06-20 17:20:08 -04:00
|
|
|
|
2014-06-12 12:52:15 -04:00
|
|
|
var container = Discourse.__container__,
|
|
|
|
topicController = container.lookup('controller:topic'),
|
2013-11-20 16:33:36 -05:00
|
|
|
opts = {},
|
|
|
|
postStream = topicController.get('postStream');
|
2013-07-03 14:06:34 -04:00
|
|
|
|
|
|
|
if (newMatches[3]) opts.nearPost = newMatches[3];
|
2014-01-31 17:55:40 -05:00
|
|
|
if (path.match(/last$/)) { opts.nearPost = topicController.get('highest_post_number'); }
|
2013-11-20 16:33:36 -05:00
|
|
|
var closest = opts.nearPost || 1;
|
|
|
|
|
2015-01-14 12:20:37 -05:00
|
|
|
var self = this;
|
2013-06-20 17:20:08 -04:00
|
|
|
postStream.refresh(opts).then(function() {
|
|
|
|
topicController.setProperties({
|
2013-11-20 16:33:36 -05:00
|
|
|
currentPost: closest,
|
|
|
|
enteredAt: new Date().getTime().toString()
|
2013-06-20 17:20:08 -04:00
|
|
|
});
|
2014-08-14 20:31:34 -04:00
|
|
|
var closestPost = postStream.closestPostForPostNumber(closest),
|
2015-01-14 12:09:40 -05:00
|
|
|
progress = postStream.progressIndexOfPost(closestPost),
|
|
|
|
progressController = container.lookup('controller:topic-progress');
|
|
|
|
|
|
|
|
progressController.set('progressPosition', progress);
|
2015-01-14 12:20:37 -05:00
|
|
|
self.appEvents.trigger('post:highlight', closest);
|
2013-11-20 16:33:36 -05:00
|
|
|
}).then(function() {
|
2015-03-04 23:01:17 -05:00
|
|
|
Discourse.URL.jumpToPost(closest, {skipIfOnScreen: true});
|
2013-06-20 17:20:08 -04:00
|
|
|
});
|
2013-02-26 15:17:52 -05:00
|
|
|
|
|
|
|
// Abort routing, we have replaced our state.
|
2013-07-04 17:31:06 -04:00
|
|
|
return true;
|
2013-02-26 14:54:43 -05:00
|
|
|
}
|
|
|
|
}
|
2013-02-26 15:17:52 -05:00
|
|
|
|
2013-07-04 17:31:06 -04:00
|
|
|
return false;
|
2013-04-22 14:21:29 -04:00
|
|
|
},
|
|
|
|
|
2013-06-20 17:20:08 -04:00
|
|
|
/**
|
2013-07-04 17:31:06 -04:00
|
|
|
@private
|
2013-06-20 17:20:08 -04:00
|
|
|
|
2013-07-04 17:31:06 -04:00
|
|
|
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
|
2013-06-20 17:20:08 -04:00
|
|
|
**/
|
2013-07-04 17:31:06 -04:00
|
|
|
navigatedToHome: function(oldPath, path) {
|
2014-04-21 12:43:44 -04:00
|
|
|
var homepage = Discourse.Utilities.defaultHomepage();
|
2014-01-23 11:08:52 -05:00
|
|
|
|
2014-10-07 15:19:17 -04:00
|
|
|
if (window.history &&
|
|
|
|
window.history.pushState &&
|
|
|
|
(path === "/" || path === "/" + homepage) &&
|
|
|
|
(oldPath === "/" || oldPath === "/" + homepage)) {
|
2014-07-24 14:59:53 -04:00
|
|
|
this.appEvents.trigger('url:refresh');
|
2013-07-04 17:31:06 -04:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
},
|
2013-06-20 17:20:08 -04:00
|
|
|
|
2013-04-22 14:21:29 -04:00
|
|
|
/**
|
|
|
|
@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
|
|
|
|
|
2013-07-04 17:31:06 -04:00
|
|
|
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.
|
2013-04-22 14:21:29 -04:00
|
|
|
|
2013-07-04 17:31:06 -04:00
|
|
|
@property router
|
2013-04-22 14:21:29 -04:00
|
|
|
**/
|
2013-07-04 17:31:06 -04:00
|
|
|
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);
|
2014-01-29 05:31:36 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
@private
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
@method handleURL
|
|
|
|
@param {String} path the url to handle
|
|
|
|
**/
|
|
|
|
handleURL: function(path) {
|
|
|
|
var router = this.get('router');
|
|
|
|
router.router.updateURL(path);
|
2014-04-29 18:01:13 -04:00
|
|
|
|
|
|
|
var split = path.split('#'),
|
|
|
|
elementId;
|
|
|
|
|
|
|
|
if (split.length === 2) {
|
|
|
|
path = split[0];
|
|
|
|
elementId = split[1];
|
|
|
|
}
|
|
|
|
|
|
|
|
var transition = router.handleURL(path);
|
2014-09-09 12:57:13 -04:00
|
|
|
transition._discourse_intercepted = true;
|
2014-04-29 18:01:13 -04:00
|
|
|
transition.promise.then(function() {
|
|
|
|
if (elementId) {
|
2014-07-16 18:14:07 -04:00
|
|
|
|
|
|
|
jumpScheduled = true;
|
2014-04-29 18:01:13 -04:00
|
|
|
Em.run.next('afterRender', function() {
|
|
|
|
var offset = $('#' + elementId).offset();
|
|
|
|
if (offset && offset.top) {
|
|
|
|
$('html, body').scrollTop(offset.top - $('header').height() - 10);
|
2014-07-16 18:14:07 -04:00
|
|
|
jumpScheduled = false;
|
2014-04-29 18:01:13 -04:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
2013-02-26 14:54:43 -05:00
|
|
|
}
|
|
|
|
|
2013-06-20 17:20:08 -04:00
|
|
|
});
|