├── .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 | 
6 |
7 | [](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 | Last update |
5 | Project |
6 | Title |
7 | Author |
8 | Branch |
9 | Labels |
10 | |
11 | |
12 | Have I voted? |
13 | CI |
14 |
15 |
16 |
17 |
18 | |
19 | {{ mergeRequest.project.name }} |
20 | |
21 |
22 |
23 |
24 |
25 | |
26 | {{ mergeRequest.source_branch }} ⇨ {{ mergeRequest.target_branch }} |
27 |
28 |
33 | |
34 |
35 |
36 |
37 | {{ mergeRequest.upvotes }}
38 |
39 |
40 | |
41 |
42 |
43 |
44 | {{ mergeRequest.downvotes }}
45 |
46 |
47 | |
48 |
49 | -
50 |
51 |
52 | |
53 |
54 | {{ mergeRequest.ci.status }}
55 | |
56 |
57 |
58 |
59 | No merge request in progress, go back to work!
60 | |
61 |
62 |
63 |
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 |
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
--------------------------------------------------------------------------------