├── .browserslistrc ├── .editorconfig ├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── .npmrc ├── .prettierrc ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── babel.config.js ├── jest.config.js ├── package.json ├── public ├── _redirects ├── favicon.ico └── index.html ├── src ├── App.vue ├── assets │ ├── global.scss │ └── img │ │ └── logo.svg ├── components │ ├── DeveloperCard.vue │ ├── FloatingActionButton.vue │ ├── HelloWorld.vue │ ├── InputSearch.vue │ ├── Languages.vue │ ├── Nav.vue │ ├── PopularLanguages.vue │ ├── RepositoryCard.vue │ ├── Statistics.vue │ └── SubNav.vue ├── composable │ └── useSearchDeveloper.js ├── config │ ├── composition-api.js │ ├── index.js │ └── materialize.js ├── main.js ├── router │ └── index.js ├── store │ ├── About.js │ ├── App.js │ ├── Developers.js │ ├── Repositories.js │ ├── index.js │ └── store.js ├── util │ ├── Searcher.js │ ├── api.js │ ├── colors.js │ └── colors.json └── views │ ├── About.vue │ ├── Developers.vue │ ├── Home.vue │ ├── NewRepositories.vue │ ├── PopularDevelopers.vue │ ├── PopularRepositories.vue │ ├── RecentlyJoinedDevelopers.vue │ └── Repositories.vue └── tests └── unit └── example.spec.js /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | end_of_line = lf 10 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | extends: ["plugin:vue/essential", "eslint:recommended", "@vue/prettier"], 7 | parserOptions: { 8 | parser: "babel-eslint" 9 | }, 10 | rules: { 11 | "no-console": process.env.NODE_ENV === "production" ? "warn" : "off", 12 | "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off", 13 | semi: "error", 14 | "prefer-const": "error", 15 | "comma-dangle": "error", 16 | "comma-spacing": "error", 17 | "comma-style": "error", 18 | curly: ["error", "all"], 19 | "no-return-assign": 0, 20 | "no-undef": 0, 21 | indent: [2, 2, { SwitchCase: 1 }], 22 | quotes: [ 23 | "error", 24 | "double", 25 | { 26 | avoidEscape: true, 27 | allowTemplateLiterals: true 28 | } 29 | ], 30 | "space-infix-ops": "error", 31 | "no-multi-spaces": ["error", { exceptions: { Property: false } }], 32 | "space-before-blocks": "error", 33 | "keyword-spacing": "error", 34 | "space-before-function-paren": ["error", "never"], 35 | "space-in-parens": "error", 36 | "object-curly-spacing": ["error", "always"], 37 | "arrow-spacing": 0 38 | }, 39 | overrides: [ 40 | { 41 | files: ["**/__tests__/*.{j,t}s?(x)", "**/tests/unit/**/*.spec.{j,t}s?(x)"], 42 | env: { 43 | jest: true 44 | } 45 | } 46 | ] 47 | }; 48 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ignore all differences in line endings 2 | * -crlf 3 | 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | package-lock.json 23 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "printWidth": 120, 5 | "trailingComma": "none", 6 | "arrowParens": "avoid" 7 | } 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to open-source 2 | We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's: 3 | 4 | - Reporting a bug 5 | - Discussing the current state of the code 6 | - Submitting a fix 7 | - Proposing new features 8 | - Becoming a maintainer 9 | 10 | ## We Develop with Github 11 | We use github to host code, to track issues and feature requests, as well as accept pull requests. 12 | 13 | ## We Use [Github Flow](https://guides.github.com/introduction/flow/index.html), So All Code Changes Happen Through Pull Requests 14 | Pull requests are the best way to propose changes to the codebase (we use [Github Flow](https://guides.github.com/introduction/flow/index.html)). We actively welcome your pull requests: 15 | 16 | 1. Fork the repo and create your branch from `master`. 17 | 2. Make sure your code lints. 18 | 3. Issue that pull request! 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Vue Dominicana 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # open-source [![Netlify Status](https://api.netlify.com/api/v1/badges/6172ee72-5dd4-42d6-9463-47bbbc5c007e/deploy-status)](https://app.netlify.com/sites/opensourcevue/deploys) 2 | 3 | > 🔦 Discover the open source ecosystem related to the Dominican Republic! 4 | > This project is inpired on [opensource](https://github.com/developersdo/opensource/) project developed by [rmariuzzo](https://github.com/rmariuzzo) 5 | 6 | ## Motivation 7 | 8 | We just want to replicate all the features that [opensource](https://github.com/developersdo/opensource/) has just for fun and at the same time to introduce more people to the open-source community. This is an excellent project for someone who wants to learn Vue. 9 | The data source in order to make this work will be: 10 | 11 | 1. https://developersdo.github.io/opensource/data/repos.json 12 | 2. https://developersdo.github.io/opensource/data/users.json 13 | 14 | 15 | ## How to contribute 16 | 17 | > :thought_balloon: If you are new in Open Source world feel free to check our [How to contribute guidelines](https://github.com/VueDominicana/open-source/blob/master/CONTRIBUTING.md) 18 | 19 | ## Notes 20 | 21 | We're using [Materialize](https://materializecss.com/) to bootstrap the project. 22 | 23 | ## Project setup 24 | 25 | ``` 26 | npm install 27 | ``` 28 | 29 | ### Compiles and hot-reloads for development 30 | 31 | ``` 32 | npm run serve 33 | ``` 34 | 35 | ### Compiles and minifies for production 36 | 37 | ``` 38 | npm run build 39 | ``` 40 | 41 | ### Run your unit tests 42 | 43 | ``` 44 | npm run test:unit 45 | ``` 46 | 47 | ### Lints and fixes files 48 | 49 | ``` 50 | npm run lint 51 | ``` 52 | 53 | ### Customize configuration 54 | 55 | See [Configuration Reference](https://cli.vuejs.org/config/). 56 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["@vue/cli-plugin-babel/preset"] 3 | }; 4 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: "@vue/cli-plugin-unit-jest" 3 | }; 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "open-source", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "test:unit": "vue-cli-service test:unit", 9 | "lint": "vue-cli-service lint" 10 | }, 11 | "dependencies": { 12 | "@vue/composition-api": "^0.6.1", 13 | "axios": "^0.19.2", 14 | "core-js": "^3.6.5", 15 | "lodash": "^4.17.15", 16 | "materialize-css": "^1.0.0-rc.2", 17 | "node-emoji": "^1.10.0", 18 | "vue": "^2.6.11", 19 | "vue-loading-overlay": "^3.3.2", 20 | "vue-router": "^3.1.6", 21 | "vuex": "^3.3.0" 22 | }, 23 | "devDependencies": { 24 | "@vue/cli-plugin-babel": "~4.3.1", 25 | "@vue/cli-plugin-eslint": "~4.3.1", 26 | "@vue/cli-plugin-router": "~4.3.1", 27 | "@vue/cli-plugin-unit-jest": "~4.3.1", 28 | "@vue/cli-service": "~4.3.1", 29 | "@vue/eslint-config-prettier": "^6.0.0", 30 | "@vue/test-utils": "1.0.0-beta.33", 31 | "babel-eslint": "^10.1.0", 32 | "eslint": "^6.8.0", 33 | "eslint-plugin-prettier": "^3.1.3", 34 | "eslint-plugin-vue": "^6.2.2", 35 | "husky": "^4.2.5", 36 | "lint-staged": "^10.2.2", 37 | "prettier": "^2.0.5", 38 | "sass": "^1.26.5", 39 | "sass-loader": "^8.0.2", 40 | "vue-template-compiler": "^2.6.11" 41 | }, 42 | "husky": { 43 | "hooks": { 44 | "pre-commit": "lint-staged" 45 | } 46 | }, 47 | "lint-staged": { 48 | "*.(vue|js)": "npm run lint" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /public/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VueDominicana/open-source/1a81ddf7298e5257be99e3c5f1241377f2698e10/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <%= htmlWebpackPlugin.options.title %> 10 | 11 | 12 | 18 |
19 | 20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 57 | 58 | 71 | -------------------------------------------------------------------------------- /src/assets/global.scss: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | height: 100%; 4 | } 5 | -------------------------------------------------------------------------------- /src/assets/img/logo.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/components/DeveloperCard.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 36 | 37 | 74 | -------------------------------------------------------------------------------- /src/components/FloatingActionButton.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 43 | -------------------------------------------------------------------------------- /src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 | 32 | -------------------------------------------------------------------------------- /src/components/InputSearch.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 32 | 33 | 58 | -------------------------------------------------------------------------------- /src/components/Languages.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 47 | 48 | 53 | -------------------------------------------------------------------------------- /src/components/Nav.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 53 | 54 | 77 | -------------------------------------------------------------------------------- /src/components/PopularLanguages.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 29 | 30 | 42 | -------------------------------------------------------------------------------- /src/components/RepositoryCard.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 90 | 91 | 135 | -------------------------------------------------------------------------------- /src/components/Statistics.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/components/SubNav.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 20 | 21 | 36 | -------------------------------------------------------------------------------- /src/composable/useSearchDeveloper.js: -------------------------------------------------------------------------------- 1 | import sortBy from "lodash/sortBy"; 2 | import Searcher from "@/util/Searcher"; 3 | import store from "../store/store"; 4 | 5 | const developerSearcher = new Searcher().setField("name").setField("login"); 6 | 7 | export function useSearchDeveloper() { 8 | function searchPopular(searchTerm) { 9 | let developers = store.state.Developers.developers; 10 | developerSearcher.setData(developers); 11 | 12 | if (searchTerm) { 13 | developers = developerSearcher.findAll(searchTerm); 14 | } 15 | 16 | return developers.slice(0, 10); 17 | } 18 | 19 | function searchNewDevelopers(searchTerm) { 20 | let developers = store.getters["Developers/newDevelopers"]; 21 | developerSearcher.setData(developers); 22 | 23 | if (searchTerm) { 24 | developers = sortBy(developerSearcher.findAll(searchTerm), dev => -Number(new Date(dev.createdAt))); 25 | } 26 | 27 | return developers.slice(0, 10); 28 | } 29 | 30 | return { searchPopular, searchNewDevelopers }; 31 | } 32 | -------------------------------------------------------------------------------- /src/config/composition-api.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import VueCompositionApi from "@vue/composition-api"; 3 | 4 | Vue.use(VueCompositionApi); 5 | -------------------------------------------------------------------------------- /src/config/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import "@/config/materialize"; 3 | import "@/config/composition-api"; 4 | 5 | Vue.config.productionTip = false; 6 | -------------------------------------------------------------------------------- /src/config/materialize.js: -------------------------------------------------------------------------------- 1 | import "materialize-css/dist/css/materialize.min.css"; 2 | import "materialize-css/dist/js/materialize.min.js"; 3 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import App from "@/App.vue"; 3 | import router from "@/router"; 4 | import "@/config"; 5 | import store from "@/store/store"; 6 | 7 | new Vue({ 8 | router, 9 | store, 10 | render: h => h(App) 11 | }).$mount("#app"); 12 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import VueRouter from "vue-router"; 3 | import PopularRepositories from "../views/PopularRepositories.vue"; 4 | 5 | Vue.use(VueRouter); 6 | 7 | const routes = [ 8 | { 9 | path: "/", 10 | redirect: { 11 | name: "PopularRepositories" 12 | } 13 | }, 14 | { 15 | path: "/repositories", 16 | redirect: { 17 | name: "PopularRepositories" 18 | }, 19 | component: () => import(/* webpackChunkName: "new-repositories" */ "../views/Repositories.vue"), 20 | children: [ 21 | { 22 | path: "popular", 23 | name: "PopularRepositories", 24 | component: PopularRepositories 25 | }, 26 | { 27 | path: ":language", 28 | name: "PopularRepositoriesByLanguages", 29 | component: PopularRepositories 30 | }, 31 | { 32 | path: "new", 33 | name: "NewRepositories", 34 | component: () => import(/* webpackChunkName: "new-repositories" */ "../views/NewRepositories.vue") 35 | } 36 | ] 37 | }, 38 | { 39 | path: "/developers", 40 | component: () => import(/* webpackChunkName: "developers" */ "../views/Developers.vue"), 41 | redirect: { 42 | name: "PopularDevelopers" 43 | }, 44 | children: [ 45 | { 46 | path: "popular", 47 | name: "PopularDevelopers", 48 | component: () => import(/* webpackChunkName: "popular-developers" */ "../views/PopularDevelopers.vue") 49 | }, 50 | { 51 | path: "recently-joined", 52 | name: "RecentlyJoinedDevelopers", 53 | component: () => 54 | import(/* webpackChunkName: "recently-joined-developers" */ "../views/RecentlyJoinedDevelopers.vue") 55 | } 56 | ] 57 | }, 58 | { 59 | path: "/about", 60 | name: "About", 61 | component: () => import(/* webpackChunkName: "about" */ "../views/About.vue") 62 | } 63 | ]; 64 | 65 | const router = new VueRouter({ 66 | mode: "history", 67 | base: process.env.BASE_URL, 68 | routes 69 | }); 70 | 71 | export default router; 72 | -------------------------------------------------------------------------------- /src/store/About.js: -------------------------------------------------------------------------------- 1 | import map from "lodash/map"; 2 | 3 | const state = { 4 | mostPopularLanguages: [], 5 | developersWithMoreThanTenRepos: 0, 6 | reposWithMoreThanOneStar: 0, 7 | reposContributionAvg: 0, 8 | reposLanguagesTotals: {}, 9 | reposLanguages: [], 10 | lessUsedLanguages: "" 11 | }; 12 | 13 | const mutations = { 14 | SET_MOST_POPULAR_LANGUAGES(state, languages) { 15 | state.mostPopularLanguages = languages; 16 | }, 17 | SET_DEVELOPERS_WITH_MORE_THAN_TEN_REPOS(state, developers) { 18 | state.developersWithMoreThanTenRepos = developers; 19 | }, 20 | REPOS_WITH_MORE_THAN_ONE_STAR(state, repositories) { 21 | state.reposWithMoreThanOneStar = repositories; 22 | }, 23 | SET_REPOS_WITH_CONTRIBUTION_AVG(state, repositories) { 24 | state.reposContributionAvg = repositories; 25 | }, 26 | SET_LANGUAGE_TOTAL(state, languages) { 27 | state.reposLanguagesTotals = languages; 28 | }, 29 | SET_REPOS_LANGUAGES(state, languages) { 30 | state.reposLanguages = languages; 31 | }, 32 | SET_LESS_USED_LANGUAGES(state, languages) { 33 | state.lessUsedLanguages = languages; 34 | } 35 | }; 36 | 37 | const actions = { 38 | mostPopularLanguages({ commit }) { 39 | const mostPopularLanguages = state.reposLanguages.slice(0, 10); 40 | commit("SET_MOST_POPULAR_LANGUAGES", mostPopularLanguages); 41 | }, 42 | developersWithMoreThanTenRepos({ commit, rootState }) { 43 | const developersWithMoreThanTenRepos = rootState.Developers.developers.filter(developer => developer.sources > 10) 44 | .length; 45 | commit("SET_DEVELOPERS_WITH_MORE_THAN_TEN_REPOS", developersWithMoreThanTenRepos); 46 | }, 47 | reposWithMoreThanOneStar({ commit, rootState }) { 48 | const reposWithMoreThanOneStar = rootState.Repositories.repositories.filter(repo => repo.stargazers > 1).length; 49 | 50 | commit("REPOS_WITH_MORE_THAN_ONE_STAR", reposWithMoreThanOneStar); 51 | }, 52 | reposContributionAvg({ commit, rootState }) { 53 | const reposWithContributionsAvg = ( 54 | rootState.Repositories.repositories.length / rootState.Developers.developers.length 55 | ).toFixed(1); 56 | 57 | commit("SET_REPOS_WITH_CONTRIBUTION_AVG", reposWithContributionsAvg); 58 | }, 59 | reposLanguagesTotals({ commit, rootState }) { 60 | const languagesTotals = rootState.Repositories.repositories.reduce((total, repo) => { 61 | if (!repo.languages.length) { 62 | return total; 63 | } 64 | 65 | return repo.languages.split(" ").reduce((sum, language) => { 66 | let languageTotal = sum[language] || 0; 67 | 68 | languageTotal++; 69 | 70 | return { ...sum, [language]: languageTotal }; 71 | }, total); 72 | }, {}); 73 | 74 | commit("SET_LANGUAGE_TOTAL", languagesTotals); 75 | }, 76 | reposLanguages({ commit, rootState }) { 77 | const reposLanguages = map(state.reposLanguagesTotals, (total, name) => ({ 78 | name, 79 | total, 80 | percentage: ((total / rootState.Repositories.repositories.length) * 100).toFixed(2) 81 | })).sort((a, b) => b.total - a.total); 82 | commit("SET_REPOS_LANGUAGES", reposLanguages); 83 | }, 84 | lessUsedLanguages({ commit }) { 85 | const lessUsedLanguages = state.reposLanguages 86 | .slice(-10) 87 | .map(lang => lang.name) 88 | .join(", "); 89 | 90 | commit("SET_LESS_USED_LANGUAGES", lessUsedLanguages); 91 | } 92 | }; 93 | 94 | const getters = {}; 95 | 96 | export default { 97 | namespaced: true, 98 | state, 99 | mutations, 100 | actions, 101 | getters 102 | }; 103 | -------------------------------------------------------------------------------- /src/store/App.js: -------------------------------------------------------------------------------- 1 | const state = { 2 | isLoading: false 3 | }; 4 | 5 | const getters = { 6 | isLoading() { 7 | return state.isLoading; 8 | } 9 | }; 10 | 11 | const actions = { 12 | setLoading({ commit }, isLoading) { 13 | commit("SET_LOADING", isLoading); 14 | } 15 | }; 16 | 17 | const mutations = { 18 | SET_LOADING(state, isLoading) { 19 | state.isLoading = !!isLoading; 20 | } 21 | }; 22 | 23 | export default { 24 | namespaced: true, 25 | state, 26 | mutations, 27 | actions, 28 | getters 29 | }; 30 | -------------------------------------------------------------------------------- /src/store/Developers.js: -------------------------------------------------------------------------------- 1 | import clone from "lodash/cloneDeep"; 2 | import sortBy from "lodash/sortBy"; 3 | import API from "@/util/api"; 4 | 5 | const state = { 6 | developers: [] 7 | }; 8 | 9 | const mutations = { 10 | SET_DEVELOPERS(state, developers) { 11 | state.developers = developers; 12 | } 13 | }; 14 | 15 | const actions = { 16 | async getDevelopers({ commit }) { 17 | const developers = await API.getDevelopers().catch(e => { 18 | console.error(e); 19 | 20 | return []; 21 | }); 22 | 23 | commit("SET_DEVELOPERS", developers); 24 | }, 25 | getDeveloperByUsername(context, username) { 26 | let developer = state.developers.find(developer => developer.login === username); 27 | if (!developer) { 28 | developer = {}; 29 | console.warn( 30 | `Could not find user by login: ${developer}, probably the user changed his login. ` + 31 | `More details at: https://github.com/developersdo/opensource/issues/89` 32 | ); 33 | } 34 | 35 | return developer; 36 | }, 37 | calculateStatistics({ commit, state, rootState }) { 38 | const developersCopy = clone(state.developers); 39 | 40 | rootState.Repositories.repositories.forEach(repository => { 41 | const developer = developersCopy.find(dev => dev.login === repository.name.split("/")[0]); 42 | 43 | if (developer) { 44 | developer.sources += 1; 45 | developer.forked += repository.forks; 46 | } 47 | }); 48 | 49 | commit("SET_DEVELOPERS", developersCopy); 50 | } 51 | }; 52 | 53 | const getters = { 54 | newDevelopers(state) { 55 | const lastMonthDate = new Date(); 56 | const ONE_MONTH = 30; 57 | lastMonthDate.setDate(lastMonthDate.getDate() - ONE_MONTH); 58 | 59 | const newDevs = state.developers.filter(dev => { 60 | return new Date(dev.createdAt) >= lastMonthDate; 61 | }); 62 | 63 | const sortedDevs = sortBy(newDevs, dev => -Number(new Date(dev.createdAt))); 64 | return clone(sortedDevs).map((dev, index) => { 65 | dev.position = index + 1; 66 | 67 | return dev; 68 | }); 69 | } 70 | }; 71 | 72 | export default { 73 | namespaced: true, 74 | state, 75 | mutations, 76 | actions, 77 | getters 78 | }; 79 | -------------------------------------------------------------------------------- /src/store/Repositories.js: -------------------------------------------------------------------------------- 1 | import API from "@/util/api"; 2 | 3 | const state = { 4 | repositories: [] 5 | }; 6 | 7 | const mutations = { 8 | SET_REPOSITORIES(state, repositories) { 9 | state.repositories = repositories; 10 | } 11 | }; 12 | 13 | const actions = { 14 | async getRepositories({ commit }) { 15 | const repositories = await API.getRepositories().catch(e => { 16 | console.error(e); 17 | 18 | return []; 19 | }); 20 | 21 | commit("SET_REPOSITORIES", repositories); 22 | } 23 | }; 24 | 25 | const getters = {}; 26 | 27 | export default { 28 | namespaced: true, 29 | state, 30 | mutations, 31 | actions, 32 | getters 33 | }; 34 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Import store modules dynamically. 3 | */ 4 | const files = require.context(".", false, /\.js$/); 5 | const store = {}; 6 | 7 | files.keys().forEach(key => { 8 | if (!["./index.js", "./store.js"].includes(key)) { 9 | store[key.replace(/(\.\/|\.js)/g, "")] = files(key).default; 10 | } 11 | }); 12 | 13 | export default store; 14 | -------------------------------------------------------------------------------- /src/store/store.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Create all modules dynamically. 3 | */ 4 | import Vue from "vue"; 5 | import Vuex from "vuex"; 6 | import modules from "@/store"; 7 | 8 | Vue.use(Vuex); 9 | 10 | export default new Vuex.Store({ 11 | modules, 12 | strict: process.env.NODE_ENV !== "production" 13 | }); 14 | -------------------------------------------------------------------------------- /src/util/Searcher.js: -------------------------------------------------------------------------------- 1 | import deburr from "lodash/deburr"; 2 | 3 | export default class Searcher { 4 | /** 5 | * @param {Array} 6 | */ 7 | fields = []; 8 | 9 | /** 10 | * 11 | * @param {string} field 12 | */ 13 | setField(field) { 14 | this.fields.push(field); 15 | return this; 16 | } 17 | 18 | /** 19 | * 20 | * @param {Array} data 21 | */ 22 | setData(data) { 23 | this.data = data; 24 | return this; 25 | } 26 | 27 | /** 28 | * 29 | * @param {string} searchTerm 30 | * @returns {Array} 31 | */ 32 | findAll(searchTerm) { 33 | const matcher = new RegExp(deburr(searchTerm), "i"); 34 | const result = this.data.filter(item => { 35 | return this.fields.some(field => matcher.test(deburr(item[field]))); 36 | }); 37 | 38 | return result; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/util/api.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import sortBy from "lodash/sortBy"; 3 | 4 | axios.defaults.baseURL = "https://developersdo.github.io/opensource-data"; 5 | 6 | export default { 7 | async getDevelopers() { 8 | const { data: developers } = await axios.get("/users.json"); 9 | const sortedDevelopers = sortBy(developers, dev => -dev.followers); 10 | return sortedDevelopers.map((dev, position) => { 11 | dev.position = position + 1; 12 | dev.sources = 0; 13 | dev.forked = 0; 14 | return dev; 15 | }); 16 | }, 17 | async getRepositories() { 18 | const { data: repositories } = await axios.get("/repos.json"); 19 | const sortedRepositories = sortBy(repositories, repo => -repo.stargazers); 20 | return sortedRepositories.map((repo, position) => { 21 | repo.position = position + 1; 22 | return repo; 23 | }); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /src/util/colors.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | /** Convert hex color to rgb 3 | * @param {string} hex 4 | * @returns {(object|null)} 5 | * @see https://stackoverflow.com/a/5624139/3367568 6 | */ 7 | export function hexToRgb(hex) { 8 | // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF") 9 | const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; 10 | hex = hex.replace(shorthandRegex, (m, r, g, b) => { 11 | return r + r + g + g + b + b; 12 | }); 13 | 14 | const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); 15 | if (result) { 16 | return { 17 | r: parseInt(result[1], 16), 18 | g: parseInt(result[2], 16), 19 | b: parseInt(result[3], 16) 20 | }; 21 | } 22 | 23 | return null; 24 | } 25 | 26 | /** Set the contrast based on brightness of a rgb color 27 | * @param {Object} rgb 28 | * @returns {string} 29 | * @see https://stackoverflow.com/questions/11867545/change-text-color-based-on-brightness-of-the-covered-background-area#comment80648498_11868159 30 | */ 31 | export function setContrast(rgb) { 32 | return (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000 > 125 ? "black" : "white"; 33 | } 34 | -------------------------------------------------------------------------------- /src/util/colors.json: -------------------------------------------------------------------------------- 1 | { 2 | "1c enterprise": "#814CCC", 3 | "4d": null, 4 | "abap": "#E8274B", 5 | "actionscript": "#882B0F", 6 | "ada": "#02f88c", 7 | "agda": "#315665", 8 | "ags script": "#B9D9FF", 9 | "alloy": "#64C800", 10 | "alpine abuild": null, 11 | "ampl": "#E6EFBB", 12 | "angelscript": "#C7D7DC", 13 | "antlr": "#9DC3FF", 14 | "apex": null, 15 | "api blueprint": "#2ACCA8", 16 | "apl": "#5A8164", 17 | "apollo guidance computer": null, 18 | "applescript": "#101F1F", 19 | "arc": "#aa2afe", 20 | "asp": "#6a40fd", 21 | "aspectj": "#a957b0", 22 | "assembly": "#6E4C13", 23 | "asymptote": "#4a0c0c", 24 | "ats": "#1ac620", 25 | "augeas": null, 26 | "autohotkey": "#6594b9", 27 | "autoit": "#1C3552", 28 | "awk": null, 29 | "ballerina": "#FF5000", 30 | "batchfile": "#C1F12E", 31 | "befunge": null, 32 | "bison": null, 33 | "bitbake": null, 34 | "blitzbasic": null, 35 | "blitzmax": "#cd6400", 36 | "bluespec": null, 37 | "boo": "#d4bec1", 38 | "brainfuck": "#2F2530", 39 | "brightscript": null, 40 | "c": "#555555", 41 | "c#": "#178600", 42 | "c++": "#f34b7d", 43 | "c2hs haskell": null, 44 | "cap'n proto": null, 45 | "cartocss": null, 46 | "ceylon": "#dfa535", 47 | "chapel": "#8dc63f", 48 | "charity": null, 49 | "chuck": null, 50 | "cirru": "#ccccff", 51 | "clarion": "#db901e", 52 | "clean": "#3F85AF", 53 | "click": "#E4E6F3", 54 | "clips": null, 55 | "clojure": "#db5855", 56 | "cmake": null, 57 | "cobol": null, 58 | "codeql": null, 59 | "coffeescript": "#244776", 60 | "coldfusion": "#ed2cd6", 61 | "coldfusion cfc": null, 62 | "common lisp": "#3fb68b", 63 | "common workflow language": "#B5314C", 64 | "component pascal": "#B0CE4E", 65 | "cool": null, 66 | "coq": null, 67 | "crystal": "#000100", 68 | "csound": null, 69 | "csound document": null, 70 | "csound score": null, 71 | "css": "#563d7c", 72 | "cuda": "#3A4E3A", 73 | "cweb": null, 74 | "cycript": null, 75 | "cython": null, 76 | "d": "#ba595e", 77 | "dart": "#00B4AB", 78 | "dataweave": "#003a52", 79 | "dhall": "#dfafff", 80 | "digital command language": null, 81 | "dm": "#447265", 82 | "dockerfile": "#384d54", 83 | "dogescript": "#cca760", 84 | "dtrace": null, 85 | "dylan": "#6c616e", 86 | "e": "#ccce35", 87 | "ec": "#913960", 88 | "ecl": "#8a1267", 89 | "eclipse": null, 90 | "eiffel": "#946d57", 91 | "elixir": "#6e4a7e", 92 | "elm": "#60B5CC", 93 | "emacs lisp": "#c065db", 94 | "emberscript": "#FFF4F3", 95 | "eq": "#a78649", 96 | "erlang": "#B83998", 97 | "f#": "#b845fc", 98 | "f*": "#572e30", 99 | "factor": "#636746", 100 | "fancy": "#7b9db4", 101 | "fantom": "#14253c", 102 | "faust": "#c37240", 103 | "filebench wml": null, 104 | "filterscript": null, 105 | "fish": null, 106 | "flux": "#88ccff", 107 | "forth": "#341708", 108 | "fortran": "#4d41b1", 109 | "freemarker": "#0050b2", 110 | "frege": "#00cafe", 111 | "g-code": "#D08CF2", 112 | "game maker language": "#71b417", 113 | "gaml": "#FFC766", 114 | "gams": null, 115 | "gap": null, 116 | "gcc machine description": null, 117 | "gdb": null, 118 | "gdscript": "#355570", 119 | "genie": "#fb855d", 120 | "genshi": null, 121 | "gentoo ebuild": null, 122 | "gentoo eclass": null, 123 | "gherkin": "#5B2063", 124 | "glsl": null, 125 | "glyph": "#c1ac7f", 126 | "gnuplot": "#f0a9f0", 127 | "go": "#00ADD8", 128 | "golo": "#88562A", 129 | "gosu": "#82937f", 130 | "grace": null, 131 | "grammatical framework": "#79aa7a", 132 | "groovy": "#e69f56", 133 | "groovy server pages": null, 134 | "hack": "#878787", 135 | "harbour": "#0e60e3", 136 | "haskell": "#5e5086", 137 | "haxe": "#df7900", 138 | "hcl": null, 139 | "hiveql": "#dce200", 140 | "hlsl": null, 141 | "holyc": "#ffefaf", 142 | "html": "#e34c26", 143 | "hy": "#7790B2", 144 | "hyphy": null, 145 | "idl": "#a3522f", 146 | "idris": "#b30000", 147 | "igor pro": "#0000cc", 148 | "inform 7": null, 149 | "inno setup": null, 150 | "io": "#a9188d", 151 | "ioke": "#078193", 152 | "isabelle": "#FEFE00", 153 | "isabelle root": null, 154 | "j": "#9EEDFF", 155 | "jasmin": null, 156 | "java": "#b07219", 157 | "java server pages": null, 158 | "javascript": "#f1e05a", 159 | "javascript+erb": null, 160 | "jflex": null, 161 | "jison": null, 162 | "jison lex": null, 163 | "jolie": "#843179", 164 | "jsoniq": "#40d47e", 165 | "jsonnet": "#0064bd", 166 | "jsx": null, 167 | "julia": "#a270ba", 168 | "jupyter notebook": "#DA5B0B", 169 | "kotlin": "#F18E33", 170 | "krl": "#28430A", 171 | "labview": null, 172 | "lasso": "#999999", 173 | "lean": null, 174 | "lex": "#DBCA00", 175 | "lfe": "#4C3023", 176 | "lilypond": null, 177 | "limbo": null, 178 | "literate agda": null, 179 | "literate coffeescript": null, 180 | "literate haskell": null, 181 | "livescript": "#499886", 182 | "llvm": "#185619", 183 | "logos": null, 184 | "logtalk": null, 185 | "lolcode": "#cc9900", 186 | "lookml": "#652B81", 187 | "loomscript": null, 188 | "lsl": "#3d9970", 189 | "lua": "#000080", 190 | "m": null, 191 | "m4": null, 192 | "m4sugar": null, 193 | "makefile": "#427819", 194 | "mako": null, 195 | "mask": "#f97732", 196 | "mathematica": null, 197 | "matlab": "#e16737", 198 | "max": "#c4a79c", 199 | "maxscript": "#00a6a6", 200 | "mcfunction": "#E22837", 201 | "mercury": "#ff2b2b", 202 | "meson": "#007800", 203 | "metal": "#8f14e9", 204 | "minid": null, 205 | "mirah": "#c7a938", 206 | "mirc script": "#926059", 207 | "mlir": "#5EC8DB", 208 | "modelica": null, 209 | "modula-2": null, 210 | "modula-3": "#223388", 211 | "module management system": null, 212 | "monkey": null, 213 | "moocode": null, 214 | "moonscript": null, 215 | "motorola 68k assembly": null, 216 | "mql4": "#62A8D6", 217 | "mql5": "#4A76B8", 218 | "mtml": "#b7e1f4", 219 | "muf": null, 220 | "mupad": null, 221 | "myghty": null, 222 | "nasl": null, 223 | "ncl": "#28431f", 224 | "nearley": "#990000", 225 | "nemerle": "#3d3c6e", 226 | "nesc": "#94B0C7", 227 | "netlinx": "#0aa0ff", 228 | "netlinx+erb": "#747faa", 229 | "netlogo": "#ff6375", 230 | "newlisp": "#87AED7", 231 | "nextflow": "#3ac486", 232 | "nim": "#37775b", 233 | "nit": "#009917", 234 | "nix": "#7e7eff", 235 | "nsis": null, 236 | "nu": "#c9df40", 237 | "numpy": null, 238 | "objective-c": "#438eff", 239 | "objective-c++": "#6866fb", 240 | "objective-j": "#ff0c5a", 241 | "objectscript": "#424893", 242 | "ocaml": "#3be133", 243 | "odin": "#60AFFE", 244 | "omgrofl": "#cabbff", 245 | "ooc": "#b0b77e", 246 | "opa": null, 247 | "opal": "#f7ede0", 248 | "open policy agent": null, 249 | "opencl": null, 250 | "openedge abl": null, 251 | "openqasm": "#AA70FF", 252 | "openrc runscript": null, 253 | "openscad": null, 254 | "ox": null, 255 | "oxygene": "#cdd0e3", 256 | "oz": "#fab738", 257 | "p4": "#7055b5", 258 | "pan": "#cc0000", 259 | "papyrus": "#6600cc", 260 | "parrot": "#f3ca0a", 261 | "parrot assembly": null, 262 | "parrot internal representation": null, 263 | "pascal": "#E3F171", 264 | "pawn": "#dbb284", 265 | "pep8": "#C76F5B", 266 | "perl": "#0298c3", 267 | "php": "#4F5D95", 268 | "picolisp": null, 269 | "piglatin": "#fcd7de", 270 | "pike": "#005390", 271 | "plpgsql": null, 272 | "plsql": "#dad8d8", 273 | "pogoscript": "#d80074", 274 | "pony": null, 275 | "postscript": "#da291c", 276 | "pov-ray sdl": null, 277 | "powerbuilder": "#8f0f8d", 278 | "powershell": "#012456", 279 | "processing": "#0096D8", 280 | "prolog": "#74283c", 281 | "propeller spin": "#7fa2a7", 282 | "puppet": "#302B6D", 283 | "purebasic": "#5a6986", 284 | "purescript": "#1D222D", 285 | "python": "#3572A5", 286 | "python console": null, 287 | "q": "#0040cd", 288 | "qmake": null, 289 | "qml": "#44a51c", 290 | "quake": "#882233", 291 | "r": "#198CE7", 292 | "racket": "#3c5caa", 293 | "ragel": "#9d5200", 294 | "raku": "#0000fb", 295 | "raml": "#77d9fb", 296 | "rascal": "#fffaa0", 297 | "realbasic": null, 298 | "reason": "#ff5847", 299 | "rebol": "#358a5b", 300 | "red": "#f50000", 301 | "redcode": null, 302 | "ren'py": "#ff7f7f", 303 | "renderscript": null, 304 | "rexx": null, 305 | "ring": "#2D54CB", 306 | "riot": "#A71E49", 307 | "robotframework": null, 308 | "roff": "#ecdebe", 309 | "rouge": "#cc0088", 310 | "rpc": null, 311 | "ruby": "#701516", 312 | "runoff": "#665a4e", 313 | "rust": "#dea584", 314 | "sage": null, 315 | "saltstack": "#646464", 316 | "sas": "#B34936", 317 | "scala": "#c22d40", 318 | "scheme": "#1e4aec", 319 | "scilab": null, 320 | "sed": "#64b970", 321 | "self": "#0579aa", 322 | "shaderlab": null, 323 | "shell": "#89e051", 324 | "shellsession": null, 325 | "shen": "#120F14", 326 | "slash": "#007eff", 327 | "slice": "#003fa2", 328 | "smali": null, 329 | "smalltalk": "#596706", 330 | "smarty": null, 331 | "smpl": "#c94949", 332 | "smt": null, 333 | "solidity": "#AA6746", 334 | "sourcepawn": "#5c7611", 335 | "sqf": "#3F3F3F", 336 | "sqlpl": null, 337 | "squirrel": "#800000", 338 | "srecode template": "#348a34", 339 | "stan": "#b2011d", 340 | "standard ml": "#dc566d", 341 | "starlark": "#76d275", 342 | "stata": null, 343 | "supercollider": "#46390b", 344 | "swift": "#ffac45", 345 | "swig": null, 346 | "systemverilog": "#DAE1C2", 347 | "tcl": "#e4cc98", 348 | "tcsh": null, 349 | "terra": "#00004c", 350 | "tex": "#3D6117", 351 | "thrift": null, 352 | "ti program": "#A0AA87", 353 | "tla": null, 354 | "tsql": null, 355 | "tsx": null, 356 | "turing": "#cf142b", 357 | "txl": null, 358 | "typescript": "#2b7489", 359 | "unified parallel c": null, 360 | "unix assembly": null, 361 | "uno": null, 362 | "unrealscript": "#a54c4d", 363 | "urweb": null, 364 | "v": "#5d87bd", 365 | "vala": "#fbe5cd", 366 | "vba": "#867db1", 367 | "vbscript": "#15dcdc", 368 | "vcl": "#148AA8", 369 | "verilog": "#b2b7f8", 370 | "vhdl": "#adb2cb", 371 | "vim script": "#199f4b", 372 | "visual basic .net": "#945db7", 373 | "volt": "#1F1F1F", 374 | "vue": "#2c3e50", 375 | "wdl": "#42f1f4", 376 | "webassembly": "#04133b", 377 | "webidl": null, 378 | "wisp": "#7582D1", 379 | "wollok": "#a23738", 380 | "x10": "#4B6BEF", 381 | "xbase": "#403a40", 382 | "xc": "#99DA07", 383 | "xojo": null, 384 | "xproc": null, 385 | "xquery": "#5232e7", 386 | "xs": null, 387 | "xslt": "#EB8CEB", 388 | "xtend": null, 389 | "yacc": "#4B6C4B", 390 | "yara": "#220000", 391 | "yasnippet": "#32AB90", 392 | "zap": "#0d665e", 393 | "zeek": null, 394 | "zenscript": "#00BCD1", 395 | "zephir": "#118f9e", 396 | "zig": "#ec915c", 397 | "zil": "#dc75e5", 398 | "zimpl": null 399 | } 400 | -------------------------------------------------------------------------------- /src/views/About.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 42 | 43 | 51 | -------------------------------------------------------------------------------- /src/views/Developers.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 18 | -------------------------------------------------------------------------------- /src/views/NewRepositories.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/views/PopularDevelopers.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 53 | 54 | 64 | -------------------------------------------------------------------------------- /src/views/PopularRepositories.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 67 | 68 | 82 | -------------------------------------------------------------------------------- /src/views/RecentlyJoinedDevelopers.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 53 | 54 | 64 | -------------------------------------------------------------------------------- /src/views/Repositories.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 32 | -------------------------------------------------------------------------------- /tests/unit/example.spec.js: -------------------------------------------------------------------------------- 1 | import { shallowMount } from "@vue/test-utils"; 2 | import HelloWorld from "@/components/HelloWorld.vue"; 3 | 4 | describe("HelloWorld.vue", () => { 5 | it("renders props.msg when passed", () => { 6 | const msg = "new message"; 7 | const wrapper = shallowMount(HelloWorld, { 8 | propsData: { msg } 9 | }); 10 | expect(wrapper.text()).toMatch(msg); 11 | }); 12 | }); 13 | --------------------------------------------------------------------------------