define('Router',[ // eslint-disable-line requirejs/amd-function-arity 'underscore', 'jquery', 'modernizr', 'backbone', 'backbone-marionette', 'home/templates/preload', 'home/views/MainLayout', 'home/views/HomepageView', 'home/views/ActivityFeedView', 'home/views/NotificationsView', 'home/views/NotificationsLoggedOutView', 'home/views/ForumProfileView', 'home/views/ProfileView', 'home/views/DiscussionCreateView', 'home/views/DiscussionPageView', 'home/views/WelcomeView', 'home/views/ChannelProfileView', 'home/views/SettingsLayout', 'home/views/ChannelsView', 'home/views/ExploreView', 'home/views/OnboardingView', 'home/viewmodels/DiscussionCreateViewModel', 'home/viewmodels/ChannelsViewModel', 'home/viewmodels/DiscussionViewModel', 'home/collections/common/ExtensibleCollection', 'home/collections/ChannelCollection', 'home/models/Session', 'core/UniqueModel', 'home/models/UserProfile', 'home/models/Thread', 'home/models/Forum', 'home/models/Channel', 'home/models/ChannelProfile', 'home/models/ForumProfile', 'home/models/HomepageProfile', 'home/models/ExploreViewModel', 'home/utils/navigationUtils', 'home/utils/trackingUtils', 'core/utils/auth', 'core/utils/url/parseQueryString', 'core/api', 'core/engagement/page', 'core/strings', 'core/switches', 'remote/config', 'core/config/urls', // imported for side effects 'home/backbone.overrides', 'home/overrides/marionette.overrides', 'disqus.sdk', // needed to load the embed on Discussion pages ], function ( _, $, Modernizr, Backbone, Marionette, preloadTemplate, MainLayout, HomepageView, AnonHomepageView, NotificationsView, NotificationsLoggedOutView, ForumProfileView, ProfileView, DiscussionCreateView, DiscussionPageView, WelcomeView, ChannelProfileView, SettingsLayout, ChannelsView, ExploreView, OnboardingView, DiscussionCreateViewModel, ChannelsViewModel, DiscussionViewModel, ExtensibleCollection, ChannelCollection, Session, UniqueModel, UserProfile, Thread, Forum, Channel, ChannelProfile, ForumProfile, HomepageProfile, ExploreViewModel, NavigationUtils, TrackingUtils, auth, parseQueryString, api, pageEngagement, strings, switches, remoteConfig, urls ) { 'use strict'; var localStorageSupported = Modernizr.localstorage; /* eslint-disable no-unused-expressions */ // This line causes the grunt task to add the Modernizr Touch test // which in turn causes Modernizr to add a css class touch/no-touch // to the tag Modernizr.touch; // Add flexbox/no-flexbox detection for IE9 Modernizr.flexbox; /* eslint-disable no-unused-expressions */ // TODO: move this to jesterUtils var logStatsD = function (eventName) { new window.Image().src = urls.jester + '/stat.gif?event=' + eventName; }; var gettext = strings.get; var Router = Backbone.Router.extend({ name: 'home', routes: { 'home/discuss/:forum(/)': 'startDiscussion', 'home/edit/:forum/:thread(/)': 'editDiscussion', 'home/search(/)': 'search', 'home/user(s)(/:username)(/:feed)(/)': 'profile', 'home/user(s)/(:username)(/:feed)(/:subfeed)(/)': 'profile', 'by/:username(/:feed)(/)': 'profile', 'by/:username(/:feed)(/:subfeed)(/)': 'profile', 'home/inbox(/:feed)(/)': 'inbox', 'home/preload(/)': 'preload', 'home/account(/)': 'account', 'home/notifications(/)': 'notifications', 'home/topic(s)/:topicId(/)': 'topics', 'home/forum(s)/:forumId(/:feed)(/)': 'forums', 'home/discussion(s)/:forum/:thread(/:activeTab)(/:permalink)(/)': 'discussion', 'home/ndiscussion(s)/:forum/:thread(/:activeTab)(/:permalink)(/)': 'nativeDiscussion', 'home/follow/channel(s)/:channelId(/)': 'followChannel', 'home/channel(s)(/)': 'channelsPage', 'home/channel(s)/:channelId(/:feed)(/)': 'channels', 'home/channel(s)/:channelId/topics/:topic(/)': 'channelTopics', 'home/channel(s)/:channelId/discussion(s)/:forumId/:thread(/:activeTab)(/:permalink)(/)': 'channelDiscussion', 'home/channel(s)/:channelId/ndiscussion(s)/:forumId/:thread(/:activeTab)(/:permalink)(/)': 'nativeChannelDiscussion', 'home/settings(/:section)/': 'settings', // TODO: remove this once nowhere in disqus-web is directing users to /home/discover // (specifically after email verification, possibly other places) // Note: also remove 'discover' route handler in trackingUtils zone names 'home/discover(/)': 'channelsPage', 'home/explore(/)': 'explore', 'home/explore/:activeTab(/)': 'explore', 'home/explore/channels/:channelId(/:activeTab)(/)': 'exploreChannel', 'home/explore/discussions/:channelId(/)': 'exploreDiscussions', // This must remain as the last item so that it doesn't match this router when // it should match something more specific (ex: home/inbox/) 'home(/:feed)(/)': 'home', }, initialize: function (options) { this.CDN_ROOT = options.CDN_ROOT; this.feeds = {}; this.listenTo(this, 'route', this.handleRouteChange); this.listenTo(this, 'route', this.logRouteChange); this.messages = []; this.onboardingWithoutChannels = switches.isFeatureActive('onboarding_without_channels'); }, /* * Start the application by running through a * sequence of initialization steps. */ bootstrap: function () { this.initSession(); this.initTracking(); this.initPageEngagementTracking(); this.initLinkHandling(); this.initMainLayout(); }, initTracking: function () { TrackingUtils.init(); TrackingUtils.trackVisit(); }, initLinkHandling: function () { $('body').on('click', 'a[href]', _.bind(NavigationUtils.handleLinkClicks, NavigationUtils)); }, initSession: function () { var session = this.session = Session.get(); session.load(); session.loadPromise().then(_.bind(function () { if (window.Raven) { window.Raven.setUser({ id: session.user.get('id'), username: session.user.get('username'), }); } this.loadModeratedForums(); }, this)); session.loadPromise().fail(function () { var provider = session.provider(); // If we know the session provider and we are rejecting the // session authentication anyway, then there was something wrong // with our disqusauth cookie (or the user is an "SSO" publisher account). if (provider) // don't log all the random provider domains, just do 'remote' instead logStatsD('home.session.rejected.' + (provider === 'disqus' ? 'disqus' : 'remote')); }); this.initUserProfile(); return session; }, initMainLayout: function () { this.layout = new MainLayout({ container: 'body', session: this.session, app: this, }); // We do not render here because rendering kicks off the creation of // certain subviews (e.g., NavView) that rely on Backbone.history.start // having been called, which does not occur until after app.bootstrap. // And we cannot init the MainLayout AFTER History.start because History.start // will trigger a route that relies on MainLayout existing. And we cannot // hook into the route event because that is fired after the route handler // has been executed. }, /* * Returns the view currently showing in the main content area of the page. * Useful for anything external to the content view (ex: NavView) to get * the current view/model being shown. */ getCurrentView: function () { return this.layout.content.currentView; }, initPageEngagementTracking: function () { pageEngagement.start(); }, /* * When the asEmbedApp mixin is applied, loadModeratedForums is only called * when the 'showPath' message is received via the FrameBus. */ loadModeratedForums: function () { this.session.loadModeratedForums(); }, logRouteChange: function (route) { // Log the page view to 'events.*.route.' logStatsD(this.name + '.route.' + route); }, /* * When the asEmbedApp mixin is applied, initUserProfile is only called when * the 'showPath' message is received via the FrameBus. */ initUserProfile: function () { var username = Session.fromCookie().username; if (username) { // It appears the user is logged in. Fetch all the necessary user details // asynchronously using the username from the auth cookie. // (If the user turns out to be not logged in, or session expired, it'll be // handled on api response) this.session.initUserProfile(username); } else { // If we can't get the username from the cookie, wait until users/details // returns and then fetch the rest of the user details using the fetched username this.session.once('change:id', function (user) { this.session.initUserProfile(user.get('username')); }, this); } }, /* * Inherits + overrides parent .execute method * Handle pre-view setup and post-view cleanup before executing a route handler. */ execute: function () { // Before showing a new view remove the logged-in requirement handling that may have been // added for the previous view. api.off('error', NavigationUtils.navigateToLoginOnAuthFail); TrackingUtils.pageChanged(); Backbone.Router.prototype.execute.apply(this, arguments); }, ensureAuthenticated: function (callback) { // Reset the api 'error' handler so we don't bind this // more than necessary. api.off('error', NavigationUtils.navigateToLoginOnAuthFail); if (Session.isKnownToBeLoggedOut()) return NavigationUtils.navigateToLogin(); // If we expect the user to be logged in, but we make // an API call that returns an error, we should direct the // user to the login page. api.on('error', NavigationUtils.navigateToLoginOnAuthFail); // Ensure the session is fetched. Once it is, call the // callback. If fetch fails, prompt user to login this.session.loadPromise().then( _.bind(callback, this, _.tail(arguments)), NavigationUtils.navigateToLogin ); }, navigateToLandingPage: function (options) { return NavigationUtils.navigate('/home/', options); }, /* * Ensures that logged in users are redirected to onboarding step 1 if * they have not completed it yet. Logged out users are not affected. */ ensureOnboardingComplete: function (callback) { // TODO: @taylangocmen once channels is removed kill this onboarding flow if (this.onboardingWithoutChannels || Session.isKnownToBeLoggedOut()) return callback.apply(this); this.session.loadPromise().always(_.bind(function () { if (this.session.isLoggedIn() && this.session.user.shouldHomeOnboard()) // Defer this so that the current route event is triggered before navigating to the 1st step of // onboarding. Not deferring it will result in route events being triggered in the wrong order. _.defer(this.navigateToOnboarding); else callback.apply(this); }, this)); }, navigateToOnboarding: function () { var welcomeRoute = 'home/explore/'; var params = parseQueryString(); var toKeep = {}; // Retain certain params for use later. // Only retain them if given truthy values, so that checks later // for these params can skip value check and only check existence. if (params.email_verified) toKeep.email_verified = params.email_verified; if (params.forum_id) toKeep.forum_id = params.forum_id; if (_.size(toKeep)) welcomeRoute += '?' + $.param(toKeep); NavigationUtils.navigate(welcomeRoute, { trigger: true, replace: true }); }, /* * Verifies the forum with the given id has a channel and that * this user can create on that channel. */ ensureCanCreateOnChannel: function (callback, forumId) { var forum = new UniqueModel(Forum, { id: forumId, }); // If forum hasn't been fetched yet, wait until fetching is complete // and then re-call this function if (forum.shouldFetch()) { forum.fetch(); this.listenToOnce(forum, 'sync', _.bind.apply(_, [this.ensureCanCreateOnChannel, this].concat(_.toArray(arguments)))); return; } // If the forum is not the primary forum for a channel, or if the // current user does not have create permissions for the channel, // redirect to an error page. if ((!forum.channel && forumId !== 'gabalafou-test') || !forum.channel.userCanCreate(this.session.user)) // TODO: this should redirect to a page with a message like: // "You do not have permission to view this page" return this.notFound(); // The forum has a channel. Call the callback. callback.apply(this, _.tail(arguments)); }, /** * Listens for a tabbed view in case it throws a badTab event. If that occurs, * rewrites the url to the given route silently. It's expected that the tabbed * view will handle the event gracefully. * * @param {tabbedView} tabbedView - A view instance that triggers a badTab event * in the case that an invalid tab is navigated * to, as TabbedView does. * @param {string} redirectRoute - The route to rewrite if the tab is not supported */ ensureTab: function (tabbedView, redirectRoute) { tabbedView.listenTo(tabbedView, 'badTab', function () { NavigationUtils.navigate(redirectRoute, { silent: true, replace: true }); }); }, preventBlacklisted: function (options) { if (_.contains(remoteConfig.timelines.BLACKLISTED_FORUMS, options.forum)) window.top.location = 'https://help.disqus.com/customer/portal/articles/1689402-redirected-from-home'; }, preload: function () { this.preloadView = new (Marionette.ItemView.extend({ template: preloadTemplate }))(); this.layout.showView(this.preloadView); }, /* * Append a trailing slash to the current route if it * doesn't have one. * * Saves the current and previous route names as properties so it can * be used by the NavView. */ handleRouteChange: function (routeName) { this.setCurrentRouteName(routeName); NavigationUtils.coerceTrailingSlash(window.location); }, setCurrentRouteName: function (routeName) { this._previousRouteName = this.getCurrentRouteName(); this._currentRouteName = routeName; }, getCurrentRouteName: function () { return this._currentRouteName; }, getPreviousRouteName: function () { return this._previousRouteName; }, onboarding: function () { return this.ensureAuthenticated(function () { this.layout.showView(new WelcomeView({ session: this.session }), { lightBackground: true }); }); }, _explore: function (options) { this.session.loadPromise() .always(Function.bind.call(function () { var showOnboarding = this.session.isLoggedIn() && this.session.user.shouldHomeOnboard(); if (showOnboarding) { options.collection = new ChannelCollection([], { listName: 'explore' }); this.layout.showView(new OnboardingView(options)); } else { options.model = new ExploreViewModel(); this.layout.showView(new ExploreView(options)); } }, this)); }, explore: function () { return this.navigateToLandingPage({ replace: true }); }, exploreChannel: function () { return this.navigateToLandingPage({ replace: true }); }, exploreDiscussions: function () { return this.navigateToLandingPage({ replace: true }); }, // When user goes to /user, take them to /user/ renavigateToProfile: function (user) { var username = user.get('username'); // Prevent potential infinite loop // .profile() -> .renavigateToProfile() -> .profile() -> ... // (happens when username never gets set to truthy value) if (!username) { this.navigateToLandingPage({ replace: true }); return void this.home(); } NavigationUtils.navigate('/by/' + username + '/', { replace: true, }); this.profile(username); }, settings: function (section) { return this.ensureAuthenticated(function () { this.layout.showView(new SettingsLayout({ session: this.session, activeTab: section, onboardingWithoutChannels: this.onboardingWithoutChannels, })); }); }, channelsPage: function () { // remove everything but discussdisqus // Channels page shows channels followed/managed by the authenticated user. // Don't allow anon users to visit the page. if (Session.isKnownToBeLoggedOut()) { NavigationUtils.navigate('/', { replace: true }); return this.home(); } this.ensureOnboardingComplete(function () { var model = new UniqueModel(ChannelsViewModel, { id: 'discover-view-model' }); model.ensureFetched(); this.layout.showView(new ChannelsView({ model: model, onboardingWithoutChannels: this.onboardingWithoutChannels, })); }); }, search: function () { return this.navigateToLandingPage({ replace: true }); }, profile: function (username, feedName, subFeedName) { // If we get here and we aren't on the /by/ profile path, // then rewrite the URL to be correct. NavigationUtils knows // how to rewrite the home/user URL correctly, so just pass the // task off to there. if (window.location.pathname.indexOf('home/user') !== -1) NavigationUtils.navigate(window.location, { replace: true, silent: true }); if (!username) { return this.ensureAuthenticated(function () { var session = this.session; var sessionUser = session.user; if (!sessionUser.get('username')) return void this.listenToOnce(session, 'change:id', this.renavigateToProfile); return void this.renavigateToProfile(sessionUser); }); } var userProfile = _.find(UniqueModel.pool(UserProfile), function (userProfile) { return userProfile.user.get('username') === username; }); if (!userProfile) userProfile = new UniqueModel(UserProfile, { username: username }); userProfile.ensureFetched(); var view = new ProfileView({ model: userProfile, activeTab: feedName, subTab: subFeedName, }); this.ensureTab(view, '/by/' + username + '/'); this.layout.showView(view); }, home: function (feedName, callback) { var isKnownToBeLoggedOut = Session.isKnownToBeLoggedOut(); if (isKnownToBeLoggedOut) { if (parseQueryString().email_verified) // Always try to login in users who have the email_verified param // in their url, so we can send them through onboarding if necessary NavigationUtils.navigateToLogin(); else // Logged out users who were not sent to home via an email verification // link should be taken to the main homepage NavigationUtils.navigateToHomepage(); return; } this.ensureOnboardingComplete(function () { // If we get here and we aren't on the / home page path, // then rewrite the URL to be correct. NavigationUtils knows // how to rewrite the /home/ to /. if (window.location.pathname === '/home/') NavigationUtils.navigate(window.location, { replace: true, silent: true }); var model, contentView; if (isKnownToBeLoggedOut) { var exploreChannelCollection = new ChannelCollection([], { listName: 'explore', }); exploreChannelCollection.ensureFetched(); contentView = new AnonHomepageView({ collection: exploreChannelCollection, }); this.layout.showView(contentView, { lightBackground: true }); } else { model = new UniqueModel(HomepageProfile, { id: 'home-view-model' }); model.ensureFetched(); contentView = new HomepageView({ model: model, activeTab: feedName, onboardingWithoutChannels: this.onboardingWithoutChannels, }); this.ensureTab(contentView, '/'); this.layout.showView(contentView); } // Check that callback is a function before calling. // This is necessary since `home` is called by the router, // which may pass a query string as an argument. if (_.isFunction(callback)) callback(); this.cleanLocalStorage(); }); }, // Previously the Home feed view used local storage to determine whether // or not to show a banner message to the user. With the removal of those // banner views we can clean up local storage that they were using. cleanLocalStorage: function () { if (!localStorageSupported) return; try { window.localStorage.removeItem('dhome_v2oo'); window.localStorage.removeItem('whatsNewViewsRemaining'); window.localStorage.removeItem('v2SwitchoverViewsRemaining'); } catch (err) { /* pass */ } }, account: function () { return NavigationUtils.navigate('/home/settings/account/', { trigger: true, replace: true }); }, notifications: function () { return NavigationUtils.navigate('/home/settings/email/', { trigger: true, replace: true }); }, ensureAdmin: function (routeHandler, args) { var session = this.session; // If we got here but the session hasn't been // fetched, fetch the session first, and then try again if (!session.fetched) { return void session.fetch().then( _.bind(this.ensureAdmin, this, routeHandler, args), _.bind(this.notFound, this)); } // Checks if session user is a global admin, and // checks Jones config for any additional accounts if (!auth.getFromCookie().staff) return this.notFound(); return routeHandler.apply(this, args); }, followChannel: function (channelId) { if (channelId === 'disqusfun') return this.ensureAdmin(this._followChannel, arguments); return this._followChannel.apply(this, arguments); }, _followChannel: function (channelId) { // Follow action will fail and prompt user to log in, so check first, before // replacing the url, so that the login page will have the correct return url this.ensureAuthenticated(function () { var channel = new UniqueModel(Channel, { slug: channelId }); if (channel.primaryForum && !channel.primaryForum.get('isFollowing')) { channel.primaryForum.follow(); } else { this.listenToOnce(channel, 'changeRelated:primaryForum', function () { if (!channel.primaryForum.get('isFollowing')) channel.primaryForum.follow(); }); } NavigationUtils.navigate('/home/channel/' + channelId + '/', { trigger: true, replace: true }); }); }, channels: function (channelId) { if (channelId !== 'discussdisqus') return this.navigateToLandingPage({ replace: true }); if (channelId === 'disqusfun' || channelId === 'gabalafou-test') return this.ensureAdmin(this._channels, arguments); return this._channels.apply(this, arguments); }, channelTopics: function (channelId, topicName) { if (channelId !== 'discussdisqus') return this.navigateToLandingPage({ replace: true }); return this.channels(channelId, 'topic-' + topicName); }, _channels: function (channelId, feedName) { var channelProfile = new UniqueModel( ChannelProfile, { id: channelId, // must have id for UniqueModel channel: channelId, } ); channelProfile.ensureFetched(); var contentView = new ChannelProfileView({ model: channelProfile, activeTab: feedName, }); this.ensureTab(contentView, 'home/channel/' + channelId + '/'); this.layout.showView(contentView); }, _forums: function (forumId, feedName) { var forumProfile = new UniqueModel( ForumProfile, { id: forumId, } ); var forum = forumProfile.forum; var self = this; // If this is the primary forum for a channel, redirect to the channel page if (forum.channel) { NavigationUtils.navigate('/home/channel/' + forum.channel.id + '/', { trigger: true, replace: true }); return; } forum.fetch().then(function () { if (forum.channel) self._forums(forumId); }, this.notFound); forumProfile.ensureFetched(); var view = new ForumProfileView({ model: forumProfile, activeTab: feedName, onboardingWithoutChannels: this.onboardingWithoutChannels, }); this.ensureTab(view, '/home/forum/' + forumId + '/'); this.layout.showView(view); }, forums: function (forumId) { this.preventBlacklisted({ forum: forumId }); if (forumId === 'disqus-disqusfun') return this.ensureAdmin(this._forums, arguments); return this._forums.apply(this, arguments); }, topics: function () { return this.navigateToLandingPage({ replace: true }); }, inbox: function (feedName) { var view; var notifications = this.session.getOrCreateNotifications(); // Kick off fetch(es) for items needed on the notifications view. // Notifications and counters will have already been fetched by // the Nav bar notifications.ensureFetchedRecommendations(); // If the user isn't logged in via Disqus or an SSO account (via embed sidebar), show the logged out // notifications inbox. if (!this.isSSOAuthenticated && Session.isKnownToBeLoggedOut()) { view = new NotificationsLoggedOutView({ model: notifications, onboardingWithoutChannels: this.onboardingWithoutChannels, }); } else { if (window.location.search.indexOf('email_verified') > -1) { var message = strings.interpolate( gettext('Your email\'s verified! Have a look around -- %(Disqus)s is a lot more than comments'), { Disqus: 'Disqus' } ); this.messages.push({ message: message }); } view = new NotificationsView({ model: notifications, activeTab: feedName, disableMOTD: this.disableMOTD, onboardingWithoutChannels: this.onboardingWithoutChannels, }); } this.ensureTab(view, '/home/inbox/'); this.layout.showView(view); }, _showDiscussionForm: function (forumId, options) { var discussionForm = new DiscussionCreateView({ session: this.session, model: new DiscussionCreateViewModel({}, _.extend({ forum: forumId }, options)), }); this.listenTo(discussionForm, 'createThread', function () { var thread = discussionForm.getThread(); // Add the recently created thread to an extensible collection so that embedly // requests (if any) will be made before showing the newly created discussion. new ExtensibleCollection().add(thread); NavigationUtils.navigate(thread.getDiscussionRoute(), { trigger: true, }); }); this.listenTo(discussionForm, 'cancelThread', function () { NavigationUtils.navigate('home/' + discussionForm.getForum().getForumRoute(), { trigger: true, }); }); this.layout.showView(discussionForm); }, startDiscussion: function (forumId) { // return if a non discussdisqus channel if (forumId.indexOf('channel-') !== -1 && forumId !== 'channel-discussdisqus') return this.navigateToLandingPage({ replace: true }); // Must be logged in and on the primary forum for a channel in order to create a discussion return this.ensureAuthenticated(function () { this.ensureCanCreateOnChannel(function () { this._showDiscussionForm.apply(this, arguments); }, forumId); }, arguments); }, editDiscussion: function (forumId, threadSlug) { // return if a non discussdisqus channel if (forumId.indexOf('channel-') !== -1 && forumId !== 'channel-discussdisqus') return this.navigateToLandingPage({ replace: true }); var session = this.session; var thread = Thread.getOrCreateThread(forumId, threadSlug); var fetchPromises = [thread.getEditPermissions(session)]; // If we got here but the session hasn't been // fetched, fetch the session first, and then try again if (thread.shouldFetch()) fetchPromises.push(thread.fetch()); return $.when.apply($, fetchPromises).then( _.bind(function (canEdit) { if (canEdit) return this._showDiscussionForm(forumId, { thread: thread }); return void NavigationUtils.navigate(thread.getDiscussionRoute(), { trigger: true, replace: true }); }, this), _.bind(this.notFound, this) ); }, discussionPage: function (forumId, threadSlug, activeTab, options) { options = options || {}; var thread = Thread.getOrCreateThread(forumId, threadSlug, options.threadFetchOptions); var model = DiscussionViewModel.getOrCreate({}, { channel: options.channel, thread: thread, }); var discussionPage = new DiscussionPageView(_.extend({ model: model, session: this.session, activeTab: activeTab, app: this, }, options)); this.layout.showView(discussionPage, { lightBackground: true }); }, _discussion: function (forumId, threadSlug, activeTab, options) { this.preventBlacklisted({ forum: forumId }); if (forumId === 'disqus-disqusfun') return this.ensureAdmin(this.discussionPage, [forumId, threadSlug, activeTab, options]); return this.discussionPage(forumId, threadSlug, activeTab, options); }, discussion: function (forumId, threadSlug, activeTab) { if (forumId.indexOf('channel-') !== -1 && forumId !== 'channel-discussdisqus') return this.navigateToLandingPage({ replace: true }); return this._discussion(forumId, threadSlug, activeTab); }, channelDiscussion: function (channelId, forumId, threadSlug, activeTab, options) { if (forumId.indexOf('channel-') !== -1 && forumId !== 'channel-discussdisqus') return this.navigateToLandingPage({ replace: true }); options = options || {}; var channel = new UniqueModel(Channel, { slug: channelId }); // Must fetch this for network threads linked through a channel. // User generated threads will end up fetching this again as // part of the forum fetch, but network threads won't, and they // need channel details for this view. if (channel.shouldFetch()) channel.fetch(); return this._discussion(forumId, threadSlug, activeTab, { channel: channel, threadFetchOptions: { data: { related: ['author'], }, }, noEmbed: options.noEmbed, }); }, // Native = no embed, iframe-less comments section nativeDiscussion: function (forumId, threadId, activeTab) { if (forumId.indexOf('channel-') !== -1 && forumId !== 'channel-discussdisqus') return this.navigateToLandingPage({ replace: true }); return this._discussion(forumId, threadId, activeTab, { noEmbed: true, }); }, // Native = no embed, iframe-less comments section nativeChannelDiscussion: function (channelId, forumId, threadSlug, activeTab) { if (channelId !== 'discussdisqus') return this.navigateToLandingPage({ replace: true }); return this.channelDiscussion(channelId, forumId, threadSlug, activeTab, { noEmbed: true, }); }, notFound: NavigationUtils.notFoundRouteHandler, }); return Router; }); // https://c.disquscdn.com/next/16dccc7/home/js/Router.js