├── .editorconfig ├── .eslintrc ├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .gitlab-ci.yml ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── Procfile ├── README.md ├── app.json ├── app ├── assets │ ├── dashboard.html │ ├── favicon.ico │ ├── index.html │ ├── logo.png │ └── settings.html ├── js │ ├── application.js │ ├── controllers │ │ ├── dashboard.js │ │ └── settings.js │ └── services │ │ ├── config_manager.js │ │ ├── favico.js │ │ ├── gitlab_manager.js │ │ └── merge_request_fetcher.js └── styles │ ├── _settings.scss │ └── main.scss ├── brunch-config.coffee ├── index.js ├── package-lock.json ├── package.json └── screenshot.png /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain 2 | # consistent coding styles between different editors and IDEs. 3 | 4 | root = true 5 | 6 | [*] 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "rules": {}, 4 | "env": { 5 | "amd": true 6 | }, 7 | "globals": { 8 | "module": false, 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Before filling this issue, search if the bug do not already exists in the database (https://github.com/Hexanet/MergeRequestsCI/issues). 2 | 3 | #### Description 4 | 5 | For a bug: Describe the bug and list the steps you used when the issue occurred. 6 | 7 | For an enhancement or new feature: Describe your needs. 8 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | #### Description 2 | 3 | Please describe the goal of this pull request. 4 | 5 | #### Resume 6 | 7 | * Bug fix: yes/no 8 | * New feature: yes/no 9 | * Fixed tickets: comma-separated list of tickets fixed by the PR, if any 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bower_components/ 2 | /node_modules/ 3 | /public/ 4 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: node:5.11 2 | 3 | pages: 4 | cache: 5 | paths: 6 | - node_modules/ 7 | 8 | script: 9 | - npm install 10 | - ./node_modules/brunch/bin/brunch b --production 11 | artifacts: 12 | paths: 13 | - public 14 | only: 15 | - master 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "8" 5 | - "9" 6 | 7 | cache: 8 | directories: 9 | - node_modules 10 | 11 | script: 12 | - npm install 13 | - npm run build 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Next release 2 | 3 | * Add config to deploy with Heroku 4 | 5 | ## 2.0.1 - 2018/03/13 6 | 7 | * Fix potential security issue in moment.js < 2.19.3 8 | * Fix list order 9 | * Update screenshot 10 | * Fix typo 11 | 12 | ## 2.0.0 - 2017/08/29 13 | 14 | **Added** 15 | * Add a "labels" column 16 | * Ability to hide the column "branch" 17 | * Display number of items in the title 18 | 19 | **Changed** 20 | * Use the new V4 API of GitLab (BC Breaks) 21 | * CI badge now links to latest pipeline 22 | 23 | **Removed** 24 | * Bower is not needed anymore 25 | 26 | ## 1.0.0 - 2016/06/07 27 | 28 | First stable version 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Hexanet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: npm run serve 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MergeRequestsCI 2 | 3 | UI to see all GitLab merge requests of your team in one place. 4 | 5 | ![MergeRequestsCI](screenshot.png) 6 | 7 | [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy) 8 | 9 | ## Requirements 10 | 11 | * [GitLab](https://about.gitlab.com/) >= 9.5 12 | 13 | ## Installation 14 | 15 | ### Clone the project 16 | 17 | ```shell 18 | $ git clone https://github.com/Hexanet/MergeRequestsCI.git 19 | $ cd MergeRequestsCI 20 | ``` 21 | 22 | ### Install dependencies 23 | 24 | ```shell 25 | $ npm install 26 | ``` 27 | 28 | ## Run the server 29 | 30 | After configuration, you have to build the code and launch the server. 31 | 32 | ```shell 33 | $ npm run serve 34 | ``` 35 | 36 | Then open `http://localhost:3000` in your browser. 37 | 38 | ## Credits 39 | 40 | Developed by [Hexanet](http://www.hexanet.fr/). 41 | 42 | ### Inspiration 43 | 44 | Thanks [M6Web](https://github.com/M6Web) ([Github Team Reviewer](https://github.com/M6Web/GithubTeamReviewer)) and [Jean-François Lépine](http://blog.lepine.pro/) ([Taylorisme de la qualité logicielle](http://lanyrd.com/2015/forumphp/sdwzzb/)). 45 | 46 | ## License 47 | 48 | [MergeRequestsCI](https://github.com/Hexanet/MergeRequestsCI) is licensed under the [MIT license](LICENSE). 49 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MergeRequestsCI", 3 | "description": "UI to see all GitLab merge requests of your teams in one place", 4 | "repository": "https://github.com/Hexanet/MergeRequestsCI", 5 | "logo": "https://raw.githubusercontent.com/Hexanet/MergeRequestsCI/master/app/assets/logo.png", 6 | "keywords": ["gitlab"] 7 | } 8 | -------------------------------------------------------------------------------- /app/assets/dashboard.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 26 | 27 | 34 | 41 | 48 | 53 | 56 | 57 | 58 | 61 | 62 | 63 |
Last updateProjectTitleAuthorBranchLabelsHave I voted?CI
{{ mergeRequest.project.name }} 22 | 23 | {{ mergeRequest.author.name }} 24 | 25 | {{ mergeRequest.source_branch }} ⇨ {{ mergeRequest.target_branch }} 28 | 33 | 35 | 36 | 37 | {{ mergeRequest.upvotes }} 38 | 39 | 40 | 42 | 43 | 44 | {{ mergeRequest.downvotes }} 45 | 46 | 47 | 49 | - 50 | 51 | 52 | 54 | {{ mergeRequest.ci.status }} 55 |
59 | No merge request in progress, go back to work! 60 |
64 | -------------------------------------------------------------------------------- /app/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hexanet/MergeRequestsCI/7d983f03563ebb8a5b75bb6acaae5c10446f5e05/app/assets/favicon.ico -------------------------------------------------------------------------------- /app/assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Merge Requests CI {{ titleAddon }} 6 | 7 | 8 | 9 | 10 |
11 |
12 | 17 |
18 |
19 | 22 |
23 |
24 | 25 | 26 |
27 | 28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hexanet/MergeRequestsCI/7d983f03563ebb8a5b75bb6acaae5c10446f5e05/app/assets/logo.png -------------------------------------------------------------------------------- /app/assets/settings.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Settings

