/* //--------------------------------------------------------------------------------------------------// //---------------------------------------- Table Of Contents ---------------------------------------// //--------------------------------------------------------------------------------------------------// --------------------------------------------------------------- # Config # --------------------------------------------------------------- // --------------------------------------------------------------- # Filters # --------------------------------------------------------------- isLoggedIn isLoggedOut isAdmin canView canPost canEditPost canEditComment hasCompletedProfile --------------------------------------------------------------- # Controllers # --------------------------------------------------------------- PostsListController PostPageController --------------------------------------------------------------- # Routes # --------------------------------------------------------------- 1) Paginated Lists ---------------------- Top New Best Pending Categories 2) Digest -------------------- Digest 3) Posts -------------------- Post Page Post Page (scroll to comment) Post Edit Post Submit 4) Comments -------------------- Comment Page Comment Edit Comment Submit 5) Users -------------------- User Profie User Edit Forgot Password Account All Users Unsubscribe (from notifications) Sign Up Sign In 6) Misc Routes -------------------- Settings Categories Toolbox 7) Server-side -------------------- API RSS */ // uncomment to disable FastRender var FastRender = {RouteController: RouteController, onAllRoutes: function() {}}; //--------------------------------------------------------------------------------------------------// //--------------------------------------------- Config ---------------------------------------------// //--------------------------------------------------------------------------------------------------// preloadSubscriptions.push('settings'); preloadSubscriptions.push('currentUser'); preloadSubscriptions.push('allUsersAdmin'); Router.configure({ layoutTemplate: 'layout', loadingTemplate: 'loading', notFoundTemplate: 'not_found', waitOn: function () { return _.map(preloadSubscriptions, function(sub){ // can either pass strings or objects with subName and subArguments properties if (typeof sub === 'object'){ Meteor.subscribe(sub.subName, sub.subArguments); }else{ Meteor.subscribe(sub); } }); } }); //--------------------------------------------------------------------------------------------------// //--------------------------------------------- Filters --------------------------------------------// //--------------------------------------------------------------------------------------------------// Router._filters = { isReady: function(pause) { if (!this.ready()) { // console.log('not ready') this.render('loading'); pause(); }else{ // console.log('ready') } }, resetScroll: function () { var scrollTo = window.currentScroll || 0; $('body').scrollTop(scrollTo); $('body').css("min-height", 0); }, isLoggedIn: function(pause) { if (!(Meteor.loggingIn() || Meteor.user())) { throwError(i18n.t('Please Sign In First.')); this.render('signin'); pause(); } }, isLoggedOut: function(pause) { if(Meteor.user()){ this.render('already_logged_in'); pause(); } }, isAdmin: function(pause) { if(!this.ready()) return; if(!isAdmin()){ this.render('no_rights'); pause(); } }, canView: function(pause) { if(!this.ready() || Meteor.loggingIn()){ this.render('loading'); pause(); }else if (!canView()) { this.render('no_rights'); pause(); } }, canPost: function (pause) { if(!this.ready() || Meteor.loggingIn()){ this.render('loading'); pause(); }else if(!canPost()){ throwError(i18n.t("Sorry, you don't have permissions to add new items.")); this.render('no_rights'); pause(); } }, canEditPost: function(pause) { if(!this.ready()) return; // Already subscribed to this post by route({waitOn: ...}) var post = Posts.findOne(this.params._id); if(!currentUserCanEdit(post)){ throwError(i18n.t("Sorry, you cannot edit this post.")); this.render('no_rights'); pause(); } }, canEditComment: function(pause) { if(!this.ready()) return; // Already subscribed to this commit by CommentPageController var comment = Comments.findOne(this.params._id); if(!currentUserCanEdit(comment)){ throwError(i18n.t("Sorry, you cannot edit this comment.")); this.render('no_rights'); pause(); } }, hasCompletedProfile: function(pause) { if(!this.ready()) return; var user = Meteor.user(); if (user && ! userProfileComplete(user)){ this.render('user_email'); pause(); } }, setTitle: function() { // set title var title = getSetting("title"); var tagline = getSetting("tagline"); document.title = (tagline ? title+': '+tagline : title) || ""; } }; var filters = Router._filters; var coreSubscriptions = new SubsManager({ // cache recent 50 subscriptions cacheLimit: 50, // expire any subscription after 30 minutes expireIn: 30 }); if(Meteor.isClient){ // Load Hooks Router.onRun( function () { Session.set('categorySlug', null); // if we're not on the search page itself, clear search query and field if(getCurrentRoute().indexOf('search') == -1){ Session.set('searchQuery', ''); $('.search-field').val('').blur(); } }); // Before Hooks // Router.onBeforeAction(filters.isReady); Router.onBeforeAction(clearSeenErrors); Router.onBeforeAction(filters.canView); Router.onBeforeAction(filters.hasCompletedProfile); Router.onBeforeAction(filters.isLoggedIn, {only: ['comment_reply','post_submit']}); Router.onBeforeAction(filters.isLoggedOut, {only: ['signin', 'signup']}); Router.onBeforeAction(filters.canPost, {only: ['posts_pending', 'comment_reply', 'post_submit']}); Router.onBeforeAction(filters.canEditPost, {only: ['post_edit']}); Router.onBeforeAction(filters.canEditComment, {only: ['comment_edit']}); Router.onBeforeAction(filters.isAdmin, {only: ['posts_pending', 'all-users', 'settings', 'toolbox', 'logs']}); // After Hooks Router.onAfterAction(filters.resetScroll, {except:['posts_top', 'posts_new', 'posts_best', 'posts_pending', 'posts_category', 'all-users']}); Router.onAfterAction(analyticsInit); // will only run once thanks to _.once() Router.onAfterAction(analyticsRequest); // log this request with mixpanel, etc Router.onAfterAction(filters.setTitle); // Unload Hooks // } //--------------------------------------------------------------------------------------------------// //------------------------------------------- Controllers ------------------------------------------// //--------------------------------------------------------------------------------------------------// // Controller for all posts lists PostsListController = FastRender.RouteController.extend({ template: getTemplate('posts_list'), onBeforeAction: function () { // take the first segment of the path to get the view, unless it's '/' in which case the view default to 'top' // note: most of the time this.params.slug will be empty this._terms = { view: this.path == '/' ? 'top' : this.path.split('/')[1], limit: this.params.limit || getSetting('postsPerPage', 10), category: this.params.slug }; if(Meteor.isClient) { this._terms.query = Session.get("searchQuery"); } return [ coreSubscriptions.subscribe('postsList', this._terms), coreSubscriptions.subscribe('postsListUsers', this._terms) ]; }, data: function () { this._terms = { view: this.path == '/' ? 'top' : this.path.split('/')[1], limit: this.params.limit || getSetting('postsPerPage', 10), category: this.params.slug }; if(Meteor.isClient) { this._terms.query = Session.get("searchQuery"); } var parameters = getParameters(this._terms), posts = Posts.find(parameters.find, parameters.options); postsCount = posts.count(); Session.set('postsLimit', this._terms.limit); return { postsList: posts, postsCount: postsCount }; }, onAfterAction: function() { var view = this.path == '/' ? 'top' : this.path.split('/')[1]; Session.set('view', view); } }); // Controller for post digest PostsDigestController = FastRender.RouteController.extend({ template: getTemplate('posts_digest'), waitOn: function() { // if day is set, use that. If not default to today var currentDate = this.params.day ? new Date(this.params.year, this.params.month-1, this.params.day) : new Date(), terms = { view: 'digest', after: moment(currentDate).startOf('day').toDate(), before: moment(currentDate).endOf('day').toDate() }; return [ coreSubscriptions.subscribe('postsList', terms), coreSubscriptions.subscribe('postsListUsers', terms) ]; }, data: function() { var currentDate = this.params.day ? new Date(this.params.year, this.params.month-1, this.params.day) : Session.get('today'), terms = { view: 'digest', after: moment(currentDate).startOf('day').toDate(), before: moment(currentDate).endOf('day').toDate() }, parameters = getParameters(terms); Session.set('currentDate', currentDate); return { posts: Posts.find(parameters.find, parameters.options) }; } }); // Controller for post pages PostPageController = FastRender.RouteController.extend({ waitOn: function() { this.postSubscription = coreSubscriptions.subscribe('singlePost', this.params._id); this.commentSubscription = coreSubscriptions.subscribe('postComments', this.params._id); this.postUsersSubscription = coreSubscriptions.subscribe('postUsers', this.params._id); }, post: function() { return Posts.findOne(this.params._id); }, onBeforeAction: function(pause) { if (! this.post()) { if (this.postSubscription.ready()) { this.render('not_found'); return pause(); } this.render('loading'); pause(); } }, data: function() { return this.post(); } }); // Controller for comment pages CommentPageController = FastRender.RouteController.extend({ waitOn: function() { return [ coreSubscriptions.subscribe('singleComment', this.params._id), coreSubscriptions.subscribe('commentUser', this.params._id), coreSubscriptions.subscribe('commentPost', this.params._id) ]; }, data: function() { return { comment: Comments.findOne(this.params._id) }; }, onAfterAction: function () { window.queueComments = false; } }); // Controller for user pages UserPageController = FastRender.RouteController.extend({ waitOn: function() { return coreSubscriptions.subscribe('singleUser', this.params._idOrSlug); }, data: function() { var findById = Meteor.users.findOne(this.params._idOrSlug); var findBySlug = Meteor.users.findOne({slug: this.params._idOrSlug}); if(typeof findById !== "undefined"){ // redirect to slug-based URL Router.go(getProfileUrl(findById), {replaceState: true}); }else{ return { user: (typeof findById == "undefined") ? findBySlug : findById }; } } }); // Controller for user account editing AccountController = FastRender.RouteController.extend({ waitOn: function() { return coreSubscriptions.subscribe('invites'); }, data: function() { return { user : Meteor.user(), invites: Invites.find({invitingUserId:Meteor.userId()}) }; } }); //--------------------------------------------------------------------------------------------------// //--------------------------------------------- Routes ---------------------------------------------// //--------------------------------------------------------------------------------------------------// Router.map(function() { // -------------------------------------------- Post Lists -------------------------------------------- // // Top this.route('posts_top', { path: '/', waitOn: function () { // NOTE: for now, use waitOn for root path only to get spinner // XXX TODO: get rid of duplicate code between this and controller // take the first segment of the path to get the view, unless it's '/' in which case the view default to 'top' // note: most of the time this.params.slug will be empty this._terms = { view: this.path == '/' ? 'top' : this.path.split('/')[1], limit: this.params.limit || getSetting('postsPerPage', 10), category: this.params.slug }; if(Meteor.isClient) { this._terms.query = Session.get("searchQuery"); } return [ coreSubscriptions.subscribe('postsList', this._terms), coreSubscriptions.subscribe('postsListUsers', this._terms) ]; }, controller: PostsListController }); this.route('posts_top', { path: '/top/:limit?', controller: PostsListController }); // New this.route('posts_new', { path: '/new/:limit?', controller: PostsListController }); // Best this.route('posts_best', { path: '/best/:limit?', controller: PostsListController }); // Pending this.route('posts_pending', { path: '/pending/:limit?', controller: PostsListController }); // TODO: enable /category/new, /category/best, etc. views // Digest this.route('posts_digest', { path: '/digest/:year/:month/:day', controller: PostsDigestController }); this.route('posts_digest', { path: '/digest', controller: PostsDigestController }); // -------------------------------------------- Post -------------------------------------------- // // Post Page this.route('post_page', { path: '/posts/:_id', controller: PostPageController }); this.route('post_page', { path: '/posts/:_id/comment/:commentId', controller: PostPageController, onAfterAction: function () { // TODO: scroll to comment position } }); // Post Edit this.route('post_edit', { template: getTemplate('post_edit'), path: '/posts/:_id/edit', waitOn: function () { return coreSubscriptions.subscribe('singlePost', this.params._id); }, data: function() { return { postId: this.params._id, post: Posts.findOne(this.params._id) }; }, fastRender: true }); // Post Submit this.route('post_submit', {path: '/submit'}); // -------------------------------------------- Comment -------------------------------------------- // // Comment Reply this.route('comment_reply', { template: getTemplate('comment_reply'), path: '/comments/:_id', controller: CommentPageController, onAfterAction: function() { window.queueComments = false; } }); // Comment Edit this.route('comment_edit', { template: getTemplate('comment_edit'), path: '/comments/:_id/edit', controller: CommentPageController, onAfterAction: function() { window.queueComments = false; } }); // -------------------------------------------- Users -------------------------------------------- // // User Profile this.route('user_profile', { template: getTemplate('user_profile'), path: '/users/:_idOrSlug', controller: UserPageController }); // User Edit this.route('user_edit', { template: getTemplate('user_edit'), path: '/users/:_idOrSlug/edit', controller: UserPageController }); // Account this.route('account', { template: getTemplate('user_edit'), path: '/account', controller: AccountController }); // Forgot Password this.route('forgot_password', { template: getTemplate('forgot_password'), }); // All Users this.route('all-users', { template: getTemplate('users'), path: '/all-users/:limit?', waitOn: function() { var limit = parseInt(this.params.limit) || 20; return coreSubscriptions.subscribe('allUsers', this.params.filterBy, this.params.sortBy, limit); }, data: function() { var limit = parseInt(this.params.limit) || 20, parameters = getUsersParameters(this.params.filterBy, this.params.sortBy, limit), filterBy = (typeof this.params.filterBy === 'string') ? this.params.filterBy : 'all', sortBy = (typeof this.params.sortBy === 'string') ? this.params.sortBy : 'createdAt'; Session.set('usersLimit', limit); return { users: Meteor.users.find(parameters.find, parameters.options), filterBy: filterBy, sortBy: sortBy }; }, fastRender: true }); // Unsubscribe (from notifications) this.route('unsubscribe', { template: getTemplate('unsubscribe'), path: '/unsubscribe/:hash', data: function() { return { hash: this.params.hash }; } }); // User Sign-Up this.route('signup', { template: getTemplate('signup'), }); // User Sign-In this.route('signin', { template: getTemplate('signin'), }); // -------------------------------------------- Other -------------------------------------------- // // Settings this.route('settings', { template: getTemplate('settings'), data: function () { // we only have one set of settings for now return { hasSettings: !!Settings.find().count(), settings: Settings.findOne() } } }); // Loading (for testing purposes) this.route('loading', { template: getTemplate('loading'), }); // Toolbox this.route('toolbox',{ template: getTemplate('toolbox'), }); // -------------------------------------------- Server-Side -------------------------------------------- // // Link Out this.route('out', { where: 'server', path: '/out', action: function(){ var query = this.request.query; if(query.url){ var decodedUrl = decodeURIComponent(query.url); var post = Posts.findOne({url: decodedUrl}); if(post){ Posts.update(post._id, {$inc: {clicks: 1}}); } this.response.writeHead(302, {'Location': query.url}); this.response.end(); } } }); }); // adding common subscriptions that's need to be loaded on all the routes // notification does not included here since it is not much critical and // it might have considerable amount of docs if(Meteor.isServer) { FastRender.onAllRoutes(function() { var router = this; _.each(preloadSubscriptions, function(sub){ router.subscribe(sub); }); }); }