├── .editorconfig ├── .github ├── FUNDING.yml ├── grid.png ├── lang.png └── list.png ├── .gitignore ├── license.md ├── package.json ├── public ├── img │ ├── favicon.ico │ ├── icon128.png │ ├── icon16.png │ ├── icon48.png │ ├── logo.svg │ └── solar.svg ├── index.html └── manifest.json ├── readme.md ├── src ├── app.js ├── components │ ├── alert │ │ └── index.js │ ├── filters │ │ ├── date-jump-filter │ │ │ ├── index.js │ │ │ └── styles.css │ │ ├── index.js │ │ ├── language-filter │ │ │ ├── index.js │ │ │ ├── languages.json │ │ │ └── styles.css │ │ ├── styles.css │ │ └── view-filter │ │ │ ├── index.js │ │ │ └── styles.css │ ├── group-heading │ │ ├── index.js │ │ └── styles.css │ ├── icons │ │ ├── fork.js │ │ ├── issue.js │ │ ├── logo.js │ │ ├── solar.js │ │ ├── star.js │ │ └── watcher.js │ ├── launcher │ │ ├── index.js │ │ └── styles.css │ ├── loader │ │ ├── index.js │ │ └── styles.css │ ├── options-form │ │ ├── index.js │ │ └── styles.css │ ├── repository-grid │ │ ├── grid-item │ │ │ ├── index.js │ │ │ └── styles.css │ │ ├── index.js │ │ └── styles.css │ ├── repository-list │ │ ├── index.js │ │ ├── list-item │ │ │ ├── index.js │ │ │ └── styles.css │ │ └── styles.css │ └── top-nav │ │ ├── index.js │ │ └── styles.css ├── containers │ ├── feed │ │ ├── index.js │ │ └── styles.css │ └── options │ │ ├── index.js │ │ └── styles.css ├── global.css ├── index.js ├── redux │ ├── github │ │ ├── actions.js │ │ ├── reducer.js │ │ ├── transform.js │ │ └── types.js │ ├── preference │ │ ├── actions.js │ │ ├── reducer.js │ │ └── types.js │ └── reducers.js ├── router │ └── index.js └── store.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: kamranahmedse 4 | -------------------------------------------------------------------------------- /.github/grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamranahmedse/githunt/4cab1dc0de5d18ee3ddc300c4227fee5f0b31c67/.github/grid.png -------------------------------------------------------------------------------- /.github/lang.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamranahmedse/githunt/4cab1dc0de5d18ee3ddc300c4227fee5f0b31c67/.github/lang.png -------------------------------------------------------------------------------- /.github/list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamranahmedse/githunt/4cab1dc0de5d18ee3ddc300c4227fee5f0b31c67/.github/list.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Thumbs.db 2 | .DS_Store 3 | .idea 4 | /node_modules 5 | /coverage 6 | /build 7 | /old 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Kamran Ahmed 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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "githunt-react", 3 | "version": "0.1.2", 4 | "private": true, 5 | "dependencies": { 6 | "axios": "^0.19.2", 7 | "bootstrap": "^4.4.1", 8 | "classnames": "^2.2.6", 9 | "font-awesome": "^4.7.0", 10 | "github-colors": "^2.2.18", 11 | "moment": "^2.24.0", 12 | "prop-types": "^15.7.2", 13 | "react": "^16.12.0", 14 | "react-dom": "^16.12.0", 15 | "react-redux": "^7.1.3", 16 | "react-router": "^5.1.2", 17 | "react-router-dom": "^5.1.2", 18 | "react-scripts": "3.3.1", 19 | "reactstrap": "^8.4.1", 20 | "redux": "^4.0.5", 21 | "redux-devtools-extension": "^2.13.8", 22 | "redux-persist": "^6.0.0", 23 | "redux-persist-expire": "^1.1.0", 24 | "redux-thunk": "^2.3.0" 25 | }, 26 | "scripts": { 27 | "start": "react-scripts start", 28 | "build-chrome": "INLINE_RUNTIME_CHUNK=false react-scripts build", 29 | "build-web": "cross-env PUBLIC_URL=/githunt react-scripts build", 30 | "test": "react-scripts test --env=jsdom", 31 | "eject": "react-scripts eject", 32 | "gh-pages": "NODE_DEBUG=gh-pages gh-pages -d build" 33 | }, 34 | "devDependencies": { 35 | "cross-env": "^7.0.0", 36 | "gh-pages": "^2.2.0" 37 | }, 38 | "browserslist": [ 39 | ">0.2%", 40 | "not dead", 41 | "not ie <= 11", 42 | "not op_mini all" 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /public/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamranahmedse/githunt/4cab1dc0de5d18ee3ddc300c4227fee5f0b31c67/public/img/favicon.ico -------------------------------------------------------------------------------- /public/img/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamranahmedse/githunt/4cab1dc0de5d18ee3ddc300c4227fee5f0b31c67/public/img/icon128.png -------------------------------------------------------------------------------- /public/img/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamranahmedse/githunt/4cab1dc0de5d18ee3ddc300c4227fee5f0b31c67/public/img/icon16.png -------------------------------------------------------------------------------- /public/img/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamranahmedse/githunt/4cab1dc0de5d18ee3ddc300c4227fee5f0b31c67/public/img/icon48.png -------------------------------------------------------------------------------- /public/img/logo.svg: -------------------------------------------------------------------------------- 1 | astronaut-helmet 2 | -------------------------------------------------------------------------------- /public/img/solar.svg: -------------------------------------------------------------------------------- 1 | solar-system -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | GitHunt – Trending Github Repositories 9 | 10 | 11 | 14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "Githunt", 4 | "short_name": "Githunt", 5 | "description": "Replace the new tab with a list of trending repositories on github belonging to any technology that you chose.", 6 | "version": "3.9", 7 | "permissions": [ 8 | "storage", 9 | "*://*.github.com/*" 10 | ], 11 | "icons": { 12 | "16": "/img/icon16.png", 13 | "48": "/img/icon48.png", 14 | "128": "/img/icon128.png" 15 | }, 16 | "chrome_url_overrides": { 17 | "newtab": "index.html" 18 | }, 19 | "homepage_url": "http://kamranahmed.info/githunt", 20 | "browser_action": { 21 | "default_title": "Githunt", 22 | "default_icon": "/img/icon128.png" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 |

2 | Githunt
GitHunt 3 |

4 | 5 |

6 | 7 | contributions 8 | 9 | 10 | version 11 | 12 | 13 | license-mit 14 | 15 |

16 | 17 |

18 | Hunt the most starred projects on GitHub
19 | ✨ React app and Chrome Extension to go through the top projects ✨ 20 |

21 | 22 |

23 |
GitHunt is a react application and
a chrome extension that lets you explore 24 |
the most starred projects on GitHub
Use OnlineInstall Extension 25 |

githunt 26 | Weekly Trending Projects – List View
27 | 💥 Keep Scrolling to load past weeks 💥 28 |

29 | 30 |

31 | githunt 32 | Weekly Trending Projects – Grid View
33 | 💥 Change the view options from the filters list 💥 34 |

35 | 36 |

37 | githunt 38 | Filter by Language
39 | 💥 Will remember your selection of language 💥 40 |

41 | 42 | 43 | ## Installation 44 | 45 | * Use Online – https://kamranahmed.info/githunt 46 | * Chrome Extension – https://bit.ly/githunt-chrome 47 | 48 | ## Contributions 49 | 50 | * Spread the word 51 | * Open pull requests 52 | * Reach out with any feedback [![Twitter URL](https://img.shields.io/twitter/url/https/twitter.com/kamranahmedse.svg?style=social&label=Follow%20%40kamranahmedse)](https://twitter.com/kamranahmedse) 53 | 54 | ## License 55 | MIT © [Kamran Ahmed](https://kamranahmed.info) 56 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Provider } from 'react-redux'; 3 | import { PersistGate } from 'redux-persist/lib/integration/react'; 4 | 5 | import Launcher from './components/launcher'; 6 | import { persist, store } from './store'; 7 | import AppRoutes from './router'; 8 | 9 | class App extends Component { 10 | render() { 11 | return ( 12 | 13 | } persistor={ persist }> 14 | 15 | 16 | 17 | ); 18 | } 19 | } 20 | 21 | export default App; 22 | -------------------------------------------------------------------------------- /src/components/alert/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classnames from 'classnames'; 4 | 5 | const Alert = (props) => ( 6 |
{ props.children }
7 | ); 8 | 9 | Alert.propTypes = { 10 | type: PropTypes.string.isRequired 11 | }; 12 | 13 | export default Alert; 14 | -------------------------------------------------------------------------------- /src/components/filters/date-jump-filter/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Dropdown, DropdownItem, DropdownMenu, DropdownToggle } from 'reactstrap'; 4 | 5 | import './styles.css'; 6 | 7 | class DateJumpFilter extends React.Component { 8 | state = { 9 | dropdownOpen: false 10 | }; 11 | 12 | updateDateJump = (dateJump) => { 13 | if (dateJump === this.props.selectedDateJump) { 14 | return; 15 | } 16 | 17 | this.props.updateDateJump(dateJump); 18 | }; 19 | 20 | toggle = () => { 21 | this.setState(prevState => ({ 22 | dropdownOpen: !prevState.dropdownOpen 23 | })); 24 | }; 25 | 26 | getSelectedDateJump() { 27 | switch (this.props.selectedDateJump) { 28 | case 'day': 29 | return 'Daily'; 30 | case 'month': 31 | return 'Monthly'; 32 | case 'year': 33 | return 'Yearly'; 34 | case 'week': 35 | return 'Weekly'; 36 | default: 37 | return 'Weekly'; 38 | } 39 | } 40 | 41 | render() { 42 | return ( 43 | 44 | 45 | 46 | { this.getSelectedDateJump() } 47 | 48 | 49 | this.updateDateJump('year') }>Yearly 50 | this.updateDateJump('month') }>Monthly 51 | this.updateDateJump('week') }>Weekly 52 | this.updateDateJump('day') }>Daily 53 | 54 | 55 | ); 56 | } 57 | } 58 | 59 | DateJumpFilter.propTypes = { 60 | updateDateJump: PropTypes.func.isRequired, 61 | selectedDateJump: PropTypes.string 62 | }; 63 | 64 | export default DateJumpFilter; 65 | -------------------------------------------------------------------------------- /src/components/filters/date-jump-filter/styles.css: -------------------------------------------------------------------------------- 1 | .date-jump-type, 2 | .date-jump-type:hover, 3 | .date-jump-type:active { 4 | border-radius: 8px; 5 | padding: 10px 20px; 6 | background: white !important; 7 | color: black !important; 8 | border: 1px solid white !important; 9 | cursor: default; 10 | } 11 | 12 | .date-jump-wrap .dropdown-menu { 13 | border: 1px solid #e8e8e8; 14 | box-shadow: 0 3px 12px rgba(27, 31, 35, 0.15); 15 | font-size: 15px; 16 | line-height: 30px; 17 | top: 3px !important; 18 | } 19 | 20 | .date-jump-wrap .dropdown-menu button { 21 | cursor: pointer; 22 | box-shadow: none; 23 | outline: none; 24 | } 25 | -------------------------------------------------------------------------------- /src/components/filters/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import LanguageFilter from './language-filter'; 5 | import ViewFilter from './view-filter'; 6 | import './styles.css'; 7 | import DateJumpFilter from './date-jump-filter'; 8 | 9 | const Filters = (props) => ( 10 |
11 |
12 | 16 |
17 |
18 | 22 |
23 |
24 | 28 |
29 |
30 | ); 31 | 32 | Filters.propTypes = { 33 | updateLanguage: PropTypes.func.isRequired, 34 | updateViewType: PropTypes.func.isRequired, 35 | updateDateJump: PropTypes.func.isRequired, 36 | selectedLanguage: PropTypes.string, 37 | selectedViewType: PropTypes.string, 38 | selectedDateJump: PropTypes.string 39 | }; 40 | 41 | export default Filters; 42 | -------------------------------------------------------------------------------- /src/components/filters/language-filter/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import PropTypes from 'prop-types'; 4 | import classNames from 'classnames'; 5 | import GithubColors from 'github-colors'; 6 | 7 | import './styles.css'; 8 | import languages from './languages'; 9 | 10 | class LanguageFilter extends React.Component { 11 | filterInputRef = React.createRef(); 12 | 13 | state = { 14 | filterText: '', 15 | selectedIndex: 0, 16 | showDropdown: false 17 | }; 18 | 19 | focusFilterInput = () => { 20 | this.filterInputRef.current.focus(); 21 | }; 22 | 23 | componentDidUpdate(prevProps, prevState) { 24 | // only scroll into view if the active item changed since last render 25 | if (prevState.selectedIndex !== this.state.selectedIndex) { 26 | this.ensureSelectedVisible(); 27 | } 28 | 29 | // If the dropdown has just been made visible focus the input 30 | if (this.state.showDropdown && !prevState.showDropdown) { 31 | this.focusFilterInput(); 32 | } 33 | } 34 | 35 | ensureSelectedVisible() { 36 | const itemComponent = this.refs.activeItem; 37 | if (!itemComponent) { 38 | return; 39 | } 40 | 41 | const domNode = ReactDOM.findDOMNode(itemComponent); 42 | if (!domNode) { 43 | return; 44 | } 45 | 46 | domNode.scrollIntoView({ block: 'end' }); 47 | } 48 | 49 | getFilteredLanguages() { 50 | let availableLanguages = [...languages]; 51 | 52 | if (this.state.filterText) { 53 | availableLanguages = availableLanguages.filter(language => { 54 | const languageText = language.title.toLowerCase(); 55 | const selectedText = this.state.filterText.toLowerCase(); 56 | 57 | return languageText.indexOf(selectedText) >= 0; 58 | }); 59 | } 60 | 61 | return availableLanguages; 62 | } 63 | 64 | renderLanguages() { 65 | let availableLanguages = this.getFilteredLanguages(); 66 | 67 | return availableLanguages.map((language, counter) => { 68 | const isSelectedIndex = counter === this.state.selectedIndex; 69 | 70 | // This will be used in making sure of the element visibility 71 | const refProp = isSelectedIndex ? { ref: 'activeItem' } : {}; 72 | const languageColor = GithubColors.get(language.title) || { 73 | color: language.title === 'All Languages' ? 'transparent' : '#e8e8e8' 74 | }; 75 | 76 | return ( 77 | this.selectLanguage(counter) } 80 | key={ counter }> 81 | 84 | { language.title } 85 | 86 | ); 87 | }); 88 | } 89 | 90 | onKeyDown = e => { 91 | const { selectedIndex } = this.state; 92 | 93 | const isEnterKey = e.keyCode === 13; 94 | const isUpKey = e.keyCode === 38; 95 | const isDownKey = e.keyCode === 40; 96 | 97 | if (!isUpKey && !isDownKey && !isEnterKey) { 98 | return; 99 | } 100 | 101 | const filteredLanguages = this.getFilteredLanguages(); 102 | e.preventDefault(); 103 | 104 | // arrow up/down button should select next/previous list element 105 | if (isUpKey && selectedIndex > 0) { 106 | this.setState(prevState => ({ 107 | selectedIndex: prevState.selectedIndex - 1 108 | })); 109 | } else if (isDownKey && selectedIndex < (filteredLanguages.length - 1)) { 110 | this.setState(prevState => ({ 111 | selectedIndex: prevState.selectedIndex + 1 112 | })); 113 | } else if (isEnterKey && filteredLanguages[selectedIndex]) { 114 | this.selectLanguage(selectedIndex); 115 | } 116 | }; 117 | 118 | selectLanguage = (selectedIndex) => { 119 | const filteredLanguages = this.getFilteredLanguages(); 120 | const selectedLanguage = filteredLanguages[selectedIndex]; 121 | if (!selectedLanguage || selectedLanguage.value === this.props.selectedLanguage) { 122 | return; 123 | } 124 | 125 | this.setState({ 126 | filterText: '', 127 | showDropdown: false 128 | }); 129 | 130 | this.props.updateLanguage(selectedLanguage.value); 131 | }; 132 | 133 | hideDropdown = () => { 134 | this.setState({ 135 | showDropdown: false, 136 | filterText: '' 137 | }); 138 | }; 139 | 140 | filterLanguages = (e) => { 141 | this.setState({ 142 | filterText: e.target.value, 143 | selectedIndex: 0 // Reset and select the first language 144 | }); 145 | }; 146 | 147 | getLanguageDropdown() { 148 | return ( 149 |
150 |
151 | Search Language 152 |
153 |
154 |
155 | 163 |
164 |
165 |
166 | { this.renderLanguages() } 167 |
168 |
169 | ); 170 | } 171 | 172 | toggleDropdown = () => { 173 | this.setState(prevState => ({ 174 | showDropdown: !prevState.showDropdown 175 | })); 176 | }; 177 | 178 | render() { 179 | return ( 180 |
181 | 185 | { this.state.showDropdown && this.getLanguageDropdown() } 186 |
187 | ); 188 | } 189 | } 190 | 191 | LanguageFilter.propTypes = { 192 | updateLanguage: PropTypes.func.isRequired, 193 | selectedLanguage: PropTypes.string 194 | }; 195 | 196 | export default LanguageFilter; 197 | -------------------------------------------------------------------------------- /src/components/filters/language-filter/languages.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "All Languages", 4 | "value": "" 5 | }, 6 | { 7 | "title": "ABAP", 8 | "value": "ABAP" 9 | }, 10 | { 11 | "title": "ActionScript", 12 | "value": "ActionScript" 13 | }, 14 | { 15 | "title": "Ada", 16 | "value": "Ada" 17 | }, 18 | { 19 | "title": "Agda", 20 | "value": "Agda" 21 | }, 22 | { 23 | "title": "AGS", 24 | "value": "AGS" 25 | }, 26 | { 27 | "title": "Alloy", 28 | "value": "Alloy" 29 | }, 30 | { 31 | "title": "AMPL", 32 | "value": "AMPL" 33 | }, 34 | { 35 | "title": "ANTLR", 36 | "value": "ANTLR" 37 | }, 38 | { 39 | "title": "ApacheConf", 40 | "value": "ApacheConf" 41 | }, 42 | { 43 | "title": "Apex", 44 | "value": "Apex" 45 | }, 46 | { 47 | "title": "API", 48 | "value": "API" 49 | }, 50 | { 51 | "title": "APL", 52 | "value": "APL" 53 | }, 54 | { 55 | "title": "AppleScript", 56 | "value": "AppleScript" 57 | }, 58 | { 59 | "title": "Arc", 60 | "value": "Arc" 61 | }, 62 | { 63 | "title": "Arduino", 64 | "value": "Arduino" 65 | }, 66 | { 67 | "title": "ASP", 68 | "value": "ASP" 69 | }, 70 | { 71 | "title": "AspectJ", 72 | "value": "AspectJ" 73 | }, 74 | { 75 | "title": "Assembly", 76 | "value": "Assembly" 77 | }, 78 | { 79 | "title": "ATS", 80 | "value": "ATS" 81 | }, 82 | { 83 | "title": "Augeas", 84 | "value": "Augeas" 85 | }, 86 | { 87 | "title": "AutoHotkey", 88 | "value": "AutoHotkey" 89 | }, 90 | { 91 | "title": "AutoIt", 92 | "value": "AutoIt" 93 | }, 94 | { 95 | "title": "Awk", 96 | "value": "Awk" 97 | }, 98 | { 99 | "title": "Batchfile", 100 | "value": "Batchfile" 101 | }, 102 | { 103 | "title": "Befunge", 104 | "value": "Befunge" 105 | }, 106 | { 107 | "title": "Bison", 108 | "value": "Bison" 109 | }, 110 | { 111 | "title": "BitBake", 112 | "value": "BitBake" 113 | }, 114 | { 115 | "title": "BlitzBasic", 116 | "value": "BlitzBasic" 117 | }, 118 | { 119 | "title": "BlitzMax", 120 | "value": "BlitzMax" 121 | }, 122 | { 123 | "title": "Bluespec", 124 | "value": "Bluespec" 125 | }, 126 | { 127 | "title": "Boo", 128 | "value": "Boo" 129 | }, 130 | { 131 | "title": "Brainfuck", 132 | "value": "Brainfuck" 133 | }, 134 | { 135 | "title": "Brightscript", 136 | "value": "Brightscript" 137 | }, 138 | { 139 | "title": "Bro", 140 | "value": "Bro" 141 | }, 142 | { 143 | "title": "C", 144 | "value": "C" 145 | }, 146 | { 147 | "title": "CSharp", 148 | "value": "CSharp" 149 | }, 150 | { 151 | "title": "C++", 152 | "value": "C++" 153 | }, 154 | { 155 | "title": "Cap", 156 | "value": "Cap" 157 | }, 158 | { 159 | "title": "CartoCSS", 160 | "value": "CartoCSS" 161 | }, 162 | { 163 | "title": "Ceylon", 164 | "value": "Ceylon" 165 | }, 166 | { 167 | "title": "Chapel", 168 | "value": "Chapel" 169 | }, 170 | { 171 | "title": "Charity", 172 | "value": "Charity" 173 | }, 174 | { 175 | "title": "ChucK", 176 | "value": "ChucK" 177 | }, 178 | { 179 | "title": "Cirru", 180 | "value": "Cirru" 181 | }, 182 | { 183 | "title": "Clarion", 184 | "value": "Clarion" 185 | }, 186 | { 187 | "title": "Clean", 188 | "value": "Clean" 189 | }, 190 | { 191 | "title": "Click", 192 | "value": "Click" 193 | }, 194 | { 195 | "title": "CLIPS", 196 | "value": "CLIPS" 197 | }, 198 | { 199 | "title": "Clojure", 200 | "value": "Clojure" 201 | }, 202 | { 203 | "title": "CMake", 204 | "value": "CMake" 205 | }, 206 | { 207 | "title": "COBOL", 208 | "value": "COBOL" 209 | }, 210 | { 211 | "title": "CoffeeScript", 212 | "value": "CoffeeScript" 213 | }, 214 | { 215 | "title": "ColdFusion", 216 | "value": "ColdFusion" 217 | }, 218 | { 219 | "title": "Common", 220 | "value": "Common" 221 | }, 222 | { 223 | "title": "Component", 224 | "value": "Component" 225 | }, 226 | { 227 | "title": "Cool", 228 | "value": "Cool" 229 | }, 230 | { 231 | "title": "Coq", 232 | "value": "Coq" 233 | }, 234 | { 235 | "title": "Crystal", 236 | "value": "Crystal" 237 | }, 238 | { 239 | "title": "CSS", 240 | "value": "CSS" 241 | }, 242 | { 243 | "title": "Cucumber", 244 | "value": "Cucumber" 245 | }, 246 | { 247 | "title": "Cuda", 248 | "value": "Cuda" 249 | }, 250 | { 251 | "title": "Cycript", 252 | "value": "Cycript" 253 | }, 254 | { 255 | "title": "D", 256 | "value": "D" 257 | }, 258 | { 259 | "title": "Darcs", 260 | "value": "Darcs" 261 | }, 262 | { 263 | "title": "Dart", 264 | "value": "Dart" 265 | }, 266 | { 267 | "title": "Diff", 268 | "value": "Diff" 269 | }, 270 | { 271 | "title": "DIGITAL", 272 | "value": "DIGITAL" 273 | }, 274 | { 275 | "title": "DM", 276 | "value": "DM" 277 | }, 278 | { 279 | "title": "Dogescript", 280 | "value": "Dogescript" 281 | }, 282 | { 283 | "title": "DTrace", 284 | "value": "DTrace" 285 | }, 286 | { 287 | "title": "Dylan", 288 | "value": "Dylan" 289 | }, 290 | { 291 | "title": "E", 292 | "value": "E" 293 | }, 294 | { 295 | "title": "Eagle", 296 | "value": "Eagle" 297 | }, 298 | { 299 | "title": "eC", 300 | "value": "eC" 301 | }, 302 | { 303 | "title": "ECL", 304 | "value": "ECL" 305 | }, 306 | { 307 | "title": "Eiffel", 308 | "value": "Eiffel" 309 | }, 310 | { 311 | "title": "Elixir", 312 | "value": "Elixir" 313 | }, 314 | { 315 | "title": "Elm", 316 | "value": "Elm" 317 | }, 318 | { 319 | "title": "Emacs", 320 | "value": "Emacs" 321 | }, 322 | { 323 | "title": "EmberScript", 324 | "value": "EmberScript" 325 | }, 326 | { 327 | "title": "Erlang", 328 | "value": "Erlang" 329 | }, 330 | { 331 | "title": "F#", 332 | "value": "F#" 333 | }, 334 | { 335 | "title": "Factor", 336 | "value": "Factor" 337 | }, 338 | { 339 | "title": "Fancy", 340 | "value": "Fancy" 341 | }, 342 | { 343 | "title": "Fantom", 344 | "value": "Fantom" 345 | }, 346 | { 347 | "title": "FLUX", 348 | "value": "FLUX" 349 | }, 350 | { 351 | "title": "Forth", 352 | "value": "Forth" 353 | }, 354 | { 355 | "title": "FORTRAN", 356 | "value": "FORTRAN" 357 | }, 358 | { 359 | "title": "FreeMarker", 360 | "value": "FreeMarker" 361 | }, 362 | { 363 | "title": "Frege", 364 | "value": "Frege" 365 | }, 366 | { 367 | "title": "Game", 368 | "value": "Game" 369 | }, 370 | { 371 | "title": "GAMS", 372 | "value": "GAMS" 373 | }, 374 | { 375 | "title": "GAP", 376 | "value": "GAP" 377 | }, 378 | { 379 | "title": "GDScript", 380 | "value": "GDScript" 381 | }, 382 | { 383 | "title": "Genshi", 384 | "value": "Genshi" 385 | }, 386 | { 387 | "title": "Gettext", 388 | "value": "Gettext" 389 | }, 390 | { 391 | "title": "GLSL", 392 | "value": "GLSL" 393 | }, 394 | { 395 | "title": "Glyph", 396 | "value": "Glyph" 397 | }, 398 | { 399 | "title": "Gnuplot", 400 | "value": "Gnuplot" 401 | }, 402 | { 403 | "title": "Go", 404 | "value": "Go" 405 | }, 406 | { 407 | "title": "Golo", 408 | "value": "Golo" 409 | }, 410 | { 411 | "title": "Gosu", 412 | "value": "Gosu" 413 | }, 414 | { 415 | "title": "Grace", 416 | "value": "Grace" 417 | }, 418 | { 419 | "title": "Grammatical", 420 | "value": "Grammatical" 421 | }, 422 | { 423 | "title": "Groff", 424 | "value": "Groff" 425 | }, 426 | { 427 | "title": "Groovy", 428 | "value": "Groovy" 429 | }, 430 | { 431 | "title": "Hack", 432 | "value": "Hack" 433 | }, 434 | { 435 | "title": "Handlebars", 436 | "value": "Handlebars" 437 | }, 438 | { 439 | "title": "Harbour", 440 | "value": "Harbour" 441 | }, 442 | { 443 | "title": "Haskell", 444 | "value": "Haskell" 445 | }, 446 | { 447 | "title": "Haxe", 448 | "value": "Haxe" 449 | }, 450 | { 451 | "title": "HCL", 452 | "value": "HCL" 453 | }, 454 | { 455 | "title": "HLSL", 456 | "value": "HLSL" 457 | }, 458 | { 459 | "title": "HTML", 460 | "value": "HTML" 461 | }, 462 | { 463 | "title": "Hy", 464 | "value": "Hy" 465 | }, 466 | { 467 | "title": "HyPhy", 468 | "value": "HyPhy" 469 | }, 470 | { 471 | "title": "IDL", 472 | "value": "IDL" 473 | }, 474 | { 475 | "title": "Idris", 476 | "value": "Idris" 477 | }, 478 | { 479 | "title": "IGOR", 480 | "value": "IGOR" 481 | }, 482 | { 483 | "title": "Inform", 484 | "value": "Inform" 485 | }, 486 | { 487 | "title": "Inno", 488 | "value": "Inno" 489 | }, 490 | { 491 | "title": "Io", 492 | "value": "Io" 493 | }, 494 | { 495 | "title": "Ioke", 496 | "value": "Ioke" 497 | }, 498 | { 499 | "title": "Isabelle", 500 | "value": "Isabelle" 501 | }, 502 | { 503 | "title": "J", 504 | "value": "J" 505 | }, 506 | { 507 | "title": "Jasmin", 508 | "value": "Jasmin" 509 | }, 510 | { 511 | "title": "Java", 512 | "value": "Java" 513 | }, 514 | { 515 | "title": "JavaScript", 516 | "value": "JavaScript" 517 | }, 518 | { 519 | "title": "JFlex", 520 | "value": "JFlex" 521 | }, 522 | { 523 | "title": "JSONiq", 524 | "value": "JSONiq" 525 | }, 526 | { 527 | "title": "Julia", 528 | "value": "Julia" 529 | }, 530 | { 531 | "title": "Jupyter", 532 | "value": "Jupyter-notebook" 533 | }, 534 | { 535 | "title": "KiCad", 536 | "value": "KiCad" 537 | }, 538 | { 539 | "title": "Kit", 540 | "value": "Kit" 541 | }, 542 | { 543 | "title": "Kotlin", 544 | "value": "Kotlin" 545 | }, 546 | { 547 | "title": "KRL", 548 | "value": "KRL" 549 | }, 550 | { 551 | "title": "LabVIEW", 552 | "value": "LabVIEW" 553 | }, 554 | { 555 | "title": "Lasso", 556 | "value": "Lasso" 557 | }, 558 | { 559 | "title": "Lean", 560 | "value": "Lean" 561 | }, 562 | { 563 | "title": "Lex", 564 | "value": "Lex" 565 | }, 566 | { 567 | "title": "LilyPond", 568 | "value": "LilyPond" 569 | }, 570 | { 571 | "title": "Limbo", 572 | "value": "Limbo" 573 | }, 574 | { 575 | "title": "Liquid", 576 | "value": "Liquid" 577 | }, 578 | { 579 | "title": "LiveScript", 580 | "value": "LiveScript" 581 | }, 582 | { 583 | "title": "LLVM", 584 | "value": "LLVM" 585 | }, 586 | { 587 | "title": "Logos", 588 | "value": "Logos" 589 | }, 590 | { 591 | "title": "Logtalk", 592 | "value": "Logtalk" 593 | }, 594 | { 595 | "title": "LOLCODE", 596 | "value": "LOLCODE" 597 | }, 598 | { 599 | "title": "LookML", 600 | "value": "LookML" 601 | }, 602 | { 603 | "title": "LoomScript", 604 | "value": "LoomScript" 605 | }, 606 | { 607 | "title": "LSL", 608 | "value": "LSL" 609 | }, 610 | { 611 | "title": "Lua", 612 | "value": "Lua" 613 | }, 614 | { 615 | "title": "M", 616 | "value": "M" 617 | }, 618 | { 619 | "title": "M4", 620 | "value": "M4" 621 | }, 622 | { 623 | "title": "Makefile", 624 | "value": "Makefile" 625 | }, 626 | { 627 | "title": "Mako", 628 | "value": "Mako" 629 | }, 630 | { 631 | "title": "Markdown", 632 | "value": "Markdown" 633 | }, 634 | { 635 | "title": "Mask", 636 | "value": "Mask" 637 | }, 638 | { 639 | "title": "Mathematica", 640 | "value": "Mathematica" 641 | }, 642 | { 643 | "title": "Matlab", 644 | "value": "Matlab" 645 | }, 646 | { 647 | "title": "Max", 648 | "value": "Max" 649 | }, 650 | { 651 | "title": "MAXScript", 652 | "value": "MAXScript" 653 | }, 654 | { 655 | "title": "Mercury", 656 | "value": "Mercury" 657 | }, 658 | { 659 | "title": "Metal", 660 | "value": "Metal" 661 | }, 662 | { 663 | "title": "MiniD", 664 | "value": "MiniD" 665 | }, 666 | { 667 | "title": "Mirah", 668 | "value": "Mirah" 669 | }, 670 | { 671 | "title": "MLIR", 672 | "value": "MLIR" 673 | }, 674 | { 675 | "title": "Modelica", 676 | "value": "Modelica" 677 | }, 678 | { 679 | "title": "Modula", 680 | "value": "Modula" 681 | }, 682 | { 683 | "title": "Module", 684 | "value": "Module" 685 | }, 686 | { 687 | "title": "Monkey", 688 | "value": "Monkey" 689 | }, 690 | { 691 | "title": "Moocode", 692 | "value": "Moocode" 693 | }, 694 | { 695 | "title": "MoonScript", 696 | "value": "MoonScript" 697 | }, 698 | { 699 | "title": "MTML", 700 | "value": "MTML" 701 | }, 702 | { 703 | "title": "mupad", 704 | "value": "mupad" 705 | }, 706 | { 707 | "title": "Myghty", 708 | "value": "Myghty" 709 | }, 710 | { 711 | "title": "NCL", 712 | "value": "NCL" 713 | }, 714 | { 715 | "title": "Nemerle", 716 | "value": "Nemerle" 717 | }, 718 | { 719 | "title": "nesC", 720 | "value": "nesC" 721 | }, 722 | { 723 | "title": "NetLinx", 724 | "value": "NetLinx" 725 | }, 726 | { 727 | "title": "NetLinx", 728 | "value": "NetLinx" 729 | }, 730 | { 731 | "title": "NetLogo", 732 | "value": "NetLogo" 733 | }, 734 | { 735 | "title": "NewLisp", 736 | "value": "NewLisp" 737 | }, 738 | { 739 | "title": "Nginx", 740 | "value": "Nginx" 741 | }, 742 | { 743 | "title": "Nim", 744 | "value": "Nim" 745 | }, 746 | { 747 | "title": "Nit", 748 | "value": "Nit" 749 | }, 750 | { 751 | "title": "Nix", 752 | "value": "Nix" 753 | }, 754 | { 755 | "title": "NSIS", 756 | "value": "NSIS" 757 | }, 758 | { 759 | "title": "Nu", 760 | "value": "Nu" 761 | }, 762 | { 763 | "title": "Objective-C", 764 | "value": "Objective-C" 765 | }, 766 | { 767 | "title": "OCaml", 768 | "value": "OCaml" 769 | }, 770 | { 771 | "title": "Omgrofl", 772 | "value": "Omgrofl" 773 | }, 774 | { 775 | "title": "ooc", 776 | "value": "ooc" 777 | }, 778 | { 779 | "title": "Opa", 780 | "value": "Opa" 781 | }, 782 | { 783 | "title": "Opal", 784 | "value": "Opal" 785 | }, 786 | { 787 | "title": "OpenEdge", 788 | "value": "OpenEdge" 789 | }, 790 | { 791 | "title": "OpenSCAD", 792 | "value": "OpenSCAD" 793 | }, 794 | { 795 | "title": "Ox", 796 | "value": "Ox" 797 | }, 798 | { 799 | "title": "Oxygene", 800 | "value": "Oxygene" 801 | }, 802 | { 803 | "title": "Oz", 804 | "value": "Oz" 805 | }, 806 | { 807 | "title": "Pan", 808 | "value": "Pan" 809 | }, 810 | { 811 | "title": "Papyrus", 812 | "value": "Papyrus" 813 | }, 814 | { 815 | "title": "Parrot", 816 | "value": "Parrot" 817 | }, 818 | { 819 | "title": "Pascal", 820 | "value": "Pascal" 821 | }, 822 | { 823 | "title": "PAWN", 824 | "value": "PAWN" 825 | }, 826 | { 827 | "title": "Perl", 828 | "value": "Perl" 829 | }, 830 | { 831 | "title": "Perl6", 832 | "value": "Perl6" 833 | }, 834 | { 835 | "title": "PHP", 836 | "value": "PHP" 837 | }, 838 | { 839 | "title": "PicoLisp", 840 | "value": "PicoLisp" 841 | }, 842 | { 843 | "title": "PigLatin", 844 | "value": "PigLatin" 845 | }, 846 | { 847 | "title": "Pike", 848 | "value": "Pike" 849 | }, 850 | { 851 | "title": "PLpgSQL", 852 | "value": "PLpgSQL" 853 | }, 854 | { 855 | "title": "PLSQL", 856 | "value": "PLSQL" 857 | }, 858 | { 859 | "title": "PogoScript", 860 | "value": "PogoScript" 861 | }, 862 | { 863 | "title": "Pony", 864 | "value": "Pony" 865 | }, 866 | { 867 | "title": "PostScript", 868 | "value": "PostScript" 869 | }, 870 | { 871 | "title": "POV", 872 | "value": "POV" 873 | }, 874 | { 875 | "title": "PowerShell", 876 | "value": "PowerShell" 877 | }, 878 | { 879 | "title": "Processing", 880 | "value": "Processing" 881 | }, 882 | { 883 | "title": "Prolog", 884 | "value": "Prolog" 885 | }, 886 | { 887 | "title": "Propeller", 888 | "value": "Propeller" 889 | }, 890 | { 891 | "title": "Protocol", 892 | "value": "Protocol" 893 | }, 894 | { 895 | "title": "Puppet", 896 | "value": "Puppet" 897 | }, 898 | { 899 | "title": "Pure", 900 | "value": "Pure" 901 | }, 902 | { 903 | "title": "PureBasic", 904 | "value": "PureBasic" 905 | }, 906 | { 907 | "title": "PureScript", 908 | "value": "PureScript" 909 | }, 910 | { 911 | "title": "Python", 912 | "value": "Python" 913 | }, 914 | { 915 | "title": "QMake", 916 | "value": "QMake" 917 | }, 918 | { 919 | "title": "QML", 920 | "value": "QML" 921 | }, 922 | { 923 | "title": "R", 924 | "value": "R" 925 | }, 926 | { 927 | "title": "Racket", 928 | "value": "Racket" 929 | }, 930 | { 931 | "title": "Ragel", 932 | "value": "Ragel" 933 | }, 934 | { 935 | "title": "RAML", 936 | "value": "RAML" 937 | }, 938 | { 939 | "title": "RDoc", 940 | "value": "RDoc" 941 | }, 942 | { 943 | "title": "REALbasic", 944 | "value": "REALbasic" 945 | }, 946 | { 947 | "title": "Rebol", 948 | "value": "Rebol" 949 | }, 950 | { 951 | "title": "Red", 952 | "value": "Red" 953 | }, 954 | { 955 | "title": "Redcode", 956 | "value": "Redcode" 957 | }, 958 | { 959 | "title": "Ren", 960 | "value": "Ren" 961 | }, 962 | { 963 | "title": "RenderScript", 964 | "value": "RenderScript" 965 | }, 966 | { 967 | "title": "RobotFramework", 968 | "value": "RobotFramework" 969 | }, 970 | { 971 | "title": "Rouge", 972 | "value": "Rouge" 973 | }, 974 | { 975 | "title": "Ruby", 976 | "value": "Ruby" 977 | }, 978 | { 979 | "title": "Rust", 980 | "value": "Rust" 981 | }, 982 | { 983 | "title": "SaltStack", 984 | "value": "SaltStack" 985 | }, 986 | { 987 | "title": "SAS", 988 | "value": "SAS" 989 | }, 990 | { 991 | "title": "Scala", 992 | "value": "Scala" 993 | }, 994 | { 995 | "title": "Scheme", 996 | "value": "Scheme" 997 | }, 998 | { 999 | "title": "Scilab", 1000 | "value": "Scilab" 1001 | }, 1002 | { 1003 | "title": "Self", 1004 | "value": "Self" 1005 | }, 1006 | { 1007 | "title": "Shell", 1008 | "value": "Shell" 1009 | }, 1010 | { 1011 | "title": "ShellSession", 1012 | "value": "ShellSession" 1013 | }, 1014 | { 1015 | "title": "Shen", 1016 | "value": "Shen" 1017 | }, 1018 | { 1019 | "title": "Slash", 1020 | "value": "Slash" 1021 | }, 1022 | { 1023 | "title": "Smali", 1024 | "value": "Smali" 1025 | }, 1026 | { 1027 | "title": "Smalltalk", 1028 | "value": "Smalltalk" 1029 | }, 1030 | { 1031 | "title": "Smarty", 1032 | "value": "Smarty" 1033 | }, 1034 | { 1035 | "title": "SMT", 1036 | "value": "SMT" 1037 | }, 1038 | { 1039 | "title": "SourcePawn", 1040 | "value": "SourcePawn" 1041 | }, 1042 | { 1043 | "title": "SQF", 1044 | "value": "SQF" 1045 | }, 1046 | { 1047 | "title": "SQL", 1048 | "value": "SQL" 1049 | }, 1050 | { 1051 | "title": "SQLPL", 1052 | "value": "SQLPL" 1053 | }, 1054 | { 1055 | "title": "Squirrel", 1056 | "value": "Squirrel" 1057 | }, 1058 | { 1059 | "title": "Stan", 1060 | "value": "Stan" 1061 | }, 1062 | { 1063 | "title": "Standard", 1064 | "value": "Standard" 1065 | }, 1066 | { 1067 | "title": "Stata", 1068 | "value": "Stata" 1069 | }, 1070 | { 1071 | "title": "SuperCollider", 1072 | "value": "SuperCollider" 1073 | }, 1074 | { 1075 | "title": "Solidity", 1076 | "value": "Solidity" 1077 | }, 1078 | { 1079 | "title": "Swift", 1080 | "value": "Swift" 1081 | }, 1082 | { 1083 | "title": "SystemVerilog", 1084 | "value": "SystemVerilog" 1085 | }, 1086 | { 1087 | "title": "Tcl", 1088 | "value": "Tcl" 1089 | }, 1090 | { 1091 | "title": "Tea", 1092 | "value": "Tea" 1093 | }, 1094 | { 1095 | "title": "TeX", 1096 | "value": "TeX" 1097 | }, 1098 | { 1099 | "title": "Thrift", 1100 | "value": "Thrift" 1101 | }, 1102 | { 1103 | "title": "Turing", 1104 | "value": "Turing" 1105 | }, 1106 | { 1107 | "title": "TXL", 1108 | "value": "TXL" 1109 | }, 1110 | { 1111 | "title": "TypeScript", 1112 | "value": "TypeScript" 1113 | }, 1114 | { 1115 | "title": "Uno", 1116 | "value": "Uno" 1117 | }, 1118 | { 1119 | "title": "UnrealScript", 1120 | "value": "UnrealScript" 1121 | }, 1122 | { 1123 | "title": "UrWeb", 1124 | "value": "UrWeb" 1125 | }, 1126 | { 1127 | "title": "Vala", 1128 | "value": "Vala" 1129 | }, 1130 | { 1131 | "title": "VCL", 1132 | "value": "VCL" 1133 | }, 1134 | { 1135 | "title": "Verilog", 1136 | "value": "Verilog" 1137 | }, 1138 | { 1139 | "title": "VHDL", 1140 | "value": "VHDL" 1141 | }, 1142 | { 1143 | "title": "VimL", 1144 | "value": "VimL" 1145 | }, 1146 | { 1147 | "title": "Visual", 1148 | "value": "Visual" 1149 | }, 1150 | { 1151 | "title": "Volt", 1152 | "value": "Volt" 1153 | }, 1154 | { 1155 | "title": "Vue", 1156 | "value": "Vue" 1157 | }, 1158 | { 1159 | "title": "Web", 1160 | "value": "Web" 1161 | }, 1162 | { 1163 | "title": "WebIDL", 1164 | "value": "WebIDL" 1165 | }, 1166 | { 1167 | "title": "wisp", 1168 | "value": "wisp" 1169 | }, 1170 | { 1171 | "title": "X10", 1172 | "value": "X10" 1173 | }, 1174 | { 1175 | "title": "xBase", 1176 | "value": "xBase" 1177 | }, 1178 | { 1179 | "title": "XC", 1180 | "value": "XC" 1181 | }, 1182 | { 1183 | "title": "XML", 1184 | "value": "XML" 1185 | }, 1186 | { 1187 | "title": "Xojo", 1188 | "value": "Xojo" 1189 | }, 1190 | { 1191 | "title": "XPages", 1192 | "value": "XPages" 1193 | }, 1194 | { 1195 | "title": "XProc", 1196 | "value": "XProc" 1197 | }, 1198 | { 1199 | "title": "XQuery", 1200 | "value": "XQuery" 1201 | }, 1202 | { 1203 | "title": "XS", 1204 | "value": "XS" 1205 | }, 1206 | { 1207 | "title": "XSLT", 1208 | "value": "XSLT" 1209 | }, 1210 | { 1211 | "title": "Xtend", 1212 | "value": "Xtend" 1213 | }, 1214 | { 1215 | "title": "Yacc", 1216 | "value": "Yacc" 1217 | }, 1218 | { 1219 | "title": "Zephir", 1220 | "value": "Zephir" 1221 | }, 1222 | { 1223 | "title": "Zimpl", 1224 | "value": "Zimpl" 1225 | } 1226 | ] 1227 | -------------------------------------------------------------------------------- /src/components/filters/language-filter/styles.css: -------------------------------------------------------------------------------- 1 | .language-filter-wrap { 2 | position: relative; 3 | z-index: 1; 4 | } 5 | 6 | .language-filter { 7 | border-radius: 8px; 8 | padding: 10px 20px; 9 | cursor: default; 10 | display: block; 11 | background: white !important; 12 | border-color: white !important; 13 | } 14 | 15 | .language-select { 16 | position: absolute; 17 | top: 100%; 18 | width: 300px; 19 | margin-top: 5px; 20 | overflow: hidden; 21 | font-size: 12px; 22 | color: #586069; 23 | background-color: #fff; 24 | background-clip: padding-box; 25 | border: 1px solid #e8e8e8; 26 | border-radius: 8px; 27 | box-shadow: 0 3px 12px rgba(27, 31, 35, 0.15); 28 | } 29 | 30 | .select-menu-header, .select-menu-divider { 31 | padding: 12px 10px; 32 | background: white; 33 | border-bottom: 1px solid #e8e8e8; 34 | font-size: 15px; 35 | } 36 | 37 | .select-menu-header .select-menu-title, .select-menu-divider { 38 | font-weight: 600; 39 | color: #24292e; 40 | } 41 | 42 | .select-menu-filters { 43 | background-color: #f4f4f4; 44 | } 45 | 46 | .select-menu-text-filter { 47 | padding: 10px; 48 | border-bottom: 1px solid #e8e8e8; 49 | } 50 | 51 | .select-menu-text-filter input { 52 | display: block; 53 | font-size: 14px; 54 | width: 100%; 55 | max-width: 100%; 56 | border: 1px solid #dfe2e5 !important; 57 | padding: 5px 10px !important; 58 | border-radius: 3px; 59 | box-shadow: none !important; 60 | outline: none !important; 61 | } 62 | 63 | .select-menu-list { 64 | position: relative; 65 | max-height: 400px; 66 | overflow: auto; 67 | } 68 | 69 | .select-menu-item { 70 | text-align: left; 71 | background-color: #fff; 72 | border-top: 0; 73 | border-right: 0; 74 | border-left: 0; 75 | display: flex; 76 | align-items: center; 77 | padding: 10px 8px 10px 16px; 78 | overflow: hidden; 79 | color: inherit; 80 | cursor: pointer; 81 | border-bottom: 1px solid #e8e8e8; 82 | } 83 | 84 | .select-menu-list .select-menu-item:last-child { 85 | border-bottom: none; 86 | } 87 | 88 | .select-menu-item:hover, .select-menu-item.active-item { 89 | color: #fff; 90 | background-color: #1157ed; 91 | text-decoration: none; 92 | } 93 | 94 | .select-menu-item:hover .select-menu-item-text, 95 | .select-menu-item.active-item .select-menu-item-text { 96 | color: white; 97 | } 98 | 99 | .select-menu-item .repo-language-color { 100 | width: 10px; 101 | height: 10px; 102 | margin-right: 5px; 103 | border-radius: 50%; 104 | } 105 | 106 | .select-menu-item-text { 107 | display: block; 108 | text-align: left; 109 | } 110 | -------------------------------------------------------------------------------- /src/components/filters/styles.css: -------------------------------------------------------------------------------- 1 | .filters-wrap .filter-item { 2 | float: left; 3 | } 4 | 5 | .filters-wrap .filter-item:not(:first-child) { 6 | margin-left: 15px; 7 | } 8 | -------------------------------------------------------------------------------- /src/components/filters/view-filter/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | import PropTypes from 'prop-types'; 4 | 5 | import './styles.css'; 6 | 7 | class ViewFilter extends React.Component { 8 | state = {}; 9 | 10 | changeSelected = (viewType) => { 11 | this.props.updateViewType(viewType); 12 | }; 13 | 14 | render() { 15 | return ( 16 |
17 |
18 | 22 | 26 |
27 |
28 | ); 29 | } 30 | } 31 | 32 | ViewFilter.propTypes = { 33 | updateViewType: PropTypes.func.isRequired, 34 | selectedViewType: PropTypes.string, 35 | }; 36 | 37 | export default ViewFilter; 38 | -------------------------------------------------------------------------------- /src/components/filters/view-filter/styles.css: -------------------------------------------------------------------------------- 1 | .view-type { 2 | border-radius: 8px; 3 | padding: 10px; 4 | background: white; 5 | cursor: default; 6 | } 7 | 8 | .view-type button { 9 | color: #ccc; 10 | text-decoration: none; 11 | margin: 0 0 0 10px; 12 | padding: 0 5px; 13 | list-style: none; 14 | border: none; 15 | background: none; 16 | cursor: pointer; 17 | -webkit-appearance: none; 18 | box-shadow: none; 19 | outline: none; 20 | } 21 | 22 | .view-type button.active, .view-type button:hover { 23 | color: #343a40; 24 | } 25 | 26 | .view-type button:first-child { 27 | margin-left: 0; 28 | } 29 | -------------------------------------------------------------------------------- /src/components/group-heading/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import moment from 'moment'; 3 | import PropTypes from 'prop-types'; 4 | 5 | import './styles.css'; 6 | 7 | class GroupHeading extends React.Component { 8 | getTitle() { 9 | const startMoment = moment(this.props.start); 10 | const currentMoment = moment(); 11 | const yesterdayMoment = moment().subtract(1, 'day'); 12 | 13 | // For "day" jumps return the day name 14 | if (this.props.dateJump === 'day') { 15 | if (startMoment.isSame(currentMoment, 'day')) { 16 | return 'Today'; 17 | } else if (startMoment.isSame(yesterdayMoment, 'day')) { 18 | return 'Yesterday'; 19 | } 20 | 21 | return startMoment.format('dddd'); 22 | } 23 | 24 | // Return the relative date 25 | const formattedDate = startMoment.fromNow(); 26 | if (formattedDate === 'a year ago') { 27 | return 'Last year'; 28 | } else if (formattedDate === '7 days ago') { 29 | return 'This week'; 30 | } else if (formattedDate === '14 days ago') { 31 | return 'Last week'; 32 | } 33 | 34 | return formattedDate; 35 | } 36 | 37 | getSubtitle() { 38 | const startMoment = moment(this.props.start); 39 | const endMoment = moment(this.props.end); 40 | 41 | if (startMoment.isSame(endMoment, 'day') || this.props.dateJump === 'day') { 42 | return startMoment.format('MMMM D, YYYY'); 43 | } 44 | 45 | return startMoment.format('MMMM D, YYYY') + ' – ' + endMoment.format('MMMM D, YYYY'); 46 | } 47 | 48 | render() { 49 | return ( 50 |
51 |

52 | { this.getTitle() } 53 | 54 | { this.getSubtitle() } 55 | 56 |

57 |
58 | ); 59 | } 60 | } 61 | 62 | GroupHeading.propTypes = { 63 | start: PropTypes.string, 64 | end: PropTypes.string, 65 | dateJump: PropTypes.oneOf(['week', 'month', 'year', 'day']) 66 | }; 67 | 68 | export default GroupHeading; 69 | -------------------------------------------------------------------------------- /src/components/group-heading/styles.css: -------------------------------------------------------------------------------- 1 | .group-heading h4:first-letter { 2 | text-transform: uppercase; 3 | } 4 | -------------------------------------------------------------------------------- /src/components/icons/fork.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Fork = (props) => ( 4 | 5 | 7 | 8 | ); 9 | 10 | export default Fork; 11 | -------------------------------------------------------------------------------- /src/components/icons/issue.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Issue = (props) => ( 4 | 5 | 6 | 7 | ); 8 | 9 | export default Issue; 10 | -------------------------------------------------------------------------------- /src/components/icons/logo.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Logo = (props) => ( 4 | 45 | ); 46 | 47 | export default Logo; 48 | -------------------------------------------------------------------------------- /src/components/icons/solar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const Solar = (props) => ( 5 | 6 | 7 | 8 | 9 | solar-system 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | ); 42 | 43 | Solar.propTypes = { 44 | className: PropTypes.string 45 | }; 46 | 47 | export default Solar; 48 | -------------------------------------------------------------------------------- /src/components/icons/star.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Star = (props) => ( 4 | 5 | 6 | 7 | ); 8 | 9 | export default Star; 10 | -------------------------------------------------------------------------------- /src/components/icons/watcher.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Watcher = (props) => ( 4 | 7 | ); 8 | 9 | export default Watcher; 10 | -------------------------------------------------------------------------------- /src/components/launcher/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import './styles.css'; 4 | import Logo from '../icons/logo'; 5 | 6 | const Launcher = () => ( 7 |
8 |
9 | 10 |
11 |

Loading ..

12 |

We're testing your patience

13 |
14 |
15 |
16 | ); 17 | 18 | export default Launcher; 19 | -------------------------------------------------------------------------------- /src/components/launcher/styles.css: -------------------------------------------------------------------------------- 1 | .launcher-body { 2 | display: flex; 3 | position: fixed; 4 | top: 0; 5 | left: 0; 6 | right: 0; 7 | bottom: 0; 8 | justify-content: center; 9 | align-items: center; 10 | text-align: center; 11 | background: white; 12 | } 13 | 14 | .launcher-body .logo-container svg { 15 | height: 150px; 16 | width: 150px; 17 | } 18 | 19 | .launcher-body .logo-container .logo-text h4 { 20 | font-weight: 700; 21 | font-size: 45px; 22 | margin-bottom: 5px; 23 | } 24 | 25 | .launcher-body .logo-container .logo-text p { 26 | margin-bottom: 0; 27 | font-size: 23px; 28 | } 29 | 30 | .launcher-body .logo-container .logo-text { 31 | margin-top: 10px; 32 | } 33 | -------------------------------------------------------------------------------- /src/components/loader/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import './styles.css'; 4 | import Solar from '../icons/solar'; 5 | 6 | const Loader = () => ( 7 |
8 | 9 |
10 | ); 11 | 12 | export default Loader; 13 | -------------------------------------------------------------------------------- /src/components/loader/styles.css: -------------------------------------------------------------------------------- 1 | .loading-indicator { 2 | margin: 50px 0 0; 3 | text-align: center; 4 | } 5 | 6 | .loading-indicator svg { 7 | height: 85px; 8 | width: 85px; 9 | } 10 | -------------------------------------------------------------------------------- /src/components/options-form/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import PropTypes from 'prop-types'; 4 | 5 | import './styles.css'; 6 | import Alert from '../alert'; 7 | 8 | class OptionsForm extends React.Component { 9 | 10 | state = { 11 | token: this.props.options.token, 12 | success: false, 13 | }; 14 | 15 | componentDidUpdate(prevProps, prevState) { 16 | if (this.props.options.token !== prevProps.options.token) { 17 | this.setState({ 18 | success: true 19 | }); 20 | } 21 | } 22 | 23 | saveOptions = () => { 24 | this.props.updateOptions({ 25 | token: this.state.token 26 | }); 27 | }; 28 | 29 | onChange = (e) => { 30 | this.setState({ 31 | [e.target.name]: e.target.value, 32 | success: false 33 | }); 34 | }; 35 | 36 | onKeyDown = (e) => { 37 | if (e.keyCode === 13) { 38 | this.saveOptions(); 39 | } 40 | }; 41 | 42 | render() { 43 | return ( 44 |
45 |
46 |

Add the Token

47 |

Generate a token and add it below to avoid hitting the rate limit.

48 |
49 | 57 | 63 |
64 | 68 | 69 | Go Home 70 | 71 | 72 | { 73 | this.state.success && ( 74 |
75 | Successfully updated 76 |
77 | ) 78 | } 79 |
80 | ); 81 | } 82 | } 83 | 84 | OptionsForm.propTypes = { 85 | updateOptions: PropTypes.func.isRequired, 86 | options: PropTypes.object.isRequired 87 | }; 88 | 89 | export default OptionsForm; 90 | -------------------------------------------------------------------------------- /src/components/options-form/styles.css: -------------------------------------------------------------------------------- 1 | .options-form ul { 2 | padding: 0; 3 | margin: 15px 0 15px 25px; 4 | } 5 | 6 | .options-form ul li { 7 | line-height: 30px; 8 | } 9 | 10 | .options-form ul li span, 11 | .options-form ul li a { 12 | background: #ffa; 13 | padding: 5px 7px; 14 | color: #343a40; 15 | border-radius: 8px; 16 | font-size: 14px; 17 | font-weight: 500; 18 | } 19 | 20 | .options-form ul li a { 21 | color: blue; 22 | text-decoration: underline; 23 | } 24 | 25 | .options-form .form-field p { 26 | margin-bottom: 15px; 27 | } 28 | 29 | .options-form .form-field input[type="text"] { 30 | display: block; 31 | box-shadow: none; 32 | height: 60px; 33 | -webkit-box-shadow: none; 34 | border: 2px solid #ececec; 35 | margin-bottom: 15px; 36 | } 37 | 38 | .options-form .btn-save { 39 | margin-bottom: 10px; 40 | } 41 | 42 | .options-form .btn { 43 | font-size: 15px; 44 | padding: 15px 25px; 45 | height: 60px; 46 | line-height: 30px; 47 | display: block; 48 | width: 100%; 49 | } 50 | -------------------------------------------------------------------------------- /src/components/repository-grid/grid-item/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import moment from 'moment'; 4 | import GithubColors from 'github-colors'; 5 | 6 | import './styles.css'; 7 | import Star from '../../icons/star'; 8 | import Fork from '../../icons/fork'; 9 | import Issue from "../../icons/issue"; 10 | 11 | class GridItem extends React.Component { 12 | render() { 13 | const languageColor = GithubColors.get(this.props.repository.language); 14 | 15 | return ( 16 |
17 |
18 |
19 | 20 |
21 | { 23 | e.target.src = '/img/logo.svg'; 24 | } } 25 | alt={ this.props.repository.owner.login }/> 26 |
27 |
28 |
{ this.props.repository.owner.login }
29 |

