2014-07-01 15:55:05 -04:00
/*global LockOn:true*/
2013-02-26 14:54:43 -05:00
/ * *
URL related functions .
@ class URL
@ namespace Discourse
@ module Discourse
* * /
2014-07-16 18:14:07 -04:00
var jumpScheduled = false ;
2013-06-20 17:20:08 -04:00
Discourse . URL = Em . 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
* * /
jumpToPost : function ( postNumber ) {
var holderId = '#post-cloak-' + postNumber ;
Em . run . schedule ( 'afterRender' , function ( ) {
if ( postNumber === 1 ) {
$ ( window ) . scrollTop ( 0 ) ;
return ;
}
new LockOn ( holderId , { offsetCalculator : 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 ) ;
} } ) . lock ( ) ;
} ) ;
} ,
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 ) {
if ( Ember . FEATURES . isEnabled ( "query-params-new" ) ) {
var search = Discourse . _ _container _ _ . lookup ( 'router:main' ) . get ( 'location.location.search' ) || '' ;
path += search ;
}
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 ) ;
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 16:59:08 +11:00
2014-05-04 14:30:17 -04:00
if ( Em . isEmpty ( path ) ) { return ; }
2014-01-15 12:07:42 +11:00
if ( Discourse . get ( "requiresRefresh" ) ) {
document . location . href = path ;
return ;
2014-01-14 16:59:08 +11: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 11:31:36 +01:00
path = path . replace ( /(https?\:)?\/\/[^\/]+/ , '' ) ;
2013-07-03 14:06:34 -04:00
2014-01-29 11:31:36 +01:00
// handle prefixes
2013-03-14 13:01:52 +01:00
if ( path . match ( /^\// ) ) {
2013-04-04 00:26:47 +02:00
var rootURL = ( Discourse . BaseUri === undefined ? "/" : Discourse . BaseUri ) ;
rootURL = rootURL . replace ( /\/$/ , '' ) ;
2013-03-14 13:01:52 +01:00
path = path . replace ( rootURL , '' ) ;
}
2013-02-26 14:54:43 -05:00
2014-02-13 15:35:19 -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-06-05 16:59:18 +10: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 11:31:36 +01:00
return this . handleURL ( path ) ;
2013-07-04 17:31:06 -04:00
} ,
/ * *
Redirect to a URL .
This has been extracted so it can be tested .
@ method redirectTo
* * /
redirectTo : function ( url ) {
window . location = Discourse . getURL ( url ) ;
} ,
2014-01-23 17:08:52 +01: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 00:54:22 -07: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 / s o m e t h i n g / : t o p i c _ i d / : p o s t _ n u m b e r
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' ) ,
topicProgressController = container . lookup ( 'controller:topic-progress' ) ,
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 ;
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 ,
highlightOnInsert : closest ,
enteredAt : new Date ( ) . getTime ( ) . toString ( )
2013-06-20 17:20:08 -04:00
} ) ;
2014-06-12 12:52:15 -04:00
topicProgressController . set ( 'progressPosition' , closest ) ;
2014-06-13 13:41:17 -04:00
Discourse . PostView . considerHighlighting ( topicController , closest ) ;
2013-11-20 16:33:36 -05:00
} ) . then ( function ( ) {
2014-07-01 15:55:05 -04:00
Discourse . URL . jumpToPost ( closest ) ;
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 20:21:29 +02: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 18:43:44 +02:00
var homepage = Discourse . Utilities . defaultHomepage ( ) ;
2014-01-23 17:08:52 +01:00
2014-06-25 12:08:53 -04:00
if ( window . history && window . history . pushState && path === "/" && ( 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 20:21:29 +02: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 20:21:29 +02:00
2013-07-04 17:31:06 -04:00
@ property router
2013-04-22 20:21:29 +02: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 11:31:36 +01: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 ) ;
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
} ) ;