├── app
├── robots.txt
├── favicon.ico
├── scripts
│ ├── app.js
│ └── components
│ │ ├── article-list.js
│ │ ├── __tests__
│ │ └── home-test.js
│ │ ├── home.js
│ │ ├── reddit-article.js
│ │ └── reddit-app.js
├── styles
│ └── main.scss
└── index.html
├── .bowerrc
├── .gitignore
├── .yo-rc.json
├── bower.json
├── .editorconfig
├── package.json
└── gulpfile.js
/app/robots.txt:
--------------------------------------------------------------------------------
1 | # robotstxt.org/
2 |
3 | User-agent: *
4 |
--------------------------------------------------------------------------------
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "app/bower_components"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist/
3 | .sass-cache/
4 | app/bower_components/
5 |
--------------------------------------------------------------------------------
/.yo-rc.json:
--------------------------------------------------------------------------------
1 | {
2 | "generator-react-6": {
3 | "includeJest": true
4 | }
5 | }
--------------------------------------------------------------------------------
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antillas21/react-reddit/master/app/favicon.ico
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-reddit",
3 | "version": "0.0.0",
4 | "dependencies": {
5 | "jquery": "^2.1.3",
6 | "bootstrap-sass-official": "^3.3.2",
7 | "modernizr": "^2.8.3"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/app/scripts/app.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import RedditApp from './components/reddit-app';
3 |
4 | window.React = React;
5 | const mountNode = document.getElementById('app');
6 |
7 | React.render(, mountNode);
8 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/app/styles/main.scss:
--------------------------------------------------------------------------------
1 | $icon-font-path: "/fonts/bootstrap/";
2 | @import "bootstrap-sass-official/assets/stylesheets/bootstrap";
3 |
4 | body {
5 | background: #fafafa;
6 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
7 | color: #333;
8 | }
9 |
10 | .votes {
11 | font-size: 16px;
12 | font-weight: normal;
13 | padding: 8px 10px;
14 | position: relative;
15 | top: 25px;
16 | }
17 |
--------------------------------------------------------------------------------
/app/scripts/components/article-list.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import RedditArticle from './reddit-article';
3 |
4 | class ArticleList extends React.Component {
5 | constructor(props) {
6 | super(props);
7 | this.renderArticle = this.renderArticle.bind(this);
8 | }
9 |
10 | renderArticle(article, index) {
11 | return
12 | }
13 |
14 | render() {
15 | return (
16 |
17 | { this.props.articles.map(this.renderArticle) }
18 |
19 | )
20 | }
21 | }
22 |
23 | export default ArticleList
24 |
--------------------------------------------------------------------------------
/app/scripts/components/__tests__/home-test.js:
--------------------------------------------------------------------------------
1 | jest.dontMock('../home');
2 |
3 | const React = require('react/addons');
4 | const Home = require('../home');
5 | const TestUtils = React.addons.TestUtils;
6 |
7 | describe('Home', function() {
8 | let home = null;
9 |
10 | beforeEach(function() {
11 | home = TestUtils.renderIntoDocument();
12 | });
13 |
14 | it('contains the word React', function() {
15 | expect(React.findDOMNode(home).textContent).toContain('React');
16 | });
17 |
18 | it('has a list of included items', function() {
19 | const items = TestUtils.scryRenderedDOMComponentsWithTag(home, 'li');
20 | expect(items.length).toBeGreaterThan(0);
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/app/scripts/components/home.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class extends React.Component {
4 | constructor(props) {
5 | super(props);
6 | this.state = {
7 | items: [
8 | 'Browserify',
9 | 'Babel',
10 | 'Bootstrap',
11 | 'Modernizr',
12 | 'Jest'
13 | ]
14 | };
15 | }
16 |
17 | render() {
18 | return (
19 |
20 |
'Allo, 'Allo!
21 |
This is a React component.
22 | You now also have:
23 |
{this.state.items.map(this.renderItem)}
24 |
25 | );
26 | }
27 |
28 | renderItem(item, index) {
29 | return {item};
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-reddit",
3 | "version": "0.0.0",
4 | "dependencies": {},
5 | "devDependencies": {
6 | "babel-jest": "^5.0.1",
7 | "babelify": "^6.0.2",
8 | "browserify": "^8.1.3",
9 | "del": "^1.1.1",
10 | "gulp": "^3.8.10",
11 | "gulp-autoprefixer": "^2.1.0",
12 | "gulp-bower": "^0.0.10",
13 | "gulp-cache": "^0.2.4",
14 | "gulp-imagemin": "^2.1.0",
15 | "gulp-jest": "^0.4.0",
16 | "gulp-load-plugins": "^0.8.0",
17 | "gulp-minify-css": "^0.4.5",
18 | "gulp-ruby-sass": "^1.0.0-alpha",
19 | "gulp-size": "^1.2.0",
20 | "gulp-sync": "^0.1.4",
21 | "gulp-useref": "^1.1.1",
22 | "gulp-uglify": "^1.1.0",
23 | "gulp-util": "^3.0.3",
24 | "gulp-webserver": "^0.9.0",
25 | "harmonize": "^1.4.0",
26 | "react": "^0.13.2",
27 | "vinyl-source-stream": "^1.0.0",
28 | "watchify": "^2.3.0"
29 | },
30 | "engines": {
31 | "node": ">=0.10.0"
32 | },
33 | "browserify": {
34 | "transform": [
35 | "babelify"
36 | ]
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/app/scripts/components/reddit-article.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | class RedditArticle extends React.Component {
4 | constructor(props) {
5 | super(props);
6 |
7 | this.voteUp = this.voteUp.bind(this);
8 | this.voteDown = this.voteDown.bind(this);
9 | this.domain = this.domain.bind(this);
10 |
11 | this.state = { votes: 0 };
12 | }
13 | voteUp() {
14 | this.setState({ votes: this.state.votes + 1 });
15 | }
16 |
17 | voteDown() {
18 | this.setState({ votes: this.state.votes - 1 });
19 | }
20 |
21 | domain() {
22 | let link = this.props.link.split("//")[1];
23 | return link.split("/")[0];
24 | }
25 |
26 | render() {
27 | return (
28 |
29 | {this.state.votes}
30 |
31 |
32 |
33 |
({this.domain()})
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | )
45 | }
46 | }
47 |
48 | export default RedditArticle
49 |
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | React - Poor Man's Reddit
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
21 |
22 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/app/scripts/components/reddit-app.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ArticleList from './article-list';
3 |
4 |
5 | class RedditApp extends React.Component {
6 | constructor(props) {
7 | super(props);
8 |
9 | this.onTitleChange = this.onTitleChange.bind(this);
10 | this.onLinkChange = this.onLinkChange.bind(this);
11 | this.handleSubmit = this.handleSubmit.bind(this);
12 |
13 | this.state = {
14 | articles: [
15 | { title: "React", link: "https://facebook.github.io/react/index.html" },
16 | { title: "Facebook", link: "https://facebook.com" }
17 | ],
18 | title: "",
19 | link: ""
20 | };
21 | }
22 |
23 | handleSubmit(e) {
24 | e.preventDefault();
25 | let newTitle = "";
26 | let newLink = "";
27 | let newArticles = this.state.articles.concat({title: this.state.title, link: this.state.link});
28 | console.log("Adding article with title ", this.state.title, " and link ", this.state.link);
29 |
30 | this.setState({
31 | articles: newArticles,
32 | title: newTitle,
33 | link: newLink
34 | });
35 | }
36 |
37 | onTitleChange(e) {
38 | this.setState({ title: e.target.value });
39 | }
40 |
41 | onLinkChange(e) {
42 | this.setState({ link: e.target.value });
43 | }
44 |
45 | render() {
46 | return (
47 |
48 |
49 | Add New Article
50 |
63 |
64 |
65 |
68 |
69 | )
70 | }
71 | }
72 |
73 | export default RedditApp
74 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var gulp = require('gulp');
4 | var $ = require('gulp-load-plugins')();
5 | var sync = $.sync(gulp).sync;
6 | var del = require('del');
7 | var browserify = require('browserify');
8 | var watchify = require('watchify');
9 | var source = require('vinyl-source-stream');
10 | var path = require('path');
11 |
12 | require('harmonize')();
13 |
14 | var bundler = {
15 | w: null,
16 | init: function() {
17 | this.w = watchify(browserify({
18 | entries: ['./app/scripts/app.js'],
19 | insertGlobals: true,
20 | cache: {},
21 | packageCache: {}
22 | }));
23 | },
24 | bundle: function() {
25 | return this.w && this.w.bundle()
26 | .on('error', $.util.log.bind($.util, 'Browserify Error'))
27 | .pipe(source('app.js'))
28 | .pipe(gulp.dest('dist/scripts'));
29 | },
30 | watch: function() {
31 | this.w && this.w.on('update', this.bundle.bind(this));
32 | },
33 | stop: function() {
34 | this.w && this.w.close();
35 | }
36 | };
37 |
38 | gulp.task('styles', function() {
39 | return $.rubySass('app/styles/main.scss', {
40 | style: 'expanded',
41 | precision: 10,
42 | loadPath: ['app/bower_components']
43 | })
44 | .on('error', $.util.log.bind($.util, 'Sass Error'))
45 | .pipe($.autoprefixer('last 1 version'))
46 | .pipe(gulp.dest('dist/styles'))
47 | .pipe($.size());
48 | });
49 |
50 | gulp.task('scripts', function() {
51 | bundler.init();
52 | return bundler.bundle();
53 | });
54 |
55 | gulp.task('html', function() {
56 | var assets = $.useref.assets();
57 | return gulp.src('app/*.html')
58 | .pipe(assets)
59 | .pipe(assets.restore())
60 | .pipe($.useref())
61 | .pipe(gulp.dest('dist'))
62 | .pipe($.size());
63 | });
64 |
65 | gulp.task('images', function() {
66 | return gulp.src('app/images/**/*')
67 | .pipe($.cache($.imagemin({
68 | optimizationLevel: 3,
69 | progressive: true,
70 | interlaced: true
71 | })))
72 | .pipe(gulp.dest('dist/images'))
73 | .pipe($.size());
74 | });
75 |
76 | gulp.task('fonts', function() {
77 | return gulp.src(['app/fonts/**/*', 'app/bower_components/bootstrap-sass-official/assets/fonts/**/*'])
78 | .pipe(gulp.dest('dist/fonts'))
79 | .pipe($.size());
80 | });
81 |
82 | gulp.task('extras', function () {
83 | return gulp.src(['app/*.txt', 'app/*.ico'])
84 | .pipe(gulp.dest('dist/'))
85 | .pipe($.size());
86 | });
87 |
88 | gulp.task('serve', function() {
89 | gulp.src('dist')
90 | .pipe($.webserver({
91 | livereload: true,
92 | port: 9000
93 | }));
94 | });
95 |
96 | gulp.task('jest', function () {
97 | var nodeModules = path.resolve('./node_modules');
98 | return gulp.src('app/scripts/**/__tests__')
99 | .pipe($.jest({
100 | scriptPreprocessor: nodeModules + '/babel-jest',
101 | unmockedModulePathPatterns: [nodeModules + '/react']
102 | }));
103 | });
104 |
105 | gulp.task('set-production', function() {
106 | process.env.NODE_ENV = 'production';
107 | });
108 |
109 | gulp.task('minify:js', function() {
110 | return gulp.src('dist/scripts/**/*.js')
111 | .pipe($.uglify())
112 | .pipe(gulp.dest('dist/scripts/'))
113 | .pipe($.size());
114 | });
115 |
116 | gulp.task('minify:css', function() {
117 | return gulp.src('dist/styles/**/*.css')
118 | .pipe($.minifyCss())
119 | .pipe(gulp.dest('dist/styles'))
120 | .pipe($.size());
121 | });
122 |
123 | gulp.task('minify', ['minify:js', 'minify:css']);
124 |
125 | gulp.task('clean', del.bind(null, 'dist'));
126 |
127 | gulp.task('bundle', ['html', 'styles', 'scripts', 'images', 'fonts', 'extras']);
128 |
129 | gulp.task('clean-bundle', sync(['clean', 'bundle']));
130 |
131 | gulp.task('build', ['clean-bundle'], bundler.stop.bind(bundler));
132 |
133 | gulp.task('build:production', sync(['set-production', 'build', 'minify']));
134 |
135 | gulp.task('serve:production', sync(['build:production', 'serve']));
136 |
137 | gulp.task('test', ['jest']);
138 |
139 | gulp.task('default', ['build']);
140 |
141 | gulp.task('watch', sync(['clean-bundle', 'serve']), function() {
142 | bundler.watch();
143 | gulp.watch('app/*.html', ['html']);
144 | gulp.watch('app/styles/**/*.scss', ['styles']);
145 | gulp.watch('app/images/**/*', ['images']);
146 | gulp.watch('app/fonts/**/*', ['fonts']);
147 | });
148 |
--------------------------------------------------------------------------------