├── 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 | 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 |

    {this.props.title}

    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 |
    23 |
    24 |
    25 |
    26 |
    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 |
    51 |
    52 | 53 | 54 |
    55 |
    56 | 57 | 58 |
    59 |
    60 | 61 |
    62 |
    63 |
    64 |
    65 |
    66 | 67 |
    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 | --------------------------------------------------------------------------------