go = (path, options) -> -> @routeDirectly path, arguments, options redirect = (path) -> -> @navigate(path, { trigger: true, replace: true }) utils = require './utils' module.exports = class CocoRouter extends Backbone.Router initialize: -> # http://nerds.airbnb.com/how-to-add-google-analytics-page-tracking-to-57536 @bind 'route', @_trackPageView Backbone.Mediator.subscribe 'router:navigate', @onNavigate, @ @initializeSocialMediaServices = _.once @initializeSocialMediaServices routes: '': -> if window.serverConfig.picoCTF return @routeDirectly 'play/CampaignView', ['picoctf'], {} if utils.getQueryVariable 'hour_of_code' return @navigate "/play", {trigger: true, replace: true} return @routeDirectly('NewHomeView', []) 'about': go('AboutView') 'account': go('account/MainAccountView') 'account/settings': go('account/AccountSettingsRootView') 'account/unsubscribe': go('account/UnsubscribeView') 'account/payments': go('account/PaymentsView') 'account/subscription': go('account/SubscriptionView') 'account/invoices': go('account/InvoicesView') 'account/prepaid': go('account/PrepaidView') 'admin': go('admin/MainAdminView') 'admin/clas': go('admin/CLAsView') 'admin/classroom-levels': go('admin/AdminClassroomLevelsView') 'admin/design-elements': go('admin/DesignElementsView') 'admin/files': go('admin/FilesView') 'admin/analytics': go('admin/AnalyticsView') 'admin/school-counts': go('admin/SchoolCountsView') 'admin/analytics/subscriptions': go('admin/AnalyticsSubscriptionsView') 'admin/level-sessions': go('admin/LevelSessionsView') 'admin/users': go('admin/UsersView') 'admin/base': go('admin/BaseView') 'admin/demo-requests': go('admin/DemoRequestsView') 'admin/trial-requests': go('admin/TrialRequestsView') 'admin/user-code-problems': go('admin/UserCodeProblemsView') 'admin/pending-patches': go('admin/PendingPatchesView') 'admin/codelogs': go('admin/CodeLogsView') 'artisans': go('artisans/ArtisansView') 'artisans/level-tasks': go('artisans/LevelTasksView') 'artisans/solution-problems': go('artisans/SolutionProblemsView') 'artisans/thang-tasks': go('artisans/ThangTasksView') 'beta': go('HomeView') 'careers': => window.location.href = 'https://jobs.lever.co/codecombat' 'Careers': => window.location.href = 'https://jobs.lever.co/codecombat' 'cla': go('CLAView') 'clans': go('clans/ClansView') 'clans/:clanID': go('clans/ClanDetailsView') 'community': go('CommunityView') 'contribute': go('contribute/MainContributeView') 'contribute/adventurer': go('contribute/AdventurerView') 'contribute/ambassador': go('contribute/AmbassadorView') 'contribute/archmage': go('contribute/ArchmageView') 'contribute/artisan': go('contribute/ArtisanView') 'contribute/diplomat': go('contribute/DiplomatView') 'contribute/scribe': go('contribute/ScribeView') 'courses': go('courses/CoursesView') 'Courses': go('courses/CoursesView') 'courses/students': redirect('/courses') 'courses/teachers': redirect('/teachers/classes') 'courses/purchase': redirect('/teachers/licenses') 'courses/enroll(/:courseID)': redirect('/teachers/licenses') 'courses/update-account': go('courses/CoursesUpdateAccountView') 'courses/:classroomID': go('courses/ClassroomView', { studentsOnly: true }) 'courses/:courseID/:courseInstanceID': go('courses/CourseDetailsView', { studentsOnly: true }) 'db/*path': 'routeToServer' 'demo(/*subpath)': go('DemoView') 'docs/components': go('docs/ComponentsDocumentationView') 'docs/systems': go('docs/SystemsDocumentationView') 'editor': go('CommunityView') 'editor/achievement': go('editor/achievement/AchievementSearchView') 'editor/achievement/:articleID': go('editor/achievement/AchievementEditView') 'editor/article': go('editor/article/ArticleSearchView') 'editor/article/preview': go('editor/article/ArticlePreviewView') 'editor/article/:articleID': go('editor/article/ArticleEditView') 'editor/level': go('editor/level/LevelSearchView') 'editor/level/:levelID': go('editor/level/LevelEditView') 'editor/thang': go('editor/thang/ThangTypeSearchView') 'editor/thang/:thangID': go('editor/thang/ThangTypeEditView') 'editor/campaign/:campaignID': go('editor/campaign/CampaignEditorView') 'editor/poll': go('editor/poll/PollSearchView') 'editor/poll/:articleID': go('editor/poll/PollEditView') 'editor/thang-tasks': go('editor/ThangTasksView') 'editor/verifier': go('editor/verifier/VerifierView') 'editor/verifier/:levelID': go('editor/verifier/VerifierView') 'file/*path': 'routeToServer' 'github/*path': 'routeToServer' 'hoc': -> # Matching /?hour_of_code=true behavior @navigate "/play", {trigger: true, replace: true} 'home': go('NewHomeView') 'i18n': go('i18n/I18NHomeView') 'i18n/thang/:handle': go('i18n/I18NEditThangTypeView') 'i18n/component/:handle': go('i18n/I18NEditComponentView') 'i18n/level/:handle': go('i18n/I18NEditLevelView') 'i18n/achievement/:handle': go('i18n/I18NEditAchievementView') 'i18n/campaign/:handle': go('i18n/I18NEditCampaignView') 'i18n/poll/:handle': go('i18n/I18NEditPollView') 'identify': go('user/IdentifyView') 'legal': go('LegalView') 'play(/)': go('play/CampaignView') # extra slash is to get Facebook app to work 'play/ladder/:levelID/:leagueType/:leagueID': go('ladder/LadderView') 'play/ladder/:levelID': go('ladder/LadderView') 'play/ladder': go('ladder/MainLadderView') 'play/level/:levelID': go('play/level/PlayLevelView') 'play/game-dev-level/:levelID/:sessionID': go('play/level/PlayGameDevLevelView') 'play/spectate/:levelID': go('play/SpectateView') 'play/:map': go('play/CampaignView') 'preview': go('HomeView') 'privacy': go('PrivacyView') 'schools': go('NewHomeView') 'seen': go('NewHomeView') 'SEEN': go('NewHomeView') 'teachers': redirect('/teachers/classes') 'teachers/classes': go('courses/TeacherClassesView', { teachersOnly: true }) 'teachers/classes/:classroomID': go('courses/TeacherClassView', { teachersOnly: true }) 'teachers/courses': go('courses/TeacherCoursesView') 'teachers/demo': go('teachers/RequestQuoteView') 'teachers/enrollments': redirect('/teachers/licenses') 'teachers/licenses': go('courses/EnrollmentsView', { teachersOnly: true }) 'teachers/freetrial': go('teachers/RequestQuoteView') 'teachers/quote': redirect('/teachers/demo') 'teachers/signup': -> return @routeDirectly('teachers/CreateTeacherAccountView', []) if me.isAnonymous() @navigate('/teachers/update-account', {trigger: true, replace: true}) 'teachers/update-account': -> return @navigate('/teachers/signup', {trigger: true, replace: true}) if me.isAnonymous() @routeDirectly('teachers/ConvertToTeacherAccountView', []) 'test(/*subpath)': go('TestView') 'user/:slugOrID': go('user/MainUserView') 'user/:userID/verify/:verificationCode': go('user/EmailVerifiedView') '*name/': 'removeTrailingSlash' '*name': go('NotFoundView') routeToServer: (e) -> window.location.href = window.location.href removeTrailingSlash: (e) -> @navigate e, {trigger: true} routeDirectly: (path, args=[], options={}) -> if options.teachersOnly and not (me.isTeacher() or me.isAdmin()) return @routeDirectly('teachers/RestrictedToTeachersView') if options.studentsOnly and not (me.isStudent() or me.isAdmin()) return @routeDirectly('courses/RestrictedToStudentsView') leavingMessage = _.result(window.currentView, 'onLeaveMessage') if leavingMessage if not confirm(leavingMessage) return @navigate(this.path, {replace: true}) else window.currentView.onLeaveMessage = _.noop # to stop repeat confirm calls path = 'play/CampaignView' if window.serverConfig.picoCTF and not /^(views)?\/?play/.test(path) path = "views/#{path}" if not _.string.startsWith(path, 'views/') ViewClass = @tryToLoadModule path if not ViewClass and application.moduleLoader.load(path) @listenToOnce application.moduleLoader, 'load-complete', -> @routeDirectly(path, args, options) return return go('NotFoundView') if not ViewClass view = new ViewClass(options, args...) # options, then any path fragment args view.render() @openView(view) tryToLoadModule: (path) -> try return require(path) catch error if error.toString().search('Cannot find module "' + path + '" from') is -1 throw error openView: (view) -> @closeCurrentView() $('#page-container').empty().append view.el window.currentView = view @activateTab() view.afterInsert() view.didReappear() @path = document.location.pathname + document.location.search closeCurrentView: -> if window.currentView?.reloadOnClose return document.location.reload() window.currentModal?.hide?() return unless window.currentView? window.currentView.destroy() $('.popover').popover 'hide' $('#flying-focus').css({top: 0, left: 0}) # otherwise it might make the page unnecessarily tall _.delay (-> $('html')[0].scrollTop = 0 $('body')[0].scrollTop = 0 ), 10 initializeSocialMediaServices: -> return if application.testing or application.demoing application.facebookHandler.loadAPI() application.gplusHandler.loadAPI() require('core/services/twitter')() renderSocialButtons: => # TODO: Refactor remaining services to Handlers, use loadAPI success callback @initializeSocialMediaServices() $('.share-buttons, .partner-badges').addClass('fade-in').delay(10000).removeClass('fade-in', 5000) application.facebookHandler.renderButtons() application.gplusHandler.renderButtons() twttr?.widgets?.load?() activateTab: -> base = _.string.words(document.location.pathname[1..], '/')[0] $("ul.nav li.#{base}").addClass('active') _trackPageView: -> window.tracker?.trackPageView() onNavigate: (e) -> if _.isString e.viewClass ViewClass = @tryToLoadModule e.viewClass if not ViewClass and application.moduleLoader.load(e.viewClass) @listenToOnce application.moduleLoader, 'load-complete', -> @onNavigate(e) return e.viewClass = ViewClass manualView = e.view or e.viewClass if (e.route is document.location.pathname) and not manualView return document.location.reload() @navigate e.route, {trigger: not manualView} @_trackPageView() return unless manualView if e.viewClass args = e.viewArgs or [] view = new e.viewClass(args...) view.render() @openView view else @openView e.view navigate: (fragment, options) -> super fragment, options Backbone.Mediator.publish 'router:navigated', route: fragment reload: -> document.location.reload()