├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── config
├── app.json
├── colors.json
├── theme.js
└── webpack.js
├── logo.md
├── package-lock.json
├── package.json
├── src
├── components
│ ├── App
│ │ ├── index.css
│ │ └── index.js
│ ├── GitHub
│ │ └── GitHubRepo.js
│ ├── HackerNews
│ │ └── HackerNewsStory.js
│ ├── Header
│ │ ├── index.css
│ │ └── index.js
│ ├── Icons
│ │ └── index.js
│ ├── Logo
│ │ └── index.js
│ ├── News
│ │ ├── index.css
│ │ └── index.js
│ ├── NewsList
│ │ ├── index.css
│ │ └── index.js
│ ├── PlaceholderShimmer
│ │ ├── index.css
│ │ └── index.js
│ └── ProductHunt
│ │ └── ProductHuntItem.js
├── data
│ └── index.js
├── index.ejs
├── index.js
└── static
│ ├── CNAME
│ ├── favicons
│ ├── android-chrome-144x144.png
│ ├── android-chrome-192x192.png
│ ├── android-chrome-36x36.png
│ ├── android-chrome-48x48.png
│ ├── android-chrome-72x72.png
│ ├── android-chrome-96x96.png
│ ├── apple-touch-icon-114x114.png
│ ├── apple-touch-icon-120x120.png
│ ├── apple-touch-icon-144x144.png
│ ├── apple-touch-icon-152x152.png
│ ├── apple-touch-icon-180x180.png
│ ├── apple-touch-icon-57x57.png
│ ├── apple-touch-icon-60x60.png
│ ├── apple-touch-icon-72x72.png
│ ├── apple-touch-icon-76x76.png
│ ├── apple-touch-icon-precomposed.png
│ ├── apple-touch-icon.png
│ ├── browserconfig.xml
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon-96x96.png
│ ├── favicon.ico
│ ├── manifest.json
│ ├── mstile-144x144.png
│ ├── mstile-150x150.png
│ ├── mstile-310x150.png
│ ├── mstile-310x310.png
│ ├── mstile-70x70.png
│ └── safari-pinned-tab.svg
│ ├── icon.png
│ ├── icon.svg
│ ├── logo.png
│ └── logo.svg
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | /node_modules
3 | *.log
4 |
5 | # OS generated files #
6 | ######################
7 | .DS_Store
8 | .DS_Store?
9 | ._*
10 | .Spotlight-V100
11 | .Trashes
12 | ehthumbs.db
13 | Thumbs.db
14 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 4
4 | cache:
5 | directories:
6 | - node_modules
7 | before_install: if [[ `npm -v` != 3* ]]; then npm i -g npm@3; fi
8 | install: npm install
9 | script: npm run prod
10 | deploy:
11 | provider: surge
12 | project: ./build/
13 | domain: devne.ws
14 | skip_cleanup: true
15 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 devnews
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://devne.ws/)
2 |
3 | [](http://stackshare.io/sunnysingh/devnews)
4 | [](https://travis-ci.org/devnews/web)
5 | [](https://david-dm.org/devnews/web)
6 |
7 | ## About
8 |
9 | [Devnews](https://devne.ws/) aggregates top news stories from Hacker News, trending repositories from GitHub, and top tech from Product Hunt.
10 |
11 | ## Roadmap
12 |
13 | The [open issues](https://github.com/devnews/web/issues) are good indications of what we have planned.
14 |
15 | Also make sure you check out the [v2 milestone](https://github.com/devnews/web/milestone/1).
16 |
17 | ## Contributing
18 |
19 | Please stay consistent with the code.
20 |
21 | 1. Clone this repository (`devnews/web`).
22 | 2. Run `npm install`.
23 | 3. Run `npm start`.
24 | 4. Submit a pull request.
25 |
--------------------------------------------------------------------------------
/config/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "devnews",
3 | "nameUpper": "Devnews",
4 | "description": "Developer news aggregator.",
5 | "twitter": "ninjalitydesign",
6 | "facebook": "ninjalitydesign",
7 | "facebook_app_id": "217662015276471"
8 | }
9 |
--------------------------------------------------------------------------------
/config/colors.json:
--------------------------------------------------------------------------------
1 | {
2 | "brandPrimary": "#3d49ba",
3 | "bodyBackground": "#f5f5f5"
4 | }
5 |
--------------------------------------------------------------------------------
/config/theme.js:
--------------------------------------------------------------------------------
1 | import getMuiTheme from 'material-ui/styles/getMuiTheme';
2 | import {
3 | indigo900,
4 | grey100, grey300, grey400, grey500,
5 | white, lightBlack, darkBlack,
6 | } from 'material-ui/styles/colors';
7 |
8 | const Theme = getMuiTheme({
9 | palette: {
10 | primary1Color: grey300,
11 | primary2Color: grey400,
12 | primary3Color: lightBlack,
13 | accent1Color: indigo900,
14 | accent2Color: grey100,
15 | accent3Color: grey500,
16 | textColor: darkBlack,
17 | alternateTextColor: darkBlack,
18 | canvasColor: white,
19 | borderColor: grey300,
20 | }
21 | });
22 |
23 | export default Theme;
24 |
--------------------------------------------------------------------------------
/config/webpack.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const fs = require('fs');
3 | const HtmlPlugin = require('html-webpack-plugin');
4 | const CopyPlugin = require('copy-webpack-plugin');
5 | const OpenBrowserPlugin = require('open-browser-webpack-plugin');
6 | const postcssImport = require('postcss-import');
7 | const cssnext = require('postcss-cssnext');
8 | const fontMagician = require('postcss-font-magician');
9 |
10 | const meta = JSON.parse(fs.readFileSync('./config/app.json', 'utf8'));
11 | const colors = JSON.parse(fs.readFileSync('./config/colors.json', 'utf8'));
12 |
13 | const PROD_ENV = process.env.NODE_ENV === 'production';
14 | const DEV_ENV = !PROD_ENV;
15 |
16 | const cssModulesNameFormat = DEV_ENV ? '[path][name]__[local]___[hash:base64:5]' : '[hash:base64]';
17 |
18 | const config = {
19 | entry: './src/index.js',
20 | output: {
21 | path: './build',
22 | filename: DEV_ENV ? 'dev.bundle.js' : '[hash].bundle.js',
23 | },
24 | plugins: [
25 | new HtmlPlugin({
26 | template: './src/index.ejs',
27 | filename: 'index.html',
28 | minify: {
29 | collapseBooleanAttributes: true,
30 | collapseWhitespace: true,
31 | removeAttributeQuotes: true,
32 | removeComments: true,
33 | removeEmptyAttributes: true,
34 | removeRedundantAttributes: true,
35 | },
36 | inject: false,
37 | meta: meta,
38 | colors: colors,
39 | baseUrl: DEV_ENV ? 'http://localhost:3000' : 'https://devne.ws',
40 | }),
41 | new CopyPlugin([
42 | {from: './src/static', to: './'},
43 | ]),
44 | new webpack.DefinePlugin({
45 | 'process.env': {
46 | 'NODE_ENV': DEV_ENV ? JSON.stringify('development') : JSON.stringify('production'),
47 | }
48 | }),
49 | ],
50 | module: {
51 | loaders: [
52 | {
53 | test: /.js$/,
54 | loader: 'babel',
55 | exclude: [/node_modules/, /.ejs$/],
56 | query: {
57 | presets: DEV_ENV ? ['env', 'react', 'react-hmre'] : ['env', 'react'],
58 | },
59 | },
60 | {
61 | test: /\.css$/,
62 | loader: 'style-loader!css-loader?modules&importLoaders=1&localIdentName='+cssModulesNameFormat+'!postcss-loader',
63 | exclude: [/node_modules/],
64 | },
65 | {
66 | test: /\.json$/,
67 | loader: 'json',
68 | exclude: [/node_modules/],
69 | },
70 | {
71 | test: /\.svg$/,
72 | loader: 'raw',
73 | exclude: [/node_modules/],
74 | },
75 | ]
76 | },
77 | postcss: function () {
78 | return [
79 | postcssImport,
80 | cssnext({
81 | features: {
82 | customProperties: {
83 | variables: colors,
84 | },
85 | },
86 | }),
87 | fontMagician(),
88 | ];
89 | },
90 | };
91 |
92 | if (DEV_ENV) {
93 | config.plugins.push(
94 | new OpenBrowserPlugin({
95 | url: 'http://localhost:3000',
96 | })
97 | );
98 | config.devtool = '#inline-source-map';
99 | }
100 |
101 | module.exports = config;
102 |
--------------------------------------------------------------------------------
/logo.md:
--------------------------------------------------------------------------------
1 | ## Logo
2 |
3 | If you're linking to devnews somewhere, we'd love to hear about it! You can tweet [@getdevnews](https://twitter.com/getdevnews) or [@ninjalitydesign](https://twitter.com/ninjality.com).
4 |
5 | The logo is available in [SVG](https://devne.ws/logo.svg) and [PNG](https://devne.ws/logo.png) formats:
6 |
7 | [](https://devne.ws/logo.svg)
8 |
9 | The icon is available in [SVG](https://devne.ws/icon.svg) and [PNG](https://devne.ws/icon.png) formats:
10 |
11 | [](https://devne.ws/icon.svg)
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "devnews",
3 | "version": "1.0.0",
4 | "description": "Developer news aggregator.",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "webpack-dev-server --config config/webpack.js --port 3000 --hot --inline --progress --colors",
8 | "build": "webpack --config config/webpack.js",
9 | "prod": "NODE_ENV=production webpack --config config/webpack.js -p",
10 | "predeploy": "npm run prod",
11 | "deploy": "surge --project ./build",
12 | "test": "echo 'Error: no test specified'"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "git+https://github.com/devnews/web.git"
17 | },
18 | "author": "Sunny Singh",
19 | "license": "MIT",
20 | "bugs": {
21 | "url": "https://github.com/devnews/web/issues"
22 | },
23 | "homepage": "https://devne.ws/",
24 | "dependencies": {
25 | "autolinker": "^1.4.4",
26 | "babel-preset-react-hmre": "^1.1.1",
27 | "html-escape": "^2.0.0",
28 | "load-script": "^1.0.0",
29 | "localforage": "^1.5.2",
30 | "material-ui": "^0.19.4",
31 | "moment": "^2.19.1",
32 | "normalize.css": "^7.0.0",
33 | "prop-types": "^15.6.0",
34 | "react": "^16.0.0",
35 | "react-dom": "^16.0.0",
36 | "react-tap-event-plugin": "^3.0.2",
37 | "superagent": "^3.6.3"
38 | },
39 | "devDependencies": {
40 | "babel-core": "^6.7.2",
41 | "babel-loader": "^6.2.4",
42 | "babel-preset-env": "^1.6.0",
43 | "babel-preset-react": "^6.5.0",
44 | "copy-webpack-plugin": "^4.0.1",
45 | "css-loader": "^0.26.0",
46 | "html-webpack-plugin": "^2.12.0",
47 | "json-loader": "^0.5.4",
48 | "open-browser-webpack-plugin": "0.0.3",
49 | "postcss-cssnext": "^2.5.1",
50 | "postcss-font-magician": "^1.4.0",
51 | "postcss-import": "^8.0.2",
52 | "postcss-loader": "^1.1.1",
53 | "raw-loader": "^0.5.1",
54 | "react-svg-inline": "^2.0.1",
55 | "style-loader": "^0.13.0",
56 | "surge": "^0.18.0",
57 | "webpack": "^1.12.14",
58 | "webpack-dev-server": "^1.14.1"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/components/App/index.css:
--------------------------------------------------------------------------------
1 | @import 'normalize.css';
2 |
3 | :root {
4 | box-sizing: border-box;
5 | font-size: 16px;
6 | font-family: "Roboto", sans-serif;
7 | color: #212121;
8 | background: var(--bodyBackground);
9 | }
10 |
11 | *,
12 | *:before,
13 | *:after {
14 | box-sizing: inherit;
15 | }
16 |
17 | a:not([type="button"]) {
18 | color: #222;
19 | border-bottom: 1px solid #222;
20 | text-decoration: none;
21 |
22 | &:hover,
23 | &:focus {
24 | color: #444;
25 | border-color: transparent;
26 | text-decoration: none;
27 | transition: all .2s ease;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/components/App/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
3 | import Theme from '../../../config/theme';
4 | import Header from '../Header';
5 | import News from '../News';
6 | import styles from './index.css';
7 |
8 | class App extends React.Component {
9 |
10 | render () {
11 | return (
12 |
13 |
14 |
15 |
16 |
17 |
18 | )
19 | }
20 |
21 | };
22 |
23 | export default App;
24 |
--------------------------------------------------------------------------------
/src/components/GitHub/GitHubRepo.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import Autolinker from 'autolinker';
4 | import escape from 'html-escape';
5 | import styles from '../NewsList/index.css';
6 |
7 | const GitHubRepo = (props) => {
8 |
9 | let getDescription = () => {
10 | const safeDescription = escape(props.repo.description);
11 | return {
12 | __html: Autolinker.link(safeDescription, {
13 | email: false,
14 | phone: false,
15 | twitter: false,
16 | }),
17 | };
18 | };
19 |
20 | return (
21 |
22 |
31 |
32 |
50 |
51 | )
52 |
53 | };
54 |
55 | GitHubRepo.propTypes = {
56 | repo: PropTypes.object.isRequired,
57 | };
58 |
59 | export default GitHubRepo;
60 |
--------------------------------------------------------------------------------
/src/components/HackerNews/HackerNewsStory.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styles from '../NewsList/index.css';
4 |
5 | const HackerNewsStory = (props) => {
6 |
7 | return (
8 |
36 | )
37 |
38 | };
39 |
40 | HackerNewsStory.propTypes = {
41 | story: PropTypes.object.isRequired,
42 | };
43 |
44 | export default HackerNewsStory;
45 |
--------------------------------------------------------------------------------
/src/components/Header/index.css:
--------------------------------------------------------------------------------
1 | .logo {
2 | padding: 15px;
3 | text-align: center;
4 | }
5 |
6 | .aboutBtnContainer {
7 | position: absolute;
8 | top: 3px;
9 | left: 3px;
10 | }
11 |
12 | .aboutContainer {
13 | padding: 30px;
14 | }
15 |
16 | .aboutHeading {
17 | margin: 0;
18 | font-size: 1.4rem;
19 | }
20 |
21 | .aboutText {
22 | margin: 15px 0 0 0;
23 | line-height: 1.5em;
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/Header/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Popover from 'material-ui/Popover';
3 | import IconButton from 'material-ui/IconButton';
4 | import InfoOutlineIcon from 'material-ui/svg-icons/action/info-outline';
5 | import Logo from '../Logo';
6 | import styles from './index.css';
7 |
8 | class Header extends React.Component {
9 |
10 | constructor () {
11 | super();
12 |
13 | this.state = {
14 | aboutOpen: false,
15 | };
16 | }
17 |
18 | handleClick (event) {
19 | this.setState({
20 | aboutOpen: true,
21 | anchorEl: event.currentTarget,
22 | });
23 | }
24 |
25 | handleRequestClose () {
26 | this.setState({
27 | aboutOpen: false,
28 | });
29 | }
30 |
31 | render () {
32 | return (
33 |
34 |
35 |
36 |
37 |
38 |
42 |
43 |
44 |
45 |
52 |
53 |
About
54 |
Devnews aggregates top news stories from Hacker News, trending repositories from GitHub, and top tech from Product Hunt.
55 |
This project was created by the ninjas at Ninjality and is open sourced on GitHub.
56 |
You can follow @getdevnews on Twitter for updates, or view our logo information for linking back.
57 |
58 |
59 |
60 | )
61 | }
62 | };
63 |
64 | export default Header;
65 |
--------------------------------------------------------------------------------
/src/components/Icons/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import SvgIcon from 'material-ui/SvgIcon';
3 |
4 | export const HackerNewsIcon = (props) => (
5 |
11 | {props.title && {props.title}}
12 |
13 |
14 |
15 |
16 |
17 | );
18 |
19 | export const GitHubIcon = (props) => (
20 |
26 | {props.title && {props.title}}
27 |
28 |
29 |
30 |
31 | );
32 |
33 | export const ProductHuntIcon = (props) => (
34 |
40 | {props.title && {props.title}}
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | );
55 |
--------------------------------------------------------------------------------
/src/components/Logo/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import SVGInline from 'react-svg-inline';
4 | import svg from '../../static/logo.svg';
5 |
6 | const Logo = (props) => {
7 | return (
8 |
15 | );
16 | };
17 |
18 | Logo.propTypes = {
19 | desc: PropTypes.string,
20 | width: PropTypes.string,
21 | height: PropTypes.string,
22 | };
23 |
24 | Logo.defaultProps = {
25 | desc: 'devnews logo',
26 | width: 'auto',
27 | height: '20px',
28 | };
29 |
30 | export default Logo;
31 |
--------------------------------------------------------------------------------
/src/components/News/index.css:
--------------------------------------------------------------------------------
1 | .heading {
2 | margin: 0;
3 | font-size: 1rem;
4 | }
5 |
6 | .tabsContainer {
7 | padding: 0;
8 | }
9 |
10 | .content {
11 | padding: 30px;
12 | }
13 |
14 | .storiesContainer {
15 | margin-top: 15px;
16 | }
17 |
--------------------------------------------------------------------------------
/src/components/News/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Tabs, Tab} from 'material-ui/Tabs';
3 | import {HackerNewsIcon, GitHubIcon, ProductHuntIcon} from '../Icons';
4 | import {hackernews, github, producthunt} from '../../data';
5 | import NewsList from '../NewsList';
6 | import styles from './index.css';
7 |
8 | class News extends React.Component {
9 |
10 | constructor () {
11 | super();
12 |
13 | this.state = {
14 | hackernews: {
15 | data: [],
16 | loaded: false,
17 | },
18 | github: {
19 | data: [],
20 | loaded: false,
21 | },
22 | producthunt: {
23 | data: [],
24 | loaded: false,
25 | },
26 | };
27 | }
28 |
29 | componentDidMount () {
30 | hackernews((data) => {
31 | this.setState({
32 | hackernews: {
33 | data: data,
34 | loaded: true,
35 | },
36 | });
37 | });
38 | }
39 |
40 | handleActiveTab (tab) {
41 | switch (tab.props.value) {
42 | case 'github':
43 | if (!this.state.github.loaded) {
44 | github((data) => {
45 | this.setState({
46 | github: {
47 | data: data,
48 | loaded: true,
49 | },
50 | });
51 | });
52 | }
53 | break;
54 | case 'producthunt':
55 | if (!this.state.producthunt.loaded) {
56 | producthunt((data) => {
57 | this.setState({
58 | producthunt: {
59 | data: data,
60 | loaded: true,
61 | },
62 | });
63 | });
64 | }
65 | break;
66 | }
67 | }
68 |
69 | render () {
70 | return (
71 |
75 |
76 | }>
77 |
78 | Hacker News
79 |
80 |
81 |
87 |
88 |
89 | Go to Hacker News (page 2)
90 |
91 |
92 |
93 | }
95 | value="github"
96 | onActive={this.handleActiveTab.bind(this)}
97 | >
98 |
99 | GitHub Trending
100 |
101 |
102 |
108 |
109 |
110 | Go to GitHub Trending
111 |
112 |
113 |
114 | }
116 | value="producthunt"
117 | onActive={this.handleActiveTab.bind(this)}
118 | >
119 |
120 | Product Hunt Tech
121 |
122 |
123 |
129 |
130 |
131 | Go to Product Hunt Tech
132 |
133 |
134 |
135 |
136 | )
137 | }
138 | };
139 |
140 | export default News;
141 |
--------------------------------------------------------------------------------
/src/components/NewsList/index.css:
--------------------------------------------------------------------------------
1 | .container {
2 | margin-bottom: 30px;
3 | }
4 |
5 | .container a:visited {
6 | color: #555;
7 | }
8 |
9 | .heading {
10 | margin: 0;
11 | line-height: 1.6em;
12 | word-break: break-all;
13 | word-break: break-word;
14 | hyphens: auto;
15 | }
16 |
17 | .description {
18 | margin-top: 15px;
19 | margin-bottom: 0;
20 | word-break: break-all;
21 | word-break: break-word;
22 | hyphens: auto;
23 | }
24 |
25 | .footer {
26 | padding-top: 15px;
27 | line-height: 1.5em;
28 | }
29 |
30 | .footerItem {
31 | display: inline-block;
32 | margin-right: 15px;
33 | }
34 |
--------------------------------------------------------------------------------
/src/components/NewsList/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import PlaceholderShimmer from '../PlaceholderShimmer';
4 | import HackerNewsStory from '../HackerNews/HackerNewsStory';
5 | import GitHubRepo from '../GitHub/GitHubRepo';
6 | import ProductHuntItem from '../ProductHunt/ProductHuntItem';
7 |
8 | class NewsList extends React.Component {
9 |
10 | shouldComponentUpdate (nextProps) {
11 | return this.props.loaded !== nextProps.loaded;
12 | }
13 |
14 | render () {
15 | if (!this.props.loaded) {
16 | return (
17 |
18 | )
19 | }
20 |
21 | if (!this.props.data.length) {
22 | return (
23 | Oops, we were unable to load the stories :(
24 | )
25 | }
26 |
27 | return (
28 |
29 | {
30 | this.props.data.map(item => {
31 | if (this.props.source == 'hackernews') {
32 | return (
33 |
37 | )
38 | }
39 | if (this.props.source == 'github') {
40 | return (
41 |
45 | )
46 | }
47 | if (this.props.source == 'producthunt') {
48 | return (
49 |
53 | )
54 | }
55 | })
56 | }
57 |
58 | )
59 | }
60 |
61 | };
62 |
63 | NewsList.propsTypes = {
64 | source: PropTypes.string.isRequired,
65 | getData: PropTypes.object.isRequired,
66 | data: PropTypes.object.isRequired,
67 | loaded: PropTypes.bool.isRequired,
68 | };
69 |
70 | export default NewsList;
71 |
--------------------------------------------------------------------------------
/src/components/PlaceholderShimmer/index.css:
--------------------------------------------------------------------------------
1 | .common {
2 | position: relative;
3 | width: 100%;
4 | height: 30px;
5 | background-color: #ddd;
6 | background-image: linear-gradient(to left, #ddd 0%, #e8e8e8 20%, #ddd 40%, #ddd 100%);
7 | background-size: 400%;
8 | animation-duration: 1s;
9 | animation-fill-mode: forwards;
10 | animation-iteration-count: infinite;
11 | animation-name: shimmer;
12 | animation-timing-function: linear;
13 | transform: translateZ(0);
14 | }
15 |
16 | .smallPlaceholder {
17 | margin: 15px 0;
18 | max-width: 300px;
19 | }
20 |
21 | .largePlaceholder {
22 | margin: 30px 0;
23 | max-width: 400px;
24 | }
25 |
26 | .heading {
27 | composes: common;
28 | }
29 |
30 | .footer {
31 | composes: common;
32 | display: inline-block;
33 | margin-top: 15px;
34 | &::before {
35 | content: '';
36 | position: absolute;
37 | right: 30%;
38 | width: 15px;
39 | height: 38px;
40 | background: var(--bodyBackground);
41 | }
42 | }
43 |
44 | .smallFooter {
45 | composes: footer;
46 | &::before {
47 | right: 50%;
48 | }
49 | }
50 |
51 | @keyframes shimmer {
52 | 0% {
53 | background-position: 100%;
54 | }
55 | 100% {
56 | background-position: 0;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/components/PlaceholderShimmer/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './index.css';
3 |
4 | const PlaceholderShimmer = (props) => (
5 |
15 | );
16 |
17 | export default PlaceholderShimmer;
18 |
--------------------------------------------------------------------------------
/src/components/ProductHunt/ProductHuntItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styles from '../NewsList/index.css';
4 |
5 | const ProductHuntItem = (props) => {
6 |
7 | return (
8 |
9 |
18 |
{props.product.tagline}
19 |
33 |
34 | )
35 |
36 | };
37 |
38 | ProductHuntItem.propTypes = {
39 | product: PropTypes.object.isRequired,
40 | };
41 |
42 | export default ProductHuntItem;
43 |
--------------------------------------------------------------------------------
/src/data/index.js:
--------------------------------------------------------------------------------
1 | import request from "superagent";
2 | import localforage from "localforage";
3 | import moment from "moment";
4 |
5 | const expiresDuration = 30;
6 | const expiresUnit = "minutes";
7 |
8 | const wrapApiKey = "vZpCx0QXD65gAcUD4Q7gAL6y0GQB1pgT";
9 |
10 | export const hackernews = (callback) => {
11 | const baseUrl = "https://hacker-news.firebaseio.com/v0";
12 |
13 | localforage.getItem("hackernews").then((cache) => {
14 | if (cache) {
15 | const notExpired = moment().diff(cache.expires) < 0;
16 | if (notExpired) {
17 | callback(cache.data);
18 | return;
19 | }
20 | }
21 |
22 | request.get(baseUrl + "/topstories.json").end((error, response) => {
23 | let stories = response.body.slice(0, 30); // grab 30 items
24 | let data = [];
25 | let index = 0;
26 |
27 | for (let storyId of stories) {
28 | const apiUrl = baseUrl + "/item/" + storyId + ".json";
29 | const cachedIndex = index + 1;
30 |
31 | request.get(apiUrl).end((error, response) => {
32 | data.push({
33 | id: response.body.id,
34 | title: response.body.title,
35 | by: response.body.by,
36 | url: response.body.url,
37 | points: response.body.score,
38 | commentCount: response.body.descendants,
39 | ago: moment.unix(response.body.time).fromNow(),
40 | });
41 |
42 | if (cachedIndex === stories.length) {
43 | localforage.setItem("hackernews", {
44 | expires: moment().add(expiresDuration, expiresUnit).valueOf(),
45 | data,
46 | });
47 | callback(data);
48 | }
49 | });
50 |
51 | index++;
52 | }
53 | });
54 | });
55 | };
56 |
57 | export const github = (callback) => {
58 | const baseUrl = "https://ghapi.huchen.dev/repositories";
59 |
60 | localforage.getItem("github").then((cache) => {
61 | if (cache) {
62 | const notExpired = moment().diff(cache.expires) < 0;
63 | if (notExpired) {
64 | callback(cache.data);
65 | return;
66 | }
67 | }
68 |
69 | request.get(baseUrl).end((error, response) => {
70 | let data = [];
71 | for (let repo of response.body) {
72 | data.push({
73 | url: repo.url,
74 | user: repo.author,
75 | name: repo.name,
76 | description: repo.description ? repo.description.trim() : null,
77 | stars: parseInt(repo.stars),
78 | language: repo.language ? repo.language.trim() : null,
79 | });
80 | }
81 |
82 | localforage.setItem("github", {
83 | expires: moment().add(expiresDuration, expiresUnit).valueOf(),
84 | data,
85 | });
86 |
87 | callback(data);
88 | });
89 | });
90 | };
91 |
92 | export const producthunt = (callback) => {
93 | const baseUrl =
94 | "https://wrapapi.com/use/sunnysingh/producthunt/todaytech/0.0.3?wrapAPIKey=" +
95 | wrapApiKey;
96 |
97 | localforage.getItem("producthunt").then((cache) => {
98 | if (cache) {
99 | const notExpired = moment().diff(cache.expires) < 0;
100 | if (notExpired) {
101 | callback(cache.data);
102 | return;
103 | }
104 | }
105 |
106 | request.get(baseUrl).end((error, response) => {
107 | let data = [];
108 | for (let product of response.body.data.posts) {
109 | data.push({
110 | id: product.id,
111 | name: product.name,
112 | tagline: product.tagline,
113 | url: product.redirect_url,
114 | votesCount: product.votes_count,
115 | commentsCount: product.comments_count,
116 | discussionUrl: product.discussion_url,
117 | });
118 | }
119 |
120 | localforage.setItem("producthunt", {
121 | expires: moment().add(expiresDuration, expiresUnit).valueOf(),
122 | data,
123 | });
124 |
125 | callback(data);
126 | });
127 | });
128 | };
129 |
--------------------------------------------------------------------------------
/src/index.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | <%= htmlWebpackPlugin.options.meta.name %>
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | <% for (var chunk in htmlWebpackPlugin.files.chunks) { %>
74 |
75 | <% } %>
76 |
77 |
78 |
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import injectTapEventPlugin from 'react-tap-event-plugin';
4 | import App from './components/App';
5 |
6 | injectTapEventPlugin();
7 |
8 | render(, document.getElementById('root'));
9 |
--------------------------------------------------------------------------------
/src/static/CNAME:
--------------------------------------------------------------------------------
1 | devne.ws
2 |
--------------------------------------------------------------------------------
/src/static/favicons/android-chrome-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devnews/web/84620724f484e89f6717f95fdb14da5184a820f8/src/static/favicons/android-chrome-144x144.png
--------------------------------------------------------------------------------
/src/static/favicons/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devnews/web/84620724f484e89f6717f95fdb14da5184a820f8/src/static/favicons/android-chrome-192x192.png
--------------------------------------------------------------------------------
/src/static/favicons/android-chrome-36x36.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devnews/web/84620724f484e89f6717f95fdb14da5184a820f8/src/static/favicons/android-chrome-36x36.png
--------------------------------------------------------------------------------
/src/static/favicons/android-chrome-48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devnews/web/84620724f484e89f6717f95fdb14da5184a820f8/src/static/favicons/android-chrome-48x48.png
--------------------------------------------------------------------------------
/src/static/favicons/android-chrome-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devnews/web/84620724f484e89f6717f95fdb14da5184a820f8/src/static/favicons/android-chrome-72x72.png
--------------------------------------------------------------------------------
/src/static/favicons/android-chrome-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devnews/web/84620724f484e89f6717f95fdb14da5184a820f8/src/static/favicons/android-chrome-96x96.png
--------------------------------------------------------------------------------
/src/static/favicons/apple-touch-icon-114x114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devnews/web/84620724f484e89f6717f95fdb14da5184a820f8/src/static/favicons/apple-touch-icon-114x114.png
--------------------------------------------------------------------------------
/src/static/favicons/apple-touch-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devnews/web/84620724f484e89f6717f95fdb14da5184a820f8/src/static/favicons/apple-touch-icon-120x120.png
--------------------------------------------------------------------------------
/src/static/favicons/apple-touch-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devnews/web/84620724f484e89f6717f95fdb14da5184a820f8/src/static/favicons/apple-touch-icon-144x144.png
--------------------------------------------------------------------------------
/src/static/favicons/apple-touch-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devnews/web/84620724f484e89f6717f95fdb14da5184a820f8/src/static/favicons/apple-touch-icon-152x152.png
--------------------------------------------------------------------------------
/src/static/favicons/apple-touch-icon-180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devnews/web/84620724f484e89f6717f95fdb14da5184a820f8/src/static/favicons/apple-touch-icon-180x180.png
--------------------------------------------------------------------------------
/src/static/favicons/apple-touch-icon-57x57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devnews/web/84620724f484e89f6717f95fdb14da5184a820f8/src/static/favicons/apple-touch-icon-57x57.png
--------------------------------------------------------------------------------
/src/static/favicons/apple-touch-icon-60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devnews/web/84620724f484e89f6717f95fdb14da5184a820f8/src/static/favicons/apple-touch-icon-60x60.png
--------------------------------------------------------------------------------
/src/static/favicons/apple-touch-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devnews/web/84620724f484e89f6717f95fdb14da5184a820f8/src/static/favicons/apple-touch-icon-72x72.png
--------------------------------------------------------------------------------
/src/static/favicons/apple-touch-icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devnews/web/84620724f484e89f6717f95fdb14da5184a820f8/src/static/favicons/apple-touch-icon-76x76.png
--------------------------------------------------------------------------------
/src/static/favicons/apple-touch-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devnews/web/84620724f484e89f6717f95fdb14da5184a820f8/src/static/favicons/apple-touch-icon-precomposed.png
--------------------------------------------------------------------------------
/src/static/favicons/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devnews/web/84620724f484e89f6717f95fdb14da5184a820f8/src/static/favicons/apple-touch-icon.png
--------------------------------------------------------------------------------
/src/static/favicons/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | #3d49ba
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/static/favicons/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devnews/web/84620724f484e89f6717f95fdb14da5184a820f8/src/static/favicons/favicon-16x16.png
--------------------------------------------------------------------------------
/src/static/favicons/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devnews/web/84620724f484e89f6717f95fdb14da5184a820f8/src/static/favicons/favicon-32x32.png
--------------------------------------------------------------------------------
/src/static/favicons/favicon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devnews/web/84620724f484e89f6717f95fdb14da5184a820f8/src/static/favicons/favicon-96x96.png
--------------------------------------------------------------------------------
/src/static/favicons/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devnews/web/84620724f484e89f6717f95fdb14da5184a820f8/src/static/favicons/favicon.ico
--------------------------------------------------------------------------------
/src/static/favicons/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "devnews",
3 | "icons": [
4 | {
5 | "src": "\/favicons\/android-chrome-36x36.png",
6 | "sizes": "36x36",
7 | "type": "image\/png",
8 | "density": 0.75
9 | },
10 | {
11 | "src": "\/favicons\/android-chrome-48x48.png",
12 | "sizes": "48x48",
13 | "type": "image\/png",
14 | "density": 1
15 | },
16 | {
17 | "src": "\/favicons\/android-chrome-72x72.png",
18 | "sizes": "72x72",
19 | "type": "image\/png",
20 | "density": 1.5
21 | },
22 | {
23 | "src": "\/favicons\/android-chrome-96x96.png",
24 | "sizes": "96x96",
25 | "type": "image\/png",
26 | "density": 2
27 | },
28 | {
29 | "src": "\/favicons\/android-chrome-144x144.png",
30 | "sizes": "144x144",
31 | "type": "image\/png",
32 | "density": 3
33 | },
34 | {
35 | "src": "\/favicons\/android-chrome-192x192.png",
36 | "sizes": "192x192",
37 | "type": "image\/png",
38 | "density": 4
39 | }
40 | ],
41 | "display": "standalone"
42 | }
43 |
--------------------------------------------------------------------------------
/src/static/favicons/mstile-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devnews/web/84620724f484e89f6717f95fdb14da5184a820f8/src/static/favicons/mstile-144x144.png
--------------------------------------------------------------------------------
/src/static/favicons/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devnews/web/84620724f484e89f6717f95fdb14da5184a820f8/src/static/favicons/mstile-150x150.png
--------------------------------------------------------------------------------
/src/static/favicons/mstile-310x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devnews/web/84620724f484e89f6717f95fdb14da5184a820f8/src/static/favicons/mstile-310x150.png
--------------------------------------------------------------------------------
/src/static/favicons/mstile-310x310.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devnews/web/84620724f484e89f6717f95fdb14da5184a820f8/src/static/favicons/mstile-310x310.png
--------------------------------------------------------------------------------
/src/static/favicons/mstile-70x70.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devnews/web/84620724f484e89f6717f95fdb14da5184a820f8/src/static/favicons/mstile-70x70.png
--------------------------------------------------------------------------------
/src/static/favicons/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
69 |
--------------------------------------------------------------------------------
/src/static/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devnews/web/84620724f484e89f6717f95fdb14da5184a820f8/src/static/icon.png
--------------------------------------------------------------------------------
/src/static/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/static/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devnews/web/84620724f484e89f6717f95fdb14da5184a820f8/src/static/logo.png
--------------------------------------------------------------------------------
/src/static/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------