├── .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 |
12 | You need to enable JavaScript to run this app.
13 |
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
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
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 GitHubUse Online • Install Extension
25 |
26 | Weekly Trending Projects – List View
27 | 💥 Keep Scrolling to load past weeks 💥
28 |
29 |
30 |
31 |
32 | Weekly Trending Projects – Grid View
33 | 💥 Change the view options from the filters list 💥
34 |
35 |
36 |
37 |
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 [](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 |
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 |
182 |
183 | { this.props.selectedLanguage || 'All Languages' }
184 |
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 | this.changeSelected('grid') } className={ classNames({ active: this.props.selectedViewType === 'grid' }) }>
19 |
20 | Grid
21 |
22 | this.changeSelected('list') } className={ classNames({ active: this.props.selectedViewType === 'list' }) }>
23 |
24 | List
25 |
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 |
5 |
6 |
7 |
8 | astronaut-helmet
9 |
10 |
12 |
13 |
14 |
15 |
16 |
18 |
19 |
20 |
21 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
43 |
44 |
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 |
5 |
6 |
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 |
65 |
66 | Save Token
67 |
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 |
33 |
43 |
44 |
{ (this.props.repository.description && this.props.repository.description.slice(0, 140)) || 'No description given.' }
45 |
46 |
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 |
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 |
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 | this.fetchNextRepositories() }>
192 |
193 | Load next { this.props.preference.dateJump }
194 |
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 |
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 |
--------------------------------------------------------------------------------