web-client/app/scripts/app.js
'use strict';
// TODO(mkhatib): Seperate these into config/routes.js and
// config/interceptors/httpInterceptors.js and add tests for them.
// TODO(mkhatib): Move the autogenerated appConfig.js to config/constants.js.
angular.module('webClientApp', [
'ngAnimate',
'ngCookies',
'ngLocale',
'ngResource',
'ngSanitize',
'ui.router',
'AppConfig',
'truncate',
'angulartics',
'angulartics.google.analytics',
'angularFileUpload',
'angular-loading-bar',
'ipCookie',
'ng-token-auth',
'monospaced.elastic'
])
/**
* Routing.
*/
.config(['$stateProvider', '$urlMatcherFactoryProvider','$locationProvider', '$urlRouterProvider',
function ($stateProvider, $urlMatcherFactoryProvider, $locationProvider, $urlRouterProvider) {
// Make a trailing slash optional
$urlMatcherFactoryProvider.strictMode(false);
// Example of using function rule as param
$urlRouterProvider.otherwise(function($injector, $location){
var state = $injector.get('$state');
state.go('app.404');
return $location.path();
});
$urlRouterProvider.rule(function ($injector, $location) {
var path = $location.url();
// check to see if the path already has a slash where it should be
if (path[path.length - 1] === '/' || path.indexOf('/?') > -1) {
return;
}
if (path.indexOf('?') > -1) {
return path.replace('?', '/?');
}
return path + '/';
});
// Set the default route to to to .popular child state
$urlRouterProvider.when('/', 'articles/list/popular/');
$stateProvider
.state('app', {
abstract: true,
url: '/'
})
.state('app.articles', {
abstract: true,
url: 'articles/',
views: {
'content@': {
controller: 'MainCtrl',
templateUrl: 'views/main.html'
}
}
})
.state('app.articles.list', {
url: 'list/:order/',
templateUrl: 'views/partials/_stream.html',
controller: 'StreamCtrl',
resolve: {
articles: ['Article', '$stateParams', function(Article, $stateParams) {
return Article.query({'order': $stateParams.order}).$promise;
}]
}
})
.state('app.articles.show', {
url: ':articleId/',
views: {
'content@': {
templateUrl: 'views/articles/show.html',
controller: 'ArticleCtrl'
}
},
resolve: {
article: ['Article', '$stateParams', '$state', function(Article, $stateParams, $state) {
return Article.get({
'articleId': $stateParams.articleId,
'next_count': 5
}, function(article) {
return article;
}, function() {
$state.go('app.404');
}).$promise;
}]
}
})
.state('app.articles.edit', {
url: ':articleId/edit/',
views: {
'content@': {
templateUrl: 'views/articles/edit.html',
controller: 'EditArticleCtrl'
}
},
resolve: {
user: ['$rootScope', '$auth', function($rootScope, $auth) {
return $auth.validateUser().then(function(user) {
return user;
}).catch(function() {
});
}],
article: ['Article', '$stateParams', '$state', 'user', function(Article, $stateParams, $state, user) {
return Article.get({'articleId': $stateParams.articleId}, function(article) {
if (article && article.body) {
$state.go('app.articles.show', { articleId: article.id });
} else if(parseInt(user.id) === parseInt(article.user.id)) {
return article;
} else {
$state.go('app.articles.show', {articleId: article.id});
}
}).$promise;
}]
}
})
.state('app.login', {
url: 'login/',
views: {
'content@': {
templateUrl: 'views/login.html',
controller: 'LoginCtrl'
}
},
resolve: {
requireNoAuth: ['$auth', '$state', function($auth, $state) {
return $auth.validateUser().then(function(user) {
if(user) {
$state.go('app.publishers.profile.user.published', { userId: user.id});
}
}, function() {
return;
}).$promise;
}]
}
})
.state('app.publishers', {
url: 'publishers/',
views: {
'content@': {
templateUrl: 'views/publishers/show.html',
controller: 'PublishersCtrl'
}
},
resolve: {
publishers: ['User', function(User) {
return User.query().$promise;
}]
}
})
.state('app.publishers.profile', {
abstract: true,
url: 'profile/',
views: {
'content@': {
templateUrl: 'views/profiles/show.html',
controller: 'ProfileInitCtrl'
}
},
})
.state('app.publishers.profile.user', {
url: ':userId/',
templateUrl: 'views/profiles/body.html',
controller: 'ProfileCtrl',
resolve: {
profile: ['User', '$stateParams', function(User, $stateParams) {
return User.get({'userId': $stateParams.userId}).$promise;
}],
publishers: ['User', '$rootScope', '$q', '$stateParams', function(User, $rootScope, $q, $stateParams) {
// Only load publishers when coming from a non-profile state.
if ($rootScope.previousState &&
$rootScope.previousState.name.indexOf('app.publishers.profile') !== -1) {
var deferred = $q.defer();
deferred.resolve();
return deferred.promise;
}
return User.query({
'pivot_id': $stateParams.userId,
'after_pivot_count': 10,
'before_pivot_count': 10,
'order_dir': 'ASC',
'order': 'published_articles_count',
'include_pivot': true
}).$promise;
}],
articles: ['UserArticle', '$stateParams', function(UserArticle, $stateParams) {
return UserArticle.query({'userId': $stateParams.userId}).$promise;
}]
}
})
.state('app.publishers.profile.user.edit', {
url: 'edit/',
views: {
'content@': {
templateUrl: 'views/profiles/edit.html',
controller: 'EditProfileCtrl'
}
},
resolve: {
canEdit: ['$auth', '$state', '$stateParams', function($auth, $state, $stateParams) {
return $auth.validateUser().then(function(user) {
if(parseInt(user.id) === parseInt($stateParams.userId)) {
return;
} else {
$state.go('app.publishers.profile.user.published', {userId: $stateParams.userId});
}
}, function() {
$state.go('app.publishers.profile.user.published', {userId: $stateParams.userId});
});
}]
}
})
.state('app.publishers.profile.user.published', {
url: 'published/',
templateUrl: 'views/profiles/stream.html',
controller: 'ProfileCtrl'
})
.state('app.publishers.profile.user.drafts', {
url: 'drafts/',
templateUrl: 'views/profiles/stream.html',
controller: 'DraftCtrl',
resolve: {
drafts: ['$auth', '$stateParams', 'UserDraft', function($auth, $stateParams, UserDraft) {
return $auth.validateUser().then(function(user) {
if(user &&
parseInt(user.id) === parseInt($stateParams.userId)) {
return UserDraft.query({}).$promise;
}
});
}]
}
})
.state('app.publishers.profile.user.stats', {
url: 'stats/',
templateUrl: 'views/profiles/stats.html',
controller: 'StatCtrl',
resolve: {
stats: ['$auth', '$stateParams', 'ArticleStats', function($auth, $stateParams, ArticleStats) {
return $auth.validateUser().then(function(user) {
if(user && parseInt(user.id) === parseInt($stateParams.userId)) {
return ArticleStats.query({}).$promise;
}
});
}]
}
})
.state('app.publishers.profile.user.recommended', {
url: 'recommended/',
templateUrl: 'views/profiles/stream.html',
controller: 'RecommendationCtrl',
resolve: {
recommendations: ['UserRecommendation', '$stateParams', function(UserRecommendation, $stateParams) {
return UserRecommendation.query({'userId': $stateParams.userId}).$promise;
}]
}
})
.state('app.publishers.profile.user.discussions', {
url: 'discussions/',
templateUrl: 'views/profiles/stream.html',
controller: 'DiscussionCtrl',
resolve: {
comments: ['UserComment', '$stateParams', function(UserComment, $stateParams) {
return UserComment.query({'userId': $stateParams.userId}).$promise;
}]
}
})
.state('app.categories', {
abstract: true,
url: 'categories/:categoryId/',
})
.state('app.categories.articles', {
abstract: true,
url: 'articles/',
views: {
'content@': {
templateUrl: 'views/categories/show.html',
controller: 'CategoryCtrl'
}
},
resolve: {
category: ['Category', '$stateParams', '$state', function(Category, $stateParams, $state) {
return Category.get({'categoryId': $stateParams.categoryId}, function(category) {
return category;
}, function() {
$state.go('app.404');
});
}],
topics: ['Topic', '$stateParams', function(Topic, $stateParams) {
return Topic.query({'categoryId': $stateParams.categoryId}).$promise;
}]
}
})
.state('app.categories.articles.list', {
url: ':order/',
templateUrl: 'views/partials/_stream.html',
controller: 'StreamCtrl',
resolve: {
articles: ['CategoryArticle', '$stateParams', function(CategoryArticle, $stateParams) {
return CategoryArticle.query({
'categoryId': $stateParams.categoryId,
'order': $stateParams.order
}).$promise;
}]
}
})
.state('app.categories.topic', {})
.state('app.categories.topic.articles', {
url: 'topics/:topicId/articles/',
abstract: true,
views: {
'content@': {
templateUrl: 'views/topics/show.html',
controller: 'TopicCtrl'
}
},
resolve: {
topic: ['Topic', '$state', '$stateParams', function(Topic, $state, $stateParams) {
return Topic.get({
'categoryId': $stateParams.categoryId,
'topicId': $stateParams.topicId
}, function(topic) {
return topic;
}, function() {
$state.go('app.404');
}).$promise;
}]
}
})
.state('app.categories.topic.articles.list', {
url: ':order/',
templateUrl: 'views/partials/_stream.html',
controller: 'StreamCtrl',
resolve: {
articles: ['TopicArticle', '$state', '$stateParams', function(TopicArticle, $state, $stateParams) {
return TopicArticle.query({
'categoryId': $stateParams.categoryId,
'topicId': $stateParams.topicId,
'order': $stateParams.order
}, function(articles) {
return articles;
}, function() {
$state.go('app.404');
}).$promise;
}]
}
})
.state('app.signup', {
url: 'signup/',
views: {
'content@': {
templateUrl: 'views/signup.html',
controller: 'SignupCtrl'
}
},
resolve: {
requireNoAuth: ['$auth', '$state', function($auth, $state) {
return $auth.validateUser().then(function(user) {
if(user) {
$state.go('app.publishers.profile.user.published', { userId: user.id});
}
}, function() {
return;
}).$promise;
}]
}
})
.state('app.reset', {
url: 'accounts/reset_password/',
views: {
'content@': {
templateUrl: 'views/accounts/reset_password.html',
controller: 'ResetPasswordController'
}
},
resolve: {
requireNoAuth: ['$auth', '$state', function($auth, $state) {
return $auth.validateUser().then(function(user) {
if(user) {
$state.go('app.publishers.profile.user.published', { userId: user.id});
}
}, function() {
return;
}).$promise;
}]
}
})
.state('app.updatePassword', {
url: 'accounts/update_password/',
views: {
'content@': {
templateUrl: 'views/accounts/update_password.html',
controller: 'UpdatePasswordController'
}
},
resolve: {
requireNoAuth: ['$auth', '$state', function($auth, $state) {
return $auth.validateUser().catch(function() {
$state.go('app.articles.list', {'order': 'popular'});
}).$promise;
}]
}
})
.state('app.admin', {
url: 'admin/',
views: {
'content@': {
templateUrl: 'views/admin/dashboard.html'
}
},
resolve: {
user: ['$auth', '$state', function($auth, $state) {
return $auth.validateUser().then(function(user) {
if(user.role === 'admin') {
return user;
} else {
$state.go('app.404');
}
}, function() {
$state.go('app.404');
}).$promise;
}]
}
})
.state('app.admin.categories', {
url: 'manage/categories/',
views: {
'content@': {
templateUrl: 'views/admin/manage/categories.html',
controller: 'ManageCategoriesCtrl'
}
}
})
.state('app.redirects', {})
.state('app.redirects.profiles', {
url: 'profiles/:userId/',
onEnter: ['$state', '$stateParams', function ($state, $stateParams) {
$state.transitionTo('app.publishers.profile.user.published', {
userId: $stateParams.userId
});
}]
})
.state('app.redirects.categories', {
url: 'categories/:categoryId/',
onEnter: ['$state', '$stateParams', function ($state, $stateParams) {
$state.transitionTo('app.categories.articles.list', {
categoryId: $stateParams.categoryId,
order: 'popular'
});
}]
})
.state('app.redirects.topics', {
url: 'categories/:categoryId/topics/:topicId/',
onEnter: ['$state', '$stateParams', function ($state, $stateParams) {
$state.transitionTo('app.categories.topic.articles.list', {
categoryId: $stateParams.categoryId,
topicId: $stateParams.topicId,
order: 'popular'
});
}]
})
.state('app.404', {
url: '404/',
views: {
'content@': {
templateUrl: 'views/404.html'
}
}
});
}])
.factory('unAuthenticatedInterceptor', ['$location', '$q', '$rootScope',
function ($location, $q, $rootScope) {
return {
'request': function(config) {
return config;
},
'requestError': function() {
// pass
},
'response': function(response) {
return response;
},
'responseError': function(response) {
if (response.status === 401) {
var previous = $location.path();
$rootScope.$broadcast('showLoginDialog', {'prev': previous});
return $q.reject(response);
}
else {
return $q.reject(response);
}
}
};
}])
/**
* Sets up authentication for ng-token-auth.
*/
.config(['$authProvider', 'API_HOST', function($authProvider, API_HOST) {
$authProvider.configure({
apiUrl: '//' + API_HOST,
omniauthWindowType: 'newWindow',
confirmationSuccessUrl: 'https://' + window.location.host + '/login',
passwordResetSuccessUrl: ('https://' + window.location.host +
'/accounts/update_password'),
authProviderPaths: {
facebook: '/auth/facebook',
gplus: '/auth/gplus'
}
});
}])
/**
* Intercept every http request and check for 401 Unauthorized
* error. Clear the current user and redirect to /login page.
*/
.config(['$httpProvider', '$locationProvider', function ($httpProvider, $locationProvider) {
$httpProvider.interceptors.push('unAuthenticatedInterceptor');
$locationProvider.html5Mode(true).hashPrefix('!');
}])
/**
* Allow embedding specific sites.
*/
.config(['$sceDelegateProvider', function ($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist([
// Allow same origin resource loads.
'self',
// Allow loading from YouTube domain.
'http://www.youtube.com/embed/**',
'https://www.youtube.com/embed/**'
]);
}])
/**
* Disable the spinner for angular-loading-bar.
*/
.config(['cfpLoadingBarProvider', function(cfpLoadingBarProvider) {
cfpLoadingBarProvider.includeSpinner = false;
}])
/**
* Disable automatic page collection. We do our own pageviews analytics
* collection to allow for page titles collection.
*/
.config(['$analyticsProvider', function($analyticsProvider) {
$analyticsProvider.virtualPageviews(false);
}])
/**
* Everytime the route change check if the user need to login.
*/
.run(['$location', '$rootScope', '$analytics', 'Category', 'GA_TRACKING_ID', 'Article', '$state', '$timeout',
function ($location, $rootScope, $analytics, Category, GA_TRACKING_ID, Article, $state, $timeout) {
// ga is the Google analytics global variable.
if (window.ga) {
ga('create', GA_TRACKING_ID);
}
$rootScope.linkPrefix = 'https://' + document.location.host;
/**
* Holds data about page-wide attributes. Like pages title.
*/
$rootScope.page = {
title: 'منصة النشر العربية',
description: 'منصة نشر متخصصة باللغة العربية مفتوحة المصدر',
image: '//' + document.location.host + '/images/manshar@200x200.png'
};
/**
* Load categories once for all application
*/
$rootScope.categories = Category.query();
/**
* Create new article
*/
$rootScope.createNewArticle = function() {
Article.save({
article: { published: false }
}, function(resource) {
$analytics.eventTrack('Article Created', {
category: 'Article'
});
$state.go('app.articles.edit', { articleId: resource.id });
}, function(response) {
$analytics.eventTrack('Article Create Error', {
category: 'Article',
label: angular.toJson(response.errors)
});
$state.go('app');
});
};
/**
* Shows the login dialog.
* @param {string} optPrev Optional previous path to go back to after login.
*/
$rootScope.showLoginDialog = function(optPrev) {
$rootScope.$broadcast('showLoginDialog', {
'prev': optPrev
});
};
/**
* Returns true if the passed user is the same user that is referenced
* in the resource. This assumes that the resource always have a user
* property, otherwise it'll return false.
* @param {Object} user The object representing the user data.
* @param {Object} resource The object representing the resource (e.g. Article).
* @returns {boolean} true if the user is the owner of the resource.
*/
$rootScope.isOwner = function (user, resource) {
var id = user && parseInt(user.id);
return (!!user && !!resource && !!resource.user &&
id === resource.user.id);
};
$rootScope.$on('$stateChangeStart', function(
event, toState, toParams, fromState) {
$rootScope.previousState = fromState;
});
$rootScope.$on('$stateChangeSuccess', function() {
if($state.current.name.indexOf('profile') > -1 ||
$state.current.name === 'app.articles.show' ||
$state.current.name === 'app.articles.edit') {
$rootScope.hideLogoText = true;
} else {
$rootScope.hideLogoText = false;
}
$timeout(function() {
var path = $location.path();
if (path[path.length - 1] === '/' || path.indexOf('/?') > -1) {
// pass.
} else if (path.indexOf('?') > -1) {
path = path.replace('?', '/?');
} else {
path = path + '/';
}
ga('send', 'pageview', {'page': path});
}, 200);
});
}]);