import _ from './vendor/underscore.1.5.2.min'; import './vendor/jquery.cookie'; import './vendor/ui-bootstrap-custom-tpls.min'; import './common/directives/frHasError'; import './common/directives/frServerErrors'; import './common/directives/frLadda'; import './common/directives/frFixedHeader'; import './common/directives/frInfiniteScroll'; import './common/services/frSearchCtrl'; import './common/directives/frClickOut'; import './common/directives/frEasyTimeAgo'; import './common/directives/frLitelay'; import './common/services/mediaEmbedder'; import './common/directives/dragScroll'; import './common/directives/scrollTo'; import './common/services/dateFormat'; import LoginController from './common/controllers/LoginController'; window._ = _; // Simple promise polyfill using Angular's $q. Not guaranteed to work correctly. if (typeof window.Promise !== 'function') { window.Promise = angular.injector(['ng']).get('$q'); } /** * Automatically attach CSRF token to AJAX request made with jQuery. */ $.ajaxSetup({ beforeSend: function (xhr, settings) { if (settings.type !== 'GET' && !this.crossDomain) { xhr.setRequestHeader('X-XSRF-TOKEN', $.cookie('XSRF-TOKEN')); } }, }); // jquery extend for :contains() selector - makes exact match $.expr[':'].econtains = function (obj, index, meta) { return (obj.textContent || obj.innerText || $(obj).text() || '').toLowerCase() === meta[3].toLowerCase(); }; // underscore template syntax asp style _.templateSettings = { interpolate: /\{\{(.+?)\}\}/g, evaluate: /\<\@(.+?)\@\>/gim, }; // Document Ready! $(document).ready(function () { hoverMenu({ menuEl: '.bar-nav li', contentEl: '.dropdown-nav' }); hoverMenu({ menuEl: '.main-nav li', contentEl: '.dropdown-nav' }); }); // Functions /* Sets up dropdown menus. * * - Uses data-menu to make connection between menu item and menu content * - Dropdown menu fades in. Also sets .active class to the menu item. * * @param options.menuEl css selector for menu element * @param options.contentEl css selector for menu content element */ function hoverMenu(options) { $(options.menuEl).each(function () { var hidden = true; var menuName = $(this).data('menu'); var contentEl = options.contentEl + '[data-menu="' + menuName + '"]'; var menuEl = options.menuEl + '[data-menu="' + menuName + '"]'; if (menuName === undefined) { return; } $(menuEl) .on('click', function (e) { if (hidden) { openDropdown(); } else { closeDropdown(); } }); function handleOutClicks(e) { // If the click target is not a child of our menu button then we // treat this click as an outside one. if ($(e.target).closest(menuEl).length === 0 || e.keyCode === 27) { closeDropdown(); } } function openDropdown() { // Close the drop down on clicks and touches outside of the dropdown menu and // its content. $('html').on('click touchend keyup', handleOutClicks); // We don't want the clicks or touches on dropdown content to propagate to // and thus close the dropdown. $(contentEl).on('click touchend', function (e) { e.stopPropagation(); }); $('.js-close-dropdown', contentEl).one('click', closeDropdown); $(contentEl).show(); // Processing on next tick other wise transitions will not work // because of `display: none`. setTimeout(function () { $(contentEl).css({ opacity: 1 }); }, 0); // Add active class. $(menuEl).addClass('is-active'); // Set class on html so that we can restyle things accordingly when this dropdown is open. $('html').addClass('is-' + menuName + '-open'); hidden = false; } function closeDropdown() { // Remove all the events that we bound when opening the dropdown. $(contentEl).off('click touchend'); $('html').off('click touchend keyup', handleOutClicks); $(contentEl).css({ opacity: 0 }); // Processing on next tick other wise transitions will not work // because of `display: none`. setTimeout(function () { $(contentEl).css({ display: 'none' }); }, 0); $(menuEl).removeClass('is-active'); // Remove previously set class on html. $('html').removeClass('is-' + menuName + '-open'); hidden = true; } }); } /** * jQquery query string extractor. */ (function ($) { $.QueryString = (function (a) { if (a == '') return {}; var b = {}; for (var i = 0; i < a.length; ++i) { var p = a[i].split('='); if (p.length != 2) continue; b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, ' ')); } return b; }(window.location.search.substr(1).split('&'))); }(jQuery)); // Initializers $(document).ready(function () { if ($.cookie('author')) $("#commenter input[name='author']").val($.cookie('author').replace(/\+/, ' ')); if ($.cookie('email')) $("#commenter input[name='email']").val($.cookie('email')); if ($.cookie('teamname')) $("#commenter input[name='teamname']").val($.cookie('teamname').replace(/\+/g, ' ')); }); /** * Filters out all duplicate items from an array by checking the specified key * @param [key] {string} the name of the attribute of each object to compare for uniqueness if the key is empty, the entire object will be compared if the key === false then no filtering will be performed * @return {array} */ angular.module('ui.filters', []).filter('unique', function () { return function (items, filterOn) { if (filterOn === false) { return items; } if ((filterOn || angular.isUndefined(filterOn)) && angular.isArray(items)) { const newItems = []; const extractValueToCompare = function (item) { if (angular.isObject(item) && angular.isString(filterOn)) { return item[filterOn]; } return item; }; angular.forEach(items, function (item) { let isDuplicate = false; for (let i = 0; i < newItems.length; i++) { if (angular.equals(extractValueToCompare(newItems[i]), extractValueToCompare(item))) { isDuplicate = true; break; } } if (!isDuplicate) { newItems.push(item); } }); items = newItems; } return items; }; }); angular.module('footyroom', [ 'ui.filters', 'mediaEmbedder', 'fr.hasError', 'fr.serverErrors', 'fr.ladda', 'fr.fixedHeader', 'fr.infiniteScroll', 'fr.searchCtrl', 'fr.clickOut', 'ui.bootstrap', 'fr.litelay', 'dragScroll', 'scrollTo', 'fr.dateFormat', 'fr.frEasyTimeAgo', ]) .controller('LoginController', LoginController); /** * Currently not needed, but left here in cases needed in the near future. */ // angular.module('footyroom').run(['cookie', 'geoip', '$q', function (cookie, geoip, $q) { // // Get user's location. // $q.when( // (function () { // if (cookie('country_code') == null) { // return geoip.get().then(function (geo) { // return cookie('country_code', geo.country_code, { expires: 200, path: '/' }); // }); // } // })() // ); // }]); angular.module('footyroom').filter('formation', function () { return function (formation) { formation = _.without(formation, 0); return formation.join('-'); }; }); angular.module('footyroom').filter('reverse', function () { return function (items) { return items.slice().reverse(); }; }); angular.module('footyroom').filter('teamNameAbbr', function () { return function (name) { return name.slice(0, 3); }; }); angular.module('footyroom').controller('AppCtrl', ['$scope', '$rootScope', 'searchCtrl', '$uibModal', function ($scope, $rootScope, searchCtrl, $uibModal) { $rootScope.data = {}; $rootScope.data.stage = window.DataStore.stage || {}; $rootScope.data.match = window.DataStore.match || {}; $rootScope.data.stages = window.DataStore.stages || {}; $rootScope.data.seasons = window.DataStore.seasons || {}; $rootScope.data.tables = window.DataStore.tables || {}; $rootScope.season = window.DataStore.season; $rootScope.stages = {}; $rootScope.ds = window.DataStore; $scope.changeSeason = function () { if ($scope.season.id) { window.location = window.location.pathname + '?stageId=' + $scope.season.id; } else { window.location = window.location.pathname; } }; $scope.changeStage = function () { if ($scope.currentStage.id) { window.location = window.location.pathname + '?stage=' + $scope.currentStage.id; } else { window.location = window.location.pathname; } }; // Build a tree from a flat stages array. (function (stages) { function metaReviver(key, value) { if (key === 'startDate' || key === 'endDate') { return new Date(value); } else if (key === 'enetStageId') { return parseInt(value); } return value; } function parseMeta(meta) { _(meta).each(function (value, key) { meta[key] = metaReviver(key, value); }); return meta || {}; } function propagateTables(stage) { var parent = $rootScope.stages[stage.parent]; if (!parent) { return; } if (stage.childrenHaveTables) { parent.childrenHaveTables = true; } propagateTables(parent); } function propagateDates(stage) { var parent = $rootScope.stages[stage.parent]; var propagate = false; if (!parent) { return; } // Give parent child's startDate and endDate. if (parent.meta.startDate > stage.meta.startDate || !parent.meta.startDate) { propagate = true; parent.meta.startDate = stage.meta.startDate; } if (parent.meta.endDate < stage.meta.endDate || !parent.meta.endDate) { propagate = true; parent.meta.endDate = stage.meta.endDate; } if (propagate) { propagateDates(parent); } } const toRemove = []; /** * Prepare stages for general processing. */ _.each(stages, function (stage, index) { // Create stages by id index. $rootScope.stages[stage.id] = stage; // Process stage meta JSON for quick, easy use. stage.meta = parseMeta(stage.meta); }); /** * Once stage index has been built and meta processed we will go through * stages again to build the tree, define tables and other various * utilities. */ _.each(stages, function (stage, index) { /** * Map tables to stages. */ var standings = _.where($rootScope.data.tables, { enetStageId: stage.meta.enetStageId }); if (standings.length) { stage.standings = standings; stage.haveTables = true; } // Find parent. let parent = $rootScope.stages[stage.parent]; if (!parent) { return; } // Give parent knowledge of child's abilities. if (stage.haveTables) { parent.childrenHaveTables = true; propagateTables(parent); } if (stage.type === 'group') { parent.childrenAreGroups = true; } // Build tree. if (!parent.children) { parent.children = []; } // Reference the stage into its parent children array. parent.children.push(stage); // Mark to remove this stage from root node. // toRemove.push(index); propagateDates(stage); }); // Remove children from root node. _.each(toRemove.reverse(), function (stage) { stages.splice(stage, 1); }); }($scope.data.stages)); // Set current stage. if (window.DataStore.currentStage) { $rootScope.currentStage = window.DataStore.currentStage.type === 'season' ? window.DataStore.currentStage : $scope.stages[window.DataStore.currentStage.id]; } $rootScope.not = function (func) { return function (item) { return !func(item); }; }; $rootScope.incidentIdToName = function (id) { switch (id) { case 'a': return 'Assist'; case 'g': case 'eg': return 'Goal'; case 'og': case 'eog': return 'Own Goal'; case 'p': case 'ep': case 'ps': return 'Penalty'; case 'pm': case 'psm': case 'epm': return 'Penalty Miss'; case 'y': return 'Yellow Card'; case 'y2': return 'Second Yellow'; case 'r': return 'Red Card'; case 'si': return 'Sub In'; case 'so': return 'Sub Out'; default: return id; } }; $scope.hideSidebar = function () { if ($scope.isShowSidebar) { $scope.isShowSidebar = false; $('html').toggleClass('is-sidebar-open'); } }; $scope.toggleSidebar = function () { $scope.isShowSidebar = !$scope.isShowSidebar; $('html').toggleClass('is-sidebar-open'); event.stopPropagation(); }; $scope.fbShare = function ($event) { if (!window.FB) { return; } window.FB.ui({ method: 'share', href: window.location.href, }); $event.preventDefault(); }; $rootScope.openBanModal = function (userId) { $uibModal.open({ template: ``, }); }; searchCtrl($scope); }]); angular.module('footyroom').controller('UserBarController', ['$scope', '$element', function ($scope, $element) { var ctrl = this; ctrl.isUserBarOpen = false; ctrl.open = open; ctrl.close = close; function open() { ctrl.isUserBarOpen = true; } function close() { ctrl.isUserBarOpen = false; } }]); /** * @author Denis Pshenov * * @depends _.path * * @description * _.path underscore mixin. * * Given an `object` and a `path` in form of 'nested.property' it will return a value in the `path`. * * TODO: Decide if there is a better name for this mixin. */ _.mixin({ path: function (object, path) { var derived = object; _.each(path.split('.'), function (part) { derived = derived[part]; }); return derived; }, }); /** * Cookie service. */ angular.module('footyroom').factory('cookie', function () { return $.cookie; }); /** * GeoIP service supported by https://freegeoip.app/ As of this writing the * usage limit is 15,000 API calls per hour. * * @param {String} ip Optional, uses clients ip by default. * @return {Promise} Promise resolves to the following: * { * "ip":"110.164.213.112", * "country_code":"TH", * "country_name":"Thailand", * "region_code":"40", * "region_name":"Krung Thep", * "city":"Bangkok", * "zipcode":"", * "latitude":13.754, * "longitude":100.5014, * "metro_code":"", * "areacode":"" * } */ angular.module('footyroom').service('geoip', ['$http', function ($http) { return { get: function (ip) { return $http.get('https://freegeoip.app/json/' + (ip || '')).then(function (response) { return response.data; }); }, }; }]); /** * Dropdown toggle Twiiter Bootstrap style. */ angular.module('footyroom').directive('dropdownToggle', ['$document', function ($document) { return { link: function (scope, element, attrs) { function closeMenu() { element.parent().removeClass('open'); } element.bind('click', function (e) { e.stopPropagation(); $document.one('click', closeMenu); element.parent().toggleClass('open'); }); element.parent().find('form').on('click', function (e) { e.stopPropagation(); }); }, }; }]);