4 |
5 |

6 | Authentication failed: please verify your url and private token 7 |

8 | 9 |
10 |
11 |
12 | 13 |
14 |
15 | 16 |
17 |
18 |
19 |
20 | 21 |
22 |
23 | 24 |

25 | Token to authenticate you against GitLab. 26 |
Click here to get your private token.
27 |

28 |
29 |
30 |
31 |
32 | 33 |
34 |
35 | 36 |
37 |
38 |
39 | Display the "Branch" column 40 |
41 | 42 | 43 |
44 |
45 |
46 | Display the "Labels" column 47 |
48 | 49 | 50 |
51 |
52 |
53 |
54 | 55 |
56 |
57 |
58 |
59 | -------------------------------------------------------------------------------- /app/js/application.js: -------------------------------------------------------------------------------- 1 | var angular = require('angular'); 2 | var emojify = require('emojify.js'); 3 | 4 | angular.module('app', [require('angular-tooltips'), require('angular-route'), require('angular-local-storage'), require('angular-moment')]) 5 | 6 | .config(['$routeProvider', 'localStorageServiceProvider', function($routeProvider, localStorageServiceProvider) { 7 | localStorageServiceProvider 8 | .setPrefix('merge-requests-ci'); 9 | 10 | $routeProvider 11 | .when('/', { 12 | templateUrl: 'dashboard.html', 13 | controller: 'DashboardCtrl', 14 | controllerAs: 'vm' 15 | }) 16 | .when('/settings', { 17 | templateUrl: 'settings.html', 18 | controller: 'SettingsCtrl', 19 | controllerAs: 'vm' 20 | }) 21 | .otherwise({ 22 | redirectTo: '/' 23 | }); 24 | }]) 25 | 26 | .filter('emojify', ['$sce', function($sce) { 27 | return function (input) { 28 | if (!input) 29 | return ""; 30 | 31 | return $sce.trustAsHtml(emojify.replace(input)); 32 | }; 33 | }]) 34 | 35 | .service('configManager', ['localStorageService', require('./services/config_manager')]) 36 | .service('gitLabManager', ['configManager', '$http', '$q', require('./services/gitlab_manager')]) 37 | .service('favicoService', require('./services/favico')) 38 | .service('MergeRequestFetcher', ['gitLabManager', 'configManager', '$q', '$http', require('./services/merge_request_fetcher')]) 39 | 40 | .controller('DashboardCtrl', ['$interval', 'MergeRequestFetcher', 'configManager', 'favicoService', require('./controllers/dashboard')]) 41 | .controller('SettingsCtrl', ['gitLabManager', 'configManager', '$location', 'MergeRequestFetcher', require('./controllers/settings')]) 42 | 43 | .run(['$rootScope', 'gitLabManager', '$location', function($rootScope, gitLabManager, $location) { 44 | $rootScope.titleAddon = ''; 45 | 46 | // This events gets triggered on refresh or URL change 47 | $rootScope.$on('$locationChangeStart', function() { 48 | if (!gitLabManager.hasCredentials()) { 49 | $location.path('/settings'); 50 | } 51 | }); 52 | 53 | }]); 54 | -------------------------------------------------------------------------------- /app/js/controllers/dashboard.js: -------------------------------------------------------------------------------- 1 | module.exports = function ($interval, MergeRequestFetcher, configManager, favicoService) { 2 | var vm = this; 3 | vm.refresh = function() { 4 | MergeRequestFetcher.getMergeRequests().then(function(mergeRequests) { 5 | vm.mergeRequests = mergeRequests; 6 | favicoService.badge(mergeRequests.length); 7 | }); 8 | }; 9 | 10 | $interval(function () { 11 | vm.refresh(); 12 | }, configManager.getRefreshRate() * 60 * 1000); 13 | 14 | vm.displayBranchColumn = configManager.displayBranchColumn(); 15 | vm.displayLabelsColumn = configManager.displayLabelsColumn(); 16 | 17 | vm.refresh(); 18 | }; 19 | -------------------------------------------------------------------------------- /app/js/controllers/settings.js: -------------------------------------------------------------------------------- 1 | module.exports = function (gitLabManager, configManager, $location) { 2 | var vm = this; 3 | vm.error = false; 4 | vm.config = { 5 | url: configManager.getUrl(), 6 | private_token: configManager.getPrivateToken(), 7 | refresh_rate: configManager.getRefreshRate(), 8 | display_branch_column: configManager.displayBranchColumn(), 9 | display_labels_column: configManager.displayLabelsColumn() 10 | }; 11 | 12 | vm.save = function(config) { 13 | gitLabManager.authenticate( 14 | config.url, 15 | config.private_token 16 | ).then(function success() { 17 | configManager.setRefreshRate(config.refresh_rate); 18 | configManager.setDisplayBranchColumn(config.display_branch_column); 19 | configManager.setDisplayLabelsColumn(config.display_labels_column); 20 | $location.path("/"); 21 | }, function failure() { 22 | vm.error = true; 23 | }); 24 | 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /app/js/services/config_manager.js: -------------------------------------------------------------------------------- 1 | module.exports = function(localStorageService) { 2 | var configManager = {}; 3 | 4 | configManager.getPrivateToken = function() { 5 | return localStorageService.get('private_token'); 6 | } 7 | 8 | configManager.setPrivateToken = function(privateToken) { 9 | localStorageService.set('private_token', privateToken); 10 | } 11 | 12 | configManager.getUrl = function() { 13 | return localStorageService.get('url'); 14 | } 15 | 16 | configManager.setUrl = function(url) { 17 | localStorageService.set('url', url); 18 | } 19 | 20 | configManager.getRefreshRate = function() { 21 | return localStorageService.get('refresh_rate') || 5; 22 | } 23 | 24 | configManager.setRefreshRate = function(refreshRate) { 25 | localStorageService.set('refresh_rate', refreshRate); 26 | } 27 | 28 | configManager.displayBranchColumn = function() { 29 | var value = localStorageService.get('display_branch_column'); 30 | return value !== null ? value : true; 31 | } 32 | 33 | configManager.setDisplayBranchColumn= function(displayBranchColumn) { 34 | localStorageService.set('display_branch_column', displayBranchColumn); 35 | } 36 | 37 | configManager.displayLabelsColumn = function() { 38 | var value = localStorageService.get('display_labels_column'); 39 | return value !== null ? value : false; 40 | } 41 | 42 | configManager.setDisplayLabelsColumn = function(displayLabelsColumn) { 43 | localStorageService.set('display_labels_column', displayLabelsColumn); 44 | } 45 | 46 | configManager.clearCredentialsValues = function() { 47 | localStorageService.remove('url', 'private_token'); 48 | } 49 | 50 | return configManager; 51 | }; 52 | -------------------------------------------------------------------------------- /app/js/services/favico.js: -------------------------------------------------------------------------------- 1 | var Favico = require('favico.js'); 2 | 3 | module.exports = function() { 4 | var favico = new Favico({ 5 | animation : 'fade' 6 | }); 7 | 8 | this.badge = function(num) { 9 | favico.badge(num); 10 | }; 11 | 12 | this.reset = function() { 13 | favico.reset(); 14 | }; 15 | }; 16 | -------------------------------------------------------------------------------- /app/js/services/gitlab_manager.js: -------------------------------------------------------------------------------- 1 | module.exports = function(configManager, $http, $q) { 2 | var gitLabManager = {}; 3 | 4 | gitLabManager.getUser = function() { 5 | var deferred = $q.defer(); 6 | 7 | if (!gitLabManager.hasCredentials()) { 8 | deferred.reject("Url and/or private token are missing"); 9 | } else { 10 | $http({ 11 | url: configManager.getUrl() + '/api/v4/user', 12 | headers: {'PRIVATE-TOKEN': configManager.getPrivateToken()} 13 | }).then(function(response) { 14 | deferred.resolve(response.data); 15 | }, function(msg) { 16 | deferred.reject(msg); 17 | }); 18 | } 19 | 20 | return deferred.promise; 21 | } 22 | 23 | gitLabManager.hasCredentials = function() { 24 | return configManager.getUrl() && configManager.getPrivateToken(); 25 | } 26 | 27 | gitLabManager.authenticate = function(url, privateToken) { 28 | configManager.setUrl(url); 29 | configManager.setPrivateToken(privateToken); 30 | 31 | var deferred = $q.defer(); 32 | 33 | gitLabManager.getUser().then(function(user) { 34 | deferred.resolve(user); 35 | }, function() { 36 | gitLabManager.logout(); 37 | deferred.reject('Unauthorized'); 38 | }); 39 | 40 | return deferred.promise; 41 | } 42 | 43 | gitLabManager.logout = function() { 44 | configManager.clearCredentialsValues(); 45 | } 46 | 47 | return gitLabManager; 48 | } 49 | -------------------------------------------------------------------------------- /app/js/services/merge_request_fetcher.js: -------------------------------------------------------------------------------- 1 | module.exports = function (gitLabManager, configManager, $q, $http) { 2 | var MergeRequestFetcher = {}; 3 | MergeRequestFetcher.labels = {}; 4 | var authenticatedUser = null; 5 | 6 | var request = function (url) { 7 | return $http({ 8 | url: configManager.getUrl() + '/api/v4' + url, 9 | headers: {'PRIVATE-TOKEN': configManager.getPrivateToken()} 10 | }); 11 | }; 12 | 13 | MergeRequestFetcher.getMergeRequests = function() { 14 | var url = '/merge_requests?state=opened&scope=all&order_by=updated_at'; 15 | return request(url).then(function(response) { 16 | var mergeRequests = response.data; 17 | 18 | return $q.all([ 19 | mergeRequests.map(addProjectToMergeRequest), 20 | mergeRequests.map(addVotesToMergeRequest), 21 | mergeRequests.map(addCiStatusToMergeRequest), 22 | mergeRequests.map(formatLabelsForMergeRequest) 23 | ]).then(function() { 24 | return mergeRequests; 25 | }); 26 | }); 27 | }; 28 | 29 | var addProjectToMergeRequest = function(mergeRequest) { 30 | var url = '/projects/' + mergeRequest.project_id; 31 | return request(url).then(function(response) { 32 | var project = response.data; 33 | mergeRequest.project = {}; 34 | mergeRequest.project.name = project.name_with_namespace; 35 | mergeRequest.project.web_url = project.web_url; 36 | }); 37 | }; 38 | 39 | 40 | var addVotesToMergeRequest = function(mergeRequest) { 41 | if (mergeRequest.upvotes === 0 && mergeRequest.downvotes === 0) { 42 | return; 43 | } 44 | 45 | var url = '/projects/' + mergeRequest.project_id + '/merge_requests/' + mergeRequest.iid + '/award_emoji?per_page=100'; 46 | return request(url).then(function(response) { 47 | var awards = response.data; 48 | 49 | mergeRequest.upvoters = []; 50 | mergeRequest.downvoters = []; 51 | mergeRequest.i_have_voted = 0; 52 | awards.forEach(function (award) { 53 | 54 | if (award.name === 'thumbsup') { 55 | mergeRequest.upvoters.push(award.user.name); 56 | 57 | if (award.user.id === authenticatedUser.id) { 58 | mergeRequest.i_have_voted = 1; 59 | } 60 | } 61 | 62 | if (award.name === 'thumbsdown') { 63 | mergeRequest.downvoters.push(award.user.name); 64 | 65 | if (award.user.id === authenticatedUser.id) { 66 | mergeRequest.i_have_voted = -1; 67 | } 68 | } 69 | }); 70 | }); 71 | }; 72 | 73 | var addCiStatusToMergeRequest = function(mergeRequest) { 74 | var url = '/projects/' + mergeRequest.project_id + '/pipelines?ref=' + encodeURIComponent(mergeRequest.source_branch); 75 | return request(url).then(function(response) { 76 | var pipelines = response.data; 77 | 78 | if (pipelines.length === 0) { 79 | return; 80 | } 81 | 82 | var pipeline = pipelines[0]; 83 | 84 | mergeRequest.ci = { 85 | pipeline_id: pipeline.id, 86 | status: pipeline.status 87 | }; 88 | }); 89 | }; 90 | 91 | var formatLabelsForMergeRequest = function(mergeRequest) { 92 | if (MergeRequestFetcher.labels[mergeRequest.project_id] !== undefined) { 93 | mergeRequest.formatted_labels = []; 94 | mergeRequest.labels.forEach(function(label) { 95 | mergeRequest.formatted_labels.push(MergeRequestFetcher.labels[mergeRequest.project_id][label]); 96 | }); 97 | } 98 | 99 | var url = '/projects/' + mergeRequest.project_id + '/labels'; 100 | return request(url).then(function(response) { 101 | MergeRequestFetcher.labels[mergeRequest.project_id] = {}; 102 | response.data.forEach(function(label) { 103 | MergeRequestFetcher.labels[mergeRequest.project_id][label.name] = label; 104 | }); 105 | 106 | mergeRequest.formatted_labels = []; 107 | mergeRequest.labels.forEach(function(label) { 108 | mergeRequest.formatted_labels.push(MergeRequestFetcher.labels[mergeRequest.project_id][label]); 109 | }); 110 | }); 111 | }; 112 | 113 | gitLabManager.getUser().then(function(user) { 114 | authenticatedUser = user; 115 | }); 116 | 117 | return MergeRequestFetcher; 118 | }; 119 | -------------------------------------------------------------------------------- /app/styles/_settings.scss: -------------------------------------------------------------------------------- 1 | // Foundation for Sites Settings 2 | // ----------------------------- 3 | // 4 | // Table of Contents: 5 | // 6 | // 1. Global 7 | // 2. Breakpoints 8 | // 3. The Grid 9 | // 4. Base Typography 10 | // 5. Typography Helpers 11 | // 6. Abide 12 | // 7. Accordion 13 | // 8. Accordion Menu 14 | // 9. Badge 15 | // 10. Breadcrumbs 16 | // 11. Button 17 | // 12. Button Group 18 | // 13. Callout 19 | // 14. Card 20 | // 15. Close Button 21 | // 16. Drilldown 22 | // 17. Dropdown 23 | // 18. Dropdown Menu 24 | // 19. Forms 25 | // 20. Label 26 | // 21. Media Object 27 | // 22. Menu 28 | // 23. Meter 29 | // 24. Off-canvas 30 | // 25. Orbit 31 | // 26. Pagination 32 | // 27. Progress Bar 33 | // 28. Responsive Embed 34 | // 29. Reveal 35 | // 30. Slider 36 | // 31. Switch 37 | // 32. Table 38 | // 33. Tabs 39 | // 34. Thumbnail 40 | // 35. Title Bar 41 | // 36. Tooltip 42 | // 37. Top Bar 43 | 44 | @import 'util/util'; 45 | 46 | // 1. Global 47 | // --------- 48 | 49 | $global-font-size: 125%; 50 | //$global-width: rem-calc(1200); 51 | $global-width: 100%; 52 | $global-lineheight: 1.5; 53 | $foundation-palette: ( 54 | primary: #1779ba, 55 | secondary: #767676, 56 | success: #3adb76, 57 | warning: #ffae00, 58 | alert: #cc4b37, 59 | ); 60 | $light-gray: #e6e6e6; 61 | $medium-gray: #cacaca; 62 | $dark-gray: #8a8a8a; 63 | $black: #0a0a0a; 64 | $white: #fefefe; 65 | $body-background: $white; 66 | $body-font-color: $black; 67 | $body-font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif; 68 | $body-antialiased: true; 69 | $global-margin: 1rem; 70 | $global-padding: 1rem; 71 | $global-weight-normal: normal; 72 | $global-weight-bold: bold; 73 | $global-radius: 0; 74 | $global-text-direction: ltr; 75 | $global-flexbox: false; 76 | $print-transparent-backgrounds: true; 77 | 78 | @include add-foundation-colors; 79 | $primary-color: #313541; 80 | $secondary-color: #41ADF0; 81 | $tertiary-color: #fff; 82 | 83 | // 2. Breakpoints 84 | // -------------- 85 | 86 | $breakpoints: ( 87 | small: 0, 88 | medium: 640px, 89 | large: 1024px, 90 | xlarge: 1200px, 91 | xxlarge: 1440px, 92 | ); 93 | $print-breakpoint: large; 94 | $breakpoint-classes: (small medium large); 95 | 96 | // 3. The Grid 97 | // ----------- 98 | 99 | $grid-row-width: $global-width; 100 | $grid-column-count: 12; 101 | $grid-column-gutter: ( 102 | small: 20px, 103 | medium: 30px, 104 | ); 105 | $grid-column-align-edge: true; 106 | $block-grid-max: 8; 107 | 108 | // 4. Base Typography 109 | // ------------------ 110 | 111 | $header-font-family: $body-font-family; 112 | $header-font-weight: $global-weight-normal; 113 | $header-font-style: normal; 114 | $font-family-monospace: Consolas, 'Liberation Mono', Courier, monospace; 115 | $header-color: inherit; 116 | $header-lineheight: 1.4; 117 | $header-margin-bottom: 0.5rem; 118 | $header-styles: ( 119 | small: ( 120 | 'h1': ('font-size': 24), 121 | 'h2': ('font-size': 20), 122 | 'h3': ('font-size': 19), 123 | 'h4': ('font-size': 18), 124 | 'h5': ('font-size': 17), 125 | 'h6': ('font-size': 16), 126 | ), 127 | medium: ( 128 | 'h1': ('font-size': 48), 129 | 'h2': ('font-size': 40), 130 | 'h3': ('font-size': 31), 131 | 'h4': ('font-size': 25), 132 | 'h5': ('font-size': 20), 133 | 'h6': ('font-size': 16), 134 | ), 135 | ); 136 | $header-text-rendering: optimizeLegibility; 137 | $small-font-size: 80%; 138 | $header-small-font-color: $medium-gray; 139 | $paragraph-lineheight: 1.6; 140 | $paragraph-margin-bottom: 1rem; 141 | $paragraph-text-rendering: optimizeLegibility; 142 | $code-color: $black; 143 | $code-font-family: $font-family-monospace; 144 | $code-font-weight: $global-weight-normal; 145 | $code-background: $light-gray; 146 | $code-border: 1px solid $medium-gray; 147 | $code-padding: rem-calc(2 5 1); 148 | $anchor-color: $primary-color; 149 | $anchor-color-hover: scale-color($anchor-color, $lightness: -14%); 150 | $anchor-text-decoration: none; 151 | $anchor-text-decoration-hover: none; 152 | $hr-width: $global-width; 153 | $hr-border: 1px solid $medium-gray; 154 | $hr-margin: rem-calc(20) auto; 155 | $list-lineheight: $paragraph-lineheight; 156 | $list-margin-bottom: $paragraph-margin-bottom; 157 | $list-style-type: disc; 158 | $list-style-position: outside; 159 | $list-side-margin: 1.25rem; 160 | $list-nested-side-margin: 1.25rem; 161 | $defnlist-margin-bottom: 1rem; 162 | $defnlist-term-weight: $global-weight-bold; 163 | $defnlist-term-margin-bottom: 0.3rem; 164 | $blockquote-color: $dark-gray; 165 | $blockquote-padding: rem-calc(9 20 0 19); 166 | $blockquote-border: 1px solid $medium-gray; 167 | $cite-font-size: rem-calc(13); 168 | $cite-color: $dark-gray; 169 | $cite-pseudo-content: '\2014 \0020'; 170 | $keystroke-font: $font-family-monospace; 171 | $keystroke-color: $black; 172 | $keystroke-background: $light-gray; 173 | $keystroke-padding: rem-calc(2 4 0); 174 | $keystroke-radius: $global-radius; 175 | $abbr-underline: 1px dotted $black; 176 | 177 | // 5. Typography Helpers 178 | // --------------------- 179 | 180 | $lead-font-size: $global-font-size * 1.25; 181 | $lead-lineheight: 1.6; 182 | $subheader-lineheight: 1.4; 183 | $subheader-color: $dark-gray; 184 | $subheader-font-weight: $global-weight-normal; 185 | $subheader-margin-top: 0.2rem; 186 | $subheader-margin-bottom: 0.5rem; 187 | $stat-font-size: 2.5rem; 188 | 189 | // 6. Abide 190 | // -------- 191 | 192 | $abide-inputs: true; 193 | $abide-labels: true; 194 | $input-background-invalid: get-color(alert); 195 | $form-label-color-invalid: get-color(alert); 196 | $input-error-color: get-color(alert); 197 | $input-error-font-size: rem-calc(12); 198 | $input-error-font-weight: $global-weight-bold; 199 | 200 | // 7. Accordion 201 | // ------------ 202 | 203 | $accordion-background: $white; 204 | $accordion-plusminus: true; 205 | $accordion-title-font-size: rem-calc(12); 206 | $accordion-item-color: $primary-color; 207 | $accordion-item-background-hover: $light-gray; 208 | $accordion-item-padding: 1.25rem 1rem; 209 | $accordion-content-background: $white; 210 | $accordion-content-border: 1px solid $light-gray; 211 | $accordion-content-color: $body-font-color; 212 | $accordion-content-padding: 1rem; 213 | 214 | // 8. Accordion Menu 215 | // ----------------- 216 | 217 | $accordionmenu-arrows: true; 218 | $accordionmenu-arrow-color: $primary-color; 219 | $accordionmenu-arrow-size: 6px; 220 | 221 | // 9. Badge 222 | // -------- 223 | 224 | $badge-background: $primary-color; 225 | $badge-color: $white; 226 | $badge-color-alt: $black; 227 | $badge-palette: $foundation-palette; 228 | $badge-padding: 0.3em; 229 | $badge-minwidth: 2.1em; 230 | $badge-font-size: 0.6rem; 231 | 232 | // 10. Breadcrumbs 233 | // --------------- 234 | 235 | $breadcrumbs-margin: 0 0 $global-margin 0; 236 | $breadcrumbs-item-font-size: rem-calc(11); 237 | $breadcrumbs-item-color: $primary-color; 238 | $breadcrumbs-item-color-current: $black; 239 | $breadcrumbs-item-color-disabled: $medium-gray; 240 | $breadcrumbs-item-margin: 0.75rem; 241 | $breadcrumbs-item-uppercase: true; 242 | $breadcrumbs-item-slash: true; 243 | 244 | // 11. Button 245 | // ---------- 246 | 247 | $button-padding: 0.85em 1em; 248 | $button-margin: 0 0 $global-margin 0; 249 | $button-fill: solid; 250 | $button-background: $primary-color; 251 | $button-background-hover: scale-color($button-background, $lightness: -15%); 252 | $button-color: $white; 253 | $button-color-alt: $black; 254 | $button-radius: $global-radius; 255 | $button-sizes: ( 256 | tiny: 0.6rem, 257 | small: 0.75rem, 258 | default: 0.9rem, 259 | large: 1.25rem, 260 | ); 261 | $button-palette: $foundation-palette; 262 | $button-opacity-disabled: 0.25; 263 | $button-background-hover-lightness: -20%; 264 | $button-hollow-hover-lightness: -50%; 265 | $button-transition: background-color 0.25s ease-out, color 0.25s ease-out; 266 | 267 | // 12. Button Group 268 | // ---------------- 269 | 270 | $buttongroup-margin: 1rem; 271 | $buttongroup-spacing: 1px; 272 | $buttongroup-child-selector: '.button'; 273 | $buttongroup-expand-max: 6; 274 | $buttongroup-radius-on-each: true; 275 | 276 | // 13. Callout 277 | // ----------- 278 | 279 | $callout-background: $white; 280 | $callout-background-fade: 85%; 281 | $callout-border: 1px solid rgba($black, 0.25); 282 | $callout-margin: 0 0 1rem 0; 283 | $callout-padding: 1rem; 284 | $callout-font-color: $body-font-color; 285 | $callout-font-color-alt: $body-background; 286 | $callout-radius: $global-radius; 287 | $callout-link-tint: 30%; 288 | 289 | // 14. Card 290 | // -------- 291 | 292 | $card-background: $white; 293 | $card-font-color: $body-font-color; 294 | $card-divider-background: $light-gray; 295 | $card-border: 1px solid $light-gray; 296 | $card-shadow: none; 297 | $card-border-radius: $global-radius; 298 | $card-padding: $global-padding; 299 | $card-margin: $global-margin; 300 | 301 | // 15. Close Button 302 | // ---------------- 303 | 304 | $closebutton-position: right top; 305 | $closebutton-offset-horizontal: ( 306 | small: 0.66rem, 307 | medium: 1rem, 308 | ); 309 | $closebutton-offset-vertical: ( 310 | small: 0.33em, 311 | medium: 0.5rem, 312 | ); 313 | $closebutton-size: ( 314 | small: 1.5em, 315 | medium: 2em, 316 | ); 317 | $closebutton-lineheight: 1; 318 | $closebutton-color: $dark-gray; 319 | $closebutton-color-hover: $black; 320 | 321 | // 16. Drilldown 322 | // ------------- 323 | 324 | $drilldown-transition: transform 0.15s linear; 325 | $drilldown-arrows: true; 326 | $drilldown-arrow-color: $primary-color; 327 | $drilldown-arrow-size: 6px; 328 | $drilldown-background: $white; 329 | 330 | // 17. Dropdown 331 | // ------------ 332 | 333 | $dropdown-padding: 1rem; 334 | $dropdown-background: $body-background; 335 | $dropdown-border: 1px solid $medium-gray; 336 | $dropdown-font-size: 1rem; 337 | $dropdown-width: 300px; 338 | $dropdown-radius: $global-radius; 339 | $dropdown-sizes: ( 340 | tiny: 100px, 341 | small: 200px, 342 | large: 400px, 343 | ); 344 | 345 | // 18. Dropdown Menu 346 | // ----------------- 347 | 348 | $dropdownmenu-arrows: true; 349 | $dropdownmenu-arrow-color: $anchor-color; 350 | $dropdownmenu-arrow-size: 6px; 351 | $dropdownmenu-min-width: 200px; 352 | $dropdownmenu-background: $white; 353 | $dropdownmenu-border: 1px solid $medium-gray; 354 | 355 | // 19. Forms 356 | // --------- 357 | 358 | $fieldset-border: 1px solid $medium-gray; 359 | $fieldset-padding: rem-calc(20); 360 | $fieldset-margin: rem-calc(18 0); 361 | $legend-padding: rem-calc(0 3); 362 | $form-spacing: rem-calc(16); 363 | $helptext-color: $black; 364 | $helptext-font-size: rem-calc(13); 365 | $helptext-font-style: italic; 366 | $input-prefix-color: $black; 367 | $input-prefix-background: $light-gray; 368 | $input-prefix-border: 1px solid $medium-gray; 369 | $input-prefix-padding: 1rem; 370 | $form-label-color: $black; 371 | $form-label-font-size: rem-calc(14); 372 | $form-label-font-weight: $global-weight-normal; 373 | $form-label-line-height: 1.8; 374 | $select-background: $white; 375 | $select-triangle-color: $dark-gray; 376 | $select-radius: $global-radius; 377 | $input-color: $black; 378 | $input-placeholder-color: $medium-gray; 379 | $input-font-family: inherit; 380 | $input-font-size: rem-calc(16); 381 | $input-font-weight: $global-weight-normal; 382 | $input-background: $white; 383 | $input-background-focus: $white; 384 | $input-background-disabled: $light-gray; 385 | $input-border: 1px solid $medium-gray; 386 | $input-border-focus: 1px solid $dark-gray; 387 | $input-shadow: inset 0 1px 2px rgba($black, 0.1); 388 | $input-shadow-focus: 0 0 5px $medium-gray; 389 | $input-cursor-disabled: not-allowed; 390 | $input-transition: box-shadow 0.5s, border-color 0.25s ease-in-out; 391 | $input-number-spinners: true; 392 | $input-radius: $global-radius; 393 | $form-button-radius: $global-radius; 394 | 395 | // 20. Label 396 | // --------- 397 | 398 | $label-background: $primary-color; 399 | $label-color: $white; 400 | $label-color-alt: $black; 401 | $label-palette: $foundation-palette; 402 | $label-font-size: 0.8rem; 403 | $label-padding: 0.33333rem 0.5rem; 404 | $label-radius: $global-radius; 405 | 406 | // 21. Media Object 407 | // ---------------- 408 | 409 | $mediaobject-margin-bottom: $global-margin; 410 | $mediaobject-section-padding: $global-padding; 411 | $mediaobject-image-width-stacked: 100%; 412 | 413 | // 22. Menu 414 | // -------- 415 | 416 | $menu-margin: 0; 417 | $menu-margin-nested: 1rem; 418 | $menu-item-padding: 0.7rem 1rem; 419 | $menu-item-color-active: $white; 420 | $menu-item-background-active: get-color(primary); 421 | $menu-icon-spacing: 0.25rem; 422 | $menu-item-background-hover: $light-gray; 423 | $menu-border: $light-gray; 424 | 425 | // 23. Meter 426 | // --------- 427 | 428 | $meter-height: 1rem; 429 | $meter-radius: $global-radius; 430 | $meter-background: $medium-gray; 431 | $meter-fill-good: $success-color; 432 | $meter-fill-medium: $warning-color; 433 | $meter-fill-bad: $alert-color; 434 | 435 | // 24. Off-canvas 436 | // -------------- 437 | 438 | $offcanvas-size: 250px; 439 | $offcanvas-vertical-size: 250px; 440 | $offcanvas-background: $light-gray; 441 | $offcanvas-shadow: 0 0 10px rgba($black, 0.7); 442 | $offcanvas-push-zindex: 1; 443 | $offcanvas-overlap-zindex: 10; 444 | $offcanvas-reveal-zindex: 1; 445 | $offcanvas-transition-length: 0.5s; 446 | $offcanvas-transition-timing: ease; 447 | $offcanvas-fixed-reveal: true; 448 | $offcanvas-exit-background: rgba($white, 0.25); 449 | $maincontent-class: 'off-canvas-content'; 450 | 451 | // 25. Orbit 452 | // --------- 453 | 454 | $orbit-bullet-background: $medium-gray; 455 | $orbit-bullet-background-active: $dark-gray; 456 | $orbit-bullet-diameter: 1.2rem; 457 | $orbit-bullet-margin: 0.1rem; 458 | $orbit-bullet-margin-top: 0.8rem; 459 | $orbit-bullet-margin-bottom: 0.8rem; 460 | $orbit-caption-background: rgba($black, 0.5); 461 | $orbit-caption-padding: 1rem; 462 | $orbit-control-background-hover: rgba($black, 0.5); 463 | $orbit-control-padding: 1rem; 464 | $orbit-control-zindex: 10; 465 | 466 | // 26. Pagination 467 | // -------------- 468 | 469 | $pagination-font-size: rem-calc(14); 470 | $pagination-margin-bottom: $global-margin; 471 | $pagination-item-color: $black; 472 | $pagination-item-padding: rem-calc(3 10); 473 | $pagination-item-spacing: rem-calc(1); 474 | $pagination-radius: $global-radius; 475 | $pagination-item-background-hover: $light-gray; 476 | $pagination-item-background-current: $primary-color; 477 | $pagination-item-color-current: $white; 478 | $pagination-item-color-disabled: $medium-gray; 479 | $pagination-ellipsis-color: $black; 480 | $pagination-mobile-items: false; 481 | $pagination-mobile-current-item: false; 482 | $pagination-arrows: true; 483 | 484 | // 27. Progress Bar 485 | // ---------------- 486 | 487 | $progress-height: 1rem; 488 | $progress-background: $medium-gray; 489 | $progress-margin-bottom: $global-margin; 490 | $progress-meter-background: $primary-color; 491 | $progress-radius: $global-radius; 492 | 493 | // 28. Responsive Embed 494 | // -------------------- 495 | 496 | $responsive-embed-margin-bottom: rem-calc(16); 497 | $responsive-embed-ratios: ( 498 | default: 4 by 3, 499 | widescreen: 16 by 9, 500 | ); 501 | 502 | // 29. Reveal 503 | // ---------- 504 | 505 | $reveal-background: $white; 506 | $reveal-width: 600px; 507 | $reveal-max-width: $global-width; 508 | $reveal-padding: $global-padding; 509 | $reveal-border: 1px solid $medium-gray; 510 | $reveal-radius: $global-radius; 511 | $reveal-zindex: 1005; 512 | $reveal-overlay-background: rgba($black, 0.45); 513 | 514 | // 30. Slider 515 | // ---------- 516 | 517 | $slider-width-vertical: 0.5rem; 518 | $slider-transition: all 0.2s ease-in-out; 519 | $slider-height: 0.5rem; 520 | $slider-background: $light-gray; 521 | $slider-fill-background: $medium-gray; 522 | $slider-handle-height: 1.4rem; 523 | $slider-handle-width: 1.4rem; 524 | $slider-handle-background: $primary-color; 525 | $slider-opacity-disabled: 0.25; 526 | $slider-radius: $global-radius; 527 | 528 | // 31. Switch 529 | // ---------- 530 | 531 | $switch-background: $medium-gray; 532 | $switch-background-active: $primary-color; 533 | $switch-height: 2rem; 534 | $switch-height-tiny: 1.5rem; 535 | $switch-height-small: 1.75rem; 536 | $switch-height-large: 2.5rem; 537 | $switch-radius: $global-radius; 538 | $switch-margin: $global-margin; 539 | $switch-paddle-background: $white; 540 | $switch-paddle-offset: 0.25rem; 541 | $switch-paddle-radius: $global-radius; 542 | $switch-paddle-transition: all 0.25s ease-out; 543 | 544 | // 32. Table 545 | // --------- 546 | 547 | $table-background: $white; 548 | $table-color-scale: 5%; 549 | $table-border: 1px solid smart-scale($table-background, $table-color-scale); 550 | $table-padding: rem-calc(8 10 10); 551 | $table-hover-scale: 2%; 552 | $table-row-hover: darken($table-background, $table-hover-scale); 553 | $table-row-stripe-hover: darken($table-background, $table-color-scale + $table-hover-scale); 554 | $table-is-striped: true; 555 | $table-striped-background: smart-scale($table-background, $table-color-scale); 556 | $table-stripe: even; 557 | $table-head-background: smart-scale($table-background, $table-color-scale / 2); 558 | $table-head-row-hover: darken($table-head-background, $table-hover-scale); 559 | $table-foot-background: smart-scale($table-background, $table-color-scale); 560 | $table-foot-row-hover: darken($table-foot-background, $table-hover-scale); 561 | $table-head-font-color: $body-font-color; 562 | $table-foot-font-color: $body-font-color; 563 | $show-header-for-stacked: false; 564 | 565 | // 33. Tabs 566 | // -------- 567 | 568 | $tab-margin: 0; 569 | $tab-background: $white; 570 | $tab-color: $primary-color; 571 | $tab-background-active: $light-gray; 572 | $tab-active-color: $primary-color; 573 | $tab-item-font-size: rem-calc(12); 574 | $tab-item-background-hover: $white; 575 | $tab-item-padding: 1.25rem 1.5rem; 576 | $tab-expand-max: 6; 577 | $tab-content-background: $white; 578 | $tab-content-border: $light-gray; 579 | $tab-content-color: $body-font-color; 580 | $tab-content-padding: 1rem; 581 | 582 | // 34. Thumbnail 583 | // ------------- 584 | 585 | $thumbnail-border: solid 4px $white; 586 | //$thumbnail-margin-bottom: $global-margin; 587 | $thumbnail-margin-bottom: 0; 588 | $thumbnail-shadow: 0 0 0 1px rgba($black, 0.2); 589 | $thumbnail-shadow-hover: 0 0 6px 1px rgba($primary-color, 0.5); 590 | $thumbnail-transition: box-shadow 200ms ease-out; 591 | $thumbnail-radius: $global-radius; 592 | 593 | // 35. Title Bar 594 | // ------------- 595 | 596 | $titlebar-background: $black; 597 | $titlebar-color: $white; 598 | $titlebar-padding: 0.5rem; 599 | $titlebar-text-font-weight: bold; 600 | $titlebar-icon-color: $white; 601 | $titlebar-icon-color-hover: $medium-gray; 602 | $titlebar-icon-spacing: 0.25rem; 603 | 604 | // 36. Tooltip 605 | // ----------- 606 | 607 | $has-tip-font-weight: $global-weight-bold; 608 | $has-tip-border-bottom: dotted 1px $dark-gray; 609 | $tooltip-background-color: $black; 610 | $tooltip-color: $white; 611 | $tooltip-padding: 0.75rem; 612 | $tooltip-font-size: $small-font-size; 613 | $tooltip-pip-width: 0.75rem; 614 | $tooltip-pip-height: $tooltip-pip-width * 0.866; 615 | $tooltip-radius: $global-radius; 616 | 617 | // 37. Top Bar 618 | // ----------- 619 | 620 | //$topbar-padding: 0.5rem; 621 | $topbar-padding: 1rem; 622 | //$topbar-background: $light-gray; 623 | $topbar-background: $primary-color; 624 | $topbar-submenu-background: $topbar-background; 625 | $topbar-title-spacing: 0.5rem 1rem 0.5rem 0; 626 | $topbar-input-width: 200px; 627 | $topbar-unstack-breakpoint: medium; 628 | -------------------------------------------------------------------------------- /app/styles/main.scss: -------------------------------------------------------------------------------- 1 | @import 'settings.scss'; 2 | @import "foundation"; 3 | @include foundation-everything; 4 | @import 'angular-tooltips.scss'; 5 | 6 | $secondary-color: #42AEF2; 7 | 8 | table { 9 | a.button { 10 | margin-bottom: 0; 11 | } 12 | } 13 | 14 | .emoji { 15 | width: 1.5em; 16 | height: 1.5em; 17 | display: inline-block; 18 | margin-bottom: -0.25em; 19 | } 20 | 21 | .top-bar { 22 | color: $tertiary-color; 23 | border-bottom: 3px solid $secondary-color; 24 | 25 | .menu-text.title { 26 | padding-top: 0; 27 | padding-bottom: 0; 28 | font-size: 2em; 29 | font-weight: 400; 30 | 31 | a { 32 | color: inherit; 33 | padding: 0; 34 | } 35 | } 36 | 37 | .logo { 38 | height: 40px; 39 | margin-right: 10px; 40 | } 41 | 42 | .menu a { 43 | &:hover { 44 | color: $tertiary-color; 45 | } 46 | 47 | color: $secondary-color; 48 | } 49 | } 50 | 51 | tooltip { 52 | white-space: nowrap; 53 | } 54 | 55 | .author, .downvote, .upvote { 56 | cursor: help; 57 | } 58 | 59 | table { 60 | border-collapse: collapse; 61 | 62 | & > tbody { 63 | border: none; 64 | 65 | .badge { 66 | font-size: 11px; 67 | } 68 | } 69 | 70 | & > thead { 71 | border: none; 72 | 73 | th { 74 | text-transform: uppercase; 75 | font-weight: 400; 76 | border-bottom: 2px solid smart-scale($table-background, $table-color-scale) 77 | } 78 | } 79 | } 80 | 81 | .author { 82 | @extend .thumbnail; 83 | } 84 | 85 | label, fieldset > legend { 86 | font-size: 14px; 87 | font-weight: bold; 88 | } 89 | 90 | h1 { 91 | margin-bottom: 25px; 92 | } 93 | -------------------------------------------------------------------------------- /brunch-config.coffee: -------------------------------------------------------------------------------- 1 | module.exports = config: 2 | server: 3 | port: 3000 4 | files: 5 | javascripts: joinTo: 6 | 'app.js': /^app/ 7 | 'vendor.js': /^(?!app)/ 8 | stylesheets: joinTo: 'app.css' 9 | plugins: 10 | assetsmanager: 11 | copyTo: 12 | 'images/emoji': ['node_modules/emojify.js/dist/images/basic/*'] 13 | sass: 14 | options: 15 | includePaths: [ 16 | 'node_modules/foundation-sites/scss', 17 | 'node_modules/angular-tooltips/lib' 18 | ] 19 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const path = require('path') 3 | const PORT = process.env.PORT || 3000 4 | 5 | express() 6 | .use(express.static(path.join(__dirname, 'public'))) 7 | .listen(PORT, () => console.log(`Starting web server on http://localhost:${ PORT }`)) 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MergeRequestsCI", 3 | "version": "2.0.1", 4 | "private": true, 5 | "licence": "MIT", 6 | "scripts": { 7 | "serve": "node index.js", 8 | "preserve": "brunch b --production", 9 | "build": "brunch b --production", 10 | "lint": "eslint.js --fix ./app/js" 11 | }, 12 | "devDependencies": { 13 | "assetsmanager-brunch": "^1.8.1", 14 | "brunch": "^2.10.17", 15 | "eslint": "^4.19.1", 16 | "eslint-brunch": "^3.12.0", 17 | "express": "^4.16.3", 18 | "sass-brunch": "^2.10.7", 19 | "uglify-js-brunch": "^2.10.0" 20 | }, 21 | "dependencies": { 22 | "angular": "^1.7.5", 23 | "angular-local-storage": "^0.7.1", 24 | "angular-moment": "^1.3.0", 25 | "angular-route": "^1.7.5", 26 | "angular-tooltips": "^1.2.2", 27 | "emojify.js": "^1.1.0", 28 | "favico.js": "^0.3.10", 29 | "foundation-sites": "~6.3.1", 30 | "moment": "^2.22.2" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hexanet/MergeRequestsCI/7d983f03563ebb8a5b75bb6acaae5c10446f5e05/screenshot.png --------------------------------------------------------------------------------