View Profile

30 |
31 |
32 |
33 |
34 |
35 | 36 | { this.props.repository.name } 37 | 38 |
39 |

40 | Built by · { this.props.repository.owner.login } · { moment(this.props.repository.created_at).format('MMMM D, YYYY') } 41 |

42 |
43 |
44 |

{ (this.props.repository.description && this.props.repository.description.slice(0, 140)) || 'No description given.' }

45 |
46 |
47 | { 48 | this.props.repository.language && ( 49 | 50 | 53 | 54 | { this.props.repository.language } 55 | 56 | 57 | ) 58 | } 59 | 63 | 64 | { this.props.repository.stargazers_count ? this.props.repository.stargazers_count.toLocaleString() : 0 } 65 | 66 | 70 | 71 | { this.props.repository.forks ? this.props.repository.forks.toLocaleString() : 0 } 72 | 73 | 77 | 78 | { this.props.repository.open_issues ? this.props.repository.open_issues.toLocaleString() : 0 } 79 | 80 |
81 |
82 |
83 | ); 84 | } 85 | } 86 | 87 | GridItem.propTypes = { 88 | repository: PropTypes.object.isRequired 89 | }; 90 | 91 | export default GridItem; 92 | -------------------------------------------------------------------------------- /src/components/repository-grid/grid-item/styles.css: -------------------------------------------------------------------------------- 1 | .grid-item-container { 2 | padding: 10px; 3 | transition: all 0.3s ease; 4 | } 5 | 6 | .grid-item-body { 7 | background: white; 8 | padding: 20px; 9 | border-radius: 10px; 10 | box-shadow: -5px 10px 60px -13px rgba(0, 0, 0, 0.20); 11 | height: 270px; 12 | } 13 | 14 | .grid-item-container .author-details { 15 | margin-bottom: 20px; 16 | } 17 | 18 | .grid-item-container .author-img img { 19 | width: 35px; 20 | height: 35px; 21 | object-fit: cover; 22 | border-radius: 8px; 23 | float: left; 24 | margin-right: 15px; 25 | } 26 | 27 | .grid-item-container .author-details h5 { 28 | margin-bottom: 2px; 29 | font-weight: 600; 30 | color: #6c7583; 31 | font-size: 14px; 32 | width: 80%; 33 | } 34 | 35 | .grid-item-container .author-details a { 36 | color: #6c7583; 37 | } 38 | 39 | .grid-item-container .author-details p { 40 | margin-bottom: 0; 41 | font-size: 12px; 42 | } 43 | 44 | .grid-item-container .author-header, .repo-header, .repo-body { 45 | margin-bottom: 15px; 46 | } 47 | 48 | .grid-item-container .author-header a { 49 | text-decoration: none; 50 | } 51 | 52 | .grid-item-container .repo-header h5 { 53 | font-size: 19px; 54 | margin-bottom: 5px; 55 | } 56 | 57 | .grid-item-container .repo-header p { 58 | margin-bottom: 0; 59 | } 60 | 61 | .grid-item-container .repo-header a { 62 | text-decoration: none; 63 | } 64 | 65 | .grid-item-container .repo-header .repo-meta a { 66 | color: currentColor; 67 | font-weight: 500; 68 | } 69 | 70 | .grid-item-container .author-name { 71 | font-weight: 400; 72 | } 73 | 74 | .grid-item-container .repo-name { 75 | display: block; 76 | font-weight: 700; 77 | word-break: break-word; 78 | width: 100%; 79 | } 80 | 81 | .grid-item-container .repo-name, 82 | .grid-item-container .author-details h5 { 83 | overflow: hidden; 84 | text-overflow: ellipsis; 85 | white-space: nowrap; 86 | } 87 | 88 | .grid-item-container .repo-body p { 89 | font-size: 14px; 90 | margin-bottom: 0; 91 | color: #6e7583; 92 | word-break: break-word; 93 | } 94 | 95 | .grid-item-container .repo-footer { 96 | font-size: 12px; 97 | position: absolute; 98 | bottom: 20px; 99 | left: 20px; 100 | right: 25px; 101 | background: white; 102 | padding: 10px; 103 | } 104 | 105 | .grid-item-container .repo-footer a { 106 | text-decoration: none; 107 | color: #6c757d; 108 | font-weight: 500; 109 | } 110 | 111 | .grid-item-container .repo-footer .repo-language-color { 112 | width: 10px; 113 | height: 10px; 114 | margin-right: 4px; 115 | border-radius: 50%; 116 | } 117 | 118 | .grid-item-container .repo-footer a svg { 119 | position: relative; 120 | top: -2px; 121 | fill: #6c757d; 122 | margin-right: 5px; 123 | } 124 | -------------------------------------------------------------------------------- /src/components/repository-grid/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import GridItem from './grid-item'; 5 | import './styles.css'; 6 | import GroupHeading from '../group-heading'; 7 | 8 | class RepositoryGrid extends React.Component { 9 | renderGroup(repositoryGroup, counter) { 10 | let groupHeading = ''; 11 | // for the first repository group, we have the 12 | // heading among the filters out of the grid 13 | if (counter !== 0) { 14 | groupHeading = ( 15 |
16 | 21 |
22 | ); 23 | } 24 | 25 | const groupKey = `${repositoryGroup.start}_${repositoryGroup.end}_${counter}`; 26 | const repositories = repositoryGroup.data.items; 27 | 28 | return ( 29 |
30 | { groupHeading } 31 |
32 | { repositories.map(repository => ) } 33 |
34 |
35 | ); 36 | } 37 | 38 | render() { 39 | return ( 40 |
41 | { 42 | this.props.repositories.map((repositoryGroup, counter) => { 43 | return this.renderGroup(repositoryGroup, counter); 44 | }) 45 | } 46 |
47 | ); 48 | } 49 | } 50 | 51 | RepositoryGrid.propTypes = { 52 | repositories: PropTypes.array.isRequired, 53 | dateJump: PropTypes.string.isRequired 54 | }; 55 | 56 | export default RepositoryGrid; 57 | -------------------------------------------------------------------------------- /src/components/repository-grid/styles.css: -------------------------------------------------------------------------------- 1 | .grid-container { 2 | margin-top: 15px; 3 | padding: 0 5px; 4 | } 5 | -------------------------------------------------------------------------------- /src/components/repository-list/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import './styles.css'; 5 | import ListItem from './list-item'; 6 | import GroupHeading from '../group-heading'; 7 | import RepositoryGrid from '../repository-grid'; 8 | 9 | class RepositoryList extends React.Component { 10 | 11 | renderGroup(repositoryGroup, counter) { 12 | let groupHeading = ''; 13 | // for the first repository group, we have the 14 | // heading among the filters out of the grid 15 | if (counter !== 0) { 16 | groupHeading = ( 17 |
18 | 23 |
24 | ); 25 | } 26 | 27 | const groupKey = `${repositoryGroup.start}_${repositoryGroup.end}_${counter}`; 28 | const repositories = repositoryGroup.data.items; 29 | 30 | return ( 31 |
32 | { groupHeading } 33 |
34 | { repositories.map(repository => ) } 35 |
36 |
37 | ); 38 | } 39 | 40 | render() { 41 | return ( 42 |
43 | { 44 | this.props.repositories.map((repositoryGroup, counter) => { 45 | return this.renderGroup(repositoryGroup, counter); 46 | }) 47 | } 48 |
49 | ); 50 | } 51 | } 52 | 53 | RepositoryGrid.propTypes = { 54 | repositories: PropTypes.array.isRequired, 55 | dateJump: PropTypes.string.isRequired 56 | }; 57 | 58 | export default RepositoryList; 59 | -------------------------------------------------------------------------------- /src/components/repository-list/list-item/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import GithubColors from 'github-colors'; 4 | 5 | import './styles.css'; 6 | import moment from 'moment'; 7 | import Star from '../../icons/star'; 8 | import Fork from '../../icons/fork'; 9 | import Issue from "../../icons/issue"; 10 | 11 | class ListItem extends React.Component { 12 | render() { 13 | const languageColor = GithubColors.get(this.props.repository.language); 14 | 15 | return ( 16 |
17 |
18 |
19 |

20 | 21 | { this.props.repository.owner.login } / 22 | { this.props.repository.name } 23 | 24 |

25 |

Built by · { this.props.repository.owner.login } · { moment(this.props.repository.created_at).format('MMMM D YYYY') }

26 |
27 |
28 |

{ this.props.repository.description || 'No description given.' }

29 |
30 |
31 | { 32 | this.props.repository.language && ( 33 | 34 | 37 | 38 | { this.props.repository.language } 39 | 40 | 41 | ) 42 | } 43 | 47 | 48 | { this.props.repository.stargazers_count.toLocaleString() } 49 | 50 | 54 | 55 | { this.props.repository.forks ? this.props.repository.forks.toLocaleString() : 0 } 56 | 57 | 61 | 62 | { this.props.repository.open_issues ? this.props.repository.open_issues.toLocaleString() : 0 } 63 | 64 |
65 |
66 | 67 | 71 | { 74 | e.target.src = '/img/logo.svg'; 75 | } } 76 | alt={ this.props.repository.owner.login } /> 77 | 78 |
79 | ); 80 | } 81 | } 82 | 83 | ListItem.propTypes = { 84 | repository: PropTypes.object.isRequired 85 | }; 86 | 87 | export default ListItem; 88 | -------------------------------------------------------------------------------- /src/components/repository-list/list-item/styles.css: -------------------------------------------------------------------------------- 1 | .list-item-container { 2 | padding: 0 0 20px; 3 | margin-bottom: 20px; 4 | border-bottom: 1px solid #e8e8e8; 5 | position: relative; 6 | } 7 | 8 | .list-item-container .repo-header h3 { 9 | margin-bottom: 4px; 10 | font-size: 20px; 11 | font-weight: 700; 12 | word-break: break-word; 13 | } 14 | 15 | @media (min-width: 768px) { 16 | .list-item-container .repo-header h3 { 17 | overflow: hidden; 18 | text-overflow: ellipsis; 19 | white-space: nowrap; 20 | width: 80%; 21 | } 22 | } 23 | 24 | .list-item-container .repo-header h3 .text-normal { 25 | font-weight: 400; 26 | } 27 | 28 | .list-item-container .repo-footer { 29 | font-size: 13px; 30 | } 31 | 32 | .list-item-container .repo-header p { 33 | margin-bottom: 0; 34 | } 35 | 36 | .list-item-container .repo-header, .list-item-container .repo-body { 37 | margin-bottom: 15px; 38 | } 39 | 40 | .list-item-container .repo-body p { 41 | margin-bottom: 0; 42 | display: block; 43 | max-width: 80%; 44 | word-break: break-all; 45 | } 46 | 47 | .list-item-container .repo-footer svg { 48 | position: relative; 49 | top: -1px; 50 | margin-right: 6px; 51 | fill: #6c757d; 52 | } 53 | 54 | .list-item-container .repo-meta a { 55 | color: currentColor; 56 | text-decoration: none; 57 | font-weight: 500; 58 | } 59 | 60 | .list-container .list-item-container:last-child { 61 | margin-bottom: 0; 62 | border-bottom: 0; 63 | padding-bottom: 0; 64 | } 65 | 66 | .list-item-container .repo-footer a { 67 | color: #6c757d; 68 | text-decoration: none; 69 | } 70 | 71 | .list-item-container .repo-footer .repo-language-color { 72 | width: 10px; 73 | height: 10px; 74 | margin-right: 4px; 75 | border-radius: 50%; 76 | } 77 | 78 | .list-item-container .author-link { 79 | position: absolute; 80 | top: 15px; 81 | right: 15px; 82 | } 83 | 84 | .list-item-container .author-img { 85 | height: 92px; 86 | width: 92px; 87 | border-radius: 92px; 88 | } 89 | -------------------------------------------------------------------------------- /src/components/repository-list/styles.css: -------------------------------------------------------------------------------- 1 | .list-container { 2 | background: white; 3 | padding: 25px; 4 | margin: 25px 0 0; 5 | border-radius: 8px; 6 | box-shadow: -5px 10px 60px -13px rgba(0, 0, 0, 0.20); 7 | } 8 | -------------------------------------------------------------------------------- /src/components/top-nav/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import './styles.css'; 4 | import Logo from '../icons/logo'; 5 | 6 | class TopNav extends React.Component { 7 | tweet = 'GitHunt – Most starred projects on Github by @kamranahmedse https://github.com/kamranahmedse/githunt'; 8 | 9 | render() { 10 | // We need that to show the extension button only if not running in extension 11 | const isRunningExtension = window.chrome && 12 | window.chrome.runtime && 13 | window.chrome.runtime.id; 14 | 15 | return ( 16 |
17 |
18 | 22 | 23 |
24 |

GitHunt

25 |

Most starred projects on GitHub

26 |

Top Github Projects

27 |
28 |
29 |
30 | View Source 34 | { 35 | !isRunningExtension && ( 36 | 40 | Use Extension 41 | 42 | ) 43 | } 44 | { 45 | isRunningExtension && ( 46 | 50 | Give Feedback 51 | 52 | ) 53 | } 54 | 58 | Tweet 59 | 60 |
61 |
62 |
63 | ); 64 | } 65 | } 66 | 67 | export default TopNav; 68 | -------------------------------------------------------------------------------- /src/components/top-nav/styles.css: -------------------------------------------------------------------------------- 1 | .top-nav { 2 | background: #fff; 3 | border-bottom: 1px solid #e8e8e8; 4 | padding: 15px 0; 5 | margin-bottom: 35px; 6 | position: relative; 7 | } 8 | 9 | .top-nav .logo { 10 | margin-left: -10px; 11 | } 12 | 13 | .top-nav .logo svg { 14 | height: 75px; 15 | float: left; 16 | } 17 | 18 | .top-nav .logo-text { 19 | float: left; 20 | margin-left: 10px; 21 | margin-top: 10px; 22 | } 23 | 24 | .top-nav .logo-text h4 { 25 | margin: 3px 0 0; 26 | font-weight: 700; 27 | color: #2d2d2d; 28 | } 29 | 30 | .top-nav .logo-text p { 31 | margin-bottom: 0; 32 | } 33 | 34 | .top-nav .external-btns .btn { 35 | margin-left: 10px; 36 | margin-top: 24px; 37 | box-shadow: none; 38 | } 39 | 40 | .btn-tweet { 41 | background: #01aced; 42 | border-color: #01aced; 43 | } 44 | 45 | -------------------------------------------------------------------------------- /src/containers/feed/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import moment from 'moment'; 3 | import { connect } from 'react-redux'; 4 | import { Link } from 'react-router-dom'; 5 | 6 | import './styles.css'; 7 | import Alert from '../../components/alert'; 8 | import Loader from '../../components/loader'; 9 | import TopNav from '../../components/top-nav'; 10 | import Filters from '../../components/filters'; 11 | import GroupHeading from '../../components/group-heading'; 12 | import { fetchTrending } from '../../redux/github/actions'; 13 | import RepositoryList from '../../components/repository-list'; 14 | import RepositoryGrid from '../../components/repository-grid'; 15 | import { updateDateJump, updateLanguage, updateViewType } from '../../redux/preference/actions'; 16 | 17 | class FeedContainer extends React.Component { 18 | componentDidMount() { 19 | const existingRepositories = this.props.github.repositories || []; 20 | 21 | // If there are no loaded repositories before, fetch them 22 | if (existingRepositories.length === 0) { 23 | this.fetchNextRepositories(); 24 | } 25 | } 26 | 27 | fetchNextRepositories() { 28 | const filters = this.getFilters(); 29 | this.props.fetchTrending(filters); 30 | } 31 | 32 | componentDidUpdate(prevProps) { 33 | const currPreferences = this.props.preference; 34 | const prevPreferences = prevProps.preference; 35 | 36 | // If language or dateJump has been updated, reload 37 | // the repositories 38 | if (currPreferences.language !== prevPreferences.language || 39 | currPreferences.dateJump !== prevPreferences.dateJump) { 40 | this.fetchNextRepositories(); 41 | } 42 | } 43 | 44 | getFilters() { 45 | const filters = {}; 46 | 47 | filters.dateRange = this.getNextDateRange(); 48 | if (this.props.preference.language) { 49 | filters.language = this.props.preference.language; 50 | } 51 | 52 | if (this.props.preference.options.token) { 53 | filters.token = this.props.preference.options.token; 54 | } 55 | 56 | return filters; 57 | } 58 | 59 | getNextDateRange() { 60 | const repositories = this.props.github.repositories || []; 61 | const dateJump = this.props.preference.dateJump; 62 | 63 | const dateRange = {}; 64 | const lastRecords = repositories[repositories.length - 1]; 65 | 66 | if (lastRecords) { 67 | dateRange.start = moment(lastRecords.start).subtract(1, dateJump).startOf('day'); 68 | dateRange.end = lastRecords.start; 69 | } else { 70 | dateRange.start = moment().subtract(1, dateJump).startOf('day'); 71 | dateRange.end = moment().startOf('day'); 72 | } 73 | 74 | return dateRange; 75 | } 76 | 77 | renderTokenWarning() { 78 | return !this.props.preference.options.token && ( 79 | 80 | Make sure to 81 | 82 | add a token 83 | 84 | to avoid hitting the rate limit 85 | 86 | ); 87 | } 88 | 89 | renderErrors() { 90 | if (!this.props.github.error) { 91 | return null; 92 | } 93 | 94 | let message = ''; 95 | switch (this.props.github.error.toLowerCase()) { 96 | case 'bad credentials': 97 | message = ( 98 | 99 | Token is invalid, try updating the token on the options page 100 | 101 | ); 102 | break; 103 | case 'network error': 104 | message = 'Error trying to connect to GitHub servers'; 105 | break; 106 | default: 107 | message = this.props.github.error; 108 | break; 109 | } 110 | 111 | return ( 112 | 113 | { message } 114 | 115 | ); 116 | } 117 | 118 | renderAlerts() { 119 | const tokenWarning = this.renderTokenWarning(); 120 | const error = this.renderErrors(); 121 | 122 | if (tokenWarning || error) { 123 | return ( 124 |
125 | { tokenWarning } 126 | { error } 127 |
128 | ); 129 | } 130 | 131 | return null; 132 | } 133 | 134 | renderRepositoriesList() { 135 | if (this.props.preference.viewType === 'grid') { 136 | return ; 140 | } 141 | 142 | return ; 146 | } 147 | 148 | hasRepositories() { 149 | return this.props.github.repositories && this.props.github.repositories.length !== 0; 150 | } 151 | 152 | render() { 153 | return ( 154 |
155 | 156 | 157 | { this.renderAlerts() } 158 | 159 |
160 |
161 | { 162 | this.hasRepositories() && 167 | } 168 |
169 | { 170 | this.hasRepositories() && 178 | } 179 |
180 |
181 |
182 | { this.renderRepositoriesList() } 183 | 184 | { this.props.github.processing && } 185 | 186 | { 187 | !this.props.github.processing && 188 | this.hasRepositories() && 189 | ( 190 | 195 | ) 196 | } 197 |
198 |
199 |
200 | ); 201 | } 202 | } 203 | 204 | const mapStateToProps = store => { 205 | return { 206 | preference: store.preference, 207 | github: store.github 208 | }; 209 | }; 210 | 211 | const mapDispatchToProps = { 212 | updateLanguage, 213 | updateViewType, 214 | updateDateJump, 215 | fetchTrending 216 | }; 217 | 218 | export default connect(mapStateToProps, mapDispatchToProps)(FeedContainer); 219 | -------------------------------------------------------------------------------- /src/containers/feed/styles.css: -------------------------------------------------------------------------------- 1 | .group-heading { 2 | font-size: 22px; 3 | font-weight: 600; 4 | float: left; 5 | } 6 | 7 | .header-row h4 { 8 | margin-top: 15px; 9 | } 10 | 11 | .group-heading h4 { 12 | margin-bottom: 0; 13 | } 14 | 15 | .group-heading .small { 16 | font-size: 14px; 17 | font-weight: 400; 18 | margin-left: 3px; 19 | } 20 | 21 | .group-filters { 22 | float: right; 23 | } 24 | 25 | .row-group { 26 | margin-top: 35px; 27 | } 28 | 29 | .row-group .group-heading { 30 | margin: auto; 31 | } 32 | 33 | .load-next-date { 34 | padding: 10px 20px; 35 | display: block; 36 | margin: 25px auto auto; 37 | } 38 | 39 | .alert-group { 40 | margin-top: -35px; 41 | margin-bottom: 25px; 42 | text-align: center; 43 | } 44 | 45 | .alert-group .alert:not(:last-child) { 46 | border-bottom: none; 47 | } 48 | 49 | .alert-group .alert { 50 | border-top: none; 51 | margin-bottom: 0; 52 | } 53 | 54 | .alert-warning { 55 | background: #ffa; 56 | } 57 | -------------------------------------------------------------------------------- /src/containers/options/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | 4 | import './styles.css'; 5 | import OptionsForm from '../../components/options-form'; 6 | import { updateOptions } from '../../redux/preference/actions'; 7 | 8 | class OptionsContainer extends React.Component { 9 | render() { 10 | return ( 11 |
12 |
13 | 17 |
18 |
19 | ); 20 | } 21 | } 22 | 23 | const mapStateToProps = (store) => { 24 | return { 25 | preference: store.preference 26 | }; 27 | }; 28 | 29 | const mapDispatchToProps = { 30 | updateOptions 31 | }; 32 | 33 | export default connect(mapStateToProps, mapDispatchToProps)(OptionsContainer); 34 | -------------------------------------------------------------------------------- /src/containers/options/styles.css: -------------------------------------------------------------------------------- 1 | .options-container { 2 | margin: 200px auto 20px; 3 | max-width: 700px; 4 | } 5 | 6 | .options-container .container { 7 | background: white; 8 | padding: 25px 30px; 9 | border-radius: 20px; 10 | } 11 | -------------------------------------------------------------------------------- /src/global.css: -------------------------------------------------------------------------------- 1 | body { 2 | /*background-color: #f9f9f9;*/ 3 | background: #f4f4f4; 4 | min-height: 100vh; 5 | } 6 | 7 | a { 8 | color: #1157ed; 9 | } 10 | 11 | .btn { 12 | font-size: 14px; 13 | border-radius: 8px; 14 | } 15 | 16 | .cursor-default { 17 | cursor: default !important; 18 | } 19 | 20 | .shadowed { 21 | box-shadow: 0 9px 62px -13px rgba(0, 0, 0, 0.20) !important; 22 | } 23 | 24 | .btn-primary { 25 | background: #1157ed; 26 | border-color: #1157ed; 27 | } 28 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import 'bootstrap/dist/css/bootstrap.css'; 4 | import 'font-awesome/css/font-awesome.css' 5 | 6 | import './global.css'; 7 | import App from './app'; 8 | 9 | ReactDOM.render(, document.getElementById('root')); 10 | -------------------------------------------------------------------------------- /src/redux/github/actions.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import moment from 'moment'; 3 | 4 | import { 5 | FETCH_TRENDING_FAILED, 6 | FETCH_TRENDING_SUCCESS, 7 | PROCESS_FETCH_TRENDING, 8 | } from './types'; 9 | 10 | const API_URL = 'https://api.github.com/search/repositories'; 11 | 12 | const transformFilters = (filters) => { 13 | const transformedFilters = {}; 14 | 15 | const startMoment = moment(filters.dateRange.start); 16 | const endMoment = moment(filters.dateRange.end); 17 | const reposDate = `created:${startMoment.format()}..${endMoment.format()}`; 18 | const reposLanguage = filters.language ? `language:${filters.language} ` : ''; 19 | 20 | transformedFilters.q = reposLanguage + reposDate; 21 | transformedFilters.sort = 'stars'; 22 | transformedFilters.order = 'desc'; 23 | 24 | return transformedFilters; 25 | }; 26 | 27 | /** 28 | * @param {object} filters 29 | * @returns {Function} 30 | */ 31 | export const fetchTrending = function (filters) { 32 | return dispatch => { 33 | dispatch({ type: PROCESS_FETCH_TRENDING }); 34 | 35 | axios.get(API_URL, { 36 | params: transformFilters(filters), 37 | headers: { 38 | ...(filters.token ? { Authorization: `token ${filters.token}` } : {}) 39 | } 40 | }).then(response => { 41 | dispatch({ 42 | type: FETCH_TRENDING_SUCCESS, 43 | payload: { 44 | start: moment(filters.dateRange.start).format(), 45 | end: moment(filters.dateRange.end).format(), 46 | data: response.data 47 | } 48 | }); 49 | }).catch(error => { 50 | let message = error.response && 51 | error.response.data && 52 | error.response.data.message; 53 | 54 | if (!message) { 55 | message = error.message; 56 | } 57 | 58 | dispatch({ 59 | type: FETCH_TRENDING_FAILED, 60 | payload: message 61 | }); 62 | }); 63 | }; 64 | }; 65 | -------------------------------------------------------------------------------- /src/redux/github/reducer.js: -------------------------------------------------------------------------------- 1 | import { FETCH_TRENDING_FAILED, FETCH_TRENDING_SUCCESS, PROCESS_FETCH_TRENDING } from './types'; 2 | import { UPDATE_DATE_TYPE, UPDATE_LANGUAGE } from '../preference/types'; 3 | 4 | export const initialState = { 5 | processing: false, 6 | // Array of objects with the below format 7 | // [ 8 | // { start: '', end: '', data: [] }, 9 | // { start: '', end: '', data: [] } 10 | // ] 11 | repositories: [], 12 | error: null, 13 | }; 14 | 15 | export default function reducer(state = initialState, action) { 16 | switch (action.type) { 17 | case PROCESS_FETCH_TRENDING: 18 | return { 19 | ...state, 20 | processing: true, 21 | error: null 22 | }; 23 | case UPDATE_DATE_TYPE: 24 | case UPDATE_LANGUAGE: 25 | return { 26 | ...state, 27 | ...initialState 28 | }; 29 | case FETCH_TRENDING_SUCCESS: 30 | return { 31 | ...state, 32 | // Append the fetched repositories to existing list 33 | repositories: [ 34 | ...state.repositories, 35 | action.payload 36 | ], 37 | processing: false, 38 | error: null 39 | }; 40 | case FETCH_TRENDING_FAILED: 41 | return { 42 | ...state, 43 | processing: false, 44 | error: action.payload 45 | }; 46 | default: 47 | return state; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/redux/github/transform.js: -------------------------------------------------------------------------------- 1 | import { createTransform } from 'redux-persist'; 2 | 3 | const GithubTransform = createTransform( 4 | // transform state on its way to being serialized and persisted. 5 | (inboundState, key) => { 6 | inboundState = inboundState || {}; 7 | 8 | // Keep the first group only to avoid overflowing the storage 9 | if (inboundState.repositories && inboundState.repositories.length > 1) { 10 | inboundState = { 11 | ...inboundState, 12 | repositories: [ 13 | inboundState.repositories[0] 14 | ] 15 | }; 16 | } 17 | 18 | // Do not persist `processing` flag or `error` information, 19 | // as we want to start fresh on reload in such cases 20 | inboundState = { 21 | ...inboundState, 22 | processing: false, 23 | error: null 24 | }; 25 | 26 | return inboundState; 27 | }, 28 | // transform state being rehydrated 29 | (outboundState, key) => { 30 | return { ...outboundState }; 31 | }, 32 | // define which reducers this transform gets called for. 33 | { 34 | whitelist: ['github'] 35 | } 36 | ); 37 | 38 | export default GithubTransform; 39 | -------------------------------------------------------------------------------- /src/redux/github/types.js: -------------------------------------------------------------------------------- 1 | export const PROCESS_FETCH_TRENDING = 'PROCESS_FETCH_TRENDING'; 2 | export const FETCH_TRENDING_SUCCESS = 'FETCH_TRENDING_SUCCESS'; 3 | export const FETCH_TRENDING_FAILED = 'FETCH_TRENDING_FAILED'; 4 | -------------------------------------------------------------------------------- /src/redux/preference/actions.js: -------------------------------------------------------------------------------- 1 | import { UPDATE_DATE_TYPE, UPDATE_LANGUAGE, UPDATE_OPTIONS, UPDATE_VIEW_TYPE } from './types'; 2 | 3 | export const updateOptions = function (options) { 4 | return dispatch => { 5 | dispatch({ 6 | type: UPDATE_OPTIONS, 7 | payload: options, 8 | }); 9 | }; 10 | }; 11 | 12 | export const updateViewType = function (viewType = 'grid') { 13 | return dispatch => { 14 | dispatch({ 15 | type: UPDATE_VIEW_TYPE, 16 | payload: viewType 17 | }); 18 | }; 19 | }; 20 | 21 | export const updateLanguage = function (language) { 22 | return dispatch => { 23 | dispatch({ 24 | type: UPDATE_LANGUAGE, 25 | payload: language 26 | }); 27 | }; 28 | }; 29 | 30 | export const updateDateJump = function (dateJump) { 31 | return dispatch => { 32 | dispatch({ 33 | type: UPDATE_DATE_TYPE, 34 | payload: dateJump 35 | }); 36 | }; 37 | }; 38 | -------------------------------------------------------------------------------- /src/redux/preference/reducer.js: -------------------------------------------------------------------------------- 1 | import { UPDATE_DATE_TYPE, UPDATE_LANGUAGE, UPDATE_OPTIONS, UPDATE_VIEW_TYPE } from './types'; 2 | 3 | const initialState = { 4 | viewType: 'list', 5 | dateJump: 'week', 6 | language: '', 7 | options: { 8 | token: '', 9 | }, 10 | }; 11 | 12 | export default function reducer(state = initialState, action) { 13 | switch (action.type) { 14 | case UPDATE_OPTIONS: 15 | return { 16 | ...state, 17 | options: action.payload 18 | }; 19 | case UPDATE_DATE_TYPE: 20 | return { 21 | ...state, 22 | dateJump: action.payload 23 | }; 24 | case UPDATE_VIEW_TYPE: 25 | return { 26 | ...state, 27 | viewType: action.payload 28 | }; 29 | case UPDATE_LANGUAGE: 30 | return { 31 | ...state, 32 | language: action.payload 33 | }; 34 | default: 35 | return state; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/redux/preference/types.js: -------------------------------------------------------------------------------- 1 | export const UPDATE_OPTIONS = 'UPDATE_OPTIONS'; 2 | export const UPDATE_VIEW_TYPE = 'UPDATE_VIEW_TYPE'; 3 | export const UPDATE_DATE_TYPE = 'UPDATE_DATE_TYPE'; 4 | export const UPDATE_LANGUAGE = 'UPDATE_LANGUAGE'; 5 | -------------------------------------------------------------------------------- /src/redux/reducers.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | 3 | import preference from './preference/reducer'; 4 | import github from './github/reducer'; 5 | 6 | export default combineReducers({ 7 | github, 8 | preference, 9 | }); 10 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { MemoryRouter, Route, Switch } from 'react-router-dom'; 3 | 4 | import FeedContainer from '../containers/feed'; 5 | import OptionsContainer from '../containers/options'; 6 | 7 | const AppRoutes = () => { 8 | return ( 9 | // @todo use browser router and generate prerendered options.html page for chrome extension 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | ); 18 | }; 19 | 20 | export default AppRoutes; 21 | -------------------------------------------------------------------------------- /src/store.js: -------------------------------------------------------------------------------- 1 | import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2'; 2 | import { composeWithDevTools } from 'redux-devtools-extension'; 3 | import { persistReducer, persistStore } from 'redux-persist'; 4 | import { applyMiddleware, createStore } from 'redux'; 5 | import storage from 'redux-persist/lib/storage'; 6 | import thunk from 'redux-thunk'; 7 | import expireReducer from 'redux-persist-expire'; 8 | 9 | import GithubTransform from './redux/github/transform'; 10 | import githubState from './redux/github/reducer'; 11 | import rootReducer from './redux/reducers'; 12 | 13 | const persistedReducers = persistReducer( 14 | { 15 | key: 'githunt:root', 16 | storage: storage, 17 | stateReconciler: autoMergeLevel2, 18 | transforms: [ 19 | GithubTransform, 20 | expireReducer('github', { 21 | expireSeconds: 3600, 22 | expiredState: { ...githubState }, 23 | autoExpire: true 24 | }) 25 | ] 26 | }, 27 | rootReducer, 28 | ); 29 | 30 | export const store = createStore(persistedReducers, composeWithDevTools( 31 | applyMiddleware( 32 | thunk, 33 | ), 34 | )); 35 | 36 | export const persist = persistStore(store); 37 | --------------------------------------------------------------------------------