| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 |
- ###
- * Based on github.com/visionmedia/page.js
- * Licensed under the MIT license
- * Copyright 2012 TJ Holowaychuk <tj@vision-media.ca>
- ###
- running = false
- currentState = null
- callbacks = []
- @page = (value, fn) ->
- if typeof value is 'function'
- page '*', value
- else if typeof fn is 'function'
- route = new Route(value)
- callbacks.push route.middleware(fn)
- else if typeof value is 'string'
- page.show(value, fn)
- else
- page.start(value)
- return
- page.start = (options = {}) ->
- unless running
- running = true
- addEventListener 'popstate', onpopstate
- addEventListener 'click', onclick
- page.replace currentPath(), null, null, true
- return
- page.stop = ->
- if running
- running = false
- removeEventListener 'click', onclick
- removeEventListener 'popstate', onpopstate
- return
- page.show = (path, state) ->
- return if path is currentState?.path
- context = new Context(path, state)
- previousState = currentState
- currentState = context.state
- if res = page.dispatch(context)
- currentState = previousState
- location.assign(res)
- else
- context.pushState()
- updateCanonicalLink()
- track()
- context
- page.replace = (path, state, skipDispatch, init) ->
- context = new Context(path, state or currentState)
- context.init = init
- currentState = context.state
- result = page.dispatch(context) unless skipDispatch
- if result
- context = new Context(result)
- context.init = init
- currentState = context.state
- page.dispatch(context)
- context.replaceState()
- updateCanonicalLink()
- track() unless skipDispatch
- context
- page.dispatch = (context) ->
- i = 0
- next = ->
- res = fn(context, next) if fn = callbacks[i++]
- return res
- return next()
- page.canGoBack = ->
- not Context.isIntialState(currentState)
- page.canGoForward = ->
- not Context.isLastState(currentState)
- currentPath = ->
- location.pathname + location.search + location.hash
- class Context
- @initialPath: currentPath()
- @sessionId: Date.now()
- @stateId: 0
- @isIntialState: (state) ->
- state.id == 0
- @isLastState: (state) ->
- state.id == @stateId - 1
- @isInitialPopState: (state) ->
- state.path is @initialPath and @stateId is 1
- @isSameSession: (state) ->
- state.sessionId is @sessionId
- constructor: (@path = '/', @state = {}) ->
- @pathname = @path.replace /(?:\?([^#]*))?(?:#(.*))?$/, (_, query, hash) =>
- @query = query
- @hash = hash
- ''
- @state.id ?= @constructor.stateId++
- @state.sessionId ?= @constructor.sessionId
- @state.path = @path
- pushState: ->
- history.pushState @state, '', @path
- return
- replaceState: ->
- try history.replaceState @state, '', @path # NS_ERROR_FAILURE in Firefox
- return
- class Route
- constructor: (@path, options = {}) ->
- @keys = []
- @regexp = pathtoRegexp @path, @keys
- middleware: (fn) ->
- (context, next) =>
- if @match context.pathname, params = []
- context.params = params
- return fn(context, next)
- else
- return next()
- match: (path, params) ->
- return unless matchData = @regexp.exec(path)
- for value, i in matchData[1..]
- value = decodeURIComponent value if typeof value is 'string'
- if key = @keys[i]
- params[key.name] = value
- else
- params.push value
- true
- pathtoRegexp = (path, keys) ->
- return path if path instanceof RegExp
- path = "(#{path.join '|'})" if path instanceof Array
- path = path
- .replace /\/\(/g, '(?:/'
- .replace /(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, (_, slash = '', format = '', key, capture, optional) ->
- keys.push name: key, optional: !!optional
- str = if optional then '' else slash
- str += '(?:'
- str += slash if optional
- str += format
- str += capture or if format then '([^/.]+?)' else '([^/]+?)'
- str += ')'
- str += optional if optional
- str
- .replace /([\/.])/g, '\\$1'
- .replace /\*/g, '(.*)'
- new RegExp "^#{path}$"
- onpopstate = (event) ->
- return if not event.state or Context.isInitialPopState(event.state)
- if Context.isSameSession(event.state)
- page.replace(event.state.path, event.state)
- else
- location.reload()
- return
- onclick = (event) ->
- try
- return if event.which isnt 1 or event.metaKey or event.ctrlKey or event.shiftKey or event.defaultPrevented
- catch
- return
- link = $.eventTarget(event)
- link = link.parentNode while link and link.tagName isnt 'A'
- if link and not link.target and isSameOrigin(link.href)
- event.preventDefault()
- path = link.pathname + link.search + link.hash
- path = path.replace /^\/\/+/, '/' # IE11 bug
- page.show(path)
- return
- isSameOrigin = (url) ->
- url.indexOf("#{location.protocol}//#{location.hostname}") is 0
- updateCanonicalLink = ->
- @canonicalLink ||= document.head.querySelector('link[rel="canonical"]')
- @canonicalLink.setAttribute('href', "https://#{location.host}#{location.pathname}")
- trackers = []
- page.track = (fn) ->
- trackers.push(fn)
- return
- track = ->
- return unless app.config.env == 'production'
- return if navigator.doNotTrack == '1'
- return if navigator.globalPrivacyControl
- consentGiven = Cookies.get('analyticsConsent')
- consentAsked = Cookies.get('analyticsConsentAsked')
- if consentGiven == '1'
- tracker.call() for tracker in trackers
- else if consentGiven == undefined and consentAsked == undefined
- # Only ask for consent once per browser session
- Cookies.set('analyticsConsentAsked', '1')
- new app.views.Notif 'AnalyticsConsent', autoHide: null
- return
- @resetAnalytics = ->
- for cookie in document.cookie.split(/;\s?/)
- name = cookie.split('=')[0]
- if name[0] == '_' && name[1] != '_'
- Cookies.expire(name)
- return
|