| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283 |
- @app =
- _$: $
- _$$: $$
- _page: page
- collections: {}
- models: {}
- templates: {}
- views: {}
- init: ->
- try @initErrorTracking() catch
- return unless @browserCheck()
- @el = $('._app')
- @localStorage = new LocalStorageStore
- @serviceWorker = new app.ServiceWorker if app.ServiceWorker.isEnabled()
- @settings = new app.Settings
- @db = new app.DB()
- @settings.initLayout()
- @docs = new app.collections.Docs
- @disabledDocs = new app.collections.Docs
- @entries = new app.collections.Entries
- @router = new app.Router
- @shortcuts = new app.Shortcuts
- @document = new app.views.Document
- @mobile = new app.views.Mobile if @isMobile()
- if document.body.hasAttribute('data-doc')
- @DOC = JSON.parse(document.body.getAttribute('data-doc'))
- @bootOne()
- else if @DOCS
- @bootAll()
- else
- @onBootError()
- return
- browserCheck: ->
- return true if @isSupportedBrowser()
- document.body.innerHTML = app.templates.unsupportedBrowser
- @hideLoadingScreen()
- false
- initErrorTracking: ->
- # Show a warning message and don't track errors when the app is loaded
- # from a domain other than our own, because things are likely to break.
- # (e.g. cross-domain requests)
- if @isInvalidLocation()
- new app.views.Notif 'InvalidLocation'
- else
- if @config.sentry_dsn
- Raven.config @config.sentry_dsn,
- release: @config.release
- whitelistUrls: [/devdocs/]
- includePaths: [/devdocs/]
- ignoreErrors: [/NPObject/, /NS_ERROR/, /^null$/, /EvalError/]
- tags:
- mode: if @isSingleDoc() then 'single' else 'full'
- iframe: (window.top isnt window).toString()
- electron: (!!window.process?.versions?.electron).toString()
- shouldSendCallback: =>
- try
- if @isInjectionError()
- @onInjectionError()
- return false
- if @isAndroidWebview()
- return false
- true
- dataCallback: (data) ->
- try
- $.extend(data.user ||= {}, app.settings.dump())
- data.user.docs = data.user.docs.split('/') if data.user.docs
- data.user.lastIDBTransaction = app.lastIDBTransaction if app.lastIDBTransaction
- data.tags.scriptCount = document.scripts.length
- data
- .install()
- @previousErrorHandler = onerror
- window.onerror = @onWindowError.bind(@)
- CookiesStore.onBlocked = @onCookieBlocked
- return
- bootOne: ->
- @doc = new app.models.Doc @DOC
- @docs.reset [@doc]
- @doc.load @start.bind(@), @onBootError.bind(@), readCache: true
- new app.views.Notice 'singleDoc', @doc
- delete @DOC
- return
- bootAll: ->
- docs = @settings.getDocs()
- for doc in @DOCS
- (if docs.indexOf(doc.slug) >= 0 then @docs else @disabledDocs).add(doc)
- @migrateDocs()
- @docs.load @start.bind(@), @onBootError.bind(@), readCache: true, writeCache: true
- delete @DOCS
- return
- start: ->
- @entries.add doc.toEntry() for doc in @docs.all()
- @entries.add doc.toEntry() for doc in @disabledDocs.all()
- @initDoc(doc) for doc in @docs.all()
- @trigger 'ready'
- @router.start()
- @hideLoadingScreen()
- setTimeout =>
- @welcomeBack() unless @doc
- @removeEvent 'ready bootError'
- , 50
- return
- initDoc: (doc) ->
- doc.entries.add type.toEntry() for type in doc.types.all()
- @entries.add doc.entries.all()
- return
- migrateDocs: ->
- for slug in @settings.getDocs() when not @docs.findBy('slug', slug)
- needsSaving = true
- doc = @disabledDocs.findBy('slug', 'webpack') if slug == 'webpack~2'
- doc = @disabledDocs.findBy('slug', 'angular') if slug == 'angular~4_typescript'
- doc = @disabledDocs.findBy('slug', 'angular~2') if slug == 'angular~2_typescript'
- doc ||= @disabledDocs.findBy('slug_without_version', slug)
- if doc
- @disabledDocs.remove(doc)
- @docs.add(doc)
- @saveDocs() if needsSaving
- return
- enableDoc: (doc, _onSuccess, onError) ->
- return if @docs.contains(doc)
- onSuccess = =>
- return if @docs.contains(doc)
- @disabledDocs.remove(doc)
- @docs.add(doc)
- @docs.sort()
- @initDoc(doc)
- @saveDocs()
- if app.settings.get('autoInstall')
- doc.install(_onSuccess, onError)
- else
- _onSuccess()
- return
- doc.load onSuccess, onError, writeCache: true
- return
- saveDocs: ->
- @settings.setDocs(doc.slug for doc in @docs.all())
- @db.migrate()
- @serviceWorker?.updateInBackground()
- welcomeBack: ->
- visitCount = @settings.get('count')
- @settings.set 'count', ++visitCount
- new app.views.Notif 'Share', autoHide: null if visitCount is 5
- new app.views.News()
- new app.views.Updates()
- @updateChecker = new app.UpdateChecker()
- reboot: ->
- if location.pathname isnt '/' and location.pathname isnt '/settings'
- window.location = "/##{location.pathname}"
- else
- window.location = '/'
- return
- reload: ->
- @docs.clearCache()
- @disabledDocs.clearCache()
- if @serviceWorker then @serviceWorker.reload() else @reboot()
- return
- reset: ->
- @localStorage.reset()
- @settings.reset()
- @db?.reset()
- @serviceWorker?.update()
- window.location = '/'
- return
- showTip: (tip) ->
- return if @isSingleDoc()
- tips = @settings.getTips()
- if tips.indexOf(tip) is -1
- tips.push(tip)
- @settings.setTips(tips)
- new app.views.Tip(tip)
- return
- hideLoadingScreen: ->
- document.body.classList.add '_overlay-scrollbars' if $.overlayScrollbarsEnabled()
- document.documentElement.classList.remove '_booting'
- return
- indexHost: ->
- # Can't load the index files from the host/CDN when service worker is
- # enabled because it doesn't support caching URLs that use CORS.
- @config[if @serviceWorker and @settings.hasDocs() then 'index_path' else 'docs_origin']
- onBootError: (args...) ->
- @trigger 'bootError'
- @hideLoadingScreen()
- return
- onQuotaExceeded: ->
- return if @quotaExceeded
- @quotaExceeded = true
- new app.views.Notif 'QuotaExceeded', autoHide: null
- return
- onCookieBlocked: (key, value, actual) ->
- return if @cookieBlocked
- @cookieBlocked = true
- new app.views.Notif 'CookieBlocked', autoHide: null
- Raven.captureMessage "CookieBlocked/#{key}", level: 'warning', extra: {value, actual}
- return
- onWindowError: (args...) ->
- return if @cookieBlocked
- if @isInjectionError args...
- @onInjectionError()
- else if @isAppError args...
- @previousErrorHandler? args...
- @hideLoadingScreen()
- @errorNotif or= new app.views.Notif 'Error'
- @errorNotif.show()
- return
- onInjectionError: ->
- unless @injectionError
- @injectionError = true
- alert """
- JavaScript code has been injected in the page which prevents DevDocs from running correctly.
- Please check your browser extensions/addons. """
- Raven.captureMessage 'injection error', level: 'info'
- return
- isInjectionError: ->
- # Some browser extensions expect the entire web to use jQuery.
- # I gave up trying to fight back.
- window.$ isnt app._$ or window.$$ isnt app._$$ or window.page isnt app._page or typeof $.empty isnt 'function' or typeof page.show isnt 'function'
- isAppError: (error, file) ->
- # Ignore errors from external scripts.
- file and file.indexOf('devdocs') isnt -1 and file.indexOf('.js') is file.length - 3
- isSupportedBrowser: ->
- try
- features =
- bind: !!Function::bind
- pushState: !!history.pushState
- matchMedia: !!window.matchMedia
- insertAdjacentHTML: !!document.body.insertAdjacentHTML
- defaultPrevented: document.createEvent('CustomEvent').defaultPrevented is false
- cssVariables: !!CSS?.supports?('(--t: 0)')
- for key, value of features when not value
- Raven.captureMessage "unsupported/#{key}", level: 'info'
- return false
- true
- catch error
- Raven.captureMessage 'unsupported/exception', level: 'info', extra: { error: error }
- false
- isSingleDoc: ->
- document.body.hasAttribute('data-doc')
- isMobile: ->
- @_isMobile ?= app.views.Mobile.detect()
- isAndroidWebview: ->
- @_isAndroidWebview ?= app.views.Mobile.detectAndroidWebview()
- isInvalidLocation: ->
- @config.env is 'production' and location.host.indexOf(app.config.production_host) isnt 0
- $.extend app, Events
|