app.coffee 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. @app =
  2. _$: $
  3. _$$: $$
  4. _page: page
  5. collections: {}
  6. models: {}
  7. templates: {}
  8. views: {}
  9. init: ->
  10. try @initErrorTracking() catch
  11. return unless @browserCheck()
  12. @showLoading()
  13. @el = $('._app')
  14. @store = new Store
  15. @appCache = new app.AppCache if app.AppCache.isEnabled()
  16. @settings = new app.Settings @store
  17. @docs = new app.collections.Docs
  18. @disabledDocs = new app.collections.Docs
  19. @entries = new app.collections.Entries
  20. @router = new app.Router
  21. @shortcuts = new app.Shortcuts
  22. @document = new app.views.Document
  23. @mobile = new app.views.Mobile if @isMobile()
  24. if navigator.userAgent.match /iPad;.*CPU.*OS 7_\d/i
  25. document.documentElement.style.height = "#{window.innerHeight}px"
  26. if @DOC
  27. @bootOne()
  28. else if @DOCS
  29. @bootAll()
  30. else
  31. @onBootError()
  32. return
  33. browserCheck: ->
  34. return true if @isSupportedBrowser()
  35. document.body.className = ''
  36. document.body.innerHTML = app.templates.unsupportedBrowser
  37. false
  38. initErrorTracking: ->
  39. # Show a warning message and don't track errors when the app is loaded
  40. # from a domain other than our own, because things are likely to break.
  41. # (e.g. cross-domain requests)
  42. if @isInvalidLocation()
  43. new app.views.Notif 'InvalidLocation'
  44. else
  45. if @config.sentry_dsn
  46. Raven.config @config.sentry_dsn,
  47. whitelistUrls: [/devdocs/]
  48. includePaths: [/devdocs/]
  49. ignoreErrors: [/dpQuery/, /NPObject/, /NS_ERROR/, /^null$/]
  50. tags:
  51. mode: if @DOC then 'single' else 'full'
  52. iframe: (window.top isnt window).toString()
  53. .install()
  54. @previousErrorHandler = onerror
  55. window.onerror = @onWindowError.bind(@)
  56. return
  57. bootOne: ->
  58. @doc = new app.models.Doc @DOC
  59. @docs.reset [@doc]
  60. @doc.load @start.bind(@), @onBootError.bind(@), readCache: true
  61. new app.views.Notice 'singleDoc', @doc
  62. delete @DOC
  63. return
  64. bootAll: ->
  65. docs = @settings.getDocs()
  66. for doc in @DOCS
  67. (if docs.indexOf(doc.slug) >= 0 then @docs else @disabledDocs).add(doc)
  68. @migrateDocs()
  69. @docs.sort()
  70. @disabledDocs.sort()
  71. @docs.load @start.bind(@), @onBootError.bind(@), readCache: true, writeCache: true
  72. delete @DOCS
  73. return
  74. start: ->
  75. @entries.add doc.toEntry() for doc in @docs.all()
  76. @entries.add doc.toEntry() for doc in @disabledDocs.all()
  77. @initDoc(doc) for doc in @docs.all()
  78. @db = new app.DB()
  79. @trigger 'ready'
  80. @router.start()
  81. @hideLoading()
  82. @welcomeBack() unless @doc
  83. @removeEvent 'ready bootError'
  84. try navigator.mozApps?.getSelf().onsuccess = -> app.mozApp = true catch
  85. return
  86. initDoc: (doc) ->
  87. @entries.add type.toEntry() for type in doc.types.all()
  88. @entries.add doc.entries.all()
  89. return
  90. migrateDocs: ->
  91. for slug in @settings.getDocs() when not @docs.findBy('slug', slug)
  92. needsSaving = true
  93. if doc = @disabledDocs.findBy('slug_without_version', slug)
  94. @disabledDocs.remove(doc)
  95. @docs.add(doc)
  96. @saveDocs() if needsSaving
  97. enableDoc: (doc, _onSuccess, onError) ->
  98. return if @docs.contains(doc)
  99. onSuccess = =>
  100. @disabledDocs.remove(doc)
  101. @docs.add(doc)
  102. @docs.sort()
  103. @initDoc(doc)
  104. @saveDocs()
  105. _onSuccess()
  106. return
  107. doc.load onSuccess, onError, writeCache: true
  108. return
  109. saveDocs: ->
  110. @settings.setDocs(doc.slug for doc in @docs.all())
  111. @appCache?.updateInBackground()
  112. welcomeBack: ->
  113. visitCount = @settings.get('count')
  114. @settings.set 'count', ++visitCount
  115. new app.views.Notif 'Share', autoHide: null if visitCount is 5
  116. new app.views.News()
  117. @updateChecker = new app.UpdateChecker()
  118. reload: ->
  119. @docs.clearCache()
  120. @disabledDocs.clearCache()
  121. if @appCache then @appCache.reload() else window.location = '/'
  122. return
  123. reset: ->
  124. @store.clear()
  125. @settings.reset()
  126. @db?.reset()
  127. @appCache?.update()
  128. window.location = '/'
  129. return
  130. showTip: (tip) ->
  131. return if @isSingleDoc()
  132. tips = @settings.get('tips') || []
  133. if tips.indexOf(tip) is -1
  134. tips.push(tip)
  135. @settings.set('tips', tips)
  136. new app.views.Tip(tip)
  137. return
  138. showLoading: ->
  139. document.body.classList.remove '_noscript'
  140. document.body.classList.add '_loading'
  141. return
  142. hideLoading: ->
  143. document.body.classList.remove '_booting'
  144. document.body.classList.remove '_loading'
  145. return
  146. indexHost: ->
  147. # Can't load the index files from the host/CDN when applicationCache is
  148. # enabled because it doesn't support caching URLs that use CORS.
  149. @config[if @appCache and @settings.hasDocs() then 'index_path' else 'docs_host']
  150. onBootError: (args...) ->
  151. @trigger 'bootError'
  152. @hideLoading()
  153. return
  154. onQuotaExceeded: ->
  155. return if @quotaExceeded
  156. @quotaExceeded = true
  157. new app.views.Notif 'QuotaExceeded', autoHide: null
  158. Raven.captureMessage 'QuotaExceededError'
  159. onWindowError: (args...) ->
  160. if @isInjectionError args...
  161. @onInjectionError()
  162. else if @isAppError args...
  163. @previousErrorHandler? args...
  164. @hideLoading()
  165. @errorNotif or= new app.views.Notif 'Error'
  166. @errorNotif.show()
  167. return
  168. onInjectionError: ->
  169. unless @injectionError
  170. @injectionError = true
  171. alert """
  172. JavaScript code has been injected in the page which prevents DevDocs from running correctly.
  173. Please check your browser extensions/addons. """
  174. Raven.captureMessage 'injection error'
  175. return
  176. isInjectionError: ->
  177. # Some browser extensions expect the entire web to use jQuery.
  178. # I gave up trying to fight back.
  179. window.$ isnt app._$ or window.$$ isnt app._$$ or window.page isnt app._page
  180. isAppError: (error, file) ->
  181. # Ignore errors from external scripts.
  182. file and file.indexOf('devdocs') isnt -1 and file.indexOf('.js') is file.length - 3
  183. isSupportedBrowser: ->
  184. try
  185. features =
  186. bind: !!Function::bind
  187. pushState: !!history.pushState
  188. matchMedia: !!window.matchMedia
  189. classList: !!document.body.classList
  190. insertAdjacentHTML: !!document.body.insertAdjacentHTML
  191. defaultPrevented: document.createEvent('CustomEvent').defaultPrevented is false
  192. cssGradients: supportsCssGradients()
  193. for key, value of features when not value
  194. Raven.captureMessage "unsupported/#{key}"
  195. return false
  196. true
  197. catch error
  198. Raven.captureMessage 'unsupported/exception', extra: { error: error }
  199. false
  200. isSingleDoc: ->
  201. !!(@DOC or @doc)
  202. isMobile: ->
  203. try
  204. # Need to sniff the user agent because some Android and Windows Phone devices don't take
  205. # resolution (dpi) into account when reporting device width/height.
  206. @_isMobile ?= (window.matchMedia('(max-device-width: 767px)').matches) or
  207. (window.matchMedia('(max-device-height: 767px) and (max-device-width: 1024px)').matches) or
  208. (navigator.userAgent.indexOf('Android') isnt -1 and navigator.userAgent.indexOf('Mobile') isnt -1) or
  209. (navigator.userAgent.indexOf('IEMobile') isnt -1)
  210. catch
  211. @_isMobile = false
  212. isInvalidLocation: ->
  213. @config.env is 'production' and location.host.indexOf(app.config.production_host) isnt 0
  214. supportsCssGradients = ->
  215. el = document.createElement('div')
  216. el.style.cssText = "background-image: -webkit-linear-gradient(top, #000, #fff); background-image: linear-gradient(to top, #000, #fff);"
  217. el.style.backgroundImage.indexOf('gradient') >= 0
  218. $.extend app, Events